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.
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