rodauth-oauth 1.6.5 → 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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -1
  3. data/doc/release_notes/1_6_6.md +25 -0
  4. data/doc/release_notes/1_6_7.md +17 -0
  5. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +6 -48
  6. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +6 -0
  7. data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +3 -1
  8. data/lib/rodauth/features/oauth_application_management.rb +22 -2
  9. data/lib/rodauth/features/oauth_authorization_code_grant.rb +2 -2
  10. data/lib/rodauth/features/oauth_authorize_base.rb +27 -1
  11. data/lib/rodauth/features/oauth_base.rb +64 -17
  12. data/lib/rodauth/features/oauth_device_code_grant.rb +5 -0
  13. data/lib/rodauth/features/oauth_dpop.rb +16 -0
  14. data/lib/rodauth/features/oauth_dynamic_client_registration.rb +71 -33
  15. data/lib/rodauth/features/oauth_grant_management.rb +6 -0
  16. data/lib/rodauth/features/oauth_jwt.rb +1 -1
  17. data/lib/rodauth/features/oauth_jwt_base.rb +56 -13
  18. data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +8 -0
  19. data/lib/rodauth/features/oauth_jwt_jwks.rb +6 -0
  20. data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +7 -1
  21. data/lib/rodauth/features/oauth_pkce.rb +35 -4
  22. data/lib/rodauth/features/oauth_pushed_authorization_request.rb +6 -0
  23. data/lib/rodauth/features/oauth_resource_indicators.rb +16 -1
  24. data/lib/rodauth/features/oauth_resource_server.rb +1 -1
  25. data/lib/rodauth/features/oauth_saml_bearer_grant.rb +8 -3
  26. data/lib/rodauth/features/oauth_tls_client_auth.rb +14 -2
  27. data/lib/rodauth/features/oauth_token_revocation.rb +2 -1
  28. data/lib/rodauth/features/oidc.rb +35 -19
  29. data/lib/rodauth/features/oidc_dynamic_client_registration.rb +1 -1
  30. data/lib/rodauth/features/oidc_frontchannel_logout.rb +12 -4
  31. data/lib/rodauth/features/oidc_rp_initiated_logout.rb +8 -2
  32. data/lib/rodauth/features/oidc_self_issued.rb +9 -1
  33. data/lib/rodauth/oauth/database_extensions.rb +1 -2
  34. data/lib/rodauth/oauth/ttl_store.rb +3 -3
  35. data/lib/rodauth/oauth/version.rb +1 -1
  36. data/locales/en.yml +4 -0
  37. data/locales/pt.yml +4 -0
  38. data/templates/authorize.str +8 -26
  39. data/templates/device_verification.str +3 -3
  40. data/templates/oauth_application.str +4 -1
  41. metadata +6 -2
@@ -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]
@@ -235,6 +249,16 @@ module Rodauth
235
249
  super unless subject_type == "pairwise"
236
250
  end
237
251
 
252
+ def authorize_form_params
253
+ super.tap do |params|
254
+ %w[prompt nonce ui_locales claims_locales claims acr_values].each do |param_name|
255
+ if (param_value = param_or_nil(param_name))
256
+ params << { "name" => param_name, "value" => param_value, "type" => "hidden" }
257
+ end
258
+ end
259
+ end
260
+ end
261
+
238
262
  private
239
263
 
240
264
  if defined?(::I18n)
@@ -616,35 +640,27 @@ module Rodauth
616
640
  claims_locales = claims_locales.split(" ").map(&:to_sym) if claims_locales
617
641
 
618
642
  unless oidc_scopes.empty?
619
- if respond_to?(:get_oidc_param)
620
- 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)
621
644
 
622
- oidc_scopes.each do |scope|
623
- scope_claims = claims
624
- params = scopes_by_claim[scope]
625
- 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)
626
649
 
627
- scope_claims = (claims["address"] = {}) if scope == "address"
650
+ scope_claims = (claims["address"] = {}) if scope == "address"
628
651
 
