sso 0.1.0.alpha5 → 0.1.0.alpha6
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/README.md +17 -2
- data/lib/sso/client/authentications/passport.rb +176 -0
- data/lib/sso/client/passport.rb +7 -3
- data/lib/sso/client/passport_verifier.rb +130 -0
- data/lib/sso/client/warden/hooks/after_fetch.rb +32 -81
- data/lib/sso/client/warden/strategies/passport.rb +43 -0
- data/lib/sso/client.rb +3 -0
- data/lib/sso/server/authentications/passport.rb +16 -49
- data/lib/sso/server/configuration.rb +36 -6
- data/lib/sso/server/doorkeeper/access_token_marker.rb +7 -7
- data/lib/sso/server/middleware/passport_destruction.rb +40 -0
- data/lib/sso/server/middleware/{passport_creation.rb → passport_exchange.rb} +15 -11
- data/lib/sso/server/middleware/passport_verification.rb +2 -2
- data/lib/sso/server/passport.rb +43 -15
- data/lib/sso/server/passports.rb +59 -31
- data/lib/sso/server/warden/hooks/after_authentication.rb +1 -1
- data/lib/sso/server/warden/hooks/before_logout.rb +1 -1
- data/lib/sso/server/warden/strategies/passport.rb +8 -6
- data/lib/sso/server.rb +2 -3
- data/spec/dummy/app/controllers/sessions_controller.rb +1 -1
- data/spec/dummy/config/application.rb +6 -0
- data/spec/dummy/config/initializers/sso.rb +10 -6
- data/spec/dummy/config/initializers/warden.rb +3 -11
- data/spec/dummy/db/migrate/20150303132931_create_passports_table.rb +29 -15
- data/spec/dummy/db/schema.rb +10 -5
- data/spec/integration/oauth/authorization_code_spec.rb +80 -10
- data/spec/integration/oauth/{password_verification_spec.rb → password_spec.rb} +39 -3
- data/spec/lib/sso/client/authentications/passport_spec.rb +92 -0
- data/spec/{integration/oauth → lib/sso/client/warden/hooks}/after_fetch_spec.rb +4 -3
- data/spec/lib/sso/server/middleware/passport_destruction_spec.rb +33 -0
- data/spec/lib/sso/server/passports_spec.rb +104 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/factories/doorkeeper/application.rb +0 -3
- data/spec/support/factories/server/passport.rb +5 -1
- data/spec/support/factories/server/user.rb +1 -1
- metadata +31 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5719f768df38f21f0427bf887e96e6c300380a52
|
4
|
+
data.tar.gz: cba2104943a75bb640747ba088229c8b14b7a1d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 938d6845a25437b1b7fc5c20d9709031f50afaec5e9b9359eb6686db8a70e5ff3bd02f4f45fed36c5fc8b08275be08801ea9ec19228486caf910c5c049522b1c
|
7
|
+
data.tar.gz: 8983a717b6a800e7add505e27c1d9fce1368b8a9e651d1625e00ee3a968ad3ff8ba321019d5cb8908767db5231c6b53c088b190fdbcd45c3b9d7cdb1cddc8c22
|
data/lib/sso/client/README.md
CHANGED
@@ -18,7 +18,7 @@
|
|
18
18
|
* A public OAuth Client, such as an `iPhone`, uses the `Resource Owner Password Credentials Grant` to exchange the `username` and `password` of the end user for an OAuth `access_token` with the OAuth permission scope `outsider`.
|
19
19
|
* You exchange the `access_token` for a passport token. That is effectively your API token used to communicate with the OAuth Rails clients.
|
20
20
|
* The OAuth Rails clients verify that token with the OAuth server at every request.
|
21
|
-
* In effect, this turns your iPhone app into a Browser, technically not
|
21
|
+
* In effect, this turns your iPhone app into a Browser, technically not a trusted OAuth Client.
|
22
22
|
|
23
23
|
#### Also good to know
|
24
24
|
|
@@ -35,7 +35,22 @@ gem 'sso', require: 'sso/client'
|
|
35
35
|
|
36
36
|
#### Make sure you activated the Warden middleware provided by the `warden` gem
|
37
37
|
|
38
|
-
See [the Warden wiki](https://github.com/hassox/warden/wiki/Setup)
|
38
|
+
See [the Warden wiki](https://github.com/hassox/warden/wiki/Setup).
|
39
|
+
However, one thing is special here, you must not store the entire object, but only a reference to the passport.
|
40
|
+
If you store the entire object, that would be a major security risk and allow for cookie replay attacks.
|
41
|
+
|
42
|
+
```
|
43
|
+
class Warden::SessionSerializer
|
44
|
+
def serialize(passport)
|
45
|
+
Redis.set passport.id, passport.to_json
|
46
|
+
end
|
47
|
+
|
48
|
+
def deserialize(passport_id)
|
49
|
+
json = Redis.get passport_id
|
50
|
+
SSO::Client::Passport.new JSON.parse(json)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
```
|
39
54
|
|
40
55
|
#### Set the URL to the SSO Server
|
41
56
|
|
@@ -0,0 +1,176 @@
|
|
1
|
+
module SSO
|
2
|
+
module Client
|
3
|
+
module Authentications
|
4
|
+
class Passport
|
5
|
+
include ::SSO::Logging
|
6
|
+
include ::SSO::Benchmarking
|
7
|
+
|
8
|
+
delegate :params, to: :request
|
9
|
+
|
10
|
+
def initialize(request)
|
11
|
+
@request = request
|
12
|
+
end
|
13
|
+
|
14
|
+
def authenticate
|
15
|
+
debug { "Performing authentication..." }
|
16
|
+
result = authenticate!
|
17
|
+
|
18
|
+
if result.success?
|
19
|
+
debug { "Authentication succeeded." }
|
20
|
+
return result
|
21
|
+
end
|
22
|
+
|
23
|
+
debug { "The Client Passport authentication failed: #{result.code}" }
|
24
|
+
Operations.failure :passport_authentication_failed, object: failure_rack_array
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :request, :passport_id
|
30
|
+
|
31
|
+
def authenticate!
|
32
|
+
chip_decryption { |failure| return failure }
|
33
|
+
check_request_signature { |failure| return failure }
|
34
|
+
passport = retrieve_passport { |failure| return failure }
|
35
|
+
passport.verified!
|
36
|
+
|
37
|
+
Operations.success :passport_received, object: passport
|
38
|
+
end
|
39
|
+
|
40
|
+
def retrieve_passport
|
41
|
+
debug { 'Retrieving Passport from server...' }
|
42
|
+
if verification.success? && verification.code == :passport_valid_and_modified
|
43
|
+
passport = verification.object
|
44
|
+
|
45
|
+
debug { "Successfully retrieved Passport with ID #{passport_id} from server." }
|
46
|
+
return passport
|
47
|
+
else
|
48
|
+
debug { 'Could not obtain Passport from server.' }
|
49
|
+
yield verification
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def check_request_signature
|
54
|
+
debug { "Verifying request signature using Passport secret #{passport_secret.inspect}" }
|
55
|
+
signature_request.authenticate do |passport_id|
|
56
|
+
@passport_id = passport_id
|
57
|
+
Signature::Token.new passport_id, passport_secret
|
58
|
+
end
|
59
|
+
debug { 'Signature looks legit.' }
|
60
|
+
Operations.success :passport_signature_valid
|
61
|
+
|
62
|
+
rescue ::Signature::AuthenticationError => exception
|
63
|
+
debug { "The Signature Authentication failed. #{exception.message}" }
|
64
|
+
yield Operations.failure :invalid_passport_signature
|
65
|
+
end
|
66
|
+
|
67
|
+
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
|
69
|
+
end
|
70
|
+
|
71
|
+
def verification
|
72
|
+
@verification ||= verifier.call
|
73
|
+
end
|
74
|
+
|
75
|
+
def failure_rack_array
|
76
|
+
payload = { success: true, code: :passport_verification_failed }
|
77
|
+
[200, { 'Content-Type' => 'application/json' }, [payload.to_json]]
|
78
|
+
end
|
79
|
+
|
80
|
+
def signature_request
|
81
|
+
debug { "Verifying signature of #{request.request_method.inspect} #{request.path.inspect} #{request.params.inspect}"}
|
82
|
+
::Signature::Request.new request.request_method, request.path, request.params
|
83
|
+
end
|
84
|
+
|
85
|
+
def check_chip
|
86
|
+
Operations.success :chip_syntax_valid
|
87
|
+
end
|
88
|
+
|
89
|
+
def chip_decryption
|
90
|
+
debug { "Validating chip decryptability of raw chip #{chip.inspect}"}
|
91
|
+
yield Operations.failure(:missing_chip, object: params) if chip.blank?
|
92
|
+
yield Operations.failure(:missing_chip_key) unless chip_key
|
93
|
+
yield Operations.failure(:missing_chip_iv) unless chip_iv
|
94
|
+
Operations.success :here_is_your_chip_plaintext, object: decrypt_chip
|
95
|
+
|
96
|
+
rescue OpenSSL::Cipher::CipherError => exception
|
97
|
+
yield Operations.failure :chip_decryption_failed, object: exception.message
|
98
|
+
end
|
99
|
+
|
100
|
+
def decrypt_chip
|
101
|
+
@decrypt_chip ||= decrypt_chip!
|
102
|
+
end
|
103
|
+
|
104
|
+
def decrypt_chip!
|
105
|
+
benchmark 'Passport chip decryption' do
|
106
|
+
decipher = chip_digest
|
107
|
+
decipher.decrypt
|
108
|
+
decipher.key = chip_key
|
109
|
+
decipher.iv = chip_iv
|
110
|
+
plaintext = decipher.update(chip_ciphertext) + decipher.final
|
111
|
+
logger.debug { "Decryptied chip plaintext #{plaintext.inspect} using key #{chip_key.inspect} and iv #{chip_iv.inspect} and ciphertext #{chip_ciphertext.inspect}"}
|
112
|
+
plaintext
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def passport_secret
|
117
|
+
decrypt_chip
|
118
|
+
end
|
119
|
+
|
120
|
+
def chip_key
|
121
|
+
::SSO.config.passport_chip_key
|
122
|
+
end
|
123
|
+
|
124
|
+
def user_state_digest
|
125
|
+
::OpenSSL::Digest.new 'sha1'
|
126
|
+
end
|
127
|
+
|
128
|
+
def chip_ciphertext
|
129
|
+
Base64.decode64 encoded_chip_ciphertext
|
130
|
+
end
|
131
|
+
|
132
|
+
def encoded_chip_ciphertext
|
133
|
+
chip_ciphertext_and_iv.first
|
134
|
+
end
|
135
|
+
|
136
|
+
def chip_iv
|
137
|
+
Base64.decode64 chip_ciphertext_and_iv.last
|
138
|
+
end
|
139
|
+
|
140
|
+
def encoded_chip_iv
|
141
|
+
chip_iv
|
142
|
+
end
|
143
|
+
|
144
|
+
def chip_ciphertext_and_iv
|
145
|
+
chip.to_s.split '|'
|
146
|
+
end
|
147
|
+
|
148
|
+
def chip
|
149
|
+
params['passport_chip']
|
150
|
+
end
|
151
|
+
|
152
|
+
#def warden
|
153
|
+
# request.env['warden']
|
154
|
+
#end
|
155
|
+
|
156
|
+
def chip_digest
|
157
|
+
::OpenSSL::Cipher::AES256.new :CBC
|
158
|
+
end
|
159
|
+
|
160
|
+
# TODO Use ActionDispatch remote IP or you might get the Load Balancer's IP instead :(
|
161
|
+
def ip
|
162
|
+
request.ip
|
163
|
+
end
|
164
|
+
|
165
|
+
def agent
|
166
|
+
request.user_agent
|
167
|
+
end
|
168
|
+
|
169
|
+
def device_id
|
170
|
+
request.params['device_id']
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
data/lib/sso/client/passport.rb
CHANGED
@@ -2,10 +2,14 @@ module SSO
|
|
2
2
|
module Client
|
3
3
|
class Passport
|
4
4
|
|
5
|
-
attr_reader :id, :secret, :state, :user
|
5
|
+
attr_reader :id, :secret, :state, :user, :chip
|
6
6
|
|
7
|
-
def initialize(id:, secret:, state:, user:)
|
8
|
-
@id
|
7
|
+
def initialize(id:, secret:, state:, user:, chip: nil)
|
8
|
+
@id = id
|
9
|
+
@secret = secret
|
10
|
+
@state = state
|
11
|
+
@user = user
|
12
|
+
@chip = chip
|
9
13
|
end
|
10
14
|
|
11
15
|
def verified!
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module SSO
|
2
|
+
module Client
|
3
|
+
class PassportVerifier
|
4
|
+
include ::SSO::Benchmarking
|
5
|
+
|
6
|
+
attr_reader :passport_id, :passport_state, :passport_secret, :user_ip, :user_agent, :device_id
|
7
|
+
|
8
|
+
def initialize(passport_id:, passport_state:, passport_secret:, user_ip:, user_agent: nil, device_id: nil)
|
9
|
+
@passport_id = passport_id
|
10
|
+
@passport_state = passport_state
|
11
|
+
@passport_secret = passport_secret
|
12
|
+
@user_ip = user_ip
|
13
|
+
@user_agent = user_agent
|
14
|
+
@device_id = device_id
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
get_response { |failure| return failure }
|
19
|
+
interpret_response
|
20
|
+
|
21
|
+
rescue JSON::ParserError
|
22
|
+
error { 'SSO Server response is not valid JSON.' }
|
23
|
+
error { response.inspect }
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def get_response
|
29
|
+
yield Operations.failure(:server_unreachable, object: response) unless response.code == 200
|
30
|
+
yield Operations.failure(:server_response_not_parseable, object: response) unless parsed_response
|
31
|
+
yield Operations.failure(:server_response_missing_success_flag, object: response) unless response_has_success_flag?
|
32
|
+
yield Operations.failure(:server_response_unsuccessful, object: response) unless parsed_response['success'].to_s == 'true'
|
33
|
+
Operations.success :server_response_looks_legit
|
34
|
+
end
|
35
|
+
|
36
|
+
def interpret_response
|
37
|
+
debug { "Interpreting response code #{response_code.inspect}" }
|
38
|
+
|
39
|
+
case response_code
|
40
|
+
when :passpord_unmodified then Operations.success(:passport_valid)
|
41
|
+
when :passport_changed then Operations.success(:passport_valid_and_modified, object: received_passport)
|
42
|
+
when :passport_invalid then Operations.failure(:passport_invalid)
|
43
|
+
else Operations.failure(:unexpected_server_response_status, object: response)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def response_code
|
48
|
+
return :unknown_response_code if parsed_response['code'].to_s == ''
|
49
|
+
parsed_response['code'].to_s.to_sym
|
50
|
+
end
|
51
|
+
|
52
|
+
def received_passport
|
53
|
+
::SSO::Client::Passport.new received_passport_attributes
|
54
|
+
|
55
|
+
rescue ArgumentError => exception
|
56
|
+
error { "Could not instantiate Passport from serialized response #{received_passport_attributes.inspect}" }
|
57
|
+
raise
|
58
|
+
end
|
59
|
+
|
60
|
+
def received_passport_attributes
|
61
|
+
attributes = parsed_response['passport']
|
62
|
+
attributes.keys.each do |key|
|
63
|
+
attributes[(key.to_sym rescue key) || key] = attributes.delete(key)
|
64
|
+
end
|
65
|
+
attributes
|
66
|
+
end
|
67
|
+
|
68
|
+
def params
|
69
|
+
{ ip: user_ip, agent: user_agent, device_id: device_id, state: passport_state }
|
70
|
+
end
|
71
|
+
|
72
|
+
def token
|
73
|
+
Signature::Token.new passport_id, passport_secret
|
74
|
+
end
|
75
|
+
|
76
|
+
def signature_request
|
77
|
+
Signature::Request.new('GET', path, params)
|
78
|
+
end
|
79
|
+
|
80
|
+
def auth_hash
|
81
|
+
signature_request.sign token
|
82
|
+
end
|
83
|
+
|
84
|
+
def timeout_in_milliseconds
|
85
|
+
::SSO.config.passport_verification_timeout_ms.to_i
|
86
|
+
end
|
87
|
+
|
88
|
+
def timeout_in_seconds
|
89
|
+
(timeout_in_milliseconds / 1000).round 2
|
90
|
+
end
|
91
|
+
|
92
|
+
# TODO Needs to be configurable
|
93
|
+
def path
|
94
|
+
OmniAuth::Strategies::SSO.passports_path
|
95
|
+
end
|
96
|
+
|
97
|
+
def base_endpoint
|
98
|
+
OmniAuth::Strategies::SSO.endpoint
|
99
|
+
end
|
100
|
+
|
101
|
+
def endpoint
|
102
|
+
URI.join(base_endpoint, path).to_s
|
103
|
+
end
|
104
|
+
|
105
|
+
def query_params
|
106
|
+
params.merge auth_hash
|
107
|
+
end
|
108
|
+
|
109
|
+
def response
|
110
|
+
@response ||= response!
|
111
|
+
end
|
112
|
+
|
113
|
+
def response!
|
114
|
+
debug { "Fetching Passport from #{endpoint.inspect}" }
|
115
|
+
benchmark 'Passport authorization request' do
|
116
|
+
::HTTParty.get endpoint, timeout: timeout_in_seconds, query: query_params, headers: { 'Accept' => 'application/json' }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def parsed_response
|
121
|
+
response.parsed_response
|
122
|
+
end
|
123
|
+
|
124
|
+
def response_has_success_flag?
|
125
|
+
parsed_response && parsed_response.respond_to?(:key?) && parsed_response.key?('success')
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -14,6 +14,8 @@ module SSO
|
|
14
14
|
include ::SSO::Benchmarking
|
15
15
|
|
16
16
|
attr_reader :passport, :warden, :options
|
17
|
+
delegate :request, to: :warden
|
18
|
+
delegate :params, to: :request
|
17
19
|
|
18
20
|
def self.activate(warden_options)
|
19
21
|
::Warden::Manager.after_fetch(warden_options) do |passport, warden, options|
|
@@ -39,51 +41,47 @@ module SSO
|
|
39
41
|
|
40
42
|
private
|
41
43
|
|
42
|
-
def verify
|
43
|
-
debug { "Validating Passport #{passport.id.inspect} of logged in #{passport.user.class} in scope #{warden_scope.inspect}" }
|
44
|
-
return server_unreachable! unless response.code == 200
|
45
|
-
return server_response_not_parseable! unless parsed_response
|
46
|
-
return server_response_missing_success_flag! unless response_has_success_flag?
|
47
|
-
return server_response_unsuccessful! unless parsed_response['success'].to_s == 'true'
|
48
|
-
verify!
|
49
44
|
|
50
|
-
|
51
|
-
|
52
|
-
error { response.inspect }
|
45
|
+
def verifier
|
46
|
+
::SSO::Client::PassportVerifier.new passport_id: passport.id, passport_state: passport.state, passport_secret: passport.secret, user_ip: ip, user_agent: agent, device_id: device_id
|
53
47
|
end
|
54
48
|
|
55
|
-
def
|
56
|
-
|
57
|
-
|
58
|
-
case code
|
59
|
-
when :passport_changed then valid_passport_changed!
|
60
|
-
when :passpord_unmodified then valid_passport_remains!
|
61
|
-
when :passport_invalid then invalid_passport!
|
62
|
-
else unexpected_server_response_status!
|
63
|
-
end
|
49
|
+
def verification
|
50
|
+
@verification ||= verifier.call
|
64
51
|
end
|
65
52
|
|
66
|
-
def
|
67
|
-
|
53
|
+
def verification_code
|
54
|
+
verification.code
|
68
55
|
end
|
69
56
|
|
70
|
-
def
|
71
|
-
|
57
|
+
def verify
|
58
|
+
debug { "Validating Passport #{passport.id.inspect} of logged in #{passport.user.class} in scope #{warden_scope.inspect}" }
|
59
|
+
|
60
|
+
case verification_code
|
61
|
+
when :server_unreachable then server_unreachable!
|
62
|
+
when :server_response_not_parseable then server_response_not_parseable!
|
63
|
+
when :server_response_missing_success_flag! then server_response_missing_success_flag!
|
64
|
+
when :server_response_unsuccessful! then server_response_unsuccessful!
|
65
|
+
when :passport_valid then passport_valid!
|
66
|
+
when :passport_valid_and_modified then passport_valid_and_modified!
|
67
|
+
when :passport_invalid then passport_invalid!
|
68
|
+
else unexpected_server_response_status!
|
69
|
+
end
|
72
70
|
end
|
73
71
|
|
74
|
-
def
|
72
|
+
def passport_valid_and_modified!
|
75
73
|
debug { 'Valid passport, but state changed' }
|
76
74
|
passport.verified!
|
77
75
|
# meter status: :valid, passport_id: user.passport_id
|
78
76
|
end
|
79
77
|
|
80
|
-
def
|
78
|
+
def passport_valid!
|
81
79
|
debug { 'Valid passport, no changes' }
|
82
|
-
|
80
|
+
passport.verified!
|
83
81
|
# meter status: :valid, passport_id: user.passport_id
|
84
82
|
end
|
85
83
|
|
86
|
-
def
|
84
|
+
def passport_invalid!
|
87
85
|
info { 'Your Passport is not valid any more.' }
|
88
86
|
warden.logout warden_scope
|
89
87
|
# meter status: :invalid, passport_id: user.passport_id
|
@@ -98,79 +96,32 @@ module SSO
|
|
98
96
|
end
|
99
97
|
|
100
98
|
def unexpected_server_response_status!
|
101
|
-
error {
|
99
|
+
error { "SSO Server response did not include a known passport status code. #{verification_code.inspect}" }
|
102
100
|
end
|
103
101
|
|
104
102
|
def server_response_not_parseable!
|
105
103
|
error { 'SSO Server response could not be parsed at all.' }
|
106
104
|
end
|
107
105
|
|
108
|
-
def endpoint
|
109
|
-
URI.join(base_endpoint, path).to_s
|
110
|
-
end
|
111
|
-
|
112
|
-
def query_params
|
113
|
-
params.merge auth_hash
|
114
|
-
end
|
115
|
-
|
116
|
-
# Needs to be configurable
|
117
|
-
def path
|
118
|
-
OmniAuth::Strategies::SSO.passports_path
|
119
|
-
end
|
120
|
-
|
121
|
-
def base_endpoint
|
122
|
-
OmniAuth::Strategies::SSO.endpoint
|
123
|
-
end
|
124
|
-
|
125
106
|
def meter(*_)
|
126
107
|
# This will be a hook for e.g. statistics, benchmarking, etc, measure everything
|
127
108
|
end
|
128
109
|
|
129
110
|
# TODO Use ActionDispatch remote IP or you might get the Load Balancer's IP instead :(
|
130
111
|
def ip
|
131
|
-
|
112
|
+
request.ip
|
132
113
|
end
|
133
114
|
|
134
115
|
def agent
|
135
|
-
|
136
|
-
end
|
137
|
-
|
138
|
-
def warden_scope
|
139
|
-
options[:scope]
|
140
|
-
end
|
141
|
-
|
142
|
-
def params
|
143
|
-
{ ip: ip, agent: agent, state: passport.state }
|
144
|
-
end
|
145
|
-
|
146
|
-
def token
|
147
|
-
Signature::Token.new passport.id, passport.secret
|
148
|
-
end
|
149
|
-
|
150
|
-
def signature_request
|
151
|
-
Signature::Request.new('GET', path, params)
|
116
|
+
request.user_agent
|
152
117
|
end
|
153
118
|
|
154
|
-
def
|
155
|
-
|
119
|
+
def device_id
|
120
|
+
params['device_id']
|
156
121
|
end
|
157
122
|
|
158
|
-
def
|
159
|
-
|
160
|
-
end
|
161
|
-
|
162
|
-
def timeout_in_seconds
|
163
|
-
0.1.seconds
|
164
|
-
end
|
165
|
-
|
166
|
-
def response
|
167
|
-
@response ||= response!
|
168
|
-
end
|
169
|
-
|
170
|
-
def response!
|
171
|
-
benchmark 'Passport authorization request' do
|
172
|
-
::HTTParty.get endpoint, timeout: timeout_in_seconds, query: query_params, headers: { 'Accept' => 'application/json' }
|
173
|
-
end
|
123
|
+
def warden_scope
|
124
|
+
options[:scope]
|
174
125
|
end
|
175
126
|
|
176
127
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module SSO
|
2
|
+
module Client
|
3
|
+
module Warden
|
4
|
+
module Strategies
|
5
|
+
# When the iPhone presents a Passport to Alpha, this is how Alpha verifies it with Bouncer.
|
6
|
+
class Passport < ::Warden::Strategies::Base
|
7
|
+
include ::SSO::Logging
|
8
|
+
include ::SSO::Benchmarking
|
9
|
+
|
10
|
+
def valid?
|
11
|
+
params['auth_version'].to_s != '' && params['state'] != ''
|
12
|
+
end
|
13
|
+
|
14
|
+
def authenticate!
|
15
|
+
debug { 'Authenticating from Passport...' }
|
16
|
+
|
17
|
+
authentication = passport_authentication
|
18
|
+
|
19
|
+
if authentication.success?
|
20
|
+
debug { 'Authentication from Passport successful.' }
|
21
|
+
debug { "Persisting trusted Passport #{authentication.object.inspect}" }
|
22
|
+
success! authentication.object
|
23
|
+
else
|
24
|
+
debug { 'Authentication from Passport failed.' }
|
25
|
+
debug { "Responding with #{authentication.object.inspect}" }
|
26
|
+
custom! authentication.object
|
27
|
+
end
|
28
|
+
|
29
|
+
rescue => exception
|
30
|
+
::SSO.config.exception_handler.call exception
|
31
|
+
end
|
32
|
+
|
33
|
+
def passport_authentication
|
34
|
+
benchmark 'Passport proxy verification' do
|
35
|
+
::SSO::Client::Authentications::Passport.new(request).authenticate
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/sso/client.rb
CHANGED
@@ -4,5 +4,8 @@ require 'warden'
|
|
4
4
|
|
5
5
|
require 'sso'
|
6
6
|
require 'sso/client/passport'
|
7
|
+
require 'sso/client/passport_verifier'
|
7
8
|
require 'sso/client/omniauth/strategies/sso'
|
8
9
|
require 'sso/client/warden/hooks/after_fetch'
|
10
|
+
require 'sso/client/authentications/passport'
|
11
|
+
require 'sso/client/warden/strategies/passport'
|