doorkeeper 3.1.0 → 4.0.0

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.

Potentially problematic release.


This version of doorkeeper might be problematic. Click here for more details.

Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +8 -10
  3. data/CONTRIBUTING.md +2 -0
  4. data/Gemfile +8 -4
  5. data/NEWS.md +57 -2
  6. data/README.md +48 -40
  7. data/Rakefile +1 -1
  8. data/app/controllers/doorkeeper/application_metal_controller.rb +1 -2
  9. data/app/controllers/doorkeeper/applications_controller.rb +2 -2
  10. data/app/controllers/doorkeeper/authorizations_controller.rb +1 -1
  11. data/app/controllers/doorkeeper/authorized_applications_controller.rb +1 -1
  12. data/app/controllers/doorkeeper/tokens_controller.rb +1 -1
  13. data/app/helpers/doorkeeper/dashboard_helper.rb +13 -11
  14. data/app/views/doorkeeper/applications/show.html.erb +1 -1
  15. data/app/views/layouts/doorkeeper/admin.html.erb +1 -1
  16. data/config/locales/en.yml +1 -0
  17. data/doorkeeper.gemspec +7 -6
  18. data/lib/doorkeeper/config.rb +10 -15
  19. data/lib/doorkeeper/engine.rb +11 -7
  20. data/lib/doorkeeper/helpers/controller.rb +1 -1
  21. data/lib/doorkeeper/models/access_grant_mixin.rb +9 -5
  22. data/lib/doorkeeper/models/access_token_mixin.rb +28 -22
  23. data/lib/doorkeeper/models/application_mixin.rb +3 -7
  24. data/lib/doorkeeper/models/concerns/expirable.rb +2 -2
  25. data/lib/doorkeeper/models/concerns/ownership.rb +6 -1
  26. data/lib/doorkeeper/models/concerns/revocable.rb +19 -2
  27. data/lib/doorkeeper/oauth/authorization/uri_builder.rb +1 -1
  28. data/lib/doorkeeper/oauth/client/credentials.rb +1 -1
  29. data/lib/doorkeeper/oauth/client_credentials/issuer.rb +2 -1
  30. data/lib/doorkeeper/oauth/client_credentials_request.rb +7 -4
  31. data/lib/doorkeeper/oauth/code_response.rb +13 -14
  32. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +1 -1
  33. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +2 -1
  34. data/lib/doorkeeper/oauth/password_access_token_request.rb +6 -10
  35. data/lib/doorkeeper/oauth/refresh_token_request.rb +23 -11
  36. data/lib/doorkeeper/oauth/scopes.rb +2 -2
  37. data/lib/doorkeeper/oauth/token.rb +6 -5
  38. data/lib/doorkeeper/oauth/token_response.rb +1 -1
  39. data/lib/doorkeeper/orm/active_record/access_token.rb +8 -0
  40. data/lib/doorkeeper/orm/active_record/application.rb +2 -7
  41. data/lib/doorkeeper/orm/active_record.rb +0 -16
  42. data/lib/doorkeeper/rails/helpers.rb +1 -1
  43. data/lib/doorkeeper/rails/routes/mapper.rb +1 -1
  44. data/lib/doorkeeper/rails/routes.rb +2 -1
  45. data/lib/doorkeeper/request/password.rb +11 -1
  46. data/lib/doorkeeper/version.rb +1 -1
  47. data/lib/doorkeeper.rb +1 -1
  48. data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +29 -0
  49. data/lib/generators/doorkeeper/templates/add_owner_to_application_migration.rb +1 -1
  50. data/lib/generators/doorkeeper/templates/add_previous_refresh_token_to_access_tokens.rb +11 -0
  51. data/lib/generators/doorkeeper/templates/initializer.rb +2 -2
  52. data/lib/generators/doorkeeper/templates/migration.rb +23 -5
  53. data/spec/controllers/authorizations_controller_spec.rb +0 -14
  54. data/spec/controllers/protected_resources_controller_spec.rb +47 -18
  55. data/spec/dummy/app/controllers/full_protected_resources_controller.rb +4 -4
  56. data/spec/dummy/app/controllers/home_controller.rb +1 -1
  57. data/spec/dummy/app/controllers/metal_controller.rb +1 -1
  58. data/spec/dummy/app/controllers/semi_protected_resources_controller.rb +3 -3
  59. data/spec/dummy/app/models/user.rb +0 -4
  60. data/spec/dummy/config/application.rb +2 -36
  61. data/spec/dummy/config/environment.rb +1 -1
  62. data/spec/dummy/config/environments/test.rb +4 -15
  63. data/spec/dummy/config/initializers/active_record_belongs_to_required_by_default.rb +6 -0
  64. data/spec/dummy/config/initializers/doorkeeper.rb +2 -2
  65. data/spec/dummy/db/migrate/{20130902165751_create_doorkeeper_tables.rb → 20151223192035_create_doorkeeper_tables.rb} +24 -5
  66. data/spec/dummy/db/migrate/{20130902175349_add_owner_to_application.rb → 20151223200000_add_owner_to_application.rb} +0 -0
  67. data/spec/dummy/db/migrate/20160320211015_add_previous_refresh_token_to_access_tokens.rb +11 -0
  68. data/spec/dummy/db/schema.rb +23 -22
  69. data/spec/lib/config_spec.rb +2 -2
  70. data/spec/lib/models/revocable_spec.rb +27 -4
  71. data/spec/lib/oauth/authorization_code_request_spec.rb +1 -1
  72. data/spec/lib/oauth/code_response_spec.rb +34 -0
  73. data/spec/lib/oauth/password_access_token_request_spec.rb +5 -5
  74. data/spec/lib/oauth/refresh_token_request_spec.rb +34 -3
  75. data/spec/lib/oauth/scopes_spec.rb +0 -1
  76. data/spec/lib/oauth/token_spec.rb +12 -5
  77. data/spec/models/doorkeeper/access_token_spec.rb +45 -1
  78. data/spec/models/doorkeeper/application_spec.rb +2 -10
  79. data/spec/requests/flows/password_spec.rb +26 -5
  80. data/spec/requests/flows/refresh_token_spec.rb +87 -17
  81. data/spec/spec_helper_integration.rb +3 -0
  82. data/spec/support/helpers/model_helper.rb +27 -5
  83. data/spec/support/http_method_shim.rb +24 -0
  84. data/spec/support/shared/controllers_shared_context.rb +13 -4
  85. data/spec/support/shared/models_shared_examples.rb +1 -1
  86. metadata +52 -32
  87. data/lib/generators/doorkeeper/application_scopes_generator.rb +0 -34
  88. data/lib/generators/doorkeeper/templates/add_scopes_to_oauth_applications.rb +0 -5
  89. data/spec/dummy/db/migrate/20141209001746_add_scopes_to_oauth_applications.rb +0 -5
