clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
user.role.*.third_party.farm_role:
type: mapping
label: 'Farm Role'
mapping:
access:
type: mapping
label: 'Access config'
mapping:
config:
type: boolean
label: 'Grant config permissions'
entity:
type: mapping
label: 'Entity permissions'
mapping:
view all:
type: boolean
label: 'Grant view permissions for all entities.'
create all:
type: boolean
label: 'Grant create permissions for all entities.'
update all:
type: boolean
label: 'Grant update permissions for all entities.'
delete all:
type: boolean
label: 'Grant delete permissions for all entities.'
type:
type: sequence
label: 'Entity types'
sequence:
type: sequence
label: 'Entity type'
sequence:
type: sequence
label: 'Operation'
sequence:
type: string
label: 'Bundle'

View File

@@ -0,0 +1,5 @@
#user-admin-permissions .checkbox input.managed:before {
content: '\2713';
display: inline-block;
padding: 0 2px 5px 2px;
}

View File

@@ -0,0 +1,7 @@
name: farmOS Role
description: Provides a framework for managed roles and permissions.
type: module
package: farmOS
core_version_requirement: ^10
dependencies:
- drupal:user

View File

@@ -0,0 +1,4 @@
managed_role:
css:
theme:
css/managed-role.css: {}

View File

@@ -0,0 +1,7 @@
farm_role:
default_permissions:
- access content
- access user profiles
- change own username
config_permissions:
- access taxonomy overview

View File

@@ -0,0 +1,80 @@
<?php
/**
* @file
* Hooks implemented by the Farm Role module.
*/
use Drupal\Core\Form\FormStateInterface;
/**
* Implements hook_entity_type_alter().
*/
function farm_role_entity_type_alter(array &$entity_types) {
/** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
// Replace the storage handler class for Roles.
$entity_types['user_role']
->setHandlerClass('storage', 'Drupal\farm_role\FarmRoleStorage');
}
/**
* Implements hook_form_BASE_FORM_ID_alter().
*/
function farm_role_form_user_admin_permissions_alter(&$form, FormStateInterface $form_state, $form_id) {
// Attach managed role CSS.
$form['#attached']['library'][] = 'farm_role/managed_role';
// Get the managed role permissions service.
/** @var \Drupal\farm_role\ManagedRolePermissionsManagerInterface $managed_role_manager */
$managed_role_manager = \Drupal::service('plugin.manager.managed_role_permissions');
// Save a list of managed role IDs keyed by their index in the form.
$managed_roles = $managed_role_manager->getMangedRoles();
$managed_roles_indices = array_intersect(
array_keys($form['role_names']['#value']),
array_keys($managed_roles)
);
// Append '(managed)' to managed role labels in the table header.
foreach ($managed_roles_indices as $index => $role) {
// Offset by 1 for the first table column.
$offset = $index + 1;
// Build new label.
$label = $form['permissions']['#header'][$offset]['data'];
$new = $label . ' (' . t('managed') . ')';
// Set new label.
$form['permissions']['#header'][$offset]['data'] = $new;
}
// Get a list of permissions.
$permissions = \Drupal::service('user.permissions')->getPermissions();
$permission_names = array_keys($permissions);
// Iterate over each permission in the form.
foreach ($form['permissions'] as $name => $permission) {
// Only check permission arrays, skip high level form and wrapper elements.
if (in_array($name, $permission_names)) {
// Iterate over each role under the permission.
foreach (array_keys($permission) as $rid) {
// Disable the checkbox for all managed roles.
if (in_array($rid, $managed_roles_indices)) {
$form['permissions'][$name][$rid]['#disabled'] = TRUE;
// If the permission is enabled on the role, add CSS class.
if ($managed_role_manager->isPermissionInRole($name, $managed_roles[$rid])) {
$form['permissions'][$name][$rid]['#attributes']['class'][] = 'managed';
}
}
}
}
}
}

View File

@@ -0,0 +1,4 @@
services:
plugin.manager.managed_role_permissions:
class: Drupal\farm_role\ManagedRolePermissionsManager
arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@controller_resolver', '@entity_type.bundle.info', '@entity_type.manager']

View File

@@ -0,0 +1,25 @@
langcode: en
status: true
dependencies:
module:
- farm_role
- farm_settings
enforced:
module:
- farm_role_account_admin
third_party_settings:
farm_role:
access:
config: true
entity:
'view all': false
'create all': false
'update all': false
'delete all': false
id: farm_account_admin
label: 'Account Admin'
weight: 1
is_admin: false
permissions:
- 'administer farm settings'
- 'administer users'

View File

@@ -0,0 +1,7 @@
farm_role_account_admin.settings:
type: config_object
label: 'farmOS Account Admin Role settings'
mapping:
allow_peer_role_assignment:
type: boolean
label: 'Allow users with the Account Admin role to assign/revoke the Account Admin role.'

View File

@@ -0,0 +1,9 @@
name: farmOS Account Admin Role
description: Provides an Account Admin role for managing users.
type: module
package: farmOS
core_version_requirement: ^10
dependencies:
- farm:farm_role
- farm:farm_settings
- role_delegation:role_delegation

View File

@@ -0,0 +1,5 @@
farm_role_account_admin.settings:
base_route: farm_settings.settings_page
route_name: farm_role_account_admin.settings
title: 'Account Admin'
weight: 5

View File

@@ -0,0 +1,3 @@
farm_role_account_admin:
permission_callbacks:
- Drupal\farm_role_account_admin\AccountAdminPermissions::permissions

View File

@@ -0,0 +1,22 @@
<?php
/**
* @file
* Hooks implemented by the farmOS Account Admin Role module.
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Implements hook_ENTITY_TYPE_access().
*/
function farm_role_account_admin_user_access(EntityInterface $entity, $operation, AccountInterface $account) {
// Only user 1 can access user 1.
if ($entity->id() == 1 && $account->id() != 1) {
return AccessResult::forbidden();
}
return AccessResult::neutral();
}

View File

@@ -0,0 +1,2 @@
configure account admin role:
title: 'Configure Account Admin role'

View File

@@ -0,0 +1,7 @@
farm_role_account_admin.settings:
path: 'farm/settings/account-admin'
defaults:
_form: '\Drupal\farm_role_account_admin\Form\AccountAdminSettingsForm'
_title: 'Account Admin Role settings'
requirements:
_permission: 'configure account admin role'

View File

@@ -0,0 +1,89 @@
<?php
namespace Drupal\farm_role_account_admin;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\farm_role\ManagedRolePermissionsManagerInterface;
use Drupal\user\RoleInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Add permissions to the Account Admin role.
*/
class AccountAdminPermissions implements ContainerInjectionInterface {
/**
* The managed role permissions manager.
*
* @var \Drupal\farm_role\ManagedRolePermissionsManagerInterface
*/
protected $managedRolePermissionsManager;
/**
* The config factory service.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* Constructs an AccountAdminPermissions object.
*
* @param \Drupal\farm_role\ManagedRolePermissionsManagerInterface $managed_role_permissions_manager
* The managed role permissions manager.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
*/
public function __construct(ManagedRolePermissionsManagerInterface $managed_role_permissions_manager, ConfigFactoryInterface $config_factory) {
$this->managedRolePermissionsManager = $managed_role_permissions_manager;
$this->configFactory = $config_factory;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.managed_role_permissions'),
$container->get('config.factory'),
);
}
/**
* Add permissions to default farmOS roles.
*
* @param \Drupal\user\RoleInterface $role
* The role to add permissions to.
*
* @return array
* An array of permission strings.
*/
public function permissions(RoleInterface $role) {
$perms = [];
// Add permissions to the farm_account_admin role.
if ($role->id() == 'farm_account_admin') {
// Load the module settings.
$settings = $this->configFactory->get('farm_role_account_admin.settings');
// Grant the ability to assign managed farmOS roles.
$roles = $this->managedRolePermissionsManager->getMangedRoles();
foreach ($roles as $role) {
// Do not allow assigning the "Account Admin" role if
// allow_peer_role_assignment is disabled.
if ($role->id() == 'farm_account_admin' && !$settings->get('allow_peer_role_assignment', FALSE)) {
continue;
}
// Add permission to assign the role.
$perms[] = 'assign ' . $role->id() . ' role';
}
}
return $perms;
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace Drupal\farm_role_account_admin\Form;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a settings form for the Account Admin Role module.
*/
class AccountAdminSettingsForm extends ConfigFormbase {
/**
* Config settings.
*
* @var string
*/
const SETTINGS = 'farm_role_account_admin.settings';
/**
* The cache tags invalidator.
*
* @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface
*/
protected $cacheTagsInvalidator;
/**
* Constructs a \Drupal\system\ConfigFormBase object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The factory for configuration objects.
* @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator
* The cache tags invalidator.
*/
public function __construct(ConfigFactoryInterface $config_factory, CacheTagsInvalidatorInterface $cache_tags_invalidator) {
$this->setConfigFactory($config_factory);
$this->cacheTagsInvalidator = $cache_tags_invalidator;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('cache_tags.invalidator'),
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'farm_role_account_admin_settings';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return [
static::SETTINGS,
];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateinterface $form_state) {
$config = $this->config(static::SETTINGS);
$form['allow_peer_role_assignment'] = [
'#type' => 'checkbox',
'#title' => $this->t('Allow peer role assignment'),
'#description' => $this->t('Allow users with the Account Admin role to assign/revoke the Account Admin role.'),
'#default_value' => $config->get('allow_peer_role_assignment'),
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->configFactory->getEditable(static::SETTINGS)
->set('allow_peer_role_assignment', $form_state->getValue('allow_peer_role_assignment'))
->save();
// Invalidate the user_role:farm_account_admin cache tag.
$this->cacheTagsInvalidator->invalidateTags(['user_role:farm_account_admin']);
parent::submitForm($form, $form_state);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Drupal\Tests\farm_role_account_admin\Functional;
use Drupal\Tests\farm_test\Functional\FarmBrowserTestBase;
/**
* Tests access to user 1.
*
* @group farm
*/
class UserAccessTest extends FarmBrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'farm_role_account_admin',
];
/**
* Test user 1 access.
*/
public function testUser1Access() {
// Create and login a user with farm_account_admin role.
$user = $this->createUser();
$user->addRole('farm_account_admin');
$user->save();
$this->drupalLogin($user);
// Confirm that the user cannot access user 1.
$this->drupalGet('user/1');
$this->assertSession()->statusCodeEquals(403);
$this->drupalGet('user/1/edit');
$this->assertSession()->statusCodeEquals(403);
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace Drupal\Tests\farm_role_account_admin\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\user\Traits\UserCreationTrait;
/**
* Tests for Account Admin role permissions.
*
* @group farm
*/
class AccountAdminPermissionsTest extends KernelTestBase {
use UserCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'farm_role',
'farm_role_account_admin',
'farm_role_roles',
'farm_settings',
'role_delegation',
'system',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp():void {
parent::setUp();
$this->installEntitySchema('user');
$this->installSchema('system', ['sequences']);
$this->installConfig(['farm_role_account_admin', 'farm_role_roles']);
}
/**
* Test that the Account Admin role gets appropriate permissions.
*/
public function testAccountAdminPermissions() {
// Create a user.
$user = $this->setUpCurrentUser([], [], FALSE);
// List Account Admin permissions.
$account_admin_permissions = [
'administer farm settings',
'administer users',
'assign farm_manager role',
'assign farm_worker role',
'assign farm_viewer role',
];
// Ensure the user does not have permissions.
foreach ($account_admin_permissions as $permission) {
$this->assertFalse($user->hasPermission($permission));
}
// Add Account Admin role.
$user->addRole('farm_account_admin');
// Ensure the user has permissions.
foreach ($account_admin_permissions as $permission) {
$this->assertTrue($user->hasPermission($permission));
}
// Ensure the user does not have the "assign farm_account_admin role"
// permission.
$this->assertFalse($user->hasPermission('assign farm_account_admin role'));
// Enable the allow_peer_role_assignment setting.
$settings = \Drupal::configFactory()->getEditable('farm_role_account_admin.settings');
$settings->set('allow_peer_role_assignment', TRUE);
$settings->save();
// Rebuild the container so the configuration change takes effect.
$kernel = \Drupal::service('kernel');
$kernel->invalidateContainer();
$kernel->rebuildContainer();
// Ensure the user has the "assign farm_account_admin role" permission.
$this->assertTrue($user->hasPermission('assign farm_account_admin role'));
}
}

View File

@@ -0,0 +1,22 @@
langcode: en
status: true
dependencies:
enforced:
module:
- farm_role_roles
module:
- farm_role
id: farm_manager
label: 'Manager'
weight: 1
is_admin: false
permissions: { }
third_party_settings:
farm_role:
access:
config: true
entity:
view all: true
create all: true
update all: true
delete all: true

View File

@@ -0,0 +1,22 @@
langcode: en
status: true
dependencies:
enforced:
module:
- farm_role_roles
module:
- farm_role
id: farm_viewer
label: 'Viewer'
weight: 1
is_admin: false
permissions: { }
third_party_settings:
farm_role:
access:
config: false
entity:
view all: true
create all: false
update all: false
delete all: false

View File

@@ -0,0 +1,22 @@
langcode: en
status: true
dependencies:
enforced:
module:
- farm_role_roles
module:
- farm_role
id: farm_worker
label: 'Worker'
weight: 1
is_admin: false
permissions: { }
third_party_settings:
farm_role:
access:
config: false
entity:
view all: true
create all: true
update all: true
delete all: true

View File

@@ -0,0 +1,7 @@
name: farmOS Default Roles
description: 'Provides default roles: Manager, Worker, Viewer.'
type: module
package: farmOS Defaults
core_version_requirement: ^10
dependencies:
- farm:farm_role

View File

@@ -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($scopes[$scope_id])) {
$scopes[$scope_id]['grant_types']['password'] = [
'status' => TRUE,
];
}
}
}
}

View File

@@ -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'

View File

@@ -0,0 +1,25 @@
<?php
namespace Drupal\farm_role;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use Symfony\Component\DependencyInjection\Reference;
/**
* Override the permission_checker service with our own class.
*/
class FarmRoleServiceProvider extends ServiceProviderBase implements ServiceProviderInterface {
/**
* {@inheritdoc}
*/
public function alter(ContainerBuilder $container) {
$definition = $container->getDefinition('permission_checker');
$definition->addArgument(new Reference('entity_type.manager'));
$definition->addArgument(new Reference('plugin.manager.managed_role_permissions'));
$definition->setClass('Drupal\farm_role\ManagedRolePermissionChecker');
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace Drupal\farm_role;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\user\RoleStorage;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* FarmRoleStorage.
*
* Extend the RoleStorage class to include permissions defined with managed
* farm roles.
*
* @ingroup farm
*/
class FarmRoleStorage extends RoleStorage {
/**
* The managed role permissions manager interface.
*
* @var \Drupal\farm_role\ManagedRolePermissionsManagerInterface
*/
protected $managedRolePermissionsManager;
/**
* Constructs a ConfigEntityStorage object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
* @param \Drupal\Component\Uuid\UuidInterface $uuid_service
* The UUID service.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface $memory_cache
* The memory cache backend.
* @param \Drupal\farm_role\ManagedRolePermissionsManagerInterface $managed_role_permissions_manager
* The managed role permissions manager.
*/
public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, MemoryCacheInterface $memory_cache, ManagedRolePermissionsManagerInterface $managed_role_permissions_manager) {
parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager, $memory_cache);
$this->managedRolePermissionsManager = $managed_role_permissions_manager;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('config.factory'),
$container->get('uuid'),
$container->get('language_manager'),
$container->get('entity.memory_cache'),
$container->get('plugin.manager.managed_role_permissions')
);
}
/**
* {@inheritdoc}
*/
public function isPermissionInRoles($permission, array $rids) {
// @todo Refactor if/when simple_oauth stops using this.
// This is currently only used by simple_oauth module's Oauth2ScopeProvider,
// since Drupal core stopped using RoleStorage::isPermissionInRoles() in
// https://www.drupal.org/project/drupal/issues/3376846.
// Check if the permission is defined directly on the role.
$has_permission = parent::isPermissionInRoles($permission, $rids);
// Else check if the permission is included via farm_role rules.
if (!$has_permission) {
foreach ($this->loadMultiple($rids) as $role) {
/** @var \Drupal\user\RoleInterface $role */
$has_permission = $this->managedRolePermissionsManager->isPermissionInRole($permission, $role);
if ($has_permission) {
break;
}
}
}
return $has_permission;
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Drupal\farm_role;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccessPolicyProcessorInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\PermissionChecker;
/**
* Checks permissions for an account.
*/
class ManagedRolePermissionChecker extends PermissionChecker {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The managed role permissions manager.
*
* @var \Drupal\farm_role\ManagedRolePermissionsManagerInterface
*/
protected $managedRolePermissionsManager;
/**
* Class constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface|\Drupal\Core\Session\AccessPolicyProcessorInterface $processor
* The access policy processor.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\farm_role\ManagedRolePermissionsManagerInterface $managed_role_permissions_manager
* The managed role permissions manager.
*/
public function __construct(protected EntityTypeManagerInterface|AccessPolicyProcessorInterface $processor, EntityTypeManagerInterface $entity_type_manager, ManagedRolePermissionsManagerInterface $managed_role_permissions_manager) {
parent::__construct($processor);
$this->entityTypeManager = $entity_type_manager;
$this->managedRolePermissionsManager = $managed_role_permissions_manager;
}
/**
* {@inheritdoc}
*/
public function hasPermission(string $permission, AccountInterface $account): bool {
$has_permission = parent::hasPermission($permission, $account);
// Check if the permission is included via farm_role rules.
if (!$has_permission) {
$managed_roles = $this->managedRolePermissionsManager->getMangedRoles();
foreach ($account->getRoles() as $role_id) {
if (in_array($role_id, array_keys($managed_roles))) {
/** @var \Drupal\user\RoleInterface $role */
$role = $this->entityTypeManager->getStorage('user_role')->load($role_id);
$has_permission = $this->managedRolePermissionsManager->isPermissionInRole($permission, $role);
if ($has_permission) {
break;
}
}
}
}
return $has_permission;
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Drupal\farm_role;
use Drupal\Core\Plugin\PluginBase;
/**
* Default class for the ManagedRolePermissions plugin.
*
* @internal
*
* @ingroup farm
*/
class ManagedRolePermissions extends PluginBase implements ManagedRolePermissionsInterface {
/**
* {@inheritdoc}
*/
public function getDefaultPermissions() {
return (array) $this->pluginDefinition['default_permissions'];
}
/**
* {@inheritdoc}
*/
public function getConfigPermissions() {
return (array) $this->pluginDefinition['config_permissions'];
}
/**
* {@inheritdoc}
*/
public function getPermissionCallbacks() {
return (array) $this->pluginDefinition['permission_callbacks'];
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Drupal\farm_role;
/**
* Provides an interface for defining ManagedRolePermissions plugins.
*
* @internal
*
* @ingroup farm
*/
interface ManagedRolePermissionsInterface {
/**
* Returns the default permissions.
*
* @return array
* Array of permission strings.
*/
public function getDefaultPermissions();
/**
* Returns the config permissions.
*
* @return array
* Array of permission strings.
*/
public function getConfigPermissions();
/**
* Returns permission callback strings.
*
* @return array
* Array of function callbacks in controller syntax, see
* \Drupal\Core\Controller\ControllerResolver
*/
public function getPermissionCallbacks();
}

View File

@@ -0,0 +1,368 @@
<?php
namespace Drupal\farm_role;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Controller\ControllerResolverInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
use Drupal\Core\Plugin\Discovery\YamlDiscovery;
use Drupal\user\RoleInterface;
/**
* ManagedRolePermissions Plugin Manager.
*
* @internal
*
* @ingroup farm
*/
class ManagedRolePermissionsManager extends DefaultPluginManager implements ManagedRolePermissionsManagerInterface {
/**
* Controller resolver service.
*
* @var \Drupal\Core\Controller\ControllerResolverInterface
*/
protected $controllerResolver;
/**
* Entity type bundle info service.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
*/
protected $entityTypeBundleInfo;
/**
* The entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Default values for each FarmRolePermissions plugin.
*
* @var array
*/
protected $defaults = [
'class' => 'Drupal\farm_role\ManagedRolePermissions',
'default_permissions' => [],
'config_permissions' => [],
'permission_callbacks' => [],
];
/**
* An array of role permissions keyed by role ID.
*
* @var array
*/
protected $rolePermissions;
/**
* Constructs a ManagedRolePermissionsManager object.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
* @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
* The controller resolver service.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle info service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager service.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, ControllerResolverInterface $controller_resolver, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct(
'Plugin/ManagedRolePermissions',
$namespaces,
$module_handler
);
$this->controllerResolver = $controller_resolver;
$this->entityTypeBundleInfo = $entity_type_bundle_info;
$this->entityTypeManager = $entity_type_manager;
$this->rolePermissions = [];
$this->alterInfo('managed_role_permissions_info');
$this->setCacheBackend($cache_backend, 'managed_role_permissions_plugins');
}
/**
* {@inheritdoc}
*/
protected function getDiscovery() {
if (!isset($this->discovery)) {
$discovery = new YamlDiscovery('managed_role_permissions', $this->moduleHandler->getModuleDirectories());
$this->discovery = new ContainerDerivativeDiscoveryDecorator($discovery);
}
return $this->discovery;
}
/**
* {@inheritdoc}
*/
public function getMangedRoles(): array {
/** @var \Drupal\user\RoleInterface[] $roles */
$roles = $this->entityTypeManager->getStorage('user_role')->loadMultiple();
return array_filter($roles, function ($role) {
return $this->isManagedRole($role);
});
}
/**
* {@inheritdoc}
*/
public function isManagedRole(RoleInterface $role) {
return $role->getThirdPartySetting('farm_role', 'access', FALSE);
}
/**
* {@inheritdoc}
*/
public function isPermissionInRole($permission, RoleInterface $role) {
// Check if permissions have been built for the specified role.
if (isset($this->rolePermissions[$role->id()])) {
$permissions = $this->rolePermissions[$role->id()];
}
else {
// Build permissions for the role.
$permissions = $this->getManagedPermissionsForRole($role);
}
return in_array($permission, $permissions);
}
/**
* Helper function to build managed permissions for managed roles.
*
* @param \Drupal\user\RoleInterface $role
* The role to load permissions for.
*
* @return array
* Array of permissions for the managed role.
*/
protected function getManagedPermissionsForRole(RoleInterface $role) {
// Start list of permissions.
$perms = [];
// If the role does not have farm_role settings, bail.
if (!$this->isManagedRole($role)) {
return $perms;
}
// Load the Role's third party farm_role access settings.
$access_settings = $role->getThirdPartySetting('farm_role', 'access');
// Get all plugin definitions.
$plugin_definitions = $this->getDefinitions();
// Include permissions defined by plugin_definitions config entities.
foreach ($plugin_definitions as $plugin_definition) {
// Create an instance of the plugin.
$plugin = $this->createInstance($plugin_definition['id']);
// Always include default permissions.
$default_perms = $plugin->getDefaultPermissions();
$perms = array_merge($perms, $default_perms);
// Include config permissions if the role has config access.
if (!empty($access_settings['config'])) {
$config_perms = $plugin->getConfigPermissions();
$perms = array_merge($perms, $config_perms);
}
// Include permissions defined by permission callbacks.
foreach ($plugin->getPermissionCallbacks() as $permission_callback) {
// Resolve callback name and call the function. Pass the Role object as
// a parameter so the callback can access the role's settings.
$callback = $this->controllerResolver->getControllerFromDefinition($permission_callback);
if ($callback_permissions = call_user_func($callback, $role)) {
// Add any callback permissions to the array of permissions.
$perms = array_merge($perms, $callback_permissions);
}
}
}
// Load the access.entity settings. Use an empty array if not provided.
$entity_settings = $access_settings['entity'] ?? [];
// Managed entity types.
$managed_entity_types = [
'asset',
'data_stream',
'log',
'plan',
'taxonomy_term',
'quantity',
];
// Start an array of permission rules. This will be a multi-dimensional
// array that ultimately defines which permission strings will be given to
// the managed role. Each entity type's operations can be granted to
// individual bundles or all bundles by providing 'all' as a bundle name.
// Once built, the array will contain the following structure:
// $permission_rules[$entity_types][$operations][$bundles];.
$permission_rules = [];
// Build permission rules for each entity type.
foreach ($managed_entity_types as $entity_type) {
// Create empty array of operations for the entity_type.
$permission_rules[$entity_type] = [];
// Different entity types support different operations. Allow each entity
// type to map the high level 'create_all', 'view all', 'update all' and
// 'delete_all' operations to their specific operations.
switch ($entity_type) {
// Entity types with EntityOwnerTrait and RevisionLogEntityTrait have
// additional permissions for view, update and delete operations:
// Owner adds "operation any bundle" or "operation own bundle".
// Revision adds "operation all bundle revisions".
case 'asset':
case 'log':
case 'plan':
case 'quantity':
// Create.
if (!empty($entity_settings['create all'])) {
$permission_rules[$entity_type]['create'] = ['all'];
}
// View.
if (!empty($entity_settings['view all'])) {
$perms[] = 'view any ' . $entity_type;
$perms[] = 'view own ' . $entity_type;
$perms[] = 'view all ' . $entity_type . ' revisions';
$permission_rules[$entity_type]['view any'] = ['all'];
$permission_rules[$entity_type]['view own'] = ['all'];
}
// Update.
if (!empty($entity_settings['update all'])) {
$perms[] = 'revert all ' . $entity_type . ' revisions';
$permission_rules[$entity_type]['update any'] = ['all'];
$permission_rules[$entity_type]['update own'] = ['all'];
}
// Delete.
if (!empty($entity_settings['delete all'])) {
$permission_rules[$entity_type]['delete any'] = ['all'];
$permission_rules[$entity_type]['delete own'] = ['all'];
}
break;
// Entity types with basic CRUD permissions.
case 'data_stream':
// Create.
if (!empty($entity_settings['create all'])) {
$permission_rules[$entity_type]['create'] = ['all'];
}
// View.
if (!empty($entity_settings['view all'])) {
$perms[] = 'view ' . $entity_type;
$permission_rules[$entity_type]['view'] = ['all'];
}
// Update.
if (!empty($entity_settings['update all'])) {
$permission_rules[$entity_type]['update'] = ['all'];
}
// Delete.
if (!empty($entity_settings['delete all'])) {
$permission_rules[$entity_type]['delete'] = ['all'];
}
break;
// Taxonomy terms are a unique case for two reasons:
// View access is determined by the "access content" permission
// and "edit" is the name for the update operation permission.
case 'taxonomy_term':
// Create.
if (!empty($entity_settings['create all'])) {
$permission_rules[$entity_type]['create'] = ['all'];
}
// Update.
if (!empty($entity_settings['update all'])) {
$permission_rules[$entity_type]['edit'] = ['all'];
}
// Delete.
if (!empty($entity_settings['delete all'])) {
$permission_rules[$entity_type]['delete'] = ['all'];
}
break;
}
}
// Include granular entity + bundle permissions if defined on the role.
if (!empty($entity_settings['type'])) {
// Recursively merge granular permissions into the permission_rules array.
$permission_rules = array_merge_recursive(
$permission_rules,
$entity_settings['type']
);
}
// Build permissions for each entity type as defined in the
// permission_rules array.
foreach ($permission_rules as $entity_type => $operations) {
// Load all bundles of this entity type.
$entity_bundle_info = $this->entityTypeBundleInfo->getBundleInfo($entity_type);
$entity_bundles = array_keys($entity_bundle_info);
// Build permissions for each operation associated with the entity.
foreach ($operations as $operation => $allowed_bundles) {
// Build operation permission for each bundle in the entity.
foreach ($entity_bundles as $bundle) {
// Build the operation permission string for each entity type. The
// permission syntax may be different for each entity type so build
// permission strings according to the entity type. Only add
// permissions if the operation explicitly lists the bundle name or
// specifies 'all' bundles.
switch ($entity_type) {
case 'asset':
case 'log':
case 'plan':
case 'quantity':
case 'data_stream':
if (array_intersect(['all', $bundle], $allowed_bundles)) {
$perms[] = $operation . ' ' . $bundle . ' ' . $entity_type;
}
break;
case 'taxonomy_term':
if (array_intersect(['all', $bundle], $allowed_bundles)) {
$perms[] = $operation . ' terms in ' . $bundle;
}
break;
}
}
}
}
$this->rolePermissions[$role->id()] = $perms;
return $perms;
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Drupal\farm_role;
use Drupal\user\RoleInterface;
/**
* Interface for the ManagedRolePermissionsManager.
*
* @internal
*
* @ingroup farm
*/
interface ManagedRolePermissionsManagerInterface {
/**
* Returns an array of managed roles.
*
* @return \Drupal\user\RoleInterface[]
* An array of managed roles.
*/
public function getMangedRoles(): array;
/**
* Checks if the role is a managed role.
*
* @param \Drupal\user\RoleInterface $role
* The Role to check.
*
* @return bool
* If the role is a managed role.
*/
public function isManagedRole(RoleInterface $role);
/**
* Checks if the role has a specified permission.
*
* @param string $permission
* The permission string to check.
* @param \Drupal\user\RoleInterface $role
* The Role to check.
*
* @return bool
* If the role has the permission.
*/
public function isPermissionInRole($permission, RoleInterface $role);
}

View File

@@ -0,0 +1,8 @@
langcode: en
status: true
id: harvest
label: Harvest
description: ''
name_pattern: 'Harvest log [log:id]'
workflow: log_default
new_revision: true

View File

@@ -0,0 +1,8 @@
langcode: en
status: true
id: observation
label: Observation
description: ''
name_pattern: 'Observation log [log:id]'
workflow: log_default
new_revision: true

View File

@@ -0,0 +1,30 @@
# Test role that only has granular entity permissions.
langcode: en
status: true
dependencies: { }
id: farm_test
label: 'Test role for farm_role'
weight: 1
is_admin: false
permissions: { }
third_party_settings:
farm_role:
access:
config: false
entity:
view all: false
create all: false
update all: false
delete all: false
type:
log:
view any:
- all
create:
- observation
update any:
- observation
update own:
- all
delete own:
- all

View File

@@ -0,0 +1,17 @@
# Test role that has typical manger access.
langcode: en
status: true
id: farm_test_manager
label: 'Test manager role for farm_role'
weight: 1
is_admin: false
permissions: { }
third_party_settings:
farm_role:
access:
config: true
entity:
view all: true
create all: true
update all: true
delete all: true

View File

@@ -0,0 +1,9 @@
name: 'Farm Role module tests'
type: module
description: 'Support module for farm_role testing.'
package: Testing
core_version_requirement: ^10
dependencies:
- log:log
- farm:farm_role

View File

@@ -0,0 +1,8 @@
# Simple test permissions to add to managed roles.
test:
default_permissions:
- test default permission
config_permissions:
- test config access permission
permission_callbacks:
- Drupal\farm_role_test\CustomTestPermissions::permissions

View File

@@ -0,0 +1,47 @@
<?php
namespace Drupal\farm_role_test;
use Drupal\user\RoleInterface;
/**
* A permission callback used for testing.
*/
class CustomTestPermissions {
/**
* Grant permissions to the specified role.
*
* @param \Drupal\user\RoleInterface $role
* The role to grant permissions.
*
* @return array
* Array of permission strings.
*/
public function permissions(RoleInterface $role) {
// Array of permissions to return.
$perms = [];
// Default callback permission.
$perms[] = 'default callback permission';
// Add permissions based on role name.
if ($role->id() == 'farm_test_manager') {
$perms[] = 'my manager permission';
}
// Get the farm_role third party settings from the Role entity.
$access_settings = $role->getThirdPartySetting('farm_role', 'access');
$entity_settings = $access_settings['entity'] ?: [];
// Only add permissions if `update all` and `delete all` are true.
if (!empty($entity_settings['update all'] && $entity_settings['delete all'])) {
$perms[] = 'recover all permission';
}
// Return array of permissions.
return $perms;
}
}

View File

@@ -0,0 +1,222 @@
<?php
namespace Drupal\Tests\farm_role\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\user\Entity\Role;
/**
* Tests for Managed Role permissions.
*
* @group farm
*/
class ManagedRolePermissionsTest extends KernelTestBase {
use UserCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'system',
'user',
'log',
'state_machine',
'farm_role',
'farm_role_test',
];
/**
* {@inheritdoc}
*/
protected function setUp():void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('log');
$this->installSchema('system', ['sequences']);
$this->installConfig(['farm_role', 'farm_role_test', 'log']);
}
/**
* Test that managed roles get default permissions.
*/
public function testManagedRoleDefaultAccess() {
// Create a user.
$user = $this->setUpCurrentUser([], [], FALSE);
// Ensure the user does not have default permissions.
$this->assertFalse($user->hasPermission('test default permission'));
// Add farm_test role.
$user->addRole('farm_test');
// Ensure the user has default permissions.
$this->assertTrue($user->hasPermission('test default permission'));
}
/**
* Test that managed roles with config access get config permissions.
*/
public function testManagedRoleConfigAccess() {
/** @var \Drupal\user\RoleInterface $role */
$role = Role::load('farm_test_manager');
// Test that the role's config setting is TRUE.
$this->assertNotEmpty($role->getThirdPartySetting('farm_role', 'access', FALSE));
$access_settings = $role->getThirdPartySetting('farm_role', 'access');
$this->assertTrue(!empty($access_settings['config']));
// Create a user.
$user = $this->setUpCurrentUser([], [], FALSE);
// Ensure the user does not have config access permissions.
$this->assertFalse($user->hasPermission('test config access permission'));
// Ensure the farm_test does not provide config access permissions.
$user->addRole('farm_test');
$this->assertFalse($user->hasPermission('test config access permission'));
// Ensure the farm_test_manager role provides config access permissions.
$user->addRole('farm_test_manager');
$this->assertTrue($user->hasPermission('test config access permission'));
}
/**
* Test that managed roles get permissions provided by callbacks.
*/
public function testManagedRolePermissionCallbacks() {
// Create a user.
$user = $this->setUpCurrentUser([], [], FALSE);
// Ensure the user does not include permission callback.
$this->assertFalse($user->hasPermission('default callback permission'));
// Ensure the farm_test includes valid callbacks permissions.
$user->addRole('farm_test');
$this->assertTrue($user->hasPermission('default callback permission'));
$this->assertFalse($user->hasPermission('my manager permission'));
$this->assertFalse($user->hasPermission('recover all permission'));
// Ensure the farm_test_manager role includes valid callback perms.
$user->addRole('farm_test_manager');
$this->assertTrue($user->hasPermission('default callback permission'));
$this->assertTrue($user->hasPermission('my manager permission'));
$this->assertTrue($user->hasPermission('recover all permission'));
}
/**
* Test that managed roles get high level operation permissions.
*/
public function testManagedRoleHighLevelOperations() {
/** @var \Drupal\user\RoleInterface $role */
$role = Role::load('farm_test_manager');
// Get the roles entity access settings.
$this->assertNotEmpty($role->getThirdPartySetting('farm_role', 'access', FALSE));
$access_settings = $role->getThirdPartySetting('farm_role', 'access');
$entity_settings = $access_settings['entity'];
// List of high level operations.
$operations = [
'view all',
'create all',
'update all',
'delete all',
];
// Ensure that the role has access to each high level operation.
foreach ($operations as $operation) {
$this->assertTrue(!empty($entity_settings[$operation]));
}
// Log bundles.
$log_bundles = ['observation', 'harvest'];
// Log entity operation prefixes.
$operation_prefixes = [
'view own',
'view any',
'create',
'update own',
'update any',
'delete own',
'delete any',
];
// Create a user.
$user = $this->setUpCurrentUser([], [], FALSE);
// Ensure the user does not have permissions to logs.
foreach ($operation_prefixes as $prefix) {
foreach ($log_bundles as $bundle) {
$this->assertFalse($user->hasPermission($prefix . ' ' . $bundle . ' log'));
}
}
// Ensure farm_test_manager provides permissions for "default" log type.
$user->addRole('farm_test_manager');
foreach ($operation_prefixes as $prefix) {
foreach ($log_bundles as $bundle) {
$this->assertTrue($user->hasPermission($prefix . ' ' . $bundle . ' log'));
}
}
}
/**
* Test that managed roles get granular entity permissions.
*/
public function testManagedRoleGranularPermissions() {
/** @var \Drupal\user\RoleInterface $role */
$role = Role::load('farm_test');
// Get the roles entity type access settings.
$this->assertNotEmpty($role->getThirdPartySetting('farm_role', 'access', FALSE));
$access_settings = $role->getThirdPartySetting('farm_role', 'access');
$entity_settings = $access_settings['entity'];
$log_settings = $entity_settings['type']['log'];
// Ensure the farm_test role's granular access is configured correctly.
// View all log types.
$this->assertTrue(in_array('all', $log_settings['view any']));
// Create all log types.
$this->assertTrue(in_array('observation', $log_settings['create']));
// Update any observation log.
$this->assertTrue(in_array('observation', $log_settings['update any']));
// Update own log types.
$this->assertTrue(in_array('all', $log_settings['update own']));
// Delete own log.
$this->assertTrue(in_array('all', $log_settings['delete own']));
// Create a user.
$user = $this->setUpCurrentUser([], [], FALSE);
$user->addRole('farm_test');
// Log bundles.
$log_bundles = ['observation', 'harvest'];
// Test that the user only has permissions to specific log bundles
// as defined by the farm_test role.
foreach ($log_settings as $operation => $granted_bundles) {
foreach ($log_bundles as $bundle) {
$should_have_permission = in_array($bundle, $granted_bundles);
if (in_array('all', $granted_bundles)) {
$should_have_permission = TRUE;
}
$has_permission = $user->hasPermission($operation . ' ' . $bundle . ' log');
$this->assertEquals($should_have_permission, $has_permission);
}
}
}
}