629
- params.each do |param|
630
- get_oidc_param[account, param, scope_claims]
631
- end
652
+ params.each do |param|
653
+ get_oidc_param[account, param, scope_claims]
632
654
  end
633
- else
634
- warn "`get_oidc_param(account, claim)` must be implemented to use oidc scopes."
635
655
  end
636
656
  end
637
657
 
638
658
  return if additional_scopes.empty?
639
659
 
640
- if respond_to?(:get_additional_param)
641
- 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)
642
661
 
643
- additional_scopes.each do |scope|
644
- get_additional_param[account, scope.to_sym]
645
- end
646
- else
647
- 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]
648
664
  end
649
665
  end
650
666
 
@@ -287,7 +287,7 @@ module Rodauth
287
287
  "The application type '#{application_type}' is not allowed."
288
288
  end
289
289
 
290
- def initialize_register_params(create_params, return_params)
290
+ def initialize_create_params(create_params, return_params)
291
291
  super
292
292
  registration_access_token = oauth_unique_id_generator
293
293
  create_params[oauth_applications_registration_access_token_column] = secret_hash(registration_access_token)
@@ -2,10 +2,10 @@
2
2
 
3
3
  require "rodauth/oauth"
4
4
 
5
- # :nocov:
5
+ # simplecov:enable
6
6
  raise LoadError, "the `:oidc_frontchannel_logout` requires rodauth 2.32.0 or higher" if Rodauth::VERSION < "2.32.0"
7
7
 
8
- # :nocov:
8
+ # simplecov:disable
9
9
 
10
10
  module Rodauth
11
11
  Feature.define(:oidc_frontchannel_logout, :OidFrontchannelLogout) do
@@ -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
 
@@ -38,6 +38,14 @@ module Rodauth
38
38
  @oauth_application = client_params
39
39
  end
40
40
 
41
+ def authorize_form_params
42
+ super.tap do |params|
43
+ if (param_value = param_or_nil("registration"))
44
+ params << { "name" => "registration", "value" => param_value, "type" => "hidden" }
45
+ end
46
+ end
47
+ end
48
+
41
49
  private
42
50
 
43
51
  def oauth_response_types_supported
@@ -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
@@ -23,7 +23,7 @@ class Rodauth::OAuth::TtlStore
23
23
  def set(key, &block)
24
24
  @store_mutex.synchronize do
25
25
  # short circuit
26
- return @store[key][:payload] if @store[key] && @store[key][:ttl] < now
26
+ return @store[key][:payload] if @store[key] && @store[key][:ttl] > now
27
27
  end
28
28
 
29
29
  payload, ttl = block.call
@@ -33,9 +33,9 @@ class Rodauth::OAuth::TtlStore
33
33
  @store_mutex.synchronize do
34
34
  # given that the block call triggers network, and two requests for the same key be processed
35
35
  # at the same time, this ensures the first one wins.
36
- return @store[key][:payload] if @store[key] && @store[key][:ttl] < now
36
+ return @store[key][:payload] if @store[key] && @store[key][:ttl] > now
37
37
 
38
- @store[key] = { payload: payload, ttl: ttl || (now + DEFAULT_TTL) }
38
+ @store[key] = { payload: payload, ttl: now + (ttl || DEFAULT_TTL) }
39
39
  end
40
40
  @store[key][:payload]
41
41
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rodauth
4
4
  module OAuth
5
- VERSION = "1.6.5"
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"
@@ -10,7 +10,7 @@
10
10
  <p class="lead">
11
11
  #{
12
12
  application_uri = rodauth.oauth_application[rodauth.oauth_applications_homepage_url_column]
13
- application_name = application_uri ? (<<-LINK) : rodauth.oauth_application[rodauth.oauth_applications_name_column]
13
+ application_name = application_uri ? (<<-LINK) : h(rodauth.oauth_application[rodauth.oauth_applications_name_column])
14
14
  <a target="_blank" href="#{h(application_uri)}">
