Skip to content

Commit

Permalink
Validate response from the exception handler. Closes #1757.
Browse files Browse the repository at this point in the history
  • Loading branch information
darren987469 authored and dblock committed Aug 30, 2018
1 parent 292976d commit c117bff
Show file tree
Hide file tree
Showing 10 changed files with 64 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#### Fixes

* Your contribution here.
* [#1776](https://github.com/ruby-grape/grape/pull/1776): Validate response returned by the exception handler - [@darren987469](https://github.com/darren987469).

### 1.1.0 (8/4/2018)

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2183,7 +2183,7 @@ You can also rescue all exceptions with a code block and handle the Rack respons
```ruby
class Twitter::API < Grape::API
rescue_from :all do |e|
Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' }).finish
Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' })

This comment has been minimized.

Copy link
@ghiculescu

ghiculescu May 8, 2019

Contributor

i feel like this change should be documented better. the fact you need to remove .finish from your response is not very clear in the PR body or changelog, so it resulted in quite a confusing debugging session when upgrading to this version of grape.

This comment has been minimized.

Copy link
@ghiculescu

ghiculescu May 8, 2019

Contributor

omg ignore me. it's in UPGRADING.md which i managed to not read. sorry!

This comment has been minimized.

Copy link
@dblock

dblock May 8, 2019

Member

No worries @ghiculescu ;)

end
end
```
Expand Down Expand Up @@ -2254,9 +2254,9 @@ class Twitter::API < Grape::API
end
```

The `rescue_from` block must return a `Rack::Response` object, call `error!` or re-raise an exception.
The `rescue_from` handler must return a `Rack::Response` object, call `error!`, or raise an exception (either the original exception or another custom one). The exception raised in `rescue_from` will be handled outside Grape. For example, if you mount Grape in Rails, the exception will be handle by [Rails Action Controller](https://guides.rubyonrails.org/action_controller_overview.html#rescue).

The `with` keyword is available as `rescue_from` options, it can be passed method name or Proc object.
Alternately, use the `with` option in `rescue_from` to specify a method or a `proc`.

```ruby
class Twitter::API < Grape::API
Expand Down
19 changes: 19 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
Upgrading Grape
===============

### Upgrading to >= 1.1.1

#### Changes in rescue_from returned object

Grape will now check the object returned from `rescue_from` and ensure that it is a `Rack::Response`. That makes sure response is valid and avoids exposing service information. Change any code that invoked `Rack::Response.new(...).finish` in a custom `rescue_from` block to `Rack::Response.new(...)` to comply with the validation.

```ruby
class Twitter::API < Grape::API
rescue_from :all do |e|
# version prior to 1.1.1
Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' }).finish
# 1.1.1 version
Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' })
end
end
```

See [#1757](https://github.com/ruby-grape/grape/pull/1757) and [#1776](https://github.com/ruby-grape/grape/pull/1776) for more information.

### Upgrading to >= 1.1.0

#### Changes in HTTP Response Code for Unsupported Content Type
Expand Down
1 change: 1 addition & 0 deletions lib/grape.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ module Exceptions
autoload :InvalidAcceptHeader
autoload :InvalidVersionHeader
autoload :MethodNotAllowed
autoload :InvalidResponse
end

module Extensions
Expand Down
9 changes: 9 additions & 0 deletions lib/grape/exceptions/invalid_response.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Grape
module Exceptions
class InvalidResponse < Base
def initialize
super(message: compose_message(:invalid_response))
end
end
end
end
1 change: 1 addition & 0 deletions lib/grape/locale/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,5 @@ en:
invalid_version_header:
problem: 'Invalid version header'
resolution: '%{message}'
invalid_response: 'Invalid response'

10 changes: 8 additions & 2 deletions lib/grape/middleware/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def rack_response(message, status = options[:default_status], headers = { Grape:
if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
message = ERB::Util.html_escape(message)
end
Rack::Response.new([message], status, headers).finish
Rack::Response.new([message], status, headers)
end

def format_message(message, backtrace, original_exception = nil)
Expand Down Expand Up @@ -127,7 +127,13 @@ def run_rescue_handler(handler, error)
handler = public_method(handler)
end

handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler)
response = handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler)

if response.is_a?(Rack::Response)
response
else
run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new)
end
end
end
end
Expand Down
10 changes: 10 additions & 0 deletions spec/grape/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1723,6 +1723,16 @@ class CustomError < Grape::Exceptions::Base; end
expect(last_response.status).to eql 500
expect(last_response.body).to eq('Formatter Error')
end

it 'uses default_rescue_handler to handle invalid response from rescue_from' do
subject.rescue_from(:all) { 'error' }
subject.get('/') { raise }

expect_any_instance_of(Grape::Middleware::Error).to receive(:default_rescue_handler).and_call_original
get '/'
expect(last_response.status).to eql 500
expect(last_response.body).to eql 'Invalid response'
end
end

describe '.rescue_from klass, block' do
Expand Down
11 changes: 11 additions & 0 deletions spec/grape/exceptions/invalid_response_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require 'spec_helper'

describe Grape::Exceptions::InvalidResponse do
describe '#message' do
let(:error) { described_class.new }

it 'contains the problem in the message' do
expect(error.message).to include('Invalid response')
end
end
end
2 changes: 1 addition & 1 deletion spec/grape/middleware/exception_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def app
subject do
Rack::Builder.app do
use Spec::Support::EndpointFaker
use Grape::Middleware::Error, rescue_handlers: { NotImplementedError => -> { [200, {}, 'rescued'] } }
use Grape::Middleware::Error, rescue_handlers: { NotImplementedError => -> { Rack::Response.new('rescued', 200, {}) } }
run ExceptionSpec::OtherExceptionApp
end
end
Expand Down

0 comments on commit c117bff

Please sign in to comment.