rodauth-oauth 1.0.0.pre.beta1 → 1.0.0.pre.beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -5
- data/doc/release_notes/1_0_0_beta2.md +34 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +19 -7
- data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +54 -43
- data/lib/rodauth/features/oauth_application_management.rb +2 -2
- data/lib/rodauth/features/oauth_authorization_code_grant.rb +31 -6
- data/lib/rodauth/features/oauth_authorize_base.rb +16 -6
- data/lib/rodauth/features/oauth_base.rb +35 -16
- data/lib/rodauth/features/oauth_dynamic_client_registration.rb +7 -4
- data/lib/rodauth/features/oauth_implicit_grant.rb +6 -5
- data/lib/rodauth/features/oauth_jwt.rb +3 -3
- data/lib/rodauth/features/oauth_jwt_base.rb +29 -6
- data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +7 -4
- data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +64 -10
- data/lib/rodauth/features/oauth_resource_indicators.rb +0 -4
- data/lib/rodauth/features/oauth_resource_server.rb +3 -3
- data/lib/rodauth/features/oauth_saml_bearer_grant.rb +2 -0
- data/lib/rodauth/features/oidc.rb +231 -183
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +65 -25
- data/lib/rodauth/features/oidc_rp_initiated_logout.rb +115 -0
- data/lib/rodauth/oauth/http_extensions.rb +15 -2
- data/lib/rodauth/oauth/ttl_store.rb +2 -0
- data/lib/rodauth/oauth/version.rb +1 -1
- data/locales/en.yml +3 -1
- data/locales/pt.yml +3 -1
- data/templates/authorize.str +17 -10
- metadata +5 -2
@@ -17,6 +17,7 @@ module Rodauth
|
|
17
17
|
issuer
|
18
18
|
authorization_endpoint
|
19
19
|
end_session_endpoint
|
20
|
+
backchannel_logout_session_supported
|
20
21
|
token_endpoint
|
21
22
|
userinfo_endpoint
|
22
23
|
jwks_uri
|
@@ -74,10 +75,9 @@ module Rodauth
|
|
74
75
|
auth_value_method :"oauth_applications_#{column}_column", column
|
75
76
|
end
|
76
77
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
auth_value_method :oauth_grants_acr_column, :acr
|
78
|
+
%i[nonce acr claims_locales claims].each do |column|
|
79
|
+
auth_value_method :"oauth_grants_#{column}_column", column
|
80
|
+
end
|
81
81
|
|
82
82
|
auth_value_method :oauth_jwt_subject_type, "public" # fallback subject type: public, pairwise
|
83
83
|
auth_value_method :oauth_jwt_subject_secret, nil # salt for pairwise generation
|
@@ -88,12 +88,10 @@ module Rodauth
|
|
88
88
|
auth_value_method :oauth_prompt_login_cookie_options, {}.freeze
|
89
89
|
auth_value_method :oauth_prompt_login_interval, 5 * 60 * 60 # 5 minutes
|
90
90
|
|
91
|
-
# logout
|
92
|
-
auth_value_method :oauth_applications_post_logout_redirect_uri_column, :post_logout_redirect_uri
|
93
|
-
auth_value_method :use_rp_initiated_logout?, false
|
94
|
-
|
95
91
|
auth_value_methods(
|
92
|
+
:oauth_acr_values_supported,
|
96
93
|
:get_oidc_account_last_login_at,
|
94
|
+
:oidc_authorize_on_prompt_none?,
|
97
95
|
:get_oidc_param,
|
98
96
|
:get_additional_param,
|
99
97
|
:require_acr_value_phr,
|
@@ -122,11 +120,28 @@ module Rodauth
|
|
122
120
|
|
123
121
|
oidc_claims = { "sub" => claims["sub"] }
|
124
122
|
|
125
|
-
fill_with_account_claims(oidc_claims, account, oauth_scopes)
|
126
|
-
|
127
123
|
@oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => claims["client_id"]).first
|
128
124
|
|
129
|
-
|
125
|
+
throw_json_response_error(oauth_authorization_required_error_status, "invalid_token") unless @oauth_application
|
126
|
+
|
127
|
+
oauth_grant = valid_oauth_grant_ds(
|
128
|
+
oauth_grants_oauth_application_id_column => @oauth_application[oauth_applications_id_column],
|
129
|
+
oauth_grants_account_id_column => account[account_id_column]
|
130
|
+
).first
|
131
|
+
|
132
|
+
claims_locales = oauth_grant[oauth_grants_claims_locales_column] if oauth_grant
|
133
|
+
|
134
|
+
if (claims = oauth_grant[oauth_grants_claims_column])
|
135
|
+
claims = JSON.parse(claims)
|
136
|
+
if (userinfo_essential_claims = claims["userinfo"])
|
137
|
+
oauth_scopes |= userinfo_essential_claims.to_a
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# 5.4 - The Claims requested by the profile, email, address, and phone scope values are returned from the UserInfo Endpoint
|
142
|
+
fill_with_account_claims(oidc_claims, account, oauth_scopes, claims_locales)
|
143
|
+
|
144
|
+
if (algo = @oauth_application[oauth_applications_userinfo_signed_response_alg_column])
|
130
145
|
params = {
|
131
146
|
jwks: oauth_application_jwks(@oauth_application),
|
132
147
|
encryption_algorithm: @oauth_application[oauth_applications_userinfo_encrypted_response_alg_column],
|
@@ -134,7 +149,12 @@ module Rodauth
|
|
134
149
|
}.compact
|
135
150
|
|
136
151
|
jwt = jwt_encode(
|
137
|
-
oidc_claims
|
152
|
+
oidc_claims.merge(
|
153
|
+
# If signed, the UserInfo Response SHOULD contain the Claims iss (issuer) and aud (audience) as members. The iss value
|
154
|
+
# SHOULD be the OP's Issuer Identifier URL. The aud value SHOULD be or include the RP's Client ID value.
|
155
|
+
iss: oauth_jwt_issuer,
|
156
|
+
aud: @oauth_application[oauth_applications_client_id_column]
|
157
|
+
),
|
138
158
|
signing_algorithm: algo,
|
139
159
|
**params
|
140
160
|
)
|
@@ -148,81 +168,6 @@ module Rodauth
|
|
148
168
|
end
|
149
169
|
end
|
150
170
|
|
151
|
-
# /oidc-logout
|
152
|
-
auth_server_route(:oidc_logout) do |r|
|
153
|
-
next unless use_rp_initiated_logout?
|
154
|
-
|
155
|
-
require_authorizable_account
|
156
|
-
before_oidc_logout_route
|
157
|
-
|
158
|
-
# OpenID Providers MUST support the use of the HTTP GET and POST methods
|
159
|
-
r.on method: %i[get post] do
|
160
|
-
catch_error do
|
161
|
-
validate_oidc_logout_params
|
162
|
-
|
163
|
-
#
|
164
|
-
# why this is done:
|
165
|
-
#
|
166
|
-
# we need to decode the id token in order to get the application, because, if the
|
167
|
-
# signing key is application-specific, we don't know how to verify the signature
|
168
|
-
# beforehand. Hence, we have to do it twice: decode-and-do-not-verify, initialize
|
169
|
-
# the @oauth_application, and then decode-and-verify.
|
170
|
-
#
|
171
|
-
claims = jwt_decode(param("id_token_hint"), verify_claims: false)
|
172
|
-
oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => claims["client_id"]).first
|
173
|
-
oauth_grant = db[oauth_grants_table]
|
174
|
-
.where(
|
175
|
-
oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
|
176
|
-
oauth_grants_account_id_column => account_id
|
177
|
-
).first
|
178
|
-
|
179
|
-
# check whether ID token belongs to currently logged-in user
|
180
|
-
redirect_response_error("invalid_request") unless oauth_grant && claims["sub"] == jwt_subject(
|
181
|
-
oauth_grant, oauth_application
|
182
|
-
)
|
183
|
-
|
184
|
-
# When an id_token_hint parameter is present, the OP MUST validate that it was the issuer of the ID Token.
|
185
|
-
redirect_response_error("invalid_request") unless claims && claims["iss"] == oauth_jwt_issuer
|
186
|
-
|
187
|
-
# now let's logout from IdP
|
188
|
-
transaction do
|
189
|
-
before_logout
|
190
|
-
logout
|
191
|
-
after_logout
|
192
|
-
end
|
193
|
-
|
194
|
-
if (post_logout_redirect_uri = param_or_nil("post_logout_redirect_uri"))
|
195
|
-
catch(:default_logout_redirect) do
|
196
|
-
oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => claims["client_id"]).first
|
197
|
-
|
198
|
-
throw(:default_logout_redirect) unless oauth_application
|
199
|
-
|
200
|
-
post_logout_redirect_uris = oauth_application[oauth_applications_post_logout_redirect_uri_column].split(" ")
|
201
|
-
|
202
|
-
throw(:default_logout_redirect) unless post_logout_redirect_uris.include?(post_logout_redirect_uri)
|
203
|
-
|
204
|
-
if (state = param_or_nil("state"))
|
205
|
-
post_logout_redirect_uri = URI(post_logout_redirect_uri)
|
206
|
-
params = ["state=#{state}"]
|
207
|
-
params << post_logout_redirect_uri.query if post_logout_redirect_uri.query
|
208
|
-
post_logout_redirect_uri.query = params.join("&")
|
209
|
-
post_logout_redirect_uri = post_logout_redirect_uri.to_s
|
210
|
-
end
|
211
|
-
|
212
|
-
redirect(post_logout_redirect_uri)
|
213
|
-
end
|
214
|
-
|
215
|
-
end
|
216
|
-
|
217
|
-
# regular logout procedure
|
218
|
-
set_notice_flash(logout_notice_flash)
|
219
|
-
redirect(logout_redirect)
|
220
|
-
end
|
221
|
-
|
222
|
-
redirect_response_error("invalid_request")
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
171
|
def load_openid_configuration_route(alt_issuer = nil)
|
227
172
|
request.on(".well-known/openid-configuration") do
|
228
173
|
allow_cors(request)
|
@@ -260,7 +205,11 @@ module Rodauth
|
|
260
205
|
end
|
261
206
|
|
262
207
|
def oauth_response_types_supported
|
263
|
-
|
208
|
+
grant_types = oauth_grant_types_supported
|
209
|
+
oidc_response_types = %w[id_token none]
|
210
|
+
oidc_response_types |= ["code id_token"] if grant_types.include?("authorization_code")
|
211
|
+
oidc_response_types |= ["code token", "id_token token", "code id_token token"] if grant_types.include?("implicit")
|
212
|
+
super | oidc_response_types
|
264
213
|
end
|
265
214
|
|
266
215
|
def current_oauth_account
|
@@ -284,18 +233,62 @@ module Rodauth
|
|
284
233
|
end
|
285
234
|
end
|
286
235
|
|
236
|
+
def oauth_acr_values_supported
|
237
|
+
acr_values = []
|
238
|
+
acr_values << "phrh" if features.include?(:webauthn_login)
|
239
|
+
acr_values << "phr" if respond_to?(:require_two_factor_authenticated)
|
240
|
+
acr_values
|
241
|
+
end
|
242
|
+
|
243
|
+
def oidc_authorize_on_prompt_none?(_account)
|
244
|
+
false
|
245
|
+
end
|
246
|
+
|
287
247
|
def validate_authorize_params
|
288
|
-
|
248
|
+
if (max_age = param_or_nil("max_age"))
|
289
249
|
|
290
|
-
|
250
|
+
max_age = Integer(max_age)
|
291
251
|
|
292
|
-
|
252
|
+
redirect_response_error("invalid_request") unless max_age.positive?
|
293
253
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
254
|
+
if Time.now - get_oidc_account_last_login_at(session_value) > max_age
|
255
|
+
# force user to re-login
|
256
|
+
clear_session
|
257
|
+
set_session_value(login_redirect_session_key, request.fullpath)
|
258
|
+
redirect require_login_redirect
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
if (claims = param_or_nil("claims"))
|
263
|
+
# The value is a JSON object listing the requested Claims.
|
264
|
+
claims = JSON.parse(claims)
|
265
|
+
|
266
|
+
claims.each do |_, individual_claims|
|
267
|
+
redirect_response_error("invalid_request") unless individual_claims.is_a?(Hash)
|
268
|
+
|
269
|
+
individual_claims.each do |_, claim|
|
270
|
+
redirect_response_error("invalid_request") unless claim.nil? || individual_claims.is_a?(Hash)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
sc = scopes
|
276
|
+
|
277
|
+
if sc && sc.include?("offline_access")
|
278
|
+
|
279
|
+
sc.delete("offline_access")
|
280
|
+
|
281
|
+
# MUST ensure that the prompt parameter contains consent
|
282
|
+
# MUST ignore the offline_access request unless the Client
|
283
|
+
# is using a response_type value that would result in an
|
284
|
+
# Authorization Code
|
285
|
+
if param_or_nil("prompt") == "consent" && (
|
286
|
+
(response_type = param_or_nil("response_type")) && response_type.split(" ").include?("code")
|
287
|
+
)
|
288
|
+
request.params["access_type"] = "offline"
|
289
|
+
end
|
290
|
+
|
291
|
+
request.params["scope"] = sc.join(" ")
|
299
292
|
end
|
300
293
|
|
301
294
|
super
|
@@ -304,7 +297,7 @@ module Rodauth
|
|
304
297
|
def require_authorizable_account
|
305
298
|
try_prompt
|
306
299
|
super
|
307
|
-
try_acr_values
|
300
|
+
@acr = try_acr_values
|
308
301
|
end
|
309
302
|
|
310
303
|
def get_oidc_account_last_login_at(account_id)
|
@@ -348,22 +341,18 @@ module Rodauth
|
|
348
341
|
|
349
342
|
case prompt
|
350
343
|
when "none"
|
344
|
+
return unless request.get?
|
345
|
+
|
351
346
|
redirect_response_error("login_required") unless logged_in?
|
352
347
|
|
353
348
|
require_account
|
354
349
|
|
355
|
-
|
356
|
-
oauth_grants_account_id_column => account_id,
|
357
|
-
oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
|
358
|
-
oauth_grants_redirect_uri_column => redirect_uri,
|
359
|
-
oauth_grants_scopes_column => scopes.join(oauth_scope_separator),
|
360
|
-
oauth_grants_access_type_column => "online"
|
361
|
-
).count.zero?
|
362
|
-
redirect_response_error("consent_required")
|
363
|
-
end
|
350
|
+
redirect_response_error("interaction_required") unless oidc_authorize_on_prompt_none?(account_from_session)
|
364
351
|
|
365
352
|
request.env["REQUEST_METHOD"] = "POST"
|
366
353
|
when "login"
|
354
|
+
return unless request.get?
|
355
|
+
|
367
356
|
if logged_in? && request.cookies[oauth_prompt_login_cookie_key] == "login"
|
368
357
|
::Rack::Utils.delete_cookie_header!(response.headers, oauth_prompt_login_cookie_key, oauth_prompt_login_cookie_options)
|
369
358
|
return
|
@@ -380,18 +369,17 @@ module Rodauth
|
|
380
369
|
|
381
370
|
redirect require_login_redirect
|
382
371
|
when "consent"
|
372
|
+
return unless request.post?
|
373
|
+
|
383
374
|
require_account
|
384
375
|
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
oauth_grants_scopes_column => scopes.join(oauth_scope_separator),
|
390
|
-
oauth_grants_access_type_column => "online"
|
391
|
-
).count.zero?
|
392
|
-
redirect_response_error("consent_required")
|
393
|
-
end
|
376
|
+
sc = scopes || []
|
377
|
+
|
378
|
+
redirect_response_error("consent_required") if sc.empty?
|
379
|
+
|
394
380
|
when "select-account"
|
381
|
+
return unless request.get?
|
382
|
+
|
395
383
|
# only works if select_account plugin is available
|
396
384
|
require_select_account if respond_to?(:require_select_account)
|
397
385
|
else
|
@@ -403,44 +391,68 @@ module Rodauth
|
|
403
391
|
return unless (acr_values = param_or_nil("acr_values"))
|
404
392
|
|
405
393
|
acr_values.split(" ").each do |acr_value|
|
394
|
+
next unless oauth_acr_values_supported.include?(acr_value)
|
395
|
+
|
406
396
|
case acr_value
|
407
|
-
when "phr"
|
408
|
-
|
397
|
+
when "phr"
|
398
|
+
return acr_value if require_acr_value_phr
|
399
|
+
when "phrh"
|
400
|
+
return acr_value if require_acr_value_phrh
|
409
401
|
else
|
410
|
-
require_acr_value(acr_value)
|
402
|
+
return acr_value if require_acr_value(acr_value)
|
411
403
|
end
|
412
404
|
end
|
405
|
+
|
406
|
+
nil
|
413
407
|
end
|
414
408
|
|
415
409
|
def require_acr_value_phr
|
416
|
-
return unless respond_to?(:require_two_factor_authenticated)
|
410
|
+
return false unless respond_to?(:require_two_factor_authenticated)
|
417
411
|
|
418
412
|
require_two_factor_authenticated
|
413
|
+
true
|
419
414
|
end
|
420
415
|
|
421
416
|
def require_acr_value_phrh
|
417
|
+
return false unless features.include?(:webauthn_login)
|
418
|
+
|
422
419
|
require_acr_value_phr && two_factor_login_type_match?("webauthn")
|
423
420
|
end
|
424
421
|
|
425
|
-
def require_acr_value(_acr)
|
422
|
+
def require_acr_value(_acr)
|
423
|
+
true
|
424
|
+
end
|
426
425
|
|
427
426
|
def create_oauth_grant(create_params = {})
|
428
|
-
|
429
|
-
create_params[oauth_grants_nonce_column] = nonce
|
430
|
-
end
|
431
|
-
if (acr = param_or_nil("acr"))
|
432
|
-
create_params[oauth_grants_acr_column] = acr
|
433
|
-
end
|
427
|
+
create_params.replace(oidc_grant_params.merge(create_params))
|
434
428
|
super
|
435
429
|
end
|
436
430
|
|
431
|
+
def create_oauth_grant_with_token(create_params = {})
|
432
|
+
create_params[oauth_grants_type_column] = "hybrid"
|
433
|
+
create_params[oauth_grants_account_id_column] = account_id
|
434
|
+
create_params[oauth_grants_expires_in_column] = Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_access_token_expires_in)
|
435
|
+
authorization_code = create_oauth_grant(create_params)
|
436
|
+
access_token = if oauth_jwt_access_tokens
|
437
|
+
_generate_jwt_access_token(create_params)
|
438
|
+
else
|
439
|
+
oauth_grant = valid_oauth_grant_ds.where(oauth_grants_code_column => authorization_code).first
|
440
|
+
_generate_access_token(oauth_grant)
|
441
|
+
end
|
442
|
+
|
443
|
+
{
|
444
|
+
"code" => authorization_code,
|
445
|
+
**json_access_token_payload(oauth_grants_token_column => access_token)
|
446
|
+
}
|
447
|
+
end
|
448
|
+
|
437
449
|
def create_token(*)
|
438
450
|
oauth_grant = super
|
439
451
|
generate_id_token(oauth_grant)
|
440
452
|
oauth_grant
|
441
453
|
end
|
442
454
|
|
443
|
-
def generate_id_token(oauth_grant)
|
455
|
+
def generate_id_token(oauth_grant, include_claims = false)
|
444
456
|
oauth_scopes = oauth_grant[oauth_grants_scopes_column].split(oauth_scope_separator)
|
445
457
|
|
446
458
|
return unless oauth_scopes.include?("openid")
|
@@ -461,7 +473,18 @@ module Rodauth
|
|
461
473
|
# who just authorized its generation.
|
462
474
|
return unless account
|
463
475
|
|
464
|
-
|
476
|
+
if (claims = oauth_grant[oauth_grants_claims_column])
|
477
|
+
claims = JSON.parse(claims)
|
478
|
+
if (id_token_essential_claims = claims["id_token"])
|
479
|
+
oauth_scopes |= id_token_essential_claims.to_a
|
480
|
+
|
481
|
+
include_claims = true
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
# 5.4 - However, when no Access Token is issued (which is the case for the response_type value id_token),
|
486
|
+
# the resulting Claims are returned in the ID Token.
|
487
|
+
fill_with_account_claims(id_token_claims, account, oauth_scopes, param_or_nil("claims_locales")) if include_claims
|
465
488
|
|
466
489
|
params = {
|
467
490
|
jwks: oauth_application_jwks(oauth_application),
|
@@ -477,11 +500,31 @@ module Rodauth
|
|
477
500
|
end
|
478
501
|
|
479
502
|
# aka fill_with_standard_claims
|
480
|
-
def fill_with_account_claims(claims, account, scopes)
|
503
|
+
def fill_with_account_claims(claims, account, scopes, claims_locales)
|
504
|
+
additional_claims_info = {}
|
505
|
+
|
481
506
|
scopes_by_claim = scopes.each_with_object({}) do |scope, by_oidc|
|
482
507
|
next if scope == "openid"
|
483
508
|
|
484
|
-
|
509
|
+
if scope.is_a?(Array)
|
510
|
+
# essential claims
|
511
|
+
param, additional_info = scope
|
512
|
+
|
513
|
+
param = param.to_sym
|
514
|
+
|
515
|
+
oidc, = OIDC_SCOPES_MAP.find do |_, oidc_scopes|
|
516
|
+
oidc_scopes.include?(param)
|
517
|
+
end || param.to_s
|
518
|
+
|
519
|
+
param = nil if oidc == param.to_s
|
520
|
+
|
521
|
+
additional_claims_info[param] = additional_info
|
522
|
+
else
|
523
|
+
|
524
|
+
oidc, param = scope.split(".", 2)
|
525
|
+
|
526
|
+
param = param.to_sym if param
|
527
|
+
end
|
485
528
|
|
486
529
|
by_oidc[oidc] ||= []
|
487
530
|
|
@@ -490,13 +533,11 @@ module Rodauth
|
|
490
533
|
|
491
534
|
oidc_scopes, additional_scopes = scopes_by_claim.keys.partition { |key| OIDC_SCOPES_MAP.key?(key) }
|
492
535
|
|
493
|
-
|
494
|
-
claims_locales = claims_locales.split(" ").map(&:to_sym)
|
495
|
-
end
|
536
|
+
claims_locales = claims_locales.split(" ").map(&:to_sym) if claims_locales
|
496
537
|
|
497
538
|
unless oidc_scopes.empty?
|
498
539
|
if respond_to?(:get_oidc_param)
|
499
|
-
get_oidc_param = proxy_get_param(:get_oidc_param, claims, claims_locales)
|
540
|
+
get_oidc_param = proxy_get_param(:get_oidc_param, claims, claims_locales, additional_claims_info)
|
500
541
|
|
501
542
|
oidc_scopes.each do |scope|
|
502
543
|
scope_claims = claims
|
@@ -517,7 +558,7 @@ module Rodauth
|
|
517
558
|
return if additional_scopes.empty?
|
518
559
|
|
519
560
|
if respond_to?(:get_additional_param)
|
520
|
-
get_additional_param = proxy_get_param(:get_additional_param, claims, claims_locales)
|
561
|
+
get_additional_param = proxy_get_param(:get_additional_param, claims, claims_locales, additional_claims_info)
|
521
562
|
|
522
563
|
additional_scopes.each do |scope|
|
523
564
|
get_additional_param[account, scope.to_sym]
|
@@ -527,23 +568,36 @@ module Rodauth
|
|
527
568
|
end
|
528
569
|
end
|
529
570
|
|
530
|
-
def proxy_get_param(get_param_func, claims, claims_locales)
|
571
|
+
def proxy_get_param(get_param_func, claims, claims_locales, additional_claims_info)
|
531
572
|
meth = method(get_param_func)
|
532
573
|
if meth.arity == 2
|
533
|
-
|
574
|
+
lambda do |account, param, cl = claims|
|
575
|
+
additional_info = additional_claims_info[param] || EMPTY_HASH
|
576
|
+
value = additional_info["value"] || meth[account, param]
|
577
|
+
value = nil if additional_info["values"] && additional_info["values"].include?(value)
|
578
|
+
cl[param] = value if value
|
579
|
+
end
|
534
580
|
elsif claims_locales.nil?
|
535
|
-
|
581
|
+
lambda do |account, param, cl = claims|
|
582
|
+
additional_info = additional_claims_info[param] || EMPTY_HASH
|
583
|
+
value = additional_info["value"] || meth[account, param, nil]
|
584
|
+
value = nil if additional_info["values"] && additional_info["values"].include?(value)
|
585
|
+
cl[param] = value if value
|
586
|
+
end
|
536
587
|
else
|
537
588
|
lambda do |account, param, cl = claims|
|
538
589
|
claims_values = claims_locales.map do |locale|
|
539
|
-
|
540
|
-
|
590
|
+
additional_info = additional_claims_info[param] || EMPTY_HASH
|
591
|
+
value = additional_info["value"] || meth[account, param, locale]
|
592
|
+
value = nil if additional_info["values"] && additional_info["values"].include?(value)
|
593
|
+
value
|
594
|
+
end.compact
|
541
595
|
|
542
596
|
if claims_values.uniq.size == 1
|
543
597
|
cl[param] = claims_values.first
|
544
598
|
else
|
545
599
|
claims_locales.zip(claims_values).each do |locale, value|
|
546
|
-
cl["#{param}##{locale}"] = value
|
600
|
+
cl["#{param}##{locale}"] = value if value
|
547
601
|
end
|
548
602
|
end
|
549
603
|
end
|
@@ -580,23 +634,39 @@ module Rodauth
|
|
580
634
|
end
|
581
635
|
|
582
636
|
def do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
|
583
|
-
|
637
|
+
response_type = param("response_type")
|
638
|
+
case response_type
|
584
639
|
when "id_token"
|
585
|
-
|
640
|
+
grant_params = oidc_grant_params
|
641
|
+
generate_id_token(grant_params, true)
|
642
|
+
response_params.replace("id_token" => grant_params[:id_token])
|
586
643
|
when "code token"
|
587
644
|
redirect_response_error("invalid_request") unless supports_token_response_type?
|
588
645
|
|
589
|
-
response_params.replace(
|
646
|
+
response_params.replace(create_oauth_grant_with_token)
|
590
647
|
when "code id_token"
|
591
|
-
|
648
|
+
params = _do_authorize_code
|
649
|
+
oauth_grant = valid_oauth_grant_ds.where(oauth_grants_code_column => params["code"]).first
|
650
|
+
generate_id_token(oauth_grant)
|
651
|
+
response_params.replace(
|
652
|
+
"id_token" => oauth_grant[:id_token],
|
653
|
+
"code" => params["code"]
|
654
|
+
)
|
592
655
|
when "id_token token"
|
593
656
|
redirect_response_error("invalid_request") unless supports_token_response_type?
|
594
657
|
|
595
|
-
|
658
|
+
oauth_grant = _do_authorize_token(oauth_grants_type_column => "hybrid")
|
659
|
+
generate_id_token(oauth_grant)
|
660
|
+
|
661
|
+
response_params.replace(json_access_token_payload(oauth_grant))
|
596
662
|
when "code id_token token"
|
597
663
|
redirect_response_error("invalid_request") unless supports_token_response_type?
|
598
664
|
|
599
|
-
|
665
|
+
params = create_oauth_grant_with_token
|
666
|
+
oauth_grant = valid_oauth_grant_ds.where(oauth_grants_code_column => params["code"]).first
|
667
|
+
generate_id_token(oauth_grant)
|
668
|
+
|
669
|
+
response_params.replace(params.merge("id_token" => oauth_grant[:id_token]))
|
600
670
|
when "none"
|
601
671
|
response_mode ||= "none"
|
602
672
|
end
|
@@ -605,23 +675,23 @@ module Rodauth
|
|
605
675
|
super(response_params, response_mode)
|
606
676
|
end
|
607
677
|
|
608
|
-
def
|
678
|
+
def oidc_grant_params
|
609
679
|
grant_params = {
|
610
680
|
oauth_grants_account_id_column => account_id,
|
611
681
|
oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
|
612
|
-
oauth_grants_scopes_column => scopes.join(
|
682
|
+
oauth_grants_scopes_column => scopes.join(oauth_scope_separator)
|
613
683
|
}
|
614
684
|
if (nonce = param_or_nil("nonce"))
|
615
685
|
grant_params[oauth_grants_nonce_column] = nonce
|
616
686
|
end
|
617
|
-
|
618
|
-
|
687
|
+
grant_params[oauth_grants_acr_column] = @acr if @acr
|
688
|
+
if (claims_locales = param_or_nil("claims_locales"))
|
689
|
+
grant_params[oauth_grants_claims_locales_column] = claims_locales
|
690
|
+
end
|
691
|
+
if (claims = param_or_nil("claims"))
|
692
|
+
grant_params[oauth_grants_claims_column] = claims
|
619
693
|
end
|
620
|
-
|
621
|
-
generate_id_token(oauth_grant)
|
622
|
-
params = json_access_token_payload(oauth_grant)
|
623
|
-
params.delete("access_token")
|
624
|
-
params
|
694
|
+
grant_params
|
625
695
|
end
|
626
696
|
|
627
697
|
def authorize_response(params, mode)
|
@@ -630,18 +700,6 @@ module Rodauth
|
|
630
700
|
super
|
631
701
|
end
|
632
702
|
|
633
|
-
# Logout
|
634
|
-
|
635
|
-
def validate_oidc_logout_params
|
636
|
-
redirect_response_error("invalid_request") unless param_or_nil("id_token_hint")
|
637
|
-
# check if valid token hint type
|
638
|
-
return unless (redirect_uri = param_or_nil("post_logout_redirect_uri"))
|
639
|
-
|
640
|
-
return if check_valid_uri?(redirect_uri)
|
641
|
-
|
642
|
-
redirect_response_error("invalid_request")
|
643
|
-
end
|
644
|
-
|
645
703
|
# Webfinger
|
646
704
|
|
647
705
|
def json_webfinger_payload
|
@@ -673,25 +731,15 @@ module Rodauth
|
|
673
731
|
|
674
732
|
scope_claims.unshift("auth_time")
|
675
733
|
|
676
|
-
response_types_supported = metadata[:response_types_supported]
|
677
|
-
|
678
|
-
response_types_supported |= %w[none]
|
679
|
-
response_types_supported |= ["code id_token"] if metadata[:grant_types_supported].include?("authorization_code")
|
680
|
-
if metadata[:grant_types_supported].include?("implicit")
|
681
|
-
response_types_supported |= ["code token", "id_token token", "code id_token token"]
|
682
|
-
end
|
683
|
-
|
684
|
-
alg_values, enc_values = oauth_jwt_jwe_keys.keys.transpose
|
685
|
-
|
686
734
|
metadata.merge(
|
687
735
|
userinfo_endpoint: userinfo_url,
|
688
|
-
end_session_endpoint: (oidc_logout_url if use_rp_initiated_logout?),
|
689
|
-
response_types_supported: response_types_supported,
|
690
736
|
subject_types_supported: %w[public pairwise],
|
737
|
+
acr_values_supported: oauth_acr_values_supported,
|
738
|
+
claims_parameter_supported: true,
|
691
739
|
|
692
|
-
id_token_signing_alg_values_supported:
|
693
|
-
id_token_encryption_alg_values_supported:
|
694
|
-
id_token_encryption_enc_values_supported:
|
740
|
+
id_token_signing_alg_values_supported: oauth_jwt_jws_algorithms_supported,
|
741
|
+
id_token_encryption_alg_values_supported: oauth_jwt_jwe_algorithms_supported,
|
742
|
+
id_token_encryption_enc_values_supported: oauth_jwt_jwe_encryption_methods_supported,
|
695
743
|
|
696
744
|
userinfo_signing_alg_values_supported: oauth_jwt_jws_algorithms_supported,
|
697
745
|
userinfo_encryption_alg_values_supported: oauth_jwt_jwe_algorithms_supported,
|