doorkeeper 5.1.0 → 5.1.1

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: d8ea67fd14f902f63a276aed1f57a134c39fa02322d4b1f114571e2a90310ead
4
- data.tar.gz: 44f9f0b886117c6dabe6a09c236107ba6b8c3469b40526a4f6c10f2e4b3e8eac
3
+ metadata.gz: 213817ff28244685026d50922c3e64957442deac4deca7fd31e8265b19654ea8
4
+ data.tar.gz: 95ac43c52b0c01b5b6f5b72af3ccad07cb6d2fa67da0cb1a698a017d7cb9a059
5
5
  SHA512:
6
- metadata.gz: 2aa8f4fbe445a84f98035e8ebeb99e715860fb7d29637d8e6cca994bf18a9ba7c051d38fda66829a5dd260f93ee74ff9f6f6d863e610203fa690da7b698b7da1
7
- data.tar.gz: 5d474565c95e341b7b4773a7c1e2a782fa1bc27c4f22874428db52ceb4df10ccbb342420ba5266ba0d779380d42df3576f56449d390ecbf795e4deee50bdaff4
6
+ metadata.gz: ef803e064768d3e04d9604acf5d32984da43dfbe1a79c6d672f8775776365865b6b42ffb46c924008a57ce27380b627834106b59a705713707e7ecb3a33b214e
7
+ data.tar.gz: 57e5cd9e2554ef3aa92b6aa9395494aba610ca969f65a3def3b9978b554c2b72ef839c186f19b2cd1b275126a9ba60c66c9c09479926b7acc9fe4e653ab01467
data/NEWS.md CHANGED
@@ -5,9 +5,10 @@ upgrade guides.
5
5
 
6
6
  User-visible changes worth mentioning.
7
7
 
8
- ## master
8
+ ## 5.1.1
9
9
 
