Rails简单的访问控制

时间:2015-02-24 21:20:52

标签: ruby-on-rails ruby access-control

我知道有几个宝石可以在Rails中处理授权。但是使用这些宝石进行简单的访问控制真的值得吗?

我的应用程序中只有一些“角色”,我觉得强大的宝石会毫无用处,甚至会缩短响应时间。

我已经实现了一个解决方案,但后来我接受了一些安全类(:p)并且我意识到我的模型是错误的(“默认允许,然后限制”而不是“默认拒绝,然后允许”)。< / p>

现在我怎样才能简单地实现“默认拒绝,允许特定情况”?

基本上我想放在ApplicationController的最顶端

class ApplicationController < ApplicationController::Base
  before_filter :deny_access

在我的其他控制器的最顶端:

class some_controller < ApplicationController
  before_filter :allow_access_to_[entity/user]

这些allow_access_to_ before_filters应该执行skip_before_filter

之类的操作
def allow_access_to_[...]
  skip_before_filter(:deny_access) if condition
end

但这不起作用,因为在allow_access before_filter

之前未评估过滤器之前的deny_access

任何解决方法,这种自定义访问控制实现的更好解决方案?

修改

  • 许多非RESTful操作
  • 我需要按操作访问控制
  • undefined method 'skip_before_filter' for #<MyController...为什么?
  • 我的before_filters可能会变得棘手
before_action :find_project, except: [:index, :new, :create]
before_action(except: [:show, :index, :new, :create]) do |c|
   c.restrict_access_to_manager(@project.manager)
end

4 个答案:

答案 0 :(得分:3)

我真的建议使用经过适当战斗测试的宝石进行身份验证&amp;授权而不是滚动自己。这些宝石拥有巨大的测试套件,并且真的难以设置。

我最近使用Pundit&amp;的角色实施了基于操作的授权。 Devise

只要您使用的宝石提供current_user方法,如果您不想进一步配置权威,设计就是可变的。

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include Pundit

  rescue_from Pundit::NotAuthorizedError, with: :rescue_unauthorized

  # Lock actions untill authorization is performed
  before_action :authorize_user

  # Fallback when not authorized
  def rescue_unauthorized(exception)
    policy_name = exception.policy.class.to_s.underscore
    flash[:notice] = t(
      "#{policy_name}.#{exception.query}",
      scope: "pundit",
      default: :default
    )
    redirect_to(request.referrer || root_path)
  end
end

# app/models/user.rb
class User < ActiveRecord::Base
  has_many :roles, through: :memberships

  def authorized?(action)
    claim = String(action)
    roles.pluck(:claim).any? { |role_claim| role_claim == claim }
  end
end

# app/policies/user_policy.rb => maps to user_controller#actions
class UserPolicy < ApplicationPolicy
  class Scope < Scope
    attr_reader :user, :scope

    # user is automagically set to current_user
    def initialize(user, scope)
      @user = user
      @scope = scope
    end

    def resolve
      scope.all
    end
  end

  def index?
    # If user has a role which has the claim :view_users
    # Allow this user to use the user#index action
    @user.authorized? :view_users 
  end

  def new?
    @user.authorized? :new_users
  end

  def edit?
    @user.authorized? :edit_users
  end

  def create?
    new?
  end

  def update?
    edit?
  end

  def destroy?
    @user.authorized? :destroy_users
  end
end

长话短说:

如果您将pundit配置为强制对github页面上详细描述的每个请求进行授权,则控制器将根据使用的控制器评估策略。

UserController -> UserPolicy

使用问号定义操作,甚至是非静止路径。

def index?
  # authorization is done inside the method.
  # true = authorization succes
  # false = authorization failure
end

这是我基于行动的授权解决方案希望它能帮到你。

优化&amp;欢迎提出反馈!

答案 1 :(得分:1)

只要您致力于推动自己的实施并不一定是坏事。

社区不会对其进行测试和维护,因此从长远来看,您必须愿意自己维护它,如果它危及安全性,您需要确定自己正在做的事情,并采取额外的谨慎措施。如果你已经覆盖了现有的替代品并不能满足你的需求,那么制作你自己的替代品并不是一个坏主意。一般来说,这是一次非常好的学习经历。

