doorkeeper 5.1.0 → 5.2.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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +812 -0
  3. data/CONTRIBUTING.md +4 -9
  4. data/Dangerfile +1 -1
  5. data/Gemfile +2 -1
  6. data/NEWS.md +1 -814
  7. data/README.md +2 -2
  8. data/RELEASING.md +6 -5
  9. data/app/controllers/doorkeeper/applications_controller.rb +2 -0
  10. data/app/controllers/doorkeeper/tokens_controller.rb +18 -8
  11. data/app/validators/redirect_uri_validator.rb +19 -9
  12. data/app/views/doorkeeper/applications/_form.html.erb +0 -6
  13. data/app/views/doorkeeper/applications/show.html.erb +1 -1
  14. data/config/locales/en.yml +3 -1
  15. data/doorkeeper.gemspec +1 -1
  16. data/gemfiles/rails_5_0.gemfile +1 -0
  17. data/gemfiles/rails_5_1.gemfile +1 -0
  18. data/gemfiles/rails_5_2.gemfile +1 -0
  19. data/gemfiles/rails_6_0.gemfile +2 -1
  20. data/gemfiles/rails_master.gemfile +1 -0
  21. data/lib/doorkeeper/config/option.rb +13 -7
  22. data/lib/doorkeeper/config.rb +30 -3
  23. data/lib/doorkeeper/grape/helpers.rb +5 -1
  24. data/lib/doorkeeper/helpers/controller.rb +16 -3
  25. data/lib/doorkeeper/oauth/authorization/code.rb +10 -8
  26. data/lib/doorkeeper/oauth/authorization/token.rb +1 -1
  27. data/lib/doorkeeper/oauth/code_response.rb +2 -2
  28. data/lib/doorkeeper/oauth/error_response.rb +1 -1
  29. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +18 -4
  30. data/lib/doorkeeper/oauth/nonstandard.rb +39 -0
  31. data/lib/doorkeeper/oauth/refresh_token_request.rb +8 -8
  32. data/lib/doorkeeper/oauth/token_introspection.rb +13 -12
  33. data/lib/doorkeeper/orm/active_record/access_grant.rb +1 -1
  34. data/lib/doorkeeper/orm/active_record/access_token.rb +2 -2
  35. data/lib/doorkeeper/orm/active_record/application.rb +7 -1
  36. data/lib/doorkeeper/orm/active_record.rb +17 -1
  37. data/lib/doorkeeper/stale_records_cleaner.rb +6 -2
  38. data/lib/doorkeeper/version.rb +2 -2
  39. data/lib/doorkeeper.rb +3 -0
  40. data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +6 -6
  41. data/lib/generators/doorkeeper/templates/initializer.rb +41 -9
  42. data/lib/generators/doorkeeper/templates/migration.rb.erb +3 -0
  43. data/spec/controllers/applications_controller_spec.rb +93 -0
  44. data/spec/controllers/tokens_controller_spec.rb +71 -3
  45. data/spec/dummy/config/application.rb +3 -1
  46. data/spec/dummy/config/initializers/doorkeeper.rb +27 -9
  47. data/spec/lib/config_spec.rb +11 -0
  48. data/spec/lib/oauth/helpers/uri_checker_spec.rb +17 -2
  49. data/spec/lib/oauth/pre_authorization_spec.rb +0 -15
  50. data/spec/requests/flows/authorization_code_spec.rb +16 -4
  51. data/spec/requests/flows/revoke_token_spec.rb +19 -11
  52. data/spec/support/doorkeeper_rspec.rb +1 -1
  53. data/spec/validators/redirect_uri_validator_spec.rb +39 -14
  54. metadata +6 -13
  55. data/.coveralls.yml +0 -1
  56. data/.github/ISSUE_TEMPLATE.md +0 -25
  57. data/.github/PULL_REQUEST_TEMPLATE.md +0 -17
  58. data/.gitignore +0 -20
  59. data/.gitlab-ci.yml +0 -16
  60. data/.hound.yml +0 -3
  61. data/.rspec +0 -1
  62. data/.rubocop.yml +0 -50
  63. data/.travis.yml +0 -35
