rodauth-oauth 1.6.6 → 1.6.7

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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_6_7.md +17 -0
  3. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +6 -0
  4. data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +3 -1
  5. data/lib/rodauth/features/oauth_application_management.rb +22 -2
  6. data/lib/rodauth/features/oauth_authorize_base.rb +7 -0
  7. data/lib/rodauth/features/oauth_base.rb +42 -13
  8. data/lib/rodauth/features/oauth_device_code_grant.rb +5 -0
  9. data/lib/rodauth/features/oauth_dpop.rb +8 -0
  10. data/lib/rodauth/features/oauth_dynamic_client_registration.rb +38 -10
  11. data/lib/rodauth/features/oauth_grant_management.rb +6 -0
  12. data/lib/rodauth/features/oauth_jwt.rb +1 -1
  13. data/lib/rodauth/features/oauth_jwt_base.rb +8 -0
  14. data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +8 -0
  15. data/lib/rodauth/features/oauth_jwt_jwks.rb +6 -0
  16. data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +7 -1
  17. data/lib/rodauth/features/oauth_pushed_authorization_request.rb +6 -0
  18. data/lib/rodauth/features/oauth_resource_indicators.rb +7 -1
  19. data/lib/rodauth/features/oauth_resource_server.rb +1 -1
  20. data/lib/rodauth/features/oauth_saml_bearer_grant.rb +8 -3
  21. data/lib/rodauth/features/oauth_tls_client_auth.rb +14 -2
  22. data/lib/rodauth/features/oauth_token_revocation.rb +2 -1
  23. data/lib/rodauth/features/oidc.rb +25 -19
  24. data/lib/rodauth/features/oidc_frontchannel_logout.rb +10 -2
  25. data/lib/rodauth/features/oidc_rp_initiated_logout.rb +8 -2
  26. data/lib/rodauth/features/oidc_self_issued.rb +1 -1
  27. data/lib/rodauth/oauth/database_extensions.rb +1 -2
  28. data/lib/rodauth/oauth/version.rb +1 -1
  29. data/locales/en.yml +4 -0
  30. data/locales/pt.yml +4 -0
  31. data/templates/oauth_application.str +4 -1
  32. metadata +3 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d114c188d7d68d73ec35837c57c6491022be90288a56aecc7dd61b26949d2509
4
- data.tar.gz: d6d55020809cc6397d23b00f85b89fd948e0b79ffd7970a1956b1b938419ad3d
3
+ metadata.gz: 47de1ced2a621692058d69609ccb69999440ac8ca3de7f7a43f8522a81b93a20
4
+ data.tar.gz: 4822470b6f682648a772b8d7244000a68557875ee12ebd2126033f58128d9241
5
5
  SHA512:
