Does the Rails Console Bypass Mass-Assignment Protection? - ruby-on-rails

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..

Related

How to avoid UniqueViolation errors

My facebook-like application gives users the opportunity to delete unwanted messages from following users. For this purpose I created a MicropostQuarantine association between users and microposts: if such an association between a user and a micropost exists, then that user will not see that micropost in their feed. However, microposts will be visible also in a user's profile page, included those already in quarantine. So, if I removed a micropost from my feed and visited the profile page of that micropost's user, I would still see that micropost and have access to the remove button that would raise the following error:
ActiveRecord::RecordNotUnique (PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_micropost_quarantines_on_user_id_and_micropost_id"
DETAIL: Key (user_id, micropost_id)=(1, 300) already exists.
: INSERT INTO "micropost_quarantines" ("user_id", "micropost_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"):
The quarantine method in the microposts controller is simple:
def quarantine
current_user.micropost_quarantines.create!(micropost_id: #micropost.id)
end
There is a before_action :entitled_user, only: :quarantine defined as follows:
def entitled_user
#micropost = Micropost.find(params[:id])
redirect_to root_url unless (current_user == #micropost.user || current_user.following.include?(#micropost.user))
end
As you can see a user can only quarantine his own microposts and microposts from following users. In order to avoid UniqueViolation errors, I thought of adding some code to the entitled_user method, to check if the association already exists:
def entitled_user
#micropost = Micropost.find(params[:id])
#quarantine = MicropostQuarantine.find_by(micropost_id: #micropost.id, user_id: current_user.id)
redirect_to root_url unless (current_user == #micropost.user || current_user.following.include?(#micropost.user) || #quarantine.nil?)
end
However this does not work: the entitled_user method is ignored/bypassed by rails for some unknown reasons, I keep receiving the UniqueViolation: ERROR from ActiveRecord and the whole unless conditional is ignored, so that I would be able to quarantine microposts from non following users.
I think we shoudn't use unless in complicated condition, try to the following:
redirect_to root_url if (current_user != #micropost.user && current_user.following.exclude?(#micropost.user)) || #quarantine.present?
Tips:
def entitled_user
#micropost = Micropost.find(params[:id])
if (current_user.id != #micropost.user_id && !current_user.following.exists?(#micropost.user_id)) ||
current_user.micropost_quarantines.exists?(micropost_id: #micropost.id)
redirect_to root_path and return
end
end
Using :exists? (SQL side) is more efficient than :include? (Ruby side)
Using #micropost.user_id instead of #micropost.user because we don't need instance #user, so we don't need to do like this:
SELECT (*) FROM users WHERE id = #{#micropost.user_id}
Hope this helps!

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.

Rails assign session variable

Hi I'm hacking around Rails the last 8 months so don't truly understand a number of things. I've seen and used helper methods that set the current_user etc. and am now trying to replicate something similar.
I'm trying to set a global value in the application controller for the current company the user is in. I know I'm doing it wrong but I can't quite figure out how to solve it or even if I'm approaching it correctly. I want it so that when the user clicks on a company, the global variable is set to the id of the company they are in. Then in any other sub-model, if I want info on the company, I use the global variable to retrieve the company object with that id.
The code that's causing the problem is in my navbar in application.html.erb
<li><%= link_to "Company", company_path, :method => :get %></li>
This works when I'm using the companies controller etc. But when I try use any other controller I'm getting the error
ActionController::UrlGenerationError in Employees#index
No route matches {:action=>"show", :controller=>"companies"} missing required keys: [:id]
which as I understand it means it can't render the url because no company id parameter is being passed?
I was trying to hack a helper method I used in another project (for getting the current_user) but have realised that it uses the session to extract the user.id to return a user object. Here's my attempt at the helper method in application_controller.rb
def current_company
#company = Company.find_by_id(params[:id])
end
I'm not able to get the current company from the session so my method above is useless unless the company id is being passed as a parameter.
I've thought about passing the company id on every sub-model method but
I'm not 100% sure how to do this
It doesn't sound very efficient
So my question is am I approaching this correctly? What's the optimal way to do it? Can I create a helper method that stores a global company id variable that gets set once a user accesses a company and can then be retrieved by other models?
I probably haven't explained it too well so let me know if you need clarification or more info. Thanks for looking.
Edit 1
Made the changes suggested by Ruby Racer and now I have:
application.html.erb
<%unless current_page?(root_path)||current_page?(companies_path)||current_company.nil? %>
<li><%= link_to "Company", company_path, :method => :get %></li>
This is not displaying the link in the navbar, I presume because current_company is nil (the other two unless statements were fine before I added current_company.nil?
I'm setting the current_company in
companies_controller.rb
before_action :set_company, only: [:show, :edit, :update, :destroy, :company_home]
def company_home
current_company = #company
respond_with(#company)
end
application_controller.rb
def current_company=(company)
session[:current_company] = company.id
puts "The current_company has been assigned"
puts params.inspect
end
def current_company
#company = Company.find_by_id(session[:current_company])
puts "The current_company helper has been called"
puts #company
puts params.inspect
end
Am I doing something wrong?
Edit 2
I have no idea why this isn't working. After the above edits, it appears as though the session[:company_id] is not being assigned so the current_company helper method is returning nil. I've tried printing the session paramaters puts session.inspect and can't find any company_id information. Anyone any idea why it isn't assigning the value?
Edit 3
Can't for the life of me figure out what's going wrong. I've tried multiple things including moving the current_company = #company into the set_company method in companies_controller.rb which now looks like this:
def company_home
puts "Test the current company"
puts "#{#company.id} #{#company.name}"
puts params.inspect
end
private
def set_company
#company = Company.find_by_id(params[:id])
if #company.nil?||current_user.organisation_id != #company.organisation.id
flash[:alert] = "Stop poking around you nosey parker"
redirect_to root_path
else
current_company = #company
end
end
The company_home method is being given a company object (I can see this in the console output below) but the current_company assignment is just not happening. Here's the console output for reference
Started GET "/company_home/1" for 80.55.210.105 at 2014-12-19 10:26:49 +0000
Processing by CompaniesController#company_home as HTML
Parameters: {"authenticity_token"=>"gfdhjfgjhoFFHGHGFHJGhjkdgkhjgdjhHGLKJGJHpDQs6yNjONwSyTrdgjhgdjgjf=", "id"=>"1"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = 6 ORDER BY "users"."id" ASC LIMIT 1
Company Load (0.3ms) SELECT "companies".* FROM "companies" WHERE "companies"."id" = 1 LIMIT 1
Organisation Load (0.3ms) SELECT "organisations".* FROM "organisations" WHERE "organisations"."id" = $1 LIMIT 1 [["id", 6]]
Test the current company
1 Cine
{"_method"=>"get", "authenticity_token"=>"gfdhjfgjhoFFHGHGFHJGhjkdgkhjgdjhHGLKJGJHpDQs6yNjONwSyTrdgjhgdjgjf=", "controller"=>"companies", "action"=>"company_home", "id"=>"1"}
Rendered companies/company_home.html.erb within layouts/application (0.1ms)
Company Load (0.6ms) SELECT "companies".* FROM "companies" WHERE "companies"."id" IS NULL LIMIT 1
The current_company helper has been called
{"_method"=>"get", "authenticity_token"=>"gfdhjfgjhoFFHGHGFHJGhjkdgkhjgdjhHGLKJGJHpDQs6yNjONwSyTrdgjhgdjgjf=", "controller"=>"companies", "action"=>"company_home", "id"=>"1"}
CACHE (0.0ms) SELECT "organisations".* FROM "organisations" WHERE "organisations"."id" = $1 LIMIT 1 [["id", 6]]
Completed 200 OK in 280ms (Views: 274.0ms | ActiveRecord: 1.7ms)
As per above, under the line The current_company helper has been called, there's a blank line where puts #company should be outputting something. This means the current_company method is returning nothing.
Also, in the company_home method in the companies_controller, if I change puts "#{#company.id} #{#company.name}" to puts "#{current_company.id} #{current_company.name}" an error gets thrown.
Has anyone any idea why the def current_company=(company) isn't assigning a session parameter? Thanks
Final Edit
I've no idea why, but it appears the problem related to this:
def current_company=(company)
session[:current_company] = company.id
puts "The current_company has been assigned"
puts params.inspect
end
It looks as though this never gets called. I don't understand why as I've never used something like this before.
I'll put my fix in an answer.
Ok, you need to do two things.
First thing, you need to assign your company.id to a session variable
def current_company=(company)
session[:company_id]=company.id
end
Second, your helper method for current_company will be as follows:
def current_company
Company.find_by_id(session[:company_id])
end
This can be nil if there is no session[:company_id] or if it corresponds to no company. That's ok...
Next, it is quite unlikely to get it working without an id, if you use /companies both for your index and your show actions.
Now, for your first task. Setting the variable:
controller:companies_controller.rb
def show
# assuming you have a before_action, something like set_company, no need to redo it
current_company=#company # this will set the session variable
end
If you want your navbar to lead to your current_company, you will need to write:
<% unless current_company.nil? %>
<li><%= link_to "Company", current_company, :method => :get %></li>
<% end %>
I don't know what you want to do if there is no current company, so I just leave it out.
Instead of using a helper method to assign a value or search for a company, I've assigned a session variable in the set_company method in companies_controller.rb. This can then be accessed around the application
companies_controller.rb
private
def set_company
#company = Company.find_by_id(params[:id])
if #company.nil?||current_user.organisation_id != #company.organisation.id
redirect_to root_path
else
session[:current_company] = #company
current_company = #company
end
end

What has happened to the salt with bcrypt?

This is from the github page:
require 'bcrypt'
class User < ActiveRecord::Base
# users.password_hash in the database is a :string
include BCrypt
def password
#password ||= Password.new(password_hash)
end
def password=(new_password)
#password = Password.create(new_password)
self.password_hash = #password
end
end
It appears that to access the password method, you need to call it as an attribute from a create method:
#user.password = user_params[:password]
#user.save
Okay...fine? But where's the salt now stored? I just don't get it at all, how is this even remotely secure anymore?
To retrieve a hashed password, you need this method:
def password
#password ||= Password.new(password_hash)
end
And call it as an attribute:
if #user.password == params[:password]
give_token
else
false
end
So it appears everything's working without a salt...how does it do this?
This means I only need one column in my database to do with passowords now, right?password or password_hash instead of password_salt | password_hash?
Well then why does the github page say this:
But even this has weaknesses -- attackers can just run lists of
possible passwords through the same algorithm, store the results in a
big database, and then look up the passwords by their hash:
PrecomputedPassword.find_by_hash(<unique gibberish>).password #=> "secret1"
Salts
And then this is what really gets me:
The solution to this is to add a small chunk of random data -- called a salt -- to the password before it's hashed:
Why are they explaining all of this if bcrypt is handling everything automatically?
hash(salt + p) #=> <really unique gibberish>
The salt is then stored along with the hash in the database, and used to check potentially valid passwords:
<really unique gibberish> =? hash(salt + just_entered_password)
bcrypt-ruby automatically handles the storage and generation of these salts for you.
Could someone explain how bcrypt stores and generates these salts? Why does it say it handles it all for me and then goes on to tell me how to generate a salt? Do I need to run something like this in my model: self.password_hash = hash(salt + p)
Argh so confused I used to get salts and hashes utterly and now they've changed it all. Terrible, unclear docs...they appear to show you how to use bcrypt without a salt with loads of examples, and then briefly mention how to do it properly with a salt down at the bottom.
Could someone please give me an example how to use the new version of bcrypt to generate a salt and hash, and how to authenticate?
Okay, the has_secure_password is really cool. You don't need to worry about salts and hashes anymore, the salt and hash are stored as one attribute ( password_digest) in the database.
It's saved in such a way that bcrypt knows which part of the password_digest string is the salt, and what is the hash.
If you're setting up authentication from scratch, you literally need to do the following:
1) Add the bcrypt rails gem:
gem bcrypt-rails
2) Add the has_secure_password method to the model tasked with handling your user records:
class User < ActiveRecord::Base
has_secure_password
end
3) Make sure your users table has a password_digest column:
class CreateUser < ActiveRecord::Migration
create_table :users do |t|
t.username
t.password_digest
end
end
4) Create a new method to create a a new empty user instance for the form to use:
class UsersController < ApplicationController
def new
#user = User.new
end
end
5) In the new view, make a form that creates populates the params hash' :password and :username entries:
<% form_for( #user ) do |f| %>
<%= f.text_field :username %>
<%= f.password_field :password %>
<% end %>
6) Back in our controller, permit the username and the password using strong params. The whole reason behind strong params is to prevent some cheeky chappy from using dev tools to create their own html form field (such as one pertaining to id) and populating the database with malicious data:
class UsersController < ApplicationController
def new
#user = User.new
end
private
def user_params
params.require(:user).permit(:username, :password)
end
end
7) Let's create the create method that will use these permitted entries to create a new user, populated by the form:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
#user.save
redirect_to root_path
end
private
def user_params
params.require(:user).permit(:username, :password)
end
end
Set up your routes as you see fit, and that's it! The user record's password_digest column will be automatically populated with one string comprised of a salt appended with the hash of the password. Very elegant.
All you need to remember: password -> password_digest.
In order to authorise the user and signout the user, create a sessions controller with a create method and a destroy method:
def create
user = User.find_by_email(params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to admin_root_path, :notice => "Welcome back, #{user.username}"
else
flash.now.alert = "Invalid email or password"
redirect_to root_path
end
end
def destroy
reset_session
flash[:info] = "Signed out successfully!"
redirect_to root_path
end
Hope this helps someone!
bcrypt got everything covered for you. Your password digest consists of a few types of information, bcrypt algorithm type, cost, salt and the checksum.
for example:
my_password = BCrypt::Password.create("my password")
#=> "$2a$10$.kyRS8M3OICtvjBpdDd1seUtlvPKO5CmYz1VM49JL7cJWZDaoYWT."
The first part: $2a$ is the variant of the algorithm see: Where 2x prefix are used in BCrypt?
The second part 10 is the cost parameter, you can increase it to slow down the process (logarithmic value) by providing a hash {cost: 12} as the second argument to create.
Now if you call my_password.salt you get "$2a$10$.kyRS8M3OICtvjBpdDd1se" which identifies the part that is being used as the key to creating your checksum.
And finally, your checksum is "UtlvPKO5CmYz1VM49JL7cJWZDaoYWT.". That's the reason if you call create the second time the string is going to be different as another salt will be used.
But as I mentioned earlier you don't need to do anything extra as all these are being taken care of for you.

Using ActiveAdmin to edit/create Users -- ForbiddenAttributesError

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

Resources