@@ -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
@@ -18,7 +18,7 @@ module Doorkeeper::OAuth
18
18
  it 'issues a new token for the client' do
19
19
  expect do
20
20
  subject.authorize
21
- end.to change { client.access_tokens.count }.by(1)
21
+ end.to change { client.reload.access_tokens.count }.by(1)
22
22
  end
23
23
 
24
24
  it "issues the token with same grant's scopes" do
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ module Doorkeeper
4
+ module OAuth
5
+ describe CodeResponse do
6
+ describe '.redirect_uri' do
7
+ context 'when generating the redirect URI for an implicit grant' do
8
+ let :pre_auth do
9
+ double(
10
+ :pre_auth,
11
+ client: double(:application, id: 1),
12
+ redirect_uri: 'http://tst.com/cb',
13
+ state: nil,
14
+ scopes: Scopes.from_string('public'),
15
+ )
16
+ end
17
+
18
+ let :auth do
19
+ Authorization::Token.new(pre_auth, double(id: 1)).tap do |c|
20
+ c.issue_token
21
+ allow(c.token).to receive(:expires_in_seconds).and_return(3600)
22
+ end
23
+ end
24
+
25
+ subject { CodeResponse.new(pre_auth, auth, response_on_fragment: true).redirect_uri }
26
+
27
+ it 'includes the remaining TTL of the token relative to the time the token was generated' do
28
+ expect(subject).to include('expires_in=3600')
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -11,23 +11,22 @@ module Doorkeeper::OAuth
11
11
  custom_access_token_expires_in: ->(_app) { nil }
