sso 0.1.0.alpha5 → 0.1.0.alpha6
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/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'
|