doorkeeper 5.2.4 → 5.2.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: adf3b17f0ba11cc257d433fe4fa18f1cf651a276403987e2d28d169736cdbf98
4
- data.tar.gz: 44c0a5be81b9c1172e8d1c301c0f6924adc11a8d1b9e0f51b878754eade99009
3
+ metadata.gz: acf9b03777c432f388b0df1fb4a8b499c07e9442c30f3a91c33188e1de83a7b3
4
+ data.tar.gz: 0c4cd8972c0b22df9c9c55e8641757d5bae55be522d125879b4c5e6c07b1a869
5
5
  SHA512:
6
- metadata.gz: e48bb0dade513bd2f8da4442167fa3681a8d1845f3cf7cfa0506fe043bc3495ae3b0f8737599c5965d6bf53e8f79280063e1b994387904cf9e3021915ca9e403
7
- data.tar.gz: 53c49b7bd09a5b4026058a7dccb4dc18aad52b4104f0b2abef1bf1c6f44bb2fff55308653ba14ae4a628c1be4d20dd833e4b7094883f8f3fa648e47d8950fa76
6
+ metadata.gz: f49977df45f15f00eaab6628c9cfb7b8309f9052ec26d11350cb0e6db380fc6b6c1fc11417f5e8bc5d9a9996cfaf3c8390628d3e8dcd18f2a4f0c65749c5291b
7
+ data.tar.gz: df1900e4654c6050763f216aa78e32444f38ee96f0366c7672affbef57d117b68ee04a6c67b2e01539818fc176b7601dee039ee89eb10490bba27ffabec1944d
@@ -5,6 +5,16 @@ upgrade guides.
5
5
 
6
6
  User-visible changes worth mentioning.
7
7
 
