rodauth-oauth 1.1.0 → 1.3.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.
@@ -9,16 +9,78 @@ module Rodauth
9
9
  before "register"
10
10
 
11
11
  auth_value_method :oauth_client_registration_required_params, %w[redirect_uris client_name]
12
+ auth_value_method :oauth_applications_registration_access_token_column, :registration_access_token
13
+ auth_value_method :registration_client_uri_route, "register"
12
14
 
13
15
  PROTECTED_APPLICATION_ATTRIBUTES = %w[account_id client_id].freeze
14
16
 
17
+ def load_registration_client_uri_routes
18
+ request.on(registration_client_uri_route) do
19
+ # CLIENT REGISTRATION URI
20
+ request.on(String) do |client_id|
21
+ (token = ((v = request.env["HTTP_AUTHORIZATION"]) && v[/\A *Bearer (.*)\Z/, 1]))
22
+
23
+ next unless token
24
+
25
+ oauth_application = db[oauth_applications_table]
26
+ .where(oauth_applications_client_id_column => client_id)
27
+ .first
28
+ next unless oauth_application
29
+
30
+ authorization_required unless password_hash_match?(oauth_application[oauth_applications_registration_access_token_column], token)
31
+
32
+ request.is do
33
+ request.get do
34
+ json_response_oauth_application(oauth_application)
35
+ end
36
+ request.on method: :put do
37
+ %w[client_id registration_access_token registration_client_uri client_secret_expires_at
38
+ client_id_issued_at].each do |prohibited_param|
39
+ if request.params.key?(prohibited_param)
40
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(prohibited_param))
41
+ end
42
+ end
43
+ validate_client_registration_params
44
+
45
+ # if the client includes the "client_secret" field in the request, the value of this field MUST match the currently
46
+ # issued client secret for that client. The client MUST NOT be allowed to overwrite its existing client secret with
47
+ # its own chosen value.
48
+ authorization_required if request.params.key?("client_secret") && secret_matches?(oauth_application,
49
+ request.params["client_secret"])
50
+
51
+ oauth_application = transaction do
52
+ applications_ds = db[oauth_applications_table]
53
+ __update_and_return__(applications_ds, @oauth_application_params)
54
+ end
55
+ json_response_oauth_application(oauth_application)
56
+ end
57
+
58
+ request.on method: :delete do
59
+ applications_ds = db[oauth_applications_table]
60
+ applications_ds.where(oauth_applications_client_id_column => client_id).delete
61
+ response.status = 204
62
+ response["Cache-Control"] = "no-store"
63
+ response["Pragma"] = "no-cache"
64
+ response.finish
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+
15
71
  # /register
16
72
  auth_server_route(:register) do |r|
17
73
  before_register_route
18
74
 
19
- validate_client_registration_params
20
-
21
75
  r.post do
76
+ oauth_client_registration_required_params.each do |required_param|
77
+ unless request.params.key?(required_param)
78
+ register_throw_json_response_error("invalid_client_metadata", register_required_param_message(required_param))
79
+ end
80
+ end
81
+
82
+ validate_client_registration_params
83
+
22
84
  response_params = transaction do
23
85
  before_register
24
86
  do_register
@@ -56,14 +118,8 @@ module Rodauth
56
118
  }
57
119
  end
58
120
 
59
- def validate_client_registration_params
60
- oauth_client_registration_required_params.each do |required_param|
61
- unless request.params.key?(required_param)
62
- register_throw_json_response_error("invalid_client_metadata", register_required_param_message(required_param))
63
- end
64
- end
65
-
66
- @oauth_application_params = request.params.each_with_object({}) do |(key, value), params|
121
+ def validate_client_registration_params(request_params = request.params)
122
+ @oauth_application_params = request_params.each_with_object({}) do |(key, value), params|
67
123
  case key
68
124
  when "redirect_uris"
69
125
  if value.is_a?(Array)
@@ -96,7 +152,7 @@ module Rodauth
96
152
  key = oauth_applications_grant_types_column
97
153
  when "response_types"
98
154
  if value.is_a?(Array)
