Skip to content

Commit d8761e0

Browse files
authored
Merge pull request #1435 from linhdangduy/not_redirectable_when_client_is_unauthorized
Make error response not redirectable when client is unauthorized
2 parents 2fd1556 + bf9d348 commit d8761e0

File tree

7 files changed

+143
-36
lines changed

7 files changed

+143
-36
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ User-visible changes worth mentioning.
77

88
## master
99

10+
- [#1435] Make error response not redirectable when client is unauthorized
1011
- [#1426] Ensure ActiveRecord callbacks are executed on token revocation.
1112
- [#1407] Remove redundant and complex to support helpers froms tests (`should_have_json`, etc).
1213
- [#1416] Don't add introspection route if token introspection completely disabled.

lib/doorkeeper/oauth/error_response.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ module OAuth
55
class ErrorResponse < BaseResponse
66
include OAuth::Helpers
77

8+
NON_REDIRECTABLE_STATES = %i[invalid_redirect_uri invalid_client unauthorized_client].freeze
9+
810
def self.from_request(request, attributes = {})
911
new(
1012
attributes.merge(
@@ -32,16 +34,15 @@ def body
3234
end
3335

3436
def status
35-
if name == :invalid_client
37+
if name == :invalid_client || name == :unauthorized_client
3638
:unauthorized
3739
else
3840
:bad_request
3941
end
4042
end
4143

4244
def redirectable?
43-
name != :invalid_redirect_uri && name != :invalid_client &&
44-
!URIChecker.oob_uri?(@redirect_uri)
45+
!NON_REDIRECTABLE_STATES.include?(name) && !URIChecker.oob_uri?(@redirect_uri)
4546
end
4647

4748
def redirect_uri

lib/doorkeeper/oauth/pre_authorization.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ def validate_client_supports_grant_flow
8484
Doorkeeper.config.allow_grant_flow_for_client?(grant_type, client.application)
8585
end
8686

87+
def validate_resource_owner_authorize_for_client
88+
# The `authorize_resource_owner_for_client` config option is used for this validation
89+
client.application.authorized_for_resource_owner?(@resource_owner)
90+
end
91+
8792
def validate_redirect_uri
8893
return false if redirect_uri.blank?
8994

@@ -125,11 +130,6 @@ def validate_code_challenge_method
125130
(code_challenge_method.present? && code_challenge_method =~ /^plain$|^S256$/)
126131
end
127132

128-
def validate_resource_owner_authorize_for_client
129-
# The `authorize_resource_owner_for_client` config option is used for this validation
130-
client.application.authorized_for_resource_owner?(@resource_owner)
131-
end
132-
133133
def response_on_fragment?
134134
response_type == "token"
135135
end

spec/controllers/authorizations_controller_spec.rb

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,37 @@ def query_params
144144
end
145145
end
146146

147+
context "when client can not use grant flow" do
148+
before do
149+
config_is_set(:allow_grant_flow_for_client, ->(*_) { false })
150+
post :create, params: {
151+
client_id: client.uid,
152+
response_type: "token",
153+
redirect_uri: client.redirect_uri,
154+
}
155+
end
156+
157+
let(:response_json_body) { JSON.parse(response.body) }
158+
159+
it "renders 401 error" do
160+
expect(response.status).to eq 401
161+
end
162+
163+
it "includes error name" do
164+
expect(response_json_body["error"]).to eq("unauthorized_client")
165+
end
166+
167+
it "includes error description" do
168+
expect(response_json_body["error_description"]).to eq(
169+
translated_error_message(:unauthorized_client),
170+
)
171+
end
172+
173+
it "does not issue any access token" do
174+
expect(Doorkeeper::AccessToken.all).to be_empty
175+
end
176+
end
177+
147178
context "when user cannot access application" do
148179
before do
149180
allow(Doorkeeper.configuration).to receive(:authorize_resource_owner_for_client).and_return(->(*_) { false })
@@ -156,7 +187,7 @@ def query_params
156187

157188
let(:response_json_body) { JSON.parse(response.body) }
158189

159-
it "renders 400 error" do
190+
it "renders 401 error" do
160191
expect(response.status).to eq 401
161192
end
162193

@@ -214,10 +245,10 @@ def query_params
214245
end
215246

216247
describe "POST #create in API mode with errors" do
248+
before { config_is_set(:api_only, true) }
249+
217250
context "when missing client_id" do
218251
before do
219-
allow(Doorkeeper.config).to receive(:api_only).and_return(true)
220-
221252
post :create, params: {
222253
client_id: "",
223254
response_type: "token",
@@ -246,9 +277,39 @@ def query_params
246277
end
247278
end
248279

280+
context "when client can not use grant flow" do
281+
before do
282+
config_is_set(:allow_grant_flow_for_client, ->(*_) { false })
283+
post :create, params: {
284+
client_id: client.uid,
285+
response_type: "token",
286+
redirect_uri: client.redirect_uri,
287+
}
288+
end
289+
290+
let(:response_json_body) { JSON.parse(response.body) }
291+
292+
it "renders 401 error" do
293+
expect(response.status).to eq 401
294+
end
295+
296+
it "includes error name" do
297+
expect(response_json_body["error"]).to eq("unauthorized_client")
298+
end
299+
300+
it "includes error description" do
301+
expect(response_json_body["error_description"]).to eq(
302+
translated_error_message(:unauthorized_client),
303+
)
304+
end
305+
306+
it "does not issue any access token" do
307+
expect(Doorkeeper::AccessToken.all).to be_empty
308+
end
309+
end
310+
249311
context "when user cannot access application" do
250312
before do
251-
allow(Doorkeeper.configuration).to receive(:api_only).and_return(true)
252313
allow(Doorkeeper.configuration).to receive(:authorize_resource_owner_for_client).and_return(->(*_) { false })
253314

254315
post :create, params: {
@@ -260,7 +321,7 @@ def query_params
260321

261322
let(:response_json_body) { JSON.parse(response.body) }
262323

263-
it "renders 400 error" do
324+
it "renders 401 error" do
264325
expect(response.status).to eq 401
265326
end
266327

@@ -281,7 +342,6 @@ def query_params
281342

282343
context "when other error happens" do
283344
before do
284-
allow(Doorkeeper.config).to receive(:api_only).and_return(true)
285345
default_scopes_exist :public
286346

287347
post :create, params: {

spec/lib/oauth/error_response_spec.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313

1414
expect(subject.status).to eq(:unauthorized)
1515
end
16+
17+
it "has a status of unauthorized for an unauthorized_client error" do
18+
subject = described_class.new(name: :unauthorized_client)
19+
20+
expect(subject.status).to eq(:unauthorized)
21+
end
1622
end
1723

1824
describe ".from_request" do
@@ -62,4 +68,36 @@
6268
it { expect(headers).to include("error_description=\"#{error_response.description}\"") }
6369
end
6470
end
71+
72+
describe ".redirectable?" do
73+
it "not redirectable when error name is invalid_redirect_uri" do
74+
subject = described_class.new(name: :invalid_redirect_uri, redirect_uri: "https://example.com")
75+
76+
expect(subject.redirectable?).to be false
77+
end
78+
79+
it "not redirectable when error name is invalid_client" do
80+
subject = described_class.new(name: :invalid_client, redirect_uri: "https://example.com")
81+
82+
expect(subject.redirectable?).to be false
83+
end
84+
85+
it "not redirectable when error name is unauthorized_client" do
86+
subject = described_class.new(name: :unauthorized_client, redirect_uri: "https://example.com")
87+
88+
expect(subject.redirectable?).to be false
89+
end
90+
91+
it "not redirectable when redirect_uri is oob uri" do
92+
subject = described_class.new(name: :other_error, redirect_uri: Doorkeeper::OAuth::NonStandard::IETF_WG_OAUTH2_OOB)
93+
94+
expect(subject.redirectable?).to be false
95+
end
96+
97+
it "is redirectable when error is not related to client or redirect_uri, and redirect_uri is not oob uri" do
98+
subject = described_class.new(name: :other_error, redirect_uri: "https://example.com")
99+
100+
expect(subject.redirectable?).to be true
101+
end
102+
end
65103
end

spec/lib/oauth/invalid_request_response_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,18 @@
5858
end
5959
end
6060
end
61+
62+
describe ".redirectable?" do
63+
it "not redirectable when missing_param is client_id" do
64+
subject = described_class.new(missing_param: :client_id)
65+
66+
expect(subject.redirectable?).to be false
67+
end
68+
69+
it "is redirectable when missing_param is other than client_id" do
70+
subject = described_class.new(missing_param: :code_verifier)
71+
72+
expect(subject.redirectable?).to be true
73+
end
74+
end
6175
end

spec/lib/oauth/pre_authorization_spec.rb

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,23 @@
2727
}
2828
end
2929

30-
it "is authorizable when request is valid" do
31-
expect(pre_auth).to be_authorizable
30+
it "must call the validations on client and redirect_uri before other validations because they are not redirectable" do
31+
validation_attributes = described_class.validations.map { |validation| validation[:attribute] }
32+
33+
expect(validation_attributes).to eq(%i[
34+
client_id
35+
client
36+
client_supports_grant_flow
37+
resource_owner_authorize_for_client
38+
redirect_uri
39+
params
40+
response_type
41+
scopes
42+
code_challenge_method
43+
])
3244
end
3345

34-
it "accepts code as response type" do
35-
attributes[:response_type] = "code"
36-
expect(pre_auth).to be_authorizable
37-
end
38-
39-
it "accepts token as response type" do
40-
allow(server).to receive(:grant_flows).and_return(["implicit"])
41-
attributes[:response_type] = "token"
46+
it "is authorizable when request is valid" do
4247
expect(pre_auth).to be_authorizable
4348
end
4449

@@ -77,18 +82,6 @@
7782
end
7883
end
7984

80-
context "when grant flow is client credentials & redirect_uri is nil" do
81-
before do
82-
allow(server).to receive(:grant_flows).and_return(["client_credentials"])
83-
allow(Doorkeeper.configuration).to receive(:allow_grant_flow_for_client?).and_return(false)
84-
application.update_column :redirect_uri, nil
85-
end
86-
87-
it "is not authorizable" do
88-
expect(pre_auth).not_to be_authorizable
89-
end
90-
end
91-
9285
context "when client application does not restrict valid scopes" do
9386
it "accepts valid scopes" do
9487
attributes[:scope] = "public"

0 commit comments

Comments
 (0)