ruckus / quickbooks-ruby

Quickbooks Online REST API V3 - Ruby
MIT License
374 stars 302 forks source link

Implement OAuth 2.0 #389

Closed Pro777 closed 5 years ago

Pro777 commented 7 years ago

Per this page: https://developer.intuit.com/docs/0100_quickbooks_online/0100_essentials/000500_authentication_and_authorization/connect_from_within_your_app

As of July 17, 2017, new or existing developers with no previous apps must implement OAuth 2.0. Click here for OAuth 1.0a documentation, available as a reference for existing applications.

ruckus commented 7 years ago

Interesting. It appears as if the current oauth gem we're using only supports 1.0.

Presumably we'd need to swap that library out for one that is full OAuth 2.0, perhaps:

https://github.com/intridea/oauth2

Methinks this is going to be a non-trivial swap

bradleybennett commented 7 years ago

Yeah, I just tried to use your gem for an app I am looking to write to connect Quickbooks Online to Shippo, but couldn't get it to auth and then realized that Intuit is requiring Oauth2 now. Doesn't that mean that anyone using this gem is broken until Oauth2 is integrated? Any plans to tackle this?

Correction: I see on the Intuit dev site where it says that as of 7/17/17... "new or existing developers with no previous apps must implement OAuth 2.0". So I guess that's me!

ruckus commented 7 years ago

I have started to look into this and what its going to take to support OAuth 2.0. I dont have any idea of when I will be able to deliver something, but I am hoping its within 2-3 weeks.

My testing/exploration uses a small Sinatra web app to implement the UI / callbacks

https://github.com/ruckus/quickbooks-ruby/compare/389-oauth2

Pro777 commented 7 years ago

@bradleybennett We chatted with Quickbooks support and they said that existing apps can continue using OAuth 1.0 for a while. We just got ours out into the wild last week, so they grandfathered us in.

ruckus commented 7 years ago

Thanks for the update @Pro777 - good to know that existing apps / OAuth 1.0 will be supported for awhile. New apps are basically SOL right now w/o a OAuth 2.0 solution

PatrickatPaperlessPCS commented 7 years ago

caught it purgatory myself - any workaround suggestions?

ruckus commented 7 years ago

@PatrickatPaperlessPCS I've started working on this and actually have a working proof of concept of completing the OAuth 2 authorization process with making a sample dummy request against the API

See here:

https://github.com/ruckus/quickbooks-ruby/blob/389-oauth2/webapp/app.rb

You might need to adjust Line 29 with a different Customer ID to fetch - this PoC assumes you have a Customer with ID=1

Depending on your own needs (e.g. you just need something simple on your end to get your own project going, e.g. fetch a list of customers or something) you could basically start rolling your own mini-library, just taking the new OAuth2 connection logic and making your own HTTP calls with an Access Token

The next challenge on my end is making this library compatible with both versions of OAuth. We cannot just rip out support for OAuth1 wholesale and move to OAuth2 -- so all of this has to be done in such a way where the library allows you to set a oauth version to use and the internals adjust accordingly.

Unfortunately I dont have a timeframe for this.

drewish commented 7 years ago

Pretty annoying that Intuit kicked this off with no advance notice.

stephenhuey commented 7 years ago

@ruckus Much obliged for your Sinatra example for the oauth2 gem. I just arrived to the world of Quickbooks in the last couple of weeks and was also struggling to get going since most examples online are using the old API. Your code got got me off to a good start on interacting with Quickbooks Online from Ruby on Rails.

edit: I especially appreciated your help since it's my first true foray into OAuth of any version and the oauth2 gem is hardly documented. :)

ruckus commented 7 years ago

@stephenhuey thats good to hear! I'm still working on OAuth2 support and bending the library to support both versions behind the scenes.

If you're already off to the races with OAuth2 then you'll need to be refreshing your access-token constantly (they expire every 1 hour).

In a nutshell wherever you make an API call in your own code you'll need to capture the 401 failure and then issue a refresh-token request, then retry your original operation.

See this helper script for an example:

https://github.com/ruckus/quickbooks-ruby/blob/389-oauth2/webapp/client.rb#L33