99
- grant_types = request.params["grant_types"] || oauth_grant_types_supported
155
+ grant_types = request_params["grant_types"] || %w[authorization_code]
100
156
  value = value.each do |response_type|
101
157
  unless oauth_response_types_supported.include?(response_type)
102
158
  register_throw_json_response_error("invalid_client_metadata",
@@ -114,9 +170,9 @@ module Rodauth
114
170
  register_throw_json_response_error("invalid_client_metadata", register_invalid_uri_message(value)) unless check_valid_uri?(value)
115
171
  case key
116
172
  when "client_uri"
117
- key = "homepage_url"
173
+ key = oauth_applications_homepage_url_column
118
174
  when "jwks_uri"
119
- if request.params.key?("jwks")
175
+ if request_params.key?("jwks")
120
176
  register_throw_json_response_error("invalid_client_metadata",
121
177
  register_invalid_jwks_param_message(key, "jwks"))
122
178
  end
@@ -124,7 +180,7 @@ module Rodauth
124
180
  key = __send__(:"oauth_applications_#{key}_column")
125
181
  when "jwks"
126
182
  register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(value)) unless value.is_a?(Hash)
127
- if request.params.key?("jwks_uri")
183
+ if request_params.key?("jwks_uri")
128
184
  register_throw_json_response_error("invalid_client_metadata",
129
185
  register_invalid_jwks_param_message(key, "jwks_uri"))
130
186
  end
@@ -132,6 +188,7 @@ module Rodauth
132
188
  key = oauth_applications_jwks_column
133
189
  value = JSON.dump(value)
134
190
  when "scope"
191
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(value)) unless value.is_a?(String)
135
192
  scopes = value.split(" ") - oauth_application_scopes
136
193
  register_throw_json_response_error("invalid_client_metadata", register_invalid_scopes_message(value)) unless scopes.empty?
137
194
  key = oauth_applications_scopes_column
@@ -141,7 +198,37 @@ module Rodauth
141
198
  value = value.join(" ")
142
199
  key = oauth_applications_contacts_column
143
200
  when "client_name"
201
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(value)) unless value.is_a?(String)
144
202
  key = oauth_applications_name_column
203
+ when "require_pushed_authorization_requests"
204
+ unless respond_to?(:oauth_applications_require_pushed_authorization_requests_column)
205
+ register_throw_json_response_error("invalid_client_metadata",
206
+ register_invalid_param_message(key))
207
+ end
208
+ request_params[key] = value = convert_to_boolean(key, value)
209
+
210
+ key = oauth_applications_require_pushed_authorization_requests_column
211
+ when "tls_client_certificate_bound_access_tokens"
212
+ property = :oauth_applications_tls_client_certificate_bound_access_tokens_column
213
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key)) unless respond_to?(property)
214
+
215
+ request_params[key] = value = convert_to_boolean(key, value)
216
+
217
+ key = oauth_applications_tls_client_certificate_bound_access_tokens_column
218
+ when /\Atls_client_auth_/
219
+ unless respond_to?(:"oauth_applications_#{key}_column")
220
+ register_throw_json_response_error("invalid_client_metadata",
221
+ register_invalid_param_message(key))
222
+ end
223
+
224
+ # client using the tls_client_auth authentication method MUST use exactly one of the below metadata
225
+ # parameters to indicate the certificate subject value that the authorization server is to expect when
226
+ # authenticating the respective client.
227
+ if params.any? { |k, _| k.to_s.start_with?("tls_client_auth_") }
228
+ register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
229
+ end
230
+
231
+ key = __send__(:"oauth_applications_#{key}_column")
145
232
  else
146
233
  if respond_to?(:"oauth_applications_#{key}_column")
147
234
  if PROTECTED_APPLICATION_ATTRIBUTES.include?(key)
@@ -183,42 +270,60 @@ module Rodauth
183
270
 
184
271
  # set defaults
185
272
  create_params = @oauth_application_params
273
+
274
+ # If omitted, an authorization server MAY register a client with a default set of scopes
186
275
  create_params[oauth_applications_scopes_column] ||= return_params["scopes"] = oauth_application_scopes.join(" ")
276
+
277
+ # https://datatracker.ietf.org/doc/html/rfc7591#section-2
187
278
  if create_params[oauth_applications_grant_types_column] ||= begin
