doorkeeper 5.1.0.rc1 → 5.1.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of doorkeeper might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +11 -2
- data/Appraisals +29 -3
- data/Gemfile +13 -5
- data/NEWS.md +52 -15
- data/README.md +68 -487
- data/app/controllers/doorkeeper/token_info_controller.rb +1 -1
- data/app/controllers/doorkeeper/tokens_controller.rb +1 -1
- data/doorkeeper.gemspec +3 -2
- data/gemfiles/rails_4_2.gemfile +8 -5
- data/gemfiles/rails_5_0.gemfile +9 -6
- data/gemfiles/rails_5_1.gemfile +9 -6
- data/gemfiles/rails_5_2.gemfile +9 -6
- data/gemfiles/rails_6_0.gemfile +16 -0
- data/gemfiles/rails_master.gemfile +8 -10
- data/lib/doorkeeper.rb +7 -1
- data/lib/doorkeeper/config.rb +110 -24
- data/lib/doorkeeper/models/access_grant_mixin.rb +15 -7
- data/lib/doorkeeper/models/access_token_mixin.rb +29 -16
- data/lib/doorkeeper/models/application_mixin.rb +18 -28
- data/lib/doorkeeper/models/concerns/expirable.rb +3 -2
- data/lib/doorkeeper/models/concerns/reusable.rb +19 -0
- data/lib/doorkeeper/models/concerns/scopes.rb +4 -0
- data/lib/doorkeeper/models/concerns/secret_storable.rb +106 -0
- data/lib/doorkeeper/oauth/authorization/token.rb +3 -1
- data/lib/doorkeeper/oauth/error_response.rb +5 -1
- data/lib/doorkeeper/oauth/helpers/unique_token.rb +12 -1
- data/lib/doorkeeper/oauth/invalid_token_response.rb +4 -0
- data/lib/doorkeeper/oauth/token_introspection.rb +72 -6
- data/lib/doorkeeper/orm/active_record/access_grant.rb +9 -8
- data/lib/doorkeeper/orm/active_record/application.rb +10 -6
- data/lib/doorkeeper/secret_storing/base.rb +63 -0
- data/lib/doorkeeper/secret_storing/bcrypt.rb +59 -0
- data/lib/doorkeeper/secret_storing/plain.rb +33 -0
- data/lib/doorkeeper/secret_storing/sha256_hash.rb +25 -0
- data/lib/doorkeeper/version.rb +1 -1
- data/lib/generators/doorkeeper/templates/initializer.rb +62 -20
- data/spec/controllers/authorizations_controller_spec.rb +3 -3
- data/spec/controllers/token_info_controller_spec.rb +1 -1
- data/spec/controllers/tokens_controller_spec.rb +78 -30
- data/spec/dummy/config/application.rb +12 -1
- data/spec/lib/config_spec.rb +119 -35
- data/spec/lib/models/expirable_spec.rb +12 -0
- data/spec/lib/models/reusable_spec.rb +40 -0
- data/spec/lib/models/scopes_spec.rb +13 -1
- data/spec/lib/models/secret_storable_spec.rb +113 -0
- data/spec/lib/oauth/authorization_code_request_spec.rb +18 -1
- data/spec/lib/oauth/client_credentials/creator_spec.rb +51 -7
- data/spec/lib/oauth/error_response_spec.rb +7 -1
- data/spec/lib/oauth/password_access_token_request_spec.rb +11 -1
- data/spec/lib/oauth/token_request_spec.rb +16 -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/models/doorkeeper/application_spec.rb +23 -4
- data/spec/requests/flows/authorization_code_spec.rb +3 -3
- data/spec/requests/flows/client_credentials_spec.rb +2 -2
- data/spec/requests/flows/implicit_grant_spec.rb +1 -1
- data/spec/requests/flows/password_spec.rb +3 -3
- data/spec/routing/custom_controller_routes_spec.rb +4 -0
- data/spec/support/shared/hashing_shared_context.rb +12 -5
- metadata +51 -21
- data/lib/doorkeeper/models/concerns/hashable.rb +0 -137
- data/spec/lib/models/hashable_spec.rb +0 -183
@@ -45,13 +45,17 @@ module Doorkeeper
|
|
45
45
|
AccessGrant.revoke_all_for(id, resource_owner)
|
46
46
|
end
|
47
47
|
|
48
|
-
# We keep a volatile copy of the raw
|
49
|
-
# The stored
|
48
|
+
# We keep a volatile copy of the raw secret for initial communication
|
49
|
+
# The stored refresh_token may be mapped and not available in cleartext.
|
50
|
+
#
|
51
|
+
# Some strategies allow restoring stored secrets (e.g. symmetric encryption)
|
52
|
+
# while hashing strategies do not, so you cannot rely on this value
|
53
|
+
# returning a present value for persisted tokens.
|
50
54
|
def plaintext_secret
|
51
|
-
if
|
52
|
-
|
55
|
+
if secret_strategy.allows_restoring_secrets?
|
56
|
+
secret_strategy.restore_secret(self, :secret)
|
53
57
|
else
|
54
|
-
|
58
|
+
@raw_secret
|
55
59
|
end
|
56
60
|
end
|
57
61
|
|
@@ -65,7 +69,7 @@ module Doorkeeper
|
|
65
69
|
return unless secret.blank?
|
66
70
|
|
67
71
|
@raw_secret = UniqueToken.generate
|
68
|
-
self
|
72
|
+
secret_strategy.store_secret(self, :secret, @raw_secret)
|
69
73
|
end
|
70
74
|
|
71
75
|
def scopes_match_configured
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Doorkeeper
|
2
|
+
module SecretStoring
|
3
|
+
|
4
|
+
##
|
5
|
+
# Base class for secret storing, including common helpers
|
6
|
+
class Base
|
7
|
+
##
|
8
|
+
# Return the value to be stored by the database
|
9
|
+
# used for looking up a database value.
|
10
|
+
# @param plain_secret The plain secret input / generated
|
11
|
+
def self.transform_secret(plain_secret)
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Transform and store the given secret attribute => value
|
17
|
+
# pair used for safely storing the attribute
|
18
|
+
# @param resource The model instance being modified
|
19
|
+
# @param attribute The secret attribute
|
20
|
+
# @param plain_secret The plain secret input / generated
|
21
|
+
def self.store_secret(resource, attribute, plain_secret)
|
22
|
+
transformed_value = self.transform_secret plain_secret
|
23
|
+
resource.public_send :"#{attribute}=", transformed_value
|
24
|
+
|
25
|
+
transformed_value
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Return the restored value from the database
|
30
|
+
# @param resource The resource instance to act on
|
31
|
+
# @param attribute The secret attribute to restore
|
32
|
+
# as retrieved from the database.
|
33
|
+
def self.restore_secret(resource, attribute)
|
34
|
+
raise NotImplementedError
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Determines whether this strategy supports restoring
|
39
|
+
# secrets from the database. This allows detecting users
|
40
|
+
# trying to use a non-restorable strategy with +reuse_access_tokens+.
|
41
|
+
def self.allows_restoring_secrets?
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Determines what secrets this strategy is applicable for
|
47
|
+
def self.validate_for(model)
|
48
|
+
valid = %i[token application]
|
49
|
+
return true if valid.include?(model.to_sym)
|
50
|
+
|
51
|
+
raise ArgumentError, "'#{name}' can not be used for #{model}."
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Securely compare the given +input+ value with a +stored+ value
|
56
|
+
# processed by +transform_secret+.
|
57
|
+
def self.secret_matches?(input, stored)
|
58
|
+
transformed_input = transform_secret(input)
|
59
|
+
ActiveSupport::SecurityUtils.secure_compare transformed_input, stored
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Doorkeeper
|
2
|
+
module SecretStoring
|
3
|
+
|
4
|
+
##
|
5
|
+
# Plain text secret storing, which is the default
|
6
|
+
# but also provides fallback lookup if
|
7
|
+
# other secret storing mechanisms are enabled.
|
8
|
+
class BCrypt < Base
|
9
|
+
##
|
10
|
+
# Return the value to be stored by the database
|
11
|
+
# @param plain_secret The plain secret input / generated
|
12
|
+
def self.transform_secret(plain_secret)
|
13
|
+
::BCrypt::Password.create(plain_secret.to_s)
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Securely compare the given +input+ value with a +stored+ value
|
18
|
+
# processed by +transform_secret+.
|
19
|
+
def self.secret_matches?(input, stored)
|
20
|
+
::BCrypt::Password.new(stored.to_s) == input.to_s
|
21
|
+
rescue ::BCrypt::Errors::InvalidHash
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Determines whether this strategy supports restoring
|
27
|
+
# secrets from the database. This allows detecting users
|
28
|
+
# trying to use a non-restorable strategy with +reuse_access_tokens+.
|
29
|
+
def self.allows_restoring_secrets?
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Determines what secrets this strategy is applicable for
|
35
|
+
def self.validate_for(model)
|
36
|
+
unless model.to_sym == :application
|
37
|
+
raise ArgumentError,
|
38
|
+
"'#{name}' can only be used for storing application secrets."
|
39
|
+
end
|
40
|
+
|
41
|
+
unless bcrypt_present?
|
42
|
+
raise ArgumentError,
|
43
|
+
"'#{name}' requires the 'bcrypt' gem being loaded."
|
44
|
+
end
|
45
|
+
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Test if we can require the BCrypt gem
|
51
|
+
def self.bcrypt_present?
|
52
|
+
require 'bcrypt'
|
53
|
+
true
|
54
|
+
rescue LoadError
|
55
|
+
false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Doorkeeper
|
2
|
+
module SecretStoring
|
3
|
+
|
4
|
+
##
|
5
|
+
# Plain text secret storing, which is the default
|
6
|
+
# but also provides fallback lookup if
|
7
|
+
# other secret storing mechanisms are enabled.
|
8
|
+
class Plain < Base
|
9
|
+
|
10
|
+
##
|
11
|
+
# Return the value to be stored by the database
|
12
|
+
# @param plain_secret The plain secret input / generated
|
13
|
+
def self.transform_secret(plain_secret)
|
14
|
+
plain_secret
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Return the restored value from the database
|
19
|
+
# @param resource The resource instance to act on
|
20
|
+
# @param attribute The secret attribute to restore
|
21
|
+
# as retrieved from the database.
|
22
|
+
def self.restore_secret(resource, attribute)
|
23
|
+
resource.public_send attribute
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Plain values obviously allow restoring
|
28
|
+
def self.allows_restoring_secrets?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Doorkeeper
|
2
|
+
module SecretStoring
|
3
|
+
|
4
|
+
##
|
5
|
+
# Plain text secret storing, which is the default
|
6
|
+
# but also provides fallback lookup if
|
7
|
+
# other secret storing mechanisms are enabled.
|
8
|
+
class Sha256Hash < Base
|
9
|
+
##
|
10
|
+
# Return the value to be stored by the database
|
11
|
+
# @param plain_secret The plain secret input / generated
|
12
|
+
def self.transform_secret(plain_secret)
|
13
|
+
::Digest::SHA256.hexdigest plain_secret
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Determines whether this strategy supports restoring
|
18
|
+
# secrets from the database. This allows detecting users
|
19
|
+
# trying to use a non-restorable strategy with +reuse_access_tokens+.
|
20
|
+
def self.allows_restoring_secrets?
|
21
|
+
false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/doorkeeper/version.rb
CHANGED
@@ -64,7 +64,7 @@ Doorkeeper.configure do
|
|
64
64
|
|
65
65
|
# The controller Doorkeeper::ApplicationController inherits from.
|
66
66
|
# Defaults to ActionController::Base.
|
67
|
-
# See https://
|
67
|
+
# See https://doorkeeper.gitbook.io/guides/configuration/other-configurations#custom-base-controller
|
68
68
|
#
|
69
69
|
# base_controller 'ApplicationController'
|
70
70
|
|
@@ -79,35 +79,59 @@ Doorkeeper.configure do
|
|
79
79
|
#
|
80
80
|
# reuse_access_token
|
81
81
|
|
82
|
+
# Set a limit for token_reuse if using reuse_access_token option
|
83
|
+
#
|
84
|
+
# This option limits token_reusability to some extent.
|
85
|
+
# If not set then access_token will be reused unless it expires.
|
86
|
+
# Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/1189
|
87
|
+
#
|
88
|
+
# This option should be a percentage(i.e. (0,100])
|
89
|
+
#
|
90
|
+
# token_reuse_limit 100
|
91
|
+
|
82
92
|
# Hash access and refresh tokens before persisting them.
|
83
|
-
#
|
93
|
+
# This will disable the possibility to use +reuse_access_token+
|
84
94
|
# since plain values can no longer be retrieved.
|
85
95
|
#
|
96
|
+
# Note: If you are already a user of doorkeeper and have existing tokens
|
97
|
+
# in your installation, they will be invalid without enabling the additional
|
98
|
+
# setting `fallback_to_plain_secrets` below.
|
99
|
+
#
|
86
100
|
# hash_token_secrets
|
101
|
+
# By default, token secrets will be hashed using the
|
102
|
+
# +Doorkeeper::Hashing::SHA256+ strategy.
|
103
|
+
#
|
104
|
+
# If you wish to use another hashing implementation, you can override
|
105
|
+
# this strategy as follows:
|
106
|
+
#
|
107
|
+
# hash_token_secrets using: '::Doorkeeper::Hashing::MyCustomHashImpl'
|
108
|
+
#
|
109
|
+
# Keep in mind that changing the hashing function will invalidate all existing
|
110
|
+
# secrets, if there are any.
|
87
111
|
|
88
112
|
# Hash application secrets before persisting them.
|
89
113
|
#
|
90
114
|
# hash_application_secrets
|
115
|
+
#
|
116
|
+
# By default, applications will be hashed
|
117
|
+
# with the +Doorkeeper::SecretStoring::SHA256+ strategy.
|
118
|
+
#
|
119
|
+
# If you wish to use bcrypt for application secret hashing, uncomment
|
120
|
+
# this line instead:
|
121
|
+
#
|
122
|
+
# hash_application_secrets using: '::Doorkeeper::SecretStoring::BCrypt'
|
91
123
|
|
92
124
|
# When the above option is enabled,
|
93
125
|
# and a hashed token or secret is not found,
|
94
|
-
#
|
126
|
+
# you can allow to fall back to another strategy.
|
127
|
+
# For users upgrading doorkeeper and wishing to enable hashing,
|
128
|
+
# you will probably want to enable the fallback to plain tokens.
|
95
129
|
#
|
96
130
|
# This will ensure that old access tokens and secrets
|
97
|
-
# will remain valid even if the hashing above is enabled
|
131
|
+
# will remain valid even if the hashing above is enabled.
|
98
132
|
#
|
99
133
|
# fallback_to_plain_secrets
|
100
134
|
|
101
|
-
#
|
102
|
-
# Since old values will not be re-hashed, lookups to tokens and secrets
|
103
|
-
# will fall back to plain value comparison so any existing tokens will
|
104
|
-
# not be invalidated.
|
105
|
-
#
|
106
|
-
# For example, to use SHA256 digests on plain values, uncomment these lines:
|
107
|
-
# hash_secrets do |plain_value|
|
108
|
-
# Digest::SHA256.hexdigest plain_value
|
109
|
-
# end
|
110
|
-
|
111
135
|
# Issue access tokens with refresh token (disabled by default), you may also
|
112
136
|
# pass a block which accepts `context` to customize when to give a refresh
|
113
137
|
# token or not. Similar to `custom_access_token_expires_in`, `context` has
|
@@ -119,12 +143,6 @@ Doorkeeper.configure do
|
|
119
143
|
#
|
120
144
|
# use_refresh_token
|
121
145
|
|
122
|
-
# Forbids creating/updating applications with arbitrary scopes that are
|
123
|
-
# not in configuration, i.e. `default_scopes` or `optional_scopes`.
|
124
|
-
# (disabled by default)
|
125
|
-
#
|
126
|
-
# enforce_configured_scopes
|
127
|
-
|
128
146
|
# Provide support for an owner to be assigned to each registered application (disabled by default)
|
129
147
|
# Optional parameter confirmation: true (default false) if you want to enforce ownership of
|
130
148
|
# a registered application
|
@@ -148,6 +166,12 @@ Doorkeeper.configure do
|
|
148
166
|
#
|
149
167
|
# scopes_by_grant_type password: [:write], client_credentials: [:update]
|
150
168
|
|
169
|
+
# Forbids creating/updating applications with arbitrary scopes that are
|
170
|
+
# not in configuration, i.e. `default_scopes` or `optional_scopes`.
|
171
|
+
# (disabled by default)
|
172
|
+
#
|
173
|
+
# enforce_configured_scopes
|
174
|
+
|
151
175
|
# Change the way client credentials are retrieved from the request object.
|
152
176
|
# By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
|
153
177
|
# falls back to the `:client_id` and `:client_secret` params from the `params` object.
|
@@ -205,6 +229,24 @@ Doorkeeper.configure do
|
|
205
229
|
#
|
206
230
|
# handle_auth_errors :raise
|
207
231
|
|
232
|
+
# Customize token introspection response.
|
233
|
+
# Allows to add your own fields to default one that are required by the OAuth spec
|
234
|
+
# for the introspection response. It could be `sub`, `aud` and so on.
|
235
|
+
# This configuration option can be a proc, lambda or any Ruby object responds
|
236
|
+
# to `.call` method and result of it's invocation must be a Hash.
|
237
|
+
#
|
238
|
+
# custom_introspection_response do |token, context|
|
239
|
+
# {
|
240
|
+
# "sub": "Z5O3upPC88QrAjx00dis",
|
241
|
+
# "aud": "https://protected.example.net/resource",
|
242
|
+
# "username": User.find(token.resource_owner_id).username
|
243
|
+
# }
|
244
|
+
# end
|
245
|
+
#
|
246
|
+
# or
|
247
|
+
#
|
248
|
+
# custom_introspection_response CustomIntrospectionResponder
|
249
|
+
|
208
250
|
# Specify what grant flows are enabled in array of Strings. The valid
|
209
251
|
# strings and the flows they enable are:
|
210
252
|
#
|
@@ -91,7 +91,7 @@ describe Doorkeeper::AuthorizationsController, 'implicit grant flow' do
|
|
91
91
|
end
|
92
92
|
|
93
93
|
it "includes access token in fragment" do
|
94
|
-
expect(redirect_uri.match(/access_token=([a-
|
94
|
+
expect(redirect_uri.match(/access_token=([a-zA-Z0-9\-_]+)&?/)[1]).to eq(Doorkeeper::AccessToken.first.token)
|
95
95
|
end
|
96
96
|
|
97
97
|
it "includes token type in fragment" do
|
@@ -165,7 +165,7 @@ describe Doorkeeper::AuthorizationsController, 'implicit grant flow' do
|
|
165
165
|
let(:redirect_uri) { response_json_body['redirect_uri'] }
|
166
166
|
|
167
167
|
it 'renders 400 error' do
|
168
|
-
expect(response.status).to eq
|
168
|
+
expect(response.status).to eq 400
|
169
169
|
end
|
170
170
|
|
171
171
|
it 'includes correct redirect URI' do
|
@@ -408,7 +408,7 @@ describe Doorkeeper::AuthorizationsController, 'implicit grant flow' do
|
|
408
408
|
expect(redirect_uri.match(/token_type=(\w+)&?/)[1]).to eq "Bearer"
|
409
409
|
expect(redirect_uri.match(/expires_in=(\d+)&?/)[1].to_i).to eq 1234
|
410
410
|
expect(
|
411
|
-
redirect_uri.match(/access_token=([a-
|
411
|
+
redirect_uri.match(/access_token=([a-zA-Z0-9\-_]+)&?/)[1]
|
412
412
|
).to eq Doorkeeper::AccessToken.first.token
|
413
413
|
end
|
414
414
|
|
@@ -40,7 +40,7 @@ describe Doorkeeper::TokenInfoController do
|
|
40
40
|
get :show
|
41
41
|
|
42
42
|
expect(response.body).to eq(
|
43
|
-
Doorkeeper::OAuth::
|
43
|
+
Doorkeeper::OAuth::InvalidTokenResponse.new.body.to_json
|
44
44
|
)
|
45
45
|
end
|
46
46
|
end
|
@@ -20,7 +20,7 @@ describe Doorkeeper::TokensController do
|
|
20
20
|
|
21
21
|
post :create
|
22
22
|
|
23
|
-
expect(response.status).to eq
|
23
|
+
expect(response.status).to eq 400
|
24
24
|
expect(response.headers['WWW-Authenticate']).to match(/Bearer/)
|
25
25
|
end
|
26
26
|
end
|
@@ -49,7 +49,7 @@ describe Doorkeeper::TokensController do
|
|
49
49
|
"error" => custom_message,
|
50
50
|
"error_description" => "Authorization custom message"
|
51
51
|
}
|
52
|
-
expect(response.status).to eq
|
52
|
+
expect(response.status).to eq 400
|
53
53
|
expect(response.headers['WWW-Authenticate']).to match(/Bearer/)
|
54
54
|
expect(JSON.parse(response.body)).to eq expected_response_body
|
55
55
|
end
|
@@ -130,28 +130,26 @@ describe Doorkeeper::TokensController do
|
|
130
130
|
end
|
131
131
|
|
132
132
|
describe 'when requested token introspection' do
|
133
|
-
|
134
|
-
|
135
|
-
|
133
|
+
let(:client) { FactoryBot.create(:application) }
|
134
|
+
let(:access_token) { FactoryBot.create(:access_token, application: client) }
|
135
|
+
let(:token_for_introspection) { FactoryBot.create(:access_token, application: client) }
|
136
136
|
|
137
|
+
context 'authorized using valid Bearer token' do
|
137
138
|
it 'responds with full token introspection' do
|
138
139
|
request.headers['Authorization'] = "Bearer #{access_token.token}"
|
139
140
|
|
140
|
-
post :introspect, params: { token:
|
141
|
+
post :introspect, params: { token: token_for_introspection.token }
|
141
142
|
|
142
143
|
should_have_json 'active', true
|
143
144
|
expect(json_response).to include('client_id', 'token_type', 'exp', 'iat')
|
144
145
|
end
|
145
146
|
end
|
146
147
|
|
147
|
-
context 'authorized using Client Authentication' do
|
148
|
-
let(:client) { FactoryBot.create(:application) }
|
149
|
-
let(:access_token) { FactoryBot.create(:access_token, application: client) }
|
150
|
-
|
148
|
+
context 'authorized using valid Client Authentication' do
|
151
149
|
it 'responds with full token introspection' do
|
152
150
|
request.headers['Authorization'] = basic_auth_header_for_client(client)
|
153
151
|
|
154
|
-
post :introspect, params: { token:
|
152
|
+
post :introspect, params: { token: token_for_introspection.token }
|
155
153
|
|
156
154
|
should_have_json 'active', true
|
157
155
|
expect(json_response).to include('client_id', 'token_type', 'exp', 'iat')
|
@@ -159,14 +157,37 @@ describe Doorkeeper::TokensController do
|
|
159
157
|
end
|
160
158
|
end
|
161
159
|
|
160
|
+
context 'using custom introspection response' do
|
161
|
+
before do
|
162
|
+
Doorkeeper.configure do
|
163
|
+
orm DOORKEEPER_ORM
|
164
|
+
custom_introspection_response do |_token, _context|
|
165
|
+
{
|
166
|
+
sub: 'Z5O3upPC88QrAjx00dis',
|
167
|
+
aud: 'https://protected.example.net/resource'
|
168
|
+
}
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'responds with full token introspection' do
|
174
|
+
request.headers['Authorization'] = "Bearer #{access_token.token}"
|
175
|
+
|
176
|
+
post :introspect, params: { token: token_for_introspection.token }
|
177
|
+
|
178
|
+
expect(json_response).to include('client_id', 'token_type', 'exp', 'iat', 'sub', 'aud')
|
179
|
+
should_have_json 'sub', 'Z5O3upPC88QrAjx00dis'
|
180
|
+
should_have_json 'aud', 'https://protected.example.net/resource'
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
162
184
|
context 'public access token' do
|
163
|
-
let(:
|
164
|
-
let(:access_token) { FactoryBot.create(:access_token, application: nil) }
|
185
|
+
let(:token_for_introspection) { FactoryBot.create(:access_token, application: nil) }
|
165
186
|
|
166
187
|
it 'responds with full token introspection' do
|
167
188
|
request.headers['Authorization'] = basic_auth_header_for_client(client)
|
168
189
|
|
169
|
-
post :introspect, params: { token:
|
190
|
+
post :introspect, params: { token: token_for_introspection.token }
|
170
191
|
|
171
192
|
should_have_json 'active', true
|
172
193
|
expect(json_response).to include('client_id', 'token_type', 'exp', 'iat')
|
@@ -175,14 +196,12 @@ describe Doorkeeper::TokensController do
|
|
175
196
|
end
|
176
197
|
|
177
198
|
context 'token was issued to a different client than is making this request' do
|
178
|
-
let(:client) { FactoryBot.create(:application) }
|
179
199
|
let(:different_client) { FactoryBot.create(:application) }
|
180
|
-
let(:access_token) { FactoryBot.create(:access_token, application: client) }
|
181
200
|
|
182
201
|
it 'responds with only active state' do
|
183
202
|
request.headers['Authorization'] = basic_auth_header_for_client(different_client)
|
184
203
|
|
185
|
-
post :introspect, params: { token:
|
204
|
+
post :introspect, params: { token: token_for_introspection.token }
|
186
205
|
|
187
206
|
expect(response).to be_successful
|
188
207
|
|
@@ -191,6 +210,36 @@ describe Doorkeeper::TokensController do
|
|
191
210
|
end
|
192
211
|
end
|
193
212
|
|
213
|
+
context 'authorized using invalid Bearer token' do
|
214
|
+
let(:token_for_introspection) do
|
215
|
+
FactoryBot.create(:access_token, application: client, revoked_at: 1.day.ago)
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'responds with invalid token error' do
|
219
|
+
request.headers['Authorization'] = "Bearer #{token_for_introspection.token}"
|
220
|
+
|
221
|
+
post :introspect, params: { token: access_token.token }
|
222
|
+
|
223
|
+
response_status_should_be 401
|
224
|
+
|
225
|
+
should_not_have_json 'active'
|
226
|
+
should_have_json 'error', 'invalid_token'
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
context 'authorized using the Bearer token that need to be introspected' do
|
231
|
+
it 'responds with invalid token error' do
|
232
|
+
request.headers['Authorization'] = "Bearer #{access_token.token}"
|
233
|
+
|
234
|
+
post :introspect, params: { token: access_token.token }
|
235
|
+
|
236
|
+
response_status_should_be 401
|
237
|
+
|
238
|
+
should_not_have_json 'active'
|
239
|
+
should_have_json 'error', 'invalid_token'
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
194
243
|
context 'using invalid credentials to authorize' do
|
195
244
|
let(:client) { double(uid: '123123', secret: '666999') }
|
196
245
|
let(:access_token) { FactoryBot.create(:access_token) }
|
@@ -209,9 +258,6 @@ describe Doorkeeper::TokensController do
|
|
209
258
|
end
|
210
259
|
|
211
260
|
context 'using wrong token value' do
|
212
|
-
let(:client) { FactoryBot.create(:application) }
|
213
|
-
let(:access_token) { FactoryBot.create(:access_token, application: client) }
|
214
|
-
|
215
261
|
it 'responds with only active state' do
|
216
262
|
request.headers['Authorization'] = basic_auth_header_for_client(client)
|
217
263
|
|
@@ -222,14 +268,15 @@ describe Doorkeeper::TokensController do
|
|
222
268
|
end
|
223
269
|
end
|
224
270
|
|
225
|
-
context 'when requested
|
226
|
-
let(:
|
227
|
-
|
271
|
+
context 'when requested access token expired' do
|
272
|
+
let(:token_for_introspection) do
|
273
|
+
FactoryBot.create(:access_token, application: client, created_at: 1.year.ago)
|
274
|
+
end
|
228
275
|
|
229
276
|
it 'responds with only active state' do
|
230
277
|
request.headers['Authorization'] = basic_auth_header_for_client(client)
|
231
278
|
|
232
|
-
post :introspect, params: { token:
|
279
|
+
post :introspect, params: { token: token_for_introspection.token }
|
233
280
|
|
234
281
|
should_have_json 'active', false
|
235
282
|
expect(json_response).not_to include('client_id', 'token_type', 'exp', 'iat')
|
@@ -237,13 +284,14 @@ describe Doorkeeper::TokensController do
|
|
237
284
|
end
|
238
285
|
|
239
286
|
context 'when requested Access Token revoked' do
|
240
|
-
let(:
|
241
|
-
|
287
|
+
let(:token_for_introspection) do
|
288
|
+
FactoryBot.create(:access_token, application: client, revoked_at: 1.year.ago)
|
289
|
+
end
|
242
290
|
|
243
291
|
it 'responds with only active state' do
|
244
292
|
request.headers['Authorization'] = basic_auth_header_for_client(client)
|
245
293
|
|
246
|
-
post :introspect, params: { token:
|
294
|
+
post :introspect, params: { token: token_for_introspection.token }
|
247
295
|
|
248
296
|
should_have_json 'active', false
|
249
297
|
expect(json_response).not_to include('client_id', 'token_type', 'exp', 'iat')
|
@@ -251,13 +299,13 @@ describe Doorkeeper::TokensController do
|
|
251
299
|
end
|
252
300
|
|
253
301
|
context 'unauthorized (no bearer token or client credentials)' do
|
254
|
-
let(:
|
302
|
+
let(:token_for_introspection) { FactoryBot.create(:access_token) }
|
255
303
|
|
256
304
|
it 'responds with invalid_request error' do
|
257
|
-
post :introspect, params: { token:
|
305
|
+
post :introspect, params: { token: token_for_introspection.token }
|
258
306
|
|
259
307
|
expect(response).not_to be_successful
|
260
|
-
response_status_should_be
|
308
|
+
response_status_should_be 400
|
261
309
|
|
262
310
|
should_not_have_json 'active'
|
263
311
|
should_have_json 'error', 'invalid_request'
|