Rails:有没有办法在Rails中构建基于动态角色的授权?

5
我正在尝试在Rails中实现基于角色的授权。
我们需要:
1. 角色应该是动态的,我们应该能够创建、编辑或删除角色。 2. 权限也应该是动态的。
调查结果:
1. 我们不能使用 "pundit" gem,因为它的策略是静态的,我们无法使其变得动态。 2. 我们可以使用 "cancan" gem,并且我们可以动态地使用它,但我不知道如何完成?它如何与数据库一起工作?
这是我的第一个关于授权部分的项目。我们的后端是Rails,前端是vue.js。无论有哪些角色,在数据库中所有数据都应为空。我们将使用种子(seed)来创建一个超级管理员角色并赋予所有权限。超级管理员将创建角色、编辑角色、销毁角色,并最终添加权限、编辑权限和销毁权限。
如果有其他有用的方法,请告诉我。
谢谢。

你能提供更多使用情况的细节吗?看起来cancan gem可以满足您的要求。但是,cancan基于模型名称管理授权。我有一个项目具有动态角色、基于控制器名称的动态权限,并且我们正在使用一些调整后的cancan。 - Feifei Xiong
“Pundit是静态的” - 这是胡说八道。Pundit只是面向对象编程,所以您可以构建任何想要的东西。我真的不建议在这里使用cancancan,因为整个笨拙的DSL只会妨碍你。Cancancan擅长缩小到琐碎的用例,但其他方面并不出色。 - max
我喜欢 Cancancan 和 Rolify。 - Int'l Man Of Coding Mystery
@FeifeiXiong,我已经更新了我的问题。如果您需要更多细节,请告诉我。 - susie
3个回答

4

Pundit vs CanCanCan

你对CanCanCan和Pundit的结论纯属胡说。它们都不是“静态”或“动态”,并且它们几乎具有相同的功能。然而,它们的架构和设计哲学却截然不同。

CanCanCan(最初叫做CanCan)被写成DSL,这是自从十年前Ryan Bates创建CanCan以来最热门的东西。它可以很好地缩小规模,并且很容易学习,但一旦达到任何复杂程度,它会变得非常丑陋。如果在CanCanCan中进行“动态授权”,那么由于其架构,这将成为一场噩梦。CanCanCan中的“能力类”是所有上帝对象中的上帝。

Pundit只是面向对象编程。在Pundit中,您的策略只是接受用户和资源作为初始化参数并响应像show?create?等方法的类。Pundit最初很难理解,但由于它只是OOP,因此可以根据您的需要进行定制。由于您的身份验证逻辑存储在单独的对象中,因此它可以更好地扩展到复杂性,并遵守SOLID原则。
如何设置动态角色系统?
这是您的标准角色系统,即Rolify
class User < ApplicationRecord
  has_many :user_roles
  has_many :roles, through: :user_roles
  def has_role?(role, resource = nil)
    roles.where({ name: role, resource: resource }.compact).exists?
  end

  def add_role(role, resource = nil)
    role = Role.find_or_create_by!({ name: role, resource: resource }.compact)
    roles << role
  end
end

# rails g model user_roles user:belongs_to role:belongs_to   
class UserRole < ApplicationRecord
  belongs_to :user
  belongs_to :role
end

# rails g model role name:string resource:belongs_to:polymorphic
class Role < ApplicationRecord
  belongs_to :resource, polymorphic: true, optional: true
  has_many :user_roles
  has_many :users, through: :user_roles
end

然后您可以将角色限定于资源:

class Forum < ApplicationRecord
  has_many :roles, as: :resource
end

Rolify让您更进一步,只需使用类作为资源定义角色。例如:user.add_role(:admin, Forum),这将使用户成为所有论坛的管理员。

如何创建权限系统?

一个简单的RBAC系统可以构建如下:

class Role < ApplicationRecord
  has_many :role_permissions 
  has_many :permissions, through: :role_permissions 

  def has_permission?(permission)
    permissions.where(name: permission).exists?
  end
end 

# rails g model permission name:string
class Permission < ApplicationRecord
end

# rails g model role_permission role:belongs_to permission:belongs_to
class RolePermission < ApplicationRecord
  belongs_to :role
  belongs_to :permission
end

