Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle bad request params #50

Merged
merged 4 commits into from
Feb 5, 2020
Merged

Conversation

martin308
Copy link
Member

As mentioned in #49, the rails middleware can break when there are bad params sent to the rails application and it tries to read out the controller and action.

In this we catch the bad request exception and set the message as the value for controller and action. I'm not sure if this super useful though. A sample of this is below. Anyone have any thoughts? We could also just not add those keys at all or set them to 'unknown' or similar?

"request.action"=>"Invalid query parameters: Invalid encoding for parameter: �"

This will allow us to add a "bad request" describe block for this new behaviour
We could catch a more general error here instead?
@cade
Copy link
Contributor

cade commented Dec 11, 2019

Anyone have any thoughts?

I don't have any preferences on what's recorded, but I'll check with the Ops team here to see if they've got any feedback!

@cade
Copy link
Contributor

cade commented Dec 11, 2019

I'll check with the Ops team

They didn't have any suggestions!

@paulosman paulosman self-requested a review February 5, 2020 12:32
@martin308 martin308 merged commit 13f6269 into master Feb 5, 2020
@martin308 martin308 deleted the martin308.handle-bad-requests branch February 5, 2020 15:50
ajvondrak added a commit to ajvondrak/beeline-ruby that referenced this pull request Feb 22, 2020
* Remove extraneous spans that were being generated due to the
  ActiveSupport::Notifications subscriptions. There's no need to
  actually configure these from the tests, since we already have the
  active_support_spec.rb, and it's not like the config in the
  rails_spec.rb even matched the one from the Rails generator or
  anything. These just get in the way of testing the Rails integration,
  since the custom fields we'd want to make assertions about only wind
  up on the root span anyway.

* Factor out assertions into a set of shared examples, and actually
  start making assertions about the `request.{action,controller,route}`
  fields that the integration adds (which weren't even being tested for
  previously 😱).

* Rework the "bad request" tests that reproduce honeycombio#49. These tests will
  now break against the old implementation, since they assert that
  routing information is still present (whereas honeycombio#50 wipes the fields out
  with error messages). There are ways to get the routing information
  without ever trying to parse the GET/POST parameters, so we can still
  save this use case. Furthermore, the only Rails 5+ specific changes
  are to whether or not Rails responds with an HTTP 400, so we needn't
  wipe out the entire `describe` block for Rails < 5.

* Add tests to reproduce the issue described in honeycombio#62, where unrecognized
  routes may cause the old implementation to erroneously fall back to
  the GET/POST parameters for routing information.

* Add tests that reproduce honeycombio#31. The bug is actually in the twirp gem,
  whose middleware fails to rewind the `rack.input` after reading it.
  I've verified these regression tests against the old version of the
  Rails integration that didn't check `if request.raw_post`, and they
  fail appropriately. I wanted to have this test in here to have
  confidence in changing the implementation of the Rails integration.
  The `raw_post` workaround of honeycombio#39 is unnecessary if we actually never
  look at the GET/POST parameters in the first place (no parsing, no
  problem).
ajvondrak added a commit to ajvondrak/beeline-ruby that referenced this pull request Feb 22, 2020
This refactoring avoids ever parsing GET/POST parameters to obtain the
Rails routing information required for the `request.action`,
`request.controller`, and `request.route` fields.

This is accomplished in two ways:

1. Use `ActionDispatch::Request#path_parameters` instead of
   `ActionDispatch::Request#params`. The former is the subset of data we
   need for `request.action` + `request.controller`, and in fact are the
   values merged into the latter hash after `#params` parses the normal
   GET/POST parameters. So we can avoid the extraneous parsing by just
   getting the path parameters straight from the horse's mouth.

2. Call `ActionDispatch::Journey::Router#recognize` on a simplified
   request that *only* contains the necessary routing information. The
   `#recognize` method will yield the `ActionDispatch::Request#params`
   when it matches a route, which again triggers the GET/POST parsing.
   But we don't care about the HTTP parameters. So instead I copied the
   approach of `ActionDispatch::Routing::RouteSet#recognize_path`, which
   reconstructs a simplified request with just the information that it
   needs. (Unfortunately we can't use `#recognize_path` directly because
   instead of returning the route it returns the path parameters, which
   we can already get directly from `#path_parameters`.)

This accomplishes several things:

* Fixes honeycombio#62, since we don't get a chance to erroneously fall back to
  GET/POST parameters in the event that the routing information is
  missing (due to requests to unrecognized routes).

* Fixes honeycombio#31, since the bug ultimately comes from the twirp gem causing
  POST parameter parsing to fail. But if we never parse the HTTP
  parameters, no problem. This is a more direct workaround than honeycombio#39, but
  ultimately is a problem that should be fixed upstream.

* Fixes honeycombio#49, since the parameter encoding won't matter if we never try
  to parse them. Renders honeycombio#50 unnecessary, since we won't have a chance
  to trigger the exceptions in question.

