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.

Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +11 -2
  3. data/Appraisals +29 -3
  4. data/Gemfile +13 -5
  5. data/NEWS.md +52 -15
  6. data/README.md +68 -487
  7. data/app/controllers/doorkeeper/token_info_controller.rb +1 -1
  8. data/app/controllers/doorkeeper/tokens_controller.rb +1 -1
  9. data/doorkeeper.gemspec +3 -2
  10. data/gemfiles/rails_4_2.gemfile +8 -5
  11. data/gemfiles/rails_5_0.gemfile +9 -6
  12. data/gemfiles/rails_5_1.gemfile +9 -6
  13. data/gemfiles/rails_5_2.gemfile +9 -6
  14. data/gemfiles/rails_6_0.gemfile +16 -0
  15. data/gemfiles/rails_master.gemfile +8 -10
  16. data/lib/doorkeeper.rb +7 -1
  17. data/lib/doorkeeper/config.rb +110 -24
  18. data/lib/doorkeeper/models/access_grant_mixin.rb +15 -7
  19. data/lib/doorkeeper/models/access_token_mixin.rb +29 -16
  20. data/lib/doorkeeper/models/application_mixin.rb +18 -28
  21. data/lib/doorkeeper/models/concerns/expirable.rb +3 -2
  22. data/lib/doorkeeper/models/concerns/reusable.rb +19 -0
  23. data/lib/doorkeeper/models/concerns/scopes.rb +4 -0
  24. data/lib/doorkeeper/models/concerns/secret_storable.rb +106 -0
  25. data/lib/doorkeeper/oauth/authorization/token.rb +3 -1
  26. data/lib/doorkeeper/oauth/error_response.rb +5 -1
  27. data/lib/doorkeeper/oauth/helpers/unique_token.rb +12 -1
  28. data/lib/doorkeeper/oauth/invalid_token_response.rb +4 -0
  29. data/lib/doorkeeper/oauth/token_introspection.rb +72 -6
  30. data/lib/doorkeeper/orm/active_record/access_grant.rb +9 -8
  31. data/lib/doorkeeper/orm/active_record/application.rb +10 -6
  32. data/lib/doorkeeper/secret_storing/base.rb +63 -0
  33. data/lib/doorkeeper/secret_storing/bcrypt.rb +59 -0
  34. data/lib/doorkeeper/secret_storing/plain.rb +33 -0
  35. data/lib/doorkeeper/secret_storing/sha256_hash.rb +25 -0
  36. data/lib/doorkeeper/version.rb +1 -1
  37. data/lib/generators/doorkeeper/templates/initializer.rb +62 -20
  38. data/spec/controllers/authorizations_controller_spec.rb +3 -3
  39. data/spec/controllers/token_info_controller_spec.rb +1 -1
  40. data/spec/controllers/tokens_controller_spec.rb +78 -30
  41. data/spec/dummy/config/application.rb +12 -1
  42. data/spec/lib/config_spec.rb +119 -35
  43. data/spec/lib/models/expirable_spec.rb +12 -0
  44. data/spec/lib/models/reusable_spec.rb +40 -0
  45. data/spec/lib/models/scopes_spec.rb +13 -1
  46. data/spec/lib/models/secret_storable_spec.rb +113 -0
  47. data/spec/lib/oauth/authorization_code_request_spec.rb +18 -1
  48. data/spec/lib/oauth/client_credentials/creator_spec.rb +51 -7
  49. data/spec/lib/oauth/error_response_spec.rb +7 -1
  50. data/spec/lib/oauth/password_access_token_request_spec.rb +11 -1
  51. data/spec/lib/oauth/token_request_spec.rb +16 -1
  52. data/spec/lib/secret_storing/base_spec.rb +60 -0
  53. data/spec/lib/secret_storing/bcrypt_spec.rb +49 -0
  54. data/spec/lib/secret_storing/plain_spec.rb +44 -0
  55. data/spec/lib/secret_storing/sha256_hash_spec.rb +48 -0
  56. data/spec/models/doorkeeper/application_spec.rb +23 -4
  57. data/spec/requests/flows/authorization_code_spec.rb +3 -3
  58. data/spec/requests/flows/client_credentials_spec.rb +2 -2
  59. data/spec/requests/flows/implicit_grant_spec.rb +1 -1
  60. data/spec/requests/flows/password_spec.rb +3 -3
  61. data/spec/routing/custom_controller_routes_spec.rb +4 -0
  62. data/spec/support/shared/hashing_shared_context.rb +12 -5
  63. metadata +51 -21
  64. data/lib/doorkeeper/models/concerns/hashable.rb +0 -137
  65. 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 client_secret for initial communication
49
- # The stored secret may be mapped and not available in cleartext.
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 perform_secret_hashing?
52
- @raw_secret
55
+ if secret_strategy.allows_restoring_secrets?
56
+ secret_strategy.restore_secret(self, :secret)
53
57
  else
54
- secret
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.secret = hashed_or_plain_token(@raw_secret)
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
@@ -10,7 +10,7 @@ module Doorkeeper
10
10
  MAJOR = 5
11
11
  MINOR = 1
12
12
  TINY = 0
13
- PRE = 'rc1'
13
+ PRE = 'rc2'
14
14
 
15
15
  # Full version number
16
16
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
@@ -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://github.com/doorkeeper-gem/doorkeeper#custom-base-controller
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
- # Note: This will disable the possibility to use +reuse_access_token+
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
- # look up the plain text token as a fallback.
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-f0-9]+)&?/)[1]).to eq(Doorkeeper::AccessToken.first.token)
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 401
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-f0-9]+)&?/)[1]
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::ErrorResponse.new(name: :invalid_request, status: :unauthorized).body.to_json
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 401
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 401
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
- context 'authorized using Bearer token' do
134
- let(:client) { FactoryBot.create(:application) }
135
- let(:access_token) { FactoryBot.create(:access_token, application: client) }
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: access_token.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: access_token.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(:client) { FactoryBot.create(:application) }
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: access_token.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: access_token.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 Access Token expired' do
226
- let(:client) { FactoryBot.create(:application) }
227
- let(:access_token) { FactoryBot.create(:access_token, application: client, created_at: 1.year.ago) }
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: access_token.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(:client) { FactoryBot.create(:application) }
241
- let(:access_token) { FactoryBot.create(:access_token, application: client, revoked_at: 1.year.ago) }
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: access_token.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(:access_token) { FactoryBot.create(:access_token) }
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: access_token.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 401
308
+ response_status_should_be 400
261
309
 
262
310
  should_not_have_json 'active'
263
311
  should_have_json 'error', 'invalid_request'