0

This section of Pundit section says that we could control which attributes are authorized to be updated. But it fails in case of the use of active_model_seriallizers gem:

def post_params
  # originally geneated by scaffold
  #params.require(:post).permit(:title, :body, :user_id)

  #To deserialize with active_model_serializers
  ActiveModelSerializers::Deserialization.jsonapi_parse!(
        params,
        only: [:title, :body, :user]
      )
end

If I modify the PostsController update action as Pundit suggested:

def update
  if @post.update(permitted_attributes(@post))
    render jsonapi: @post
  else
    render jsonapi: @post.errors, status: :unprocessable_entity
  end
end

it fails with error:

ActionController::ParameterMissing (param is missing or the value is empty: post):
app/controllers/posts_controller.rb:29:in `update'

I also create the PostPolicy as follows:

class PostPolicy < ApplicationPolicy
  def permitted_attributes
    if user.admin? || user.national?
      [:title, :body]
    else
      [:body]
    end
  end
end

but it has no impact on the above error.

Any idea on how can we do that?

belgoros
  • 3,590
  • 7
  • 38
  • 76
  • `ActionController::ParameterMissing` is raised by [`ActionController::Parameters.html#require`](https://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-require) so you may be looking at the wrong culprit. – max Oct 26 '19 at 22:21
  • 1
    I'm getting closer to the solution. I added `pundit_params_for` to the `PostsController` as follows: ```def pundit_params_for(_record) params.fetch(:data, {}).fetch(:attributes, {}) end```, and modified the `update` action as follows: ```def update if @post.update(permitted_attributes(@post)) render jsonapi: @post else render jsonapi: @post.errors, status: :unprocessable_entity end end ```. Now if a User is not authorized to update `title`, I see in the console: `Unpermitted parameter: :title`. – belgoros Oct 27 '19 at 11:19
  • Nice, but I would consider using `require(:data).require(:attributes)` instead of fetch. You want to bail early here as there is no point in continuing if the input does not match the spec. – max Oct 27 '19 at 11:24
  • did you mean `fail early` instead of `bail early` ? – belgoros Oct 27 '19 at 11:26
  • Yeah, you say tomato... – max Oct 27 '19 at 11:35
  • I think, to be able to raise and catch the exception in case of unpermitted parameters, I'll have to add `ActionController::Parameters.action_on_unpermitted_parameters = :raise` somewhere in `application.rb`. Then rescue it in the `application_controller.rb` in a custom way to send back with the response JSON. – belgoros Oct 27 '19 at 14:55
  • You can just use `rescue_from`. – max Oct 27 '19 at 15:23

1 Answers1

0

The solution I came to (thanks to @max for some tips and tricks) is as follows:

  1. Add the following line to config/application.rb:
config.action_controller.action_on_unpermitted_parameters = :raise
  1. Add the rescue_from either to the AplicationController or the one you are precisely interested:
class ApplicationController < ActionController::API
  include ActionController::MimeResponds
  include Pundit

  rescue_from Pundit::NotAuthorizedError, ActionController::UnpermittedParameters, with: :user_not_authorized

...
private

def user_not_authorized
    render jsonapi: errors_response, status: :unathorized
  end

  def errors_response
    {
      errors:
      [
        { message: 'You are not authorized to perform this action.' }
      ]
    }
  end
end

Then add pundit_params_for method to the PostsController and change the update action (in my case I'd like to restrict some attributes in update action only:)

class PostsController < ApplicationController
...

def update
  if @post.update(permitted_attributes(@post))
    render jsonapi: @post
  else
    render jsonapi: @post.errors, status: :unprocessable_entity
  end
end

private
  def post_params
     ActiveModelSerializers::Deserialization.jsonapi_parse!(
       params,
       only: [:title, :body, :user]
     )
    end

  def pundit_params_for(_record)
    params.fetch(:data, {}).fetch(:attributes, {})
  end
end

Voilà. Now if an unpermitted attribute will be submitted for the update action, the response will have 500 status and contain the error as specified in ApplicationController#errors_response method.

ATTENTION: It still fails if you have some relations posted with the request (for example, you can have an Author as belongs_to relation with Post). Using pundit_params_for as before will fail to extract the corresponding author_id value. To see the way, here my another post where I explained how to use it.

Hope this helps.

belgoros
  • 3,590
  • 7
  • 38
  • 76