doorkeeper 4.2.5 → 5.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.
Files changed (215) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +25 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +17 -0
  4. data/.gitignore +2 -1
  5. data/.gitlab-ci.yml +16 -0
  6. data/.hound.yml +2 -13
  7. data/.rubocop.yml +17 -0
  8. data/.travis.yml +19 -4
  9. data/Appraisals +8 -4
  10. data/CODE_OF_CONDUCT.md +46 -0
  11. data/Gemfile +1 -1
  12. data/NEWS.md +115 -0
  13. data/README.md +175 -39
  14. data/RELEASING.md +5 -12
  15. data/Rakefile +6 -0
  16. data/SECURITY.md +15 -0
  17. data/UPGRADE.md +2 -0
  18. data/app/assets/stylesheets/doorkeeper/admin/application.css +2 -2
  19. data/app/controllers/doorkeeper/application_controller.rb +2 -5
  20. data/app/controllers/doorkeeper/application_metal_controller.rb +4 -0
  21. data/app/controllers/doorkeeper/applications_controller.rb +47 -13
  22. data/app/controllers/doorkeeper/authorizations_controller.rb +55 -12
  23. data/app/controllers/doorkeeper/authorized_applications_controller.rb +19 -2
  24. data/app/controllers/doorkeeper/tokens_controller.rb +24 -17
  25. data/app/helpers/doorkeeper/dashboard_helper.rb +8 -6
  26. data/app/validators/redirect_uri_validator.rb +13 -2
  27. data/app/views/doorkeeper/applications/_delete_form.html.erb +3 -1
  28. data/app/views/doorkeeper/applications/_form.html.erb +31 -19
  29. data/app/views/doorkeeper/applications/edit.html.erb +1 -1
  30. data/app/views/doorkeeper/applications/index.html.erb +18 -6
  31. data/app/views/doorkeeper/applications/new.html.erb +1 -1
  32. data/app/views/doorkeeper/applications/show.html.erb +8 -5
  33. data/app/views/doorkeeper/authorizations/error.html.erb +1 -1
  34. data/app/views/doorkeeper/authorizations/new.html.erb +5 -1
  35. data/app/views/doorkeeper/authorized_applications/index.html.erb +0 -1
  36. data/app/views/layouts/doorkeeper/admin.html.erb +15 -15
  37. data/config/locales/en.yml +19 -6
  38. data/doorkeeper.gemspec +20 -20
  39. data/gemfiles/rails_4_2.gemfile +6 -4
  40. data/gemfiles/rails_5_0.gemfile +4 -4
  41. data/gemfiles/rails_5_1.gemfile +6 -7
  42. data/gemfiles/rails_5_2.gemfile +12 -0
  43. data/gemfiles/rails_master.gemfile +17 -0
  44. data/lib/doorkeeper/config.rb +123 -71
  45. data/lib/doorkeeper/engine.rb +7 -3
  46. data/lib/doorkeeper/errors.rb +20 -5
  47. data/lib/doorkeeper/grape/helpers.rb +14 -9
  48. data/lib/doorkeeper/helpers/controller.rb +16 -22
  49. data/lib/doorkeeper/models/access_grant_mixin.rb +67 -23
  50. data/lib/doorkeeper/models/access_token_mixin.rb +54 -55
  51. data/lib/doorkeeper/models/application_mixin.rb +16 -30
  52. data/lib/doorkeeper/models/concerns/expirable.rb +7 -5
  53. data/lib/doorkeeper/models/concerns/orderable.rb +13 -0
  54. data/lib/doorkeeper/models/concerns/scopes.rb +1 -1
  55. data/lib/doorkeeper/oauth/authorization/code.rb +31 -8
  56. data/lib/doorkeeper/oauth/authorization/context.rb +15 -0
  57. data/lib/doorkeeper/oauth/authorization/token.rb +46 -20
  58. data/lib/doorkeeper/oauth/authorization_code_request.rb +33 -3
  59. data/lib/doorkeeper/oauth/base_request.rb +23 -7
  60. data/lib/doorkeeper/oauth/client/credentials.rb +6 -4
  61. data/lib/doorkeeper/oauth/client.rb +2 -4
  62. data/lib/doorkeeper/oauth/client_credentials/creator.rb +2 -1
  63. data/lib/doorkeeper/oauth/client_credentials/issuer.rb +6 -3
  64. data/lib/doorkeeper/oauth/client_credentials/validation.rb +4 -6
  65. data/lib/doorkeeper/oauth/client_credentials_request.rb +0 -4
  66. data/lib/doorkeeper/oauth/error.rb +2 -2
  67. data/lib/doorkeeper/oauth/error_response.rb +11 -4
  68. data/lib/doorkeeper/oauth/forbidden_token_response.rb +1 -1
  69. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +0 -8
  70. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +15 -0
  71. data/lib/doorkeeper/oauth/invalid_token_response.rb +3 -4
  72. data/lib/doorkeeper/oauth/password_access_token_request.rb +8 -4
  73. data/lib/doorkeeper/oauth/pre_authorization.rb +45 -13
  74. data/lib/doorkeeper/oauth/refresh_token_request.rb +7 -1
  75. data/lib/doorkeeper/oauth/scopes.rb +19 -9
  76. data/lib/doorkeeper/oauth/token.rb +6 -3
  77. data/lib/doorkeeper/oauth/token_introspection.rb +128 -0
  78. data/lib/doorkeeper/oauth/token_response.rb +4 -2
  79. data/lib/doorkeeper/oauth.rb +13 -0
  80. data/lib/doorkeeper/orm/active_record/access_grant.rb +27 -0
  81. data/lib/doorkeeper/orm/active_record/access_token.rb +21 -20
  82. data/lib/doorkeeper/orm/active_record/application.rb +45 -0
  83. data/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb +26 -0
  84. data/lib/doorkeeper/orm/active_record.rb +21 -8
  85. data/lib/doorkeeper/rails/helpers.rb +7 -10
  86. data/lib/doorkeeper/rails/routes.rb +21 -7
  87. data/lib/doorkeeper/rake/db.rake +40 -0
  88. data/lib/doorkeeper/rake/setup.rake +6 -0
  89. data/lib/doorkeeper/rake.rb +14 -0
  90. data/lib/doorkeeper/request/authorization_code.rb +0 -2
  91. data/lib/doorkeeper/request/client_credentials.rb +0 -2
  92. data/lib/doorkeeper/request/code.rb +0 -2
  93. data/lib/doorkeeper/request/password.rb +1 -13
  94. data/lib/doorkeeper/request/refresh_token.rb +0 -2
  95. data/lib/doorkeeper/request/token.rb +0 -2
  96. data/lib/doorkeeper/request.rb +29 -30
  97. data/lib/doorkeeper/validations.rb +3 -2
  98. data/lib/doorkeeper/version.rb +14 -1
  99. data/lib/doorkeeper.rb +21 -17
  100. data/lib/generators/doorkeeper/application_owner_generator.rb +26 -12
  101. data/lib/generators/doorkeeper/confidential_applications_generator.rb +32 -0
  102. data/lib/generators/doorkeeper/install_generator.rb +17 -9
  103. data/lib/generators/doorkeeper/migration_generator.rb +26 -9
  104. data/lib/generators/doorkeeper/pkce_generator.rb +32 -0
  105. data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +31 -20
  106. data/lib/generators/doorkeeper/templates/add_confidential_to_applications.rb.erb +13 -0
  107. data/lib/generators/doorkeeper/templates/{add_owner_to_application_migration.rb → add_owner_to_application_migration.rb.erb} +1 -1
  108. data/lib/generators/doorkeeper/templates/{add_previous_refresh_token_to_access_tokens.rb → add_previous_refresh_token_to_access_tokens.rb.erb} +1 -1
  109. data/lib/generators/doorkeeper/templates/enable_pkce_migration.rb.erb +6 -0
  110. data/lib/generators/doorkeeper/templates/initializer.rb +104 -12
  111. data/lib/generators/doorkeeper/templates/{migration.rb → migration.rb.erb} +2 -1
  112. data/lib/generators/doorkeeper/views_generator.rb +3 -1
  113. data/spec/controllers/application_metal_controller_spec.rb +50 -0
  114. data/spec/controllers/applications_controller_spec.rb +141 -17
  115. data/spec/controllers/authorizations_controller_spec.rb +304 -27
  116. data/spec/controllers/protected_resources_controller_spec.rb +44 -35
  117. data/spec/controllers/token_info_controller_spec.rb +17 -21
  118. data/spec/controllers/tokens_controller_spec.rb +201 -17
  119. data/spec/dummy/app/assets/config/manifest.js +2 -0
  120. data/spec/dummy/config/environments/test.rb +4 -5
  121. data/spec/dummy/config/initializers/doorkeeper.rb +23 -2
  122. data/spec/dummy/config/initializers/{active_record_belongs_to_required_by_default.rb → new_framework_defaults.rb} +5 -1
  123. data/spec/dummy/config/initializers/secret_token.rb +0 -1
  124. data/spec/dummy/config/routes.rb +3 -42
  125. data/spec/dummy/db/migrate/20111122132257_create_users.rb +3 -1
  126. data/spec/dummy/db/migrate/20120312140401_add_password_to_users.rb +3 -1
  127. data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +3 -1
  128. data/spec/dummy/db/migrate/20151223200000_add_owner_to_application.rb +3 -1
  129. data/spec/dummy/db/migrate/20160320211015_add_previous_refresh_token_to_access_tokens.rb +3 -1
  130. data/spec/dummy/db/migrate/20170822064514_enable_pkce.rb +6 -0
  131. data/spec/dummy/db/migrate/20180210183654_add_confidential_to_applications.rb +13 -0
  132. data/spec/dummy/db/schema.rb +38 -37
  133. data/spec/factories.rb +1 -1
  134. data/spec/generators/application_owner_generator_spec.rb +25 -6
  135. data/spec/generators/confidential_applications_generator_spec.rb +45 -0
  136. data/spec/generators/install_generator_spec.rb +1 -1
  137. data/spec/generators/migration_generator_spec.rb +25 -4
  138. data/spec/generators/pkce_generator_spec.rb +43 -0
  139. data/spec/generators/previous_refresh_token_generator_spec.rb +57 -0
  140. data/spec/generators/views_generator_spec.rb +1 -1
  141. data/spec/grape/grape_integration_spec.rb +135 -0
  142. data/spec/helpers/doorkeeper/dashboard_helper_spec.rb +2 -2
  143. data/spec/lib/config_spec.rb +199 -22
  144. data/spec/lib/doorkeeper_spec.rb +1 -126
  145. data/spec/lib/models/expirable_spec.rb +0 -4
  146. data/spec/lib/models/revocable_spec.rb +2 -4
  147. data/spec/lib/models/scopes_spec.rb +0 -4
  148. data/spec/lib/oauth/authorization/uri_builder_spec.rb +0 -4
  149. data/spec/lib/oauth/authorization_code_request_spec.rb +63 -13
  150. data/spec/lib/oauth/base_request_spec.rb +43 -10
  151. data/spec/lib/oauth/base_response_spec.rb +1 -1
  152. data/spec/lib/oauth/client/credentials_spec.rb +5 -5
  153. data/spec/lib/oauth/client_credentials/creator_spec.rb +6 -2
  154. data/spec/lib/oauth/client_credentials/issuer_spec.rb +26 -7
  155. data/spec/lib/oauth/client_credentials/validation_spec.rb +2 -3
  156. data/spec/lib/oauth/client_credentials_integration_spec.rb +2 -2
  157. data/spec/lib/oauth/client_credentials_request_spec.rb +4 -5
  158. data/spec/lib/oauth/client_spec.rb +0 -3
  159. data/spec/lib/oauth/code_request_spec.rb +5 -5
  160. data/spec/lib/oauth/error_response_spec.rb +0 -3
  161. data/spec/lib/oauth/error_spec.rb +1 -3
  162. data/spec/lib/oauth/forbidden_token_response_spec.rb +1 -4
  163. data/spec/lib/oauth/helpers/scope_checker_spec.rb +0 -3
  164. data/spec/lib/oauth/helpers/unique_token_spec.rb +0 -1
  165. data/spec/lib/oauth/helpers/uri_checker_spec.rb +115 -3
  166. data/spec/lib/oauth/invalid_token_response_spec.rb +2 -5
  167. data/spec/lib/oauth/password_access_token_request_spec.rb +46 -5
  168. data/spec/lib/oauth/pre_authorization_spec.rb +40 -6
  169. data/spec/lib/oauth/refresh_token_request_spec.rb +30 -14
  170. data/spec/lib/oauth/scopes_spec.rb +28 -4
  171. data/spec/lib/oauth/token_request_spec.rb +10 -13
  172. data/spec/lib/oauth/token_response_spec.rb +0 -1
  173. data/spec/lib/oauth/token_spec.rb +37 -14
  174. data/spec/lib/orm/active_record/stale_records_cleaner_spec.rb +79 -0
  175. data/spec/lib/request/strategy_spec.rb +0 -1
  176. data/spec/lib/server_spec.rb +10 -0
  177. data/spec/models/doorkeeper/access_grant_spec.rb +45 -2
  178. data/spec/models/doorkeeper/access_token_spec.rb +132 -64
  179. data/spec/models/doorkeeper/application_spec.rb +114 -23
  180. data/spec/requests/applications/applications_request_spec.rb +139 -6
  181. data/spec/requests/applications/authorized_applications_spec.rb +1 -1
  182. data/spec/requests/endpoints/authorization_spec.rb +1 -1
  183. data/spec/requests/endpoints/token_spec.rb +15 -6
  184. data/spec/requests/flows/authorization_code_errors_spec.rb +12 -2
  185. data/spec/requests/flows/authorization_code_spec.rb +199 -2
  186. data/spec/requests/flows/client_credentials_spec.rb +73 -5
  187. data/spec/requests/flows/implicit_grant_errors_spec.rb +3 -3
  188. data/spec/requests/flows/implicit_grant_spec.rb +38 -11
  189. data/spec/requests/flows/password_spec.rb +160 -24
  190. data/spec/requests/flows/refresh_token_spec.rb +6 -6
  191. data/spec/requests/flows/revoke_token_spec.rb +26 -26
  192. data/spec/requests/flows/skip_authorization_spec.rb +16 -11
  193. data/spec/requests/protected_resources/metal_spec.rb +2 -2
  194. data/spec/requests/protected_resources/private_api_spec.rb +2 -2
  195. data/spec/routing/custom_controller_routes_spec.rb +63 -7
  196. data/spec/routing/default_routes_spec.rb +6 -2
  197. data/spec/routing/scoped_routes_spec.rb +16 -2
  198. data/spec/spec_helper.rb +54 -3
  199. data/spec/spec_helper_integration.rb +2 -64
  200. data/spec/support/dependencies/factory_bot.rb +2 -0
  201. data/spec/support/doorkeeper_rspec.rb +19 -0
  202. data/spec/support/helpers/access_token_request_helper.rb +1 -1
  203. data/spec/support/helpers/authorization_request_helper.rb +4 -4
  204. data/spec/support/helpers/model_helper.rb +9 -4
  205. data/spec/support/helpers/request_spec_helper.rb +18 -6
  206. data/spec/support/helpers/url_helper.rb +15 -10
  207. data/spec/support/http_method_shim.rb +12 -16
  208. data/spec/support/shared/controllers_shared_context.rb +2 -6
  209. data/spec/support/shared/models_shared_examples.rb +4 -4
  210. data/spec/validators/redirect_uri_validator_spec.rb +58 -7
  211. data/spec/version/version_spec.rb +15 -0
  212. data/vendor/assets/stylesheets/doorkeeper/bootstrap.min.css +4 -5
  213. metadata +75 -33
  214. data/spec/controllers/application_metal_controller.rb +0 -10
  215. data/spec/support/dependencies/factory_girl.rb +0 -2
