Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

session nil after updating own password #536

Closed
KevinInTx opened this Issue · 7 comments

2 participants

@KevinInTx

I'm using CanCan along with Devise and for some reason the session is lost after updating their password.
An admin user can update other user's passwords just fine but if they update their own the session is lost.

Ruby 1.9.2
Rails 3.1.0
CanCan 1.6.7
Devise (1.4.8,1.5.0,1.5.1,1.5.2)
Devise Security Extension 0.4.2

@KevinInTx

ability model

class Ability
   include CanCan::Ability
    def initialize(user)
      user ||= User.new 
      Rails.logger.debug "!!!!!!! Checking Devise for: \n#{user.inspect}"
      Rails.logger.debug "********************* Current User ------ #{@current_user.inspect}"
      Rails.logger.debug "=============== Current Ability ======= #{@current_ability.inspect}"
      unless user.id.nil?
         can :read, Bank
         can :read, Task
         can :read, Template
         can :read, User, :id => user.id
         can :update, User, :id => user.id
      end

      if user.role? :administrator
        can :manage, User
      end 

      if user.role? :operator
        can :manage, Bank
      end 

      if user.role? :developer
        can :manage, Bank
        can :manage, Task
        can :manage, Template
      end
    end  
  end
@KevinInTx

My update action in my User controller

def update
  @user = User.find(params[:id])
  user = current_user #get current user making change before save/update
  respond_to do |format|
    if @user.update_attributes(params[:user])
      sign_in(user, :bypass=>true)
      format.html { redirect_to @user, 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
@KevinInTx

Info from server log starting when I click on save after changing my password.
If you notice the log info that I had added, the session is fine until after the info is updated and it tries to hit the show in my controller. Then when it checks the ability it starts a redirect back and just loops continuously until the browser errors out.

CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1
(0.8ms) BEGIN
(1.4ms) UPDATE "users" SET "encrypted_password" = '$2a$10$/5MxE4yWrwdyRZTEohogdehXA57J6B6AgDKrduYDv8j0498xe5siW', "password_changed_at" = '2011-12-28 18:39:39.377699', "updated_at" = '2011-12-28 18:39:39.386309' WHERE "users"."id" = 2
SQL (0.9ms) INSERT INTO "versions" ("created_at", "event", "item_id", "item_type", "object", "password_change", "whodunnit") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id" ["created_at", Wed, 28 Dec 2011 18:39:39 UTC +00:00], ["event", "update"], ["item_id", 2], ["item_type", "User"], ["object", "---\nusername: kevin\ninitials: kp\nemail: kevin.parrish@ebanksystems.com\nsign_in_count: 55\ncurrent_sign_in_at: 2011-12-28 18:39:30.421425000Z\nlast_sign_in_at: 2011-12-28 18:34:14.229308000Z\ncurrent_sign_in_ip: 127.0.0.1\nlast_sign_in_ip: 127.0.0.1\npassword_changed_at: 2011-12-28 18:34:52.923814000Z\nfailed_attempts: 0\nlocked_at: !!null \nreset_password_token: !!null \nreset_password_sent_at: !!null \ncreated_at: 2011-12-22 16:26:18.717736000Z\nupdated_at: 2011-12-28 18:39:30.430774000Z\nroles_mask: 5\nis_locked: false\nid: 2\n"], ["password_change", "Password Change"], ["whodunnit", #] COMMIT
~~~~~~~~~~~~ After Update - Signing In
~~~~~~~~~~~~ @user.id => 2
~~~~~~~~~~~~ current_user => #
-------------- session => {"session_id"=>"796198de5e4a777f33ab5d764564d8bf", "_csrf_token"=>"kwiEqwrD4nO5WvJhatlHwURKjNg9Ldg/Cx5qrMsEiDw=", "warden.user.user.session"=>{"last_request_at"=>2011-12-28 18:39:39 UTC, :password_expired=>false}, "warden.user.user.key"=>["User", [2], "$2a$10$b6k3qyTqfkq0VeUPBbK3uO"]}
Redirected to http://localhost:3000/users/2
Completed 302 Found in 345ms

Started GET "/users/2" for 127.0.0.1 at 2011-12-28 12:39:39 -0600
Processing by UsersController#show as HTML
Parameters: {"id"=>"2"}
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
User Load (0.9ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", "2"]]
!!!!!!! Checking Devise for:
#
********************* Current User ------ nil
=============== Current Ability ======= nil
-------------- session => nil
You are not authorized to access this page.
Redirected to http://localhost:3000/users/2/edit
Completed 302 Found in 53ms

Started GET "/users/2/edit" for 127.0.0.1 at 2011-12-28 12:39:39 -0600
Processing by UsersController#edit as HTML
Parameters: {"id"=>"2"}
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", "2"]]
!!!!!!! Checking Devise for:
#
********************* Current User ------ nil
=============== Current Ability ======= nil
-------------- session => nil
You are not authorized to access this page.
Redirected to http://localhost:3000/users/2/edit
Completed 302 Found in 98ms

@KevinInTx

My rescue code in my application controller which gives the "You are not authorized to access this page."

rescue_from CanCan::AccessDenied do |exception|
  Rails.logger.error(exception, exception.backtrace)
  redirect_to :back, :alert => exception.message
end
@sethvargo

This isn't a CanCan error, it's a logic error. This is because current_user and @user are not the same thing. Assume a user only has 2 fields: username and password. In your method, you are setting user = current_user and @user = User.find(params[:id]).

Now, assume we are updating the current user, in that case, user == @user. Adding print statements as follows, you can see this is true:

def update
  @user = User.find(params[:id])
  user = current_user
  p @user == user
  ....
end

However, after your call to @user.update_attributes(params[:user]), they are no longer the same. More specifically, the passwords differ:

def update
  @user = User.find(params[:id])
  user = current_user

  respond_to do |format|
    if @user.update_attributes(params[:user])
      puts @user == user
      puts @user.password
      puts user.password
      ...
    end
  end     
end

You've updated the record in the database, but your current_user still holds a "cached" copy of the old record with the old password. Now, when you call sign_in with :bypass => true, you are telling devise to sign in a user with an invalid password. As expected, this fails.

You need to reload current_user before calling signin:

def update
  @user = User.find(params[:id])

  respond_to do |format|
    if @user.update_attributes(params[:user])
      current_user = @user if @user.id == current_user.id
      sign_in(current_user, :bypass=>true)

      format.html { redirect_to @user, 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

On a side note: do you really want to be signing in the user every single time they make a user update? That seems excessive...

@sethvargo

Also, in your ability.rb, you should be checking if user.nil?, not user.id.nil?. If user is ever nil, an exception will be thrown in the Ability model.

@sethvargo

Lastly, your failsafe for CanCan::AccessDenied will break when the user navigates to your page when the history is empty. (If they close their browser, then visit a restricted page directly, for example).

rescue_from CanCan::AccessDenied do |exception|
  Rails.logger.error(exception, exception.backtrace)
  # :back isn't defined all the time...
  redirect_to :back, :alert => exception.message
end
@KevinInTx KevinInTx closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.