12
12
  )
13
13
  end
14
- let(:credentials) { Client::Credentials.new(client.uid, client.secret) }
15
14
  let(:client) { FactoryGirl.create(:application) }
16
15
  let(:owner) { double :owner, id: 99 }
17
16
 
18
17
  subject do
19
- PasswordAccessTokenRequest.new(server, credentials, owner)
18
+ PasswordAccessTokenRequest.new(server, client, owner)
20
19
  end
21
20
 
22
21
  it 'issues a new token for the client' do
23
22
  expect do
24
23
  subject.authorize
25
- end.to change { client.access_tokens.count }.by(1)
24
+ end.to change { client.reload.access_tokens.count }.by(1)
26
25
  end
27
26
 
28
27
  it 'issues a new token without a client' do
29
28
  expect do
30
- subject.credentials = nil
29
+ subject.client = nil
31
30
  subject.authorize
32
31
  end.to change { Doorkeeper::AccessToken.count }.by(1)
33
32
  end
@@ -35,6 +34,7 @@ module Doorkeeper::OAuth
35
34
  it 'does not issue a new token with an invalid client' do
36
35
  expect do
37
36
  subject.client = nil
37
+ subject.parameters = { client_id: 'bad_id' }
38
38
  subject.authorize
39
39
  end.to_not change { Doorkeeper::AccessToken.count }
40
40
 
@@ -48,7 +48,7 @@ module Doorkeeper::OAuth
48
48
  end
49
49
 
50
50
  it 'optionally accepts the client' do
51
- subject.credentials = nil
51
+ subject.client = nil
52
52
  expect(subject).to be_valid
53
53
  end
54
54
 
@@ -2,6 +2,9 @@ require 'spec_helper_integration'
2
2
 
3
3
  module Doorkeeper::OAuth
4
4
  describe RefreshTokenRequest do
5
+ before do
6
+ allow(Doorkeeper::AccessToken).to receive(:refresh_token_revoked_on_use?).and_return(false)
7
+ end
5
8
  let(:server) do
6
9
  double :server,
7
10
  access_token_expires_in: 2.minutes,
@@ -16,9 +19,7 @@ module Doorkeeper::OAuth
16
19
  subject { RefreshTokenRequest.new server, refresh_token, credentials }
17
20
 
18
21
  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)
22
+ expect { subject.authorize }.to change { client.reload.access_tokens.count }.by(1)
22
23
  expect(client.reload.access_tokens.last.expires_in).to eq(120)
23
24
  end
24
25
 
@@ -27,6 +28,8 @@ module Doorkeeper::OAuth
27
28
  access_token_expires_in: 2.minutes,
28
29
  custom_access_token_expires_in: ->(_oauth_client) { 1234 }
29
30
 
31
+ allow(Doorkeeper::AccessToken).to receive(:refresh_token_revoked_on_use?).and_return(false)
32
+
30
33
  RefreshTokenRequest.new(server, refresh_token, credentials).authorize
31
34
 
32
35
  expect(client.reload.access_tokens.last.expires_in).to eq(1234)
@@ -67,6 +70,34 @@ module Doorkeeper::OAuth
67
70
  expect(subject).to be_valid
