sso 0.1.0.alpha7 → 0.1.0.beta1
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/authentications/passport.rb +38 -8
- data/lib/sso/client/passport.rb +18 -1
- data/lib/sso/client/warden/hooks/after_fetch.rb +5 -2
- data/lib/sso/client/warden/strategies/passport.rb +1 -1
- data/lib/sso/server/authentications/passport.rb +1 -0
- data/lib/sso/server/passport.rb +4 -2
- data/lib/sso/server/warden/strategies/passport.rb +1 -1
- data/spec/dummy/app/models/user.rb +10 -0
- data/spec/dummy/config/initializers/sso.rb +1 -1
- data/spec/lib/sso/client/warden/hooks/after_fetch_spec.rb +62 -7
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 68585960729029037de661b49771b7766ae1c2c5
|
4
|
+
data.tar.gz: 74381326cc5605fdd6b8b3a1d044bf65fc4915e0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8ae9e8fa88060efe6c39e36f5bce7750d739c382e9137c9ba409dfe3587674774a16bd491369fb7fd6e05ccf00e5366f7eed4a06435807b7ff00be6c9f6a473
|
7
|
+
data.tar.gz: be505aafa56fe22050682280c7c09a53ceddb25044a01ee4ee7479eb77e992cb349151adc4f7becec0519c43b595b63872f86df31b3cd93e1c24e80e821ff834
|
@@ -51,10 +51,9 @@ module SSO
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def check_request_signature
|
54
|
-
debug { "Verifying request signature using Passport secret #{
|
54
|
+
debug { "Verifying request signature using Passport secret #{chip_passport_secret.inspect}" }
|
55
55
|
signature_request.authenticate do |passport_id|
|
56
|
-
|
57
|
-
Signature::Token.new passport_id, passport_secret
|
56
|
+
Signature::Token.new passport_id, chip_passport_secret
|
58
57
|
end
|
59
58
|
debug { 'Signature looks legit.' }
|
60
59
|
Operations.success :passport_signature_valid
|
@@ -65,7 +64,7 @@ module SSO
|
|
65
64
|
end
|
66
65
|
|
67
66
|
def verifier
|
68
|
-
::SSO::Client::PassportVerifier.new passport_id: passport_id, passport_state: 'refresh', passport_secret:
|
67
|
+
::SSO::Client::PassportVerifier.new passport_id: passport_id, passport_state: 'refresh', passport_secret: chip_passport_secret, user_ip: ip, user_agent: agent, device_id: device_id
|
69
68
|
end
|
70
69
|
|
71
70
|
def verification
|
@@ -82,8 +81,14 @@ module SSO
|
|
82
81
|
::Signature::Request.new request.request_method, request.path, request.params
|
83
82
|
end
|
84
83
|
|
85
|
-
def
|
86
|
-
|
84
|
+
def passport_id
|
85
|
+
return @passport_id if @passport_id
|
86
|
+
signature_request.authenticate do |auth_key|
|
87
|
+
return @passport_id = auth_key
|
88
|
+
end
|
89
|
+
|
90
|
+
rescue ::Signature::AuthenticationError
|
91
|
+
nil
|
87
92
|
end
|
88
93
|
|
89
94
|
def chip_decryption
|
@@ -91,6 +96,7 @@ module SSO
|
|
91
96
|
yield Operations.failure(:missing_chip, object: params) if chip.blank?
|
92
97
|
yield Operations.failure(:missing_chip_key) unless chip_key
|
93
98
|
yield Operations.failure(:missing_chip_iv) unless chip_iv
|
99
|
+
yield Operations.failure(:chip_does_not_belong_to_passport) unless chip_belongs_to_passport?
|
94
100
|
Operations.success :here_is_your_chip_plaintext, object: decrypt_chip
|
95
101
|
|
96
102
|
rescue OpenSSL::Cipher::CipherError => exception
|
@@ -113,8 +119,32 @@ module SSO
|
|
113
119
|
end
|
114
120
|
end
|
115
121
|
|
116
|
-
def
|
117
|
-
decrypt_chip
|
122
|
+
def chip_passport_secret
|
123
|
+
decrypt_chip.to_s.split('|').last
|
124
|
+
end
|
125
|
+
|
126
|
+
def chip_passport_id
|
127
|
+
decrypt_chip.to_s.split('|').first
|
128
|
+
end
|
129
|
+
|
130
|
+
def chip_belongs_to_passport?
|
131
|
+
unless passport_id
|
132
|
+
debug { "Unknown passport_id" }
|
133
|
+
return false
|
134
|
+
end
|
135
|
+
|
136
|
+
unless chip_passport_id
|
137
|
+
debug { "Unknown passport_id" }
|
138
|
+
return false
|
139
|
+
end
|
140
|
+
|
141
|
+
if passport_id.to_s == chip_passport_id
|
142
|
+
debug { "The chip on passport #{passport_id.inspect} appears to belong to it." }
|
143
|
+
true
|
144
|
+
else
|
145
|
+
info { "The passport with ID #{passport_id.inspect} has a chip with the wrong ID #{chip_passport_id.inspect}" }
|
146
|
+
false
|
147
|
+
end
|
118
148
|
end
|
119
149
|
|
120
150
|
def chip_key
|
data/lib/sso/client/passport.rb
CHANGED
@@ -2,7 +2,8 @@ module SSO
|
|
2
2
|
module Client
|
3
3
|
class Passport
|
4
4
|
|
5
|
-
attr_reader :id, :secret, :
|
5
|
+
attr_reader :id, :secret, :chip
|
6
|
+
attr_accessor :state, :user
|
6
7
|
|
7
8
|
def initialize(id:, secret:, state:, user:, chip: nil)
|
8
9
|
@id = id
|
@@ -24,6 +25,22 @@ module SSO
|
|
24
25
|
!verified?
|
25
26
|
end
|
26
27
|
|
28
|
+
def modified!
|
29
|
+
@modified = true
|
30
|
+
end
|
31
|
+
|
32
|
+
def modified?
|
33
|
+
@modified == true
|
34
|
+
end
|
35
|
+
|
36
|
+
def unmodified?
|
37
|
+
!modified?
|
38
|
+
end
|
39
|
+
|
40
|
+
def delta
|
41
|
+
{ state: state, user: user }
|
42
|
+
end
|
43
|
+
|
27
44
|
end
|
28
45
|
end
|
29
46
|
end
|
@@ -63,15 +63,18 @@ module SSO
|
|
63
63
|
when :server_response_missing_success_flag! then server_response_missing_success_flag!
|
64
64
|
when :server_response_unsuccessful! then server_response_unsuccessful!
|
65
65
|
when :passport_valid then passport_valid!
|
66
|
-
when :passport_valid_and_modified then passport_valid_and_modified!
|
66
|
+
when :passport_valid_and_modified then passport_valid_and_modified!(verification.object)
|
67
67
|
when :passport_invalid then passport_invalid!
|
68
68
|
else unexpected_server_response_status!
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
|
-
def passport_valid_and_modified!
|
72
|
+
def passport_valid_and_modified!(modified_passport)
|
73
73
|
debug { 'Valid passport, but state changed' }
|
74
74
|
passport.verified!
|
75
|
+
passport.modified!
|
76
|
+
passport.user = modified_passport.user
|
77
|
+
passport.state = modified_passport.state
|
75
78
|
# meter status: :valid, passport_id: user.passport_id
|
76
79
|
end
|
77
80
|
|
@@ -17,7 +17,7 @@ module SSO
|
|
17
17
|
authentication = passport_authentication
|
18
18
|
|
19
19
|
if authentication.success?
|
20
|
-
debug { 'Authentication from Passport successful.' }
|
20
|
+
debug { 'Authentication on Client from Passport successful.' }
|
21
21
|
debug { "Persisting trusted Passport #{authentication.object.inspect}" }
|
22
22
|
success! authentication.object
|
23
23
|
else
|
@@ -38,6 +38,7 @@ module SSO
|
|
38
38
|
passport.load_user!
|
39
39
|
|
40
40
|
if passport.state == state
|
41
|
+
debug { "The current user state #{passport.state.inspect} did not change." }
|
41
42
|
Operations.success :signature_approved_no_changes, object: success_same_state_rack_array
|
42
43
|
else
|
43
44
|
debug { "The current user state #{passport.state.inspect} does not match the provided state #{state.inspect}" }
|
data/lib/sso/server/passport.rb
CHANGED
@@ -46,9 +46,11 @@ module SSO
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def state!
|
49
|
-
benchmark 'Passport user state calculation' do
|
49
|
+
result = benchmark 'Passport user state calculation' do
|
50
50
|
OpenSSL::HMAC.hexdigest user_state_digest, user_state_key, user_state_base
|
51
51
|
end
|
52
|
+
debug { "The user state is #{result.inspect}" }
|
53
|
+
result
|
52
54
|
end
|
53
55
|
|
54
56
|
def load_user!
|
@@ -89,7 +91,7 @@ module SSO
|
|
89
91
|
|
90
92
|
# Don't get confused, the chip plaintext is the passport secret
|
91
93
|
def chip_plaintext
|
92
|
-
secret
|
94
|
+
[id, secret].join '|'
|
93
95
|
end
|
94
96
|
|
95
97
|
def user_state_key
|
@@ -16,7 +16,7 @@ module SSO
|
|
16
16
|
authentication = passport_authentication
|
17
17
|
|
18
18
|
if authentication.success?
|
19
|
-
debug { 'Authentication from Passport successful.' }
|
19
|
+
debug { 'Authentication on Server from Passport successful.' }
|
20
20
|
debug { "Responding with #{authentication.object}" }
|
21
21
|
custom! authentication.object
|
22
22
|
else
|
@@ -1,9 +1,19 @@
|
|
1
1
|
class User < ActiveRecord::Base
|
2
|
+
include ::SSO::Logging
|
2
3
|
|
3
4
|
# This is a test implementation only, do not try this at home.
|
5
|
+
#
|
4
6
|
def self.authenticate(username, password)
|
5
7
|
Rails.logger.debug('User') { "Checking password of user #{username.inspect}..." }
|
6
8
|
where(email: username, password: password).first
|
7
9
|
end
|
8
10
|
|
11
|
+
# Don't try this at home, you should include the *encrypted* password, not the plaintext here.
|
12
|
+
#
|
13
|
+
def state_base
|
14
|
+
result = [email.to_s, password.to_s, tags.map(&:to_s).sort].join
|
15
|
+
debug { "The user state base is #{result.inspect}" }
|
16
|
+
result
|
17
|
+
end
|
18
|
+
|
9
19
|
end
|
@@ -30,7 +30,7 @@ SSO.configure do |config|
|
|
30
30
|
config.user_state_base = proc do |user|
|
31
31
|
# Include the end-user credentials to force all OAuth client apps to refetch the end-user Passports.
|
32
32
|
# This way you can revoke all relevant Passports on SSO-logout and the OAuth client apps are immediately aware of it.
|
33
|
-
|
33
|
+
user.state_base
|
34
34
|
end
|
35
35
|
|
36
36
|
# This is a rather static key used to calculate whether a user state changed and needs to be propagated to the OAuth clients.
|
@@ -4,11 +4,11 @@ RSpec.describe SSO::Client::Warden::Hooks::AfterFetch, type: :request, db: true
|
|
4
4
|
|
5
5
|
# Client side
|
6
6
|
let(:warden_env) { {} }
|
7
|
-
let(:client_params) { {
|
7
|
+
let(:client_params) { { device_id: 'unique device identifier' } }
|
8
8
|
let(:warden_request) { double :warden_request, ip: ip, user_agent: agent, params: client_params, env: warden_env }
|
9
9
|
let(:warden) { double :warden, request: warden_request }
|
10
10
|
let(:hook) { described_class.new passport: client_passport, warden: warden, options: {} }
|
11
|
-
let(:client_user) { double :client_user }
|
11
|
+
let(:client_user) { double :client_user, name: 'Good old client user' }
|
12
12
|
let(:client_passport) { ::SSO::Client::Passport.new id: passport_id, secret: passport_secret, state: passport_state, user: client_user }
|
13
13
|
|
14
14
|
# Shared
|
@@ -20,24 +20,79 @@ RSpec.describe SSO::Client::Warden::Hooks::AfterFetch, type: :request, db: true
|
|
20
20
|
let(:agent) { 'IE7' }
|
21
21
|
|
22
22
|
# Server side
|
23
|
-
let!(:server_user) { create :user }
|
23
|
+
let!(:server_user) { create :user, tags: %w(wears_glasses never_gives_up) }
|
24
24
|
let!(:server_passport) { create :passport, user: server_user, owner_id: server_user.id, ip: ip, agent: agent }
|
25
25
|
|
26
|
-
|
26
|
+
before do
|
27
|
+
# The server dynamically injects some tags. In order to calculate the user state correctly in our test setup,
|
28
|
+
# We need to "simulate" what the tags will look like once the server modified them. No big problem.
|
29
|
+
allow(server_user).to receive(:tags).and_return %w(wears_glasses is_working_from_home never_gives_up)
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'user does not change' do
|
27
33
|
it 'verifies the passport' do
|
28
|
-
expect(client_passport).
|
34
|
+
expect(client_passport).to_not be_verified
|
35
|
+
hook.call
|
36
|
+
expect(client_passport).to be_verified
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'does not modify the passport' do
|
40
|
+
expect(client_passport).to_not be_modified
|
41
|
+
hook.call
|
42
|
+
expect(client_passport).to_not be_modified
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'does not modify the encapsulated user' do
|
29
46
|
hook.call
|
47
|
+
expect(client_passport.user.name).to eq 'Good old client user'
|
30
48
|
end
|
31
49
|
end
|
32
50
|
|
33
|
-
context '
|
51
|
+
context 'user attribute changed which is not included in the state digest' do
|
34
52
|
before do
|
53
|
+
hook
|
35
54
|
server_user.update_attribute :name, 'Something new'
|
36
55
|
end
|
37
56
|
|
38
57
|
it 'verifies the passport' do
|
39
|
-
expect(client_passport).
|
58
|
+
expect(client_passport).to_not be_verified
|
59
|
+
hook.call
|
60
|
+
expect(client_passport).to be_verified
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'does not modify the passport' do
|
64
|
+
expect(client_passport).to_not be_modified
|
65
|
+
hook.call
|
66
|
+
expect(client_passport).to_not be_modified
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'does not modify the encapsulated user' do
|
70
|
+
hook.call
|
71
|
+
expect(client_passport.user.name).to eq 'Good old client user'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'user attribute changed which results in a new state digest' do
|
76
|
+
before do
|
77
|
+
hook
|
78
|
+
server_user.update_attribute :email, 'brand-new@example.com'
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'verifies the passport' do
|
82
|
+
expect(client_passport).to_not be_verified
|
83
|
+
hook.call
|
84
|
+
expect(client_passport).to be_verified
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'modifies the passport' do
|
88
|
+
expect(client_passport).to_not be_modified
|
89
|
+
hook.call
|
90
|
+
expect(client_passport).to be_modified
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'updates the client user to reflect the server user' do
|
40
94
|
hook.call
|
95
|
+
expect(client_passport.user['name']).to eq server_user.name
|
41
96
|
end
|
42
97
|
end
|
43
98
|
|