Its still not 100% clear to me if the access_token and/or the refresh_token themselves change after/during a refresh. If they dont change then its conceivable the library could handle a refresh internally. But if they do change then I think its best (albeit a pain in the ass) to leave it up to the caller to handle this - since they'll need to persist the changes wherever they need to.

stephenhuey commented 7 years ago

@ruckus Thanks, your script will come in handy! I am indeed off to the races but so far I've procrastinated on handling the refresh and was just doing it manually when testing. ;)

thejourneydude commented 7 years ago

Any update on this? Thanks for your hard work so far.

ruckus commented 7 years ago

@thejourneydude yes I have been working on this. Admittedly the move to be backwards compatible and support both oauth versions is bringing lots of complexity to the table. Yes, if we just wholesale cut out oauth v1 then I probably would've been done earlier.

But I'm not sure thats really possible (?). I mean, wouldnt it require that all current oauth v1 users just stop-the-world and update their apps / integrations? I know it would for me at $JOB.

That being said, I feel like I am 90% done. I'm working on the test suite. It does raise the question: would we just have the same tests but for v2? LIke straight-up copy and paste those tests but run them under Quickbooks.oauth_version=2 ?

That doesnt make much sense either. My current half-baked idea is to literally implement an RSpec-before-all hook which before each spec run just randomly sets the framework in either version and then runs that test. So across many test iterations ostensibly every spec would be run under both versions. Just a thought.

stevenmaguire commented 7 years ago

@ruckus thanks for digging into this problem and for sharing some insight into your approach to support the new QBO changes. I'd like to submit a thought based on your last message.

I don't think it's required to support both OAuth1 and OAuth2 in the same version of the library, especially if it's creating more work/complexity and we know the OAuth1 support is EOL soon. I think you can rely on the awesome power of semantic versioning to draw a symbolic line in the sand between supporting OAuth1 and OAuth2 with a major version bump, that package managers and consuming dev teams will honor. With a new major version out, you can still maintain bug fixes and general patching of the previous major version by incrementing the minor release or patch release versions and maintaining a separate tag, from which you can cut branches.

Again, just a thought to share, take it or leave it. We use this awesome package in one of our projects and are currently blocked from moving forward because QBO is only creating OAuth2 apps so we would love to see the support launch as fast as possible :)

drewish commented 7 years ago

I think the major version bump makes quite a bit of sense if you just have one set of credentials to upgrade. Having both auth flows available would be helpful for apps that have multiple sets of stored credentials. In our case getting all our users to upgrade their credentials after upgrading the gem would be pretty inconvenient. It would be much nicer if we could mark all the current credentials as OAuth v1 and start replacing them with V2 tokens as people reauthenticate. That assumes QBO will give you client credentials for both versions at the same time… I suppose you could do separate apps if not.

ruckus commented 7 years ago

@stevenmaguire great idea on the versioning approach. I didnt even think of that but it does sound like it could be a great solution.

However, as @drewish points out it would be problematic for existing users (like all of us) and needing to store two different credentials (marking existing as oauth v1 and new credentials as v2) but now that I'm thinking more about it would require jumping thru more hoops, e.g. when a new user of ours/yours product needs to connect with QBO we'd need to set the Quickbooks.oauth_version=2 and then proceed with the connection, etc.

To even add more complexity a question that just came to mind: is taking a global approach like Quickbooks.oauth_version=X thread-safe? I dont think it is. And this could cause problems with users doing operations in threaded environments like Puma / Sidekiq..

Given all of this, the next thought is to then possibly consider forking the whole library into quickbooks-oauth2 or something, changing all of the namespaces and then requiring us existing users to add both gems to our apps and then having client-logic switching between one or the other as needed. This would mean we'd have:

QuickbooksOauth2::Model::Customer Quickbooks::Model::Customer

etc. Which might not be that bad since all callers of that code would ostensibly be safe in that both versions of a name property - or whatever - so the objects are the same shape. And since Ruby is un-typed ....

stevenmaguire commented 7 years ago

The issue of supporting both in parallel does seem to be a practical use case; not a problem we are currently facing. I think the namespacing idea is a nice way to effectively fork the code while keeping in the same project. I would offer a suggestion to name the new namespace based on some upstream version or project name from QBO instead of the technical implementation. What if they change their API again but still use OAuth2? Or will the new namespace be isolated to Auth related activities and the existing auth agnostic code will still function as expected?

