18

根据角色使用declarative_authorization保护属性有什么好方法?例如,用户可以编辑他的联系信息,但不能编辑他的角色。使用declarative_authorization保护敏感属性

我的第一个倾向是为不同的场景创建多个控制器操作。随着受保护属性的数量增加,我很快意识到这可能会变得多么笨拙。为用户角色做这件事是一回事,但我可以想象多个受保护的属性。添加很多控制器操作和路由感觉不对。

我的第二个倾向是围绕特定的敏感属性创建权限,然后使用由declarative_authorizations提供的View hepers包装表单元素。然而,这种模型和控制器方面在我看来有点模糊。建议会很棒。

请建议使用declarative_authorizations按角色保护属性的最佳方法。

+2

scoped_attr_accessible https://github.com/thefrontiergroup/scoped_attr_accessible – jrhicks 2011-03-11 21:25:17

+0

attr_accessible_block https://github.com/dmitry/attr_accessible_block – jrhicks 2011-03-11 21:26:23

+0

你能指定,究竟你要实现的目标(或举个例子)。受保护的属性访问时究竟应该发生什么?我可以想象至少有两种情况:1.用于开发的保护应该可能引发和异常2.用于刺激的保护应该可能返回零,但是如果您每次询问任何属性值时都很难检查零受到任何用户的保护。 – gorn 2011-03-14 23:43:55

回答

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 added 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 
+0

感谢您的洞察 – jrhicks 2011-03-21 01:51:03

+0

类似的东西现在在Rails的3.1RC https://github.com/rails/rails/blob/master/activerecord/test/cases/mass_assignment_security_test.rb所以我建议现在去那条路线。 – scragz 2011-05-22 07:27:41

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_pa​​rams,并有控制器,用于这些用途相同的过滤方法。你可以通过在应用程序控制器的方法是吐出了一个前过滤器,这样节省一些样板:

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 

这些例子意在说明,而不是决定性的,我希望他们用的这个实施帮助。

3

我会用scoped_attr_accessible,看起来就像你在找什么。只有您需要在所有模型的请求开始时设置范围。

为了做到这一点,在你application_controller.rb使用before_filter

before_filter do |controller| 
    ScopedAttrAccessible.current_sanitizer_scope = controller.current_user.role 
end 
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)