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