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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sso/client/README.md +17 -2
  3. data/lib/sso/client/authentications/passport.rb +176 -0
  4. data/lib/sso/client/passport.rb +7 -3
  5. data/lib/sso/client/passport_verifier.rb +130 -0
  6. data/lib/sso/client/warden/hooks/after_fetch.rb +32 -81
  7. data/lib/sso/client/warden/strategies/passport.rb +43 -0
  8. data/lib/sso/client.rb +3 -0
  9. data/lib/sso/server/authentications/passport.rb +16 -49
  10. data/lib/sso/server/configuration.rb +36 -6
  11. data/lib/sso/server/doorkeeper/access_token_marker.rb +7 -7
  12. data/lib/sso/server/middleware/passport_destruction.rb +40 -0
  13. data/lib/sso/server/middleware/{passport_creation.rb → passport_exchange.rb} +15 -11
  14. data/lib/sso/server/middleware/passport_verification.rb +2 -2
  15. data/lib/sso/server/passport.rb +43 -15
  16. data/lib/sso/server/passports.rb +59 -31
  17. data/lib/sso/server/warden/hooks/after_authentication.rb +1 -1
  18. data/lib/sso/server/warden/hooks/before_logout.rb +1 -1
  19. data/lib/sso/server/warden/strategies/passport.rb +8 -6
  20. data/lib/sso/server.rb +2 -3
  21. data/spec/dummy/app/controllers/sessions_controller.rb +1 -1
  22. data/spec/dummy/config/application.rb +6 -0
  23. data/spec/dummy/config/initializers/sso.rb +10 -6
  24. data/spec/dummy/config/initializers/warden.rb +3 -11
  25. data/spec/dummy/db/migrate/20150303132931_create_passports_table.rb +29 -15
  26. data/spec/dummy/db/schema.rb +10 -5
  27. data/spec/integration/oauth/authorization_code_spec.rb +80 -10
  28. data/spec/integration/oauth/{password_verification_spec.rb → password_spec.rb} +39 -3
  29. data/spec/lib/sso/client/authentications/passport_spec.rb +92 -0
  30. data/spec/{integration/oauth → lib/sso/client/warden/hooks}/after_fetch_spec.rb +4 -3
  31. data/spec/lib/sso/server/middleware/passport_destruction_spec.rb +33 -0
  32. data/spec/lib/sso/server/passports_spec.rb +104 -0
  33. data/spec/spec_helper.rb +2 -0
  34. data/spec/support/factories/doorkeeper/application.rb +0 -3
  35. data/spec/support/factories/server/passport.rb +5 -1
  36. data/spec/support/factories/server/user.rb +1 -1
  37. 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, ip|
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
- # If these flags are included in the #user_state base (see below), all OAuth client apps are immediately aware of the change.
17
- if ip == '198.51.100.74'
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
- else
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
- t.integer :oauth_access_grant_id
13
- t.integer :oauth_access_token_id
14
- t.integer :application_id, null: false
15
- t.integer :owner_id, null: false
16
- t.string :group_id, null: false
17
- t.string :secret, null: false, unique: true
18
- t.inet :ip, null: false
19
- t.string :agent
20
- t.string :location
21
- t.datetime :activity_at, null: false
22
- t.datetime :revoked_at
23
- t.string :revoke_reason
24
- t.timestamps null: false
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, :application_id
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
@@ -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.integer "application_id", null: false
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.datetime "activity_at", null: false
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", ["application_id"], name: "index_passports_on_application_id", using: :btree
78
- add_index "passports", ["group_id"], name: "index_passports_on_group_id", using: :btree
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 :unscoped_doorkeeper_application }
6
+ let!(:client) { create :outsider_doorkeeper_application }
7
7
  let(:redirect_uri) { client.redirect_uri }
8
8
 
9
- let(:grant_params) { { client_id: client.uid, redirect_uri: redirect_uri, response_type: :code, scope: :insider, state: 'some_random_string' } }
10
- let(:latest_grant) { Doorkeeper::AccessGrant.last }
11
- let(:latest_passport) { SSO::Server::Passport.last }
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) { ::Rack::Utils.parse_query(URI.parse(response.location).query).fetch('code') }
42
- let(:grant_type) { :authorization_code }
43
- let(:params) { { client_id: client.uid, client_secret: client.secret, code: grant, grant_type: grant_type, redirect_uri: redirect_uri } }
44
- let(:access_token) { JSON.parse(response.body).fetch 'access_token' }
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 'gets the access token' do
51
- expect(access_token).to be_present
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 :unscoped_doorkeeper_application }
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(:warden_request) { double :warden_request, ip: ip, user_agent: agent, env: warden_env }
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 :unscoped_doorkeeper_application }
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, application_id: oauth_app.id }
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
@@ -9,9 +9,6 @@ FactoryGirl.define do
9
9
  scopes { :outsider }
10
10
  end
11
11
 
12
- factory :unscoped_doorkeeper_application do
13
- end
14
-
15
12
  uid { SecureRandom.hex }
16
13
  secret { SecureRandom.hex }
17
14
  name { %w(Alpha Beta Gamma Delta Epsilon).sample }
@@ -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
- group_id { SecureRandom.hex }
10
+ owner_id { generate(:owner_id) }
11
+ ip { generate(:ip) }
8
12
 
9
13
  end
10
14
  end