doorkeeper 4.2.6 → 5.0.0.rc1
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/.hound.yml +2 -13
- data/.rubocop.yml +17 -0
- data/.travis.yml +19 -5
- data/Appraisals +8 -4
- data/CODE_OF_CONDUCT.md +46 -0
- data/Gemfile +1 -1
- data/NEWS.md +77 -0
- data/README.md +169 -34
- data/Rakefile +6 -0
- data/SECURITY.md +15 -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 +15 -1
- data/app/controllers/doorkeeper/tokens_controller.rb +15 -7
- 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 +4 -0
- 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 +18 -6
- data/doorkeeper.gemspec +6 -5
- 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 +14 -0
- data/lib/doorkeeper/config.rb +107 -68
- data/lib/doorkeeper/engine.rb +7 -3
- data/lib/doorkeeper/errors.rb +2 -5
- data/lib/doorkeeper/grape/helpers.rb +14 -9
- data/lib/doorkeeper/helpers/controller.rb +15 -6
- data/lib/doorkeeper/models/access_grant_mixin.rb +52 -23
- data/lib/doorkeeper/models/access_token_mixin.rb +51 -52
- 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 +41 -20
- data/lib/doorkeeper/oauth/authorization_code_request.rb +33 -3
- data/lib/doorkeeper/oauth/base_request.rb +22 -7
- data/lib/doorkeeper/oauth/client/credentials.rb +6 -4
- data/lib/doorkeeper/oauth/client.rb +2 -2
- data/lib/doorkeeper/oauth/client_credentials/issuer.rb +6 -1
- data/lib/doorkeeper/oauth/client_credentials/validation.rb +4 -2
- 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 +34 -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/password.rb +1 -11
- data/lib/doorkeeper/request.rb +29 -23
- data/lib/doorkeeper/validations.rb +3 -2
- data/lib/doorkeeper/version.rb +14 -1
- data/lib/doorkeeper.rb +6 -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 +88 -10
- 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 +255 -20
- 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 +142 -10
- 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 +18 -1
- 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 +170 -22
- data/spec/lib/doorkeeper_spec.rb +1 -126
- data/spec/lib/models/expirable_spec.rb +0 -3
- 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 +19 -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 +2 -2
- data/spec/models/doorkeeper/access_token_spec.rb +118 -60
- data/spec/models/doorkeeper/application_spec.rb +101 -23
- data/spec/requests/applications/applications_request_spec.rb +94 -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 +1 -1
- data/spec/requests/flows/authorization_code_spec.rb +198 -1
- 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 -63
- 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 +10 -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 +73 -19
- data/spec/controllers/application_metal_controller.rb +0 -10
- data/spec/support/dependencies/factory_girl.rb +0 -2
|
@@ -13,7 +13,9 @@ module Doorkeeper
|
|
|
13
13
|
validate :scopes, error: :invalid_scope
|
|
14
14
|
|
|
15
15
|
def initialize(server, request)
|
|
16
|
-
@server
|
|
16
|
+
@server = server
|
|
17
|
+
@request = request
|
|
18
|
+
@client = request.client
|
|
17
19
|
|
|
18
20
|
validate
|
|
19
21
|
end
|
|
@@ -25,7 +27,7 @@ module Doorkeeper
|
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def validate_scopes
|
|
28
|
-
return true
|
|
30
|
+
return true if @request.scopes.blank?
|
|
29
31
|
|
|
30
32
|
application_scopes = if @client.present?
|
|
31
33
|
@client.application.scopes
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
module Doorkeeper
|
|
2
2
|
module OAuth
|
|
3
|
-
|
|
3
|
+
Error = Struct.new(:name, :state) do
|
|
4
4
|
def description
|
|
5
5
|
I18n.translate(
|
|
6
6
|
name,
|
|
7
|
-
scope: [
|
|
7
|
+
scope: %i[doorkeeper errors messages],
|
|
8
8
|
default: :server_error
|
|
9
9
|
)
|
|
10
10
|
end
|
|
@@ -4,8 +4,13 @@ module Doorkeeper
|
|
|
4
4
|
include OAuth::Helpers
|
|
5
5
|
|
|
6
6
|
def self.from_request(request, attributes = {})
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
new(
|
|
8
|
+
attributes.merge(
|
|
9
|
+
name: request.error,
|
|
10
|
+
state: request.try(:state),
|
|
11
|
+
redirect_uri: request.try(:redirect_uri)
|
|
12
|
+
)
|
|
13
|
+
)
|
|
9
14
|
end
|
|
10
15
|
|
|
11
16
|
delegate :name, :description, :state, to: :@error
|
|
@@ -42,10 +47,12 @@ module Doorkeeper
|
|
|
42
47
|
end
|
|
43
48
|
|
|
44
49
|
def headers
|
|
45
|
-
{
|
|
50
|
+
{
|
|
51
|
+
'Cache-Control' => 'no-store',
|
|
46
52
|
'Pragma' => 'no-cache',
|
|
47
53
|
'Content-Type' => 'application/json; charset=utf-8',
|
|
48
|
-
'WWW-Authenticate' => authenticate_info
|
|
54
|
+
'WWW-Authenticate' => authenticate_info
|
|
55
|
+
}
|
|
49
56
|
end
|
|
50
57
|
|
|
51
58
|
protected
|
|
@@ -17,10 +17,6 @@ module Doorkeeper
|
|
|
17
17
|
@valid_scopes.has_scopes?(parsed_scopes)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def match?
|
|
21
|
-
valid? && parsed_scopes.has_scopes?(@valid_scopes)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
20
|
private
|
|
25
21
|
|
|
26
22
|
def valid_scopes(server_scopes, application_scopes)
|
|
@@ -35,10 +31,6 @@ module Doorkeeper
|
|
|
35
31
|
def self.valid?(scope_str, server_scopes, application_scopes = nil)
|
|
36
32
|
Validator.new(scope_str, server_scopes, application_scopes).valid?
|
|
37
33
|
end
|
|
38
|
-
|
|
39
|
-
def self.match?(scope_str, server_scopes, application_scopes = nil)
|
|
40
|
-
Validator.new(scope_str, server_scopes, application_scopes).match?
|
|
41
|
-
end
|
|
42
34
|
end
|
|
43
35
|
end
|
|
44
36
|
end
|
|
@@ -3,6 +3,7 @@ module Doorkeeper
|
|
|
3
3
|
module Helpers
|
|
4
4
|
module URIChecker
|
|
5
5
|
def self.valid?(url)
|
|
6
|
+
return true if native_uri?(url)
|
|
6
7
|
uri = as_uri(url)
|
|
7
8
|
uri.fragment.nil? && !uri.host.nil? && !uri.scheme.nil?
|
|
8
9
|
rescue URI::InvalidURIError
|
|
@@ -12,6 +13,13 @@ module Doorkeeper
|
|
|
12
13
|
def self.matches?(url, client_url)
|
|
13
14
|
url = as_uri(url)
|
|
14
15
|
client_url = as_uri(client_url)
|
|
16
|
+
|
|
17
|
+
if client_url.query.present?
|
|
18
|
+
return false unless query_matches?(url.query, client_url.query)
|
|
19
|
+
# Clear out queries so rest of URI can be tested. This allows query
|
|
20
|
+
# params to be in the request but order not mattering.
|
|
21
|
+
client_url.query = nil
|
|
22
|
+
end
|
|
15
23
|
url.query = nil
|
|
16
24
|
url == client_url
|
|
17
25
|
end
|
|
@@ -24,6 +32,13 @@ module Doorkeeper
|
|
|
24
32
|
URI.parse(url)
|
|
25
33
|
end
|
|
26
34
|
|
|
35
|
+
def self.query_matches?(query, client_query)
|
|
36
|
+
return true if client_query.nil? && query.nil?
|
|
37
|
+
return false if client_query.nil? || query.nil?
|
|
38
|
+
# Will return true independent of query order
|
|
39
|
+
client_query.split('&').sort == query.split('&').sort
|
|
40
|
+
end
|
|
41
|
+
|
|
27
42
|
def self.native_uri?(url)
|
|
28
43
|
url == Doorkeeper.configuration.native_redirect_uri
|
|
29
44
|
end
|
|
@@ -4,10 +4,9 @@ module Doorkeeper
|
|
|
4
4
|
attr_reader :reason
|
|
5
5
|
|
|
6
6
|
def self.from_access_token(access_token, attributes = {})
|
|
7
|
-
reason =
|
|
8
|
-
when access_token.try(:revoked?)
|
|
7
|
+
reason = if access_token.try(:revoked?)
|
|
9
8
|
:revoked
|
|
10
|
-
|
|
9
|
+
elsif access_token.try(:expired?)
|
|
11
10
|
:expired
|
|
12
11
|
else
|
|
13
12
|
:unknown
|
|
@@ -22,7 +21,7 @@ module Doorkeeper
|
|
|
22
21
|
end
|
|
23
22
|
|
|
24
23
|
def description
|
|
25
|
-
scope = { scope: [
|
|
24
|
+
scope = { scope: %i[doorkeeper errors messages invalid_token] }
|
|
26
25
|
@description ||= I18n.translate @reason, scope
|
|
27
26
|
end
|
|
28
27
|
end
|
|
@@ -16,25 +16,29 @@ module Doorkeeper
|
|
|
16
16
|
@client = client
|
|
17
17
|
@parameters = parameters
|
|
18
18
|
@original_scopes = parameters[:scope]
|
|
19
|
+
@grant_type = Doorkeeper::OAuth::PASSWORD
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
private
|
|
22
23
|
|
|
23
24
|
def before_successful_response
|
|
24
25
|
find_or_create_access_token(client, resource_owner.id, scopes, server)
|
|
26
|
+
super
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def validate_scopes
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
client_scopes = client.try(:scopes)
|
|
31
|
+
return true if scopes.blank?
|
|
32
|
+
|
|
33
|
+
ScopeChecker.valid?(scopes.to_s, server.scopes, client_scopes)
|
|
30
34
|
end
|
|
31
35
|
|
|
32
36
|
def validate_resource_owner
|
|
33
|
-
|
|
37
|
+
!resource_owner.nil?
|
|
34
38
|
end
|
|
35
39
|
|
|
36
40
|
def validate_client
|
|
37
|
-
!parameters[:client_id] ||
|
|
41
|
+
!parameters[:client_id] || !client.nil?
|
|
38
42
|
end
|
|
39
43
|
end
|
|
40
44
|
end
|
|
@@ -7,17 +7,21 @@ module Doorkeeper
|
|
|
7
7
|
validate :client, error: :invalid_client
|
|
8
8
|
validate :scopes, error: :invalid_scope
|
|
9
9
|
validate :redirect_uri, error: :invalid_redirect_uri
|
|
10
|
+
validate :code_challenge_method, error: :invalid_code_challenge_method
|
|
10
11
|
|
|
11
|
-
attr_accessor :server, :client, :response_type, :redirect_uri, :state
|
|
12
|
+
attr_accessor :server, :client, :response_type, :redirect_uri, :state,
|
|
13
|
+
:code_challenge, :code_challenge_method
|
|
12
14
|
attr_writer :scope
|
|
13
15
|
|
|
14
16
|
def initialize(server, client, attrs = {})
|
|
15
|
-
@server
|
|
16
|
-
@client
|
|
17
|
-
@response_type
|
|
18
|
-
@redirect_uri
|
|
19
|
-
@scope
|
|
20
|
-
@state
|
|
17
|
+
@server = server
|
|
18
|
+
@client = client
|
|
19
|
+
@response_type = attrs[:response_type]
|
|
20
|
+
@redirect_uri = attrs[:redirect_uri]
|
|
21
|
+
@scope = attrs[:scope]
|
|
22
|
+
@state = attrs[:state]
|
|
23
|
+
@code_challenge = attrs[:code_challenge]
|
|
24
|
+
@code_challenge_method = attrs[:code_challenge_method]
|
|
21
25
|
end
|
|
22
26
|
|
|
23
27
|
def authorizable?
|
|
@@ -29,15 +33,36 @@ module Doorkeeper
|
|
|
29
33
|
end
|
|
30
34
|
|
|
31
35
|
def scope
|
|
32
|
-
@scope.presence ||
|
|
36
|
+
@scope.presence || build_scopes
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
def error_response
|
|
36
40
|
OAuth::ErrorResponse.from_request(self)
|
|
37
41
|
end
|
|
38
42
|
|
|
43
|
+
def as_json(_options)
|
|
44
|
+
{
|
|
45
|
+
client_id: client.uid,
|
|
46
|
+
redirect_uri: redirect_uri,
|
|
47
|
+
state: state,
|
|
48
|
+
response_type: response_type,
|
|
49
|
+
scope: scope,
|
|
50
|
+
client_name: client.name,
|
|
51
|
+
status: I18n.t('doorkeeper.pre_authorization.status')
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
39
55
|
private
|
|
40
56
|
|
|
57
|
+
def build_scopes
|
|
58
|
+
client_scopes = client.application.scopes
|
|
59
|
+
if client_scopes.blank?
|
|
60
|
+
server.default_scopes.to_s
|
|
61
|
+
else
|
|
62
|
+
(server.default_scopes & client_scopes).to_s
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
41
66
|
def validate_response_type
|
|
42
67
|
server.authorization_response_types.include? response_type
|
|
43
68
|
end
|
|
@@ -47,7 +72,8 @@ module Doorkeeper
|
|
|
47
72
|
end
|
|
48
73
|
|
|
49
74
|
def validate_scopes
|
|
50
|
-
return true
|
|
75
|
+
return true if scope.blank?
|
|
76
|
+
|
|
51
77
|
Helpers::ScopeChecker.valid?(
|
|
52
78
|
scope,
|
|
53
79
|
server.scopes,
|
|
@@ -55,11 +81,17 @@ module Doorkeeper
|
|
|
55
81
|
)
|
|
56
82
|
end
|
|
57
83
|
|
|
58
|
-
# TODO: test uri should be matched against the client's one
|
|
59
84
|
def validate_redirect_uri
|
|
60
|
-
return false
|
|
61
|
-
|
|
62
|
-
|
|
85
|
+
return false if redirect_uri.blank?
|
|
86
|
+
|
|
87
|
+
Helpers::URIChecker.valid_for_authorization?(
|
|
88
|
+
redirect_uri,
|
|
89
|
+
client.redirect_uri
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def validate_code_challenge_method
|
|
94
|
+
!code_challenge.present? || (code_challenge_method.present? && code_challenge_method =~ /^plain$|^S256$/)
|
|
63
95
|
end
|
|
64
96
|
end
|
|
65
97
|
end
|
|
@@ -35,6 +35,7 @@ module Doorkeeper
|
|
|
35
35
|
refresh_token.revoke unless refresh_token_revoked_on_use?
|
|
36
36
|
create_access_token
|
|
37
37
|
end
|
|
38
|
+
super
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
def refresh_token_revoked_on_use?
|
|
@@ -64,7 +65,12 @@ module Doorkeeper
|
|
|
64
65
|
end
|
|
65
66
|
|
|
66
67
|
def access_token_expires_in
|
|
67
|
-
Authorization::Token.access_token_expires_in(
|
|
68
|
+
Authorization::Token.access_token_expires_in(
|
|
69
|
+
server,
|
|
70
|
+
client,
|
|
71
|
+
Doorkeeper::OAuth::REFRESH_TOKEN,
|
|
72
|
+
scopes
|
|
73
|
+
)
|
|
68
74
|
end
|
|
69
75
|
|
|
70
76
|
def validate_token_presence
|
|
@@ -41,24 +41,34 @@ module Doorkeeper
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def has_scopes?(scopes)
|
|
44
|
-
scopes.all? { |
|
|
44
|
+
scopes.all? { |scope| exists?(scope) }
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def +(other)
|
|
48
|
-
|
|
49
|
-
self.class.from_array(all + other.all)
|
|
50
|
-
else
|
|
51
|
-
super(other)
|
|
52
|
-
end
|
|
48
|
+
self.class.from_array(all + to_array(other))
|
|
53
49
|
end
|
|
54
50
|
|
|
55
51
|
def <=>(other)
|
|
56
|
-
|
|
52
|
+
if other.respond_to?(:map)
|
|
53
|
+
map(&:to_s).sort <=> other.map(&:to_s).sort
|
|
54
|
+
else
|
|
55
|
+
super
|
|
56
|
+
end
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def &(other)
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
self.class.from_array(all & to_array(other))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def to_array(other)
|
|
66
|
+
case other
|
|
67
|
+
when Scopes
|
|
68
|
+
other.all
|
|
69
|
+
else
|
|
70
|
+
other.to_a
|
|
71
|
+
end
|
|
62
72
|
end
|
|
63
73
|
end
|
|
64
74
|
end
|
|
@@ -3,7 +3,7 @@ module Doorkeeper
|
|
|
3
3
|
class Token
|
|
4
4
|
class << self
|
|
5
5
|
def from_request(request, *methods)
|
|
6
|
-
methods.inject(nil) do |
|
|
6
|
+
methods.inject(nil) do |_, method|
|
|
7
7
|
method = self.method(method) if method.is_a?(Symbol)
|
|
8
8
|
credentials = method.call(request)
|
|
9
9
|
break credentials unless credentials.blank?
|
|
@@ -11,9 +11,12 @@ module Doorkeeper
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def authenticate(request, *methods)
|
|
14
|
-
if token = from_request(request, *methods)
|
|
14
|
+
if (token = from_request(request, *methods))
|
|
15
15
|
access_token = AccessToken.by_token(token)
|
|
16
|
-
|
|
16
|
+
refresh_token_enabled = Doorkeeper.configuration.refresh_token_enabled?
|
|
17
|
+
if access_token.present? && refresh_token_enabled
|
|
18
|
+
access_token.revoke_previous_refresh_token!
|
|
19
|
+
end
|
|
17
20
|
access_token
|
|
18
21
|
end
|
|
19
22
|
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
module Doorkeeper
|
|
2
|
+
module OAuth
|
|
3
|
+
# RFC7662 OAuth 2.0 Token Introspection
|
|
4
|
+
#
|
|
5
|
+
# @see https://tools.ietf.org/html/rfc7662
|
|
6
|
+
class TokenIntrospection
|
|
7
|
+
attr_reader :server, :token
|
|
8
|
+
attr_reader :error
|
|
9
|
+
|
|
10
|
+
def initialize(server, token)
|
|
11
|
+
@server = server
|
|
12
|
+
@token = token
|
|
13
|
+
|
|
14
|
+
authorize!
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def authorized?
|
|
18
|
+
@error.blank?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_json
|
|
22
|
+
active? ? success_response : failure_response
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# If the protected resource uses OAuth 2.0 client credentials to
|
|
28
|
+
# authenticate to the introspection endpoint and its credentials are
|
|
29
|
+
# invalid, the authorization server responds with an HTTP 401
|
|
30
|
+
# (Unauthorized) as described in Section 5.2 of OAuth 2.0 [RFC6749].
|
|
31
|
+
#
|
|
32
|
+
# Endpoint must first validate the authentication.
|
|
33
|
+
# If the authentication is invalid, the endpoint should respond with
|
|
34
|
+
# an HTTP 401 status code and an invalid_client response.
|
|
35
|
+
#
|
|
36
|
+
# @see https://www.oauth.com/oauth2-servers/token-introspection-endpoint/
|
|
37
|
+
#
|
|
38
|
+
def authorize!
|
|
39
|
+
# Requested client authorization
|
|
40
|
+
if server.credentials
|
|
41
|
+
@error = :invalid_client unless authorized_client
|
|
42
|
+
else
|
|
43
|
+
# Requested bearer token authorization
|
|
44
|
+
@error = :invalid_request unless authorized_token
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Client Authentication
|
|
49
|
+
def authorized_client
|
|
50
|
+
@authorized_client ||= server.credentials && server.client
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Bearer Token Authentication
|
|
54
|
+
def authorized_token
|
|
55
|
+
@authorized_token ||=
|
|
56
|
+
OAuth::Token.authenticate(server.context.request, :from_bearer_authorization)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# 2.2. Introspection Response
|
|
60
|
+
def success_response
|
|
61
|
+
{
|
|
62
|
+
active: true,
|
|
63
|
+
scope: @token.scopes_string,
|
|
64
|
+
client_id: @token.try(:application).try(:uid),
|
|
65
|
+
token_type: @token.token_type,
|
|
66
|
+
exp: @token.expires_at.to_i,
|
|
67
|
+
iat: @token.created_at.to_i
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# If the introspection call is properly authorized but the token is not
|
|
72
|
+
# active, does not exist on this server, or the protected resource is
|
|
73
|
+
# not allowed to introspect this particular token, then the
|
|
74
|
+
# authorization server MUST return an introspection response with the
|
|
75
|
+
# "active" field set to "false". Note that to avoid disclosing too
|
|
76
|
+
# much of the authorization server's state to a third party, the
|
|
77
|
+
# authorization server SHOULD NOT include any additional information
|
|
78
|
+
# about an inactive token, including why the token is inactive.
|
|
79
|
+
#
|
|
80
|
+
# @see https://tools.ietf.org/html/rfc7662 2.2. Introspection Response
|
|
81
|
+
#
|
|
82
|
+
def failure_response
|
|
83
|
+
{
|
|
84
|
+
active: false
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Boolean indicator of whether or not the presented token
|
|
89
|
+
# is currently active. The specifics of a token's "active" state
|
|
90
|
+
# will vary depending on the implementation of the authorization
|
|
91
|
+
# server and the information it keeps about its tokens, but a "true"
|
|
92
|
+
# value return for the "active" property will generally indicate
|
|
93
|
+
# that a given token has been issued by this authorization server,
|
|
94
|
+
# has not been revoked by the resource owner, and is within its
|
|
95
|
+
# given time window of validity (e.g., after its issuance time and
|
|
96
|
+
# before its expiration time).
|
|
97
|
+
#
|
|
98
|
+
# Any other error is considered an "inactive" token.
|
|
99
|
+
#
|
|
100
|
+
# * The token requested does not exist or is invalid
|
|
101
|
+
# * The token expired
|
|
102
|
+
# * The token was issued to a different client than is making this request
|
|
103
|
+
#
|
|
104
|
+
def active?
|
|
105
|
+
if authorized_client
|
|
106
|
+
valid_token? && authorized_for_client?
|
|
107
|
+
else
|
|
108
|
+
valid_token?
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Token can be valid only if it is not expired or revoked.
|
|
113
|
+
def valid_token?
|
|
114
|
+
@token.present? && @token.accessible?
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# If token doesn't belong to some client, then it is public.
|
|
118
|
+
# Otherwise in it required for token to be connected to the same client.
|
|
119
|
+
def authorized_for_client?
|
|
120
|
+
if @token.application.present?
|
|
121
|
+
@token.application == authorized_client.application
|
|
122
|
+
else
|
|
123
|
+
true
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -23,9 +23,11 @@ module Doorkeeper
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def headers
|
|
26
|
-
{
|
|
26
|
+
{
|
|
27
|
+
'Cache-Control' => 'no-store',
|
|
27
28
|
'Pragma' => 'no-cache',
|
|
28
|
-
'Content-Type' => 'application/json; charset=utf-8'
|
|
29
|
+
'Content-Type' => 'application/json; charset=utf-8'
|
|
30
|
+
}
|
|
29
31
|
end
|
|
30
32
|
end
|
|
31
33
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doorkeeper
|
|
4
|
+
module OAuth
|
|
5
|
+
GRANT_TYPES = [
|
|
6
|
+
AUTHORIZATION_CODE = 'authorization_code'.freeze,
|
|
7
|
+
IMPLICIT = 'implicit'.freeze,
|
|
8
|
+
PASSWORD = 'password'.freeze,
|
|
9
|
+
CLIENT_CREDENTIALS = 'client_credentials'.freeze,
|
|
10
|
+
REFRESH_TOKEN = 'refresh_token'.freeze
|
|
11
|
+
].freeze
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -3,5 +3,32 @@ module Doorkeeper
|
|
|
3
3
|
self.table_name = "#{table_name_prefix}oauth_access_grants#{table_name_suffix}".to_sym
|
|
4
4
|
|
|
5
5
|
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
|
+
|
|
13
|
+
if defined?(ActiveRecord::Base) && ActiveRecord::VERSION::MAJOR >= 5
|
|
14
|
+
belongs_to_options[:optional] = true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
belongs_to :application, belongs_to_options
|
|
18
|
+
|
|
19
|
+
validates :resource_owner_id, :application_id, :token, :expires_in, :redirect_uri, presence: true
|
|
20
|
+
validates :token, uniqueness: true
|
|
21
|
+
|
|
22
|
+
before_validation :generate_token, on: :create
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# Generates token value with UniqueToken class.
|
|
27
|
+
#
|
|
28
|
+
# @return [String] token value
|
|
29
|
+
#
|
|
30
|
+
def generate_token
|
|
31
|
+
self.token = UniqueToken.generate
|
|
32
|
+
end
|
|
6
33
|
end
|
|
7
34
|
end
|
|
@@ -3,18 +3,29 @@ module Doorkeeper
|
|
|
3
3
|
self.table_name = "#{table_name_prefix}oauth_access_tokens#{table_name_suffix}".to_sym
|
|
4
4
|
|
|
5
5
|
include AccessTokenMixin
|
|
6
|
+
include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
where(application_id: application_id,
|
|
15
|
-
resource_owner_id: resource_owner.id).delete_all
|
|
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
|
|
16
15
|
end
|
|
17
|
-
|
|
16
|
+
|
|
17
|
+
belongs_to :application, belongs_to_options
|
|
18
|
+
|
|
19
|
+
validates :token, presence: true, uniqueness: true
|
|
20
|
+
validates :refresh_token, uniqueness: true, if: :use_refresh_token?
|
|
21
|
+
|
|
22
|
+
# @attr_writer [Boolean, nil] use_refresh_token
|
|
23
|
+
# indicates the possibility of using refresh token
|
|
24
|
+
attr_writer :use_refresh_token
|
|
25
|
+
|
|
26
|
+
before_validation :generate_token, on: :create
|
|
27
|
+
before_validation :generate_refresh_token,
|
|
28
|
+
on: :create, if: :use_refresh_token?
|
|
18
29
|
|
|
19
30
|
# Searches for not revoked Access Tokens associated with the
|
|
20
31
|
# specific Resource Owner.
|
|
@@ -29,18 +40,8 @@ module Doorkeeper
|
|
|
29
40
|
where(resource_owner_id: resource_owner.id, revoked_at: nil)
|
|
30
41
|
end
|
|
31
42
|
|
|
32
|
-
# ORM-specific order method.
|
|
33
|
-
def self.order_method
|
|
34
|
-
:order
|
|
35
|
-
end
|
|
36
|
-
|
|
37
43
|
def self.refresh_token_revoked_on_use?
|
|
38
44
|
column_names.include?('previous_refresh_token')
|
|
39
45
|
end
|
|
40
|
-
|
|
41
|
-
# ORM-specific DESC order for `:created_at` column.
|
|
42
|
-
def self.created_at_desc
|
|
43
|
-
'created_at desc'
|
|
44
|
-
end
|
|
45
46
|
end
|
|
46
47
|
end
|
|
@@ -3,6 +3,19 @@ module Doorkeeper
|
|
|
3
3
|
self.table_name = "#{table_name_prefix}oauth_applications#{table_name_suffix}".to_sym
|
|
4
4
|
|
|
5
5
|
include ApplicationMixin
|
|
6
|
+
include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
|
|
7
|
+
|
|
8
|
+
has_many :access_grants, dependent: :delete_all, class_name: 'Doorkeeper::AccessGrant'
|
|
9
|
+
has_many :access_tokens, dependent: :delete_all, class_name: 'Doorkeeper::AccessToken'
|
|
10
|
+
|
|
11
|
+
validates :name, :secret, :uid, presence: true
|
|
12
|
+
validates :uid, uniqueness: true
|
|
13
|
+
validates :redirect_uri, redirect_uri: true
|
|
14
|
+
validates :confidential, inclusion: { in: [true, false] }
|
|
15
|
+
|
|
16
|
+
validate :scopes_match_configured, if: :enforce_scopes?
|
|
17
|
+
|
|
18
|
+
before_validation :generate_uid, :generate_secret, on: :create
|
|
6
19
|
|
|
7
20
|
has_many :authorized_tokens, -> { where(revoked_at: nil) }, class_name: 'AccessToken'
|
|
8
21
|
has_many :authorized_applications, through: :authorized_tokens, source: :application
|
|
@@ -20,5 +33,26 @@ module Doorkeeper
|
|
|
20
33
|
resource_access_tokens = AccessToken.active_for(resource_owner)
|
|
21
34
|
where(id: resource_access_tokens.select(:application_id).distinct)
|
|
22
35
|
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def generate_uid
|
|
40
|
+
self.uid = UniqueToken.generate if uid.blank?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def generate_secret
|
|
44
|
+
self.secret = UniqueToken.generate if secret.blank?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def scopes_match_configured
|
|
48
|
+
if scopes.present? &&
|
|
49
|
+
!ScopeChecker.valid?(scopes.to_s, Doorkeeper.configuration.scopes)
|
|
50
|
+
errors.add(:scopes, :not_match_configured)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def enforce_scopes?
|
|
55
|
+
Doorkeeper.configuration.enforce_configured_scopes?
|
|
56
|
+
end
|
|
23
57
|
end
|
|
24
58
|
end
|