sso 0.1.0.alpha5 → 0.1.0.alpha6
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 +4 -4
- data/lib/sso/client/README.md +17 -2
- data/lib/sso/client/authentications/passport.rb +176 -0
- data/lib/sso/client/passport.rb +7 -3
- data/lib/sso/client/passport_verifier.rb +130 -0
- data/lib/sso/client/warden/hooks/after_fetch.rb +32 -81
- data/lib/sso/client/warden/strategies/passport.rb +43 -0
- data/lib/sso/client.rb +3 -0
- data/lib/sso/server/authentications/passport.rb +16 -49
- data/lib/sso/server/configuration.rb +36 -6
- data/lib/sso/server/doorkeeper/access_token_marker.rb +7 -7
- data/lib/sso/server/middleware/passport_destruction.rb +40 -0
- data/lib/sso/server/middleware/{passport_creation.rb → passport_exchange.rb} +15 -11
- data/lib/sso/server/middleware/passport_verification.rb +2 -2
- data/lib/sso/server/passport.rb +43 -15
- data/lib/sso/server/passports.rb +59 -31
- data/lib/sso/server/warden/hooks/after_authentication.rb +1 -1
- data/lib/sso/server/warden/hooks/before_logout.rb +1 -1
- data/lib/sso/server/warden/strategies/passport.rb +8 -6
- data/lib/sso/server.rb +2 -3
- data/spec/dummy/app/controllers/sessions_controller.rb +1 -1
- data/spec/dummy/config/application.rb +6 -0
- data/spec/dummy/config/initializers/sso.rb +10 -6
- data/spec/dummy/config/initializers/warden.rb +3 -11
- data/spec/dummy/db/migrate/20150303132931_create_passports_table.rb +29 -15
- data/spec/dummy/db/schema.rb +10 -5
- data/spec/integration/oauth/authorization_code_spec.rb +80 -10
- data/spec/integration/oauth/{password_verification_spec.rb → password_spec.rb} +39 -3
- data/spec/lib/sso/client/authentications/passport_spec.rb +92 -0
- data/spec/{integration/oauth → lib/sso/client/warden/hooks}/after_fetch_spec.rb +4 -3
- data/spec/lib/sso/server/middleware/passport_destruction_spec.rb +33 -0
- data/spec/lib/sso/server/passports_spec.rb +104 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/factories/doorkeeper/application.rb +0 -3
- data/spec/support/factories/server/passport.rb +5 -1
- data/spec/support/factories/server/user.rb +1 -1
- metadata +31 -21
@@ -4,20 +4,24 @@
|
|
4
4
|
|
5
5
|
SSO.configure do |config|
|
6
6
|
|
7
|
-
config.find_user_for_passport = proc do |passport
|
7
|
+
config.find_user_for_passport = proc do |passport:|
|
8
8
|
# This is your chance to modify the user instance before it is handed out to the OAuth client apps.
|
9
|
+
# The Passport has already been updated with the most recent IP metadata, so you can take that into consideration.
|
9
10
|
|
10
11
|
progname = 'SSO.config.find_user_for_passport'
|
11
|
-
Rails.logger.debug(progname) { "Looking up User #{passport.owner_id} belonging to Passport #{passport.id} surfing with IP #{ip}..." }
|
12
|
+
Rails.logger.debug(progname) { "Looking up User #{passport.owner_id.inspect} belonging to Passport #{passport.id.inspect} surfing with IP #{passport.ip} or #{passport.ip}..." }
|
12
13
|
user = User.find_by_id passport.owner_id
|
13
14
|
return unless user
|
14
15
|
|
15
16
|
# The IP address, for example, might be used to set certain flags on the user object.
|
16
|
-
#
|
17
|
-
|
17
|
+
# Note that the IP can be nil in which case we don't know it.
|
18
|
+
|
19
|
+
if passport.ip == '198.51.100.74'
|
18
20
|
user.tags << :is_at_the_office
|
19
|
-
|
21
|
+
elsif passport.ip
|
20
22
|
user.tags << :is_working_from_home
|
23
|
+
else
|
24
|
+
user.tags << :location_is_unknown
|
21
25
|
end
|
22
26
|
|
23
27
|
user
|
@@ -26,7 +30,7 @@ SSO.configure do |config|
|
|
26
30
|
config.user_state_base = proc do |user|
|
27
31
|
# Include the end-user credentials to force all OAuth client apps to refetch the end-user Passports.
|
28
32
|
# This way you can revoke all relevant Passports on SSO-logout and the OAuth client apps are immediately aware of it.
|
29
|
-
[user.email, user.password, user.tags.sort].join
|
33
|
+
[user.email, user.password, user.tags.map(&:to_s).sort].join
|
30
34
|
end
|
31
35
|
|
32
36
|
# This is a rather static key used to calculate whether a user state changed and needs to be propagated to the OAuth clients.
|
@@ -1,4 +1,6 @@
|
|
1
1
|
# POI
|
2
|
+
# This is the logic to retrieve a user object given
|
3
|
+
|
2
4
|
::Warden::Strategies.add :password do
|
3
5
|
def valid?
|
4
6
|
params['username'].present?
|
@@ -6,17 +8,6 @@
|
|
6
8
|
|
7
9
|
def authenticate!
|
8
10
|
Rails.logger.debug(progname) { 'Authenticating from username and password...' }
|
9
|
-
|
10
|
-
# Note that at this point you might want to log the end-user IP for the attempted login.
|
11
|
-
# That's up to you to solve, but remember one thing:
|
12
|
-
# If you both have an untrusted OAuth client (iPhone) and a trusted one (Alpha Rails app)
|
13
|
-
# and the login at Alpha is performed using the "Resource Owner Password Credentials Grant"
|
14
|
-
# Then you will get Alphas IP, but not the end-users IP. So you might have to pass on the
|
15
|
-
# end user IP from Alpha via params. But you cannot trust params, since the iPhone Client
|
16
|
-
# is not trusted. Thus, in this particular scenario, you cannot blindly trust params['ip']
|
17
|
-
# but you'd have to work with the "insider" and "outsider" doorkeeper application scope
|
18
|
-
# restrictions much like SSO::Server::Authentications::Passport#ip does.
|
19
|
-
|
20
11
|
user = ::User.authenticate params['username'], params['password']
|
21
12
|
|
22
13
|
if user
|
@@ -33,6 +24,7 @@
|
|
33
24
|
end
|
34
25
|
end
|
35
26
|
|
27
|
+
|
36
28
|
# POI
|
37
29
|
Warden::Manager.after_authentication(&::SSO::Server::Warden::Hooks::AfterAuthentication.to_proc)
|
38
30
|
Warden::Manager.before_logout(&::SSO::Server::Warden::Hooks::BeforeLogout.to_proc)
|
@@ -7,32 +7,46 @@
|
|
7
7
|
class CreatePassportsTable < ActiveRecord::Migration
|
8
8
|
def change
|
9
9
|
enable_extension 'uuid-ossp'
|
10
|
+
enable_extension 'hstore'
|
10
11
|
|
11
12
|
create_table :passports, id: :uuid do |t|
|
12
|
-
|
13
|
-
t.integer :
|
14
|
-
t.integer :
|
15
|
-
t.
|
16
|
-
|
17
|
-
|
18
|
-
t.
|
19
|
-
t.string :
|
20
|
-
|
21
|
-
|
22
|
-
t.datetime :
|
23
|
-
t.
|
24
|
-
t.
|
13
|
+
# Relationships with Doorkeeper-internal tables
|
14
|
+
t.integer :oauth_access_grant_id # OAuth Grant Token
|
15
|
+
t.integer :oauth_access_token_id # OAuth Access Token
|
16
|
+
t.boolean :insider # Denormalized: Is the client app trusted?
|
17
|
+
|
18
|
+
# Passport information
|
19
|
+
t.integer :owner_id, null: false # User ID
|
20
|
+
t.string :secret, null: false, unique: true # Random secret string
|
21
|
+
|
22
|
+
# Passport activity
|
23
|
+
t.datetime :activity_at, null: false # Timestamp of most recent usage
|
24
|
+
t.inet :ip, null: false # Most recent IP which used this Passport
|
25
|
+
t.string :agent # Post recent User Agent which used this Passport
|
26
|
+
t.string :location # Human-readable city of the IP (geolocation)
|
27
|
+
t.string :device # Mobile client hardware UUID (if applicable)
|
28
|
+
t.hstore :stamps # Keeping track of *all* IPs which use(d) this Passport
|
29
|
+
|
30
|
+
# Revocation
|
31
|
+
t.datetime :revoked_at # If set, consider this record to be deleted
|
32
|
+
t.string :revoke_reason # Slug describing why deleted (logout, timeout, etc)
|
33
|
+
t.timestamps null: false # Internal Rails created_at and updated_at columns
|
25
34
|
end
|
26
35
|
|
36
|
+
# Doorkeeper is not guaranteed to create a new access token upon each login, it may just return an existing one
|
37
|
+
# That's why we need to check for `revoked_at`, only valid passports bear the constraint
|
27
38
|
add_index :passports, [:owner_id, :oauth_access_token_id], where: 'revoked_at IS NULL AND oauth_access_token_id IS NOT NULL', unique: true, name: :one_access_token_per_owner
|
28
39
|
|
29
40
|
add_index :passports, :oauth_access_grant_id
|
30
41
|
add_index :passports, :oauth_access_token_id
|
31
|
-
add_index :passports, :
|
42
|
+
add_index :passports, :insider
|
32
43
|
add_index :passports, :owner_id
|
33
|
-
add_index :passports, :group_id
|
34
44
|
add_index :passports, :secret
|
45
|
+
add_index :passports, :activity_at
|
35
46
|
add_index :passports, :ip
|
47
|
+
add_index :passports, :location
|
48
|
+
add_index :passports, :device
|
49
|
+
add_index :passports, :revoked_at
|
36
50
|
add_index :passports, :revoke_reason
|
37
51
|
end
|
38
52
|
end
|
data/spec/dummy/db/schema.rb
CHANGED
@@ -16,6 +16,7 @@ ActiveRecord::Schema.define(version: 20150303132931) do
|
|
16
16
|
# These are extensions that must be enabled in order to support this database
|
17
17
|
enable_extension "plpgsql"
|
18
18
|
enable_extension "uuid-ossp"
|
19
|
+
enable_extension "hstore"
|
19
20
|
|
20
21
|
create_table "oauth_access_grants", force: :cascade do |t|
|
21
22
|
t.integer "resource_owner_id", null: false
|
@@ -60,28 +61,32 @@ ActiveRecord::Schema.define(version: 20150303132931) do
|
|
60
61
|
create_table "passports", id: :uuid, default: "uuid_generate_v4()", force: :cascade do |t|
|
61
62
|
t.integer "oauth_access_grant_id"
|
62
63
|
t.integer "oauth_access_token_id"
|
63
|
-
t.
|
64
|
+
t.boolean "insider"
|
64
65
|
t.integer "owner_id", null: false
|
65
|
-
t.string "group_id", null: false
|
66
66
|
t.string "secret", null: false
|
67
|
+
t.datetime "activity_at", null: false
|
67
68
|
t.inet "ip", null: false
|
68
69
|
t.string "agent"
|
69
70
|
t.string "location"
|
70
|
-
t.
|
71
|
+
t.string "device"
|
72
|
+
t.hstore "stamps"
|
71
73
|
t.datetime "revoked_at"
|
72
74
|
t.string "revoke_reason"
|
73
75
|
t.datetime "created_at", null: false
|
74
76
|
t.datetime "updated_at", null: false
|
75
77
|
end
|
76
78
|
|
77
|
-
add_index "passports", ["
|
78
|
-
add_index "passports", ["
|
79
|
+
add_index "passports", ["activity_at"], name: "index_passports_on_activity_at", using: :btree
|
80
|
+
add_index "passports", ["device"], name: "index_passports_on_device", using: :btree
|
81
|
+
add_index "passports", ["insider"], name: "index_passports_on_insider", using: :btree
|
79
82
|
add_index "passports", ["ip"], name: "index_passports_on_ip", using: :btree
|
83
|
+
add_index "passports", ["location"], name: "index_passports_on_location", using: :btree
|
80
84
|
add_index "passports", ["oauth_access_grant_id"], name: "index_passports_on_oauth_access_grant_id", using: :btree
|
81
85
|
add_index "passports", ["oauth_access_token_id"], name: "index_passports_on_oauth_access_token_id", using: :btree
|
82
86
|
add_index "passports", ["owner_id", "oauth_access_token_id"], name: "one_access_token_per_owner", unique: true, where: "((revoked_at IS NULL) AND (oauth_access_token_id IS NOT NULL))", using: :btree
|
83
87
|
add_index "passports", ["owner_id"], name: "index_passports_on_owner_id", using: :btree
|
84
88
|
add_index "passports", ["revoke_reason"], name: "index_passports_on_revoke_reason", using: :btree
|
89
|
+
add_index "passports", ["revoked_at"], name: "index_passports_on_revoked_at", using: :btree
|
85
90
|
add_index "passports", ["secret"], name: "index_passports_on_secret", using: :btree
|
86
91
|
|
87
92
|
create_table "users", force: :cascade do |t|
|
@@ -3,12 +3,20 @@ require 'spec_helper'
|
|
3
3
|
RSpec.describe 'OAuth 2.0 Authorization Grant Flow', type: :request, db: true do
|
4
4
|
|
5
5
|
let!(:user) { create :user }
|
6
|
-
let!(:client) { create :
|
6
|
+
let!(:client) { create :outsider_doorkeeper_application }
|
7
7
|
let(:redirect_uri) { client.redirect_uri }
|
8
8
|
|
9
|
-
let(:
|
10
|
-
let(:
|
11
|
-
let(:
|
9
|
+
let(:scope) { :outsider }
|
10
|
+
let(:grant_params) { { client_id: client.uid, redirect_uri: redirect_uri, response_type: :code, scope: scope, state: 'some_random_string' } }
|
11
|
+
let(:result) { JSON.parse(response.body) }
|
12
|
+
|
13
|
+
let(:latest_grant) { ::Doorkeeper::AccessGrant.last }
|
14
|
+
let(:latest_access_token) { ::Doorkeeper::AccessToken.last }
|
15
|
+
let(:access_token_count) { ::Doorkeeper::AccessToken.count }
|
16
|
+
let(:grant_count) { ::Doorkeeper::AccessGrant.count }
|
17
|
+
|
18
|
+
let(:latest_passport) { ::SSO::Server::Passport.last }
|
19
|
+
let(:passport_count) { ::SSO::Server::Passport.count }
|
12
20
|
|
13
21
|
before do
|
14
22
|
get_via_redirect '/oauth/authorize', grant_params
|
@@ -37,19 +45,81 @@ RSpec.describe 'OAuth 2.0 Authorization Grant Flow', type: :request, db: true do
|
|
37
45
|
expect(latest_passport.oauth_access_grant_id).to eq latest_grant.id
|
38
46
|
end
|
39
47
|
|
48
|
+
it 'does not generate multiple authorization grants' do
|
49
|
+
expect(grant_count).to eq 1
|
50
|
+
end
|
51
|
+
|
40
52
|
context 'Exchanging the Authorization Grant for an Access Token' do
|
41
|
-
let(:grant)
|
42
|
-
let(:grant_type)
|
43
|
-
let(:params)
|
44
|
-
let(:
|
53
|
+
let(:grant) { ::Rack::Utils.parse_query(URI.parse(response.location).query).fetch('code') }
|
54
|
+
let(:grant_type) { :authorization_code }
|
55
|
+
let(:params) { { client_id: client.uid, client_secret: client.secret, code: grant, grant_type: grant_type, redirect_uri: redirect_uri } }
|
56
|
+
let(:token) { JSON.parse(response.body).fetch 'access_token' }
|
45
57
|
|
46
58
|
before do
|
47
59
|
post '/oauth/token', params
|
48
60
|
end
|
49
61
|
|
50
|
-
it '
|
51
|
-
expect(
|
62
|
+
it 'succeeds' do
|
63
|
+
expect(response.status).to eq 200
|
52
64
|
end
|
65
|
+
|
66
|
+
it 'responds with JSON serialized params' do
|
67
|
+
expect(result).to be_instance_of Hash
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'includes the access_token' do
|
71
|
+
expect(result['access_token']).to eq latest_access_token.token
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'generates a passport with the grant token attached to it' do
|
75
|
+
expect(latest_passport.oauth_access_token_id).to eq latest_access_token.id
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'does not generate multiple passports' do
|
79
|
+
expect(passport_count).to eq 1
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'does not generate multiple access tokens' do
|
83
|
+
expect(access_token_count).to eq 1
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'succeeds' do
|
87
|
+
expect(response.status).to eq 200
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'Exchanging the Access Token for a Passport' do
|
91
|
+
before do
|
92
|
+
SSO.config.passport_chip_key = SecureRandom.hex
|
93
|
+
post '/oauth/sso/v1/passports', access_token: token
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'succeeds' do
|
97
|
+
expect(response.status).to eq 200
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'gets the passport' do
|
101
|
+
expect(result['passport']).to be_present
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'is the passport for that access token' do
|
105
|
+
expect(result['passport']['id']).to eq latest_passport.id
|
106
|
+
expect(latest_passport.oauth_access_token_id).to eq latest_access_token.id
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'is an outsider passport' do
|
110
|
+
expect(latest_passport).to_not be_insider
|
111
|
+
end
|
112
|
+
|
113
|
+
context 'insider application' do
|
114
|
+
let!(:client) { create :insider_doorkeeper_application }
|
115
|
+
let(:scope) { :insider }
|
116
|
+
|
117
|
+
it 'is an insider passport' do
|
118
|
+
expect(latest_passport).to be_insider
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
53
123
|
end
|
54
124
|
end
|
55
125
|
|
@@ -3,18 +3,20 @@ require 'spec_helper'
|
|
3
3
|
RSpec.describe 'OAuth 2.0 Resource Owner Password Credentials Grant', type: :request, db: true do
|
4
4
|
|
5
5
|
let!(:user) { create :user }
|
6
|
-
let!(:client) { create :
|
6
|
+
let!(:client) { create :outsider_doorkeeper_application }
|
7
7
|
|
8
|
+
let(:scope) { :outsider }
|
8
9
|
let(:password) { user.password }
|
9
|
-
let(:params) { { grant_type: :password, client_id: client.uid, client_secret: client.secret, username: user.email, password: password } }
|
10
|
+
let(:params) { { grant_type: :password, client_id: client.uid, client_secret: client.secret, username: user.email, password: password, scope: scope } }
|
10
11
|
let(:headers) { { 'HTTP_ACCEPT' => 'application/json' } }
|
11
12
|
|
13
|
+
let(:latest_access_token) { ::Doorkeeper::AccessToken.last }
|
12
14
|
let(:latest_passport) { ::SSO::Server::Passport.last }
|
13
15
|
let(:passport_count) { ::SSO::Server::Passport.count }
|
14
|
-
let(:latest_access_token) { ::Doorkeeper::AccessToken.last }
|
15
16
|
let(:result) { JSON.parse(response.body) }
|
16
17
|
|
17
18
|
before do
|
19
|
+
SSO.config.passport_chip_key = SecureRandom.hex
|
18
20
|
post '/oauth/token', params, headers
|
19
21
|
end
|
20
22
|
|
@@ -38,6 +40,40 @@ RSpec.describe 'OAuth 2.0 Resource Owner Password Credentials Grant', type: :req
|
|
38
40
|
it 'does not generate multiple passports' do
|
39
41
|
expect(passport_count).to eq 1
|
40
42
|
end
|
43
|
+
|
44
|
+
context 'Exchanging the Access Token for a Passport' do
|
45
|
+
let(:token) { JSON.parse(response.body).fetch 'access_token' }
|
46
|
+
|
47
|
+
before do
|
48
|
+
post '/oauth/sso/v1/passports', access_token: token
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'succeeds' do
|
52
|
+
expect(response.status).to eq 200
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'gets the passport' do
|
56
|
+
expect(result['passport']).to be_present
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'is the passport for that access token' do
|
60
|
+
expect(result['passport']['id']).to eq latest_passport.id
|
61
|
+
expect(latest_passport.oauth_access_token_id).to eq latest_access_token.id
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'is an outsider passport' do
|
65
|
+
expect(latest_passport).to_not be_insider
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'insider application' do
|
69
|
+
let!(:client) { create :insider_doorkeeper_application }
|
70
|
+
let(:scope) { :insider }
|
71
|
+
|
72
|
+
it 'is an insider passport' do
|
73
|
+
expect(latest_passport).to be_insider
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
41
77
|
end
|
42
78
|
|
43
79
|
context 'wrong password' do
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe SSO::Client::Authentications::Passport, type: :request, db: true do
|
4
|
+
|
5
|
+
# Untrusted Client
|
6
|
+
let(:request_method) { 'GET' }
|
7
|
+
let(:request_path) { '/some/resource' }
|
8
|
+
let(:request_params) { { passport_chip: passport_chip } }
|
9
|
+
let(:signature_token) { Signature::Token.new passport_id, passport_secret }
|
10
|
+
let(:signature_request) { Signature::Request.new(request_method, request_path, request_params) }
|
11
|
+
let(:auth_hash) { signature_request.sign signature_token }
|
12
|
+
let(:query_params) { request_params.merge auth_hash }
|
13
|
+
let(:ip) { '198.51.100.74' }
|
14
|
+
let(:agent) { 'IE7' }
|
15
|
+
|
16
|
+
# Trusted Client
|
17
|
+
let(:rack_request) { double :rack_request, request_method: request_method, ip: ip, user_agent: agent, path: request_path, query_parameters: query_params.stringify_keys, params: query_params.stringify_keys }
|
18
|
+
let(:warden_env) { {} }
|
19
|
+
let(:warden_request) { double :warden_request, ip: ip, user_agent: agent, env: warden_env }
|
20
|
+
let(:warden) { double :warden, request: warden_request }
|
21
|
+
let(:client_user) { double :client_user }
|
22
|
+
let(:client_passport) { ::SSO::Client::Passport.new id: passport_id, secret: passport_secret, state: passport_state, user: client_user }
|
23
|
+
let(:authentication) { described_class.new rack_request }
|
24
|
+
let(:operation) { authentication.authenticate }
|
25
|
+
let(:passport) { operation.object }
|
26
|
+
|
27
|
+
# Shared
|
28
|
+
let(:passport_id) { server_passport.id }
|
29
|
+
let(:passport_state) { server_passport.state }
|
30
|
+
let(:passport_secret) { server_passport.secret }
|
31
|
+
let(:passport_chip) { server_passport.chip! }
|
32
|
+
|
33
|
+
# Server
|
34
|
+
let(:insider) { false }
|
35
|
+
let!(:server_user) { create :user, name: 'Emily', tags: %i(cool nice) }
|
36
|
+
let!(:server_passport) { create :passport, user: server_user, owner_id: server_user.id, ip: ip, agent: agent, insider: insider }
|
37
|
+
|
38
|
+
before do
|
39
|
+
SSO.config.passport_chip_key = SecureRandom.hex
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'no changes' do
|
43
|
+
before do
|
44
|
+
operation
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'outsider passport' do
|
48
|
+
it 'succeeds' do
|
49
|
+
expect(operation).to be_success
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'verifies the passport' do
|
53
|
+
expect(passport).to be_verified
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'tracks the immediate request IP' do
|
57
|
+
expect(server_passport.reload.ip).to eq '127.0.0.1'
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'attaches the user attributes to the passport' do
|
61
|
+
expect(passport.user).to be_instance_of Hash
|
62
|
+
expect(passport.user['name']).to eq 'Emily'
|
63
|
+
expect(passport.user['email']).to eq 'emily@example.com'
|
64
|
+
expect(passport.user['tags'].sort).to eq %w(cool is_working_from_home nice).sort
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'insider passport' do
|
69
|
+
let(:insider) { true }
|
70
|
+
|
71
|
+
it 'succeeds' do
|
72
|
+
expect(operation).to be_success
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'verifies the passport' do
|
76
|
+
expect(passport).to be_verified
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'tracks the untrusted client IP' do
|
80
|
+
expect(server_passport.reload.ip).to eq ip
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'attaches the user attributes to the passport' do
|
84
|
+
expect(passport.user).to be_instance_of Hash
|
85
|
+
expect(passport.user['name']).to eq 'Emily'
|
86
|
+
expect(passport.user['email']).to eq 'emily@example.com'
|
87
|
+
expect(passport.user['tags'].sort).to eq %w(cool is_at_the_office nice).sort
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -4,14 +4,15 @@ RSpec.describe SSO::Client::Warden::Hooks::AfterFetch, type: :request, db: true
|
|
4
4
|
|
5
5
|
# Client side
|
6
6
|
let(:warden_env) { {} }
|
7
|
-
let(:
|
7
|
+
let(:client_params) { { udid: 'unique device identifier' } }
|
8
|
+
let(:warden_request) { double :warden_request, ip: ip, user_agent: agent, params: client_params, env: warden_env }
|
8
9
|
let(:warden) { double :warden, request: warden_request }
|
9
10
|
let(:hook) { described_class.new passport: client_passport, warden: warden, options: {} }
|
10
11
|
let(:client_user) { double :client_user }
|
11
12
|
let(:client_passport) { ::SSO::Client::Passport.new id: passport_id, secret: passport_secret, state: passport_state, user: client_user }
|
12
13
|
|
13
14
|
# Shared
|
14
|
-
let!(:oauth_app) { create :
|
15
|
+
let!(:oauth_app) { create :outsider_doorkeeper_application }
|
15
16
|
let(:passport_id) { server_passport.id }
|
16
17
|
let(:passport_state) { server_passport.state }
|
17
18
|
let(:passport_secret) { server_passport.secret }
|
@@ -20,7 +21,7 @@ RSpec.describe SSO::Client::Warden::Hooks::AfterFetch, type: :request, db: true
|
|
20
21
|
|
21
22
|
# Server side
|
22
23
|
let!(:server_user) { create :user }
|
23
|
-
let!(:server_passport) { create :passport, user: server_user, owner_id: server_user.id, ip: ip, agent: agent
|
24
|
+
let!(:server_passport) { create :passport, user: server_user, owner_id: server_user.id, ip: ip, agent: agent }
|
24
25
|
|
25
26
|
context 'no changes' do
|
26
27
|
it 'verifies the passport' do
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe SSO::Server::Middleware::PassportDestruction, type: :request, db: true do
|
4
|
+
|
5
|
+
let(:updated_passport) { ::SSO::Server::Passports.find(passport.id).object }
|
6
|
+
|
7
|
+
before do
|
8
|
+
Timecop.freeze
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'passport exists' do
|
12
|
+
let!(:passport) { create :passport }
|
13
|
+
|
14
|
+
it 'succeeds' do
|
15
|
+
delete "/oauth/sso/v1/passports/#{passport.id}"
|
16
|
+
expect(response.status).to eq 200
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'revokes the passport' do
|
20
|
+
delete "/oauth/sso/v1/passports/#{passport.id}"
|
21
|
+
expect(updated_passport.revoked_at.to_i).to eq Time.now.to_i
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'logs out from warden' do
|
25
|
+
Warden.on_next_request do |proxy|
|
26
|
+
expect(proxy).to receive(:logout)
|
27
|
+
end
|
28
|
+
|
29
|
+
delete "/oauth/sso/v1/passports/#{passport.id}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe SSO::Server::Passports do
|
4
|
+
let(:passports) { described_class }
|
5
|
+
|
6
|
+
before do
|
7
|
+
Timecop.freeze
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '.update_activity' do
|
11
|
+
let(:env) { { 'REMOTE_ADDR' => ip, 'rack.input' => '', 'HTTP_USER_AGENT' => 'Safari', 'QUERY_STRING' => 'agent=Chrome&ip=198.51.100.1&device_id=my_device_id' } }
|
12
|
+
let(:ip) { '198.51.100.99' }
|
13
|
+
let(:request) { Rack::Request.new env }
|
14
|
+
|
15
|
+
let(:another_env) { { 'REMOTE_ADDR' => another_ip, 'rack.input' => '', 'HTTP_USER_AGENT' => 'Opera', 'QUERY_STRING' => 'agent=Firefox&ip=198.51.100.2&device_id=another_my_device_id' } }
|
16
|
+
let(:another_ip) { '198.51.100.100' }
|
17
|
+
let(:another_request) { Rack::Request.new another_env }
|
18
|
+
|
19
|
+
let(:insider) { false }
|
20
|
+
let(:passport) { create :passport, activity_at: 1.week.ago, insider: insider }
|
21
|
+
|
22
|
+
before do
|
23
|
+
passports.update_activity passport_id: passport.id, request: request
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'outsider' do
|
27
|
+
it 'creates a brand new stamp' do
|
28
|
+
expect(passport.reload.stamps).to eq '198.51.100.99' => Time.now.to_i.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'tracks the imediate IP' do
|
32
|
+
expect(passport.reload.ip).to eq '198.51.100.99'
|
33
|
+
expect(passport.reload.agent).to eq 'Safari'
|
34
|
+
expect(passport.reload.device).to eq 'my_device_id'
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'updates activity_at' do
|
38
|
+
expect(passport.reload.activity_at.to_i).to eq Time.now.to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'another request' do
|
42
|
+
before do
|
43
|
+
Timecop.freeze 5.minutes.from_now
|
44
|
+
passports.update_activity passport_id: passport.id, request: another_request
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'adds another stamp' do
|
48
|
+
expect(passport.reload.stamps).to eq '198.51.100.99' => 5.minutes.ago.to_i.to_s, '198.51.100.100' => Time.now.to_i.to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'updates activity_at' do
|
52
|
+
expect(passport.reload.activity_at.to_i).to eq Time.now.to_i
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'updates the imediate IP' do
|
56
|
+
expect(passport.reload.ip).to eq '198.51.100.100'
|
57
|
+
expect(passport.reload.agent).to eq 'Opera'
|
58
|
+
expect(passport.reload.device).to eq 'another_my_device_id'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'insider' do
|
64
|
+
let(:insider) { true }
|
65
|
+
|
66
|
+
it 'creates a brand new stamp' do
|
67
|
+
expect(passport.reload.stamps).to eq '198.51.100.1' => Time.now.to_i.to_s
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'tracks the proxied IP' do
|
71
|
+
expect(passport.reload.ip).to eq '198.51.100.1'
|
72
|
+
expect(passport.reload.agent).to eq 'Chrome'
|
73
|
+
expect(passport.reload.device).to eq 'my_device_id'
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'updates activity_at' do
|
77
|
+
expect(passport.reload.activity_at.to_i).to eq Time.now.to_i
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'another request' do
|
81
|
+
before do
|
82
|
+
Timecop.freeze 5.minutes.from_now
|
83
|
+
passports.update_activity passport_id: passport.id, request: another_request
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'adds another stamp' do
|
87
|
+
expect(passport.reload.stamps).to eq '198.51.100.1' => 5.minutes.ago.to_i.to_s, '198.51.100.2' => Time.now.to_i.to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'updates activity_at' do
|
91
|
+
expect(passport.reload.activity_at.to_i).to eq Time.now.to_i
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'updates the proxied IP' do
|
95
|
+
expect(passport.reload.ip).to eq '198.51.100.2'
|
96
|
+
expect(passport.reload.agent).to eq 'Firefox'
|
97
|
+
expect(passport.reload.device).to eq 'another_my_device_id'
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -23,6 +23,7 @@ RSpec.configure do |config|
|
|
23
23
|
|
24
24
|
config.include FactoryGirl::Syntax::Methods
|
25
25
|
config.include SSO::Test::Helpers
|
26
|
+
config.include Warden::Test::Helpers
|
26
27
|
|
27
28
|
config.color = true
|
28
29
|
config.disable_monkey_patching!
|
@@ -45,6 +46,7 @@ RSpec.configure do |config|
|
|
45
46
|
|
46
47
|
config.after :each do
|
47
48
|
Timecop.return
|
49
|
+
Warden.test_reset!
|
48
50
|
end
|
49
51
|
|
50
52
|
config.after :each, db: true do
|
@@ -1,10 +1,14 @@
|
|
1
1
|
FactoryGirl.define do
|
2
|
+
sequence(:owner_id) { |n| (n * 2) + 424242 }
|
3
|
+
sequence(:ip) { |n| IPAddr.new("198.51.100.#{n}").to_s }
|
4
|
+
|
2
5
|
factory :parent_of_all_passports, class: SSO::Server::Passport do
|
3
6
|
|
4
7
|
factory :passport do
|
5
8
|
end
|
6
9
|
|
7
|
-
|
10
|
+
owner_id { generate(:owner_id) }
|
11
|
+
ip { generate(:ip) }
|
8
12
|
|
9
13
|
end
|
10
14
|
end
|