Problem/Motivation

While using the module with Scope Granularity I find them kinda to scoped.
One role or one permission is very very small and not workable in a real environment.
With almost al my deployments, we do a custom extension where we have multiple roles/rights.

Are you open to accept that we make the selection to use multiples
Or do i oversee a security kinda impact?

CommentFileSizeAuthor
#2 multiple-permissions-per-scope.png104.21 KBsander wemagine

Comments

makertimswis created an issue. See original summary.

sander wemagine’s picture

StatusFileSize
new104.21 KB

I am also running into the same limitation. Would be very nice if we can map multiple permissions to one scope.

How did you fix this? I have created a custom ScopeGranularity plugin for this. For my use-case via Static Scopes it's fully functional and easy to manage. If you use dynamic scopes via the GUI it's a bit less easy to work with since it's a textarea, but it will do the job.

Example of static scope YML file:

'organisation:update':
  description: 'Update organisation.'
  umbrella: false
  grant_types:
    refresh_token:
      status: true
      description: ''
    authorization_code:
      status: true
      description: ''
  parent: organisation
  granularity_id: permission_set
  granularity_configuration:
    permissions:
      - 'update any organisation'
      - 'update assigned organisation'

The ScopeGranularity plugin:

<?php

declare(strict_types=1);

namespace Drupal\my_module\Plugin\ScopeGranularity;

use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\simple_oauth\Attribute\ScopeGranularity;
use Drupal\simple_oauth\Plugin\ScopeGranularityBase;
use Drupal\user\PermissionHandlerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * This makes it possible to have multiple permissions mapped to one scope.
 */
#[ScopeGranularity(
  'permission_set',
  new TranslatableMarkup('Permission set'),
)]
class PermissionSet extends ScopeGranularityBase implements ContainerFactoryPluginInterface {

  public function __construct(
    array $configuration,
    string $pluginId,
    array $pluginDefinition,
    protected PermissionHandlerInterface $permissionHandler,
    protected ModuleExtensionList $moduleList,
  ) {
    parent::__construct($configuration, $pluginId, $pluginDefinition);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new self(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('user.permissions'),
      $container->get('extension.list.module'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
      'permissions' => [],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfiguration(array $configuration): void {
    if (empty($configuration['permissions']) || !is_array($configuration['permissions'])) {
      throw new PluginException('The "permissions" configuration value is required and must be an array.');
    }

    $permissions = $this->permissionHandler->getPermissions();
    foreach ($configuration['permissions'] as $permission) {
      if (!is_string($permission) || $permission === '') {
        throw new PluginException('Each permission must be a non-empty string.');
      }
      if (!array_key_exists($permission, $permissions)) {
        throw new PluginException(sprintf('Permission "%s" is undefined.', $permission));
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function hasPermission(string $permission): bool {
    return in_array($permission, $this->getConfiguration()['permissions'], TRUE);
  }

  /**
   * {@inheritdoc}
   */
  public function getPermissions(): array {
    return $this->getConfiguration()['permissions'];
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    $form['permissions'] = [
      '#type' => 'textarea',
      '#title' => $this->t('Permissions'),
      '#description' => $this->t('One permission machine name per line.'),
      '#default_value' => implode("\n", $this->getConfiguration()['permissions']),
      '#required' => TRUE,
    ];
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    $raw = (string) $form_state->getValue('permissions', '');
    $permissions = preg_split('/\R+/', trim($raw)) ?: [];
    $permissions = array_values(array_filter(array_map('trim', $permissions)));
    $this->setConfiguration(['permissions' => $permissions]);
  }

}