Возврат 403, когда 404 происходит в Rails API - PullRequest
1 голос
/ 02 мая 2020

У меня есть Rails API с контроллером, который имеет before_action в функции создания, которая проверяет, является ли current_user, создающий «вставку», также владельцем указанного room_id в paste_params.

Если current_user.rooms.find(params[:paste][:room_id]).user_id не найден или если он найден, но не равен current_user.id, сервер всегда возвращает 404.

Как я могу go сделать это вернуть 403 вместо 404? Поскольку эта проверка предназначена для определения того, является ли пользователь, создающий вставку, владельцем комнаты, к которой будет привязана вставка, если пользователь не является владельцем этой комнаты, это означает, что он не авторизован для создания вставки в эта комната.

Вот соответствующие части контроллера:

class Api::V1::PastesController < ApplicationController
  before_action :check_room_owner, only: %i[create update destroy]

  def create
    paste = current_user.pastes.build(paste_params)
    if paste.save
      render json: PasteSerializer.new(paste).serializable_hash, status: :created
    else
      render json: { errors: paste.errors }, status: :unprocessable_entity
    end
  end

  private

  # Convert to 403 forbidden if not found
  def check_room_owner
    head :forbidden unless current_user.rooms.find(params[:paste][:room_id]).user_id === current_user.id
  end

end

Ура!

Ответы [ 3 ]

2 голосов
/ 02 мая 2020

Это называется авторизацией, и если вы действительно собираетесь изобретать велосипед, по крайней мере, сделайте это правильно:

# app/errors/authentication_error.rb
class AuthenticationError < StandardError
end
class ApplicationController
  rescue_from 'AuthenticationError', with: :deny_access

  def deny_access
    head :forbidden
  end
end
# Do not use :: when declaring classes!
module API
  module V1
    class PastesController < ApplicationController
      before_action :find_and_authenticate_room!

      def create
        paste = current_user.pastes.build(paste_params)
        if paste.save
          render json: PasteSerializer.new(paste).serializable_hash, status: :created
        else
          render json: { errors: paste.errors }, status: :unprocessable_entity
        end
      end

      private
      def find_and_authenticate_room!
        # This smells really bad - use a nested route instead!
        @room = Room.find(params[:paste][:room_id])
        raise AuthenticationError unless @room.user == current_user
      end
    end
  end
end

Это отделяет логику c ответа от определения того, что разрешено и использует наследование для DRY всего процесса. Еще лучше было бы не изобретать велосипед и использовать Pundit или CanCanCan, который отделяет правила авторизации от вашего контроллера, что делает его стройным.

0 голосов
/ 02 мая 2020

Прекрасный ответ Марка, но я также нашел другое решение, которое работает достаточно хорошо.

  def check_room_owner
    selected_room = current_user.rooms.find(params[:paste][:room_id]).user_id
    rescue ActiveRecord::RecordNotFound
     head :forbidden
  end

Если selected_room не найден, рельсы вызовут исключение ActiveRecord::RecordNotFound вместо того, чтобы оставить переменную неопределенной или ноль Таким образом, мы можем использовать rescue для обработки исключения и вернуть 403 вместо того, чтобы позволить rails по умолчанию возвращать 404.

0 голосов
/ 02 мая 2020

Я думаю, вы ищете что-то вроде этого:

class Api::V1::PastesController < ApplicationController

  def create
    paste = current_user.pastes.build(paste_params)
    return head :forbidden unless paste.room.user_id === current_user.id
    if paste.save
      render json: PasteSerializer.new(paste).serializable_hash, status: :created
    else
      render json: { errors: paste.errors }, status: :unprocessable_entity
    end
  end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...