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

added grape helper #567

Merged
merged 1 commit into from
Mar 12, 2015
Merged

added grape helper #567

merged 1 commit into from
Mar 12, 2015

Conversation

hobofan
Copy link
Contributor

@hobofan hobofan commented Feb 2, 2015

Proof of concept for #563.

Should not be merged yet, since it still has a few rough edges:

  • I couldn't figure out how to get a array of scopes [:private, :private_write] into a comparable format for valid_doorkeeper_token?(*scopes). The current implementation probably doesn't work well with multiple scopes.
  • specs are missing
  • currently only the from_access_token_param and from_bearer_token_param access_token methods work, sind Grape::Request doesn't provide a similiar method to ActionDispatch::Request#authorization

small code sample on how to use it:

require 'doorkeeper/grape/helpers'

module API
    class Base < Grape::API
       before do
          doorkeeper_authorize!
       end

       get '/', scopes: [:users] do
           @users = Users.all
       end
   end
end

nil
end

def doorkeeper_error_renderer(error, options = {})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused method argument - options. If it's necessary, use _ or _options as an argument name to indicate that it won't be used.

@tute
Copy link
Contributor

tute commented Feb 3, 2015

Thanks for your work.

I want to do the opposite though: instead of adding framework specifics into doorkeeper, remove them, so new libraries can be built that bridge the gap between an app in any framework and doorkeeper. The refactors I envision are about removing Rails specifics, so doorkeeper runs without loading any Rails framework.

If will of course expect some API, like params, my goal is to minimize it as much as possible.

@tute
Copy link
Contributor

tute commented Feb 14, 2015

For example, you mention in #563 (comment):

