Размещение исходных данных JSON с помощью Rails 3.2.11 и RSpec

Чтобы убедиться, что мое приложение не уязвимо для этого эксплойта, я пытаюсь создать тест контроллера в RSpec, чтобы покрыть его. Для этого мне нужно иметь возможность публиковать сырой JSON, но я, похоже, не нашел способа сделать это. Проводя некоторые исследования, я определил, что, по крайней мере, раньше был способ сделать это, используя RAW_POST_DATA заголовок, но это больше не работает:

it "should not be exploitable by using an integer token value" do
  request.env["CONTENT_TYPE"] = "application/json"
  request.env["RAW_POST_DATA"]  = { token: 0 }.to_json
  post :reset_password
end

Когда я смотрю на хэш параметров, токен вообще не устанавливается, а просто содержит { "controller" => "user", "action" => "reset_password" }, Я получаю те же результаты, когда пытаюсь использовать XML или даже просто пытаюсь использовать обычные почтовые данные, во всех случаях кажется, что он не устанавливает период.

Я знаю, что в связи с недавними уязвимостями в Rails способ хэширования параметров был изменен, но есть ли еще способ размещения необработанных данных через RSpec? Можно ли как-то напрямую использовать Rack::Test::Methods?

7 ответов

Решение

Насколько я был в состоянии сказать, отправка необработанных данных POST больше не возможна в пределах спецификации контроллера. Тем не менее, это может быть сделано довольно легко в спецификации запроса:

describe "Example", :type => :request do
  params = { token: 0 }
  post "/user/reset_password", params.to_json, { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
  #=> params contains { "controller" => "user", "action" => "reset_password", "token" => 0 }
end

То, что мы сделали в наших тестах контроллера, явно установлено RAW_POST_DATA:

before do
  @request.env['RAW_POST_DATA'] = payload.to_json
  post :my_action
end

Это способ отправки необработанного JSON в действие контроллера (Rails 3+):

Допустим, у нас есть такой маршрут:

post "/users/:username/posts" => "posts#create"

Допустим, вы ожидаете, что тело будет json, которое вы прочитали, выполнив:

JSON.parse(request.body.read)

Тогда ваш тест будет выглядеть так:

it "should create a post from a json body" do
  json_payload = '{"message": "My opinion is very important"}'
  post :create, json_payload, {format: 'json', username: "larry" }
end

{format: 'json'} это магия, которая делает это возможным Кроме того, если мы посмотрим на источник для TestCase # post http://api.rubyonrails.org/classes/ActionController/TestCase/Behavior.html, вы увидите, что он принимает первый аргумент после действия (json_payload) и если это строка, она устанавливает это как необработанное тело сообщения и анализирует остальные аргументы как обычно.

Также важно отметить, что rspec - это просто DSL поверх архитектуры тестирования Rails. post Метод выше - ActionController::TestCase#post, а не какое-то изобретение rspec.

Пример Rails 5:

RSpec.describe "Sessions responds to JSON", :type => :request do

  scenario 'with correct authentication' do
    params = {id: 1, format: :json}
    post "/users/sign_in", params: params.to_json, headers: { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
    expect(response.header['Content-Type']).to include 'application/json'
  end
end

Вот полный рабочий пример теста контроллера, отправляющего необработанные данные json:

describe UsersController, :type => :controller do

  describe "#update" do
    context 'when resource is found' do
      before(:each) do
        @user = FactoryGirl.create(:user)
      end

      it 'updates the resource with valid data' do
        @request.headers['Content-Type'] = 'application/vnd.api+json'
        old_email = @user.email
        new_email = Faker::Internet.email
        jsondata = 
        {
          "data" => {
            "type" => "users",
            "id" => @user.id,
            "attributes" => {
              "email" => new_email
            }
          }
        }

        patch :update, jsondata.to_json, jsondata.merge({:id => old_id})

        expect(response.status).to eq(200)
        json_response = JSON.parse(response.body)
        expect(json_response['data']['id']).to eq(@user.id)
        expect(json_response['data']['attributes']['email']).to eq(new_email)
      end
    end
  end
end

Важными частями являются:

@request.headers['Content-Type'] = 'application/vnd.api+json'

а также

patch :update, jsondata.to_json, jsondata.merge({:id => old_id})

Первый гарантирует, что тип контента правильно установлен для вашего запроса, это довольно просто. Вторая часть давала мне головную боль в течение нескольких часов, мой первоначальный подход был несколько иным, но оказалось, что есть ошибка Rails, которая не позволяет нам отправлять необработанные данные в функциональных тестах (но позволяет нам в интеграционных тестах).), и это уродливый обходной путь, но он работает (на rails 4.1.8 и rspec-rails 3.0.0).

На рельсах 4:

      params = { shop: { shop_id: new_subscrip.shop.id } }
post api_v1_shop_stats_path, params.to_json, { 'CONTENT_TYPE' => 'application/json',
                                                     'ACCEPT' => 'application/json' }

Небольшая альтернатива ответу @daniel-vandersluis на rails 3.0.6, с rspec 2.99а также rspec-rails 2.99:

      describe "Example", :type => :request do
  params = { token: 0 }
  post "/user/reset_password", params.merge({format: 'json'}).to_json, { 'CONTENT_TYPE' => 'application/json', 'HTTP_ACCEPT' => 'application/json' }
end

Заголовок не имел большого значения (он может быть либо HTTP_ACCEPTили просто ACCEPT). Но в моем случае, чтобы это сработало, параметры должны были: иметь .merge({format: 'json'})а также .to_json

Другой вариант:

      describe "Example", :type => :request do
  params = { token: 0 }
  post "/user/reset_password", params.merge({format: 'json'}).to_json, { 'CONTENT_TYPE' => Mime::JSON.to_s, 'HTTP_ACCEPT' => Mime::JSON }
end

Оно использует Mime::JSONа также Mime::JSON.to_sвместо application/jsonдля значений заголовка.

Другие вопросы по тегам