我用ActionAccess滚动自己,我对结果感到高兴。

  • 默认锁定 aproach:

    class ApplicationController < ActionController::Base
      lock_access
    
      # ...
    end
    
  • 每次操作访问控制:

    class ArticlesController < ApplicationController
      let :admins, :all
      let :editors, [:index, :show, :edit, :update]
      let :all, [:index, :show]
    
      def index
        # ...
      end
    
      # ...
    end
    
  • 真正轻量级实施。

我鼓励你不要使用它,但要查看源代码,它有一些评论的票价,应该是一个很好的灵感来源。 ControllerAdditions可能是一个很好的起点。

ActionAccess在内部遵循不同的方法,但你可以重构你的答案来模仿它的API,如下所示:

module AccessControl
  extend ActiveSupport::Concern

  included do
    before_filter :lock_access
  end

  module ClassMethods
    def lock_access
      unless @authorized
        # Redirect user...
      end
    end

    def allow_manager_to(actions = [])
      prepend_before_action only: actions do
        @authorized = true if current_user_is_a_manager?
      end
    end
  end
end

class ApplicationController < ActionController::Base
  include AccessControl  # Locked by default

  # ...
end

class ProjectController < ApplicationController
  allow_managers_to [:edit, :update]  # Per-action access control

  # ...
end

以伪代码为例,我没有测试过。

希望这有帮助。

答案 2 :(得分:0)

编辑:过时的答案,我有一个更友好的实施,涉及使用access_control

与evanbikes建议一起,现在我将使用prepend_before动作。我发现它非常简单&amp;灵活,但如果我意识到它不够好,我会尝试其他的东西。

此外,如果您发现以下解决方案存在安全问题/其他问题,请发表评论和/或关注。我不喜欢在SO中留下不好的例子。

class ApplicationController < ApplicationController::Base
  include AccessControl
  before_filter :access_denied
  ...

我的访问控制模块

module AccessControl
  extend ActiveSupport::Concern
  included do
    def access_denied(message: nil)
        unless @authorized
            flash.alert = 'Unauthorized access'
            flash.info = "Authorized entities : #{@authorized_entities.join(', '}" if @authorized_entities
            render 'static_pages/home', :status => :unauthorized
            end
        end

        def allow_access_to_managers
            (@authorized_entities ||= []) << "Project managers"
            @authorized = true if manager_logged_in?
        end
        ...

我如何在控制器中使用AC:

class ProjectController < ApplicationController
  # In reverse because `prepend_` is LIFO
  prepend_before_action(except: [:show, :index, :new, :create]) do |c|
    c.allow_access_to_manager(@manager.administrateur)
  end
  prepend_before_action :find_manager, except: [:index, :new, :create]

答案 3 :(得分:0)

我不喜欢使用prepend_before_action的先前解决方案,这是使用ActionController回调的一个很好的实现

module AccessControl
  extend ActiveSupport::Concern

  class UnauthorizedException < Exception
  end

  class_methods do
    define_method :access_control do |*names, &blk|
      _insert_callbacks(names, blk) do |name, options|
        set_callback(:access_control, :before, name, options)
      end
    end
  end

  included do

    define_callbacks :access_control

    before_action :deny_by_default
    around_action :perform_if_access_granted

    def perform_if_access_granted
      run_callbacks :access_control do
        if @access_denied and not @access_authorized
          @request_authentication = true unless user_signed_in?
          render(
            file: File.join(Rails.root, 'app/views/errors/403.html'),
            status: 403,
            layout: 'error')
        else
          yield
        end
      end
    end

    def deny_by_default
      @access_denied ||= true
    end

    def allow_access
      @access_authorized = true
    end
  end
end

然后您可以添加自己的allow_access_to_x方法(例如,在相同的AccessControl问题中):

def allow_access_to_participants_of(project)
  return unless user_signed_in?
  allow_access if current_user.in?(project.executants)
end

以下列方式在您的控制器中使用它(不要忘记在ApplicationController

中包含AccessControl
class ProjectsController < ApplicationController
  access_control(only: [:show, :edit, :update]) do
    set_project
    allow_access_to_participants_of(@project)
    allow_access_to_project_managers
  end

  def index; ...; end;
  def show; ...; end;
  def edit; ...; end;
  def update; ...; end;

  def set_project
    @project = Project.find(params[:project_id])
  end
end