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.
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
.
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.