为了确保我的应用程序不受此漏洞的攻击,我试图在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
当我查看params散列时,令牌根本没有设置,它只包含{ "controller" => "user", "action" => "reset_password" }
。当我尝试使用XML时,甚至当我尝试只使用常规的post数据时,我都会得到相同的结果,在所有情况下,它似乎都没有设置它的周期。
我知道最近的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发送到控制器操作(Rails3+)的方法:
假设我们有一条这样的路线:
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中,您可以看到它接受操作后的第一个参数(jsonpayload),如果它是一个字符串,它会将其设置为原始post主体,并正常解析其余的参数。
同样重要的是要指出,rspec只是Rails测试体系结构之上的DSL。上面的post
方法是ActionController::TestCase#post,而不是一些rspec发明。
我们在控制器测试中所做的是显式设置RAW_POST_DATA:
before do
@request.env['RAW_POST_DATA'] = payload.to_json
post :my_action
end
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和rsspec Rails 3.0.0上)。
On 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' }
对于@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
报头没有太大区别(它可以是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
作为标头值。