ruby on rails - cancancan - Proteger los atributos sensibles con la autorización declarativa
cancan gem (5)
Evitaría todas las soluciones basadas en el acceso de usuarios en el modelo porque parece potencialmente peligroso. Yo intentaría este enfoque:
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
Luego en su controlador cambie su acción de actualización desde:
@user.update_attributes(params[:user])
a
@user.update_attributes_as_user(params[:user], current_user)
¿Cuál es una buena manera de proteger los atributos por rol utilizando la declarative_authorization ? Por ejemplo, un usuario puede editar su información de contacto pero no su función.
Mi primera inclinación fue crear múltiples acciones de controlador para diferentes escenarios. Rápidamente me di cuenta de lo difícil que podría ser esto a medida que el número de atributos protegidos crece. Hacer esto para el rol de usuario es una cosa, pero puedo imaginar múltiples atributos protegidos. Agregar muchas acciones y rutas de control no se siente bien.
Mi segunda inclinación fue crear permisos en torno a atributos sensibles específicos y luego envolver los elementos del formulario con View hepers proporcionados por las autorizaciones declarativas. Sin embargo, el aspecto del modelo y del controlador es un poco confuso en mi mente. Las sugerencias serían impresionantes.
Por favor, aconseje sobre la mejor manera de proteger los atributos por rol utilizando las autorizaciones_certativas.
Para mí, este problema de filtrado es algo que debe aplicarse en el nivel del controlador.
Querrá tener algo en algún lugar que defina cómo decidir qué atributos se pueden escribir para un usuario determinado.
# 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
Luego, en el controlador de la aplicación puede definir un método para filtrar parámetros.
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
Y finalmente, en la acción de tu controlador puedes usar este filtro:
class UserController < ActionController::Base
# ...
def update
restrict_params(:user, User, @current_user)
# and continue as normal
end
# ...
end
La idea es que luego podría definir allowed_params en cada uno de sus modelos, y hacer que los controladores para cada uno de estos utilicen el mismo método de filtro. Usted podría guardar algo de la placa de la caldera si tiene un método en el controlador de la aplicación que emita un filtro anterior, como este:
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
Estos ejemplos pretenden ser ilustrativos en lugar de definitivos, espero que ayuden con la implementación de esto.
Rails 3.1+ viene con un método + assign_attributes + para este propósito - http://apidock.com/rails/ActiveRecord/AttributeAssignment/assign_attributes .
github.com/thefrontiergroup/scoped_attr_accessible , que se parece a lo que estás buscando. Solo necesita establecer el alcance al inicio de una solicitud para todos los modelos.
Para hacerlo, usa un before_filter
en tu application_controller.rb:
before_filter do |controller|
ScopedAttrAccessible.current_sanitizer_scope = controller.current_user.role
end
EDITAR 2011-05-22
Algo similar está ahora en Rails a partir de 3.1RC https://github.com/rails/rails/blob/master/activerecord/test/cases/mass_assignment_security_test.rb por lo que sugeriría ir esa ruta ahora.
RESPUESTA ORIGINAL
Simplemente tuve que transferir lo que había estado usando anteriormente a Rails 3. Nunca he usado una autorización declarativa específicamente, pero esto es bastante simple y lo suficientemente directo como para que pueda adaptarse a él.
Rails 3 agregó mass_assignment_authorizer
, lo que hace que todo esto sea realmente simple. Utilicé ese tutorial vinculado como base y lo hice encajar mejor en mi modelo de dominio, con herencia de clase y agrupando los atributos en roles.
En modelo
acts_as_accessible :admin => :all, :moderator => [:is_spam, :is_featured]
attr_accessible :title, :body # :admin, :moderator, and anyone else can set these
En el controlador
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