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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +812 -0
- data/CONTRIBUTING.md +4 -9
- data/Dangerfile +1 -1
- data/Gemfile +2 -1
- data/NEWS.md +1 -814
- data/README.md +2 -2
- data/RELEASING.md +6 -5
- data/app/controllers/doorkeeper/applications_controller.rb +2 -0
- data/app/controllers/doorkeeper/tokens_controller.rb +18 -8
- data/app/validators/redirect_uri_validator.rb +19 -9
- data/app/views/doorkeeper/applications/_form.html.erb +0 -6
- data/app/views/doorkeeper/applications/show.html.erb +1 -1
- data/config/locales/en.yml +3 -1
- data/doorkeeper.gemspec +1 -1
- data/gemfiles/rails_5_0.gemfile +1 -0
- data/gemfiles/rails_5_1.gemfile +1 -0
- data/gemfiles/rails_5_2.gemfile +1 -0
- data/gemfiles/rails_6_0.gemfile +2 -1
- data/gemfiles/rails_master.gemfile +1 -0
- data/lib/doorkeeper/config/option.rb +13 -7
- data/lib/doorkeeper/config.rb +30 -3
- data/lib/doorkeeper/grape/helpers.rb +5 -1
- data/lib/doorkeeper/helpers/controller.rb +16 -3
- data/lib/doorkeeper/oauth/authorization/code.rb +10 -8
- data/lib/doorkeeper/oauth/authorization/token.rb +1 -1
- data/lib/doorkeeper/oauth/code_response.rb +2 -2
- data/lib/doorkeeper/oauth/error_response.rb +1 -1
- data/lib/doorkeeper/oauth/helpers/uri_checker.rb +18 -4
- data/lib/doorkeeper/oauth/nonstandard.rb +39 -0
- data/lib/doorkeeper/oauth/refresh_token_request.rb +8 -8
- data/lib/doorkeeper/oauth/token_introspection.rb +13 -12
- data/lib/doorkeeper/orm/active_record/access_grant.rb +1 -1
- data/lib/doorkeeper/orm/active_record/access_token.rb +2 -2
- data/lib/doorkeeper/orm/active_record/application.rb +7 -1
- data/lib/doorkeeper/orm/active_record.rb +17 -1
- data/lib/doorkeeper/stale_records_cleaner.rb +6 -2
- data/lib/doorkeeper/version.rb +2 -2
- data/lib/doorkeeper.rb +3 -0
- data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +6 -6
- data/lib/generators/doorkeeper/templates/initializer.rb +41 -9
- data/lib/generators/doorkeeper/templates/migration.rb.erb +3 -0
- data/spec/controllers/applications_controller_spec.rb +93 -0
- data/spec/controllers/tokens_controller_spec.rb +71 -3
- data/spec/dummy/config/application.rb +3 -1
- data/spec/dummy/config/initializers/doorkeeper.rb +27 -9
- data/spec/lib/config_spec.rb +11 -0
- data/spec/lib/oauth/helpers/uri_checker_spec.rb +17 -2
- data/spec/lib/oauth/pre_authorization_spec.rb +0 -15
- data/spec/requests/flows/authorization_code_spec.rb +16 -4
- data/spec/requests/flows/revoke_token_spec.rb +19 -11
- data/spec/support/doorkeeper_rspec.rb +1 -1
- data/spec/validators/redirect_uri_validator_spec.rb +39 -14
- metadata +6 -13
- data/.coveralls.yml +0 -1
- data/.github/ISSUE_TEMPLATE.md +0 -25
- data/.github/PULL_REQUEST_TEMPLATE.md +0 -17
- data/.gitignore +0 -20
- data/.gitlab-ci.yml +0 -16
- data/.hound.yml +0 -3
- data/.rspec +0 -1
- data/.rubocop.yml +0 -50
- data/.travis.yml +0 -35
|
@@ -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
|
-
|
|
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/#{
|
|
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, "'#{
|
|
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)
|
data/lib/doorkeeper/version.rb
CHANGED
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
105
|
+
it "returns 403" do
|
|
106
106
|
post :revoke, params: { token: access_token.token }
|
|
107
107
|
|
|
108
|
-
expect(response.status).to eq
|
|
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
|
|
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
|
data/spec/lib/config_spec.rb
CHANGED
|
@@ -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
|
|
49
|
-
uri = "
|
|
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
|
-
|
|
32
|
-
@client.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
|
|
54
|
-
@client.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"
|