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

Client application specific scopes #469

Merged
merged 1 commit into from
Dec 2, 2014

Conversation

cpapazian
Copy link

This PR attempts to address #317. We saw @tute's comments, but could not find a great way to implement this without either putting all of the logic into the authoriizations controller (doing an end run around the work that the OAuth::PreAuthorizations class does, or monkeypatching Doorkeeper a bit. So I created this PR in an attempt to implement this functionality in a way that fits with the rest of the Doorkeeper API.

It works by adding a scopes field to the Doorkeeper::Application model and validating the requested scopes against it during pre-authorization. When enable_application_scopes is set, the Doorkeeper::Models::Scopes mixin is added to Doorkeeper::Application and validate_scopes will check both the server/config scopes as well as the client application's scopes.

Still TODO: adding support to the admin pages and testing with ORM's besides ActiveRecord

In the meantime, I would greatly appreciate some feedback.

thanks,
-chris

class ScopesValidator < ActiveModel::Validator
def validate(record)
return unless record.scopes_string.present?
unless Doorkeeper::OAuth::Helpers::ScopeChecker.valid?(record.scopes_string, Doorkeeper.configuration.scopes)

Choose a reason for hiding this comment

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

Line is too long. [117/80]

Copy link
Contributor

Choose a reason for hiding this comment

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

You can extract the condition to a private method.

@christopherhein
Copy link

I think overall it's an interesting idea (one that I would personally like to use) that being said it doesn't seem like @tute wants to support this in Doorkeeper, which I can understand. I'd be interested to see if there was a way to put it out into a gem? The Admin panel isn't easily plugable from what I've seen, but I may just be doing it wrong.

@sunteya
Copy link

sunteya commented Sep 25, 2014

Nice feature, my project also has the same problem.
In my project, the need for many kind of Application, but each application has a different scope limit for the client credentials flow.

@tute
Copy link
Contributor

tute commented Oct 11, 2014

Changing my mind about this feature. I wish OAuth spec was not so broad. :)

Citing https://tools.ietf.org/html/rfc6749#section-10.3:

The authorization server SHOULD take the client identity into account when choosing how to honor the requested scope.

This implies that this PR should sooner or later land into Doorkeeper. @iangreenleaf your solution in #492 is more general, but in this case, given that section of the spec, we'll actually want to make client-specific scopes a first class citizen.

Thank you all for your work. Will keep this in the roadmap.

@@ -9,6 +9,7 @@ def self.configure(&block)
@config = Config::Builder.new(&block).build
enable_orm
setup_application_owner if @config.enable_application_owner?
setup_application_scopes if @config.enable_application_scopes?
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't need the config, it should always be enabled, as the spec expects this.

Copy link
Contributor

Choose a reason for hiding this comment

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

This file should remain mostly unchanged.

@iangreenleaf
Copy link

One thing that neither my PR nor this PR offers, but that probably should be implemented based on the spec, is the ability to ignore the scopes requested by the client and respond with a different set of scopes. That actually seems to be the preferred way of dealing with this, rather than throwing an error as we have been doing.

@iangreenleaf
Copy link

I'd also still advocate for a more generic solution, based on this part of the spec:

The authorization server MAY fully or partially ignore the scope
requested by the client, based on the authorization server policy or
the resource owner's instructions. If the issued access token scope
is different from the one requested by the client, the authorization
server MUST include the "scope" response parameter to inform the
client of the actual scope granted.

To me, "based on the authorization server policy" casts a pretty wide net, so it would be nice to support scope limits based on a variety of different policies.

@cpapazian
Copy link
Author

excellent. i'll update this PR with your comments as soon as i can.

@cpapazian
Copy link
Author

@iangreenleaf it's pretty easy for the application server to modify the scopes in the authorizations controller without getting too deep. we already do this, even going as far as allowing the user to deny scopes that the client applications request.

other_array = other.present? ? other.all : []
self.class.from_array(self.all & other_array)
end

Choose a reason for hiding this comment

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

Extra empty line detected at body end.

other_array = other.present? ? other.all : []
self.class.from_array(self.all & other_array)
end

Choose a reason for hiding this comment

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

Extra empty line detected at body end.

@@ -38,5 +39,6 @@ def generate_uid
def generate_secret
self.secret ||= UniqueToken.generate
end

Choose a reason for hiding this comment

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

Extra empty line detected at body end.

@tute
Copy link
Contributor

tute commented Nov 15, 2014

Left a few comments, let's also take all Hound comments into account. Thanks for your work!

application
end

let(:client) { double :client, redirect_uri: 'http://tst.com/auth', application: application }

Choose a reason for hiding this comment

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

Line is too long. [98/80]

@tute
Copy link
Contributor

tute commented Nov 19, 2014

This is looking great, thanks for your work! Looking forward to merge it and release doorkeeper 2.

@tute
Copy link
Contributor

tute commented Nov 21, 2014

@cpapazian regarding default behavior, I'll explain what I had in mind, and you please tell what what you have in mind until we solve it the best way.

I think of a transparent "it's there if you need it" feature, where the application level scopes are blank (empty string) in the DB by default. If you decide to use them, in the admin for oauth apps you just define which scopes can an application use. In that case, you enable this functionality.

