Usando o strong_parameters no Rails
Leia em 2 minutos
À partir do Rails 3.2.3 foi adicionada uma medida de segurança que marca todos os atributos de modelos do ActiveRecord como protegidos, em uma tentativa para impedir que incidentes como o do Github aconteçam novamente.
Mesmo com esta medida, que usa o método ActiveRecord::Base.attr_accessible, foram iniciadas discussões sobre outras maneiras de se resolver o problema. O Rails 4 virá com uma funcionalidade que permite filtrar quais parâmetros pode ser recebidos, mas que pode ser usada hoje mesmo em seu app através da biblioteca strong_parameters.
Configurando seu app
Se você quiser, pode desativar a whitelist dos atributos que podem ser atribuídos. Para fazer isso, basta editar o arquivo “config/application.rb”.
require File.expand_path("../boot", __FILE__)
require "rails"
require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "sprockets/railtie"
Bundler.require(*Rails.groups(:assets => %w[development test]))
module HOWTO
class Application < Rails::Application
config.active_record.whitelist_attributes = false
end
end
Particularmente acho uma boa ideia continuar usando o ActiveRecord::Base.attr_accessible
, principalmente se você terá vários perfis de atribuição, como o exemplo abaixo:
class User < ActiveRecord::Base
DEFAULT_ATTRIBUTES = [:name, :email, :password, :password_confirmation, :username]
attr_accessible *DEFAULT_ATTRIBUTES
attr_accessible *DEFAULT_ATTRIBUTES, :role, :as => :admin
end
Uma outra motivação é que o ponto de entrada de dados pode mudar na aplicação, como background jobs e importadores de conteúdo, e nem sempre queremos que todos os atributos sejam definidos por atribuição em massa.
Na prática, isso significa que um usuário que é admin, pode facilmente definir atributos extras usando este novo perfil.
User.new(params[:user], :as => :admin)
Independente de sua escolha, você precisará incluir o módulo ActiveModel::ForbiddenAttributesProtection
em cada modelo que pretende proteger. Supondo que você queira fazer isso no modelo User
:
class User < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
end
Não é preciso dizer que isso pode ser extremamente chato de se fazer. Uma alternativa é incluir este módulo diretamente na classe ActiveRecord::Base
. Crie um arquivo “config/initializers/strong_parameters.rb” com o conteúdo abaixo. Óbviamente, isso fará com que todos os modelos sejam protegidos.
ActiveRecord::Base.class_eval do
include ActiveModel::ForbiddenAttributesProtection
end
Agora você pode definir no controller quais atributos são obrigatórios, além da lista de atributos permitidos durante a atribuição.
class SignupController < ApplicationController
def create
@user = User.create(user_params)
respond_with(@user)
end
private
def user_params
params
.require(:user)
.permit(:name, :email, :password, :password_confirmation, :username)
end
end
Perceba que estou usando o método SignupController#user_params
para definir quais serão os atributos utilizados. Se um atributo não permitido for atribuído, uma exceção ActiveModel::ForbiddenAttributes
será lançada. E caso o atributo params[:user]
não seja enviado, uma resposta 400 Bad Request
será retornada com o texto Required parameter missing: user
.
Uma das coisas que não gostei desta abordagem foi ter justamente estas configurações em meu controller. Eu penso que isso introduz um ruído um tanto quanto desnecessário. Pensando em uma forma de como diminuir este ruído, decidi criar uma classe chamada PermittedParams
, que contém todas essas definições. Além disso, sobrescrevi o método ActionController::Base#params
para que ele use esta mesma configuração.
Primeiro vamos sobrescrever o método ActionController#params
. Abra o arquivo “app/controllers/application_controller.rb” e adicione as linhas abaixo:
class ApplicationController < ActionController::Base
private
def params
PermittedParams.new(super)
end
end
Nossa classe PermittedParams
irá delegar todas as chamadas para a implementação original, definindo apenas nossos filtros.
class PermittedParams < SimpleDelegator
def user
params
.require(:user)
.permit(:name, :email, :password, :password_confirmation, :username)
end
private
def params
__getobj__
end
end
Agora, precisamos alterar o nosso controller. Para isso, remova o método SignupController#user_params
, substituindo pela nossa nova implementação.
class SignupController < ApplicationController
def create
@user = User.create(params.user)
respond_with(@user)
end
end
Todos os filtros de outros modelos devem ser adicionados à classe PermittedParams
. A grande vantagem desta abordagem é que fica muito simples ver todos os filtros existentes no sistema. Além disso, em apps com um grande número de filtros, é muito fácil fazer a organização desses métodos em módulos que podem ser incluídos na classe PermittedParams
.