例如,您可以通过以下方式将“destroy”授权给Forum.find(1)上的“moderators”:
role = Role.find_by!(name: 'moderator', resource: Forum.find(1))
role.permissions.create!(name: 'destroy')
role.has_permission?('destroy') # true

虽然我怀疑在现实中它并不会如此简单。


嘿,Max,谢谢。你能告诉我们如何在上面实现Pundit吗? 还有数据库是如何工作的? - susie
Pundit提供API吗? - susie
2
你需要自己动手做功课。你提出的问题非常广泛,只有通过自己的研究才能得到答案。如何实现这个取决于领域。而且,像“数据库是如何工作的?”这样的问题,我甚至不知道从哪里开始回答。如果你连这个都不知道,那么你甚至无法开始这个任务。 - max
我知道数据库如何工作,但是pundit提供API吗?我在这里只谈论pundit。如果我创建角色和权限表,并将pundit用作:class RolePolicy < ApplicationPolicy role_id = @current_user.role_id @@permission_for_role = Permission.find(role_id) def update? @@permission_for_role.u? end end ...... 它有效。无论如何感谢。 - susie
1
“提供API”是什么意思?Pundit适用于各种应用程序,包括API应用程序。它有一个API(几乎所有软件都有),可以提供给您的控制器和视图(即authorizepolicy方法)。它不会自动创建HTTP API。 - max

2

如果我理解你的要求是正确的,你应该可以使用Pundit来实现这个功能。

据我所知,

  • 用户有角色
  • 用户可以在运行时被分配和取消角色
  • 权限可以直接授予角色或用户
  • 权限可以在运行时更新

因此,你可以像这样设置:

class User
  has_many :user_role_mappings
  has_many :roles, through: :user_role_mappings
  has_many :permissions, through: :roles
  ...
end

class UserRoleMapping
  belongs_to :user
  belongs_to :role
end

class Role
  has_many :role_permission_mappings
  has_many :permissions, through :role_permission_mappings
  ...

  def has_permission?(permission)
    permissions.where(name: permission).exists?
  end
end

class RolePermissionMapping
  belongs_to :role
  belongs_to :permission
end

class Permission
  ...
end

在您的策略中,您可以检查用户的任何角色是否具有所需的权限。

class PostPolicy < ApplicationPolicy
  def update?
    user.roles.any? { |role| role.has_permission?('update_post') }
  end
end

编辑:

通过使用映射表,您可以从管理员仪表板更新角色的权限以及用户的角色。


1
作为一个小细节,你不需要真的去做 .pluck(:name).include?,你可以使用 permissions.where(name: name).exists? 进行计数查询。.pluck 是 Rails 中最容易被误用的方法之一。 - max
我喜欢你的回答@anuj。我可以问一下吗?我想为每个控制器的操作添加一个模型,用于在数据库上创建、更新和删除权限。您能否详细说明如何使用Pundit实现这一点,以便每个用户/角色的权限可以从应用程序用户界面动态修改,而不是从后端进行修改。谢谢。 - Promise Preston
1
感谢@PromisePreston。我已经更新了答案并添加了更多细节。基本上,您将拥有一个用户和角色之间的映射表,以及另一个角色和权限之间的映射表。您可以创建和删除这些映射以从仪表板调整用户权限。 - Anuj
我会尝试一下并看看情况如何。虽然我仍然对每个控制器的读取、写入、删除和更新权限如何保存到角色权限表中感到困惑。我是指如何将它们保存到数据库中,因为它们只是操作而不是真正的值。 - Promise Preston
1
每个控制器操作所需的权限将保持不变。您可以将它们保存在Permissions表中,例如update_contacts。您要更改的是不同角色/用户的权限,而不是不同操作所需的权限。 - Anuj

0

你可以使用 pundit 实现动态角色。 pundit 允许你定义普通的 Ruby 对象,并调用其中的方法来确定用户是否有权限执行某个操作。例如:

class PostPolicy < ApplicationPolicy
  def update?
    user.has_role('admin')
  end
end

如果您想让用户拥有多个角色,可以在您的User模型上设置一个has_and_belongs_to_many: :roles关联。
参见:https://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association

1
user.has_role('admin'),意味着管理员角色在此处是静态的。不是吗? - susie
1
如果has_role是一个查询数据库的方法,那么这个逻辑你需要自己编写,你可以在其他答案中找到一些例子。 - Johan Baaij

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接