sso 0.1.0.alpha7 → 0.1.0.beta1
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/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
|
|