10
- - [#PR] Add your PR description here.
10
+ [#1371] Backport: add #as_json method and attributes serialization restriction for Application model.
11
+ Fixes information disclosure vulnerability (CVE-2020-10187).
11
12
 
12
13
  ## 5.1.0
13
14
 
@@ -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
 
@@ -35,7 +35,7 @@ module Doorkeeper
35
35
 
36
36
  respond_to do |format|
37
37
  format.html { redirect_to oauth_application_url(@application) }
38
- format.json { render json: @application }
38
+ format.json { render json: @application, as_owner: true }
39
39
  end
40
40
  else
41
41
  respond_to do |format|
@@ -57,7 +57,7 @@ module Doorkeeper
57
57
 
58
58
  respond_to do |format|
59
59
  format.html { redirect_to oauth_application_url(@application) }
60
- format.json { render json: @application }
60
+ format.json { render json: @application, as_owner: true }
61
61
  end
62
62
  else
63
63
  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
 
@@ -60,6 +60,29 @@ module Doorkeeper
60
60
  end
61
61
  end
62
62
 
63
+ # Represents client as set of it's attributes in JSON format.
64
+ # This is the right way how we want to override ActiveRecord #to_json.
65
+ #
66
+ # Respects privacy settings and serializes minimum set of attributes
67
+ # for public/private clients and full set for authorized owners.
68
+ #
69
+ # @return [Hash] entity attributes for JSON
70
+ #
71
+ def as_json(options = {})
72
+ # if application belongs to some owner we need to check if it's the same as
73
+ # the one passed in the options or check if we render the client as an owner
74
+ if (respond_to?(:owner) && owner && owner == options[:current_resource_owner]) ||
75
+ options[:as_owner]
76
+ # Owners can see all the client attributes, fallback to ActiveModel serialization
77
+ super
78
+ else
79
+ # if application has no owner or it's owner doesn't match one from the options
80
+ # we render only minimum set of attributes that could be exposed to a public
81
+ only = extract_serializable_attributes(options)
82
+ super(options.merge(only: only))
83
+ end
84
+ end
85
+
63
86
  private
64
87
 
65
88
  def generate_uid
@@ -84,5 +107,48 @@ module Doorkeeper
84
107
  def enforce_scopes?
85
108
  Doorkeeper.configuration.enforce_configured_scopes?
86
109
  end
110
+
111
+ # Helper method to extract collection of serializable attribute names
112
+ # considering serialization options (like `only`, `except` and so on).
113
+ #
114
+ # @param options [Hash] serialization options
115
+ #
116
+ # @return [Array<String>]
117
+ # collection of attributes to be serialized using #as_json
118
+ #
119
+ def extract_serializable_attributes(options = {})
120
+ opts = options.try(:dup) || {}
121
+ only = Array.wrap(opts[:only]).map(&:to_s)
122
+
123
+ only = if only.blank?
124
+ serializable_attributes
125
+ else
126
+ only & serializable_attributes
127
+ end
128
+
129
+ only -= Array.wrap(opts[:except]).map(&:to_s) if opts.key?(:except)
130
+ only.uniq
131
+ end
132
+
133
+ # We need to hook into this method to allow serializing plan-text secrets
134
+ # when secrets hashing enabled.
135
+ #
136
+ # @param key [String] attribute name
137
+ #
138
+ def read_attribute_for_serialization(key)
139
+ return super unless key.to_s == "secret"
140
+
141
+ plaintext_secret || secret
142
+ end
143
+
144
+ # Collection of attributes that could be serialized for public.
145
+ # Override this method if you need additional attributes to be serialized.
146
+ #
147
+ # @return [Array<String>] collection of serializable attributes
148
+ def serializable_attributes
149
+ attributes = %w[id name created_at]
150
+ attributes << "uid" unless confidential?
151
+ attributes
152
+ end
87
153
  end
88
154
  end
@@ -9,7 +9,7 @@ module Doorkeeper
9
9
  # Semantic versioning
10
10
  MAJOR = 5
11
11
  MINOR = 1
12
- TINY = 0
12
+ TINY = 1
13
13
  PRE = nil
14
14
 
15
15
  # Full version number
@@ -166,7 +166,7 @@ describe "doorkeeper authorize filter" do
166
166
  it "it renders a custom JSON response", token: :invalid do
167
167
  get :index, params: { access_token: token_string }
168
168
  expect(response.status).to eq 401
169
- expect(response.content_type).to eq("application/json")
169
+ expect(response.content_type).to match(/application\/json/)
170
170
  expect(response.header["WWW-Authenticate"]).to match(/^Bearer/)
171
171
 
172
172
  expect(json_response).not_to be_nil
@@ -196,7 +196,7 @@ describe "doorkeeper authorize filter" do
196
196
  it "it renders a custom text response", token: :invalid do
197
197
  get :index, params: { access_token: token_string }
198
198
  expect(response.status).to eq 401
199
- expect(response.content_type).to eq("text/plain")
199
+ expect(response.content_type).to match(/text\/plain/)
200
200
  expect(response.header["WWW-Authenticate"]).to match(/^Bearer/)
201
201
  expect(response.body).to eq("Unauthorized")
202
202
  end
@@ -246,7 +246,7 @@ describe "doorkeeper authorize filter" do
246
246
  it "renders a custom JSON response" do
247
247
  get :index, params: { access_token: token_string }
248
248
  expect(response.header).to_not include("WWW-Authenticate")
249
- expect(response.content_type).to eq("application/json")
249
+ expect(response.content_type).to match(/application\/json/)
250
250
  expect(response.status).to eq 403
251
251
 
252
252
  expect(json_response).not_to be_nil
@@ -3,364 +3,469 @@
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"
206
-
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)
279
+ describe "#authorized_for" do
280
+ let(:resource_owner) { FactoryBot.create(:doorkeeper_testing_user) }
281
+ let(:other_resource_owner) { FactoryBot.create(:doorkeeper_testing_user) }
210
282
 
211
- lookup = clazz.by_uid_and_secret(app.uid, app.plaintext_secret)
212
- expect(lookup).to eq(app)
213
- end
214
- end
283
+ it "is empty if the application is not authorized for anyone" do
284
+ expect(described_class.authorized_for(resource_owner)).to be_empty
285
+ end
215
286
 
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
287
+ it "returns only application for a specific resource owner" do
288
+ FactoryBot.create(
289
+ :access_token,
290
+ resource_owner_id: other_resource_owner.id,
291
+ )
292
+ token = FactoryBot.create(
293
+ :access_token,
294
+ resource_owner_id: resource_owner.id,
295
+ )
296
+ expect(described_class.authorized_for(resource_owner)).to eq([token.application])
220
297
  end
221
298
 
222
- describe "destroy related models on cascade" do
223
- before(:each) do
224
- new_application.save
225
- end
299
+ it "excludes revoked tokens" do
300
+ FactoryBot.create(
301
+ :access_token,
302
+ resource_owner_id: resource_owner.id,
303
+ revoked_at: 2.days.ago,
304
+ )
305
+ expect(described_class.authorized_for(resource_owner)).to be_empty
306
+ end
226
307
 
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
308
+ it "returns all applications that have been authorized" do
309
+ token1 = FactoryBot.create(
310
+ :access_token,
311
+ resource_owner_id: resource_owner.id,
312
+ )
313
+ token2 = FactoryBot.create(
314
+ :access_token,
315
+ resource_owner_id: resource_owner.id,
316
+ )
317
+ expect(described_class.authorized_for(resource_owner))
318
+ .to eq([token1.application, token2.application])
319
+ end
231
320
 
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
321
+ it "returns only one application even if it has been authorized twice" do
322
+ application = FactoryBot.create(:application)
323
+ FactoryBot.create(
324
+ :access_token,
325
+ resource_owner_id: resource_owner.id,
326
+ application: application,
327
+ )
328
+ FactoryBot.create(
329
+ :access_token,
330
+ resource_owner_id: resource_owner.id,
331
+ application: application,
332
+ )
333
+ expect(described_class.authorized_for(resource_owner)).to eq([application])
239
334
  end
335
+ end
240
336
 
241
- describe :ordered_by do
242
- let(:applications) { FactoryBot.create_list(:application, 5) }
337
+ describe "#revoke_tokens_and_grants_for" do
338
+ it "revokes all access tokens and access grants" do
339
+ application_id = 42
340
+ resource_owner = double
341
+ expect(Doorkeeper::AccessToken)
342
+ .to receive(:revoke_all_for).with(application_id, resource_owner)
343
+ expect(Doorkeeper::AccessGrant)
344
+ .to receive(:revoke_all_for).with(application_id, resource_owner)
243
345
 
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
346
+ described_class.revoke_tokens_and_grants_for(application_id, resource_owner)
347
+ end
348
+ end
250
349
 
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)
350
+ describe "#by_uid_and_secret" do
351
+ context "when application is private/confidential" do
352
+ it "finds the application via uid/secret" do
353
+ app = FactoryBot.create :application
354
+ authenticated = described_class.by_uid_and_secret(app.uid, app.secret)
355
+ expect(authenticated).to eq(app)
356
+ end
357
+ context "when secret is wrong" do
358
+ it "should not find the application" do
359
+ app = FactoryBot.create :application
360
+ authenticated = described_class.by_uid_and_secret(app.uid, "bad")
361
+ expect(authenticated).to eq(nil)
255
362
  end
