Skip to content

Make error response not redirectable when client is unauthorized #1435

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ User-visible changes worth mentioning.

## master

- [#1435] Make error response not redirectable when client is unauthorized
- [#1426] Ensure ActiveRecord callbacks are executed on token revocation.
- [#1407] Remove redundant and complex to support helpers froms tests (`should_have_json`, etc).
- [#1416] Don't add introspection route if token introspection completely disabled.
Expand Down
7 changes: 4 additions & 3 deletions lib/doorkeeper/oauth/error_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ module OAuth
class ErrorResponse < BaseResponse
include OAuth::Helpers

NON_REDIRECTABLE_STATES = %i[invalid_redirect_uri invalid_client unauthorized_client].freeze

def self.from_request(request, attributes = {})
new(
attributes.merge(
Expand Down Expand Up @@ -32,16 +34,15 @@ def body
end

def status
if name == :invalid_client
if name == :invalid_client || name == :unauthorized_client
:unauthorized
else
:bad_request
end
end

def redirectable?
name != :invalid_redirect_uri && name != :invalid_client &&
!URIChecker.oob_uri?(@redirect_uri)
!NON_REDIRECTABLE_STATES.include?(name) && !URIChecker.oob_uri?(@redirect_uri)
Comment on lines -43 to +45
Copy link
Contributor Author

@linhdangduy linhdangduy Aug 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nbulaj I added these error names to NON_REDIRECTABLE_STATES

end

def redirect_uri
Expand Down
10 changes: 5 additions & 5 deletions lib/doorkeeper/oauth/pre_authorization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ def validate_client_supports_grant_flow
Doorkeeper.config.allow_grant_flow_for_client?(grant_type, client.application)
end

def validate_resource_owner_authorize_for_client
# The `authorize_resource_owner_for_client` config option is used for this validation
client.application.authorized_for_resource_owner?(@resource_owner)
end

def validate_redirect_uri
return false if redirect_uri.blank?

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

def validate_resource_owner_authorize_for_client
# The `authorize_resource_owner_for_client` config option is used for this validation
client.application.authorized_for_resource_owner?(@resource_owner)
end

def response_on_fragment?
response_type == "token"
end
Expand Down
72 changes: 66 additions & 6 deletions spec/controllers/authorizations_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,37 @@ def query_params
end
end

context "when client can not use grant flow" do
before do
config_is_set(:allow_grant_flow_for_client, ->(*_) { false })
post :create, params: {
client_id: client.uid,
response_type: "token",
redirect_uri: client.redirect_uri,
}
end

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

it "renders 401 error" do
expect(response.status).to eq 401
end

it "includes error name" do
expect(response_json_body["error"]).to eq("unauthorized_client")
end

it "includes error description" do
expect(response_json_body["error_description"]).to eq(
translated_error_message(:unauthorized_client),
)
end

it "does not issue any access token" do
expect(Doorkeeper::AccessToken.all).to be_empty
end
end

context "when user cannot access application" do
before do
allow(Doorkeeper.configuration).to receive(:authorize_resource_owner_for_client).and_return(->(*_) { false })
Expand All @@ -156,7 +187,7 @@ def query_params

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

it "renders 400 error" do
it "renders 401 error" do
expect(response.status).to eq 401
end

Expand Down Expand Up @@ -214,10 +245,10 @@ def query_params
end

describe "POST #create in API mode with errors" do
before { config_is_set(:api_only, true) }

context "when missing client_id" do
before do
allow(Doorkeeper.config).to receive(:api_only).and_return(true)

post :create, params: {
client_id: "",
response_type: "token",
Expand Down Expand Up @@ -246,9 +277,39 @@ def query_params
end
end

context "when client can not use grant flow" do
before do
config_is_set(:allow_grant_flow_for_client, ->(*_) { false })
post :create, params: {
client_id: client.uid,
response_type: "token",
redirect_uri: client.redirect_uri,
}
end

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

it "renders 401 error" do
expect(response.status).to eq 401
end

it "includes error name" do
expect(response_json_body["error"]).to eq("unauthorized_client")
end

it "includes error description" do
expect(response_json_body["error_description"]).to eq(
translated_error_message(:unauthorized_client),
)
end

it "does not issue any access token" do
expect(Doorkeeper::AccessToken.all).to be_empty
end
end

context "when user cannot access application" do
before do
allow(Doorkeeper.configuration).to receive(:api_only).and_return(true)
allow(Doorkeeper.configuration).to receive(:authorize_resource_owner_for_client).and_return(->(*_) { false })

post :create, params: {
Expand All @@ -260,7 +321,7 @@ def query_params

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

it "renders 400 error" do
it "renders 401 error" do
expect(response.status).to eq 401
end

Expand All @@ -281,7 +342,6 @@ def query_params

context "when other error happens" do
before do
allow(Doorkeeper.config).to receive(:api_only).and_return(true)
default_scopes_exist :public

post :create, params: {
Expand Down
38 changes: 38 additions & 0 deletions spec/lib/oauth/error_response_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@

expect(subject.status).to eq(:unauthorized)
end

it "has a status of unauthorized for an unauthorized_client error" do
subject = described_class.new(name: :unauthorized_client)

expect(subject.status).to eq(:unauthorized)
end
end

describe ".from_request" do
Expand Down Expand Up @@ -62,4 +68,36 @@
it { expect(headers).to include("error_description=\"#{error_response.description}\"") }
end
end

describe ".redirectable?" do
it "not redirectable when error name is invalid_redirect_uri" do
subject = described_class.new(name: :invalid_redirect_uri, redirect_uri: "https://example.com")

expect(subject.redirectable?).to be false
end

it "not redirectable when error name is invalid_client" do
subject = described_class.new(name: :invalid_client, redirect_uri: "https://example.com")

expect(subject.redirectable?).to be false
end

it "not redirectable when error name is unauthorized_client" do
subject = described_class.new(name: :unauthorized_client, redirect_uri: "https://example.com")

expect(subject.redirectable?).to be false
end

it "not redirectable when redirect_uri is oob uri" do
subject = described_class.new(name: :other_error, redirect_uri: Doorkeeper::OAuth::NonStandard::IETF_WG_OAUTH2_OOB)

expect(subject.redirectable?).to be false
end

it "is redirectable when error is not related to client or redirect_uri, and redirect_uri is not oob uri" do
subject = described_class.new(name: :other_error, redirect_uri: "https://example.com")

expect(subject.redirectable?).to be true
end
end
end
14 changes: 14 additions & 0 deletions spec/lib/oauth/invalid_request_response_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,18 @@
end
end
end

describe ".redirectable?" do
it "not redirectable when missing_param is client_id" do
subject = described_class.new(missing_param: :client_id)

expect(subject.redirectable?).to be false
end

it "is redirectable when missing_param is other than client_id" do
subject = described_class.new(missing_param: :code_verifier)

expect(subject.redirectable?).to be true
end
end
end
37 changes: 15 additions & 22 deletions spec/lib/oauth/pre_authorization_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,23 @@
}
end

it "is authorizable when request is valid" do
expect(pre_auth).to be_authorizable
it "must call the validations on client and redirect_uri before other validations because they are not redirectable" do
validation_attributes = described_class.validations.map { |validation| validation[:attribute] }

expect(validation_attributes).to eq(%i[
client_id
client
client_supports_grant_flow
resource_owner_authorize_for_client
redirect_uri
params
response_type
scopes
code_challenge_method
])
end

it "accepts code as response type" do
attributes[:response_type] = "code"
expect(pre_auth).to be_authorizable
end

it "accepts token as response type" do
allow(server).to receive(:grant_flows).and_return(["implicit"])
attributes[:response_type] = "token"
it "is authorizable when request is valid" do
expect(pre_auth).to be_authorizable
end

Expand Down Expand Up @@ -77,18 +82,6 @@
end
end

context "when grant flow is client credentials & redirect_uri is nil" do
before do
allow(server).to receive(:grant_flows).and_return(["client_credentials"])
allow(Doorkeeper.configuration).to receive(:allow_grant_flow_for_client?).and_return(false)
application.update_column :redirect_uri, nil
end

it "is not authorizable" do
expect(pre_auth).not_to be_authorizable
end
end

context "when client application does not restrict valid scopes" do
it "accepts valid scopes" do
attributes[:scope] = "public"
Expand Down