2

In the devise_for module we have right now, we have at least two roles: admin and user. My goal is that at any given time, there is one admin, and only admin can create/delete other users. I've followed this post so that I have to login and have permissions to create a new user, since by default the sign_up page needs no permission. However, right now there is no difference between an admin and a user, meaning that both roles can create others, which is not my desired functionality. What should I do so that only an admin can create other users, i.e., when accessing /users/sign_up under the role of a user, it will pop out an error like "not enough permission?"

Let me give you what I have right now:

app/policies/user_policy.rb:

class UserPolicy
  attr_reader :current_user, :model

  def initialize(current_user, model)
    @current_user = current_user
    @user = model
  end

  def index?
    @current_user.admin?
  end

  def new?
    @current_user.admin?
  end

  def show?
    @current_user.admin? or @current_user == @user
  end

  def create?
    @current_user.admin?
  end

  def update?
    @current_user.admin?
  end

  def destroy?
    return false if @current_user == @user
    @current_user.admin?
  end
end

app/controllers/registrations_controller.rb

class RegistrationsController < Devise::RegistrationsController
  prepend_before_action :require_no_authentication, only: [:cancel]
  prepend_before_action :authenticate_scope!, only: [:new, :create, :edit, :update, :destroy]

  def new
    super
  end
end 

config/routes.rb:

...
  devise_for :users, :controllers => {:registrations => "registrations"}
  resources :users
...

P.S. I try to see what I can do for the original code devise/registrations_controller.rb [link], but didn't see anything obviously enough for me to change...

Community
  • 1
  • 1
TimeString
  • 1,778
  • 14
  • 25

1 Answers1

2

Something like this might help:

class RegistrationsController < Devise::RegistrationsController
  prepend_before_action :require_no_authentication, only: [:cancel]
  prepend_before_action :authenticate_scope!, only: [:new, :create, :edit, :update, :destroy]
  before_action :authorize_user!, only: [:new, :create]

  def new
    super
  end

private

  def authorize_user!
    if !current_user || !current_user.admin?
      redirect_to '/', notice: "Your permissions do not allow access to this page"
    end
  end

  # Override from Devise::RegistrationsController to not sign in after sign up
  def sign_up(resource_name, resource)
    # Nothing to do
  end
end

Now that the code is out of the way, it's time for some explanations. The first bit of code adds a before_action handler, which specifies to call the authorize_user! method, but only when the action being executed is either new or create. While the derived controller doesn't define a create method, it's available from the base controller. (Side note: the new method doesn't add anything here, and can be deleted at any time).

The authorize_user! method is declared private, so that it can't be accidentally called from outside the controller - this is specifically to prevent security vulnerabilities.

Within the method, the user is checked to make sure first that someone is logged in, and then that the logged in user has Admin privileges. If the authorization fails, the user will be redirected to the home page (to the '/' route) with a notification message to tell them that they're not worthy.

A sign_up method is called from within the Devise RegistrationsController#create method, and the default implementation is to immediately call sign_in. As it's preferable to keep the admin user logged in, the method is overridden in the application's RegistrationsController to omit the unwanted sign_in step.

In a situation in which the admins create user accounts, it's useful to omit the link to the signup page when the current user is not an admin. Something like this snippet normally works for me:

<% if current_user && current_user.admin? %><%= link_to "Sign Up", devise_signup_link %><% end %>

To use this snippet, you'll need to update the link_to tag to use your application's semantics (link text, route, HTML attributes, etc).

Once you have these items in place, your signup page will be inaccessible to non-admin types, and attempts to access it will result in a suitable error message.

Michael Gaskill
  • 7,913
  • 10
  • 38
  • 43
  • Thanks Michael, it's 60% on the way. Now normal users don't have the privilege to create new users. But when an admin creates a user, it will automatically switch to the new user identity, which is not desired: An admin will always be an admin even after one creates a new user. Is there a way to fix it? – TimeString May 10 '16 at 21:50
  • This link to the SO answer for [Allow a user to add new users in Devise, and remain logged in as themselves](http://stackoverflow.com/a/23175370/6263819) might give you the answer that you need. It might be a routes issue, where the routes are conflicted between `devise_for :users` and `resources :users` in your `routes.rb` file. The suggested fix is to change to use `resources :users_admin` instead. I have not tried this, but you might be able to do it quickly. – Michael Gaskill May 10 '16 at 21:58
  • I carefully checked the SO you pointed to, but I think my question and what this SO asks are orthogonal. In this SO it takes the approach of bypassing the devise module, but I intentionally use the sign-up page in devise since I think it's more secure. That said, the `Devise::RegistrationController` (https://github.com/plataformatec/devise/blob/master/app/controllers/devise/registrations_controller.rb) must do something to change the user, but I cannot find where it does the trick. – TimeString May 10 '16 at 23:10
  • I dug through the Devise code and found a method intended to be overridden [Devise::RegistrationsController](https://github.com/plataformatec/devise/blob/master/app/controllers/devise/registrations_controller.rb) starting at line 103. I've updated the answer to include the overridden method and a brief explanation. See if that helps with the unintended sign in after the admin creates the user. – Michael Gaskill May 11 '16 at 02:01
  • So simple! I was so dumb... The answer is so elegant and serves the purpose! I don't know what words I should put to appreciate you, you're the life saver when I'm about to give up, thanks for your patience! – TimeString May 12 '16 at 06:58