8
+ ## 5.2.5
9
+
10
+ - [#1371] Backport: add `#as_json` method and attributes serialization restriction for Application model.
11
+ Fixes information disclosure vulnerability (CVE-2020-10187).
12
+
13
+ **[IMPORTANT]** you need to re-implement `#as_json` method for Doorkeeper Application model
14
+ if you previously used `#to_json` serialization with custom options or attributes or rely on
15
+ JSON response from /oauth/applications.json or /oauth/authorized_applications.json. This change
16
+ is a breaking change which restricts serialized attributes to a very small set of columns.
17
+
8
18
  ## 5.2.4
9
19
 
10
20
  - [#1360] Increase `matching_token_for` batch lookup size to 10 000 and make it configurable.
@@ -19,7 +19,7 @@ module Doorkeeper
19
19
  def show
20
20
  respond_to do |format|
21
21
  format.html
22
- format.json { render json: @application }
22
+ format.json { render json: @application, as_owner: true }
23
23
  end
24
24
  end
25
25
 
@@ -36,7 +36,7 @@ module Doorkeeper
36
36
 
37
37
  respond_to do |format|
38
38
  format.html { redirect_to oauth_application_url(@application) }
39
- format.json { render json: @application }
39
+ format.json { render json: @application, as_owner: true }
40
40
  end
41
41
  else
42
42
  respond_to do |format|
@@ -58,7 +58,7 @@ module Doorkeeper
58
58
 
59
59
  respond_to do |format|
60
60
  format.html { redirect_to oauth_application_url(@application) }
61
- format.json { render json: @application }
61
+ format.json { render json: @application, as_owner: true }
62
62
  end
63
63
  else
64
64
  respond_to do |format|
@@ -9,7 +9,7 @@ module Doorkeeper
9
9
 
10
10
  respond_to do |format|
11
11
  format.html
12
- format.json { render json: @applications }
12
+ format.json { render json: @applications, current_resource_owner: current_resource_owner }
13
13
  end
14
14
  end
15
15
 
@@ -70,10 +70,27 @@ module Doorkeeper
70
70
  end
71
71
  end
72
72
 
73
- def to_json(options = nil)
74
- serializable_hash(except: :secret)
75
- .merge(secret: plaintext_secret)
76
- .to_json(options)
73
+ # Represents client as set of it's attributes in JSON format.
74
+ # This is the right way how we want to override ActiveRecord #to_json.
75
+ #
76
+ # Respects privacy settings and serializes minimum set of attributes
77
+ # for public/private clients and full set for authorized owners.
78
+ #
79
+ # @return [Hash] entity attributes for JSON
80
+ #
81
+ def as_json(options = {})
82
+ # if application belongs to some owner we need to check if it's the same as
83
+ # the one passed in the options or check if we render the client as an owner
84
+ if (respond_to?(:owner) && owner && owner == options[:current_resource_owner]) ||
85
+ options[:as_owner]
86
+ # Owners can see all the client attributes, fallback to ActiveModel serialization
87
+ super
88
+ else
89
+ # if application has no owner or it's owner doesn't match one from the options
90
+ # we render only minimum set of attributes that could be exposed to a public
91
+ only = extract_serializable_attributes(options)
92
+ super(options.merge(only: only))
93
+ end
77
94
  end
78
95
 
79
96
  private
@@ -98,5 +115,48 @@ module Doorkeeper
98
115
  def enforce_scopes?
99
116
  Doorkeeper.configuration.enforce_configured_scopes?
100
117
  end
118
+
119
+ # Helper method to extract collection of serializable attribute names
120
+ # considering serialization options (like `only`, `except` and so on).
121
+ #
122
+ # @param options [Hash] serialization options
123
+ #
124
+ # @return [Array<String>]
125
+ # collection of attributes to be serialized using #as_json
126
+ #
127
+ def extract_serializable_attributes(options = {})
128
+ opts = options.try(:dup) || {}
129
+ only = Array.wrap(opts[:only]).map(&:to_s)
130
+
131
+ only = if only.blank?
132
+ serializable_attributes
133
+ else
134
+ only & serializable_attributes
135
+ end
136
+
137
+ only -= Array.wrap(opts[:except]).map(&:to_s) if opts.key?(:except)
138
+ only.uniq
139
+ end
140
+
141
+ # We need to hook into this method to allow serializing plan-text secrets
142
+ # when secrets hashing enabled.
143
+ #
144
+ # @param key [String] attribute name
145
+ #
146
+ def read_attribute_for_serialization(key)
147
+ return super unless key.to_s == "secret"
148
+
149
+ plaintext_secret || secret
150
+ end
151
+
152
+ # Collection of attributes that could be serialized for public.
153
+ # Override this method if you need additional attributes to be serialized.
154
+ #
155
+ # @return [Array<String>] collection of serializable attributes
156
+ def serializable_attributes
157
+ attributes = %w[id name created_at]
158
+ attributes << "uid" unless confidential?
159
+ attributes
160
+ end
101
161
  end
102
162
  end
@@ -9,7 +9,7 @@ module Doorkeeper
9
9
  # Semantic versioning
10
10
  MAJOR = 5
11
11
  MINOR = 2
12
- TINY = 4
12
+ TINY = 5
13
13
  PRE = nil
14
14
 
15
15
  # Full version number
@@ -3,374 +3,479 @@
3
3
  require "spec_helper"
4
4
  require "bcrypt"
5
5
 
6
- module Doorkeeper
7
- describe Application do
8
- let(:clazz) { Doorkeeper::Application }
9
- let(:require_owner) { Doorkeeper.configuration.instance_variable_set("@confirm_application_owner", true) }
10
- let(:unset_require_owner) { Doorkeeper.configuration.instance_variable_set("@confirm_application_owner", false) }
11
- let(:new_application) { FactoryBot.build(:application) }
6
+ describe Doorkeeper::Application do
7
+ let(:require_owner) { Doorkeeper.configuration.instance_variable_set("@confirm_application_owner", true) }
8
+ let(:unset_require_owner) { Doorkeeper.configuration.instance_variable_set("@confirm_application_owner", false) }
9
+ let(:new_application) { FactoryBot.build(:application) }
12
10
 
13
- let(:uid) { SecureRandom.hex(8) }
14
- let(:secret) { SecureRandom.hex(8) }
11
+ let(:uid) { SecureRandom.hex(8) }
12
+ let(:secret) { SecureRandom.hex(8) }
15
13
 
16
- context "application_owner is enabled" do
17
- before do
18
- Doorkeeper.configure do
19
- orm DOORKEEPER_ORM
20
- enable_application_owner
21
- end
22
- end
14
+ it "is invalid without a name" do
15
+ new_application.name = nil
16
+ expect(new_application).not_to be_valid
17
+ end
23
18
 
24
- context "application owner is not required" do
25
- before(:each) do
26
- unset_require_owner
27
- end
19
+ it "is invalid without determining confidentiality" do
20
+ new_application.confidential = nil
21
+ expect(new_application).not_to be_valid
22
+ end
28
23
 
29
- it "is valid given valid attributes" do
30
- expect(new_application).to be_valid
31
- end
32
- end
24
+ it "generates uid on create" do
25
+ expect(new_application.uid).to be_nil
26
+ new_application.save
27
+ expect(new_application.uid).not_to be_nil
28
+ end
33
29
 
34
- context "application owner is required" do
35
- before(:each) do
36
- require_owner
37
- @owner = FactoryBot.build_stubbed(:doorkeeper_testing_user)
38
- end
30
+ it "generates uid on create if an empty string" do
31
+ new_application.uid = ""
32
+ new_application.save
33
+ expect(new_application.uid).not_to be_blank
34
+ end
39
35
 
40
- it "is invalid without an owner" do
41
- expect(new_application).not_to be_valid
42
- end
36
+ it "generates uid on create unless one is set" do
37
+ new_application.uid = uid
38
+ new_application.save
39
+ expect(new_application.uid).to eq(uid)
40
+ end
43
41
 
44
- it "is valid with an owner" do
45
- new_application.owner = @owner
46
- expect(new_application).to be_valid
47
- end
42
+ it "is invalid without uid" do
43
+ new_application.save
44
+ new_application.uid = nil
45
+ expect(new_application).not_to be_valid
46
+ end
47
+
48
+ it "checks uniqueness of uid" do
49
+ app1 = FactoryBot.create(:application)
50
+ app2 = FactoryBot.create(:application)
51
+ app2.uid = app1.uid
52
+ expect(app2).not_to be_valid
53
+ end
54
+
55
+ it "expects database to throw an error when uids are the same" do
56
+ app1 = FactoryBot.create(:application)
57
+ app2 = FactoryBot.create(:application)
58
+ app2.uid = app1.uid
59
+ expect { app2.save!(validate: false) }.to raise_error(uniqueness_error)
60
+ end
61
+
62
+ it "generate secret on create" do
63
+ expect(new_application.secret).to be_nil
64
+ new_application.save
65
+ expect(new_application.secret).not_to be_nil
66
+ end
67
+
68
+ it "generate secret on create if is blank string" do
69
+ new_application.secret = ""
70
+ new_application.save
71
+ expect(new_application.secret).not_to be_blank
72
+ end
73
+
74
+ it "generate secret on create unless one is set" do
75
+ new_application.secret = secret
76
+ new_application.save
77
+ expect(new_application.secret).to eq(secret)
78
+ end
79
+
80
+ it "is invalid without secret" do
81
+ new_application.save
82
+ new_application.secret = nil
83
+ expect(new_application).not_to be_valid
84
+ end
85
+
86
+ context "application_owner is enabled" do
87
+ before do
88
+ Doorkeeper.configure do
89
+ orm DOORKEEPER_ORM
90
+ enable_application_owner
48
91
  end
49
92
  end
50
93
 
51
- it "is invalid without a name" do
52
- new_application.name = nil
53
- expect(new_application).not_to be_valid
54
- end
94
+ context "application owner is not required" do
95
+ before(:each) do
96
+ unset_require_owner
97
+ end
55
98
 
56
- it "is invalid without determining confidentiality" do
57
- new_application.confidential = nil
58
- expect(new_application).not_to be_valid
99
+ it "is valid given valid attributes" do
100
+ expect(new_application).to be_valid
101
+ end
59
102
  end
60
103
 
61
- it "generates uid on create" do
62
- expect(new_application.uid).to be_nil
63
- new_application.save
64
- expect(new_application.uid).not_to be_nil
65
- end
104
+ context "application owner is required" do
105
+ before do
106
+ require_owner
107
+ @owner = FactoryBot.build_stubbed(:doorkeeper_testing_user)
108
+ end
66
109
 
67
- it "generates uid on create if an empty string" do
68
- new_application.uid = ""
69
- new_application.save
70
- expect(new_application.uid).not_to be_blank
71
- end
110
+ it "is invalid without an owner" do
111
+ expect(new_application).not_to be_valid
112
+ end
72
113
 
73
- it "generates uid on create unless one is set" do
74
- new_application.uid = uid
75
- new_application.save
76
- expect(new_application.uid).to eq(uid)
114
+ it "is valid with an owner" do
115
+ new_application.owner = @owner
116
+ expect(new_application).to be_valid
117
+ end
77
118
  end
119
+ end
78
120
 
79
- it "is invalid without uid" do
80
- new_application.save
81
- new_application.uid = nil
82
- expect(new_application).not_to be_valid
121
+ context "redirect URI" do
122
+ context "when grant flows allow blank redirect URI" do
123
+ before do
124
+ Doorkeeper.configure do
125
+ grant_flows %w[password client_credentials]
126
+ end
127
+ end
128
+
129
+ it "is valid without redirect_uri" do
130
+ new_application.save
131
+ new_application.redirect_uri = nil
132
+ expect(new_application).to be_valid
133
+ end
83
134
  end
84
135
 
85
- context "redirect URI" do
86
- context "when grant flows allow blank redirect URI" do
87
- before do
88
- Doorkeeper.configure do
89
- grant_flows %w[password client_credentials]
90
- end
136
+ context "when grant flows require redirect URI" do
137
+ before do
138
+ Doorkeeper.configure do
139
+ grant_flows %w[password client_credentials authorization_code]
91
140
  end
141
+ end
92
142
 
93
- it "is valid without redirect_uri" do
94
- new_application.save
95
- new_application.redirect_uri = nil
96
- expect(new_application).to be_valid
97
- end
143
+ it "is invalid without redirect_uri" do
144
+ new_application.save
145
+ new_application.redirect_uri = nil
146
+ expect(new_application).not_to be_valid
98
147
  end
148
+ end
99
149
 
100
- context "when grant flows require redirect URI" do
101
- before do
102
- Doorkeeper.configure do
103
- grant_flows %w[password client_credentials authorization_code]
104
- end
150
+ context "when blank URI option disabled" do
151
+ before do
152
+ Doorkeeper.configure do
153
+ grant_flows %w[password client_credentials]
154
+ allow_blank_redirect_uri false
105
155
  end
156
+ end
106
157
 
107
- it "is invalid without redirect_uri" do
108
- new_application.save
109
- new_application.redirect_uri = nil
110
- expect(new_application).not_to be_valid
111
- end
158
+ it "is invalid without redirect_uri" do
159
+ new_application.save
160
+ new_application.redirect_uri = nil
161
+ expect(new_application).not_to be_valid
112
162
  end
163
+ end
164
+ end
113
165
 
114
- context "when blank URI option disabled" do
115
- before do
116
- Doorkeeper.configure do
117
- grant_flows %w[password client_credentials]
118
- allow_blank_redirect_uri false
119
- end
120
- end
166
+ context "with hashing enabled" do
167
+ include_context "with application hashing enabled"
168
+ let(:app) { FactoryBot.create :application }
169
+ let(:default_strategy) { Doorkeeper::SecretStoring::Sha256Hash }
170
+
171
+ it "uses SHA256 to avoid additional dependencies" do
172
+ # Ensure token was generated
173
+ app.validate
174
+ expect(app.secret).to eq(default_strategy.transform_secret(app.plaintext_secret))
175
+ end
121
176
 
122
- it "is invalid without redirect_uri" do
123
- new_application.save
124
- new_application.redirect_uri = nil
125
- expect(new_application).not_to be_valid
177
+ context "when bcrypt strategy is configured" do
178
+ # In this text context, we have bcrypt loaded so `bcrypt_present?`
179
+ # will always be true
180
+ before do
181
+ Doorkeeper.configure do
182
+ hash_application_secrets using: "Doorkeeper::SecretStoring::BCrypt"
126
183
  end
127
184
  end
185
+
186
+ it "holds a volatile plaintext and BCrypt secret" do
187
+ expect(app.secret_strategy).to eq Doorkeeper::SecretStoring::BCrypt
188
+ expect(app.plaintext_secret).to be_a(String)
189
+ expect(app.secret).not_to eq(app.plaintext_secret)
190
+ expect { ::BCrypt::Password.create(app.secret) }.not_to raise_error
191
+ end
128
192
  end
129
193
 
130
- it "checks uniqueness of uid" do
131
- app1 = FactoryBot.create(:application)
132
- app2 = FactoryBot.create(:application)
133
- app2.uid = app1.uid
134
- expect(app2).not_to be_valid
194
+ it "does not fallback to plain lookup by default" do
195
+ lookup = described_class.by_uid_and_secret(app.uid, app.secret)
196
+ expect(lookup).to eq(nil)
197
+
198
+ lookup = described_class.by_uid_and_secret(app.uid, app.plaintext_secret)
199
+ expect(lookup).to eq(app)
135
200
  end
136
201
 
137
- it "expects database to throw an error when uids are the same" do
138
- app1 = FactoryBot.create(:application)
139
- app2 = FactoryBot.create(:application)
140
- app2.uid = app1.uid
141
- expect { app2.save!(validate: false) }.to raise_error(uniqueness_error)
202
+ context "with fallback enabled" do
203
+ include_context "with token hashing and fallback lookup enabled"
204
+
205
+ it "provides plain and hashed lookup" do
206
+ lookup = described_class.by_uid_and_secret(app.uid, app.secret)
207
+ expect(lookup).to eq(app)
208
+
209
+ lookup = described_class.by_uid_and_secret(app.uid, app.plaintext_secret)
210
+ expect(lookup).to eq(app)
211
+ end
142
212
  end
143
213
 
144
- it "generate secret on create" do
145
- expect(new_application.secret).to be_nil
146
- new_application.save
147
- expect(new_application.secret).not_to be_nil
214
+ it "does not provide access to secret after loading" do
215
+ lookup = described_class.by_uid_and_secret(app.uid, app.plaintext_secret)
216
+ expect(lookup.plaintext_secret).to be_nil
148
217
  end
218
+ end
149
219
 
150
- it "generate secret on create if is blank string" do
151
- new_application.secret = ""
220
+ describe "destroy related models on cascade" do
221
+ before(:each) do
152
222
  new_application.save
153
- expect(new_application.secret).not_to be_blank
154
223
  end
155
224
 
156
- it "generate secret on create unless one is set" do
157
- new_application.secret = secret
158
- new_application.save
159
- expect(new_application.secret).to eq(secret)
225
+ let(:resource_owner) { FactoryBot.create(:doorkeeper_testing_user) }
226
+
227
+ it "should destroy its access grants" do
228
+ FactoryBot.create(
229
+ :access_grant,
230
+ application: new_application,
231
+ resource_owner_id: resource_owner.id,
232
+ )
233
+
234
+ expect { new_application.destroy }.to change { Doorkeeper::AccessGrant.count }.by(-1)
160
235
  end
161
236
 
162
- it "is invalid without secret" do
163
- new_application.save
164
- new_application.secret = nil
165
- expect(new_application).not_to be_valid
237
+ it "should destroy its access tokens" do
238
+ FactoryBot.create(:access_token, application: new_application)
239
+ FactoryBot.create(:access_token, application: new_application, revoked_at: Time.now.utc)
240
+ expect do
241
+ new_application.destroy
242
+ end.to change { Doorkeeper::AccessToken.count }.by(-2)
166
243
  end
244
+ end
167
245
 
168
- context "with hashing enabled" do
169
- include_context "with application hashing enabled"
170
- let(:app) { FactoryBot.create :application }
171
- let(:default_strategy) { Doorkeeper::SecretStoring::Sha256Hash }
246
+ describe "#ordered_by" do
247
+ let(:applications) { FactoryBot.create_list(:application, 5) }
172
248
 
173
- it "uses SHA256 to avoid additional dependencies" do
174
- # Ensure token was generated
175
- app.validate
176
- expect(app.secret).to eq(default_strategy.transform_secret(app.plaintext_secret))
249
+ context "when a direction is not specified" do
250
+ it "calls order with a default order of asc" do
251
+ names = applications.map(&:name).sort
252
+ expect(described_class.ordered_by(:name).map(&:name)).to eq(names)
177
253
  end
254
+ end
178
255
 
179
- context "when bcrypt strategy is configured" do
180
- # In this text context, we have bcrypt loaded so `bcrypt_present?`
181
- # will always be true
182
- before do
183
- Doorkeeper.configure do
184
- hash_application_secrets using: "Doorkeeper::SecretStoring::BCrypt"
185
- end
186
- end
187
-
188
- it "holds a volatile plaintext and BCrypt secret" do
189
- expect(app.secret_strategy).to eq Doorkeeper::SecretStoring::BCrypt
190
- expect(app.plaintext_secret).to be_a(String)
191
- expect(app.secret).not_to eq(app.plaintext_secret)
192
- expect { ::BCrypt::Password.create(app.secret) }.not_to raise_error
193
- end
256
+ context "when a direction is specified" do
257
+ it "calls order with specified direction" do
258
+ names = applications.map(&:name).sort.reverse
259
+ expect(described_class.ordered_by(:name, :desc).map(&:name)).to eq(names)
194
260
  end
261
+ end
262
+ end
195
263
 
196
- it "does not fallback to plain lookup by default" do
197
- lookup = clazz.by_uid_and_secret(app.uid, app.secret)
198
- expect(lookup).to eq(nil)
199
-
200
- lookup = clazz.by_uid_and_secret(app.uid, app.plaintext_secret)
201
- expect(lookup).to eq(app)
264
+ describe "#redirect_uri=" do
265
+ context "when array of valid redirect_uris" do
266
+ it "should join by newline" do
267
+ new_application.redirect_uri = ["http://localhost/callback1", "http://localhost/callback2"]
268
+ expect(new_application.redirect_uri).to eq("http://localhost/callback1\nhttp://localhost/callback2")
202
269
  end
270
+ end
271
+ context "when string of valid redirect_uris" do
272
+ it "should store as-is" do
273
+ new_application.redirect_uri = "http://localhost/callback1\nhttp://localhost/callback2"
274
+ expect(new_application.redirect_uri).to eq("http://localhost/callback1\nhttp://localhost/callback2")
275
+ end
276
+ end
277
+ end
203
278
 
204
- context "with fallback enabled" do
205
- include_context "with token hashing and fallback lookup enabled"
279
+ describe "#renew_secret" do
280
+ let(:app) { FactoryBot.create :application }
206
281
 
207
- it "provides plain and hashed lookup" do
208
- lookup = clazz.by_uid_and_secret(app.uid, app.secret)
209
- expect(lookup).to eq(app)
282
+ it "should generate a new secret" do
283
+ old_secret = app.secret
284
+ app.renew_secret
285
+ expect(old_secret).not_to eq(app.secret)
286
+ end
287
+ end
210
288
 
211
- lookup = clazz.by_uid_and_secret(app.uid, app.plaintext_secret)
212
- expect(lookup).to eq(app)
213
- end
214
- end
289
+ describe "#authorized_for" do
290
+ let(:resource_owner) { FactoryBot.create(:doorkeeper_testing_user) }
291
+ let(:other_resource_owner) { FactoryBot.create(:doorkeeper_testing_user) }
215
292
 
216
- it "does not provide access to secret after loading" do
217
- lookup = clazz.by_uid_and_secret(app.uid, app.plaintext_secret)
218
- expect(lookup.plaintext_secret).to be_nil
219
- end
293
+ it "is empty if the application is not authorized for anyone" do
294
+ expect(described_class.authorized_for(resource_owner)).to be_empty
220
295
  end
221
296
 
222
- describe "destroy related models on cascade" do
223
- before(:each) do
224
- new_application.save
225
- end
297
+ it "returns only application for a specific resource owner" do
298
+ FactoryBot.create(
299
+ :access_token,
300
+ resource_owner_id: other_resource_owner.id,
301
+ )
302
+ token = FactoryBot.create(
303
+ :access_token,
304
+ resource_owner_id: resource_owner.id,
305
+ )
306
+ expect(described_class.authorized_for(resource_owner)).to eq([token.application])
307
+ end
226
308
 
227
- it "should destroy its access grants" do
228
- FactoryBot.create(:access_grant, application: new_application)
229
- expect { new_application.destroy }.to change { Doorkeeper::AccessGrant.count }.by(-1)
230
- end
309
+ it "excludes revoked tokens" do
310
+ FactoryBot.create(
311
+ :access_token,
312
+ resource_owner_id: resource_owner.id,
313
+ revoked_at: 2.days.ago,
314
+ )
315
+ expect(described_class.authorized_for(resource_owner)).to be_empty
316
+ end
231
317
 
232
- it "should destroy its access tokens" do
233
- FactoryBot.create(:access_token, application: new_application)
234
- FactoryBot.create(:access_token, application: new_application, revoked_at: Time.now.utc)
235
- expect do
236
- new_application.destroy
237
- end.to change { Doorkeeper::AccessToken.count }.by(-2)
238
- end
318
+ it "returns all applications that have been authorized" do
319
+ token1 = FactoryBot.create(
320
+ :access_token,
321
+ resource_owner_id: resource_owner.id,
322
+ )
323
+ token2 = FactoryBot.create(
324
+ :access_token,
325
+ resource_owner_id: resource_owner.id,
326
+ )
327
+ expect(described_class.authorized_for(resource_owner))
328
+ .to eq([token1.application, token2.application])
239
329
  end
240
330
 
241
- describe :ordered_by do
242
- let(:applications) { FactoryBot.create_list(:application, 5) }
331
+ it "returns only one application even if it has been authorized twice" do
332
+ application = FactoryBot.create(:application)
333
+ FactoryBot.create(
334
+ :access_token,
335
+ resource_owner_id: resource_owner.id,
336
+ application: application,
337
+ )
338
+ FactoryBot.create(
339
+ :access_token,
340
+ resource_owner_id: resource_owner.id,
341
+ application: application,
342
+ )
343
+ expect(described_class.authorized_for(resource_owner)).to eq([application])
344
+ end
345
+ end
243
346
 
244
- context "when a direction is not specified" do
245
- it "calls order with a default order of asc" do
246
- names = applications.map(&:name).sort
247
- expect(Application.ordered_by(:name).map(&:name)).to eq(names)
248
- end
249
- end
347
+ describe "#revoke_tokens_and_grants_for" do
348
+ it "revokes all access tokens and access grants" do
349
+ application_id = 42
350
+ resource_owner = double
351
+ expect(Doorkeeper::AccessToken)
352
+ .to receive(:revoke_all_for).with(application_id, resource_owner)
353
+ expect(Doorkeeper::AccessGrant)
354
+ .to receive(:revoke_all_for).with(application_id, resource_owner)
355
+
356
+ described_class.revoke_tokens_and_grants_for(application_id, resource_owner)
357
+ end
358
+ end
250
359
 
251
- context "when a direction is specified" do
252
- it "calls order with specified direction" do
253
- names = applications.map(&:name).sort.reverse
254
- expect(Application.ordered_by(:name, :desc).map(&:name)).to eq(names)
360
+ describe "#by_uid_and_secret" do
361
+ context "when application is private/confidential" do
362
+ it "finds the application via uid/secret" do
363
+ app = FactoryBot.create :application
364
+ authenticated = described_class.by_uid_and_secret(app.uid, app.secret)
365
+ expect(authenticated).to eq(app)
366
+ end
367
+ context "when secret is wrong" do
368
+ it "should not find the application" do
369
+ app = FactoryBot.create :application
370
+ authenticated = described_class.by_uid_and_secret(app.uid, "bad")
371
+ expect(authenticated).to eq(nil)
255
372
  end
256
373
  end
257
374
  end
258
375
 
259
- describe "#redirect_uri=" do
260
- context "when array of valid redirect_uris" do
261
- it "should join by newline" do
262
- new_application.redirect_uri = ["http://localhost/callback1", "http://localhost/callback2"]
263
- expect(new_application.redirect_uri).to eq("http://localhost/callback1\nhttp://localhost/callback2")
376
+ context "when application is public/non-confidential" do
377
+ context "when secret is blank" do
378
+ it "should find the application" do
379
+ app = FactoryBot.create :application, confidential: false
380
+ authenticated = described_class.by_uid_and_secret(app.uid, nil)
381
+ expect(authenticated).to eq(app)
264
382
  end
265
383
  end
266
- context "when string of valid redirect_uris" do
267
- it "should store as-is" do
268
- new_application.redirect_uri = "http://localhost/callback1\nhttp://localhost/callback2"
269
- expect(new_application.redirect_uri).to eq("http://localhost/callback1\nhttp://localhost/callback2")
384
+ context "when secret is wrong" do
385
+ it "should not find the application" do
386
+ app = FactoryBot.create :application, confidential: false
387
+ authenticated = described_class.by_uid_and_secret(app.uid, "bad")
388
+ expect(authenticated).to eq(nil)
270
389
  end
271
390
  end
272
391
  end
392
+ end
273
393
 
274
- describe "#renew_secret" do
275
- let(:app) { FactoryBot.create :application }
394
+ describe "#confidential?" do
395
+ subject { FactoryBot.create(:application, confidential: confidential).confidential? }
276
396
 
277
- it "should generate a new secret" do
278
- old_secret = app.secret
279
- app.renew_secret
280
- expect(old_secret).not_to eq(app.secret)
281
- end
397
+ context "when application is private/confidential" do
398
+ let(:confidential) { true }
399
+ it { expect(subject).to eq(true) }
282
400
  end
283
401
 
284
- describe :authorized_for do
285
- let(:resource_owner) { double(:resource_owner, id: 10) }
402
+ context "when application is public/non-confidential" do
403
+ let(:confidential) { false }
404
+ it { expect(subject).to eq(false) }
405
+ end
406
+ end
286
407
 
287
- it "is empty if the application is not authorized for anyone" do
288
- expect(Application.authorized_for(resource_owner)).to be_empty
289
- end
408
+ describe "#as_json" do
409
+ let(:app) { FactoryBot.create :application, secret: "123123123" }
290
410
 
291
- it "returns only application for a specific resource owner" do
292
- FactoryBot.create(:access_token, resource_owner_id: resource_owner.id + 1)
293
- token = FactoryBot.create(:access_token, resource_owner_id: resource_owner.id)
294
- expect(Application.authorized_for(resource_owner)).to eq([token.application])
295
- end
411
+ before do
412
+ allow(Doorkeeper.configuration)
413
+ .to receive(:application_secret_strategy).and_return(Doorkeeper::SecretStoring::Plain)
414
+ end
296
415
 
297
- it "excludes revoked tokens" do
298
- FactoryBot.create(:access_token, resource_owner_id: resource_owner.id, revoked_at: 2.days.ago)
299
- expect(Application.authorized_for(resource_owner)).to be_empty
416
+ # AR specific feature
417
+ if DOORKEEPER_ORM == :active_record
418
+ it "correctly works with #to_json" do
419
+ ActiveRecord::Base.include_root_in_json = true
420
+ expect(app.to_json(include_root_in_json: true)).to match(/application.+?:\{/)
421
+ ActiveRecord::Base.include_root_in_json = false
300
422
  end
423
+ end
301
424
 
302
- it "returns all applications that have been authorized" do
303
- token1 = FactoryBot.create(:access_token, resource_owner_id: resource_owner.id)
304
- token2 = FactoryBot.create(:access_token, resource_owner_id: resource_owner.id)
305
- expect(Application.authorized_for(resource_owner)).to eq([token1.application, token2.application])
425
+ context "when called without authorized resource owner" do
426
+ it "includes minimal set of attributes" do
427
+ expect(app.as_json).to match(
428
+ "id" => app.id,
429
+ "name" => app.name,
430
+ "created_at" => an_instance_of(String),
431
+ )
306
432
  end
307
433
 
308
- it "returns only one application even if it has been authorized twice" do
309
- application = FactoryBot.create(:application)
310
- FactoryBot.create(:access_token, resource_owner_id: resource_owner.id, application: application)
311
- FactoryBot.create(:access_token, resource_owner_id: resource_owner.id, application: application)
312
- expect(Application.authorized_for(resource_owner)).to eq([application])
313
- end
314
- end
434
+ it "includes application UID if it's public" do
435
+ app = FactoryBot.create :application, secret: "123123123", confidential: false
315
436
 
316
- describe :revoke_tokens_and_grants_for do
317
- it "revokes all access tokens and access grants" do
318
- application_id = 42
319
- resource_owner = double
320
- expect(Doorkeeper::AccessToken)
321
- .to receive(:revoke_all_for).with(application_id, resource_owner)
322
- expect(Doorkeeper::AccessGrant)
323
- .to receive(:revoke_all_for).with(application_id, resource_owner)
437
+ expect(app.as_json).to match(
438
+ "id" => app.id,
439
+ "name" => app.name,
440
+ "created_at" => an_instance_of(String),
441
+ "uid" => app.uid,
442
+ )
443
+ end
324
444
 
325
- Application.revoke_tokens_and_grants_for(application_id, resource_owner)
445
+ it "respects custom options" do
446
+ expect(app.as_json(except: :id)).not_to include("id")
447
+ expect(app.as_json(only: %i[name created_at secret]))
448
+ .to match(
449
+ "name" => app.name,
450
+ "created_at" => an_instance_of(String),
451
+ )
326
452
  end
327
453
  end
328
454
 
329
- describe :by_uid_and_secret do
330
- context "when application is private/confidential" do
331
- it "finds the application via uid/secret" do
332
- app = FactoryBot.create :application
333
- authenticated = Application.by_uid_and_secret(app.uid, app.secret)
334
- expect(authenticated).to eq(app)
335
- end
336
- context "when secret is wrong" do
337
- it "should not find the application" do
338
- app = FactoryBot.create :application
339
- authenticated = Application.by_uid_and_secret(app.uid, "bad")
340
- expect(authenticated).to eq(nil)
341
- end
342
- end
343
- end
455
+ context "when called with authorized resource owner" do
456
+ let(:owner) { FactoryBot.create(:doorkeeper_testing_user) }
457
+ let(:other_owner) { FactoryBot.create(:doorkeeper_testing_user) }
458
+ let(:app) { FactoryBot.create(:application, secret: "123123123", owner: owner) }
344
459
 
345
- context "when application is public/non-confidential" do
346
- context "when secret is blank" do
347
- it "should find the application" do
348
- app = FactoryBot.create :application, confidential: false
349
- authenticated = Application.by_uid_and_secret(app.uid, nil)
350
- expect(authenticated).to eq(app)
351
- end
352
- end
353
- context "when secret is wrong" do
354
- it "should not find the application" do
355
- app = FactoryBot.create :application, confidential: false
356
- authenticated = Application.by_uid_and_secret(app.uid, "bad")
357
- expect(authenticated).to eq(nil)
358
- end
460
+ before do
461
+ Doorkeeper.configure do
462
+ orm DOORKEEPER_ORM
463
+ enable_application_owner confirmation: false
359
464
  end
360
465
  end
361
- end
362
-
363
- describe :confidential? do
364
- subject { FactoryBot.create(:application, confidential: confidential).confidential? }
365
466
 
366
- context "when application is private/confidential" do
367
- let(:confidential) { true }
368
- it { expect(subject).to eq(true) }
467
+ it "includes all the attributes" do
468
+ expect(app.as_json(current_resource_owner: owner))
469
+ .to include(
470
+ "secret" => "123123123",
471
+ "redirect_uri" => app.redirect_uri,
472
+ "uid" => app.uid,
473
+ )
369
474
  end
370
475
 
371
- context "when application is public/non-confidential" do
372
- let(:confidential) { false }
373
- it { expect(subject).to eq(false) }
476
+ it "doesn't include unsafe attributes if current owner isn't the same as owner" do
477
+ expect(app.as_json(current_resource_owner: other_owner))
478
+ .not_to include("redirect_uri")
374
479
  end
375
480
  end
376
481
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doorkeeper
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.4
4
+ version: 5.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felipe Elias Philipp
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2020-02-09 00:00:00.000000000 Z
14
+ date: 2020-05-02 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: railties