I am using clearance for signup and have a couple extra attributes beyond email and password that are in the signup form including first_name, last_name, role, and university conditional on if role is staff.
I have three user roles:
enum role: { staff: 0, clinician: 1, admin: 2 }
Something is wrong with my implementation, because signup attempt leads to the following error:
ActiveModel::ForbiddenAttributesError in Clearance::UsersController#create
with this line of code highlighted as the offending line:
Clearance.configuration.user_model.new(user_params).tap do |user|
What am I doing wrong? Any help would be greatly appreciated.
Here is my entire app/clearance/users_controller.rb
class Clearance::UsersController < Clearance::BaseController
if respond_to?(:before_action)
before_action :redirect_signed_in_users, only: [:create, :new]
skip_before_action :require_login, only: [:create, :new], raise: false
skip_before_action :authorize, only: [:create, :new], raise: false
else
before_filter :redirect_signed_in_users, only: [:create, :new]
skip_before_filter :require_login, only: [:create, :new], raise: false
skip_before_filter :authorize, only: [:create, :new], raise: false
end
layout 'authentication'
def new
@user = user_from_params
render template: "users/new"
end
def create
@user = user_from_params
if @user.save
case @user.role
when "staff"
validate_user(@user)
when "clinician"
user.update_attribute(:approved, true)
deliver_email(@user)
end
else
render template: "users/new"
end
end
private
def validate_user(user)
if user.whitelisted?
user.update_attribute(:approved, true)
deliver_email(user)
else
redirect_to sign_in_path,
notice: "Your request will be analyzed"
end
end
def deliver_email(user)
user.forgot_password! #Generates confirmation token only
mail = ::ClearanceMailer.confirm_email(user)
if mail.respond_to?(:deliver_later)
mail.deliver_later
else
mail.deliver
end
redirect_to sign_in_path,
notice: "Please confirm your email address."
end
def avoid_sign_in
warn "[DEPRECATION] Clearance's `avoid_sign_in` before_filter is " +
"deprecated. Use `redirect_signed_in_users` instead. " +
"Be sure to update any instances of `skip_before_filter :avoid_sign_in`" +
" or `skip_before_action :avoid_sign_in` as well"
redirect_signed_in_users
end
def redirect_signed_in_users
if signed_in?
redirect_to Clearance.configuration.redirect_url
end
end
def url_after_create
Clearance.configuration.redirect_url
end
def user_params
params.require(:user).permit(
:email,
:password,
:role,
:first_name,
:last_name,
:university_id
)
end
Clearance.configuration.user_model.new(user_params).tap do |user|
user.email = email
user.password = password
user.role = role
user.first_name = first_name
user.last_name = last_name
if role == "staff"
user.university = University.find(university)
end
end
end
def user_params
params[Clearance.configuration.user_parameter] || Hash.new
end
end
Clearance is built to be usable with new versions of Rails which use Strong Parameters and older versions of Rails that do not. This means this code is a bit more complex than desired. The default implementation of user_from_params
explicitly assigns email and password so we completely avoid mass assignment when using attr_accessible or strong parameters. However, we also pass the remaining parameters through to the user model with Clearance.configuration.user_model.new(user_params)
.
Instead of overriding user_from_params
, I'd investigate overriding user_params
instead, to explicitly permit the fields you want to allow through:
def user_params
params.require(:user).permit(
:email,
:password,
:role,
:first_name,
:last_name,
:university_id
)
end
It's worth pointing out that assigning role
like this does actually seem like a mass assignment vulnerability. You're allowing the user to select their access level. If this is some sort of trusted system where this is okay, then the approach you're taking seems fine I guess. If you're relying on some front end form fields to limit who can register for each role... don't.
This method should live inside your users controller, which should inherit from Clearance::UsersController
. It seems you've opted to redefine Clearance::UsersController
instead. I'd recommend creating your own UsersController like the following:
class UsersController < Clearance::UsersController
layout "authentication"
private
def user_params
params.require(:user).permit(
:email,
:password,
:role,
:first_name,
:last_name,
:university_id
)
end
end
This has none of the user validation logic of the controller you submitted. I'd try to move this to your User
model. Failing that, you can override create
in your controller as well.
You'll need to make sure your routes file points to your UsersController
rather than Clearance::UsersController
.