Trouble with Mass-Assignment Error for Admin User - ruby-on-rails-3

I was trying to follow the railscasts tutorial that explains how to handle mass-assignment errors and attr_accessible for admins, but since that was a little outdated, I'm trying to follow what's in the rails API dock for 3.2.6 here.
All I want to do is allow the admin user the ability to access the "winning" attribute for the Proposal Model on the Update action.
Here's my Proposal Model showing the current attr_accessible.
class Proposal < ActiveRecord::Base
attr_accessible :email, :email_confirmation, :link, :name, :references, :short_description
belongs_to :idea
Here's my code for the Proposal Controller's Update action.
class ProposalsController < ApplicationController
include ActiveModel::MassAssignmentSecurity
attr_accessible :email, :email_confirmation, :link, :name, :references, :short_description
attr_accessible :email, :email_confirmation, :link, :name, :references, :short_description, :winning, :as => :admin
def update
#idea = Idea.find(params[:idea_id])
#proposal = #idea.proposals.find(params[:id])
if #proposal.update_attributes(proposal_params)
redirect_to idea_proposals_url(#idea)
else
render 'edit'
end
end
protected
def proposal_params
role = current_user.admin ? :admin : :default
sanitize_for_mass_assignment(params[:proposal], role)
end

Check out this Railscast. I had a similar issue with an Admin field Boolean and didn't want any user to circumvent the security by sending a curl post. If the user is an admin then it gave them the ability to access the field, otherwise Mass Assignment would protect the field from being modified.
http://railscasts.com/episodes/237-dynamic-attr-accessible?view=asciicast

Related

Rails Polymorphing has_one association redirect_to controller

In my app, user is used for the authentication phase, and with a polymorphic has_one association, it will be associated at different type of users, with different actions.
This is the user model:
class User < ApplicationRecord
has_secure_password
validates :username, presence: true,
uniqueness: true
belongs_to :role, :polymorphic => true, dependent: :destroy
end
and this is one of the models associated
class Guest < ApplicationRecord
has_one :user, as: :role
end
Logging and authentication call home_index_path, and the current user is stored in current_user.
In the Home Controller i have:
def index
if current_user
redirect_to current_user.role
else
render 'unlogged'
end
end
In route.rb i have:
resource :guest do
member do
get 'dashboard'
end
end
resolve ('Guest') {[:guest]}
Now the problem: assuming that the user is a guest, in this way i'm redirected to method show of GuestsController, but i need that is redirected to method dashboard.
How can i do?
Ok, i've resolved with polymorphing_path
redirect_to polymorphic_path([:dashboard, current_user.role])

Devise - mass assignment error when changing other users passwords in specific password change page

In my RoR application I'm using devise and the client requires a bit of customisation - basically he requires that the administrator be able to change passwords of other users and that the password change page be different than the page to edit profile details. I've set up custom actions to handle this namely my own change_password action in users controller.
Users Controller Actions
def change_password
#user = User.find(params[:id])
end
def update_password # I post to this
#user = User.find(params[:id])
if #user.update_attributes!(params[:user])
redirect_to users_path, :notice => "User updated."
else
redirect_to users_path, :alert => "Unable to update user."
end
end
Heres the routes.rb entries
devise_for :users, :skip => [:registrations]
as :user do
get 'users/edit' => 'devise/registrations#edit', :as => 'edit_user_registration'
put 'users' => 'devise/registrations#update', :as => 'user_registration'
end
resources :users
...
match "/users/:id/change_password" =>"users#change_password", :as=>:change_password_user, :via=>:get
match "/users/:id/update_password" => "users#update_password", :as=>:update_password_user, :via=>:post
And this is my users model
class User < ActiveRecord::Base
rolify
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable, :registerable,
devise :database_authenticatable, #:registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :role_ids, :as => :admin
attr_protected :username, :name, :email, :password, :password_confirmation, :remember_me
validates_uniqueness_of :username
validates_presence_of :username, :email
validates_uniqueness_of :email
end
however I keep getting this mass attributes assignment error
Can't mass-assign protected attributes: password, password_confirmation
the weird thing is that I've set all these attributes to accessible_protected. I can edit other users details but can't edit their passwords. Whats going on here?
There are many ways you can fix this problem. I'll try to explain a few.
I think the key to your problem is that you are mixing up the MassAssignmentSecurity roles. You've defined a Whitelist for the admin role and a Blacklist for the default role. The error says that you tried to assign something that was on the Blacklist for the default role.
Since you are defining different roles, I assume you probably want to fix it this way:
Change your admin Whitelist
attr_accessible :role_ids, :password, :password_confirmation, as: :admin
Then assign as the admin:
if #user.update_attributes!(params[:user], as: :admin)
(If your controller action includes fields other than the password fields, this may cause new violations.)
A different option is to stick to the default role. You can bypass security a couple ways.
The first option which I don't recommend is to not pass the password and password confirmation as part of the User params, and send them separately in your view. You can then manually set those fields like so:
#user.assign_attributes(params[:user])
#user.password = params[:password]
#user.password_confirmation = params[:password_confirmation]
if #user.save!
However, it's even easier to do the following to just skip protection:
#user.assign_attributes(params[:user], without_protection: true)
if #user.save!
For more information, this guide is fairly good:
http://guides.rubyonrails.org/security.html#mass-assignment
I hope that helps.

Can't mass-assign protected attributes with has_many association and create

This is EXTREMELY bizarre. I'm upgrading a Rails 2.3.12 app and running into this same problem over and over again. I'm stumped and nothing else out there seems to touch on it.
I have two models:
class User < ActiveRecord::Base
has_many :logs, :class_name => 'UserLog'
end
and
class UserLog < ActiveRecord::Base
attr_accessor :site_id, :controller, :action, :url, :session
belongs_to :user
validates_presence_of :user
end
then in another controller I'm doing this:
def log_user_activity
#current_user.logs.create(:site_id => #site.id, :controller => params[:controller],
:action => params[:action], :url => request.path,
:session => request.session_options[:id]) if #current_user
end
as you can see, it's pretty straightforward but when I call log_user_activity I'm getting this:
Can't mass-assign protected attributes: site_id, controller, action, url, session
HOWEVER, if I change all my creates or builds to this:
def log_user_activity
log = #current_user.logs.new
log.site_id = #site.id
log.controller = params[:controller]
log.action = params[:action]
log.url = request.path
log.session = request.session_options[:id]
log.save
end
then it works fine!?
Has anyone seen this? Any clues?
In class UserLog, add the following:
attr_accessible :site_id, :controller, :action, :url, :session
The reason you have to use attr_accessible is most likely because you are utilizing a plugin that is relying on this being present for a model. It has happened to all of us and is a royal pita)
Once attr_accessible is designated for a class, then any attribute that is not specified as 'accessible' will not be allowed to be updated.