@@ -1,4 +1,4 @@
1
- require 'spec_helper_integration'
1
+ require 'spec_helper'
2
2
 
3
3
  module Doorkeeper::OAuth
4
4
  describe PasswordAccessTokenRequest do
@@ -8,10 +8,12 @@ module Doorkeeper::OAuth
8
8
  default_scopes: Doorkeeper::OAuth::Scopes.new,
9
9
  access_token_expires_in: 2.hours,
10
10
  refresh_token_enabled?: false,
11
- custom_access_token_expires_in: ->(_app) { nil }
11
+ custom_access_token_expires_in: lambda { |context|
12
+ context.grant_type == Doorkeeper::OAuth::PASSWORD ? 1234 : nil
13
+ }
12
14
  )
13
15
  end
14
- let(:client) { FactoryGirl.create(:application) }
16
+ let(:client) { FactoryBot.create(:application) }
15
17
  let(:owner) { double :owner, id: 99 }
16
18
 
17
19
  subject do
@@ -22,6 +24,7 @@ module Doorkeeper::OAuth
22
24
  expect do
23
25
  subject.authorize
24
26
  end.to change { client.reload.access_tokens.count }.by(1)
27
+ expect(client.reload.access_tokens.sort_by(&:created_at).last.expires_in).to eq(1234)
25
28
  end
