sinatra-fx-auth 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +56 -0
- data/Rakefile +9 -0
- data/features/api_profile_list.feature +0 -0
- data/features/api_sign_off.feature +9 -0
- data/features/api_sign_on.feature +76 -0
- data/features/api_sign_up.feature +38 -0
- data/features/step_definitions/api_sign_off/api_sign_off.rb +13 -0
- data/features/step_definitions/api_sign_on/api_invalid_credentials.rb +18 -0
- data/features/step_definitions/api_sign_on/api_locked_account.rb +13 -0
- data/features/step_definitions/api_sign_on/api_valid_credentials.rb +27 -0
- data/features/step_definitions/api_sign_up/api_duplicate_email.rb +20 -0
- data/features/step_definitions/api_sign_up/api_invalid_email.rb +18 -0
- data/features/step_definitions/api_sign_up/api_missing_email.rb +18 -0
- data/features/step_definitions/api_sign_up/api_valid_sign_up.rb +23 -0
- data/features/step_definitions/api_sign_up/missing_password.rb +19 -0
- data/features/support/env.rb +23 -0
- data/lib/sinatra/fx-auth.rb +263 -0
- data/lib/sinatra/version.rb +9 -0
- data/lib/sinatra-fx-auth.rb +2 -0
- data/sinatra-fx-auth.gemspec +33 -0
- data/test/factories.rb +17 -0
- data/test/seeds.rb +23 -0
- metadata +196 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6a6f47b09ab7c03c218d5f31d127b7ded750369b
|
4
|
+
data.tar.gz: 374c52ff11220a5726cd5238b3da53b0368de8f7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a1428420f5b125f9b93190dfe1f2e1ac04ee21b813f57e2674d072e2f99672c50c0567e6c3ab120fdfd11d7da219ef6c4d69ff9248c76758873ea609f090db1b
|
7
|
+
data.tar.gz: 3cc1af4f9055ff0269e58aa985b56d71b5450a3d82ba96683c90005f3c22aaf78a81a67a252fe312250f9cc9f9a0161c5bb73fcd1fc15238c72e6db136328a14
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Dave Jackson
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# Sinatra::Fx::Auth
|
2
|
+
|
3
|
+
Sinatra::Fx::Auth is a RESTful Authentication and Role-based Authorization extension for Sinatra.
|
4
|
+
No sessions or cookies required.
|
5
|
+
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'sinatra-fx-auth'
|
12
|
+
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install sinatra-fx-auth
|
22
|
+
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
``` ruby
|
26
|
+
require 'sinatra/base'
|
27
|
+
require 'sinatra/fx-auth'
|
28
|
+
|
29
|
+
class MyApp < Sinatra::Base
|
30
|
+
register Sinatra::Fx::Auth
|
31
|
+
|
32
|
+
# Accessible by all
|
33
|
+
get '/products' do
|
34
|
+
# ...
|
35
|
+
end
|
36
|
+
|
37
|
+
# Requires an authenticated :admin
|
38
|
+
put '/products/:id', :auth => :admin do
|
39
|
+
# ...
|
40
|
+
end
|
41
|
+
|
42
|
+
# Requires an authenticated :admin or the :user with the given :id
|
43
|
+
get '/customers/:id/cart', :auth => [:admin, :user] do
|
44
|
+
# ...
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
|
50
|
+
## Contributing
|
51
|
+
|
52
|
+
1. Fork it
|
53
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
54
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
55
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
56
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
File without changes
|
@@ -0,0 +1,76 @@
|
|
1
|
+
Feature: API Sign On
|
2
|
+
|
3
|
+
Scenario: API Valid Credentials
|
4
|
+
|
5
|
+
Given I have a User Profile
|
6
|
+
And I am offline
|
7
|
+
When the App signs me on with valid credentials
|
8
|
+
Then the App receives a 201 response code
|
9
|
+
And the App receives an Auth Token
|
10
|
+
# And I am online
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
Scenario: API Invalid Credentials
|
15
|
+
|
16
|
+
Given I have a User Profile
|
17
|
+
And I am offline
|
18
|
+
When the App tries to sign me on with invalid credentials
|
19
|
+
Then the App receives a 401 response code
|
20
|
+
And the App receives an Invalid User error
|
21
|
+
# And I am offline
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
Scenario: API Locked Account
|
26
|
+
|
27
|
+
Given I have a User Profile
|
28
|
+
And I am offline
|
29
|
+
When the App tries to sign me on with invalid credentials
|
30
|
+
And the App tries to sign me on with invalid credentials
|
31
|
+
And the App tries to sign me on with invalid credentials
|
32
|
+
Then the App receives a 423 response code
|
33
|
+
And the App receives a Locked User error
|
34
|
+
# And I am locked out
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
Scenario: API Locked Valid Sign On Attempt
|
39
|
+
|
40
|
+
Given I have a Locked User Profile
|
41
|
+
When the App signs me on with valid credentials
|
42
|
+
Then the App receives a 423 response code
|
43
|
+
And the App receives a Locked User error
|
44
|
+
# And I am locked out
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
Scenario: API Locked Invalid Sign On Attempt
|
49
|
+
|
50
|
+
Given I have a Locked User Profile
|
51
|
+
When the App tries to sign me on with invalid credentials
|
52
|
+
Then the App receives a 423 response code
|
53
|
+
And the App receives a Locked User error
|
54
|
+
# And I am locked out
|
55
|
+
|
56
|
+
|
57
|
+
|
58
|
+
Scenario: API Online Valid Sign On Attempt
|
59
|
+
|
60
|
+
Given I have a User Profile
|
61
|
+
When the App signs me on with valid credentials
|
62
|
+
And the App signs me on with valid credentials
|
63
|
+
Then the App receives a 201 response code
|
64
|
+
And the App receives an Auth Token
|
65
|
+
# And I am online
|
66
|
+
|
67
|
+
|
68
|
+
# TODO FIX ME - How should this respond?
|
69
|
+
Scenario: API Online Invalid Sign On Attempt
|
70
|
+
|
71
|
+
Given I have a User Profile
|
72
|
+
When the App signs me on with valid credentials
|
73
|
+
And the App tries to sign me on with invalid credentials
|
74
|
+
Then the App receives a 201 response code
|
75
|
+
And the App receives an Auth Token
|
76
|
+
# And I am online
|
@@ -0,0 +1,38 @@
|
|
1
|
+
Feature: API Sign Up
|
2
|
+
|
3
|
+
Scenario: API Valid Sign Up
|
4
|
+
|
5
|
+
When the App signs me up with valid credentials
|
6
|
+
Then the App receives a 201 response code
|
7
|
+
And the App receives an Auth Token
|
8
|
+
|
9
|
+
|
10
|
+
Scenario: API Sign Up with Duplicate Email
|
11
|
+
|
12
|
+
When the App tries to sign me up with a duplicate email
|
13
|
+
Then the App receives a 409 response code
|
14
|
+
And the App receives a duplicate email error
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
Scenario: API Sign Up with Invalid Email
|
19
|
+
|
20
|
+
When the App tries to sign me up with an invalid email address
|
21
|
+
Then the App receives a 412 response code
|
22
|
+
And the App receive an invalid email error
|
23
|
+
|
24
|
+
|
25
|
+
# TODO Return 422 for semantic error?
|
26
|
+
Scenario: API Sign Up with Missing Email
|
27
|
+
|
28
|
+
When the App tries to sign me up with a missing email address
|
29
|
+
Then the App receives a 412 response code
|
30
|
+
And the App receives a missing email error
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
Scenario: API Sign Up with Missing Password
|
35
|
+
|
36
|
+
When the App tries to sign me up with a missing password
|
37
|
+
Then the App receives a 412 response code
|
38
|
+
And the App receives a missing password error
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
When /^the App signs me off$/ do
|
3
|
+
@client = Rack::Test::Session.new Rack::MockSession.new Test::App
|
4
|
+
@client.header 'X-AUTH-TOKEN', @user.pass_key.token
|
5
|
+
@client.delete '/profiles/' + @user.id.to_s + '/key'
|
6
|
+
@user.reload # Refresh from the database
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
Then /^I am offline$/ do
|
11
|
+
@user.status.should == :offline
|
12
|
+
end
|
13
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
When /^the App tries to sign me on with invalid credentials$/ do
|
3
|
+
credentials = {
|
4
|
+
:profile => {
|
5
|
+
:email => "bad@email.com",
|
6
|
+
:pass_phrase => "bad-password"
|
7
|
+
}
|
8
|
+
}
|
9
|
+
@client = Rack::Test::Session.new Rack::MockSession.new Test::App
|
10
|
+
@client.post '/profiles/' + @user.id.to_s + '/key', credentials
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
When /^the App receives an Invalid User error$/ do
|
15
|
+
received = JSON.parse @client.last_response.body
|
16
|
+
expected = JSON.parse({:error => "The email or pass phrase you provided doesn't match our records."}.to_json)
|
17
|
+
received.should == expected
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
When /^the App receives a Locked User error$/ do
|
3
|
+
received = JSON.parse @client.last_response.body
|
4
|
+
expected = JSON.parse({:error => "Your account is locked. You can try to sign on again in 30 minutes."}.to_json)
|
5
|
+
received.should == expected
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
Given /^I have a Locked User Profile$/ do
|
10
|
+
@user = FactoryGirl.create :user_profile
|
11
|
+
@user.status = :locked
|
12
|
+
@user.save
|
13
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
Given /^I have a User Profile$/ do
|
3
|
+
@user = FactoryGirl.create :user_profile
|
4
|
+
@user.save # TODO Determine why this is necessary
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
When /^the App signs me on with valid credentials$/ do
|
9
|
+
credentials = {
|
10
|
+
:profile => {
|
11
|
+
:email => @user.email,
|
12
|
+
:pass_phrase => "password"
|
13
|
+
}
|
14
|
+
}
|
15
|
+
@client = Rack::Test::Session.new Rack::MockSession.new Test::App
|
16
|
+
@client.post '/profiles/' + @user.id.to_s + '/key', credentials
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
When /^I sign on with valid credentials$/ do
|
21
|
+
@user.sign_on @user.email, 'password'
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
When /^I am online$/ do
|
26
|
+
@user.status.should == :online
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
When /^the App tries to sign me up with a duplicate email$/ do
|
3
|
+
email = FactoryGirl.generate :email
|
4
|
+
@user = AuthFx::UserProfile.sign_up email, "password"
|
5
|
+
credentials = {
|
6
|
+
:profile => {
|
7
|
+
:email => email,
|
8
|
+
:pass_phrase => "password"
|
9
|
+
}
|
10
|
+
}
|
11
|
+
@client = Rack::Test::Session.new Rack::MockSession.new Test::App
|
12
|
+
@client.post '/profiles/', credentials
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
Then /^the App receives a duplicate email error$/ do
|
17
|
+
received = JSON.parse @client.last_response.body
|
18
|
+
expected = JSON.parse({:error => "We already have that email."}.to_json)
|
19
|
+
received.should == expected
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
When /^the App tries to sign me up with an invalid email address$/ do
|
3
|
+
credentials = {
|
4
|
+
:profile => {
|
5
|
+
:email => "bad.email.com",
|
6
|
+
:pass_phrase => "password"
|
7
|
+
}
|
8
|
+
}
|
9
|
+
@client = Rack::Test::Session.new Rack::MockSession.new Test::App
|
10
|
+
@client.post '/profiles/', credentials
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
Then /^the App receive an invalid email error$/ do
|
15
|
+
received = JSON.parse @client.last_response.body
|
16
|
+
expected = JSON.parse({:errors => {:email => ["Doesn't look like an email address to me ..."]}}.to_json)
|
17
|
+
received.should == expected
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
When /^the App tries to sign me up with a missing email address$/ do
|
3
|
+
credentials = {
|
4
|
+
:profile => {
|
5
|
+
:email => "",
|
6
|
+
:pass_phrase => "password"
|
7
|
+
}
|
8
|
+
}
|
9
|
+
@client = Rack::Test::Session.new Rack::MockSession.new Test::App
|
10
|
+
@client.post '/profiles/', credentials
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
Then /^the App receives a missing email error$/ do
|
15
|
+
received = JSON.parse @client.last_response.body
|
16
|
+
expected = JSON.parse({:errors => {:email => ["We need your email address."]}}.to_json)
|
17
|
+
received.should == expected
|
18
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
When /^the App signs me up with valid credentials$/ do
|
3
|
+
email = FactoryGirl.generate :email
|
4
|
+
credentials = {
|
5
|
+
:profile => {
|
6
|
+
:email => email,
|
7
|
+
:pass_phrase => "password"
|
8
|
+
}
|
9
|
+
}
|
10
|
+
@client = Rack::Test::Session.new Rack::MockSession.new Test::App
|
11
|
+
@client.post '/profiles/', credentials
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
Then /^the App receives a (\d+) response code$/ do |arg|
|
16
|
+
@client.last_response.status.should == arg.to_i
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
Then /^the App receives an Auth Token$/ do
|
21
|
+
token = @client.last_response.headers['X-AUTH-TOKEN']
|
22
|
+
token.should_not.nil?
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
When /^the App tries to sign me up with a missing password$/ do
|
3
|
+
email = FactoryGirl.generate :email
|
4
|
+
credentials = {
|
5
|
+
:profile => {
|
6
|
+
:email => email,
|
7
|
+
:pass_phrase => ""
|
8
|
+
}
|
9
|
+
}
|
10
|
+
@client = Rack::Test::Session.new Rack::MockSession.new Test::App
|
11
|
+
@client.post '/profiles/', credentials
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
Then /^the App receives a missing password error$/ do
|
16
|
+
received = JSON.parse @client.last_response.body
|
17
|
+
expected = JSON.parse({:errors => {:pass_phrase => ["Pass phrase must be between 5 and 50 characters long"]}}.to_json)
|
18
|
+
received.should == expected
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'sinatra/fx-auth'
|
3
|
+
require 'rack/test'
|
4
|
+
|
5
|
+
require 'fx-auth/models'
|
6
|
+
require './test/factories'
|
7
|
+
|
8
|
+
|
9
|
+
module Test
|
10
|
+
class App < Sinatra::Base
|
11
|
+
register Sinatra::Fx::Auth
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
module AppHelper
|
17
|
+
def app
|
18
|
+
Test::App
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
World(Rack::Test::Methods, AppHelper)
|
@@ -0,0 +1,263 @@
|
|
1
|
+
require 'fx-auth/models'
|
2
|
+
|
3
|
+
|
4
|
+
module Sinatra
|
5
|
+
module Fx
|
6
|
+
module Auth
|
7
|
+
include AuthFx
|
8
|
+
|
9
|
+
module Helpers
|
10
|
+
|
11
|
+
def authenticated?
|
12
|
+
authenticated = false
|
13
|
+
profile, token = token_credentials
|
14
|
+
authenticated = profile.authenticate? token if profile #, request.ip if profile
|
15
|
+
log_authentication_failure profile, token unless authenticated #, request.ip unless authenticated
|
16
|
+
authenticated
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def authorized? *roles
|
21
|
+
authorized = false
|
22
|
+
profile, token = token_credentials
|
23
|
+
authorized = profile.authorized? roles if profile
|
24
|
+
log_authorization_failure profile, roles unless authorized
|
25
|
+
authorized
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
|
32
|
+
def token_credentials
|
33
|
+
profile = nil
|
34
|
+
token = request.env['HTTP_X_AUTH_TOKEN']
|
35
|
+
unless token.nil?
|
36
|
+
passkey = Auth::PassKey.first :token => token
|
37
|
+
profile = passkey.user_profile if passkey
|
38
|
+
end
|
39
|
+
return profile, token
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def param_credentials
|
44
|
+
return params[:profile][:email], params[:profile][:pass_phrase]
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def valid_params?
|
49
|
+
# TODO Handle JSON body as well as FORM encoding
|
50
|
+
# request.body.rewind # in case someone already read it
|
51
|
+
# profile = JSON.parse request.body.read
|
52
|
+
|
53
|
+
params[:profile] and params[:profile][:email] and params[:profile][:pass_phrase]
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def error_message
|
58
|
+
message = env['sinatra.error'].message
|
59
|
+
logger.error '### Error: ' + message + ' ###'
|
60
|
+
{:error => message}.to_json
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def find_user
|
65
|
+
profile = Auth::UserProfile.get params[:id]
|
66
|
+
raise Auth::MissingUserError unless profile
|
67
|
+
profile
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def profile_exclusions
|
72
|
+
[
|
73
|
+
:created_at,
|
74
|
+
:updated_at,
|
75
|
+
:email_verification_code,
|
76
|
+
:pass_phrase,
|
77
|
+
:pass_phrase_crypt,
|
78
|
+
:pass_phrase_expires_at,
|
79
|
+
:sign_on_attempts,
|
80
|
+
:locked_until
|
81
|
+
]
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
def pass_key_exclusions
|
86
|
+
[
|
87
|
+
:id,
|
88
|
+
:created_at,
|
89
|
+
:updated_at,
|
90
|
+
:user_profile_id
|
91
|
+
]
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def log_authentication_failure profile, token #, ip_address
|
96
|
+
logger.warn '### BEGIN Authentication FAILURE ###'
|
97
|
+
if profile
|
98
|
+
logger.warn ' Profile: ' + profile.id.to_s
|
99
|
+
logger.warn ' Status: ' + profile.status.to_s if profile.status != :online
|
100
|
+
if profile.pass_key
|
101
|
+
logger.warn ' Token: ' + profile.pass_key.token + ' != Attempted: ' + token if profile.pass_key.token != token
|
102
|
+
logger.warn ' Expired: ' + profile.pass_key.expires.to_s + ' < ' + Time.now.to_s if profile.pass_key.expired?
|
103
|
+
#logger.warn ' IP: ' + profile.pass_key.ip_address.to_s + ' != Attempted: ' + ip_address if profile.pass_key.ip_address != ip_address
|
104
|
+
else
|
105
|
+
logger.warn ' PassKey: Missing'
|
106
|
+
end
|
107
|
+
else
|
108
|
+
logger.warn ' Profile: Not Found'
|
109
|
+
end
|
110
|
+
logger.warn '### END Authentication FAILURE ###'
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
def log_authorization_failure profile, allowed_roles
|
115
|
+
logger.warn '### BEGIN Authorization FAILURE ###'
|
116
|
+
if profile
|
117
|
+
profile_roles = []
|
118
|
+
profile.roles.each { |role| profile_roles.push role.name.to_sym }
|
119
|
+
logger.warn ' Profile: ' + profile.id.to_s
|
120
|
+
logger.warn ' Allowed Roles: ' + allowed_roles.to_s
|
121
|
+
logger.warn ' Profile Roles: ' + profile_roles.to_s
|
122
|
+
else
|
123
|
+
logger.warn ' Profile: Not Found'
|
124
|
+
end
|
125
|
+
logger.warn '### END Authorization FAILURE ###'
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
def self.registered app
|
132
|
+
app.helpers Auth::Helpers
|
133
|
+
|
134
|
+
app.enable :logging
|
135
|
+
|
136
|
+
app.set :raise_errors, Proc.new { false }
|
137
|
+
app.set :show_exceptions, false
|
138
|
+
|
139
|
+
|
140
|
+
app.set :auth do |*roles|
|
141
|
+
condition do
|
142
|
+
unless authenticated? and authorized? *roles
|
143
|
+
halt 401 # TODO Return any additional info? Expired session, etc.?
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
app.before do
|
150
|
+
content_type 'application/json' # TODO Support other representations, XML, etc.
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
# Sign Up
|
155
|
+
app.post '/profiles/?' do
|
156
|
+
halt 422 unless valid_params?
|
157
|
+
|
158
|
+
email, pass_phrase = param_credentials
|
159
|
+
profile = UserProfile.sign_up email, pass_phrase #, request.ip
|
160
|
+
|
161
|
+
if profile.errors.length == 0
|
162
|
+
headers "location" => '/profiles/' + profile.id.to_s,
|
163
|
+
"X-AUTH-TOKEN" => profile.pass_key.token
|
164
|
+
body profile.to_json :exclude => profile_exclusions
|
165
|
+
status 201
|
166
|
+
|
167
|
+
else
|
168
|
+
errs = {:errors => profile.errors.to_h}
|
169
|
+
body errs.to_json
|
170
|
+
status 412
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
# Sign On
|
176
|
+
app.post '/profiles/:id/key/?' do
|
177
|
+
profile = find_user
|
178
|
+
|
179
|
+
raise InvalidUserError unless valid_params?
|
180
|
+
raise LockedUserError.new :locked_until => profile.locked_until if profile.status == :locked
|
181
|
+
|
182
|
+
email, pass_phrase = param_credentials
|
183
|
+
pass_key = profile.sign_on email, pass_phrase #, request.ip
|
184
|
+
|
185
|
+
if pass_key
|
186
|
+
headers "location" => '/profiles/' + profile.id.to_s + '/key',
|
187
|
+
"X-AUTH-TOKEN" => pass_key.token
|
188
|
+
body pass_key.to_json :exclude => pass_key_exclusions
|
189
|
+
status 201
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
# Sign Off
|
195
|
+
app.delete '/profiles/:id/key', :auth => [:admin, :user] do
|
196
|
+
profile = find_user
|
197
|
+
profile.sign_off
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
app.get '/profiles/:id', :auth => [:admin, :user] do
|
202
|
+
profile = find_user
|
203
|
+
profile.to_json :exclude => profile_exclusions
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
app.put '/profiles/:id', :auth => [:admin, :user] do
|
208
|
+
profile = find_user
|
209
|
+
if profile.update params[:profile]
|
210
|
+
profile.to_json :exclude => profile_exclusions
|
211
|
+
else
|
212
|
+
errs = {:errors => profile.errors.to_h}
|
213
|
+
body errs.to_json
|
214
|
+
status 412
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
app.get '/profiles/?', :auth => :admin do
|
220
|
+
UserProfile.all.to_json :exclude => profile_exclusions
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
app.delete '/profiles/:id', :auth => :admin do
|
225
|
+
profile = find_user
|
226
|
+
unless profile.destroy
|
227
|
+
errs = {:errors => profile.errors.to_h}
|
228
|
+
body errs.to_json
|
229
|
+
status 412
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
|
234
|
+
app.error InvalidUserError do
|
235
|
+
halt 401, error_message
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
app.error MissingUserError do
|
240
|
+
halt 404, error_message
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
app.error DuplicateUserError do
|
245
|
+
halt 409, error_message
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
app.error LockedUserError do
|
250
|
+
halt 423, error_message
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
app.error do
|
255
|
+
halt 500, error_message
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
259
|
+
|
260
|
+
#register Auth
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'sinatra/version'
|
7
|
+
|
8
|
+
|
9
|
+
Gem::Specification.new do |gem|
|
10
|
+
gem.name = "sinatra-fx-auth"
|
11
|
+
gem.version = Sinatra::Fx::Auth::VERSION
|
12
|
+
|
13
|
+
gem.authors = ["Dave Jackson"]
|
14
|
+
gem.email = %w(dave.jackson@anywarefx.com)
|
15
|
+
gem.description = %q{Sinatra::Fx::Auth - RESTful Authentication with Role-based Authorization for Sinatra}
|
16
|
+
gem.summary = %q{Sinatra::Fx::Auth - RESTful Authentication with Role-based Authorization for Sinatra}
|
17
|
+
gem.homepage = ""
|
18
|
+
|
19
|
+
gem.files = `git ls-files`.split($/)
|
20
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
21
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
22
|
+
gem.require_paths = %w(lib)
|
23
|
+
|
24
|
+
gem.add_dependency 'sinatra'
|
25
|
+
gem.add_dependency 'fx-auth'
|
26
|
+
|
27
|
+
gem.add_development_dependency 'rake'
|
28
|
+
gem.add_development_dependency 'cucumber'
|
29
|
+
gem.add_development_dependency 'rspec'
|
30
|
+
gem.add_development_dependency 'factory_girl'
|
31
|
+
gem.add_development_dependency 'rack-test'
|
32
|
+
gem.add_development_dependency 'dm-sqlite-adapter'
|
33
|
+
end
|
data/test/factories.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'factory_girl'
|
2
|
+
require 'test/seeds'
|
3
|
+
|
4
|
+
|
5
|
+
FactoryGirl.define do
|
6
|
+
|
7
|
+
sequence :email do |n|
|
8
|
+
"profile#{n}@example.com"
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
factory :user_profile, :class => AuthFx::UserProfile do
|
13
|
+
email
|
14
|
+
pass_phrase "password"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/test/seeds.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'fx-auth/models'
|
2
|
+
|
3
|
+
|
4
|
+
DataMapper::Logger.new $stdout, :debug
|
5
|
+
DataMapper.setup :default, 'sqlite::memory:'
|
6
|
+
DataMapper.auto_migrate!
|
7
|
+
|
8
|
+
|
9
|
+
AuthFx::UserProfile.create(
|
10
|
+
{
|
11
|
+
:email => 'admin@authfx.com',
|
12
|
+
:pass_phrase => 'password',
|
13
|
+
|
14
|
+
:roles => [
|
15
|
+
{
|
16
|
+
:name => 'admin'
|
17
|
+
},
|
18
|
+
{
|
19
|
+
:name => 'user'
|
20
|
+
}
|
21
|
+
]
|
22
|
+
}
|
23
|
+
)
|
metadata
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sinatra-fx-auth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dave Jackson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - '>='
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
name: sinatra
|
20
|
+
prerelease: false
|
21
|
+
type: :runtime
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
name: fx-auth
|
34
|
+
prerelease: false
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
name: rake
|
48
|
+
prerelease: false
|
49
|
+
type: :development
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
name: cucumber
|
62
|
+
prerelease: false
|
63
|
+
type: :development
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
name: rspec
|
76
|
+
prerelease: false
|
77
|
+
type: :development
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
name: factory_girl
|
90
|
+
prerelease: false
|
91
|
+
type: :development
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
name: rack-test
|
104
|
+
prerelease: false
|
105
|
+
type: :development
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - '>='
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
name: dm-sqlite-adapter
|
118
|
+
prerelease: false
|
119
|
+
type: :development
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: Sinatra::Fx::Auth - RESTful Authentication with Role-based Authorization for Sinatra
|
126
|
+
email:
|
127
|
+
- dave.jackson@anywarefx.com
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- .gitignore
|
133
|
+
- Gemfile
|
134
|
+
- LICENSE.txt
|
135
|
+
- README.md
|
136
|
+
- Rakefile
|
137
|
+
- features/api_profile_list.feature
|
138
|
+
- features/api_sign_off.feature
|
139
|
+
- features/api_sign_on.feature
|
140
|
+
- features/api_sign_up.feature
|
141
|
+
- features/step_definitions/api_sign_off/api_sign_off.rb
|
142
|
+
- features/step_definitions/api_sign_on/api_invalid_credentials.rb
|
143
|
+
- features/step_definitions/api_sign_on/api_locked_account.rb
|
144
|
+
- features/step_definitions/api_sign_on/api_valid_credentials.rb
|
145
|
+
- features/step_definitions/api_sign_up/api_duplicate_email.rb
|
146
|
+
- features/step_definitions/api_sign_up/api_invalid_email.rb
|
147
|
+
- features/step_definitions/api_sign_up/api_missing_email.rb
|
148
|
+
- features/step_definitions/api_sign_up/api_valid_sign_up.rb
|
149
|
+
- features/step_definitions/api_sign_up/missing_password.rb
|
150
|
+
- features/support/env.rb
|
151
|
+
- lib/sinatra-fx-auth.rb
|
152
|
+
- lib/sinatra/fx-auth.rb
|
153
|
+
- lib/sinatra/version.rb
|
154
|
+
- sinatra-fx-auth.gemspec
|
155
|
+
- test/factories.rb
|
156
|
+
- test/seeds.rb
|
157
|
+
homepage: ''
|
158
|
+
licenses: []
|
159
|
+
metadata: {}
|
160
|
+
post_install_message:
|
161
|
+
rdoc_options: []
|
162
|
+
require_paths:
|
163
|
+
- lib
|
164
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - '>='
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
requirements: []
|
175
|
+
rubyforge_project:
|
176
|
+
rubygems_version: 2.1.9
|
177
|
+
signing_key:
|
178
|
+
specification_version: 4
|
179
|
+
summary: Sinatra::Fx::Auth - RESTful Authentication with Role-based Authorization for Sinatra
|
180
|
+
test_files:
|
181
|
+
- features/api_profile_list.feature
|
182
|
+
- features/api_sign_off.feature
|
183
|
+
- features/api_sign_on.feature
|
184
|
+
- features/api_sign_up.feature
|
185
|
+
- features/step_definitions/api_sign_off/api_sign_off.rb
|
186
|
+
- features/step_definitions/api_sign_on/api_invalid_credentials.rb
|
187
|
+
- features/step_definitions/api_sign_on/api_locked_account.rb
|
188
|
+
- features/step_definitions/api_sign_on/api_valid_credentials.rb
|
189
|
+
- features/step_definitions/api_sign_up/api_duplicate_email.rb
|
190
|
+
- features/step_definitions/api_sign_up/api_invalid_email.rb
|
191
|
+
- features/step_definitions/api_sign_up/api_missing_email.rb
|
192
|
+
- features/step_definitions/api_sign_up/api_valid_sign_up.rb
|
193
|
+
- features/step_definitions/api_sign_up/missing_password.rb
|
194
|
+
- features/support/env.rb
|
195
|
+
- test/factories.rb
|
196
|
+
- test/seeds.rb
|