I would still bump a major release to keep things clean :)

gregawoods commented 7 years ago

Hi @ruckus - thanks for all your work on this.

I'm thinking more about it would require jumping thru more hoops, e.g. when a new user of ours/yours product needs to connect with QBO we'd need to set the Quickbooks.oauth_version=2 and then proceed with the connection, etc.

What if instead the version is set at the service level? e.g., service = Quickbooks::Service::Customer.new(oauth: 2). That way you could initialize the appropriate service for the current user.

The value of a global Quickbooks.oauth_version could be used to determine the default setting when no oauth: argument is passed, but changing the global flag on the fly would be discouraged.

ruckus commented 7 years ago

Thanks everyone for your thoughts and suggestions. Honestly I dont know what to do and feel like I'm stuck in analysis-paralsysis.

gregawoods commented 7 years ago

How would you feel about determining which response type to wrap based on the result of response.is_a?? I am using the following code in my version, and it's working ok for simple GET requests:

def self.wrap(response)
  if response.is_a?(OAuth2::Response)
    Quickbooks::Service::Responses::OAuth2HttpResponse.new(response)
  else
    Quickbooks::Service::Responses::OAuth1HttpResponse.new(response)
  end
end

This would allow you to remove the troublesome global attribute.

Regarding testing: What I would do, personally, is test one version of OAuth at a time using an environment variable. I would then set up CI to run rspec twice, once for each version of OAuth. I noticed that the specs here run very fast, so running twice shouldn't be too much of a burden. Here is what my OAuthHelpers currently looks like:

module OauthHelpers
  def construct_oauth
    FakeWeb.allow_net_connect = false

    if ENV['OAUTH'] == '1'
      oauth1_token
    else
      oauth2_token
    end
  end

  def oauth1_token
    oauth_consumer = OAuth::Consumer.new("app_key", "app_secret", {
        :site               => "https://oauth.intuit.com",
        :request_token_path => "/oauth/v1/get_request_token",
        :authorize_url      => "https://appcenter.intuit.com/Connect/Begin",
        :access_token_path  => "/oauth/v1/get_access_token"
    })
    OAuth::AccessToken.new(oauth_consumer, "token", "secret")
  end

  def oauth2_token
    client = OAuth2::Client.new('client_id', 'app_secret',
      site: 'https://appcenter.intuit.com/connect/oauth2',
      authorize_url: 'https://appcenter.intuit.com/connect/oauth2',
      token_url: 'https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer'
    )
    OAuth2::AccessToken.new(client, 'token')
  end

  # [...]
end

https://github.com/moser-inc/quickbooks-ruby/tree/oauth2-wip

ruckus commented 7 years ago

@gregawoods good point and question. I've just pushed the latest of my work-in-progress to the 389-oauth2 branch -- one thing that changed is that making requests has slightly changed:

See here:

https://github.com/ruckus/quickbooks-ruby/blob/389-oauth2/lib/quickbooks/service/base_service.rb#L250

So its not just reading the response type and going from there. The library needs to knows its OAuth version so it can make a different request signature.

gregawoods commented 7 years ago

Makes sense. I feel like similar logic could be applied though?

def oauth_post(url, body, headers)
  if @oauth.is_a? OAuth::AccessToken
    @oauth.post(url, body, headers)
  elsif @oauth.is_a? OAuth2::AccessToken
    @oauth.post(url, headers: headers, body: body)
  end
end
gregawoods commented 7 years ago

I spent some time this morning getting file uploads to work. Here is what I've found.

The OAuth2 client, which uses faraday, needs to be configured with a :multipart handler. I have a helper in my app that looks like this, and works for both file uploads and regular api requests:

module QuickbooksHelper
  def self.client
    OAuth2::Client.new(client_id, secret,
      site: 'https://appcenter.intuit.com/connect/oauth2',
      authorize_url: 'https://appcenter.intuit.com/connect/oauth2',
      token_url: 'https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer'
    ) do |builder|
      builder.request :multipart
      builder.request :url_encoded
      builder.adapter :net_http
    end
  end
end

In my testing the builder.request :url_encoded may not be 100% necessary. I'm still learning how faraday works with regards to which request handler it prioritizes.

