Skip to content

Loading…

Only enable specific grant flows. #295

Merged
merged 5 commits into from

6 participants

@chitsaou

I'd like to disable some unused grant flows in my API design, such as Resource Owner Password Credentials Grant and Client Credentials Grant. I found there is no way to disable it, so I implemented it.

In practice, the Authorization Endpoint and Token Endpoint would check for available response / grant types (the "strategies") according to the grant flows enabled. For example, if :implicit is not enabled, then Authorization Endpoint will return "Unsupported Response Type" error when response_type is set to token.

By default it enables all the four grant flows. I've also modified initializer file:

  # Change what grant flows are enabled
  # By default it enables all the four grant flows. Remove any of them from
  # the array to disable.
  #
  # grant_flows [
  #               :authorization_code,
  #               :implicit,
  #               :password_credentials,
  #               :client_credentials
  #             ]

Notes:

  1. I don't know whether the commit chitsaou/doorkeeper@110c0d0 is necessary, since the "Unsupported Response Type" is filtered in Doorkeeper::Request. I can remove this commit if you think this is not necessary.
  2. Instead of RSpec tests, I've tested this feature with my demo app, and it seems work.
  3. How do you think about the config values in my commit? In fact I feel that :resource_owner_password_credentials is too long. Update: changed to :password_credentials thanks to advise from @simonbnrd!
  4. Token refreshing on Token Endpoint is currently always-enabled. I think it would make more sense if it is enabled only when use_refresh_token is enabled.
  5. server_spec.rb cannot be run standalone, it would raise some exceptions. I instead tested this spec with rake spec.

Update

According to discussions below, the grant_flows setting is now in array of strings:

grant_flows %w(authorization_code implicit password client_credentials)
@chitsaou

@tute Hi, sorry for pinging you here, but I'd like to know how this PR will be? Is it acceptable? Do I need to refactor it? Thanks. :)

@tute
doorkeeper rubygem member

@chitsaou, thank you very much for your work and your poke. I've been on vacations and hanging around Github only for quick comments, your PR deserves more time that I expect to give next week!
Till next, thank you again,
Tute.

@chitsaou

@tute oh I see, sorry for the interruption. Enjoy your vacation :)

@kevintom

I was thinking of something similar but on the oauth_applications level,

having a list of supported grant_types column on the applications table (default :all ?)

and then doing the check against the applications supported list

