Using ActiveAdmin to edit/create Users -- ForbiddenAttributesError - ruby-on-rails

So I've gone through the Rails tutorial here:
http://ruby.railstutorial.org/ruby-on-rails-tutorial-book
and am trying to get ActiveAdmin to be able to delete Users. Via the tutorial, my User model has_secure_password and also has a remember_token attribute. Consequently, when I go to my ActiveAdmin Users page and try to edit a User, the fields that are to be filled in are: Username, Email, Password Digest, Remember Token.
When I, for instance, modify the name field and try to submit the edit request, I get a ActiveModel::ForbiddenAttributesError. This happens when I try to create a User as well. I'm thinking this obviously has something to do with my authentication/password setup, but being fairly new to Rails, I'm not sure where to start looking. Any ideas?
EDIT: I tried adding this to my app/admin/user.rb file:
controller do
def resource_params
return [] if request.get?
[ params.require(:active).permit(:name, :email, :password_digest, :remember_token) ]
end
end
and this error in my stack trace disappears:
Unpermitted parameters: utf8, _method, authenticity_token, commit, id
Now, when I hit update within ActiveAdmin, I no longer get a ForbiddenAttributesError. Instead, the page reloads, but the changes aren't committed, and I get this message in my terminal:
Started PATCH "/admin/users/59" for ...
...
...
(0.1ms) begin transaction
User Exists (0.5ms) SELECT 1 AS one FROM "users" WHERE (LOWER("users"."email") = LOWER('example-58#railstutorial.org') AND "users"."id" != 59) LIMIT 1
(0.2ms) rollback transaction
This is my users_controller.rb:
def update
#active = Active.find(params[:id])
if #active.update_attributes(active_params)
flash[:success] = "Profile updated"
redirect_to #active
else
render 'edit'
end
end
private
def active_params
return [] if request.get?
[ params.require(:active).permit(:name, :email, :password_digest, :remember_token) ]
end

I don't know ActiveAdmin specifically, but your error says you're not permitting your id param
Params
You've got your params like this:
params.permit user: [:name, :email, :password_digest, :remember_token ]
I'd start by trying this:
params.require(:user).permit(:name, :email, :password_digest, :remember_token)
ActiveAdmin
How to get ActiveAdmin to work with Strong Parameters?
According to this question, you'll need to look at the official documentation and may be able to try this:
config.before_filter do
params.permit!
end

This is an existing problem with Active Admin: https://github.com/gregbell/active_admin/issues/2595
Which is a symptom of setting:
config.action_controller.action_on_unpermitted_parameters = :raise
I don't know of a solution as of yet, and as you can see no one has commented on that ticket. The most expedient option would be not to :raise on unpermitted parameters, but to use the default behavior of skipping over them.

User.rb for ActiveAdmin example
In this case, User has_one :account
ActiveAdmin.register User do
config.batch_actions = false
# Your params here
permit_params :first_name, :last_name, :email,
:born_date, :password, :password_confirmation, :account,
account_attributes: [:country_id,:university_id, :english_level]
# stuff
end

Related

Does the Rails Console Bypass Mass-Assignment Protection?