6
- metadata.gz: 546dd06b480c261c6818a7852cb85c4366011631190c4ee749ef59e33a8cb11b3fd1fedca8e7a7bf79e100f1ee406a3f8e71a895e1b0e4fe66abc63d0ba83efa
7
- data.tar.gz: 8239900aebd23ba93d91311a06bb66d88a0a732d70772bc6ef6b0b21d01c91225e1cdc5b76d0a14de3400aa3fd02cbb7d0a406ed2b9a43693f5008d4563c40e5
6
+ metadata.gz: 29604d9791ebdab3ea7a85a8ad05cc1ff32aaf8b73aad85b367be9cdd2ab5f199540d25b179147ef5346da7d709fcbf470aac05db6b903d80c483ce47214d399
7
+ data.tar.gz: 7d853b0d1a139fe2d10ec2d865493c8bdf9db313df79e4778e44943900f5634eabb03ac851a65f4f0bc31708014b037116ac46bf150b9fed8bee93fab5f9909c
@@ -0,0 +1,17 @@
1
+ ### 1.6.7
2
+
3
+ #### Improvements
4
+
5
+ * Some new capabilities were built into `rodauth` recently, which `rodauth-oauth` now taps into:
6
+ * plugins are now object shape friendly, by defining ivars using `rodauth`'s `:uses_instance_variables` auth method (introduced in v2.44)
7
+ * `:oidc` plugin `get_oidc_param` and `get_additional_param` now have a default implementation, which raises an error (eliminates warnings).
8
+ * `only_json?` auth method is no longer defined (unless the `:jwt` plugin is used).
9
+ * some internal methods were now moved to private, instead of being needlessly exposed (eliminates security warnings).
10
+ * a new auth method, `confidential?(oauth_application)`, was added to the `:oauth_base` plugin, which is now used internally to check in some key flows whether the OAuth client application is a public or a confidential client, as it's defined in the OAuth RFC:
11
+ * when using the `:oauth_dynamic_client_registration` plugin, a client secret won't be generated for public clients (same logic will be applied for client registration management endpoints).
12
+ * when using the `:oauth_application_management` plugin, default templates will include a client type column, and omit the client secret for public clients, where they would previously.
13
+
14
+ #### Bugfixes
15
+
16
+ * do not render null fields in the payload of the oauth server metadata endpoint (RFC 8414 section 2 requires omission of undefined values, so clients can apply the recommended defaults).
17
+ * fixed regexp used for json requests (same fix as in [rodauth](https://github.com/jeremyevans/rodauth/commit/3e0d7ab2d49a5733d1afcaaf1062b8a8258aa57a))
@@ -7,8 +7,14 @@
7
7
  <dd><%= oauth_application[rodauth.oauth_applications_description_column] %></dd>
8
8
  <dt><%= rodauth.oauth_applications_homepage_url_label %>: </dt>
9
9
  <dd><%= oauth_application[rodauth.oauth_applications_homepage_url_column] %></dd>
10
+ <dt><%= rodauth.oauth_applications_client_type_label %>: </dt>
11
+ <dd><%= rodauth.oauth_application_client_type_label(oauth_application) %></dd>
10
12
  <dt><%= rodauth.oauth_applications_client_id_label %>: </dt>
11
13
  <dd><%= oauth_application[rodauth.oauth_applications_client_id_column] %></dd>
14
+ <% if rodauth.confidential?(oauth_application) %>
15
+ <dt><%= rodauth.oauth_applications_client_secret_label %>: </dt>
16
+ <dd><%= oauth_application[rodauth.oauth_applications_client_secret_column] %></dd>
17
+ <% end %>
12
18
  <dt><%= rodauth.oauth_applications_redirect_uri_label %>: </dt>
13
19
  <dd><%= oauth_application[rodauth.oauth_applications_redirect_uri_column] %></dd>
14
20
  <dt><%= rodauth.oauth_applications_scopes_label %>: </dt>
@@ -8,7 +8,9 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
8
8
  t.string :homepage_url, null: true
9
9
  t.string :redirect_uri, null: false
10
10
  t.string :client_id, null: false, index: { unique: true }
11
- t.string :client_secret, null: false, index: { unique: true }
11
+ # change `:null` contraints to false if you want to support confidential clients only, and
12
+ # no scheme relying on JWT private keys advertised by the JWKs uri.
13
+ t.string :client_secret, null: true, index: { unique: true }
12
14
  t.string :registration_access_token, null: true
13
15
  t.string :scopes, null: false
14
16
  t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP(6)" }
@@ -35,8 +35,11 @@ module Rodauth
35
35
  translatable_method :oauth_applications_jwks_uri_label, "JSON Web Keys URI"
36
36
  translatable_method :oauth_applications_homepage_url_label, "Homepage URL"
37
37
  translatable_method :oauth_applications_redirect_uri_label, "Redirect URI"
38
- translatable_method :oauth_applications_client_secret_label, "Client Secret"
39
38
  translatable_method :oauth_applications_client_id_label, "Client ID"
39
+ translatable_method :oauth_applications_client_secret_label, "Client Secret"
40
+ translatable_method :oauth_applications_client_type_label, "Client Type"
41
+ translatable_method :oauth_applications_confidential_label, "Confidential"
42
+ translatable_method :oauth_applications_public_label, "Public"
40
43
 
41
44
  %w[type token refresh_token expires_in revoked_at].each do |param|
42
45
  translatable_method :"oauth_grants_#{param}_label", param.gsub("_", " ").capitalize
@@ -58,9 +61,18 @@ module Rodauth
58
61
  translatable_method :oauth_no_grants_text, "No oauth grants yet!"
59
62
 
60
63
  auth_methods(
61
- :oauth_application_path
64
+ :oauth_application_path,
65
+ :oauth_application_client_type_label
62
66
  )
63
67
 
68
+ if respond_to?(:uses_instance_variables)
69
+ uses_instance_variables(
70
+ :@oauth_applications,
71
+ :@oauth_grants,
72
+ :@field_errors
73
+ )
74
+ end
75
+
64
76
  def oauth_applications_path(opts = {})
65
77
  route_path(oauth_applications_route, opts)
66
78
  end
@@ -69,6 +81,14 @@ module Rodauth
69
81
  "#{oauth_applications_path}/#{id}"
70
82
  end
71
83
 
84
+ def oauth_application_client_type_label(oauth_application)
85
+ if confidential?(oauth_application)
86
+ oauth_applications_confidential_label
87
+ else
88
+ oauth_applications_public_label
89
+ end
90
+ end
91
+
72
92
  # /oauth-applications routes
73
93
  def load_oauth_application_management_routes
74
94
  request.on(oauth_applications_route) do
@@ -34,6 +34,13 @@ module Rodauth
34
34
  :authorize_form_params
35
35
  )
36
36
 
37
+ if respond_to?(:uses_instance_variables)
38
+ uses_instance_variables(
39
+ :@error,
40
+ :@back_url
41
+ )
42
+ end
43
+
37
44
  OAUTH_ACCESS_TYPES = %w[offline online].freeze
38
45
 
39
46
  OAUTH_APPROVAL_PROMPTS = %w[force auto].freeze
@@ -76,7 +76,7 @@ module Rodauth
76
76
  account_id
77
77
  name description scopes
78
78
  client_id client_secret
79
- homepage_url redirect_uri
79
+ confidential homepage_url redirect_uri
80
80
  token_endpoint_auth_method grant_types response_types response_modes
81
81
  logo_uri tos_uri policy_uri jwks jwks_uri
82
82
  contacts software_id software_version
@@ -107,9 +107,7 @@ module Rodauth
107
107
 
108
108
  auth_value_method :is_authorization_server?, true
109
109
 
110
- auth_value_methods(:only_json?)
111
-
112
- auth_value_method :json_request_regexp, %r{\bapplication/(?:vnd\.api\+)?json\b}i
110
+ auth_value_method :json_request_regexp, %r{\Aapplication/(?:vnd\.api\+)?json\b}i
113
111
 
114
112
  # METADATA
115
113
  auth_value_method :oauth_metadata_service_documentation, nil
@@ -130,9 +128,18 @@ module Rodauth
130
128
  :oauth_unique_id_generator,
131
129
  :require_authorizable_account,
132
130
  :oauth_account_ds,
133
- :oauth_application_ds
131
+ :oauth_application_ds,
132
+ :confidential?
134
133
  )