Would it be worthwhile for the quickbooks-ruby gem to check for this? For example when an upload is ran, we could inspect the contents of client.connection.builder.handlers to see if the multipart handler is present, and if not, append it? Or is it better for the readme to instruct developers to use the appropriate handler on their own?

In services/upload.rb I had to determine what class to use for the upload - UploadIO or Faraday::UploadIO.

def upload(path_to_file, mime_type, attachable = nil)
  url = url_for_resource("upload")
  uploadIO = class_for_io.new(path_to_file, mime_type)
  response = do_http_file_upload(uploadIO, url, attachable)
  prefix = "AttachableResponse/xmlns:Attachable"
  if response.code.to_i == 200
    model.from_xml(parse_singular_entity_response(model, response.plain_body, prefix))
  else
    nil
  end
end

def class_for_io
  oauth.is_a?(OAuth2::AccessToken) ? Faraday::UploadIO : UploadIO
end

If any of this sounds useful I'm happy to create a PR - not trying to be pushy if you have other plans. Thanks!

ruckus commented 7 years ago

@gregawoods thanks so much! No, please by pushy; your initiative is much appreciated. I dont have any concrete plans .. as you can see :)

Would it be worthwhile for the quickbooks-ruby gem to check for this? For example when an upload is ran, we could inspect the contents of client.connection.builder.handlers to see if the multipart handler is present, and if not, append it? Or is it better for the readme to instruct developers to use the appropriate handler on their own?

Is there any harm to just doing this configuration once like you've done and keep it that way? You say it works for both regular & upload requests? So if thats the case lets just lock it in!

Your Faraday::UploadIO : UploadIO stuff looks great too.

I also like your previous responses of just doing is_a? checks to determine the oauth version. That seems like a good approach.

If you want to run with that and whip up a PR that would be amazing.

It seems like the final question is then how to approach the testing aspect. It would suck to duplicate all tests for 1 and 2 modes.

gregawoods commented 7 years ago

Cool! I'll look at PR'ing my stuff. Two quick questions:

My current test strategy is to run the entire rspec suite twice. Not ideal, but on the bright side the specs do run very fast.

#!/bin/bash
set -e
OAUTH=1 bundle exec rspec spec
OAUTH=2 bundle exec rspec spec
ruckus commented 7 years ago

Hi @gregawoods yes if/when I merge 389-oauth2 into master I would squash all those intermediate commits. I'm not concerned about history.

My current test strategy is to run the entire rspec suite twice.

I think this is probably the best plan. Good idea.

ruckus commented 7 years ago

Thanks @gregawoods for the PR!

All tests are passing. Now the real question: who wants to be the first to try the 389-oauth2 branch in their production? :| :)

stevenmaguire commented 7 years ago

We can give it whirl starting tomorrow or Friday. Are the docs updated?

ruckus commented 7 years ago

Hi @stevenmaguire no, the README has not been updated right now.

Great question though. I/we do need to think about what exactly that needs to be updated.

In terms of the library itself theoretically its plug-and-play compatible in terms of creating a new consumer:

client_id = ENV['OAUTH_CLIENT_ID']
client_secret = ENV['OAUTH_CLIENT_SECRET']

oauth_params = {
  :site => "https://appcenter.intuit.com/connect/oauth2",
  :authorize_url => "https://appcenter.intuit.com/connect/oauth2",
  :token_url => "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer",
  :connection_opts => {
    :proxy => "http://127.0.0.1:8888"
  }
}
oauth_client = OAuth2::Client.new(client_id, client_secret, oauth_params)

Of course the token requesting / granting flow has changed a little as well. Please have a look at this sinatra app for the new flow:

https://github.com/ruckus/quickbooks-ruby/blob/389-oauth2/webapp/app.rb

Once a token has been retrieved you can feed it to any Service implementation "like normal".

The biggest change that you will need to be aware of (and implement in your own application) is the token refreshment.

@minimul has done a great job of documenting this and providing a proof of concept solution at:

http://minimul.com/refreshing-the-quickbooks-oauth2-access-token.html

brianhenryhf commented 7 years ago

Thank you @ruckus and @gregawoods! I'm working on making use of your changes and am wondering: is disconnect/revoke fxnality something planned or in-progress, perchance?

fabrouy commented 7 years ago

Hello guys, I'm going to use QB in production so happy to help with this branch.