279
+ # If omitted, the default behavior is that the client will use only the "authorization_code" Grant Type.
188
280
  return_params["grant_types"] = %w[authorization_code] # rubocop:disable Lint/AssignmentInCondition
189
281
  "authorization_code"
190
282
  end
191
283
  create_params[oauth_applications_token_endpoint_auth_method_column] ||= begin
284
+ # If unspecified or omitted, the default is "client_secret_basic", denoting the HTTP Basic
285
+ # authentication scheme as specified in Section 2.3.1 of OAuth 2.0.
192
286
  return_params["token_endpoint_auth_method"] = "client_secret_basic"
193
287
  "client_secret_basic"
194
288
  end
195
289
  end
196
290
  create_params[oauth_applications_response_types_column] ||= begin
291
+ # If omitted, the default is that the client will use only the "code" response type.
197
292
  return_params["response_types"] = %w[code]
198
293
  "code"
199
294
  end
200
295
  rescue_from_uniqueness_error do
201
- client_id = oauth_unique_id_generator
202
- create_params[oauth_applications_client_id_column] = client_id
203
- return_params["client_id"] = client_id
204
- return_params["client_id_issued_at"] = Time.now.utc.iso8601
205
- if create_params.key?(oauth_applications_client_secret_column)
206
- set_client_secret(create_params, create_params[oauth_applications_client_secret_column])
207
- return_params.delete("client_secret")
208
- else
209
- client_secret = oauth_unique_id_generator
210
- set_client_secret(create_params, client_secret)
211
- return_params["client_secret"] = client_secret
212
- return_params["client_secret_expires_at"] = 0
213
-
214
- create_params.delete_if { |k, _| !application_columns.include?(k) }
215
- end
296
+ initialize_register_params(create_params, return_params)
297
+ create_params.delete_if { |k, _| !application_columns.include?(k) }
216
298
  applications_ds.insert(create_params)
217
299
  end
218
300
 
219
301
  return_params
220
302
  end
221
303
 
304
+ def initialize_register_params(create_params, return_params)
305
+ client_id = oauth_unique_id_generator
306
+ create_params[oauth_applications_client_id_column] = client_id
307
+ return_params["client_id"] = client_id
308
+ return_params["client_id_issued_at"] = Time.now.utc.iso8601
309
+
310
+ registration_access_token = oauth_unique_id_generator
311
+ create_params[oauth_applications_registration_access_token_column] = secret_hash(registration_access_token)
312
+ return_params["registration_access_token"] = registration_access_token
313
+ return_params["registration_client_uri"] = "#{base_url}/#{registration_client_uri_route}/#{return_params['client_id']}"
314
+
315
+ if create_params.key?(oauth_applications_client_secret_column)
316
+ set_client_secret(create_params, create_params[oauth_applications_client_secret_column])
317
+ return_params.delete("client_secret")
318
+ else
319
+ client_secret = oauth_unique_id_generator
320
+ set_client_secret(create_params, client_secret)
321
+ return_params["client_secret"] = client_secret
322
+ return_params["client_secret_expires_at"] = 0
323
+
324
+ end
325
+ end
326
+
222
327
  def register_throw_json_response_error(code, message)
223
328
  throw_json_response_error(oauth_invalid_response_status, code, message)
224
329
  end
@@ -264,6 +369,54 @@ module Rodauth
264
369
  "type '#{response_type}' to be allowed."
265
370
  end
266
371
 