* Gives us more data in more situations. The new implementation of the
  `request.route` field now comes up non-nil in the event of errors
  because we use the `ActionDispatch::Request#original_fullpath` and not
  the `#path_info` that gets [rewritten by
  `ActionDispatch::ShowExceptions`](https://github.com/rails/rails/blob/758e4f8406e680a6cbf21b170749202c537a2576/actionpack/lib/action_dispatch/middleware/show_exceptions.rb#L49).

* Gives us more data in more Rails versions. With the addition of actual
  tests for the field values, it became clear that `request.route` was
  missing in Rails 4, simply because of an interface change introduced
  in Rails 5.
martin308 pushed a commit that referenced this pull request Mar 9, 2020
This refactoring avoids ever parsing GET/POST parameters to obtain the
Rails routing information required for the `request.action`,
`request.controller`, and `request.route` fields.

This is accomplished in two ways:

1. Use `ActionDispatch::Request#path_parameters` instead of
   `ActionDispatch::Request#params`. The former is the subset of data we
   need for `request.action` + `request.controller`, and in fact are the
   values merged into the latter hash after `#params` parses the normal
   GET/POST parameters. So we can avoid the extraneous parsing by just
   getting the path parameters straight from the horse's mouth.

2. Call `ActionDispatch::Journey::Router#recognize` on a simplified
   request that *only* contains the necessary routing information. The
   `#recognize` method will yield the `ActionDispatch::Request#params`
   when it matches a route, which again triggers the GET/POST parsing.
   But we don't care about the HTTP parameters. So instead I copied the
   approach of `ActionDispatch::Routing::RouteSet#recognize_path`, which
   reconstructs a simplified request with just the information that it
   needs. (Unfortunately we can't use `#recognize_path` directly because
   instead of returning the route it returns the path parameters, which
   we can already get directly from `#path_parameters`.)

This accomplishes several things:

* Fixes #62, since we don't get a chance to erroneously fall back to
  GET/POST parameters in the event that the routing information is
  missing (due to requests to unrecognized routes).

* Fixes #31, since the bug ultimately comes from the twirp gem causing
  POST parameter parsing to fail. But if we never parse the HTTP
  parameters, no problem. This is a more direct workaround than #39, but
  ultimately is a problem that should be fixed upstream.

* Fixes #49, since the parameter encoding won't matter if we never try
  to parse them. Renders #50 unnecessary, since we won't have a chance
  to trigger the exceptions in question.

* Gives us more data in more situations. The new implementation of the
  `request.route` field now comes up non-nil in the event of errors
  because we use the `ActionDispatch::Request#original_fullpath` and not
  the `#path_info` that gets [rewritten by
  `ActionDispatch::ShowExceptions`](https://github.com/rails/rails/blob/758e4f8406e680a6cbf21b170749202c537a2576/actionpack/lib/action_dispatch/middleware/show_exceptions.rb#L49).

* Gives us more data in more Rails versions. With the addition of actual
  tests for the field values, it became clear that `request.route` was
  missing in Rails 4, simply because of an interface change introduced
  in Rails 5.

---

Revamp the Rails integration's tests



* Remove extraneous spans that were being generated due to the
  ActiveSupport::Notifications subscriptions. There's no need to
  actually configure these from the tests, since we already have the
  active_support_spec.rb, and it's not like the config in the
  rails_spec.rb even matched the one from the Rails generator or
  anything. These just get in the way of testing the Rails integration,
  since the custom fields we'd want to make assertions about only wind
  up on the root span anyway.

* Factor out assertions into a set of shared examples, and actually
  start making assertions about the `request.{action,controller,route}`
  fields that the integration adds (which weren't even being tested for
  previously 😱).

* Rework the "bad request" tests that reproduce #49. These tests will
  now break against the old implementation, since they assert that
  routing information is still present (whereas #50 wipes the fields out
  with error messages). There are ways to get the routing information
  without ever trying to parse the GET/POST parameters, so we can still
  save this use case. Furthermore, the only Rails 5+ specific changes
  are to whether or not Rails responds with an HTTP 400, so we needn't
  wipe out the entire `describe` block for Rails < 5.

* Add tests to reproduce the issue described in #62, where unrecognized
  routes may cause the old implementation to erroneously fall back to
  the GET/POST parameters for routing information.

* Add tests that reproduce #31. The bug is actually in the twirp gem,
  whose middleware fails to rewind the `rack.input` after reading it.
  I've verified these regression tests against the old version of the
  Rails integration that didn't check `if request.raw_post`, and they
  fail appropriately. I wanted to have this test in here to have
  confidence in changing the implementation of the Rails integration.
  The `raw_post` workaround of #39 is unnecessary if we actually never
  look at the GET/POST parameters in the first place (no parsing, no
  problem).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants