diff --git a/entity.permissions.yml b/entity.permissions.yml new file mode 100644 index 0000000000000000000000000000000000000000..1676e880b4e3a9cb35b9f3f8835980a5547c06a4 --- /dev/null +++ b/entity.permissions.yml @@ -0,0 +1,2 @@ +permission_callbacks: + - \Drupal\entity\EntityPermissions::buildPermissions diff --git a/src/EntityAccessControlHandler.php b/src/EntityAccessControlHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..8f8cb43a0cc5943b879f71f9bafb7268375d778b --- /dev/null +++ b/src/EntityAccessControlHandler.php @@ -0,0 +1,109 @@ +<?php + +namespace Drupal\entity; + +use Drupal\Core\Access\AccessResult; +use Drupal\Core\Entity\EntityAccessControlHandler as CoreEntityAccessControlHandler; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\user\EntityOwnerInterface; + +/** + * Provides per-bundle entity CRUD permissions. + */ +class EntityAccessControlHandler extends CoreEntityAccessControlHandler { + + /** + * {@inheritdoc} + */ + protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { + $account = $this->prepareUser($account); + /** @var \Drupal\Core\Access\AccessResult $result */ + $result = parent::checkAccess($entity, $operation, $account); + + if ($result->isNeutral()) { + if ($entity instanceof EntityOwnerInterface) { + $result = $this->checkEntityOwnerPermissions($entity, $operation, $account); + } + else { + $result = $this->checkEntityPermissions($entity, $operation, $account); + } + } + + // Ensure that access is evaluated again when the entity changes. + return $result->addCacheableDependency($entity); + } + + /** + * Checks the entity operation and bundle permissions. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity for which to check access. + * @param string $operation + * The entity operation. Usually one of 'view', 'view label', 'update' or + * 'delete'. + * @param \Drupal\Core\Session\AccountInterface $account + * The user for which to check access. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + protected function checkEntityPermissions(EntityInterface $entity, $operation, AccountInterface $account) { + return AccessResult::allowedIfHasPermissions($account, [ + "$operation {$entity->getEntityTypeId()}", + "$operation {$entity->bundle()} {$entity->getEntityTypeId()}", + ], 'OR'); + } + + /** + * Checks the entity operation and bundle permissions, with owners. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity for which to check access. + * @param string $operation + * The entity operation. Usually one of 'view', 'view label', 'update' or + * 'delete'. + * @param \Drupal\Core\Session\AccountInterface $account + * The user for which to check access. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + protected function checkEntityOwnerPermissions(EntityInterface $entity, $operation, AccountInterface $account) { + /** @var \Drupal\Core\Entity\EntityInterface|\Drupal\user\EntityOwnerInterface $entity */ + if (($account->id() == $entity->getOwnerId())) { + $result = AccessResult::allowedIfHasPermissions($account, [ + "$operation own {$entity->getEntityTypeId()}", + "$operation any {$entity->getEntityTypeId()}", + "$operation own {$entity->bundle()} {$entity->getEntityTypeId()}", + "$operation any {$entity->bundle()} {$entity->getEntityTypeId()}", + ], 'OR'); + } + else { + $result = AccessResult::allowedIfHasPermissions($account, [ + "$operation any {$entity->getEntityTypeId()}", + "$operation any {$entity->bundle()} {$entity->getEntityTypeId()}", + ], 'OR'); + } + + return $result->cachePerUser(); + } + + /** + * {@inheritdoc} + */ + protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) { + $result = parent::checkCreateAccess($account, $context, $entity_bundle); + if ($result->isNeutral()) { + $result = AccessResult::allowedIfHasPermissions($account, [ + 'administer ' . $this->entityTypeId, + 'create ' . $entity_bundle . ' ' . $this->entityTypeId, + 'create any ' . $entity_bundle . ' ' . $this->entityTypeId, + 'create own ' . $entity_bundle . ' ' . $this->entityTypeId, + ], 'OR'); + } + + return $result; + } + +} diff --git a/src/EntityPermissionProvider.php b/src/EntityPermissionProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..502fba2010767bbe5aed4de92a106e7215154a69 --- /dev/null +++ b/src/EntityPermissionProvider.php @@ -0,0 +1,266 @@ +<?php + +namespace Drupal\entity; + +use Drupal\Core\Entity\EntityHandlerInterface; +use Drupal\Core\Entity\EntityTypeBundleInfoInterface; +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\user\EntityOwnerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Provides permissions for entities. + */ +class EntityPermissionProvider implements EntityPermissionProviderInterface, EntityHandlerInterface { + + use StringTranslationTrait; + + /** + * The entity type bundle info. + * + * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface + */ + protected $entityTypeBundleInfo; + + /** + * Constructs a new EntityPermissionProvider object. + * + * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info + * The entity type bundle info. + */ + public function __construct(EntityTypeBundleInfoInterface $entity_type_bundle_info) { + $this->entityTypeBundleInfo = $entity_type_bundle_info; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $container->get('entity_type.bundle.info') + ); + } + + /** + * {@inheritdoc} + */ + public function buildPermissions(EntityTypeInterface $entity_type) { + $entity_type_id = $entity_type->id(); + $plural_label = $entity_type->getPluralLabel(); + + $permissions = []; + $permissions["administer {$entity_type_id}"] = [ + 'title' => $this->t('Administer @type', ['@type' => $plural_label]), + 'restrict access' => TRUE, + ]; + $permissions["access {$entity_type_id} overview"] = [ + 'title' => $this->t('Access the @type overview page', ['@type' => $plural_label]), + ]; + if ($entity_type->getPermissionGranularity() == 'entity_type') { + $permissions += $this->buildEntityTypePermissions($entity_type); + } + else { + $permissions += $this->buildBundlePermissions($entity_type); + } + + foreach ($permissions as $name => $permission) { + // Permissions are grouped by provider on admin/people/permissions. + $permissions[$name]['provider'] = $entity_type->getProvider(); + // TranslatableMarkup objects don't sort properly. + $permissions[$name]['title'] = (string) $permission['title']; + } + + return $permissions; + } + + /** + * Builds permissions for the entity_type granularity. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return array + * The permissions. + */ + protected function buildEntityTypePermissions(EntityTypeInterface $entity_type) { + $entity_type_id = $entity_type->id(); + $has_owner = $entity_type->isSubclassOf(EntityOwnerInterface::class); + $singular_label = $entity_type->getSingularLabel(); + $plural_label = $entity_type->getPluralLabel(); + + $permissions = []; + if ($has_owner) { + $permissions["create any {$entity_type_id}"] = [ + 'title' => $this->t('Create any @type', [ + '@type' => $singular_label, + ]), + ]; + $permissions["create own {$entity_type_id}"] = [ + 'title' => $this->t('Create own @type', [ + '@type' => $plural_label, + ]), + ]; + + $permissions["view any {$entity_type_id}"] = [ + 'title' => $this->t('View any @type', [ + '@type' => $singular_label, + ]), + ]; + $permissions["view own {$entity_type_id}"] = [ + 'title' => $this->t('View own @type', [ + '@type' => $plural_label, + ]), + ]; + + $permissions["update any {$entity_type_id}"] = [ + 'title' => $this->t('Update any @type', [ + '@type' => $singular_label, + ]), + ]; + $permissions["update own {$entity_type_id}"] = [ + 'title' => $this->t('Update own @type', [ + '@type' => $plural_label, + ]), + ]; + + $permissions["delete any {$entity_type_id}"] = [ + 'title' => $this->t('Delete any @type', [ + '@type' => $singular_label, + ]), + ]; + $permissions["delete own {$entity_type_id}"] = [ + 'title' => $this->t('Delete own @type', [ + '@type' => $plural_label, + ]), + ]; + } + else { + $permissions["create {$entity_type_id}"] = [ + 'title' => $this->t('Create @type', [ + '@type' => $plural_label, + ]), + ]; + $permissions["view {$entity_type_id}"] = [ + 'title' => $this->t('View @type', [ + '@type' => $plural_label, + ]), + ]; + $permissions["update {$entity_type_id}"] = [ + 'title' => $this->t('Update @type', [ + '@type' => $plural_label, + ]), + ]; + $permissions["delete {$entity_type_id}"] = [ + 'title' => $this->t('Delete @type', [ + '@type' => $plural_label, + ]), + ]; + } + + return $permissions; + } + + /** + * Builds permissions for the bundle granularity. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return array + * The permissions. + */ + protected function buildBundlePermissions(EntityTypeInterface $entity_type) { + $entity_type_id = $entity_type->id(); + $bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id); + $has_owner = $entity_type->isSubclassOf(EntityOwnerInterface::class); + $singular_label = $entity_type->getSingularLabel(); + $plural_label = $entity_type->getPluralLabel(); + + $permissions = []; + foreach ($bundles as $bundle_name => $bundle_info) { + if ($has_owner) { + $permissions["create any {$bundle_name} {$entity_type_id}"] = [ + 'title' => $this->t('@bundle: Create any @type', [ + '@bundle' => $bundle_info['label'], + '@type' => $singular_label, + ]), + ]; + $permissions["create own {$bundle_name} {$entity_type_id}"] = [ + 'title' => $this->t('@bundle: Create own @type', [ + '@bundle' => $bundle_info['label'], + '@type' => $plural_label, + ]), + ]; + + $permissions["view any {$bundle_name} {$entity_type_id}"] = [ + 'title' => $this->t('@bundle: View own @type', [ + '@bundle' => $bundle_info['label'], + '@type' => $singular_label, + ]), + ]; + $permissions["view own {$bundle_name} {$entity_type_id}"] = [ + 'title' => $this->t('@bundle: View own @type', [ + '@bundle' => $bundle_info['label'], + '@type' => $plural_label, + ]), + ]; + + $permissions["update any {$bundle_name} {$entity_type_id}"] = [ + 'title' => $this->t('@bundle: Update any @type', [ + '@bundle' => $bundle_info['label'], + '@type' => $singular_label, + ]), + ]; + $permissions["update own {$bundle_name} {$entity_type_id}"] = [ + 'title' => $this->t('@bundle: Update own @type', [ + '@bundle' => $bundle_info['label'], + '@type' => $plural_label, + ]), + ]; + + $permissions["delete any {$bundle_name} {$entity_type_id}"] = [ + 'title' => $this->t('@bundle: Delete any @type', [ + '@bundle' => $bundle_info['label'], + '@type' => $singular_label, + ]), + ]; + $permissions["delete own {$bundle_name} {$entity_type_id}"] = [ + 'title' => $this->t('@bundle: Delete own @type', [ + '@bundle' => $bundle_info['label'], + '@type' => $plural_label, + ]), + ]; + } + else { + $permissions["create {$bundle_name} {$entity_type_id}"] = [ + 'title' => $this->t('@bundle: Create @type', [ + '@bundle' => $bundle_info['label'], + '@type' => $plural_label, + ]), + ]; + $permissions["view {$bundle_name} {$entity_type_id}"] = [ + 'title' => $this->t('@bundle: View @type', [ + '@bundle' => $bundle_info['label'], + '@type' => $plural_label, + ]), + ]; + $permissions["update {$bundle_name} {$entity_type_id}"] = [ + 'title' => $this->t('@bundle: Update @type', [ + '@bundle' => $bundle_info['label'], + '@type' => $plural_label, + ]), + ]; + $permissions["delete {$bundle_name} {$entity_type_id}"] = [ + 'title' => $this->t('@bundle: Delete @type', [ + '@bundle' => $bundle_info['label'], + '@type' => $plural_label, + ]), + ]; + } + } + + return $permissions; + } + +} diff --git a/src/EntityPermissionProviderInterface.php b/src/EntityPermissionProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..50f3ccaa9490a83f16905d186979d7a1be0bcedd --- /dev/null +++ b/src/EntityPermissionProviderInterface.php @@ -0,0 +1,23 @@ +<?php + +namespace Drupal\entity; + +use Drupal\Core\Entity\EntityTypeInterface; + +/** + * Allows entity types to provide permissions. + */ +interface EntityPermissionProviderInterface { + + /** + * Builds permissions for the given entity type. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return array + * The permissions. + */ + public function buildPermissions(EntityTypeInterface $entity_type); + +} diff --git a/src/EntityPermissions.php b/src/EntityPermissions.php new file mode 100644 index 0000000000000000000000000000000000000000..18002d9d03580ca25cb5a4a35480a60269d3a24e --- /dev/null +++ b/src/EntityPermissions.php @@ -0,0 +1,60 @@ +<?php + +namespace Drupal\entity; + +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Class for generating per-bundle CRUD permissions. + */ +class EntityPermissions implements ContainerInjectionInterface { + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * Constructs a new EntityPermissions object. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + */ + public function __construct(EntityTypeManagerInterface $entity_type_manager) { + $this->entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager') + ); + } + + /** + * Builds a list of permissions for the participating entity types. + * + * @return array + * The permissions. + */ + public function buildPermissions() { + $permissions = []; + /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */ + foreach ($this->entityTypeManager->getDefinitions() as $entity_type) { + if ($entity_type->hasHandlerClass('permission_provider')) { + $permission_provider_class = $entity_type->getHandlerClass('permission_provider'); + $permission_provider = $this->entityTypeManager->createHandlerInstance($permission_provider_class, $entity_type); + $permissions += $permission_provider->buildPermissions($entity_type); + } + } + + return $permissions; + } + +} diff --git a/tests/modules/entity_module_test/entity_module_test.permissions.yml b/tests/modules/entity_module_test/entity_module_test.permissions.yml index b9ff91d5b83c6af3f6fd09b8bf5bf613d0c850ff..8aa844ed4390df08b71ee375a36c03302680d0bd 100644 --- a/tests/modules/entity_module_test/entity_module_test.permissions.yml +++ b/tests/modules/entity_module_test/entity_module_test.permissions.yml @@ -1,7 +1,3 @@ -'administer entity_test_enhanced': - title: 'Administer entity_test_enhanced' - 'restrict access': TRUE - 'view all entity_test_enhanced revisions': title: 'View all entity_test_enhanced revisions' 'restrict access': TRUE diff --git a/tests/modules/entity_module_test/src/Entity/EnhancedEntity.php b/tests/modules/entity_module_test/src/Entity/EnhancedEntity.php index 32b2bbd6416996505fc76dbf7bda7c9df1137fb5..5114b4926d147631fec9845ce938dc20acdfbbf3 100644 --- a/tests/modules/entity_module_test/src/Entity/EnhancedEntity.php +++ b/tests/modules/entity_module_test/src/Entity/EnhancedEntity.php @@ -19,6 +19,8 @@ use Drupal\entity\Revision\RevisionableContentEntityBase; * label = @Translation("Entity test with enhancements"), * handlers = { * "storage" = "\Drupal\Core\Entity\Sql\SqlContentEntityStorage", + * "access" = "\Drupal\entity\EntityAccessControlHandler", + * "permission_provider" = "\Drupal\entity\EntityPermissionProvider", * "form" = { * "add" = "\Drupal\entity\Form\RevisionableContentEntityForm", * "edit" = "\Drupal\entity\Form\RevisionableContentEntityForm", @@ -38,6 +40,7 @@ use Drupal\entity\Revision\RevisionableContentEntityBase; * translatable = TRUE, * revisionable = TRUE, * admin_permission = "administer entity_test_enhanced", + * permission_granularity = "bundle", * entity_keys = { * "id" = "id", * "bundle" = "type", diff --git a/tests/modules/entity_module_test/src/Entity/EnhancedOwnerEntity.php b/tests/modules/entity_module_test/src/Entity/EnhancedOwnerEntity.php new file mode 100644 index 0000000000000000000000000000000000000000..938ea89f700d853fb321f104008887690fe985ac --- /dev/null +++ b/tests/modules/entity_module_test/src/Entity/EnhancedOwnerEntity.php @@ -0,0 +1,139 @@ +<?php + +namespace Drupal\entity_module_test\Entity; + +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\entity\Revision\RevisionableContentEntityBase; +use Drupal\user\EntityOwnerInterface; +use Drupal\user\UserInterface; + +/** + * Provides a test entity which uses all the capabilities of entity module. + * + * @ContentEntityType( + * id = "entity_test_owner", + * label = @Translation("Entity owner test with enhancements"), + * handlers = { + * "storage" = "\Drupal\Core\Entity\Sql\SqlContentEntityStorage", + * "access" = "\Drupal\entity\EntityAccessControlHandler", + * "permission_provider" = "\Drupal\entity\EntityPermissionProvider", + * "form" = { + * "add" = "\Drupal\entity\Form\RevisionableContentEntityForm", + * "edit" = "\Drupal\entity\Form\RevisionableContentEntityForm", + * "delete" = "\Drupal\Core\Entity\EntityDeleteForm", + * }, + * "route_provider" = { + * "html" = "\Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider", + * "revision" = "\Drupal\entity\Routing\RevisionRouteProvider", + * "delete-multiple" = "\Drupal\entity\Routing\DeleteMultipleRouteProvider", + * }, + * "list_builder" = "\Drupal\Core\Entity\EntityListBuilder", + * }, + * base_table = "entity_test_owner", + * data_table = "entity_test_owner_field_data", + * revision_table = "entity_test_owner_revision", + * revision_data_table = "entity_test_owner_field_revision", + * translatable = TRUE, + * revisionable = TRUE, + * admin_permission = "administer entity_test_owner", + * permission_granularity = "bundle", + * entity_keys = { + * "id" = "id", + * "bundle" = "type", + * "revision" = "vid", + * "langcode" = "langcode", + * "uuid" = "uuid", + * }, + * links = { + * "add-page" = "/entity_test_owner/add", + * "add-form" = "/entity_test_owner/add/{type}", + * "edit-form" = "/entity_test_owner/{entity_test_owner}/edit", + * "canonical" = "/entity_test_owner/{entity_test_owner}", + * "collection" = "/entity_test_owner", + * "delete-multiple-form" = "/entity_test_owner/delete", + * "revision" = "/entity_test_owner/{entity_test_owner}/revisions/{entity_test_owner_revision}/view", + * "revision-revert-form" = "/entity_test_owner/{entity_test_owner}/revisions/{entity_test_owner_revision}/revert", + * "version-history" = "/entity_test_owner/{entity_test_owner}/revisions", + * }, + * bundle_entity_type = "entity_test_owner_bundle", + * ) + */ +class EnhancedOwnerEntity extends RevisionableContentEntityBase implements EntityOwnerInterface { + + /** + * {@inheritdoc} + */ + public function getOwner() { + return $this->get('uid')->entity; + } + + /** + * {@inheritdoc} + */ + public function getOwnerId() { + return $this->get('uid')->target_id; + } + + /** + * {@inheritdoc} + */ + public function setOwnerId($uid) { + $this->set('uid', $uid); + return $this; + } + + /** + * {@inheritdoc} + */ + public function setOwner(UserInterface $account) { + $this->set('uid', $account->id()); + return $this; + } + + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { + $fields = parent::baseFieldDefinitions($entity_type); + + $fields['name'] = BaseFieldDefinition::create('string') + ->setLabel('Name') + ->setRevisionable(TRUE) + ->setDisplayOptions('view', [ + 'label' => 'hidden', + 'type' => 'string', + 'weight' => -5, + ]); + + $fields['uid'] = BaseFieldDefinition::create('entity_reference') + ->setLabel(t('Owner')) + ->setDescription(t('The order owner.')) + ->setSetting('target_type', 'user') + ->setSetting('handler', 'default') + ->setDefaultValueCallback('Drupal\entity_module_test\Entity\EnhancedOwnerEntity::getCurrentUserId') + ->setTranslatable(TRUE) + ->setDisplayOptions('view', [ + 'label' => 'above', + 'type' => 'author', + 'weight' => 0, + ]) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + return $fields; + } + + /** + * Default value callback for 'uid' base field definition. + * + * @see ::baseFieldDefinitions() + * + * @return array + * An array of default values. + */ + public static function getCurrentUserId() { + return [\Drupal::currentUser()->id()]; + } + +} diff --git a/tests/modules/entity_module_test/src/Entity/EnhancedOwnerEntityBundle.php b/tests/modules/entity_module_test/src/Entity/EnhancedOwnerEntityBundle.php new file mode 100644 index 0000000000000000000000000000000000000000..8686e3eb4eecfe09f6bbaab64e8ffff0b9a1dddb --- /dev/null +++ b/tests/modules/entity_module_test/src/Entity/EnhancedOwnerEntityBundle.php @@ -0,0 +1,81 @@ +<?php + +namespace Drupal\entity_module_test\Entity; + +use Drupal\Core\Config\Entity\ConfigEntityBundleBase; +use Drupal\Core\Entity\EntityDescriptionInterface; +use Drupal\entity\Entity\RevisionableEntityBundleInterface; + +/** + * Provides bundles for the test entity. + * + * @ConfigEntityType( + * id = "entity_test_owner_bundle", + * label = @Translation("Entity owner test with enhancements - Bundle"), + * admin_permission = "administer entity_test_enhanced_owner", + * config_prefix = "entity_test_owner_bundle", + * bundle_of = "entity_test_owner", + * entity_keys = { + * "id" = "id", + * "label" = "label" + * }, + * config_export = { + * "id", + * "label", + * "description" + * }, + * ) + */ +class EnhancedOwnerEntityBundle extends ConfigEntityBundleBase implements EntityDescriptionInterface, RevisionableEntityBundleInterface { + + /** + * The bundle ID. + * + * @var string + */ + protected $id; + + /** + * The bundle label. + * + * @var string + */ + protected $label; + + /** + * The bundle description. + * + * @var string + */ + protected $description; + + /** + * Should new entities of this bundle have a new revision by default. + * + * @var bool + */ + protected $new_revision = FALSE; + + /** + * {@inheritdoc} + */ + public function getDescription() { + return $this->description; + } + + /** + * {@inheritdoc} + */ + public function setDescription($description) { + $this->description = $description; + return $this; + } + + /** + * {@inheritdoc} + */ + public function shouldCreateNewRevision() { + return $this->new_revision; + } + +} diff --git a/tests/modules/entity_module_test/src/EntityEnhancedOwnerPermissions.php b/tests/modules/entity_module_test/src/EntityEnhancedOwnerPermissions.php new file mode 100644 index 0000000000000000000000000000000000000000..acccd642e08be78124f7464d18dfadebf88c9ef1 --- /dev/null +++ b/tests/modules/entity_module_test/src/EntityEnhancedOwnerPermissions.php @@ -0,0 +1,19 @@ +<?php + +namespace Drupal\entity_module_test; + +use Drupal\entity\EntityPermissions; + +/** + * Permissions implementation for entity_test_enhanced. + */ +class EntityEnhancedOwnerPermissions extends EntityPermissions { + + /** + * {@inheritdoc} + */ + protected function getEntityTypeId() { + return 'entity_test_owner'; + } + +} diff --git a/tests/modules/entity_module_test/src/EntityEnhancedPermissions.php b/tests/modules/entity_module_test/src/EntityEnhancedPermissions.php new file mode 100644 index 0000000000000000000000000000000000000000..79617d9822fd5419f7c62354b2eca44aa6d1b11f --- /dev/null +++ b/tests/modules/entity_module_test/src/EntityEnhancedPermissions.php @@ -0,0 +1,19 @@ +<?php + +namespace Drupal\entity_module_test; + +use Drupal\entity\EntityPermissions; + +/** + * Permissions implementation for entity_test_enhanced. + */ +class EntityEnhancedPermissions extends EntityPermissions { + + /** + * {@inheritdoc} + */ + protected function getEntityTypeId() { + return 'entity_test_enhanced'; + } + +} diff --git a/tests/src/Kernel/PermissionsTest.php b/tests/src/Kernel/PermissionsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1f3d2a4c9b4cdbdb9cf37037c85fca8d7769b45a --- /dev/null +++ b/tests/src/Kernel/PermissionsTest.php @@ -0,0 +1,122 @@ +<?php + +namespace Drupal\Tests\entity\Kernel; + +use Drupal\entity_module_test\Entity\EnhancedEntity; +use Drupal\entity_module_test\Entity\EnhancedEntityBundle; +use Drupal\entity_module_test\Entity\EnhancedOwnerEntity; +use Drupal\KernelTests\Core\Entity\EntityKernelTestBase; + +/** + * Tests the permissions builder and generic entity access control handler. + * + * @group entity + */ +class PermissionsTest extends EntityKernelTestBase { + + /** + * {@inheritdoc} + */ + public static $modules = ['entity_module_test', 'system', 'user', 'entity']; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->installEntitySchema('user'); + $this->installEntitySchema('entity_test_enhanced'); + $this->installEntitySchema('entity_test_owner'); + $this->installSchema('system', 'router'); + $this->installConfig(['system']); + + $bundle = EnhancedEntityBundle::create([ + 'id' => 'default', + 'label' => 'Default', + ]); + $bundle->save(); + $bundle = EnhancedEntityBundle::create([ + 'id' => 'tester', + 'label' => 'Tester', + ]); + $bundle->save(); + + $this->container->get('router.builder')->rebuild(); + } + + /** + * Tests the generated permissions. + */ + public function testGeneratedPermissions() { + $permissions = $this->container->get('user.permissions')->getPermissions(); + + $this->assertTrue(isset($permissions['administer entity_test_enhanced'])); + $this->assertTrue(isset($permissions['access entity_test_enhanced overview'])); + $this->assertTrue(isset($permissions['create default entity_test_enhanced'])); + $this->assertTrue(isset($permissions['create tester entity_test_enhanced'])); + $this->assertFalse(isset($permissions['create own tester entity_test_enhanced'])); + } + + /** + * Tests the access controller. + */ + public function testAccessControlHandler() { + // Offset uid = 1. + $this->createUser(); + + $entity = EnhancedEntity::create([ + 'name' => 'Llama', + 'type' => 'default', + ]); + $entity->save(); + + $user1 = $this->createUser([], ['administer entity_test_enhanced']); + $user2 = $this->createUser([], ['create default entity_test_enhanced', 'update default entity_test_enhanced']); + $user3 = $this->createUser([], ['create tester entity_test_enhanced', 'update tester entity_test_enhanced']); + + $this->assertTrue($entity->access('create', $user1)); + $this->assertTrue($entity->access('create', $user2)); + $this->assertFalse($entity->access('create', $user3)); + $this->assertTrue($entity->access('create', $user1)); + $this->assertTrue($entity->access('create', $user2)); + $this->assertFalse($entity->access('create', $user3)); + $this->assertTrue($entity->access('update', $user1)); + $this->assertTrue($entity->access('update', $user2)); + $this->assertFalse($entity->access('update', $user3)); + + $user4 = $this->createUser([], ['update own default entity_test_owner']); + $user5 = $this->createUser([], ['update any default entity_test_owner']); + $user6 = $this->createUser([], ['administer entity_test_owner']); + + $entity = EnhancedOwnerEntity::create([ + 'name' => 'Alpaca', + 'type' => 'default', + 'uid' => $user4->id(), + ]); + $entity->save(); + $other_entity = EnhancedOwnerEntity::create([ + 'name' => 'Emu', + 'type' => 'default', + 'uid' => $user5->id(), + ]); + $other_entity->save(); + + // Owner can update entity. + $this->assertTrue($entity->access('update', $user4)); + + // User cannot update entities they do not own. + $this->assertFalse($other_entity->access('update', $user4)); + + // User with "any" can update entities they do not own. + $this->assertTrue($entity->access('update', $user5)); + + // User with "any" can update their own entries. + $this->assertTrue($other_entity->access('update', $user5)); + + // User with "administer" can update both entities. + $this->assertTrue($entity->access('update', $user6)); + $this->assertTrue($other_entity->access('update', $user6)); + } + +}