rodauth-oauth 1.3.0 → 1.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/doc/release_notes/1_3_1.md +10 -0
- data/doc/release_notes/1_3_2.md +14 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +23 -23
- data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +1 -0
- data/lib/rodauth/features/oauth_base.rb +3 -1
- data/lib/rodauth/features/oauth_dynamic_client_registration.rb +8 -0
- data/lib/rodauth/features/oauth_jwt_base.rb +8 -2
- data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +25 -6
- data/lib/rodauth/features/oauth_pkce.rb +1 -2
- data/lib/rodauth/features/oauth_pushed_authorization_request.rb +33 -24
- data/lib/rodauth/oauth/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf721a3632883e34bff44e33ca84c9a5aa2248748ccecf52c180edd25086abad
|
4
|
+
data.tar.gz: 59966edc773dbee470be46770532ee9159dfe370d346eca936a2ebc48a8fd224
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6855059fe2d8225e4f3650dca01ad4ff3d82ad4e42623b43e5ea1d42ef3dbec317bf9488a2b1598556c135f9b9bf1d45ac41512d7347e3c5c55b7c5e8bd63305
|
7
|
+
data.tar.gz: 9a564b8ad6812841f4a2737ee263af188e5231c49b8b475f0c575a5b0496aa6f534709552845caaaacbb73dbe1afabb9079a17265683be5df73f62075a78896e
|
data/README.md
CHANGED
@@ -90,8 +90,8 @@ Or install it yourself as:
|
|
90
90
|
|
91
91
|
## Articles
|
92
92
|
|
93
|
-
* [How to use rodauth-oauth with rails and rodauth](https://honeyryderchuck.gitlab.io/
|
94
|
-
* [How to use rodauth-oauth with rails and without rodauth](https://honeyryderchuck.gitlab.io/
|
93
|
+
* [How to use rodauth-oauth with rails and rodauth](https://honeyryderchuck.gitlab.io/2021/03/15/oidc-provider-on-rails-using-rodauth-oauth.html)
|
94
|
+
* [How to use rodauth-oauth with rails and without rodauth](https://honeyryderchuck.gitlab.io/2021/09/08/using-rodauth-oauth-in-rails-without-rodauth-based-auth.html)
|
95
95
|
|
96
96
|
## Usage
|
97
97
|
|
@@ -0,0 +1,10 @@
|
|
1
|
+
### 1.3.1 (27/06/2023)
|
2
|
+
|
3
|
+
#### Bugfixes
|
4
|
+
|
5
|
+
* Set 401 error response when `client_id` parameter is invalid, or from an unexisting client application, instead of failing with a 500 (@igor-alexandrov).
|
6
|
+
* update rails authorize form to use roda request params instead, as plain params was breaking JAR and PAR-based authorize forms in rails applications.
|
7
|
+
|
8
|
+
#### Chore
|
9
|
+
|
10
|
+
* set `:padding` to `false` in `Base64.urlsafe_encode64` calls (@felipe.zavan)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
### 1.3.2 (27/07/2023)
|
2
|
+
|
3
|
+
#### Improvements
|
4
|
+
|
5
|
+
* `require_signed_request_object` option for JAR (`oauth_jwt_secured_authorization_request` plugin) is now supported:
|
6
|
+
* in the oauth server metadata endpoint
|
7
|
+
* as a plugin config option (`oauth_require_signed_request_object`, defaults to `false`)
|
8
|
+
* as a oauth dynamic registration endpoint param (`require_signed_request_object`, requires corresponding columnn)
|
9
|
+
* enforces JAR-based authorization, andd does not allow unsigned JAR JWTs, when turned on.
|
10
|
+
|
11
|
+
#### Bugfixes
|
12
|
+
|
13
|
+
* JWT decoding failed in circumstances where a declared encryption algo didn't have key/method declared.
|
14
|
+
* fix for when PAR (`oauth_pushed_authorization_request` feature) is used with JAR (`oauth_jwt_secured_authorization_request` plugin), and PAR `request_uri` param wasn't being removed when validating authorize request parameters, thereby making JAR logic evaluate it as a JAR `requuest_uri` (it is now correctly not taken into account in such a case);
|
@@ -37,10 +37,10 @@
|
|
37
37
|
</div>
|
38
38
|
<% end %>
|
39
39
|
<% end %>
|
40
|
-
<%= hidden_field_tag :client_id,
|
41
|
-
<% %
|
42
|
-
<% if
|
43
|
-
<%= hidden_field_tag oauth_param,
|
40
|
+
<%= hidden_field_tag :client_id, rodauth.raw_param("client_id") %>
|
41
|
+
<% %w[access_type response_type response_mode state redirect_uri].each do |oauth_param| %>
|
42
|
+
<% if rodauth.raw_param(oauth_param) %>
|
43
|
+
<%= hidden_field_tag oauth_param, rodauth.raw_param(oauth_param) %>
|
44
44
|
<% end %>
|
45
45
|
<% end %>
|
46
46
|
<% if rodauth.features.include?(:oauth_resource_indicators) && rodauth.resource_indicators %>
|
@@ -49,39 +49,39 @@
|
|
49
49
|
<% end %>
|
50
50
|
<% end %>
|
51
51
|
<% if rodauth.features.include?(:oauth_pkce) %>
|
52
|
-
<% if
|
53
|
-
<%= hidden_field_tag :code_challenge,
|
52
|
+
<% if rodauth.raw_param("code_challenge") %>
|
53
|
+
<%= hidden_field_tag :code_challenge, rodauth.raw_param("code_challenge") %>
|
54
54
|
<% end %>
|
55
|
-
<% if
|
56
|
-
<%= hidden_field_tag :code_challenge_method,
|
55
|
+
<% if rodauth.raw_param("code_challenge_method") %>
|
56
|
+
<%= hidden_field_tag :code_challenge_method, rodauth.raw_param("code_challenge_method") %>
|
57
57
|
<% end %>
|
58
58
|
<% end %>
|
59
59
|
<% if rodauth.features.include?(:oidc) %>
|
60
|
-
<% if
|
61
|
-
<%= hidden_field_tag :prompt,
|
60
|
+
<% if rodauth.raw_param("prompt") %>
|
61
|
+
<%= hidden_field_tag :prompt, rodauth.raw_param("prompt") %>
|
62
62
|
<% end %>
|
63
|
-
<% if
|
64
|
-
<%= hidden_field_tag :nonce,
|
63
|
+
<% if rodauth.raw_param("nonce") %>
|
64
|
+
<%= hidden_field_tag :nonce, rodauth.raw_param("nonce") %>
|
65
65
|
<% end %>
|
66
|
-
<% if
|
67
|
-
<%= hidden_field_tag :ui_locales,
|
66
|
+
<% if rodauth.raw_param("ui_locales") %>
|
67
|
+
<%= hidden_field_tag :ui_locales, rodauth.raw_param("ui_locales") %>
|
68
68
|
<% end %>
|
69
|
-
<% if
|
70
|
-
<%= hidden_field_tag :claims_locales,
|
69
|
+
<% if rodauth.raw_param("claims_locales") %>
|
70
|
+
<%= hidden_field_tag :claims_locales, rodauth.raw_param("claims_locales") %>
|
71
71
|
<% end %>
|
72
|
-
<% if
|
73
|
-
<%= hidden_field_tag :claims,
|
72
|
+
<% if rodauth.raw_param("claims") %>
|
73
|
+
<%= hidden_field_tag :claims, sanitize(rodauth.raw_param("claims")) %>
|
74
74
|
<% end %>
|
75
|
-
<% if
|
76
|
-
<%= hidden_field_tag :acr_values,
|
75
|
+
<% if rodauth.raw_param("acr_values") %>
|
76
|
+
<%= hidden_field_tag :acr_values, rodauth.raw_param("acr_values") %>
|
77
77
|
<% end %>
|
78
|
-
<% if
|
79
|
-
<%= hidden_field_tag :registration,
|
78
|
+
<% if rodauth.raw_param("registration") %>
|
79
|
+
<%= hidden_field_tag :registration, rodauth.raw_param("registration") %>
|
80
80
|
<% end %>
|
81
81
|
<% end %>
|
82
82
|
</div>
|
83
83
|
<p class="text-center">
|
84
84
|
<%= submit_tag rodauth.oauth_authorize_button, class: "btn btn-outline-primary" %>
|
85
|
-
<%= link_to rodauth.oauth_cancel_button, "#{rodauth.redirect_uri}?error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request#{"&state=\#{CGI.escape(rodauth.state)}" if
|
85
|
+
<%= link_to rodauth.oauth_cancel_button, "#{rodauth.redirect_uri}?error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request#{"&state=\#{CGI.escape(rodauth.state)}" if rodauth.raw_param("state") }", class: "btn btn-outline-danger" %>
|
86
86
|
</p>
|
87
87
|
<% end %>
|
@@ -46,6 +46,7 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
|
|
46
46
|
t.string :request_object_encryption_alg, null: true
|
47
47
|
t.string :request_object_encryption_enc, null: true
|
48
48
|
t.string :request_uris, null: true
|
49
|
+
t.boolean :require_signed_request_object, null: true
|
49
50
|
t.boolean :require_pushed_authorization_requests, null: false, default: false
|
50
51
|
|
51
52
|
# :oauth_tls_client_auth
|
@@ -367,7 +367,7 @@ module Rodauth
|
|
367
367
|
end
|
368
368
|
|
369
369
|
def require_oauth_application_from_client_secret_basic(token)
|
370
|
-
client_id, client_secret = Base64.decode64(token).split(
|
370
|
+
client_id, client_secret = Base64.decode64(token).split(":", 2)
|
371
371
|
authorization_required unless client_id
|
372
372
|
oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => client_id).first
|
373
373
|
authorization_required unless supports_auth_method?(oauth_application,
|
@@ -389,6 +389,8 @@ module Rodauth
|
|
389
389
|
end
|
390
390
|
|
391
391
|
def supports_auth_method?(oauth_application, auth_method)
|
392
|
+
return false unless oauth_application
|
393
|
+
|
392
394
|
supported_auth_methods = if oauth_application[oauth_applications_token_endpoint_auth_method_column]
|
393
395
|
oauth_application[oauth_applications_token_endpoint_auth_method_column].split(/ +/)
|
394
396
|
else
|
@@ -200,6 +200,14 @@ module Rodauth
|
|
200
200
|
when "client_name"
|
201
201
|
register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(value)) unless value.is_a?(String)
|
202
202
|
key = oauth_applications_name_column
|
203
|
+
when "require_signed_request_object"
|
204
|
+
unless respond_to?(:oauth_applications_require_signed_request_object_column)
|
205
|
+
register_throw_json_response_error("invalid_client_metadata",
|
206
|
+
register_invalid_param_message(key))
|
207
|
+
end
|
208
|
+
request_params[key] = value = convert_to_boolean(key, value)
|
209
|
+
|
210
|
+
key = oauth_applications_require_signed_request_object_column
|
203
211
|
when "require_pushed_authorization_requests"
|
204
212
|
unless respond_to?(:oauth_applications_require_pushed_authorization_requests_column)
|
205
213
|
register_throw_json_response_error("invalid_client_metadata",
|
@@ -189,14 +189,16 @@ module Rodauth
|
|
189
189
|
jwt = jwt.sign(jwk, signing_algorithm)
|
190
190
|
jwt.kid = jwk.thumbprint
|
191
191
|
|
192
|
+
return jwt.to_s unless encryption_algorithm && encryption_method
|
193
|
+
|
192
194
|
if jwks && (jwk = jwks.find { |k| k[:use] == "enc" && k[:alg] == encryption_algorithm && k[:enc] == encryption_method })
|
193
195
|
jwk = JSON::JWK.new(jwk)
|
194
196
|
jwe = jwt.encrypt(jwk, encryption_algorithm.to_sym, encryption_method.to_sym)
|
195
197
|
jwe.to_s
|
196
198
|
elsif jwe_key
|
197
199
|
jwe_key = jwe_key.first if jwe_key.is_a?(Array)
|
198
|
-
algorithm = encryption_algorithm.to_sym
|
199
|
-
meth = encryption_method.to_sym
|
200
|
+
algorithm = encryption_algorithm.to_sym
|
201
|
+
meth = encryption_method.to_sym
|
200
202
|
jwt.encrypt(jwe_key, algorithm, meth)
|
201
203
|
else
|
202
204
|
jwt.to_s
|
@@ -246,6 +248,8 @@ module Rodauth
|
|
246
248
|
jws
|
247
249
|
elsif jws_key
|
248
250
|
JSON::JWT.decode(token, jws_key)
|
251
|
+
else
|
252
|
+
JSON::JWT.decode(token, nil, jws_algorithm)
|
249
253
|
end
|
250
254
|
elsif (jwks = auth_server_jwks_set)
|
251
255
|
JSON::JWT.decode(token, JSON::JWK::Set.new(jwks))
|
@@ -428,6 +432,8 @@ module Rodauth
|
|
428
432
|
end
|
429
433
|
elsif jws_key
|
430
434
|
JWT.decode(token, jws_key, true, algorithms: [jws_algorithm], **verify_claims_params).first
|
435
|
+
else
|
436
|
+
JWT.decode(token, jws_key, false, **verify_claims_params).first
|
431
437
|
end
|
432
438
|
elsif (jwks = auth_server_jwks_set)
|
433
439
|
algorithms = jwks[:keys].select { |k| k[:use] == "sig" }.map { |k| k[:alg] }
|
@@ -9,13 +9,15 @@ module Rodauth
|
|
9
9
|
depends :oauth_authorize_base, :oauth_jwt_base
|
10
10
|
|
11
11
|
auth_value_method :oauth_require_request_uri_registration, false
|
12
|
+
auth_value_method :oauth_require_signed_request_object, false
|
12
13
|
auth_value_method :oauth_request_object_signing_alg_allow_none, false
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
%i[
|
16
|
+
request_uris require_signed_request_object request_object_signing_alg
|
17
|
+
request_object_encryption_alg request_object_encryption_enc
|
18
|
+
].each do |column|
|
19
|
+
auth_value_method :"oauth_applications_#{column}_column", column
|
20
|
+
end
|
19
21
|
|
20
22
|
translatable_method :oauth_invalid_request_object_message, "request object is invalid"
|
21
23
|
|
@@ -30,7 +32,13 @@ module Rodauth
|
|
30
32
|
|
31
33
|
request_uri = param_or_nil("request_uri")
|
32
34
|
|
33
|
-
|
35
|
+
unless (request_object || request_uri) && oauth_application
|
36
|
+
if request.path == authorize_path && request.get? && require_signed_request_object?
|
37
|
+
redirect_response_error("invalid_request_object")
|
38
|
+
end
|
39
|
+
|
40
|
+
return super
|
41
|
+
end
|
34
42
|
|
35
43
|
if request_uri
|
36
44
|
request_uri = CGI.unescape(request_uri)
|
@@ -84,6 +92,14 @@ module Rodauth
|
|
84
92
|
request_uris.nil? || request_uris.split(oauth_scope_separator).one? { |uri| request_uri.start_with?(uri) }
|
85
93
|
end
|
86
94
|
|
95
|
+
def require_signed_request_object?
|
96
|
+
return @require_signed_request_object if defined?(@require_signed_request_object)
|
97
|
+
|
98
|
+
@require_signed_request_object = (oauth_application[oauth_applications_require_signed_request_object_column] if oauth_application)
|
99
|
+
@require_signed_request_object = oauth_require_signed_request_object if @require_signed_request_object.nil?
|
100
|
+
@require_signed_request_object
|
101
|
+
end
|
102
|
+
|
87
103
|
def decode_request_object(request_object)
|
88
104
|
request_sig_enc_opts = {
|
89
105
|
jws_algorithm: oauth_application[oauth_applications_request_object_signing_alg_column],
|
@@ -94,6 +110,8 @@ module Rodauth
|
|
94
110
|
request_sig_enc_opts[:jws_algorithm] ||= "none" if oauth_request_object_signing_alg_allow_none
|
95
111
|
|
96
112
|
if request_sig_enc_opts[:jws_algorithm] == "none"
|
113
|
+
redirect_response_error("invalid_request_object") if require_signed_request_object?
|
114
|
+
|
97
115
|
jwks = nil
|
98
116
|
elsif (jwks = oauth_application_jwks(oauth_application))
|
99
117
|
jwks = JSON.parse(jwks, symbolize_names: true) if jwks.is_a?(String)
|
@@ -118,6 +136,7 @@ module Rodauth
|
|
118
136
|
data[:request_parameter_supported] = true
|
119
137
|
data[:request_uri_parameter_supported] = true
|
120
138
|
data[:require_request_uri_registration] = oauth_require_request_uri_registration
|
139
|
+
data[:require_signed_request_object] = oauth_require_signed_request_object
|
121
140
|
end
|
122
141
|
end
|
123
142
|
end
|
@@ -76,8 +76,7 @@ module Rodauth
|
|
76
76
|
when "plain"
|
77
77
|
challenge == verifier
|
78
78
|
when "S256"
|
79
|
-
generated_challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(verifier))
|
80
|
-
generated_challenge.delete_suffix!("=") while generated_challenge.end_with?("=")
|
79
|
+
generated_challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(verifier), padding: false)
|
81
80
|
|
82
81
|
challenge == generated_challenge
|
83
82
|
else
|
@@ -65,32 +65,37 @@ module Rodauth
|
|
65
65
|
# The request_uri authorization request parameter is one exception, and it MUST NOT be provided.
|
66
66
|
redirect_response_error("invalid_request") if param_or_nil("request_uri")
|
67
67
|
|
68
|
-
if
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
68
|
+
if features.include?(:oauth_jwt_secured_authorization_request)
|
69
|
+
|
70
|
+
if (request_object = param_or_nil("request"))
|
71
|
+
claims = decode_request_object(request_object)
|
72
|
+
|
73
|
+
# https://datatracker.ietf.org/doc/html/rfc9126#section-3-5.3
|
74
|
+
# reject the request if the authenticated client_id does not match the client_id claim in the Request Object
|
75
|
+
if (client_id = claims["client_id"]) && (client_id != oauth_application[oauth_applications_client_id_column])
|
76
|
+
redirect_response_error("invalid_request_object")
|
77
|
+
end
|
78
|
+
|
79
|
+
# requiring the iss claim to match the client_id is at the discretion of the authorization server
|
80
|
+
if oauth_require_pushed_authorization_request_iss_request_object &&
|
81
|
+
(iss = claims.delete("iss")) &&
|
82
|
+
iss != oauth_application[oauth_applications_client_id_column]
|
83
|
+
redirect_response_error("invalid_request_object")
|
84
|
+
end
|
85
|
+
|
86
|
+
if (aud = claims.delete("aud")) && !verify_aud(aud, oauth_jwt_issuer)
|
87
|
+
redirect_response_error("invalid_request_object")
|
88
|
+
end
|
89
|
+
|
90
|
+
claims.delete("exp")
|
91
|
+
request.params.delete("request")
|
92
|
+
|
93
|
+
claims.each do |k, v|
|
94
|
+
request.params[k.to_s] = v
|
95
|
+
end
|
96
|
+
elsif require_signed_request_object?
|
85
97
|
redirect_response_error("invalid_request_object")
|
86
98
|
end
|
87
|
-
|
88
|
-
claims.delete("exp")
|
89
|
-
request.params.delete("request")
|
90
|
-
|
91
|
-
claims.each do |k, v|
|
92
|
-
request.params[k.to_s] = v
|
93
|
-
end
|
94
99
|
end
|
95
100
|
|
96
101
|
validate_authorize_params
|
@@ -118,6 +123,10 @@ module Rodauth
|
|
118
123
|
request.params[k.to_s] = v
|
119
124
|
end
|
120
125
|
|
126
|
+
request.params.delete("request_uri")
|
127
|
+
|
128
|
+
# we're removing the request_uri here, so the checkup for signed reqest has to be invalidated.
|
129
|
+
@require_signed_request_object = false
|
121
130
|
elsif oauth_require_pushed_authorization_requests ||
|
122
131
|
(oauth_application && oauth_application[oauth_applications_require_pushed_authorization_requests_column])
|
123
132
|
redirect_authorize_error("request_uri")
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rodauth-oauth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tiago Cardoso
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-07-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rodauth
|
@@ -70,6 +70,8 @@ extra_rdoc_files:
|
|
70
70
|
- doc/release_notes/1_1_0.md
|
71
71
|
- doc/release_notes/1_2_0.md
|
72
72
|
- doc/release_notes/1_3_0.md
|
73
|
+
- doc/release_notes/1_3_1.md
|
74
|
+
- doc/release_notes/1_3_2.md
|
73
75
|
files:
|
74
76
|
- CHANGELOG.md
|
75
77
|
- LICENSE.txt
|
@@ -111,6 +113,8 @@ files:
|
|
111
113
|
- doc/release_notes/1_1_0.md
|
112
114
|
- doc/release_notes/1_2_0.md
|
113
115
|
- doc/release_notes/1_3_0.md
|
116
|
+
- doc/release_notes/1_3_1.md
|
117
|
+
- doc/release_notes/1_3_2.md
|
114
118
|
- lib/generators/rodauth/oauth/install_generator.rb
|
115
119
|
- lib/generators/rodauth/oauth/templates/app/models/oauth_application.rb
|
116
120
|
- lib/generators/rodauth/oauth/templates/app/models/oauth_grant.rb
|
@@ -204,7 +208,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
204
208
|
- !ruby/object:Gem::Version
|
205
209
|
version: '0'
|
206
210
|
requirements: []
|
207
|
-
rubygems_version: 3.
|
211
|
+
rubygems_version: 3.4.10
|
208
212
|
signing_key:
|
209
213
|
specification_version: 4
|
210
214
|
summary: Implementation of the OAuth 2.0 protocol on top of rodauth.
|