doorkeeper 5.5.2 → 5.6.6
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 +76 -8
- data/README.md +16 -15
- data/app/controllers/doorkeeper/authorizations_controller.rb +21 -7
- data/app/controllers/doorkeeper/tokens_controller.rb +11 -8
- data/app/helpers/doorkeeper/dashboard_helper.rb +1 -1
- data/app/views/doorkeeper/authorizations/error.html.erb +3 -1
- data/app/views/doorkeeper/authorizations/new.html.erb +16 -16
- data/config/locales/en.yml +3 -0
- data/lib/doorkeeper/config/abstract_builder.rb +1 -1
- data/lib/doorkeeper/config/validations.rb +3 -3
- data/lib/doorkeeper/config.rb +44 -54
- data/lib/doorkeeper/engine.rb +10 -3
- data/lib/doorkeeper/helpers/controller.rb +1 -1
- data/lib/doorkeeper/models/access_grant_mixin.rb +1 -1
- data/lib/doorkeeper/models/access_token_mixin.rb +7 -7
- data/lib/doorkeeper/models/concerns/expiration_time_sql_math.rb +88 -0
- data/lib/doorkeeper/models/concerns/polymorphic_resource_owner.rb +30 -0
- data/lib/doorkeeper/oauth/authorization/code.rb +7 -1
- data/lib/doorkeeper/oauth/authorization/token.rb +7 -1
- data/lib/doorkeeper/oauth/authorization_code_request.rb +17 -7
- data/lib/doorkeeper/oauth/base_request.rb +11 -10
- data/lib/doorkeeper/oauth/client_credentials/creator.rb +10 -13
- data/lib/doorkeeper/oauth/client_credentials/validator.rb +1 -2
- data/lib/doorkeeper/oauth/error_response.rb +1 -2
- data/lib/doorkeeper/oauth/forbidden_token_response.rb +2 -1
- data/lib/doorkeeper/oauth/helpers/unique_token.rb +2 -2
- data/lib/doorkeeper/oauth/helpers/uri_checker.rb +4 -4
- data/lib/doorkeeper/oauth/password_access_token_request.rb +3 -3
- data/lib/doorkeeper/oauth/pre_authorization.rb +11 -10
- data/lib/doorkeeper/oauth/refresh_token_request.rb +13 -5
- data/lib/doorkeeper/oauth/token_introspection.rb +4 -4
- data/lib/doorkeeper/oauth/token_response.rb +1 -2
- data/lib/doorkeeper/orm/active_record/mixins/access_grant.rb +1 -6
- data/lib/doorkeeper/orm/active_record/mixins/access_token.rb +22 -4
- data/lib/doorkeeper/orm/active_record/mixins/application.rb +13 -1
- data/lib/doorkeeper/orm/active_record/redirect_uri_validator.rb +2 -2
- data/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb +5 -2
- data/lib/doorkeeper/orm/active_record.rb +30 -37
- data/lib/doorkeeper/rails/routes.rb +12 -3
- data/lib/doorkeeper/rake/setup.rake +0 -5
- data/lib/doorkeeper/version.rb +2 -2
- data/lib/doorkeeper.rb +73 -5
- data/lib/generators/doorkeeper/templates/initializer.rb +25 -7
- data/lib/generators/doorkeeper/templates/migration.rb.erb +15 -5
- metadata +21 -19
|
@@ -13,6 +13,7 @@ module Doorkeeper
|
|
|
13
13
|
include Models::SecretStorable
|
|
14
14
|
include Models::Scopes
|
|
15
15
|
include Models::ResourceOwnerable
|
|
16
|
+
include Models::ExpirationTimeSqlMath
|
|
16
17
|
|
|
17
18
|
module ClassMethods
|
|
18
19
|
# Returns an instance of the Doorkeeper::AccessToken with
|
|
@@ -86,8 +87,9 @@ module Doorkeeper
|
|
|
86
87
|
# @return [Doorkeeper::AccessToken, nil] Access Token instance or
|
|
87
88
|
# nil if matching record was not found
|
|
88
89
|
#
|
|
89
|
-
def matching_token_for(application, resource_owner, scopes)
|
|
90
|
+
def matching_token_for(application, resource_owner, scopes, include_expired: true)
|
|
90
91
|
tokens = authorized_tokens_for(application&.id, resource_owner)
|
|
92
|
+
tokens = tokens.not_expired unless include_expired
|
|
91
93
|
find_matching_token(tokens, application, scopes)
|
|
92
94
|
end
|
|
93
95
|
|
|
@@ -104,9 +106,7 @@ module Doorkeeper
|
|
|
104
106
|
#
|
|
105
107
|
# ActiveRecord 5.x - 6.x ignores custom ordering so we can't perform a
|
|
106
108
|
# database sort by created_at, so we need to load all the matching records,
|
|
107
|
-
# sort them and find latest one.
|
|
108
|
-
# query using Time math if possible, but we n eed to consider ORM and
|
|
109
|
-
# different databases support.
|
|
109
|
+
# sort them and find latest one.
|
|
110
110
|
#
|
|
111
111
|
# @param relation [ActiveRecord::Relation]
|
|
112
112
|
# Access tokens relation
|
|
@@ -181,7 +181,7 @@ module Doorkeeper
|
|
|
181
181
|
#
|
|
182
182
|
def find_or_create_for(application:, resource_owner:, scopes:, **token_attributes)
|
|
183
183
|
if Doorkeeper.config.reuse_access_token
|
|
184
|
-
access_token = matching_token_for(application, resource_owner, scopes)
|
|
184
|
+
access_token = matching_token_for(application, resource_owner, scopes, include_expired: false)
|
|
185
185
|
|
|
186
186
|
return access_token if access_token&.reusable?
|
|
187
187
|
end
|
|
@@ -213,7 +213,7 @@ module Doorkeeper
|
|
|
213
213
|
# @return [Doorkeeper::AccessToken] new access token
|
|
214
214
|
#
|
|
215
215
|
def create_for(application:, resource_owner:, scopes:, **token_attributes)
|
|
216
|
-
token_attributes[:
|
|
216
|
+
token_attributes[:application] = application
|
|
217
217
|
token_attributes[:scopes] = scopes.to_s
|
|
218
218
|
|
|
219
219
|
if Doorkeeper.config.polymorphic_resource_owner?
|
|
@@ -279,7 +279,7 @@ module Doorkeeper
|
|
|
279
279
|
end
|
|
280
280
|
|
|
281
281
|
# Access Token type: Bearer.
|
|
282
|
-
# @see https://
|
|
282
|
+
# @see https://datatracker.ietf.org/doc/html/rfc6750
|
|
283
283
|
# The OAuth 2.0 Authorization Framework: Bearer Token Usage
|
|
284
284
|
#
|
|
285
285
|
def token_type
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doorkeeper
|
|
4
|
+
module Models
|
|
5
|
+
module ExpirationTimeSqlMath
|
|
6
|
+
extend ::ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
class ExpirationTimeSqlGenerator
|
|
9
|
+
attr_reader :model
|
|
10
|
+
|
|
11
|
+
delegate :table_name, to: :@model
|
|
12
|
+
|
|
13
|
+
def initialize(model)
|
|
14
|
+
@model = model
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def generate_sql
|
|
18
|
+
raise "`generate_sql` should be overridden for a #{self.class.name}!"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class MySqlExpirationTimeSqlGenerator < ExpirationTimeSqlGenerator
|
|
23
|
+
def generate_sql
|
|
24
|
+
Arel.sql("DATE_ADD(#{table_name}.created_at, INTERVAL #{table_name}.expires_in SECOND)")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class SqlLiteExpirationTimeSqlGenerator < ExpirationTimeSqlGenerator
|
|
29
|
+
def generate_sql
|
|
30
|
+
Arel.sql("DATETIME(#{table_name}.created_at, '+' || #{table_name}.expires_in || ' SECONDS')")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class SqlServerExpirationTimeSqlGenerator < ExpirationTimeSqlGenerator
|
|
35
|
+
def generate_sql
|
|
36
|
+
Arel.sql("DATEADD(second, #{table_name}.expires_in, #{table_name}.created_at) AT TIME ZONE 'UTC'")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class OracleExpirationTimeSqlGenerator < ExpirationTimeSqlGenerator
|
|
41
|
+
def generate_sql
|
|
42
|
+
Arel.sql("#{table_name}.created_at + INTERVAL to_char(#{table_name}.expires_in) second")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class PostgresExpirationTimeSqlGenerator < ExpirationTimeSqlGenerator
|
|
47
|
+
def generate_sql
|
|
48
|
+
Arel.sql("#{table_name}.created_at + #{table_name}.expires_in * INTERVAL '1 SECOND'")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
ADAPTERS_MAPPING = {
|
|
53
|
+
"sqlite" => SqlLiteExpirationTimeSqlGenerator,
|
|
54
|
+
"sqlite3" => SqlLiteExpirationTimeSqlGenerator,
|
|
55
|
+
"postgis" => PostgresExpirationTimeSqlGenerator,
|
|
56
|
+
"postgresql" => PostgresExpirationTimeSqlGenerator,
|
|
57
|
+
"mysql" => MySqlExpirationTimeSqlGenerator,
|
|
58
|
+
"mysql2" => MySqlExpirationTimeSqlGenerator,
|
|
59
|
+
"trilogy" => MySqlExpirationTimeSqlGenerator,
|
|
60
|
+
"sqlserver" => SqlServerExpirationTimeSqlGenerator,
|
|
61
|
+
"oracleenhanced" => OracleExpirationTimeSqlGenerator,
|
|
62
|
+
}.freeze
|
|
63
|
+
|
|
64
|
+
module ClassMethods
|
|
65
|
+
def supports_expiration_time_math?
|
|
66
|
+
ADAPTERS_MAPPING.key?(adapter_name.downcase) ||
|
|
67
|
+
respond_to?(:custom_expiration_time_sql)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def expiration_time_sql
|
|
71
|
+
if respond_to?(:custom_expiration_time_sql)
|
|
72
|
+
custom_expiration_time_sql
|
|
73
|
+
else
|
|
74
|
+
expiration_time_sql_expression
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def expiration_time_sql_expression
|
|
79
|
+
ADAPTERS_MAPPING.fetch(adapter_name.downcase).new(self).generate_sql
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def adapter_name
|
|
83
|
+
ActiveRecord::Base.connection.adapter_name
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doorkeeper
|
|
4
|
+
module Models
|
|
5
|
+
module PolymorphicResourceOwner
|
|
6
|
+
module ForAccessGrant
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
included do
|
|
10
|
+
if Doorkeeper.config.polymorphic_resource_owner?
|
|
11
|
+
belongs_to :resource_owner, polymorphic: true, optional: false
|
|
12
|
+
else
|
|
13
|
+
validates :resource_owner_id, presence: true
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module ForAccessToken
|
|
19
|
+
extend ActiveSupport::Concern
|
|
20
|
+
|
|
21
|
+
included do
|
|
22
|
+
if Doorkeeper.config.polymorphic_resource_owner?
|
|
23
|
+
belongs_to :resource_owner, polymorphic: true, optional: true
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
@@ -45,7 +45,13 @@ module Doorkeeper
|
|
|
45
45
|
attributes[:resource_owner_id] = resource_owner.id
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
pkce_attributes.merge(attributes)
|
|
48
|
+
pkce_attributes.merge(attributes).merge(custom_attributes)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def custom_attributes
|
|
52
|
+
# Custom access token attributes are saved into the access grant,
|
|
53
|
+
# and then included in subsequently generated access tokens.
|
|
54
|
+
@pre_auth.custom_access_token_attributes.to_h.with_indifferent_access
|
|
49
55
|
end
|
|
50
56
|
|
|
51
57
|
def pkce_attributes
|
|
@@ -60,7 +60,7 @@ module Doorkeeper
|
|
|
60
60
|
)
|
|
61
61
|
|
|
62
62
|
@token = Doorkeeper.config.access_token_model.find_or_create_for(
|
|
63
|
-
application:
|
|
63
|
+
application: application,
|
|
64
64
|
resource_owner: resource_owner,
|
|
65
65
|
scopes: pre_auth.scopes,
|
|
66
66
|
expires_in: self.class.access_token_expires_in(Doorkeeper.config, context),
|
|
@@ -68,6 +68,12 @@ module Doorkeeper
|
|
|
68
68
|
)
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
+
def application
|
|
72
|
+
return unless pre_auth.client
|
|
73
|
+
|
|
74
|
+
pre_auth.client.is_a?(Doorkeeper.config.application_model) ? pre_auth.client : pre_auth.client.application
|
|
75
|
+
end
|
|
76
|
+
|
|
71
77
|
def oob_redirect
|
|
72
78
|
{
|
|
73
79
|
controller: controller,
|
|
@@ -6,7 +6,7 @@ module Doorkeeper
|
|
|
6
6
|
validate :params, error: :invalid_request
|
|
7
7
|
validate :client, error: :invalid_client
|
|
8
8
|
validate :grant, error: :invalid_grant
|
|
9
|
-
# @see https://
|
|
9
|
+
# @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
|
|
10
10
|
validate :redirect_uri, error: :invalid_grant
|
|
11
11
|
validate :code_verifier, error: :invalid_grant
|
|
12
12
|
|
|
@@ -35,6 +35,7 @@ module Doorkeeper
|
|
|
35
35
|
grant.application,
|
|
36
36
|
resource_owner,
|
|
37
37
|
grant.scopes,
|
|
38
|
+
custom_token_attributes_with_data,
|
|
38
39
|
server,
|
|
39
40
|
)
|
|
40
41
|
end
|
|
@@ -55,11 +56,12 @@ module Doorkeeper
|
|
|
55
56
|
end
|
|
56
57
|
|
|
57
58
|
def validate_params
|
|
58
|
-
@missing_param =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
@missing_param =
|
|
60
|
+
if grant&.uses_pkce? && code_verifier.blank?
|
|
61
|
+
:code_verifier
|
|
62
|
+
elsif redirect_uri.blank?
|
|
63
|
+
:redirect_uri
|
|
64
|
+
end
|
|
63
65
|
|
|
64
66
|
@missing_param.nil?
|
|
65
67
|
end
|
|
@@ -97,7 +99,15 @@ module Doorkeeper
|
|
|
97
99
|
end
|
|
98
100
|
|
|
99
101
|
def generate_code_challenge(code_verifier)
|
|
100
|
-
|
|
102
|
+
Doorkeeper.config.access_grant_model.generate_code_challenge(code_verifier)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def custom_token_attributes_with_data
|
|
106
|
+
grant
|
|
107
|
+
.attributes
|
|
108
|
+
.with_indifferent_access
|
|
109
|
+
.slice(*Doorkeeper.config.custom_access_token_attributes)
|
|
110
|
+
.symbolize_keys
|
|
101
111
|
end
|
|
102
112
|
end
|
|
103
113
|
end
|
|
@@ -26,27 +26,28 @@ module Doorkeeper
|
|
|
26
26
|
@scopes ||= build_scopes
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def find_or_create_access_token(client, resource_owner, scopes, server)
|
|
29
|
+
def find_or_create_access_token(client, resource_owner, scopes, custom_attributes, server)
|
|
30
30
|
context = Authorization::Token.build_context(client, grant_type, scopes, resource_owner)
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
application = client.is_a?(Doorkeeper.config.application_model) ? client : client&.application
|
|
32
|
+
|
|
33
|
+
token_attributes = {
|
|
34
|
+
application: application,
|
|
33
35
|
resource_owner: resource_owner,
|
|
34
36
|
scopes: scopes,
|
|
35
37
|
expires_in: Authorization::Token.access_token_expires_in(server, context),
|
|
36
38
|
use_refresh_token: Authorization::Token.refresh_token_enabled?(server, context),
|
|
37
|
-
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@access_token =
|
|
42
|
+
Doorkeeper.config.access_token_model.find_or_create_for(**token_attributes.merge(custom_attributes))
|
|
38
43
|
end
|
|
39
44
|
|
|
40
45
|
def before_successful_response
|
|
41
|
-
|
|
46
|
+
Doorkeeper.config.before_successful_strategy_response.call(self)
|
|
42
47
|
end
|
|
43
48
|
|
|
44
49
|
def after_successful_response
|
|
45
|
-
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def server_config
|
|
49
|
-
Doorkeeper.config
|
|
50
|
+
Doorkeeper.config.after_successful_strategy_response.call(self, @response)
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
private
|
|
@@ -8,13 +8,14 @@ module Doorkeeper
|
|
|
8
8
|
existing_token = nil
|
|
9
9
|
|
|
10
10
|
if lookup_existing_token?
|
|
11
|
-
existing_token =
|
|
12
|
-
return existing_token if
|
|
11
|
+
existing_token = find_active_existing_token_for(client, scopes)
|
|
12
|
+
return existing_token if Doorkeeper.config.reuse_access_token && existing_token&.reusable?
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
with_revocation(existing_token: existing_token) do
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
application = client.is_a?(Doorkeeper.config.application_model) ? client : client&.application
|
|
17
|
+
Doorkeeper.config.access_token_model.create_for(
|
|
18
|
+
application: application,
|
|
18
19
|
resource_owner: nil,
|
|
19
20
|
scopes: scopes,
|
|
20
21
|
**attributes,
|
|
@@ -25,7 +26,7 @@ module Doorkeeper
|
|
|
25
26
|
private
|
|
26
27
|
|
|
27
28
|
def with_revocation(existing_token:)
|
|
28
|
-
if existing_token &&
|
|
29
|
+
if existing_token && Doorkeeper.config.revoke_previous_client_credentials_token?
|
|
29
30
|
existing_token.with_lock do
|
|
30
31
|
raise Errors::DoorkeeperError, :invalid_token_reuse if existing_token.revoked?
|
|
31
32
|
|
|
@@ -39,16 +40,12 @@ module Doorkeeper
|
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
def lookup_existing_token?
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
Doorkeeper.config.reuse_access_token ||
|
|
44
|
+
Doorkeeper.config.revoke_previous_client_credentials_token?
|
|
44
45
|
end
|
|
45
46
|
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def server_config
|
|
51
|
-
Doorkeeper.config
|
|
47
|
+
def find_active_existing_token_for(client, scopes)
|
|
48
|
+
Doorkeeper.config.access_token_model.matching_token_for(client, nil, scopes, include_expired: false)
|
|
52
49
|
end
|
|
53
50
|
end
|
|
54
51
|
end
|
|
@@ -35,13 +35,12 @@ module Doorkeeper
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def validate_scopes
|
|
38
|
-
return true if @request.scopes.blank?
|
|
39
|
-
|
|
40
38
|
application_scopes = if @client.present?
|
|
41
39
|
@client.application.scopes
|
|
42
40
|
else
|
|
43
41
|
""
|
|
44
42
|
end
|
|
43
|
+
return true if @request.scopes.blank? && application_scopes.blank?
|
|
45
44
|
|
|
46
45
|
ScopeChecker.valid?(
|
|
47
46
|
scope_str: @request.scopes.to_s,
|
|
@@ -23,7 +23,8 @@ module Doorkeeper
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def description
|
|
26
|
-
@description ||=
|
|
26
|
+
@description ||= I18n.t("doorkeeper.errors.messages.forbidden_token.missing_scope",
|
|
27
|
+
oauth_scopes: @scopes.map(&:to_s).join(" "),)
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
protected
|
|
@@ -11,8 +11,8 @@ module Doorkeeper
|
|
|
11
11
|
# Access Token value must be 1*VSCHAR or
|
|
12
12
|
# 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="
|
|
13
13
|
#
|
|
14
|
-
# @see https://
|
|
15
|
-
# @see https://
|
|
14
|
+
# @see https://datatracker.ietf.org/doc/html/rfc6749#appendix-A.12
|
|
15
|
+
# @see https://datatracker.ietf.org/doc/html/rfc6750#section-2.1
|
|
16
16
|
#
|
|
17
17
|
generator = options.delete(:generator) || SecureRandom.method(default_generator_method)
|
|
18
18
|
token_size = options.delete(:size) || 32
|
|
@@ -28,7 +28,7 @@ module Doorkeeper
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
# RFC8252, Paragraph 7.3
|
|
31
|
-
# @see https://
|
|
31
|
+
# @see https://datatracker.ietf.org/doc/html/rfc8252#section-7.3
|
|
32
32
|
if loopback_uri?(url) && loopback_uri?(client_url)
|
|
33
33
|
url.port = nil
|
|
34
34
|
client_url.port = nil
|
|
@@ -61,9 +61,9 @@ module Doorkeeper
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def self.valid_scheme?(uri)
|
|
64
|
-
return false if uri.scheme.
|
|
64
|
+
return false if uri.scheme.blank?
|
|
65
65
|
|
|
66
|
-
%w[localhost].
|
|
66
|
+
%w[localhost].exclude?(uri.scheme)
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
def self.hypertext_scheme?(uri)
|
|
@@ -71,7 +71,7 @@ module Doorkeeper
|
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
def self.iff_host?(uri)
|
|
74
|
-
!(hypertext_scheme?(uri) && uri.host.
|
|
74
|
+
!(hypertext_scheme?(uri) && uri.host.blank?)
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
def self.oob_uri?(uri)
|
|
@@ -25,7 +25,7 @@ module Doorkeeper
|
|
|
25
25
|
private
|
|
26
26
|
|
|
27
27
|
def before_successful_response
|
|
28
|
-
find_or_create_access_token(client, resource_owner, scopes, server)
|
|
28
|
+
find_or_create_access_token(client, resource_owner, scopes, {}, server)
|
|
29
29
|
super
|
|
30
30
|
end
|
|
31
31
|
|
|
@@ -57,7 +57,7 @@ module Doorkeeper
|
|
|
57
57
|
#
|
|
58
58
|
# o authenticate the client if client authentication is included,
|
|
59
59
|
#
|
|
60
|
-
# @see https://
|
|
60
|
+
# @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.3
|
|
61
61
|
#
|
|
62
62
|
def validate_client
|
|
63
63
|
if Doorkeeper.config.skip_client_authentication_for_password_grant
|
|
@@ -68,7 +68,7 @@ module Doorkeeper
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def validate_client_supports_grant_flow
|
|
71
|
-
|
|
71
|
+
Doorkeeper.config.allow_grant_flow_for_client?(grant_type, client&.application)
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
end
|
|
@@ -18,19 +18,20 @@ module Doorkeeper
|
|
|
18
18
|
|
|
19
19
|
attr_reader :client, :code_challenge, :code_challenge_method, :missing_param,
|
|
20
20
|
:redirect_uri, :resource_owner, :response_type, :state,
|
|
21
|
-
:authorization_response_flow, :response_mode
|
|
21
|
+
:authorization_response_flow, :response_mode, :custom_access_token_attributes
|
|
22
22
|
|
|
23
23
|
def initialize(server, parameters = {}, resource_owner = nil)
|
|
24
|
-
@server
|
|
25
|
-
@client_id
|
|
26
|
-
@response_type
|
|
27
|
-
@response_mode
|
|
28
|
-
@redirect_uri
|
|
29
|
-
@scope
|
|
30
|
-
@state
|
|
31
|
-
@code_challenge
|
|
24
|
+
@server = server
|
|
25
|
+
@client_id = parameters[:client_id]
|
|
26
|
+
@response_type = parameters[:response_type]
|
|
27
|
+
@response_mode = parameters[:response_mode]
|
|
28
|
+
@redirect_uri = parameters[:redirect_uri]
|
|
29
|
+
@scope = parameters[:scope]
|
|
30
|
+
@state = parameters[:state]
|
|
31
|
+
@code_challenge = parameters[:code_challenge]
|
|
32
32
|
@code_challenge_method = parameters[:code_challenge_method]
|
|
33
|
-
@resource_owner
|
|
33
|
+
@resource_owner = resource_owner
|
|
34
|
+
@custom_access_token_attributes = parameters.slice(*Doorkeeper.config.custom_access_token_attributes)
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
def authorizable?
|
|
@@ -26,7 +26,7 @@ module Doorkeeper
|
|
|
26
26
|
private
|
|
27
27
|
|
|
28
28
|
def load_client(credentials)
|
|
29
|
-
|
|
29
|
+
Doorkeeper.config.application_model.by_uid_and_secret(credentials.uid, credentials.secret)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def before_successful_response
|
|
@@ -41,7 +41,7 @@ module Doorkeeper
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def refresh_token_revoked_on_use?
|
|
44
|
-
|
|
44
|
+
Doorkeeper.config.access_token_model.refresh_token_revoked_on_use?
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def default_scopes
|
|
@@ -49,7 +49,7 @@ module Doorkeeper
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def create_access_token
|
|
52
|
-
attributes = {}
|
|
52
|
+
attributes = {}.merge(custom_token_attributes_with_data)
|
|
53
53
|
|
|
54
54
|
resource_owner =
|
|
55
55
|
if Doorkeeper.config.polymorphic_resource_owner?
|
|
@@ -75,7 +75,7 @@ module Doorkeeper
|
|
|
75
75
|
# Here we assume that TTL of the token received after refreshing should be
|
|
76
76
|
# the same as that of the original token.
|
|
77
77
|
#
|
|
78
|
-
@access_token =
|
|
78
|
+
@access_token = Doorkeeper.config.access_token_model.create_for(
|
|
79
79
|
application: refresh_token.application,
|
|
80
80
|
resource_owner: resource_owner,
|
|
81
81
|
scopes: scopes,
|
|
@@ -101,7 +101,7 @@ module Doorkeeper
|
|
|
101
101
|
client.present?
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
-
# @see https://
|
|
104
|
+
# @see https://datatracker.ietf.org/doc/html/rfc6749#section-1.5
|
|
105
105
|
#
|
|
106
106
|
def validate_client_match
|
|
107
107
|
return true if refresh_token.application_id.blank?
|
|
@@ -119,6 +119,14 @@ module Doorkeeper
|
|
|
119
119
|
true
|
|
120
120
|
end
|
|
121
121
|
end
|
|
122
|
+
|
|
123
|
+
def custom_token_attributes_with_data
|
|
124
|
+
refresh_token
|
|
125
|
+
.attributes
|
|
126
|
+
.with_indifferent_access
|
|
127
|
+
.slice(*Doorkeeper.config.custom_access_token_attributes)
|
|
128
|
+
.symbolize_keys
|
|
129
|
+
end
|
|
122
130
|
end
|
|
123
131
|
end
|
|
124
132
|
end
|
|
@@ -4,7 +4,7 @@ module Doorkeeper
|
|
|
4
4
|
module OAuth
|
|
5
5
|
# RFC7662 OAuth 2.0 Token Introspection
|
|
6
6
|
#
|
|
7
|
-
# @see https://
|
|
7
|
+
# @see https://datatracker.ietf.org/doc/html/rfc7662
|
|
8
8
|
class TokenIntrospection
|
|
9
9
|
def initialize(server, token)
|
|
10
10
|
@server = server
|
|
@@ -107,7 +107,7 @@ module Doorkeeper
|
|
|
107
107
|
# authorization server SHOULD NOT include any additional information
|
|
108
108
|
# about an inactive token, including why the token is inactive.
|
|
109
109
|
#
|
|
110
|
-
# @see https://
|
|
110
|
+
# @see https://datatracker.ietf.org/doc/html/rfc7662 2.2. Introspection Response
|
|
111
111
|
#
|
|
112
112
|
def failure_response
|
|
113
113
|
{
|
|
@@ -134,7 +134,7 @@ module Doorkeeper
|
|
|
134
134
|
# Since resource servers using token introspection rely on the
|
|
135
135
|
# authorization server to determine the state of a token, the
|
|
136
136
|
# authorization server MUST perform all applicable checks against a
|
|
137
|
-
# token's state.
|
|
137
|
+
# token's state. For instance, these tests include the following:
|
|
138
138
|
#
|
|
139
139
|
# o If the token can expire, the authorization server MUST determine
|
|
140
140
|
# whether or not the token has expired.
|
|
@@ -186,7 +186,7 @@ module Doorkeeper
|
|
|
186
186
|
# Provides context (controller) and token for generating developer-specific
|
|
187
187
|
# response.
|
|
188
188
|
#
|
|
189
|
-
# @see https://
|
|
189
|
+
# @see https://datatracker.ietf.org/doc/html/rfc7662#section-2.2
|
|
190
190
|
#
|
|
191
191
|
def customize_response(response)
|
|
192
192
|
customized_response = Doorkeeper.config.custom_introspection_response.call(
|
|
@@ -6,6 +6,7 @@ module Doorkeeper::Orm::ActiveRecord::Mixins
|
|
|
6
6
|
|
|
7
7
|
included do
|
|
8
8
|
self.table_name = compute_doorkeeper_table_name
|
|
9
|
+
self.strict_loading_by_default = false if respond_to?(:strict_loading_by_default)
|
|
9
10
|
|
|
10
11
|
include ::Doorkeeper::AccessGrantMixin
|
|
11
12
|
|
|
@@ -13,12 +14,6 @@ module Doorkeeper::Orm::ActiveRecord::Mixins
|
|
|
13
14
|
optional: true,
|
|
14
15
|
inverse_of: :access_grants
|
|
15
16
|
|
|
16
|
-
if Doorkeeper.config.polymorphic_resource_owner?
|
|
17
|
-
belongs_to :resource_owner, polymorphic: true, optional: false
|
|
18
|
-
else
|
|
19
|
-
validates :resource_owner_id, presence: true
|
|
20
|
-
end
|
|
21
|
-
|
|
22
17
|
validates :application_id,
|
|
23
18
|
:token,
|
|
24
19
|
:expires_in,
|
|
@@ -6,6 +6,7 @@ module Doorkeeper::Orm::ActiveRecord::Mixins
|
|
|
6
6
|
|
|
7
7
|
included do
|
|
8
8
|
self.table_name = compute_doorkeeper_table_name
|
|
9
|
+
self.strict_loading_by_default = false if respond_to?(:strict_loading_by_default)
|
|
9
10
|
|
|
10
11
|
include ::Doorkeeper::AccessTokenMixin
|
|
11
12
|
|
|
@@ -13,10 +14,6 @@ module Doorkeeper::Orm::ActiveRecord::Mixins
|
|
|
13
14
|
inverse_of: :access_tokens,
|
|
14
15
|
optional: true
|
|
15
16
|
|
|
16
|
-
if Doorkeeper.config.polymorphic_resource_owner?
|
|
17
|
-
belongs_to :resource_owner, polymorphic: true, optional: true
|
|
18
|
-
end
|
|
19
|
-
|
|
20
17
|
validates :token, presence: true, uniqueness: { case_sensitive: true }
|
|
21
18
|
validates :refresh_token, uniqueness: { case_sensitive: true }, if: :use_refresh_token?
|
|
22
19
|
|
|
@@ -47,6 +44,27 @@ module Doorkeeper::Orm::ActiveRecord::Mixins
|
|
|
47
44
|
column_names.include?("previous_refresh_token")
|
|
48
45
|
end
|
|
49
46
|
|
|
47
|
+
# Returns non-expired and non-revoked access tokens
|
|
48
|
+
def not_expired
|
|
49
|
+
relation = where(revoked_at: nil)
|
|
50
|
+
|
|
51
|
+
if supports_expiration_time_math?
|
|
52
|
+
# have not reached the expiration time or it never expires
|
|
53
|
+
relation.where("#{expiration_time_sql} > ?", Time.now.utc).or(
|
|
54
|
+
relation.where(expires_in: nil)
|
|
55
|
+
)
|
|
56
|
+
else
|
|
57
|
+
::Kernel.warn <<~WARNING.squish
|
|
58
|
+
[DOORKEEPER] Doorkeeper doesn't support expiration time math for your database adapter (#{adapter_name}).
|
|
59
|
+
Please add a class method `custom_expiration_time_sql` for your AccessToken class/mixin to provide a custom
|
|
60
|
+
SQL expression to calculate access token expiration time. See lib/doorkeeper/orm/active_record/mixins/access_token.rb
|
|
61
|
+
for more details.
|
|
62
|
+
WARNING
|
|
63
|
+
|
|
64
|
+
relation
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
50
68
|
private
|
|
51
69
|
|
|
52
70
|
def compute_doorkeeper_table_name
|