doorkeeper 4.0.0.rc2 → 4.0.0.rc3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of doorkeeper might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +3 -3
- data/Gemfile +0 -4
- data/NEWS.md +20 -1
- data/README.md +23 -25
- data/app/views/doorkeeper/applications/show.html.erb +1 -1
- data/doorkeeper.gemspec +4 -3
- data/lib/doorkeeper/config.rb +16 -4
- data/lib/doorkeeper/engine.rb +2 -7
- data/lib/doorkeeper/models/access_grant_mixin.rb +0 -4
- data/lib/doorkeeper/models/access_token_mixin.rb +1 -5
- data/lib/doorkeeper/models/application_mixin.rb +0 -4
- data/lib/doorkeeper/models/concerns/expirable.rb +2 -2
- data/lib/doorkeeper/models/concerns/revocable.rb +19 -2
- data/lib/doorkeeper/oauth/client/credentials.rb +1 -1
- data/lib/doorkeeper/oauth/client_credentials_request.rb +3 -1
- data/lib/doorkeeper/oauth/helpers/scope_checker.rb +1 -1
- data/lib/doorkeeper/oauth/refresh_token_request.rb +23 -11
- data/lib/doorkeeper/oauth/token.rb +3 -1
- data/lib/doorkeeper/version.rb +1 -1
- data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +29 -0
- data/lib/generators/doorkeeper/templates/add_previous_refresh_token_to_access_tokens.rb +11 -0
- data/lib/generators/doorkeeper/templates/initializer.rb +2 -2
- data/lib/generators/doorkeeper/templates/migration.rb +10 -2
- data/spec/controllers/protected_resources_controller_spec.rb +35 -6
- data/spec/dummy/app/models/user.rb +0 -4
- data/spec/dummy/config/initializers/doorkeeper.rb +2 -2
- data/spec/dummy/db/migrate/20160320211015_add_previous_refresh_token_to_access_tokens.rb +11 -0
- data/spec/dummy/db/schema.rb +4 -3
- data/spec/lib/config_spec.rb +1 -1
- data/spec/lib/models/revocable_spec.rb +27 -4
- data/spec/lib/oauth/refresh_token_request_spec.rb +30 -5
- data/spec/lib/oauth/scopes_spec.rb +0 -1
- data/spec/lib/oauth/token_spec.rb +12 -5
- data/spec/models/doorkeeper/access_token_spec.rb +22 -1
- data/spec/models/doorkeeper/application_spec.rb +1 -1
- data/spec/requests/flows/refresh_token_spec.rb +87 -17
- data/spec/support/shared/controllers_shared_context.rb +13 -4
- metadata +40 -22
@@ -55,7 +55,9 @@ module Doorkeeper
|
|
55
55
|
|
56
56
|
def self.authenticate(request, *methods)
|
57
57
|
if token = from_request(request, *methods)
|
58
|
-
AccessToken.by_token(token)
|
58
|
+
access_token = AccessToken.by_token(token)
|
59
|
+
access_token.revoke_previous_refresh_token! if access_token
|
60
|
+
access_token
|
59
61
|
end
|
60
62
|
end
|
61
63
|
end
|
data/lib/doorkeeper/version.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rails/generators/active_record'
|
2
|
+
|
3
|
+
class Doorkeeper::PreviousRefreshTokenGenerator < Rails::Generators::Base
|
4
|
+
include Rails::Generators::Migration
|
5
|
+
source_root File.expand_path('../templates', __FILE__)
|
6
|
+
desc 'Support revoke refresh token on access token use'
|
7
|
+
|
8
|
+
def self.next_migration_number(path)
|
9
|
+
ActiveRecord::Generators::Base.next_migration_number(path)
|
10
|
+
end
|
11
|
+
|
12
|
+
def previous_refresh_token
|
13
|
+
if no_previous_refresh_token_column?
|
14
|
+
migration_template(
|
15
|
+
'add_previous_refresh_token_to_access_tokens.rb',
|
16
|
+
'db/migrate/add_previous_refresh_token_to_access_tokens.rb'
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def no_previous_refresh_token_column?
|
24
|
+
!ActiveRecord::Base.connection.column_exists?(
|
25
|
+
:oauth_access_tokens,
|
26
|
+
:previous_refresh_token
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
@@ -41,10 +41,10 @@ Doorkeeper.configure do
|
|
41
41
|
# use_refresh_token
|
42
42
|
|
43
43
|
# Provide support for an owner to be assigned to each registered application (disabled by default)
|
44
|
-
# Optional parameter :
|
44
|
+
# Optional parameter confirmation: true (default false) if you want to enforce ownership of
|
45
45
|
# a registered application
|
46
46
|
# Note: you must also run the rails g doorkeeper:application_owner generator to provide the necessary support
|
47
|
-
# enable_application_owner :
|
47
|
+
# enable_application_owner confirmation: false
|
48
48
|
|
49
49
|
# Define access token scopes for your provider
|
50
50
|
# For more information go to
|
@@ -39,13 +39,21 @@ class CreateDoorkeeperTables < ActiveRecord::Migration
|
|
39
39
|
# https://github.com/doorkeeper-gem/doorkeeper/tree/v3.0.0.rc1#custom-access-token-generator
|
40
40
|
#
|
41
41
|
# t.text :token, null: false
|
42
|
-
t.string :token,
|
42
|
+
t.string :token, null: false
|
43
43
|
|
44
44
|
t.string :refresh_token
|
45
45
|
t.integer :expires_in
|
46
46
|
t.datetime :revoked_at
|
47
|
-
t.datetime :created_at,
|
47
|
+
t.datetime :created_at, null: false
|
48
48
|
t.string :scopes
|
49
|
+
|
50
|
+
# If there is a previous_refresh_token column,
|
51
|
+
# refresh tokens will be revoked after a related access token is used.
|
52
|
+
# If there is no previous_refresh_token column,
|
53
|
+
# previous tokens are revoked as soon as a new access token is created.
|
54
|
+
# Comment out this line if you'd rather have refresh tokens
|
55
|
+
# instantly revoked.
|
56
|
+
t.string :previous_refresh_token, null: false, default: ""
|
49
57
|
end
|
50
58
|
|
51
59
|
add_index :oauth_access_tokens, :token, unique: true
|
@@ -8,6 +8,12 @@ module ControllerActions
|
|
8
8
|
def show
|
9
9
|
render text: 'show'
|
10
10
|
end
|
11
|
+
|
12
|
+
def doorkeeper_unauthorized_render_options(*)
|
13
|
+
end
|
14
|
+
|
15
|
+
def doorkeeper_forbidden_render_options(*)
|
16
|
+
end
|
11
17
|
end
|
12
18
|
|
13
19
|
describe 'doorkeeper authorize filter' do
|
@@ -22,7 +28,9 @@ describe 'doorkeeper authorize filter' do
|
|
22
28
|
|
23
29
|
let(:token_string) { '1A2BC3' }
|
24
30
|
let(:token) do
|
25
|
-
double(Doorkeeper::AccessToken,
|
31
|
+
double(Doorkeeper::AccessToken,
|
32
|
+
acceptable?: true, previous_refresh_token: "",
|
33
|
+
revoke_previous_refresh_token!: true)
|
26
34
|
end
|
27
35
|
|
28
36
|
it 'access_token param' do
|
@@ -100,16 +108,26 @@ describe 'doorkeeper authorize filter' do
|
|
100
108
|
let(:token_string) { '1A2DUWE' }
|
101
109
|
|
102
110
|
it 'allows if the token has particular scopes' do
|
103
|
-
token = double(Doorkeeper::AccessToken,
|
111
|
+
token = double(Doorkeeper::AccessToken,
|
112
|
+
accessible?: true, scopes: %w(write public),
|
113
|
+
previous_refresh_token: "",
|
114
|
+
revoke_previous_refresh_token!: true)
|
104
115
|
expect(token).to receive(:acceptable?).with([:write]).and_return(true)
|
105
|
-
expect(
|
116
|
+
expect(
|
117
|
+
Doorkeeper::AccessToken
|
118
|
+
).to receive(:by_token).with(token_string).and_return(token)
|
106
119
|
get :index, access_token: token_string
|
107
120
|
expect(response).to be_success
|
108
121
|
end
|
109
122
|
|
110
123
|
it 'does not allow if the token does not include given scope' do
|
111
|
-
token = double(Doorkeeper::AccessToken,
|
112
|
-
|
124
|
+
token = double(Doorkeeper::AccessToken,
|
125
|
+
accessible?: true, scopes: ['public'], revoked?: false,
|
126
|
+
expired?: false, previous_refresh_token: "",
|
127
|
+
revoke_previous_refresh_token!: true)
|
128
|
+
expect(
|
129
|
+
Doorkeeper::AccessToken
|
130
|
+
).to receive(:by_token).with(token_string).and_return(token)
|
113
131
|
expect(token).to receive(:acceptable?).with([:write]).and_return(false)
|
114
132
|
get :index, access_token: token_string
|
115
133
|
expect(response.status).to eq 403
|
@@ -127,6 +145,7 @@ describe 'doorkeeper authorize filter' do
|
|
127
145
|
context 'with a JSON custom render', token: :invalid do
|
128
146
|
before do
|
129
147
|
module ControllerActions
|
148
|
+
remove_method :doorkeeper_unauthorized_render_options
|
130
149
|
def doorkeeper_unauthorized_render_options(error: nil)
|
131
150
|
{ json: ActiveSupport::JSON.encode(error_message: error.description) }
|
132
151
|
end
|
@@ -134,6 +153,7 @@ describe 'doorkeeper authorize filter' do
|
|
134
153
|
end
|
135
154
|
after do
|
136
155
|
module ControllerActions
|
156
|
+
remove_method :doorkeeper_unauthorized_render_options
|
137
157
|
def doorkeeper_unauthorized_render_options(error: nil)
|
138
158
|
end
|
139
159
|
end
|
@@ -153,6 +173,7 @@ describe 'doorkeeper authorize filter' do
|
|
153
173
|
context 'with a text custom render', token: :invalid do
|
154
174
|
before do
|
155
175
|
module ControllerActions
|
176
|
+
remove_method :doorkeeper_unauthorized_render_options
|
156
177
|
def doorkeeper_unauthorized_render_options(error: nil)
|
157
178
|
{ text: 'Unauthorized' }
|
158
179
|
end
|
@@ -160,6 +181,7 @@ describe 'doorkeeper authorize filter' do
|
|
160
181
|
end
|
161
182
|
after do
|
162
183
|
module ControllerActions
|
184
|
+
remove_method :doorkeeper_unauthorized_render_options
|
163
185
|
def doorkeeper_unauthorized_render_options(error: nil)
|
164
186
|
end
|
165
187
|
end
|
@@ -183,6 +205,7 @@ describe 'doorkeeper authorize filter' do
|
|
183
205
|
|
184
206
|
after do
|
185
207
|
module ControllerActions
|
208
|
+
remove_method :doorkeeper_forbidden_render_options
|
186
209
|
def doorkeeper_forbidden_render_options(*)
|
187
210
|
end
|
188
211
|
end
|
@@ -196,13 +219,16 @@ describe 'doorkeeper authorize filter' do
|
|
196
219
|
|
197
220
|
let(:token) do
|
198
221
|
double(Doorkeeper::AccessToken,
|
199
|
-
accessible?: true, scopes: ['public'], revoked?: false,
|
222
|
+
accessible?: true, scopes: ['public'], revoked?: false,
|
223
|
+
expired?: false, previous_refresh_token: "",
|
224
|
+
revoke_previous_refresh_token!: true)
|
200
225
|
end
|
201
226
|
let(:token_string) { '1A2DUWE' }
|
202
227
|
|
203
228
|
context 'with a JSON custom render' do
|
204
229
|
before do
|
205
230
|
module ControllerActions
|
231
|
+
remove_method :doorkeeper_forbidden_render_options
|
206
232
|
def doorkeeper_forbidden_render_options(*)
|
207
233
|
{ json: { error_message: 'Forbidden' } }
|
208
234
|
end
|
@@ -223,6 +249,7 @@ describe 'doorkeeper authorize filter' do
|
|
223
249
|
context 'with a status and JSON custom render' do
|
224
250
|
before do
|
225
251
|
module ControllerActions
|
252
|
+
remove_method :doorkeeper_forbidden_render_options
|
226
253
|
def doorkeeper_forbidden_render_options(*)
|
227
254
|
{ json: { error_message: 'Not Found' },
|
228
255
|
respond_not_found_when_forbidden: true }
|
@@ -239,6 +266,7 @@ describe 'doorkeeper authorize filter' do
|
|
239
266
|
context 'with a text custom render' do
|
240
267
|
before do
|
241
268
|
module ControllerActions
|
269
|
+
remove_method :doorkeeper_forbidden_render_options
|
242
270
|
def doorkeeper_forbidden_render_options(*)
|
243
271
|
{ text: 'Forbidden' }
|
244
272
|
end
|
@@ -256,6 +284,7 @@ describe 'doorkeeper authorize filter' do
|
|
256
284
|
context 'with a status and text custom render' do
|
257
285
|
before do
|
258
286
|
module ControllerActions
|
287
|
+
remove_method :doorkeeper_forbidden_render_options
|
259
288
|
def doorkeeper_forbidden_render_options(*)
|
260
289
|
{ respond_not_found_when_forbidden: true, text: 'Not Found' }
|
261
290
|
end
|
@@ -30,10 +30,10 @@ Doorkeeper.configure do
|
|
30
30
|
use_refresh_token
|
31
31
|
|
32
32
|
# Provide support for an owner to be assigned to each registered application (disabled by default)
|
33
|
-
# Optional parameter :
|
33
|
+
# Optional parameter confirmation: true (default false) if you want to enforce ownership of
|
34
34
|
# a registered application
|
35
35
|
# Note: you must also run the rails g doorkeeper:application_owner generator to provide the necessary support
|
36
|
-
# enable_application_owner :
|
36
|
+
# enable_application_owner confirmation: false
|
37
37
|
|
38
38
|
# Define access token scopes for your provider
|
39
39
|
# For more information go to
|
data/spec/dummy/db/schema.rb
CHANGED
@@ -11,7 +11,7 @@
|
|
11
11
|
#
|
12
12
|
# It's strongly recommended that you check this file into your version control system.
|
13
13
|
|
14
|
-
ActiveRecord::Schema.define(version:
|
14
|
+
ActiveRecord::Schema.define(version: 20160320211015) do
|
15
15
|
|
16
16
|
create_table "oauth_access_grants", force: :cascade do |t|
|
17
17
|
t.integer "resource_owner_id", null: false
|
@@ -29,12 +29,13 @@ ActiveRecord::Schema.define(version: 20151223200000) do
|
|
29
29
|
create_table "oauth_access_tokens", force: :cascade do |t|
|
30
30
|
t.integer "resource_owner_id"
|
31
31
|
t.integer "application_id"
|
32
|
-
t.string "token",
|
32
|
+
t.string "token", null: false
|
33
33
|
t.string "refresh_token"
|
34
34
|
t.integer "expires_in"
|
35
35
|
t.datetime "revoked_at"
|
36
|
-
t.datetime "created_at",
|
36
|
+
t.datetime "created_at", null: false
|
37
37
|
t.string "scopes"
|
38
|
+
t.string "previous_refresh_token", default: "", null: false
|
38
39
|
end
|
39
40
|
|
40
41
|
add_index "oauth_access_tokens", ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true
|
data/spec/lib/config_spec.rb
CHANGED
@@ -11,20 +11,21 @@ describe 'Revocable' do
|
|
11
11
|
|
12
12
|
describe :revoke do
|
13
13
|
it 'updates :revoked_at attribute with current time' do
|
14
|
-
|
15
|
-
|
14
|
+
utc = double utc: double
|
15
|
+
clock = double now: utc
|
16
|
+
expect(subject).to receive(:update_attribute).with(:revoked_at, clock.now.utc)
|
16
17
|
subject.revoke(clock)
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
20
21
|
describe :revoked? do
|
21
22
|
it 'is revoked if :revoked_at has passed' do
|
22
|
-
allow(subject).to receive(:revoked_at).and_return(Time.now - 1000)
|
23
|
+
allow(subject).to receive(:revoked_at).and_return(Time.now.utc - 1000)
|
23
24
|
expect(subject).to be_revoked
|
24
25
|
end
|
25
26
|
|
26
27
|
it 'is not revoked if :revoked_at has not passed' do
|
27
|
-
allow(subject).to receive(:revoked_at).and_return(Time.now + 1000)
|
28
|
+
allow(subject).to receive(:revoked_at).and_return(Time.now.utc + 1000)
|
28
29
|
expect(subject).not_to be_revoked
|
29
30
|
end
|
30
31
|
|
@@ -33,4 +34,26 @@ describe 'Revocable' do
|
|
33
34
|
expect(subject).not_to be_revoked
|
34
35
|
end
|
35
36
|
end
|
37
|
+
|
38
|
+
describe :revoke_previous_refresh_token! do
|
39
|
+
it "revokes the previous token if existing, and resets the
|
40
|
+
`previous_refresh_token` attribute" do
|
41
|
+
previous_token = FactoryGirl.create(
|
42
|
+
:access_token,
|
43
|
+
refresh_token: "refresh_token"
|
44
|
+
)
|
45
|
+
current_token = FactoryGirl.create(
|
46
|
+
:access_token,
|
47
|
+
previous_refresh_token: previous_token.refresh_token
|
48
|
+
)
|
49
|
+
|
50
|
+
expect_any_instance_of(
|
51
|
+
Doorkeeper::AccessToken
|
52
|
+
).to receive(:revoke).and_call_original
|
53
|
+
current_token.revoke_previous_refresh_token!
|
54
|
+
|
55
|
+
expect(current_token.previous_refresh_token).to be_empty
|
56
|
+
expect(previous_token.reload).to be_revoked
|
57
|
+
end
|
58
|
+
end
|
36
59
|
end
|
@@ -5,7 +5,8 @@ module Doorkeeper::OAuth
|
|
5
5
|
let(:server) do
|
6
6
|
double :server,
|
7
7
|
access_token_expires_in: 2.minutes,
|
8
|
-
custom_access_token_expires_in: -> (_oauth_client) { nil }
|
8
|
+
custom_access_token_expires_in: -> (_oauth_client) { nil },
|
9
|
+
refresh_token_revoked_on_use?: false
|
9
10
|
end
|
10
11
|
let(:refresh_token) do
|
11
12
|
FactoryGirl.create(:access_token, use_refresh_token: true)
|
@@ -16,16 +17,15 @@ module Doorkeeper::OAuth
|
|
16
17
|
subject { RefreshTokenRequest.new server, refresh_token, credentials }
|
17
18
|
|
18
19
|
it 'issues a new token for the client' do
|
19
|
-
expect
|
20
|
-
subject.authorize
|
21
|
-
end.to change { client.access_tokens.count }.by(1)
|
20
|
+
expect { subject.authorize }.to change { client.access_tokens.count }.by(1)
|
22
21
|
expect(client.reload.access_tokens.last.expires_in).to eq(120)
|
23
22
|
end
|
24
23
|
|
25
24
|
it 'issues a new token for the client with custom expires_in' do
|
26
25
|
server = double :server,
|
27
26
|
access_token_expires_in: 2.minutes,
|
28
|
-
custom_access_token_expires_in: ->(_oauth_client) { 1234 }
|
27
|
+
custom_access_token_expires_in: ->(_oauth_client) { 1234 },
|
28
|
+
refresh_token_revoked_on_use?: false
|
29
29
|
|
30
30
|
RefreshTokenRequest.new(server, refresh_token, credentials).authorize
|
31
31
|
|
@@ -67,6 +67,31 @@ module Doorkeeper::OAuth
|
|
67
67
|
expect(subject).to be_valid
|
68
68
|
end
|
69
69
|
|
70
|
+
context 'refresh tokens expire on access token use' do
|
71
|
+
let(:server) do
|
72
|
+
double :server,
|
73
|
+
access_token_expires_in: 2.minutes,
|
74
|
+
custom_access_token_expires_in: ->(_oauth_client) { 1234 },
|
75
|
+
refresh_token_revoked_on_use?: true
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'issues a new token for the client' do
|
79
|
+
expect { subject.authorize }.to change { client.access_tokens.count }.by(1)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'does not revoke the previous token' do
|
83
|
+
subject.authorize
|
84
|
+
expect(refresh_token).not_to be_revoked
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'sets the previous refresh token in the new access token' do
|
88
|
+
subject.authorize
|
89
|
+
expect(
|
90
|
+
client.access_tokens.last.previous_refresh_token
|
91
|
+
).to eq(refresh_token.refresh_token)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
70
95
|
context 'clientless access tokens' do
|
71
96
|
let!(:refresh_token) { FactoryGirl.create(:clientless_access_token, use_refresh_token: true) }
|
72
97
|
|
@@ -30,7 +30,7 @@ module Doorkeeper
|
|
30
30
|
it 'stops at the first credentials found' do
|
31
31
|
not_called_method = double
|
32
32
|
expect(not_called_method).not_to receive(:call)
|
33
|
-
Token.from_request request, ->(
|
33
|
+
Token.from_request request, ->(_r) {}, method, not_called_method
|
34
34
|
end
|
35
35
|
|
36
36
|
it 'returns the credential from extractor method' do
|
@@ -96,13 +96,20 @@ module Doorkeeper
|
|
96
96
|
end
|
97
97
|
|
98
98
|
describe :authenticate do
|
99
|
-
|
100
|
-
|
101
|
-
it 'calls the finder if token was found' do
|
102
|
-
token = ->(r) { 'token' }
|
99
|
+
it 'calls the finder if token was returned' do
|
100
|
+
token = ->(_r) { 'token' }
|
103
101
|
expect(AccessToken).to receive(:by_token).with('token')
|
104
102
|
Token.authenticate double, token
|
105
103
|
end
|
104
|
+
|
105
|
+
it 'revokes previous refresh_token if token was found' do
|
106
|
+
token = ->(_r) { 'token' }
|
107
|
+
expect(
|
108
|
+
AccessToken
|
109
|
+
).to receive(:by_token).with('token').and_return(token)
|
110
|
+
expect(token).to receive(:revoke_previous_refresh_token!)
|
111
|
+
Token.authenticate double, token
|
112
|
+
end
|
106
113
|
end
|
107
114
|
end
|
108
115
|
end
|