Rails 4/Devise/MongoDB: “Unpermitted parameters” u

2019-02-09 17:49发布

问题:

Trying to add a nested custom attribute, Profile (a Mongoid document), to my devise User class. When the Devise registration form is submitted, it should create both a User and a corresponding Profile object as well.

I'd like the end-result to look something like this in my MongoDB:

User:

{
  # Devise fields:
  "email": "my@email.com",
  ...
  # Custom field
  "profile" : "<object_id>"
}

Profile:

{
  "first_name": "Dave",
  ....
}

Unfortunately, I am receiving this in my console whenever I submit my registration. It successfully creates a User but fails to create an associated Profile.

Started POST "/" for 127.0.0.1 at 2013-04-20 23:37:10 -0400
Processing by Users::RegistrationsController#create as HTML
Parameters:
   {"utf8"=>"✓",
   "authenticity_token"=>"awN2GU8EYEfisU0",
   "user"=>
       {"profile_attributes"=>
           {"first_name"=>"Dave",
           "birthday(2i)"=>"4",
           "birthday(3i)"=>"21",
           "birthday(1i)"=>"1933",
           "occupation_title"=>"Software Developer"},
        "password"=>"[FILTERED]",
        "password_confirmation"=>"[FILTERED]",
        "email"=>"my@email.com"}}
Unpermitted parameters: profile_attributes

I have setup:

  • Rails 4.0.0beta1, Ruby 2.0.0-p0
  • Devise ('rails4' branch), Mongoid (from git)
  • A custom Devise registrations controller to add a definition for strong parameters.

models/user.rb:

class User
  include Mongoid::Document

  devise :database_authenticatable, :registerable,
     :recoverable, :rememberable, :trackable, :validatable,
     :token_authenticatable, :confirmable, :lockable, :timeoutable

  field :email,              type: String, default: ''

  ...

  has_one :profile
  accepts_nested_attributes_for :profile
end

models/profile.rb:

class Profile
  include Mongoid::Document
  include Mongoid::Timestamps

  # Attributes
  # ----------
  field :slug,                type: String, default: '' # Acts as user-'friendlier' slug
  field :birthday,            type: DateTime, default: DateTime.now
  field :first_name,          type: String, default: ''
  field :occupation_title,    type: String, default: ''

  belongs_to :user
  embeds_many :photos
  has_one :occupation_industry, :as => :industry
end

controllers/users/registrations_controller.rb

class Users::RegistrationsController < Devise::RegistrationsController

  def resource_params
    params.require(:user).permit(:email, :password, :password_confirmation, :profile_attributes)
  end
  private :resource_params
end

routes.rb

devise_for  :users,
              :path => '',
              :path_names => {
                :sign_in => 'login',
                :sign_out => 'logout',
                :sign_up => 'register'
                },
              :controllers => {
                :registrations => "users/registrations",
                :passwords => "users/passwords"
              }

I have already looked at these related posts, they didn't seem to help:

  • Rails 4 Nested Attributes Unpermitted Parameters
  • https://gist.github.com/kazpsp/3350730

EDIT:

Looks like Devise does actually support strong parameters in its 'rails4' branch (which is supposed to be merged into master in a few days.) Looking through the code, it appears you can override a params function for each action on devise controllers. For creating new users, its sign_up_params instead of resource_params in my example.

Despite changing this name to the proper one, it still didn't work... only whitelisting all parameters using this bang seemed to work:

def sign_up_params
  params.require(:user).permit!
end

Obviously, this kind of defeats the purpose of strong parameters... so now the question is how do I permit my nested attributes profile_attributes (as seen in my original question)?

回答1:

I had the exact same issue and overriding sign_up_params did work for me

def sign_up_params
   params.require(:user).permit(:email, :password, :password_confirmation, :other, :etc)
end

of course, the difference is in that mine are just scalar values, while you're trying to mass assign a relation... I guess that's where you should look for.

By the way, the documentations is still inexistint in this topic (too new), and code commnents suggest to override devise_parameter_sanitizer, which isn't necessary.



回答2:

I found a different method that allows all the devise overriding logic and code to reside in the application controller. This allows any and all custom params to be passed through for each devise action (sign in, sign up, update). I also add a parameter sanitizer for devise_invitable and handle that logic here (invite, accept_invitation). I've got custom params like avatar, avatar_cache, etc:

#application_controller.rb

  before_filter :configure_permitted_parameters, if: :devise_controller?

protected
  # There are just three actions in Devise that allows any set of parameters to be passed down to the model, 
  # therefore requiring sanitization. Their names and the permited parameters by default are:

  # sign_in (Devise::SessionsController#new) - Permits only the authentication keys (like email)
  # 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. More at https://github.com/plataformatec/devise#strong-parameters

  def configure_permitted_parameters
    devise_parameter_sanitizer.for(:accept_invitation) do |u|
      u.permit(:username,:validate_username, :password,:password_confirmation, :invitation_token)
    end
    devise_parameter_sanitizer.for(:invite) do |u|
      u.permit(:name,:comments)
    end

    devise_parameter_sanitizer.for(:sign_up) do |u|
      u.permit(:username,:password,:password_confirmation)
    end
    devise_parameter_sanitizer.for(:sign_in) do |u|
      u.permit(:username,:email,:password,:password_confirmation,:phone, :validate_username, :avatar_cache, :remove_avatar, :current_password,:remember_me)
    end

    devise_parameter_sanitizer.for(:account_update) do |u|
      u.permit(:username,:email,:password,:password_confirmation,:phone, :validate_username,:avatar, :avatar_cache, :remove_avatar, :current_password)
    end
  end

Find and read more at https://github.com/plataformatec/devise#strong-parameters



回答3:

I had the same issue when login, it says: Unpermitted parameters: password, remember_me. and because i have any controller that inheriting Devise::SessionsController, so i use my own parameter sanitizer.

here is what i do:

Create a file in '#{Rails.root}/lib' fold, my is hzsapa_parameter_sanitizer.rb and required in config/application.rb, then override devise_parameter_sanitizer method in application_controller.rb

lib/hzsapa_parameter_sanitizer.rb

class HzsapaParameterSanitizer < Devise::ParameterSanitizer
  def sign_in
    default_params.permit(auth_keys + [:password, :remember_me])
  end
end

You can override those method depends on your issue:

def sign_in
  default_params.permit(auth_keys)
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

config/application.rb

require "hzsapa_parameter_sanitizer"

app/application_controller.rb

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  def devise_parameter_sanitizer
    @devise_parameter_sanitizer ||= if defined?(ActionController::StrongParameters)
                                      HzsapaParameterSanitizer.new(resource_class, resource_name, params)
                                    else
                                      Devise::BaseSanitizer.new(resource_class, resource_name, params)
                                    end
  end
end

Edit: i just found the solution in devise README, you can follow it here



回答4:

I used your code and it worked for me!

Here is what I did

class RegistrationsController < Devise::RegistrationsController
  skip_before_filter :verify_authenticity_token, :only => :create #, :if => Proc.new { |c| c.request.format == 'application/json' }
  respond_to :json, :html, :xml

  def create
    user = User.new(devise_registrations_permitted_parameters)
    if user.save
      render :json=> user.as_json(:auth_token=>user.authentication_token, :email=>user.email,:name => user.name), :status=>201
      return
    else
      warden.custom_failure!
      render :json=> user.errors, :status=>422
    end
  end


  protected                                                            
    def devise_registrations_permitted_parameters
      params.require(:user).permit(:name, :email, :password, :password_confirmation)
    end

end