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.

Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -3
  3. data/Gemfile +0 -4
  4. data/NEWS.md +20 -1
  5. data/README.md +23 -25
  6. data/app/views/doorkeeper/applications/show.html.erb +1 -1
  7. data/doorkeeper.gemspec +4 -3
  8. data/lib/doorkeeper/config.rb +16 -4
  9. data/lib/doorkeeper/engine.rb +2 -7
  10. data/lib/doorkeeper/models/access_grant_mixin.rb +0 -4
  11. data/lib/doorkeeper/models/access_token_mixin.rb +1 -5
  12. data/lib/doorkeeper/models/application_mixin.rb +0 -4
  13. data/lib/doorkeeper/models/concerns/expirable.rb +2 -2
  14. data/lib/doorkeeper/models/concerns/revocable.rb +19 -2
  15. data/lib/doorkeeper/oauth/client/credentials.rb +1 -1
  16. data/lib/doorkeeper/oauth/client_credentials_request.rb +3 -1
  17. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +1 -1
  18. data/lib/doorkeeper/oauth/refresh_token_request.rb +23 -11
  19. data/lib/doorkeeper/oauth/token.rb +3 -1
  20. data/lib/doorkeeper/version.rb +1 -1
  21. data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +29 -0
  22. data/lib/generators/doorkeeper/templates/add_previous_refresh_token_to_access_tokens.rb +11 -0
  23. data/lib/generators/doorkeeper/templates/initializer.rb +2 -2
  24. data/lib/generators/doorkeeper/templates/migration.rb +10 -2
  25. data/spec/controllers/protected_resources_controller_spec.rb +35 -6
  26. data/spec/dummy/app/models/user.rb +0 -4
  27. data/spec/dummy/config/initializers/doorkeeper.rb +2 -2
  28. data/spec/dummy/db/migrate/20160320211015_add_previous_refresh_token_to_access_tokens.rb +11 -0
  29. data/spec/dummy/db/schema.rb +4 -3
  30. data/spec/lib/config_spec.rb +1 -1
  31. data/spec/lib/models/revocable_spec.rb +27 -4
  32. data/spec/lib/oauth/refresh_token_request_spec.rb +30 -5
  33. data/spec/lib/oauth/scopes_spec.rb +0 -1
  34. data/spec/lib/oauth/token_spec.rb +12 -5
  35. data/spec/models/doorkeeper/access_token_spec.rb +22 -1
  36. data/spec/models/doorkeeper/application_spec.rb +1 -1
  37. data/spec/requests/flows/refresh_token_spec.rb +87 -17
  38. data/spec/support/shared/controllers_shared_context.rb +13 -4
  39. 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
@@ -1,3 +1,3 @@
1
1
  module Doorkeeper
2
- VERSION = "4.0.0.rc2"
2
+ VERSION = "4.0.0.rc3"
3
3
  end
@@ -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
@@ -0,0 +1,11 @@
1
+ class AddPreviousRefreshTokenToAccessTokens < ActiveRecord::Migration
2
+ def change
3
+ add_column(
4
+ :oauth_access_tokens,
5
+ :previous_refresh_token,
6
+ :string,
7
+ default: "",
8
+ null: false
9
+ )
10
+ end
11
+ 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 :confirmation => true (default false) if you want to enforce ownership of
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 :confirmation => false
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, null: false
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, null: false
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, acceptable?: true)
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, accessible?: true, scopes: %w(write public))
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(Doorkeeper::AccessToken).to receive(:by_token).with(token_string).and_return(token)
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, accessible?: true, scopes: ['public'], revoked?: false, expired?: false)
112
- expect(Doorkeeper::AccessToken).to receive(:by_token).with(token_string).and_return(token)
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, expired?: 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
@@ -1,8 +1,4 @@
1
1
  class User < ActiveRecord::Base
2
- if respond_to?(:attr_accessible)
3
- attr_accessible :name, :password
4
- end
5
-
6
2
  def self.authenticate!(name, password)
7
3
  User.where(name: name, password: password).first
8
4
  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 :confirmation => true (default false) if you want to enforce ownership of
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 :confirmation => false
36
+ # enable_application_owner confirmation: false
37
37
 
38
38
  # Define access token scopes for your provider
39
39
  # For more information go to
@@ -0,0 +1,11 @@
1
+ class AddPreviousRefreshTokenToAccessTokens < ActiveRecord::Migration
2
+ def change
3
+ add_column(
4
+ :oauth_access_tokens,
5
+ :previous_refresh_token,
6
+ :string,
7
+ default: "",
8
+ null: false
9
+ )
10
+ end
11
+ end
@@ -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: 20151223200000) do
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", null: false
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", null: false
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
@@ -8,7 +8,7 @@ describe Doorkeeper, 'configuration' do
8
8
  block = proc {}
9
9
  Doorkeeper.configure do
10
10
  orm DOORKEEPER_ORM
11
- resource_owner_authenticator &block
11
+ resource_owner_authenticator(&block)
12
12
  end
13
13
  expect(subject.authenticate_resource_owner).to eq(block)
14
14
  end
@@ -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
- clock = double now: double
15
- expect(subject).to receive(:update_attribute).with(:revoked_at, clock.now)
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 do
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
 
@@ -67,7 +67,6 @@ module Doorkeeper::OAuth
67
67
 
68
68
  it 'does not change the existing object' do
69
69
  origin = Scopes.from_string('public')
70
- new_scope = origin + Scopes.from_string('admin')
71
70
  expect(origin.to_s).to eq('public')
72
71
  end
73
72
 
@@ -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, ->(r) {}, method, not_called_method
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
- let(:finder) { double :finder }
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