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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8009db0ae10bdd4fd77e5b09c1e59919f3eaed12
4
- data.tar.gz: c419f2355fe41f34d7828dcc91f11fc5f3dd3512
3
+ metadata.gz: 68585960729029037de661b49771b7766ae1c2c5
4
+ data.tar.gz: 74381326cc5605fdd6b8b3a1d044bf65fc4915e0
5
5
  SHA512:
6
- metadata.gz: 1cc76c2bee474ee034e978ea0a9e29cf73ff124495cf8565773081654b59f8ad48d87fce7f2e60669073406350222ef56d7abf0ec9a12e3f8714af39820efe69
7
- data.tar.gz: aedea34649873d4b0b03417089a58e2a60aadfea1ab5b92653dfc0ccba7a705f93fdfbd1e1411c344aa277e49ac3007614a48600ac57faef36d5990bffa13638
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 #{passport_secret.inspect}" }
54
+ debug { "Verifying request signature using Passport secret #{chip_passport_secret.inspect}" }
55
55
  signature_request.authenticate do |passport_id|
56
- @passport_id = passport_id
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: passport_secret, user_ip: ip, user_agent: agent, device_id: device_id
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 check_chip
86
- Operations.success :chip_syntax_valid
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 passport_secret
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
@@ -2,7 +2,8 @@ module SSO
2
2
  module Client
3
3
  class Passport
4
4
 
5
- attr_reader :id, :secret, :state, :user, :chip
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}" }
@@ -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
- [user.email, user.password, user.tags.map(&:to_s).sort].join
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) { { udid: 'unique device identifier' } }
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
- context 'no changes' do
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).to receive(:verified!)
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 'a user attribute changed which is not included in the state digest' do
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).to receive(:verified!)
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
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sso
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.alpha7
4
+ version: 0.1.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - halo