diff --git a/tests/src/Kernel/CollaboraMediaAccessTest.php b/tests/src/Kernel/CollaboraMediaAccessTest.php index 498bfac9..fd9a9c42 100644 --- a/tests/src/Kernel/CollaboraMediaAccessTest.php +++ b/tests/src/Kernel/CollaboraMediaAccessTest.php @@ -5,6 +5,7 @@ namespace Drupal\Tests\collabora_online\Kernel; use Drupal\Component\Serialization\Yaml; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\Session\AnonymousUserSession; use Drupal\Core\Url; use Drupal\file\Entity\File; @@ -66,311 +67,208 @@ protected function setUp(): void { * Tests media access for Collabora routes and operations. */ public function testCollaboraMediaAccess(): void { - /** @var \Drupal\Core\Session\AccountInterface[] $accounts */ - $accounts = [ - // The default roles for anonymous and authenticated are not created - // in a kernel test. Therefore these users don't get any kind of - // access. - 'anonymous' => new AnonymousUserSession(), - 'authenticated' => $this->createUser(), - // Media permission do not grant access to Collabora operations. - 'media permissions only' => $this->createUser([ + $this->assertCollaboraMediaAccess( + [], + new AnonymousUserSession(), + 'No access for anonymous without permissions', + ); + + // Check authenticated with irrelevant permissions. + // This also covers the "no permissions" case. + $this->assertCollaboraMediaAccess( + [], + $this->createUser([ + // Add general 'media' permissions. 'administer media types', 'view media', 'update any media', 'view own unpublished media', - ]), - // Permissions for 'book' do not grant access to 'document'. - 'Bookworm' => $this->createUser([ + // Add Collabora permissions for a different media type. 'preview book in collabora', 'preview own unpublished book in collabora', 'edit any book in collabora', 'edit own book in collabora', ]), - 'Previewer' => $this->createUser([ - 'preview document in collabora', - ]), - 'Sean (preview own)' => $this->createUser([ - 'preview own unpublished document in collabora', - ]), - 'Editor' => $this->createUser([ - 'edit any document in collabora', - ]), - 'Kelly (edit own)' => $this->createUser([ - 'edit own document in collabora', - ]), - 'Media admin' => $this->createUser([ - 'administer media', - ]), - ]; + 'No access with irrelevant permissions', + ); - /** @var \Drupal\media\MediaInterface[] $media_entities */ - $media_entities = [ - "Sean's published document" => $this->createMediaEntity('document', [ - 'uid' => $accounts['Sean (preview own)']->id(), - ]), - "Sean's unpublished document" => $this->createMediaEntity('document', [ - 'uid' => $accounts['Sean (preview own)']->id(), - 'status' => 0, - ]), - "Kelly's published document" => $this->createMediaEntity('document', [ - 'uid' => $accounts['Kelly (edit own)']->id(), - ]), - "Kelly's unpublished document" => $this->createMediaEntity('document', [ - 'uid' => $accounts['Kelly (edit own)']->id(), - 'status' => 0, - ]), - ]; + $this->assertCollaboraMediaAccessForPermission( + [ + 'published document' => ['preview'], + 'own published document' => ['preview'], + ], + 'preview document in collabora', + ); - $this->assertEntityAccess( + $this->assertCollaboraMediaAccessForPermission( [ - 'anonymous' => [], - 'authenticated' => [], - 'media permissions only' => [], - 'Bookworm' => [], - 'Previewer' => [ - "Sean's published document" => ['preview in collabora'], - "Kelly's published document" => ['preview in collabora'], - ], - 'Sean (preview own)' => [ - "Sean's unpublished document" => ['preview in collabora'], - ], - 'Editor' => [ - "Sean's published document" => ['edit in collabora'], - "Sean's unpublished document" => ['edit in collabora'], - "Kelly's published document" => ['edit in collabora'], - "Kelly's unpublished document" => ['edit in collabora'], - ], - 'Kelly (edit own)' => [ - "Kelly's published document" => ['edit in collabora'], - "Kelly's unpublished document" => ['edit in collabora'], - ], - 'Media admin' => [ - "Sean's published document" => ['preview in collabora', 'edit in collabora'], - "Sean's unpublished document" => ['preview in collabora', 'edit in collabora'], - "Kelly's published document" => ['preview in collabora', 'edit in collabora'], - "Kelly's unpublished document" => ['preview in collabora', 'edit in collabora'], - ], + 'own unpublished document' => ['preview'], ], - $accounts, - $media_entities, + 'preview own unpublished document in collabora', + ); + + $this->assertCollaboraMediaAccessForPermission( + [ + 'published document' => ['edit'], + 'unpublished document' => ['edit'], + 'own published document' => ['edit'], + 'own unpublished document' => ['edit'], + ], + 'edit any document in collabora', + ); + + $this->assertCollaboraMediaAccessForPermission( [ - 'preview in collabora', - 'edit in collabora', + 'own published document' => ['edit'], + 'own unpublished document' => ['edit'], ], + 'edit own document in collabora', ); - $this->assertEntityPathsAccess( + // The 'administer media' permission grants access to everything. + $this->assertCollaboraMediaAccessForPermission( [ - 'anonymous' => [], - 'authenticated' => [], - 'media permissions only' => [], - 'Bookworm' => [], - 'Previewer' => [ - "/cool/view/", - "/cool/view/", - ], - 'Sean (preview own)' => [ - "/cool/view/", - ], - 'Editor' => [ - "/cool/edit/", - "/cool/edit/", - "/cool/edit/", - "/cool/edit/", - ], - 'Kelly (edit own)' => [ - "/cool/edit/", - "/cool/edit/", - ], - 'Media admin' => [ - "/cool/view/", - "/cool/edit/", - "/cool/view/", - "/cool/edit/", - "/cool/view/", - "/cool/edit/", - "/cool/view/", - "/cool/edit/", - ], + 'published document' => ['preview', 'edit'], + 'unpublished document' => ['preview', 'edit'], + 'own published document' => ['preview', 'edit'], + 'own unpublished document' => ['preview', 'edit'], ], - $accounts, - $media_entities, + 'administer media', + ); + + $this->assertCollaboraMediaAccess( [ - '/cool/view/%s', - '/cool/edit/%s', + 'published document' => ['preview', 'edit'], + 'unpublished document' => ['preview', 'edit'], + 'own published document' => ['preview', 'edit'], + 'own unpublished document' => ['preview', 'edit'], ], + $this->createUser(admin: TRUE), + "Access for admin user", ); } /** - * Tests a scenario where the anonymous user has more permissions. + * Tests scenarios where the anonymous user has more permissions. + * + * This verifies the special treatment of uid 0 to determine the owner of a + * media entity. */ public function testAnonymousOwnAccess(): void { user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, [ 'preview own unpublished document in collabora', 'edit own document in collabora', ]); - - /** @var \Drupal\Core\Session\AccountInterface[] $accounts */ - $accounts = [ - 'anonymous' => new AnonymousUserSession(), - 'Emilia' => $this->createUser(), - ]; - - /** @var \Drupal\media\MediaInterface[] $media_entities */ - $media_entities = [ - // Set uid = 0 to verify that anonymous is not seen as the owner. - "published document" => $this->createMediaEntity('document', [ - 'uid' => 0, - ]), - "unpublished document" => $this->createMediaEntity('document', [ - 'uid' => 0, - 'status' => 0, - ]), - "Emilia's published document" => $this->createMediaEntity('document', [ - 'uid' => $accounts['Emilia']->id(), - ]), - "Emilia's unpublished document" => $this->createMediaEntity('document', [ - 'uid' => $accounts['Emilia']->id(), - 'status' => 0, - ]), - ]; - - $this->assertEntityAccess( - [ - 'anonymous' => [], - 'Emilia' => [], - ], - $accounts, - $media_entities, - [ - 'preview in collabora', - 'edit in collabora', - ], + $this->assertCollaboraMediaAccess( + [], + new AnonymousUserSession(), + "Anonymous user with '... own ...' permissions.", ); user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, [ 'preview document in collabora', 'edit any document in collabora', ]); - - $this->assertEntityAccess( + $this->assertCollaboraMediaAccess( [ - 'anonymous' => [ - "published document" => ['preview in collabora', 'edit in collabora'], - "unpublished document" => ['edit in collabora'], - "Emilia's published document" => ['preview in collabora', 'edit in collabora'], - "Emilia's unpublished document" => ['edit in collabora'], - ], - 'Emilia' => [], - ], - $accounts, - $media_entities, - [ - 'preview in collabora', - 'edit in collabora', + 'published document' => ['preview', 'edit'], + 'unpublished document' => ['edit'], + 'own published document' => ['preview', 'edit'], + 'own unpublished document' => ['edit'], ], + new AnonymousUserSession(), + "Anonymous user with all Collabora media permissions.", ); } /** - * Asserts which users can access which entity operations. + * Creates a user with one permission, and asserts access to media entities. * - * @param array>> $expected - * Expected outcome. - * The array is keyed by entity key and operation. - * The values are lists of keys from the $accounts parameter. - * @param array $accounts - * Entities to check. - * @param array $entities - * Operations to check. - * @param list $operations - * User accounts to check. + * @param array> $expected + * Expected access. + * @param string $permission + * Permission machine name. */ - protected function assertEntityAccess(array $expected, array $accounts, array $entities, array $operations): void { - $actual = []; - foreach ($accounts as $account_key => $account) { - $actual[$account_key] = []; - foreach ($entities as $entity_key => $entity) { - foreach ($operations as $operation) { - $has_access = $entity->access($operation, $account); - if ($has_access) { - $actual[$account_key][$entity_key][] = $operation; - } - } - } - } - $this->assertSameYaml( - $expected, - $actual, - 'Users with access to given entities', - ); + protected function assertCollaboraMediaAccessForPermission(array $expected, string $permission): void { + $account = $this->createUser([$permission]); + $message = "User with '$permission' permission."; + $this->assertCollaboraMediaAccess($expected, $account, $message); } /** - * Asserts which users have access to which entity paths. + * Asserts Collabora media access for a user account. * - * @param array> $expected - * Array indicating which url should be accessible by which user. - * The array keys are either string keys from the $paths array. - * The array values are lists of keys from the $accounts array with access - * to that path. - * @param array $accounts - * Accounts to test access with, keyed by a distinguishable name. - * @param array $entities - * Entities for which to build paths. - * @param array $sprintf_path_patterns - * Path patterns with '%s' placeholder for the entity id. + * @param array $expected + * Expected access matrix. + * The keys identify media entities that are created in this test. + * The values identify operations. + * @param \Drupal\Core\Session\AccountInterface $account + * Account to check access for. + * @param string $message + * Message for the assertion. */ - protected function assertEntityPathsAccess(array $expected, array $accounts, array $entities, array $sprintf_path_patterns) { - $paths = []; - // Build Collabora media paths for all media entities. + protected function assertCollaboraMediaAccess(array $expected, AccountInterface $account, string $message): void { + $other_user = $this->createUser(); + $entities = [ + "published document" => $this->createMediaEntity('document', [ + 'uid' => $other_user->id(), + ]), + "unpublished document" => $this->createMediaEntity('document', [ + 'uid' => $other_user->id(), + 'status' => 0, + ]), + "own published document" => $this->createMediaEntity('document', [ + 'uid' => $account->id(), + ]), + "own unpublished document" => $this->createMediaEntity('document', [ + 'uid' => $account->id(), + 'status' => 0, + ]), + ]; + + // Test $entity->access() with different operations on all entities. + $operations = [ + 'preview' => 'preview in collabora', + 'edit' => 'edit in collabora', + ]; + $actual_entity_access = []; foreach ($entities as $entity_key => $entity) { - foreach ($sprintf_path_patterns as $pattern) { - $paths[sprintf($pattern, "<$entity_key>")] = sprintf($pattern, $entity->id()); + foreach ($operations as $operation_key => $operation) { + $has_entity_access = $entity->access($operation, $account); + if ($has_entity_access) { + $actual_entity_access[$entity_key][] = $operation_key; + } } } - $this->assertPathsAccessByUsers($expected, $accounts, $paths); - } + $this->assertSameYaml( + $expected, + $actual_entity_access, + 'Entity access: ' . $message, + ); - /** - * Asserts which users have access to which paths. - * - * @param array> $expected - * Array indicating which url should be accessible by which user. - * The array keys are either paths or string keys from the $paths array. - * The array values are lists of keys from the $accounts array with access - * to that path. - * @param array $accounts - * Accounts to test access with, keyed by a distinguishable name. - * @param array|null $paths - * An array of paths, or NULL to just use the array keys from $expected. - * This parameter is useful if the paths all look very similar. - */ - protected function assertPathsAccessByUsers(array $expected, array $accounts, ?array $paths = NULL): void { - if ($paths === NULL) { - $paths = array_keys($expected); - $paths = array_combine($paths, $paths); - } - // Build a report and assert it all at once, to have a more complete - // overview on failure. - $actual = []; - foreach ($accounts as $account_key => $account) { - $actual[$account_key] = []; - foreach ($paths as $path_key => $path) { - $url = Url::fromUserInput($path); - // Filter the user list by access to the url. - $has_access = $url->access($account); - if ($has_access) { - $actual[$account_key][] = $path_key; + // Test path access. + // The result is expected to be exactly the same, due to how the route + // access is configured. + // Testing the paths like this introduces some level of redundancy or + // duplication, but it is cheap and easy, so for now this is what we do. + $sprintf_path_patterns = [ + 'preview' => '/cool/view/%s', + 'edit' => '/cool/edit/%s', + ]; + $actual_path_access = []; + foreach ($entities as $entity_key => $entity) { + foreach ($sprintf_path_patterns as $pattern_key => $sprintf_path_pattern) { + $path = sprintf($sprintf_path_pattern, $entity->id()); + $has_path_access = Url::fromUserInput($path)->access($account); + if ($has_path_access) { + $actual_path_access[$entity_key][] = $pattern_key; } } } $this->assertSameYaml( $expected, - $actual, - 'Users with access to given paths', + $actual_path_access, + 'Path access: ' . $message, ); }