18

declarative_authorizationを使用して役割ごとに属性を保護するクールな方法は何ですか? たとえば、ユーザーは自分の連絡先情報を編集できますが、役割は編集できません。

私の最初の傾向は、さまざまなシナリオに対して複数のコントローラー アクションを作成することでした。保護された属性の数が増えるにつれて、これがいかに扱いにくくなるかがすぐにわかりました。ユーザー ロールに対してこれを行うことは 1 つのことですが、複数の保護された属性を想像できます。コントローラーのアクションとルートを大量に追加するのは適切ではありません。

私の 2 番目の傾向は、特定の機密属性に関するアクセス許可を作成し、フォーム要素を declarative_authorizations によって提供される View ヒーパーでラップすることでした。ただし、これのモデルとコントローラーの側面は、私の頭の中では少し曇っています。提案は素晴らしいでしょう。

declarative_authorizations を使用して役割ごとに属性を保護する最善の方法についてアドバイスしてください。

4

5 に答える 5

5

編集2011-05-223.1RChttps ://github.com/rails/rails/blob/master/activerecord/test/cases/mass_assignment_security_test.rb
の時点で、同様の何かがRailsにあるので、今すぐそのルートに進むことをお勧めします。

元の回答以前使用していたものをRails3
に移植する必要がありました。特に宣言型承認を使用したことはありませんが、これは非常に単純でわかりやすいため、適応できるはずです。

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)

spec / 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
于 2011-03-16T20:51:30.780 に答える
3

あなたが探しているもののように見えるscoped_attr_accessibleを使用します。すべてのモデルのリクエストの開始時にスコープを設定する必要があるだけです。

これを行うにはbefore_filter、application_controller.rb で a を使用します。

before_filter do |controller|
  ScopedAttrAccessible.current_sanitizer_scope = controller.current_user.role
end
于 2011-03-15T18:41:32.137 に答える
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 を定義し、これらの各コントローラーに同じフィルター メソッドを使用させることができます。次のように、アプリケーション コントローラーに before フィルターを吐き出すメソッドを用意することで、ボイラープレートを節約できます。

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

これらの例は、決定的なものではなく、説明することを目的としています。これを実装するのに役立つことを願っています.

于 2011-03-15T11:09:29.797 に答える
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

次に、コントローラーで update アクションを次のように変更します。

@user.update_attributes(params[:user])

@user.update_attributes_as_user(params[:user], current_user)
于 2011-03-16T11:02:06.810 に答える
0

Rails 3.1+ には、この目的のために +assign_attributes+ メソッドが付属しています - http://apidock.com/rails/ActiveRecord/AttributeAssignment/assign_attributes

于 2012-06-30T07:13:04.447 に答える