diff --git a/README.md b/README.md index 00dc8407..52135f22 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ There are just three actions in Devise that allows any set of parameters to be p * `sign_up` (`Devise::RegistrationsController#create`) - Permits authentication keys plus `password` and `password_confirmation` * `account_update` (`Devise::RegistrationsController#update`) - Permits authentication keys plus `password`, `password_confirmation` and `current_password` -In case you want to customize the permitted parameters (the lazy way™) you can do with a simple before filter in your `ApplicationController`: +In case you want to permit additional parameters (the lazy way™) you can do with a simple before filter in your `ApplicationController`: ```ruby class ApplicationController < ActionController::Base @@ -196,11 +196,27 @@ class ApplicationController < ActionController::Base protected def configure_permitted_parameters - devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email) } + # permit parameters for all actions + devise_permitted_parameters.add(:username, :age) + + # permit a parameter for a single action + devise_permitted_parameters.for(:sign_up) << :hometown end end ``` +To remove or overwrite the defaults that Devise provides: + +```ruby +def configure_permitted_parameters + # remove a permitted parameter + devise_permitted_parameters.remove(:email) + + # overwrite the Devise defaults + devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email) } +end +``` + If you have multiple Devise models, you may want to set up different parameter sanitizer per model. In this case, we recommend inheriting from `Devise::ParameterSanitizer` and add your own logic: ```ruby diff --git a/lib/devise/controllers/helpers.rb b/lib/devise/controllers/helpers.rb index a53469a5..a187fddf 100644 --- a/lib/devise/controllers/helpers.rb +++ b/lib/devise/controllers/helpers.rb @@ -91,6 +91,10 @@ module Devise end end + def devise_permitted_parameters + devise_parameter_sanitizer.permitted_parameters + end + # Tell warden that params authentication is allowed for that specific page. def allow_params_authentication! request.env["devise.allow_params_authentication"] = true diff --git a/lib/devise/parameter_sanitizer.rb b/lib/devise/parameter_sanitizer.rb index 0a6f3d4b..69f6c9c8 100644 --- a/lib/devise/parameter_sanitizer.rb +++ b/lib/devise/parameter_sanitizer.rb @@ -30,34 +30,61 @@ module Devise end class ParameterSanitizer < BaseSanitizer + + class PermittedParameters + + def initialize(resource_class) + @resource_class = resource_class + @for = { :sign_in => sign_in, :sign_up => sign_up, :account_update => account_update } + end + + def sign_in + auth_keys + [:password, :remember_me] + end + + def sign_up + auth_keys + [:password, :password_confirmation] + end + + def account_update + auth_keys + [:password, :password_confirmation, :current_password] + end + + def auth_keys + @resource_class.authentication_keys.respond_to?(:keys) ? @resource_class.authentication_keys.keys : @resource_class.authentication_keys + end + + def for(kind) + @for[kind] + end + + def add(*params) + @for.each { |action, permitted| permitted.push *params } + end + + def remove(*params) + @for.each do |action, permitted| + permitted.delete_if { |param| params.include? param } + end + end + + end + + def permitted_parameters + @permitted_parameters ||= PermittedParameters.new(@resource_class) + end + private def fallback_for(kind) if respond_to?(kind, true) send(kind) + elsif (permitted = permitted_parameters.for(kind)) + default_params.permit permitted else raise NotImplementedError, "Devise Parameter Sanitizer doesn't know how to sanitize parameters for #{kind}" end end - # These are the params used to sign in a user so we don't need to - # mass-assign the password param in order to authenticate. Excluding it - # here allows us to construct a new user without sensitive information if - # authentication fails. - def sign_in - default_params.permit(*auth_keys + [:password, :remember_me]) - end - - def sign_up - default_params.permit(*(auth_keys + [:password, :password_confirmation])) - end - - def account_update - default_params.permit(*(auth_keys + [:password, :password_confirmation, :current_password])) - end - - def auth_keys - resource_class.authentication_keys.respond_to?(:keys) ? resource_class.authentication_keys.keys : resource_class.authentication_keys - end end end diff --git a/test/parameter_sanitizer_test.rb b/test/parameter_sanitizer_test.rb index 244d515e..061f8f7b 100644 --- a/test/parameter_sanitizer_test.rb +++ b/test/parameter_sanitizer_test.rb @@ -48,6 +48,46 @@ if defined?(ActionController::StrongParameters) assert_equal({ "email" => "jose", "password" => "invalid" }, sanitizer.for(:sign_in)) end + test 'adding permitted parameters for a single action' do + sanitizer = sanitizer(user: { "email" => "jose", "username" => "jose1" }) + sanitizer.permitted_parameters.for(:sign_up).push(:username) + + assert_equal({ "email" => "jose", "username" => "jose1" }, sanitizer.for(:sign_up)) + assert_equal({ "email" => "jose" }, sanitizer.for(:sign_in)) + end + + test 'adding permitted parameters for all actions' do + sanitizer = sanitizer(user: { "email" => "jose", "username" => "jose1" }) + sanitizer.permitted_parameters.add(:username) + + assert_equal({ "email" => "jose", "username" => "jose1" }, sanitizer.for(:sign_in)) + assert_equal({ "email" => "jose", "username" => "jose1" }, sanitizer.for(:sign_up)) + assert_equal({ "email" => "jose", "username" => "jose1" }, sanitizer.for(:account_update)) + end + + test 'removing default parameters' do + sanitizer = sanitizer(user: { "email" => "jose", "password" => "invalid" }) + sanitizer.permitted_parameters.remove(:email) + + assert_equal({ "password" => "invalid" }, sanitizer.for(:sign_in)) + assert_equal({ "password" => "invalid" }, sanitizer.for(:sign_up)) + assert_equal({ "password" => "invalid" }, sanitizer.for(:account_update)) + end + + test 'adding multiple permitted parameters' do + sanitizer = sanitizer(user: { "email" => "jose", "username" => "jose1", "role" => "valid" }) + + sanitizer.permitted_parameters.add(:username, :role) + assert_equal({ "email" => "jose", "username" => "jose1", "role" => "valid" }, sanitizer.for(:sign_in)) + end + + test 'removing multiple default parameters' do + sanitizer = sanitizer(user: { "email" => "jose", "password" => "invalid", "remember_me" => "1" }) + sanitizer.permitted_parameters.remove(:email, :password) + + assert_equal({ "remember_me" => "1" }, sanitizer.for(:sign_in)) + end + test 'raises on unknown hooks' do sanitizer = sanitizer(user: { "email" => "jose", "password" => "invalid" }) assert_raise NotImplementedError do