sso 0.1.0.alpha5 → 0.1.0.alpha6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|