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 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.