使用declarative_authorization按角色保护属性有什么好方法?例如,用户可以编辑他的联系信息,但不能编辑他的角色。
我的第一个倾向是为不同的场景创建多个控制器动作。随着受保护属性数量的增加,我很快意识到这种情况会变得多么笨拙。为用户角色执行此操作是一回事,但我可以想象多个受保护的属性。添加很多控制器操作和路线感觉不对。
我的第二个倾向是围绕特定敏感属性创建权限,然后使用declarative_authorizations提供的View hepers包装表单元素。然而,我的模型和控制器方面有点模糊。建议会很棒。
请使用declarative_authorizations建议通过角色保护属性的最佳方法。
答案 0 :(得分:5)
编辑2011-05-22
在3.1RC https://github.com/rails/rails/blob/master/activerecord/test/cases/mass_assignment_security_test.rb的Rails中现在有类似的东西,所以我建议现在就去那条路。
原始回答
我只需要将之前使用过的东西移植到Rails 3.我从未专门使用过声明授权,但这非常简单明了,你应该能够适应它。
Rails 3添加了mass_assignment_authorizer
,这使得这一切都非常简单。我使用该链接教程作为基础,并使其更适合我的域模型,使用类继承并将属性分组为角色。
在模型中
acts_as_accessible :admin => :all, :moderator => [:is_spam, :is_featured]
attr_accessible :title, :body # :admin, :moderator, and anyone else can set these
在控制器中
post.accessed_by(current_user.roles.collect(&:code)) # or however yours works
post.attributes = params[:post]
<强> LIB / active_record / acts_as_accessible.rb 强>
# A way to have different attr_accessible attributes based on a Role
# @see ActsAsAccessible::ActMethods#acts_as_accessible
module ActiveRecord
module ActsAsAccessible
module ActMethods
# In model
# acts_as_accessible :admin => :all, :moderator => [:is_spam]
# attr_accessible :title, :body
#
# In controller
# post.accessed_by(current_user.roles.collect(&:code))
# post.attributes = params[:post]
#
# Warning: This frequently wouldn't be the concern of the model where this is declared in,
# but it is so much more useful to have it in there with the attr_accessible declaration.
# OHWELL.
#
# @param [Hash] roles Hash of { :role => [:attr, :attr] }
# @see acts_as_accessible_attributes
def acts_as_accessible(*roles)
roles_attributes_hash = Hash.new {|h,k| h[k] ||= [] }
roles_attributes_hash = roles_attributes_hash.merge(roles.extract_options!).symbolize_keys
if !self.respond_to? :acts_as_accessible_attributes
attr_accessible
write_inheritable_attribute :acts_as_accessible_attributes, roles_attributes_hash.symbolize_keys
class_inheritable_reader :acts_as_accessible_attributes
# extend ClassMethods unless (class << self; included_modules; end).include?(ClassMethods)
include InstanceMethods unless included_modules.include?(InstanceMethods)
else # subclass
new_acts_as_accessible_attributes = self.acts_as_accessible_attributes.dup
roles_attributes_hash.each do |role,attrs|
new_acts_as_accessible_attributes[role] += attrs
end
write_inheritable_attribute :acts_as_accessible_attributes, new_acts_as_accessible_attributes.symbolize_keys
end
end
end
module InstanceMethods
# @param [Array, NilClass] roles Array of Roles or nil to reset
# @return [Array, NilClass]
def accessed_by(*roles)
if roles.any?
case roles.first
when NilClass
@accessed_by = nil
when Array
@accessed_by = roles.first.flatten.collect(&:to_sym)
else
@accessed_by = roles.flatten.flatten.collect(&:to_sym)
end
end
@accessed_by
end
private
# This is what really does the work in attr_accessible/attr_protected.
# This override adds the acts_as_accessible_attributes for the current accessed_by roles.
# @see http://asciicasts.com/episodes/237-dynamic-attr-accessible
def mass_assignment_authorizer
attrs = []
if self.accessed_by
self.accessed_by.each do |role|
if self.acts_as_accessible_attributes.include? role
if self.acts_as_accessible_attributes[role] == :all
return self.class.protected_attributes
else
attrs += self.acts_as_accessible_attributes[role]
end
end
end
end
super + attrs
end
end
end
end
ActiveRecord::Base.send(:extend, ActiveRecord::ActsAsAccessible::ActMethods)
<强>规格/ LIB / active_record / acts_as_accessible.rb 强>
require 'spec_helper'
class TestActsAsAccessible
include ActiveModel::MassAssignmentSecurity
extend ActiveRecord::ActsAsAccessible::ActMethods
attr_accessor :foo, :bar, :baz, :qux
acts_as_accessible :dude => [:bar], :bra => [:baz, :qux], :admin => :all
attr_accessible :foo
def attributes=(values)
sanitize_for_mass_assignment(values).each do |k, v|
send("#{k}=", v)
end
end
end
describe TestActsAsAccessible do
it "should still allow mass assignment to accessible attributes by default" do
subject.attributes = {:foo => 'fooo'}
subject.foo.should == 'fooo'
end
it "should not allow mass assignment to non-accessible attributes by default" do
subject.attributes = {:bar => 'baaar'}
subject.bar.should be_nil
end
it "should allow mass assignment to acts_as_accessible attributes when passed appropriate accessed_by" do
subject.accessed_by :dude
subject.attributes = {:bar => 'baaar'}
subject.bar.should == 'baaar'
end
it "should allow mass assignment to multiple acts_as_accessible attributes when passed appropriate accessed_by" do
subject.accessed_by :bra
subject.attributes = {:baz => 'baaaz', :qux => 'quuux'}
subject.baz.should == 'baaaz'
subject.qux.should == 'quuux'
end
it "should allow multiple accessed_by to be specified" do
subject.accessed_by :dude, :bra
subject.attributes = {:bar => 'baaar', :baz => 'baaaz', :qux => 'quuux'}
subject.bar.should == 'baaar'
subject.baz.should == 'baaaz'
subject.qux.should == 'quuux'
end
it "should allow :all access" do
subject.accessed_by :admin
subject.attributes = {:bar => 'baaar', :baz => 'baaaz', :qux => 'quuux'}
subject.bar.should == 'baaar'
subject.baz.should == 'baaaz'
subject.qux.should == 'quuux'
end
end
答案 1 :(得分:3)
对我来说,这个过滤问题应该在控制器级别应用。
您希望在某处定义某些内容,以定义如何确定哪些属性可供给定用户使用。
# On the user model
class User < ActiveRecord::Base
# ...
# Return a list of symbols representing the accessible attributes
def self.allowed_params(user)
if user.admin?
[:name, :email, :role]
else
[:name, email]
end
end
end
然后,在应用程序控制器中,您可以定义一个过滤参数的方法。
class ApplicationController < ActionController::Base
# ...
protected
def restrict_params(param, model, user)
params[param].reject! do |k,v|
!model.allowed_params(user).include?(k)
end
end
# ...
end
最后在您的控制器操作中,您可以使用此过滤器:
class UserController < ActionController::Base
# ...
def update
restrict_params(:user, User, @current_user)
# and continue as normal
end
# ...
end
我们的想法是,您可以在每个模型上定义allowed_params,并让每个模型的控制器使用相同的过滤方法。您可以通过在应用程序控制器中使用一个方法来保存一些样板文件,该方法可以使用前一个过滤器,如下所示:
def self.param_restrictions(param, model)
before_filter do
restrict_params(param, model, @current_user) if params[param]
end
end
# in UserController
param_restrictions :user, User
这些示例旨在说明而不是确定,我希望它们有助于实现这一点。
答案 2 :(得分:3)
我使用scoped_attr_accessible,这看起来就像你正在寻找的那样。只需要在所有模型的请求开始时设置范围。
为此,请在application_controller.rb中使用before_filter
:
before_filter do |controller|
ScopedAttrAccessible.current_sanitizer_scope = controller.current_user.role
end
答案 3 :(得分:1)
我会避免在模型中基于用户访问的每个解决方案,因为它似乎有潜在危险。我会尝试这种方法:
class User < ActiveRecord::Base
def update_attributes_as_user(values, user)
values.each do |attribute, value|
# Update the attribute if the user is allowed to
@user.send("#{attribute}=", value) if user.modifiable_attributes.include?(attribute)
end
save
end
def modifiable_attributes
admin? ? [:name, :email, :role] : [:name, :email]
end
end
然后在您的控制器中更改您的更新操作:
@user.update_attributes(params[:user])
到
@user.update_attributes_as_user(params[:user], current_user)
答案 4 :(得分:0)
Rails 3.1+为此提供了+ assign_attributes +方法 - http://apidock.com/rails/ActiveRecord/AttributeAssignment/assign_attributes。