@@ -16,7 +16,7 @@ module Doorkeeper
16
16
  :redirect_uri,
17
17
  presence: true
18
18
 
19
- validates :token, uniqueness: true
19
+ validates :token, uniqueness: { case_sensitive: true }
20
20
 
21
21
  before_validation :generate_token, on: :create
22
22
 
@@ -9,8 +9,8 @@ module Doorkeeper
9
9
  belongs_to :application, class_name: "Doorkeeper::Application",
10
10
  inverse_of: :access_tokens, optional: true
11
11
 
12
- validates :token, presence: true, uniqueness: true
13
- validates :refresh_token, uniqueness: true, if: :use_refresh_token?
12
+ validates :token, presence: true, uniqueness: { case_sensitive: true }
13
+ validates :refresh_token, uniqueness: { case_sensitive: true }, if: :use_refresh_token?
14
14
 
15
15
  # @attr_writer [Boolean, nil] use_refresh_token
16
16
  # indicates the possibility of using refresh token
@@ -10,7 +10,7 @@ module Doorkeeper
10
10
  has_many :access_tokens, dependent: :delete_all, class_name: "Doorkeeper::AccessToken"
11
11
 
12
12
  validates :name, :secret, :uid, presence: true
13
- validates :uid, uniqueness: true
13
+ validates :uid, uniqueness: { case_sensitive: true }
14
14
  validates :redirect_uri, redirect_uri: true
15
15
  validates :confidential, inclusion: { in: [true, false] }
16
16
 
@@ -60,6 +60,12 @@ module Doorkeeper
60
60
  end
61
61
  end
62
62
 
63
+ def to_json(options)
64
+ serializable_hash(except: :secret)
65
+ .merge(secret: plaintext_secret)
66
+ .to_json(options)
67
+ end
68
+
63
69
  private
64
70
 
65
71
  def generate_uid
@@ -6,6 +6,14 @@ require "doorkeeper/orm/active_record/stale_records_cleaner"
6
6
 
7
7
  module Doorkeeper
8
8
  module Orm
9
+ # ActiveRecord ORM for Doorkeeper entity models.
10
+ # Consists of three main OAuth entities:
11
+ # * Access Token
12
+ # * Access Grant
13
+ # * Application (client)
14
+ #
15
+ # Do a lazy loading of all the required and configured stuff.
16
+ #
9
17
  module ActiveRecord
10
18
  def self.initialize_models!
11
19
  lazy_load do
@@ -14,7 +22,7 @@ module Doorkeeper
14
22
  require "doorkeeper/orm/active_record/application"
15
23
 
16
24
  if Doorkeeper.configuration.active_record_options[:establish_connection]
17
- [Doorkeeper::AccessGrant, Doorkeeper::AccessToken, Doorkeeper::Application].each do |model|
25
+ Doorkeeper::Orm::ActiveRecord.models.each do |model|
18
26
  options = Doorkeeper.configuration.active_record_options[:establish_connection]
19
27
  model.establish_connection(options)
20
28
  end
@@ -33,6 +41,14 @@ module Doorkeeper
33
41
  def self.lazy_load(&block)
34
42
  ActiveSupport.on_load(:active_record, {}, &block)
35
43
  end
44
+
45
+ def self.models
46
+ [
47
+ Doorkeeper::AccessGrant,
48
+ Doorkeeper::AccessToken,
49
+ Doorkeeper::Application,
50
+ ]
51
+ end
36
52
  end
37
53
  end
38
54
  end
@@ -5,12 +5,16 @@ module Doorkeeper
5
5
  CLEANER_CLASS = "StaleRecordsCleaner"
6
6
 
7
7
  def self.for(base_scope)
8
- orm_adapter = "doorkeeper/orm/#{Doorkeeper.configuration.orm}".classify
8
+ orm_adapter = "doorkeeper/orm/#{configured_orm}".classify
9
9
 
10
10
  orm_cleaner = "#{orm_adapter}::#{CLEANER_CLASS}".constantize
11
11
  orm_cleaner.new(base_scope)
12
12
  rescue NameError
