web-dev-qa-db-ja.com

Railsには「認証されていない」例外がありますか?

私は、プレーンな古いRubyオブジェクト(PORO)を使用して、コントローラーから承認ロジックを抽象化するアプリケーションを作成しています。

現在、コントローラレベルでrescue_fromというNotAuthorizedというカスタム例外クラスがありますが、知りたいと思っていました:Does Rails 4 already come この例外を実装することで車輪を再発明していますか?

明確化raise AuthorizationExceptionはコントローラーの内部では発生していません。コントローラーの外部で完全に分離されたPOROの内部で発生しています。オブジェクトには、HTTP、ルート、またはコントローラーに関する知識がありません。

31
Rick

Railsは:unauthorizedに例外をマップしないようです。

デフォルトのマッピングは 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
)

および 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
)

アプリケーションの構成内からカスタム例外を追加できます(またはカスタム Railtie ):

Your::Application.configure do

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

  # ...

end

または、単にrescue_fromを使用します。

31
Stefan

私は理由を推測していますRailsこの例外を導入しなかったのは、認証と認証がRailsネイティブ動作(もちろん、基本認証を考慮しない)。

通常、これらは他のライブラリの責任です Devise NotAuthenticated; Pundit 、CanCanCan、NotAuthorizedのRollify)実際にActionControllerActionController::NotAuthorizedのようなカスタム例外で拡張するのは悪いことかもしれないと主張します責任)

私がこの問題にどのように取り組んだかというと、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

したがって、私のコントローラーでできること

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

これは、この例外が発生してキャッチされることを期待している層が、サードパーティのライブラリではなく、アプリケーション層であることを明確に定義しています。

問題は、ライブラリが変更される可能性があることです(そして、これはRailsも意味します)サードパーティのlibクラスで例外を定義し、アプリケーション層でそれらを救済することは、例外クラスの意味が変わるかのように本当に危険です) rescue_fromにブレーキをかけます

Rails raise-rescue_fromが現代のgotoであることに注意を払っている多くの記事を読むことができます。専門家)そして特定の拡張ではそれは真実ですが、あなたが完全に制御できない例外を救出している場合にのみ!!

つまり、サードパーティの例外(DeviseおよびRails特定のポイントまで)を含みます。アプリケーションで例外クラスを定義する場合、サードパーティのlibにリレーしていません=>フルコントロール=>これをアンチパターンにせずにrescue_fromできます。

21
equivalent8