This might be a stupid question, but please bear with me.
I've been playing around with a rails app that I am working on, and I was in the console (ie. rails c), and I decided to try to add a user to the database via the console.
My User model has a role attribute, which is excluded from the list of strong params in the UsersController. However, when I was using the console, I was able to edit the value of the new user's role, by doing update_attribute. This concerns me. Does this mean that I am not doing strong params correctly and have somehow not protected my User model from mass-assignment? Or does the rails console bypass mass assignment intentionally? Is there any security vulnerability here?
Here is the console input/output:
2.3.1 :004 > user.update_attribute("role", "admin")
(0.1ms) begin transaction
SQL (0.7ms) UPDATE "users" SET "updated_at" = ?, "role" = ? WHERE "users"."id" = ? [["updated_at", "2017-06-21 10:25:34.134203"], ["role", "admin"], ["id", 4]]
(92.1ms) commit transaction
=> true
and here is the relevant part of UsersController:
def create
sleep(rand(5)) # random delay; mitigates Brute-Force attacks
#user = User.new(user_params)
if #user.save #&& verify_recaptcha(model: #user)
if #user.update_attribute('role', 'user')
#user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
else
render 'new' #Reset the signup page
end
end
#...
#Defines which fields are permitted/required when making a new user.
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
thank you in advance.
user.update_attribute("role", "admin")
it has got nothing to do with strong parameters..
That just generates an sql query as you see in the console which updates the record.
strong parameters are used to restrict unpermitted params coming from the view/client and modify your record.
As in your case,
your user_params does not include role because you are assigning it yourself. in case you had not done that and in the request body I had sent role: 'admin',
User.new(params)
would make the user admin, if verify_recaptcha(model: #user) condition fails..

Updating Strong Params Only

First of all, I believe there must be some people, who already asked this question before but I don't know how can I google this problem. So, if it is duplicate I am sorry.
I am working on a social media site. I have user model, which I use to register users to the site. It validates, name, email, and password when registering.
I use the same model to make users edit their informations, like username.
This is what I have in my update controller:
def update
# Find an existing object using form parameters
#profile = User.find_by_id(current_user.id)
# Update the object
if #profile.update_attributes!(settings_profile_params)
# If save succeeds, redirect to itself
redirect_to request.referrer
else
# If save fails, redisplay the form so user can fix the problems
render('edit')
end
end
private # user_params is not an action, that is why it is private.
def settings_profile_params
params.require(:user).permit(:first_name, :last_name, :username, :school, :program, :website, :information)
end
The problem is, I only want to update strong parameters that I defined there. But I am getting an exception because of password validation. I don't know why am I getting this exception. How can I set up system to update the values in strong parameter only.
Thank you.
You can achieve this by changing you password validation. You need to add a condition on password validation.
# Password
validates :password,
:presence => {:message => 'Password cannot be blank'},
:length => {:within => 8..99, :message => 'Password length should be within 8 and 99 characters'}
:if => Proc.new { new_record? || !password.nil? }
By calling update_attributes you are implicitly invoking the same range of validations as an other update and save. You need to update on the specific params you're targeting (e.g. omitting :password).
Here, we can store that list of permitted keys in a variable that is reusable. Then we call update_attribute on each of those keys — doing so within a reduce that gives the same true/false for the switch to edit or display.
def update
# Find an existing object using form parameters
#profile = User.find_by_id(current_user.id)
# Update the object
if PERMITTED_KEYS.reduce(true) {|bool, key| bool &&= #profile.update_attribute(key, #profile.send(key)) }
# If save succeeds, redirect to itself
redirect_to request.referrer
else
# If save fails, redisplay the form so user can fix the problems
render('edit')
end
end
private
PERMITTED_KEYS = [:first_name, :last_name, :username, :school, :program, :website, :information]
# user_params is not an action, that is why it is private.
def settings_profile_params
params.require(:user).permit(PERMITTED_KEYS)
end
Having not used strong_parameters gem before, I think this would be more idiomatic to the use of the gem:
def update
# Find an existing object using form parameters
#profile = User.find_by_id(current_user.id)
# Update the object
if settings_profile_params.keys.reduce(true) {|bool, key| bool &&= #profile.update_attribute(key, #profile.send(key)) }
# If save succeeds, redirect to itself
redirect_to request.referrer
else
# If save fails, redisplay the form so user can fix the problems
render('edit')
end
end
private
# user_params is not an action, that is why it is private.
def settings_profile_params
params.require(:user).permit(
:first_name, :last_name, :username,
:school, :program,
:website, :information
)
end
Though, I still think this is a duplicate question, since it regard how to update model data without all of the defined validation. I've answered in case the update_attributes loop is felt to be a sufficiently unique solution to warrant non-duplication.
Okay, now I found the problem. First of all, #Muntasim figured out a way to solve this problem. But I actually don't need to use this solution, because there is another easy way to fix this.
In this situation, when I let users to update their profiles, rails should not validate my password or any other column in user model, if I don't ask it to. But why was it validating? Because I have validates :password in user model. Instead it has to be validates :digest_password. Because I am using bcrypt.
I don't know why :password was working fine when I register even though I used bcrypt.

Associated models and a nested form with validation not working

Update2: I've cleaned up the code, which seems to have solved some of the problems. I've posted the new code as a new question here.
Update: Organization and User have a 1:many relationship. My question concerns a joined signup form where both an organization and user are required. After maxcal's help on the original post, I've written a new create method for my nested form ("organization has many users"), as shown below. Also I added begin...rescue...end to the create method. The situation/problem now:
Submitted with all valid info it works correctly.
Submitted with invalid info for organization (doesn't matter if user is also invalid or not), it renders the page with the error messages, as we want it to, but it only shows errors for the organization details. Also, for the user details it has then emptied all the fields, which it shouldn't.
Submitted with invalid info only for user, it renders the form again but without any error messages and all fields for user have been emptied.
Anyone got an idea what is wrong with the code? The problem seems to be more with the nested user than with organization (the parent). Also, users_attributes.empty? doesn't work, since an empty submitted form still includes such attributes, according to the log:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"***", "organization"=>{"name"=>"", "bag"=>"", "users_attributes"=>{"0"=>{"email"=>"", "username"=>"", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "usertype"=>"2", "admin"=>"true"}}}, "commit"=>"Register"}
.
def create
#organization = Organization.new(new_params.except(:users_attributes))
begin
if users_attributes.empty?
#organisation.errors.add(:users, 'No user provided')
end
#organization.transaction do
#organization.save!
if users_attributes.any?
#organization.users.create!(users_attributes)
end
end
rescue ActiveRecord::RecordInvalid => invalid
if #organization.persisted?
if #organization.users.any?
#organization.users.each do |single_user|
single_user.send_activation_email
end
end
flash[:success] = "Confirmation email sent."
redirect_to root_url
else
#organization.users.build if #organization.users.blank?
render :new
end
end
end
private
# converts the hash of nested attributes hashes to an array
def users_attributes
new_params[:users_attributes].values
end
end
Original question:
I have two associated models and a nested form with validation. Unfortunately, it’s not working. 1) On seeding it generates the error Validation failed: Users organization can't be blank. I previously posted a question about this and prematurely concluded it had solved it. It hasn’t. 2) Submitting my nested signup form with all fields filled in correctly, produces the flash error message The form contains 1 error. Users organization can't be blank.
How should I adjust my code to solve these issues?
Model files:
#User model
belongs_to :organization, inverse_of: :users
validates_presence_of :organization_id, :unless => 'usertype == 1'
# Organization model
has_many :users, dependent: :destroy
accepts_nested_attributes_for :users, :reject_if => :all_blank, :allow_destroy => true
validate :check_user
private
def check_user
if users.empty?
errors.add(:base, 'User not present')
end
end
Organization Controller methods
def new
#organization = Organization.new
#user = #organization.users.build
end
def create
#organization = Organization.new(new_params)
if #organization.save
#organization.users.each do |single_user|
single_user.send_activation_email # Method in user model file.
end
flash[:success] = "Confirmation email sent."
redirect_to root_url
else
#organization.users.build if #organization.users.blank?
render 'new'
end
end
def new_params
params.require(:organization).permit(:name, :bag,
users_attributes: [:email, :username, :usertype, :password, :password_confirmation])
end
The form:
<%= form_for #organization, url: organizations_path do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.text_field :name %>
<%= f.text_field :bag %>
<%= f.fields_for :users do |p| %>
<%= p.email_field :email %>
<%= p.text_field :username %>
<%= p.text_field :fullname %>
<%= p.password_field :password %>
<%= p.password_field :password_confirmation %>
<%= p.hidden_field :usertype, value: 2 %>
<% end %>
In my Seeds file I have:
Organization.create!(name: "Fictious business",
address: Faker::Address.street_address,
city: Faker::Address.city,
users_attributes: [email: "helpst#example.com",
username: "helpyzghtst",
usertype: 2,
password: "foobar",
password_confirmation: "foobar"])
The log on the error on submitting the signup form:
Started POST "/organizations"
Processing by OrganizationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"0cR***Nnx4iReMiePg==", "organization"=>{"name"=>"test21", "bag"=>"tes21", "users_attributes"=>{"0"=>{"email"=>"test21#example.com", "username"=>"test21", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "usertype"=>"2"}}}, "commit"=>"Register"}
(0.2ms) BEGIN
User Exists (1.1ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER('test21#example.com') LIMIT 1
(0.7ms) SELECT "users"."email" FROM "users" ORDER BY "users"."username" ASC
User Exists (0.3ms) SELECT 1 AS one FROM "users" WHERE LOWER(users"."username") = LOWER('test21') LIMIT 1
Organization Exists (0.6ms) SELECT 1 AS one FROM "organizations" WHERE LOWER("organizations"."name") = LOWER('test21') LIMIT 1
Organization Exists (0.4ms) SELECT 1 AS one FROM "organizations" WHERE LOWER("organizations"."bag") = LOWER('tes21') LIMIT 1
(0.2ms) ROLLBACK
Your validation does not work due to a Catch-22
To apply for this job, you would have to be insane; but if you are
insane, you are unacceptable.
ActiveRecord models get their ID from the database when they are saved.
But the validation on the nested user runs before the organization is inserted into the the database.
You would guess that just checking validates_presence_of instead would pass:
validates_presence_of :organization, unless: -> { usertype == 1 }
Unfortunatly not. In order for validates_presence_of :organization to pass the organization must be persisted to the database. Catch-22 again.
In order for the validation to pass we would need to split creating the organization and user into two steps:
org = Organization.create(name: 'M & M Enterprises')
user = org.users.build(username: 'milo_minderbinder', ...)
user.valid?
Unfortunatly the means that you cannot use accepts_nested_attributes_for :users - well at least not straight off the bat.
By using a transaction we can insert the organization into the the database and and roll back if the user is not valid.
def create
#organization = Organization.new(new_params.except(:users_attributes))
#organization.transaction do
#organization.save!
if new_params[:users_attributes].any?
#organization.users.create!(new_params[:users_attributes])
end
end
if #organization.persisted?
# ...
if #organization.users.any?
# send emails ...
end
else
#organization.users.build if #organization.users.blank?
render :new
end
end
Followup questions
We use #organization.persisted? since we presumably want to redirect to the newly created organisation no matter if the there is a User record created.
because the emails are sent to users? It shouldn't matter since organization is rolled back if no user is created.
The transaction is not rolled back if there is no user created. Only if the user(s) fails to save due to invalid parameters. This is based on your requirement:
But an organization can also (temporarily) have no users.
If you need the #organisation to be invalid without users you could do:
#organisation.errors.add(:users, 'No users provided') unless new_params[:users_attributes].any?
#organization.transaction do
#organization.save!
if new_params[:users_attributes].any?
#organization.users.create!(new_params[:users_attributes])
end
end
You would use #organization.users.any? to check if there are any users. #organization.users.persisted? will not work since .persisted? is a method on model instances - not collections.
On a different note, I assume it's not possible to overwrite/update an existing organization/user with this method (which shouldn't be) instead of always creating a new record?
Right, since this will always issue two SQL insert statements it will not alter existing records.
It is up to you however to create validations that guarantee the uniqueness of the database columns (IE you don't want several records with the same user.email or organiation.name).
On the plus side is that none of these caveats apply when updating an existing organization:
def update
#organisation.update(... params for org and and users ...)
end
Since you don't get the whole chicken or egg dilemma when validating the users.

how to update db with private helper method?

New to rails, and I think i found my problem but I'm not sure if this is the case
def additional_info
#user = User.find params[:id]
end
def update
#user = User.find(params[:id])
if #user.update(user_addinfo)
render user_path
else
render action: 'additional_info'
end
end
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
def user_addinfo
params.require(:user).permit(:years_business, :years_relationships, :years_careers, :years_lifeoutlook)
end
end
A user suggested that i change the
if #user.update(user_addinfo) to -> if#user.update!(user_addinfo)
the result was an error page saying my password is too short! I went back reread Michael Hartl's guide and rails api on the params. section and i believe this is causing an issue? I've tried changing to
params.permit(:years_business, :years_relationships, :years_careers, :years_lifeoutlook)
only and it still gives a password too short error.... what am i doing wrong? or am i totally misunderstanding the params?
There's probably a length validation in the User model that checks for the length of the given password.
Open the app/models/user.rb file and look for a line like this:
validates :password, length: { minimum: 4 }
or:
validates_length_of :password

Rails 3.2, Mass Assignment, Dynamic Roles?

I have a Rails app with a user model that contains an admin attribute. It's locked down using attr_accessible. My model looks like this:
attr_accessible :name, :email, :other_email, :plant_id, :password, :password_confirmation
attr_accessible :name, :email, :other_email, :plant_id, :password, :password_confirmation, :admin, :as => :admin
And here's what my update method in my users controller looks like:
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user], :as => current_user_role.to_sym)
flash[:notice] = "Profile updated"
redirect_to edit_user_url(#user)
else
render 'edit'
end
end
I have a helper method in my application controller that passes back the role as a string:
def current_user_role
#current_user_role ||= current_user.admin? ? "admin" : "default"
end
helper_method :current_user_role
I've also set config.active_record.whitelist_attributes = true in config/application.rb.
I've verified that the current_user_role method is returning the proper value based on the current user's admin status. Rails isn't throwing a mass-assignment error. But when I try to update a user's admin status while logged in as an admin, Rails performs the update and silently ignores the admin attribute. Pulling up the user's record in the Rails console shows that the record hasn't been modified.
I have a feeling there's a Ruby- or Rails-specific issue at play that I'm not aware of. I can't locate any info on making the role dynamic. The best I could find was this.
There was an errant attr_accessor :admin in my model that was left in from a prior attempt at getting this to work. I overlooked it. Removing it fixed it.
So, the upshot is that this is a pretty simple way to get dynamic roles working in Rails 3.2.
Looks like it could be a bug in Rails 3.2
https://github.com/stffn/declarative_authorization/issues/127

Resources