rodauth-oauth 0.8.0 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -3
  3. data/doc/release_notes/0_9_0.md +56 -0
  4. data/doc/release_notes/0_9_1.md +9 -0
  5. data/doc/release_notes/0_9_2.md +10 -0
  6. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +22 -1
  7. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +8 -3
  8. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +8 -2
  9. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +1 -0
  10. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +1 -0
  11. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +1 -0
  12. data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +13 -1
  13. data/lib/rodauth/features/oauth.rb +2 -2
  14. data/lib/rodauth/features/oauth_application_management.rb +23 -7
  15. data/lib/rodauth/features/oauth_assertion_base.rb +1 -1
  16. data/lib/rodauth/features/oauth_authorization_code_grant.rb +4 -1
  17. data/lib/rodauth/features/oauth_base.rb +57 -14
  18. data/lib/rodauth/features/oauth_client_credentials_grant.rb +33 -0
  19. data/lib/rodauth/features/oauth_device_grant.rb +4 -5
  20. data/lib/rodauth/features/oauth_dynamic_client_registration.rb +252 -0
  21. data/lib/rodauth/features/oauth_jwt.rb +251 -49
  22. data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +1 -0
  23. data/lib/rodauth/features/oauth_management_base.rb +72 -0
  24. data/lib/rodauth/features/oauth_pkce.rb +1 -1
  25. data/lib/rodauth/features/oauth_token_management.rb +8 -6
  26. data/lib/rodauth/features/oidc.rb +37 -7
  27. data/lib/rodauth/features/oidc_dynamic_client_registration.rb +147 -0
  28. data/lib/rodauth/oauth/jwe_extensions.rb +64 -0
  29. data/lib/rodauth/oauth/ttl_store.rb +9 -3
  30. data/lib/rodauth/oauth/version.rb +1 -1
  31. data/locales/en.yml +6 -1
  32. data/templates/authorize.str +50 -1
  33. data/templates/jwks_field.str +4 -0
  34. data/templates/jwt_public_key_field.str +1 -1
  35. data/templates/new_oauth_application.str +1 -1
  36. data/templates/oauth_application.str +1 -1
  37. data/templates/oauth_application_oauth_tokens.str +1 -0
  38. data/templates/oauth_applications.str +1 -0
  39. data/templates/oauth_tokens.str +1 -0
  40. data/templates/scope_field.str +3 -2
  41. metadata +14 -3
  42. 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