13
- raise Doorkeeper::Errors::NoOrmCleaner, "'#{Doorkeeper.configuration.orm}' ORM has no cleaner!"
13
+ raise Doorkeeper::Errors::NoOrmCleaner, "'#{configured_orm}' ORM has no cleaner!"
14
+ end
15
+
16
+ def self.configured_orm
17
+ Doorkeeper.configuration.orm
14
18
  end
15
19
 
16
20
  def self.new(base_scope)
@@ -8,9 +8,9 @@ module Doorkeeper
8
8
  module VERSION
9
9
  # Semantic versioning
10
10
  MAJOR = 5
11
- MINOR = 1
11
+ MINOR = 2
12
12
  TINY = 0
13
- PRE = nil
13
+ PRE = "rc1"
14
14
 
15
15
  # Full version number
16
16
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
data/lib/doorkeeper.rb CHANGED
@@ -52,6 +52,7 @@ require "doorkeeper/oauth/token"
52
52
  require "doorkeeper/oauth/token_introspection"
53
53
  require "doorkeeper/oauth/invalid_token_response"
54
54
  require "doorkeeper/oauth/forbidden_token_response"
55
+ require "doorkeeper/oauth/nonstandard"
55
56
 
56
57
  require "doorkeeper/secret_storing/base"
57
58
  require "doorkeeper/secret_storing/plain"
@@ -80,6 +81,8 @@ require "doorkeeper/stale_records_cleaner"
80
81
 
81
82
  require "doorkeeper/orm/active_record"
82
83
 
84
+ # Main Doorkeeper namespace.
85
+ #
83
86
  module Doorkeeper
84
87
  def self.authenticate(request, methods = Doorkeeper.configuration.access_token_methods)
85
88
  OAuth::Token.authenticate(request, *methods)
@@ -17,12 +17,12 @@ module Doorkeeper
17
17
  end
18
18
 
19
19
  def previous_refresh_token
20
- if no_previous_refresh_token_column?
21
- migration_template(
22
- "add_previous_refresh_token_to_access_tokens.rb.erb",
23
- "db/migrate/add_previous_refresh_token_to_access_tokens.rb"
24
- )
25
- end
20
+ return unless no_previous_refresh_token_column?
21
+
22
+ migration_template(
23
+ "add_previous_refresh_token_to_access_tokens.rb.erb",
24
+ "db/migrate/add_previous_refresh_token_to_access_tokens.rb"
25
+ )
26
26
  end
27
27
 
28
28
  private
@@ -196,15 +196,6 @@ Doorkeeper.configure do
196
196
  #
197
197
  # access_token_methods :from_bearer_authorization, :from_access_token_param, :from_bearer_param
198
198
 
199
- # Change the native redirect uri for client apps
200
- # When clients register with the following redirect uri, they won't be redirected to
201
- # any server and the authorizationcode will be displayed within the provider
202
- # The value can be any string. Use nil to disable this feature. When disabled, clients
203
- # must providea valid URL
204
- # (Similar behaviour: https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi)
205
- #
206
- # native_redirect_uri 'urn:ietf:wg:oauth:2.0:oob'
207
-
208
199
  # Forces the usage of the HTTPS protocol in non-native redirect uris (enabled
209
200
  # by default in non-development environments). OAuth2 delegates security in
210
201
  # communication to the HTTPS protocol so it is wise to keep this enabled.
@@ -323,6 +314,47 @@ Doorkeeper.configure do
323
314
  # client.superapp? or resource_owner.admin?
324
315
  # end
325
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
320
+ # belong to any client (public token). Otherwise requester has no access to the
321
+ # introspection and it will return response as stated in the RFC.
322
+ #
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
+ # Block arguments:
335
+ #
336
+ # @param token [Doorkeeper::AccessToken]
337
+ # token to be introspected
338
+ #
339
+ # @param authorized_client [Doorkeeper::Application]
340
+ # authorized client (if request is authorized using Basic auth with
341
+ # Client Credentials for example)
342
+ #
343
+ # @param authorized_token [Doorkeeper::AccessToken]
344
+ # Bearer token used to authorize the request
345
+ #
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.
349
+ #
350
+ # You can completely disable any token introspection:
351
+ #
352
+ # allow_token_introspection false
353
+ #
354
+ # In such case every request for token introspection will get { "active": false } response.
355
+ # If you need to block the request at all, then configure your routes.rb or web-server
356
+ # like nginx to forbid the request.
357
+
326
358
  # WWW-Authenticate Realm (default "Doorkeeper").
