rodauth-oauth 0.7.3 → 0.9.0

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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -418
  3. data/README.md +30 -390
  4. data/doc/release_notes/0_0_1.md +3 -0
  5. data/doc/release_notes/0_0_2.md +15 -0
  6. data/doc/release_notes/0_0_3.md +31 -0
  7. data/doc/release_notes/0_0_4.md +36 -0
  8. data/doc/release_notes/0_0_5.md +36 -0
  9. data/doc/release_notes/0_0_6.md +21 -0
  10. data/doc/release_notes/0_1_0.md +44 -0
  11. data/doc/release_notes/0_2_0.md +43 -0
  12. data/doc/release_notes/0_3_0.md +28 -0
  13. data/doc/release_notes/0_4_0.md +18 -0
  14. data/doc/release_notes/0_4_1.md +9 -0
  15. data/doc/release_notes/0_4_2.md +5 -0
  16. data/doc/release_notes/0_4_3.md +3 -0
  17. data/doc/release_notes/0_5_0.md +11 -0
  18. data/doc/release_notes/0_5_1.md +13 -0
  19. data/doc/release_notes/0_6_0.md +9 -0
  20. data/doc/release_notes/0_6_1.md +6 -0
  21. data/doc/release_notes/0_7_0.md +20 -0
  22. data/doc/release_notes/0_7_1.md +10 -0
  23. data/doc/release_notes/0_7_2.md +21 -0
  24. data/doc/release_notes/0_7_3.md +10 -0
  25. data/doc/release_notes/0_7_4.md +5 -0
  26. data/doc/release_notes/0_8_0.md +37 -0
  27. data/doc/release_notes/0_9_0.md +56 -0
  28. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +50 -0
  29. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb +11 -0
  30. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb +20 -0
  31. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +55 -0
  32. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +29 -0
  33. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +39 -0
  34. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +30 -0
  35. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +35 -0
  36. data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +21 -1
  37. data/lib/rodauth/features/oauth.rb +3 -1418
  38. data/lib/rodauth/features/oauth_application_management.rb +225 -0
  39. data/lib/rodauth/features/oauth_assertion_base.rb +96 -0
  40. data/lib/rodauth/features/oauth_authorization_code_grant.rb +252 -0
  41. data/lib/rodauth/features/oauth_authorization_server.rb +0 -0
  42. data/lib/rodauth/features/oauth_base.rb +771 -0
  43. data/lib/rodauth/features/oauth_client_credentials_grant.rb +33 -0
  44. data/lib/rodauth/features/oauth_device_grant.rb +220 -0
  45. data/lib/rodauth/features/oauth_dynamic_client_registration.rb +252 -0
  46. data/lib/rodauth/features/oauth_http_mac.rb +3 -21
  47. data/lib/rodauth/features/oauth_implicit_grant.rb +59 -0
  48. data/lib/rodauth/features/oauth_jwt.rb +276 -100
  49. data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +59 -0
  50. data/lib/rodauth/features/oauth_management_base.rb +68 -0
  51. data/lib/rodauth/features/oauth_pkce.rb +98 -0
  52. data/lib/rodauth/features/oauth_resource_server.rb +21 -0
  53. data/lib/rodauth/features/oauth_saml_bearer_grant.rb +102 -0
  54. data/lib/rodauth/features/oauth_token_introspection.rb +108 -0
  55. data/lib/rodauth/features/oauth_token_management.rb +79 -0
  56. data/lib/rodauth/features/oauth_token_revocation.rb +109 -0
  57. data/lib/rodauth/features/oidc.rb +36 -6
  58. data/lib/rodauth/features/oidc_dynamic_client_registration.rb +147 -0
  59. data/lib/rodauth/oauth/database_extensions.rb +15 -2
  60. data/lib/rodauth/oauth/jwe_extensions.rb +64 -0
  61. data/lib/rodauth/oauth/refinements.rb +48 -0
  62. data/lib/rodauth/oauth/ttl_store.rb +9 -3
  63. data/lib/rodauth/oauth/version.rb +1 -1
  64. data/locales/en.yml +33 -12
  65. data/templates/authorize.str +57 -8
  66. data/templates/client_secret_field.str +2 -2
  67. data/templates/description_field.str +1 -1
  68. data/templates/device_search.str +11 -0
  69. data/templates/device_verification.str +24 -0
  70. data/templates/homepage_url_field.str +2 -2
  71. data/templates/jwks_field.str +4 -0
  72. data/templates/jwt_public_key_field.str +4 -0
  73. data/templates/name_field.str +1 -1
  74. data/templates/new_oauth_application.str +9 -0
  75. data/templates/oauth_application.str +7 -3
  76. data/templates/oauth_application_oauth_tokens.str +52 -0
  77. data/templates/oauth_applications.str +3 -2
  78. data/templates/oauth_tokens.str +10 -11
  79. data/templates/redirect_uri_field.str +2 -2
  80. metadata +84 -4
  81. data/lib/rodauth/features/oauth_saml.rb +0 -104
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:oauth_client_credentials_grant, :OauthClientCredentialsGrant) do
5
+ depends :oauth_base
6
+
7
+ auth_value_method :use_oauth_client_credentials_grant_type?, false
8
+
9
+ private
10
+
11
+ def create_oauth_token(grant_type)
12
+ return super unless grant_type == "client_credentials" && use_oauth_client_credentials_grant_type?
13
+
14
+ create_params = {
15
+ oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
16
+ oauth_tokens_scopes_column => scopes.join(oauth_scope_separator)
17
+ }
18
+ generate_oauth_token(create_params, false)
19
+ end
20
+
21
+ def oauth_server_metadata_body(*)
22
+ super.tap do |data|
23
+ data[:grant_types_supported] << "client_credentials" if use_oauth_client_credentials_grant_type?
24
+ end
25
+ end
26
+
27
+ def check_valid_response_type?
28
+ return true if use_oauth_implicit_grant_type? && param_or_nil("response_type") == "token"
29
+
30
+ super
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:oauth_device_grant, :OauthDeviceGrant) do
5
+ depends :oauth_base
6
+
7
+ auth_value_method :use_oauth_device_code_grant_type?, false
8
+
9
+ before "device_authorization"
10
+ before "device_verification"
11
+
12
+ notice_flash "The device is verified", "device_verification"
13
+ error_flash "No device to authorize with the given user code", "user_code_not_found"
14
+
15
+ view "device_verification", "Device Verification", "device_verification"
16
+ view "device_search", "Device Search", "device_search"
17
+
18
+ button "Verify", "oauth_device_verification"
19
+ button "Search", "oauth_device_search"
20
+
21
+ auth_value_method :oauth_grants_user_code_column, :user_code
22
+ auth_value_method :oauth_grants_last_polled_at_column, :last_polled_at
23
+
24
+ translatable_method :expired_token_message, "the device code has expired"
25
+ translatable_method :access_denied_message, "the authorization request has been denied"
26
+ translatable_method :authorization_pending_message, "the authorization request is still pending"
27
+ translatable_method :slow_down_message, "authorization request is still pending but poll interval should be increased"
28
+
29
+ auth_value_method :oauth_device_code_grant_polling_interval, 5 # seconds
30
+ auth_value_method :oauth_device_code_grant_user_code_size, 8 # characters
31
+ %w[user_code].each do |param|
32
+ auth_value_method :"oauth_grant_#{param}_param", param
33
+ end
34
+ translatable_method :oauth_grant_user_code_label, "User code"
35
+
36
+ auth_value_methods(
37
+ :generate_user_code
38
+ )
39
+
40
+ # /device-authorization
41
+ route(:device_authorization) do |r|
42
+ next unless is_authorization_server? && use_oauth_device_code_grant_type?
43
+
44
+ before_device_authorization_route
45
+
46
+ r.post do
47
+ require_oauth_application
48
+
49
+ user_code = generate_user_code
50
+ device_code = transaction do
51
+ before_device_authorization
52
+ create_oauth_grant(oauth_grants_user_code_column => user_code)
53
+ end
54
+
55
+ json_response_success \
56
+ "device_code" => device_code,
57
+ "user_code" => user_code,
58
+ "verification_uri" => device_url,
59
+ "verification_uri_complete" => device_url(user_code: user_code),
60
+ "expires_in" => oauth_grant_expires_in,
61
+ "interval" => oauth_device_code_grant_polling_interval
62
+ end
63
+ end
64
+
65
+ # /device
66
+ route(:device) do |r|
67
+ next unless is_authorization_server? && use_oauth_device_code_grant_type?
68
+
69
+ before_device_route
70
+ require_authorizable_account
71
+
72
+ r.get do
73
+ if (user_code = param_or_nil("user_code"))
74
+ oauth_grant = db[oauth_grants_table].where(
75
+ oauth_grants_user_code_column => user_code,
76
+ oauth_grants_revoked_at_column => nil
77
+ ).where(Sequel[oauth_grants_expires_in_column] >= Sequel::CURRENT_TIMESTAMP).first
78
+
79
+ unless oauth_grant
80
+ set_redirect_error_flash user_code_not_found_error_flash
81
+ redirect device_path
82
+ end
83
+
84
+ scope.instance_variable_set(:@oauth_grant, oauth_grant)
85
+ device_verification_view
86
+ else
87
+ device_search_view
88
+ end
89
+ end
90
+
91
+ r.post do
92
+ catch_error do
93
+ unless param_or_nil("user_code")
94
+ set_redirect_error_flash invalid_grant_message
95
+ redirect device_path
96
+ end
97
+
98
+ transaction do
99
+ before_device_verification
100
+ create_oauth_token("device_code")
101
+ end
102
+ end
103
+ set_notice_flash device_verification_notice_flash
104
+ redirect device_path
105
+ end
106
+ end
107
+
108
+ def check_csrf?
109
+ case request.path
110
+ when device_authorization_path
111
+ false
112
+ else
113
+ super
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ def generate_user_code
120
+ user_code_size = oauth_device_code_grant_user_code_size
121
+ SecureRandom.random_number(36**user_code_size)
122
+ .to_s(36) # 0 to 9, a to z
123
+ .upcase
124
+ .rjust(user_code_size, "0")
125
+ end
126
+
127
+ def authorized_oauth_application?(oauth_application, client_secret, _)
128
+ # skip if using device grant
129
+ #
130
+ # requests may be performed by devices with no knowledge of client secret.
131
+ return true if !client_secret && use_oauth_device_code_grant_type?
132
+
133
+ super
134
+ end
135
+
136
+ def create_oauth_token(grant_type)
137
+ if supported_grant_type?(grant_type, "urn:ietf:params:oauth:grant-type:device_code")
138
+ throw_json_response_error(invalid_oauth_response_status, "invalid_grant_type") unless use_oauth_device_code_grant_type?
139
+
140
+ oauth_grant = db[oauth_grants_table].where(
141
+ oauth_grants_code_column => param("device_code"),
142
+ oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column]
143
+ ).for_update.first
144
+
145
+ throw_json_response_error(invalid_oauth_response_status, "invalid_grant") unless oauth_grant
146
+
147
+ now = Time.now
148
+
149
+ if oauth_grant[oauth_grants_revoked_at_column]
150
+ oauth_token = db[oauth_tokens_table]
151
+ .where(Sequel[oauth_tokens_table][oauth_tokens_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
152
+ .where(Sequel[oauth_tokens_table][oauth_tokens_revoked_at_column] => nil)
153
+ .where(oauth_tokens_oauth_grant_id_column => oauth_grant[oauth_grants_id_column])
154
+ .first
155
+
156
+ throw_json_response_error(invalid_oauth_response_status, "access_denied") unless oauth_token
157
+ elsif oauth_grant[oauth_grants_expires_in_column] < now
158
+ throw_json_response_error(invalid_oauth_response_status, "expired_token")
159
+ else
160
+ last_polled_at = oauth_grant[oauth_grants_last_polled_at_column]
161
+ if last_polled_at && convert_timestamp(last_polled_at) + oauth_device_code_grant_polling_interval > now
162
+ throw_json_response_error(invalid_oauth_response_status, "slow_down")
163
+ else
164
+ db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
165
+ .update(oauth_grants_last_polled_at_column => Sequel::CURRENT_TIMESTAMP)
166
+ throw_json_response_error(invalid_oauth_response_status, "authorization_pending")
167
+ end
168
+ end
169
+ oauth_token
170
+ elsif grant_type == "device_code"
171
+ redirect_response_error("invalid_grant_type") unless use_oauth_device_code_grant_type?
172
+
173
+ # fetch oauth grant
174
+ oauth_grant = db[oauth_grants_table].where(
175
+ oauth_grants_user_code_column => param("user_code"),
176
+ oauth_grants_revoked_at_column => nil
177
+ ).where(Sequel[oauth_grants_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
178
+ .for_update
179
+ .first
180
+
181
+ return unless oauth_grant
182
+
183
+ # update ownership
184
+ db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
185
+ .update(
186
+ oauth_grants_user_code_column => nil,
187
+ oauth_grants_account_id_column => account_id
188
+ )
189
+
190
+ create_params = {
191
+ oauth_tokens_account_id_column => account_id,
192
+ oauth_tokens_oauth_application_id_column => oauth_grant[oauth_grants_oauth_application_id_column],
193
+ oauth_tokens_oauth_grant_id_column => oauth_grant[oauth_grants_id_column],
194
+ oauth_tokens_scopes_column => oauth_grant[oauth_grants_scopes_column]
195
+ }
196
+ create_oauth_token_from_authorization_code(oauth_grant, create_params)
197
+ else
198
+ super
199
+ end
200
+ end
201
+
202
+ def validate_oauth_token_params
203
+ grant_type = param_or_nil("grant_type")
204
+
205
+ if grant_type == "urn:ietf:params:oauth:grant-type:device_code" && !param_or_nil("device_code")
206
+ redirect_response_error("invalid_request")
207
+ end
208
+ super
209
+ end
210
+
211
+ def oauth_server_metadata_body(*)
212
+ super.tap do |data|
213
+ if use_oauth_device_code_grant_type?
214
+ data[:grant_types_supported] << "urn:ietf:params:oauth:grant-type:device_code"
215
+ data[:device_authorization_endpoint] = device_authorization_url
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end
@@ -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
@@ -1,28 +1,10 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ require "rodauth/oauth/refinements"
4
+
3
5
  module Rodauth
4
6
  Feature.define(:oauth_http_mac, :OauthHttpMac) do
5
- unless String.method_defined?(:delete_prefix)
6
- module PrefixExtensions
7
- refine(String) do
8
- def delete_suffix(suffix)
9
- suffix = suffix.to_s
10
- len = suffix.length
11
- return dup unless len.positive? && index(suffix, -len)
12
-
13
- self[0...-len]
14
- end
15
-
16
- def delete_prefix(prefix)
17
- prefix = prefix.to_s
18
- return dup unless rindex(prefix, 0)
19
-
20
- self[prefix.length..-1]
21
- end
22
- end
23
- end
24
- using(PrefixExtensions)
25
- end
7
+ using PrefixExtensions
26
8
 
27
9
  depends :oauth
28
10
 
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:oauth_implicit_grant, :OauthImplicitGrant) do
5
+ depends :oauth_base
6
+
7
+ auth_value_method :use_oauth_implicit_grant_type?, false
8
+
9
+ private
10
+
11
+ def do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
12
+ return super unless param("response_type") == "token" && use_oauth_implicit_grant_type?
13
+
14
+ response_mode ||= "fragment"
15
+ response_params.replace(_do_authorize_token)
16
+
17
+ response_params["state"] = param("state") if param_or_nil("state")
18
+
19
+ [response_params, response_mode]
20
+ end
21
+
22
+ def _do_authorize_token
23
+ create_params = {
24
+ oauth_tokens_account_id_column => account_id,
25
+ oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
26
+ oauth_tokens_scopes_column => scopes
27
+ }
28
+ oauth_token = generate_oauth_token(create_params, false)
29
+
30
+ json_access_token_payload(oauth_token)
31
+ end
32
+
33
+ def authorize_response(params, mode)
34
+ return super unless mode == "fragment"
35
+
36
+ redirect_url = URI.parse(redirect_uri)
37
+ params = params.map { |k, v| "#{k}=#{v}" }
38
+ params << redirect_url.query if redirect_url.query
39
+ redirect_url.fragment = params.join("&")
40
+ redirect(redirect_url.to_s)
41
+ end
42
+
43
+ def oauth_server_metadata_body(*)
44
+ super.tap do |data|
45
+ if use_oauth_implicit_grant_type?
46
+ data[:response_types_supported] << "token"
47
+ data[:response_modes_supported] << "fragment"
48
+ data[:grant_types_supported] << "implicit"
49
+ end
50
+ end
51
+ end
52
+
53
+ def check_valid_response_type?
54
+ return true if use_oauth_implicit_grant_type? && param_or_nil("response_type") == "token"
55
+
56
+ super
57
+ end
58
+ end
59
+ end