In order to ensure that my application is not vulnerable to this exploit, I am trying to create a controller test in RSpec to cover it. In order to do so, I need to be able to post raw JSON, but I haven't seemed to find a way to do that. In doing some research, I've determined that there at least used to be a way to do so using the RAW_POST_DATA
header, but this doesn't seem to work anymore:
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
When I look at the params hash, token is not set at all, and it just contains { "controller" => "user", "action" => "reset_password" }
. I get the same results when trying to use XML, or even when trying to just use regular post data, in all cases, it seems to not set it period.
I know that with the recent Rails vulnerabilities, the way parameters are hashed was changed, but is there still a way to post raw data through RSpec? Can I somehow directly use Rack::Test::Methods
?
As far as I have been able to tell, sending raw POST data is no longer possible within a controller spec. However, it can be done pretty easily in a request spec:
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
This is the way to send raw JSON to a controller action (Rails 3+):
Let's say we have a route like this:
post "/users/:username/posts" => "posts#create"
And let's say you expect the body to be a json that you read by doing:
JSON.parse(request.body.read)
Then your test will look like this:
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'}
is the magic that makes it happen. Additionally, if we look at the source for TestCase#post http://api.rubyonrails.org/classes/ActionController/TestCase/Behavior.html#method-i-process you can see that it takes the first argument after the action (json_payload) and if it is a string it sets that as raw post body, and parses the rest of the args as normal.
It's also important to point out that rspec is simply a DSL on top of the Rails testing architecture. The post
method above is the ActionController::TestCase#post and not some rspec invention.
What we've done in our controller tests is explicitly set the RAW_POST_DATA:
before do
@request.env['RAW_POST_DATA'] = payload.to_json
post :my_action
end
Rails 5 example:
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
Here is a full working example of a controller test sending raw json data:
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
The important parts are:
@request.headers['Content-Type'] = 'application/vnd.api+json'
and
patch :update, jsondata.to_json, jsondata.merge({:id => old_id})
The first makes sure that the content type is correctly set for your request, this is pretty straightforward. The second part was giving me headaches for a few hours, my initial approach was quite a bit different, but it turned out that there is a Rails bug, which prevents us from sending raw post data in functional tests (but allows us in integration tests), and this is an ugly workaround, but it works (on rails 4.1.8 and rspec-rails 3.0.0).
来源:https://stackoverflow.com/questions/14775998/posting-raw-json-data-with-rails-3-2-11-and-rspec