372
+ def convert_to_boolean(key, value)
373
+ case value
374
+ when "true" then true
375
+ when "false" then false
376
+ else
377
+ register_throw_json_response_error(
378
+ "invalid_client_metadata",
379
+ register_invalid_param_message(key)
380
+ )
381
+ end
382
+ end
383
+
384
+ def json_response_oauth_application(oauth_application)
385
+ params = methods.map { |k| k.to_s[/\Aoauth_applications_(\w+)_column\z/, 1] }.compact
386
+
387
+ body = params.each_with_object({}) do |k, hash|
388
+ next if %w[id account_id client_id client_secret cliennt_secret_hash].include?(k)
389
+
390
+ value = oauth_application[__send__(:"oauth_applications_#{k}_column")]
391
+
392
+ next unless value
393
+
394
+ case k
395
+ when "redirect_uri"
396
+ hash["redirect_uris"] = value.split(" ")
397
+ when "token_endpoint_auth_method", "grant_types", "response_types", "request_uris", "post_logout_redirect_uris"
398
+ hash[k] = value.split(" ")
399
+ when "scopes"
400
+ hash["scope"] = value
401
+ when "jwks"
402
+ hash[k] = value.is_a?(String) ? JSON.parse(value) : value
403
+ when "homepage_url"
404
+ hash["client_uri"] = value
405
+ when "name"
406
+ hash["client_name"] = value
407
+ else
408
+ hash[k] = value
409
+ end
410
+ end
411
+
412
+ response.status = 200
413
+ response["Content-Type"] ||= json_response_content_type
414
+ response["Cache-Control"] = "no-store"
415
+ response["Pragma"] = "no-cache"
416
+ json_payload = _json_response_body(body)
417
+ return_response(json_payload)
418
+ end
419
+
267
420
  def oauth_server_metadata_body(*)
268
421
  super.tap do |data|
269
422
  data[:registration_endpoint] = register_url
@@ -20,6 +20,24 @@ module Rodauth
20
20
 
21
21
  private
22
22
 
23
+ def validate_authorize_params
24
+ super
25
+
26
+ response_mode = param_or_nil("response_mode")
27
+
28
+ return unless response_mode
29
+
30
+ response_type = param_or_nil("response_type")
31
+
32
+ return unless response_type == "token"
33
+
34
+ redirect_response_error("invalid_request") unless oauth_response_modes_for_token_supported.include?(response_mode)
35
+ end
36
+
37
+ def oauth_response_modes_for_token_supported
38
+ %w[fragment]
39
+ end
40
+
23
41
  def do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
24
42
  response_type = param("response_type")
25
43
  return super unless response_type == "token" && supported_response_type?(response_type)
@@ -42,19 +60,19 @@ module Rodauth
42
60
  oauth_grants_type_column => "implicit",
43
61
  oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
44
62
  oauth_grants_scopes_column => scopes,
45
- oauth_grants_account_id_column => account_id
63
+ **resource_owner_params
46
64
  }.merge(grant_params)
47
65
 
48
66
  generate_token(grant_params, false)
49
67
  end
50
68
 
51
- def _redirect_response_error(redirect_url, query_params)
69
+ def _redirect_response_error(redirect_url, params)
52
70
  response_types = param("response_type").split(/ +/)
53
71
 
54
72
  return super if response_types.empty? || response_types == %w[code]
55
73
 
56
- query_params = query_params.map { |k, v| "#{k}=#{v}" }
57
- redirect_url.fragment = query_params.join("&")
74
+ params = params.map { |k, v| "#{k}=#{v}" }
75
+ redirect_url.fragment = params.join("&")
58
76
  redirect(redirect_url.to_s)
59
77
  end
60
78
 
@@ -62,7 +80,7 @@ module Rodauth
62
80
  return super unless mode == "fragment"
63
81
 
64
82
  redirect_url = URI.parse(redirect_uri)
65
- params = params.map { |k, v| "#{k}=#{v}" }
83
+ params = [URI.encode_www_form(params)]
66
84
  params << redirect_url.query if redirect_url.query
67
85
  redirect_url.fragment = params.join("&")
68
86
  redirect(redirect_url.to_s)
@@ -9,6 +9,8 @@ module Rodauth
9
9
 
10
10
  auth_value_method :oauth_jwt_access_tokens, true
11
11
 
12
+ auth_value_methods(:jwt_claims)
13
+
12
14
  def require_oauth_authorization(*scopes)
13
15
  return super unless oauth_jwt_access_tokens
14
16
 
@@ -24,7 +24,8 @@ module Rodauth
24
24
  :jwt_decode_no_key,
25
25
  :generate_jti,
26
26
  :oauth_jwt_issuer,
27
- :oauth_jwt_audience
27
+ :oauth_jwt_audience,
28
+ :resource_owner_params_from_jwt_claims
28
29
  )
29
30
 
30
31
  private
@@ -70,6 +71,10 @@ module Rodauth
70
71
  client_application[oauth_applications_client_id_column]
71
72
  end
72
73
 
74
+ def resource_owner_params_from_jwt_claims(claims)
75
+ { oauth_grants_account_id_column => claims["sub"] }
76
+ end
77
+
73
78
  def oauth_server_metadata_body(path = nil)
74
79
  metadata = super
75
80
  metadata.merge! \
@@ -81,14 +86,6 @@ module Rodauth
81
86
  @_jwt_key ||= (oauth_application_jwks(oauth_application) if oauth_application)
82
87
  end
83
88
 
84
- def _jwt_public_key
85
- @_jwt_public_key ||= if oauth_application
86
- oauth_application_jwks(oauth_application)
87
- else
88
- _jwt_key
89
- end
90
- end
91
-
92
89
  # Resource Server only!
93
90
  #
94
91
  # returns the jwks set from the authorization server.
@@ -152,10 +149,28 @@ module Rodauth
152
149
  A128GCM A256GCM A128CBC-HS256 A256CBC-HS512
153
150
  ]
154
151
 
155
- def jwk_export(key)
152
+ def key_to_jwk(key)
156
153
  JSON::JWK.new(key)
157
154
  end
158
155
 