26
29
 
27
30
  it 'issues a new token without a client' do
@@ -53,7 +56,7 @@ module Doorkeeper::OAuth
53
56
  end
54
57
 
55
58
  it 'creates token even when there is already one (default)' do
56
- FactoryGirl.create(:access_token, application_id: client.id, resource_owner_id: owner.id)
59
+ FactoryBot.create(:access_token, application_id: client.id, resource_owner_id: owner.id)
57
60
  expect do
58
61
  subject.authorize
59
62
  end.to change { Doorkeeper::AccessToken.count }.by(1)
@@ -61,12 +64,18 @@ module Doorkeeper::OAuth
61
64
 
62
65
  it 'skips token creation if there is already one' do
63
66
  allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
64
- FactoryGirl.create(:access_token, application_id: client.id, resource_owner_id: owner.id)
67
+ FactoryBot.create(:access_token, application_id: client.id, resource_owner_id: owner.id)
65
68
  expect do
66
69
  subject.authorize
67
70
  end.to_not change { Doorkeeper::AccessToken.count }
68
71
  end
69
72
 
73
+ it "calls configured request callback methods" do
74
+ expect(Doorkeeper.configuration.before_successful_strategy_response).to receive(:call).with(subject).once
75
+ expect(Doorkeeper.configuration.after_successful_strategy_response).to receive(:call).with(subject, instance_of(Doorkeeper::OAuth::TokenResponse)).once
76
+ subject.authorize
77
+ end
78
+
70
79
  describe 'with scopes' do
