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