rodauth-oauth 0.10.4 → 1.0.0.pre.beta1
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 +22 -30
- data/doc/release_notes/1_0_0_beta1.md +38 -0
- data/lib/generators/rodauth/oauth/install_generator.rb +0 -1
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +4 -6
- 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 +18 -29
- data/lib/rodauth/features/oauth_application_management.rb +59 -72
- data/lib/rodauth/features/oauth_assertion_base.rb +19 -23
- data/lib/rodauth/features/oauth_authorization_code_grant.rb +35 -88
- data/lib/rodauth/features/oauth_authorize_base.rb +103 -20
- data/lib/rodauth/features/oauth_base.rb +365 -302
- 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 +46 -28
- data/lib/rodauth/features/oauth_grant_management.rb +70 -0
- data/lib/rodauth/features/oauth_implicit_grant.rb +25 -24
- data/lib/rodauth/features/oauth_jwt.rb +52 -688
- data/lib/rodauth/features/oauth_jwt_base.rb +435 -0
- data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +45 -17
- data/lib/rodauth/features/oauth_jwt_jwks.rb +47 -0
- data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +62 -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 -21
- data/lib/rodauth/features/oauth_resource_server.rb +59 -0
- data/lib/rodauth/features/oauth_saml_bearer_grant.rb +5 -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 +188 -95
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +89 -53
- data/lib/rodauth/oauth/database_extensions.rb +8 -6
- data/lib/rodauth/oauth/http_extensions.rb +61 -0
- data/lib/rodauth/oauth/railtie.rb +20 -0
- data/lib/rodauth/oauth/version.rb +1 -1
- data/lib/rodauth/oauth.rb +29 -1
- data/locales/en.yml +32 -22
- data/locales/pt.yml +32 -22
- data/templates/authorize.str +19 -24
- 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 +20 -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,5 +1,7 @@
|
|
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
|
@@ -8,12 +10,10 @@ module Rodauth
|
|
8
10
|
|
9
11
|
auth_value_method :oauth_client_registration_required_params, %w[redirect_uris client_name client_uri]
|
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,7 +62,6 @@ 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
|
@@ -67,7 +75,7 @@ module Rodauth
|
|
67
75
|
end
|
68
76
|
key = oauth_applications_redirect_uri_column
|
69
77
|
when "token_endpoint_auth_method"
|
70
|
-
unless
|
78
|
+
unless oauth_token_endpoint_auth_methods_supported.include?(value)
|
71
79
|
register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
|
72
80
|
end
|
73
81
|
# verify if in range
|
@@ -75,19 +83,19 @@ module Rodauth
|
|
75
83
|
when "grant_types"
|
76
84
|
if value.is_a?(Array)
|
77
85
|
value = value.each do |grant_type|
|
78
|
-
unless
|
79
|
-
register_throw_json_response_error("invalid_client_metadata",
|
86
|
+
unless oauth_grant_types_supported.include?(grant_type)
|
87
|
+
register_throw_json_response_error("invalid_client_metadata", register_oauth_invalid_grant_type_message(grant_type))
|
80
88
|
end
|
81
89
|
end.join(" ")
|
82
90
|
else
|
83
|
-
|
91
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(key, value))
|
84
92
|
end
|
85
93
|
key = oauth_applications_grant_types_column
|
86
94
|
when "response_types"
|
87
95
|
if value.is_a?(Array)
|
88
|
-
grant_types = request.params["grant_types"] ||
|
96
|
+
grant_types = request.params["grant_types"] || oauth_grant_types_supported
|
89
97
|
value = value.each do |response_type|
|
90
|
-
unless
|
98
|
+
unless oauth_response_types_supported.include?(response_type)
|
91
99
|
register_throw_json_response_error("invalid_client_metadata",
|
92
100
|
register_invalid_response_type_message(response_type))
|
93
101
|
end
|
@@ -95,7 +103,7 @@ module Rodauth
|
|
95
103
|
validate_client_registration_response_type(response_type, grant_types)
|
96
104
|
end.join(" ")
|
97
105
|
else
|
98
|
-
|
106
|
+
register_throw_json_response_error("invalid_client_metadata", register_invalid_client_metadata_message(key, value))
|
99
107
|
end
|
100
108
|
key = oauth_applications_response_types_column
|
101
109
|
# verify if in range and match grant type
|
@@ -133,10 +141,10 @@ module Rodauth
|
|
133
141
|
key = oauth_applications_name_column
|
134
142
|
else
|
135
143
|
if respond_to?(:"oauth_applications_#{key}_column")
|
136
|
-
|
137
|
-
if PROTECTED_APPLICATION_ATTRIBUTES.include?(property)
|
144
|
+
if PROTECTED_APPLICATION_ATTRIBUTES.include?(key)
|
138
145
|
register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
|
139
146
|
end
|
147
|
+
property = :"oauth_applications_#{key}_column"
|
140
148
|
key = __send__(property)
|
141
149
|
elsif !db[oauth_applications_table].columns.include?(key.to_sym)
|
142
150
|
register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
|
@@ -167,16 +175,20 @@ module Rodauth
|
|
167
175
|
end
|
168
176
|
|
169
177
|
def do_register(return_params = request.params.dup)
|
178
|
+
applications_ds = db[oauth_applications_table]
|
179
|
+
application_columns = applications_ds.columns
|
180
|
+
|
170
181
|
# set defaults
|
171
182
|
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]
|
183
|
+
create_params[oauth_applications_scopes_column] ||= return_params["scopes"] = oauth_application_scopes.join(" ")
|
184
|
+
if create_params[oauth_applications_grant_types_column] ||= begin
|
185
|
+
return_params["grant_types"] = %w[authorization_code] # rubocop:disable Lint/AssignmentInCondition
|
179
186
|
"authorization_code"
|
187
|
+
end
|
188
|
+
create_params[oauth_applications_token_endpoint_auth_method_column] ||= begin
|
189
|
+
return_params["token_endpoint_auth_method"] = "client_secret_basic"
|
190
|
+
"client_secret_basic"
|
191
|
+
end
|
180
192
|
end
|
181
193
|
create_params[oauth_applications_response_types_column] ||= begin
|
182
194
|
return_params["response_types"] = %w[code]
|
@@ -188,22 +200,24 @@ module Rodauth
|
|
188
200
|
return_params["client_id"] = client_id
|
189
201
|
return_params["client_id_issued_at"] = Time.now.utc.iso8601
|
190
202
|
if create_params.key?(oauth_applications_client_secret_column)
|
191
|
-
create_params
|
203
|
+
set_client_secret(create_params, create_params[oauth_applications_client_secret_column])
|
192
204
|
return_params.delete("client_secret")
|
193
205
|
else
|
194
206
|
client_secret = oauth_unique_id_generator
|
195
|
-
create_params
|
207
|
+
set_client_secret(create_params, client_secret)
|
196
208
|
return_params["client_secret"] = client_secret
|
197
209
|
return_params["client_secret_expires_at"] = 0
|
210
|
+
|
211
|
+
create_params.delete_if { |k, _| !application_columns.include?(k) }
|
198
212
|
end
|
199
|
-
|
213
|
+
applications_ds.insert(create_params)
|
200
214
|
end
|
201
215
|
|
202
216
|
return_params
|
203
217
|
end
|
204
218
|
|
205
219
|
def register_throw_json_response_error(code, message)
|
206
|
-
throw_json_response_error(
|
220
|
+
throw_json_response_error(oauth_invalid_response_status, code, message)
|
207
221
|
end
|
208
222
|
|
209
223
|
def register_required_param_message(key)
|
@@ -214,6 +228,10 @@ module Rodauth
|
|
214
228
|
"The param '#{key}' is not supported by this server."
|
215
229
|
end
|
216
230
|
|
231
|
+
def register_invalid_client_metadata_message(key, value)
|
232
|
+
"The value '#{value}' is not supported by this server for param '#{key}'."
|
233
|
+
end
|
234
|
+
|
217
235
|
def register_invalid_contacts_message(contacts)
|
218
236
|
"The contacts '#{contacts}' are not allowed by this server."
|
219
237
|
end
|
@@ -230,7 +248,7 @@ module Rodauth
|
|
230
248
|
"The given scopes (#{scopes}) are not allowed by this server."
|
231
249
|
end
|
232
250
|
|
233
|
-
def
|
251
|
+
def register_oauth_invalid_grant_type_message(grant_type)
|
234
252
|
"The grant type #{grant_type} is not allowed by this server."
|
235
253
|
end
|
236
254
|
|
@@ -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,23 +1,33 @@
|
|
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"
|
28
|
+
|
29
|
+
redirect_response_error("invalid_request") unless supported_response_mode?(response_mode)
|
30
|
+
|
21
31
|
response_params.replace(_do_authorize_token)
|
22
32
|
|
23
33
|
response_params["state"] = param("state") if param_or_nil("state")
|
@@ -26,14 +36,15 @@ module Rodauth
|
|
26
36
|
end
|
27
37
|
|
28
38
|
def _do_authorize_token
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
39
|
+
grant_params = {
|
40
|
+
oauth_grants_type_column => "implicit",
|
41
|
+
oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
|
42
|
+
oauth_grants_scopes_column => scopes,
|
43
|
+
oauth_grants_account_id_column => account_id
|
33
44
|
}
|
34
|
-
|
45
|
+
oauth_grant = generate_token(grant_params, false)
|
35
46
|
|
36
|
-
json_access_token_payload(
|
47
|
+
json_access_token_payload(oauth_grant)
|
37
48
|
end
|
38
49
|
|
39
50
|
def authorize_response(params, mode)
|
@@ -46,18 +57,8 @@ module Rodauth
|
|
46
57
|
redirect(redirect_url.to_s)
|
47
58
|
end
|
48
59
|
|
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
60
|
def check_valid_response_type?
|
60
|
-
return true if
|
61
|
+
return true if param_or_nil("response_type") == "token"
|
61
62
|
|
62
63
|
super
|
63
64
|
end
|