Let's say I have an existing app, where I don't have this behavior yet (or even a new one which I'm now starting). I run the migrations, and, no matter what I configure as Doorkeeper scopes, the DB starts with blank scopes. I use everything, all works, then I decide to filter out write scope for an app, and enable it only for read. I seed in read as it's scope, and call it done.

The intersection, then, should happen if an application overrides the default empty string. To make this very clear from the code up to the admin interface, we could call the DB attribute allowed_scopes.

The reason I prefer to avoid needing to always set the scopes is that both they have to be in sync with the configuration of Doorkeeper, which may change app per app; and one may forget to seed in correct values.

What do you think of it? Thanks again for your work!

@cpapazian
Copy link
Author

Yup, that all makes sense, and is what I had in mind when I originally set this up as a configuration setting. I'll make the changes to what I have to fit your description.

I I'd like to keep the database attribute as scopes so that I can reuse the mixin that AccessGrant and AccessToken use. I'm also going to remove the default: '' from the schema definition, and use nil to indicate that the client application does not restrict the scopes.

@tute
Copy link
Contributor

tute commented Nov 21, 2014

All great!

I'm also going to remove the default: '' from the schema definition, and use nil to indicate that the client application does not restrict the scopes.

Better an empty string, so we keep the same type everywhere and we avoid NoMethodErrors.

@cpapazian
Copy link
Author

I see what you mean ... Doorkeeper::OAuth::Scopes initializes @scopes as an empty array, and self.from_string(...) converts nil to '', which makes it difficult to deal with nil values.

The issue I am having is that I'd like to differentiate between a client application disallowing all scopes from a client application that does not want to specify scopes (and let the config/server manage that). If you don't think that's important, then it'll be simple to use the empty string to mean that the client application will not restrict scopes. Or maybe you have some thoughts on how to achieve this another way.

@cpapazian
Copy link
Author

BTW, this is what I had in mind with nil application scopes: cpapazian#1

I'm keeping it out of this branch for now, as it required even more changes than expected, but it accomplishes the "it's there if you need it" requirement. Changing the default value of scopes on the access_grants and access_tokens table seems like a good thing, but there was quite a few hoops to jump through to handle nil scopes on application (to be fair, you did warn me). Anyway, curious your thoughts.

@tute
Copy link
Contributor

tute commented Nov 23, 2014

The issue I am having is that I'd like to differentiate between a client application disallowing all scopes from a client application that does not want to specify scopes (and let the config/server manage that).

Why would such application be defined in the provider? Could it perform any OAuth flow, if it rejects any scope?

I think we are fine making empty string mean "accept all".

@@ -0,0 +1,5 @@
class AddScopesToApplication < ActiveRecord::Migration
def change
add_column :oauth_applications, :scopes, :string, default: ''
Copy link
Contributor

Choose a reason for hiding this comment

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

Forgot to mention: let's disallow NULL: null: false.

@tute
Copy link
Contributor

tute commented Nov 23, 2014

I'm keeping it out of this branch for now, as it required even more changes than expected, but it accomplishes the "it's there if you need it" requirement. Changing the default value of scopes on the access_grants and access_tokens table seems like a good thing, but there was quite a few hoops to jump through to handle nil scopes on application (to be fair, you did warn me). Anyway, curious your thoughts.

nil should be avoided as a general rule. See:

@cpapazian
Copy link
Author

Sounds good. I'll make the changes tomorrow.

FWIW, It was pointed out to me (by @yozlet) that If a provider really wants this null scope behavior, they could define a "null" scope that does not grant any interesting permissions.

if client.application.scopes.empty?
Helpers::ScopeChecker.valid?(scope, server.scopes)
else
Helpers::ScopeChecker.valid?(scope, server.scopes & client.application.scopes)

Choose a reason for hiding this comment

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

Line is too long. [88/80]

@tute
Copy link
Contributor

tute commented Nov 27, 2014

Looks great!

  1. Can you pleas add notes to CHANGELOG.md about the addition, and to UPGRADING.md about the migration that needs to be run for the upgrade?
  2. Can you squash the commits together and rebase on top of master?

Soon to merge. Thanks! 👏

@cpapazian
Copy link
Author

Added a line to CHANGELOG.md. Didn't see an UPGRADING.md, so I created one ... assuming you'll move that to the wiki?

Rebased and sqaushed, too.

If there no scopes (empty string) defined in the Doorkeeper::Application record, PreAuthorization should only compare scopes against the config/server scopes. If there are scopes on the Doorkeeper::Application record, the set of valid scopes is the intersection of the config/server and the Doorkeeper::Application record scopes.
tute added a commit that referenced this pull request Dec 2, 2014
@tute tute merged commit 69621bd into doorkeeper-gem:master Dec 2, 2014
@tute
Copy link
Contributor

tute commented Dec 2, 2014

Thank you!

vinayvinay pushed a commit to alphagov/signon that referenced this pull request Jan 30, 2015
in order to restrict scopes that applications can grant to clients:
doorkeeper-gem/doorkeeper#469
vinayvinay pushed a commit to alphagov/signon that referenced this pull request Feb 2, 2015
in order to restrict scopes that applications can grant to clients:
doorkeeper-gem/doorkeeper#469
@seanarnold
Copy link

@tute @cpapazian Just stumbled upon this and it's saved me so much time. I thought I had to do this manually.

Is this documented anywhere? https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes Only mentions creating and using scopes, but not limiting per Application. I can write something in the Wiki about it unless it's documented somewhere else?

@tute
Copy link
Contributor

tute commented Mar 17, 2016

I can write something in the Wiki

Please do! Thank you. :)

@seanarnold
Copy link

@tute Got around to adding this https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes#limiting-scopes-per-application

Feel free to make your own changes.

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.

7 participants