68
71
  end
69
72
 
73
+ context 'refresh tokens expire on access token use' do
74
+ let(:server) do
75
+ double :server,
76
+ access_token_expires_in: 2.minutes,
77
+ custom_access_token_expires_in: ->(_oauth_client) { 1234 }
78
+ end
79
+
80
+ before do
81
+ allow(Doorkeeper::AccessToken).to receive(:refresh_token_revoked_on_use?).and_return(true)
82
+ end
83
+
84
+ it 'issues a new token for the client' do
85
+ expect { subject.authorize }.to change { client.reload.access_tokens.count }.by(1)
86
+ end
87
+
88
+ it 'does not revoke the previous token' do
89
+ subject.authorize
90
+ expect(refresh_token).not_to be_revoked
91
+ end
92
+
93
+ it 'sets the previous refresh token in the new access token' do
94
+ subject.authorize
95
+ expect(
96
+ client.access_tokens.last.previous_refresh_token
97
+ ).to eq(refresh_token.refresh_token)
98
+ end
99
+ end
100
+
70
101
  context 'clientless access tokens' do
71
102
  let!(:refresh_token) { FactoryGirl.create(:clientless_access_token, use_refresh_token: true) }
72
103
 
@@ -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
@@ -12,6 +12,11 @@ module Doorkeeper
12
12
  let(:factory_name) { :access_token }
13
13
  end
14
14
 
15
+ module CustomGeneratorArgs
16
+ def self.generate
17
+ end
18
+ end
19
+
15
20
  describe :generate_token do
16
21
  it 'generates a token using the default method' do
17
22
  FactoryGirl.create :access_token
@@ -21,6 +26,10 @@ module Doorkeeper
21
26
  end
22
27
 
23
28
  it 'generates a token using a custom object' do
29
+ eigenclass = class << CustomGeneratorArgs; self; end
30
+ eigenclass.class_eval do
31
+ remove_method :generate
32
+ end
24
33
  module CustomGeneratorArgs
25
34
  def self.generate(opts = {})
26
35
  "custom_generator_token_#{opts[:resource_owner_id]}"
@@ -37,6 +46,10 @@ module Doorkeeper
37
46
  end
38
47
 
39
48
  it 'allows the custom generator to access the application details' do
49
+ eigenclass = class << CustomGeneratorArgs; self; end
50
+ eigenclass.class_eval do
51
+ remove_method :generate
52
+ end
40
53
  module CustomGeneratorArgs
41
54
  def self.generate(opts = {})
42
55
  "custom_generator_token_#{opts[:application].name}"
@@ -53,6 +66,10 @@ module Doorkeeper
53
66
  end
54
67
 
55
68
  it 'allows the custom generator to access the scopes' do
69
+ eigenclass = class << CustomGeneratorArgs; self; end
70
+ eigenclass.class_eval do
71
+ remove_method :generate
72
+ end
56
73
  module CustomGeneratorArgs
57
74
  def self.generate(opts = {})
58
75
  "custom_generator_token_#{opts[:scopes].count}_#{opts[:scopes]}"
@@ -70,6 +87,10 @@ module Doorkeeper
70
87
  end
71
88
 
72
89
  it 'allows the custom generator to access the expiry length' do
90
+ eigenclass = class << CustomGeneratorArgs; self; end
91
+ eigenclass.class_eval do
92
+ remove_method :generate
93
+ end
73
94
  module CustomGeneratorArgs
74
95
  def self.generate(opts = {})
75
96
  "custom_generator_token_#{opts[:expires_in]}"
@@ -85,6 +106,23 @@ module Doorkeeper
85
106
  expect(token.token).to eq 'custom_generator_token_7200'
86
107
  end
87
108
 
