Защитите чувствительные атрибуты с декларативным_авторизацией - PullRequest
18 голосов
/ 11 марта 2011

Какой классный способ защитить атрибуты по ролям, используя Declarative_authorization ?Например, пользователь может редактировать свою контактную информацию, но не свою роль.

Первым делом я хотел создать несколько действий контроллера для разных сценариев.Я быстро понял, насколько громоздким это может стать по мере роста количества защищаемых атрибутов.Выполнение этого для роли пользователя - это одно, но я могу представить несколько защищенных атрибутов.Добавление большого количества действий и маршрутов контроллеров кажется неправильным.

Вторым моим стремлением было создать разрешения для определенных чувствительных атрибутов, а затем обернуть элементы формы с помощью помощников View, предоставленных декларативным_авторизацией.Тем не менее, аспект модели и контроллера немного туманен в моей памяти.Предложения будут отличными.

Посоветуйте, пожалуйста, лучший способ защиты атрибутов по ролям с помощью Declarative_authorizations.

Ответы [ 5 ]

5 голосов
/ 16 марта 2011

РЕДАКТИРОВАТЬ 2011-05-22
Нечто подобное теперь есть в Rails с 3.1RC https://github.com/rails/rails/blob/master/activerecord/test/cases/mass_assignment_security_test.rb, поэтому я бы предложил пойти по этому маршруту сейчас.

ОРИГИНАЛЬНЫЙ ОТВЕТ
Мне просто нужно было перенести то, что я использовал ранее, на Rails 3. Я никогда не использовал декларативную авторизацию специально, но это довольно просто и достаточно просто, чтобы вы могли адаптироваться кit.

В 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
3 голосов
/ 15 марта 2011

Я бы использовал scoped_attr_accessible , который выглядит как раз то, что вы ищете.Только вам нужно установить область в начале запроса для всех моделей.

Для этого используйте before_filter в вашем application_controller.rb:

before_filter do |controller|
  ScopedAttrAccessible.current_sanitizer_scope = controller.current_user.role
end
3 голосов
/ 15 марта 2011

Для меня эта проблема с фильтрацией должна применяться на уровне контроллера.

Вам понадобится что-то, что определит, как решить, какие атрибуты доступны для записи для данного пользователя.

# 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

Идея состоит в том, что вы могли бы затем определить allow_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

Эти примеры предназначены скорее для иллюстрации, чем для определенности, я надеюсь, что они помогут в реализации этого.

1 голос
/ 16 марта 2011

Я бы избегал любого решения, основанного на доступе пользователя в модели, потому что оно кажется потенциально опасным.Я бы попробовал этот подход:

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)
0 голосов
/ 30 июня 2012

В Rails 3.1+ для этой цели предусмотрен метод + assign_attributes + - http://apidock.com/rails/ActiveRecord/AttributeAssignment/assign_attributes.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...