doorkeeper 4.2.5 → 5.0.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/.github/ISSUE_TEMPLATE.md +25 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +17 -0
- data/.gitignore +2 -1
- data/.gitlab-ci.yml +16 -0
- data/.hound.yml +2 -13
- data/.rubocop.yml +17 -0
- data/.travis.yml +19 -4
- data/Appraisals +8 -4
- data/CODE_OF_CONDUCT.md +46 -0
- data/Gemfile +1 -1
- data/NEWS.md +115 -0
- data/README.md +175 -39
- data/RELEASING.md +5 -12
- data/Rakefile +6 -0
- data/SECURITY.md +15 -0
- data/UPGRADE.md +2 -0
- data/app/assets/stylesheets/doorkeeper/admin/application.css +2 -2
- data/app/controllers/doorkeeper/application_controller.rb +2 -5
- data/app/controllers/doorkeeper/application_metal_controller.rb +4 -0
- data/app/controllers/doorkeeper/applications_controller.rb +47 -13
- data/app/controllers/doorkeeper/authorizations_controller.rb +55 -12
- data/app/controllers/doorkeeper/authorized_applications_controller.rb +19 -2
- data/app/controllers/doorkeeper/tokens_controller.rb +24 -17
- data/app/helpers/doorkeeper/dashboard_helper.rb +8 -6
- data/app/validators/redirect_uri_validator.rb +13 -2
- data/app/views/doorkeeper/applications/_delete_form.html.erb +3 -1
- data/app/views/doorkeeper/applications/_form.html.erb +31 -19
- data/app/views/doorkeeper/applications/edit.html.erb +1 -1
- data/app/views/doorkeeper/applications/index.html.erb +18 -6
- data/app/views/doorkeeper/applications/new.html.erb +1 -1
- data/app/views/doorkeeper/applications/show.html.erb +8 -5
- data/app/views/doorkeeper/authorizations/error.html.erb +1 -1
- data/app/views/doorkeeper/authorizations/new.html.erb +5 -1
- data/app/views/doorkeeper/authorized_applications/index.html.erb +0 -1
- data/app/views/layouts/doorkeeper/admin.html.erb +15 -15
- data/config/locales/en.yml +19 -6
- data/doorkeeper.gemspec +20 -20
- data/gemfiles/rails_4_2.gemfile +6 -4
- data/gemfiles/rails_5_0.gemfile +4 -4
- data/gemfiles/rails_5_1.gemfile +6 -7
- data/gemfiles/rails_5_2.gemfile +12 -0
- data/gemfiles/rails_master.gemfile +17 -0
- data/lib/doorkeeper/config.rb +123 -71
- data/lib/doorkeeper/engine.rb +7 -3
- data/lib/doorkeeper/errors.rb +20 -5
- data/lib/doorkeeper/grape/helpers.rb +14 -9
- data/lib/doorkeeper/helpers/controller.rb +16 -22
- data/lib/doorkeeper/models/access_grant_mixin.rb +67 -23
- data/lib/doorkeeper/models/access_token_mixin.rb +54 -55
- data/lib/doorkeeper/models/application_mixin.rb +16 -30
- data/lib/doorkeeper/models/concerns/expirable.rb +7 -5
- data/lib/doorkeeper/models/concerns/orderable.rb +13 -0
- data/lib/doorkeeper/models/concerns/scopes.rb +1 -1
- data/lib/doorkeeper/oauth/authorization/code.rb +31 -8
- data/lib/doorkeeper/oauth/authorization/context.rb +15 -0
- data/lib/doorkeeper/oauth/authorization/token.rb +46 -20
- data/lib/doorkeeper/oauth/authorization_code_request.rb +33 -3
- data/lib/doorkeeper/oauth/base_request.rb +23 -7
- data/lib/doorkeeper/oauth/client/credentials.rb +6 -4
- data/lib/doorkeeper/oauth/client.rb +2 -4
- data/lib/doorkeeper/oauth/client_credentials/creator.rb +2 -1
- data/lib/doorkeeper/oauth/client_credentials/issuer.rb +6 -3
- data/lib/doorkeeper/oauth/client_credentials/validation.rb +4 -6
- data/lib/doorkeeper/oauth/client_credentials_request.rb +0 -4
- data/lib/doorkeeper/oauth/error.rb +2 -2
- data/lib/doorkeeper/oauth/error_response.rb +11 -4
- data/lib/doorkeeper/oauth/forbidden_token_response.rb +1 -1
- data/lib/doorkeeper/oauth/helpers/scope_checker.rb +0 -8
- data/lib/doorkeeper/oauth/helpers/uri_checker.rb +15 -0
- data/lib/doorkeeper/oauth/invalid_token_response.rb +3 -4
- data/lib/doorkeeper/oauth/password_access_token_request.rb +8 -4
- data/lib/doorkeeper/oauth/pre_authorization.rb +45 -13
- data/lib/doorkeeper/oauth/refresh_token_request.rb +7 -1
- data/lib/doorkeeper/oauth/scopes.rb +19 -9
- data/lib/doorkeeper/oauth/token.rb +6 -3
- data/lib/doorkeeper/oauth/token_introspection.rb +128 -0
- data/lib/doorkeeper/oauth/token_response.rb +4 -2
- data/lib/doorkeeper/oauth.rb +13 -0
- data/lib/doorkeeper/orm/active_record/access_grant.rb +27 -0
- data/lib/doorkeeper/orm/active_record/access_token.rb +21 -20
- data/lib/doorkeeper/orm/active_record/application.rb +45 -0
- data/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb +26 -0
- data/lib/doorkeeper/orm/active_record.rb +21 -8
- data/lib/doorkeeper/rails/helpers.rb +7 -10
- data/lib/doorkeeper/rails/routes.rb +21 -7
- data/lib/doorkeeper/rake/db.rake +40 -0
- data/lib/doorkeeper/rake/setup.rake +6 -0
- data/lib/doorkeeper/rake.rb +14 -0
- data/lib/doorkeeper/request/authorization_code.rb +0 -2
- data/lib/doorkeeper/request/client_credentials.rb +0 -2
- data/lib/doorkeeper/request/code.rb +0 -2
- data/lib/doorkeeper/request/password.rb +1 -13
- data/lib/doorkeeper/request/refresh_token.rb +0 -2
- data/lib/doorkeeper/request/token.rb +0 -2
- data/lib/doorkeeper/request.rb +29 -30
- data/lib/doorkeeper/validations.rb +3 -2
- data/lib/doorkeeper/version.rb +14 -1
- data/lib/doorkeeper.rb +21 -17
- data/lib/generators/doorkeeper/application_owner_generator.rb +26 -12
- data/lib/generators/doorkeeper/confidential_applications_generator.rb +32 -0
- data/lib/generators/doorkeeper/install_generator.rb +17 -9
- data/lib/generators/doorkeeper/migration_generator.rb +26 -9
- data/lib/generators/doorkeeper/pkce_generator.rb +32 -0
- data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +31 -20
- data/lib/generators/doorkeeper/templates/add_confidential_to_applications.rb.erb +13 -0
- data/lib/generators/doorkeeper/templates/{add_owner_to_application_migration.rb → add_owner_to_application_migration.rb.erb} +1 -1
- data/lib/generators/doorkeeper/templates/{add_previous_refresh_token_to_access_tokens.rb → add_previous_refresh_token_to_access_tokens.rb.erb} +1 -1
- data/lib/generators/doorkeeper/templates/enable_pkce_migration.rb.erb +6 -0
- data/lib/generators/doorkeeper/templates/initializer.rb +104 -12
- data/lib/generators/doorkeeper/templates/{migration.rb → migration.rb.erb} +2 -1
- data/lib/generators/doorkeeper/views_generator.rb +3 -1
- data/spec/controllers/application_metal_controller_spec.rb +50 -0
- data/spec/controllers/applications_controller_spec.rb +141 -17
- data/spec/controllers/authorizations_controller_spec.rb +304 -27
- data/spec/controllers/protected_resources_controller_spec.rb +44 -35
- data/spec/controllers/token_info_controller_spec.rb +17 -21
- data/spec/controllers/tokens_controller_spec.rb +201 -17
- data/spec/dummy/app/assets/config/manifest.js +2 -0
- data/spec/dummy/config/environments/test.rb +4 -5
- data/spec/dummy/config/initializers/doorkeeper.rb +23 -2
- data/spec/dummy/config/initializers/{active_record_belongs_to_required_by_default.rb → new_framework_defaults.rb} +5 -1
- data/spec/dummy/config/initializers/secret_token.rb +0 -1
- data/spec/dummy/config/routes.rb +3 -42
- data/spec/dummy/db/migrate/20111122132257_create_users.rb +3 -1
- data/spec/dummy/db/migrate/20120312140401_add_password_to_users.rb +3 -1
- data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +3 -1
- data/spec/dummy/db/migrate/20151223200000_add_owner_to_application.rb +3 -1
- data/spec/dummy/db/migrate/20160320211015_add_previous_refresh_token_to_access_tokens.rb +3 -1
- data/spec/dummy/db/migrate/20170822064514_enable_pkce.rb +6 -0
- data/spec/dummy/db/migrate/20180210183654_add_confidential_to_applications.rb +13 -0
- data/spec/dummy/db/schema.rb +38 -37
- data/spec/factories.rb +1 -1
- data/spec/generators/application_owner_generator_spec.rb +25 -6
- data/spec/generators/confidential_applications_generator_spec.rb +45 -0
- data/spec/generators/install_generator_spec.rb +1 -1
- data/spec/generators/migration_generator_spec.rb +25 -4
- data/spec/generators/pkce_generator_spec.rb +43 -0
- data/spec/generators/previous_refresh_token_generator_spec.rb +57 -0
- data/spec/generators/views_generator_spec.rb +1 -1
- data/spec/grape/grape_integration_spec.rb +135 -0
- data/spec/helpers/doorkeeper/dashboard_helper_spec.rb +2 -2
- data/spec/lib/config_spec.rb +199 -22
- data/spec/lib/doorkeeper_spec.rb +1 -126
- data/spec/lib/models/expirable_spec.rb +0 -4
- data/spec/lib/models/revocable_spec.rb +2 -4
- data/spec/lib/models/scopes_spec.rb +0 -4
- data/spec/lib/oauth/authorization/uri_builder_spec.rb +0 -4
- data/spec/lib/oauth/authorization_code_request_spec.rb +63 -13
- data/spec/lib/oauth/base_request_spec.rb +43 -10
- data/spec/lib/oauth/base_response_spec.rb +1 -1
- data/spec/lib/oauth/client/credentials_spec.rb +5 -5
- data/spec/lib/oauth/client_credentials/creator_spec.rb +6 -2
- data/spec/lib/oauth/client_credentials/issuer_spec.rb +26 -7
- data/spec/lib/oauth/client_credentials/validation_spec.rb +2 -3
- data/spec/lib/oauth/client_credentials_integration_spec.rb +2 -2
- data/spec/lib/oauth/client_credentials_request_spec.rb +4 -5
- data/spec/lib/oauth/client_spec.rb +0 -3
- data/spec/lib/oauth/code_request_spec.rb +5 -5
- data/spec/lib/oauth/error_response_spec.rb +0 -3
- data/spec/lib/oauth/error_spec.rb +1 -3
- data/spec/lib/oauth/forbidden_token_response_spec.rb +1 -4
- data/spec/lib/oauth/helpers/scope_checker_spec.rb +0 -3
- data/spec/lib/oauth/helpers/unique_token_spec.rb +0 -1
- data/spec/lib/oauth/helpers/uri_checker_spec.rb +115 -3
- data/spec/lib/oauth/invalid_token_response_spec.rb +2 -5
- data/spec/lib/oauth/password_access_token_request_spec.rb +46 -5
- data/spec/lib/oauth/pre_authorization_spec.rb +40 -6
- data/spec/lib/oauth/refresh_token_request_spec.rb +30 -14
- data/spec/lib/oauth/scopes_spec.rb +28 -4
- data/spec/lib/oauth/token_request_spec.rb +10 -13
- data/spec/lib/oauth/token_response_spec.rb +0 -1
- data/spec/lib/oauth/token_spec.rb +37 -14
- data/spec/lib/orm/active_record/stale_records_cleaner_spec.rb +79 -0
- data/spec/lib/request/strategy_spec.rb +0 -1
- data/spec/lib/server_spec.rb +10 -0
- data/spec/models/doorkeeper/access_grant_spec.rb +45 -2
- data/spec/models/doorkeeper/access_token_spec.rb +132 -64
- data/spec/models/doorkeeper/application_spec.rb +114 -23
- data/spec/requests/applications/applications_request_spec.rb +139 -6
- data/spec/requests/applications/authorized_applications_spec.rb +1 -1
- data/spec/requests/endpoints/authorization_spec.rb +1 -1
- data/spec/requests/endpoints/token_spec.rb +15 -6
- data/spec/requests/flows/authorization_code_errors_spec.rb +12 -2
- data/spec/requests/flows/authorization_code_spec.rb +199 -2
- data/spec/requests/flows/client_credentials_spec.rb +73 -5
- data/spec/requests/flows/implicit_grant_errors_spec.rb +3 -3
- data/spec/requests/flows/implicit_grant_spec.rb +38 -11
- data/spec/requests/flows/password_spec.rb +160 -24
- data/spec/requests/flows/refresh_token_spec.rb +6 -6
- data/spec/requests/flows/revoke_token_spec.rb +26 -26
- data/spec/requests/flows/skip_authorization_spec.rb +16 -11
- data/spec/requests/protected_resources/metal_spec.rb +2 -2
- data/spec/requests/protected_resources/private_api_spec.rb +2 -2
- data/spec/routing/custom_controller_routes_spec.rb +63 -7
- data/spec/routing/default_routes_spec.rb +6 -2
- data/spec/routing/scoped_routes_spec.rb +16 -2
- data/spec/spec_helper.rb +54 -3
- data/spec/spec_helper_integration.rb +2 -64
- data/spec/support/dependencies/factory_bot.rb +2 -0
- data/spec/support/doorkeeper_rspec.rb +19 -0
- data/spec/support/helpers/access_token_request_helper.rb +1 -1
- data/spec/support/helpers/authorization_request_helper.rb +4 -4
- data/spec/support/helpers/model_helper.rb +9 -4
- data/spec/support/helpers/request_spec_helper.rb +18 -6
- data/spec/support/helpers/url_helper.rb +15 -10
- data/spec/support/http_method_shim.rb +12 -16
- data/spec/support/shared/controllers_shared_context.rb +2 -6
- data/spec/support/shared/models_shared_examples.rb +4 -4
- data/spec/validators/redirect_uri_validator_spec.rb +58 -7
- data/spec/version/version_spec.rb +15 -0
- data/vendor/assets/stylesheets/doorkeeper/bootstrap.min.css +4 -5
- metadata +75 -33
- data/spec/controllers/application_metal_controller.rb +0 -10
- data/spec/support/dependencies/factory_girl.rb +0 -2
|
@@ -6,24 +6,16 @@ module Doorkeeper
|
|
|
6
6
|
include Models::Expirable
|
|
7
7
|
include Models::Revocable
|
|
8
8
|
include Models::Accessible
|
|
9
|
+
include Models::Orderable
|
|
9
10
|
include Models::Scopes
|
|
10
|
-
include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
|
|
11
|
-
|
|
12
|
-
included do
|
|
13
|
-
belongs_to_options = {
|
|
14
|
-
class_name: 'Doorkeeper::Application',
|
|
15
|
-
inverse_of: :access_grants
|
|
16
|
-
}
|
|
17
|
-
if defined?(ActiveRecord::Base) && ActiveRecord::VERSION::MAJOR >= 5
|
|
18
|
-
belongs_to_options[:optional] = true
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
belongs_to :application, belongs_to_options
|
|
22
11
|
|
|
23
|
-
|
|
24
|
-
|
|
12
|
+
# never uses pkce, if pkce migrations were not generated
|
|
13
|
+
def uses_pkce?
|
|
14
|
+
pkce_supported? && code_challenge.present?
|
|
15
|
+
end
|
|
25
16
|
|
|
26
|
-
|
|
17
|
+
def pkce_supported?
|
|
18
|
+
respond_to? :code_challenge
|
|
27
19
|
end
|
|
28
20
|
|
|
29
21
|
module ClassMethods
|
|
@@ -38,16 +30,68 @@ module Doorkeeper
|
|
|
38
30
|
def by_token(token)
|
|
39
31
|
find_by(token: token.to_s)
|
|
40
32
|
end
|
|
41
|
-
end
|
|
42
33
|
|
|
43
|
-
|
|
34
|
+
# Revokes AccessGrant records that have not been revoked and associated
|
|
35
|
+
# with the specific Application and Resource Owner.
|
|
36
|
+
#
|
|
37
|
+
# @param application_id [Integer]
|
|
38
|
+
# ID of the Application
|
|
39
|
+
# @param resource_owner [ActiveRecord::Base]
|
|
40
|
+
# instance of the Resource Owner model
|
|
41
|
+
#
|
|
42
|
+
def revoke_all_for(application_id, resource_owner, clock = Time)
|
|
43
|
+
where(application_id: application_id,
|
|
44
|
+
resource_owner_id: resource_owner.id,
|
|
45
|
+
revoked_at: nil).
|
|
46
|
+
update_all(revoked_at: clock.now.utc)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Implements PKCE code_challenge encoding without base64 padding as described in the spec.
|
|
50
|
+
# https://tools.ietf.org/html/rfc7636#appendix-A
|
|
51
|
+
# Appendix A. Notes on Implementing Base64url Encoding without Padding
|
|
52
|
+
#
|
|
53
|
+
# This appendix describes how to implement a base64url-encoding
|
|
54
|
+
# function without padding, based upon the standard base64-encoding
|
|
55
|
+
# function that uses padding.
|
|
56
|
+
#
|
|
57
|
+
# To be concrete, example C# code implementing these functions is shown
|
|
58
|
+
# below. Similar code could be used in other languages.
|
|
59
|
+
#
|
|
60
|
+
# static string base64urlencode(byte [] arg)
|
|
61
|
+
# {
|
|
62
|
+
# string s = Convert.ToBase64String(arg); // Regular base64 encoder
|
|
63
|
+
# s = s.Split('=')[0]; // Remove any trailing '='s
|
|
64
|
+
# s = s.Replace('+', '-'); // 62nd char of encoding
|
|
65
|
+
# s = s.Replace('/', '_'); // 63rd char of encoding
|
|
66
|
+
# return s;
|
|
67
|
+
# }
|
|
68
|
+
#
|
|
69
|
+
# An example correspondence between unencoded and encoded values
|
|
70
|
+
# follows. The octet sequence below encodes into the string below,
|
|
71
|
+
# which when decoded, reproduces the octet sequence.
|
|
72
|
+
#
|
|
73
|
+
# 3 236 255 224 193
|
|
74
|
+
#
|
|
75
|
+
# A-z_4ME
|
|
76
|
+
#
|
|
77
|
+
# https://ruby-doc.org/stdlib-2.1.3/libdoc/base64/rdoc/Base64.html#method-i-urlsafe_encode64
|
|
78
|
+
#
|
|
79
|
+
# urlsafe_encode64(bin)
|
|
80
|
+
# Returns the Base64-encoded version of bin. This method complies with
|
|
81
|
+
# “Base 64 Encoding with URL and Filename Safe Alphabet” in RFC 4648.
|
|
82
|
+
# The alphabet uses '-' instead of '+' and '_' instead of '/'.
|
|
83
|
+
|
|
84
|
+
# @param code_verifier [#to_s] a one time use value (any object that responds to `#to_s`)
|
|
85
|
+
#
|
|
86
|
+
# @return [#to_s] An encoded code challenge based on the provided verifier suitable for PKCE validation
|
|
87
|
+
def generate_code_challenge(code_verifier)
|
|
88
|
+
padded_result = Base64.urlsafe_encode64(Digest::SHA256.digest(code_verifier))
|
|
89
|
+
padded_result.split('=')[0] # Remove any trailing '='
|
|
90
|
+
end
|
|
44
91
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
#
|
|
49
|
-
def generate_token
|
|
50
|
-
self.token = UniqueToken.generate
|
|
92
|
+
def pkce_supported?
|
|
93
|
+
new.pkce_supported?
|
|
94
|
+
end
|
|
51
95
|
end
|
|
52
96
|
end
|
|
53
97
|
end
|
|
@@ -6,32 +6,8 @@ module Doorkeeper
|
|
|
6
6
|
include Models::Expirable
|
|
7
7
|
include Models::Revocable
|
|
8
8
|
include Models::Accessible
|
|
9
|
+
include Models::Orderable
|
|
9
10
|
include Models::Scopes
|
|
10
|
-
include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
|
|
11
|
-
|
|
12
|
-
included do
|
|
13
|
-
belongs_to_options = {
|
|
14
|
-
class_name: 'Doorkeeper::Application',
|
|
15
|
-
inverse_of: :access_tokens
|
|
16
|
-
}
|
|
17
|
-
if defined?(ActiveRecord::Base) && ActiveRecord::VERSION::MAJOR >= 5
|
|
18
|
-
belongs_to_options[:optional] = true
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
belongs_to :application, belongs_to_options
|
|
22
|
-
|
|
23
|
-
validates :token, presence: true, uniqueness: true
|
|
24
|
-
validates :refresh_token, uniqueness: true, if: :use_refresh_token?
|
|
25
|
-
|
|
26
|
-
# @attr_writer [Boolean, nil] use_refresh_token
|
|
27
|
-
# indicates the possibility of using refresh token
|
|
28
|
-
attr_writer :use_refresh_token
|
|
29
|
-
|
|
30
|
-
before_validation :generate_token, on: :create
|
|
31
|
-
before_validation :generate_refresh_token,
|
|
32
|
-
on: :create,
|
|
33
|
-
if: :use_refresh_token?
|
|
34
|
-
end
|
|
35
11
|
|
|
36
12
|
module ClassMethods
|
|
37
13
|
# Returns an instance of the Doorkeeper::AccessToken with
|
|
@@ -68,11 +44,11 @@ module Doorkeeper
|
|
|
68
44
|
# @param resource_owner [ActiveRecord::Base]
|
|
69
45
|
# instance of the Resource Owner model
|
|
70
46
|
#
|
|
71
|
-
def revoke_all_for(application_id, resource_owner)
|
|
47
|
+
def revoke_all_for(application_id, resource_owner, clock = Time)
|
|
72
48
|
where(application_id: application_id,
|
|
73
49
|
resource_owner_id: resource_owner.id,
|
|
74
50
|
revoked_at: nil).
|
|
75
|
-
|
|
51
|
+
update_all(revoked_at: clock.now.utc)
|
|
76
52
|
end
|
|
77
53
|
|
|
78
54
|
# Looking for not expired Access Token with a matching set of scopes
|
|
@@ -94,30 +70,34 @@ module Doorkeeper
|
|
|
94
70
|
else
|
|
95
71
|
resource_owner_or_id
|
|
96
72
|
end
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
73
|
+
|
|
74
|
+
tokens = authorized_tokens_for(application.try(:id), resource_owner_id)
|
|
75
|
+
tokens.detect do |token|
|
|
76
|
+
scopes_match?(token.scopes, scopes, application.try(:scopes))
|
|
100
77
|
end
|
|
101
78
|
end
|
|
102
79
|
|
|
103
|
-
# Checks whether the token scopes match the scopes from the parameters
|
|
104
|
-
# Application scopes (if present).
|
|
80
|
+
# Checks whether the token scopes match the scopes from the parameters
|
|
105
81
|
#
|
|
106
82
|
# @param token_scopes [#to_s]
|
|
107
83
|
# set of scopes (any object that responds to `#to_s`)
|
|
108
|
-
# @param param_scopes [
|
|
84
|
+
# @param param_scopes [Doorkeeper::OAuth::Scopes]
|
|
109
85
|
# scopes from params
|
|
110
|
-
# @param app_scopes [
|
|
86
|
+
# @param app_scopes [Doorkeeper::OAuth::Scopes]
|
|
111
87
|
# Application scopes
|
|
112
88
|
#
|
|
113
|
-
# @return [Boolean] true if
|
|
89
|
+
# @return [Boolean] true if the param scopes match the token scopes,
|
|
90
|
+
# and all the param scopes are defined in the application (or in the
|
|
91
|
+
# server configuration if the application doesn't define any scopes),
|
|
114
92
|
# and false in other cases
|
|
115
93
|
#
|
|
116
94
|
def scopes_match?(token_scopes, param_scopes, app_scopes)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
95
|
+
return true if token_scopes.empty? && param_scopes.empty?
|
|
96
|
+
|
|
97
|
+
(token_scopes.sort == param_scopes.sort) &&
|
|
98
|
+
Doorkeeper::OAuth::Helpers::ScopeChecker.valid?(
|
|
99
|
+
param_scopes.to_s,
|
|
100
|
+
Doorkeeper.configuration.scopes,
|
|
121
101
|
app_scopes
|
|
122
102
|
)
|
|
123
103
|
end
|
|
@@ -142,9 +122,8 @@ module Doorkeeper
|
|
|
142
122
|
def find_or_create_for(application, resource_owner_id, scopes, expires_in, use_refresh_token)
|
|
143
123
|
if Doorkeeper.configuration.reuse_access_token
|
|
144
124
|
access_token = matching_token_for(application, resource_owner_id, scopes)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
end
|
|
125
|
+
|
|
126
|
+
return access_token if access_token && !access_token.expired?
|
|
148
127
|
end
|
|
149
128
|
|
|
150
129
|
create!(
|
|
@@ -156,7 +135,7 @@ module Doorkeeper
|
|
|
156
135
|
)
|
|
157
136
|
end
|
|
158
137
|
|
|
159
|
-
# Looking for not revoked Access Token
|
|
138
|
+
# Looking for not revoked Access Token records that belongs to specific
|
|
160
139
|
# Application and Resource Owner.
|
|
161
140
|
#
|
|
162
141
|
# @param application_id [Integer]
|
|
@@ -164,14 +143,28 @@ module Doorkeeper
|
|
|
164
143
|
# @param resource_owner_id [Integer]
|
|
165
144
|
# ID of the Resource Owner model instance
|
|
166
145
|
#
|
|
146
|
+
# @return [Doorkeeper::AccessToken] array of matching AccessToken objects
|
|
147
|
+
#
|
|
148
|
+
def authorized_tokens_for(application_id, resource_owner_id)
|
|
149
|
+
ordered_by(:created_at, :desc)
|
|
150
|
+
.where(application_id: application_id,
|
|
151
|
+
resource_owner_id: resource_owner_id,
|
|
152
|
+
revoked_at: nil)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Convenience method for backwards-compatibility, return the last
|
|
156
|
+
# matching token for the given Application and Resource Owner.
|
|
157
|
+
#
|
|
158
|
+
# @param application_id [Integer]
|
|
159
|
+
# ID of the Application model instance
|
|
160
|
+
# @param resource_owner_id [Integer]
|
|
161
|
+
# ID of the Resource Owner model instance
|
|
162
|
+
#
|
|
167
163
|
# @return [Doorkeeper::AccessToken, nil] matching AccessToken object or
|
|
168
164
|
# nil if nothing was found
|
|
169
165
|
#
|
|
170
166
|
def last_authorized_token_for(application_id, resource_owner_id)
|
|
171
|
-
|
|
172
|
-
find_by(application_id: application_id,
|
|
173
|
-
resource_owner_id: resource_owner_id,
|
|
174
|
-
revoked_at: nil)
|
|
167
|
+
authorized_tokens_for(application_id, resource_owner_id).first
|
|
175
168
|
end
|
|
176
169
|
end
|
|
177
170
|
|
|
@@ -180,7 +173,7 @@ module Doorkeeper
|
|
|
180
173
|
# The OAuth 2.0 Authorization Framework: Bearer Token Usage
|
|
181
174
|
#
|
|
182
175
|
def token_type
|
|
183
|
-
'
|
|
176
|
+
'Bearer'
|
|
184
177
|
end
|
|
185
178
|
|
|
186
179
|
def use_refresh_token?
|
|
@@ -194,8 +187,8 @@ module Doorkeeper
|
|
|
194
187
|
def as_json(_options = {})
|
|
195
188
|
{
|
|
196
189
|
resource_owner_id: resource_owner_id,
|
|
197
|
-
|
|
198
|
-
|
|
190
|
+
scope: scopes,
|
|
191
|
+
expires_in: expires_in_seconds,
|
|
199
192
|
application: { uid: application.try(:uid) },
|
|
200
193
|
created_at: created_at.to_i
|
|
201
194
|
}
|
|
@@ -231,7 +224,7 @@ module Doorkeeper
|
|
|
231
224
|
# @return [String] refresh token value
|
|
232
225
|
#
|
|
233
226
|
def generate_refresh_token
|
|
234
|
-
|
|
227
|
+
self.refresh_token = UniqueToken.generate
|
|
235
228
|
end
|
|
236
229
|
|
|
237
230
|
# Generates and sets the token value with the
|
|
@@ -247,18 +240,24 @@ module Doorkeeper
|
|
|
247
240
|
def generate_token
|
|
248
241
|
self.created_at ||= Time.now.utc
|
|
249
242
|
|
|
250
|
-
|
|
251
|
-
self.token = generator.generate(
|
|
243
|
+
self.token = token_generator.generate(
|
|
252
244
|
resource_owner_id: resource_owner_id,
|
|
253
245
|
scopes: scopes,
|
|
254
246
|
application: application,
|
|
255
247
|
expires_in: expires_in,
|
|
256
248
|
created_at: created_at
|
|
257
249
|
)
|
|
258
|
-
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def token_generator
|
|
253
|
+
generator_name = Doorkeeper.configuration.access_token_generator
|
|
254
|
+
generator = generator_name.constantize
|
|
255
|
+
|
|
256
|
+
return generator if generator.respond_to?(:generate)
|
|
257
|
+
|
|
259
258
|
raise Errors::UnableToGenerateToken, "#{generator} does not respond to `.generate`."
|
|
260
259
|
rescue NameError
|
|
261
|
-
raise Errors::TokenGeneratorNotFound, "#{
|
|
260
|
+
raise Errors::TokenGeneratorNotFound, "#{generator_name} not found"
|
|
262
261
|
end
|
|
263
262
|
end
|
|
264
263
|
end
|
|
@@ -3,24 +3,16 @@ module Doorkeeper
|
|
|
3
3
|
extend ActiveSupport::Concern
|
|
4
4
|
|
|
5
5
|
include OAuth::Helpers
|
|
6
|
+
include Models::Orderable
|
|
6
7
|
include Models::Scopes
|
|
7
|
-
include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
|
|
8
|
-
|
|
9
|
-
included do
|
|
10
|
-
has_many :access_grants, dependent: :delete_all, class_name: 'Doorkeeper::AccessGrant'
|
|
11
|
-
has_many :access_tokens, dependent: :delete_all, class_name: 'Doorkeeper::AccessToken'
|
|
12
|
-
|
|
13
|
-
validates :name, :secret, :uid, presence: true
|
|
14
|
-
validates :uid, uniqueness: true
|
|
15
|
-
validates :redirect_uri, redirect_uri: true
|
|
16
|
-
|
|
17
|
-
before_validation :generate_uid, :generate_secret, on: :create
|
|
18
|
-
end
|
|
19
8
|
|
|
20
9
|
module ClassMethods
|
|
21
10
|
# Returns an instance of the Doorkeeper::Application with
|
|
22
11
|
# specific UID and secret.
|
|
23
12
|
#
|
|
13
|
+
# Public/Non-confidential applications will only find by uid if secret is
|
|
14
|
+
# blank.
|
|
15
|
+
#
|
|
24
16
|
# @param uid [#to_s] UID (any object that responds to `#to_s`)
|
|
25
17
|
# @param secret [#to_s] secret (any object that responds to `#to_s`)
|
|
26
18
|
#
|
|
@@ -28,7 +20,11 @@ module Doorkeeper
|
|
|
28
20
|
# if there is no record with such credentials
|
|
29
21
|
#
|
|
30
22
|
def by_uid_and_secret(uid, secret)
|
|
31
|
-
|
|
23
|
+
app = by_uid(uid)
|
|
24
|
+
return unless app
|
|
25
|
+
return app if secret.blank? && !app.confidential?
|
|
26
|
+
return unless app.secret == secret
|
|
27
|
+
app
|
|
32
28
|
end
|
|
33
29
|
|
|
34
30
|
# Returns an instance of the Doorkeeper::Application with specific UID.
|
|
@@ -43,23 +39,13 @@ module Doorkeeper
|
|
|
43
39
|
end
|
|
44
40
|
end
|
|
45
41
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def generate_uid
|
|
54
|
-
if uid.blank?
|
|
55
|
-
self.uid = UniqueToken.generate
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def generate_secret
|
|
60
|
-
if secret.blank?
|
|
61
|
-
self.secret = UniqueToken.generate
|
|
62
|
-
end
|
|
42
|
+
# Set an application's valid redirect URIs.
|
|
43
|
+
#
|
|
44
|
+
# @param uris [String, Array] Newline-separated string or array the URI(s)
|
|
45
|
+
#
|
|
46
|
+
# @return [String] The redirect URI(s) seperated by newlines.
|
|
47
|
+
def redirect_uri=(uris)
|
|
48
|
+
super(uris.is_a?(Array) ? uris.join("\n") : uris)
|
|
63
49
|
end
|
|
64
50
|
end
|
|
65
51
|
end
|
|
@@ -6,7 +6,7 @@ module Doorkeeper
|
|
|
6
6
|
#
|
|
7
7
|
# @return [Boolean] true if object expired and false in other case
|
|
8
8
|
def expired?
|
|
9
|
-
expires_in && Time.now.utc >
|
|
9
|
+
expires_in && Time.now.utc > expires_at
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
# Calculates expiration time in seconds.
|
|
@@ -15,14 +15,16 @@ module Doorkeeper
|
|
|
15
15
|
# or nil if object never expires.
|
|
16
16
|
def expires_in_seconds
|
|
17
17
|
return nil if expires_in.nil?
|
|
18
|
-
expires =
|
|
18
|
+
expires = expires_at - Time.now.utc
|
|
19
19
|
expires_sec = expires.seconds.round(0)
|
|
20
20
|
expires_sec > 0 ? expires_sec : 0
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
# Expiration time (date time of creation + TTL).
|
|
24
|
+
#
|
|
25
|
+
# @return [Time] expiration time in UTC
|
|
26
|
+
#
|
|
27
|
+
def expires_at
|
|
26
28
|
created_at + expires_in.seconds
|
|
27
29
|
end
|
|
28
30
|
end
|
|
@@ -5,18 +5,12 @@ module Doorkeeper
|
|
|
5
5
|
attr_accessor :pre_auth, :resource_owner, :token
|
|
6
6
|
|
|
7
7
|
def initialize(pre_auth, resource_owner)
|
|
8
|
-
@pre_auth
|
|
8
|
+
@pre_auth = pre_auth
|
|
9
9
|
@resource_owner = resource_owner
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def issue_token
|
|
13
|
-
@token ||= AccessGrant.create!
|
|
14
|
-
application_id: pre_auth.client.id,
|
|
15
|
-
resource_owner_id: resource_owner.id,
|
|
16
|
-
expires_in: configuration.authorization_code_expires_in,
|
|
17
|
-
redirect_uri: pre_auth.redirect_uri,
|
|
18
|
-
scopes: pre_auth.scopes.to_s
|
|
19
|
-
)
|
|
13
|
+
@token ||= AccessGrant.create! access_grant_attributes
|
|
20
14
|
end
|
|
21
15
|
|
|
22
16
|
def native_redirect
|
|
@@ -26,6 +20,35 @@ module Doorkeeper
|
|
|
26
20
|
def configuration
|
|
27
21
|
Doorkeeper.configuration
|
|
28
22
|
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def authorization_code_expires_in
|
|
27
|
+
configuration.authorization_code_expires_in
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def access_grant_attributes
|
|
31
|
+
pkce_attributes.merge application_id: pre_auth.client.id,
|
|
32
|
+
resource_owner_id: resource_owner.id,
|
|
33
|
+
expires_in: authorization_code_expires_in,
|
|
34
|
+
redirect_uri: pre_auth.redirect_uri,
|
|
35
|
+
scopes: pre_auth.scopes.to_s
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def pkce_attributes
|
|
39
|
+
return {} unless pkce_supported?
|
|
40
|
+
|
|
41
|
+
{
|
|
42
|
+
code_challenge: pre_auth.code_challenge,
|
|
43
|
+
code_challenge_method: pre_auth.code_challenge_method
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# ensures firstly, if migration with additional pcke columns was
|
|
48
|
+
# generated and migrated
|
|
49
|
+
def pkce_supported?
|
|
50
|
+
Doorkeeper::AccessGrant.pkce_supported?
|
|
51
|
+
end
|
|
29
52
|
end
|
|
30
53
|
end
|
|
31
54
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Doorkeeper
|
|
2
|
+
module OAuth
|
|
3
|
+
module Authorization
|
|
4
|
+
class Context
|
|
5
|
+
attr_reader :client, :grant_type, :scopes
|
|
6
|
+
|
|
7
|
+
def initialize(client, grant_type, scopes)
|
|
8
|
+
@client = client
|
|
9
|
+
@grant_type = grant_type
|
|
10
|
+
@scopes = scopes
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -4,32 +4,61 @@ module Doorkeeper
|
|
|
4
4
|
class Token
|
|
5
5
|
attr_accessor :pre_auth, :resource_owner, :token
|
|
6
6
|
|
|
7
|
+
class << self
|
|
8
|
+
def build_context(pre_auth_or_oauth_client, grant_type, scopes)
|
|
9
|
+
oauth_client = if pre_auth_or_oauth_client.respond_to?(:client)
|
|
10
|
+
pre_auth_or_oauth_client.client
|
|
11
|
+
else
|
|
12
|
+
pre_auth_or_oauth_client
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
Doorkeeper::OAuth::Authorization::Context.new(
|
|
16
|
+
oauth_client,
|
|
17
|
+
grant_type,
|
|
18
|
+
scopes
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def access_token_expires_in(server, context)
|
|
23
|
+
if (expiration = server.custom_access_token_expires_in.call(context))
|
|
24
|
+
expiration
|
|
25
|
+
else
|
|
26
|
+
server.access_token_expires_in
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def refresh_token_enabled?(server, context)
|
|
31
|
+
if server.refresh_token_enabled?.respond_to? :call
|
|
32
|
+
server.refresh_token_enabled?.call(context)
|
|
33
|
+
else
|
|
34
|
+
!!server.refresh_token_enabled?
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
7
39
|
def initialize(pre_auth, resource_owner)
|
|
8
40
|
@pre_auth = pre_auth
|
|
9
41
|
@resource_owner = resource_owner
|
|
10
42
|
end
|
|
11
43
|
|
|
12
|
-
def self.access_token_expires_in(server, pre_auth_or_oauth_client)
|
|
13
|
-
if expiration = custom_expiration(server, pre_auth_or_oauth_client)
|
|
14
|
-
expiration
|
|
15
|
-
else
|
|
16
|
-
server.access_token_expires_in
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
44
|
def issue_token
|
|
45
|
+
context = self.class.build_context(
|
|
46
|
+
pre_auth.client,
|
|
47
|
+
Doorkeeper::OAuth::IMPLICIT,
|
|
48
|
+
pre_auth.scopes
|
|
49
|
+
)
|
|
21
50
|
@token ||= AccessToken.find_or_create_for(
|
|
22
51
|
pre_auth.client,
|
|
23
52
|
resource_owner.id,
|
|
24
53
|
pre_auth.scopes,
|
|
25
|
-
self.class.access_token_expires_in(configuration,
|
|
54
|
+
self.class.access_token_expires_in(configuration, context),
|
|
26
55
|
false
|
|
27
56
|
)
|
|
28
57
|
end
|
|
29
58
|
|
|
30
59
|
def native_redirect
|
|
31
60
|
{
|
|
32
|
-
controller:
|
|
61
|
+
controller: controller,
|
|
33
62
|
action: :show,
|
|
34
63
|
access_token: token.token
|
|
35
64
|
}
|
|
@@ -37,19 +66,16 @@ module Doorkeeper
|
|
|
37
66
|
|
|
38
67
|
private
|
|
39
68
|
|
|
40
|
-
def self.custom_expiration(server, pre_auth_or_oauth_client)
|
|
41
|
-
oauth_client = if pre_auth_or_oauth_client.respond_to?(:client)
|
|
42
|
-
pre_auth_or_oauth_client.client
|
|
43
|
-
else
|
|
44
|
-
pre_auth_or_oauth_client
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
server.custom_access_token_expires_in.call(oauth_client)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
69
|
def configuration
|
|
51
70
|
Doorkeeper.configuration
|
|
52
71
|
end
|
|
72
|
+
|
|
73
|
+
def controller
|
|
74
|
+
@controller ||= begin
|
|
75
|
+
mapping = Doorkeeper::Rails::Routes.mapping[:token_info] || {}
|
|
76
|
+
mapping[:controllers] || 'doorkeeper/token_info'
|
|
77
|
+
end
|
|
78
|
+
end
|
|
53
79
|
end
|
|
54
80
|
end
|
|
55
81
|
end
|
|
@@ -4,19 +4,28 @@ module Doorkeeper
|
|
|
4
4
|
validate :attributes, error: :invalid_request
|
|
5
5
|
validate :client, error: :invalid_client
|
|
6
6
|
validate :grant, error: :invalid_grant
|
|
7
|
+
# @see https://tools.ietf.org/html/rfc6749#section-5.2
|
|
7
8
|
validate :redirect_uri, error: :invalid_grant
|
|
9
|
+
validate :code_verifier, error: :invalid_grant
|
|
8
10
|
|
|
9
|
-
attr_accessor :server, :grant, :client, :redirect_uri, :access_token
|
|
11
|
+
attr_accessor :server, :grant, :client, :redirect_uri, :access_token,
|
|
12
|
+
:code_verifier
|
|
10
13
|
|
|
11
14
|
def initialize(server, grant, client, parameters = {})
|
|
12
15
|
@server = server
|
|
13
16
|
@client = client
|
|
14
17
|
@grant = grant
|
|
18
|
+
@grant_type = Doorkeeper::OAuth::AUTHORIZATION_CODE
|
|
15
19
|
@redirect_uri = parameters[:redirect_uri]
|
|
20
|
+
@code_verifier = parameters[:code_verifier]
|
|
16
21
|
end
|
|
17
22
|
|
|
18
23
|
private
|
|
19
24
|
|
|
25
|
+
def client_by_uid(parameters)
|
|
26
|
+
Doorkeeper::Application.by_uid(parameters[:client_id])
|
|
27
|
+
end
|
|
28
|
+
|
|
20
29
|
def before_successful_response
|
|
21
30
|
grant.transaction do
|
|
22
31
|
grant.lock!
|
|
@@ -28,14 +37,17 @@ module Doorkeeper
|
|
|
28
37
|
grant.scopes,
|
|
29
38
|
server)
|
|
30
39
|
end
|
|
40
|
+
super
|
|
31
41
|
end
|
|
32
42
|
|
|
33
43
|
def validate_attributes
|
|
44
|
+
return false if grant && grant.uses_pkce? && code_verifier.blank?
|
|
45
|
+
return false if grant && !grant.pkce_supported? && !code_verifier.blank?
|
|
34
46
|
redirect_uri.present?
|
|
35
47
|
end
|
|
36
48
|
|
|
37
49
|
def validate_client
|
|
38
|
-
|
|
50
|
+
!client.nil?
|
|
39
51
|
end
|
|
40
52
|
|
|
41
53
|
def validate_grant
|
|
@@ -44,7 +56,25 @@ module Doorkeeper
|
|
|
44
56
|
end
|
|
45
57
|
|
|
46
58
|
def validate_redirect_uri
|
|
47
|
-
|
|
59
|
+
Helpers::URIChecker.valid_for_authorization?(
|
|
60
|
+
redirect_uri,
|
|
61
|
+
grant.redirect_uri
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# if either side (server or client) request pkce, check the verifier
|
|
66
|
+
# against the DB - if pkce is supported
|
|
67
|
+
def validate_code_verifier
|
|
68
|
+
return true unless grant.uses_pkce? || code_verifier
|
|
69
|
+
return false unless grant.pkce_supported?
|
|
70
|
+
|
|
71
|
+
if grant.code_challenge_method == 'S256'
|
|
72
|
+
grant.code_challenge == AccessGrant.generate_code_challenge(code_verifier)
|
|
73
|
+
elsif grant.code_challenge_method == 'plain'
|
|
74
|
+
grant.code_challenge == code_verifier
|
|
75
|
+
else
|
|
76
|
+
false
|
|
77
|
+
end
|
|
48
78
|
end
|
|
49
79
|
end
|
|
50
80
|
end
|