1

I'm using devise and have let admins manage users with a Manage::UsersController.

This is locked down using cancan:

# models/ability.rb    
def initialize(user)
  if user admin? 
   can :manage, User
  end
end

Normal users can have nothing to do with User other than through devise, so everything looks secure.

Now I want to give users a 'show' page for their own account information (rather than customising the devise 'edit' page). The advice (1,2,3) seems to be to add a users_controller with a show method.

I tried to give non-admins the ability to read only their own information with:

if user admin? 
  can :manage, User
else
  can :read, User, :id => user.id    # edited based on @Edwards's answer
end

However this doesn't seem to restrict access to Manage::UsersController#index, so it means that everybody can see a list of all users.

What's the simplest way to achieve this? I can see two options, (but I'm not sure either is right):

1) Prevent user access to Manage::UsersController#index

def index
  @users = User.all
  authorize! :manage, User   # feels hackish because this is 'read' action
end

2) Over-write devise controller

Per this answer over-writing a devise controller could be a good approach, but the question is which controller (the registrations controller?) and how. One of my concerns with going this route is that the information I want to display relates to the User object but not devise specifically (i.e. user plan information etc.). I'm also worried about getting bogged down when trying to test this.

What do you recommend?

Community
  • 1
  • 1
Derek Hill
  • 5,965
  • 5
  • 55
  • 74

2 Answers2

1

I would keep your registration and authentication as Devise controllers; then, create your own User controller that is not a devise controller.

In your own controller, let's call it a ProfilesController, you could only show the specific actions for the one profile (the current_user)

routes

resource :profile

profiles controller

class ProfilesController
  respond_to :html
  def show
    @user = current_user
  end

  def edit
    @user = current_user
  end

  def update
    @user = current_user
    @user.update_attributes(params[:user])
    respond_with @user
  end  
end

Since it's always only editing YOU, it restricts the ability to edit or see others.

Jesse Wolgamott
  • 40,197
  • 4
  • 83
  • 109
  • Thanks Jesse. The bit I don't get is that I would have to grant some kind of user permission in cancan so people could see their own profile, but how would I do this in a way that didn't also give them access to Manage::UsersController#index? – Derek Hill Apr 19 '13 at 18:36
  • @DerekHill in my example there is no index for ProfilesController – Jesse Wolgamott Apr 19 '13 at 18:40
  • No, but there is on the Manage::UsersController which allows an admin to see a list of all users. – Derek Hill Apr 19 '13 at 18:43
  • There's nothing wrong with `authorize! :manage, User` if you want to stick to cancan's authorization model. – Jesse Wolgamott Apr 19 '13 at 18:51
  • Thanks. To be honest I'm not sure I do want to stick with cancan for keeping users out of the admin area. It feels vulnerable for this to be protected as a single method, when really they should be kept out of the whole thing. – Derek Hill Apr 19 '13 at 19:01
  • Derek -- is there anything else you want here? I've given you an answer and code that'll work -- what else is needed? – Jesse Wolgamott Apr 20 '13 at 18:07
  • Appreciate your help Jesse. It's true that adding a `ProfilesController` like you suggest and adapting `Ability` as Edward suggests works. I was just going to leave it open a little bit to see if any other suggestions come in. It seems to me a convention to have this manage area for admin functions (controllers/manage & views/manage), and I feel like there must be a way to keep non-admins out of it completely. – Derek Hill Apr 21 '13 at 06:44
  • 1
    Derek -- convention is to have a User and an Admin (each different devise models), and authenticate_admin! for areas you only want Admins to be able to access. Then, users can manage only themselves. – Jesse Wolgamott Apr 21 '13 at 18:05
1

In your ability.rb you have

  can :read, User, :user_id => user.id

The User model won't have a user_id - you want the logged in user to be able to see their own account - that is it has the same id as the current_user. Also, :read is an alias for [:index, :show], you only want :show. So,

  can :show, User, :id => user.id

should do the job.

Edward
  • 3,429
  • 2
  • 27
  • 43
  • Sorry. That was a mistake when I was cleaning things up. Changing it to can :read, User, :id => user.id does not seem to prohibit access to an index action. – Derek Hill Apr 19 '13 at 18:59
  • 1
    Ah. If I remember correctly `:read` is an alias for `[:index, :show]`. So try with just `:show`. – Edward Apr 19 '13 at 19:17
  • You are right! That does work. I'm just going to leave this answer open for a bit to see if anybody comes up with a way of keeping users out of the admin area completely (`Manage::UsersController`), otherwise I will go ahead and accept your answer. – Derek Hill Apr 19 '13 at 19:23