rodauth-oauth 0.0.5 → 0.4.0
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/CHANGELOG.md +180 -5
- data/README.md +45 -21
- data/lib/generators/roda/oauth/templates/db/migrate/create_rodauth_oauth.rb +8 -5
- data/lib/rodauth/features/oauth.rb +514 -416
- data/lib/rodauth/features/oauth_http_mac.rb +6 -10
- data/lib/rodauth/features/oauth_jwt.rb +178 -74
- data/lib/rodauth/features/oauth_saml.rb +104 -0
- data/lib/rodauth/features/oidc.rb +399 -0
- data/lib/rodauth/oauth/database_extensions.rb +73 -0
- data/lib/rodauth/oauth/ttl_store.rb +1 -1
- data/lib/rodauth/oauth/version.rb +1 -1
- data/templates/authorize.str +34 -0
- data/templates/client_secret_field.str +4 -0
- data/templates/description_field.str +4 -0
- data/templates/homepage_url_field.str +4 -0
- data/templates/name_field.str +4 -0
- data/templates/new_oauth_application.str +10 -0
- data/templates/oauth_application.str +11 -0
- data/templates/oauth_applications.str +14 -0
- data/templates/oauth_tokens.str +49 -0
- data/templates/redirect_uri_field.str +4 -0
- data/templates/scope_field.str +10 -0
- metadata +24 -10
@@ -8,20 +8,16 @@ module Rodauth
|
|
8
8
|
def delete_suffix(suffix)
|
9
9
|
suffix = suffix.to_s
|
10
10
|
len = suffix.length
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
dup
|
15
|
-
end
|
11
|
+
return dup unless len.positive? && index(suffix, -len)
|
12
|
+
|
13
|
+
self[0...-len]
|
16
14
|
end
|
17
15
|
|
18
16
|
def delete_prefix(prefix)
|
19
17
|
prefix = prefix.to_s
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
dup
|
24
|
-
end
|
18
|
+
return dup unless rindex(prefix, 0)
|
19
|
+
|
20
|
+
self[prefix.length..-1]
|
25
21
|
end
|
26
22
|
end
|
27
23
|
end
|
@@ -6,7 +6,14 @@ module Rodauth
|
|
6
6
|
Feature.define(:oauth_jwt) do
|
7
7
|
depends :oauth
|
8
8
|
|
9
|
-
|
9
|
+
JWKS = OAuth::TtlStore.new
|
10
|
+
|
11
|
+
auth_value_method :oauth_jwt_subject_type, "public" # public, pairwise
|
12
|
+
auth_value_method :oauth_jwt_subject_secret, nil # salt for pairwise generation
|
13
|
+
|
14
|
+
auth_value_method :oauth_jwt_token_issuer, nil
|
15
|
+
|
16
|
+
auth_value_method :oauth_application_jws_jwk_column, nil
|
10
17
|
|
11
18
|
auth_value_method :oauth_jwt_key, nil
|
12
19
|
auth_value_method :oauth_jwt_public_key, nil
|
@@ -17,16 +24,30 @@ module Rodauth
|
|
17
24
|
auth_value_method :oauth_jwt_jwe_algorithm, nil
|
18
25
|
auth_value_method :oauth_jwt_jwe_encryption_method, nil
|
19
26
|
|
27
|
+
# values used for rotating keys
|
28
|
+
auth_value_method :oauth_jwt_legacy_public_key, nil
|
29
|
+
auth_value_method :oauth_jwt_legacy_algorithm, nil
|
30
|
+
|
20
31
|
auth_value_method :oauth_jwt_jwe_copyright, nil
|
21
32
|
auth_value_method :oauth_jwt_audience, nil
|
22
33
|
|
34
|
+
auth_value_method :request_uri_not_supported_message, "request uri is unsupported"
|
35
|
+
auth_value_method :invalid_request_object_message, "request object is invalid"
|
36
|
+
|
23
37
|
auth_value_methods(
|
24
38
|
:jwt_encode,
|
25
39
|
:jwt_decode,
|
26
|
-
:jwks_set
|
40
|
+
:jwks_set,
|
41
|
+
:last_account_login_at
|
27
42
|
)
|
28
43
|
|
29
|
-
|
44
|
+
route(:jwks) do |r|
|
45
|
+
next unless is_authorization_server?
|
46
|
+
|
47
|
+
r.get do
|
48
|
+
json_response_success({ keys: jwks_set }, true)
|
49
|
+
end
|
50
|
+
end
|
30
51
|
|
31
52
|
def require_oauth_authorization(*scopes)
|
32
53
|
authorization_required unless authorization_token
|
@@ -40,6 +61,12 @@ module Rodauth
|
|
40
61
|
|
41
62
|
private
|
42
63
|
|
64
|
+
unless method_defined?(:last_account_login_at)
|
65
|
+
def last_account_login_at
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
43
70
|
def authorization_token
|
44
71
|
return @authorization_token if defined?(@authorization_token)
|
45
72
|
|
@@ -52,21 +79,67 @@ module Rodauth
|
|
52
79
|
|
53
80
|
return unless jwt_token
|
54
81
|
|
55
|
-
return if jwt_token["iss"] != oauth_jwt_token_issuer ||
|
56
|
-
jwt_token["aud"] != oauth_jwt_audience ||
|
82
|
+
return if jwt_token["iss"] != (oauth_jwt_token_issuer || authorization_server_url) ||
|
83
|
+
(oauth_jwt_audience && jwt_token["aud"] != oauth_jwt_audience) ||
|
57
84
|
!jwt_token["sub"]
|
58
85
|
|
59
86
|
jwt_token
|
60
87
|
end
|
61
88
|
end
|
62
89
|
|
90
|
+
# /authorize
|
91
|
+
|
92
|
+
def validate_oauth_grant_params
|
93
|
+
# TODO: add support for requst_uri
|
94
|
+
redirect_response_error("request_uri_not_supported") if param_or_nil("request_uri")
|
95
|
+
|
96
|
+
request_object = param_or_nil("request")
|
97
|
+
|
98
|
+
return super unless request_object && oauth_application
|
99
|
+
|
100
|
+
jws_jwk = if oauth_application[oauth_application_jws_jwk_column]
|
101
|
+
jwk = oauth_application[oauth_application_jws_jwk_column]
|
102
|
+
|
103
|
+
jwk = JSON.parse(jwk, symbolize_names: true) if jwk && jwk.is_a?(String)
|
104
|
+
else
|
105
|
+
redirect_response_error("invalid_request_object")
|
106
|
+
end
|
107
|
+
|
108
|
+
claims = jwt_decode(request_object, jws_key: jwk_import(jws_jwk), jws_algorithm: jwk[:alg])
|
109
|
+
|
110
|
+
redirect_response_error("invalid_request_object") unless claims
|
111
|
+
|
112
|
+
# If signed, the Authorization Request
|
113
|
+
# Object SHOULD contain the Claims "iss" (issuer) and "aud" (audience)
|
114
|
+
# as members, with their semantics being the same as defined in the JWT
|
115
|
+
# [RFC7519] specification. The value of "aud" should be the value of
|
116
|
+
# the Authorization Server (AS) "issuer" as defined in RFC8414
|
117
|
+
# [RFC8414].
|
118
|
+
claims.delete("iss")
|
119
|
+
audience = claims.delete("aud")
|
120
|
+
|
121
|
+
redirect_response_error("invalid_request_object") if audience && audience != authorization_server_url
|
122
|
+
|
123
|
+
claims.each do |k, v|
|
124
|
+
request.params[k.to_s] = v
|
125
|
+
end
|
126
|
+
|
127
|
+
super
|
128
|
+
end
|
129
|
+
|
63
130
|
# /token
|
64
131
|
|
65
|
-
def
|
132
|
+
def require_oauth_application
|
66
133
|
# requset authentication optional for assertions
|
67
|
-
return
|
134
|
+
return super unless param("grant_type") == "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
68
135
|
|
69
|
-
|
136
|
+
claims = jwt_decode(param("assertion"))
|
137
|
+
|
138
|
+
redirect_response_error("invalid_grant") unless claims
|
139
|
+
|
140
|
+
@oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => claims["client_id"]).first
|
141
|
+
|
142
|
+
authorization_required unless @oauth_application
|
70
143
|
end
|
71
144
|
|
72
145
|
def validate_oauth_token_params
|
@@ -88,10 +161,6 @@ module Rodauth
|
|
88
161
|
def create_oauth_token_from_assertion
|
89
162
|
claims = jwt_decode(param("assertion"))
|
90
163
|
|
91
|
-
redirect_response_error("invalid_grant") unless claims
|
92
|
-
|
93
|
-
@oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => claims["client_id"]).first
|
94
|
-
|
95
164
|
account = account_ds(claims["sub"]).first
|
96
165
|
|
97
166
|
redirect_response_error("invalid_client") unless oauth_application && account
|
@@ -107,26 +176,40 @@ module Rodauth
|
|
107
176
|
|
108
177
|
def generate_oauth_token(params = {}, should_generate_refresh_token = true)
|
109
178
|
create_params = {
|
110
|
-
oauth_grants_expires_in_column =>
|
179
|
+
oauth_grants_expires_in_column => Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_token_expires_in)
|
111
180
|
}.merge(params)
|
112
181
|
|
113
|
-
|
114
|
-
|
182
|
+
oauth_token = rescue_from_uniqueness_error do
|
183
|
+
if should_generate_refresh_token
|
184
|
+
refresh_token = oauth_unique_id_generator
|
115
185
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
186
|
+
if oauth_tokens_refresh_token_hash_column
|
187
|
+
create_params[oauth_tokens_refresh_token_hash_column] = generate_token_hash(refresh_token)
|
188
|
+
else
|
189
|
+
create_params[oauth_tokens_refresh_token_column] = refresh_token
|
190
|
+
end
|
120
191
|
end
|
192
|
+
|
193
|
+
_generate_oauth_token(create_params)
|
121
194
|
end
|
122
195
|
|
123
|
-
|
196
|
+
claims = jwt_claims(oauth_token)
|
197
|
+
|
198
|
+
# one of the points of using jwt is avoiding database lookups, so we put here all relevant
|
199
|
+
# token data.
|
200
|
+
claims[:scope] = oauth_token[oauth_tokens_scopes_column]
|
201
|
+
|
202
|
+
token = jwt_encode(claims)
|
203
|
+
|
204
|
+
oauth_token[oauth_tokens_token_column] = token
|
205
|
+
oauth_token
|
206
|
+
end
|
124
207
|
|
125
|
-
|
208
|
+
def jwt_claims(oauth_token)
|
209
|
+
issued_at = Time.now.to_i
|
126
210
|
|
127
|
-
|
128
|
-
|
129
|
-
iss: oauth_jwt_token_issuer, # issuer
|
211
|
+
claims = {
|
212
|
+
iss: (oauth_jwt_token_issuer || authorization_server_url), # issuer
|
130
213
|
iat: issued_at, # issued at
|
131
214
|
#
|
132
215
|
# sub REQUIRED - as defined in section 4.1.2 of [RFC7519]. In case of
|
@@ -137,23 +220,32 @@ module Rodauth
|
|
137
220
|
# owner is involved, such as the client credentials grant, the value
|
138
221
|
# of "sub" SHOULD correspond to an identifier the authorization
|
139
222
|
# server uses to indicate the client application.
|
223
|
+
sub: jwt_subject(oauth_token),
|
140
224
|
client_id: oauth_application[oauth_applications_client_id_column],
|
141
225
|
|
142
226
|
exp: issued_at + oauth_token_expires_in,
|
143
|
-
aud: oauth_jwt_audience
|
144
|
-
|
145
|
-
# one of the points of using jwt is avoiding database lookups, so we put here all relevant
|
146
|
-
# token data.
|
147
|
-
scope: oauth_token[oauth_tokens_scopes_column]
|
227
|
+
aud: (oauth_jwt_audience || oauth_application[oauth_applications_client_id_column])
|
148
228
|
}
|
149
229
|
|
150
|
-
|
230
|
+
claims[:auth_time] = last_account_login_at.to_i if last_account_login_at
|
151
231
|
|
152
|
-
|
153
|
-
|
232
|
+
claims
|
233
|
+
end
|
234
|
+
|
235
|
+
def jwt_subject(oauth_token)
|
236
|
+
case oauth_jwt_subject_type
|
237
|
+
when "public"
|
238
|
+
oauth_token[oauth_tokens_account_id_column]
|
239
|
+
when "pairwise"
|
240
|
+
id = oauth_token[oauth_tokens_account_id_column]
|
241
|
+
application_id = oauth_token[oauth_tokens_oauth_application_id_column]
|
242
|
+
Digest::SHA256.hexdigest("#{id}#{application_id}#{oauth_jwt_subject_secret}")
|
243
|
+
else
|
244
|
+
raise StandardError, "unexpected subject (#{oauth_jwt_subject_type})"
|
245
|
+
end
|
154
246
|
end
|
155
247
|
|
156
|
-
def oauth_token_by_token(token
|
248
|
+
def oauth_token_by_token(token)
|
157
249
|
jwt_decode(token)
|
158
250
|
end
|
159
251
|
|
@@ -181,17 +273,11 @@ module Rodauth
|
|
181
273
|
def oauth_server_metadata_body(path)
|
182
274
|
metadata = super
|
183
275
|
metadata.merge! \
|
184
|
-
jwks_uri:
|
276
|
+
jwks_uri: jwks_url,
|
185
277
|
token_endpoint_auth_signing_alg_values_supported: [oauth_jwt_algorithm]
|
186
278
|
metadata
|
187
279
|
end
|
188
280
|
|
189
|
-
def token_from_application?(oauth_token, oauth_application)
|
190
|
-
return super unless oauth_token["sub"] # naive check on whether it's a jwt token
|
191
|
-
|
192
|
-
oauth_token["client_id"] == oauth_application[oauth_applications_client_id_column]
|
193
|
-
end
|
194
|
-
|
195
281
|
def _jwt_key
|
196
282
|
@_jwt_key ||= oauth_jwt_key || (oauth_application[oauth_applications_client_secret_column] if oauth_application)
|
197
283
|
end
|
@@ -221,10 +307,10 @@ module Rodauth
|
|
221
307
|
|
222
308
|
# time-to-live
|
223
309
|
ttl = if response.key?("cache-control")
|
224
|
-
cache_control = response["
|
225
|
-
cache_control[/max-age=(\d+)/, 1]
|
310
|
+
cache_control = response["cache-control"]
|
311
|
+
cache_control[/max-age=(\d+)/, 1].to_i
|
226
312
|
elsif response.key?("expires")
|
227
|
-
Time.
|
313
|
+
Time.parse(response["expires"]).to_i - Time.now.to_i
|
228
314
|
end
|
229
315
|
|
230
316
|
[JSON.parse(response.body, symbolize_names: true), ttl]
|
@@ -232,7 +318,10 @@ module Rodauth
|
|
232
318
|
end
|
233
319
|
|
234
320
|
if defined?(JSON::JWT)
|
235
|
-
|
321
|
+
|
322
|
+
def jwk_import(data)
|
323
|
+
JSON::JWK.new(data)
|
324
|
+
end
|
236
325
|
|
237
326
|
# json-jwt
|
238
327
|
def jwt_encode(payload)
|
@@ -251,34 +340,38 @@ module Rodauth
|
|
251
340
|
jwt.to_s
|
252
341
|
end
|
253
342
|
|
254
|
-
def jwt_decode(token)
|
255
|
-
return @jwt_token if defined?(@jwt_token)
|
256
|
-
|
343
|
+
def jwt_decode(token, jws_key: oauth_jwt_public_key || _jwt_key, **)
|
257
344
|
token = JSON::JWT.decode(token, oauth_jwt_jwe_key).plain_text if oauth_jwt_jwe_key
|
258
345
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
346
|
+
if is_authorization_server?
|
347
|
+
if oauth_jwt_legacy_public_key
|
348
|
+
JSON::JWT.decode(token, JSON::JWK::Set.new({ keys: jwks_set }))
|
349
|
+
elsif jws_key
|
350
|
+
JSON::JWT.decode(token, jws_key)
|
351
|
+
end
|
352
|
+
elsif (jwks = auth_server_jwks_set)
|
353
|
+
JSON::JWT.decode(token, JSON::JWK::Set.new(jwks))
|
354
|
+
end
|
266
355
|
rescue JSON::JWT::Exception
|
267
356
|
nil
|
268
357
|
end
|
269
358
|
|
270
359
|
def jwks_set
|
271
|
-
[
|
360
|
+
@jwks_set ||= [
|
272
361
|
(JSON::JWK.new(oauth_jwt_public_key).merge(use: "sig", alg: oauth_jwt_algorithm) if oauth_jwt_public_key),
|
362
|
+
(JSON::JWK.new(oauth_jwt_legacy_public_key).merge(use: "sig", alg: oauth_jwt_legacy_algorithm) if oauth_jwt_legacy_public_key),
|
273
363
|
(JSON::JWK.new(oauth_jwt_jwe_public_key).merge(use: "enc", alg: oauth_jwt_jwe_algorithm) if oauth_jwt_jwe_public_key)
|
274
364
|
].compact
|
275
365
|
end
|
276
366
|
|
277
|
-
# :nocov:
|
278
367
|
elsif defined?(JWT)
|
279
368
|
|
280
369
|
# ruby-jwt
|
281
370
|
|
371
|
+
def jwk_import(data)
|
372
|
+
JWT::JWK.import(data).keypair
|
373
|
+
end
|
374
|
+
|
282
375
|
def jwt_encode(payload)
|
283
376
|
headers = {}
|
284
377
|
|
@@ -312,38 +405,47 @@ module Rodauth
|
|
312
405
|
token
|
313
406
|
end
|
314
407
|
|
315
|
-
def jwt_decode(token)
|
316
|
-
return @jwt_token if defined?(@jwt_token)
|
317
|
-
|
408
|
+
def jwt_decode(token, jws_key: oauth_jwt_public_key || _jwt_key, jws_algorithm: oauth_jwt_algorithm)
|
318
409
|
# decrypt jwe
|
319
410
|
token = JWE.decrypt(token, oauth_jwt_jwe_key) if oauth_jwt_jwe_key
|
320
|
-
|
321
411
|
# decode jwt
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
412
|
+
if is_authorization_server?
|
413
|
+
if oauth_jwt_legacy_public_key
|
414
|
+
algorithms = jwks_set.select { |k| k[:use] == "sig" }.map { |k| k[:alg] }
|
415
|
+
JWT.decode(token, nil, true, jwks: { keys: jwks_set }, algorithms: algorithms).first
|
416
|
+
elsif jws_key
|
417
|
+
JWT.decode(token, jws_key, true, algorithms: [jws_algorithm]).first
|
418
|
+
end
|
419
|
+
elsif (jwks = auth_server_jwks_set)
|
420
|
+
algorithms = jwks[:keys].select { |k| k[:use] == "sig" }.map { |k| k[:alg] }
|
421
|
+
JWT.decode(token, nil, true, jwks: jwks, algorithms: algorithms).first
|
422
|
+
end
|
330
423
|
rescue JWT::DecodeError, JWT::JWKError
|
331
424
|
nil
|
332
425
|
end
|
333
426
|
|
334
427
|
def jwks_set
|
335
|
-
[
|
428
|
+
@jwks_set ||= [
|
336
429
|
(JWT::JWK.new(oauth_jwt_public_key).export.merge(use: "sig", alg: oauth_jwt_algorithm) if oauth_jwt_public_key),
|
430
|
+
(
|
431
|
+
if oauth_jwt_legacy_public_key
|
432
|
+
JWT::JWK.new(oauth_jwt_legacy_public_key).export.merge(use: "sig", alg: oauth_jwt_legacy_algorithm)
|
433
|
+
end
|
434
|
+
),
|
337
435
|
(JWT::JWK.new(oauth_jwt_jwe_public_key).export.merge(use: "enc", alg: oauth_jwt_jwe_algorithm) if oauth_jwt_jwe_public_key)
|
338
436
|
].compact
|
339
437
|
end
|
340
438
|
else
|
341
439
|
# :nocov:
|
440
|
+
def jwk_import(_data)
|
441
|
+
raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
|
442
|
+
end
|
443
|
+
|
342
444
|
def jwt_encode(_token)
|
343
445
|
raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
|
344
446
|
end
|
345
447
|
|
346
|
-
def jwt_decode(_token)
|
448
|
+
def jwt_decode(_token, **)
|
347
449
|
raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
|
348
450
|
end
|
349
451
|
|
@@ -353,10 +455,12 @@ module Rodauth
|
|
353
455
|
# :nocov:
|
354
456
|
end
|
355
457
|
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
458
|
+
def validate_oauth_revoke_params
|
459
|
+
token_hint = param_or_nil("token_type_hint")
|
460
|
+
|
461
|
+
throw(:rodauth_error) if !token_hint || token_hint == "access_token"
|
462
|
+
|
463
|
+
super
|
360
464
|
end
|
361
465
|
end
|
362
466
|
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require "onelogin/ruby-saml"
|
4
|
+
|
5
|
+
module Rodauth
|
6
|
+
Feature.define(:oauth_saml) do
|
7
|
+
depends :oauth
|
8
|
+
|
9
|
+
auth_value_method :oauth_saml_cert_fingerprint, "9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D"
|
10
|
+
auth_value_method :oauth_saml_cert_fingerprint_algorithm, nil
|
11
|
+
auth_value_method :oauth_saml_name_identifier_format, "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
12
|
+
|
13
|
+
auth_value_method :oauth_saml_security_authn_requests_signed, false
|
14
|
+
auth_value_method :oauth_saml_security_metadata_signed, false
|
15
|
+
auth_value_method :oauth_saml_security_digest_method, XMLSecurity::Document::SHA1
|
16
|
+
auth_value_method :oauth_saml_security_signature_method, XMLSecurity::Document::RSA_SHA1
|
17
|
+
|
18
|
+
SAML_GRANT_TYPE = "http://oauth.net/grant_type/assertion/saml/2.0/bearer"
|
19
|
+
|
20
|
+
# /token
|
21
|
+
|
22
|
+
def require_oauth_application
|
23
|
+
# requset authentication optional for assertions
|
24
|
+
return super unless param("grant_type") == SAML_GRANT_TYPE && !param_or_nil("client_id")
|
25
|
+
|
26
|
+
# TODO: invalid grant
|
27
|
+
authorization_required unless saml_assertion
|
28
|
+
|
29
|
+
redirect_uri = saml_assertion.destination
|
30
|
+
|
31
|
+
@oauth_application = db[oauth_applications_table].where(
|
32
|
+
oauth_applications_homepage_url_column => saml_assertion.audiences,
|
33
|
+
oauth_applications_redirect_uri_column => redirect_uri
|
34
|
+
).first
|
35
|
+
|
36
|
+
# The Assertion's <Issuer> element MUST contain a unique identifier
|
37
|
+
# for the entity that issued the Assertion.
|
38
|
+
authorization_required unless saml_assertion.issuers.all? do |issuer|
|
39
|
+
issuer.start_with?(@oauth_application[oauth_applications_homepage_url_column])
|
40
|
+
end
|
41
|
+
|
42
|
+
authorization_required unless @oauth_application
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def secret_matches?(oauth_application, secret)
|
48
|
+
return super unless param_or_nil("assertion")
|
49
|
+
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
def saml_assertion
|
54
|
+
return @saml_assertion if defined?(@saml_assertion)
|
55
|
+
|
56
|
+
@saml_assertion = begin
|
57
|
+
settings = OneLogin::RubySaml::Settings.new
|
58
|
+
settings.idp_cert_fingerprint = oauth_saml_cert_fingerprint
|
59
|
+
settings.idp_cert_fingerprint_algorithm = oauth_saml_cert_fingerprint_algorithm
|
60
|
+
settings.name_identifier_format = oauth_saml_name_identifier_format
|
61
|
+
settings.security[:authn_requests_signed] = oauth_saml_security_authn_requests_signed
|
62
|
+
settings.security[:metadata_signed] = oauth_saml_security_metadata_signed
|
63
|
+
settings.security[:digest_method] = oauth_saml_security_digest_method
|
64
|
+
settings.security[:signature_method] = oauth_saml_security_signature_method
|
65
|
+
|
66
|
+
response = OneLogin::RubySaml::Response.new(param("assertion"), settings: settings, skip_recipient_check: true)
|
67
|
+
|
68
|
+
return unless response.is_valid?
|
69
|
+
|
70
|
+
response
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def validate_oauth_token_params
|
75
|
+
return super unless param("grant_type") == SAML_GRANT_TYPE
|
76
|
+
|
77
|
+
redirect_response_error("invalid_client") unless param_or_nil("assertion")
|
78
|
+
|
79
|
+
redirect_response_error("invalid_scope") unless check_valid_scopes?
|
80
|
+
end
|
81
|
+
|
82
|
+
def create_oauth_token
|
83
|
+
if param("grant_type") == SAML_GRANT_TYPE
|
84
|
+
create_oauth_token_from_saml_assertion
|
85
|
+
else
|
86
|
+
super
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_oauth_token_from_saml_assertion
|
91
|
+
account = db[accounts_table].where(login_column => saml_assertion.nameid).first
|
92
|
+
|
93
|
+
redirect_response_error("invalid_client") unless oauth_application && account
|
94
|
+
|
95
|
+
create_params = {
|
96
|
+
oauth_tokens_account_id_column => account[account_id_column],
|
97
|
+
oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
|
98
|
+
oauth_tokens_scopes_column => (param_or_nil("scope") || oauth_application[oauth_applications_scopes_column])
|
99
|
+
}
|
100
|
+
|
101
|
+
generate_oauth_token(create_params, false)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|