rodauth-oauth 0.8.0 → 0.9.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 +4 -4
- data/README.md +6 -3
- data/doc/release_notes/0_9_0.md +56 -0
- data/doc/release_notes/0_9_1.md +9 -0
- data/doc/release_notes/0_9_2.md +10 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +22 -1
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +8 -3
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +8 -2
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +1 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +1 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +1 -0
- data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +13 -1
- data/lib/rodauth/features/oauth.rb +2 -2
- data/lib/rodauth/features/oauth_application_management.rb +23 -7
- data/lib/rodauth/features/oauth_assertion_base.rb +1 -1
- data/lib/rodauth/features/oauth_authorization_code_grant.rb +4 -1
- data/lib/rodauth/features/oauth_base.rb +57 -14
- data/lib/rodauth/features/oauth_client_credentials_grant.rb +33 -0
- data/lib/rodauth/features/oauth_device_grant.rb +4 -5
- data/lib/rodauth/features/oauth_dynamic_client_registration.rb +252 -0
- data/lib/rodauth/features/oauth_jwt.rb +251 -49
- data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +1 -0
- data/lib/rodauth/features/oauth_management_base.rb +72 -0
- data/lib/rodauth/features/oauth_pkce.rb +1 -1
- data/lib/rodauth/features/oauth_token_management.rb +8 -6
- data/lib/rodauth/features/oidc.rb +37 -7
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +147 -0
- data/lib/rodauth/oauth/jwe_extensions.rb +64 -0
- data/lib/rodauth/oauth/ttl_store.rb +9 -3
- data/lib/rodauth/oauth/version.rb +1 -1
- data/locales/en.yml +6 -1
- data/templates/authorize.str +50 -1
- data/templates/jwks_field.str +4 -0
- data/templates/jwt_public_key_field.str +1 -1
- data/templates/new_oauth_application.str +1 -1
- data/templates/oauth_application.str +1 -1
- data/templates/oauth_application_oauth_tokens.str +1 -0
- data/templates/oauth_applications.str +1 -0
- data/templates/oauth_tokens.str +1 -0
- data/templates/scope_field.str +3 -2
- metadata +14 -3
- data/templates/jws_jwk_field.str +0 -4
@@ -0,0 +1,252 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rodauth
|
4
|
+
Feature.define(:oauth_dynamic_client_registration, :OauthDynamicClientRegistration) do
|
5
|
+
depends :oauth_base
|
6
|
+
|
7
|
+
before "register"
|
8
|
+
|
9
|
+
auth_value_method :oauth_client_registration_required_params, %w[redirect_uris client_name client_uri]
|
10
|
+
|
11
|
+
PROTECTED_APPLICATION_ATTRIBUTES = %i[account_id client_id].freeze
|
12
|
+
|
13
|
+
# /register
|
14
|
+
route(:register) do |r|
|
15
|
+
next unless is_authorization_server?
|
16
|
+
|
17
|
+
before_register_route
|
18
|
+
|
19
|
+
validate_client_registration_params
|
20
|
+
|
21
|
+
r.post do
|
22
|
+
response_params = transaction do
|
23
|
+
before_register
|
24
|
+
do_register
|
25
|
+
end
|
26
|
+
|
27
|
+
response.status = 201
|
28
|
+
response["Content-Type"] = json_response_content_type
|
29
|
+
response["Cache-Control"] = "no-store"
|
30
|
+
response["Pragma"] = "no-cache"
|
31
|
+
response.write(_json_response_body(response_params))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def check_csrf?
|
36
|
+
case request.path
|
37
|
+
when register_path
|
38
|
+
false
|
39
|
+
else
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def registration_metadata
|
47
|
+
oauth_server_metadata_body
|
48
|
+
end
|
49
|
+
|
50
|
+
def validate_client_registration_params
|
51
|
+
oauth_client_registration_required_params.each do |required_param|
|
52
|
+
unless request.params.key?(required_param)
|
53
|
+
register_throw_json_response_error("invalid_client_metadata", register_required_param_message(required_param))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
metadata = registration_metadata
|
57
|
+
|
58
|
+
@oauth_application_params = request.params.each_with_object({}) do |(key, value), params|
|
59
|
+
case key
|
60
|
+
when "redirect_uris"
|
61
|
+
if value.is_a?(Array)
|
62
|
+
value = value.each do |uri|
|
63
|
+
register_throw_json_response_error("invalid_redirect_uri", register_invalid_uri_message(uri)) unless check_valid_uri?(uri)
|
64
|
+
end.join(" ")
|
65
|
+
else
|
66
|
+
register_throw_json_response_error("invalid_redirect_uri", register_invalid_uri_message(value))
|
67
|
+
end
|
68
|
+
key = oauth_applications_redirect_uri_column
|
69
|
+
when "token_endpoint_auth_method"
|
70
|
+
unless oauth_auth_methods_supported.include?(value)
|
71
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
|
72
|
+
end
|
73
|
+
# verify if in range
|
74
|
+
key = oauth_applications_token_endpoint_auth_method_column
|
75
|
+
when "grant_types"
|
76
|
+
if value.is_a?(Array)
|
77
|
+
value = value.each do |grant_type|
|
78
|
+
unless metadata[:grant_types_supported].include?(grant_type)
|
79
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_grant_type_message(grant_type))
|
80
|
+
end
|
81
|
+
end.join(" ")
|
82
|
+
else
|
83
|
+
set_field_error(key, invalid_client_metadata_message)
|
84
|
+
end
|
85
|
+
key = oauth_applications_grant_types_column
|
86
|
+
when "response_types"
|
87
|
+
if value.is_a?(Array)
|
88
|
+
grant_types = request.params["grant_types"] || metadata[:grant_types_supported]
|
89
|
+
value = value.each do |response_type|
|
90
|
+
unless metadata[:response_types_supported].include?(response_type)
|
91
|
+
register_throw_json_response_error("invalid_client_metadata",
|
92
|
+
register_invalid_response_type_message(response_type))
|
93
|
+
end
|
94
|
+
|
95
|
+
validate_client_registration_response_type(response_type, grant_types)
|
96
|
+
end.join(" ")
|
97
|
+
else
|
98
|
+
set_field_error(key, invalid_client_metadata_message)
|
99
|
+
end
|
100
|
+
key = oauth_applications_response_types_column
|
101
|
+
# verify if in range and match grant type
|
102
|
+
when "client_uri", "logo_uri", "tos_uri", "policy_uri", "jwks_uri"
|
103
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_uri_message(value)) unless check_valid_uri?(value)
|
104
|
+
case key
|
105
|
+
when "client_uri"
|
106
|
+
key = "homepage_url"
|
107
|
+
when "jwks_uri"
|
108
|
+
if request.params.key?("jwks")
|
109
|
+
register_throw_json_response_error("invalid_client_metadata",
|
110
|
+
register_invalid_jwks_param_message(key, "jwks"))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
key = __send__(:"oauth_applications_#{key}_column")
|
114
|
+
when "jwks"
|
115
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(value)) unless value.is_a?(Hash)
|
116
|
+
if request.params.key?("jwks_uri")
|
117
|
+
register_throw_json_response_error("invalid_client_metadata",
|
118
|
+
register_invalid_jwks_param_message(key, "jwks_uri"))
|
119
|
+
end
|
120
|
+
|
121
|
+
key = oauth_applications_jwks_column
|
122
|
+
value = JSON.dump(value)
|
123
|
+
when "scope"
|
124
|
+
scopes = value.split(" ") - oauth_application_scopes
|
125
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_scopes_message(value)) unless scopes.empty?
|
126
|
+
key = oauth_applications_scopes_column
|
127
|
+
# verify if in range
|
128
|
+
when "contacts"
|
129
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_contacts_message(value)) unless value.is_a?(Array)
|
130
|
+
value = value.join(" ")
|
131
|
+
key = oauth_applications_contacts_column
|
132
|
+
when "client_name"
|
133
|
+
key = oauth_applications_name_column
|
134
|
+
else
|
135
|
+
if respond_to?(:"oauth_applications_#{key}_column")
|
136
|
+
property = :"oauth_applications_#{key}_column"
|
137
|
+
if PROTECTED_APPLICATION_ATTRIBUTES.include?(property)
|
138
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
|
139
|
+
end
|
140
|
+
key = __send__(property)
|
141
|
+
elsif !db[oauth_applications_table].columns.include?(key.to_sym)
|
142
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
|
143
|
+
end
|
144
|
+
end
|
145
|
+
params[key] = value
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def validate_client_registration_response_type(response_type, grant_types)
|
150
|
+
case response_type
|
151
|
+
when "code"
|
152
|
+
unless grant_types.include?("authorization_code")
|
153
|
+
register_throw_json_response_error("invalid_client_metadata",
|
154
|
+
register_invalid_response_type_for_grant_type_message(response_type,
|
155
|
+
"authorization_code"))
|
156
|
+
end
|
157
|
+
when "token"
|
158
|
+
unless grant_types.include?("implicit")
|
159
|
+
register_throw_json_response_error("invalid_client_metadata",
|
160
|
+
register_invalid_response_type_for_grant_type_message(response_type, "implicit"))
|
161
|
+
end
|
162
|
+
when "none"
|
163
|
+
if grant_types.include?("implicit") || grant_types.include?("authorization_code")
|
164
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_response_type_message(response_type))
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def do_register(return_params = request.params.dup)
|
170
|
+
# set defaults
|
171
|
+
create_params = @oauth_application_params
|
172
|
+
create_params[oauth_applications_scopes_column] ||= return_params["scopes"] = oauth_application_default_scope.join(" ")
|
173
|
+
create_params[oauth_applications_token_endpoint_auth_method_column] ||= begin
|
174
|
+
return_params["token_endpoint_auth_method"] = "client_secret_basic"
|
175
|
+
"client_secret_basic"
|
176
|
+
end
|
177
|
+
create_params[oauth_applications_grant_types_column] ||= begin
|
178
|
+
return_params["grant_types"] = %w[authorization_code]
|
179
|
+
"authorization_code"
|
180
|
+
end
|
181
|
+
create_params[oauth_applications_response_types_column] ||= begin
|
182
|
+
return_params["response_types"] = %w[code]
|
183
|
+
"code"
|
184
|
+
end
|
185
|
+
rescue_from_uniqueness_error do
|
186
|
+
client_id = oauth_unique_id_generator
|
187
|
+
create_params[oauth_applications_client_id_column] = client_id
|
188
|
+
return_params["client_id"] = client_id
|
189
|
+
return_params["client_id_issued_at"] = Time.now.utc.iso8601
|
190
|
+
if create_params.key?(oauth_applications_client_secret_column)
|
191
|
+
create_params[oauth_applications_client_secret_column] = secret_hash(create_params[oauth_applications_client_secret_column])
|
192
|
+
return_params.delete("client_secret")
|
193
|
+
else
|
194
|
+
client_secret = oauth_unique_id_generator
|
195
|
+
create_params[oauth_applications_client_secret_column] = secret_hash(client_secret)
|
196
|
+
return_params["client_secret"] = client_secret
|
197
|
+
return_params["client_secret_expires_at"] = 0
|
198
|
+
end
|
199
|
+
db[oauth_applications_table].insert(create_params)
|
200
|
+
end
|
201
|
+
|
202
|
+
return_params
|
203
|
+
end
|
204
|
+
|
205
|
+
def register_throw_json_response_error(code, message)
|
206
|
+
throw_json_response_error(invalid_oauth_response_status, code, message)
|
207
|
+
end
|
208
|
+
|
209
|
+
def register_required_param_message(key)
|
210
|
+
"The param '#{key}' is required by this server."
|
211
|
+
end
|
212
|
+
|
213
|
+
def register_invalid_param_message(key)
|
214
|
+
"The param '#{key}' is not supported by this server."
|
215
|
+
end
|
216
|
+
|
217
|
+
def register_invalid_contacts_message(contacts)
|
218
|
+
"The contacts '#{contacts}' are not allowed by this server."
|
219
|
+
end
|
220
|
+
|
221
|
+
def register_invalid_uri_message(uri)
|
222
|
+
"The '#{uri}' URL is not allowed by this server."
|
223
|
+
end
|
224
|
+
|
225
|
+
def register_invalid_jwks_param_message(key1, key2)
|
226
|
+
"The param '#{key1}' cannot be accepted together with param '#{key2}'."
|
227
|
+
end
|
228
|
+
|
229
|
+
def register_invalid_scopes_message(scopes)
|
230
|
+
"The given scopes (#{scopes}) are not allowed by this server."
|
231
|
+
end
|
232
|
+
|
233
|
+
def register_invalid_grant_type_message(grant_type)
|
234
|
+
"The grant type #{grant_type} is not allowed by this server."
|
235
|
+
end
|
236
|
+
|
237
|
+
def register_invalid_response_type_message(response_type)
|
238
|
+
"The response type #{response_type} is not allowed by this server."
|
239
|
+
end
|
240
|
+
|
241
|
+
def register_invalid_response_type_for_grant_type_message(response_type, grant_type)
|
242
|
+
"The grant type '#{grant_type}' must be registered for the response " \
|
243
|
+
"type '#{response_type}' to be allowed."
|
244
|
+
end
|
245
|
+
|
246
|
+
def oauth_server_metadata_body(*)
|
247
|
+
super.tap do |data|
|
248
|
+
data[:registration_endpoint] = register_url
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|