Rails API/Pundit: сильные параметры с ActiveModelSerializers

В этом разделе раздела Pundit говорится, что мы можем контролировать, какие атрибуты разрешено обновлять. Но это не работает в случае использования 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

Если я изменю действие PostsController update, как предложил Pundit:

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

не получается с ошибкой:

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

Я также создаю PostPolicy следующим образом:

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

но это не влияет на вышеуказанную ошибку.

Любая идея о том, как мы можем это сделать?


person belgoros    schedule 25.10.2019    source источник
comment
ActionController::ParameterMissing вызывается ActionController::Parameters.html#require, поэтому вы можете искать у неправильного преступника.   -  person max    schedule 27.10.2019
comment
Я приближаюсь к решению. Я добавил pundit_params_for к PostsController следующим образом: def pundit_params_for(_record) params.fetch(:data, {}).fetch(:attributes, {}) end и изменил действие update следующим образом: def update if @post.update(permitted_attributes(@post)) render jsonapi: @post else render jsonapi: @post.errors, status: :unprocessable_entity end end . Теперь, если пользователь не авторизован для обновления title, я вижу в консоли: Unpermitted parameter: :title.   -  person belgoros    schedule 27.10.2019
comment
Хорошо, но я бы подумал об использовании require(:data).require(:attributes) вместо выборки. Вы хотите уйти пораньше здесь, так как нет смысла продолжать, если входные данные не соответствуют спецификации.   -  person max    schedule 27.10.2019
comment
вы имели в виду fail early вместо bail early ?   -  person belgoros    schedule 27.10.2019
comment
Ага, ты говоришь помидор...   -  person max    schedule 27.10.2019
comment
Я думаю, чтобы иметь возможность генерировать и ловить исключение в случае недопустимых параметров, мне придется добавить ActionController::Parameters.action_on_unpermitted_parameters = :raise где-то в application.rb. Затем спасите его в application_controller.rb нестандартным способом, чтобы отправить обратно с ответом JSON.   -  person belgoros    schedule 27.10.2019
comment
Вы можете просто использовать rescue_from.   -  person max    schedule 27.10.2019


Ответы (1)


Решение, к которому я пришел (спасибо @max за некоторые советы и рекомендации), выглядит следующим образом:

  1. Добавьте следующую строку в config/application.rb:
config.action_controller.action_on_unpermitted_parameters = :raise
  1. Добавьте rescue_from либо к AplicationController, либо к тому, который вас интересует:
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

Затем добавьте метод pundit_params_for к PostsController и измените действие update (в моем случае я хотел бы ограничить некоторые атрибуты только в действии update :)

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

Вуаля. Теперь, если для действия update будет отправлен неразрешенный атрибут, ответ будет иметь статус 500 и содержать ошибку, указанную в ApplicationController#errors_response method.

ВНИМАНИЕ: все еще не удается, если у вас есть какие-либо отношения, опубликованные с запросом (например, у вас может быть отношение Author в качестве принадлежности_к с Post). Использование pundit_params_for, как и раньше, не позволит извлечь соответствующее значение author_id. Чтобы увидеть, как это сделать, вот мой другой пост, где я объяснил как это использовать.

Надеюсь это поможет.

person belgoros    schedule 28.10.2019