not sure if this address the same need
(a little more versatile for me, since i'd like to permit password grant type for my iOS app and android only and have every other api consumer use the authorization_code grant type)

@birarda

This would be a very useful feature to have. Is it against the OAuth spec to disable certain grant types for an application?

@chitsaou

No, it does not against the spec. Most websites implement only one or two of them. For example, Authorization Code grant type is the only implemented type for most website, and Twitter does only implement Client Credentials grant type.

I've summarized a list of OAuth 2 implementation differences among popular websites on my blog, and you can see that none of them implement all the 4 built-in grant types. Sorry it's in Chinese; here are some brief translations: 自製 = self-made (non-OAuth 2); 半自製 = partially self-made; 無 scope = scope not implemented; spec 相容 = compatible with spec; client 認證 = client authentication

@sbnnrd

Really interesting @chitsaou ! / ping @tute :smile:

This is also something I was looking for. Now that grant flows such as Resource Owner Password Credentials Grant do not require a client_id and a secret (thanks to OAuth2 spec !), I think that we should be able to disable these grant-types if we want to.

@sbnnrd sbnnrd commented on an outdated diff
spec/lib/config_spec.rb
((58 lines not shown))
+ end
+
+ it "includes ':password' in token_grant_types" do
+ subject.token_grant_types.should include :password
+ end
+ end
+
+ context "when including ':client_credentials'" do
+ before do
+ Doorkeeper.configure {
+ orm DOORKEEPER_ORM
+ grant_flows [ :client_credentials ]
+ }
+ end
+
+ it "includes ':password' in token_grant_types" do
@sbnnrd
sbnnrd added a note

should be :client_credentials instead of :password

Fixed. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@sbnnrd sbnnrd commented on an outdated diff
lib/generators/doorkeeper/templates/initializer.rb
@@ -58,6 +58,17 @@
#
# test_redirect_uri 'urn:ietf:wg:oauth:2.0:oob'
+ # Change what grant flows are enabled
+ # By default it enables all the four grant flows. Remove any of them from
+ # the array to disable.
+ #
+ # grant_flows [
+ # :authorization_code,
+ # :implicit,
+ # :resource_owner_password_credentials,
@sbnnrd
sbnnrd added a note

if resource_owner_password_credentials is too long, maybe that password_credentials could be enough ?

Good idea. I've changed to password_credentials. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@chitsaou

I've left the master branch too far. Maybe I should rebase my changes onto the current version first?

@sbnnrd

It may help yes.

@chitsaou

OK, I'll rebase onto master later today (living in UTC+8)

@chitsaou

I found it is hard to rebase because I don't know how to fix unit tests :(

@houndci houndci commented on an outdated diff
lib/doorkeeper/config.rb
((12 lines not shown))
+
+ private
+
+ def calculate_authorization_response_types(flows)
+ response_types = []
+ response_types << :code if flows.include? :authorization_code
+ response_types << :token if flows.include? :implicit
+
+ response_types
+ end
+
+ def calculate_token_grant_types(flows)
+ response_types = []
+
+ response_types << :authorization_code if flows.include? :authorization_code
+ response_types << :password if flows.include? :password_credentials
@houndci
houndci added a note

Line is too long. [83/80]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@houndci houndci commented on an outdated diff
spec/lib/config_spec.rb
((41 lines not shown))
+ Doorkeeper.configure {
+ orm DOORKEEPER_ORM
+ grant_flows [ :implicit ]
+ }
+ end
+
+ it "includes ':token' in authorization_response_types" do
+ subject.authorization_response_types.should include :token
+ end
+ end
+
+ context "when including ':password_credentials'" do
+ before do
+ Doorkeeper.configure {
+ orm DOORKEEPER_ORM
+ grant_flows [ :password_credentials ]
@houndci
houndci added a note

Space inside square brackets detected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tute tute commented on an outdated diff
lib/doorkeeper/config.rb
((16 lines not shown))
+ types = []
+ types << :code if flows.include? :authorization_code
+ types << :token if flows.include? :implicit
+
+ types
+ end
+
+ def calculate_token_grant_types(flows)
+ types = []
+
+ types << :authorization_code if flows.include? :authorization_code
+ types << :password if flows.include? :password_credentials
+ types << :client_credentials if flows.include? :client_credentials
+
+ # FIXME: allow this response type accoring to refresh_token_enabled option
+ types << :refresh_token
@tute doorkeeper rubygem member
tute added a note

Can ask with refresh_token_enabled?.

thanks, will add it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tute tute commented on an outdated diff
lib/doorkeeper/config.rb
((12 lines not shown))
+
+ private
+
+ def calculate_authorization_response_types(flows)
+ types = []
+ types << :code if flows.include? :authorization_code
+ types << :token if flows.include? :implicit
+
+ types
+ end
+
+ def calculate_token_grant_types(flows)
+ types = []
+
+ types << :authorization_code if flows.include? :authorization_code
+ types << :password if flows.include? :password_credentials
@tute doorkeeper rubygem member
tute added a note

If we name password_credentials flow to password, token_grant_types and grant_flows would be one and the same (excepting implicit and refresh token)? That could simplify code.

I should keep it consistent. I'll use :password_credentials instead.

OK, I know why I used :password instead of :password_credentials

Because in the OAuth 2 spec, when requesting token, the parameter will be grant_type=password. Therefore I think we should keep :password but add comments on why they're inconsistent.

see http://tools.ietf.org/html/rfc6749#section-4.3.2

@tute doorkeeper rubygem member
tute added a note

I'm ok with the simpler password. It's anyway inside of the grant_flows array, providing the context needed to understand what it is. Anyhow, the best way to know is to see the before/after code, your take.

You're right. It's much easier to understand. I'll take it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tute tute commented on an outdated diff
lib/doorkeeper/oauth/pre_authorization.rb
@@ -39,7 +39,9 @@ def error_response
private
def validate_response_type
- %w[code token].include? response_type
+ # FIXME: the checking of strategies is done in Doorkeeper::Request.
+ # Is it proper to check the same logic here?
+ server.authorization_response_types.include? response_type.try(:to_sym)
@tute doorkeeper rubygem member
tute added a note
  1. Yes, let's check in both places (and remove the FIXME).
  2. Please change symbols to strings to avoid memory leaks: server.authorization_response_types.map(&:to_s).include? response_type.try(:to_s)

Thanks, will fix it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tute tute commented on an outdated diff
lib/doorkeeper/config.rb
@@ -170,6 +170,13 @@ def extended(base)
option :active_record_options, :default => {}
option :realm, :default => "Doorkeeper"
option :wildcard_redirect_uri, :default => false
+ option :grant_flows,
@tute doorkeeper rubygem member
tute added a note

I'd like this to read: default: %w(authorization_code implicit password client_credentials). That way they are strings and we avoid converting parameters to symbols (happens more than once in this PR). That allows memory leaks, and thus DoS attacks.

Also, array looks more like token_grant_types, and code can be simplified (see password change).

@tute doorkeeper rubygem member
tute added a note

Just read you'll use password_credentials instead of password, whichever as long as it's consistent! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tute tute commented on an outdated diff
lib/doorkeeper/request.rb
((13 lines not shown))
rescue NameError
raise Errors::InvalidTokenStrategy
end
def get_strategy(strategy, available)
raise Errors::MissingRequestStrategy unless strategy.present?
- raise NameError unless available.include?(strategy.to_s)
+ raise NameError unless available.include?(strategy.to_sym)
@tute doorkeeper rubygem member
tute added a note

Favor string over symbol.

Oh, that's my bad. Will fix it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tute tute commented on an outdated diff
lib/generators/doorkeeper/templates/initializer.rb
@@ -59,6 +59,17 @@
#
# test_redirect_uri 'urn:ietf:wg:oauth:2.0:oob'
+ # Change what grant flows are enabled
+ # By default it enables all the four grant flows. Remove any of them from
+ # the array to disable.
+ #
+ # grant_flows [
+ # :authorization_code,
+ # :implicit,
+ # :password_credentials,
+ # :client_credentials
+ # ]
@tute doorkeeper rubygem member
tute added a note

If we go with using strings in this option, comment should reflect that.

I feel the syntax you proposed is better:

%w(authorization_code implicit password_credentials client_credentials)

and we can avoid all the potential symbol DoS's

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tute
doorkeeper rubygem member

Good work, thank you! Sorry I delayed so much for the review, looking good!

@chitsaou

No problem! I'm correcting my code according to your advise. Actually didn't know those symbol DoS issues at that time until recently :( Sorry for that.

@chitsaou chitsaou commented on an outdated diff
spec/lib/config_spec.rb
@@ -154,7 +171,7 @@
describe "wildcard_redirect_uri" do
it "is disabled by default" do
- Doorkeeper.configuration.wildcard_redirect_uri.should be_false
+ expect(Doorkeeper.configuration.wildcard_redirect_uri).to be_false

Accidentally changed this line. Will revert.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@houndci houndci commented on an outdated diff
spec/requests/endpoints/token_spec.rb
((4 lines not shown))
+ scenario 'returns unsupported_grant_type for disabled grant flows' do
+ config_is_set(:grant_flows, [:implicit])
+ post token_endpoint_url(:code => @authorization.token, :client => @client, :grant_type => 'authorization_code')
+
+ should_not_have_json 'access_token'
+ should_have_json 'error', 'unsupported_grant_type'
+ should_have_json 'error_description', translated_error_message('unsupported_grant_type')
+ end
+
+ context 'refresh_token is not in use' do
+ scenario 'returns unsupported_grant_type for disabled grant flows' do
+ post token_endpoint_url(:code => @authorization.token, :client => @client, :grant_type => 'refresh_token')
+
+ should_not_have_json 'access_token'
+ should_have_json 'error', 'unsupported_grant_type'
+ should_have_json 'error_description', translated_error_message('unsupported_grant_type')
@houndci
houndci added a note

Line is too long. [94/80]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@houndci houndci commented on an outdated diff
spec/requests/endpoints/token_spec.rb
@@ -36,6 +36,25 @@
should_have_json 'error_description', translated_error_message('unsupported_grant_type')
end
+ scenario 'returns unsupported_grant_type for disabled grant flows' do
+ config_is_set(:grant_flows, [:implicit])
+ post token_endpoint_url(:code => @authorization.token, :client => @client, :grant_type => 'authorization_code')
+
+ should_not_have_json 'access_token'
+ should_have_json 'error', 'unsupported_grant_type'
+ should_have_json 'error_description', translated_error_message('unsupported_grant_type')
+ end
+
+ context 'refresh_token is not in use' do
+ scenario 'returns unsupported_grant_type for disabled grant flows' do
+ post token_endpoint_url(:code => @authorization.token, :client => @client, :grant_type => 'refresh_token')
@houndci
houndci added a note

Use the new Ruby 1.9 hash syntax.
Line is too long. [112/80]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@chitsaou
  • All parameters are now in strings instead of symbols.
  • Use a simpler term password for Resource Owner Password Credentials Grant Flow
  • Improved notes about grant_flows command in initializer template.
  • When requesting for token refresh but Doorkeeper's refresh token is not enabled, the server returns unsupported_grant_type error.

I think it's good to merge. Feel free to tell me if there is anything to improve.

@chitsaou chitsaou referenced this pull request
Closed

Block Flows #238

@tute tute commented on an outdated diff
lib/doorkeeper/config.rb
((31 lines not shown))
+ # for 'grant_type' param in OAuth 2, and return them in array.
+ #
+ # If refresh_token_enabled? is enabled, it also returns 'refresh_token'.
+ #
+ def calculate_token_grant_types(flows)
+ types = []
+
+ # Left: OAuth 2 grant_type param
+ # Right: Flows enabled in configuration.grant_flows
+ types << 'authorization_code' if flows.include? 'authorization_code'
+ types << 'password' if flows.include? 'password'
+ types << 'client_credentials' if flows.include? 'client_credentials'
+
+ types << 'refresh_token' if refresh_token_enabled?
+
+ types
@tute doorkeeper rubygem member
tute added a note
  • This method's body can be replaced by:
types = flows - ['implicit']
types << 'refresh_token' if refresh_token_enabled?
types
  • We don't need the comment of line 243 (unless we link to the four related parts of the spec, your take).
  • We don't need the flows parameter, as it's an accessible option in this scope.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tute tute commented on an outdated diff
lib/doorkeeper/oauth/pre_authorization.rb
@@ -39,7 +39,8 @@ def error_response
private
def validate_response_type
- %w[code token].include? response_type
+ # FIXME: the checking of strategies is done in Doorkeeper::Request.
+ server.authorization_response_types.include? response_type.try(:to_s)
@tute doorkeeper rubygem member
tute added a note
  • We don't need the FIXME note.
  • We can always call to_s (no need of try). We probably don't need to_s call either.
@chitsaou
chitsaou added a note

thanks, will fix it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tute tute commented on an outdated diff
lib/doorkeeper/config.rb
((4 lines not shown))
+
+ def authorization_response_types
+ @authorization_response_types ||= calculate_authorization_response_types(grant_flows)
+ end
+
+ def token_grant_types
+ @token_grant_types ||= calculate_token_grant_types(grant_flows)
+ end
+
+ private
+
+ # Given an array of grant flows, determines what values are acceptable
+ # for 'response_type' param in OAuth 2 code request endpoint, and return
+ # them in an array of strings.
+ #
+ def calculate_authorization_response_types(flows)
@tute doorkeeper rubygem member
tute added a note
  • We don't need the flows parameter, as it's an accessible option in this scope.
@chitsaou
chitsaou added a note

Okay, will fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tute tute commented on an outdated diff
lib/doorkeeper/config.rb
((8 lines not shown))
+
+ def token_grant_types
+ @token_grant_types ||= calculate_token_grant_types(grant_flows)
+ end
+
+ private
+
+ # Given an array of grant flows, determines what values are acceptable
+ # for 'response_type' param in OAuth 2 code request endpoint, and return
+ # them in an array of strings.
+ #
+ def calculate_authorization_response_types(flows)
+ types = []
+
+ # Left: OAuth 2 response_type param
+ # Right: Flows enabled in configuration.grant_flows
@tute doorkeeper rubygem member
tute added a note

We don't need these comments (nor the new lines) in this method.

@chitsaou
chitsaou added a note

Ok, will remove them. As long as there are comments before these two methods indicating what the inputs and outputs are, these in-body comments are not necessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tute tute commented on an outdated diff
spec/lib/oauth/pre_authorization_spec.rb
@@ -2,7 +2,15 @@
module Doorkeeper::OAuth
describe PreAuthorization do
- let(:server) { double :server, :default_scopes => Scopes.new, :scopes => Scopes.from_string('public') }
+ def make_server
@tute doorkeeper rubygem member
tute added a note

We don't need line 12 calling this method if we do here let(:server) do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tute tute commented on an outdated diff
spec/lib/oauth/pre_authorization_spec.rb
((17 lines not shown))
+ let(:server) do
+ server = make_server
+ server.stub(:grant_flows) { [:implicit] }
+ server
+ end
+
+ it 'does not accept "code" as response type' do
+ subject.response_type = 'code'
+ expect(subject).not_to be_authorizable
+ end
+ end
+
+ context 'when implicit grant flow is disabled' do
+ let(:server) do
+ server = make_server
+ server.stub(:grant_flows) { [:authorization_code] }
@tute doorkeeper rubygem member
tute added a note

Do we need to stub? Now that server is not a double, can't we just assign the different configurations?

@chitsaou
chitsaou added a note

I found it hard to change grant_flows configuration. Could you give me some examples?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tute tute commented on an outdated diff
spec/lib/server_spec.rb
@@ -19,6 +19,20 @@
expect { subject.token_request(:code) }.to raise_error(Doorkeeper::Errors::InvalidTokenStrategy)
end
+ context 'when only Authorization Code strategy is enabled' do
+ before do
+ Doorkeeper.configuration.stub(:grant_flows) { [:authorization_code] }
+ end
+
+ it 'raises error when using the disabled Implicit strategy' do
+ expect { subject.authorization_request(:token) }.to raise_error(Doorkeeper::Errors::InvalidAuthorizationStrategy)
@tute doorkeeper rubygem member
tute added a note

Let's use blocks to make it more readable:

expect do
  subject.authorization_request(:token)
end.to # ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tute tute commented on an outdated diff
spec/lib/server_spec.rb
@@ -19,6 +19,20 @@
expect { subject.token_request(:code) }.to raise_error(Doorkeeper::Errors::InvalidTokenStrategy)
end
+ context 'when only Authorization Code strategy is enabled' do
+ before do
+ Doorkeeper.configuration.stub(:grant_flows) { [:authorization_code] }
+ end
+
+ it 'raises error when using the disabled Implicit strategy' do
+ expect { subject.authorization_request(:token) }.to raise_error(Doorkeeper::Errors::InvalidAuthorizationStrategy)
+ end
+
+ it 'raises error when using the disabled Client Credentials strategy' do
+ expect { subject.token_request(:client_credentials) }.to raise_error(Doorkeeper::Errors::InvalidTokenStrategy)
@tute doorkeeper rubygem member
tute added a note

Let's use blocks to make it more readable:

expect do
  subject.token_request(:client_credentials)
end.to # ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@houndci houndci commented on an outdated diff
spec/lib/oauth/pre_authorization_spec.rb
@@ -2,7 +2,13 @@
module Doorkeeper::OAuth
describe PreAuthorization do
- let(:server) { double :server, :default_scopes => Scopes.new, :scopes => Scopes.from_string('public') }
+ let(:server) {
@houndci
houndci added a note

Avoid using {...} for multi-line blocks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tute tute commented on the diff
spec/lib/config_spec.rb
@@ -86,6 +90,19 @@
}
expect(subject.refresh_token_enabled?).to be_true
end
+
+ context "is enabled" do
+ before do
+ Doorkeeper.configure {
+ orm DOORKEEPER_ORM
+ use_refresh_token
+ }
@tute doorkeeper rubygem member
tute added a note

No need of orm here, can be a one liner: Doorkeeper.configure { use_refresh_token }.

@chitsaou
chitsaou added a note

fixed, thanks!

@chitsaou
chitsaou added a note

In the later discussions we found that, without orm then the tests won't pass for Mongoid ORM. That's why we have to declare orm here. To test with Mongoid use orm=mongoid{2,3,4} rake spec.

(Just want to add notes for future people to make sure that they get this correct.)

@tute doorkeeper rubygem member
tute added a note

Some specs configure it in a before filter, which is smarter and avoids this kind of confusion.

@tute doorkeeper rubygem member
tute added a note

(Some code does it and some not.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tute
doorkeeper rubygem member

If you can rebase/squash your commits history that'd be amazing. I think I'll merge it tomorrow!

@chitsaou

That'll take some time, but I'll try. Anyway thanks for your advises!

@chitsaou

Rebasing done and it seems much better now!

@tute
doorkeeper rubygem member

Just retriggered Travis tests, as they were failing. Do they pass on your copy? Did you test with mongoid?

@chitsaou

Yes, all rake spec tests passes on my copy. How do I test it with mongoid? I think it is covered by RSpec tests.

@chitsaou

It seems that the mongoid case was not passing due to missing Ruby 2.1 binary: https://travis-ci.org/doorkeeper-gem/doorkeeper/jobs/24195759

Maybe it could be fixed with Travis CI configuration?

@tute
doorkeeper rubygem member

Current master has that fixed, want to rebase on top of it? Thanks so much! :+1:

@chitsaou

I see these tests are also broken:

https://travis-ci.org/doorkeeper-gem/doorkeeper/jobs/24195746

Anyway I'll rebase on master and trigger Travis CI again.

@tute
doorkeeper rubygem member

Those are actually the weird ones.

@chitsaou

Rebased & force-pushed. Waiting for Travis CI.

@tute
doorkeeper rubygem member

It's unmergeable, your master branch is out of sync. Do something like:

git remote add upstream https://github.com/doorkeeper-gem/doorkeeper.git
git fetch upstream
git checkout config-grant-flows
git rebase upstream/master
# Fix conflicts, then `git rebase --continue`, repeat until done
git push -f
@chitsaou

Oh I was wrong. I did not rebase on the correct master branch. Will re-rebase.

@houndci houndci commented on an outdated diff
spec/requests/endpoints/token_spec.rb
@@ -36,6 +36,23 @@
should_have_json 'error_description', translated_error_message('unsupported_grant_type')
end
+ scenario 'returns unsupported_grant_type for disabled grant flows' do
+ config_is_set(:grant_flows, [:implicit])
+ post token_endpoint_url(:code => @authorization.token, :client => @client, :grant_type => 'authorization_code')
+
+ should_not_have_json 'access_token'
+ should_have_json 'error', 'unsupported_grant_type'
+ should_have_json 'error_description', translated_error_message('unsupported_grant_type')
+ end
+
+ scenario 'returns unsupported_grant_type when refresh_token is not in use' do
+ post token_endpoint_url(:code => @authorization.token, :client => @client, :grant_type => 'refresh_token')
@houndci
houndci added a note

Use the new Ruby 1.9 hash syntax.
Line is too long. [110/80]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@houndci houndci commented on an outdated diff
spec/requests/endpoints/token_spec.rb
@@ -36,6 +36,23 @@
should_have_json 'error_description', translated_error_message('unsupported_grant_type')
end
+ scenario 'returns unsupported_grant_type for disabled grant flows' do
+ config_is_set(:grant_flows, [:implicit])
+ post token_endpoint_url(:code => @authorization.token, :client => @client, :grant_type => 'authorization_code')
+
+ should_not_have_json 'access_token'
+ should_have_json 'error', 'unsupported_grant_type'
+ should_have_json 'error_description', translated_error_message('unsupported_grant_type')
+ end
+
+ scenario 'returns unsupported_grant_type when refresh_token is not in use' do
+ post token_endpoint_url(:code => @authorization.token, :client => @client, :grant_type => 'refresh_token')
+
+ should_not_have_json 'access_token'
+ should_have_json 'error', 'unsupported_grant_type'
+ should_have_json 'error_description', translated_error_message('unsupported_grant_type')
@houndci
houndci added a note

Line is too long. [92/80]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tute
doorkeeper rubygem member

Cool, thank you! Will do a last review, and merge upon green CI.

@chitsaou

For the last review:

  1. I've improved (?) the description of grant_flows in initializer.rb, hopefully to be much readable for everyone.
  2. In calculate_token_grant_types it now simply removes 'implicit' from the original array. What if users (developers) set an array like 'refresh_token' or other things that Doorkeeper does not understand? I once thought about this kind of code:

      types = grant_flows & %w(authorization_code password client_credentials)
    

    to keep only those valid grant_type values.

@tute
doorkeeper rubygem member
  1. Perfect!
  2. We could be nice and raise an error if there's an unexpected value/object in that config (fail fast, and avoid typos driving programmers crazy). If you'd like you can add a check and a nice error message, not necessary though I think for this case (people will just delete the configs they don't want and it will be ok).
@chitsaou

Okay. As long as we have default values in initializer.rb, we can expect that people will remove what they don't need and reference to valid strings in the comments, instead of manually typing.

@tute
doorkeeper rubygem member
@chitsaou

Got it. Will dig into this.

@tute
doorkeeper rubygem member

Probably we do need orm DOORKEEPER_ORM. Sorry, it seems tests need it, I made you take them out.

@tute
doorkeeper rubygem member

For ActiveRecord they pass because that's default, and when trying mongoid it fails. That will do the trick! Sorry again, my mistake.

@chitsaou

Oh, no problem, and I will not blame on you :smile:

I think we need a way to run tests locally using Mongoid.

@tute
doorkeeper rubygem member

There is, we have to install and run mongoid, and define the ORM env variable. See https://github.com/doorkeeper-gem/doorkeeper/blob/master/.travis.yml.

@chitsaou

Thanks, I'll run test through all mongo adapters and make sure that they pass.

@houndci houndci commented on an outdated diff
spec/requests/endpoints/token_spec.rb
@@ -36,6 +36,23 @@
should_have_json 'error_description', translated_error_message('unsupported_grant_type')
end
+ scenario 'returns unsupported_grant_type for disabled grant flows' do
+ config_is_set(:grant_flows, [:implicit])
+ post token_endpoint_url(:code => @authorization.token, :client => @client, :grant_type => 'authorization_code')
+
+ should_not_have_json 'access_token'
+ should_have_json 'error', 'unsupported_grant_type'
+ should_have_json 'error_description', translated_error_message('unsupported_grant_type')
+ end
+
+ scenario 'returns unsupported_grant_type when refresh_token is not in use' do
+ post token_endpoint_url(:code => @authorization.token, :client => @client, :grant_type => 'refresh_token')
@houndci
houndci added a note

Use the new Ruby 1.9 hash syntax.
Line is too long. [110/80]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@houndci houndci commented on an outdated diff
spec/requests/endpoints/token_spec.rb
@@ -36,6 +36,23 @@
should_have_json 'error_description', translated_error_message('unsupported_grant_type')
end
+ scenario 'returns unsupported_grant_type for disabled grant flows' do
+ config_is_set(:grant_flows, [:implicit])
+ post token_endpoint_url(:code => @authorization.token, :client => @client, :grant_type => 'authorization_code')
+
+ should_not_have_json 'access_token'
+ should_have_json 'error', 'unsupported_grant_type'
+ should_have_json 'error_description', translated_error_message('unsupported_grant_type')
@houndci
houndci added a note

Line is too long. [92/80]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@houndci houndci commented on an outdated diff
spec/requests/endpoints/token_spec.rb
@@ -36,6 +36,23 @@
should_have_json 'error_description', translated_error_message('unsupported_grant_type')
end
+ scenario 'returns unsupported_grant_type for disabled grant flows' do
+ config_is_set(:grant_flows, [:implicit])
+ post token_endpoint_url(:code => @authorization.token, :client => @client, :grant_type => 'authorization_code')
+
+ should_not_have_json 'access_token'
+ should_have_json 'error', 'unsupported_grant_type'
+ should_have_json 'error_description', translated_error_message('unsupported_grant_type')
+ end
+
+ scenario 'returns unsupported_grant_type when refresh_token is not in use' do
+ post token_endpoint_url(:code => @authorization.token, :client => @client, :grant_type => 'refresh_token')
+
+ should_not_have_json 'access_token'
+ should_have_json 'error', 'unsupported_grant_type'
+ should_have_json 'error_description', translated_error_message('unsupported_grant_type')
@houndci
houndci added a note

Line is too long. [92/80]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@chitsaou

Fixed. Waiting for Travis CI :p

@tute
doorkeeper rubygem member

Well, just merged a big Pull Request. Rebase needed, sorry to bother, and thank you for being so responsive!

@chitsaou

No problem. I think I can do another rebase again.

@chitsaou

Some test are failed. I'll fix them and push again.

@houndci houndci commented on the diff
spec/requests/endpoints/token_spec.rb
@@ -37,6 +37,23 @@
should_have_json 'error_description', translated_error_message('unsupported_grant_type')
end
+ scenario 'returns unsupported_grant_type for disabled grant flows' do
+ config_is_set(:grant_flows, ['implicit'])
+ post token_endpoint_url(code: @authorization.token, client: @client, grant_type: 'authorization_code')
+
+ should_not_have_json 'access_token'
+ should_have_json 'error', 'unsupported_grant_type'
+ should_have_json 'error_description', translated_error_message('unsupported_grant_type')
@houndci
houndci added a note

Line is too long. [92/80]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@houndci houndci commented on the diff
spec/requests/endpoints/token_spec.rb
@@ -37,6 +37,23 @@
should_have_json 'error_description', translated_error_message('unsupported_grant_type')
end
+ scenario 'returns unsupported_grant_type for disabled grant flows' do
+ config_is_set(:grant_flows, ['implicit'])
+ post token_endpoint_url(code: @authorization.token, client: @client, grant_type: 'authorization_code')
+
+ should_not_have_json 'access_token'
+ should_have_json 'error', 'unsupported_grant_type'
+ should_have_json 'error_description', translated_error_message('unsupported_grant_type')
+ end
+
+ scenario 'returns unsupported_grant_type when refresh_token is not in use' do
+ post token_endpoint_url(code: @authorization.token, client: @client, grant_type: 'refresh_token')
@houndci
houndci added a note

Line is too long. [101/80]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@houndci houndci commented on the diff
spec/requests/endpoints/token_spec.rb
@@ -37,6 +37,23 @@
should_have_json 'error_description', translated_error_message('unsupported_grant_type')
end
+ scenario 'returns unsupported_grant_type for disabled grant flows' do
+ config_is_set(:grant_flows, ['implicit'])
+ post token_endpoint_url(code: @authorization.token, client: @client, grant_type: 'authorization_code')
+
+ should_not_have_json 'access_token'
+ should_have_json 'error', 'unsupported_grant_type'
+ should_have_json 'error_description', translated_error_message('unsupported_grant_type')
+ end
+
+ scenario 'returns unsupported_grant_type when refresh_token is not in use' do
+ post token_endpoint_url(code: @authorization.token, client: @client, grant_type: 'refresh_token')
+
+ should_not_have_json 'access_token'
+ should_have_json 'error', 'unsupported_grant_type'
+ should_have_json 'error_description', translated_error_message('unsupported_grant_type')
@houndci
houndci added a note

Line is too long. [92/80]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@chitsaou

Done rebasing! I've also converted hash to 1.9 style.

By the way according to test cases, it seems that if specifying grant_flows in symbols (i.e. %i(implicit password)) the grant flow restriction will also work.

@chitsaou

Previously the tests are setting grant_flows in array of symbols. I've corrected them to array of strings, but the tests pass in both cases.

@tute tute merged commit 0ce32ff into doorkeeper-gem:master

1 check passed

Details continuous-integration/travis-ci The Travis CI build passed
@tute
doorkeeper rubygem member

Thank you! :heart:

@chitsaou

:+1: Thanks a lot!

@sbnnrd

Good job @chitsaou and @tute !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 2, 2014
  1. @chitsaou

    add configuration "grant_flows"

    chitsaou committed
    defaults to all grant flows + 'refresh_token'
  2. @chitsaou
  3. @chitsaou
  4. @chitsaou

    completely turn off disabled strategies on token endpoint (see note)

    chitsaou committed
    Note: I'm not sure whether this is necessary, as there is already a
    validation in Doorkeeper::Request.
  5. @chitsaou

    test that refresh_token request returns unsupported_grant_type error …

    chitsaou committed
    …when it is not enabled explicitly.
View
31 lib/doorkeeper/config.rb
@@ -168,6 +168,8 @@ def extended(base)
option :active_record_options, default: {}
option :realm, default: 'Doorkeeper'
option :wildcard_redirect_uri, default: false
+ option :grant_flows,
+ default: %w(authorization_code implicit password client_credentials)
def refresh_token_enabled?
!!@refresh_token_enabled
@@ -208,5 +210,34 @@ def access_token_methods
def realm
@realm ||= 'Doorkeeper'
end
+
+ def authorization_response_types
+ @authorization_response_types ||= calculate_authorization_response_types
+ end
+
+ def token_grant_types
+ @token_grant_types ||= calculate_token_grant_types
+ end
+
+ private
+
+ # Determines what values are acceptable for 'response_type' param in
+ # authorization request endpoint, and return them as an array of strings.
+ #
+ def calculate_authorization_response_types
+ types = []
+ types << 'code' if grant_flows.include? 'authorization_code'
+ types << 'token' if grant_flows.include? 'implicit'
+ types
+ end
+
+ # Determines what values are acceptable for 'grant_type' param token
+ # request endpoint, and return them in array.
+ #
+ def calculate_token_grant_types
+ types = grant_flows - ['implicit']
+ types << 'refresh_token' if refresh_token_enabled?
+ types
+ end
end
end
View
2 lib/doorkeeper/oauth/pre_authorization.rb
@@ -39,7 +39,7 @@ def error_response
private
def validate_response_type
- %w(code token).include? response_type
+ server.authorization_response_types.include? response_type
end
def validate_client
View
4 lib/doorkeeper/request.rb
@@ -10,13 +10,13 @@ module Request
module_function
def authorization_strategy(strategy)
- get_strategy strategy, %w(code token)
+ get_strategy strategy, Doorkeeper.configuration.authorization_response_types
rescue NameError
raise Errors::InvalidAuthorizationStrategy
end
def token_strategy(strategy)
- get_strategy strategy, %w(password client_credentials authorization_code refresh_token)
+ get_strategy strategy, Doorkeeper.configuration.token_grant_types
rescue NameError
raise Errors::InvalidTokenStrategy
end
View
12 lib/generators/doorkeeper/templates/initializer.rb
@@ -59,6 +59,18 @@
#
# test_redirect_uri 'urn:ietf:wg:oauth:2.0:oob'
+ # Specify what grant flows are enabled in array of Strings. The valid
+ # strings and the flows they enable are:
+ #
+ # "authorization_code" => Authorization Code Grant Flow
+ # "implicit" => Implicit Grant Flow
+ # "password" => Resource Owner Password Credentials Grant Flow
+ # "client_credentials" => Client Credentials Grant Flow
+ #
+ # If not specified, Doorkeeper enables all the four grant flows.
+ #
+ # grant_flows %w(authorization_code implicit password client_credentials)
+
# Under some circumstances you might want to have applications auto-approved,
# so that the user skips the authorization step.
# For example if dealing with trusted a application.
View
92 spec/lib/config_spec.rb
@@ -86,6 +86,23 @@
end
expect(subject.refresh_token_enabled?).to be_true
end
+
+ it "does not includes 'refresh_token' in authorization_response_types" do
+ expect(subject.token_grant_types).not_to include 'refresh_token'
+ end
+
+ context "is enabled" do
+ before do
+ Doorkeeper.configure {
+ orm DOORKEEPER_ORM
+ use_refresh_token
+ }
@tute doorkeeper rubygem member
tute added a note

No need of orm here, can be a one liner: Doorkeeper.configure { use_refresh_token }.

@chitsaou
chitsaou added a note

fixed, thanks!

@chitsaou
chitsaou added a note

In the later discussions we found that, without orm then the tests won't pass for Mongoid ORM. That's why we have to declare orm here. To test with Mongoid use orm=mongoid{2,3,4} rake spec.

(Just want to add notes for future people to make sure that they get this correct.)

@tute doorkeeper rubygem member
tute added a note

Some specs configure it in a before filter, which is smarter and avoids this kind of confusion.

@tute doorkeeper rubygem member
tute added a note

(Some code does it and some not.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ end
+
+ it "includes 'refresh_token' in authorization_response_types" do
+ expect(subject.token_grant_types).to include 'refresh_token'
+ end
+ end
end
describe 'client_credentials' do
@@ -172,6 +189,81 @@
end
end
+ describe "grant_flows" do
+ it "is set to all grant flows by default" do
+ expect(Doorkeeper.configuration.grant_flows).to eq [
+ 'authorization_code',
+ 'implicit',
+ 'password',
+ 'client_credentials'
+ ]
+ end
+
+ it "can change the value" do
+ Doorkeeper.configure {
+ orm DOORKEEPER_ORM
+ grant_flows [ 'authorization_code', 'implicit' ]
+ }
+ expect(subject.grant_flows).to eq ['authorization_code', 'implicit']
+ end
+
+ context "when including 'authorization_code'" do
+ before do
+ Doorkeeper.configure {
+ orm DOORKEEPER_ORM
+ grant_flows ['authorization_code']
+ }
+ end
+
+ it "includes 'code' in authorization_response_types" do
+ expect(subject.authorization_response_types).to include 'code'
+ end
+
+ it "includes 'authorization_code' in token_grant_types" do
+ expect(subject.token_grant_types).to include 'authorization_code'
+ end
+ end
+
+ context "when including 'implicit'" do
+ before do
+ Doorkeeper.configure {
+ orm DOORKEEPER_ORM
+ grant_flows ['implicit']
+ }
+ end
+
+ it "includes 'token' in authorization_response_types" do
+ expect(subject.authorization_response_types).to include 'token'
+ end
+ end
+
+ context "when including 'password'" do
+ before do
+ Doorkeeper.configure {
+ orm DOORKEEPER_ORM
+ grant_flows ['password']
+ }
+ end
+
+ it "includes 'password' in token_grant_types" do
+ expect(subject.token_grant_types).to include 'password'
+ end
+ end
+
+ context "when including 'client_credentials'" do
+ before do
+ Doorkeeper.configure {
+ orm DOORKEEPER_ORM
+ grant_flows ['client_credentials']
+ }
+ end
+
+ it "includes 'client_credentials' in token_grant_types" do
+ expect(subject.token_grant_types).to include 'client_credentials'
+ end
+ end
+ end
+
it 'raises an exception when configuration is not set' do
old_config = Doorkeeper.configuration
Doorkeeper.module_eval do
View
42 spec/lib/oauth/pre_authorization_spec.rb
@@ -2,7 +2,13 @@
module Doorkeeper::OAuth
describe PreAuthorization do
- let(:server) { double :server, default_scopes: Scopes.new, scopes: Scopes.from_string('public') }
+ let(:server) {
+ server = Doorkeeper.configuration
+ server.stub(:default_scopes) { Scopes.new }
+ server.stub(:scopes) { Scopes.from_string('public') }
+ server
+ }
+
let(:client) { double :client, redirect_uri: 'http://tst.com/auth' }
let :attributes do
@@ -31,6 +37,40 @@ module Doorkeeper::OAuth
expect(subject).to be_authorizable
end
+ context 'when using default grant flows' do
+ it 'accepts "code" as response type' do
+ subject.response_type = 'code'
+ expect(subject).to be_authorizable
+ end
+
+ it 'accepts "token" as response type' do
+ subject.response_type = 'token'
+ expect(subject).to be_authorizable
+ end
+ end
+
+ context 'when authorization code grant flow is disabled' do
+ before do
+ server.stub(:grant_flows) { ['implicit'] }
+ end
+
+ it 'does not accept "code" as response type' do
+ subject.response_type = 'code'
+ expect(subject).not_to be_authorizable
+ end
+ end
+
+ context 'when implicit grant flow is disabled' do
+ before do
+ server.stub(:grant_flows) { ['authorization_code'] }
+ end
+
+ it 'does not accept "token" as response type' do
+ subject.response_type = 'token'
+ expect(subject).not_to be_authorizable
+ end
+ end
+
it 'accepts valid scopes' do
subject.scope = 'public'
expect(subject).to be_authorizable
View
18 spec/lib/server_spec.rb
@@ -23,6 +23,24 @@
end.to raise_error(Doorkeeper::Errors::InvalidTokenStrategy)
end
+ context 'when only Authorization Code strategy is enabled' do
+ before do
+ Doorkeeper.configuration.stub(:grant_flows) { ['authorization_code'] }
+ end
+
+ it 'raises error when using the disabled Implicit strategy' do
+ expect do
+ subject.authorization_request(:token)
+ end.to raise_error(Doorkeeper::Errors::InvalidAuthorizationStrategy)
+ end
+
+ it 'raises error when using the disabled Client Credentials strategy' do
+ expect do
+ subject.token_request(:client_credentials)
+ end.to raise_error(Doorkeeper::Errors::InvalidTokenStrategy)
+ end
+ end
+
it 'builds the request with selected strategy' do
stub_const 'Doorkeeper::Request::Code', fake_class
expect(fake_class).to receive(:build).with(subject)
View
7 spec/requests/endpoints/authorization_spec.rb
@@ -43,5 +43,12 @@
i_should_not_see 'Authorize'
i_should_see_translated_error_message :unsupported_response_type
end
+
+ scenario "displays unsupported_response_type error when using a disabled response type" do
+ config_is_set(:grant_flows, ['implicit'])
+ visit authorization_endpoint_url(client: @client, response_type: 'code')
+ i_should_not_see "Authorize"
+ i_should_see_translated_error_message :unsupported_response_type
+ end
end
end
View
17 spec/requests/endpoints/token_spec.rb
@@ -37,6 +37,23 @@
should_have_json 'error_description', translated_error_message('unsupported_grant_type')
end
+ scenario 'returns unsupported_grant_type for disabled grant flows' do
+ config_is_set(:grant_flows, ['implicit'])
+ post token_endpoint_url(code: @authorization.token, client: @client, grant_type: 'authorization_code')
+
+ should_not_have_json 'access_token'
+ should_have_json 'error', 'unsupported_grant_type'
+ should_have_json 'error_description', translated_error_message('unsupported_grant_type')
@houndci
houndci added a note

Line is too long. [92/80]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ end
+
+ scenario 'returns unsupported_grant_type when refresh_token is not in use' do
+ post token_endpoint_url(code: @authorization.token, client: @client, grant_type: 'refresh_token')
@houndci
houndci added a note

Line is too long. [101/80]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ should_not_have_json 'access_token'
+ should_have_json 'error', 'unsupported_grant_type'
+ should_have_json 'error_description', translated_error_message('unsupported_grant_type')
@houndci
houndci added a note

Line is too long. [92/80]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ end
+
scenario 'returns invalid_request if grant_type is missing' do
post token_endpoint_url(code: @authorization.token, client: @client, grant_type: '')
Something went wrong with that request. Please try again.