71
80
  subject do
72
81
  PasswordAccessTokenRequest.new(server, client, owner, scope: 'public')
@@ -86,5 +95,37 @@ module Doorkeeper::OAuth
86
95
  expect(Doorkeeper::AccessToken.last.scopes).to include('public')
87
96
  end
88
97
  end
98
+
99
+ describe 'with custom expiry' do
100
+ let(:server) do
101
+ double(
102
+ :server,
103
+ default_scopes: Doorkeeper::OAuth::Scopes.new,
104
+ access_token_expires_in: 2.hours,
105
+ refresh_token_enabled?: false,
106
+ custom_access_token_expires_in: lambda { |context|
107
+ context.scopes.exists?('public') ? 222 : nil
108
+ }
109
+ )
110
+ end
111
+
112
+ it 'checks scopes' do
113
+ subject = PasswordAccessTokenRequest.new(server, client, owner, scope: 'public')
114
+ allow(server).to receive(:scopes).and_return(Doorkeeper::OAuth::Scopes.from_string('public'))
115
+ expect do
116
+ subject.authorize
117
+ end.to change { Doorkeeper::AccessToken.count }.by(1)
118
+ expect(Doorkeeper::AccessToken.last.expires_in).to eq(222)
119
+ end
120
+
121
+ it 'falls back to the default otherwise' do
122
+ subject = PasswordAccessTokenRequest.new(server, client, owner, scope: 'private')
123
+ allow(server).to receive(:scopes).and_return(Doorkeeper::OAuth::Scopes.from_string('private'))
124
+ expect do
125
+ subject.authorize
126
+ end.to change { Doorkeeper::AccessToken.count }.by(1)
127
+ expect(Doorkeeper::AccessToken.last.expires_in).to eq(2.hours)
128
+ end
129
+ end
89
130
  end
90
131
  end
@@ -1,13 +1,13 @@
1
- require 'spec_helper_integration'
1
+ require 'spec_helper'
2
2
 
3
3
  module Doorkeeper::OAuth
4
4
  describe PreAuthorization do
5
- let(:server) {
5
+ let(:server) do
6
6
  server = Doorkeeper.configuration
7
7
  allow(server).to receive(:default_scopes).and_return(Scopes.new)
8
8
  allow(server).to receive(:scopes).and_return(Scopes.from_string('public profile'))
9
9
  server
10
- }
10
+ end
11
11
 
12
12
  let(:application) do
13
13
  application = double :application
@@ -123,9 +123,19 @@ module Doorkeeper::OAuth
123
123
  expect(subject.scopes).to eq(Scopes.from_string('default'))
124
124
  end
125
125
 
