rodauth-oauth 1.0.0.pre.beta1 → 1.0.0.pre.beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -5
- data/doc/release_notes/1_0_0_beta2.md +34 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +19 -7
- data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +54 -43
- data/lib/rodauth/features/oauth_application_management.rb +2 -2
- data/lib/rodauth/features/oauth_authorization_code_grant.rb +31 -6
- data/lib/rodauth/features/oauth_authorize_base.rb +16 -6
- data/lib/rodauth/features/oauth_base.rb +35 -16
- data/lib/rodauth/features/oauth_dynamic_client_registration.rb +7 -4
- data/lib/rodauth/features/oauth_implicit_grant.rb +6 -5
- data/lib/rodauth/features/oauth_jwt.rb +3 -3
- data/lib/rodauth/features/oauth_jwt_base.rb +29 -6
- data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +7 -4
- data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +64 -10
- data/lib/rodauth/features/oauth_resource_indicators.rb +0 -4
- data/lib/rodauth/features/oauth_resource_server.rb +3 -3
- data/lib/rodauth/features/oauth_saml_bearer_grant.rb +2 -0
- data/lib/rodauth/features/oidc.rb +231 -183
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +65 -25
- data/lib/rodauth/features/oidc_rp_initiated_logout.rb +115 -0
- data/lib/rodauth/oauth/http_extensions.rb +15 -2
- data/lib/rodauth/oauth/ttl_store.rb +2 -0
- data/lib/rodauth/oauth/version.rb +1 -1
- data/locales/en.yml +3 -1
- data/locales/pt.yml +3 -1
- data/templates/authorize.str +17 -10
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f96bced835f21567ea5603751e8cf06b53d4eae70d9cab8bc685e2fca8c2027
|
4
|
+
data.tar.gz: 59badde6a055fa2638bcb41b01534f0b5b8de9eac541ac596f25e6d5cd6fb043
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 492a5c3c12bcc678c5eb6171e9d9851db745412fdb3675674c61b512dbfd1ff1aec09da02a3d4df3acb9133148990538200c49ce05de7a34dea85107aebf151b
|
7
|
+
data.tar.gz: 65f1223065b2a0bce609b4137b8fadd206fffa9904caac97c41dc4d429528dea474a8b450128409ed164f6407c690e95a3e7c4a83b8f9302fb41d0336e132dea
|
data/README.md
CHANGED
@@ -35,11 +35,12 @@ This gem implements the following RFCs and features of OAuth:
|
|
35
35
|
|
36
36
|
It also implements the [OpenID Connect layer](https://openid.net/connect/) (via the `openid` feature) on top of the OAuth features it provides, including:
|
37
37
|
|
38
|
-
*
|
39
|
-
* [OpenID Connect
|
40
|
-
* [OpenID
|
41
|
-
* [OpenID
|
42
|
-
* [
|
38
|
+
* `oidc`
|
39
|
+
* [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html);
|
40
|
+
* [OpenID Connect Discovery](https://openid.net/specs/openid-connect-discovery-1_0-29.html);
|
41
|
+
* [OpenID Multiple Response Types](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html);
|
42
|
+
* `oidc_dynamic_client_registration` - [OpenID Connect Dynamic Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html);
|
43
|
+
* `oidc_rp_initiated_logout` - [RP Initiated Logout](https://openid.net/specs/openid-connect-rpinitiated-1_0.html);
|
43
44
|
|
44
45
|
This gem supports also rails (through [rodauth-rails]((https://github.com/janko/rodauth-rails))).
|
45
46
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
This version passes the conformance tests for the following OpenID Connect certification profiles:
|
2
|
+
|
3
|
+
* Basic certification
|
4
|
+
* Form-post basic certification
|
5
|
+
* Config certification
|
6
|
+
* Dynamic Config certification (`response_type=code`)
|
7
|
+
|
8
|
+
## Breaking Changes
|
9
|
+
|
10
|
+
* homepage url is no longer a client application required property.
|
11
|
+
* OIDC RP-initiated logout extracted into `oidc_rp_initiated_logout` feature.
|
12
|
+
|
13
|
+
## Features
|
14
|
+
|
15
|
+
* `oauth_jwt_secured_authorization_request` now supports a `request_uri` query param as well.
|
16
|
+
* `oidc` supports essential claims, via the `claims` authorization request query parameter.
|
17
|
+
|
18
|
+
## Improvements
|
19
|
+
|
20
|
+
* exposing `acr_values_supported` in the openid configuration.
|
21
|
+
* `oauth_request_object_signing_alg_allow_none` enables `"none"` as an accepted request object signing alg when `true` (`false` by default).
|
22
|
+
* OIDC `offline_access` supported.
|
23
|
+
|
24
|
+
## Bugfixes
|
25
|
+
|
26
|
+
* JWT: "sub" is now always a string.
|
27
|
+
* `response_type` is now an authorization request required parameter (as per the RFC).
|
28
|
+
* `state` is now passed along when redirecting from authorization requeests with `error`;
|
29
|
+
* access token can now be read from POST body or GET quety params (as per the RFC).
|
30
|
+
* id token no longer shipping with claims with `null` value;
|
31
|
+
* id token no longer encoding claims by default (only when `response_type=id_token`, as per the RFC).
|
32
|
+
* support "JWT without kid" when doing jwt decoding for JWT tokens not generated in the provider (such as request objects).
|
33
|
+
* Set `iss` and `aud` claims in the Userinfo JWT response.
|
34
|
+
* Make sure errors are also delivered via form POST, when `response_mode=form_post`.
|
@@ -2,7 +2,9 @@
|
|
2
2
|
<% if rodauth.oauth_application[rodauth.oauth_applications_logo_uri_column] %>
|
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] %>
|
7
|
+
<p class="lead"><%= rodauth.authorize_page_lead(name: application_name).html_safe %></p>
|
6
8
|
|
7
9
|
<div class="list-group">
|
8
10
|
<% if rodauth.oauth_application[rodauth.oauth_applications_tos_uri_column] %>
|
@@ -26,10 +28,14 @@
|
|
26
28
|
<h1 class="display-6"><%= rodauth.oauth_grants_scopes_label %></h1>
|
27
29
|
|
28
30
|
<% rodauth.authorize_scopes.each do |scope| %>
|
29
|
-
|
30
|
-
<%=
|
31
|
-
|
32
|
-
|
31
|
+
<% if rodauth.features.include?(:oidc) && scope == "offline_access" %>
|
32
|
+
<%= hidden_field_tag "scope[]", scope %>
|
33
|
+
<% else %>
|
34
|
+
<div class="form-check">
|
35
|
+
<%= check_box_tag "scope[]", scope, id: scope, class: "form-check-input" %>
|
36
|
+
<%= label_tag scope, scope, class: "form-check-label" %>
|
37
|
+
</div>
|
38
|
+
<% end %>
|
33
39
|
<% end %>
|
34
40
|
<%= hidden_field_tag :client_id, params[:client_id] %>
|
35
41
|
<% %i[access_type response_type response_mode state redirect_uri].each do |oauth_param| %>
|
@@ -51,6 +57,9 @@
|
|
51
57
|
<% end %>
|
52
58
|
<% end %>
|
53
59
|
<% if rodauth.features.include?(:oidc) %>
|
60
|
+
<% if params[:prompt] %>
|
61
|
+
<%= hidden_field_tag :prompt, params[:prompt] %>
|
62
|
+
<% end %>
|
54
63
|
<% if params[:nonce] %>
|
55
64
|
<%= hidden_field_tag :nonce, params[:nonce] %>
|
56
65
|
<% end %>
|
@@ -60,13 +69,16 @@
|
|
60
69
|
<% if params[:claims_locales] %>
|
61
70
|
<%= hidden_field_tag :claims_locales, params[:claims_locales] %>
|
62
71
|
<% end %>
|
72
|
+
<% if params[:claims] %>
|
73
|
+
<%= hidden_field_tag :claims, sanitize(params[:claims]) %>
|
74
|
+
<% end %>
|
63
75
|
<% if params[:acr_values] %>
|
64
|
-
<%= hidden_field_tag :
|
76
|
+
<%= hidden_field_tag :acr_values, params[:acr_values] %>
|
65
77
|
<% end %>
|
66
78
|
<% end %>
|
67
79
|
</div>
|
68
80
|
<p class="text-center">
|
69
81
|
<%= submit_tag rodauth.oauth_authorize_button, class: "btn btn-outline-primary" %>
|
70
|
-
<%= link_to rodauth.oauth_cancel_button, "#{rodauth.redirect_uri}?error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request#{"&state=\#{rodauth.state}" if params[:state] }", class: "btn btn-outline-danger" %>
|
82
|
+
<%= 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" %>
|
71
83
|
</p>
|
72
84
|
<% end %>
|
@@ -5,42 +5,48 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
|
|
5
5
|
t.foreign_key :accounts, column: :account_id
|
6
6
|
t.string :name, null: false
|
7
7
|
t.string :description, null: true
|
8
|
-
t.string :homepage_url, null:
|
8
|
+
t.string :homepage_url, null: true
|
9
9
|
t.string :redirect_uri, null: false
|
10
10
|
t.string :client_id, null: false, index: { unique: true }
|
11
11
|
t.string :client_secret, null: false, index: { unique: true }
|
12
12
|
t.string :scopes, null: false
|
13
13
|
t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
|
14
|
-
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
#
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
#
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
#
|
43
|
-
|
14
|
+
|
15
|
+
# :oauth_dynamic_client_configuration enabled, extra optional params
|
16
|
+
t.string :token_endpoint_auth_method, null: true
|
17
|
+
t.string :grant_types, null: true
|
18
|
+
t.string :response_types, null: true
|
19
|
+
t.string :client_uri, null: true
|
20
|
+
t.string :logo_uri, null: true
|
21
|
+
t.string :tos_uri, null: true
|
22
|
+
t.string :policy_uri, null: true
|
23
|
+
t.string :jwks_uri, null: true
|
24
|
+
t.string :jwks, null: true
|
25
|
+
t.string :contacts, null: true
|
26
|
+
t.string :software_id, null: true
|
27
|
+
t.string :software_version, null: true
|
28
|
+
|
29
|
+
# :oidc_dynamic_client_configuration enabled, extra optional params
|
30
|
+
t.string :sector_identifier_uri, null: true
|
31
|
+
t.string :application_type, null: true
|
32
|
+
|
33
|
+
# :oidc enabled
|
34
|
+
t.string :subject_type, null: true
|
35
|
+
t.string :id_token_signed_response_alg, null: true
|
36
|
+
t.string :id_token_encrypted_response_alg, null: true
|
37
|
+
t.string :id_token_encrypted_response_enc, null: true
|
38
|
+
t.string :userinfo_signed_response_alg, null: true
|
39
|
+
t.string :userinfo_encrypted_response_alg, null: true
|
40
|
+
t.string :userinfo_encrypted_response_enc, null: true
|
41
|
+
|
42
|
+
# :oauth_jwt_secured_authorization_request
|
43
|
+
t.string :request_object_signing_alg, null: true
|
44
|
+
t.string :request_object_encryption_alg, null: true
|
45
|
+
t.string :request_object_encryption_enc, null: true
|
46
|
+
t.string :request_uris, null: true
|
47
|
+
|
48
|
+
# :oidc_rp_initiated_logout enabled
|
49
|
+
t.string :post_logout_redirect_uris, null: false
|
44
50
|
end
|
45
51
|
|
46
52
|
create_table :oauth_grants do |t|
|
@@ -58,19 +64,24 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
|
|
58
64
|
t.datetime :revoked_at
|
59
65
|
t.string :scopes, null: false
|
60
66
|
t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
|
61
|
-
# for using access_types
|
62
67
|
t.string :access_type, null: false, default: "offline"
|
63
|
-
|
64
|
-
#
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
#
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
#
|
73
|
-
|
68
|
+
|
69
|
+
# :oauth_pkce enabled
|
70
|
+
t.string :code_challenge
|
71
|
+
t.string :code_challenge_method
|
72
|
+
|
73
|
+
# :oauth_device_code_grant enabled
|
74
|
+
t.string :user_code, null: true, unique: true
|
75
|
+
t.datetime :last_polled_at, null: true
|
76
|
+
|
77
|
+
# :resource_indicators enabled
|
78
|
+
t.string :resource
|
79
|
+
|
80
|
+
# :oidc enabled
|
81
|
+
t.string :nonce
|
82
|
+
t.string :acr
|
83
|
+
t.string :claims_locales
|
84
|
+
t.string :claims
|
74
85
|
end
|
75
86
|
end
|
76
87
|
end
|
@@ -165,10 +165,10 @@ module Rodauth
|
|
165
165
|
value.each do |uri|
|
166
166
|
next if uri.empty?
|
167
167
|
|
168
|
-
set_field_error(key, invalid_url_message) unless
|
168
|
+
set_field_error(key, invalid_url_message) unless check_valid_no_fragment_uri?(uri)
|
169
169
|
end
|
170
170
|
else
|
171
|
-
set_field_error(key, invalid_url_message) unless
|
171
|
+
set_field_error(key, invalid_url_message) unless check_valid_no_fragment_uri?(value)
|
172
172
|
end
|
173
173
|
elsif key == oauth_application_scopes_param
|
174
174
|
|
@@ -25,9 +25,9 @@ module Rodauth
|
|
25
25
|
def validate_authorize_params
|
26
26
|
super
|
27
27
|
|
28
|
-
|
28
|
+
response_mode = param_or_nil("response_mode")
|
29
29
|
|
30
|
-
redirect_response_error("invalid_request")
|
30
|
+
redirect_response_error("invalid_request") if response_mode && !oauth_response_modes_supported.include?(response_mode)
|
31
31
|
end
|
32
32
|
|
33
33
|
def validate_token_params
|
@@ -46,7 +46,6 @@ module Rodauth
|
|
46
46
|
|
47
47
|
case response_type
|
48
48
|
when "code", nil
|
49
|
-
response_mode ||= oauth_response_mode
|
50
49
|
response_params.replace(_do_authorize_code)
|
51
50
|
end
|
52
51
|
|
@@ -68,7 +67,7 @@ module Rodauth
|
|
68
67
|
redirect_url = URI.parse(redirect_uri)
|
69
68
|
case mode
|
70
69
|
when "query"
|
71
|
-
params = params.map { |k, v| "#{k}=#{v}" }
|
70
|
+
params = params.map { |k, v| "#{CGI.escape(k)}=#{CGI.escape(v)}" }
|
72
71
|
params << redirect_url.query if redirect_url.query
|
73
72
|
redirect_url.query = params.join("&")
|
74
73
|
redirect(redirect_url.to_s)
|
@@ -80,7 +79,7 @@ module Rodauth
|
|
80
79
|
<form method="post" action="#{redirect_uri}">
|
81
80
|
#{
|
82
81
|
params.map do |name, value|
|
83
|
-
"<input type=\"hidden\" name=\"#{name}\" value=\"#{scope.h(value)}\" />"
|
82
|
+
"<input type=\"hidden\" name=\"#{scope.h(name)}\" value=\"#{scope.h(value)}\" />"
|
84
83
|
end.join
|
85
84
|
}
|
86
85
|
<input type="submit" class="btn btn-outline-primary" value="#{scope.h(oauth_authorize_post_button)}"/>
|
@@ -91,6 +90,32 @@ module Rodauth
|
|
91
90
|
end
|
92
91
|
end
|
93
92
|
|
93
|
+
def _redirect_response_error(redirect_url, query_params)
|
94
|
+
response_mode = param_or_nil("response_mode") || oauth_response_mode
|
95
|
+
|
96
|
+
case response_mode
|
97
|
+
when "form_post"
|
98
|
+
response["Content-Type"] = "text/html"
|
99
|
+
response.write <<-FORM
|
100
|
+
<html>
|
101
|
+
<head><title></title></head>
|
102
|
+
<body onload="javascript:document.forms[0].submit()">
|
103
|
+
<form method="post" action="#{redirect_uri}">
|
104
|
+
#{
|
105
|
+
query_params.map do |name, value|
|
106
|
+
"<input type=\"hidden\" name=\"#{name}\" value=\"#{scope.h(value)}\" />"
|
107
|
+
end.join
|
108
|
+
}
|
109
|
+
</form>
|
110
|
+
</body>
|
111
|
+
</html>
|
112
|
+
FORM
|
113
|
+
request.halt
|
114
|
+
else
|
115
|
+
super
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
94
119
|
def create_token(grant_type)
|
95
120
|
return super unless supported_grant_type?(grant_type, "authorization_code")
|
96
121
|
|
@@ -107,7 +132,7 @@ module Rodauth
|
|
107
132
|
def check_valid_response_type?
|
108
133
|
response_type = param_or_nil("response_type")
|
109
134
|
|
110
|
-
response_type
|
135
|
+
response_type == "code" || response_type == "none" || super
|
111
136
|
end
|
112
137
|
|
113
138
|
def oauth_server_metadata_body(*)
|
@@ -23,6 +23,8 @@ module Rodauth
|
|
23
23
|
translatable_method :oauth_applications_contacts_label, "Contacts"
|
24
24
|
translatable_method :oauth_applications_tos_uri_label, "Terms of service URL"
|
25
25
|
translatable_method :oauth_applications_policy_uri_label, "Policy URL"
|
26
|
+
translatable_method :oauth_unsupported_response_type_message, "Unsupported response type"
|
27
|
+
translatable_method :oauth_authorize_parameter_required, "'%<parameter>s' is a required parameter"
|
26
28
|
|
27
29
|
# /authorize
|
28
30
|
auth_server_route(:authorize) do |r|
|
@@ -65,7 +67,7 @@ module Rodauth
|
|
65
67
|
def validate_authorize_params
|
66
68
|
redirect_response_error("invalid_request", request.referer || default_redirect) unless oauth_application && check_valid_redirect_uri?
|
67
69
|
|
68
|
-
redirect_response_error("
|
70
|
+
redirect_response_error("unsupported_response_type") unless check_valid_response_type?
|
69
71
|
|
70
72
|
redirect_response_error("invalid_request") unless check_valid_access_type? && check_valid_approval_prompt?
|
71
73
|
|
@@ -79,7 +81,15 @@ module Rodauth
|
|
79
81
|
end
|
80
82
|
|
81
83
|
def check_valid_redirect_uri?
|
82
|
-
oauth_application[oauth_applications_redirect_uri_column].split(" ")
|
84
|
+
application_redirect_uris = oauth_application[oauth_applications_redirect_uri_column].split(" ")
|
85
|
+
|
86
|
+
if (redirect_uri = param_or_nil("redirect_uri"))
|
87
|
+
application_redirect_uris.include?(redirect_uri)
|
88
|
+
else
|
89
|
+
set_error_flash(oauth_authorize_parameter_required(parameter: "redirect_uri")) if application_redirect_uris.size > 1
|
90
|
+
|
91
|
+
true
|
92
|
+
end
|
83
93
|
end
|
84
94
|
|
85
95
|
ACCESS_TYPES = %w[offline online].freeze
|
@@ -140,10 +150,10 @@ module Rodauth
|
|
140
150
|
end
|
141
151
|
|
142
152
|
def create_oauth_grant(create_params = {})
|
143
|
-
create_params[oauth_grants_oauth_application_id_column]
|
144
|
-
create_params[oauth_grants_redirect_uri_column]
|
145
|
-
create_params[oauth_grants_expires_in_column]
|
146
|
-
create_params[oauth_grants_scopes_column]
|
153
|
+
create_params[oauth_grants_oauth_application_id_column] ||= oauth_application[oauth_applications_id_column]
|
154
|
+
create_params[oauth_grants_redirect_uri_column] ||= redirect_uri
|
155
|
+
create_params[oauth_grants_expires_in_column] ||= Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_grant_expires_in)
|
156
|
+
create_params[oauth_grants_scopes_column] ||= scopes.join(oauth_scope_separator)
|
147
157
|
|
148
158
|
if use_oauth_access_type? && (access_type = param_or_nil("access_type"))
|
149
159
|
create_params[oauth_grants_access_type_column] = access_type
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "time"
|
4
4
|
require "base64"
|
5
5
|
require "securerandom"
|
6
|
+
require "cgi"
|
6
7
|
require "rodauth/version"
|
7
8
|
require "rodauth/oauth"
|
8
9
|
require "rodauth/oauth/database_extensions"
|
@@ -17,8 +18,6 @@ module Rodauth
|
|
17
18
|
auth_value_methods(:http_request)
|
18
19
|
auth_value_methods(:http_request_cache)
|
19
20
|
|
20
|
-
SCOPES = %w[profile.read].freeze
|
21
|
-
|
22
21
|
before "token"
|
23
22
|
|
24
23
|
error_flash "Please authorize to continue", "require_authorization"
|
@@ -82,7 +81,7 @@ module Rodauth
|
|
82
81
|
auth_value_method :oauth_already_in_use_response_status, 409
|
83
82
|
|
84
83
|
# Feature options
|
85
|
-
auth_value_method :oauth_application_scopes,
|
84
|
+
auth_value_method :oauth_application_scopes, []
|
86
85
|
auth_value_method :oauth_token_type, "bearer"
|
87
86
|
auth_value_method :oauth_refresh_token_protection_policy, "rotation" # can be: none, sender_constrained, rotation
|
88
87
|
|
@@ -228,13 +227,20 @@ module Rodauth
|
|
228
227
|
end
|
229
228
|
|
230
229
|
def fetch_access_token
|
231
|
-
|
230
|
+
if (token = request.params["access_token"])
|
231
|
+
if request.post? && !(request.content_type.start_with?("application/x-www-form-urlencoded") &&
|
232
|
+
request.params.size == 1)
|
233
|
+
return
|
234
|
+
end
|
235
|
+
else
|
236
|
+
value = request.env["HTTP_AUTHORIZATION"]
|
232
237
|
|
233
|
-
|
238
|
+
return unless value && !value.empty?
|
234
239
|
|
235
|
-
|
240
|
+
scheme, token = value.split(" ", 2)
|
236
241
|
|
237
|
-
|
242
|
+
return unless scheme.downcase == oauth_token_type
|
243
|
+
end
|
238
244
|
|
239
245
|
return if token.nil? || token.empty?
|
240
246
|
|
@@ -245,11 +251,11 @@ module Rodauth
|
|
245
251
|
return @authorization_token if defined?(@authorization_token)
|
246
252
|
|
247
253
|
# check if there is a token
|
248
|
-
|
254
|
+
access_token = fetch_access_token
|
249
255
|
|
250
|
-
return unless
|
256
|
+
return unless access_token
|
251
257
|
|
252
|
-
@authorization_token = oauth_grant_by_token(
|
258
|
+
@authorization_token = oauth_grant_by_token(access_token)
|
253
259
|
end
|
254
260
|
|
255
261
|
def require_oauth_authorization(*scopes)
|
@@ -758,22 +764,31 @@ module Rodauth
|
|
758
764
|
query_params = []
|
759
765
|
|
760
766
|
query_params << if respond_to?(:"oauth_#{error_code}_error_code")
|
761
|
-
"error
|
767
|
+
["error", send(:"oauth_#{error_code}_error_code")]
|
762
768
|
else
|
763
|
-
"error
|
769
|
+
["error", error_code]
|
764
770
|
end
|
765
771
|
|
766
772
|
if respond_to?(:"oauth_#{error_code}_message")
|
767
773
|
message = send(:"oauth_#{error_code}_message")
|
768
|
-
query_params << ["error_description
|
774
|
+
query_params << ["error_description", CGI.escape(message)]
|
769
775
|
end
|
770
776
|
|
771
|
-
|
772
|
-
|
773
|
-
|
777
|
+
state = param_or_nil("state")
|
778
|
+
|
779
|
+
query_params << ["state", state] if state
|
780
|
+
|
781
|
+
_redirect_response_error(redirect_url, query_params)
|
774
782
|
end
|
775
783
|
end
|
776
784
|
|
785
|
+
def _redirect_response_error(redirect_url, query_params)
|
786
|
+
query_params = query_params.map { |k, v| "#{k}=#{v}" }
|
787
|
+
query_params << redirect_url.query if redirect_url.query
|
788
|
+
redirect_url.query = query_params.join("&")
|
789
|
+
redirect(redirect_url.to_s)
|
790
|
+
end
|
791
|
+
|
777
792
|
def json_response_success(body, cache = false)
|
778
793
|
response.status = 200
|
779
794
|
response["Content-Type"] ||= json_response_content_type
|
@@ -835,6 +850,10 @@ module Rodauth
|
|
835
850
|
URI::DEFAULT_PARSER.make_regexp(oauth_valid_uri_schemes).match?(uri)
|
836
851
|
end
|
837
852
|
|
853
|
+
def check_valid_no_fragment_uri?(uri)
|
854
|
+
check_valid_uri?(uri) && URI.parse(uri).fragment.nil?
|
855
|
+
end
|
856
|
+
|
838
857
|
# Resource server mode
|
839
858
|
|
840
859
|
def authorization_server_metadata
|
@@ -8,7 +8,7 @@ module Rodauth
|
|
8
8
|
|
9
9
|
before "register"
|
10
10
|
|
11
|
-
auth_value_method :oauth_client_registration_required_params, %w[redirect_uris client_name
|
11
|
+
auth_value_method :oauth_client_registration_required_params, %w[redirect_uris client_name]
|
12
12
|
|
13
13
|
PROTECTED_APPLICATION_ATTRIBUTES = %w[account_id client_id].freeze
|
14
14
|
|
@@ -68,7 +68,10 @@ module Rodauth
|
|
68
68
|
when "redirect_uris"
|
69
69
|
if value.is_a?(Array)
|
70
70
|
value = value.each do |uri|
|
71
|
-
|
71
|
+
unless check_valid_no_fragment_uri?(uri)
|
72
|
+
register_throw_json_response_error("invalid_redirect_uri",
|
73
|
+
register_invalid_uri_message(uri))
|
74
|
+
end
|
72
75
|
end.join(" ")
|
73
76
|
else
|
74
77
|
register_throw_json_response_error("invalid_redirect_uri", register_invalid_uri_message(value))
|
@@ -76,7 +79,7 @@ module Rodauth
|
|
76
79
|
key = oauth_applications_redirect_uri_column
|
77
80
|
when "token_endpoint_auth_method"
|
78
81
|
unless oauth_token_endpoint_auth_methods_supported.include?(value)
|
79
|
-
register_throw_json_response_error("invalid_client_metadata",
|
82
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(key, value))
|
80
83
|
end
|
81
84
|
# verify if in range
|
82
85
|
key = oauth_applications_token_endpoint_auth_method_column
|
@@ -84,7 +87,7 @@ module Rodauth
|
|
84
87
|
if value.is_a?(Array)
|
85
88
|
value = value.each do |grant_type|
|
86
89
|
unless oauth_grant_types_supported.include?(grant_type)
|
87
|
-
register_throw_json_response_error("invalid_client_metadata",
|
90
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(grant_type, value))
|
88
91
|
end
|
89
92
|
end.join(" ")
|
90
93
|
else
|
@@ -28,23 +28,24 @@ module Rodauth
|
|
28
28
|
|
29
29
|
redirect_response_error("invalid_request") unless supported_response_mode?(response_mode)
|
30
30
|
|
31
|
-
|
31
|
+
oauth_grant = _do_authorize_token
|
32
|
+
|
33
|
+
response_params.replace(json_access_token_payload(oauth_grant))
|
32
34
|
|
33
35
|
response_params["state"] = param("state") if param_or_nil("state")
|
34
36
|
|
35
37
|
[response_params, response_mode]
|
36
38
|
end
|
37
39
|
|
38
|
-
def _do_authorize_token
|
40
|
+
def _do_authorize_token(grant_params = {})
|
39
41
|
grant_params = {
|
40
42
|
oauth_grants_type_column => "implicit",
|
41
43
|
oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
|
42
44
|
oauth_grants_scopes_column => scopes,
|
43
45
|
oauth_grants_account_id_column => account_id
|
44
|
-
}
|
45
|
-
oauth_grant = generate_token(grant_params, false)
|
46
|
+
}.merge(grant_params)
|
46
47
|
|
47
|
-
|
48
|
+
generate_token(grant_params, false)
|
48
49
|
end
|
49
50
|
|
50
51
|
def authorize_response(params, mode)
|
@@ -49,11 +49,11 @@ module Rodauth
|
|
49
49
|
return @authorization_token if defined?(@authorization_token)
|
50
50
|
|
51
51
|
@authorization_token = begin
|
52
|
-
|
52
|
+
access_token = fetch_access_token
|
53
53
|
|
54
|
-
return unless
|
54
|
+
return unless access_token
|
55
55
|
|
56
|
-
jwt_claims = jwt_decode(
|
56
|
+
jwt_claims = jwt_decode(access_token)
|
57
57
|
|
58
58
|
return unless jwt_claims
|
59
59
|
|
@@ -63,7 +63,11 @@ module Rodauth
|
|
63
63
|
end
|
64
64
|
|
65
65
|
def jwt_subject(oauth_grant, client_application = oauth_application)
|
66
|
-
oauth_grant[oauth_grants_account_id_column]
|
66
|
+
account_id = oauth_grant[oauth_grants_account_id_column]
|
67
|
+
|
68
|
+
return account_id.to_s if account_id
|
69
|
+
|
70
|
+
client_application[oauth_applications_client_id_column]
|
67
71
|
end
|
68
72
|
|
69
73
|
def oauth_server_metadata_body(path = nil)
|
@@ -207,14 +211,23 @@ module Rodauth
|
|
207
211
|
|
208
212
|
claims = if is_authorization_server?
|
209
213
|
if jwks
|
214
|
+
jwks = jwks[:keys] if jwks.is_a?(Hash)
|
215
|
+
|
210
216
|
enc_algs = [jws_encryption_algorithm].compact
|
211
217
|
enc_meths = [jws_encryption_method].compact
|
212
218
|
|
213
219
|
sig_algs = jws_algorithm ? [jws_algorithm] : jwks.select { |k| k[:use] == "sig" }.map { |k| k[:alg] }
|
214
220
|
sig_algs = sig_algs.compact.map(&:to_sym)
|
215
221
|
|
216
|
-
|
217
|
-
|
222
|
+
# JWKs may be set up without a KID, when there's a single one
|
223
|
+
if jwks.size == 1 && !jwks[0][:kid]
|
224
|
+
key = jwks[0]
|
225
|
+
jwk_key = JSON::JWK.new(key)
|
226
|
+
jws = JSON::JWT.decode(token, jwk_key)
|
227
|
+
else
|
228
|
+
jws = JSON::JWT.decode(token, JSON::JWK::Set.new({ keys: jwks }), enc_algs + sig_algs, enc_meths)
|
229
|
+
jws = JSON::JWT.decode(jws.plain_text, JSON::JWK::Set.new({ keys: jwks }), sig_algs) if jws.is_a?(JSON::JWE)
|
230
|
+
end
|
218
231
|
jws
|
219
232
|
elsif jws_key
|
220
233
|
JSON::JWT.decode(token, jws_key)
|
@@ -279,7 +292,7 @@ module Rodauth
|
|
279
292
|
end
|
280
293
|
|
281
294
|
def jwt_encode(payload,
|
282
|
-
signing_algorithm: oauth_jwt_keys.keys.first)
|
295
|
+
signing_algorithm: oauth_jwt_keys.keys.first, **)
|
283
296
|
headers = {}
|
284
297
|
|
285
298
|
key = oauth_jwt_keys[signing_algorithm] || _jwt_key
|
@@ -368,8 +381,18 @@ module Rodauth
|
|
368
381
|
# decode jwt
|
369
382
|
claims = if is_authorization_server?
|
370
383
|
if jwks
|
371
|
-
|
372
|
-
|
384
|
+
jwks = jwks[:keys] if jwks.is_a?(Hash)
|
385
|
+
|
386
|
+
# JWKs may be set up without a KID, when there's a single one
|
387
|
+
if jwks.size == 1 && !jwks[0][:kid]
|
388
|
+
key = jwks[0]
|
389
|
+
algo = key[:alg]
|
390
|
+
key = JWT::JWK.import(key).keypair
|
391
|
+
JWT.decode(token, key, true, algorithms: [algo], **verify_claims_params).first
|
392
|
+
else
|
393
|
+
algorithms = jws_algorithm ? [jws_algorithm] : jwks.select { |k| k[:use] == "sig" }.map { |k| k[:alg] }
|
394
|
+
JWT.decode(token, nil, true, algorithms: algorithms, jwks: { keys: jwks }, **verify_claims_params).first
|
395
|
+
end
|
373
396
|
elsif jws_key
|
374
397
|
JWT.decode(token, jws_key, true, algorithms: [jws_algorithm], **verify_claims_params).first
|
375
398
|
end
|