doorkeeper 5.2.0.rc1 → 5.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8d957f89709f38c434bcaa2f50d2b7a540ed613c37c49a7206cbab07c32c240
4
- data.tar.gz: f1945351442b325395da930863b402cb45091f576075ffe0b71dbc285911d846
3
+ metadata.gz: 30363aebde1b255fb64b8df11e5d9b1c80f0d896ebb970908f764a8a5fd17fdf
4
+ data.tar.gz: 129cddd18b7ea164da1387d8a3fef5c40b4be96eca9f141ebb0167a318f28a07
5
5
  SHA512:
6
- metadata.gz: 97b9c9d403403af0227f670b66876be06dac554beeb024f507e3c460a0fcd6c5eeb75b8747946ff622065791992ce1bf9c30f8366f964e57545e229366533048
7
- data.tar.gz: f235dee893b1ad79ad1873006e68e0ad508a25f676c4d6ecd6f867cca0ec8c5f1776420d79e0efb8cefc7309b1f467ef34de3382249c698fd842872c3eea5254
6
+ metadata.gz: 48f707ac0ee598c6987cf396cc168f57975abe8fbf78ce0479733e1b7fdf35ef6abcd73474f0141c72eee1b08ce8a9f0cf46d866f974c0feea8ae9609cb36d07
7
+ data.tar.gz: 79c4aa3fdae50be283f3ef78c544554ad5d50d8395d6f6970a41c73a0d46d427b859dd07e38685120d0df9b1016567a3a34b79d882b9b23d6bb470a19c95ecd8
@@ -7,6 +7,19 @@ User-visible changes worth mentioning.
7
7
 
8
8
  ## master
9
9
 
