rodauth-oauth 1.3.0 → 1.3.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d7d5f8b68686703954bf4e335cef0ea33f9e31c94c439df84f08e8ff3270829
4
- data.tar.gz: 1da57ba2082818a74dbca4d1c6bcab0c15f97da891e12c03a8bf91440a4edcfd
3
+ metadata.gz: bf721a3632883e34bff44e33ca84c9a5aa2248748ccecf52c180edd25086abad
4
+ data.tar.gz: 59966edc773dbee470be46770532ee9159dfe370d346eca936a2ebc48a8fd224
5
5
  SHA512:
6
- metadata.gz: 8230b54e51d2081e25d1386d6294745d54eebbe11a6677bdb9cade14e0a418658bc2b8a67ae2e6355458f4b43d8a2df1700cd3e0496fa8a10e690318f3d03ba0
7
- data.tar.gz: 31ab5721a6464b751860b6896f47999e189592582842ba419ab0a057ff38af98612d54a8b00177092e2fe5993af1e5554cecafbbfaab18a495656117f19ce4fd
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/httpx/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/httpx/2021/09/08/using-rodauth-oauth-in-rails-without-rodauth-based-auth.html)
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, params[:client_id] %>
41
- <% %i[access_type response_type response_mode state redirect_uri].each do |oauth_param| %>
42
- <% if params[oauth_param] %>
43
- <%= hidden_field_tag oauth_param, params[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 params[:code_challenge] %>
53
- <%= hidden_field_tag :code_challenge, params[: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 params[:code_challenge_method] %>
56
- <%= hidden_field_tag :code_challenge_method, params[: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 params[:prompt] %>
61
- <%= hidden_field_tag :prompt, params[:prompt] %>
60
+ <% if rodauth.raw_param("prompt") %>
61
+ <%= hidden_field_tag :prompt, rodauth.raw_param("prompt") %>
62
62
  <% end %>
63
- <% if params[:nonce] %>
64
- <%= hidden_field_tag :nonce, params[:nonce] %>
63
+ <% if rodauth.raw_param("nonce") %>
64
+ <%= hidden_field_tag :nonce, rodauth.raw_param("nonce") %>
65
65
  <% end %>
66
- <% if params[:ui_locales] %>
67
- <%= hidden_field_tag :ui_locales, params[: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 params[:claims_locales] %>
70
- <%= hidden_field_tag :claims_locales, params[: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 params[:claims] %>
73
- <%= hidden_field_tag :claims, sanitize(params[:claims]) %>
72
+ <% if rodauth.raw_param("claims") %>
73
+ <%= hidden_field_tag :claims, sanitize(rodauth.raw_param("claims")) %>
74
74
  <% end %>
75
- <% if params[:acr_values] %>
76
- <%= hidden_field_tag :acr_values, params[: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 params[:registration] %>
79
- <%= hidden_field_tag :registration, params[: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 params[:state] }", class: "btn btn-outline-danger" %>
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(/:/, 2)
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 if encryption_algorithm
199
- meth = encryption_method.to_sym if encryption_method
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
- auth_value_method :oauth_applications_request_uris_column, :request_uris
15
-
16
- auth_value_method :oauth_applications_request_object_signing_alg_column, :request_object_signing_alg
17
- auth_value_method :oauth_applications_request_object_encryption_alg_column, :request_object_encryption_alg
18
- auth_value_method :oauth_applications_request_object_encryption_enc_column, :request_object_encryption_enc
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
- return super unless (request_object || request_uri) && oauth_application
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 (request_object = param_or_nil("request")) && features.include?(:oauth_jwt_secured_authorization_request)
69
- claims = decode_request_object(request_object)
70
-
71
- # https://datatracker.ietf.org/doc/html/rfc9126#section-3-5.3
72
- # reject the request if the authenticated client_id does not match the client_id claim in the Request Object
73
- if (client_id = claims["client_id"]) && (client_id != oauth_application[oauth_applications_client_id_column])
74
- redirect_response_error("invalid_request_object")
75
- end
76
-
77
- # requiring the iss claim to match the client_id is at the discretion of the authorization server
78
- if oauth_require_pushed_authorization_request_iss_request_object &&
79
- (iss = claims.delete("iss")) &&
80
- iss != oauth_application[oauth_applications_client_id_column]
81
- redirect_response_error("invalid_request_object")
82
- end
83
-
84
- if (aud = claims.delete("aud")) && !verify_aud(aud, oauth_jwt_issuer)
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")
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rodauth
4
4
  module OAuth
5
- VERSION = "1.3.0"
5
+ VERSION = "1.3.2"
6
6
  end
7
7
  end
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.0
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-04-01 00:00:00.000000000 Z
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.3.7
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.