327
359
  #
328
360
  # realm "Doorkeeper"
@@ -36,6 +36,9 @@ class CreateDoorkeeperTables < ActiveRecord::Migration<%= migration_version %>
36
36
 
37
37
  create_table :oauth_access_tokens do |t|
38
38
  t.references :resource_owner, index: true
39
+
40
+ # Remove `null: false` if you are planning to use Password
41
+ # Credentials Grant flow that doesn't require an application.
39
42
  t.references :application, null: false
40
43
 
41
44
  # If you use a custom token generator you may need to change this column
@@ -26,6 +26,10 @@ module Doorkeeper
26
26
 
27
27
  expect(json_response).to include("id", "name", "uid", "secret", "redirect_uri", "scopes")
28
28
 
29
+ application = Application.last
30
+ secret_from_response = json_response["secret"]
31
+ expect(application.secret_matches?(secret_from_response)).to be_truthy
32
+
29
33
  expect(json_response["name"]).to eq("Example")
30
34
  expect(json_response["redirect_uri"]).to eq("https://example.com")
31
35
  end
@@ -44,6 +48,21 @@ module Doorkeeper
44
48
  expect(json_response).to include("errors")
45
49
  end
46
50
 
51
+ it "returns validations on wrong create params (unspecified scheme)" do
52
+ expect do
53
+ post :create, params: {
54
+ doorkeeper_application: {
55
+ name: "Example",
56
+ redirect_uri: "app.com:80",
57
+ }, format: :json,
58
+ }
59
+ end.not_to(change { Doorkeeper::Application.count })
60
+
61
+ expect(response).to have_http_status(422)
62
+
63
+ expect(json_response).to include("errors")
64
+ end
65
+
47
66
  it "returns application info" do
48
67
  application = FactoryBot.create(:application, name: "Change me")
49
68
 
@@ -121,6 +140,72 @@ module Doorkeeper
121
140
  end
122
141
 
123
142
  context "when admin is authenticated" do
143
+ context "when application secrets are hashed" do
144
+ before do
145
+ allow(Doorkeeper.configuration).to receive(:application_secret_strategy).and_return(Doorkeeper::SecretStoring::Sha256Hash)
146
+ end
147
+
148
+ it "shows the application secret after creating a new application" do
149
+ expect do
150
+ post :create, params: {
151
+ doorkeeper_application: {
152
+ name: "Example",
153
+ redirect_uri: "https://example.com",
154
+ },
155
+ }
156
+ end.to change { Doorkeeper::Application.count }.by(1)
157
+
158
+ application = Application.last
159
+
160
+ secret_from_flash = flash[:application_secret]
161
+ expect(secret_from_flash).not_to be_empty
162
+ expect(application.secret_matches?(secret_from_flash)).to be_truthy
163
+ expect(response).to redirect_to(controller.main_app.oauth_application_url(application.id))
164
+
165
+ get :show, params: { id: application.id, format: :html }
166
+
167
+ # We don't know the application secret here (because its hashed) so we can not assert its text on the page
168
+ # Instead, we read it from the page and then check if it matches the application secret
169
+ code_element = %r{<code.*id="secret".*>(.*)<\/code>}.match(response.body)
170
+ secret_from_page = code_element[1]
171
+
172
+ expect(response.body).to have_selector("code#application_id", text: application.uid)
173
+ expect(response.body).to have_selector("code#secret")
174
+ expect(secret_from_page).not_to be_empty
175
+ expect(application.secret_matches?(secret_from_page)).to be_truthy
176
+ end
177
+
178
+ it "does not show an application secret when application did already exist" do
179
+ application = FactoryBot.create(:application)
180
+ get :show, params: { id: application.id, format: :html }
181
+
182
+ expect(response.body).to have_selector("code#application_id", text: application.uid)
183
+ expect(response.body).to have_selector("code#secret", text: "")
184
+ end
185
+
186
+ it "returns the application details in a json response" do
187
+ expect do
188
+ post :create, params: {
189
+ doorkeeper_application: {
190
+ name: "Example",
191
+ redirect_uri: "https://example.com",
192
+ }, format: :json,
193
+ }
194
+ end.to(change { Doorkeeper::Application.count })
195
+
196
+ expect(response).to be_successful
197
+
198
+ expect(json_response).to include("id", "name", "uid", "secret", "redirect_uri", "scopes")
199
+
200
+ application = Application.last
201
+ secret_from_response = json_response["secret"]
202
+ expect(application.secret_matches?(secret_from_response)).to be_truthy
203
+
204
+ expect(json_response["name"]).to eq("Example")
205
+ expect(json_response["redirect_uri"]).to eq("https://example.com")
206
+ end
207
+ end
208
+
124
209
  render_views
