Skip to content

Commit

Permalink
Validate response from the exception handler. Closes ruby-grape#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' })
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.