126
- it 'accepts test uri' do
127
- subject.redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
128
- expect(subject).to be_authorizable
126
+ context 'with native redirect uri' do
127
+ let(:native_redirect_uri) { 'urn:ietf:wg:oauth:2.0:oob' }
128
+
129
+ it 'accepts redirect_uri when it matches with the client' do
130
+ subject.redirect_uri = native_redirect_uri
131
+ allow(subject.client).to receive(:redirect_uri) { native_redirect_uri }
132
+ expect(subject).to be_authorizable
133
+ end
134
+
135
+ it 'invalidates redirect_uri when it does\'n match with the client' do
136
+ subject.redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
137
+ expect(subject).not_to be_authorizable
138
+ end
129
139
  end
130
140
 
131
141
  it 'matches the redirect uri against client\'s one' do
@@ -151,5 +161,29 @@ module Doorkeeper::OAuth
151
161
  subject.redirect_uri = nil
152
162
  expect(subject).not_to be_authorizable
153
163
  end
164
+
165
+ describe "as_json" do
166
+ let(:client_id) { "client_uid_123" }
167
+ let(:client_name) { "Acme Co." }
168
+
169
+ before do
170
+ allow(client).to receive(:uid).and_return client_id
171
+ allow(client).to receive(:name).and_return client_name
172
+ end
173
+
174
+ let(:json) { subject.as_json({}) }
175
+
176
+ it { is_expected.to respond_to :as_json }
177
+
178
+ it "returns correct values" do
179
+ expect(json[:client_id]).to eq client_id
180
+ expect(json[:redirect_uri]).to eq subject.redirect_uri
181
+ expect(json[:state]).to eq subject.state
182
+ expect(json[:response_type]).to eq subject.response_type
183
+ expect(json[:scope]).to eq subject.scope
184
+ expect(json[:client_name]).to eq client_name
185
+ expect(json[:status]).to eq I18n.t('doorkeeper.pre_authorization.status')
186
+ end
187
+ end
154
188
  end
155
189
  end
@@ -1,18 +1,21 @@
1
- require 'spec_helper_integration'
1
+ require 'spec_helper'
2
2
 
3
3
  module Doorkeeper::OAuth
4
4
  describe RefreshTokenRequest do
5
5
  before do
6
6
  allow(Doorkeeper::AccessToken).to receive(:refresh_token_revoked_on_use?).and_return(false)
7
7
  end
8
+
8
9
  let(:server) do
9
10
  double :server,
10
11
  access_token_expires_in: 2.minutes,
11
- custom_access_token_expires_in: -> (_oauth_client) { nil }
12
+ custom_access_token_expires_in: ->(_context) { nil }
12
13
  end
14
+
13
15
  let(:refresh_token) do
14
- FactoryGirl.create(:access_token, use_refresh_token: true)
16
+ FactoryBot.create(:access_token, use_refresh_token: true)
15
17
  end
18
+
16
19
  let(:client) { refresh_token.application }
17
20
  let(:credentials) { Client::Credentials.new(client.uid, client.secret) }
18
21
 
@@ -20,25 +23,35 @@ module Doorkeeper::OAuth
20
23
 
21
24
  it 'issues a new token for the client' do
22
25
  expect { subject.authorize }.to change { client.reload.access_tokens.count }.by(1)
23
- expect(client.reload.access_tokens.last.expires_in).to eq(120)
26
+ # #sort_by used for MongoDB ORM extensions for valid ordering
27
+ expect(client.reload.access_tokens.sort_by(&:created_at).last.expires_in).to eq(120)
24
28
  end
25
29
 
26
30
  it 'issues a new token for the client with custom expires_in' do
27
31
  server = double :server,
28
32
  access_token_expires_in: 2.minutes,
29
- custom_access_token_expires_in: ->(_oauth_client) { 1234 }
33
+ custom_access_token_expires_in: lambda { |context|
34
+ context.grant_type == Doorkeeper::OAuth::REFRESH_TOKEN ? 1234 : nil
35
+ }
30
36
 
31
37
  allow(Doorkeeper::AccessToken).to receive(:refresh_token_revoked_on_use?).and_return(false)
32
38
 
33
39
  RefreshTokenRequest.new(server, refresh_token, credentials).authorize
34
40
 
35
- expect(client.reload.access_tokens.last.expires_in).to eq(1234)
41
+ # #sort_by used for MongoDB ORM extensions for valid ordering
42
+ expect(client.reload.access_tokens.sort_by(&:created_at).last.expires_in).to eq(1234)
36
43
  end
37
44
 
38
45
  it 'revokes the previous token' do
39
46
  expect { subject.authorize }.to change { refresh_token.revoked? }.from(false).to(true)
40
47
  end
41
48
 
49
+ it "calls configured request callback methods" do
50
+ expect(Doorkeeper.configuration.before_successful_strategy_response).to receive(:call).with(subject).once
51
+ expect(Doorkeeper.configuration.after_successful_strategy_response).to receive(:call).with(subject, instance_of(Doorkeeper::OAuth::TokenResponse)).once
52
+ subject.authorize
53
+ end
54
+
42
55
  it 'requires the refresh token' do
43
56
  subject.refresh_token = nil
44
57
  subject.validate
@@ -52,7 +65,7 @@ module Doorkeeper::OAuth
52
65
  end
53
66
 
54
67
  it "requires the token's client and current client to match" do
55
- subject.client = FactoryGirl.create(:application)
68
+ subject.client = FactoryBot.create(:application)
56
69
  subject.validate
57
70
  expect(subject.error).to eq(:invalid_grant)
58
71
  end