If Doorkeeper would only rely, or provide a fallback to Rack::Request, integrations for other frameworks should be pretty trivial. (e.g. two access_token_methods rely on the existence of Request#authorization which is only provided by ActionDispatch::Request)

Detaching doorkeeper internals from Rails, and building a facade that other frameworks can implement would be nice. I'd leave for now Rails facade in as doorkeeper was born as a Rails plugin.

@tute
Copy link
Contributor

tute commented Feb 14, 2015

I like your Doorkeeper::Grape::Helpers module. Do you think we can extract an analogous Doorkeeper::Rails::Helpers?

@ljkbennett
Copy link

Just been playing with this and managed to get round the impact on the core Doorkeeper code by using a delegator, e.g.:

module Doorkeeper
  module Grape
    class RequestDecorator < SimpleDelegator

      def parameters
        params
      end

      def authorization
        env = __getobj__.env
        env['HTTP_AUTHORIZATION']   ||
        env['X-HTTP_AUTHORIZATION'] ||
        env['X_HTTP_AUTHORIZATION'] ||
        env['REDIRECT_X_HTTP_AUTHORIZATION']
      end

    end
  end
end

then wrapping up the request in that before passing it into OAuth::Token.authenticate

e.g., in doorkeeper/grape/helpers.rb:

         def decorated_request
            RequestDecorator.new(request)
          end

          def doorkeeper_token
            @_doorkeeper_token ||= OAuth::Token.authenticate decorated_request, *Doorkeeper.configuration.access_token_methods
          end

The helper and decorator could then easily be pulled out into a separate gem to provide Grape support through the existing interface.

@hobofan
Copy link
Contributor Author

hobofan commented Feb 16, 2015

You mean this one? 😉

That's what I based my helper on.

It should probably be possible to create a common helper which implements the authorization methods, so that only the error rendering has to be implemented by the framework specific helper.

@tute
Copy link
Contributor

tute commented Feb 20, 2015

It should probably be possible to create a common helper which implements the authorization methods, so that only the error rendering has to be implemented by the framework specific helper.

That's exactly how it should be working indeed! Both helpers repeat methods that are quite trivial, I then don't feel strongly they shouldn't be repeated, although that's not ideal. The difference in OAuth::Token::Methods is more awkward with the conditionals.

My hesitation about merging the PR is that I don't use Grape, so I won't be able to maintain it well. Two questions:

  1. Do you think we could test doorkeeper with Grape? How?
  2. How could doorkeeper make it easier for Grape (or other framework) users to configure doorkeeper_authorize! and doorkeeper_error_renderer?

tute added a commit that referenced this pull request Feb 20, 2015
Makes explicit what the public API is to inject doorkeeper behavior into
application endpoints.

This should be the only interface between Rails and doorkeeper. Other
frameworks can define their own interface and use doorkeeper too. This
is a step into decoupling doorkeeper from Rails, so that integration
is easier.

Related with #567.
@tute
Copy link
Contributor

tute commented Feb 20, 2015

@hobofan it would be nice if you can rebase your branch on top of latest master. See refactors in referenced commit.

tute added a commit that referenced this pull request Feb 20, 2015
Makes explicit what the public API is to inject doorkeeper behavior into
application endpoints.

This should be the only interface between Rails and doorkeeper. Other
frameworks can define their own interface and use doorkeeper too. This
is a step into decoupling doorkeeper from Rails, so that integration
is easier.

Related with #567.
end

# endpoint specific scopes > parameter scopes > default scopes
def doorkeeper_authorize!(*scopes)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assignment Branch Condition size for doorkeeper_authorize! is too high. [36.07/15]
Perceived complexity for doorkeeper_authorize! is too high. [10/7]


def authorization
env = __getobj__.env
env['HTTP_AUTHORIZATION'] ||

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary spacing detected.

@hobofan
Copy link
Contributor Author

hobofan commented Feb 20, 2015

  1. I think the testing would require a dummy app, similar to the way wine_bouncer is testing it. This would add quite some weight to the specs in doorkeeper so I think that I will ultimately make this PR into a gem.
  2. I think the error rendering is handled pretty well now and can be implemented easily by another framework.

As for doorkeeper_authorize!, I am having a hard time figuring out what the scopes argument should look like. I tried everything from an array of symbols and strings of the scope names to Doorkeeper::OAuth::Scopes, and that's how I ended up with the current buggy implementation. What would be the simplest way to convert an array of symbols into the expected format?

I also noticed this piece of code:

def doorkeeper_authorize!(*scopes)
  @_doorkeeper_scopes = scopes || Doorkeeper.configuration.default_scopes

*scopes will default to [] if no parameters are provided, so the default Doorkeeper scopes will never be used.

@tute
Copy link
Contributor

tute commented Feb 21, 2015

  1. I think the testing would require a dummy app, similar to the way wine_bouncer is testing it. This would add quite some weight to the specs in doorkeeper so I think that I will ultimately make this PR into a gem.

👍

  1. I think the error rendering is handled pretty well now and can be implemented easily by another framework.

👍

As for doorkeeper_authorize!, I am having a hard time figuring out what the scopes argument should look like. I tried everything from an array of symbols and strings of the scope names to Doorkeeper::OAuth::Scopes, and that's how I ended up with the current buggy implementation. What would be the simplest way to convert an array of symbols into the expected format?

There's samples in https://github.com/doorkeeper-gem/doorkeeper/#access-token-scopes. It accepts many strings/symbols.

I also noticed this piece of code:

def doorkeeper_authorize!(*scopes)
  @_doorkeeper_scopes = scopes || Doorkeeper.configuration.default_scopes

*scopes will default to [] if no parameters are provided, so the default Doorkeeper scopes will never be used.

Very true. To my surprise, it was buggy before, too; see https://github.com/doorkeeper-gem/doorkeeper/blob/v2.1.1/lib/doorkeeper/rails/helpers.rb#L29 (default scopes would not be taken into account). I started a branch to take care of this (https://github.com/doorkeeper-gem/doorkeeper/tree/tc-bugfix) but I want to be able to unit test it, couldn't do it yet.

Thanks for your time and work!

@tute
Copy link
Contributor

tute commented Feb 21, 2015

Your PR is looking better, I like the decorator. We now need to fix the scopes implementations, and unit test it. Cheers!

endpoint_scopes = env['api.endpoint'].options[:route_options][:scopes]
scope_string = Doorkeeper::OAuth::Scopes.from_array(scopes).all.inspect if scopes && !scopes.empty?
scope_string = Doorkeeper::OAuth::Scopes.from_array(endpoint_scopes).all.inspect if endpoint_scopes
scopes = Array.wrap(scope_string) if scope_string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the block level if style, better.

Also, why do you use .inspect?

@tute
Copy link
Contributor

tute commented Mar 11, 2015

@hobofan I'm liking this PR. Can you please rebase it on top of latest master? Thanks!

includes decorated request as suggested by @littleowllabs
@hobofan hobofan force-pushed the grape-integration branch from 5c3c15d to ff0da09 Compare March 11, 2015 22:25
@hobofan
Copy link
Contributor Author

hobofan commented Mar 11, 2015

The .inspect was due too strange behaviour I was experiencing with multiple scopes, but that turned out to be caused by the way the test was written in our app.
I used AccessToken.create(scopes: ['scope1, scope2']), but it turns out that scopes: expects an actual OAuth::Scopes object, where an Array worked in the past. I don't know if that's a regression or an expected breakage, since the Doorkeeper version I previously used was 1.4.0.

Either way, the .inspects are now gone, and it now works as expected with multiple scopes etc. and has been running well in our production code for a few days.

@tute tute merged commit ff0da09 into doorkeeper-gem:master Mar 12, 2015
@tute
Copy link
Contributor

tute commented Mar 12, 2015

Thank you! :)

