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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -418
- data/README.md +30 -390
- data/doc/release_notes/0_0_1.md +3 -0
- data/doc/release_notes/0_0_2.md +15 -0
- data/doc/release_notes/0_0_3.md +31 -0
- data/doc/release_notes/0_0_4.md +36 -0
- data/doc/release_notes/0_0_5.md +36 -0
- data/doc/release_notes/0_0_6.md +21 -0
- data/doc/release_notes/0_1_0.md +44 -0
- data/doc/release_notes/0_2_0.md +43 -0
- data/doc/release_notes/0_3_0.md +28 -0
- data/doc/release_notes/0_4_0.md +18 -0
- data/doc/release_notes/0_4_1.md +9 -0
- data/doc/release_notes/0_4_2.md +5 -0
- data/doc/release_notes/0_4_3.md +3 -0
- data/doc/release_notes/0_5_0.md +11 -0
- data/doc/release_notes/0_5_1.md +13 -0
- data/doc/release_notes/0_6_0.md +9 -0
- data/doc/release_notes/0_6_1.md +6 -0
- data/doc/release_notes/0_7_0.md +20 -0
- data/doc/release_notes/0_7_1.md +10 -0
- data/doc/release_notes/0_7_2.md +21 -0
- data/doc/release_notes/0_7_3.md +10 -0
- data/doc/release_notes/0_7_4.md +5 -0
- data/doc/release_notes/0_8_0.md +37 -0
- data/doc/release_notes/0_9_0.md +56 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +50 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb +11 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb +20 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +55 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +29 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +39 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +30 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +35 -0
- data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +21 -1
- data/lib/rodauth/features/oauth.rb +3 -1418
- data/lib/rodauth/features/oauth_application_management.rb +225 -0
- data/lib/rodauth/features/oauth_assertion_base.rb +96 -0
- data/lib/rodauth/features/oauth_authorization_code_grant.rb +252 -0
- data/lib/rodauth/features/oauth_authorization_server.rb +0 -0
- data/lib/rodauth/features/oauth_base.rb +771 -0
- data/lib/rodauth/features/oauth_client_credentials_grant.rb +33 -0
- data/lib/rodauth/features/oauth_device_grant.rb +220 -0
- data/lib/rodauth/features/oauth_dynamic_client_registration.rb +252 -0
- data/lib/rodauth/features/oauth_http_mac.rb +3 -21
- data/lib/rodauth/features/oauth_implicit_grant.rb +59 -0
- data/lib/rodauth/features/oauth_jwt.rb +276 -100
- data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +59 -0
- data/lib/rodauth/features/oauth_management_base.rb +68 -0
- data/lib/rodauth/features/oauth_pkce.rb +98 -0
- data/lib/rodauth/features/oauth_resource_server.rb +21 -0
- data/lib/rodauth/features/oauth_saml_bearer_grant.rb +102 -0
- data/lib/rodauth/features/oauth_token_introspection.rb +108 -0
- data/lib/rodauth/features/oauth_token_management.rb +79 -0
- data/lib/rodauth/features/oauth_token_revocation.rb +109 -0
- data/lib/rodauth/features/oidc.rb +36 -6
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +147 -0
- data/lib/rodauth/oauth/database_extensions.rb +15 -2
- data/lib/rodauth/oauth/jwe_extensions.rb +64 -0
- data/lib/rodauth/oauth/refinements.rb +48 -0
- data/lib/rodauth/oauth/ttl_store.rb +9 -3
- data/lib/rodauth/oauth/version.rb +1 -1
- data/locales/en.yml +33 -12
- data/templates/authorize.str +57 -8
- data/templates/client_secret_field.str +2 -2
- data/templates/description_field.str +1 -1
- data/templates/device_search.str +11 -0
- data/templates/device_verification.str +24 -0
- data/templates/homepage_url_field.str +2 -2
- data/templates/jwks_field.str +4 -0
- data/templates/jwt_public_key_field.str +4 -0
- data/templates/name_field.str +1 -1
- data/templates/new_oauth_application.str +9 -0
- data/templates/oauth_application.str +7 -3
- data/templates/oauth_application_oauth_tokens.str +52 -0
- data/templates/oauth_applications.str +3 -2
- data/templates/oauth_tokens.str +10 -11
- data/templates/redirect_uri_field.str +2 -2
- metadata +84 -4
- 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
|
-
|
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
|