@@ -74,7 +87,9 @@ module Doorkeeper::OAuth
74
87
  let(:server) do
75
88
  double :server,
76
89
  access_token_expires_in: 2.minutes,
77
- custom_access_token_expires_in: ->(_oauth_client) { 1234 }
90
+ custom_access_token_expires_in: lambda { |context|
91
+ context.grant_type == Doorkeeper::OAuth::REFRESH_TOKEN ? 1234 : nil
92
+ }
78
93
  end
79
94
 
80
95
  before do
@@ -93,13 +108,14 @@ module Doorkeeper::OAuth
93
108
  it 'sets the previous refresh token in the new access token' do
94
109
  subject.authorize
95
110
  expect(
96
- client.access_tokens.last.previous_refresh_token
111
+ # #sort_by used for MongoDB ORM extensions for valid ordering
112
+ client.access_tokens.sort_by(&:created_at).last.previous_refresh_token
97
113
  ).to eq(refresh_token.refresh_token)
98
114
  end
99
115
  end
100
116
 
101
117
  context 'clientless access tokens' do
102
- let!(:refresh_token) { FactoryGirl.create(:clientless_access_token, use_refresh_token: true) }
118
+ let!(:refresh_token) { FactoryBot.create(:clientless_access_token, use_refresh_token: true) }
103
119
 
104
120
  subject { RefreshTokenRequest.new server, refresh_token, nil }
105
121
 
@@ -110,7 +126,7 @@ module Doorkeeper::OAuth
110
126
 
111
127
  context 'with scopes' do
112
128
  let(:refresh_token) do
113
- FactoryGirl.create :access_token,
129
+ FactoryBot.create :access_token,
114
130
  use_refresh_token: true,
115
131
  scopes: 'public write'
116
132
  end
@@ -119,13 +135,13 @@ module Doorkeeper::OAuth
119
135
 
120
136
  it 'transfers scopes from the old token to the new token' do
121
137
  subject.authorize
122
- expect(Doorkeeper::AccessToken.last.scopes).to eq([:public, :write])
138
+ expect(Doorkeeper::AccessToken.last.scopes).to eq(%i[public write])
123
139
  end
124
140
 
125
141
  it 'reduces scopes to the provided scopes' do
126
142
  parameters[:scopes] = 'public'
127
143
  subject.authorize
128
- expect(Doorkeeper::AccessToken.last.scopes).to eq([:public])
144
+ expect(Doorkeeper::AccessToken.last.scopes).to eq(%i[public])
129
145
  end
130
146
 
131
147
  it 'validates that scopes are included in the original access token' do
@@ -139,7 +155,7 @@ module Doorkeeper::OAuth
139
155
  parameters[:scopes] = 'public update'
140
156
  parameters[:scope] = 'public'
141
157
  subject.authorize
142
- expect(Doorkeeper::AccessToken.last.scopes).to eq([:public])
158
+ expect(Doorkeeper::AccessToken.last.scopes).to eq(%i[public])
143
159
  end
144
160
 
145
161
  it 'uses params[:scope] in favor of scopes if present (invalid)' do
@@ -1,7 +1,4 @@
1
1
  require 'spec_helper'
2
- require 'active_support/core_ext/module/delegation'
3
- require 'active_support/core_ext/string'
4
- require 'doorkeeper/oauth/scopes'
5
2
 
6
3
  module Doorkeeper::OAuth
7
4
  describe Scopes do
@@ -62,7 +59,7 @@ module Doorkeeper::OAuth
62
59
  describe '#+' do
63
60
  it 'can add to another scope object' do
64
61
  scopes = Scopes.from_string('public') + Scopes.from_string('admin')
65
- expect(scopes.all).to eq(%w(public admin))
62
+ expect(scopes.all).to eq(%w[public admin])
66
63
  end
67
64
 
68
65
  it 'does not change the existing object' do
@@ -70,6 +67,11 @@ module Doorkeeper::OAuth
70
67
  expect(origin.to_s).to eq('public')
71
68
  end
72
69
 
70
+ it 'can add an array to a scope object' do
71
+ scopes = Scopes.from_string('public') + ['admin']
72
+ expect(scopes.all).to eq(%w[public admin])
73
+ end
74
+
73
75
  it 'raises an error if cannot handle addition' do
74
76
  expect do
75
77
  Scopes.from_string('public') + 'admin'
@@ -77,6 +79,24 @@ module Doorkeeper::OAuth
77
79
  end
78
80
  end
79
81
 
82
+ describe '#&' do
83
+ it 'can get intersection with another scope object' do
84
+ scopes = Scopes.from_string('public admin') & Scopes.from_string('write admin')
85
+ expect(scopes.all).to eq(%w[admin])
86
+ end
87
+
88
+ it 'does not change the existing object' do
89
+ origin = Scopes.from_string('public admin')
90
+ origin & Scopes.from_string('write admin')
91
+ expect(origin.to_s).to eq('public admin')
92
+ end
93
+
94
+ it 'can get intersection with an array' do
95
+ scopes = Scopes.from_string('public admin') & %w[write admin]
96
+ expect(scopes.all).to eq(%w[admin])
97
+ end
98
+ end
99
+
80
100
  describe '#==' do
81
101
  it 'is equal to another set of scopes' do
82
102
  expect(Scopes.from_string('public')).to eq(Scopes.from_string('public'))
@@ -89,6 +109,10 @@ module Doorkeeper::OAuth
89
109
  it 'differs from another set of scopes when scopes are not the same' do
90
110
  expect(Scopes.from_string('public write')).not_to eq(Scopes.from_string('write'))
91
111
  end
112
+
113
+ it "does not raise an error when compared to a non-enumerable object" do
114
+ expect { Scopes.from_string("public") == false }.not_to raise_error
115
+ end
92
116
  end
93
117
 
94
118
  describe '#has_scopes?' do
@@ -1,11 +1,11 @@
1
- require 'spec_helper_integration'
1
+ require 'spec_helper'
2
2
 
3
3
  module Doorkeeper::OAuth
4
4
  describe TokenRequest do
5
5
  let :application do
6
- scopes = double(all: ['public'])
7
- double(:application, id: 9990, scopes: scopes)
6
+ FactoryBot.create(:application, scopes: "public")
8
7
  end
8
+
9
9
  let :pre_auth do
10
10
  double(
11
11
  :pre_auth,
@@ -38,9 +38,7 @@ module Doorkeeper::OAuth
38
38
 
39
39
  it 'does not create token when not authorizable' do
40
40
  allow(pre_auth).to receive(:authorizable?).and_return(false)
41
- expect do
42
- subject.authorize
43
- end.to_not change { Doorkeeper::AccessToken.count }
41
+ expect { subject.authorize }.not_to change { Doorkeeper::AccessToken.count }
44
42
  end
45
43
 
46
44
  it 'returns a error response' do
@@ -52,8 +50,8 @@ module Doorkeeper::OAuth
52
50
  before do
53
51
  Doorkeeper.configure do
54
52
  orm DOORKEEPER_ORM
55
- custom_access_token_expires_in do |_oauth_client|
56
- 1234
53
+ custom_access_token_expires_in do |context|
54
+ context.grant_type == Doorkeeper::OAuth::IMPLICIT ? 1234 : nil
57
55
  end
58
56
  end
59
57
  end
@@ -75,7 +73,7 @@ module Doorkeeper::OAuth
75
73
 
76
74
  it 'creates a new token if scopes do not match' do
77
75
  allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
78
- FactoryGirl.create(:access_token, application_id: pre_auth.client.id,
76
+ FactoryBot.create(:access_token, application_id: pre_auth.client.id,
79
77
  resource_owner_id: owner.id, scopes: '')
80
78
  expect do
81
79
  subject.authorize
@@ -86,12 +84,11 @@ module Doorkeeper::OAuth
86
84
  allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
87
85
  allow(application.scopes).to receive(:has_scopes?).and_return(true)
88
86
  allow(application.scopes).to receive(:all?).and_return(true)
89
- FactoryGirl.create(:access_token, application_id: pre_auth.client.id,
87
+
88
+ FactoryBot.create(:access_token, application_id: pre_auth.client.id,
90
89
  resource_owner_id: owner.id, scopes: 'public')
91
90
 
92
- expect do
93
- subject.authorize
94
- end.to_not change { Doorkeeper::AccessToken.count }
91
+ expect { subject.authorize }.not_to change { Doorkeeper::AccessToken.count }
95
92
  end
96
93
  end
97
94
  end
@@ -1,5 +1,4 @@
1
1
  require 'spec_helper'
2
- require 'doorkeeper/oauth/token_response'
3
2
 
4
3
  module Doorkeeper::OAuth
5
4
  describe TokenResponse do
@@ -1,6 +1,4 @@
1
1
  require 'spec_helper'
2
- require 'active_support/core_ext/string'
3
- require 'doorkeeper/oauth/token'
4
2
 
5
3
  module Doorkeeper
6
4
  unless defined?(AccessToken)
@@ -14,7 +12,7 @@ module Doorkeeper
14
12
  let(:request) { double.as_null_object }
15
13
 
16
14
  let(:method) do
17
- ->(request) { return 'token-value' }
15
+ ->(*) { 'token-value' }
18
16
  end
19
17
 
20
18
  it 'accepts anything that responds to #call' do
@@ -96,19 +94,44 @@ module Doorkeeper
96
94
  end
97
95
 
98
96
  describe :authenticate do
99
- it 'calls the finder if token was returned' do
100
- token = ->(_r) { 'token' }
101
- expect(AccessToken).to receive(:by_token).with('token')
102
- Token.authenticate double, token
97
+ context 'refresh tokens are disabled (default)' do
98
+ context 'refresh tokens are enabled' do
99
+ it 'does not revoke previous refresh_token if token was found' do
100
+ token = ->(_r) { 'token' }
101
+ expect(
102
+ AccessToken
103
+ ).to receive(:by_token).with('token').and_return(token)
104
+ expect(token).not_to receive(:revoke_previous_refresh_token!)
105
+ Token.authenticate double, token
106
+ end
107
+ end
108
+
109
+ it 'calls the finder if token was returned' do
110
+ token = ->(_r) { 'token' }
111
+ expect(AccessToken).to receive(:by_token).with('token')
112
+ Token.authenticate double, token
113
+ end
103
114
  end
104
115
 
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
116
+ context 'refresh tokens are enabled' do
117
+ before do
118
+ Doorkeeper.configure { use_refresh_token }
119
+ end
120
+
121
+ it 'revokes previous refresh_token if token was found' do
122
+ token = ->(_r) { 'token' }
123
+ expect(
124
+ AccessToken
125
+ ).to receive(:by_token).with('token').and_return(token)
126
+ expect(token).to receive(:revoke_previous_refresh_token!)
127
+ Token.authenticate double, token
128
+ end
129
+
130
+ it 'calls the finder if token was returned' do
131
+ token = ->(_r) { 'token' }
132
+ expect(AccessToken).to receive(:by_token).with('token')
133
+ Token.authenticate double, token
134
+ end
112
135
  end
113
136
  end
114
137
  end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Doorkeeper::Orm::ActiveRecord::StaleRecordsCleaner do
6
+ let(:cleaner) { described_class.new(model) }
7
+ let(:models_by_name) do
8
+ {
9
+ access_token: Doorkeeper::AccessToken,
10
+ access_grant: Doorkeeper::AccessGrant
11
+ }
12
+ end
13
+
14
+ %i[access_token access_grant].each do |model_name|
15
+ context "(#{model_name})" do
16
+ let(:model) { models_by_name.fetch(model_name) }
17
+
18
+ describe '#clean_revoked' do
19
+ subject { cleaner.clean_revoked }
20
+
21
+ context 'with revoked record' do
22
+ before do
23
+ FactoryBot.create model_name, revoked_at: Time.current - 1.minute
24
+ end
25
+
26
+ it 'removes the record' do
27
+ expect { subject }.to change { model.count }.to(0)
28
+ end
29
+ end
30
+
31
+ context 'with record revoked in the future' do
32
+ before do
33
+ FactoryBot.create model_name, revoked_at: Time.current + 1.minute
34
+ end
35
+
36
+ it 'keeps the record' do
37
+ expect { subject }.not_to change { model.count }
38
+ end
39
+ end
40
+
41
+ context 'with unrevoked record' do
42
+ before do
43
+ FactoryBot.create model_name, revoked_at: nil
44
+ end
45
+
46
+ it 'keeps the record' do
47
+ expect { subject }.not_to change { model.count }
48
+ end
49
+ end
50
+ end
51
+
52
+ describe '#clean_expired' do
53
+ subject { cleaner.clean_expired(ttl) }
54
+ let(:ttl) { 500 }
55
+ let(:expiry_border) { ttl.seconds.ago }
56
+
57
+ context 'with record that is expired' do
58
+ before do
59
+ FactoryBot.create model_name, created_at: expiry_border - 1.minute
60
+ end
61
+
62
+ it 'removes the record' do
63
+ expect { subject }.to change { model.count }.to(0)
64
+ end
65
+ end
66
+
67
+ context 'with record that is not expired' do
68
+ before do
69
+ FactoryBot.create model_name, created_at: expiry_border + 1.minute
70
+ end
71
+
72
+ it 'keeps the record' do
73
+ expect { subject }.not_to change { model.count }
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -1,5 +1,4 @@
1
1
  require 'spec_helper'
2
- require 'doorkeeper/request/strategy'
3
2
 
4
3
  module Doorkeeper
5
4
  module Request
@@ -45,5 +45,15 @@ describe Doorkeeper::Server do
45
45
  expect(fake_class).to receive(:new).with(subject)
46
46
  subject.authorization_request :code
47
47
  end
48
+
49
+ it 'builds the request with composite strategy name' do
50
+ allow(Doorkeeper.configuration).
51
+ to receive(:authorization_response_types).
52
+ and_return(['id_token token'])
53
+
54
+ stub_const 'Doorkeeper::Request::IdTokenToken', fake_class
55
+ expect(fake_class).to receive(:new).with(subject)
56
+ subject.authorization_request 'id_token token'
57
+ end
48
58
  end
49
59
  end
@@ -1,7 +1,7 @@
1
- require 'spec_helper_integration'
1
+ require 'spec_helper'
2
2
 
3
3
  describe Doorkeeper::AccessGrant do
4
- subject { FactoryGirl.build(:access_grant) }
4
+ subject { FactoryBot.build(:access_grant) }
5
5
 
6
6
  it { expect(subject).to be_valid }
7
7
 
@@ -33,4 +33,47 @@ describe Doorkeeper::AccessGrant do
33
33
  expect(subject).not_to be_valid
34
34
  end
35
35
  end
36
+
37
+ describe '.revoke_all_for' do
38
+ let(:resource_owner) { double(id: 100) }
39
+ let(:application) { FactoryBot.create :application }
40
+ let(:default_attributes) do
41
+ {
42
+ application: application,
43
+ resource_owner_id: resource_owner.id
44
+ }
45
+ end
46
+
47
+ it 'revokes all tokens for given application and resource owner' do
48
+ FactoryBot.create :access_grant, default_attributes
49
+
50
+ described_class.revoke_all_for(application.id, resource_owner)
51
+
52
+ described_class.all.each do |token|
53
+ expect(token).to be_revoked
54
+ end
55
+ end
56
+
57
+ it 'matches application' do
58
+ access_grant_for_different_app = FactoryBot.create(
59
+ :access_grant,
60
+ default_attributes.merge(application: FactoryBot.create(:application))
61
+ )
62
+
63
+ described_class.revoke_all_for(application.id, resource_owner)
64
+
65
+ expect(access_grant_for_different_app.reload).not_to be_revoked
66
+ end
67
+
68
+ it 'matches resource owner' do
69
+ access_grant_for_different_owner = FactoryBot.create(
70
+ :access_grant,
71
+ default_attributes.merge(resource_owner_id: 90)
72
+ )
73
+
74
+ described_class.revoke_all_for application.id, resource_owner
75
+
76
+ expect(access_grant_for_different_owner.reload).not_to be_revoked
77
+ end
78
+ end
36
79
  end