rodauth-oauth 0.10.4 → 1.0.0.pre.beta2
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/MIGRATION-GUIDE-v1.md +286 -0
- data/README.md +28 -35
- data/doc/release_notes/1_0_0_beta1.md +38 -0
- data/doc/release_notes/1_0_0_beta2.md +34 -0
- data/lib/generators/rodauth/oauth/install_generator.rb +0 -1
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +21 -11
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb +1 -1
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb +2 -2
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +1 -6
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +0 -2
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_grants.html.erb +41 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +2 -2
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_grants.html.erb +37 -0
- data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +57 -57
- data/lib/rodauth/features/oauth_application_management.rb +61 -74
- data/lib/rodauth/features/oauth_assertion_base.rb +19 -23
- data/lib/rodauth/features/oauth_authorization_code_grant.rb +62 -90
- data/lib/rodauth/features/oauth_authorize_base.rb +115 -22
- data/lib/rodauth/features/oauth_base.rb +397 -315
- data/lib/rodauth/features/oauth_client_credentials_grant.rb +20 -18
- data/lib/rodauth/features/{oauth_device_grant.rb → oauth_device_code_grant.rb} +62 -73
- data/lib/rodauth/features/oauth_dynamic_client_registration.rb +52 -31
- data/lib/rodauth/features/oauth_grant_management.rb +70 -0
- data/lib/rodauth/features/oauth_implicit_grant.rb +29 -27
- data/lib/rodauth/features/oauth_jwt.rb +53 -689
- data/lib/rodauth/features/oauth_jwt_base.rb +458 -0
- data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +48 -17
- data/lib/rodauth/features/oauth_jwt_jwks.rb +47 -0
- data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +116 -0
- data/lib/rodauth/features/oauth_management_base.rb +2 -0
- data/lib/rodauth/features/oauth_pkce.rb +22 -26
- data/lib/rodauth/features/oauth_resource_indicators.rb +33 -25
- data/lib/rodauth/features/oauth_resource_server.rb +59 -0
- data/lib/rodauth/features/oauth_saml_bearer_grant.rb +7 -1
- data/lib/rodauth/features/oauth_token_introspection.rb +76 -46
- data/lib/rodauth/features/oauth_token_revocation.rb +46 -33
- data/lib/rodauth/features/oidc.rb +382 -241
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +127 -51
- data/lib/rodauth/features/oidc_rp_initiated_logout.rb +115 -0
- data/lib/rodauth/oauth/database_extensions.rb +8 -6
- data/lib/rodauth/oauth/http_extensions.rb +74 -0
- data/lib/rodauth/oauth/railtie.rb +20 -0
- data/lib/rodauth/oauth/ttl_store.rb +2 -0
- data/lib/rodauth/oauth/version.rb +1 -1
- data/lib/rodauth/oauth.rb +29 -1
- data/locales/en.yml +34 -22
- data/locales/pt.yml +34 -22
- data/templates/authorize.str +19 -17
- data/templates/device_search.str +1 -1
- data/templates/device_verification.str +2 -2
- data/templates/jwks_field.str +1 -0
- data/templates/new_oauth_application.str +1 -2
- data/templates/oauth_application.str +2 -2
- data/templates/oauth_application_oauth_grants.str +54 -0
- data/templates/oauth_applications.str +2 -2
- data/templates/oauth_grants.str +52 -0
- metadata +23 -16
- data/lib/generators/rodauth/oauth/templates/app/models/oauth_token.rb +0 -4
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +0 -39
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +0 -35
- data/lib/rodauth/features/oauth.rb +0 -9
- data/lib/rodauth/features/oauth_http_mac.rb +0 -86
- data/lib/rodauth/features/oauth_token_management.rb +0 -81
- data/lib/rodauth/oauth/refinements.rb +0 -48
- data/templates/jwt_public_key_field.str +0 -4
- data/templates/oauth_application_oauth_tokens.str +0 -52
- data/templates/oauth_tokens.str +0 -50
@@ -1,33 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rodauth/oauth"
|
4
|
+
|
3
5
|
module Rodauth
|
4
6
|
Feature.define(:oauth_client_credentials_grant, :OauthClientCredentialsGrant) do
|
5
7
|
depends :oauth_base
|
6
8
|
|
7
|
-
|
9
|
+
def oauth_grant_types_supported
|
10
|
+
super | %w[client_credentials]
|
11
|
+
end
|
8
12
|
|
9
13
|
private
|
10
14
|
|
11
|
-
def
|
12
|
-
return super unless grant_type
|
15
|
+
def create_token(grant_type)
|
16
|
+
return super unless supported_grant_type?(grant_type, "client_credentials")
|
13
17
|
|
14
|
-
|
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
|
18
|
+
grant_scopes = scopes
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def check_valid_response_type?
|
28
|
-
return true if use_oauth_implicit_grant_type? && param_or_nil("response_type") == "token"
|
20
|
+
grant_scopes = if grant_scopes
|
21
|
+
redirect_response_error("invalid_scope") unless check_valid_scopes?
|
22
|
+
grant_scopes.join(oauth_scope_separator)
|
23
|
+
else
|
24
|
+
oauth_application[oauth_applications_scopes_column]
|
25
|
+
end
|
29
26
|
|
30
|
-
|
27
|
+
grant_params = {
|
28
|
+
oauth_grants_type_column => "client_credentials",
|
29
|
+
oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
|
30
|
+
oauth_grants_scopes_column => grant_scopes
|
31
|
+
}
|
32
|
+
generate_token(grant_params, false)
|
31
33
|
end
|
32
34
|
end
|
33
35
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rodauth/oauth"
|
4
|
+
|
3
5
|
module Rodauth
|
4
|
-
Feature.define(:
|
6
|
+
Feature.define(:oauth_device_code_grant, :OauthDeviceCodeGrant) do
|
5
7
|
depends :oauth_authorize_base
|
6
8
|
|
7
|
-
auth_value_method :use_oauth_device_code_grant_type?, false
|
8
|
-
|
9
9
|
before "device_authorization"
|
10
10
|
before "device_verification"
|
11
11
|
|
@@ -21,10 +21,12 @@ module Rodauth
|
|
21
21
|
auth_value_method :oauth_grants_user_code_column, :user_code
|
22
22
|
auth_value_method :oauth_grants_last_polled_at_column, :last_polled_at
|
23
23
|
|
24
|
-
translatable_method :
|
25
|
-
translatable_method :
|
26
|
-
translatable_method :
|
27
|
-
translatable_method :
|
24
|
+
translatable_method :oauth_device_search_page_lead, "Insert the user code from the device you'd like to authorize."
|
25
|
+
translatable_method :oauth_device_verification_page_lead, "The device with user code %<user_code>s would like to access your data."
|
26
|
+
translatable_method :oauth_expired_token_message, "the device code has expired"
|
27
|
+
translatable_method :oauth_access_denied_message, "the authorization request has been denied"
|
28
|
+
translatable_method :oauth_authorization_pending_message, "the authorization request is still pending"
|
29
|
+
translatable_method :oauth_slow_down_message, "authorization request is still pending but poll interval should be increased"
|
28
30
|
|
29
31
|
auth_value_method :oauth_device_code_grant_polling_interval, 5 # seconds
|
30
32
|
auth_value_method :oauth_device_code_grant_user_code_size, 8 # characters
|
@@ -38,18 +40,18 @@ module Rodauth
|
|
38
40
|
)
|
39
41
|
|
40
42
|
# /device-authorization
|
41
|
-
|
42
|
-
|
43
|
-
|
43
|
+
auth_server_route(:device_authorization) do |r|
|
44
|
+
require_oauth_application
|
44
45
|
before_device_authorization_route
|
45
46
|
|
46
47
|
r.post do
|
47
|
-
require_oauth_application
|
48
|
-
|
49
48
|
user_code = generate_user_code
|
50
49
|
device_code = transaction do
|
51
50
|
before_device_authorization
|
52
|
-
create_oauth_grant(
|
51
|
+
create_oauth_grant(
|
52
|
+
oauth_grants_type_column => "device_code",
|
53
|
+
oauth_grants_user_code_column => user_code
|
54
|
+
)
|
53
55
|
end
|
54
56
|
|
55
57
|
json_response_success \
|
@@ -63,18 +65,13 @@ module Rodauth
|
|
63
65
|
end
|
64
66
|
|
65
67
|
# /device
|
66
|
-
|
67
|
-
next unless is_authorization_server? && use_oauth_device_code_grant_type?
|
68
|
-
|
69
|
-
before_device_route
|
68
|
+
auth_server_route(:device) do |r|
|
70
69
|
require_authorizable_account
|
70
|
+
before_device_route
|
71
71
|
|
72
72
|
r.get do
|
73
73
|
if (user_code = param_or_nil("user_code"))
|
74
|
-
oauth_grant =
|
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
|
74
|
+
oauth_grant = valid_oauth_grant_ds(oauth_grants_user_code_column => user_code).first
|
78
75
|
|
79
76
|
unless oauth_grant
|
80
77
|
set_redirect_error_flash user_code_not_found_error_flash
|
@@ -90,14 +87,14 @@ module Rodauth
|
|
90
87
|
|
91
88
|
r.post do
|
92
89
|
catch_error do
|
93
|
-
unless param_or_nil("user_code")
|
94
|
-
set_redirect_error_flash
|
90
|
+
unless (user_code = param_or_nil("user_code")) && !user_code.empty?
|
91
|
+
set_redirect_error_flash oauth_invalid_grant_message
|
95
92
|
redirect device_path
|
96
93
|
end
|
97
94
|
|
98
95
|
transaction do
|
99
96
|
before_device_verification
|
100
|
-
|
97
|
+
create_token("device_code")
|
101
98
|
end
|
102
99
|
end
|
103
100
|
set_notice_flash device_verification_notice_flash
|
@@ -114,6 +111,10 @@ module Rodauth
|
|
114
111
|
end
|
115
112
|
end
|
116
113
|
|
114
|
+
def oauth_grant_types_supported
|
115
|
+
super | %w[urn:ietf:params:oauth:grant-type:device_code]
|
116
|
+
end
|
117
|
+
|
117
118
|
private
|
118
119
|
|
119
120
|
def generate_user_code
|
@@ -124,82 +125,61 @@ module Rodauth
|
|
124
125
|
.rjust(user_code_size, "0")
|
125
126
|
end
|
126
127
|
|
127
|
-
|
128
|
-
|
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?
|
128
|
+
# TODO: think about removing this and recommend PKCE
|
129
|
+
def supports_auth_method?(oauth_application, auth_method)
|
130
|
+
return super unless auth_method == "none"
|
132
131
|
|
133
|
-
super
|
132
|
+
request.path == device_authorization_path || request.params.key?("device_code") || super
|
134
133
|
end
|
135
134
|
|
136
|
-
def
|
135
|
+
def create_token(grant_type)
|
137
136
|
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
137
|
|
140
138
|
oauth_grant = db[oauth_grants_table].where(
|
139
|
+
oauth_grants_type_column => "device_code",
|
141
140
|
oauth_grants_code_column => param("device_code"),
|
142
141
|
oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column]
|
143
142
|
).for_update.first
|
144
143
|
|
145
|
-
throw_json_response_error(
|
144
|
+
throw_json_response_error(oauth_invalid_response_status, "invalid_grant") unless oauth_grant
|
146
145
|
|
147
146
|
now = Time.now
|
148
147
|
|
149
|
-
if oauth_grant[
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
148
|
+
if oauth_grant[oauth_grants_user_code_column].nil?
|
149
|
+
return create_token_from_authorization_code(
|
150
|
+
{ oauth_grants_id_column => oauth_grant[oauth_grants_id_column] },
|
151
|
+
oauth_grant: oauth_grant
|
152
|
+
)
|
153
|
+
end
|
155
154
|
|
156
|
-
|
155
|
+
if oauth_grant[oauth_grants_revoked_at_column]
|
156
|
+
throw_json_response_error(oauth_invalid_response_status, "access_denied")
|
157
157
|
elsif oauth_grant[oauth_grants_expires_in_column] < now
|
158
|
-
throw_json_response_error(
|
158
|
+
throw_json_response_error(oauth_invalid_response_status, "expired_token")
|
159
159
|
else
|
160
160
|
last_polled_at = oauth_grant[oauth_grants_last_polled_at_column]
|
161
161
|
if last_polled_at && convert_timestamp(last_polled_at) + oauth_device_code_grant_polling_interval > now
|
162
|
-
throw_json_response_error(
|
162
|
+
throw_json_response_error(oauth_invalid_response_status, "slow_down")
|
163
163
|
else
|
164
164
|
db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
|
165
165
|
.update(oauth_grants_last_polled_at_column => Sequel::CURRENT_TIMESTAMP)
|
166
|
-
throw_json_response_error(
|
166
|
+
throw_json_response_error(oauth_invalid_response_status, "authorization_pending")
|
167
167
|
end
|
168
168
|
end
|
169
|
-
oauth_token
|
170
169
|
elsif grant_type == "device_code"
|
171
|
-
redirect_response_error("invalid_grant_type") unless use_oauth_device_code_grant_type?
|
172
170
|
|
173
171
|
# fetch oauth grant
|
174
|
-
|
175
|
-
oauth_grants_user_code_column => param("user_code")
|
176
|
-
|
177
|
-
|
178
|
-
|
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)
|
172
|
+
rs = valid_oauth_grant_ds(
|
173
|
+
oauth_grants_user_code_column => param("user_code")
|
174
|
+
).update(oauth_grants_user_code_column => nil, oauth_grants_type_column => "device_code")
|
175
|
+
|
176
|
+
return unless rs.positive?
|
197
177
|
else
|
198
178
|
super
|
199
179
|
end
|
200
180
|
end
|
201
181
|
|
202
|
-
def
|
182
|
+
def validate_token_params
|
203
183
|
grant_type = param_or_nil("grant_type")
|
204
184
|
|
205
185
|
if grant_type == "urn:ietf:params:oauth:grant-type:device_code" && !param_or_nil("device_code")
|
@@ -208,12 +188,21 @@ module Rodauth
|
|
208
188
|
super
|
209
189
|
end
|
210
190
|
|
191
|
+
def store_token(grant_params, update_params = {})
|
192
|
+
return super unless grant_params[oauth_grants_user_code_column]
|
193
|
+
|
194
|
+
# do not clean up device code just yet
|
195
|
+
update_params.delete(oauth_grants_code_column)
|
196
|
+
|
197
|
+
update_params[oauth_grants_user_code_column] = nil
|
198
|
+
update_params[oauth_grants_account_id_column] = account_id
|
199
|
+
|
200
|
+
super(grant_params, update_params)
|
201
|
+
end
|
202
|
+
|
211
203
|
def oauth_server_metadata_body(*)
|
212
204
|
super.tap do |data|
|
213
|
-
|
214
|
-
data[:grant_types_supported] << "urn:ietf:params:oauth:grant-type:device_code"
|
215
|
-
data[:device_authorization_endpoint] = device_authorization_url
|
216
|
-
end
|
205
|
+
data[:device_authorization_endpoint] = device_authorization_url
|
217
206
|
end
|
218
207
|
end
|
219
208
|
end
|
@@ -1,19 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rodauth/oauth"
|
4
|
+
|
3
5
|
module Rodauth
|
4
6
|
Feature.define(:oauth_dynamic_client_registration, :OauthDynamicClientRegistration) do
|
5
7
|
depends :oauth_base
|
6
8
|
|
7
9
|
before "register"
|
8
10
|
|
9
|
-
auth_value_method :oauth_client_registration_required_params, %w[redirect_uris client_name
|
11
|
+
auth_value_method :oauth_client_registration_required_params, %w[redirect_uris client_name]
|
10
12
|
|
11
|
-
PROTECTED_APPLICATION_ATTRIBUTES = %
|
13
|
+
PROTECTED_APPLICATION_ATTRIBUTES = %w[account_id client_id].freeze
|
12
14
|
|
13
15
|
# /register
|
14
|
-
|
15
|
-
next unless is_authorization_server?
|
16
|
-
|
16
|
+
auth_server_route(:register) do |r|
|
17
17
|
before_register_route
|
18
18
|
|
19
19
|
validate_client_registration_params
|
@@ -43,8 +43,17 @@ module Rodauth
|
|
43
43
|
|
44
44
|
private
|
45
45
|
|
46
|
-
def
|
47
|
-
|
46
|
+
def _before_register
|
47
|
+
raise %{dynamic client registration requires authentication.
|
48
|
+
Override ´before_register` to perform it.
|
49
|
+
example:
|
50
|
+
|
51
|
+
before_register do
|
52
|
+
account = _account_from_login(request.env["HTTP_X_USER_EMAIL"])
|
53
|
+
authorization_required unless account
|
54
|
+
@oauth_application_params[:account_id] = account[:id]
|
55
|
+
end
|
56
|
+
}
|
48
57
|
end
|
49
58
|
|
50
59
|
def validate_client_registration_params
|
@@ -53,41 +62,43 @@ module Rodauth
|
|
53
62
|
register_throw_json_response_error("invalid_client_metadata", register_required_param_message(required_param))
|
54
63
|
end
|
55
64
|
end
|
56
|
-
metadata = registration_metadata
|
57
65
|
|
58
66
|
@oauth_application_params = request.params.each_with_object({}) do |(key, value), params|
|
59
67
|
case key
|
60
68
|
when "redirect_uris"
|
61
69
|
if value.is_a?(Array)
|
62
70
|
value = value.each do |uri|
|
63
|
-
|
71
|
+
unless check_valid_no_fragment_uri?(uri)
|
72
|
+
register_throw_json_response_error("invalid_redirect_uri",
|
73
|
+
register_invalid_uri_message(uri))
|
74
|
+
end
|
64
75
|
end.join(" ")
|
65
76
|
else
|
66
77
|
register_throw_json_response_error("invalid_redirect_uri", register_invalid_uri_message(value))
|
67
78
|
end
|
68
79
|
key = oauth_applications_redirect_uri_column
|
69
80
|
when "token_endpoint_auth_method"
|
70
|
-
unless
|
71
|
-
register_throw_json_response_error("invalid_client_metadata",
|
81
|
+
unless oauth_token_endpoint_auth_methods_supported.include?(value)
|
82
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(key, value))
|
72
83
|
end
|
73
84
|
# verify if in range
|
74
85
|
key = oauth_applications_token_endpoint_auth_method_column
|
75
86
|
when "grant_types"
|
76
87
|
if value.is_a?(Array)
|
77
88
|
value = value.each do |grant_type|
|
78
|
-
unless
|
79
|
-
register_throw_json_response_error("invalid_client_metadata",
|
89
|
+
unless oauth_grant_types_supported.include?(grant_type)
|
90
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(grant_type, value))
|
80
91
|
end
|
81
92
|
end.join(" ")
|
82
93
|
else
|
83
|
-
|
94
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(key, value))
|
84
95
|
end
|
85
96
|
key = oauth_applications_grant_types_column
|
86
97
|
when "response_types"
|
87
98
|
if value.is_a?(Array)
|
88
|
-
grant_types = request.params["grant_types"] ||
|
99
|
+
grant_types = request.params["grant_types"] || oauth_grant_types_supported
|
89
100
|
value = value.each do |response_type|
|
90
|
-
unless
|
101
|
+
unless oauth_response_types_supported.include?(response_type)
|
91
102
|
register_throw_json_response_error("invalid_client_metadata",
|
92
103
|
register_invalid_response_type_message(response_type))
|
93
104
|
end
|
@@ -95,7 +106,7 @@ module Rodauth
|
|
95
106
|
validate_client_registration_response_type(response_type, grant_types)
|
96
107
|
end.join(" ")
|
97
108
|
else
|
98
|
-
|
109
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(key, value))
|
99
110
|
end
|
100
111
|
key = oauth_applications_response_types_column
|
101
112
|
# verify if in range and match grant type
|
@@ -133,10 +144,10 @@ module Rodauth
|
|
133
144
|
key = oauth_applications_name_column
|
134
145
|
else
|
135
146
|
if respond_to?(:"oauth_applications_#{key}_column")
|
136
|
-
|
137
|
-
if PROTECTED_APPLICATION_ATTRIBUTES.include?(property)
|
147
|
+
if PROTECTED_APPLICATION_ATTRIBUTES.include?(key)
|
138
148
|
register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
|
139
149
|
end
|
150
|
+
property = :"oauth_applications_#{key}_column"
|
140
151
|
key = __send__(property)
|
141
152
|
elsif !db[oauth_applications_table].columns.include?(key.to_sym)
|
142
153
|
register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
|
@@ -167,16 +178,20 @@ module Rodauth
|
|
167
178
|
end
|
168
179
|
|
169
180
|
def do_register(return_params = request.params.dup)
|
181
|
+
applications_ds = db[oauth_applications_table]
|
182
|
+
application_columns = applications_ds.columns
|
183
|
+
|
170
184
|
# set defaults
|
171
185
|
create_params = @oauth_application_params
|
172
|
-
create_params[oauth_applications_scopes_column] ||= return_params["scopes"] =
|
173
|
-
create_params[
|
174
|
-
return_params["
|
175
|
-
"client_secret_basic"
|
176
|
-
end
|
177
|
-
create_params[oauth_applications_grant_types_column] ||= begin
|
178
|
-
return_params["grant_types"] = %w[authorization_code]
|
186
|
+
create_params[oauth_applications_scopes_column] ||= return_params["scopes"] = oauth_application_scopes.join(" ")
|
187
|
+
if create_params[oauth_applications_grant_types_column] ||= begin
|
188
|
+
return_params["grant_types"] = %w[authorization_code] # rubocop:disable Lint/AssignmentInCondition
|
179
189
|
"authorization_code"
|
190
|
+
end
|
191
|
+
create_params[oauth_applications_token_endpoint_auth_method_column] ||= begin
|
192
|
+
return_params["token_endpoint_auth_method"] = "client_secret_basic"
|
193
|
+
"client_secret_basic"
|
194
|
+
end
|
180
195
|
end
|
181
196
|
create_params[oauth_applications_response_types_column] ||= begin
|
182
197
|
return_params["response_types"] = %w[code]
|
@@ -188,22 +203,24 @@ module Rodauth
|
|
188
203
|
return_params["client_id"] = client_id
|
189
204
|
return_params["client_id_issued_at"] = Time.now.utc.iso8601
|
190
205
|
if create_params.key?(oauth_applications_client_secret_column)
|
191
|
-
create_params
|
206
|
+
set_client_secret(create_params, create_params[oauth_applications_client_secret_column])
|
192
207
|
return_params.delete("client_secret")
|
193
208
|
else
|
194
209
|
client_secret = oauth_unique_id_generator
|
195
|
-
create_params
|
210
|
+
set_client_secret(create_params, client_secret)
|
196
211
|
return_params["client_secret"] = client_secret
|
197
212
|
return_params["client_secret_expires_at"] = 0
|
213
|
+
|
214
|
+
create_params.delete_if { |k, _| !application_columns.include?(k) }
|
198
215
|
end
|
199
|
-
|
216
|
+
applications_ds.insert(create_params)
|
200
217
|
end
|
201
218
|
|
202
219
|
return_params
|
203
220
|
end
|
204
221
|
|
205
222
|
def register_throw_json_response_error(code, message)
|
206
|
-
throw_json_response_error(
|
223
|
+
throw_json_response_error(oauth_invalid_response_status, code, message)
|
207
224
|
end
|
208
225
|
|
209
226
|
def register_required_param_message(key)
|
@@ -214,6 +231,10 @@ module Rodauth
|
|
214
231
|
"The param '#{key}' is not supported by this server."
|
215
232
|
end
|
216
233
|
|
234
|
+
def register_invalid_client_metadata_message(key, value)
|
235
|
+
"The value '#{value}' is not supported by this server for param '#{key}'."
|
236
|
+
end
|
237
|
+
|
217
238
|
def register_invalid_contacts_message(contacts)
|
218
239
|
"The contacts '#{contacts}' are not allowed by this server."
|
219
240
|
end
|
@@ -230,7 +251,7 @@ module Rodauth
|
|
230
251
|
"The given scopes (#{scopes}) are not allowed by this server."
|
231
252
|
end
|
232
253
|
|
233
|
-
def
|
254
|
+
def register_oauth_invalid_grant_type_message(grant_type)
|
234
255
|
"The grant type #{grant_type} is not allowed by this server."
|
235
256
|
end
|
236
257
|
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rodauth/oauth"
|
4
|
+
|
5
|
+
module Rodauth
|
6
|
+
Feature.define(:oauth_grant_management, :OauthTokenManagement) do
|
7
|
+
depends :oauth_management_base, :oauth_token_revocation
|
8
|
+
|
9
|
+
view "oauth_grants", "My Oauth Grants", "oauth_grants"
|
10
|
+
|
11
|
+
button "Revoke", "oauth_grant_revoke"
|
12
|
+
|
13
|
+
auth_value_method :oauth_grants_path, "oauth-grants"
|
14
|
+
|
15
|
+
%w[type token refresh_token expires_in revoked_at].each do |param|
|
16
|
+
translatable_method :"oauth_grants_#{param}_label", param.gsub("_", " ").capitalize
|
17
|
+
end
|
18
|
+
translatable_method :oauth_no_grants_text, "No oauth grants yet!"
|
19
|
+
|
20
|
+
auth_value_method :oauth_grants_route, "oauth-grants"
|
21
|
+
auth_value_method :oauth_grants_id_pattern, Integer
|
22
|
+
auth_value_method :oauth_grants_per_page, 20
|
23
|
+
|
24
|
+
auth_value_methods(
|
25
|
+
:oauth_grant_path
|
26
|
+
)
|
27
|
+
|
28
|
+
def oauth_grants_path(opts = {})
|
29
|
+
route_path(oauth_grants_route, opts)
|
30
|
+
end
|
31
|
+
|
32
|
+
def oauth_grant_path(id)
|
33
|
+
"#{oauth_grants_path}/#{id}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def load_oauth_grant_management_routes
|
37
|
+
request.on(oauth_grants_route) do
|
38
|
+
check_csrf if check_csrf?
|
39
|
+
require_account
|
40
|
+
|
41
|
+
request.post(oauth_grants_id_pattern) do |id|
|
42
|
+
db[oauth_grants_table]
|
43
|
+
.where(oauth_grants_id_column => id)
|
44
|
+
.where(oauth_grants_account_id_column => account_id)
|
45
|
+
.update(oauth_grants_revoked_at_column => Sequel::CURRENT_TIMESTAMP)
|
46
|
+
|
47
|
+
set_notice_flash revoke_oauth_grant_notice_flash
|
48
|
+
redirect oauth_grants_path || "/"
|
49
|
+
end
|
50
|
+
|
51
|
+
request.is do
|
52
|
+
request.get do
|
53
|
+
page = Integer(param_or_nil("page") || 1)
|
54
|
+
per_page = per_page_param(oauth_grants_per_page)
|
55
|
+
|
56
|
+
scope.instance_variable_set(:@oauth_grants, db[oauth_grants_table]
|
57
|
+
.select(Sequel[oauth_grants_table].*, Sequel[oauth_applications_table][oauth_applications_name_column])
|
58
|
+
.join(oauth_applications_table, Sequel[oauth_grants_table][oauth_grants_oauth_application_id_column] =>
|
59
|
+
Sequel[oauth_applications_table][oauth_applications_id_column])
|
60
|
+
.where(Sequel[oauth_grants_table][oauth_grants_account_id_column] => account_id)
|
61
|
+
.where(oauth_grants_revoked_at_column => nil)
|
62
|
+
.order(Sequel.desc(oauth_grants_id_column))
|
63
|
+
.paginate(page, per_page))
|
64
|
+
oauth_grants_view
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -1,39 +1,51 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rodauth/oauth"
|
4
|
+
|
3
5
|
module Rodauth
|
4
6
|
Feature.define(:oauth_implicit_grant, :OauthImplicitGrant) do
|
5
7
|
depends :oauth_authorize_base
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
9
|
+
def oauth_grant_types_supported
|
10
|
+
super | %w[implicit]
|
11
|
+
end
|
10
12
|
|
11
|
-
def
|
12
|
-
|
13
|
+
def oauth_response_types_supported
|
14
|
+
super | %w[token]
|
15
|
+
end
|
13
16
|
|
14
|
-
|
17
|
+
def oauth_response_modes_supported
|
18
|
+
super | %w[fragment]
|
15
19
|
end
|
16
20
|
|
21
|
+
private
|
22
|
+
|
17
23
|
def do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
|
18
|
-
|
24
|
+
response_type = param("response_type")
|
25
|
+
return super unless response_type == "token" && supported_response_type?(response_type)
|
19
26
|
|
20
27
|
response_mode ||= "fragment"
|
21
|
-
|
28
|
+
|
29
|
+
redirect_response_error("invalid_request") unless supported_response_mode?(response_mode)
|
30
|
+
|
31
|
+
oauth_grant = _do_authorize_token
|
32
|
+
|
33
|
+
response_params.replace(json_access_token_payload(oauth_grant))
|
22
34
|
|
23
35
|
response_params["state"] = param("state") if param_or_nil("state")
|
24
36
|
|
25
37
|
[response_params, response_mode]
|
26
38
|
end
|
27
39
|
|
28
|
-
def _do_authorize_token
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
40
|
+
def _do_authorize_token(grant_params = {})
|
41
|
+
grant_params = {
|
42
|
+
oauth_grants_type_column => "implicit",
|
43
|
+
oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
|
44
|
+
oauth_grants_scopes_column => scopes,
|
45
|
+
oauth_grants_account_id_column => account_id
|
46
|
+
}.merge(grant_params)
|
35
47
|
|
36
|
-
|
48
|
+
generate_token(grant_params, false)
|
37
49
|
end
|
38
50
|
|
39
51
|
def authorize_response(params, mode)
|
@@ -46,18 +58,8 @@ module Rodauth
|
|
46
58
|
redirect(redirect_url.to_s)
|
47
59
|
end
|
48
60
|
|
49
|
-
def oauth_server_metadata_body(*)
|
50
|
-
super.tap do |data|
|
51
|
-
if use_oauth_implicit_grant_type?
|
52
|
-
data[:response_types_supported] << "token"
|
53
|
-
data[:response_modes_supported] << "fragment"
|
54
|
-
data[:grant_types_supported] << "implicit"
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
61
|
def check_valid_response_type?
|
60
|
-
return true if
|
62
|
+
return true if param_or_nil("response_type") == "token"
|
61
63
|
|
62
64
|
super
|
63
65
|
end
|