【问题标题】:Multitenant scoping using Pundit使用 Pundit 的多租户范围
【发布时间】:2015-08-06 10:04:30
【问题描述】:

我正在使用 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 似乎希望我按用户过滤 Posts,例如:

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 超出了它的设计目的?

【问题讨论】:

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


    【解决方案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使用where实现方法showsource.

    因此,如果您的范围不返回 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-26
      • 1970-01-01
      • 2019-10-28
      • 1970-01-01
      • 2017-01-15
      • 1970-01-01
      相关资源
      最近更新 更多