@tute tute mentioned this pull request Mar 12, 2015
@calfzhou
Copy link
Contributor

This feature looks great. I'm using doorkeeper as oauth provider for my grape api server.

In my opinion it might be better to keep this feature in a standalone gem, like wine_bouncer and grape-doorkeeper. Since with grape, we also pay attention on its document generation, e.g. i use grape-swagger to generate a swagger spec for my apis. Wine_bouncer has a good feature that it can automatically inject oauth requirements into api endpoints' description. If put all these logics into doorkeeper will make it hard to maintain - it need tracks grape, swagger, and grape-swagger's new features to keep everything fine.

@calfzhou
Copy link
Contributor

one more comment on the usage:

       get '/', scopes: [:users] do
           @users = Users.all
       end

it would be better move scopes to above of get, for example

       scopes: [:users]
       get '/' do
           @users = Users.all
       end

with this syntax we can easily inject scopes requirements into endpoint's description to generate better documentation.

@tute
Copy link
Contributor

tute commented Mar 12, 2015

@calfzhou I agree this belongs in its own project, maintained by people who use Grape on a daily basis.

I merged it anyway because it's simple enough that I can maintain it without significant effort and encapsulated enough to revert it when it's no longer needed. It's a useful addition for me also to understand better the API doorkeeper should expose to frameworks.

All you mention sound good, and I'm happy to help make it happen.

@RinkeRiezebos
Copy link
Contributor

Using

require 'doorkeeper/grape/helpers'

module API
    module V1
        class Users < Grape::API
            before do
                 doorkeeper_authorize!
            end

                       ....
        end
    end
end

causes a Users/Username/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/activesupport-4.2.0/lib/active_support/dependencies.rb:274:in 'require': cannot load such file -- doorkeeper/grape/helpers (LoadError)

I have my class in api/api/v2/users.rb

I'm trying to use it with rails 4.2.0, doorkeeper 2.1.3, grape 0.11.0 and grape-roar 0.3.0

Do you have any suggestions how I can make it work?

Edit: I post it here because I think it is related and I'm trying to use it in practice. Sorry, if I'm wrong!

@tute
Copy link
Contributor

tute commented Mar 24, 2015

@RinkeRiezebos you are doing it right, I didn't yet release 2.1.4 with Grape helpers. Can you point your Gemfile to GitHub's master? It should work fine.

@RinkeRiezebos
Copy link
Contributor

@tute Thanks for the quick reply! I pointed the gem to master (gem 'doorkeeper', git: 'https://github.com/doorkeeper-gem/doorkeeper.git') and indeed I don't get the same method anymore.

However, I now get a undefined method 'doorkeeper_authorize!' for #<Grape::Endpoint::0x007f8a2d880978> I guess that's because the helpers aren't released yet? I will wait until v2.1.4. Thanks again!

@tute
Copy link
Contributor

tute commented Mar 24, 2015

Now it's probably @hobofan's territory. When either gets an app working with this, can you please post how you did it in the wiki?
Thanks so much!

@RinkeRiezebos
Copy link
Contributor

yes! Thanks!

@hobofan
Copy link
Contributor Author

hobofan commented Mar 24, 2015

@RinkeRiezebos requiring the helpers isn't enough, you have to tell grape to also load the helpers from the module. You can do this with helpers Doorkeeper::Grape::Helpers, so your code should look like this:

require 'doorkeeper/grape/helpers'

module API
    module V1
        class Users < Grape::API
            helpers Doorkeeper::Grape::Helpers

            before do
                 doorkeeper_authorize!
            end

             ...
        end
    end
end

I added this code snippet to the wiki.

@RinkeRiezebos
Copy link
Contributor

@hobofan Awesome, it works! Thanks! Think I had it at some point (but then removed it by accident?)

RinkeRiezebos added a commit to RinkeRiezebos/doorkeeper that referenced this pull request Mar 24, 2015
Added description how to Protect your API with OAuth when using Grape after PR567 doorkeeper-gem#567 (comment)
@RinkeRiezebos RinkeRiezebos mentioned this pull request Mar 24, 2015
tute pushed a commit that referenced this pull request Mar 25, 2015
Added description how to Protect your API with OAuth when using Grape
after PR 567.

#567 (comment)
tute pushed a commit that referenced this pull request Mar 25, 2015
Added description how to Protect your API with OAuth when using Grape
after PR 567.

#567 (comment)

[ci skip]
tute pushed a commit that referenced this pull request Mar 25, 2015
Added description how to Protect your API with OAuth when using Grape
after PR 567.

#567 (comment)
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.

6 participants