使用Rails 3.2.11和RSpec发布原始JSON数据

为了确保我的应用程序不易受此漏洞攻击 ,我试图在RSpec中创build一个控制器testing来覆盖它。 为了做到这一点,我需要能够发布原始的JSON,但我似乎没有find办法做到这一点。 在做一些研究的时候,我已经确定至less使用过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 

当我查看params散列时,token完全没有设置,它只包含{ "controller" => "user", "action" => "reset_password" } 。 在尝试使用XML时,甚至在试图只使用普通的发布数据时,我都会得到相同的结果,在所有情况下,似乎都没有设置这个时间段。

我知道在最近的Rails漏洞中,散列参数的方式发生了变化,但是仍然有办法通过RSpec发布原始数据吗? 我可以以某种方式直接使用Rack::Test::Methods

据我所知,发送原始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 

这是将原始JSON发送到控制器操作(Rails 3+)的方法:

假设我们有这样的路线:

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

假设你期望身体是一个你读过的json:

JSON.parse(request.body.read)

然后你的testing将如下所示:

 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的源代码,你可以看到它采用了action之后的第一个参数( json_payload),如果它是一个string,它将其设置为原始的后主体,并正常parsing其余的参数。

指出rspec只是一个在Railstesting架构之上的DSL是很重要的。 上面的post方法是ActionController :: TestCase#后,而不是一些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 

我们在控制器testing中所做的是显式设置RAW_POST_DATA:

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

这里是一个控制器testing发送原始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}) 

第一个确保内容types正确地为您的请求设置,这是非常简单的。 第二部分让我头痛了几个小时,最初的方法有点不同,但事实certificate,有一个Rails的bug ,这阻止我们在functiontesting中发送原始的post数据(但允许我们在集成testing),这是一个丑陋的解决方法,但它的工作原理(在rails 4.1.8和rspec-rails 3.0.0)。