109
+ it 'allows the custom generator to access the created time' do
110
+ module CustomGeneratorArgs
111
+ def self.generate(opts = {})
112
+ "custom_generator_token_#{opts[:created_at].to_i}"
113
+ end
114
+ end
115
+
116
+ Doorkeeper.configure do
117
+ orm DOORKEEPER_ORM
118
+ access_token_generator "Doorkeeper::CustomGeneratorArgs"
119
+ end
120
+
121
+ token = FactoryGirl.create :access_token
122
+ created_at = token.created_at
123
+ expect(token.token).to eq "custom_generator_token_#{created_at.to_i}"
124
+ end
125
+
88
126
  it 'raises an error if the custom object does not support generate' do
89
127
  module NoGenerate
90
128
  end
@@ -133,7 +171,7 @@ module Doorkeeper
133
171
  expect do
134
172
  token2.refresh_token = token1.refresh_token
135
173
  token2.save(validate: false)
136
- end.to raise_error(ActiveRecord::RecordNotUnique)
174
+ end.to raise_error(uniqueness_error)
137
175
  end
138
176
  end
139
177
 
@@ -143,6 +181,12 @@ module Doorkeeper
143
181
  subject.resource_owner_id = nil
144
182
  expect(subject).to be_valid
145
183
  end
184
+
185
+ it 'is valid without application_id' do
186
+ # For resource owner credentials flow
187
+ subject.application_id = nil
188
+ expect(subject).to be_valid
189
+ end
146
190
  end
147
191
 
148
192
  describe '#same_credential?' do
@@ -90,7 +90,7 @@ module Doorkeeper
90
90
  app1 = FactoryGirl.create(:application)
91
91
  app2 = FactoryGirl.create(:application)
92
92
  app2.uid = app1.uid
93
- expect { app2.save!(validate: false) }.to raise_error(ActiveRecord::RecordNotUnique)
93
+ expect { app2.save!(validate: false) }.to raise_error(uniqueness_error)
94
94
  end
95
95
 
96
96
  it 'generate secret on create' do
@@ -129,7 +129,7 @@ module Doorkeeper
129
129
 
130
130
  it 'should destroy its access tokens' do
131
131
  FactoryGirl.create(:access_token, application: new_application)
132
- FactoryGirl.create(:access_token, application: new_application, revoked_at: Time.now)
132
+ FactoryGirl.create(:access_token, application: new_application, revoked_at: Time.now.utc)
133
133
  expect do
134
134
  new_application.destroy
135
135
  end.to change { Doorkeeper::AccessToken.count }.by(-2)
@@ -166,14 +166,6 @@ module Doorkeeper
166
166
  FactoryGirl.create(:access_token, resource_owner_id: resource_owner.id, application: application)
167
167
  expect(Application.authorized_for(resource_owner)).to eq([application])
168
168
  end
169
-
170
- it 'should fail to mass assign a new application', if: ::Rails::VERSION::MAJOR < 4 do
171
- mass_assign = { name: 'Something',
172
- redirect_uri: 'http://somewhere.com/something',
173
- uid: 123,
174
- secret: 'something' }
175
- expect(Application.create(mass_assign).uid).not_to eq(123)
176
- end
177
169
  end
178
170
 
179
171
  describe :authenticate do
@@ -24,14 +24,26 @@ describe 'Resource Owner Password Credentials Flow' do
24
24
  end
25
25
 
26
26
  context 'with valid user credentials' do
27
- it 'should issue new token' do
27
+ it 'should issue new token with confidential client' do
28
28
  expect do
29
29
  post password_token_endpoint_url(client: @client, resource_owner: @resource_owner)
30
30
  end.to change { Doorkeeper::AccessToken.count }.by(1)
31
31
 
32
32
  token = Doorkeeper::AccessToken.first
33
33
 
34
- should_have_json 'access_token', token.token
34
+ expect(token.application_id).to eq @client.id
35
+ should_have_json 'access_token', token.token
36
+ end
37
+
38
+ it 'should issue new token with public client (only client_id present)' do
39
+ expect do
40
+ post password_token_endpoint_url(client_id: @client.uid, resource_owner: @resource_owner)
41
+ end.to change { Doorkeeper::AccessToken.count }.by(1)
42
+
43
+ token = Doorkeeper::AccessToken.first
44
+
45
+ expect(token.application_id).to eq @client.id
46
+ should_have_json 'access_token', token.token
35
47
  end
36
48
 
37
49
  it 'should issue new token without client credentials' do
@@ -41,7 +53,8 @@ describe 'Resource Owner Password Credentials Flow' do
41
53
 
42
54
  token = Doorkeeper::AccessToken.first
43
55
 
44
- should_have_json 'access_token', token.token
56
+ expect(token.application_id).to be_nil
57
+ should_have_json 'access_token', token.token
45
58
  end
46
59
 
47
60
  it 'should issue a refresh token if enabled' do
@@ -51,7 +64,7 @@ describe 'Resource Owner Password Credentials Flow' do
51
64
 
52
65
  token = Doorkeeper::AccessToken.first
53
66
 
54
- should_have_json 'refresh_token', token.refresh_token
67
+ should_have_json 'refresh_token', token.refresh_token
55
68
  end
56
69
 
57
70
  it 'should return the same token if it is still accessible' do
@@ -82,7 +95,7 @@ describe 'Resource Owner Password Credentials Flow' do
82
95
  end
83
96
  end
84
97
 
85
- context 'with invalid client credentials' do
98
+ context 'with invalid confidential client credentials' do
86
99
  it 'should not issue new token with bad client credentials' do
87
100
  expect do
