I'm using Devise and CanCan for user authentication and administrating roles restricting access to parts of my Rails 4 app for certain users.
I've run into some problems with updating a user. The update works fine and the user object in the db get updated as it should, but my user session is lost on the following redirect_to my user show action. current_user becomes nil which means that CanCan restricts the access to the user show action.
Why does current_user become nil after update, when this does not happen on other actions (e.g create, destroy etc.)?
These are the devise settings in my user model:
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :authentication_keys => [:login]
This is my users_controller.rb's update method:
class UsersController < ApplicationController
load_and_authorize_resource
before_filter :authenticate_user!
def update
#user = User.find(params[:id])
if params[:user][:password].blank?
params[:user].delete(:password)
end
respond_to do |format|
if #user.update_attributes(user_params)
format.html { redirect_to user_path, :notice => 'User was successfully updated.' }
format.json { head :ok }
else
format.html { render :action => "edit" }
format.json { render :json => #user.errors, :status => :unprocessable_entity }
end
end
end
end
And this is my ability.rb file:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if defined?(user.role_id)
if user.role? :admin, user.role_id
can :manage, :all
elsif user.role? :hauler, user.role_id
can :manage, [User,Trip,Invoice], user_id: user.id.to_s
else
can :create, :Trip
end
end
end
end
It depends on the update being performed. Sessions are serialized with certain bits of user data.
For instance updating the password will cause a session to be nullified because the encrypted password is part of the serialized hash, and if that is changed, the session can no longer reference the original encrypted password.
Worked for me
def update
respond_to do |format|
if #user.update(user_params)
sign_in(#user, :bypass=>true)
format.html { redirect_to #user, notice: 'User was successfully updated.' }
format.json { render :show, status: :ok, location: #user }
else
format.html { render :edit }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
Magic happens in :bypass=>true
Related
I am developing a web application for task management using ruby on rails.
I have two models user and task. The models look like this
class User < ActiveRecord::Base
has_many :tasks
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
def role?(role_name)
role == role_name
end
def self.assigned_user(task_params)
User.where("name = ?", task_params["assigned_to"])
end
end
class Task < ActiveRecord::Base
belongs_to :user
end
what i want is assign task to user when a task is created. But i don't know how to do it in create action of tasks_controller. My create action looks like this
def create
#task = Task.new(task_params)
respond_to do |format|
if #task.save
#user = User.assigned_user(task_params)
#user.tasks << Task.last
format.html { redirect_to #task, notice: 'Task was successfully created.' }
format.json { render :show, status: :created, location: #task }
else
format.html { render :new }
format.json { render json: #task.errors, status: :unprocessable_entity }
end
end
end
Whenever admin assigns a task when its created, it gives an error saying undefined method tasks for #<User::ActiveRecord_Relation:0x007fe7b1c60610>. Does anyone can help what can be the problem with this?
The error it is giving you is because the where method is returning a collection of users and you want just one user:
def self.assigned_user(task_params)
User.where("name = ?", task_params["assigned_to"]).first
end
You probably want to revise your code as I guess you could have problems if name is not unique. Make sure the field you use to retrieve the user is unique.
The code for creation can look like this:
def create
#task = Task.new(task_params)
respond_to do |format|
if #task.save
#user = User.assigned_user(task_params)
#user.tasks << #task
format.html { redirect_to #task, notice: 'Task was successfully created.' }
format.json { render :show, status: :created, location: #task }
else
format.html { render :new }
format.json { render json: #task.errors, status: :unprocessable_entity }
end
end
end
Well the error, speaks for itself:
undefined method tasks for User::ActiveRecord_Relation:0x007fe7b1c60610>
def self.assigned_user(task_params)
User.where("name = ?", task_params["assigned_to"])
end
where method returns an array(ActiveRecord_Relation), then since you need only to fetch one object, it shoud be:
User.where(name: task_params["assigned_to"]).first
or even better:
User.find_by(name: task_params["assigned_to"])
Also this line:
#user.tasks << Task.last
Will do a query in database unnecessarily.
So it should be changed to:
#user.tasks << #task
Hope it helps!!
I've been working on a social link aggregator site and have run into a wall with regards to admin permissions.
Currently all users can edit and destroy their own posts (admins included) but an error is thrown when an admin tries to delete a post created by another user.
While I'm fairly well versed in ruby my rails knowledge is severely lacking so it may well be something extremely simple staring me in the face.
When attempting to edit a post the error is: undefined method `model_name' for TrueClass:Class
When attempting to delete a post the error is: undefined method `destroy' for true:TrueClass
Here are relevant parts of my links_controller.rb:
def update
respond_to do |format|
if #link.update(link_params)
format.html { redirect_to #link, notice: 'Link was successfully updated.' }
format.json { render :show, status: :ok, location: #link }
else
format.html { render :edit }
format.json { render json: #link.errors, status: :unprocessable_entity }
end
end
end
def destroy
#link.destroy
respond_to do |format|
format.html { redirect_to links_url, notice: 'Link was successfully destroyed.' }
format.json { head :no_content }
end
end
def authorized_user
#link = current_user.links.find_by(id: params[:id]) || current_user.admin?
redirect_to links_path, notice: "Not authorized to edit this link" if #link.nil?
end
The user model:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :links
has_many :posts
acts_as_voter
validates :twitter, :twitter => { :format => :username_with_at, allow_blank: true }
validates :avatar_url, :url => { allow_blank: true }
end
And the admin model:
class Admin < ActiveRecord::Base
devise :database_authenticatable, :trackable, :timeoutable, :lockable
end
If you need any more information I'll be happy to provide it, alternatively if any of the code I've posted is irrelevant to the issue let me know and I'll remove it from the post.
#link will be true if current_user is admin, it's not correct, #link should be an instance of Link
#link = current_user.links.find_by(id: params[:id]) || current_user.admin?
You should use an authorization gem to check permission such as action_access, cancan, cancancan, pundit,...
problem is the following line
#link = current_user.links.find_by(id: params[:id]) || current_user.admin?
here if the user is admin then the value of #link will be 'true'
so #link.destroy becomes true.destroy which is not a valid function
undefined method `model_name' for TrueClass:Class
This basically means you've populated a variable with true instead of an object. You know this already; it should direct you towards what to look for to identify the problem:
#link = current_user.links.find_by(id: params[:id]) || current_user.admin?
The error here is that you're calling current_user.admin?. Because your initial argument is returning false, Ruby is referring to the last. the admin? method must be returning true.
--
Authorization
The fix for this is something called Authorization.
You'll probably know this already; in the sense of Rails, authorization is not handled very well at all at the moment. The best implementations have been CanCan by Ryan Bates (of Railscasts fame).
In short, what you need is some sort of system which examines who you are, and allows you to perform an action based on your credentials.
If you handled this manually, you'd do this:
#link = current_user.links.find_by(id: params[:id]) || (Link.find(params[:id]) if current_user.admin?) #-> not tested!!!
If you used something something like CanCan (now updated to CanCanCan) you could use:
#app/controllers/your_controller....
def destroy
#link = Link.find params[:id]
#link.destroy if can? :destroy, #link
end
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :manage, :all if user.has_role? :admin
can [:update, :destroy], Link, user_id: user.id
end
end
I am trying to make it so that when a user signs up (i.e: a new user is created) it will redirect them to the tutorial. However, when a user signs up it will give an error message saying that the username and email must be unique (even if they are) and render the 'new' page again.
This works fine if I redirect to #user instead.
This is my controller:
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
login(#user)
format.html { redirect_to "static/tutorial", success: 'Congratulations on starting your journey!' }
format.json { render json: #user, status: :created, location: #user }
else
format.html { render action: "new" }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
And these are the relevant lines in User.rb:
validates_confirmation_of :plain_password
validates_presence_of :name, :username, :email
validates_presence_of :plain_password, :on => :create
validates_uniqueness_of :email, :username
When I see that I am quite scared
validates_presence_of :plain_password, :on => :create
Are you saving unencrypted password in your database ?
Regarding your issue you may want to consider using respond_to / respond_with
class UsersController < ApplicationController
respond_to :html, :json
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
# set sessions
end
respond_with #user, location: 'static/tutorial'
end
end
Read this blog post http://bendyworks.com/geekville/tutorials/2012/6/respond-with-an-explanation
I figured it out - partly.
I needed to add a route to the tutorial in my routes file:
match "tutorial" => "static#tutorial"
And then redirect to that instead of a string:
format.html { redirect_to tutorial_path, success: 'Congratulations on starting your journey!' }
There is probably a full explanation to the theory behind this but I'll leave that for someone else to answer. This is how I solved it.
In my app, im disallowing user registrations and having it being managed by the sole admin user as the application is only intended to be use by a very small team of people.
When I create a user at the moment though. I'm being directed to the user that I created is being automatically signed in and im being redirected to the homepage with a flash message thanking me for signing in.
This isn't right and present's a problem when I create more then one user as I recieve an error telling me that im already signed in.
Here's my user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :username, :password, :password_confirmation, :remember_me
end
My users_controller.rb
class UsersController < ApplicationController
# GET /users
# GET /users.xml
def index
#users = User.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #users }
end
end
# GET /users/1
# GET /users/1.xml
def show
#user = User.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #user }
end
end
# GET /users/new
# GET /users/new.xml
def new
#user = User.new
#current_method = "new"
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #user }
end
end
# GET /users/1/edit
def edit
#user = User.find(params[:id])
end
# POST /users
# POST /users.xml
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to(#user, :notice => 'User was successfully created.') }
format.xml { render :xml => #user, :status => :created, :location => #user }
else
format.html { render :action => "new" }
format.xml { render :xml => #user.errors, :status => :unprocessable_entity }
end
end
end
# PUT /users/1
# PUT /users/1.xml
def update
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
format.html { redirect_to(#user, :notice => 'User was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #user.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /users/1
# DELETE /users/1.xml
def destroy
#user = User.find(params[:id])
#user.destroy
respond_to do |format|
format.html { redirect_to(users_url) }
format.xml { head :ok }
end
end
end
My routes
DocumentManager::Application.routes.draw do
devise_for :users
get "index/index"
devise_for :users, :controllers => {:sessions => 'sessions'}
resources :clients, :jobs, :users
end
Can anyone point me in the right direction or know how to get the functionality that im looking for?
As I said. I need the redirect to go back to the User creation screen, the user not to be signed in automaticly and the flash message to be something differant.
This happens because you're using the Devise registrations controller which logs the user in after signing up. Also, you aren't disabling the registration module, so everyone will be able to create an account accessing /users/sign_up.
In order to do what you want, do this:
Remove the registerable module;
Restrict the access of users controller to only admins through a before_filter;
Remove one of the devise_for on your routes;
Use the users controllers to create your users;
You may also set a random password and ask the user to generate a first time password sending the edit password link after signing in.
I have this method:
def update
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
if params[:mypmnode]
session[:return_to] = projects_pmnode_path(params[:mypmnode])
sign_in(#user)
end
format.html { redirect_to(session[:return_to], :notice => 'User was successfully updated.') }
format.xml { head :ok }
else
#create_company = true if params[:user][:company_id].blank? and params[:user][:company_attributes].length > 0
#create_department = true if params[:user][:department_id].blank? and params[:user][:department_attributes].length > 0
format.html { render :action => "edit" }
format.xml { render :xml => #user.errors, :status => :unprocessable_entity }
end
end
end
The idea is that if the user is updated, He is automatically signed-in and redirected to a page where authentication is required.
In this page, I have: before_filter :authenticate_user!
This doesn't work on redirect.
If I then go to another page making use of this sign_in function, then the user logs-in correctly.
Any idea why redirect doesn't work? Thx!
UPDATE:
to make it clearer, I insert the second page code (controller):
class PmnodesController < Projects::BaseController
before_filter authenticate_user!
def index
#pmnodes = Pmnode.all
respond_to do |format|
format.html
end
end
If the password is updated on #user, devise will invalidate the session. After the update_attributes, you could try calling sign_out first.
sign_out(#user)
sign_in(#user)
Are you sure that your progam goes inside this blog
if params[:mypmnode]
session[:return_to] = projects_pmnode_path(params[:mypmnode])
sign_in(#user)
end
if not this should sign in your use automatically.
def update
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
if params[:mypmnode]
session[:return_to] = projects_pmnode_path(params[:mypmnode])
end
sign_in(#user)
format.html { redirect_to(session[:return_to], :notice => 'User was successfully updated.') }
format.xml { head :ok }
else
#create_company = true if params[:user][:company_id].blank? and params[:user][:company_attributes].length > 0
#create_department = true if params[:user][:department_id].blank? and params[:user][:department_attributes].length > 0
format.html { render :action => "edit" }
format.xml { render :xml => #user.errors, :status => :unprocessable_entity }
end
end
end
I had a similar problem:
I had a controller method that created and signed in a user
def new
#user = User.create!
sign_in #user
redirect_to some_nondefault_path
end
where some_nondefault_path required authentication. The new action did not require authentication. The user was getting created and signed in, but the user session wasn't persisting and the user was getting 401-unauthorized and redirected to the signin page instead of some_nondefault_path.
I ended up solving it by adding
skip_before_filter :verify_authenticity_token, :only => :new
to the first controller. It seemed to be trying to verify the CSRF token before creating the user session, which was failing and blocking the creation of a normal user session (even though it wasn't trying to authenticate_user!).
Hope this helps!