From 1c04e650f743fac39d64b35b8f25c87ab8fe0f91 Mon Sep 17 00:00:00 2001 From: Pieter Frenssen Date: Sun, 2 Aug 2020 14:59:08 +0300 Subject: [PATCH] Allow modules to alter access to group content entity operations. --- src/OgAccess.php | 19 +- tests/modules/og_test/og_test.module | 25 +++ tests/modules/og_test/og_test.permissions.yml | 3 + .../GroupContentOperationAccessAlterTest.php | 179 ++++++++++++++++++ 4 files changed, 222 insertions(+), 4 deletions(-) create mode 100644 tests/modules/og_test/og_test.permissions.yml create mode 100644 tests/src/Kernel/Access/GroupContentOperationAccessAlterTest.php diff --git a/src/OgAccess.php b/src/OgAccess.php index 7a0b15356..9fd950aef 100644 --- a/src/OgAccess.php +++ b/src/OgAccess.php @@ -324,6 +324,8 @@ public function userAccessEntityOperation(string $operation, EntityInterface $en * {@inheritdoc} */ public function userAccessGroupContentEntityOperation(string $operation, EntityInterface $group_entity, EntityInterface $group_content_entity, AccountInterface $user = NULL): AccessResultInterface { + $access_result = AccessResult::neutral(); + // Default to the current user. $user = $user ?: $this->accountProxy->getAccount(); @@ -350,9 +352,9 @@ public function userAccessGroupContentEntityOperation(string $operation, EntityI if ($permissions) { foreach ($permissions as $permission) { - $user_access = $this->userAccess($group_entity, $permission->getName(), $user); - if ($user_access->isAllowed()) { - return $user_access; + $access_result = $this->userAccess($group_entity, $permission->getName(), $user); + if ($access_result->isAllowed()) { + break; } } } @@ -366,7 +368,16 @@ public function userAccessGroupContentEntityOperation(string $operation, EntityI $cacheable_metadata->addCacheContexts(['user']); } - return AccessResult::neutral()->addCacheableDependency($cacheable_metadata); + // Let modules alter the access result. + $context = [ + 'operation' => $operation, + 'group' => $group_entity, + 'group_content' => $group_content_entity, + 'user' => $user, + ]; + $this->moduleHandler->alter('og_user_access_entity_operation', $access_result, $cacheable_metadata, $context); + + return $access_result->addCacheableDependency($cacheable_metadata); } /** diff --git a/tests/modules/og_test/og_test.module b/tests/modules/og_test/og_test.module index f2a14fc10..19b82ebc5 100644 --- a/tests/modules/og_test/og_test.module +++ b/tests/modules/og_test/og_test.module @@ -1,10 +1,15 @@ save(); } } + +/** + * Implements hook_og_user_access_entity_operation_alter(). + */ +function og_test_og_user_access_entity_operation_alter(AccessResultInterface &$access_result, CacheableMetadata &$cacheable_metadata, $context): void { + if (\Drupal::state()->get('og_test_group_content_entity_operation_access_alter', FALSE)) { + // Moderators should have access to edit and delete all comments in all + // groups. + /** @var \Drupal\Core\Session\AccountProxyInterface $user */ + $user = $context['user']; + $group_content = $context['group_content']; + + $is_comment = $group_content->getEntityTypeId() === 'comment'; + $user_can_moderate_comments = $user->hasPermission('edit and delete comments in all groups'); + + if ($is_comment && $user_can_moderate_comments) { + $access_result = new AccessResultAllowed(); + } + } +} diff --git a/tests/modules/og_test/og_test.permissions.yml b/tests/modules/og_test/og_test.permissions.yml new file mode 100644 index 000000000..cbf42a419 --- /dev/null +++ b/tests/modules/og_test/og_test.permissions.yml @@ -0,0 +1,3 @@ +# A permission given to users who can moderate comments in all groups. +edit and delete comments in all groups: + title: 'Moderate comments in all groups' diff --git a/tests/src/Kernel/Access/GroupContentOperationAccessAlterTest.php b/tests/src/Kernel/Access/GroupContentOperationAccessAlterTest.php new file mode 100644 index 000000000..3a6bd10e1 --- /dev/null +++ b/tests/src/Kernel/Access/GroupContentOperationAccessAlterTest.php @@ -0,0 +1,179 @@ +installEntitySchema('comment'); + $this->installEntitySchema('entity_test'); + $this->installEntitySchema('og_membership'); + $this->installEntitySchema('user'); + $this->installSchema('system', 'sequences'); + + $this->ogAccess = $this->container->get('og.access'); + + // Create a dummy user which will get UID 1. We cannot use this for testing + // since this user becomes the super administrator and is not suitable for + // testing access control. + User::create(['name' => $this->randomString()])->save(); + + // Create a test user with the 'moderator' role which has global permission + // to moderate comments in all groups, even ones they are not a member of. + $this->user = $this->createUser(['edit and delete comments in all groups']); + + // Create the test group along with a user that serves as the group owner. + $group_bundle = mb_strtolower($this->randomMachineName()); + $this->group = EntityTest::create([ + 'type' => $group_bundle, + 'name' => $this->randomString(), + 'user_id' => $this->createUser()->id(), + ]); + $this->group->save(); + + // Declare that the test entity type is a group type. + Og::groupTypeManager()->addGroup('entity_test', $group_bundle); + + // Create a group content type. + CommentType::create([ + 'id' => 'comment', + 'label' => 'Comment subscription', + 'target_entity_type_id' => 'entity_test', + ])->save(); + $settings = [ + 'field_storage_config' => [ + 'settings' => [ + 'target_type' => 'entity_test', + ], + ], + ]; + Og::createField(OgGroupAudienceHelperInterface::DEFAULT_FIELD, 'comment', 'comment', $settings); + + // Create a group content entity. + $values = [ + 'subject' => 'subscribe', + 'comment_type' => 'comment', + 'entity_id' => $this->group->id(), + 'entity_type' => 'entity_test', + 'field_name' => 'an_imaginary_field', + OgGroupAudienceHelperInterface::DEFAULT_FIELD => [['target_id' => $this->group->id()]], + ]; + $this->groupContent = Comment::create($values); + $this->groupContent->save(); + } + + /** + * Tests that modules can alter group content entity operation access. + * + * This mimicks a use case where a moderator has access to edit and delete + * comments in all groups. + * + * @see \og_test_og_user_access_entity_operation_alter() + * + * @dataProvider groupContentEntityOperationAccessAlterHookTestProvider + */ + public function testGroupContentEntityOperationAccessAlterHook(string $operation): void { + // Check that our test user doesn't have access to edit or delete comments + // in the group. + // This is the default behavior for users that are not a group member. + $this->assertFalse($this->userHasAccess($operation)); + + // Now enable our hook which will alter the group content entity operation + // access rules to allow moderators to edit and delete comments in all + // groups. Since our user is a moderator they should now have access. + \Drupal::state()->set('og_test_group_content_entity_operation_access_alter', TRUE); + $this->assertTrue($this->userHasAccess($operation)); + } + + /** + * Checks whether the test user has access to perform the entity operation. + * + * @param string $operation + * The entity operation to check. + * + * @return bool + * TRUE if the user has access, FALSE otherwise. + */ + protected function userHasAccess(string $operation): bool { + return $this->ogAccess->userAccessGroupContentEntityOperation($operation, $this->group, $this->groupContent, $this->user)->isAllowed(); + } + + /** + * Provides test data for ::testGroupContentEntityOperationAccessAlterHook(). + * + * @return string[][] + * Test cases for the 'update' and 'delete' entity operations. + */ + public function groupContentEntityOperationAccessAlterHookTestProvider(): array { + return [ + ['update'], + ['delete'], + ]; + } + +}