rodauth-oauth 1.6.5 → 1.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +9 -1
- data/doc/release_notes/1_6_6.md +25 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +6 -48
- data/lib/rodauth/features/oauth_authorization_code_grant.rb +2 -2
- data/lib/rodauth/features/oauth_authorize_base.rb +20 -1
- data/lib/rodauth/features/oauth_base.rb +22 -4
- data/lib/rodauth/features/oauth_dpop.rb +8 -0
- data/lib/rodauth/features/oauth_dynamic_client_registration.rb +39 -29
- data/lib/rodauth/features/oauth_jwt_base.rb +48 -13
- data/lib/rodauth/features/oauth_pkce.rb +35 -4
- data/lib/rodauth/features/oauth_resource_indicators.rb +9 -0
- data/lib/rodauth/features/oidc.rb +10 -0
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +1 -1
- data/lib/rodauth/features/oidc_frontchannel_logout.rb +2 -2
- data/lib/rodauth/features/oidc_self_issued.rb +8 -0
- data/lib/rodauth/oauth/ttl_store.rb +3 -3
- data/lib/rodauth/oauth/version.rb +1 -1
- data/templates/authorize.str +8 -26
- data/templates/device_verification.str +3 -3
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d114c188d7d68d73ec35837c57c6491022be90288a56aecc7dd61b26949d2509
|
|
4
|
+
data.tar.gz: d6d55020809cc6397d23b00f85b89fd948e0b79ffd7970a1956b1b938419ad3d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 546dd06b480c261c6818a7852cb85c4366011631190c4ee749ef59e33a8cb11b3fd1fedca8e7a7bf79e100f1ee406a3f8e71a895e1b0e4fe66abc63d0ba83efa
|
|
7
|
+
data.tar.gz: 8239900aebd23ba93d91311a06bb66d88a0a732d70772bc6ef6b0b21d01c91225e1cdc5b76d0a14de3400aa3fd02cbb7d0a406ed2b9a43693f5008d4563c40e5
|
data/README.md
CHANGED
|
@@ -183,7 +183,15 @@ If you're starting from scratch though, the recommendation is to stick to the de
|
|
|
183
183
|
|
|
184
184
|
You'll have to generate HTML templates for the Oauth Authorization form.
|
|
185
185
|
|
|
186
|
-
The rodauth default setup expects the roda `render` plugin to be activated; by default, it expects a `views` directory to be defined in the project root folder. The Oauth Authorization template must be therefore defined there, and it should be called `
|
|
186
|
+
The rodauth default setup expects the roda `render` plugin to be activated; by default, it expects a `views` directory to be defined in the project root folder. The Oauth Authorization template must be therefore defined there, and it should be called `authorize.(erb|str|...)` (read the [roda `render` plugin documentation](http://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/Render.html) for more info about HTML templating).
|
|
187
|
+
|
|
188
|
+
Start it from [the bundled template](templates/authorize.str), which is written for the `str` engine, rather than from a blank file:
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
cp "$(bundle info rodauth-oauth --path)/templates/authorize.str" views/authorize.str
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The form posts back to the authorize endpoint, so every authorization request parameter the grant depends on has to be carried over into it. A parameter left out is dropped from the grant silently, and only surfaces later as an error at the token endpoint, which is why adapting the bundled template beats writing one from scratch. See [the authorize form docs](doc/oauth_authorize_base.rdoc) for the parts which are easiest to lose.
|
|
187
195
|
|
|
188
196
|
### OAuth applications management
|
|
189
197
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
### 1.6.6
|
|
2
|
+
|
|
3
|
+
#### Improvements
|
|
4
|
+
|
|
5
|
+
* Authorization form params will now be exposed to the HTML rendering via the `rodauth.authorize_form_params`; this not only reduces the size of the bundled forms, but also makes it much easier to add or remove features with their own required authorize params.
|
|
6
|
+
* `:oauth_jwt_base` feature: new `:oauth_jwt_iat_leeway` option (defaults to 30, as in seconds), is used now to make the JWT claim `iat` claim tolerant to clock skew. This option is used in both the `jwt` and `json/jwt` gem integrations.
|
|
7
|
+
* `:oauth_pkce` feature: new `:oauth_pkce_allow_plain_method` option, which may disallow the usage of `"plain"` as a PKCE challenge method
|
|
8
|
+
* defaults to `true` in order not to break backwards compatibility, but this is going to change in a future version, so you are recommended to set it explicitly in order not to break your application in a future upgrade.
|
|
9
|
+
|
|
10
|
+
#### Bugfixes
|
|
11
|
+
|
|
12
|
+
* (critical) `:oauth_dynamic_client_registration` feature: `PUT /register` was incorrectly authenticating the request (responding with 200 when secret didn't match), thereby allowing anyone (else) to update application parameters.
|
|
13
|
+
* (critical) `:oauth_dynamic_client_registration` feature: `PUT /register` was not hashing the client secret when hashing on the client secret was enforced.
|
|
14
|
+
* `:oauth_authorization_code_grant` feature: escape untrusted values in the authorize form (hidden request params, the `"state"` in the cancel link, the client application name), in the authorize form post POST response form, and in the device verification template.
|
|
15
|
+
* token endpoint: enforce 4XX HTTP responses with JSON encoded body when there are errors (instead of making it rely on the "accept" HTTP header).
|
|
16
|
+
* client authentication: fallback to `oauth_default_token_endpoint_auth_methods` when the application being authentication has no `token_endpoint_auth_method` set (instead of assuming it as "none").
|
|
17
|
+
* `:oauth_dynamic_client_registration` feature: client metadata which the server does not understand is now ignored (RFC 7591 section 3.1), instead of failing the registration with an `"invalid_client_metadata"` error.
|
|
18
|
+
* `:oauth_dynamic_client_registration` feature: fix typo which was exposing `client_secret` in the registration response in cases where it shouldn't.
|
|
19
|
+
* `:oauth_dynamic_client_registration` feature: emit `"client_id_issued_at"` as epoch seconds instead of an ISO8601 string, as per [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591#section-3.2.1).
|
|
20
|
+
* `:oauth_dynamic_client_registration` feature: remove "registration_access_token" and "registration_client_uri" parameters from the payload (these are only valid for the OIDC counterpart).
|
|
21
|
+
* `json/jwt`: decoding a JWT was bypassing claims verification.
|
|
22
|
+
|
|
23
|
+
#### Security
|
|
24
|
+
|
|
25
|
+
* Using `rodauth` `timing_safe_eql?` helper method, a string comparison function which is safe against timing attacks, in client secret matching and PKCE verification.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<%= image_tag rodauth.oauth_application[rodauth.oauth_applications_logo_uri_column] %>
|
|
4
4
|
<% end %>
|
|
5
5
|
<% application_uri = rodauth.oauth_application[rodauth.oauth_applications_homepage_url_column] %>
|
|
6
|
-
<% application_name = application_uri ? link_to(rodauth.oauth_application[rodauth.oauth_applications_name_column], application_uri) : rodauth.oauth_application[rodauth.oauth_applications_name_column] %>
|
|
6
|
+
<% application_name = application_uri ? link_to(rodauth.oauth_application[rodauth.oauth_applications_name_column], application_uri) : h(rodauth.oauth_application[rodauth.oauth_applications_name_column]) %>
|
|
7
7
|
<p class="lead"><%= rodauth.authorize_page_lead(name: application_name).html_safe %></p>
|
|
8
8
|
|
|
9
9
|
<div class="list-group">
|
|
@@ -37,56 +37,14 @@
|
|
|
37
37
|
</div>
|
|
38
38
|
<% end %>
|
|
39
39
|
<% end %>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<% if rodauth.param_or_nil(oauth_param) %>
|
|
43
|
-
<%= hidden_field_tag oauth_param, rodauth.param_or_nil(oauth_param) %>
|
|
44
|
-
<% end %>
|
|
45
|
-
<% end %>
|
|
46
|
-
<% if rodauth.features.include?(:oauth_resource_indicators) && rodauth.resource_indicators %>
|
|
47
|
-
<% rodauth.resource_indicators.each do |resource| %>
|
|
48
|
-
<%= hidden_field_tag "resource", resource %>
|
|
49
|
-
<% end %>
|
|
50
|
-
<% end %>
|
|
51
|
-
<% if rodauth.features.include?(:oauth_pkce) %>
|
|
52
|
-
<% if rodauth.param_or_nil("code_challenge") %>
|
|
53
|
-
<%= hidden_field_tag :code_challenge, rodauth.param_or_nil("code_challenge") %>
|
|
54
|
-
<% end %>
|
|
55
|
-
<% if rodauth.param_or_nil("code_challenge_method") %>
|
|
56
|
-
<%= hidden_field_tag :code_challenge_method, rodauth.param_or_nil("code_challenge_method") %>
|
|
57
|
-
<% end %>
|
|
58
|
-
<% end %>
|
|
59
|
-
<% if rodauth.features.include?(:oidc) %>
|
|
60
|
-
<% if rodauth.param_or_nil("prompt") %>
|
|
61
|
-
<%= hidden_field_tag :prompt, rodauth.param_or_nil("prompt") %>
|
|
62
|
-
<% end %>
|
|
63
|
-
<% if rodauth.param_or_nil("nonce") %>
|
|
64
|
-
<%= hidden_field_tag :nonce, rodauth.param_or_nil("nonce") %>
|
|
65
|
-
<% end %>
|
|
66
|
-
<% if rodauth.param_or_nil("ui_locales") %>
|
|
67
|
-
<%= hidden_field_tag :ui_locales, rodauth.param_or_nil("ui_locales") %>
|
|
68
|
-
<% end %>
|
|
69
|
-
<% if rodauth.param_or_nil("claims_locales") %>
|
|
70
|
-
<%= hidden_field_tag :claims_locales, rodauth.param_or_nil("claims_locales") %>
|
|
71
|
-
<% end %>
|
|
72
|
-
<% if rodauth.param_or_nil("claims") %>
|
|
73
|
-
<%= hidden_field_tag :claims, sanitize(rodauth.param_or_nil("claims")) %>
|
|
74
|
-
<% end %>
|
|
75
|
-
<% if rodauth.param_or_nil("acr_values") %>
|
|
76
|
-
<%= hidden_field_tag :acr_values, rodauth.param_or_nil("acr_values") %>
|
|
77
|
-
<% end %>
|
|
78
|
-
<% if rodauth.param_or_nil("registration") %>
|
|
79
|
-
<%= hidden_field_tag :registration, rodauth.param_or_nil("registration") %>
|
|
80
|
-
<% end %>
|
|
81
|
-
<% end %>
|
|
82
|
-
<% if rodauth.features.include?(:oauth_dpop) %>
|
|
83
|
-
<% if rodauth.param_or_nil("dpop_jkt") %>
|
|
84
|
-
<%= hidden_field_tag :dpop_jkt, rodauth.param_or_nil("dpop_jkt") %>
|
|
85
|
-
<% end %>
|
|
40
|
+
<% rodauth.authorize_form_params.each do |field| %>
|
|
41
|
+
<%= tag(:input, type: field["type"], name: field["name"], value: field["value"]) %>
|
|
86
42
|
<% end %>
|
|
87
43
|
</div>
|
|
88
44
|
<p class="text-center">
|
|
89
45
|
<%= submit_tag rodauth.oauth_authorize_button, class: "btn btn-outline-primary" %>
|
|
90
|
-
|
|
46
|
+
<% cancel_url = "#{rodauth.redirect_uri}?error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request" %>
|
|
47
|
+
<% cancel_url += "&state=#{CGI.escape(rodauth.param("state"))}" if rodauth.param_or_nil("state") %>
|
|
48
|
+
<%= link_to rodauth.oauth_cancel_button, cancel_url, class: "btn btn-outline-danger" %>
|
|
91
49
|
</p>
|
|
92
50
|
<% end %>
|
|
@@ -116,7 +116,7 @@ module Rodauth
|
|
|
116
116
|
<html>
|
|
117
117
|
<head><title>Authorized</title></head>
|
|
118
118
|
<body onload="javascript:document.forms[0].submit()">
|
|
119
|
-
<form method="post" action="#{url}">
|
|
119
|
+
<form method="post" action="#{scope.h(url)}">
|
|
120
120
|
#{yield}
|
|
121
121
|
<input type="submit" class="btn btn-outline-primary" value="#{scope.h(oauth_authorize_post_button)}"/>
|
|
122
122
|
</form>
|
|
@@ -130,7 +130,7 @@ module Rodauth
|
|
|
130
130
|
<html>
|
|
131
131
|
<head><title></title></head>
|
|
132
132
|
<body onload="javascript:document.forms[0].submit()">
|
|
133
|
-
<form method="post" action="#{url}">
|
|
133
|
+
<form method="post" action="#{scope.h(url)}">
|
|
134
134
|
#{yield}
|
|
135
135
|
</form>
|
|
136
136
|
</body>
|
|
@@ -30,7 +30,8 @@ module Rodauth
|
|
|
30
30
|
|
|
31
31
|
auth_methods(
|
|
32
32
|
:resource_owner_params,
|
|
33
|
-
:oauth_grants_resource_owner_columns
|
|
33
|
+
:oauth_grants_resource_owner_columns,
|
|
34
|
+
:authorize_form_params
|
|
34
35
|
)
|
|
35
36
|
|
|
36
37
|
OAUTH_ACCESS_TYPES = %w[offline online].freeze
|
|
@@ -73,6 +74,24 @@ module Rodauth
|
|
|
73
74
|
end
|
|
74
75
|
end
|
|
75
76
|
|
|
77
|
+
# The authorization request is validated on the GET which renders the form, and again on the
|
|
78
|
+
# POST which the resource owner submits, and the latter is a fresh request. These are the
|
|
79
|
+
# params of the authorization request which the form has to carry over into it, one entry per
|
|
80
|
+
# field, each with the "name", "value" and "type" it is rendered with. Features which introduce
|
|
81
|
+
# authorization request params of their own extend it, so a view which renders all of them
|
|
82
|
+
# keeps working as features get enabled.
|
|
83
|
+
def authorize_form_params
|
|
84
|
+
params = [{ "name" => "client_id", "value" => param("client_id"), "type" => "hidden" }]
|
|
85
|
+
|
|
86
|
+
%w[access_type response_type response_mode state redirect_uri].each do |param_name|
|
|
87
|
+
if (param_value = param_or_nil(param_name))
|
|
88
|
+
params << { "name" => param_name, "value" => param_value, "type" => "hidden" }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
params
|
|
93
|
+
end
|
|
94
|
+
|
|
76
95
|
private
|
|
77
96
|
|
|
78
97
|
def validate_authorize_params
|
|
@@ -34,6 +34,15 @@ module Rodauth
|
|
|
34
34
|
auth_value_method :oauth_unique_id_generation_retries, 3
|
|
35
35
|
|
|
36
36
|
auth_value_method :oauth_token_endpoint_auth_methods_supported, %w[client_secret_basic client_secret_post]
|
|
37
|
+
# An application which has no auth method of its own falls back to these, and not to every
|
|
38
|
+
# method the server supports: enabling a new method server-wide (such as "none", which requires
|
|
39
|
+
# no client authentication at all) must not turn existing confidential clients into public ones.
|
|
40
|
+
#
|
|
41
|
+
# TODO: change this to "client_secret_basic" only in a future major version, as per
|
|
42
|
+
# https://datatracker.ietf.org/doc/html/rfc7591#section-2, which defaults an omitted
|
|
43
|
+
# "token_endpoint_auth_method" to it. Doing so now would lock out "client_secret_post" clients
|
|
44
|
+
# whose application row has no auth method set.
|
|
45
|
+
auth_value_method :oauth_default_token_endpoint_auth_methods, %w[client_secret_basic client_secret_post]
|
|
37
46
|
auth_value_method :oauth_grant_types_supported, %w[refresh_token]
|
|
38
47
|
auth_value_method :oauth_response_types_supported, []
|
|
39
48
|
auth_value_method :oauth_response_modes_supported, []
|
|
@@ -188,6 +197,11 @@ module Rodauth
|
|
|
188
197
|
def accepts_json?
|
|
189
198
|
return true if only_json?
|
|
190
199
|
|
|
200
|
+
# the token endpoint responds in json no matter what the client asks for, including its
|
|
201
|
+
# errors, which are never bounced off the client's redirect uri.
|
|
202
|
+
# https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
|
|
203
|
+
return true if request.path == token_path
|
|
204
|
+
|
|
191
205
|
(accept = request.env["HTTP_ACCEPT"]) && accept =~ json_request_regexp
|
|
192
206
|
end
|
|
193
207
|
|
|
@@ -404,10 +418,10 @@ module Rodauth
|
|
|
404
418
|
def supports_auth_method?(oauth_application, auth_method)
|
|
405
419
|
return false unless oauth_application
|
|
406
420
|
|
|
407
|
-
supported_auth_methods = if oauth_application[oauth_applications_token_endpoint_auth_method_column]
|
|
408
|
-
|
|
421
|
+
supported_auth_methods = if (auth_methods = oauth_application[oauth_applications_token_endpoint_auth_method_column])
|
|
422
|
+
auth_methods.split(/ +/)
|
|
409
423
|
else
|
|
410
|
-
|
|
424
|
+
oauth_default_token_endpoint_auth_methods
|
|
411
425
|
end
|
|
412
426
|
|
|
413
427
|
supported_auth_methods.include?(auth_method)
|
|
@@ -431,7 +445,11 @@ module Rodauth
|
|
|
431
445
|
if oauth_applications_client_secret_hash_column
|
|
432
446
|
BCrypt::Password.new(oauth_application[oauth_applications_client_secret_hash_column]) == secret
|
|
433
447
|
else
|
|
434
|
-
oauth_application[oauth_applications_client_secret_column]
|
|
448
|
+
stored_secret = oauth_application[oauth_applications_client_secret_column]
|
|
449
|
+
# A missing submitted or stored secret must never match. Without these guards, .to_s would
|
|
450
|
+
# coerce nil to "", letting an absent secret authenticate a client whose stored secret is
|
|
451
|
+
# empty/NULL. The real comparison stays constant-time via timing_safe_eql?.
|
|
452
|
+
!secret.nil? && !stored_secret.nil? && timing_safe_eql?(secret.to_s, stored_secret.to_s)
|
|
435
453
|
end
|
|
436
454
|
end
|
|
437
455
|
|
|
@@ -85,6 +85,14 @@ module Rodauth
|
|
|
85
85
|
super
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
+
def authorize_form_params
|
|
89
|
+
super.tap do |params|
|
|
90
|
+
if (param_value = param_or_nil("dpop_jkt"))
|
|
91
|
+
params << { "name" => "dpop_jkt", "value" => param_value, "type" => "hidden" }
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
88
96
|
private
|
|
89
97
|
|
|
90
98
|
def validate_token_params
|
|
@@ -12,7 +12,12 @@ 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
|
-
|
|
15
|
+
PROTECTED_APPLICATION_CREATE_ATTRIBUTES = %w[account_id client_id].freeze
|
|
16
|
+
|
|
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
|
|
19
|
+
|
|
20
|
+
HIDDEN_APPLICATION_ATTRIBUTES = %w[id account_id client_id client_secret client_secret_hash].freeze
|
|
16
21
|
|
|
17
22
|
def load_registration_client_uri_routes
|
|
18
23
|
request.on(registration_client_uri_route) do
|
|
@@ -34,19 +39,14 @@ module Rodauth
|
|
|
34
39
|
json_response_oauth_application(oauth_application)
|
|
35
40
|
end
|
|
36
41
|
request.on method: :put do
|
|
37
|
-
|
|
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
|
|
42
|
+
validate_client_registration_params(request.params, PROTECTED_APPLICATION_UPDATE_ATTRIBUTES)
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
if request.params.key?("client_secret") && !secret_matches?(oauth_application, request.params["client_secret"])
|
|
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
|
|
49
|
+
end
|
|
50
50
|
|
|
51
51
|
oauth_application = transaction do
|
|
52
52
|
applications_ds = db[oauth_applications_table]
|
|
@@ -80,7 +80,7 @@ module Rodauth
|
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
validate_client_registration_params
|
|
83
|
+
validate_client_registration_params(request.params, PROTECTED_APPLICATION_CREATE_ATTRIBUTES)
|
|
84
84
|
|
|
85
85
|
response_params = transaction do
|
|
86
86
|
before_register
|
|
@@ -119,8 +119,14 @@ module Rodauth
|
|
|
119
119
|
}
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
-
def validate_client_registration_params(request_params =
|
|
122
|
+
def validate_client_registration_params(request_params, protected_attributes = PROTECTED_APPLICATION_CREATE_ATTRIBUTES)
|
|
123
|
+
@oauth_application_unrecognized_params = []
|
|
123
124
|
@oauth_application_params = request_params.each_with_object({}) do |(key, value), params|
|
|
125
|
+
if protected_attributes.include?(key)
|
|
126
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
|
|
127
|
+
next
|
|
128
|
+
end
|
|
129
|
+
|
|
124
130
|
case key
|
|
125
131
|
when "redirect_uris"
|
|
126
132
|
if value.is_a?(Array)
|
|
@@ -179,6 +185,10 @@ module Rodauth
|
|
|
179
185
|
end
|
|
180
186
|
end
|
|
181
187
|
key = __send__(:"oauth_applications_#{key}_column")
|
|
188
|
+
when "client_secret"
|
|
189
|
+
# use internal routine which may hash the secret
|
|
190
|
+
set_client_secret(params, value)
|
|
191
|
+
next
|
|
182
192
|
when "jwks"
|
|
183
193
|
register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(value)) unless value.is_a?(Hash)
|
|
184
194
|
if request_params.key?("jwks_uri")
|
|
@@ -248,13 +258,14 @@ module Rodauth
|
|
|
248
258
|
key = __send__(:"oauth_applications_#{key}_column")
|
|
249
259
|
else
|
|
250
260
|
if respond_to?(:"oauth_applications_#{key}_column")
|
|
251
|
-
if PROTECTED_APPLICATION_ATTRIBUTES.include?(key)
|
|
252
|
-
register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
|
|
253
|
-
end
|
|
254
261
|
property = :"oauth_applications_#{key}_column"
|
|
255
262
|
key = __send__(property)
|
|
256
263
|
elsif !db[oauth_applications_table].columns.include?(key.to_sym)
|
|
257
|
-
|
|
264
|
+
# https://datatracker.ietf.org/doc/html/rfc7591#section-3.1
|
|
265
|
+
# "The authorization server MUST ignore any client metadata sent by the client that it
|
|
266
|
+
# does not understand".
|
|
267
|
+
@oauth_application_unrecognized_params << key
|
|
268
|
+
next
|
|
258
269
|
end
|
|
259
270
|
end
|
|
260
271
|
params[key] = value
|
|
@@ -285,6 +296,9 @@ module Rodauth
|
|
|
285
296
|
applications_ds = db[oauth_applications_table]
|
|
286
297
|
application_columns = applications_ds.columns
|
|
287
298
|
|
|
299
|
+
# ignored metadata is not part of the registration, so it does not belong in the response either
|
|
300
|
+
Array(@oauth_application_unrecognized_params).each { |key| return_params.delete(key) }
|
|
301
|
+
|
|
288
302
|
# set defaults
|
|
289
303
|
create_params = @oauth_application_params
|
|
290
304
|
|
|
@@ -311,7 +325,7 @@ module Rodauth
|
|
|
311
325
|
"code"
|
|
312
326
|
end
|
|
313
327
|
rescue_from_uniqueness_error do
|
|
314
|
-
|
|
328
|
+
initialize_create_params(create_params, return_params)
|
|
315
329
|
create_params.delete_if { |k, _| !application_columns.include?(k) }
|
|
316
330
|
applications_ds.insert(create_params)
|
|
317
331
|
end
|
|
@@ -319,19 +333,15 @@ module Rodauth
|
|
|
319
333
|
return_params
|
|
320
334
|
end
|
|
321
335
|
|
|
322
|
-
def
|
|
336
|
+
def initialize_create_params(create_params, return_params)
|
|
323
337
|
client_id = oauth_unique_id_generator
|
|
324
338
|
create_params[oauth_applications_client_id_column] = client_id
|
|
325
339
|
return_params["client_id"] = client_id
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
create_params[oauth_applications_registration_access_token_column] = secret_hash(registration_access_token)
|
|
330
|
-
return_params["registration_access_token"] = registration_access_token
|
|
331
|
-
return_params["registration_client_uri"] = "#{base_url}/#{registration_client_uri_route}/#{return_params['client_id']}"
|
|
340
|
+
# https://datatracker.ietf.org/doc/html/rfc7591#section-3.2.1
|
|
341
|
+
# "client_id_issued_at" is a number, expressed as seconds since 1970-01-01T00:00:00Z.
|
|
342
|
+
return_params["client_id_issued_at"] = Time.now.to_i
|
|
332
343
|
|
|
333
344
|
if create_params.key?(oauth_applications_client_secret_column)
|
|
334
|
-
set_client_secret(create_params, create_params[oauth_applications_client_secret_column])
|
|
335
345
|
return_params.delete("client_secret")
|
|
336
346
|
else
|
|
337
347
|
client_secret = oauth_unique_id_generator
|
|
@@ -404,7 +414,7 @@ module Rodauth
|
|
|
404
414
|
params = methods.map { |k| k.to_s[/\Aoauth_applications_(\w+)_column\z/, 1] }.compact
|
|
405
415
|
|
|
406
416
|
body = params.each_with_object({}) do |k, hash|
|
|
407
|
-
next if
|
|
417
|
+
next if HIDDEN_APPLICATION_ATTRIBUTES.include?(k)
|
|
408
418
|
|
|
409
419
|
value = oauth_application[__send__(:"oauth_applications_#{k}_column")]
|
|
410
420
|
|
|
@@ -18,6 +18,11 @@ module Rodauth
|
|
|
18
18
|
|
|
19
19
|
auth_value_method :oauth_jwt_jwe_copyright, nil
|
|
20
20
|
|
|
21
|
+
# Clock-skew leeway, in seconds, tolerated when rejecting a JWT whose iat (issued-at) is in the
|
|
22
|
+
# future. iat is informational per RFC 7519 section 4.1.6, so this only guards against tokens
|
|
23
|
+
# issued well ahead of now. Applies to the json-jwt verification path (see jwt_decode).
|
|
24
|
+
auth_value_method :oauth_jwt_iat_leeway, 30
|
|
25
|
+
|
|
21
26
|
auth_methods(
|
|
22
27
|
:jwt_encode,
|
|
23
28
|
:jwt_decode,
|
|
@@ -266,15 +271,28 @@ module Rodauth
|
|
|
266
271
|
end
|
|
267
272
|
|
|
268
273
|
now = Time.now
|
|
269
|
-
if verify_claims
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
274
|
+
if verify_claims
|
|
275
|
+
# Each line asserts that a claim is acceptable: absent / not checked, or valid. The token
|
|
276
|
+
# is rejected unless all of them hold. These are deliberately PASS conditions joined with
|
|
277
|
+
# `&&` -- the inverse (failure conditions joined with `&&`) only rejects when every check
|
|
278
|
+
# fails at once, which was the original verification bug. exp/nbf/iat are optional and
|
|
279
|
+
# only enforced when present (e.g. DPoP proofs carry no exp).
|
|
280
|
+
#
|
|
281
|
+
# iat is informational per RFC 7519 section 4.1.6 (RFC 9068 section 4 requires it be present,
|
|
282
|
+
# not verified), so we only reject tokens issued in the future, tolerating
|
|
283
|
+
# oauth_jwt_iat_leeway seconds of clock skew. Both backends now honor oauth_jwt_iat_leeway:
|
|
284
|
+
# the ruby-jwt path disables verify_iat (which removed iat_leeway in 2.2.0, jwt/ruby-jwt#319)
|
|
285
|
+
# and performs the same leeway'd future-iat check manually, so behavior is unified.
|
|
286
|
+
# A non-numeric iat is malformed (NumericDate per RFC 7519); reject it rather than letting
|
|
287
|
+
# Time.at raise, mirroring the ruby-jwt path so both backends reject malformed iat alike.
|
|
288
|
+
claims_valid = (!claims[:exp] || Time.at(claims[:exp]) >= now) &&
|
|
289
|
+
(!claims[:nbf] || Time.at(claims[:nbf]) <= now) &&
|
|
290
|
+
(!claims[:iat] || (claims[:iat].is_a?(Numeric) && Time.at(claims[:iat]) <= now + oauth_jwt_iat_leeway)) &&
|
|
291
|
+
(!verify_iss || claims[:iss] == oauth_jwt_issuer) &&
|
|
292
|
+
(!verify_aud || verify_aud(claims[:aud], claims[:client_id])) &&
|
|
293
|
+
(!verify_jti || verify_jti(claims[:jti], claims))
|
|
294
|
+
|
|
295
|
+
return unless claims_valid
|
|
278
296
|
end
|
|
279
297
|
|
|
280
298
|
return if verify_headers && !verify_headers.call(claims.header)
|
|
@@ -413,7 +431,12 @@ module Rodauth
|
|
|
413
431
|
#
|
|
414
432
|
# issuer: check that server generated the token
|
|
415
433
|
# aud: check the audience field (client is who he says he is)
|
|
416
|
-
#
|
|
434
|
+
# exp: check that the token hasn't expired
|
|
435
|
+
# iat: informational per RFC 7519 section 4.1.6, so we only reject a future iat, tolerating
|
|
436
|
+
# oauth_jwt_iat_leeway seconds of clock skew. ruby-jwt's verify_iat offers no leeway knob
|
|
437
|
+
# (iat_leeway was removed in 2.2.0, jwt/ruby-jwt#319), so verify_iat is disabled below and
|
|
438
|
+
# iat is checked manually -- unifying behavior with the json-jwt path, which honors the
|
|
439
|
+
# same oauth_jwt_iat_leeway.
|
|
417
440
|
#
|
|
418
441
|
# subject can't be verified automatically without having access to the account id,
|
|
419
442
|
# which we don't because that's the whole point.
|
|
@@ -425,7 +448,9 @@ module Rodauth
|
|
|
425
448
|
# can't use stock aud verification, as it's dependent on the client application id
|
|
426
449
|
verify_aud: false,
|
|
427
450
|
verify_jti: (verify_jti ? method(:verify_jti) : false),
|
|
428
|
-
|
|
451
|
+
# iat is verified manually below (with leeway) rather than via
|
|
452
|
+
# ruby-jwt's verify_iat, which rejects a future iat with no leeway.
|
|
453
|
+
verify_iat: false
|
|
429
454
|
}
|
|
430
455
|
else
|
|
431
456
|
{}
|
|
@@ -456,6 +481,16 @@ module Rodauth
|
|
|
456
481
|
JWT.decode(token, nil, true, jwks: jwks, algorithms: algorithms, **verify_claims_params)
|
|
457
482
|
end
|
|
458
483
|
|
|
484
|
+
# iat is only rejected when issued in the future, tolerating oauth_jwt_iat_leeway seconds of
|
|
485
|
+
# clock skew -- mirroring the json-jwt path so both backends behave identically. ruby-jwt
|
|
486
|
+
# claims are string-keyed. Since verify_iat is disabled above, ruby-jwt no longer validates
|
|
487
|
+
# that iat is a NumericDate, so reject a non-numeric iat here rather than letting Time.at
|
|
488
|
+
# raise an error the JWT::DecodeError handler would not catch.
|
|
489
|
+
if verify_claims && (iat = claims["iat"])
|
|
490
|
+
return unless iat.is_a?(Numeric)
|
|
491
|
+
return if Time.at(iat) > Time.now + oauth_jwt_iat_leeway
|
|
492
|
+
end
|
|
493
|
+
|
|
459
494
|
return if verify_claims && verify_aud && !verify_aud(claims["aud"], claims["client_id"])
|
|
460
495
|
|
|
461
496
|
return if verify_headers && !verify_headers.call(headers)
|
|
@@ -496,7 +531,7 @@ module Rodauth
|
|
|
496
531
|
JWT.decode(token, nil, false)
|
|
497
532
|
end
|
|
498
533
|
else
|
|
499
|
-
# :
|
|
534
|
+
# simplecov:enable
|
|
500
535
|
def jwk_export(_key)
|
|
501
536
|
raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
|
|
502
537
|
end
|
|
@@ -520,7 +555,7 @@ module Rodauth
|
|
|
520
555
|
def private_jwk?(_jwk)
|
|
521
556
|
raise "#{__method__} is undefined, redefine it or require either \"jwt\" or \"json-jwt\""
|
|
522
557
|
end
|
|
523
|
-
# :
|
|
558
|
+
# simplecov:disable
|
|
524
559
|
end
|
|
525
560
|
end
|
|
526
561
|
end
|
|
@@ -9,6 +9,11 @@ module Rodauth
|
|
|
9
9
|
auth_value_method :oauth_require_pkce, true
|
|
10
10
|
auth_value_method :oauth_pkce_challenge_method, "S256"
|
|
11
11
|
|
|
12
|
+
# TODO: change to false in a major version bump
|
|
13
|
+
# RFC 7636 §4.2 discourages the "plain" transform: it transmits the raw code_verifier,
|
|
14
|
+
# so an intercepted authorization request exposes it and defeats PKCE.
|
|
15
|
+
auth_value_method :oauth_pkce_allow_plain_method, true
|
|
16
|
+
|
|
12
17
|
auth_value_method :oauth_grants_code_challenge_column, :code_challenge
|
|
13
18
|
auth_value_method :oauth_grants_code_challenge_method_column, :code_challenge_method
|
|
14
19
|
|
|
@@ -17,6 +22,16 @@ module Rodauth
|
|
|
17
22
|
auth_value_method :oauth_unsupported_transform_algorithm_error_code, "invalid_request"
|
|
18
23
|
translatable_method :oauth_unsupported_transform_algorithm_message, "transform algorithm not supported"
|
|
19
24
|
|
|
25
|
+
def authorize_form_params
|
|
26
|
+
super.tap do |params|
|
|
27
|
+
%w[code_challenge code_challenge_method].each do |param_name|
|
|
28
|
+
if (param_value = param_or_nil(param_name))
|
|
29
|
+
params << { "name" => param_name, "value" => param_value, "type" => "hidden" }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
20
35
|
private
|
|
21
36
|
|
|
22
37
|
def supports_auth_method?(oauth_application, auth_method)
|
|
@@ -61,7 +76,13 @@ module Rodauth
|
|
|
61
76
|
if param_or_nil("code_challenge")
|
|
62
77
|
|
|
63
78
|
challenge_method = param_or_nil("code_challenge_method")
|
|
64
|
-
|
|
79
|
+
|
|
80
|
+
# Reject the weak "plain" transform unless it has been explicitly opted into. This runs
|
|
81
|
+
# before the supported-method check so the disabled method surfaces a meaningful error
|
|
82
|
+
# instead of the generic "code challenge required".
|
|
83
|
+
redirect_response_error("unsupported_transform_algorithm") if challenge_method == "plain" && !oauth_pkce_allow_plain_method
|
|
84
|
+
|
|
85
|
+
redirect_response_error("code_challenge_required") unless oauth_pkce_challenge_methods.include?(challenge_method)
|
|
65
86
|
else
|
|
66
87
|
return unless oauth_require_pkce
|
|
67
88
|
|
|
@@ -74,19 +95,29 @@ module Rodauth
|
|
|
74
95
|
|
|
75
96
|
case grant[oauth_grants_code_challenge_method_column]
|
|
76
97
|
when "plain"
|
|
77
|
-
|
|
98
|
+
# A grant stored with the weak "plain" method must not be redeemable unless the
|
|
99
|
+
# method has been explicitly enabled, otherwise PKCE is silently downgraded.
|
|
100
|
+
redirect_response_error("unsupported_transform_algorithm") unless oauth_pkce_allow_plain_method
|
|
101
|
+
timing_safe_eql?(verifier.to_s, challenge.to_s)
|
|
78
102
|
when "S256"
|
|
79
103
|
generated_challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(verifier), padding: false)
|
|
80
104
|
|
|
81
|
-
challenge
|
|
105
|
+
timing_safe_eql?(generated_challenge, challenge.to_s)
|
|
82
106
|
else
|
|
83
107
|
redirect_response_error("unsupported_transform_algorithm")
|
|
84
108
|
end
|
|
85
109
|
end
|
|
86
110
|
|
|
111
|
+
# The PKCE transform methods the server accepts. "S256" is always supported; the weak "plain"
|
|
112
|
+
# transform is only included when explicitly opted into via oauth_pkce_allow_plain_method.
|
|
113
|
+
def oauth_pkce_challenge_methods
|
|
114
|
+
oauth_pkce_allow_plain_method ? %w[S256 plain] : %w[S256]
|
|
115
|
+
end
|
|
116
|
+
|
|
87
117
|
def oauth_server_metadata_body(*)
|
|
88
118
|
super.tap do |data|
|
|
89
|
-
|
|
119
|
+
# RFC 8414: code_challenge_methods_supported is a JSON array of the supported methods.
|
|
120
|
+
data[:code_challenge_methods_supported] = oauth_pkce_challenge_methods
|
|
90
121
|
end
|
|
91
122
|
end
|
|
92
123
|
end
|
|
@@ -46,6 +46,15 @@ module Rodauth
|
|
|
46
46
|
authorization_required unless token_indicators.any? { |resource| base_url.start_with?(resource) }
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
def authorize_form_params
|
|
50
|
+
super.tap do |params|
|
|
51
|
+
# the "resource" param may be repeated, so it carries one entry per resource indicator
|
|
52
|
+
Array(resource_indicators).each do |resource|
|
|
53
|
+
params << { "name" => "resource", "value" => resource, "type" => "hidden" }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
49
58
|
private
|
|
50
59
|
|
|
51
60
|
def validate_token_params
|
|
@@ -235,6 +235,16 @@ module Rodauth
|
|
|
235
235
|
super unless subject_type == "pairwise"
|
|
236
236
|
end
|
|
237
237
|
|
|
238
|
+
def authorize_form_params
|
|
239
|
+
super.tap do |params|
|
|
240
|
+
%w[prompt nonce ui_locales claims_locales claims acr_values].each do |param_name|
|
|
241
|
+
if (param_value = param_or_nil(param_name))
|
|
242
|
+
params << { "name" => param_name, "value" => param_value, "type" => "hidden" }
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
238
248
|
private
|
|
239
249
|
|
|
240
250
|
if defined?(::I18n)
|
|
@@ -287,7 +287,7 @@ module Rodauth
|
|
|
287
287
|
"The application type '#{application_type}' is not allowed."
|
|
288
288
|
end
|
|
289
289
|
|
|
290
|
-
def
|
|
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
|
-
# :
|
|
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
|
-
# :
|
|
8
|
+
# simplecov:disable
|
|
9
9
|
|
|
10
10
|
module Rodauth
|
|
11
11
|
Feature.define(:oidc_frontchannel_logout, :OidFrontchannelLogout) do
|
|
@@ -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
|
|
@@ -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]
|
|
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]
|
|
36
|
+
return @store[key][:payload] if @store[key] && @store[key][:ttl] > now
|
|
37
37
|
|
|
38
|
-
@store[key] = { payload: payload, ttl:
|
|
38
|
+
@store[key] = { payload: payload, ttl: now + (ttl || DEFAULT_TTL) }
|
|
39
39
|
end
|
|
40
40
|
@store[key][:payload]
|
|
41
41
|
end
|
data/templates/authorize.str
CHANGED
|
@@ -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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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>
|
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.
|
|
4
|
+
version: 1.6.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tiago Cardoso
|
|
@@ -93,6 +93,7 @@ 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
|
|
96
97
|
files:
|
|
97
98
|
- CHANGELOG.md
|
|
98
99
|
- LICENSE.txt
|
|
@@ -144,6 +145,7 @@ files:
|
|
|
144
145
|
- doc/release_notes/1_6_3.md
|
|
145
146
|
- doc/release_notes/1_6_4.md
|
|
146
147
|
- doc/release_notes/1_6_5.md
|
|
148
|
+
- doc/release_notes/1_6_6.md
|
|
147
149
|
- lib/generators/rodauth/oauth/install_generator.rb
|
|
148
150
|
- lib/generators/rodauth/oauth/templates/app/models/oauth_application.rb
|
|
149
151
|
- lib/generators/rodauth/oauth/templates/app/models/oauth_grant.rb
|
|
@@ -244,7 +246,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
244
246
|
- !ruby/object:Gem::Version
|
|
245
247
|
version: '0'
|
|
246
248
|
requirements: []
|
|
247
|
-
rubygems_version:
|
|
249
|
+
rubygems_version: 4.0.16
|
|
248
250
|
specification_version: 4
|
|
249
251
|
summary: Implementation of the OAuth 2.0 protocol on top of rodauth.
|
|
250
252
|
test_files: []
|