88
101
  post password_token_endpoint_url(client_id: @client.uid,
@@ -91,4 +104,12 @@ describe 'Resource Owner Password Credentials Flow' do
91
104
  end.to_not change { Doorkeeper::AccessToken.count }
92
105
  end
93
106
  end
107
+
108
+ context 'with invalid public client id' do
109
+ it 'should not issue new token with bad client id' do
110
+ expect do
111
+ post password_token_endpoint_url(client_id: 'bad_id', resource_owner: @resource_owner)
112
+ end.to_not change { Doorkeeper::AccessToken.count }
113
+ end
114
+ end
94
115
  end
@@ -37,20 +37,62 @@ describe 'Refresh Token Flow' do
37
37
 
38
38
  context 'refreshing the token' do
39
39
  before do
40
- @token = FactoryGirl.create(:access_token, application: @client, resource_owner_id: 1, use_refresh_token: true)
40
+ @token = FactoryGirl.create(
41
+ :access_token,
42
+ application: @client,
43
+ resource_owner_id: 1,
44
+ use_refresh_token: true
45
+ )
41
46
  end
42
47
 
43
- it 'client request a token with refresh token' do
44
- post refresh_token_endpoint_url(client: @client, refresh_token: @token.refresh_token)
45
- should_have_json 'refresh_token', Doorkeeper::AccessToken.last.refresh_token
46
- expect(@token.reload).to be_revoked
48
+ context "refresh_token revoked on use" do
49
+ it 'client request a token with refresh token' do
50
+ post refresh_token_endpoint_url(
51
+ client: @client, refresh_token: @token.refresh_token
52
+ )
53
+ should_have_json(
54
+ 'refresh_token', Doorkeeper::AccessToken.last.refresh_token
55
+ )
56
+ expect(@token.reload).not_to be_revoked
57
+ end
58
+
59
+ it 'client request a token with expired access token' do
60
+ @token.update_attribute :expires_in, -100
61
+ post refresh_token_endpoint_url(
62
+ client: @client, refresh_token: @token.refresh_token
63
+ )
64
+ should_have_json(
65
+ 'refresh_token', Doorkeeper::AccessToken.last.refresh_token
66
+ )
67
+ expect(@token.reload).not_to be_revoked
68
+ end
47
69
  end
48
70
 
49
- it 'client request a token with expired access token' do
50
- @token.update_attribute :expires_in, -100
51
- post refresh_token_endpoint_url(client: @client, refresh_token: @token.refresh_token)
52
- should_have_json 'refresh_token', Doorkeeper::AccessToken.last.refresh_token
53
- expect(@token.reload).to be_revoked
71
+ context "refresh_token revoked on refresh_token request" do
72
+ before do
73
+ allow(Doorkeeper::AccessToken).to receive(:refresh_token_revoked_on_use?).and_return(false)
74
+ end
75
+
76
+ it 'client request a token with refresh token' do
77
+ post refresh_token_endpoint_url(
78
+ client: @client, refresh_token: @token.refresh_token
79
+ )
80
+ should_have_json(
81
+ 'refresh_token', Doorkeeper::AccessToken.last.refresh_token
82
+ )
83
+ expect(@token.reload).to be_revoked
84
+ end
85
+
86
+ it 'client request a token with expired access token' do
87
+ @token.update_attribute :expires_in, -100
88
+ post refresh_token_endpoint_url(
89
+ client: @client, refresh_token: @token.refresh_token
90
+ )
91
+ should_have_json(
92
+ 'refresh_token', Doorkeeper::AccessToken.last.refresh_token
93
+ )
94
+ expect(@token.reload).to be_revoked
95
+ end
54
96
  end
55
97
 
56
98
  it 'client gets an error for invalid refresh token' do
@@ -79,20 +121,48 @@ describe 'Refresh Token Flow' do
79
121
  before do
80
122
  # enable password auth to simulate other devices
81
123
  config_is_set(:grant_flows, ["password"])
82
- config_is_set(:resource_owner_from_credentials) { User.authenticate! params[:username], params[:password] }
124
+ config_is_set(:resource_owner_from_credentials) do
125
+ User.authenticate! params[:username], params[:password]
126
+ end
83
127
  create_resource_owner
84
- _another_token = post password_token_endpoint_url(client: @client, resource_owner: @resource_owner)
128
+ _another_token = post password_token_endpoint_url(
129
+ client: @client, resource_owner: @resource_owner
130
+ )
85
131
  last_token.update_attribute :created_at, 5.seconds.ago
86
132
 
87
- @token = FactoryGirl.create(:access_token, application: @client, resource_owner_id: @resource_owner.id, use_refresh_token: true)
133
+ @token = FactoryGirl.create(
134
+ :access_token,
135
+ application: @client,
136
+ resource_owner_id: @resource_owner.id,
137
+ use_refresh_token: true
138
+ )
88
139
  @token.update_attribute :expires_in, -100
89
140
  end
90
141
 
91
- it 'client request a token after creating another token with the same user' do
92
- post refresh_token_endpoint_url(client: @client, refresh_token: @token.refresh_token)
142
+ context "refresh_token revoked on use" do
143
+ it 'client request a token after creating another token with the same user' do
144
+ post refresh_token_endpoint_url(
145
+ client: @client, refresh_token: @token.refresh_token
146
+ )
147
+
148
+ should_have_json 'refresh_token', last_token.refresh_token
149
+ expect(@token.reload).not_to be_revoked
150
+ end
151
+ end
152
+
153
+ context "refresh_token revoked on refresh_token request" do
154
+ before do
155
+ allow(Doorkeeper::AccessToken).to receive(:refresh_token_revoked_on_use?).and_return(false)
156
+ end
157
+
158
+ it 'client request a token after creating another token with the same user' do
159
+ post refresh_token_endpoint_url(
160
+ client: @client, refresh_token: @token.refresh_token
161
+ )
93
162
 
94
- should_have_json 'refresh_token', last_token.refresh_token
95
- expect(@token.reload).to be_revoked
163
+ should_have_json 'refresh_token', last_token.refresh_token
164
+ expect(@token.reload).to be_revoked
165
+ end
96
166
  end
97
167
 
98
168
  def last_token
@@ -35,6 +35,9 @@ ENGINE_RAILS_ROOT = File.join(File.dirname(__FILE__), '../')
35
35
 
36
36
  Dir["#{File.dirname(__FILE__)}/support/{dependencies,helpers,shared}/*.rb"].each { |f| require f }
37
37
 
38
+ # Remove after dropping support of Rails 4.2
39
+ require "#{File.dirname(__FILE__)}/support/http_method_shim.rb"
40
+
38
41
  RSpec.configure do |config|
39
42
  config.infer_spec_type_from_file_location!
40
43
  config.mock_with :rspec
@@ -13,14 +13,20 @@ module ModelHelper
13
13
 
14
14
  def access_grant_should_exist_for(client, resource_owner)
15
15
  grant = Doorkeeper::AccessGrant.first
16
- expect(grant.application).to eq(client)
17
- grant.resource_owner_id == resource_owner.id
16
+
17
+ expect(grant.application).to have_attributes(id: client.id).
18
+ and(be_instance_of(Doorkeeper::Application))
19
+
20
+ expect(grant.resource_owner_id).to eq(resource_owner.id)
18
21
  end
19
22
 
20
23
  def access_token_should_exist_for(client, resource_owner)
21
- grant = Doorkeeper::AccessToken.first
22
- expect(grant.application).to eq(client)
23
- grant.resource_owner_id == resource_owner.id
24
+ token = Doorkeeper::AccessToken.first
25
+
26
+ expect(token.application).to have_attributes(id: client.id).
27
+ and(be_instance_of(Doorkeeper::Application))
28
+
29
+ expect(token.resource_owner_id).to eq(resource_owner.id)
24
30
  end
25
31
 
26
32
  def access_grant_should_not_exist
@@ -40,6 +46,22 @@ module ModelHelper
40
46
  grant = Doorkeeper::AccessToken.last
41
47
  expect(grant.scopes).to eq(Doorkeeper::OAuth::Scopes.from_array(args))
42
48
  end
49
+
50
+ def uniqueness_error
51
+ case DOORKEEPER_ORM
52
+ when :active_record
53
+ ActiveRecord::RecordNotUnique
54
+ when :sequel
55
+ error_classes = [Sequel::UniqueConstraintViolation, Sequel::ValidationFailed]
56
+ proc { |error| expect(error.class).to be_in(error_classes) }
57
+ when :mongo_mapper
58
+ MongoMapper::DocumentNotValid
59
+ when /mongoid/
60
+ Mongoid::Errors::Validations
61
+ else
62
+ raise "'#{DOORKEEPER_ORM}' ORM is not supported!"
63
+ end
64
+ end
43
65
  end
44
66
 
45
67
  RSpec.configuration.send :include, ModelHelper
@@ -0,0 +1,24 @@
1
+ # Rails 5 deprecates calling HTTP action methods with positional arguments
2
+ # in favor of keyword arguments. However, the keyword argument form is only
3
+ # supported in Rails 5+. Since we support back to 4, we need some sort of shim
4
+ # to avoid super noisy deprecations when running tests.
5
+ module HTTPMethodShim
6
+ def get(path, params = nil, headers = nil)
7
+ super(path, params: params, headers: headers)
8
+ end
9
+
10
+ def post(path, params = nil, headers = nil)
11
+ super(path, params: params, headers: headers)
12
+ end
13
+
14
+ def put(path, params = nil, headers = nil)
15
+ super(path, params: params, headers: headers)
16
+ end
17
+ end
18
+
19
+ if ::Rails::VERSION::MAJOR >= 5
20
+ RSpec.configure do |config|
21
+ config.include HTTPMethodShim, type: :controller
22
+ config.include HTTPMethodShim, type: :request
23
+ end
24
+ end