Rails Pass A Parameter To Conditional Validation

I'm importing heaps of student data from an spreadsheet document. Each row of student data will represent a new user, however, the possibility of importing an already existing student exists and I want to bypass some of my user validations such as username uniqueness accordingly so that I can build associations for both new and existing records, but only if they're being imported to the same school.
Thus far I have the following validation setup in my User model:
user.rb
validates_uniqueness_of :username, :unless => :not_unique_to_school?
def not_unique_to_school?
user = find_by_username(self.username)
user.present? && user.school_id == 6
end
Now how would I go about replacing that 6 with a value I have access to in the controller? Instructors will be the ones handling the importing and they'll be importing students to their school so I would typically run current_user.school_id to retrieve the school id that I want them to be imported to, but I don't have access to the current_user helper in my model.
I'm not concerned about duplicating usernames as I'll be handling that on a different step, this is just the preliminary validation.
Edit
Simplified school & user model:
user.rb
class User < ActiveRecord::Base
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :username,
:first_name, :last_name, :school_id, :roles_mask
belongs_to :school
validates_presence_of :username, :on => :create, :message => "can't be blank"
validates_uniqueness_of :username, :unless => :unique_to_school?
def unique_to_school?
user = find_by_username(self.username)
user.present? && user.school_id == 6
end
def find_by_username(username)
User.where(:username => username).first
end
end
school.rb
class School < ActiveRecord::Base
attr_accessible :country_id, :name, :state_id
has_many :users
end
I'd add a method to your School model:
def student_named?(name)
self.users.where(:username => name).any?
end
then in your validation:
def not_unique_to_school?
self.school.student_named?(self.username)
end
Here's what ended up working for me:
validate :user_cant_be_duplicate_in_other_schools
def user_cant_be_duplicate_in_other_schools
errors.add(:username, :taken) if User.count(:conditions => ["school_id != ? AND username = ?", self.school_id, self.username]) > 0
end
As opposed to testing if a User belongs to a particular school we're testing for the lack of belonging to a particular school. I didn't come up with this answer, another user posted this as an answer but deleted it shortly after for reasons unknown.

Rails: Need help with nested form rejecting nested object but still creating main object

I have the following in my user model
class User < ActiveRecord::Base
has_many :gym_users
attr_accessible :gym_users_attributes, :gym_users
accepts_nested_attributes_for :gym_users, :reject_if => lambda { |a| a[:role_id].blank? }
end
This correctly rejects the gym_user model if the role_id is not present, the problem is it still creates the user and simply doesn'
t create the gym_user. Is there a way to make it not create or delete the user when the gym_user is rejected?
You can add
validates_associated :gym_users
to your User model and move validation from reject_if to GymUsers model
validates_presence_of :role_id
Add validates :gym_users, :presence => true to your User model