10
+ - [#PR ID] Add your description here
11
+
12
+ ## 5.2.0.rc2
13
+
14
+ - [#1270] Find matching tokens in batches for `reuse_access_token` option (fix #1193).
15
+ - [#1271] Reintroduce existing token revocation for client credentials.
16
+ - [#1269] Update initializer template documentation.
17
+ - [#1266] Use strong parameters within pre-authorization.
18
+ - [#1264] Add :before_successful_authorization and :after_successful_authorization hooks in TokensController
19
+ - [#1263] Response properly when introspection fails and fix configurations's user guide.
20
+
21
+ ## 5.2.0.rc1
22
+
10
23
  - [#1260], [#1261] Improve Token Introspection configuration option (access to tokens, client).
11
24
  - [#1257] Add constraint configuration when using client authentication on introspection endpoint.
12
25
  - [#1252] Returning `unauthorized` when the revocation of the token should not be performed due to wrong permissions.
@@ -31,7 +44,7 @@ User-visible changes worth mentioning.
31
44
 
32
45
  - [#1208] Unify hashing implementation into secret storing strategies
33
46
 
34
- **[IMPORTANT]**: If you have been using the master branch of doorkeeper with bcrypt in your Gemfile.lock,
47
+ **[IMPORTANT]** If you have been using the master branch of doorkeeper with bcrypt in your Gemfile.lock,
35
48
  your application secrets have been hashed using BCrypt. To restore this behavior, use the initializer option
36
49
  `use_application_hashing using: 'Doorkeeper::SecretStoring::BCrypt`.
37
50
 
@@ -7,6 +7,13 @@ Fork, then clone the repo:
7
7
 
8
8
  git clone git@github.com:your-username/doorkeeper.git
9
9
 
10
+ ### Docker Setup
11
+
12
+ Build the container image with: `docker build --pull -t doorkeeper:test .`
13
+ Run the tests with: `docker run -it --rm doorkeeper:test`
14
+
15
+ ### Local Setup
16
+
10
17
  Set up Ruby dependencies via Bundler
11
18
 
12
19
  bundle install
data/Dangerfile CHANGED
@@ -11,7 +11,7 @@ def changelog_entry_example
11
11
  .sub(/[?.!,;]?$/, '')
12
12
  .capitalize
13
13
 
14
- "- [##{pr_number}]: #{pr_title}."
14
+ "- [##{pr_number}] #{pr_title}."
15
15
  end
16
16
 
17
17
  # --------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,29 @@
1
+ FROM ruby:2.6.3-alpine3.9
2
+
3
+ RUN apk add --no-cache \
4
+ ca-certificates \
5
+ wget \
6
+ openssl \
7
+ bash \
8
+ build-base \
9
+ git \
10
+ sqlite-dev \
11
+ tzdata
12
+
13
+ ENV LANG en_US.UTF-8
14
+ ENV LANGUAGE en_US:en
15
+ ENV LC_ALL en_US.UTF-8
16
+
17
+ ENV BUNDLER_VERSION 2.0.1
18
+ RUN gem install bundler -v ${BUNDLER_VERSION} -i /usr/local/lib/ruby/gems/$(ls /usr/local/lib/ruby/gems) --force
19
+
20
+ WORKDIR /srv
21
+
22
+ COPY Gemfile doorkeeper.gemspec /srv/
23
+ COPY lib/doorkeeper/version.rb /srv/lib/doorkeeper/version.rb
24
+
25
+ RUN bundle install
26
+
27
+ COPY . /srv/
28
+
29
+ CMD ["rake"]
@@ -68,7 +68,12 @@ module Doorkeeper
68
68
  def pre_auth
69
69
  @pre_auth ||= OAuth::PreAuthorization.new(Doorkeeper.configuration,
70
70
  server.client_via_uid,
71
- params)
71
+ pre_auth_params)
72
+ end
73
+
74
+ def pre_auth_params
75
+ params.permit(:response_type, :redirect_uri, :scope, :state,
76
+ :code_challenge, :code_challenge_method)
72
77
  end
73
78
 
74
79
  def authorization
@@ -88,7 +88,20 @@ module Doorkeeper
88
88
  end
89
89
 
90
90
  def authorize_response
91
- @authorize_response ||= strategy.authorize
91
+ @authorize_response ||= begin
92
+ before_successful_authorization
93
+ auth = strategy.authorize
94
+ after_successful_authorization unless auth.is_a?(Doorkeeper::OAuth::ErrorResponse)
95
+ auth
96
+ end
97
+ end
98
+
99
+ def after_successful_authorization
100
+ Doorkeeper.configuration.after_successful_authorization.call(self)
101
+ end
102
+
103
+ def before_successful_authorization
104
+ Doorkeeper.configuration.before_successful_authorization.call(self)
92
105
  end
93
106
 
94
107
  def revocation_error_response
@@ -322,9 +322,10 @@ module Doorkeeper
322
322
  end)
323
323
 
324
324
  # Configure protection of token introspection request.
325
- # By default token introspection is allowed only for clients that
326
- # introspected token belongs to or if access token been introspected
327
- # is a public one (doesn't belong to any client).
325
+ # By default this configuration allows to introspect a token by
326
+ # another token of the same application, or to introspect the token
327
+ # that belongs to authorized client, or access token has been introspected
328
+ # is a public one (doesn't belong to any client)
328
329
  #
329
330
  # You can define any custom rule you need or just disable token
330
331
  # introspection at all.
@@ -340,9 +341,11 @@ module Doorkeeper
340
341
  # Bearer token used to authorize the request
341
342
  #
342
343
  option :allow_token_introspection,
343
- default: (lambda do |token, authorized_client, _authorized_token|
344
- if token.application
345
- token.application == authorized_client
344
+ default: (lambda do |token, authorized_client, authorized_token|
345
+ if authorized_token
346
+ authorized_token.application == token&.application
347
+ elsif token.application
348
+ authorized_client == token.application
346
349
  else
347
350
  true
348
351
  end
@@ -76,9 +76,50 @@ module Doorkeeper
76
76
  end
77
77
 
78
78
  tokens = authorized_tokens_for(application.try(:id), resource_owner_id)
79
- tokens.detect do |token|
80
- scopes_match?(token.scopes, scopes, application.try(:scopes))
79
+ find_matching_token(tokens, application, scopes)
80
+ end
81
+
82
+ # Interface to enumerate access token records in batches in order not
83
+ # to bloat the memory. Could be overloaded in any ORM extension.
84
+ #
85
+ def find_access_token_in_batches(relation, *args, &block)
86
+ relation.find_in_batches(*args, &block)
87
+ end
88
+
89
+ # Enumerates AccessToken records in batches to find a matching token.
90
+ # Batching is required in order not to pollute the memory if Application
91
+ # has huge amount of associated records.
92
+ #
93
+ # ActiveRecord 5.x - 6.x ignores custom ordering so we can't perform a
94
+ # database sort by created_at, so we need to load all the matching records,
95
+ # sort them and find latest one. Probably it would be better to rewrite this
96
+ # query using Time math if possible, but we n eed to consider ORM and
97
+ # different databases support.
98
+ #
99
+ # @param relation [ActiveRecord::Relation]
100
+ # Access tokens relation
101
+ # @param application [Doorkeeper::Application]
102
+ # Application instance
103
+ # @param scopes [String, Doorkeeper::OAuth::Scopes]
104
+ # set of scopes
105
+ #
106
+ # @return [Doorkeeper::AccessToken, nil] Access Token instance or
107
+ # nil if matching record was not found
108
+ #
109
+ def find_matching_token(relation, application, scopes)
110
+ return nil unless relation
111
+
112
+ matching_tokens = []
113
+
114
+ find_access_token_in_batches(relation) do |batch|
115
+ tokens = batch.select do |token|
116
+ scopes_match?(token.scopes, scopes, application.try(:scopes))
117
+ end
118
+
119
+ matching_tokens.concat(tokens)
81
120
  end
121
+
122
+ matching_tokens.max_by(&:created_at)
82
123
  end
83
124
 
84
125
  # Checks whether the token scopes match the scopes from the parameters
@@ -5,11 +5,25 @@ module Doorkeeper
5
5
  class ClientCredentialsRequest < BaseRequest
6
6
  class Creator
7
7
  def call(client, scopes, attributes = {})
8
+ existing_token = existing_token_for(client, scopes)
9
+
10
+ if Doorkeeper.configuration.reuse_access_token && existing_token&.reusable?
11
+ return existing_token
12
+ end
13
+
14
+ existing_token&.revoke
15
+
8
16
  AccessToken.find_or_create_for(
9
17
  client, nil, scopes, attributes[:expires_in],
10
18
  attributes[:use_refresh_token]
11
19
  )
12
20
  end
21
+
22
+ private
23
+
24
+ def existing_token_for(client, scopes)
25
+ Doorkeeper::AccessToken.matching_token_for client, nil, scopes
26
+ end
13
27
  end
14
28
  end
15
29
  end
@@ -67,7 +67,7 @@ module Doorkeeper
67
67
  # HTTP 401 code as described in Section 3 of OAuth 2.0 Bearer Token
68
68
  # Usage [RFC6750].
69
69
  #
70
- @error = :invalid_token if authorized_token_matches_introspected? || !authorized_token.accessible?
70
+ @error = :invalid_token unless valid_authorized_token?
71
71
  else
72
72
  @error = :invalid_request
73
73
  end
@@ -149,9 +149,9 @@ module Doorkeeper
149
149
  #
150
150
  def active?
151
151
  if authorized_client
152
- valid_token? && token_introspection_allowed?(authorized_client.application)
152
+ valid_token? && token_introspection_allowed?(auth_client: authorized_client.application)
153
153
  else
154
- valid_token? && token_introspection_allowed?(authorized_token&.application)
154
+ valid_token?
155
155
  end
156
156
  end
157
157
 
@@ -160,20 +160,26 @@ module Doorkeeper
160
160
  @token&.accessible?
161
161
  end
162
162
 
163
+ def valid_authorized_token?
164
+ !authorized_token_matches_introspected? &&
165
+ authorized_token.accessible? &&
166
+ token_introspection_allowed?(auth_token: authorized_token)
167
+ end
168
+
163
169
  # RFC7662 Section 2.1
164
170
  def authorized_token_matches_introspected?
165
171
  authorized_token.token == @token&.token
166
172
  end
167
173
 
168
174
  # config constraints for introspection in Doorkeeper.configuration.allow_token_introspection
169
- def token_introspection_allowed?(client)
175
+ def token_introspection_allowed?(auth_client: nil, auth_token: nil)
170
176
  allow_introspection = Doorkeeper.configuration.allow_token_introspection
171
177
  return allow_introspection unless allow_introspection.respond_to?(:call)
172
178
 
173
179
  allow_introspection.call(
174
180
  @token,
175
- client,
176
- authorized_token
181
+ auth_client,
182
+ auth_token
177
183
  )
178
184
  end
179
185
 
@@ -10,7 +10,7 @@ module Doorkeeper
10
10
  MAJOR = 5
11
11
  MINOR = 2
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(".")
@@ -65,7 +65,7 @@ Doorkeeper.configure do
65
65
  # end
66
66
 
67
67
  # Use a custom class for generating the access token.
68
- # See https://github.com/doorkeeper-gem/doorkeeper#custom-access-token-generator
68
+ # See https://doorkeeper.gitbook.io/guides/configuration/other-configurations#custom-access-token-generator
69
69
  #
70
70
  # access_token_generator '::Doorkeeper::JWT'
71
71
 
@@ -160,7 +160,7 @@ Doorkeeper.configure do
160
160
 
161
161
  # Define access token scopes for your provider
162
162
  # For more information go to
163
- # https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
163
+ # https://doorkeeper.gitbook.io/guides/ruby-on-rails/scopes
164
164
  #
165
165
  # default_scopes :public
166
166
  # optional_scopes :write, :update
@@ -296,7 +296,7 @@ Doorkeeper.configure do
296
296
  # or add any other functionality.
297
297
  #
298
298
  # before_successful_authorization do |controller|
299
- # Rails.logger.info(params.inspect)
299
+ # Rails.logger.info(controller.request.params.inspect)
300
300
  # end
301
301
  #
302
302
  # after_successful_authorization do |controller|
@@ -314,23 +314,13 @@ Doorkeeper.configure do
314
314
  # client.superapp? or resource_owner.admin?
315
315
  # end
316
316
 
317
- # Configure custom constraints for the Token Introspection request. By default
318
- # this configuration option allows to introspect the token that belongs to authorized
319
- # client (from Bearer token or from authenticated client) OR when token doesn't
317
+ # Configure custom constraints for the Token Introspection request.
318
+ # By default this configuration option allows to introspect a token by another
319
+ # token of the same application, OR to introspect the token that belongs to
320
+ # authorized client (from authenticated client) OR when token doesn't
320
321
  # belong to any client (public token). Otherwise requester has no access to the
321
322
  # introspection and it will return response as stated in the RFC.
322
323
  #
323
- # You can define your custom check:
324
- #
325
- # allow_token_introspection do |token, authorized_client, _authorized_token|
326
- # if token.application
327
- # # `protected_resource` is a new database boolean column, for example
328
- # token.application == authorized_client || authorized_client.protected_resource?
329
- # else
330
- # true
331
- # end
332
- # end
333
- #
334
324
  # Block arguments:
335
325
  #
336
326
  # @param token [Doorkeeper::AccessToken]
@@ -343,15 +333,38 @@ Doorkeeper.configure do
343
333
  # @param authorized_token [Doorkeeper::AccessToken]
344
334
  # Bearer token used to authorize the request
345
335
  #
346
- # Keep in mind, that in case the block returns `nil` or `false` introspection response
347
- # doesn't have 401 status code and some descriptive body, you'll get 200 with
348
- # { "active": false } body as stated in the RFC 7662 section 2.2. Introspection Response.
336
+ # In case the block returns `nil` or `false` introspection responses with 401 status code
337
+ # when using authorized token to introspect, or you'll get 200 with { "active": false } body
338
+ # when using authorized client to introspect as stated in the
339
+ # RFC 7662 section 2.2. Introspection Response.
340
+ #
341
+ # Using with caution:
342
+ # Keep in mind that these three parameters pass to block can be nil as following case:
343
+ # `authorized_client` is nil if and only if `authorized_token` is present, and vice versa.
344
+ # `token` will be nil if and only if `authorized_token` is present.
345
+ # So remember to use `&` or check if it is present before calling method on
346
+ # them to make sure you doesn't get NoMethodError exception.
347
+ #
348
+ # You can define your custom check:
349
+ #
350
+ # allow_token_introspection do |token, authorized_client, authorized_token|
351
+ # if authorized_token
352
+ # # customize: require `introspection` scope
353
+ # authorized_token.application == token&.application ||
354
+ # authorized_token.scopes.include?("introspection")
355
+ # elsif token.application
356
+ # # `protected_resource` is a new database boolean column, for example
357
+ # authorized_client == token.application || authorized_client.protected_resource?
358
+ # else
359
+ # # public token (when token.application is nil, token doesn't belong to any application)
360
+ # true
361
+ # end
362
+ # end
349
363
  #
350
- # You can completely disable any token introspection:
364
+ # Or you can completely disable any token introspection:
351
365
  #
352
366
  # allow_token_introspection false
353
367
  #
354
- # In such case every request for token introspection will get { "active": false } response.
355
368
  # If you need to block the request at all, then configure your routes.rb or web-server
356
369
  # like nginx to forbid the request.
357
370
 
@@ -524,4 +524,17 @@ describe Doorkeeper::AuthorizationsController, "implicit grant flow" do
524
524
  post :create
525
525
  end
526
526
  end
527
+
528
+ describe "strong parameters" do
529
+ it "ignores non-scalar scope parameter" do
530
+ get :new, params: {
531
+ client_id: client.uid,
532
+ response_type: "token",
533
+ redirect_uri: client.redirect_uri,
534
+ scope: { "0" => "profile" },
535
+ }
536
+
537
+ expect(response).to be_successful
538
+ end
539
+ end
527
540
  end
@@ -3,31 +3,139 @@
3
3
  require "spec_helper"
4
4
 
5
5
  describe Doorkeeper::TokensController do
6
- describe "when authorization has succeeded" do
7
- let(:token) { double(:token, authorize: true) }
6
+ let(:client) { FactoryBot.create :application }
7
+ let!(:user) { User.create!(name: "Joe", password: "sekret") }
8
8
 
9
- it "returns the authorization" do
10
- skip "verify need of these specs"
9
+ before do
10
+ Doorkeeper.configure do
11
+ resource_owner_from_credentials do
12
+ User.first
13
+ end
14
+ end
11
15
 
12
- expect(token).to receive(:authorization)
16
+ allow(Doorkeeper.configuration).to receive(:grant_flows).and_return(["password"])
17
+ end
13
18
 
14
- post :create
19
+ subject { JSON.parse(response.body) }
20
+
21
+ describe "POST #create" do
22
+ before do
23
+ post :create, params: {
24
+ client_id: client.uid,
25
+ client_secret: client.secret,
26
+ grant_type: "password",
27
+ }
28
+ end
29
+
30
+ it "responds after authorization" do
31
+ expect(response).to be_successful
32
+ end
33
+
34
+ it "includes access token in response" do
35
+ expect(subject["access_token"]).to eq(Doorkeeper::AccessToken.first.token)
36
+ end
37
+
38
+ it "includes token type in response" do
39
+ expect(subject["token_type"]).to eq("Bearer")
40
+ end
41
+
42
+ it "includes token expiration in response" do
43
+ expect(subject["expires_in"].to_i).to eq(Doorkeeper.configuration.access_token_expires_in)
44
+ end
45
+
46
+ it "issues the token for the current client" do
47
+ expect(Doorkeeper::AccessToken.first.application_id).to eq(client.id)
48
+ end
49
+
50
+ it "issues the token for the current resource owner" do
51
+ expect(Doorkeeper::AccessToken.first.resource_owner_id).to eq(user.id)
15
52
  end
16
53
  end
17
54
 
18
- describe "when authorization has failed" do
19
- it "returns the error response" do
20
- token = double(:token, authorize: false)
21
- allow(controller).to receive(:token) { token }
55
+ describe "POST #create with errors" do
56
+ before do
57
+ post :create, params: {
58
+ client_id: client.uid,
59
+ client_secret: "invalid",
60
+ grant_type: "password",
61
+ }
62
+ end
22
63
 
23
- post :create
64
+ it "responds after authorization" do
65
+ expect(response).to be_unauthorized
66
+ end
24
67
 
25
- expect(response.status).to eq 400
26
- expect(response.headers["WWW-Authenticate"]).to match(/Bearer/)
68
+ it "include error in response" do
69
+ expect(subject["error"]).to eq("invalid_client")
70
+ end
71
+
72
+ it "include error_description in response" do
73
+ expect(subject["error_description"]).to be
74
+ end
75
+
76
+ it "does not include access token in response" do
77
+ expect(subject["access_token"]).to be_nil
78
+ end
79
+
80
+ it "does not include token type in response" do
81
+ expect(subject["token_type"]).to be_nil
82
+ end
83
+
84
+ it "does not include token expiration in response" do
85
+ expect(subject["expires_in"]).to be_nil
86
+ end
87
+
88
+ it "does not issue any access token" do
89
+ expect(Doorkeeper::AccessToken.all).to be_empty
90
+ end
91
+ end
92
+
93
+ describe "POST #create with callbacks" do
94
+ after do
95
+ client.update_attribute :redirect_uri, "urn:ietf:wg:oauth:2.0:oob"
96
+ end
97
+
98
+ describe "when successful" do
99
+ after do
100
+ post :create, params: {
101
+ client_id: client.uid,
102
+ client_secret: client.secret,
103
+ grant_type: "password",
104
+ }
105
+ end
106
+
107
+ it "should call :before_successful_authorization callback" do
108
+ expect(Doorkeeper.configuration)
109
+ .to receive_message_chain(:before_successful_authorization, :call).with(instance_of(described_class))
110
+ end
111
+
112
+ it "should call :after_successful_authorization callback" do
113
+ expect(Doorkeeper.configuration)
114
+ .to receive_message_chain(:after_successful_authorization, :call).with(instance_of(described_class))
115
+ end
116
+ end
117
+
118
+ describe "with errors" do
119
+ after do
120
+ post :create, params: {
121
+ client_id: client.uid,
122
+ client_secret: "invalid",
123
+ grant_type: "password",
124
+ }
125
+ end
126
+
127
+ it "should call :before_successful_authorization callback" do
128
+ expect(Doorkeeper.configuration)
129
+ .to receive_message_chain(:before_successful_authorization, :call).with(instance_of(described_class))
130
+ end
131
+
132
+ it "should not call :after_successful_authorization callback" do
133
+ expect(Doorkeeper.configuration).not_to receive(:after_successful_authorization)
134
+ end
27
135
  end
28
136
  end
29
137
 
30
- describe "when there is a failure due to a custom error" do
138
+ describe "POST #create with custom error" do
31
139
  it "returns the error response with a custom message" do
32
140
  # I18n looks for `doorkeeper.errors.messages.custom_message` in locale files
33
141
  custom_message = "my_message"
@@ -58,7 +166,7 @@ describe Doorkeeper::TokensController do
58
166
  end
59
167
 
60
168
  # http://tools.ietf.org/html/rfc7009#section-2.2
61
- describe "revoking tokens" do
169
+ describe "POST #revoke" do
62
170
  let(:client) { FactoryBot.create(:application) }
63
171
  let(:access_token) { FactoryBot.create(:access_token, application: client) }
64
172
 
@@ -117,21 +225,7 @@ describe Doorkeeper::TokensController do
117
225
  end
118
226
  end
119
227
 
120
- describe "authorize response memoization" do
121
- it "memoizes the result of the authorization" do
122
- strategy = double(:strategy, authorize: true)
123
- expect(strategy).to receive(:authorize).once
124
- allow(controller).to receive(:strategy) { strategy }
125
- allow(controller).to receive(:create) do
126
- 2.times { controller.send :authorize_response }
127
- controller.render json: {}, status: :ok
128
- end
129
-
130
- post :create
131
- end
132
- end
133
-
134
- describe "when requested token introspection" do
228
+ describe "POST #introspect" do
135
229
  let(:client) { FactoryBot.create(:application) }
136
230
  let(:access_token) { FactoryBot.create(:access_token, application: client) }
137
231
  let(:token_for_introspection) { FactoryBot.create(:access_token, application: client) }
@@ -167,13 +261,15 @@ describe Doorkeeper::TokensController do
167
261
  end
168
262
  end
169
263
 
170
- it "responds with just active: false response" do
264
+ it "responds with invalid_token error" do
171
265
  request.headers["Authorization"] = "Bearer #{access_token.token}"
172
266
 
173
267
  post :introspect, params: { token: token_for_introspection.token }
174
268
 
175
- should_have_json "active", false
176
- expect(json_response).not_to include("client_id", "token_type", "exp", "iat")
269
+ response_status_should_be 401
270
+
271
+ should_not_have_json "active"
272
+ should_have_json "error", "invalid_token"
177
273
  end
178
274
  end
179
275
 
@@ -268,15 +364,17 @@ describe Doorkeeper::TokensController do
268
364
  expect(json_response).to include("client_id", "token_type", "exp", "iat")
269
365
  end
270
366
 
271
- it "responds with just active status if authorized token doesn't have introspection scope" do
367
+ it "responds with invalid_token error if authorized token doesn't have introspection scope" do
272
368
  access_token.update(scopes: "read write")
273
369
 
274
370
  request.headers["Authorization"] = "Bearer #{access_token.token}"
275
371
 
276
372
  post :introspect, params: { token: token_for_introspection.token }
277
373
 
278
- should_have_json "active", false
279
- expect(json_response).not_to include("client_id", "token_type", "exp", "iat")
374
+ response_status_should_be 401
375
+
376
+ should_not_have_json "active"
377
+ should_have_json "error", "invalid_token"
280
378
  end
281
379
  end
282
380
 
@@ -285,7 +383,7 @@ describe Doorkeeper::TokensController do
285
383
  FactoryBot.create(:access_token, application: client, revoked_at: 1.day.ago)
286
384
  end
287
385
 
288
- it "responds with invalid token error" do
386
+ it "responds with invalid_token error" do
289
387
  request.headers["Authorization"] = "Bearer #{access_token.token}"
290
388
 
291
389
  post :introspect, params: { token: token_for_introspection.token }
@@ -340,13 +438,15 @@ describe Doorkeeper::TokensController do
340
438
  end
341
439
 
342
440
  context "authorized using valid Bearer token" do
343
- it "responds with only active state" do
441
+ it "responds with invalid_token error" do
344
442
  request.headers["Authorization"] = "Bearer #{access_token.token}"
345
443
 
346
444
  post :introspect, params: { token: SecureRandom.hex(16) }
347
445
 
348
- should_have_json "active", false
349
- expect(json_response).not_to include("client_id", "token_type", "exp", "iat")
446
+ response_status_should_be 401
447
+
448
+ should_not_have_json "active"
449
+ should_have_json "error", "invalid_token"
350
450
  end
351
451
  end
352
452
  end
@@ -107,32 +107,59 @@ Doorkeeper.configure do
107
107
  # client.superapp? or resource_owner.admin?
108
108
  # end
109
109
 
110
- # Implement constraints in case you use Client Credentials to authenticate
111
- # the introspection endpoint.
112
- # By default allow introspection if the introspected token belongs to authorized client,
113
- # OR token doesn't belong to any client (public token). Otherwise disallow.
114
- #
115
- # Params:
116
- # `token` - the token to be introspected (see Doorkeeper::AccessToken)
117
- # `client` - the client application authorized for the endpoint (see Doorkeeper::Application)
118
- #
119
- # You can completely ignore it:
120
- # allow_token_introspection do |_token, _client|
121
- # false
122
- # end
110
+ # Configure custom constraints for the Token Introspection request.
111
+ # By default this configuration option allows to introspect a token by another
112
+ # token of the same application, OR to introspect the token that belongs to
113
+ # authorized client (from authenticated client) OR when token doesn't
114
+ # belong to any client (public token). Otherwise requester has no access to the
115
+ # introspection and it will return response as stated in the RFC.
116
+ #
117
+ # Block arguments:
118
+ #
119
+ # @param token [Doorkeeper::AccessToken]
120
+ # token to be introspected
121
+ #
122
+ # @param authorized_client [Doorkeeper::Application]
123
+ # authorized client (if request is authorized using Basic auth with
124
+ # Client Credentials for example)
125
+ #
126
+ # @param authorized_token [Doorkeeper::AccessToken]
127
+ # Bearer token used to authorize the request
123
128
  #
124
- # Or you can define your custom check:
125
- # Adding `protected_resource` boolean column to applications table
126
- # to allow protected_resource client introspect the token of normal client.
127
- # In this case, protected resource client must be confidential.
129
+ # In case the block returns `nil` or `false` introspection responses with 401 status code
130
+ # when using authorized token to introspect, or you'll get 200 with { "active": false } body
131
+ # when using authorized client to introspect as stated in the
132
+ # RFC 7662 section 2.2. Introspection Response.
128
133
  #
129
- # allow_token_introspection do |token, client|
130
- # if token.application
131
- # token.application == client || client.protected_resource?
134
+ # Using with caution:
135
+ # Keep in mind that these three parameters pass to block can be nil as following case:
136
+ # `authorized_client` is nil if and only if `authorized_token` is present, and vice versa.
137
+ # `token` will be nil if and only if `authorized_token` is present.
138
+ # So remember to use `&` or check if it is present before calling method on
139
+ # them to make sure you doesn't get NoMethodError exception.
140
+ #
141
+ # You can define your custom check:
142
+ #
143
+ # allow_token_introspection do |token, authorized_client, authorized_token|
144
+ # if authorized_token
145
+ # # customize: require `introspection` scope
146
+ # authorized_token.application == token&.application ||
147
+ # authorized_token.scopes.include?("introspection")
148
+ # elsif token.application
149
+ # # `protected_resource` is a new database boolean column, for example
150
+ # authorized_client == token.application || authorized_client.protected_resource?
132
151
  # else
152
+ # # public token (when token.application is nil, token doesn't belong to any application)
133
153
  # true
134
154
  # end
135
155
  # end
156
+ #
157
+ # Or you can completely disable any token introspection:
158
+ #
159
+ # allow_token_introspection false
160
+ #
161
+ # If you need to block the request at all, then configure your routes.rb or web-server
162
+ # like nginx to forbid the request.
136
163
 
137
164
  # WWW-Authenticate Realm (default "Doorkeeper").
138
165
  realm "Doorkeeper"
@@ -55,6 +55,7 @@ class Doorkeeper::OAuth::ClientCredentialsRequest
55
55
 
56
56
  expect(Doorkeeper::AccessToken.count).to eq(2)
57
57
  expect(result).not_to eq(existing_token)
58
+ expect(existing_token.reload).to be_revoked
58
59
  end
59
60
  end
60
61
 
@@ -69,6 +70,7 @@ class Doorkeeper::OAuth::ClientCredentialsRequest
69
70
 
70
71
  expect(Doorkeeper::AccessToken.count).to eq(2)
71
72
  expect(result).not_to eq(existing_token)
73
+ expect(existing_token.reload).to be_revoked
72
74
  end
73
75
  end
74
76
  end
@@ -82,6 +84,7 @@ class Doorkeeper::OAuth::ClientCredentialsRequest
82
84
 
83
85
  expect(Doorkeeper::AccessToken.count).to eq(2)
84
86
  expect(result).not_to eq(existing_token)
87
+ expect(existing_token.reload).to be_revoked
85
88
  end
86
89
  end
87
90
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doorkeeper
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.0.rc1
4
+ version: 5.2.0.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felipe Elias Philipp
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2019-05-23 00:00:00.000000000 Z
14
+ date: 2019-06-17 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: railties
@@ -179,6 +179,7 @@ files:
179
179
  - CODE_OF_CONDUCT.md
180
180
  - CONTRIBUTING.md
181
181
  - Dangerfile
182
+ - Dockerfile
182
183
  - Gemfile
183
184
  - MIT-LICENSE
184
185
  - NEWS.md