doorkeeper 5.0.3 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.hound.yml +2 -1
- data/.rubocop.yml +37 -4
- data/.travis.yml +11 -21
- data/Appraisals +29 -7
- data/Dangerfile +5 -2
- data/Gemfile +19 -5
- data/NEWS.md +81 -21
- data/README.md +75 -485
- data/Rakefile +10 -8
- data/app/controllers/doorkeeper/application_controller.rb +1 -2
- data/app/controllers/doorkeeper/application_metal_controller.rb +2 -13
- data/app/controllers/doorkeeper/applications_controller.rb +20 -8
- data/app/controllers/doorkeeper/authorized_applications_controller.rb +1 -1
- data/app/controllers/doorkeeper/token_info_controller.rb +2 -2
- data/app/controllers/doorkeeper/tokens_controller.rb +11 -11
- data/app/helpers/doorkeeper/dashboard_helper.rb +1 -1
- data/app/validators/redirect_uri_validator.rb +5 -2
- data/app/views/doorkeeper/applications/_form.html.erb +6 -0
- data/app/views/doorkeeper/applications/show.html.erb +1 -1
- data/app/views/layouts/doorkeeper/admin.html.erb +5 -3
- data/bin/console +16 -0
- data/config/locales/en.yml +1 -0
- data/doorkeeper.gemspec +24 -21
- data/gemfiles/rails_5_0.gemfile +9 -4
- data/gemfiles/rails_5_1.gemfile +9 -4
- data/gemfiles/rails_5_2.gemfile +10 -5
- data/gemfiles/rails_6_0.gemfile +17 -0
- data/gemfiles/rails_master.gemfile +9 -9
- data/lib/doorkeeper/config/option.rb +64 -0
- data/lib/doorkeeper/config.rb +191 -75
- data/lib/doorkeeper/engine.rb +1 -1
- data/lib/doorkeeper/grape/authorization_decorator.rb +4 -4
- data/lib/doorkeeper/grape/helpers.rb +3 -3
- data/lib/doorkeeper/helpers/controller.rb +3 -2
- data/lib/doorkeeper/models/access_grant_mixin.rb +20 -3
- data/lib/doorkeeper/models/access_token_mixin.rb +63 -19
- data/lib/doorkeeper/models/application_mixin.rb +43 -1
- data/lib/doorkeeper/models/concerns/expirable.rb +4 -2
- data/lib/doorkeeper/models/concerns/ownership.rb +1 -6
- data/lib/doorkeeper/models/concerns/reusable.rb +19 -0
- data/lib/doorkeeper/models/concerns/revocable.rb +2 -1
- data/lib/doorkeeper/models/concerns/scopes.rb +5 -1
- data/lib/doorkeeper/models/concerns/secret_storable.rb +108 -0
- data/lib/doorkeeper/oauth/authorization/code.rb +2 -2
- data/lib/doorkeeper/oauth/authorization/token.rb +12 -7
- data/lib/doorkeeper/oauth/authorization/uri_builder.rb +1 -1
- data/lib/doorkeeper/oauth/authorization_code_request.rb +6 -4
- data/lib/doorkeeper/oauth/client.rb +1 -1
- data/lib/doorkeeper/oauth/client_credentials/validation.rb +5 -4
- data/lib/doorkeeper/oauth/client_credentials_request.rb +1 -1
- data/lib/doorkeeper/oauth/code_response.rb +2 -2
- data/lib/doorkeeper/oauth/error_response.rb +10 -6
- data/lib/doorkeeper/oauth/forbidden_token_response.rb +1 -1
- data/lib/doorkeeper/oauth/helpers/scope_checker.rb +23 -8
- data/lib/doorkeeper/oauth/helpers/unique_token.rb +13 -1
- data/lib/doorkeeper/oauth/helpers/uri_checker.rb +37 -1
- data/lib/doorkeeper/oauth/invalid_token_response.rb +5 -1
- data/lib/doorkeeper/oauth/password_access_token_request.rb +7 -2
- data/lib/doorkeeper/oauth/pre_authorization.rb +11 -5
- data/lib/doorkeeper/oauth/refresh_token_request.rb +5 -2
- data/lib/doorkeeper/oauth/scopes.rb +5 -3
- data/lib/doorkeeper/oauth/token.rb +2 -2
- data/lib/doorkeeper/oauth/token_introspection.rb +74 -8
- data/lib/doorkeeper/oauth/token_response.rb +9 -9
- data/lib/doorkeeper/oauth.rb +5 -5
- data/lib/doorkeeper/orm/active_record/access_grant.rb +27 -13
- data/lib/doorkeeper/orm/active_record/access_token.rb +6 -13
- data/lib/doorkeeper/orm/active_record/application.rb +21 -57
- data/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb +10 -3
- data/lib/doorkeeper/orm/active_record.rb +6 -6
- data/lib/doorkeeper/rails/helpers.rb +1 -1
- data/lib/doorkeeper/rails/routes/mapping.rb +7 -7
- data/lib/doorkeeper/rails/routes.rb +11 -11
- data/lib/doorkeeper/rake/db.rake +13 -13
- data/lib/doorkeeper/rake.rb +1 -1
- data/lib/doorkeeper/request.rb +1 -1
- data/lib/doorkeeper/secret_storing/base.rb +64 -0
- data/lib/doorkeeper/secret_storing/bcrypt.rb +60 -0
- data/lib/doorkeeper/secret_storing/plain.rb +33 -0
- data/lib/doorkeeper/secret_storing/sha256_hash.rb +26 -0
- data/lib/doorkeeper/stale_records_cleaner.rb +1 -1
- data/lib/doorkeeper/version.rb +3 -3
- data/lib/doorkeeper.rb +69 -60
- data/lib/generators/doorkeeper/application_owner_generator.rb +10 -9
- data/lib/generators/doorkeeper/confidential_applications_generator.rb +10 -9
- data/lib/generators/doorkeeper/install_generator.rb +11 -9
- data/lib/generators/doorkeeper/migration_generator.rb +9 -9
- data/lib/generators/doorkeeper/pkce_generator.rb +10 -9
- data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +10 -9
- data/lib/generators/doorkeeper/templates/initializer.rb +120 -13
- data/lib/generators/doorkeeper/templates/migration.rb.erb +15 -7
- data/lib/generators/doorkeeper/views_generator.rb +6 -4
- data/spec/controllers/application_metal_controller_spec.rb +24 -10
- data/spec/controllers/applications_controller_spec.rb +54 -52
- data/spec/controllers/authorizations_controller_spec.rb +139 -145
- data/spec/controllers/protected_resources_controller_spec.rb +78 -76
- data/spec/controllers/token_info_controller_spec.rb +14 -12
- data/spec/controllers/tokens_controller_spec.rb +167 -108
- data/spec/dummy/Rakefile +3 -1
- data/spec/dummy/app/controllers/application_controller.rb +3 -1
- data/spec/dummy/app/controllers/custom_authorizations_controller.rb +2 -0
- data/spec/dummy/app/controllers/full_protected_resources_controller.rb +4 -2
- data/spec/dummy/app/controllers/home_controller.rb +5 -3
- data/spec/dummy/app/controllers/metal_controller.rb +2 -0
- data/spec/dummy/app/controllers/semi_protected_resources_controller.rb +4 -2
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/user.rb +2 -0
- data/spec/dummy/config/application.rb +25 -1
- data/spec/dummy/config/environments/development.rb +2 -0
- data/spec/dummy/config/environments/production.rb +2 -0
- data/spec/dummy/config/environments/test.rb +3 -1
- data/spec/dummy/config/initializers/backtrace_silencers.rb +2 -0
- data/spec/dummy/config/initializers/doorkeeper.rb +5 -2
- data/spec/dummy/config/initializers/secret_token.rb +3 -1
- data/spec/dummy/config/initializers/session_store.rb +3 -1
- data/spec/dummy/config/initializers/wrap_parameters.rb +2 -0
- data/spec/dummy/config.ru +3 -1
- data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +17 -10
- data/spec/dummy/db/migrate/20170822064514_enable_pkce.rb +2 -0
- data/spec/dummy/db/schema.rb +1 -1
- data/spec/dummy/script/rails +5 -3
- data/spec/factories.rb +8 -6
- data/spec/generators/application_owner_generator_spec.rb +13 -26
- data/spec/generators/confidential_applications_generator_spec.rb +12 -28
- data/spec/generators/install_generator_spec.rb +17 -15
- data/spec/generators/migration_generator_spec.rb +13 -26
- data/spec/generators/pkce_generator_spec.rb +11 -26
- data/spec/generators/previous_refresh_token_generator_spec.rb +16 -29
- data/spec/generators/templates/routes.rb +2 -0
- data/spec/generators/views_generator_spec.rb +14 -12
- data/spec/grape/grape_integration_spec.rb +34 -32
- data/spec/helpers/doorkeeper/dashboard_helper_spec.rb +9 -7
- data/spec/lib/config_spec.rb +268 -99
- data/spec/lib/doorkeeper_spec.rb +3 -1
- data/spec/lib/models/expirable_spec.rb +22 -8
- data/spec/lib/models/reusable_spec.rb +40 -0
- data/spec/lib/models/revocable_spec.rb +8 -6
- data/spec/lib/models/scopes_spec.rb +27 -13
- data/spec/lib/models/secret_storable_spec.rb +135 -0
- data/spec/lib/oauth/authorization/uri_builder_spec.rb +17 -15
- data/spec/lib/oauth/authorization_code_request_spec.rb +34 -11
- data/spec/lib/oauth/base_request_spec.rb +20 -8
- data/spec/lib/oauth/base_response_spec.rb +3 -1
- data/spec/lib/oauth/client/credentials_spec.rb +24 -22
- data/spec/lib/oauth/client_credentials/creator_spec.rb +56 -10
- data/spec/lib/oauth/client_credentials/issuer_spec.rb +27 -18
- data/spec/lib/oauth/client_credentials/validation_spec.rb +20 -15
- data/spec/lib/oauth/client_credentials_integration_spec.rb +7 -5
- data/spec/lib/oauth/client_credentials_request_spec.rb +27 -21
- data/spec/lib/oauth/client_spec.rb +15 -13
- data/spec/lib/oauth/code_request_spec.rb +8 -6
- data/spec/lib/oauth/code_response_spec.rb +9 -7
- data/spec/lib/oauth/error_response_spec.rb +19 -11
- data/spec/lib/oauth/error_spec.rb +4 -2
- data/spec/lib/oauth/forbidden_token_response_spec.rb +7 -5
- data/spec/lib/oauth/helpers/scope_checker_spec.rb +68 -31
- data/spec/lib/oauth/helpers/unique_token_spec.rb +8 -6
- data/spec/lib/oauth/helpers/uri_checker_spec.rb +115 -95
- data/spec/lib/oauth/invalid_token_response_spec.rb +3 -1
- data/spec/lib/oauth/password_access_token_request_spec.rb +79 -30
- data/spec/lib/oauth/pre_authorization_spec.rb +80 -54
- data/spec/lib/oauth/refresh_token_request_spec.rb +36 -33
- data/spec/lib/oauth/scopes_spec.rb +63 -61
- data/spec/lib/oauth/token_request_spec.rb +79 -24
- data/spec/lib/oauth/token_response_spec.rb +40 -38
- data/spec/lib/oauth/token_spec.rb +60 -44
- data/spec/lib/request/strategy_spec.rb +3 -1
- data/spec/lib/secret_storing/base_spec.rb +60 -0
- data/spec/lib/secret_storing/bcrypt_spec.rb +49 -0
- data/spec/lib/secret_storing/plain_spec.rb +44 -0
- data/spec/lib/secret_storing/sha256_hash_spec.rb +48 -0
- data/spec/lib/server_spec.rb +16 -14
- data/spec/lib/stale_records_cleaner_spec.rb +17 -17
- data/spec/models/doorkeeper/access_grant_spec.rb +80 -15
- data/spec/models/doorkeeper/access_token_spec.rb +198 -73
- data/spec/models/doorkeeper/application_spec.rb +280 -288
- data/spec/requests/applications/applications_request_spec.rb +98 -66
- data/spec/requests/applications/authorized_applications_spec.rb +20 -18
- data/spec/requests/endpoints/authorization_spec.rb +25 -23
- data/spec/requests/endpoints/token_spec.rb +38 -36
- data/spec/requests/flows/authorization_code_errors_spec.rb +26 -24
- data/spec/requests/flows/authorization_code_spec.rb +193 -151
- data/spec/requests/flows/client_credentials_spec.rb +54 -52
- data/spec/requests/flows/implicit_grant_errors_spec.rb +10 -8
- data/spec/requests/flows/implicit_grant_spec.rb +27 -25
- data/spec/requests/flows/password_spec.rb +59 -55
- data/spec/requests/flows/refresh_token_spec.rb +45 -43
- data/spec/requests/flows/revoke_token_spec.rb +40 -54
- data/spec/requests/flows/skip_authorization_spec.rb +23 -21
- data/spec/requests/protected_resources/metal_spec.rb +7 -5
- data/spec/requests/protected_resources/private_api_spec.rb +35 -33
- data/spec/routing/custom_controller_routes_spec.rb +71 -65
- data/spec/routing/default_routes_spec.rb +22 -20
- data/spec/routing/scoped_routes_spec.rb +20 -18
- data/spec/spec_helper.rb +15 -13
- data/spec/spec_helper_integration.rb +3 -1
- data/spec/support/dependencies/factory_bot.rb +3 -1
- data/spec/support/doorkeeper_rspec.rb +3 -1
- data/spec/support/helpers/access_token_request_helper.rb +3 -1
- data/spec/support/helpers/authorization_request_helper.rb +4 -2
- data/spec/support/helpers/config_helper.rb +2 -0
- data/spec/support/helpers/model_helper.rb +3 -1
- data/spec/support/helpers/request_spec_helper.rb +5 -3
- data/spec/support/helpers/url_helper.rb +9 -7
- data/spec/support/http_method_shim.rb +4 -9
- data/spec/support/orm/active_record.rb +3 -1
- data/spec/support/shared/controllers_shared_context.rb +18 -16
- data/spec/support/shared/hashing_shared_context.rb +36 -0
- data/spec/support/shared/models_shared_examples.rb +12 -10
- data/spec/validators/redirect_uri_validator_spec.rb +74 -45
- data/spec/version/version_spec.rb +7 -5
- metadata +60 -26
- data/gemfiles/rails_4_2.gemfile +0 -13
- data/spec/dummy/config/initializers/new_framework_defaults.rb +0 -8
|
@@ -7,31 +7,46 @@ module Doorkeeper
|
|
|
7
7
|
class Validator
|
|
8
8
|
attr_reader :parsed_scopes, :scope_str
|
|
9
9
|
|
|
10
|
-
def initialize(scope_str, server_scopes,
|
|
10
|
+
def initialize(scope_str, server_scopes, app_scopes, grant_type)
|
|
11
11
|
@parsed_scopes = OAuth::Scopes.from_string(scope_str)
|
|
12
12
|
@scope_str = scope_str
|
|
13
|
-
@valid_scopes = valid_scopes(server_scopes,
|
|
13
|
+
@valid_scopes = valid_scopes(server_scopes, app_scopes)
|
|
14
|
+
|
|
15
|
+
if grant_type
|
|
16
|
+
@scopes_by_grant_type = Doorkeeper.configuration.scopes_by_grant_type[grant_type.to_sym]
|
|
17
|
+
end
|
|
14
18
|
end
|
|
15
19
|
|
|
16
20
|
def valid?
|
|
17
21
|
scope_str.present? &&
|
|
18
22
|
scope_str !~ /[\n\r\t]/ &&
|
|
19
|
-
@valid_scopes.has_scopes?(parsed_scopes)
|
|
23
|
+
@valid_scopes.has_scopes?(parsed_scopes) &&
|
|
24
|
+
permitted_to_grant_type?
|
|
20
25
|
end
|
|
21
26
|
|
|
22
27
|
private
|
|
23
28
|
|
|
24
|
-
def valid_scopes(server_scopes,
|
|
25
|
-
if
|
|
26
|
-
|
|
29
|
+
def valid_scopes(server_scopes, app_scopes)
|
|
30
|
+
if app_scopes.present?
|
|
31
|
+
app_scopes
|
|
27
32
|
else
|
|
28
33
|
server_scopes
|
|
29
34
|
end
|
|
30
35
|
end
|
|
36
|
+
|
|
37
|
+
def permitted_to_grant_type?
|
|
38
|
+
return true unless @scopes_by_grant_type
|
|
39
|
+
|
|
40
|
+
OAuth::Scopes.from_array(@scopes_by_grant_type)
|
|
41
|
+
.has_scopes?(parsed_scopes)
|
|
42
|
+
end
|
|
31
43
|
end
|
|
32
44
|
|
|
33
|
-
def self.valid?(scope_str
|
|
34
|
-
Validator.new(scope_str,
|
|
45
|
+
def self.valid?(scope_str:, server_scopes:, app_scopes: nil, grant_type: nil)
|
|
46
|
+
Validator.new(scope_str,
|
|
47
|
+
server_scopes,
|
|
48
|
+
app_scopes,
|
|
49
|
+
grant_type).valid?
|
|
35
50
|
end
|
|
36
51
|
end
|
|
37
52
|
end
|
|
@@ -5,10 +5,22 @@ module Doorkeeper
|
|
|
5
5
|
module Helpers
|
|
6
6
|
module UniqueToken
|
|
7
7
|
def self.generate(options = {})
|
|
8
|
-
|
|
8
|
+
# Access Token value must be 1*VSCHAR or
|
|
9
|
+
# 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="
|
|
10
|
+
#
|
|
11
|
+
# @see https://tools.ietf.org/html/rfc6749#appendix-A.12
|
|
12
|
+
# @see https://tools.ietf.org/html/rfc6750#section-2.1
|
|
13
|
+
#
|
|
14
|
+
generator_method = options.delete(:generator) || SecureRandom.method(self.generator_method)
|
|
9
15
|
token_size = options.delete(:size) || 32
|
|
10
16
|
generator_method.call(token_size)
|
|
11
17
|
end
|
|
18
|
+
|
|
19
|
+
# Generator method for default generator class (SecureRandom)
|
|
20
|
+
#
|
|
21
|
+
def self.generator_method
|
|
22
|
+
Doorkeeper.configuration.default_generator_method
|
|
23
|
+
end
|
|
12
24
|
end
|
|
13
25
|
end
|
|
14
26
|
end
|
|
@@ -1,11 +1,32 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "ipaddr"
|
|
4
|
+
|
|
3
5
|
module Doorkeeper
|
|
6
|
+
module IPAddrLoopback
|
|
7
|
+
def loopback?
|
|
8
|
+
case @family
|
|
9
|
+
when Socket::AF_INET
|
|
10
|
+
@addr & 0xff000000 == 0x7f000000
|
|
11
|
+
when Socket::AF_INET6
|
|
12
|
+
@addr == 1
|
|
13
|
+
else
|
|
14
|
+
raise AddressFamilyError, "unsupported address family"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# For backward compatibility with old rubies
|
|
20
|
+
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.5.0")
|
|
21
|
+
IPAddr.send(:include, Doorkeeper::IPAddrLoopback)
|
|
22
|
+
end
|
|
23
|
+
|
|
4
24
|
module OAuth
|
|
5
25
|
module Helpers
|
|
6
26
|
module URIChecker
|
|
7
27
|
def self.valid?(url)
|
|
8
28
|
return true if native_uri?(url)
|
|
29
|
+
|
|
9
30
|
uri = as_uri(url)
|
|
10
31
|
uri.fragment.nil? && !uri.host.nil? && !uri.scheme.nil?
|
|
11
32
|
rescue URI::InvalidURIError
|
|
@@ -18,15 +39,29 @@ module Doorkeeper
|
|
|
18
39
|
|
|
19
40
|
unless client_url.query.nil?
|
|
20
41
|
return false unless query_matches?(url.query, client_url.query)
|
|
42
|
+
|
|
21
43
|
# Clear out queries so rest of URI can be tested. This allows query
|
|
22
44
|
# params to be in the request but order not mattering.
|
|
23
45
|
client_url.query = nil
|
|
24
46
|
end
|
|
25
47
|
|
|
48
|
+
# RFC8252, Paragraph 7.3
|
|
49
|
+
# @see https://tools.ietf.org/html/rfc8252#section-7.3
|
|
50
|
+
if loopback_uri?(url) && loopback_uri?(client_url)
|
|
51
|
+
url.port = nil
|
|
52
|
+
client_url.port = nil
|
|
53
|
+
end
|
|
54
|
+
|
|
26
55
|
url.query = nil
|
|
27
56
|
url == client_url
|
|
28
57
|
end
|
|
29
58
|
|
|
59
|
+
def self.loopback_uri?(uri)
|
|
60
|
+
IPAddr.new(uri.host).loopback?
|
|
61
|
+
rescue IPAddr::Error
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
|
|
30
65
|
def self.valid_for_authorization?(url, client_url)
|
|
31
66
|
valid?(url) && client_url.split.any? { |other_url| matches?(url, other_url) }
|
|
32
67
|
end
|
|
@@ -38,8 +73,9 @@ module Doorkeeper
|
|
|
38
73
|
def self.query_matches?(query, client_query)
|
|
39
74
|
return true if client_query.blank? && query.blank?
|
|
40
75
|
return false if client_query.nil? || query.nil?
|
|
76
|
+
|
|
41
77
|
# Will return true independent of query order
|
|
42
|
-
client_query.split(
|
|
78
|
+
client_query.split("&").sort == query.split("&").sort
|
|
43
79
|
end
|
|
44
80
|
|
|
45
81
|
def self.native_uri?(url)
|
|
@@ -22,6 +22,10 @@ module Doorkeeper
|
|
|
22
22
|
@reason = attributes[:reason] || :unknown
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
def status
|
|
26
|
+
:unauthorized
|
|
27
|
+
end
|
|
28
|
+
|
|
25
29
|
def description
|
|
26
30
|
scope = { scope: %i[doorkeeper errors messages invalid_token] }
|
|
27
31
|
@description ||= I18n.translate @reason, scope
|
|
@@ -39,7 +43,7 @@ module Doorkeeper
|
|
|
39
43
|
{
|
|
40
44
|
expired: Doorkeeper::Errors::TokenExpired,
|
|
41
45
|
revoked: Doorkeeper::Errors::TokenRevoked,
|
|
42
|
-
unknown: Doorkeeper::Errors::TokenUnknown
|
|
46
|
+
unknown: Doorkeeper::Errors::TokenUnknown,
|
|
43
47
|
}
|
|
44
48
|
end
|
|
45
49
|
end
|
|
@@ -32,7 +32,12 @@ module Doorkeeper
|
|
|
32
32
|
client_scopes = client.try(:scopes)
|
|
33
33
|
return true if scopes.blank?
|
|
34
34
|
|
|
35
|
-
ScopeChecker.valid?(
|
|
35
|
+
ScopeChecker.valid?(
|
|
36
|
+
scope_str: scopes.to_s,
|
|
37
|
+
server_scopes: server.scopes,
|
|
38
|
+
app_scopes: client_scopes,
|
|
39
|
+
grant_type: grant_type
|
|
40
|
+
)
|
|
36
41
|
end
|
|
37
42
|
|
|
38
43
|
def validate_resource_owner
|
|
@@ -40,7 +45,7 @@ module Doorkeeper
|
|
|
40
45
|
end
|
|
41
46
|
|
|
42
47
|
def validate_client
|
|
43
|
-
!parameters[:client_id] ||
|
|
48
|
+
!parameters[:client_id] || client.present?
|
|
44
49
|
end
|
|
45
50
|
end
|
|
46
51
|
end
|
|
@@ -50,7 +50,7 @@ module Doorkeeper
|
|
|
50
50
|
response_type: response_type,
|
|
51
51
|
scope: scope,
|
|
52
52
|
client_name: client.name,
|
|
53
|
-
status: I18n.t(
|
|
53
|
+
status: I18n.t("doorkeeper.pre_authorization.status"),
|
|
54
54
|
}
|
|
55
55
|
end
|
|
56
56
|
|
|
@@ -77,12 +77,17 @@ module Doorkeeper
|
|
|
77
77
|
return true if scope.blank?
|
|
78
78
|
|
|
79
79
|
Helpers::ScopeChecker.valid?(
|
|
80
|
-
scope,
|
|
81
|
-
server.scopes,
|
|
82
|
-
client.application.scopes
|
|
80
|
+
scope_str: scope,
|
|
81
|
+
server_scopes: server.scopes,
|
|
82
|
+
app_scopes: client.application.scopes,
|
|
83
|
+
grant_type: grant_type
|
|
83
84
|
)
|
|
84
85
|
end
|
|
85
86
|
|
|
87
|
+
def grant_type
|
|
88
|
+
response_type == "code" ? AUTHORIZATION_CODE : IMPLICIT
|
|
89
|
+
end
|
|
90
|
+
|
|
86
91
|
def validate_redirect_uri
|
|
87
92
|
return false if redirect_uri.blank?
|
|
88
93
|
|
|
@@ -93,7 +98,8 @@ module Doorkeeper
|
|
|
93
98
|
end
|
|
94
99
|
|
|
95
100
|
def validate_code_challenge_method
|
|
96
|
-
|
|
101
|
+
code_challenge.blank? ||
|
|
102
|
+
(code_challenge_method.present? && code_challenge_method =~ /^plain$|^S256$/)
|
|
97
103
|
end
|
|
98
104
|
end
|
|
99
105
|
end
|
|
@@ -58,7 +58,7 @@ module Doorkeeper
|
|
|
58
58
|
resource_owner_id: refresh_token.resource_owner_id,
|
|
59
59
|
scopes: scopes.to_s,
|
|
60
60
|
expires_in: access_token_expires_in,
|
|
61
|
-
use_refresh_token: true
|
|
61
|
+
use_refresh_token: true,
|
|
62
62
|
}.tap do |attributes|
|
|
63
63
|
if refresh_token_revoked_on_use?
|
|
64
64
|
attributes[:previous_refresh_token] = refresh_token.refresh_token
|
|
@@ -99,7 +99,10 @@ module Doorkeeper
|
|
|
99
99
|
|
|
100
100
|
def validate_scope
|
|
101
101
|
if @original_scopes.present?
|
|
102
|
-
ScopeChecker.valid?(
|
|
102
|
+
ScopeChecker.valid?(
|
|
103
|
+
scope_str: @original_scopes,
|
|
104
|
+
server_scopes: refresh_token.scopes
|
|
105
|
+
)
|
|
103
106
|
else
|
|
104
107
|
true
|
|
105
108
|
end
|
|
@@ -7,7 +7,7 @@ module Doorkeeper
|
|
|
7
7
|
include Comparable
|
|
8
8
|
|
|
9
9
|
def self.from_string(string)
|
|
10
|
-
string ||=
|
|
10
|
+
string ||= ""
|
|
11
11
|
new.tap do |scope|
|
|
12
12
|
scope.add(*string.split)
|
|
13
13
|
end
|
|
@@ -39,13 +39,15 @@ module Doorkeeper
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def to_s
|
|
42
|
-
@scopes.join(
|
|
42
|
+
@scopes.join(" ")
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
def
|
|
45
|
+
def scopes?(scopes)
|
|
46
46
|
scopes.all? { |scope| exists?(scope) }
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
alias has_scopes? scopes?
|
|
50
|
+
|
|
49
51
|
def +(other)
|
|
50
52
|
self.class.from_array(all + to_array(other))
|
|
51
53
|
end
|
|
@@ -55,11 +55,11 @@ module Doorkeeper
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def token_from_header(header, pattern)
|
|
58
|
-
header.gsub pattern,
|
|
58
|
+
header.gsub pattern, ""
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def match?(header, pattern)
|
|
62
|
-
header
|
|
62
|
+
header&.match(pattern)
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
end
|
|
@@ -20,7 +20,17 @@ module Doorkeeper
|
|
|
20
20
|
@error.blank?
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def
|
|
23
|
+
def error_response
|
|
24
|
+
return if @error.blank?
|
|
25
|
+
|
|
26
|
+
if @error == :invalid_token
|
|
27
|
+
OAuth::InvalidTokenResponse.from_access_token(authorized_token)
|
|
28
|
+
else
|
|
29
|
+
OAuth::ErrorResponse.new(name: @error)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def to_json(*)
|
|
24
34
|
active? ? success_response : failure_response
|
|
25
35
|
end
|
|
26
36
|
|
|
@@ -37,13 +47,29 @@ module Doorkeeper
|
|
|
37
47
|
#
|
|
38
48
|
# @see https://www.oauth.com/oauth2-servers/token-introspection-endpoint/
|
|
39
49
|
#
|
|
50
|
+
# To prevent token scanning attacks, the endpoint MUST also require
|
|
51
|
+
# some form of authorization to access this endpoint, such as client
|
|
52
|
+
# authentication as described in OAuth 2.0 [RFC6749] or a separate
|
|
53
|
+
# OAuth 2.0 access token such as the bearer token described in OAuth
|
|
54
|
+
# 2.0 Bearer Token Usage [RFC6750].
|
|
55
|
+
#
|
|
40
56
|
def authorize!
|
|
41
57
|
# Requested client authorization
|
|
42
58
|
if server.credentials
|
|
43
59
|
@error = :invalid_client unless authorized_client
|
|
44
|
-
|
|
60
|
+
elsif authorized_token
|
|
45
61
|
# Requested bearer token authorization
|
|
46
|
-
|
|
62
|
+
#
|
|
63
|
+
# If the protected resource uses an OAuth 2.0 bearer token to authorize
|
|
64
|
+
# its call to the introspection endpoint and the token used for
|
|
65
|
+
# authorization does not contain sufficient privileges or is otherwise
|
|
66
|
+
# invalid for this request, the authorization server responds with an
|
|
67
|
+
# HTTP 401 code as described in Section 3 of OAuth 2.0 Bearer Token
|
|
68
|
+
# Usage [RFC6750].
|
|
69
|
+
#
|
|
70
|
+
@error = :invalid_token if authorized_token_matches_introspected? || !authorized_token.accessible?
|
|
71
|
+
else
|
|
72
|
+
@error = :invalid_request
|
|
47
73
|
end
|
|
48
74
|
end
|
|
49
75
|
|
|
@@ -60,14 +86,14 @@ module Doorkeeper
|
|
|
60
86
|
|
|
61
87
|
# 2.2. Introspection Response
|
|
62
88
|
def success_response
|
|
63
|
-
|
|
89
|
+
customize_response(
|
|
64
90
|
active: true,
|
|
65
91
|
scope: @token.scopes_string,
|
|
66
92
|
client_id: @token.try(:application).try(:uid),
|
|
67
93
|
token_type: @token.token_type,
|
|
68
94
|
exp: @token.expires_at.to_i,
|
|
69
95
|
iat: @token.created_at.to_i
|
|
70
|
-
|
|
96
|
+
)
|
|
71
97
|
end
|
|
72
98
|
|
|
73
99
|
# If the introspection call is properly authorized but the token is not
|
|
@@ -83,7 +109,7 @@ module Doorkeeper
|
|
|
83
109
|
#
|
|
84
110
|
def failure_response
|
|
85
111
|
{
|
|
86
|
-
active: false
|
|
112
|
+
active: false,
|
|
87
113
|
}
|
|
88
114
|
end
|
|
89
115
|
|
|
@@ -103,6 +129,25 @@ module Doorkeeper
|
|
|
103
129
|
# * The token expired
|
|
104
130
|
# * The token was issued to a different client than is making this request
|
|
105
131
|
#
|
|
132
|
+
# Since resource servers using token introspection rely on the
|
|
133
|
+
# authorization server to determine the state of a token, the
|
|
134
|
+
# authorization server MUST perform all applicable checks against a
|
|
135
|
+
# token's state. For instance, these tests include the following:
|
|
136
|
+
#
|
|
137
|
+
# o If the token can expire, the authorization server MUST determine
|
|
138
|
+
# whether or not the token has expired.
|
|
139
|
+
# o If the token can be issued before it is able to be used, the
|
|
140
|
+
# authorization server MUST determine whether or not a token's valid
|
|
141
|
+
# period has started yet.
|
|
142
|
+
# o If the token can be revoked after it was issued, the authorization
|
|
143
|
+
# server MUST determine whether or not such a revocation has taken
|
|
144
|
+
# place.
|
|
145
|
+
# o If the token has been signed, the authorization server MUST
|
|
146
|
+
# validate the signature.
|
|
147
|
+
# o If the token can be used only at certain resource servers, the
|
|
148
|
+
# authorization server MUST determine whether or not the token can
|
|
149
|
+
# be used at the resource server making the introspection call.
|
|
150
|
+
#
|
|
106
151
|
def active?
|
|
107
152
|
if authorized_client
|
|
108
153
|
valid_token? && authorized_for_client?
|
|
@@ -113,18 +158,39 @@ module Doorkeeper
|
|
|
113
158
|
|
|
114
159
|
# Token can be valid only if it is not expired or revoked.
|
|
115
160
|
def valid_token?
|
|
116
|
-
@token
|
|
161
|
+
@token&.accessible?
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# RFC7662 Section 2.1
|
|
165
|
+
def authorized_token_matches_introspected?
|
|
166
|
+
authorized_token.token == @token&.token
|
|
117
167
|
end
|
|
118
168
|
|
|
119
169
|
# If token doesn't belong to some client, then it is public.
|
|
120
170
|
# Otherwise in it required for token to be connected to the same client.
|
|
121
171
|
def authorized_for_client?
|
|
122
|
-
if @token.application
|
|
172
|
+
if @token.application
|
|
123
173
|
@token.application == authorized_client.application
|
|
124
174
|
else
|
|
125
175
|
true
|
|
126
176
|
end
|
|
127
177
|
end
|
|
178
|
+
|
|
179
|
+
# Allows to customize introspection response.
|
|
180
|
+
# Provides context (controller) and token for generating developer-specific
|
|
181
|
+
# response.
|
|
182
|
+
#
|
|
183
|
+
# @see https://tools.ietf.org/html/rfc7662#section-2.2
|
|
184
|
+
#
|
|
185
|
+
def customize_response(response)
|
|
186
|
+
customized_response = Doorkeeper.configuration.custom_introspection_response.call(
|
|
187
|
+
token,
|
|
188
|
+
server.context
|
|
189
|
+
)
|
|
190
|
+
return response if customized_response.blank?
|
|
191
|
+
|
|
192
|
+
response.merge(customized_response)
|
|
193
|
+
end
|
|
128
194
|
end
|
|
129
195
|
end
|
|
130
196
|
end
|
|
@@ -11,12 +11,12 @@ module Doorkeeper
|
|
|
11
11
|
|
|
12
12
|
def body
|
|
13
13
|
{
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
"access_token" => token.plaintext_token,
|
|
15
|
+
"token_type" => token.token_type,
|
|
16
|
+
"expires_in" => token.expires_in_seconds,
|
|
17
|
+
"refresh_token" => token.plaintext_refresh_token,
|
|
18
|
+
"scope" => token.scopes_string,
|
|
19
|
+
"created_at" => token.created_at.to_i,
|
|
20
20
|
}.reject { |_, value| value.blank? }
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -26,9 +26,9 @@ module Doorkeeper
|
|
|
26
26
|
|
|
27
27
|
def headers
|
|
28
28
|
{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
"Cache-Control" => "no-store",
|
|
30
|
+
"Pragma" => "no-cache",
|
|
31
|
+
"Content-Type" => "application/json; charset=utf-8",
|
|
32
32
|
}
|
|
33
33
|
end
|
|
34
34
|
end
|
data/lib/doorkeeper/oauth.rb
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
module Doorkeeper
|
|
4
4
|
module OAuth
|
|
5
5
|
GRANT_TYPES = [
|
|
6
|
-
AUTHORIZATION_CODE =
|
|
7
|
-
IMPLICIT =
|
|
8
|
-
PASSWORD =
|
|
9
|
-
CLIENT_CREDENTIALS =
|
|
10
|
-
REFRESH_TOKEN =
|
|
6
|
+
AUTHORIZATION_CODE = "authorization_code",
|
|
7
|
+
IMPLICIT = "implicit",
|
|
8
|
+
PASSWORD = "password",
|
|
9
|
+
CLIENT_CREDENTIALS = "client_credentials",
|
|
10
|
+
REFRESH_TOKEN = "refresh_token",
|
|
11
11
|
].freeze
|
|
12
12
|
end
|
|
13
13
|
end
|
|
@@ -1,26 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Doorkeeper
|
|
2
4
|
class AccessGrant < ActiveRecord::Base
|
|
3
|
-
self.table_name = "#{table_name_prefix}oauth_access_grants#{table_name_suffix}"
|
|
5
|
+
self.table_name = "#{table_name_prefix}oauth_access_grants#{table_name_suffix}"
|
|
4
6
|
|
|
5
7
|
include AccessGrantMixin
|
|
6
|
-
include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
|
|
7
|
-
|
|
8
|
-
belongs_to_options = {
|
|
9
|
-
class_name: 'Doorkeeper::Application',
|
|
10
|
-
inverse_of: :access_grants
|
|
11
|
-
}
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
end
|
|
9
|
+
belongs_to :application, class_name: "Doorkeeper::Application",
|
|
10
|
+
optional: true, inverse_of: :access_grants
|
|
16
11
|
|
|
17
|
-
|
|
12
|
+
validates :resource_owner_id,
|
|
13
|
+
:application_id,
|
|
14
|
+
:token,
|
|
15
|
+
:expires_in,
|
|
16
|
+
:redirect_uri,
|
|
17
|
+
presence: true
|
|
18
18
|
|
|
19
|
-
validates :resource_owner_id, :application_id, :token, :expires_in, :redirect_uri, presence: true
|
|
20
19
|
validates :token, uniqueness: true
|
|
21
20
|
|
|
22
21
|
before_validation :generate_token, on: :create
|
|
23
22
|
|
|
23
|
+
# We keep a volatile copy of the raw token for initial communication
|
|
24
|
+
# The stored refresh_token may be mapped and not available in cleartext.
|
|
25
|
+
#
|
|
26
|
+
# Some strategies allow restoring stored secrets (e.g. symmetric encryption)
|
|
27
|
+
# while hashing strategies do not, so you cannot rely on this value
|
|
28
|
+
# returning a present value for persisted tokens.
|
|
29
|
+
def plaintext_token
|
|
30
|
+
if secret_strategy.allows_restoring_secrets?
|
|
31
|
+
secret_strategy.restore_secret(self, :token)
|
|
32
|
+
else
|
|
33
|
+
@raw_token
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
24
37
|
private
|
|
25
38
|
|
|
26
39
|
# Generates token value with UniqueToken class.
|
|
@@ -28,7 +41,8 @@ module Doorkeeper
|
|
|
28
41
|
# @return [String] token value
|
|
29
42
|
#
|
|
30
43
|
def generate_token
|
|
31
|
-
|
|
44
|
+
@raw_token = UniqueToken.generate
|
|
45
|
+
secret_strategy.store_secret(self, :token, @raw_token)
|
|
32
46
|
end
|
|
33
47
|
end
|
|
34
48
|
end
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Doorkeeper
|
|
2
4
|
class AccessToken < ActiveRecord::Base
|
|
3
|
-
self.table_name = "#{table_name_prefix}oauth_access_tokens#{table_name_suffix}"
|
|
5
|
+
self.table_name = "#{table_name_prefix}oauth_access_tokens#{table_name_suffix}"
|
|
4
6
|
|
|
5
7
|
include AccessTokenMixin
|
|
6
|
-
include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
|
|
7
|
-
|
|
8
|
-
belongs_to_options = {
|
|
9
|
-
class_name: 'Doorkeeper::Application',
|
|
10
|
-
inverse_of: :access_tokens
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if defined?(ActiveRecord::Base) && ActiveRecord::VERSION::MAJOR >= 5
|
|
14
|
-
belongs_to_options[:optional] = true
|
|
15
|
-
end
|
|
16
8
|
|
|
17
|
-
belongs_to :application,
|
|
9
|
+
belongs_to :application, class_name: "Doorkeeper::Application",
|
|
10
|
+
inverse_of: :access_tokens, optional: true
|
|
18
11
|
|
|
19
12
|
validates :token, presence: true, uniqueness: true
|
|
20
13
|
validates :refresh_token, uniqueness: true, if: :use_refresh_token?
|
|
@@ -41,7 +34,7 @@ module Doorkeeper
|
|
|
41
34
|
end
|
|
42
35
|
|
|
43
36
|
def self.refresh_token_revoked_on_use?
|
|
44
|
-
column_names.include?(
|
|
37
|
+
column_names.include?("previous_refresh_token")
|
|
45
38
|
end
|
|
46
39
|
end
|
|
47
40
|
end
|