45

为了确保我的应用程序不受此漏洞利用的影响,我试图在 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吗?

4

7 回答 7

81

据我所知,在控制器规范中不再可能发送原始 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
于 2013-02-08T22:46:12.127 回答
11

这是将原始 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#method-i-process的源代码,您可以看到它在操作之后采用第一个参数( json_payload),如果它是一个字符串,它会将其设置为原始帖子正文,并正常解析其余的 args。

同样重要的是要指出 rspec 只是 Rails 测试架构之上的 DSL。上面的post方法是 ActionController::TestCase#post 而不是一些 rspec 发明。

于 2015-02-20T15:01:56.033 回答
10

我们在控制器测试中所做的是明确设置 RAW_POST_DATA:

before do
  @request.env['RAW_POST_DATA'] = payload.to_json
  post :my_action
end
于 2015-03-18T20:46:02.873 回答
8

导轨 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
于 2017-02-19T01:18:18.903 回答
6

这是发送原始 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 上)。

于 2015-07-17T10:58:06.133 回答
0

在 Rails 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' }
于 2021-06-16T17:41:18.280 回答
0

@daniel-vandersluis 回答的一个小替代品, on rails 3.0.6, with rspec 2.99and 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没有太大区别,(它可以是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::JSONandMime::JSON.to_s而不是application/json标题值。

于 2021-12-19T19:22:23.460 回答