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)