From the: OAuth Docs
Scope is a mechanism in OAuth 2.0 to limit an application's access to a user's account. An application can request one or more scopes, this information is then presented to the user in the consent screen, and the access token issued to the application will be limited to the scopes granted.
In farmOS 1.x we only have the user_access and farm_info scopes available to OAuth Clients. Due to how the oauth2_server module works, it requires additional code to check & validate OAuth scopes before returning protected info. As a first pass we simply implemented the user_access scope which logs in the Authorized user, thus limiting an access_token to the permissions given to the user that authorized a client with that token. The farm_info scope was provided authorize OAuth Clients with only access to info included in the /farm.json endpoint.
The simple_oauth module is different in that it implements OAuth Scopes as Drupal Roles. This will require a different approach for implementing granular access control via OAuth. The notable difference is that when users authorize an OAuth Client (modeled as consumer entities which are configured with Drupal roles), the client is granted the roles configured with that OAuth Client. In other words, when requests are authenticated with the access_token generated from this authorization, the OAuth Client may have different roles (and thus different permissions) than the user that authorized the client. This is an important consideration because the client may be provided with a higher level of access than the authorizing user. (See this issue for more info. It also includes a patch which changes this functionality to not grant more roles than a user has access to: https://www.drupal.org/project/simple_oauth/issues/3077125)
Depending on how we design permissions & roles in farmOS 2.x this isn't necessarily a problem. There may in fact be use cases where a user should be able to authorize a 3rd party client with different permissions than they have. After chatting with @mstenta about this, it seems that we will likely a need a separate permission for authorizing each individual OAuth client eg: "authorize [consumer client_id] via oauth".
Another notable change from oauth2_server is that simple_oauth (by default) grants all Scopes assigned to the OAuth Client. Typically only the scopes that are requested during the authorization flow are granted to the client. This is an important feature for the farmOS-Aggregator which allows users to select which Scopes they would like the client to have (note the patch in the above issue provides this functionality too). On the other hand, other uses cases may require that the OAuth Client indeed has a set level or permissions in order for an integration to work. It seems that we will need to provide some way of configuring clients/consumers to behave one way or another.
Comments
Comment #2
paul121 commentedComment #3
paul121 commentedSome highlights from chat that helped us arrive at the above summary...
@mstenta
@mstenta
@mstenta
@paul121
Comment #4
m.stentaComment #5
paul121 commentedGoing to try and break this down a bit further:
See separate issue: https://www.drupal.org/project/farm/issues/3172315 Having granular permissions that enable users to authorize individual OAuth Clients (Consumers) with any grant type (I don't think we need separate permissions for each grant type) would be great. Not only would this solve the "set of permissions for granting certain permissions" issue, it would also allow general configuration of which users interact with 1st & 3rd party clients. For example, only users with "authorize farm_client consumers" would be able to use farmOS Field Kit.
In the farm_access module we are using the User Role's third party settings feature to add additional data for "managed roles". I suggest we do the same for the farm_api module: Roles that have the
user.role.*.third_party.farm_api.oauth_roleflag set to TRUE designates that this role is primarily used as an OAuth Scope. This allows us to hide these specific roles from the UI that Farm managers/admins use when editing user permissions.Different Scope/Role use cases applicable to both 1st & 3rd party OAuth integrations:
- Scope to only grant the user's permissions (user_access)
- Scope to grant a hard coded set of permissions, potentially more/different than the user's permissions eg: harvest_info
- During Authorization, allow the user to choose which scopes a the client is granted.
I suggest we add additional settings to the Consumer entities that define how & if they handle these additional behaviors:
-
consumer.grant_user_access: Always grant the authorizing user's roles when authorizing access for this consumer.-
consumer.limit_to_user_access: Limit granted roles to only include those assigned to the authorizing user.-
consumer.limit_to_requested_scopes: Only grant scopes included in the authorization request. While the consumer entity is configured with all of the possible roles an integration may use, this allows the user to select which scopes/roles they want to grant to a third party.The patch provided in the simple_oauth issue referenced above limits the granted scopes to only the requested scopes, as well as only the roles granted to the authorizing user. This logic is largely implemented by calling this additional function when building roles for the OAuthToken entity. It would be pretty trivial to check the Consumer entity for the above flags and add logic to add/remove scopes accordingly:
and
Again, I think this is behavior that should be "configurable" by the Consumer entity. By default the authenticated user is associated with the token, but consumers can have a "default user" that is used in case there was no authenticated user during authorization (normally this would only happen with the ClientCredentials grant, where no intervention from a User is required to grant access because the client id/client secret are truly secret values). So we need a way to configure a consumer to make a Token always reference the Consumer's default user. We could modify the AccessTokenRepository::getnewToken() method to check a
consumer.force_default_userflag and set the$token->userIdentifieraccordingly.Some background on how a token is authenticated with simple_oauth:
- When a token is created, the authenticated user is saved with the token.
- BUT Consumers can be configured with a "default" user: "When no specific user is authenticated Drupal will use this user as the author of all the actions made."
- The SimpleOAuthAuthenticationProvider::authenticate() method checks for a valid token in the request and returns an AccountInterface if all is successful:
return new TokenAuthUser($token);. This works because TokenAuthUser saves a reference to a user object; either 1) the Authenticated user from the token or 2) The Consumer's default user. TokenAuthUser implements the UserInterface (which implements AccountInterface), and delegates most of the interface methods to it's referenced user object.- Once authenticated, a Token appears just like a User. When checking permissions, the
hasPermission()method loads the token's scopes/roles and callsisPermissionInRoles($permission, $roles).Comment #6
paul121 commentedI've implemented the following consumer "config" options (that I outlined above) as additional fields on the consumer entity:
consumer.grant_user_access: Always grant the authorizing user's accessconsumer.limit_user_access: Never grant the consumer more access than the authorizing user hasconsumer.limit_requested_accessOnly grant the "scopes" that are requested - this allows the users to select which scopes to grant the consumer.Combinations of the 3 above configurations should cover most of the 1st and 3rd party API integration use cases for farmOS. One option I haven't yet implemented is the
consumer.force_default_user. This would force the Consumer to always use the optional "default user" that can be configured with a Consumer. This is a bit trickier to implement because it requires modifying how the simple_oauth module uses theTokenAuthUserto effectively authenticate tokens as a Drupal user.There also is a slight security consideration here where anyone could authorize a "public" client at anytime via the Client Credentials grant without having a Drupal user account (more details here: https://www.drupal.org/project/simple_oauth/issues/3173947#comment-13846016)
This isn't a problem if there isn't a "default user" configured with the consumer, but it would be good to put some precautions in place. The oauth2_server module had the ability to configure which grants were available on a per-client level. This way a client could be configured to NEVER accept the Client Credentials grant, and only the Authorization Code or Password Credentials. This will be an easy option to implement since we can alter logic to check the supplied grant type in the
validateClient()method of theClientRepositoryservice (that we're already decorating!)Similarly... I'm curious if farmOS (via the farm_api module) should disable the Password Credentials grant for all Consumers that are configured as "third party". This would help enforce best-practices of the OAuth standard, specifically that Users should not be entering their credentials in 3rd party applications. It would be a *little* limiting, but this would leave the Authorization Code and Client Credentials grant available for third parties to use (and Client Credentials ONLY if the the 3rd party can maintain a true "client secret").
Lastly, we will want to put some thought into the user-facing UI of these features: (Perhaps we wait on this until we partner with an OpenTEAM parter to build a solid 3rd party integration?)
grant_user_acessis enabled, a message stating "This 1st/3rd client will be granted all the access your user account has." should be clearly displayed.Comment #7
m.stentaComment #8
m.stentaComment #9
m.stentaLinking to this old v1 GitHub issue for more thoughts/context: https://github.com/farmOS/farmOS/issues/228
Comment #10
m.stentaI'm going to close this as "outdated". We are currently working on upgrading Simple OAuth from v5 to v6 in the 3.x branch of farmOS, which may change some of the considerations described above.