125
210
 
126
211
  before do
@@ -151,6 +236,14 @@ module Doorkeeper
151
236
  expect(response).to be_redirect
152
237
  end
153
238
 
239
+ it "shows application details" do
240
+ application = FactoryBot.create(:application)
241
+ get :show, params: { id: application.id, format: :html }
242
+
243
+ expect(response.body).to have_selector("code#application_id", text: application.uid)
244
+ expect(response.body).to have_selector("code#secret", text: application.plaintext_secret)
245
+ end
246
+
154
247
  it "does not allow mass assignment of uid or secret" do
155
248
  application = FactoryBot.create(:application)
156
249
  put :update, params: {
@@ -102,10 +102,10 @@ describe Doorkeeper::TokensController do
102
102
  let(:some_other_client) { FactoryBot.create(:application, confidential: true) }
103
103
  let(:oauth_client) { Doorkeeper::OAuth::Client.new(some_other_client) }
104
104
 
105
- it "returns 200" do
105
+ it "returns 403" do
106
106
  post :revoke, params: { token: access_token.token }
107
107
 
108
- expect(response.status).to eq 200
108
+ expect(response.status).to eq 403
109
109
  end
110
110
 
111
111
  it "does not revoke the access token" do
@@ -147,7 +147,7 @@ describe Doorkeeper::TokensController do
147
147
  end
148
148
  end
149
149
 
150
- context "authorized using valid Client Authentication" do
150
+ context "authorized using Client Credentials of the client that token is issued to" do
151
151
  it "responds with full token introspection" do
152
152
  request.headers["Authorization"] = basic_auth_header_for_client(client)
153
153
 
@@ -159,6 +159,24 @@ describe Doorkeeper::TokensController do
159
159
  end
160
160
  end
161
161
 
162
+ context "configured token introspection disabled" do
163
+ before do
164
+ Doorkeeper.configure do
165
+ orm DOORKEEPER_ORM
166
+ allow_token_introspection false
167
+ end
168
+ end
169
+
170
+ it "responds with just active: false response" do
171
+ request.headers["Authorization"] = "Bearer #{access_token.token}"
172
+
173
+ post :introspect, params: { token: token_for_introspection.token }
174
+
175
+ should_have_json "active", false
176
+ expect(json_response).not_to include("client_id", "token_type", "exp", "iat")
177
+ end
178
+ end
179
+
162
180
  context "using custom introspection response" do
163
181
  before do
164
182
  Doorkeeper.configure do
@@ -212,6 +230,56 @@ describe Doorkeeper::TokensController do
212
230
  end
213
231
  end
214
232
 
233
+ context "introspection request authorized by a client and allow_token_introspection is true" do
234
+ let(:different_client) { FactoryBot.create(:application) }
235
+
236
+ before do
237
+ allow(Doorkeeper.configuration).to receive(:allow_token_introspection).and_return(proc do
238
+ true
239
+ end)
240
+ end
241
+
242
+ it "responds with full token introspection" do
243
+ request.headers["Authorization"] = basic_auth_header_for_client(different_client)
244
+
245
+ post :introspect, params: { token: token_for_introspection.token }
246
+
247
+ should_have_json "active", true
248
+ expect(json_response).to include("client_id", "token_type", "exp", "iat")
249
+ should_have_json "client_id", client.uid
250
+ end
251
+ end
252
+
253
+ context "allow_token_introspection requires authorized token with special scope" do
254
+ let(:access_token) { FactoryBot.create(:access_token, scopes: "introspection") }
255
+
256
+ before do
257
+ allow(Doorkeeper.configuration).to receive(:allow_token_introspection).and_return(proc do |_token, _client, authorized_token|
258
+ authorized_token.scopes.include?("introspection")
259
+ end)
260
+ end
261
+
262
+ it "responds with full token introspection if authorized token has introspection scope" do
263
+ request.headers["Authorization"] = "Bearer #{access_token.token}"
264
+
265
+ post :introspect, params: { token: token_for_introspection.token }
266
+
267
+ should_have_json "active", true
268
+ expect(json_response).to include("client_id", "token_type", "exp", "iat")
269
+ end
270
+
271
+ it "responds with just active status if authorized token doesn't have introspection scope" do
272
+ access_token.update(scopes: "read write")
273
+
274
+ request.headers["Authorization"] = "Bearer #{access_token.token}"
275
+
276
+ post :introspect, params: { token: token_for_introspection.token }
277
+
278
+ should_have_json "active", false
279
+ expect(json_response).not_to include("client_id", "token_type", "exp", "iat")
280
+ end
281
+ end
282
+
215
283
  context "authorized using invalid Bearer token" do
216
284
  let(:access_token) do
217
285
  FactoryBot.create(:access_token, application: client, revoked_at: 1.day.ago)
@@ -5,11 +5,13 @@ require "rails"
5
5
  %w[
6
6
  action_controller/railtie
7
7
  action_view/railtie
8
+ action_cable/engine
8
9
  sprockets/railtie
9
10
  ].each do |railtie|
10
11
  begin
11
12
  require railtie
12
- rescue LoadError
13
+ rescue LoadError => e
14
+ puts "Error loading '#{railtie}' (#{e.message})"
13
15
  end
14
16
  end
15
17
 
@@ -65,15 +65,6 @@ Doorkeeper.configure do
65
65
  # Check out the wiki for more information on customization
66
66
  # access_token_methods :from_bearer_authorization, :from_access_token_param, :from_bearer_param
67
67
 
68
- # Change the native redirect uri for client apps
69
- # When clients register with the following redirect uri, they won't be redirected to any server and
70
- # the authorization code will be displayed within the provider
71
- # The value can be any string. Use nil to disable this feature.
72
- # When disabled, clients must provide a valid URL
73
- # (Similar behaviour: https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi)
74
- #
75
- # native_redirect_uri 'urn:ietf:wg:oauth:2.0:oob'
76
-
77
68
  # Forces the usage of the HTTPS protocol in non-native redirect uris (enabled
78
69
  # by default in non-development environments). OAuth2 delegates security in
79
70
  # communication to the HTTPS protocol so it is wise to keep this enabled.
@@ -116,6 +107,33 @@ Doorkeeper.configure do
116
107
  # client.superapp? or resource_owner.admin?
117
108
  # end
118
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
123
+ #
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.
128
+ #
129
+ # allow_token_introspection do |token, client|
130
+ # if token.application
131
+ # token.application == client || client.protected_resource?
132
+ # else
133
+ # true
134
+ # end
135
+ # end
136
+
119
137
  # WWW-Authenticate Realm (default "Doorkeeper").
120
138
  realm "Doorkeeper"
121
139
  end
@@ -694,4 +694,15 @@ describe Doorkeeper, "configuration" do
694
694
  end
695
695
  end
696
696
  end
697
+
698
+ describe "options deprecation" do
699
+ it "prints a warning message when an option is deprecated" do
700
+ expect(Kernel).to receive(:warn).with(
701
+ "[DOORKEEPER] native_redirect_uri has been deprecated and will soon be removed"
702
+ )
703
+ Doorkeeper.configure do
704
+ native_redirect_uri "urn:ietf:wg:oauth:2.0:oob"
705
+ end
706
+ end
707
+ end
697
708
  end
@@ -40,13 +40,28 @@ module Doorkeeper::OAuth::Helpers
40
40
  expect(URIChecker.valid?(uri)).to be_falsey
41
41
  end
42
42
 
43
+ it "is invalid if localhost is resolved as as scheme (no scheme specified)" do
44
+ uri = "localhost:8080"
45
+ expect(URIChecker.valid?(uri)).to be_falsey
46
+ end
47
+
48
+ it "is invalid if scheme is missing #2" do
49
+ uri = "app.co:80"
50
+ expect(URIChecker.valid?(uri)).to be_falsey
51
+ end
52
+
43
53
  it "is invalid if is not an uri" do
44
54
  uri = " "
45
55
  expect(URIChecker.valid?(uri)).to be_falsey
46
56
  end
47
57
 
48
- it "is valid for native uris" do
49
- uri = "urn:ietf:wg:oauth:2.0:oob"
58
+ it "is valid for custom schemes" do
59
+ uri = "com.example.app:/test"
60
+ expect(URIChecker.valid?(uri)).to be_truthy
61
+ end
62
+
63
+ it "is valid for custom schemes with authority marker (common misconfiguration)" do
64
+ uri = "com.example.app://test"
50
65
  expect(URIChecker.valid?(uri)).to be_truthy
51
66
  end
52
67
  end
@@ -149,21 +149,6 @@ module Doorkeeper::OAuth
149
149
  expect(subject.scopes).to eq(Scopes.from_string("default"))
150
150
  end
151
151
 
152
- context "with native redirect uri" do
153
- let(:native_redirect_uri) { "urn:ietf:wg:oauth:2.0:oob" }
154
-
155
- it "accepts redirect_uri when it matches with the client" do
156
- subject.redirect_uri = native_redirect_uri
157
- allow(subject.client).to receive(:redirect_uri) { native_redirect_uri }
158
- expect(subject).to be_authorizable
159
- end
160
-
161
- it "invalidates redirect_uri when it does'n match with the client" do
162
- subject.redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
163
- expect(subject).not_to be_authorizable
164
- end
165
- end
166
-
167
152
  it "matches the redirect uri against client's one" do
168
153
  subject.redirect_uri = "http://nothesame.com"
169
154
  expect(subject).not_to be_authorizable
@@ -28,8 +28,8 @@ feature "Authorization Code Flow" do
28
28
  config_is_set(:token_secret_strategy, ::Doorkeeper::SecretStoring::Sha256Hash)
29
29
  end
30
30
 
31
- scenario "Authorization Code Flow with hashing" do
32
- @client.redirect_uri = Doorkeeper.configuration.native_redirect_uri
31
+ def authorize(redirect_url)
32
+ @client.redirect_uri = redirect_url
33
33
  @client.save!
34
34
  visit authorization_endpoint_url(client: @client)
35
35
  click_on "Authorize"
@@ -42,16 +42,28 @@ feature "Authorization Code Flow" do
42
42
  hashed_code = Doorkeeper::AccessGrant.secret_strategy.transform_secret code
43
43
  expect(hashed_code).to eq Doorkeeper::AccessGrant.first.token
44
44
 
45
+ [code, hashed_code]
46
+ end
47
+
48
+ scenario "using redirect_url urn:ietf:wg:oauth:2.0:oob" do
49
+ code, hashed_code = authorize("urn:ietf:wg:oauth:2.0:oob")
45
50
  expect(code).not_to eq(hashed_code)
51
+ i_should_see "Authorization code:"
52
+ i_should_see code
53
+ i_should_not_see hashed_code
54
+ end
46
55
 
56
+ scenario "using redirect_url urn:ietf:wg:oauth:2.0:oob:auto" do
57
+ code, hashed_code = authorize("urn:ietf:wg:oauth:2.0:oob:auto")
58
+ expect(code).not_to eq(hashed_code)
47
59
  i_should_see "Authorization code:"
48
60
  i_should_see code
49
61
  i_should_not_see hashed_code
50
62
  end
51
63
  end
52
64
 
53
- scenario "resource owner authorizes using test url" do
54
- @client.redirect_uri = Doorkeeper.configuration.native_redirect_uri
65
+ scenario "resource owner authorizes using oob url" do
66
+ @client.redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
55
67
  @client.save!
56
68
  visit authorization_endpoint_url(client: @client)
57
69
  click_on "Authorize"