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

NoMethodError for nil:NilClass on headers.reverse_merge after upgrading Grape from 1.6.2 to 1.8.0 #2481

Closed
alekgosk opened this issue Jul 24, 2024 · 6 comments
Labels

Comments

@alekgosk
Copy link

alekgosk commented Jul 24, 2024

Environment

  • Ruby version: 3.1.6
  • Grape version: 1.8.0 (upgraded from 1.6.2)
  • OS: MacOS Sonoma 14.5

Description

After upgrading Grape from version 1.6.2 to 1.8.0, we started encountering a NoMethodError for nil:NilClass when calling reverse_merge on the headers variable. This issue occurs in our custom middleware where we manipulate the headers.

Steps to Reproduce

  1. Upgrade Grape from version 1.6.2 to 1.8.0.
  2. Execute a request that goes through the custom middleware which attempts to call reverse_merge on the headers variable.

Expected Behavior

The headers variable should be able to merge with another hash without throwing an error, as it worked in Grape version 1.6.2.

Actual Behavior

The application throws a NoMethodError indicating that reverse_merge is called on nil:NilClass. This suggests that the headers variable is nil at the time of the call.

Error Log

  1) Blinkist::Mobile::V4::Oauth2Api POST /oauth2/token when HTTP Signature is present and body is malformed returns bad request
     Failure/Error: post url, params

     NoMethodError:
       undefined method `reverse_merge' for nil:NilClass

               headers = headers.reverse_merge(Grape::Http::Headers::CONTENT_TYPE => content_type)
                                ^^^^^^^^^^^^^^
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/middleware/error.rb:54:in `error!'
     # ./api/shared/basic_api_trait.rb:93:in `block (2 levels) in <module:BasicApiTrait>'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/middleware/error.rb:129:in `instance_exec'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/middleware/error.rb:129:in `run_rescue_handler'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/middleware/error.rb:49:in `rescue in call!'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/middleware/error.rb:37:in `call!'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/middleware/base.rb:29:in `call'
     # /usr/local/bundle/gems/rack-2.2.9/lib/rack/head.rb:12:in `call'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/endpoint.rb:224:in `call!'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/endpoint.rb:218:in `call'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/router/route.rb:58:in `exec'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/router.rb:120:in `process_route'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/router.rb:74:in `block in identity'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/router.rb:94:in `transaction'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/router.rb:72:in `identity'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/router.rb:56:in `block in call'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/router.rb:136:in `with_optimization'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/router.rb:55:in `call'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/api/instance.rb:165:in `call'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/api/instance.rb:70:in `call!'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/api/instance.rb:65:in `call'
     # /usr/local/bundle/gems/grape-1.8.0/lib/grape/api.rb:81:in `call'
     # /usr/local/bundle/gems/rack-test-2.1.0/lib/rack/test.rb:360:in `process_request'
     # /usr/local/bundle/gems/rack-test-2.1.0/lib/rack/test.rb:163:in `custom_request'
     # /usr/local/bundle/gems/rack-test-2.1.0/lib/rack/test.rb:112:in `post'
     # ./spec/api/v4/api_oauth2_spec.rb:167:in `block (5 levels) in <top (required)>'
     # /usr/local/bundle/gems/webmock-3.23.1/lib/webmock/rspec.rb:39:in `block (2 levels) in <top (required)>'
     # ------------------
     # --- Caused by: ---
     # JSON::ParserError:
     #   Empty input (after ) at line 1, column 1 [parse.c:1062]
     #   /usr/local/bundle/gems/multi_json-1.15.0/lib/multi_json/adapters/oj.rb:34:in `load'

Rspec test that is failing

describe "POST /oauth2/token" do
  let(:trusted_user) { create(:user_blinkist) }
  let(:url) { "oauth2/token" }
  let!(:client) { create(:client, :secret => "abcde", :identifier => "12345") }
  let(:client_id) { client.identifier }
  let(:grant_type) { "client_credentials" }

  context "when HTTP Signature is present" do
    let(:client_secret) { client.secret_secret }
    let(:date) { Time.now.rfc2822 }

    let(:params) do
      {
        grant_type: grant_type,
        client_id: client_id,
      }.to_json
    end

    let(:signature) do
      data = JSON.parse(params).merge!({ "date" => date })
      digest = OpenSSL::Digest.new("sha256")
      OpenSSL::HMAC.hexdigest(digest, client_secret, data.to_json)
    end

    before do
      header "Authorization", "Signature #{signature}"
      header "Date", date
    end

    context "and body is malformed" do
      let(:signature) { "some signature" }
       let(:params) { { grant_type: grant_type, } }

      it "returns bad request" do
        post url, params
        expect(r_status).to be 400
      end
    end
  end
end
@dblock
Copy link
Member

dblock commented Jul 24, 2024

I would start by writing a spec in Grape 1.6 to reproduce this and bisect to a change between 1.6 and 1.8.

There has also been quite a few rack-related changes since 1.8. We can see if that test passes now and commit the test to ensure it doesn't break in the future.

@dblock dblock added the bug? label Jul 24, 2024
@ericproulx
Copy link
Contributor

ericproulx commented Jul 25, 2024

@alekgosk headers is {} unless you pass nil.

def error!(message, status = options[:default_status], headers = {}, backtrace = [], original_exception = nil)
Any chance that you call error! where the 3rd parameter is nil ?

@alekgosk
Copy link
Author

alekgosk commented Jul 25, 2024

Good catch @ericproulx, the culrpit is currently the following code:

# this code comes from a trait

rescue_from :grape_exceptions do |e|           
   # e.headers is nil here when request body is malformed
   error! e.message, e.status, e.headers
end

@ericproulx
Copy link
Contributor

#2342 introduced the block execution of a rescue_from :grape_exceptions and It's been released in 1.8.0. So in 1.6.2, your block has never been executed. I would suggest to just remove the block and let grape handle it for you. FYI, It will works from 2.1.0

@dblock
Copy link
Member

dblock commented Jul 25, 2024

Thanks for digging this up @ericproulx! @alekgosk close?

@alekgosk
Copy link
Author

Yup, I think we can close it, thanks @dblock

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants