Update Simple OAuth module to v6 #743
This commit is contained in:
commit
69b36c065f
|
@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- [Issue #3394069: Update quantities to use bundle permission granularity](https://www.drupal.org/node/3394069)
|
||||
- [Issue #3357679: Allow material quantities to reference multiple material types](https://www.drupal.org/project/farm/issues/3357679)
|
||||
- [Issue #3330490: Update Drupal core to 10.x in farmOS](https://www.drupal.org/project/farm/issues/3330490)
|
||||
- [Issue #3256745: Move default farm OAuth2 client to a separate module](https://www.drupal.org/project/farm/issues/3256745)
|
||||
- [Update Simple OAuth module to v6 #743](https://github.com/farmOS/farmOS/pull/743)
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
@ -42,7 +42,8 @@
|
|||
"drupal/migrate_source_ui": "^1.0",
|
||||
"drupal/migrate_tools": "^6.0.2",
|
||||
"drupal/role_delegation": "^1.2",
|
||||
"drupal/simple_oauth": "5.2.4",
|
||||
"drupal/simple_oauth": "6.0.0-beta5",
|
||||
"drupal/simple_oauth_password_grant": "^1.0@RC",
|
||||
"drupal/state_machine": "1.8",
|
||||
"drupal/subrequests": "^3.0.6",
|
||||
"drupal/token": "^1.11",
|
||||
|
@ -79,7 +80,8 @@
|
|||
"Issue #3322227: Document schema title wrong for multiple resource types": "https://www.drupal.org/files/issues/2022-11-17/3322227-0.patch"
|
||||
},
|
||||
"drupal/simple_oauth": {
|
||||
"Issue #3322325: Cannot authorize clients with empty string set as secret": "https://www.drupal.org/files/issues/2022-11-17/3322325-1.patch"
|
||||
"Issue #3322325: Cannot authorize clients with empty string set as secret": "https://www.drupal.org/files/issues/2023-10-31/3322325-8.patch",
|
||||
"Issue #3397590: Add method to check if scope has permission": "https://www.drupal.org/files/issues/2023-10-30/3397590-5_0.patch"
|
||||
},
|
||||
"drupal/state_machine": {
|
||||
"Issue #3396186: State constraint is not validated on new entities": "https://www.drupal.org/files/issues/2023-10-23/3396186-2.patch"
|
||||
|
|
|
@ -31,44 +31,60 @@ server.
|
|||
|
||||
### Scopes
|
||||
|
||||
OAuth Scopes define different levels of permission. The farmOS server
|
||||
implements scopes as roles associated with OAuth clients. This means that users
|
||||
will authorize clients with roles that determine how much access they have
|
||||
to data on the server.
|
||||
OAuth Scopes define different levels of access. The farmOS server
|
||||
implements scopes that represent individual roles or permissions. Users will
|
||||
authorize clients with one or more scopes that determine how much access they
|
||||
have to data on the server.
|
||||
|
||||
The farmOS Default Roles module provides an OAuth scope for each of the default
|
||||
roles: `farm_manager`, `farm_worker`, and `farm_viewer`.
|
||||
|
||||
If you are creating an integration with farmOS, see the
|
||||
[OAuth](/development/module/oauth) page of the farmOS module development docs
|
||||
for steps to create additional OAuth Scopes.
|
||||
|
||||
### Clients
|
||||
|
||||
An OAuth Client represents a 1st or 3rd party integration with the farmOS
|
||||
server. Clients are uniquely identified by a `client_id` and are
|
||||
configured to use different `scopes`.
|
||||
server. Clients are uniquely identified by a `client_id` and can have an
|
||||
optional `client_secret` for private integrations. Clients are configured to
|
||||
allow only specific OAuth grants and can specify default `scopes` that are
|
||||
granted when none are requested.
|
||||
|
||||
The core `farm_api` module provides a default client with
|
||||
`client_id = farm`. If you are writing a script that communicates with *your*
|
||||
farmOS server via the API, you should use this client to authorize access and
|
||||
generate an `access_token` for authentication.
|
||||
The core `farm_api_default_consumer` module provides a default client with
|
||||
`client_id = farm` that can use the `password` and `refresh_token` grant. You
|
||||
can use this client for general usage of the API, like writing a script that
|
||||
communicates with *your* farmOS server, but it comes with limitations.
|
||||
|
||||
If you are creating a third party integration with farmOS, see the
|
||||
If you are creating an integration with farmOS, see the
|
||||
[OAuth](/development/module/oauth) page of the farmOS module development docs
|
||||
for steps to create an OAuth Client.
|
||||
|
||||
### Authorization Flows
|
||||
|
||||
The [OAuth 2.0 standards](https://oauth.net/2/) outline 5
|
||||
[Oauth2 Grant Types](https://oauth.net/2/grant-types/) to be used in an OAuth2
|
||||
Authorization Flow - They are the *Authorization Code, Implicit, Password
|
||||
Credentials, Client Credentials* and *Refresh Token* Grants. The
|
||||
The [OAuth 2.0 standards](https://oauth.net/2/) outline 3
|
||||
[Oauth2 Grant Types](https://oauth.net/2/grant-types/) to be used in an OAuth2 Authorization Flow - They are
|
||||
the *Authorization Code, Client Credentials* and *Refresh Token* Grants. The
|
||||
[Authorization Code](#authorization-code-grant) and
|
||||
[Refresh Token](#refreshing-tokens) grants are the only Authorization Flows
|
||||
recommended by farmOS for use with 3rd party clients.
|
||||
[Refresh Token](#refreshing-tokens) grants are the only Authorization Flows recommended by
|
||||
farmOS for use with 3rd party clients.
|
||||
|
||||
**NOTE:** Only use the **Password Grant** if the client can be trusted with a
|
||||
farmOS username and password (this is considered *1st party*). The
|
||||
**Client Credentials Grant** is often used for machine authentication not
|
||||
The **Client Credentials Grant** is often used for machine authentication not
|
||||
associated with a user account. The client credentials grant should only be
|
||||
used if a `client_secret` can be kept secret. If connecting to multiple
|
||||
farmOS servers, each server should use a different secret. This is
|
||||
challenging due to the nature of farmOS being a self-hosted application.
|
||||
|
||||
The [Password Credentials Grant](#password-credentials-grant) is a legacy
|
||||
grant type that is
|
||||
[no longer recommended](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-2.4).
|
||||
Only use the Password Credentials Grant if the client can be trusted with a
|
||||
farmOS username and password (this is considered *1st party*). Even if the
|
||||
client is trusted, this grant type exposes the username and password and
|
||||
results in an increased attack surface. In most cases the **Client Credentials
|
||||
Grant** can be used with an OAuth client that is configured for each separate
|
||||
integration.
|
||||
|
||||
#### Authorization Code Grant
|
||||
|
||||
The Authorization Code Grant is most popular for 3rd party client
|
||||
|
@ -106,8 +122,13 @@ resources. The header is an Authorization header with a Bearer token:
|
|||
|
||||
#### Password Credentials Grant
|
||||
|
||||
**NOTE:** Only use the **Password Grant** if the client can be trusted with a
|
||||
farmOS username and password (this is considered *1st party*).
|
||||
**NOTE:** The **Password Credentials Grant** is a legacy grant type that is
|
||||
[no longer recommended](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-2.4).
|
||||
Only use the **Password Grant** if the client can be trusted with a farmOS
|
||||
username and password (this is considered *1st party*).
|
||||
|
||||
**NOTE:** The [Simple OAuth Password Grant](https://www.drupal.org/project/simple_oauth_password_grant)
|
||||
module must be enabled to use the password grant.
|
||||
|
||||
The Password Credentials Grant uses a farmOS `username` and `password` to
|
||||
retrieve an `access_token` and `refresh_token` in one step. For the user, this
|
||||
|
|
|
@ -2,6 +2,29 @@
|
|||
|
||||
## 3.x vs 2.x
|
||||
|
||||
The [Simple OAuth](https://www.drupal.org/project/simple_oauth) module has been
|
||||
updated to version 6. This includes a few breaking changes which may affect API
|
||||
integrations. farmOS includes code to handle the transition of its own OAuth
|
||||
clients and scopes, but if you have made any additional clients that used
|
||||
special roles they will also need to be updated.
|
||||
|
||||
The biggest changes are that the "Implicit" grant type has been
|
||||
removed, and the "Password Credentials" grant type has been moved to an optional
|
||||
"Simple OAuth Password Grant" module, which must be enabled in order to use that
|
||||
grant type.
|
||||
|
||||
There have also been changes to how scopes are provided. User roles no longer
|
||||
act as scopes by default. Instead, scopes must be created separately to
|
||||
reference each role they represent. Scopes can also be associated with
|
||||
individual permissions and can reference parent scopes to create
|
||||
hierarchical scope trees. farmOS provides `static` scopes for each of the
|
||||
default roles: `farm_manager`, `farm_worker` and `farm_viewer`.
|
||||
|
||||
The default farmOS client that is included with farmOS has also been
|
||||
moved to a separate module that is not enabled by default. After the update to
|
||||
farmOS 3.x, all access tokens will be invalidated, but refresh tokens will still
|
||||
work to get a new access token.
|
||||
|
||||
- [Material quantities can reference multiple material types](https://www.drupal.org/node/3395697)
|
||||
|
||||
## 2.x vs 1.x
|
||||
|
|
|
@ -5,22 +5,101 @@ to provide an [OAuth2 standard](https://oauth.net/2/) authorization server.
|
|||
|
||||
For documentation on using and authenticating with the farmOS API see [API](/api).
|
||||
|
||||
## Providing OAuth Scopes
|
||||
|
||||
OAuth Scopes define different levels of access. The farmOS server
|
||||
implements scopes that represent individual roles or permissions. Users will
|
||||
authorize clients with one or more scopes that determine how much access they
|
||||
have to data on the server.
|
||||
|
||||
OAuth scopes are provided to the server using scope provider plugins. Each
|
||||
scope provider determines how scopes are implemented and created. The OAuth
|
||||
server must choose a single scope provider to provide all the scopes
|
||||
necessary for the server's authorization needs. All scopes use the same
|
||||
configuration and provide the same features regardless of the scope provider.
|
||||
|
||||
The [Simple OAuth](https://www.drupal.org/project/simple_oauth) module
|
||||
provides two scope providers: `static` and `dynamic`. The `static` scope
|
||||
provider implements scopes as a `yaml` plugin that must be provided by modules
|
||||
and prevents scopes from being modified. The `dynamic` scope provider
|
||||
implements scopes as a config entity and allows scopes to be created and
|
||||
modified via the UI. Modules can provide `dynamic` scopes as well but there are
|
||||
no guarantees that these scopes will remain unmodified.
|
||||
|
||||
farmOS defaults to using the `static` scope provider. This allows modules
|
||||
providing OAuth scopes to guarantee that their scopes exist unmodified within
|
||||
the server. The farmOS administrator can change to using the `dynamic` scope
|
||||
provider if necessary, but may need to re-create any `static` scopes that
|
||||
are needed for integrations provided by other modules.
|
||||
|
||||
The farmOS Default Roles module provides a `static` OAuth scope for each of the
|
||||
default roles: `farm_manager`, `farm_worker`, and `farm_viewer`.
|
||||
|
||||
### Scope Configuration
|
||||
|
||||
All scopes use the same configuration and provide the same features
|
||||
regardless of the scope provider:
|
||||
- Scopes must provide a `name` to uniquely identify the scope
|
||||
- Scopes must provide a `description`
|
||||
- Scopes must specify if they are an `umbrella` scope. Umbrella scopes are
|
||||
only used as parent for child scopes to reference and do not specify a
|
||||
`granularity`.
|
||||
- Scopes must configure which `grant_types` they allow. Each grant type can
|
||||
include an optional `description` to describe how the scope is used in the
|
||||
context of each grant type.
|
||||
- Scopes can optionally specify a `parent` scope that the scope is a part of.
|
||||
When the parent scope is requested, all of its child scopes are granted as
|
||||
well.
|
||||
- Scopes must specify a `granularity` if they are not an `umbrella` scope.
|
||||
This value must be equal to `permission` or `role`. The scope must
|
||||
provide a single value for the `permission` or `role` it is associated with.
|
||||
|
||||
This configuration is most easily demonstrated with
|
||||
`static` scopes that are provided in a `module.oauth2_scopes.yml` plugin file.
|
||||
|
||||
```yaml
|
||||
"scope:name":
|
||||
description: string (required)
|
||||
umbrella: boolean (required)
|
||||
grant_types: (required)
|
||||
GRANT_TYPE_PLUGIN_ID: (required: only known grant types)
|
||||
status: boolean (required)
|
||||
description: string
|
||||
parent: string
|
||||
granularity: string (required: if umbrella is FALSE, values: permission or role)
|
||||
permission: string (required: if umbrella is FALSE and granularity set to permission)
|
||||
role: string (required: if umbrella is FALSE and granularity set to role)
|
||||
```
|
||||
|
||||
An example of the static `farm_manager` scope provided by the farmOS Role
|
||||
Roles mdoule:
|
||||
```yaml
|
||||
farm_manager:
|
||||
description: 'Grants access to the Farm Manager role.'
|
||||
umbrella: false
|
||||
grant_types:
|
||||
authorization_code:
|
||||
status: true
|
||||
refresh_token:
|
||||
status: true
|
||||
password:
|
||||
status: true
|
||||
granularity: 'role'
|
||||
role: 'farm_manager'
|
||||
```
|
||||
|
||||
## Providing OAuth Clients
|
||||
|
||||
OAuth clients are modeled as "Consumer" entities (provided by the
|
||||
[Consumers](https://www.drupal.org/project/consumers) module. The `farm_api`
|
||||
module provides a default client with `client_id = farm`. This can be used for
|
||||
general usage of the API, but comes with limitations. To create a third party
|
||||
integration with farmOS a `consumer` entity must be created that identifies
|
||||
the integration and configures the OAuth Client authorization behavior.
|
||||
[Consumers](https://www.drupal.org/project/consumers) module. To create
|
||||
integrations with farmOS a `consumer` entity must be created that
|
||||
identifies the integration and configures the OAuth Client for the desired
|
||||
authorization behavior.
|
||||
|
||||
## Scopes
|
||||
|
||||
OAuth scopes define different levels of permission. OAuth clients are
|
||||
configured with the scopes needed for the purposes of a specific integration.
|
||||
With consumers, these scopes are implemented as Drupal Roles. This means that
|
||||
OAuth clients interacting with farmOS over the API use the same permission
|
||||
system as Users normally using the site.
|
||||
The core `farm_api_default_consumer` module provides a default client with
|
||||
`client_id = farm` that can use the `password` and `refresh_token` grant. You
|
||||
can use this client for general usage of the API, like writing a script that
|
||||
communicates with *your* farmOS server, but it comes with limitations.
|
||||
|
||||
## Client Configuration
|
||||
|
||||
|
@ -40,6 +119,13 @@ Standard Consumer configuration:
|
|||
- `consumer.user_id` - When no specific user is authenticated Drupal will use
|
||||
this user as the author of all the actions made by this consumer.
|
||||
- This is only the case during the `Client Credentials` authorization flow.
|
||||
- `consumer.grant_types` - A list of the grant types that the client allows.
|
||||
- `consumer.scopes` - A list of default scopes that will be granted for this
|
||||
client if no scopes are requested during the authorization flow. No scopes
|
||||
will be granted that the user does not have access to.
|
||||
- `consumer.access_token_expiration` - The lifetime of access tokens in seconds.
|
||||
- `consumer.refresh_token_expiration` - The lifetime of refresh tokens in
|
||||
seconds.
|
||||
- `consumer.redirect_uri` - The URI this client will redirect to when needed.
|
||||
- This is used with the Authorization Code authorization flow.
|
||||
- `consumer.allowed_origins` - Define any allowed origins the farmOS server
|
||||
|
@ -48,28 +134,3 @@ Standard Consumer configuration:
|
|||
- `consumer.third_party` - Enable if the Consumer represents a third party.
|
||||
- Users will skip the "grant" step of the authorization flow for first
|
||||
party consumers only.
|
||||
|
||||
farmOS extends the `consumers` and `simple_oauth` modules to provide additional
|
||||
authorization options on consumer entities. These additional options make it
|
||||
possible to support different third party integration use cases via the same
|
||||
OAuth Authorization server. They can be configured via the UI or when creating
|
||||
a consumer entity programmatically.
|
||||
|
||||
Authorization options (all are disabled by default):
|
||||
|
||||
- `consumer.grant_user_access` - Always grant the authorizing user's access
|
||||
to this consumer.
|
||||
- This is how the farmOS Field Kit consumer is configured. If this is the
|
||||
only option enabled, then the consumer will only be granted the roles
|
||||
the user has access to.
|
||||
- `consumer.limit_requested_access` - Only grant this consumer the scopes
|
||||
requested during authorization.
|
||||
- By default, all scopes configured with the consumer will be granted
|
||||
during authorization. This allows users to select which scopes they want
|
||||
to grant the third party during authorization.
|
||||
- `consumer.limit_user_access` - Never grant the consumer more access than
|
||||
the authorizing user.
|
||||
- It is possible that clients will be configured with different roles
|
||||
than the user that authorizes access to a third party. There are times
|
||||
that this may be intentional, but this setting ensures that consumers
|
||||
will not be granted more access than the authorizing user.
|
||||
|
|
|
@ -58,6 +58,7 @@ function farm_modules() {
|
|||
'farm_import_csv' => t('CSV importer'),
|
||||
'farm_kml' => t('KML export features'),
|
||||
'farm_import_kml' => t('KML asset importer'),
|
||||
'farm_api_default_consumer' => t('Default API Consumer'),
|
||||
'farm_fieldkit' => t('Field Kit integration'),
|
||||
'farm_l10n' => t('Translation/localization features'),
|
||||
'farm_role_account_admin' => t('Account Admin role'),
|
||||
|
|
|
@ -9,4 +9,5 @@ dependencies:
|
|||
- jsonapi_extras:jsonapi_extras
|
||||
- jsonapi_schema:jsonapi_schema
|
||||
- simple_oauth:simple_oauth
|
||||
- simple_oauth:simple_oauth_static_scope
|
||||
- subrequests:subrequests
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* Install, update and uninstall functions for the farm_api module.
|
||||
*/
|
||||
|
||||
use Drupal\consumers\Entity\Consumer;
|
||||
use Drupal\Core\Utility\Error;
|
||||
|
||||
/**
|
||||
|
@ -22,13 +21,13 @@ function farm_api_install() {
|
|||
// Load the simple_oauth module settings.
|
||||
$simple_oauth_settings = \Drupal::configFactory()->getEditable('simple_oauth.settings');
|
||||
|
||||
// Increase access token expiration time to 1 hour.
|
||||
$simple_oauth_settings->set('access_token_expiration', 3600);
|
||||
|
||||
// Explicitly set the public/private key path.
|
||||
$simple_oauth_settings->set('public_key', '../keys/public.key');
|
||||
$simple_oauth_settings->set('private_key', '../keys/private.key');
|
||||
|
||||
// Use static scopes by default.
|
||||
$simple_oauth_settings->set('scope_provider', 'static');
|
||||
|
||||
// Save simple_oauth settings.
|
||||
$simple_oauth_settings->save();
|
||||
|
||||
|
@ -73,39 +72,6 @@ function farm_api_install() {
|
|||
$default_consumer->delete();
|
||||
}
|
||||
|
||||
// Create a "Farm default" consumer.
|
||||
$base_url = \Drupal::service('router.request_context')->getCompleteBaseUrl();
|
||||
$farm_consumer = Consumer::create([
|
||||
'label' => 'Farm default',
|
||||
'client_id' => 'farm',
|
||||
'redirect' => $base_url,
|
||||
'is_default' => TRUE,
|
||||
'owner_id' => '',
|
||||
'secret' => NULL,
|
||||
'confidential' => FALSE,
|
||||
'third_party' => FALSE,
|
||||
'grant_user_access' => TRUE,
|
||||
'limit_user_access' => TRUE,
|
||||
'limit_requested_access' => FALSE,
|
||||
]);
|
||||
$farm_consumer->save();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function farm_api_uninstall() {
|
||||
|
||||
// Load the default farm consumer.
|
||||
$consumers = \Drupal::entityTypeManager()->getStorage('consumer')
|
||||
->loadByProperties(['client_id' => 'farm']);
|
||||
|
||||
// If found, delete the consumer.
|
||||
if (!empty($consumers)) {
|
||||
$farm_consumer = reset($consumers);
|
||||
$farm_consumer->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
farm_api:
|
||||
default_permissions:
|
||||
- grant simple_oauth codes
|
||||
- issue subrequests
|
||||
|
|
|
@ -34,47 +34,8 @@ function farm_api_consumers_list_alter(&$data, $context) {
|
|||
function farm_api_entity_base_field_info(EntityTypeInterface $entity_type) {
|
||||
$fields = [];
|
||||
|
||||
// Add bundle fields to the consumer entity.
|
||||
// Add allowed_origins field to the consumer entity.
|
||||
if ($entity_type->id() == 'consumer') {
|
||||
$fields['grant_user_access'] = BundleFieldDefinition::create('boolean')
|
||||
->setLabel(t('Grant user access'))
|
||||
->setDescription(t("Always grant the authorizing user's access to this consumer."))
|
||||
->setSetting('on_label', t('Yes'))
|
||||
->setSetting('off_label', t('No'))
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'boolean_checkbox',
|
||||
'settings' => [
|
||||
'display_label' => TRUE,
|
||||
],
|
||||
'weight' => 4,
|
||||
]);
|
||||
|
||||
$fields['limit_requested_access'] = BundleFieldDefinition::create('boolean')
|
||||
->setLabel(t('Limit to requested access'))
|
||||
->setDescription(t('Only grant this consumer the scopes requested during authorization.'))
|
||||
->setSetting('on_label', t('Yes'))
|
||||
->setSetting('off_label', t('No'))
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'boolean_checkbox',
|
||||
'settings' => [
|
||||
'display_label' => TRUE,
|
||||
],
|
||||
'weight' => 4,
|
||||
]);
|
||||
|
||||
$fields['limit_user_access'] = BundleFieldDefinition::create('boolean')
|
||||
->setLabel(t('Limit to user access'))
|
||||
->setDescription(t('Never grant this consumer more access than the authorizing user.'))
|
||||
->setSetting('on_label', t('Yes'))
|
||||
->setSetting('off_label', t('No'))
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'boolean_checkbox',
|
||||
'settings' => [
|
||||
'display_label' => TRUE,
|
||||
],
|
||||
'weight' => 4,
|
||||
]);
|
||||
|
||||
$fields['allowed_origins'] = BundleFieldDefinition::create('string')
|
||||
->setLabel(t('Allowed origins'))
|
||||
->setDescription(t('Configure CORS origins for this consumer.'))
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Post update functions for farm_settings module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Remove farm_api consumer bundle fields.
|
||||
*/
|
||||
function farm_api_post_update_remove_consumer_fields(&$sandbox = NULL) {
|
||||
|
||||
// Remove old consumer fields.
|
||||
$fields = [
|
||||
'grant_user_access',
|
||||
'limit_user_access',
|
||||
'limit_requested_access',
|
||||
];
|
||||
foreach ($fields as $field) {
|
||||
$entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
|
||||
$roles_field_definition = $entity_definition_update_manager->getFieldStorageDefinition($field, 'consumer');
|
||||
$entity_definition_update_manager->uninstallFieldStorageDefinition($roles_field_definition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable static oauth2 scopes.
|
||||
*/
|
||||
function farm_api_post_update_enable_static_oauth2_scopes(&$sandbox = NULL) {
|
||||
|
||||
// Enable static scope module.
|
||||
if (!\Drupal::service('module_handler')->moduleExists('simple_oauth_static_scope')) {
|
||||
\Drupal::service('module_installer')->install(['simple_oauth_static_scope']);
|
||||
}
|
||||
|
||||
// Use static scope provider.
|
||||
$simple_oauth_settings = \Drupal::configFactory()->getEditable('simple_oauth.settings');
|
||||
$simple_oauth_settings->set('scope_provider', 'static');
|
||||
$simple_oauth_settings->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable default consumer module.
|
||||
*/
|
||||
function farm_api_post_update_enable_default_consumer_module(&$sandbox = NULL) {
|
||||
|
||||
// Check for an existing farm default consumer.
|
||||
$consumers = \Drupal::entityTypeManager()->getStorage('consumer')
|
||||
->loadByProperties(['client_id' => 'farm']);
|
||||
if (!empty($consumers)) {
|
||||
|
||||
// Enable default consumer module.
|
||||
if (!\Drupal::service('module_handler')->moduleExists('farm_api_default_consumer')) {
|
||||
\Drupal::service('module_installer')->install(['farm_api_default_consumer']);
|
||||
}
|
||||
|
||||
// Update values on the consumer.
|
||||
/** @var \Drupal\consumers\Entity\ConsumerInterface $farm_default */
|
||||
$farm_default = reset($consumers);
|
||||
$farm_default->set('user_id', NULL);
|
||||
$farm_default->set('grant_types', ['authorization_code', 'refresh_token', 'password']);
|
||||
$farm_default->save();
|
||||
}
|
||||
|
||||
}
|
|
@ -10,7 +10,3 @@ services:
|
|||
class: Drupal\farm_api\Routing\RouteSubscriber
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
farm_api.repositories.scope:
|
||||
class: Drupal\farm_api\Repositories\FarmScopeRepository
|
||||
decorates: simple_oauth.repositories.scope
|
||||
arguments: [ '@entity_type.manager' ]
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
name: farmOS Default API Consumer
|
||||
description: Provides a default consumer for using the farmOS API.
|
||||
type: module
|
||||
package: farmOS
|
||||
core_version_requirement: ^10
|
||||
dependencies:
|
||||
- farm:farm_api
|
||||
- simple_oauth_password_grant:simple_oauth_password_grant
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install and uninstall functions for the farm_api_default_consumer module.
|
||||
*/
|
||||
|
||||
use Drupal\consumers\Entity\Consumer;
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function farm_api_default_consumer_install() {
|
||||
|
||||
// Check for an existing farm default consumer.
|
||||
$consumers = \Drupal::entityTypeManager()->getStorage('consumer')
|
||||
->loadByProperties(['client_id' => 'farm']);
|
||||
|
||||
// If not found, create the farm default consumer.
|
||||
if (empty($consumers)) {
|
||||
$base_url = \Drupal::service('router.request_context')->getCompleteBaseUrl();
|
||||
$farm_consumer = Consumer::create([
|
||||
'label' => 'Farm default',
|
||||
'client_id' => 'farm',
|
||||
'access_token_expiration' => 3600,
|
||||
'grant_types' => [
|
||||
'authorization_code',
|
||||
'refresh_token',
|
||||
'password',
|
||||
],
|
||||
'redirect' => $base_url,
|
||||
'is_default' => TRUE,
|
||||
'owner_id' => NULL,
|
||||
'secret' => NULL,
|
||||
'confidential' => FALSE,
|
||||
'third_party' => FALSE,
|
||||
]);
|
||||
$farm_consumer->save();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function farm_api_default_consumer_uninstall() {
|
||||
|
||||
// Load the default farm consumer.
|
||||
$consumers = \Drupal::entityTypeManager()->getStorage('consumer')
|
||||
->loadByProperties(['client_id' => 'farm']);
|
||||
|
||||
// If found, delete the consumer.
|
||||
if (!empty($consumers)) {
|
||||
$farm_consumer = reset($consumers);
|
||||
$farm_consumer->delete();
|
||||
}
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\farm_api\Repositories;
|
||||
|
||||
use Drupal\simple_oauth\Repositories\ScopeRepository;
|
||||
use Drupal\user\RoleInterface;
|
||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
||||
|
||||
/**
|
||||
* Decorates the simple_oauth ScopeRepository.
|
||||
*
|
||||
* Alter the default behavior to account for additional consumer config options:
|
||||
* - consumer.grant_user_access: Always grant the user's roles.
|
||||
* - consumer.limit_requested_access: Always limit to the requested scopes.
|
||||
* - consumer.limit_user_access: Always limit access to what the user has.
|
||||
*
|
||||
* @ingroup farm
|
||||
*/
|
||||
class FarmScopeRepository extends ScopeRepository {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function finalizeScopes(array $scopes, $grant_type, ClientEntityInterface $client_entity, $user_identifier = NULL) {
|
||||
|
||||
// Start a list of allowed roles.
|
||||
$allowed_roles = [];
|
||||
|
||||
// Load the consumer entity.
|
||||
/** @var \Drupal\consumers\Entity\Consumer $client_drupal_entity */
|
||||
$consumer_entity = $client_entity->getDrupalEntity();
|
||||
|
||||
// Load role ids of roles the consumer has.
|
||||
$consumer_roles = array_map(function ($role) {
|
||||
return $role['target_id'];
|
||||
}, $consumer_entity->get('roles')->getValue());
|
||||
|
||||
// Include consumer roles.
|
||||
// By default all consumer roles are available to authorization.
|
||||
$allowed_roles = array_merge($allowed_roles, $consumer_roles);
|
||||
|
||||
// Load the default user associated with the consumer.
|
||||
// This is an optional setting, so it may not exist.
|
||||
$default_user = NULL;
|
||||
try {
|
||||
$default_user = $client_entity->getDrupalEntity()->get('user_id')->entity;
|
||||
}
|
||||
catch (\InvalidArgumentException $e) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
// Load the user associated with the token.
|
||||
// If there is no user, use the default user.
|
||||
/** @var \Drupal\user\UserInterface $user */
|
||||
$user = $user_identifier
|
||||
? $this->entityTypeManager->getStorage('user')->load($user_identifier)
|
||||
: $default_user;
|
||||
if (!$user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Load the user's roles.
|
||||
// Load all roles for user 1 so they can be granted all possible scopes.
|
||||
if ((int) $user->id() === 1) {
|
||||
$user_roles = array_map(function (RoleInterface $role) {
|
||||
return $role->id();
|
||||
}, $this->entityTypeManager->getStorage('user_role')->loadMultiple());
|
||||
}
|
||||
// Else load the normal user's roles.
|
||||
else {
|
||||
$user_roles = $user->getRoles();
|
||||
}
|
||||
|
||||
// Include the user's roles if enabled.
|
||||
if ($consumer_entity->get('grant_user_access')->value) {
|
||||
$allowed_roles = array_merge($allowed_roles, $user_roles);
|
||||
}
|
||||
|
||||
/* Limit the roles granted to the token. */
|
||||
|
||||
// Limit to requested roles if enabled.
|
||||
if ($consumer_entity->get('limit_requested_access')->value) {
|
||||
|
||||
// Save the requested scopes (roles) that were passed to this
|
||||
// finalizeScopes() method.
|
||||
$requested_roles = array_map(function (ScopeEntityInterface $scope) {
|
||||
return $scope->getIdentifier();
|
||||
}, $scopes);
|
||||
|
||||
// Reduce the requested roles to only those in allowed roles.
|
||||
// This prevents additional roles being granted than the user
|
||||
// and consumer have available.
|
||||
$allowed_requested_roles = array_filter($requested_roles, function ($role_id) use ($allowed_roles) {
|
||||
return in_array($role_id, $allowed_roles);
|
||||
});
|
||||
|
||||
// Filter the allowed roles to only those requested.
|
||||
$allowed_roles = array_intersect($allowed_roles, $allowed_requested_roles);
|
||||
}
|
||||
|
||||
// Limit to roles the user already has, if enabled.
|
||||
if ($consumer_entity->get('limit_user_access')->value) {
|
||||
$allowed_roles = array_intersect($allowed_roles, $user_roles);
|
||||
}
|
||||
|
||||
// Always include the authenticated role.
|
||||
$allowed_roles[] = RoleInterface::AUTHENTICATED_ID;
|
||||
|
||||
// Build a new list of ScopeEntityInterface to return.
|
||||
$scopes = [];
|
||||
foreach ($allowed_roles as $role_id) {
|
||||
$scopes = $this->addRoleToScopes($scopes, $role_id);
|
||||
}
|
||||
return $scopes;
|
||||
}
|
||||
|
||||
}
|
|
@ -6,3 +6,4 @@ core_version_requirement: ^10
|
|||
dependencies:
|
||||
- farm:asset
|
||||
- log:log
|
||||
- simple_oauth_password_grant:simple_oauth_password_grant
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
test:password:
|
||||
description: 'Static scope for password grant.'
|
||||
umbrella: false
|
||||
grant_types:
|
||||
authorization_code:
|
||||
status: true
|
||||
description: 'Authorization code grant.'
|
||||
password:
|
||||
status: true
|
||||
description: 'Password grant'
|
||||
granularity: 'permission'
|
||||
permission: 'access content'
|
|
@ -1,282 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\farm_api\Functional;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Tests using the consumer.client_id field.
|
||||
*
|
||||
* @group farm
|
||||
*/
|
||||
class ConsumerConfigTest extends OauthTestBase {
|
||||
|
||||
/**
|
||||
* The URL for debugging tokens.
|
||||
*
|
||||
* @var \Drupal\Core\Url
|
||||
*/
|
||||
protected $tokenDebugUrl;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
|
||||
parent::setUp();
|
||||
|
||||
$this->tokenDebugUrl = Url::fromRoute('oauth2_token.user_debug');
|
||||
|
||||
// Override the additional roles created by parent.
|
||||
$this->additionalRoles = [];
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$role = Role::create([
|
||||
'id' => 'scope_' . $i,
|
||||
'label' => 'Scope: ' . $i,
|
||||
'is_admin' => FALSE,
|
||||
]);
|
||||
$role->save();
|
||||
$this->additionalRoles[] = $role;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test consumer.grant_user_access config.
|
||||
*/
|
||||
public function testGrantUserAccess() {
|
||||
|
||||
// Set up the client.
|
||||
$this->client->set('grant_user_access', FALSE);
|
||||
$this->client->set('limit_requested_access', FALSE);
|
||||
$this->client->set('limit_user_access', FALSE);
|
||||
$this->client->save();
|
||||
|
||||
// Grant the user more roles than the consumer.
|
||||
$this->user->addRole('scope_1');
|
||||
$this->user->addRole('scope_2');
|
||||
$this->user->save();
|
||||
|
||||
// 1. Test that only the consumers roles are granted.
|
||||
// Prepare expected roles. Include all roles the consumer has.
|
||||
$expected_roles = array_merge($this->getClientRoleIds(), ['authenticated']);
|
||||
// Check the token.
|
||||
$access_token = $this->getAccessToken();
|
||||
$token_info = $this->getTokenInfo($access_token);
|
||||
$this->assertEquals($this->user->id(), $token_info['id']);
|
||||
$this->assertEqualsCanonicalizing($expected_roles, $token_info['roles']);
|
||||
|
||||
// 2. Test that the user's roles are granted as well.
|
||||
// Update the client.
|
||||
$this->client->set('grant_user_access', TRUE);
|
||||
$this->client->save();
|
||||
// Include the consumer + user roles.
|
||||
$expected_roles = array_merge($expected_roles, ['scope_1', 'scope_2']);
|
||||
// Check the token.
|
||||
$access_token = $this->getAccessToken();
|
||||
$token_info = $this->getTokenInfo($access_token);
|
||||
$this->assertEquals($this->user->id(), $token_info['id']);
|
||||
$this->assertEqualsCanonicalizing($expected_roles, $token_info['roles']);
|
||||
|
||||
// 3. Test that additional roles are not granted.
|
||||
// Request "scope_3" even though it is not given to the user or consumer.
|
||||
// Check the token.
|
||||
$access_token = $this->getAccessToken(['scope_3']);
|
||||
$token_info = $this->getTokenInfo($access_token);
|
||||
$this->assertEquals($this->user->id(), $token_info['id']);
|
||||
$this->assertEqualsCanonicalizing($expected_roles, $token_info['roles']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test consumer.limit_requested_access.
|
||||
*/
|
||||
public function testLimitRequestedAccess() {
|
||||
|
||||
// Set up the client.
|
||||
$this->client->set('grant_user_access', FALSE);
|
||||
$this->client->set('limit_requested_access', FALSE);
|
||||
$this->client->set('limit_user_access', FALSE);
|
||||
$this->client->save();
|
||||
|
||||
// Grant the user additional roles.
|
||||
$this->user->addRole('scope_1');
|
||||
$this->user->addRole('scope_2');
|
||||
$this->user->save();
|
||||
|
||||
// Grant the client additional roles.
|
||||
$client_roles = array_merge(
|
||||
$this->getClientRoleIds(),
|
||||
['scope_3']
|
||||
);
|
||||
$this->grantClientRoles($client_roles);
|
||||
|
||||
// Array of expected roles. Includes all roles the consumer has.
|
||||
$expected_roles = array_merge($client_roles, ['authenticated']);
|
||||
|
||||
// 1. Test that all roles on the consumer are granted.
|
||||
$access_token = $this->getAccessToken();
|
||||
$token_info = $this->getTokenInfo($access_token);
|
||||
$this->assertEquals($this->user->id(), $token_info['id']);
|
||||
$this->assertEqualsCanonicalizing($expected_roles, $token_info['roles']);
|
||||
|
||||
// 2. Test that only the requested scopes (roles) are granted.
|
||||
// Update the client.
|
||||
$this->client->set('limit_requested_access', TRUE);
|
||||
$this->client->save();
|
||||
$requested_roles = ['scope_3'];
|
||||
$expected_roles = array_merge($requested_roles, ['authenticated']);
|
||||
// Check the token.
|
||||
$access_token = $this->getAccessToken($requested_roles);
|
||||
$token_info = $this->getTokenInfo($access_token);
|
||||
$this->assertEquals($this->user->id(), $token_info['id']);
|
||||
$this->assertEqualsCanonicalizing($expected_roles, $token_info['roles']);
|
||||
|
||||
// 3. Test only the requested roles are granted,
|
||||
// even if user roles are granted.
|
||||
$this->client->set('limit_requested_access', TRUE);
|
||||
$this->client->set('grant_user_access', TRUE);
|
||||
$this->client->save();
|
||||
$requested_roles = ['scope_1', 'scope_3'];
|
||||
$expected_roles = array_merge($requested_roles, ['authenticated']);
|
||||
// Check the token.
|
||||
$access_token = $this->getAccessToken($requested_roles);
|
||||
$token_info = $this->getTokenInfo($access_token);
|
||||
$this->assertEquals($this->user->id(), $token_info['id']);
|
||||
$this->assertEqualsCanonicalizing($expected_roles, $token_info['roles']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test consumer.limit_user_access.
|
||||
*/
|
||||
public function testLimitUserAccess() {
|
||||
|
||||
// Set up the client.
|
||||
$this->client->set('grant_user_access', FALSE);
|
||||
$this->client->set('limit_requested_access', FALSE);
|
||||
$this->client->set('limit_user_access', FALSE);
|
||||
$this->client->save();
|
||||
|
||||
// Grant the user one additional role.
|
||||
$this->user->addRole('scope_1');
|
||||
$this->user->save();
|
||||
|
||||
// Grant the client all roles.
|
||||
$client_roles = array_merge(
|
||||
$this->getClientRoleIds(),
|
||||
['scope_1', 'scope_2', 'scope_3']
|
||||
);
|
||||
$this->grantClientRoles($client_roles);
|
||||
|
||||
// Array of expected roles. Includes all roles the consumer has.
|
||||
$expected_roles = array_merge($client_roles, ['authenticated']);
|
||||
|
||||
// 1. Test that all roles on the consumer are granted.
|
||||
$access_token = $this->getAccessToken();
|
||||
$token_info = $this->getTokenInfo($access_token);
|
||||
$this->assertEquals($this->user->id(), $token_info['id']);
|
||||
$this->assertEqualsCanonicalizing($expected_roles, $token_info['roles']);
|
||||
|
||||
// 2. Test that only the roles the user has are granted.
|
||||
// Update the client.
|
||||
$this->client->set('limit_user_access', TRUE);
|
||||
$this->client->save();
|
||||
$requested_roles = ['scope_1', 'scope_3'];
|
||||
$expected_roles = ['scope_1', 'authenticated'];
|
||||
// Check the token.
|
||||
$access_token = $this->getAccessToken($requested_roles);
|
||||
$token_info = $this->getTokenInfo($access_token);
|
||||
$this->assertEquals($this->user->id(), $token_info['id']);
|
||||
$this->assertEqualsCanonicalizing($expected_roles, $token_info['roles']);
|
||||
|
||||
// 3. Test that limit_user_access and grant_user_access work together.
|
||||
$this->client->set('grant_user_access', TRUE);
|
||||
$this->client->set('limit_user_access', TRUE);
|
||||
$this->client->save();
|
||||
$requested_roles = [];
|
||||
$expected_roles = ['scope_1', 'authenticated'];
|
||||
// Check the token.
|
||||
$access_token = $this->getAccessToken($requested_roles);
|
||||
$token_info = $this->getTokenInfo($access_token);
|
||||
$this->assertEquals($this->user->id(), $token_info['id']);
|
||||
$this->assertEqualsCanonicalizing($expected_roles, $token_info['roles']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the response from oauth/debug.
|
||||
*
|
||||
* @param string $access_token
|
||||
* The access_token to use for authentication.
|
||||
*
|
||||
* @return mixed
|
||||
* The JSON parsed response.
|
||||
*/
|
||||
protected function getTokenInfo($access_token) {
|
||||
$response = $this->get(
|
||||
$this->tokenDebugUrl,
|
||||
[
|
||||
'query' => ['_format' => 'json'],
|
||||
'headers' => [
|
||||
'Authorization' => 'Bearer ' . $access_token,
|
||||
],
|
||||
]
|
||||
);
|
||||
return Json::decode((string) $response->getBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get role IDs the client has.
|
||||
*
|
||||
* @return array
|
||||
* Array of role IDs.
|
||||
*/
|
||||
protected function getClientRoleIds() {
|
||||
return array_map(function ($role) {
|
||||
return $role['target_id'];
|
||||
}, $this->client->get('roles')->getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to grant roles to the client.
|
||||
*
|
||||
* @param array $role_ids
|
||||
* Role IDs to add.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\EntityStorageException
|
||||
*/
|
||||
protected function grantClientRoles(array $role_ids) {
|
||||
$roles = [];
|
||||
foreach ($role_ids as $id) {
|
||||
$roles[] = ['target_id' => $id];
|
||||
}
|
||||
$this->client->set('roles', $roles);
|
||||
$this->client->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an access token.
|
||||
*
|
||||
* @param array $scopes
|
||||
* The scopes.
|
||||
*
|
||||
* @return string
|
||||
* The access token.
|
||||
*/
|
||||
protected function getAccessToken(array $scopes = []) {
|
||||
$valid_payload = [
|
||||
'grant_type' => 'password',
|
||||
'client_id' => $this->client->get('client_id')->value,
|
||||
'client_secret' => $this->clientSecret,
|
||||
'username' => $this->user->getAccountName(),
|
||||
'password' => $this->user->pass_raw,
|
||||
];
|
||||
if (!empty($scopes)) {
|
||||
$valid_payload['scope'] = implode(' ', $scopes);
|
||||
}
|
||||
$response = $this->post($this->url, $valid_payload);
|
||||
$parsed_response = Json::decode((string) $response->getBody());
|
||||
|
||||
return $parsed_response['access_token'] ?? NULL;
|
||||
}
|
||||
|
||||
}
|
|
@ -42,6 +42,7 @@ class FarmApiTest extends KernelTestBase {
|
|||
'jsonapi',
|
||||
'jsonapi_extras',
|
||||
'log',
|
||||
'options',
|
||||
'serialization',
|
||||
'simple_oauth',
|
||||
'state_machine',
|
||||
|
|
|
@ -6,3 +6,4 @@ core_version_requirement: ^10
|
|||
dependencies:
|
||||
- entity:entity
|
||||
- farm:farm_api
|
||||
- simple_oauth_password_grant:simple_oauth_password_grant
|
||||
|
|
|
@ -6,25 +6,37 @@
|
|||
*/
|
||||
|
||||
use Drupal\consumers\Entity\Consumer;
|
||||
use Drupal\simple_oauth\Oauth2ScopeInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function farm_fieldkit_install() {
|
||||
|
||||
// Check for default role scopes.
|
||||
/** @var \Drupal\simple_oauth\Oauth2ScopeProviderInterface $scope_provider */
|
||||
$scope_provider = \Drupal::service('simple_oauth.oauth2_scope.provider');
|
||||
$scopes = $scope_provider->loadMultiple(['farm_manager', 'farm_worker']);
|
||||
$scope_ids = array_map(function (Oauth2ScopeInterface $scope) {
|
||||
return $scope->id();
|
||||
}, $scopes);
|
||||
|
||||
// Create a consumer for the farmOS Field Kit client.
|
||||
$fk_consumer = Consumer::create([
|
||||
'label' => 'Field Kit',
|
||||
'client_id' => 'fieldkit',
|
||||
'access_token_expiration' => 3600,
|
||||
'grant_types' => [
|
||||
'refresh_token',
|
||||
'password',
|
||||
],
|
||||
'scopes' => array_values($scope_ids),
|
||||
'redirect' => 'https://farmOS.app',
|
||||
'allowed_origins' => 'https://farmos.app',
|
||||
'owner_id' => '',
|
||||
'owner_id' => NULL,
|
||||
'secret' => NULL,
|
||||
'confidential' => FALSE,
|
||||
'third_party' => FALSE,
|
||||
'grant_user_access' => TRUE,
|
||||
'limit_user_access' => TRUE,
|
||||
'limit_requested_access' => FALSE,
|
||||
]);
|
||||
$fk_consumer->save();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Post update functions for farm_fieldkit module.
|
||||
*/
|
||||
|
||||
use Drupal\simple_oauth\Oauth2ScopeInterface;
|
||||
|
||||
/**
|
||||
* Enable simple oauth password grant.
|
||||
*/
|
||||
function farm_fieldkit_post_update_enable_password_grant(&$sandbox = NULL) {
|
||||
|
||||
// Enable password grant module.
|
||||
if (!\Drupal::service('module_handler')->moduleExists('simple_oauth_password_grant')) {
|
||||
\Drupal::service('module_installer')->install(['simple_oauth_password_grant']);
|
||||
}
|
||||
|
||||
// Check for default role scopes.
|
||||
/** @var \Drupal\simple_oauth\Oauth2ScopeProviderInterface $scope_provider */
|
||||
$scope_provider = \Drupal::service('simple_oauth.oauth2_scope.provider');
|
||||
$scopes = $scope_provider->loadMultiple(['farm_manager', 'farm_worker']);
|
||||
$scope_ids = array_map(function (Oauth2ScopeInterface $scope) {
|
||||
return $scope->id();
|
||||
}, $scopes);
|
||||
|
||||
// Update existing fieldkit consumer.
|
||||
$consumers = \Drupal::entityTypeManager()->getStorage('consumer')
|
||||
->loadByProperties(['client_id' => 'fieldkit']);
|
||||
if (!empty($consumers)) {
|
||||
/** @var \Drupal\consumers\Entity\ConsumerInterface $fieldkit */
|
||||
$fieldkit = reset($consumers);
|
||||
$fieldkit->set('user_id', NULL);
|
||||
$fieldkit->set('grant_types', ['refresh_token', 'password']);
|
||||
$fieldkit->set('scopes', array_values($scope_ids));
|
||||
$fieldkit->save();
|
||||
}
|
||||
}
|
|
@ -26,10 +26,24 @@ class OauthPasswordTest extends OauthTestBase {
|
|||
'simple_oauth',
|
||||
'text',
|
||||
'user',
|
||||
'farm_api',
|
||||
'farm_api_default_consumer',
|
||||
'farm_api_test',
|
||||
'farm_login',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Add support for password grant and password scope consumer.
|
||||
$this->client->get('grant_types')->appendItem('password');
|
||||
$this->client->set('scopes', ['test:password']);
|
||||
$this->client->save();
|
||||
$this->scope = 'test:password';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a valid Password grant using username and email.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Hooks implemented by the Farm Role Roles module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_oauth2_scope_info_alter().
|
||||
*/
|
||||
function farm_role_roles_oauth2_scope_info_alter(array &$scopes) {
|
||||
|
||||
// Enable the password grant for static role scopes.
|
||||
if (\Drupal::moduleHandler()->moduleExists('simple_oauth_password_grant')) {
|
||||
$target_scopes = [
|
||||
'farm_manager',
|
||||
'farm_worker',
|
||||
'farm_viewer',
|
||||
];
|
||||
foreach ($target_scopes as $scope_id) {
|
||||
if (isset($target_scopes[$scope_id])) {
|
||||
$scopes[$scope_id]['grant_types']['password'] = [
|
||||
'status' => TRUE,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
farm_manager:
|
||||
description: 'Grants access to the Farm Manager role.'
|
||||
umbrella: false
|
||||
grant_types:
|
||||
authorization_code:
|
||||
status: true
|
||||
client_credentials:
|
||||
status: true
|
||||
refresh_token:
|
||||
status: true
|
||||
granularity: 'role'
|
||||
role: 'farm_manager'
|
||||
farm_worker:
|
||||
description: 'Grants access to the Farm Worker role.'
|
||||
umbrella: false
|
||||
grant_types:
|
||||
authorization_code:
|
||||
status: true
|
||||
client_credentials:
|
||||
status: true
|
||||
refresh_token:
|
||||
status: true
|
||||
granularity: 'role'
|
||||
role: 'farm_worker'
|
||||
farm_viewer:
|
||||
description: 'Grants access to the Farm Viewer role.'
|
||||
umbrella: false
|
||||
grant_types:
|
||||
authorization_code:
|
||||
status: true
|
||||
client_credentials:
|
||||
status: true
|
||||
refresh_token:
|
||||
status: true
|
||||
granularity: 'role'
|
||||
role: 'farm_viewer'
|
Loading…
Reference in New Issue