15
15
  #{h(rodauth.oauth_application[rodauth.oauth_applications_name_column])}
16
16
  </a>
@@ -62,7 +62,7 @@
62
62
  #{
63
63
  rodauth.authorize_scopes.map do |scope|
64
64
  if rodauth.features.include?(:oidc) && scope == "offline_access"
65
- "<input type=\"hidden\" name=\"scope[]\" value=\"#{scope}\" />"
65
+ "<input type=\"hidden\" name=\"scope[]\" value=\"#{h(scope)}\" />"
66
66
  else
67
67
  <<-HTML
68
68
  <div class="form-check">
@@ -74,34 +74,16 @@
74
74
  end.join
75
75
  }
76
76
 
77
- <input type="hidden" name="client_id" value="#{rodauth.param("client_id")}"/>
78
- #{"<input type=\"hidden\" name=\"access_type\" value=\"#{rodauth.param("access_type")}\"/>" if rodauth.param_or_nil("access_type")}
79
- #{"<input type=\"hidden\" name=\"response_type\" value=\"#{rodauth.param("response_type")}\"/>" if rodauth.param_or_nil("response_type")}
80
- #{"<input type=\"hidden\" name=\"response_mode\" value=\"#{rodauth.param("response_mode")}\"/>" if rodauth.param_or_nil("response_mode")}
81
- #{"<input type=\"hidden\" name=\"state\" value=\"#{rodauth.param("state")}\"/>" if rodauth.param_or_nil("state")}
82
- #{"<input type=\"hidden\" name=\"redirect_uri\" value=\"#{rodauth.redirect_uri}\"/>" if rodauth.param_or_nil("redirect_uri")}
83
- #{"<input type=\"hidden\" name=\"code_challenge\" value=\"#{rodauth.param("code_challenge")}\"/>" if rodauth.features.include?(:oauth_pkce) && rodauth.param_or_nil("code_challenge")}
84
- #{"<input type=\"hidden\" name=\"code_challenge_method\" value=\"#{rodauth.param("code_challenge_method")}\"/>" if rodauth.features.include?(:oauth_pkce) && rodauth.param_or_nil("code_challenge_method")}
85
- #{"<input type=\"hidden\" name=\"prompt\" value=\"#{rodauth.param("prompt")}\"/>" if rodauth.features.include?(:oidc) && rodauth.param_or_nil("prompt")}
86
- #{"<input type=\"hidden\" name=\"nonce\" value=\"#{rodauth.param("nonce")}\"/>" if rodauth.features.include?(:oidc) && rodauth.param_or_nil("nonce")}
87
- #{"<input type=\"hidden\" name=\"ui_locales\" value=\"#{rodauth.param("ui_locales")}\"/>" if rodauth.features.include?(:oidc) && rodauth.param_or_nil("ui_locales")}
88
- #{"<input type=\"hidden\" name=\"claims_locales\" value=\"#{rodauth.param("claims_locales")}\"/>" if rodauth.features.include?(:oidc) && rodauth.param_or_nil("claims_locales")}
89
- #{"<input type=\"hidden\" name=\"claims\" value=\"#{h(rodauth.param("claims"))}\"/>" if rodauth.features.include?(:oidc) && rodauth.param_or_nil("claims")}
90
- #{"<input type=\"hidden\" name=\"acr_values\" value=\"#{rodauth.param("acr_values")}\"/>" if rodauth.features.include?(:oidc) && rodauth.param_or_nil("acr_values")}
91
- #{"<input type=\"hidden\" name=\"registration\" value=\"#{h(rodauth.param("registration"))}\"/>" if rodauth.features.include?(:oidc_self_issued) && rodauth.param_or_nil("registration")}
92
77
  #{