135
134
 
135
+ if respond_to?(:uses_instance_variables)
136
+ uses_instance_variables(
137
+ :@json_request,
138
+ :@oauth_application,
139
+ :@authorization_token
140
+ )
141
+ end
142
+
136
143
  # /token
137
144
  auth_server_route(:token) do |r|
138
145
  require_oauth_application
@@ -233,7 +240,7 @@ module Rodauth
233
240
  end
234
241
 
235
242
  def oauth_application
236
- return @oauth_application if defined?(@oauth_application)
243
+ return @oauth_application if @oauth_application
237
244
 
238
245
  client_id = param_or_nil("client_id")
239
246
 
@@ -242,6 +249,21 @@ module Rodauth
242
249
  @oauth_application = db[oauth_applications_table].filter(oauth_applications_client_id_column => client_id).first
243
250
  end
244
251
 
252
+ def confidential?(oauth_application)
253
+ token_endpoint_auth_method = oauth_application[oauth_applications_token_endpoint_auth_method_column]
254
+
255
+ # https://datatracker.ietf.org/doc/html/rfc7591#section-2
256
+ # "none": The client is a public client as defined in OAuth 2.0, Section 2.1, and does not have a client secret.
257
+ return false if token_endpoint_auth_method == "none"
258
+
259
+ oauth_confidential_token_endpoint_auth_methods.include?(token_endpoint_auth_method) ||
260
+ !(
261
+ # not exclusively registering implicit grant
262
+ Array(oauth_application[oauth_applications_grant_types_column]).include?("implicit") &&
263
+ Array(oauth_application[oauth_applications_response_types_column]).include?("token")
264
+ )
265
+ end
266
+
245
267
  def fetch_access_token
246
268
  if (token = request.params["access_token"])
