使用Pundit的多租户范围

时间:2015-06-22 22:11:04

标签: ruby-on-rails authorization multi-tenant pundit

我使用Pundit进行授权,我想利用其多租户范围机制(由主机名驱动)。

我通过以下方式手动执行此操作:

class ApplicationController < ActionController::Base
  # Returns a single Client record
  def current_client
    @current_client ||= Client.by_host(request.host)
  end
end

然后在我的控制器中执行以下操作:

class PostsController < ApplicationController
  def index
    @posts = current_client.posts
  end
end

非常标准的票价,真的。

我喜欢Pundit的verify_policy_scoped过滤器的简单性,以确保绝对每个操作都限定为正确的Client。对我来说,如果未正式执行范围确定,那么确实值得500错误。

鉴于Pundit政策范围:

class PostPolicy < ApplicationPolicy
  class Scope < Scope
    def resolve
      # have access to #scope => Post class
      # have access to #user => User object or nil
    end
  end
end

现在,Pundit似乎希望我按用户过滤Post,例如:

def resolve
  scope.where(user_id: user.id)
end

但是,在这种情况下,我实际上希望按current_client.posts过滤作为默认情况。我不确定如何在这种情况下使用Pundit示波器,但我的感觉是它需要看起来像:

def resolve
  current_client.posts
end

但是current_client自然不会在Pundit范围内可用。

一种解决方案可能是将current_client.posts传递给policy_scope

def index
  @posts = policy_scope(current_client.posts)
end

但是我觉得这可以分散我的租约范围,从而破坏了使用Pundit完成这项任务的目的。

有什么想法吗?还是我驾驶Pundit超出它的设计目标?

1 个答案:

答案 0 :(得分:1)

处理此问题的最“权威兼容”方法是在Post模型中创建范围:

Class Post < ActiveRecord::Base
  scope :from_user, -> (user) do
    user.posts
  end
end

然后,您就可以在自己的政策中使用它,其中user填充了您的控制器中的current_user

class PostPolicy < ApplicationPolicy
  class Scope
    attr_reader :user, :scope

    def initialize(user, scope)
      @user = user
      @scope = scope
    end

    def resolve
      scope.from_user(user)
    end
  end
end

如果从作用域返回ActiveRecord :: Relation,则可以从此处停止读取。

如果您的范围返回数组

默认ApplicationPolicy使用show实施方法wheresource

因此,如果您的作用域不返回AR :: Relation而是返回数组,那么可以使用一种解决方法来覆盖此show方法:

class PostPolicy < ApplicationPolicy
  class Scope
    # same content than above
  end

  def show?
    post = scope.find do |post_in_scope|
      post_in_scope.id == post.id
    end
    post.present?
  end
end

无论您的实施是什么,您只需要使用控制器中的PostPolicy“Pundit-way”:

class PostsController < ApplicationController
  def index
    @posts = policy_scope(Post)
  end

  def show
    @post = Post.find(params[:id])
    authorize @post
  end
end