doorkeeper 5.2.0.rc2 → 5.2.0.rc3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of doorkeeper might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Appraisals +1 -1
- data/CHANGELOG.md +15 -2
- data/Gemfile +1 -1
- data/README.md +9 -1
- data/app/controllers/doorkeeper/application_metal_controller.rb +1 -1
- data/app/controllers/doorkeeper/authorizations_controller.rb +11 -9
- data/config/locales/en.yml +5 -1
- data/doorkeeper.gemspec +8 -0
- data/gemfiles/rails_6_0.gemfile +1 -1
- data/lib/doorkeeper.rb +1 -0
- data/lib/doorkeeper/config.rb +41 -2
- data/lib/doorkeeper/errors.rb +13 -18
- data/lib/doorkeeper/helpers/controller.rb +6 -2
- data/lib/doorkeeper/oauth/authorization/code.rb +1 -5
- data/lib/doorkeeper/oauth/authorization_code_request.rb +18 -9
- data/lib/doorkeeper/oauth/base_request.rb +2 -0
- data/lib/doorkeeper/oauth/client_credentials/validation.rb +8 -0
- data/lib/doorkeeper/oauth/code_request.rb +5 -11
- data/lib/doorkeeper/oauth/invalid_request_response.rb +43 -0
- data/lib/doorkeeper/oauth/password_access_token_request.rb +7 -2
- data/lib/doorkeeper/oauth/pre_authorization.rb +70 -37
- data/lib/doorkeeper/oauth/refresh_token_request.rb +5 -2
- data/lib/doorkeeper/oauth/token_introspection.rb +4 -1
- data/lib/doorkeeper/oauth/token_request.rb +4 -18
- data/lib/doorkeeper/orm/active_record.rb +2 -2
- data/lib/doorkeeper/orm/active_record/application.rb +1 -1
- data/lib/doorkeeper/orm/active_record/redirect_uri_validator.rb +61 -0
- data/lib/doorkeeper/request.rb +6 -11
- data/lib/doorkeeper/request/authorization_code.rb +2 -0
- data/lib/doorkeeper/server.rb +2 -6
- data/lib/doorkeeper/version.rb +1 -1
- data/lib/generators/doorkeeper/templates/initializer.rb +33 -2
- data/lib/generators/doorkeeper/templates/migration.rb.erb +1 -1
- data/spec/controllers/authorizations_controller_spec.rb +127 -61
- data/spec/controllers/protected_resources_controller_spec.rb +3 -3
- data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +1 -1
- data/spec/lib/config_spec.rb +17 -0
- data/spec/lib/oauth/authorization_code_request_spec.rb +11 -1
- data/spec/lib/oauth/base_request_spec.rb +33 -16
- data/spec/lib/oauth/code_request_spec.rb +27 -28
- data/spec/lib/oauth/invalid_request_response_spec.rb +75 -0
- data/spec/lib/oauth/pre_authorization_spec.rb +80 -55
- data/spec/lib/oauth/refresh_token_request_spec.rb +1 -0
- data/spec/lib/oauth/token_request_spec.rb +20 -17
- data/spec/lib/server_spec.rb +0 -12
- data/spec/requests/endpoints/authorization_spec.rb +21 -5
- data/spec/requests/endpoints/token_spec.rb +1 -1
- data/spec/requests/flows/authorization_code_errors_spec.rb +1 -0
- data/spec/requests/flows/authorization_code_spec.rb +77 -23
- data/spec/requests/flows/client_credentials_spec.rb +38 -0
- data/spec/requests/flows/implicit_grant_errors_spec.rb +22 -10
- data/spec/requests/flows/implicit_grant_spec.rb +9 -8
- data/spec/requests/flows/password_spec.rb +37 -0
- data/spec/requests/flows/refresh_token_spec.rb +1 -1
- data/spec/support/helpers/request_spec_helper.rb +14 -2
- data/spec/validators/redirect_uri_validator_spec.rb +1 -1
- metadata +12 -4
- data/app/validators/redirect_uri_validator.rb +0 -60
@@ -3,28 +3,22 @@
|
|
3
3
|
module Doorkeeper
|
4
4
|
module OAuth
|
5
5
|
class CodeRequest
|
6
|
-
attr_accessor :pre_auth, :resource_owner
|
6
|
+
attr_accessor :pre_auth, :resource_owner
|
7
7
|
|
8
8
|
def initialize(pre_auth, resource_owner)
|
9
9
|
@pre_auth = pre_auth
|
10
|
-
@client = pre_auth.client
|
11
10
|
@resource_owner = resource_owner
|
12
11
|
end
|
13
12
|
|
14
13
|
def authorize
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
@response = CodeResponse.new pre_auth, auth
|
19
|
-
else
|
20
|
-
@response = ErrorResponse.from_request pre_auth
|
21
|
-
end
|
14
|
+
auth = Authorization::Code.new(pre_auth, resource_owner)
|
15
|
+
auth.issue_token
|
16
|
+
CodeResponse.new(pre_auth, auth)
|
22
17
|
end
|
23
18
|
|
24
19
|
def deny
|
25
20
|
pre_auth.error = :access_denied
|
26
|
-
|
27
|
-
redirect_uri: pre_auth.redirect_uri
|
21
|
+
pre_auth.error_response
|
28
22
|
end
|
29
23
|
end
|
30
24
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doorkeeper
|
4
|
+
module OAuth
|
5
|
+
class InvalidRequestResponse < ErrorResponse
|
6
|
+
attr_reader :reason
|
7
|
+
|
8
|
+
def self.from_request(request, attributes = {})
|
9
|
+
new(
|
10
|
+
attributes.merge(
|
11
|
+
state: request.try(:state),
|
12
|
+
redirect_uri: request.try(:redirect_uri),
|
13
|
+
missing_param: request.try(:missing_param),
|
14
|
+
reason: request.try(:invalid_request_reason)
|
15
|
+
)
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(attributes = {})
|
20
|
+
super(attributes.merge(name: :invalid_request))
|
21
|
+
@missing_param = attributes[:missing_param]
|
22
|
+
@reason = @missing_param.nil? ? attributes[:reason] : :missing_param
|
23
|
+
end
|
24
|
+
|
25
|
+
def status
|
26
|
+
:bad_request
|
27
|
+
end
|
28
|
+
|
29
|
+
def description
|
30
|
+
I18n.translate(
|
31
|
+
reason,
|
32
|
+
scope: %i[doorkeeper errors messages invalid_request],
|
33
|
+
default: :unknown,
|
34
|
+
value: @missing_param
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def redirectable?
|
39
|
+
super && @missing_param != :client_id
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -5,9 +5,10 @@ module Doorkeeper
|
|
5
5
|
class PasswordAccessTokenRequest < BaseRequest
|
6
6
|
include OAuth::Helpers
|
7
7
|
|
8
|
-
validate :client,
|
8
|
+
validate :client, error: :invalid_client
|
9
|
+
validate :client_supports_grant_flow, error: :unauthorized_client
|
9
10
|
validate :resource_owner, error: :invalid_grant
|
10
|
-
validate :scopes,
|
11
|
+
validate :scopes, error: :invalid_scope
|
11
12
|
|
12
13
|
attr_accessor :server, :client, :resource_owner, :parameters,
|
13
14
|
:access_token
|
@@ -47,6 +48,10 @@ module Doorkeeper
|
|
47
48
|
def validate_client
|
48
49
|
!parameters[:client_id] || client.present?
|
49
50
|
end
|
51
|
+
|
52
|
+
def validate_client_supports_grant_flow
|
53
|
+
Doorkeeper.configuration.allow_grant_flow_for_client?(grant_type, client)
|
54
|
+
end
|
50
55
|
end
|
51
56
|
end
|
52
57
|
end
|
@@ -5,19 +5,21 @@ module Doorkeeper
|
|
5
5
|
class PreAuthorization
|
6
6
|
include Validations
|
7
7
|
|
8
|
-
validate :
|
9
|
-
validate :client,
|
10
|
-
validate :
|
11
|
-
validate :
|
8
|
+
validate :client_id, error: :invalid_request
|
9
|
+
validate :client, error: :invalid_client
|
10
|
+
validate :redirect_uri, error: :invalid_redirect_uri
|
11
|
+
validate :params, error: :invalid_request
|
12
|
+
validate :response_type, error: :unsupported_response_type
|
13
|
+
validate :scopes, error: :invalid_scope
|
12
14
|
validate :code_challenge_method, error: :invalid_code_challenge_method
|
15
|
+
validate :client_supports_grant_flow, error: :unauthorized_client
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
attr_writer :scope
|
17
|
+
attr_reader :server, :client_id, :client, :redirect_uri, :response_type, :state,
|
18
|
+
:code_challenge, :code_challenge_method, :missing_param
|
17
19
|
|
18
|
-
def initialize(server,
|
20
|
+
def initialize(server, attrs = {})
|
19
21
|
@server = server
|
20
|
-
@
|
22
|
+
@client_id = attrs[:client_id]
|
21
23
|
@response_type = attrs[:response_type]
|
22
24
|
@redirect_uri = attrs[:redirect_uri]
|
23
25
|
@scope = attrs[:scope]
|
@@ -30,34 +32,38 @@ module Doorkeeper
|
|
30
32
|
valid?
|
31
33
|
end
|
32
34
|
|
35
|
+
def validate_client_supports_grant_flow
|
36
|
+
Doorkeeper.configuration.allow_grant_flow_for_client?(grant_type, client.application)
|
37
|
+
end
|
38
|
+
|
33
39
|
def scopes
|
34
40
|
Scopes.from_string scope
|
35
41
|
end
|
36
42
|
|
37
43
|
def scope
|
38
|
-
@scope.presence || build_scopes
|
44
|
+
@scope.presence || (server.default_scopes.presence && build_scopes)
|
39
45
|
end
|
40
46
|
|
41
47
|
def error_response
|
42
|
-
|
48
|
+
is_implicit_flow = response_type == "token"
|
49
|
+
|
50
|
+
if error == :invalid_request
|
51
|
+
OAuth::InvalidRequestResponse.from_request(self, response_on_fragment: is_implicit_flow)
|
52
|
+
else
|
53
|
+
OAuth::ErrorResponse.from_request(self, response_on_fragment: is_implicit_flow)
|
54
|
+
end
|
43
55
|
end
|
44
56
|
|
45
|
-
def as_json(
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
state: state,
|
50
|
-
response_type: response_type,
|
51
|
-
scope: scope,
|
52
|
-
client_name: client.name,
|
53
|
-
status: I18n.t("doorkeeper.pre_authorization.status"),
|
54
|
-
}
|
57
|
+
def as_json(attributes = {})
|
58
|
+
return pre_auth_hash.merge(attributes.to_h) if attributes.respond_to?(:to_h)
|
59
|
+
|
60
|
+
pre_auth_hash
|
55
61
|
end
|
56
62
|
|
57
63
|
private
|
58
64
|
|
59
65
|
def build_scopes
|
60
|
-
client_scopes = client.
|
66
|
+
client_scopes = client.scopes
|
61
67
|
if client_scopes.blank?
|
62
68
|
server.default_scopes.to_s
|
63
69
|
else
|
@@ -65,21 +71,45 @@ module Doorkeeper
|
|
65
71
|
end
|
66
72
|
end
|
67
73
|
|
68
|
-
def
|
69
|
-
|
74
|
+
def validate_client_id
|
75
|
+
@missing_param = :client_id if client_id.blank?
|
76
|
+
|
77
|
+
@missing_param.nil?
|
70
78
|
end
|
71
79
|
|
72
80
|
def validate_client
|
73
|
-
client.
|
81
|
+
@client = OAuth::Client.find(client_id)
|
82
|
+
@client.present?
|
74
83
|
end
|
75
84
|
|
76
|
-
def
|
77
|
-
return
|
85
|
+
def validate_redirect_uri
|
86
|
+
return false if redirect_uri.blank?
|
87
|
+
|
88
|
+
Helpers::URIChecker.valid_for_authorization?(
|
89
|
+
redirect_uri,
|
90
|
+
client.redirect_uri
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
def validate_params
|
95
|
+
@missing_param = if response_type.blank?
|
96
|
+
:response_type
|
97
|
+
elsif @scope.blank? && server.default_scopes.blank?
|
98
|
+
:scope
|
99
|
+
end
|
78
100
|
|
101
|
+
@missing_param.nil?
|
102
|
+
end
|
103
|
+
|
104
|
+
def validate_response_type
|
105
|
+
server.authorization_response_types.include?(response_type)
|
106
|
+
end
|
107
|
+
|
108
|
+
def validate_scopes
|
79
109
|
Helpers::ScopeChecker.valid?(
|
80
110
|
scope_str: scope,
|
81
111
|
server_scopes: server.scopes,
|
82
|
-
app_scopes: client.
|
112
|
+
app_scopes: client.scopes,
|
83
113
|
grant_type: grant_type
|
84
114
|
)
|
85
115
|
end
|
@@ -88,19 +118,22 @@ module Doorkeeper
|
|
88
118
|
response_type == "code" ? AUTHORIZATION_CODE : IMPLICIT
|
89
119
|
end
|
90
120
|
|
91
|
-
def validate_redirect_uri
|
92
|
-
return false if redirect_uri.blank?
|
93
|
-
|
94
|
-
Helpers::URIChecker.valid_for_authorization?(
|
95
|
-
redirect_uri,
|
96
|
-
client.redirect_uri
|
97
|
-
)
|
98
|
-
end
|
99
|
-
|
100
121
|
def validate_code_challenge_method
|
101
122
|
code_challenge.blank? ||
|
102
123
|
(code_challenge_method.present? && code_challenge_method =~ /^plain$|^S256$/)
|
103
124
|
end
|
125
|
+
|
126
|
+
def pre_auth_hash
|
127
|
+
{
|
128
|
+
client_id: client.uid,
|
129
|
+
redirect_uri: redirect_uri,
|
130
|
+
state: state,
|
131
|
+
response_type: response_type,
|
132
|
+
scope: scope,
|
133
|
+
client_name: client.name,
|
134
|
+
status: I18n.t("doorkeeper.pre_authorization.status"),
|
135
|
+
}
|
136
|
+
end
|
104
137
|
end
|
105
138
|
end
|
106
139
|
end
|
@@ -13,6 +13,7 @@ module Doorkeeper
|
|
13
13
|
|
14
14
|
attr_accessor :access_token, :client, :credentials, :refresh_token,
|
15
15
|
:server
|
16
|
+
attr_reader :missing_param
|
16
17
|
|
17
18
|
def initialize(server, refresh_token, credentials, parameters = {})
|
18
19
|
@server = server
|
@@ -32,7 +33,7 @@ module Doorkeeper
|
|
32
33
|
def before_successful_response
|
33
34
|
refresh_token.transaction do
|
34
35
|
refresh_token.lock!
|
35
|
-
raise Errors::
|
36
|
+
raise Errors::InvalidGrantReuse if refresh_token.revoked?
|
36
37
|
|
37
38
|
refresh_token.revoke unless refresh_token_revoked_on_use?
|
38
39
|
create_access_token
|
@@ -76,7 +77,9 @@ module Doorkeeper
|
|
76
77
|
end
|
77
78
|
|
78
79
|
def validate_token_presence
|
79
|
-
refresh_token.
|
80
|
+
@missing_param = :refresh_token if refresh_token.blank? && @refresh_token_parameter.blank?
|
81
|
+
|
82
|
+
@missing_param.nil?
|
80
83
|
end
|
81
84
|
|
82
85
|
def validate_token
|
@@ -7,7 +7,7 @@ module Doorkeeper
|
|
7
7
|
# @see https://tools.ietf.org/html/rfc7662
|
8
8
|
class TokenIntrospection
|
9
9
|
attr_reader :server, :token
|
10
|
-
attr_reader :error
|
10
|
+
attr_reader :error, :invalid_request_reason
|
11
11
|
|
12
12
|
def initialize(server, token)
|
13
13
|
@server = server
|
@@ -25,6 +25,8 @@ module Doorkeeper
|
|
25
25
|
|
26
26
|
if @error == :invalid_token
|
27
27
|
OAuth::InvalidTokenResponse.from_access_token(authorized_token)
|
28
|
+
elsif @error == :invalid_request
|
29
|
+
OAuth::InvalidRequestResponse.from_request(self)
|
28
30
|
else
|
29
31
|
OAuth::ErrorResponse.new(name: @error)
|
30
32
|
end
|
@@ -70,6 +72,7 @@ module Doorkeeper
|
|
70
72
|
@error = :invalid_token unless valid_authorized_token?
|
71
73
|
else
|
72
74
|
@error = :invalid_request
|
75
|
+
@invalid_request_reason = :request_not_authorized
|
73
76
|
end
|
74
77
|
end
|
75
78
|
|
@@ -11,28 +11,14 @@ module Doorkeeper
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def authorize
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
@response = CodeResponse.new pre_auth,
|
18
|
-
auth,
|
19
|
-
response_on_fragment: true
|
20
|
-
else
|
21
|
-
@response = error_response
|
22
|
-
end
|
14
|
+
auth = Authorization::Token.new(pre_auth, resource_owner)
|
15
|
+
auth.issue_token
|
16
|
+
CodeResponse.new(pre_auth, auth, response_on_fragment: true)
|
23
17
|
end
|
24
18
|
|
25
19
|
def deny
|
26
20
|
pre_auth.error = :access_denied
|
27
|
-
error_response
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def error_response
|
33
|
-
ErrorResponse.from_request pre_auth,
|
34
|
-
redirect_uri: pre_auth.redirect_uri,
|
35
|
-
response_on_fragment: true
|
21
|
+
pre_auth.error_response
|
36
22
|
end
|
37
23
|
end
|
38
24
|
end
|
@@ -2,8 +2,6 @@
|
|
2
2
|
|
3
3
|
require "active_support/lazy_load_hooks"
|
4
4
|
|
5
|
-
require "doorkeeper/orm/active_record/stale_records_cleaner"
|
6
|
-
|
7
5
|
module Doorkeeper
|
8
6
|
module Orm
|
9
7
|
# ActiveRecord ORM for Doorkeeper entity models.
|
@@ -17,6 +15,8 @@ module Doorkeeper
|
|
17
15
|
module ActiveRecord
|
18
16
|
def self.initialize_models!
|
19
17
|
lazy_load do
|
18
|
+
require "doorkeeper/orm/active_record/stale_records_cleaner"
|
19
|
+
require "doorkeeper/orm/active_record/redirect_uri_validator"
|
20
20
|
require "doorkeeper/orm/active_record/access_grant"
|
21
21
|
require "doorkeeper/orm/active_record/access_token"
|
22
22
|
require "doorkeeper/orm/active_record/application"
|
@@ -11,7 +11,7 @@ module Doorkeeper
|
|
11
11
|
|
12
12
|
validates :name, :secret, :uid, presence: true
|
13
13
|
validates :uid, uniqueness: { case_sensitive: true }
|
14
|
-
validates :redirect_uri, redirect_uri: true
|
14
|
+
validates :redirect_uri, "doorkeeper/redirect_uri": true
|
15
15
|
validates :confidential, inclusion: { in: [true, false] }
|
16
16
|
|
17
17
|
validate :scopes_match_configured, if: :enforce_scopes?
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
|
5
|
+
module Doorkeeper
|
6
|
+
# ActiveModel validator for redirect URI validation in according
|
7
|
+
# to OAuth standards and Doorkeeper configuration.
|
8
|
+
class RedirectUriValidator < ActiveModel::EachValidator
|
9
|
+
def validate_each(record, attribute, value)
|
10
|
+
if value.blank?
|
11
|
+
return if Doorkeeper.configuration.allow_blank_redirect_uri?(record)
|
12
|
+
|
13
|
+
record.errors.add(attribute, :blank)
|
14
|
+
else
|
15
|
+
value.split.each do |val|
|
16
|
+
next if oob_redirect_uri?(val)
|
17
|
+
|
18
|
+
uri = ::URI.parse(val)
|
19
|
+
record.errors.add(attribute, :forbidden_uri) if forbidden_uri?(uri)
|
20
|
+
record.errors.add(attribute, :fragment_present) unless uri.fragment.nil?
|
21
|
+
record.errors.add(attribute, :unspecified_scheme) if unspecified_scheme?(uri)
|
22
|
+
record.errors.add(attribute, :relative_uri) if relative_uri?(uri)
|
23
|
+
record.errors.add(attribute, :secured_uri) if invalid_ssl_uri?(uri)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
rescue URI::InvalidURIError
|
27
|
+
record.errors.add(attribute, :invalid_uri)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def oob_redirect_uri?(uri)
|
33
|
+
Doorkeeper::OAuth::NonStandard::IETF_WG_OAUTH2_OOB_METHODS.include?(uri)
|
34
|
+
end
|
35
|
+
|
36
|
+
def forbidden_uri?(uri)
|
37
|
+
Doorkeeper.configuration.forbid_redirect_uri.call(uri)
|
38
|
+
end
|
39
|
+
|
40
|
+
def unspecified_scheme?(uri)
|
41
|
+
return true if uri.opaque.present?
|
42
|
+
|
43
|
+
%w[localhost].include?(uri.try(:scheme))
|
44
|
+
end
|
45
|
+
|
46
|
+
def relative_uri?(uri)
|
47
|
+
uri.scheme.nil? && uri.host.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
def invalid_ssl_uri?(uri)
|
51
|
+
forces_ssl = Doorkeeper.configuration.force_ssl_in_redirect_uri
|
52
|
+
non_https = uri.try(:scheme) == "http"
|
53
|
+
|
54
|
+
if forces_ssl.respond_to?(:call)
|
55
|
+
forces_ssl.call(uri) && non_https
|
56
|
+
else
|
57
|
+
forces_ssl && non_https
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/doorkeeper/request.rb
CHANGED
@@ -4,30 +4,25 @@ module Doorkeeper
|
|
4
4
|
module Request
|
5
5
|
class << self
|
6
6
|
def authorization_strategy(response_type)
|
7
|
-
|
8
|
-
rescue NameError
|
9
|
-
raise Errors::InvalidAuthorizationStrategy
|
7
|
+
build_strategy_class(response_type)
|
10
8
|
end
|
11
9
|
|
12
10
|
def token_strategy(grant_type)
|
11
|
+
raise Errors::MissingRequiredParameter, :grant_type if grant_type.blank?
|
12
|
+
|
13
13
|
get_strategy(grant_type, token_grant_types)
|
14
14
|
rescue NameError
|
15
15
|
raise Errors::InvalidTokenStrategy
|
16
16
|
end
|
17
17
|
|
18
|
-
def get_strategy(
|
19
|
-
raise
|
20
|
-
raise NameError unless available.include?(grant_or_request_type.to_s)
|
18
|
+
def get_strategy(grant_type, available)
|
19
|
+
raise NameError unless available.include?(grant_type.to_s)
|
21
20
|
|
22
|
-
build_strategy_class(
|
21
|
+
build_strategy_class(grant_type)
|
23
22
|
end
|
24
23
|
|
25
24
|
private
|
26
25
|
|
27
|
-
def authorization_response_types
|
28
|
-
Doorkeeper.configuration.authorization_response_types
|
29
|
-
end
|
30
|
-
|
31
26
|
def token_grant_types
|
32
27
|
Doorkeeper.configuration.token_grant_types
|
33
28
|
end
|