Something to clarify, even this branch is called oauth2, it's still making the requests using oauth1 right? I ask because this is the error I'm getting when trying to pull customers:

#<Quickbooks::Service::Responses::OAuth1HttpResponse:0x007fee3e6e6fe8 @real_response=nil>
NoMethodError: undefined method `code' for nil:NilClass

This is the line causing the error: https://github.com/ruckus/quickbooks-ruby/blob/389-oauth2/lib/quickbooks/service/responses/oauth1_http_response.rb#L19

I do not want to add or update any code pieces until I'm sure the above is not just a problem on my end.

ruckus commented 7 years ago

Are you using the latest from the branch? I have a local version in my Rails app:

gem "quickbooks-ruby", :path => "/Users/cody/Projects/quickbooks-ruby"

and no outstanding commits to push and I can write a new Customer and read all Customers

ruckus commented 7 years ago

@fabrouy

Something to clarify, even this branch is called oauth2, it's still making the requests using oauth1 right?

Yes. The library automatically detects the OAuth version ala

https://github.com/ruckus/quickbooks-ruby/blob/389-oauth2/lib/quickbooks/service/base_service.rb#L40-L47

So assuming you've made no changes on your end you should be OAuth1 mode.

<Quickbooks::Service::Responses::OAuth1HttpResponse:0x007fee3e6e6fe8 @real_response=nil>

NoMethodError: undefined method `code' for nil:NilClass

This suggests that the request itself did not complete (@real_response is nil) or at least the request didn't get properly executed and deserialized at the first stage.

Can you enable logging and post more output?

brianhenryhf commented 7 years ago

on @gregawoods question above:

we could inspect the contents of client.connection.builder.handlers to see if the multipart handler is present, and if not, append it? Or is it better for the readme to instruct developers to use the appropriate handler on their own?

I just hit a snag in trying to use the same OAuth2::Client instance to refresh an access token and then perform a quickbooks api call (non-upload-related), and ended up with StackLocked exception ("can't modify middleware stack after making a request"). I hadn't set up the client with the 'multipart' Faraday middleware, and the (successful) refresh call locked the stack; the subsequent api call hit https://github.com/ruckus/quickbooks-ruby/blob/389-oauth2/lib/quickbooks/service/base_service.rb#L28, trying to mod the stack (here), which errored out, as Faraday doesn't allow the change after initial use.

Seems like useful info to have in the readme - even if you don't plan to use upload, you should set up your client with multipart. (or, maybe it makes sense to include something like @gregawoods 's QuickbooksHelper::client functionality to try to abstract away the underlying oauth/faraday guts and quickbooks urls?)

gregawoods commented 7 years ago

Ah, fascinating. It looks like we can check builder.locked? to detect that and avoid rebuilding the connection. I would probably log a warning, and raise an error within post_with_multipart only if the developer attempts to perform a file upload without multipart support. That way developers not using the file upload feature are not affected.

Though, I do like the idea of building client helpers directly into the library. @ruckus, do you see any problems with that?

v1_client = Quickbooks.oauth_v1_client(key, secret)
v2_client = Quickbooks.oauth_v2_client(key, secret)

I haven't touched the README so far, mostly because I'm not sure how @ruckus wants to organize information re OAuth v1 and v2.

fabrouy commented 7 years ago

Hey @ruckus,

This is what I'm doing:

gem 'quickbooks-ruby', git: 'https://github.com/ruckus/quickbooks-ruby.git', branch: '389-oauth2'
service = Quickbooks::Service::Customer.new(company_id: QuickbooksToken.realm_id, access_token: QuickbooksToken.access_token)
customers = service.query()

And this is the logger output:

I, [2017-10-20T14:14:38.673769 #11901]  INFO -- : ------ QUICKBOOKS-RUBY REQUEST ------
I, [2017-10-20T14:14:38.673849 #11901]  INFO -- : METHOD = get
I, [2017-10-20T14:14:38.673869 #11901]  INFO -- : RESOURCE = https://sandbox-quickbooks.api.intuit.com/v3/company/193514638242359/query?query=SELECT+%2A+FROM+Customer+STARTPOSITION+1+MAXRESULTS+20
I, [2017-10-20T14:14:38.673887 #11901]  INFO -- : REQUEST BODY:
I, [2017-10-20T14:14:38.673905 #11901]  INFO -- : {}
I, [2017-10-20T14:14:38.673943 #11901]  INFO -- : REQUEST HEADERS = {"Content-Type"=>"application/xml", "Accept"=>"application/xml", "Accept-Encoding"=>"gzip, deflate"}
I, [2017-10-20T14:14:38.673980 #11901]  INFO -- : ------ QUICKBOOKS-RUBY RESPONSE ------
NoMethodError: undefined method `code' for nil:NilClass
from /.../ruby-2.2.5/bundler/gems/quickbooks-ruby-3c2b7b9a6508/lib/quickbooks/service/responses/oauth1_http_response.rb:19:in `code'

I can perform the request and pull the customers manually (without the gem) just fine. Any ideas where I'm stuck into?

ruckus commented 7 years ago

Great find!

Though, I do like the idea of building client helpers directly into the library. @ruckus, do you see any problems with that?

That could be useful. If you want to attach a PR onto 389 that would be great.

I haven't touched the README so far, mostly because I'm not sure how @ruckus wants to organize information re OAuth v1 and v2.

Great question. I haven't tackled this yet because I am waiting for the dust to settle.

Here is a quick diff on a proposed solution. When assigning the access_token= we'll just build the Faraday client if in oauth_v2?

diff --git a/lib/quickbooks/service/base_service.rb b/lib/quickbooks/service/base_service.rb
index 581226f..d5a7154 100644
--- a/lib/quickbooks/service/base_service.rb
+++ b/lib/quickbooks/service/base_service.rb
@@ -25,7 +25,11 @@ module Quickbooks

       def access_token=(token)
         @oauth = token
-        verify_multipart!
+
+        # OAuth2 requires the Faraday stack to be built with multipart support
+        if oauth_v2?
+          verify_multipart!
+        end
       end

       def company_id=(company_id)
@@ -47,7 +51,9 @@ module Quickbooks

       # Multipart is required for file upload
       def verify_multipart!
-        if oauth_v2? && !@oauth.client.connection.builder.handlers.include?(Faraday::Request::Multipart)
+        return if @oauth.client.connection.builder.locked?
+
+        unless @oauth.client.connection.builder.handlers.include?(Faraday::Request::Multipart)
           @oauth.client.connection.build do |builder|
             builder.request :multipart
             builder.request :url_encoded
ruckus commented 7 years ago

@fabrouy You might be hitting this snag I found yesterday where the asterisk was necessary in the pseudo-SQL. It was resolved here:

https://github.com/ruckus/quickbooks-ruby/commit/3c2b7b9a6508ceb8837d2f3928bb10644a5f0b67

Can you try the latest from the branch and try again? Thanks!

fabrouy commented 7 years ago

@ruckus Same result. I will to some digging and get back.

ruckus commented 7 years ago

@fabrouy I'd be curious to know if the API response from Intuit is truly blank or whether its an issue in the deserialization of that response.

You can hook a HTTP proxy into the library to see the raw HTTP operations and this should answer that question, see

https://github.com/ruckus/quickbooks-ruby#debugging

Personally I'm a big fan of Charles Proxy and I have it running all the time when doing quickbooks-ruby development.

stephenhuey commented 7 years ago

@ruckus I know you're not responsible for Faraday or oauth2 but after you showed us how to get started, I've made some great headway with it. I wanted to be able to see in the logs the requests that oauth2 was making with Faraday, but after setting the environment variable OAUTH_DEBUG to 'true' I'm running into this problem:

Faraday::RackBuilder::StackLocked (can't modify middleware stack after making a request): app/models/quickbooks_client.rb:184:in request_access_token' app/controllers/quickbooks_controller.rb:62:inwebhook'

I'm specifically seeing this when I try to call this method which always worked before:

oauth2_client.auth_code.get_token(authorization_code, :redirect_uri => QB_REDIRECT_URI)

I imagine you would've needed this at some point, so do you know some other way to log the outgoing requests and their parameters?

yoonwaiyan commented 7 years ago

@ruckus based on the documentation:

service = Quickbooks::Service::AccessToken.new
service.access_token = access_token
service.company_id = record.company_id
new_token = service.renew

Can I use the same call to refresh the token for OAuth 2.0? I've tried using it but no luck :( Getting the same result as comments above, apparently it's still calling OAuth 1 based on the stacktrace:

NoMethodError: undefined method `code' for nil:NilClass
from /Users/yoonwaiyan/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/bundler/gems/quickbooks-ruby-fbe96496fa40/lib/quickbooks/service/responses/oauth1_http_response.rb:19:in `code`
fabrouy commented 7 years ago

@yoonwaiyan Can you try the code on this PR please? Your exception seems like the one I was getting.

brianhenryhf commented 7 years ago

@stephenhuey I haven't wrestled this problem, but it looks like others have. Perhaps a fix listed/linked from there can help with that flag.

Otherwise, @ruckus 's comment on a proxy could help - you could opt to not use the debug flag and instead watch the requests/responses in the proxy (for local use, anyway). I don't use Charles, but Owasp ZAP can log your HTTP(S) requests as well (and is free).

yoonwaiyan commented 7 years ago

@fabrouy tried the merged branch of your PR, unfortunately it's not working but the error is different now:

/Users/yoonwaiyan/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/nokogiri-1.6.8.1/lib/nokogiri/xml/document.rb:44: warning: constant ::Fixnum is deprecated
Quickbooks::IntuitRequestException: :
        <?xml version="1.0"?>
from /Users/yoonwaiyan/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/bundler/gems/quickbooks-ruby-053460340765/lib/quickbooks/service/base_service.rb:381:in `parse_and_raise_exception'

any idea?

fabrouy commented 7 years ago

@yoonwaiyan Would you be so kind to enable logging? Quickbooks.log = true

yoonwaiyan commented 7 years ago

@fabrouy This is the output after enabling logging:

[7] pry(main)> new_token = service.renew
I, [2017-10-25T11:23:42.201653 #54041]  INFO -- : ------ QUICKBOOKS-RUBY REQUEST ------
I, [2017-10-25T11:23:42.201769 #54041]  INFO -- : METHOD = get
I, [2017-10-25T11:23:42.201812 #54041]  INFO -- : RESOURCE = https://appcenter.intuit.com/api/v1/connection/reconnect
I, [2017-10-25T11:23:42.201851 #54041]  INFO -- : REQUEST BODY:
I, [2017-10-25T11:23:42.201924 #54041]  INFO -- : {}
I, [2017-10-25T11:23:42.201972 #54041]  INFO -- : REQUEST HEADERS = {"Content-Type"=>"application/xml", "Accept"=>"application/xml", "Accept-Encoding"=>"gzip, deflate"}
I, [2017-10-25T11:23:46.030669 #54041]  INFO -- : ------ QUICKBOOKS-RUBY RESPONSE ------
I, [2017-10-25T11:23:46.030757 #54041]  INFO -- : RESPONSE CODE = 200
I, [2017-10-25T11:23:46.030803 #54041]  INFO -- : RESPONSE BODY:
/Users/yoonwaiyan/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/nokogiri-1.6.8.1/lib/nokogiri/xml/document.rb:44: warning: constant ::Fixnum is deprecated
I, [2017-10-25T11:23:46.040571 #54041]  INFO -- : <?xml version="1.0"?>

Quickbooks::IntuitRequestException: :
        <?xml version="1.0"?>
from /Users/yoonwaiyan/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/bundler/gems/quickbooks-ruby-053460340765/lib/quickbooks/service/base_service.rb:381:in `parse_and_raise_exception'
ruckus commented 7 years ago

@yoonwaiyan It looks like you're running Ruby 2.4.x - I saw that :Fixnum issue come up when another user reported issues with Ruby 2.4.x:

https://github.com/ruckus/quickbooks-ruby/issues/386

yoonwaiyan commented 7 years ago

@ruckus deprecation warning shows on other call as well. By the way one workaround for token refresh is just refresh the token directly from OAuth2 client. Seems like it was undocumented in OAuth2 client as well and I have to search through the OAuth2 repo to know that it can be refreshed in this way:

new_access_token = access_token.refresh!

since the snippets in README.md is not working.

fabrouy commented 7 years ago

@yoonwaiyan Great find! Would you be so kind to submit a PR with this changes to 389-oauth2 branch? As you can see this branch is a WIP and docs are mentioning the old OAuth library.

By the way, could you test with ruby < 2.4 to check if the error you are getting persists?