256
363
  end
257
364
  end
258
365
 
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")
366
+ context "when application is public/non-confidential" do
367
+ context "when secret is blank" do
368
+ it "should find the application" do
369
+ app = FactoryBot.create :application, confidential: false
370
+ authenticated = described_class.by_uid_and_secret(app.uid, nil)
371
+ expect(authenticated).to eq(app)
264
372
  end
265
373
  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")
374
+ context "when secret is wrong" do
375
+ it "should not find the application" do
376
+ app = FactoryBot.create :application, confidential: false
377
+ authenticated = described_class.by_uid_and_secret(app.uid, "bad")
378
+ expect(authenticated).to eq(nil)
270
379
  end
271
380
  end
272
381
  end
382
+ end
273
383
 
274
- describe :authorized_for do
275
- let(:resource_owner) { double(:resource_owner, id: 10) }
384
+ describe "#confidential?" do
385
+ subject { FactoryBot.create(:application, confidential: confidential).confidential? }
276
386
 
277
- it "is empty if the application is not authorized for anyone" do
278
- expect(Application.authorized_for(resource_owner)).to be_empty
279
- end
387
+ context "when application is private/confidential" do
388
+ let(:confidential) { true }
389
+ it { expect(subject).to eq(true) }
390
+ end
280
391
 
281
- it "returns only application for a specific resource owner" do
282
- FactoryBot.create(:access_token, resource_owner_id: resource_owner.id + 1)
283
- token = FactoryBot.create(:access_token, resource_owner_id: resource_owner.id)
284
- expect(Application.authorized_for(resource_owner)).to eq([token.application])
285
- end
392
+ context "when application is public/non-confidential" do
393
+ let(:confidential) { false }
394
+ it { expect(subject).to eq(false) }
395
+ end
396
+ end
286
397
 
287
- it "excludes revoked tokens" do
288
- FactoryBot.create(:access_token, resource_owner_id: resource_owner.id, revoked_at: 2.days.ago)
289
- expect(Application.authorized_for(resource_owner)).to be_empty
290
- end
398
+ describe "#as_json" do
399
+ let(:app) { FactoryBot.create :application, secret: "123123123" }
291
400
 
292
- it "returns all applications that have been authorized" do
293
- token1 = FactoryBot.create(:access_token, resource_owner_id: resource_owner.id)
294
- token2 = FactoryBot.create(:access_token, resource_owner_id: resource_owner.id)
295
- expect(Application.authorized_for(resource_owner)).to eq([token1.application, token2.application])
296
- end
401
+ before do
402
+ allow(Doorkeeper.configuration)
403
+ .to receive(:application_secret_strategy).and_return(Doorkeeper::SecretStoring::Plain)
404
+ end
297
405
 
