Loading commerce_license.services.yml +5 −0 Changes for commerce_license.services.yml: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -63,3 +63,8 @@ services: commerce_license.cron: class: \Drupal\commerce_license\Cron arguments: ['@entity_type.manager', '@datetime.time'] commerce_license.license_admin_route_subscriber: class: Drupal\commerce_license\EventSubscriber\LicenseAdminRouteSubscriber tags: - { name: event_subscriber } src/Entity/License.php +6 −1 Changes for src/Entity/License.php: 6 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -35,7 +35,7 @@ use Drupal\Core\StringTranslation\StringTranslationTrait; * bundle_label = @Translation("License type"), * bundle_plugin_type = "commerce_license_type", * handlers = { * "access" = "\Drupal\entity\UncacheableEntityAccessControlHandler", * "access" = "\Drupal\commerce_license\LicenseAccessControlHandler", * "event" = "Drupal\commerce_license\Event\LicenseEvent", * "permission_provider" = "\Drupal\commerce_license\LicensePermissionProvider", * "list_builder" = "Drupal\commerce_license\LicenseListBuilder", Loading Loading @@ -74,6 +74,11 @@ use Drupal\Core\StringTranslation\StringTranslationTrait; * "state-transition-form" = "/admin/commerce/licenses/{commerce_license}/{field_name}/{transition_id}" * }, * field_ui_base_route = "entity.commerce_license.field_ui_fields", * restricted_fields = { * "uid", "state", "product_variation", "expiration_type", * "originating_order", "created", "changed", "granted", "renewed", * "expires" * }, * ) */ class License extends ContentEntityBase implements LicenseInterface { Loading src/EventSubscriber/LicenseAdminRouteSubscriber.php 0 → 100644 +48 −0 Changes for src/EventSubscriber/LicenseAdminRouteSubscriber.php: 48 added lines, 0 removed lines. Original line number Diff line number Diff line <?php namespace Drupal\commerce_license\EventSubscriber; use Drupal\Core\Routing\RouteSubscriberBase; use Drupal\Core\Routing\RoutingEvents; use Symfony\Component\Routing\RouteCollection; /** * Restricts the license admin routes to administrators. * * Customers can be granted "update own" and "delete own" access to their * licenses, so admin routes that only check license access would be * reachable by them. This applies to routes provided by state_machine and by * contrib modules, which is why the permission is enforced here. * * Routes that already require a permission are left untouched, to avoid * overriding or weakening it. * * @see \Drupal\state_machine\Routing\RouteSubscriber::alterRoutes() */ class LicenseAdminRouteSubscriber extends RouteSubscriberBase { /** * {@inheritdoc} */ protected function alterRoutes(RouteCollection $collection): void { foreach ($collection as $route) { $path = $route->getPath(); if (!str_starts_with($path, '/admin/') || !str_contains($path, '{commerce_license}') || $route->hasRequirement('_permission')) { continue; } $route->setRequirement('_permission', 'access commerce administration pages'); } } /** * {@inheritdoc} */ public static function getSubscribedEvents(): array { // Run late, so that routes added by other subscribers are included. $events[RoutingEvents::ALTER] = ['onAlterRoutes', -1024]; return $events; } } src/LicenseAccessControlHandler.php 0 → 100644 +46 −0 Changes for src/LicenseAccessControlHandler.php: 46 added lines, 0 removed lines. Original line number Diff line number Diff line <?php namespace Drupal\commerce_license; use Drupal\Core\Access\AccessResult; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Session\AccountInterface; use Drupal\entity\UncacheableEntityAccessControlHandler; use Drupal\field\FieldConfigInterface; /** * Provides an access control handler for licenses. */ class LicenseAccessControlHandler extends UncacheableEntityAccessControlHandler { /** * {@inheritdoc} */ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, ?FieldItemListInterface $items = NULL) { // Only users who can update any license through their permissions can // edit restricted fields. Customers can be granted "update own" // access, which must not allow changing the granted rights, the state, // the owner, etc. (e.g. through the edit form, JSON:API or REST). // Modules add their own fields to the "restricted_fields" entity // type property, in hook_entity_type_build(). // Fields provided by the license type plugins hold the granted rights // (e.g. the role), so they are always restricted. $restricted_fields = $this->entityType->get('restricted_fields') ?? []; $is_bundle_plugin_field = !$field_definition->getFieldStorageDefinition()->isBaseField() && !$field_definition instanceof FieldConfigInterface; if ($operation == 'edit' && ($is_bundle_plugin_field || in_array($field_definition->getName(), $restricted_fields, TRUE))) { $permissions = [ $this->entityType->getAdminPermission(), "update any {$this->entityTypeId}", ]; // Users who can create licenses can set these fields on new licenses. if ($items && $items->getEntity()->isNew()) { $permissions[] = "create {$this->entityTypeId}"; } return AccessResult::allowedIfHasPermissions($account, $permissions, 'OR'); } return parent::checkFieldAccess($operation, $field_definition, $account, $items); } } src/LicensePermissionProvider.php +4 −0 Changes for src/LicensePermissionProvider.php: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -23,6 +23,10 @@ class LicensePermissionProvider extends UncacheableEntityPermissionProvider { // Mark the 'overview' permission as restricted. $permissions["access $entity_type_id overview"]['restrict access'] = TRUE; // Users who can create licenses or update any license can set the granted // rights (e.g. the role), so these permissions are restricted as well. $permissions["create $entity_type_id"]['restrict access'] = TRUE; $permissions["update any $entity_type_id"]['restrict access'] = TRUE; // Add a description to the 'create' to make it clear that it only covers // admin creation, not creation via product purchase. Loading Loading
commerce_license.services.yml +5 −0 Changes for commerce_license.services.yml: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -63,3 +63,8 @@ services: commerce_license.cron: class: \Drupal\commerce_license\Cron arguments: ['@entity_type.manager', '@datetime.time'] commerce_license.license_admin_route_subscriber: class: Drupal\commerce_license\EventSubscriber\LicenseAdminRouteSubscriber tags: - { name: event_subscriber }
src/Entity/License.php +6 −1 Changes for src/Entity/License.php: 6 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -35,7 +35,7 @@ use Drupal\Core\StringTranslation\StringTranslationTrait; * bundle_label = @Translation("License type"), * bundle_plugin_type = "commerce_license_type", * handlers = { * "access" = "\Drupal\entity\UncacheableEntityAccessControlHandler", * "access" = "\Drupal\commerce_license\LicenseAccessControlHandler", * "event" = "Drupal\commerce_license\Event\LicenseEvent", * "permission_provider" = "\Drupal\commerce_license\LicensePermissionProvider", * "list_builder" = "Drupal\commerce_license\LicenseListBuilder", Loading Loading @@ -74,6 +74,11 @@ use Drupal\Core\StringTranslation\StringTranslationTrait; * "state-transition-form" = "/admin/commerce/licenses/{commerce_license}/{field_name}/{transition_id}" * }, * field_ui_base_route = "entity.commerce_license.field_ui_fields", * restricted_fields = { * "uid", "state", "product_variation", "expiration_type", * "originating_order", "created", "changed", "granted", "renewed", * "expires" * }, * ) */ class License extends ContentEntityBase implements LicenseInterface { Loading
src/EventSubscriber/LicenseAdminRouteSubscriber.php 0 → 100644 +48 −0 Changes for src/EventSubscriber/LicenseAdminRouteSubscriber.php: 48 added lines, 0 removed lines. Original line number Diff line number Diff line <?php namespace Drupal\commerce_license\EventSubscriber; use Drupal\Core\Routing\RouteSubscriberBase; use Drupal\Core\Routing\RoutingEvents; use Symfony\Component\Routing\RouteCollection; /** * Restricts the license admin routes to administrators. * * Customers can be granted "update own" and "delete own" access to their * licenses, so admin routes that only check license access would be * reachable by them. This applies to routes provided by state_machine and by * contrib modules, which is why the permission is enforced here. * * Routes that already require a permission are left untouched, to avoid * overriding or weakening it. * * @see \Drupal\state_machine\Routing\RouteSubscriber::alterRoutes() */ class LicenseAdminRouteSubscriber extends RouteSubscriberBase { /** * {@inheritdoc} */ protected function alterRoutes(RouteCollection $collection): void { foreach ($collection as $route) { $path = $route->getPath(); if (!str_starts_with($path, '/admin/') || !str_contains($path, '{commerce_license}') || $route->hasRequirement('_permission')) { continue; } $route->setRequirement('_permission', 'access commerce administration pages'); } } /** * {@inheritdoc} */ public static function getSubscribedEvents(): array { // Run late, so that routes added by other subscribers are included. $events[RoutingEvents::ALTER] = ['onAlterRoutes', -1024]; return $events; } }
src/LicenseAccessControlHandler.php 0 → 100644 +46 −0 Changes for src/LicenseAccessControlHandler.php: 46 added lines, 0 removed lines. Original line number Diff line number Diff line <?php namespace Drupal\commerce_license; use Drupal\Core\Access\AccessResult; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Session\AccountInterface; use Drupal\entity\UncacheableEntityAccessControlHandler; use Drupal\field\FieldConfigInterface; /** * Provides an access control handler for licenses. */ class LicenseAccessControlHandler extends UncacheableEntityAccessControlHandler { /** * {@inheritdoc} */ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, ?FieldItemListInterface $items = NULL) { // Only users who can update any license through their permissions can // edit restricted fields. Customers can be granted "update own" // access, which must not allow changing the granted rights, the state, // the owner, etc. (e.g. through the edit form, JSON:API or REST). // Modules add their own fields to the "restricted_fields" entity // type property, in hook_entity_type_build(). // Fields provided by the license type plugins hold the granted rights // (e.g. the role), so they are always restricted. $restricted_fields = $this->entityType->get('restricted_fields') ?? []; $is_bundle_plugin_field = !$field_definition->getFieldStorageDefinition()->isBaseField() && !$field_definition instanceof FieldConfigInterface; if ($operation == 'edit' && ($is_bundle_plugin_field || in_array($field_definition->getName(), $restricted_fields, TRUE))) { $permissions = [ $this->entityType->getAdminPermission(), "update any {$this->entityTypeId}", ]; // Users who can create licenses can set these fields on new licenses. if ($items && $items->getEntity()->isNew()) { $permissions[] = "create {$this->entityTypeId}"; } return AccessResult::allowedIfHasPermissions($account, $permissions, 'OR'); } return parent::checkFieldAccess($operation, $field_definition, $account, $items); } }
src/LicensePermissionProvider.php +4 −0 Changes for src/LicensePermissionProvider.php: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -23,6 +23,10 @@ class LicensePermissionProvider extends UncacheableEntityPermissionProvider { // Mark the 'overview' permission as restricted. $permissions["access $entity_type_id overview"]['restrict access'] = TRUE; // Users who can create licenses or update any license can set the granted // rights (e.g. the role), so these permissions are restricted as well. $permissions["create $entity_type_id"]['restrict access'] = TRUE; $permissions["update any $entity_type_id"]['restrict access'] = TRUE; // Add a description to the 'create' to make it clear that it only covers // admin creation, not creation via product purchase. Loading