93
- if rodauth.features.include?(:oauth_resource_indicators) && rodauth.resource_indicators
94
- rodauth.resource_indicators.map do |resource|
95
- "<input type=\"hidden\" name=\"resource\" value=\"#{resource}\"/>"
96
- end.join
97
- end
98
- }
99
- #{"<input type=\"hidden\" name=\"dpop_jkt\" value=\"#{h(rodauth.param("dpop_jkt"))}\"/>" if rodauth.features.include?(:oauth_dpop) && rodauth.param_or_nil("dpop_jkt")}
78
+ rodauth.authorize_form_params.map do |field|
79
+ "<input type=\"#{h(field["type"])}\" name=\"#{h(field["name"])}\" value=\"#{h(field["value"])}\"/>"
80
+ end.join
81
+ }
100
82
  </div>
101
83
  <p class="text-center">
102
84
  <input type="submit" class="btn btn-outline-primary" value="#{h(rodauth.oauth_authorize_button)}"/>
103
- <a href="#{rodauth.redirect_uri}?error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request#{ "&state=#{rodauth.param("state")}" if rodauth.param_or_nil("state")}" class="btn btn-outline-danger">
104
- #{rodauth.oauth_cancel_button}
85
+ <a href="#{h(rodauth.redirect_uri)}?error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request#{ "&state=#{CGI.escape(rodauth.param("state"))}" if rodauth.param_or_nil("state")}" class="btn btn-outline-danger">
86
+ #{h(rodauth.oauth_cancel_button)}
105
87
  </a>
106
88
  </p>
107
89
  </form>
@@ -10,15 +10,15 @@
10
10
  scopes = @oauth_grant[rodauth.oauth_grants_scopes_column].split(rodauth.oauth_scope_separator)
11
11
  scopes.map do |scope|
12
12
  <<-HTML
13
- <li class="list-group-item">#{scope}</li>
13
+ <li class="list-group-item">#{h(scope)}</li>
14
14
  HTML
15
15
  end.join
16
16
  }
17
17
  </ul>
18
18
  </div>
19
- <input type="hidden" name="user_code" value="#{rodauth.param("user_code")}"/>
19
+ <input type="hidden" name="user_code" value="#{h(rodauth.param("user_code"))}"/>
20
20
  <p class="text-center">
21
21
  <input type="submit" class="btn btn-outline-primary" value="#{h(rodauth.oauth_device_verification_button)}"/>
22
- <a href="#{rodauth.device_path}?error=access_denied" class="btn btn-outline-danger">#{rodauth.oauth_cancel_button}</a>
22
+ <a href="#{rodauth.device_path}?error=access_denied" class="btn btn-outline-danger">#{h(rodauth.oauth_cancel_button)}</a>
23
23
  </p>
24
24
  </form>
@@ -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.5
4
+ version: 1.6.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
@@ -93,6 +93,8 @@ extra_rdoc_files:
93
93
  - doc/release_notes/1_6_3.md
94
94
  - doc/release_notes/1_6_4.md
95
95
  - doc/release_notes/1_6_5.md
96
+ - doc/release_notes/1_6_6.md
97
+ - doc/release_notes/1_6_7.md
96
98
  files:
97
99
  - CHANGELOG.md
98
100
  - LICENSE.txt
@@ -144,6 +146,8 @@ files:
144
146
  - doc/release_notes/1_6_3.md
145
147
  - doc/release_notes/1_6_4.md
146
148
  - doc/release_notes/1_6_5.md
149
+ - doc/release_notes/1_6_6.md
150
+ - doc/release_notes/1_6_7.md
147
151
  - lib/generators/rodauth/oauth/install_generator.rb
148
152
  - lib/generators/rodauth/oauth/templates/app/models/oauth_application.rb
149
153
  - lib/generators/rodauth/oauth/templates/app/models/oauth_grant.rb
@@ -244,7 +248,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
244
248
  - !ruby/object:Gem::Version
245
249
  version: '0'
246
250
  requirements: []
247
- rubygems_version: 3.6.9
251
+ rubygems_version: 4.0.16
248
252
  specification_version: 4
249
253
  summary: Implementation of the OAuth 2.0 protocol on top of rodauth.
250
254
  test_files: []