Rails 多对多关系模型的Cancancan授权
Rails Cancancan authorization of models in many-to-many relation
我通过 UserRoleAssoc 在多对多关系中拥有 User 和 Role 模型在 Ruby-上-Rails.
我需要一个页面(Web 界面),用户可以从该页面(网络界面)add/delete 与用户关联的角色,普通用户和管理员可以在其中 编辑 自己的角色只有.
我的问题是如何实施该方案,特别是授权。
这里是 User 和 Role 的模型(只是标准的多对多):
class User < ApplicationRecord
has_many :user_role_assocs, dependent: :destroy
has_many :roles, through: :user_role_assocs
end
class Role < ApplicationRecord
has_many :user_role_assocs
has_many :users, through: :user_role_assocs
end
class UserRoleAssoc < ApplicationRecord
belongs_to :user
belongs_to :role
end
根据 DHH 的原则(参见 Jerome Dalbert 的“How DHH Organizes His Rails Controllers”),这些操作应该像控制器一样实现,比如说,ManageUserRolesController
,执行一个或多个 CRUD 操作.在这种情况下,ManageUserRolesController
create
和 delete
中的一个或两个 UserRoleAssoc
上的多个记录。
由于 Web 用户界面应该允许人们从 URL 一次性管理角色列表(带有 select 框),我制作了 create
方法ManageUserRolesController
两者都做,在 params
中接收用户 ID (user
) 和一组角色 ID (roles
)(不过我愿意接受建议!) . routes.rb
如下:
resources :manage_user_role, only: [:create] # index may be provided, too.
现在,为了将一个用户限制为 add/delete 对任何其他用户的角色,我想在 models/ability.rb
中写一些类似的东西,连同一个控制器:
# models/ability.rb`
can :create, ManageUserRoles, :PARAMS => {user: user} # "PARAMS" is invalid!! Any alternative ideas?
can :manage, ManageUserRoles if user.administrator?
# controllers/manage_user_roles_controller.rb
class ManageUserRolesController < ApplicationController
load_and_authorize_resource
end
似乎可以按照“Passing params to CanCan in RoR" and CanCan wiki的答案中描述的方式实现它,虽然我认为必须定义与控制器对应的模型以指向非标准table , 在 models/manage_user_role.rb
class ManageUserRole < ApplicationRecord
self.table_name = 'user_role_assocs'
end
但这看起来很别扭……
'Rails'实现多对多模型授权的方式(Version 6+)是什么?具体来说,对于具有某些约束的用户 add/delete 多个角色来说,什么是好的界面?
注意路由不一定要像上面的示例代码那样;可以设置路由,以便用户 ID 作为路径的一部分传递,例如 /manage_user_role/:user_id
而不是通过 params
,只要授权有效。
这是一个答案,我最后使用的解决方案。
背景
根据定义,多对多关系很复杂,我认为没有适合所有情况的简单解决方案。当然,CanCanCan 中的 Ability 默认不支持它(除非你做一些复杂的黑客攻击,比如 OP 想要避免的方式,如问题中所述)。
然而,在这种特殊情况下,OP 想要处理的情况是基于用户 ID 的约束,这基本上是一对多(或 has_many
)关系,即一个用户拥有多个角色。然后,它实际上可以按照 Cancancan/Ability 的标准方式进行工作。
一般来说,OP的用户和角色之间存在多对多关系的情况有三种处理方式(即,每个用户可以有多个角色和一个角色可能属于多个用户):
- 像在用户(控制器)模型中一样处理它,
- 像在角色(控制器)模型中一样处理它,
- 或者UserRoleAssoc(Controller),也就是User和Role之间jointable关联的一个model(n.b.,这个Controller是默认情况下不会创建,因此如果您使用它,您必须手动创建它)。
让我在下面讨论三个中哪一个最符合Cancancan授权的目的。
Cancancan 如何使用 Ability 进行授权以及什么最适合这种情况
对于默认的 CRUD 操作,Cancancan 处理 can
语句如下(以我的理解);这基本上是对 official reference of Cancancan:
这种情况的简要总结
- 对于动作
index
,除了动作类型index
之外,Cancancan仅有的信息是用户,模型Class(with/without范围) .所以,基本上,Cancancan 不会也不能做太多。重要的是,与 can
语句关联的 Ruby 块(如果有)是 不 调用的。
- 如果模型的(主要)ID在路径中给出,即对于
show
、edit
、update
、destroy
、Cancancan的动作从数据库中检索模型,并将其提供给您使用 can
语句提供的算法,如果给定,包括 Ruby 块。
在 OP 的情况下,用户不应被授权处理除 her/himself 以外的任何其他用户的角色。那么,就必须根据即、current_user
中的一个和path/route中给出的两个user-id进行判断。为了Rails自动从路径中拾取后者,必须相应地设置路由。
然后,因为“ID”是一个User-ID,处理这种情况最自然的解决方案是使用UsersController(上面描述的情况1);那么包含在默认路由中的ID被Rails和Cancancan解释为User#id
。相反,如果采用情况 2,路径中的默认 ID 将被解释为 Role#id
,这不适用于这种情况。至于case 3(问题中提到的),UserRoleAssoc#id
只是给一个协会的随机数,与User#id
或Role#id
无关。所以,也不适合这种情况。
解决方案
如上所述,必须仔细选择控制器的操作,以便 Cancancan 根据路径中给定的 ID 正确设置用户。
OP 提到控制器的 create
和 delete
(destroy
)。从技术上讲,在这种情况下,所需的操作是 create 和 delete 用户和角色之间的新关联之一或两者。然而,在Rails'默认路由中,create
不带ID参数(当然不是,因为ID是在DB创建时给定的!)。因此,create
的动作名称在这种情况下并不合适。 update
最合适。在自然语言中,我们将其解释为用户的(角色关联)状态将是 update-d 与 Controller 的此操作。 update
的默认HTTP方法是PATCH/PUT
,也符合操作的意思。
最后,这是我找到的解决方案(使用 Rails 6.1):
routes.rb
resources :manage_user_roles, only: [:update]
# => Route: manage_user_role PATCH /manage_user_roles/:id(.:format) manage_user_roles#update
manage_user_roles_controller.rb
class ManageUserRolesController < ApplicationController
load_and_authorize_resource :user
# This means as far as authorization is concerned,
# the model and controller are User and UsersController.
my_params = params.permit('add_role_11', 'del_role_11', 'add_role_12', 'del_role_12')
end
查看(提交数据)
这可以在用户的 show.html.erb
或其他任何地方。
<%= form_with(method: :patch, url: manage_user_role_path(@user)) do |form| %>
Form components follow...
app/models/ability.rb
def initialize(user)
if user.present?
can :update, User, id: user.id
end
end
我认为,关键是简化案例。尽管多对多关系本质上是复杂的,但您最好将每种情况分成更小、更简单的片段来处理。然后,他们可能会毫不费力地适应现有的方案。
我通过 UserRoleAssoc 在多对多关系中拥有 User 和 Role 模型在 Ruby-上-Rails.
我需要一个页面(Web 界面),用户可以从该页面(网络界面)add/delete 与用户关联的角色,普通用户和管理员可以在其中 编辑 自己的角色只有.
我的问题是如何实施该方案,特别是授权。
这里是 User 和 Role 的模型(只是标准的多对多):
class User < ApplicationRecord
has_many :user_role_assocs, dependent: :destroy
has_many :roles, through: :user_role_assocs
end
class Role < ApplicationRecord
has_many :user_role_assocs
has_many :users, through: :user_role_assocs
end
class UserRoleAssoc < ApplicationRecord
belongs_to :user
belongs_to :role
end
根据 DHH 的原则(参见 Jerome Dalbert 的“How DHH Organizes His Rails Controllers”),这些操作应该像控制器一样实现,比如说,ManageUserRolesController
,执行一个或多个 CRUD 操作.在这种情况下,ManageUserRolesController
create
和 delete
中的一个或两个 UserRoleAssoc
上的多个记录。
由于 Web 用户界面应该允许人们从 URL 一次性管理角色列表(带有 select 框),我制作了 create
方法ManageUserRolesController
两者都做,在 params
中接收用户 ID (user
) 和一组角色 ID (roles
)(不过我愿意接受建议!) . routes.rb
如下:
resources :manage_user_role, only: [:create] # index may be provided, too.
现在,为了将一个用户限制为 add/delete 对任何其他用户的角色,我想在 models/ability.rb
中写一些类似的东西,连同一个控制器:
# models/ability.rb`
can :create, ManageUserRoles, :PARAMS => {user: user} # "PARAMS" is invalid!! Any alternative ideas?
can :manage, ManageUserRoles if user.administrator?
# controllers/manage_user_roles_controller.rb
class ManageUserRolesController < ApplicationController
load_and_authorize_resource
end
似乎可以按照“Passing params to CanCan in RoR" and CanCan wiki的答案中描述的方式实现它,虽然我认为必须定义与控制器对应的模型以指向非标准table , 在 models/manage_user_role.rb
class ManageUserRole < ApplicationRecord
self.table_name = 'user_role_assocs'
end
但这看起来很别扭……
'Rails'实现多对多模型授权的方式(Version 6+)是什么?具体来说,对于具有某些约束的用户 add/delete 多个角色来说,什么是好的界面?
注意路由不一定要像上面的示例代码那样;可以设置路由,以便用户 ID 作为路径的一部分传递,例如 /manage_user_role/:user_id
而不是通过 params
,只要授权有效。
这是一个答案,我最后使用的解决方案。
背景
根据定义,多对多关系很复杂,我认为没有适合所有情况的简单解决方案。当然,CanCanCan 中的 Ability 默认不支持它(除非你做一些复杂的黑客攻击,比如 OP 想要避免的方式,如问题中所述)。
然而,在这种特殊情况下,OP 想要处理的情况是基于用户 ID 的约束,这基本上是一对多(或 has_many
)关系,即一个用户拥有多个角色。然后,它实际上可以按照 Cancancan/Ability 的标准方式进行工作。
一般来说,OP的用户和角色之间存在多对多关系的情况有三种处理方式(即,每个用户可以有多个角色和一个角色可能属于多个用户):
- 像在用户(控制器)模型中一样处理它,
- 像在角色(控制器)模型中一样处理它,
- 或者UserRoleAssoc(Controller),也就是User和Role之间jointable关联的一个model(n.b.,这个Controller是默认情况下不会创建,因此如果您使用它,您必须手动创建它)。
让我在下面讨论三个中哪一个最符合Cancancan授权的目的。
Cancancan 如何使用 Ability 进行授权以及什么最适合这种情况
对于默认的 CRUD 操作,Cancancan 处理 can
语句如下(以我的理解);这基本上是对 official reference of Cancancan:
- 对于动作
index
,除了动作类型index
之外,Cancancan仅有的信息是用户,模型Class(with/without范围) .所以,基本上,Cancancan 不会也不能做太多。重要的是,与can
语句关联的 Ruby 块(如果有)是 不 调用的。 - 如果模型的(主要)ID在路径中给出,即对于
show
、edit
、update
、destroy
、Cancancan的动作从数据库中检索模型,并将其提供给您使用can
语句提供的算法,如果给定,包括 Ruby 块。
在 OP 的情况下,用户不应被授权处理除 her/himself 以外的任何其他用户的角色。那么,就必须根据即、current_user
中的一个和path/route中给出的两个user-id进行判断。为了Rails自动从路径中拾取后者,必须相应地设置路由。
然后,因为“ID”是一个User-ID,处理这种情况最自然的解决方案是使用UsersController(上面描述的情况1);那么包含在默认路由中的ID被Rails和Cancancan解释为User#id
。相反,如果采用情况 2,路径中的默认 ID 将被解释为 Role#id
,这不适用于这种情况。至于case 3(问题中提到的),UserRoleAssoc#id
只是给一个协会的随机数,与User#id
或Role#id
无关。所以,也不适合这种情况。
解决方案
如上所述,必须仔细选择控制器的操作,以便 Cancancan 根据路径中给定的 ID 正确设置用户。
OP 提到控制器的 create
和 delete
(destroy
)。从技术上讲,在这种情况下,所需的操作是 create 和 delete 用户和角色之间的新关联之一或两者。然而,在Rails'默认路由中,create
不带ID参数(当然不是,因为ID是在DB创建时给定的!)。因此,create
的动作名称在这种情况下并不合适。 update
最合适。在自然语言中,我们将其解释为用户的(角色关联)状态将是 update-d 与 Controller 的此操作。 update
的默认HTTP方法是PATCH/PUT
,也符合操作的意思。
最后,这是我找到的解决方案(使用 Rails 6.1):
routes.rb
resources :manage_user_roles, only: [:update]
# => Route: manage_user_role PATCH /manage_user_roles/:id(.:format) manage_user_roles#update
manage_user_roles_controller.rb
class ManageUserRolesController < ApplicationController
load_and_authorize_resource :user
# This means as far as authorization is concerned,
# the model and controller are User and UsersController.
my_params = params.permit('add_role_11', 'del_role_11', 'add_role_12', 'del_role_12')
end
查看(提交数据)
这可以在用户的 show.html.erb
或其他任何地方。
<%= form_with(method: :patch, url: manage_user_role_path(@user)) do |form| %>
Form components follow...
app/models/ability.rb
def initialize(user)
if user.present?
can :update, User, id: user.id
end
end
我认为,关键是简化案例。尽管多对多关系本质上是复杂的,但您最好将每种情况分成更小、更简单的片段来处理。然后,他们可能会毫不费力地适应现有的方案。