web-dev-qa-db-fra.com

Rails vient-il avec une exception "non autorisé"?

J'écris une application qui utilise de vieux objets Ruby (PORO) pour abstraire la logique d'autorisation des contrôleurs.

Actuellement, j'ai une classe d'exception personnalisée appelée NotAuthorized que je rescue_from au niveau du contrôleur, mais j'étais curieux de savoir: Est-ce que Rails 4 vient déjà avec une exception pour indiquer qu'une action n'a pas été autorisée? Suis-je en train de réinventer la roue en mettant en œuvre cette exception?

Clarification: Le raise AuthorizationException ne se produit nulle part à l'intérieur d'un contrôleur, il se produit à l'intérieur d'un PORO complètement découplé à l'extérieur du contrôleur. L'objet n'a aucune connaissance de HTTP, des routes ou des contrôleurs.

31
Rick

Rails ne semble pas mapper une exception à :unauthorized.

Les mappages par défaut sont définis dans activerecord/lib/active_record/railtie.rb :

config.action_dispatch.rescue_responses.merge!(
  'ActiveRecord::RecordNotFound'   => :not_found,
  'ActiveRecord::StaleObjectError' => :conflict,
  'ActiveRecord::RecordInvalid'    => :unprocessable_entity,
  'ActiveRecord::RecordNotSaved'   => :unprocessable_entity
)

et actionpack/lib/action_dispatch/middleware/exception_wrapper.rb :

@@rescue_responses.merge!(
  'ActionController::RoutingError'             => :not_found,
  'AbstractController::ActionNotFound'         => :not_found,
  'ActionController::MethodNotAllowed'         => :method_not_allowed,
  'ActionController::UnknownHttpMethod'        => :method_not_allowed,
  'ActionController::NotImplemented'           => :not_implemented,
  'ActionController::UnknownFormat'            => :not_acceptable,
  'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
  'ActionDispatch::ParamsParser::ParseError'   => :bad_request,
  'ActionController::BadRequest'               => :bad_request,
  'ActionController::ParameterMissing'         => :bad_request
)

Vous pouvez ajouter une exception personnalisée à partir de la configuration de votre application (ou un railtie personnalisé ):

Your::Application.configure do

  config.action_dispatch.rescue_responses.merge!(
    'AuthorizationException' => :unauthorized
  )

  # ...

end

Ou utilisez simplement rescue_from.

31
Stefan

Je suppose que la raison Rails n'a pas introduit cette exception est parce que l'autorisation et l'authentification ne sont pas Rails comportement natif (ne tenant pas compte de l'authentification de base bien sûr).

Habituellement, ce sont les responsabilités des autres bibliothèques Devise pour NotAuthenticated; Pundit , CanCanCan, Rollify for NotAuthorized) Je dirais en fait que ce peut être une mauvaise chose d'étendre ActionController avec des exceptions personnalisées comme ActionController::NotAuthorized (car comme je l'ai dit ce n'est pas sa responsabilité)

Donc, la façon dont j'ai habituellement résolu ce problème est que j'ai introduit des exceptions personnalisées sur ApplicationController

class ApplicationController  < ActionController::Base
  NotAuthorized = Class.new(StandardError)
  # ...or if you really want it to be ActionController
  # NotAuthorized = Class.new(ActionController::RoutingError)

  rescue_from ActiveRecord::RecordNotFound do |exception|
    render_error_page(status: 404, text: 'Not found')
  end

  rescue_from ApplicationController::NotAuthorized do |exception|
    render_error_page(status: 403, text: 'Forbidden')
  end

  private

  def render_error_page(status:, text:, template: 'errors/routing')
    respond_to do |format|
      format.json { render json: {errors: [message: "#{status} #{text}"]}, status: status }
      format.html { render template: template, status: status, layout: false }
      format.any  { head status }
    end
  end
end

Par conséquent, dans mes contrôleurs, je peux faire

class MyStuff < ApplicationController
  def index
    if current_user.admin?
      # ....
    else 
      raise ApplicationController::NotAuthorized
    end
  end
end

Cela définit clairement que la couche que vous attendez que cette exception soit levée et interceptée est votre couche d'application, et non une bibliothèque tierce.

Le fait est que les bibliothèques peuvent changer (et oui, cela signifie Rails aussi) définir une exception sur des classes de bibliothèque tierces et les sauver dans votre couche d'application est vraiment dangereux comme si la signification de la classe d'exception change ça freine votre rescue_from

Vous pouvez lire de nombreux articles sur lesquels les gens font des réserves Rails raise - rescue_from étant le moderne goto (envisageant maintenant l'anti-pattern parmi certains experts) et dans une certaine mesure c'est vrai, mais seulement si vous sauvez des exceptions que vous n'avez pas le contrôle total !!

Cela signifie des exceptions tierces (y compris Devise et Rails jusqu'à un certain point). Si vous définissez les classes d'exceptions dans votre application, vous ne relayez pas sur une librairie tierce = = vous avez le contrôle total => vous pouvez rescue_from sans que ce soit un anti-motif.

21
equivalent8