Skip to content
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

Prevent requested scope be empty on authorization request, handle and add description for invalid request error #1277

Merged
merged 15 commits into from
Aug 28, 2019
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 @@ -15,6 +15,7 @@ User-visible changes worth mentioning.
- [#1286] Add ability to customize grant flows per application (OAuth client) (#1245 , #1207)
- [#1283] Allow to customize base class for `Doorkeeper::ApplicationMetalController` (new configuration
option called `base_metal_controller` (fix #1273).
- [#1277] Prevent requested scope be empty on authorization request, handle and add description for invalid request.

## 5.2.0.rc2

Expand Down
8 changes: 4 additions & 4 deletions app/controllers/doorkeeper/authorizations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ def new
end
end

# TODO: Handle raise invalid authorization
def create
redirect_or_render authorize_response
end
Expand Down Expand Up @@ -84,10 +83,11 @@ def strategy

def authorize_response
@authorize_response ||= begin
authorizable = pre_auth.authorizable?
before_successful_authorization if authorizable
return pre_auth.error_response unless pre_auth.authorizable?

before_successful_authorization
auth = strategy.authorize
after_successful_authorization if authorizable
after_successful_authorization
auth
end
end
Expand Down
6 changes: 5 additions & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ en:
errors:
messages:
# Common error messages
invalid_request: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
invalid_request:
unknown: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
missing_param: 'Missing required parameter: #{value}.'
not_support_pkce: 'Invalid code_verifier parameter. Server does not support pkce.'
request_not_authorized: 'Request need to be authorized. Required parameter for authorizing request is missing or invalid.'
invalid_redirect_uri: "The requested redirect uri is malformed or doesn't match client redirect URI."
unauthorized_client: 'The client is not authorized to perform this request using this method.'
access_denied: 'The resource owner or authorization server denied the request.'
Expand Down
1 change: 1 addition & 0 deletions lib/doorkeeper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
require "doorkeeper/oauth/token_introspection"
require "doorkeeper/oauth/invalid_token_response"
require "doorkeeper/oauth/forbidden_token_response"
require "doorkeeper/oauth/invalid_request_response"
require "doorkeeper/oauth/nonstandard"

require "doorkeeper/secret_storing/base"
Expand Down
21 changes: 8 additions & 13 deletions lib/doorkeeper/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,6 @@ def type
end
end

class InvalidAuthorizationStrategy < DoorkeeperError
def type
:unsupported_response_type
end
end

class InvalidTokenReuse < DoorkeeperError
def type
:invalid_request
end
end

class InvalidGrantReuse < DoorkeeperError
def type
:invalid_grant
Expand All @@ -32,7 +20,14 @@ def type
end
end

class MissingRequestStrategy < DoorkeeperError
class MissingRequiredParameter < DoorkeeperError
nbulaj marked this conversation as resolved.
Show resolved Hide resolved
attr_reader :missing_param

def initialize(missing_param)
super
@missing_param = missing_param
end

def type
:invalid_request
end
Expand Down
4 changes: 4 additions & 0 deletions lib/doorkeeper/helpers/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ def config_methods
def get_error_response_from_exception(exception)
if exception.respond_to?(:response)
exception.response
elsif exception.type == :invalid_request
OAuth::InvalidRequestResponse.new(name: exception.type,
state: params[:state],
missing_param: exception.missing_param)
else
OAuth::ErrorResponse.new(name: exception.type, state: params[:state])
end
Expand Down
27 changes: 18 additions & 9 deletions lib/doorkeeper/oauth/authorization_code_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
module Doorkeeper
module OAuth
class AuthorizationCodeRequest < BaseRequest
validate :attributes, error: :invalid_request
validate :pkce_support, error: :invalid_request
validate :params, error: :invalid_request
validate :client, error: :invalid_client
validate :grant, error: :invalid_grant
# @see https://tools.ietf.org/html/rfc6749#section-5.2
Expand All @@ -12,6 +13,7 @@ class AuthorizationCodeRequest < BaseRequest

attr_accessor :server, :grant, :client, :redirect_uri, :access_token,
:code_verifier
attr_reader :invalid_request_reason, :missing_param

def initialize(server, grant, client, parameters = {})
@server = server
Expand All @@ -24,10 +26,6 @@ def initialize(server, grant, client, parameters = {})

private

def client_by_uid(parameters)
Doorkeeper::Application.by_uid(parameters[:client_id])
end

def before_successful_response
grant.transaction do
grant.lock!
Expand All @@ -42,11 +40,22 @@ def before_successful_response
super
end

def validate_attributes
return false if grant&.uses_pkce? && code_verifier.blank?
return false if grant && !grant.pkce_supported? && !code_verifier.blank?
def validate_pkce_support
@invalid_request_reason = :not_support_pkce if grant &&
!grant.pkce_supported? &&
code_verifier.present?

@invalid_request_reason.nil?
end

def validate_params
nbulaj marked this conversation as resolved.
Show resolved Hide resolved
@missing_param = if grant&.uses_pkce? && code_verifier.blank?
:code_verifier
elsif redirect_uri.blank?
:redirect_uri
end

redirect_uri.present?
@missing_param.nil?
end

def validate_client
Expand Down
2 changes: 2 additions & 0 deletions lib/doorkeeper/oauth/base_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ def authorize
@response = TokenResponse.new(access_token)
after_successful_response
@response
elsif error == :invalid_request
@response = InvalidRequestResponse.from_request(self)
else
@response = ErrorResponse.from_request(self)
end
Expand Down
19 changes: 5 additions & 14 deletions lib/doorkeeper/oauth/code_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,22 @@
module Doorkeeper
module OAuth
class CodeRequest
attr_accessor :pre_auth, :resource_owner, :client
attr_accessor :pre_auth, :resource_owner

def initialize(pre_auth, resource_owner)
@pre_auth = pre_auth
@client = pre_auth.client
@resource_owner = resource_owner
end

def authorize
if pre_auth.authorizable?
auth = Authorization::Code.new(pre_auth, resource_owner)
auth.issue_token
@response = CodeResponse.new(pre_auth, auth)
else
@response = ErrorResponse.from_request(pre_auth)
end
auth = Authorization::Code.new(pre_auth, resource_owner)
auth.issue_token
CodeResponse.new(pre_auth, auth)
end

def deny
pre_auth.error = :access_denied

ErrorResponse.from_request(
pre_auth,
redirect_uri: pre_auth.redirect_uri
)
pre_auth.error_response
end
end
end
Expand Down
43 changes: 43 additions & 0 deletions lib/doorkeeper/oauth/invalid_request_response.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

module Doorkeeper
module OAuth
class InvalidRequestResponse < ErrorResponse
nbulaj marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor Author

@linhdangduy linhdangduy Jul 13, 2019

Choose a reason for hiding this comment

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

Introduce InvalidRequestResponse (error response) to handle invalid_request error.

  • At this class, I create error_description for invalid_request error by two following variable: missing_parameter (for missing required parameter) and invalid_request_reason (for reason other than missing_parameter: E.g. :request_not_authorized at token_introspection.rb, or :not_support_pkce at authorization_code_request.rb)

attr_reader :reason

def self.from_request(request, attributes = {})
new(
attributes.merge(
state: request.try(:state),
redirect_uri: request.try(:redirect_uri),
missing_param: request.try(:missing_param),
reason: request.try(:invalid_request_reason)
)
)
end

def initialize(attributes = {})
super(attributes.merge(name: :invalid_request))
@missing_param = attributes[:missing_param]
@reason = @missing_param.nil? ? attributes[:reason] : :missing_param
end

def status
:bad_request
end

def description
I18n.translate(
reason,
scope: %i[doorkeeper errors messages invalid_request],
default: :unknown,
value: @missing_param
)
end

def redirectable?
super && @missing_param != :client_id
end
end
end
end
64 changes: 43 additions & 21 deletions lib/doorkeeper/oauth/pre_authorization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ module OAuth
class PreAuthorization
include Validations

validate :response_type, error: :unsupported_response_type
validate :client, error: :invalid_client
validate :scopes, error: :invalid_scope
validate :redirect_uri, error: :invalid_redirect_uri
validate :client_id, error: :invalid_request
validate :client, error: :invalid_client
validate :redirect_uri, error: :invalid_redirect_uri
Copy link
Contributor Author

@linhdangduy linhdangduy Aug 2, 2019

Choose a reason for hiding this comment

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

Because with missing or invalid client and redirect_uri param, AuthorizationsController must not redirect client to redirect_uri. So I change the order of validate: Firstly, checked client and redirect_uri param. This order makes sure that after these two validation, ErrorResponse is always redirectable

validate :params, error: :invalid_request
validate :response_type, error: :unsupported_response_type
validate :scopes, error: :invalid_scope
validate :code_challenge_method, error: :invalid_code_challenge_method
validate :client_supports_grant_flow, error: :unauthorized_client

attr_reader :server, :client, :client_id, :response_type, :redirect_uri,
:state, :code_challenge, :code_challenge_method
attr_reader :server, :client_id, :client, :redirect_uri, :response_type, :state,
:code_challenge, :code_challenge_method, :missing_param

def initialize(server, attrs = {})
@server = server
Expand All @@ -39,11 +41,17 @@ def scopes
end

def scope
@scope.presence || build_scopes
@scope.presence || (server.default_scopes.presence && build_scopes)
nbulaj marked this conversation as resolved.
Show resolved Hide resolved
end

def error_response
OAuth::ErrorResponse.from_request(self)
is_implicit_flow = response_type == "token"

if error == :invalid_request
OAuth::InvalidRequestResponse.from_request(self, response_on_fragment: is_implicit_flow)
else
OAuth::ErrorResponse.from_request(self, response_on_fragment: is_implicit_flow)
end
end

def as_json(attributes = {})
Expand All @@ -63,18 +71,41 @@ def build_scopes
end
end

def validate_response_type
server.authorization_response_types.include?(response_type)
def validate_client_id
@missing_param = :client_id if client_id.blank?

@missing_param.nil?
end

def validate_client
@client = OAuth::Client.find(client_id)
@client.present?
end

def validate_scopes
return true if scope.blank?
def validate_redirect_uri
return false if redirect_uri.blank?

Helpers::URIChecker.valid_for_authorization?(
redirect_uri,
client.redirect_uri
)
end

def validate_params
@missing_param = if response_type.blank?
:response_type
elsif @scope.blank? && server.default_scopes.blank?
:scope
end

@missing_param.nil?
end

def validate_response_type
server.authorization_response_types.include?(response_type)
end

def validate_scopes
Helpers::ScopeChecker.valid?(
scope_str: scope,
server_scopes: server.scopes,
Expand All @@ -87,15 +118,6 @@ def grant_type
response_type == "code" ? AUTHORIZATION_CODE : IMPLICIT
end

def validate_redirect_uri
return false if redirect_uri.blank?

Helpers::URIChecker.valid_for_authorization?(
redirect_uri,
client.redirect_uri
)
end

def validate_code_challenge_method
code_challenge.blank? ||
(code_challenge_method.present? && code_challenge_method =~ /^plain$|^S256$/)
Expand Down
7 changes: 5 additions & 2 deletions lib/doorkeeper/oauth/refresh_token_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class RefreshTokenRequest < BaseRequest

attr_accessor :access_token, :client, :credentials, :refresh_token,
:server
attr_reader :missing_param

def initialize(server, refresh_token, credentials, parameters = {})
@server = server
Expand All @@ -32,7 +33,7 @@ def load_client(credentials)
def before_successful_response
refresh_token.transaction do
refresh_token.lock!
raise Errors::InvalidTokenReuse if refresh_token.revoked?
raise Errors::InvalidGrantReuse if refresh_token.revoked?
Copy link
Member

Choose a reason for hiding this comment

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

What's the reason to change this class? 🤔

Copy link
Contributor Author

@linhdangduy linhdangduy Aug 27, 2019

Choose a reason for hiding this comment

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

As I describe at PR's description: 3. When refresh_token is revoked
because refresh_token is revoked, so we should return invalid_grant error as rfc's error response definition

invalid_grant
The provided authorization grant (e.g., authorization
code, resource owner credentials) or refresh token is
invalid, expired, revoked,
does not match the redirection
URI used in the authorization request, or was issued to
another client.

we can see that Errors::InvalidGrantReuse returns invalid_grant, while InvalidTokenReuse returns invalid_request.

Copy link
Member

Choose a reason for hiding this comment

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

Ahh, sorry man, too much I need to check and do after vacation 🙇‍♂️
👍


refresh_token.revoke unless refresh_token_revoked_on_use?
create_access_token
Expand Down Expand Up @@ -76,7 +77,9 @@ def access_token_expires_in
end

def validate_token_presence
refresh_token.present? || @refresh_token_parameter.present?
@missing_param = :refresh_token if refresh_token.blank? && @refresh_token_parameter.blank?

@missing_param.nil?
end

def validate_token
Expand Down
Loading