298
- it "returns only one application even if it has been authorized twice" do
299
- application = FactoryBot.create(:application)
300
- FactoryBot.create(:access_token, resource_owner_id: resource_owner.id, application: application)
301
- FactoryBot.create(:access_token, resource_owner_id: resource_owner.id, application: application)
302
- expect(Application.authorized_for(resource_owner)).to eq([application])
406
+ # AR specific feature
407
+ if DOORKEEPER_ORM == :active_record
408
+ it "correctly works with #to_json" do
409
+ ActiveRecord::Base.include_root_in_json = true
410
+ expect(app.to_json(include_root_in_json: true)).to match(/application.+?:\{/)
411
+ ActiveRecord::Base.include_root_in_json = false
303
412
  end
304
413
  end
305
414
 
306
- describe :revoke_tokens_and_grants_for do
307
- it "revokes all access tokens and access grants" do
308
- application_id = 42
309
- resource_owner = double
310
- expect(Doorkeeper::AccessToken)
311
- .to receive(:revoke_all_for).with(application_id, resource_owner)
312
- expect(Doorkeeper::AccessGrant)
313
- .to receive(:revoke_all_for).with(application_id, resource_owner)
314
-
315
- Application.revoke_tokens_and_grants_for(application_id, resource_owner)
415
+ context "when called without authorized resource owner" do
416
+ it "includes minimal set of attributes" do
417
+ expect(app.as_json).to match(
418
+ "id" => app.id,
419
+ "name" => app.name,
420
+ "created_at" => an_instance_of(String),
421
+ )
316
422
  end
317
- end
318
423
 
319
- describe :by_uid_and_secret do
320
- context "when application is private/confidential" do
321
- it "finds the application via uid/secret" do
322
- app = FactoryBot.create :application
323
- authenticated = Application.by_uid_and_secret(app.uid, app.secret)
324
- expect(authenticated).to eq(app)
325
- end
326
- context "when secret is wrong" do
327
- it "should not find the application" do
328
- app = FactoryBot.create :application
329
- authenticated = Application.by_uid_and_secret(app.uid, "bad")
330
- expect(authenticated).to eq(nil)
331
- end
332
- end
424
+ it "includes application UID if it's public" do
425
+ app = FactoryBot.create :application, secret: "123123123", confidential: false
426
+
427
+ expect(app.as_json).to match(
428
+ "id" => app.id,
429
+ "name" => app.name,
430
+ "created_at" => an_instance_of(String),
431
+ "uid" => app.uid,
432
+ )
333
433
  end
334
434
 
335
- context "when application is public/non-confidential" do
336
- context "when secret is blank" do
337
- it "should find the application" do
338
- app = FactoryBot.create :application, confidential: false
339
- authenticated = Application.by_uid_and_secret(app.uid, nil)
340
- expect(authenticated).to eq(app)
341
- end
342
- end
343
- context "when secret is wrong" do
344
- it "should not find the application" do
345
- app = FactoryBot.create :application, confidential: false
346
- authenticated = Application.by_uid_and_secret(app.uid, "bad")
347
- expect(authenticated).to eq(nil)
348
- end
349
- end
435
+ it "respects custom options" do
436
+ expect(app.as_json(except: :id)).not_to include("id")
437
+ expect(app.as_json(only: %i[name created_at secret]))
438
+ .to match(
439
+ "name" => app.name,
440
+ "created_at" => an_instance_of(String),
441
+ )
350
442
  end
351
443
  end
352
444
 
353
- describe :confidential? do
354
- subject { FactoryBot.create(:application, confidential: confidential).confidential? }
445
+ context "when called with authorized resource owner" do
446
+ let(:owner) { FactoryBot.create(:doorkeeper_testing_user) }
447
+ let(:other_owner) { FactoryBot.create(:doorkeeper_testing_user) }
448
+ let(:app) { FactoryBot.create(:application, secret: "123123123", owner: owner) }
449
+
450
+ before do
451
+ Doorkeeper.configure do
452
+ orm DOORKEEPER_ORM
453
+ enable_application_owner confirmation: false
454
+ end
455
+ end
355
456
 
356
- context "when application is private/confidential" do
357
- let(:confidential) { true }
358
- it { expect(subject).to eq(true) }
457
+ it "includes all the attributes" do
458
+ expect(app.as_json(current_resource_owner: owner))
459
+ .to include(
460
+ "secret" => "123123123",
461
+ "redirect_uri" => app.redirect_uri,
462
+ "uid" => app.uid,
463
+ )
359
464
  end
360
465
 
361
- context "when application is public/non-confidential" do
362
- let(:confidential) { false }
363
- it { expect(subject).to eq(false) }
466
+ it "doesn't include unsafe attributes if current owner isn't the same as owner" do
467
+ expect(app.as_json(current_resource_owner: other_owner))
468
+ .not_to include("redirect_uri")
364
469
  end
365
470
  end
366
471
  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.1.0
4
+ version: 5.1.1
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: 2019-04-17 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