156
+ def jwk_export(key)
157
+ key_to_jwk(key)
158
+ end
159
+
160
+ def jwk_import(jwk)
161
+ JSON::JWK.new(jwk)
162
+ end
163
+
164
+ def jwk_key(jwk)
165
+ jwk = jwk_import(jwk) unless jwk.is_a?(JSON::JWK)
166
+ jwk.to_key
167
+ end
168
+
169
+ def jwk_thumbprint(jwk)
170
+ jwk = jwk_import(jwk) if jwk.is_a?(Hash)
171
+ jwk.thumbprint
172
+ end
173
+
159
174
  def jwt_encode(payload,
160
175
  jwks: nil,
161
176
  encryption_algorithm: oauth_jwt_jwe_keys.keys.dig(0, 0),
@@ -287,8 +302,26 @@ module Rodauth
287
302
  auth_value_method :oauth_jwt_jwe_encryption_methods_supported, []
288
303
  end
289
304
 
305
+ def key_to_jwk(key)
306
+ JWT::JWK.new(key)
307
+ end
308
+
290
309
  def jwk_export(key)
291
- JWT::JWK.new(key).export
310
+ key_to_jwk(key).export
311
+ end
312
+
313
+ def jwk_import(jwk)
314
+ JWT::JWK.import(jwk)
315
+ end
316
+
317
+ def jwk_key(jwk)
318
+ jwk = jwk_import(jwk) unless jwk.is_a?(JWT::JWK)
319
+ jwk.keypair
320
+ end
321
+
322
+ def jwk_thumbprint(jwk)
323
+ jwk = jwk_import(jwk) if jwk.is_a?(Hash)
324
+ JWT::JWK::Thumbprint.new(jwk).generate
292
325
  end
293
326
 
294
327
  def jwt_encode(payload,
@@ -445,6 +478,14 @@ module Rodauth
445
478
  raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
446
479
  end
447
480
 
481
+ def jwk_import(_jwk)
482
+ raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
483
+ end
484
+
485
+ def jwk_thumbprint(_jwk)
486
+ raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
487
+ end
488
+
448
489
  def jwt_encode(_token)
449
490
  raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
450
491
  end
@@ -46,28 +46,7 @@ module Rodauth
46
46
  request_object = response.body
47
47
  end
48
48
 
49
- request_sig_enc_opts = {
50
- jws_algorithm: oauth_application[oauth_applications_request_object_signing_alg_column],
51
- jws_encryption_algorithm: oauth_application[oauth_applications_request_object_encryption_alg_column],
52
- jws_encryption_method: oauth_application[oauth_applications_request_object_encryption_enc_column]
53
- }.compact
54
-
55
- request_sig_enc_opts[:jws_algorithm] ||= "none" if oauth_request_object_signing_alg_allow_none
56
-
57
- if request_sig_enc_opts[:jws_algorithm] == "none"
58
- jwks = nil
59
- elsif (jwks = oauth_application_jwks(oauth_application))
60
- jwks = JSON.parse(jwks, symbolize_names: true) if jwks.is_a?(String)
61
- else
62
- redirect_response_error("invalid_request_object")
63
- end
64
-
65
- claims = jwt_decode(request_object,
66
- jwks: jwks,
67
- verify_jti: false,
68
- verify_iss: false,
69
- verify_aud: false,
70
- **request_sig_enc_opts)
49
+ claims = decode_request_object(request_object)
71
50
 
72
51
  redirect_response_error("invalid_request_object") unless claims
73
52
 
@@ -105,6 +84,35 @@ module Rodauth
105
84
  request_uris.nil? || request_uris.split(oauth_scope_separator).one? { |uri| request_uri.start_with?(uri) }
106
85
  end
107
86
 
87
+ def decode_request_object(request_object)
88
+ request_sig_enc_opts = {
89
+ jws_algorithm: oauth_application[oauth_applications_request_object_signing_alg_column],
90
+ jws_encryption_algorithm: oauth_application[oauth_applications_request_object_encryption_alg_column],
91
+ jws_encryption_method: oauth_application[oauth_applications_request_object_encryption_enc_column]
92
+ }.compact
93
+
94
+ request_sig_enc_opts[:jws_algorithm] ||= "none" if oauth_request_object_signing_alg_allow_none
95
+
96
+ if request_sig_enc_opts[:jws_algorithm] == "none"
97
+ jwks = nil
98
+ elsif (jwks = oauth_application_jwks(oauth_application))
99
+ jwks = JSON.parse(jwks, symbolize_names: true) if jwks.is_a?(String)
100
+ else
101
+ redirect_response_error("invalid_request_object")
102
+ end
103
+
104
+ claims = jwt_decode(request_object,
105
+ jwks: jwks,
106
+ verify_jti: false,
107
+ verify_iss: false,
108
+ verify_aud: false,
109
+ **request_sig_enc_opts)
110
+
111
+ redirect_response_error("invalid_request_object") unless claims
112
+
113
+ claims
114
+ end
115
+
108
116
  def oauth_server_metadata_body(*)
109
117
  super.tap do |data|
110
118
  data[:request_parameter_supported] = true
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rodauth/oauth"
4
+
5
+ module Rodauth
6
+ Feature.define(:oauth_jwt_secured_authorization_response_mode, :OauthJwtSecuredAuthorizationResponseMode) do
7
+ depends :oauth_authorize_base, :oauth_jwt_base
8
+
9
+ auth_value_method :oauth_authorization_response_mode_expires_in, 60 * 5 # 5 minutes
10
+
11
+ auth_value_method :oauth_applications_authorization_signed_response_alg_column, :authorization_signed_response_alg
12
+ auth_value_method :oauth_applications_authorization_encrypted_response_alg_column, :authorization_encrypted_response_alg
13
+ auth_value_method :oauth_applications_authorization_encrypted_response_enc_column, :authorization_encrypted_response_enc
14
+
15
+ auth_value_methods(
16
+ :authorization_signing_alg_values_supported,
17
+ :authorization_encryption_alg_values_supported,
18
+ :authorization_encryption_enc_values_supported
19
+ )
20
+
21
+ def oauth_response_modes_supported
22
+ jwt_response_modes = %w[jwt]
23
+ jwt_response_modes.push("query.jwt", "form_post.jwt") if features.include?(:oauth_authorization_code_grant)
24
+ jwt_response_modes << "fragment.jwt" if features.include?(:oauth_implicit_grant)
25
+
26
+ super | jwt_response_modes
27
+ end
28
+
29
+ def authorization_signing_alg_values_supported
30
+ oauth_jwt_jws_algorithms_supported
31
+ end
32
+
33
+ def authorization_encryption_alg_values_supported
34
+ oauth_jwt_jwe_algorithms_supported
35
+ end
36
+
37
+ def authorization_encryption_enc_values_supported
38
+ oauth_jwt_jwe_encryption_methods_supported
39
+ end
40
+
41
+ private
42
+
43
+ def oauth_response_modes_for_code_supported
44
+ return [] unless features.include?(:oauth_authorization_code_grant)
45
+
46
+ super | %w[query.jwt form_post.jwt jwt]
47
+ end
48
+
49
+ def oauth_response_modes_for_token_supported
50
+ return [] unless features.include?(:oauth_implicit_grant)
51
+
52
+ super | %w[fragment.jwt jwt]
53
+ end
54
+
55
+ def authorize_response(params, mode)
56
+ return super unless mode.end_with?("jwt")
57
+
58
+ response_type = param_or_nil("response_type")
59
+
60
+ redirect_url = URI.parse(redirect_uri)
61
+
62
+ jwt = jwt_encode_authorization_response_mode(params)
63
+
64
+ if mode == "query.jwt" || (mode == "jwt" && response_type == "code")
65
+ return super unless features.include?(:oauth_authorization_code_grant)
66
+
67
+ params = ["response=#{CGI.escape(jwt)}"]
68
+ params << redirect_url.query if redirect_url.query
69
+ redirect_url.query = params.join("&")
70
+ redirect(redirect_url.to_s)
71
+ elsif mode == "form_post.jwt"
72
+ return super unless features.include?(:oauth_authorization_code_grant)
73
+
74
+ response["Content-Type"] = "text/html"
75
+ body = form_post_response_html(redirect_url) do
76
+ "<input type=\"hidden\" name=\"response\" value=\"#{scope.h(jwt)}\" />"
77
+ end
78
+ response.write(body)
79
+ request.halt
80
+ elsif mode == "fragment.jwt" || (mode == "jwt" && response_type == "token")
81
+ return super unless features.include?(:oauth_implicit_grant)
82
+
83
+ params = ["response=#{CGI.escape(jwt)}"]
84
+ params << redirect_url.query if redirect_url.query
85
+ redirect_url.fragment = params.join("&")
86
+ redirect(redirect_url.to_s)
87
+ else
88
+ super
89
+ end
90
+ end
91
+
92
+ def _redirect_response_error(redirect_url, params)
93
+ response_mode = param_or_nil("response_mode")
94
+ return super unless response_mode.end_with?("jwt")
95
+
96
+ authorize_response(Hash[params], response_mode)
97
+ end
98
+
99
+ def jwt_encode_authorization_response_mode(params)
100
+ now = Time.now.to_i
101
+ claims = {
102
+ iss: oauth_jwt_issuer,
103
+ aud: oauth_application[oauth_applications_client_id_column],
104
+ exp: now + oauth_authorization_response_mode_expires_in,
105
+ iat: now
106
+ }.merge(params)
107
+
108
+ encode_params = {
109
+ jwks: oauth_application_jwks(oauth_application),
110
+ signing_algorithm: oauth_application[oauth_applications_authorization_signed_response_alg_column],
111
+ encryption_algorithm: oauth_application[oauth_applications_authorization_encrypted_response_alg_column],
112
+ encryption_method: oauth_application[oauth_applications_authorization_encrypted_response_enc_column]
113
+ }.compact
114
+
115
+ jwt_encode(claims, **encode_params)
116
+ end
117
+
118
+ def oauth_server_metadata_body(*)
119
+ super.tap do |data|
120
+ data[:authorization_signing_alg_values_supported] = authorization_signing_alg_values_supported
121
+ data[:authorization_encryption_alg_values_supported] = authorization_encryption_alg_values_supported
122
+ data[:authorization_encryption_enc_values_supported] = authorization_encryption_enc_values_supported
123
+ end
124
+ end
125
+ end
126
+ end
@@ -23,9 +23,7 @@ module Rodauth
23
23
  classes += " disabled" if current || !page
24
24
  classes += " active" if current
25
25
  if page
26
- params = request.GET.merge("page" => page).map do |k, v|
27
- v ? "#{CGI.escape(String(k))}=#{CGI.escape(String(v))}" : CGI.escape(String(k))
28
- end.join("&")
26
+ params = URI.encode_www_form(request.GET.merge("page" => page))
29
27
 
30
28
  href = "#{request.path}?#{params}"
31
29