在我的一个Rails(6.1.0)模型中,我有几个列需要根据用户角色来保护它们不受CRUD操作的影响。我已经实现了前端表单输入禁用,但不确定我是否正确/安全地保护后端。我尝试了几种方法,并在params对象上确定了这种条件连接,但由于它不是我明确看到的东西,所以我不相信它是最佳实践和/或完全安全的。有没有更好的办法?
给定一些用户权限/访问级别:
class User < ApplicationRecord
...
enum role: {guest: 0, employee: 1, manager: 2, executive: 3, administrator: 4, superadmin: 5, developer: 6}
#TODO: Extract & expand this authorization scheme (Pundit?)
def has_employee_rights?
self.employee? || self.manager? || self.executive? || self.administrator? || self.superadmin? || self.developer?
end
def has_manager_rights?
self.manager? || self.executive? || self.administrator? || self.superadmin? || self.developer?
end
def has_executive_rights?
self.executive? || self.administrator? || self.superadmin? || self.developer?
end
def has_administrator_rights?
self.administrator? || self.superadmin? || self.developer?
end
def has_superadmin_rights?
self.superadmin? || self.developer?
end
def has_developer_rights?
self.developer?
end
...
end
我通过设置'readonly'属性来处理前端表单(为了清晰起见,删除了Haml/classes):
# Normal form field
= form.label :prov_dist_code, "Provisional Dist. Code"
= form.text_field :prov_dist_code, value: @project.prov_dist_code
# "Protected" form fields
= form.label :dist_code, "Final Distribution Code", readonly: !current_user.has_manager_rights?
= form.text_field :dist_code, value: @project.dist_code
= form.label :pcode, "PCode"
= form.text_field :pcode, readonly: !current_user.has_administrator_rights?, value: @project.pcode
然后在控制器中进一步保护列:
class ProjectsController < ApplicationController
...
def create
@project = Project.new(project_params)
if @project.save
redirect_to @project, notice: "Project was successfully created."
else
render :new
end
end
...
private
#TODO: Is this conditional concatenation safe/adequate?
def allowed_params
ap = [:network_id,
:dist_title,
:dist_description,
:prov_dist_code,
:name,
:hosts,
:guests...]
ap << :pcode if current_user.has_administrator_rights?
ap << :dist_code if current_user.has_manager_rights?
end
def project_params
params.require(:project).permit(*allowed_params)
end
end
您所做的实际上只是以编程方式创建一个参数数组,并使用splat操作符将该数组应用为参数列表。这并没有什么本质上的不安全。唯一真正的安全问题是如果您从用户那里获取白名单的密钥。
从代码审查的角度来看,它确实在控制器层中放置了许多逻辑,这很难测试,并为您的模型增加了更多的责任。既然您提到了Pundit,它实际上是巩固授权逻辑的绝佳选择,因为它易于单独测试。
class ApplicationPolicy < Pundit::Policy
# ...
private
def employee?
user.employee? || user.manager? || user.executive? || user.administrator? || user.superadmin? || user.developer?
end
def manager?
user.manager? || user.executive? || user.administrator? || user.superadmin? || user.developer?
end
def executive?
user.executive? || user.administrator? || user.superadmin? || user.developer?
end
def administrator?
user.administrator? || user.superadmin? || user.developer?
end
def superadmin?
user.superadmin? || user.developer?
end
def developer?
user.developer?
end
end
class ProjectPolicy < ApplicationPolicy
BASE_ATTRIBUTES = [
:network_id,
:dist_title,
:dist_description,
:prov_dist_code,
:name,
:hosts,
:guests
].freeze
# ...
def permitted_attributes
BASE_ATTRIBUTES.dup.then do |attrs|
attrs << :pcode if administrator?
attrs << :dist_code if manager?
end
end
end
然后您可以使用您的策略将参数列入白名单:
class ProjectsController < ApplicationController
...
def create
@project = Project.new(permitted_attributes(@project))
if @project.save
redirect_to @project, notice: "Project was successfully created."
else
render :new
end
end
end