2

So I'm using the Pundit gem to allow/disallow users to access routes, and it's working fine for that. I have a certain requirement where for users, both admin and non-admin can create a user, but only admin can restrict a user(Users are restricted by setting restricted_at to DateTime.now).

So the #create and #update actions are allowed to be accessed by both admin/non-admin, but it's the specific attribute I want to authorize.

My options are:

  1. I could just create the model, but nil the restricted_at(if set), if the user is non-admin

class UserPolicy def create? @record.assign_attributes({restricted_at: nil}) unless @user.admin? return true end end

But I don't think this is the best way to do it. I'd much rather return an error here. However if I try to add an error like @record.errors.add(:restricted_at, "can only be set by admin"), then later in the controller when save is called, it'll revalidate, and since the models validations don't validate who is changing something.

So is there way to do this with Pundit? What makes the most sense from a user/UX perspective? - Return a 401 Unauthorized if a non-admin tries to create a user with restricted_at set, or update the attribute on an existing user. I personally think this is bad because the users isn't unauthorized for the route, just for the model. And a blanket 401 doesn't explain WHY. - Create the model, but just nix anything a non-admin does to that attribute, like I do above - Some alternate where I either add a model error with Pundit(if possible) or in the controller(less ideal, i'd rather keep all the validation/permission stuff in the model or pundit. If I put logic in the controller, then I'll have validations in three places, the model, the controller, and pundit, which seems smelly to me)

Just to make it clear, those are the possibilites, but what I want, and what I think is best, is to NOT create the model (if a non admin sets the retricted_at on create), and return an error message.

How can I sort this out? Thanks.

Peter R
  • 3,185
  • 23
  • 43

1 Answers1

0

One way to achieve that is to use different controllers and policy methods for admins and users. For instance:

class UsersController < ApplicationController

  def create
    @user = User.create(user_params)
    # ...
  end

  def update
    @user.update_attributes(user_params)
    # ...
  end

  private

  def user_params
    params.require(:user).permit(
      # permit only attributes that are accessible by non-admins
    )
  end
end

and there would be Admins::UsersController with actions that accept all available parameters:

class Admins::UsersController < ApplicationController
  before_action :authorize_user!

  # ...

  private

  def authorize_user!
    authorize @user, :admin_manage?
  end

  def user_params
    params.require(:user).permit(
      # permit all attributes that are accessible by admins
    )
  end
end

Then in the UserPolicy:

class UserPolicy
  def admin_manage?
    @user.admin?
  end
end
Slava.K
  • 3,073
  • 3
  • 17
  • 28
  • Thank you for sharing. How are you going to manage which controller to hit depending on the User's role? By adding some `if/else` conditions to the views/templates, etc. I have a request to enable/disable certain fields in the views depending on a User's role. Thank you. – belgoros Oct 25 '19 at 07:34
  • May be using [policy namespacing](https://github.com/varvet/pundit#policy-namespacing) or manage it via [strong parameters](https://github.com/varvet/pundit#strong-parameters) would be a better approach. – belgoros Oct 25 '19 at 08:17