247
269
  if request.post? && !(request.content_type.start_with?("application/x-www-form-urlencoded") &&
@@ -270,7 +292,7 @@ module Rodauth
270
292
  end
271
293
 
272
294
  def authorization_token
273
- return @authorization_token if defined?(@authorization_token)
295
+ return @authorization_token if @authorization_token
274
296
 
275
297
  # check if there is a token
276
298
  access_token = fetch_access_token
@@ -329,6 +351,10 @@ module Rodauth
329
351
 
330
352
  private
331
353
 
354
+ def oauth_confidential_token_endpoint_auth_methods
355
+ %w[client_secret_basic client_secret_post]
356
+ end
357
+
332
358
  def oauth_account_ds(account_id)
333
359
  account_ds(account_id)
334
360
  end
@@ -763,19 +789,22 @@ module Rodauth
763
789
  issuer = base_url
764
790
  issuer += "/#{path}" if path
765
791
 
766
- {
792
+ payload = {
767
793
  issuer: issuer,
768
794
  token_endpoint: token_url,
769
795
  scopes_supported: oauth_application_scopes,
770
796
  response_types_supported: oauth_response_types_supported,
771
797
  response_modes_supported: oauth_response_modes_supported,
772
798
  grant_types_supported: oauth_grant_types_supported,
773
- token_endpoint_auth_methods_supported: oauth_token_endpoint_auth_methods_supported,
774
- service_documentation: oauth_metadata_service_documentation,
775
- ui_locales_supported: oauth_metadata_ui_locales_supported,
776
- op_policy_uri: oauth_metadata_op_policy_uri,
777
- op_tos_uri: oauth_metadata_op_tos_uri
799
+ token_endpoint_auth_methods_supported: oauth_token_endpoint_auth_methods_supported
778
800
  }
801
+
802
+ # RFC 8414, section 2: unused optional metadata parameters must be omitted, not null
803
+ payload[:service_documentation] = oauth_metadata_service_documentation if oauth_metadata_service_documentation
804
+ payload[:ui_locales_supported] = oauth_metadata_ui_locales_supported if oauth_metadata_ui_locales_supported
805
+ payload[:op_policy_uri] = oauth_metadata_op_policy_uri if oauth_metadata_op_policy_uri
806
+ payload[:op_tos_uri] = oauth_metadata_op_tos_uri if oauth_metadata_op_tos_uri
807
+ payload
779
808
  end
780
809
 
781
810
  def redirect_response_error(error_code, message = nil)
@@ -38,6 +38,11 @@ module Rodauth
38
38
  auth_methods(
39
39
  :generate_user_code
40
40
  )
41
+ if respond_to?(:uses_instance_variables)
42
+ uses_instance_variables(
43
+ :@oauth_grant
44
+ )
45
+ end
41
46
 
42
47
  # /device-authorization
43
48
  auth_server_route(:device_authorization) do |r|
@@ -54,6 +54,14 @@ module Rodauth
54
54
 
55
55
  auth_methods(:validate_dpop_proof_usage)
56
56
 
57
+ if respond_to?(:uses_instance_variables)
58
+ uses_instance_variables(
59
+ :@dpop_access_token,
60
+ :@dpop_claims,
61
+ :@dpop_thumbprint
62
+ )
63
+ end
64
+
57
65
  def require_oauth_authorization(*scopes)
58
66
  @dpop_access_token = fetch_access_token_from_authorization_header("dpop")
59
67
 
@@ -12,10 +12,21 @@ module Rodauth
12
12
  auth_value_method :oauth_applications_registration_access_token_column, :registration_access_token
13
13
  auth_value_method :registration_client_uri_route, "register"
14
14
 
15
+ translatable_method :register_no_client_secret_if_public, "Client secret is not allowed for a public client"
16
+
17
+ if respond_to?(:uses_instance_variables)
18
+ uses_instance_variables(
19
+ :@oauth_application_params,
20
+ :@oauth_application_unrecognized_params
21
+ )
22
+ end
23
+
15
24
  PROTECTED_APPLICATION_CREATE_ATTRIBUTES = %w[account_id client_id].freeze
16
25
 
17
- PROTECTED_APPLICATION_UPDATE_ATTRIBUTES = %w[account_id client_id registration_access_token registration_client_uri
18
- client_secret_expires_at client_id_issued_at].freeze
26
+ PROTECTED_APPLICATION_UPDATE_ATTRIBUTES = %w[
27
+ account_id client_id registration_access_token
28
+ registration_client_uri client_secret_expires_at client_id_issued_at
29
+ ].freeze
19
30
 
20
31
  HIDDEN_APPLICATION_ATTRIBUTES = %w[id account_id client_id client_secret client_secret_hash].freeze
21
32
 
@@ -39,15 +50,24 @@ module Rodauth
39
50
  json_response_oauth_application(oauth_application)
40
51
  end
41
52
  request.on method: :put do
42
- validate_client_registration_params(request.params, PROTECTED_APPLICATION_UPDATE_ATTRIBUTES)
53
+ params = request.params
54
+
55
+ validate_client_registration_params(params, PROTECTED_APPLICATION_UPDATE_ATTRIBUTES)
43
56
 
44
- if request.params.key?("client_secret") && !secret_matches?(oauth_application, request.params["client_secret"])
57
+ if params.key?("client_secret") && !secret_matches?(oauth_application, params["client_secret"])
45
58
  # if the client includes the "client_secret" field in the request, the value of this field MUST match the currently
46
59
  # issued client secret for that client. The client MUST NOT be allowed to overwrite its existing client secret with
47
60
  # its own chosen value.
48
61
  authorization_required
49
62
  end
50
63
 
64
+ if params.key?("client_secret")
65
+ next_oauth_application = oauth_application.merge(@oauth_application_params)
66
+ unless confidential?(next_oauth_application)
67
+ register_throw_json_response_error("invalid_client_metadata", register_no_client_secret_if_public)
68
+ end
69
+ end
70
+
51
71
  oauth_application = transaction do
52
72
  applications_ds = db[oauth_applications_table]
53
73
  .where(oauth_applications_client_id_column => client_id)
@@ -108,7 +128,7 @@ module Rodauth
108
128
 
109
129
  def _before_register
110
130
  raise %{dynamic client registration requires authentication.
111
- Override ´before_register` to perform it.
131
+ Override `before_register` to perform it.
112
132
  example:
113
133
 
114
134
  before_register do
@@ -324,6 +344,7 @@ module Rodauth
324
344
  return_params["response_types"] = %w[code]
325
345
  "code"
326
346
  end
347
+
327
348
  rescue_from_uniqueness_error do
328
349
  initialize_create_params(create_params, return_params)
329
350
  create_params.delete_if { |k, _| !application_columns.include?(k) }
@@ -341,15 +362,22 @@ module Rodauth
341
362
  # "client_id_issued_at" is a number, expressed as seconds since 1970-01-01T00:00:00Z.
342
363
  return_params["client_id_issued_at"] = Time.now.to_i
343
364
 
344
- if create_params.key?(oauth_applications_client_secret_column)
345
- return_params.delete("client_secret")
346
- else
365
+ if create_params.key?(oauth_applications_client_secret_column) && !confidential?(create_params)
366
+ register_throw_json_response_error("invalid_client_metadata", register_no_client_secret_if_public)
367
+ end
368
+
369
+ return_params.delete("client_secret")
370
+
371
+ unless (client_secret = create_params.key?(oauth_applications_client_secret_column))
372
+ # do not generate a secret for a confidential client
373
+ return unless confidential?(create_params)
374
+
347
375
  client_secret = oauth_unique_id_generator
348
- set_client_secret(create_params, client_secret)
349
376
  return_params["client_secret"] = client_secret
350
377
  return_params["client_secret_expires_at"] = 0
351
-
352
378
  end
379
+
380
+ set_client_secret(create_params, client_secret)
353
381
  end
354
382
 
355
383
  def register_throw_json_response_error(code, message)
@@ -25,6 +25,12 @@ module Rodauth
25
25
  :oauth_grant_path
26
26
  )
27
27
 
28
+ if respond_to?(:uses_instance_variables)
29
+ uses_instance_variables(
30
+ :@oauth_grants
31
+ )
32
+ end
33
+
28
34
  def oauth_grants_path(opts = {})
29
35
  route_path(oauth_grants_route, opts)
30
36
  end
@@ -51,7 +51,7 @@ module Rodauth
51
51
  def authorization_token
52
52
  return super unless oauth_jwt_access_tokens
53
53
 
54
- return @authorization_token if defined?(@authorization_token)
54
+ return @authorization_token if @authorization_token
55
55
 
56
56
  @authorization_token = decode_access_token
57
57
  end
@@ -33,6 +33,14 @@ module Rodauth
33
33
  :resource_owner_params_from_jwt_claims
34
34
  )
35
35
 
36
+ if respond_to?(:uses_instance_variables)
37
+ uses_instance_variables(
38
+ :@oauth_jwt_issuer,
39
+ :@oauth_jwt_audience,
40
+ :@_jwt_key
41
+ )
42
+ end
43
+
36
44
  private
37
45
 
38
46
  def oauth_jwt_issuer
@@ -28,6 +28,14 @@ module Rodauth
28
28
 
29
29
  private
30
30
 
31
+ def oauth_confidential_token_endpoint_auth_methods
32
+ if oauth_applications_client_secret_hash_column.nil?
33
+ super | %w[client_secret_jwt private_key_jwt urn:ietf:params:oauth:client-assertion-type:jwt-bearer]
34
+ else
35
+ super | %w[private_key_jwt]
36
+ end
37
+ end
38
+
31
39
  def require_oauth_application_from_jwt_bearer_assertion_issuer(assertion)
32
40
  claims = jwt_assertion(assertion)
33
41
 
@@ -17,6 +17,12 @@ module Rodauth
17
17
  end
18
18
  end
19
19
 
20
+ if respond_to?(:uses_instance_variables)
21
+ uses_instance_variables(
22
+ :@jwks_set
23
+ )
24
+ end
25
+
20
26
  private
21
27
 
22
28
  def oauth_server_metadata_body(path = nil)
@@ -23,6 +23,12 @@ module Rodauth
23
23
 
24
24
  auth_value_method :max_param_bytesize, nil if Rodauth::VERSION >= "2.26.0"
25
25
 
26
+ if respond_to?(:uses_instance_variables)
27
+ uses_instance_variables(
28
+ :@require_signed_request_object
29
+ )
30
+ end
31
+
26
32
  private
27
33
 
28
34
  # /authorize
@@ -93,7 +99,7 @@ module Rodauth
93
99
  end
94
100
 
95
101
  def require_signed_request_object?
96
- return @require_signed_request_object if defined?(@require_signed_request_object)
102
+ return @require_signed_request_object if @require_signed_request_object
97
103
 
98
104
  @require_signed_request_object = (oauth_application[oauth_applications_require_signed_request_object_column] if oauth_application)
99
105
  @require_signed_request_object = oauth_require_signed_request_object if @require_signed_request_object.nil?
@@ -19,6 +19,12 @@ module Rodauth
19
19
  auth_value_method :"oauth_pushed_authorization_requests_#{column}_column", column
20
20
  end
21
21
 
22
+ if respond_to?(:uses_instance_variables)
23
+ uses_instance_variables(
24
+ :@require_signed_request_object
25
+ )
26
+ end
27
+
22
28
  # /par
23
29
  auth_server_route(:par) do |r|
24
30
  require_oauth_application
@@ -8,8 +8,14 @@ module Rodauth
8
8
 
9
9
  auth_value_method :oauth_grants_resource_column, :resource
10
10
 
11
+ if respond_to?(:uses_instance_variables)
12
+ uses_instance_variables(
13
+ :@resource_indicators
14
+ )
15
+ end
16
+
11
17
  def resource_indicators
12
- return @resource_indicators if defined?(@resource_indicators)
18
+ return @resource_indicators if @resource_indicators
13
19
 
14
20
  resources = param_or_nil("resource")
15
21
 
@@ -13,7 +13,7 @@ module Rodauth
13
13
  )
14
14
 
15
15
  def authorization_token
16
- return @authorization_token if defined?(@authorization_token)
16
+ return @authorization_token if @authorization_token
17
17
 
18
18
  # check if there is a token
19
19
  access_token = fetch_access_token
@@ -34,6 +34,13 @@ module Rodauth
34
34
  :account_from_saml2_bearer_assertion
35
35
  )
36
36
 
37
+ if respond_to?(:uses_instance_variables)
38
+ uses_instance_variables(
39
+ :@saml_settings,
40
+ :@assertion
41
+ )
42
+ end
43
+
37
44
  def oauth_grant_types_supported
38
45
  super | %w[urn:ietf:params:oauth:grant-type:saml2-bearer]
39
46
  end
@@ -98,9 +105,8 @@ module Rodauth
98
105
  settings
99
106
  end
100
107
 
101
- # rubocop:disable Naming/MemoizedInstanceVariableName
102
108
  def parse_saml_assertion(assertion)
103
- return @assertion if defined?(@assertion)
109
+ return @assertion if @assertion
104
110
 
105
111
  response = OneLogin::RubySaml::Response.new(assertion)
106
112
 
@@ -129,7 +135,6 @@ module Rodauth
129
135
 
130
136
  @assertion = response
131
137
  end
132
- # rubocop:enable Naming/MemoizedInstanceVariableName
133
138
 
134
139
  def oauth_server_metadata_body(*)
135
140
  super.tap do |data|
@@ -21,12 +21,24 @@ module Rodauth
21
21
 
22
22
  auth_value_method :oauth_grants_certificate_thumbprint_column, :certificate_thumbprint
23
23
 
24
+ if respond_to?(:uses_instance_variables)
25
+ uses_instance_variables(
26
+ :@client_certificate,
27
+ :@certificate,
28
+ :@client_certificate_sans
29
+ )
30
+ end
31
+
24
32
  def oauth_token_endpoint_auth_methods_supported
25
33
  super | %w[tls_client_auth self_signed_tls_client_auth]
26
34
  end
27
35
 
28
36
  private
29
37
 
38
+ def oauth_confidential_token_endpoint_auth_methods
39
+ super | %w[tls_client_auth self_signed_tls_client_auth]
40
+ end
41
+
30
42
  def validate_token_params
31
43
  # For all requests to the authorization server utilizing mutual-TLS client authentication,
32
44
  # the client MUST include the client_id parameter
@@ -124,7 +136,7 @@ module Rodauth
124
136
  end
125
137
 
126
138
  def client_certificate
127
- return @client_certificate if defined?(@client_certificate)
139
+ return @client_certificate if @client_certificate
128
140
 
129
141
  unless (pem_cert = request.env["SSL_CLIENT_CERT"] || request.env["HTTP_SSL_CLIENT_CERT"] || request.env["HTTP_X_SSL_CLIENT_CERT"])
130
142
  return
@@ -136,7 +148,7 @@ module Rodauth
136
148
  end
137
149
 
138
150
  def client_certificate_sans
139
- return @client_certificate_sans if defined?(@client_certificate_sans)
151
+ return @client_certificate_sans if @client_certificate_sans
140
152
 
141
153
  @client_certificate_sans =
142
154
  if client_certificate
@@ -117,7 +117,8 @@ module Rodauth
117
117
  def oauth_server_metadata_body(*)
118
118
  super.tap do |data|
119
119
  data[:revocation_endpoint] = revoke_url
120
- data[:revocation_endpoint_auth_methods_supported] = nil # because it's client_secret_basic
120
+ # revocation_endpoint_auth_methods_supported must be absent: it defaults to client_secret_basic
121
+ data.delete(:revocation_endpoint_auth_methods_supported)
121
122
  end
122
123
  end
123
124
  end
@@ -115,6 +115,12 @@ module Rodauth
115
115
  :json_webfinger_payload
116
116
  )
117
117
 
118
+ if respond_to?(:uses_instance_variables)
119
+ uses_instance_variables(
120
+ :@acr
121
+ )
122
+ end
123
+
118
124
  # /userinfo
119
125
  auth_server_route(:userinfo) do |r|
120
126
  r.on method: %i[get post] do
@@ -221,6 +227,14 @@ module Rodauth
221
227
  end
222
228
  end
223
229
 
230
+ def get_oidc_param(_, _)
231
+ raise NotImplementedError, "`get_oidc_param(account, claim)` auth method must be implemented to use oidc scopes."
232
+ end
233
+
234
+ def get_additional_param(_, _)
235
+ raise NotImplementedError, "`get_oidc_param(account, claim)` auth method must be implemented to use oidc scopes."
236
+ end
237
+
224
238
  def oauth_response_types_supported
225
239
  grant_types = oauth_grant_types_supported
226
240
  oidc_response_types = %w[id_token none]
@@ -626,35 +640,27 @@ module Rodauth
626
640
  claims_locales = claims_locales.split(" ").map(&:to_sym) if claims_locales
627
641
 
628
642
  unless oidc_scopes.empty?
629
- if respond_to?(:get_oidc_param)
630
- get_oidc_param = proxy_get_param(:get_oidc_param, claims, claims_locales, additional_claims_info)
643
+ get_oidc_param = proxy_get_param(:get_oidc_param, claims, claims_locales, additional_claims_info)
631
644
 
632
- oidc_scopes.each do |scope|
633
- scope_claims = claims
634
- params = scopes_by_claim[scope]
635
- params = params.empty? ? OIDC_SCOPES_MAP[scope] : (OIDC_SCOPES_MAP[scope] & params)
645
+ oidc_scopes.each do |scope|
646
+ scope_claims = claims
647
+ params = scopes_by_claim[scope]
648
+ params = params.empty? ? OIDC_SCOPES_MAP[scope] : (OIDC_SCOPES_MAP[scope] & params)
636
649
 
637
- scope_claims = (claims["address"] = {}) if scope == "address"
650
+ scope_claims = (claims["address"] = {}) if scope == "address"
638
651
 
639
- params.each do |param|
640
- get_oidc_param[account, param, scope_claims]
641
- end
652
+ params.each do |param|
653
+ get_oidc_param[account, param, scope_claims]
642
654
  end
643
- else
644
- warn "`get_oidc_param(account, claim)` must be implemented to use oidc scopes."
645
655
  end
646
656
  end
647
657
 
648
658
  return if additional_scopes.empty?
649
659
 
650
- if respond_to?(:get_additional_param)
651
- get_additional_param = proxy_get_param(:get_additional_param, claims, claims_locales, additional_claims_info)
660
+ get_additional_param = proxy_get_param(:get_additional_param, claims, claims_locales, additional_claims_info)
652
661
 
653
- additional_scopes.each do |scope|
654
- get_additional_param[account, scope.to_sym]
655
- end
656
- else
657
- warn "`get_additional_param(account, claim)` must be implemented to use oidc scopes."
662
+ additional_scopes.each do |scope|
663
+ get_additional_param[account, scope.to_sym]
658
664
  end
659
665
  end
660
666
 
@@ -22,6 +22,14 @@ module Rodauth
22
22
  auth_value_method :oauth_applications_frontchannel_logout_uri_column, :frontchannel_logout_uri
23
23
  auth_value_method :oauth_applications_frontchannel_logout_session_required_column, :frontchannel_logout_session_required
24
24
 
25
+ if respond_to?(:uses_instance_variables)
26
+ uses_instance_variables(
27
+ :@visited_sites,
28
+ :@frontchannel_logout_redirect,
29
+ :@frontchannel_logout_urls
30
+ )
31
+ end
32
+
25
33
  attr_reader :frontchannel_logout_urls
26
34
 
27
35
  attr_reader :frontchannel_logout_redirect
@@ -32,6 +40,8 @@ module Rodauth
32
40
  super
33
41
  end
34
42
 
43
+ private
44
+
35
45
  def _logout_response
36
46
  visited_sites = @visited_sites
37
47
 
@@ -71,8 +81,6 @@ module Rodauth
71
81
  return_response frontchannel_logout_view
72
82
  end
73
83
 
74
- private
75
-
76
84
  def generate_frontchannel_logout_urls(visited_sites, logout_urls)
77
85
  @frontchannel_logout_urls = logout_urls.flat_map do |client_id, logout_url|
78
86
  next unless logout_url
@@ -13,6 +13,12 @@ module Rodauth
13
13
 
14
14
  attr_reader :oidc_logout_redirect
15
15
 
16
+ if respond_to?(:uses_instance_variables)
17
+ uses_instance_variables(
18
+ :@oidc_logout_redirect
19
+ )
20
+ end
21
+
16
22
  # /oidc-logout
17
23
  auth_server_route(:oidc_logout) do |r|
18
24
  require_authorizable_account
@@ -109,12 +115,12 @@ module Rodauth
109
115
  end
110
116
  end
111
117
 
118
+ private
119
+
112
120
  def _oidc_logout_response
113
121
  redirect(oidc_logout_redirect)
114
122
  end
115
123
 
116
- private
117
-
118
124
  # Logout
119
125
 
120
126
  def validate_oidc_logout_params
@@ -19,7 +19,7 @@ module Rodauth
19
19
  }.freeze
20
20
 
21
21
  def oauth_application
22
- return @oauth_application if defined?(@oauth_application)
22
+ return @oauth_application if @oauth_application
23
23
 
24
24
  return super unless (registration = param_or_nil("registration"))
25
25
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rodauth
4
4
  module OAuth
5
- # rubocop:disable Naming/MethodName
5
+ # rubocop:disable-next Naming/MethodName
6
6
  def self.ExtendDatabase(db)
7
7
  Module.new do
8
8
  dataset = db.dataset
@@ -87,6 +87,5 @@ module Rodauth
87
87
  end
88
88
  end
89
89
  end
90
- # rubocop:enable Naming/MethodName
91
90
  end
92
91
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rodauth
4
4
  module OAuth
5
- VERSION = "1.6.6"
5
+ VERSION = "1.6.7"
6
6
  end
7
7
  end
data/locales/en.yml CHANGED
@@ -40,6 +40,9 @@ en:
40
40
  oauth_applications_redirect_uri_label: "Redirect URL"
41
41
  oauth_applications_client_secret_label: "Client Secret"
42
42
  oauth_applications_client_id_label: "Client ID"
43
+ oauth_applications_client_type_label: "Client Type"
44
+ oauth_applications_confidential_label: "Confidential"
45
+ oauth_applications_public_label: "Public"
43
46
  oauth_no_applications_text: "No oauth applications yet!"
44
47
  oauth_grant_user_code_label: "User code"
45
48
  oauth_grant_user_jws_jwk_label: "JSON Web Keys"
@@ -87,3 +90,4 @@ en:
87
90
  oauth_use_dpop_nonce_message: "DPoP nonce is required"
88
91
  oauth_access_token_dpop_bound_message: "DPoP bound access token requires DPoP proof"
89
92
  oauth_multiple_auth_methods_message: "Multiple methods used to include access token"
93
+ register_no_client_secret_if_public: "Client secret is not allowed for a public OAuth application"
data/locales/pt.yml CHANGED
@@ -40,6 +40,9 @@ pt:
40
40
  oauth_applications_redirect_uri_label: "URL para redireccionamento"
41
41
  oauth_applications_client_secret_label: "Segredo de cliente"
42
42
  oauth_applications_client_id_label: "ID do cliente"
43
+ oauth_applications_client_type_label: "Tipo de cliente"
44
+ oauth_applications_confidential_label: "Confidencial"
45
+ oauth_applications_public_label: "Público"
43
46
  oauth_no_applications_text: "Nenhum aplicativo OAuth ainda!"
44
47
  oauth_grant_user_code_label: "Código do usuário"
45
48
  oauth_grant_user_jws_jwk_label: "Chaves JSON Web"
@@ -77,3 +80,4 @@ pt:
77
80
  oauth_saml_assertion_single_issuer_message: "A asserção SAML tem que ter um único emissor"
78
81
  oauth_saml_settings_not_found_message: "Nenhuma configuração SAML encontrada para o emissor"
79
82
  oauth_invalid_id_token_hint_message: "ID token hint inválido"
83
+ register_no_client_secret_if_public: "Segredo de cliente não é permitido em um aplicativo OAuth público"
@@ -1,7 +1,10 @@
1
1
  <div id="oauth-application">
2
2
  <dl>
3
+ <dt class="confidential">#{rodauth.oauth_applications_client_type_label}</dt>
4
+ <dd class="confidential">#{rodauth.oauth_application_client_type_label(@oauth_application)}</dd>
3
5
  #{
4
- params = [*rodauth.oauth_application_required_params, "client_id", "client_secret"]
6
+ params = [*rodauth.oauth_application_required_params, "client_id"]
7
+ params << "client_secret" if rodauth.confidential?(@oauth_application)
5
8
  if rodauth.features.include?(:oauth_jwt)
6
9
  params += %w[jwks]
7
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rodauth-oauth
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.6
4
+ version: 1.6.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
@@ -94,6 +94,7 @@ extra_rdoc_files:
94
94
  - doc/release_notes/1_6_4.md
95
95
  - doc/release_notes/1_6_5.md
96
96
  - doc/release_notes/1_6_6.md
97
+ - doc/release_notes/1_6_7.md
97
98
  files:
98
99
  - CHANGELOG.md
99
100
  - LICENSE.txt
@@ -146,6 +147,7 @@ files:
146
147
  - doc/release_notes/1_6_4.md
147
148
  - doc/release_notes/1_6_5.md
148
149
  - doc/release_notes/1_6_6.md
150
+ - doc/release_notes/1_6_7.md
149
151
  - lib/generators/rodauth/oauth/install_generator.rb
150
152
  - lib/generators/rodauth/oauth/templates/app/models/oauth_application.rb
151
153
  - lib/generators/rodauth/oauth/templates/app/models/oauth_grant.rb