diff --git a/eZ/Publish/API/Repository/Tests/URLAliasServiceAuthorizationTest.php b/eZ/Publish/API/Repository/Tests/URLAliasServiceAuthorizationTest.php new file mode 100644 index 00000000000..7b831d0cd84 --- /dev/null +++ b/eZ/Publish/API/Repository/Tests/URLAliasServiceAuthorizationTest.php @@ -0,0 +1,103 @@ +getRepository(); + + $anonymousUserId = $this->generateId('user', 10); + $parentLocationId = $this->generateId('location', 2); + /* BEGIN: Use Case */ + // $anonymousUserId is the ID of the "Anonymous" user in a eZ + // Publish demo installation. + // $locationId is the ID of an existing location + $userService = $repository->getUserService(); + $urlAliasService = $repository->getURLAliasService(); + $locationService = $repository->getLocationService(); + + $location = $locationService->newLocationCreateStruct($parentLocationId); + + $anonymousUser = $userService->loadUser($anonymousUserId); + $repository->getPermissionResolver()->setCurrentUserReference($anonymousUser); + + // This call will fail with an UnauthorizedException + $urlAliasService->createUrlAlias($location, '/Home/My-New-Site', 'eng-US'); + /* END: Use Case */ + } + + /** + * Test for the createGlobalUrlAlias() method. + * + * @covers \eZ\Publish\API\Repository\URLAliasService::createGlobalUrlAlias() + * @expectedException \eZ\Publish\API\Repository\Exceptions\UnauthorizedException + * @depends \eZ\Publish\API\Repository\Tests\URLAliasServiceTest::testCreateGlobalUrlAlias + */ + public function testCreateGlobalUrlAliasThrowsUnauthorizedException() + { + $repository = $this->getRepository(); + + $anonymousUserId = $this->generateId('user', 10); + /* BEGIN: Use Case */ + // $anonymousUserId is the ID of the "Anonymous" user in a eZ + // Publish demo installation. + $userService = $repository->getUserService(); + $urlAliasService = $repository->getURLAliasService(); + + $anonymousUser = $userService->loadUser($anonymousUserId); + $repository->getPermissionResolver()->setCurrentUserReference($anonymousUser); + + // This call will fail with an UnauthorizedException + $urlAliasService->createGlobalUrlAlias('module:content/search?SearchText=eZ', '/Home/My-New-Site', 'eng-US'); + /* END: Use Case */ + } + + /** + * Test for the removeAliases() method. + * + * @covers \eZ\Publish\API\Repository\URLAliasService::removeAliases() + * @expectedException \eZ\Publish\API\Repository\Exceptions\UnauthorizedException + * @depends \eZ\Publish\API\Repository\Tests\URLAliasServiceTest::testRemoveAliases + */ + public function testRemoveAliasesThrowsUnauthorizedException() + { + $repository = $this->getRepository(); + $anonymousUserId = $this->generateId('user', 10); + + $locationService = $repository->getLocationService(); + $someLocation = $locationService->loadLocation( + $this->generateId('location', 12) + ); + + /* BEGIN: Use Case */ + // $someLocation contains a location with automatically generated + // aliases assigned + // $anonymousUserId is the ID of the "Anonymous" user in a eZ + $urlAliasService = $repository->getURLAliasService(); + $userService = $repository->getUserService(); + + $anonymousUser = $userService->loadUser($anonymousUserId); + $repository->getPermissionResolver()->setCurrentUserReference($anonymousUser); + + $initialAliases = $urlAliasService->listLocationAliases($someLocation); + + // This call will fail with an UnauthorizedException + $urlAliasService->removeAliases($initialAliases); + /* END: Use Case */ + } +} diff --git a/eZ/Publish/API/Repository/URLAliasService.php b/eZ/Publish/API/Repository/URLAliasService.php index 0c42f52104e..a1e7edf643b 100644 --- a/eZ/Publish/API/Repository/URLAliasService.php +++ b/eZ/Publish/API/Repository/URLAliasService.php @@ -30,6 +30,7 @@ interface URLAliasService * @param bool $forwarding if true a redirect is performed * @param bool $alwaysAvailable * + * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create url alias * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the path already exists for the given language * * @return \eZ\Publish\API\Repository\Values\Content\URLAlias @@ -46,6 +47,8 @@ public function createUrlAlias(Location $location, $path, $languageCode, $forwar * * $alwaysAvailable makes the alias available in all languages. * + * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create global + * url alias * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the path already exists for the given * language or if resource is not valid * @@ -86,6 +89,7 @@ public function listGlobalAliases($languageCode = null, $offset = 0, $limit = -1 * * This method does not remove autogenerated aliases for locations. * + * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove url alias * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if alias list contains * autogenerated alias * diff --git a/eZ/Publish/Core/Repository/Tests/Service/Mock/UrlAliasTest.php b/eZ/Publish/Core/Repository/Tests/Service/Mock/UrlAliasTest.php index e85583b4440..1d217cd88c0 100644 --- a/eZ/Publish/Core/Repository/Tests/Service/Mock/UrlAliasTest.php +++ b/eZ/Publish/Core/Repository/Tests/Service/Mock/UrlAliasTest.php @@ -17,6 +17,7 @@ use eZ\Publish\Core\Repository\Values\Content\Location; use eZ\Publish\Core\Base\Exceptions\NotFoundException; use eZ\Publish\Core\Base\Exceptions\ForbiddenException; +use eZ\Publish\API\Repository\PermissionResolver; use Exception; /** @@ -189,6 +190,21 @@ public function testRemoveAliasesThrowsInvalidArgumentException() { $aliasList = array(new UrlAlias(array('isCustom' => false))); $mockedService = $this->getPartlyMockedURLAliasServiceService(); + $permissionResolverMock = $this->createMock(PermissionResolver::class); + $permissionResolverMock + ->expects($this->once()) + ->method('hasAccess')->with( + $this->equalTo('content'), + $this->equalTo('urltranslator') + )->will($this->returnValue(true)); + + $repositoryMock = $this->getRepositoryMock(); + + $repositoryMock + ->expects($this->atLeastOnce()) + ->method('getPermissionResolver') + ->willReturn($permissionResolverMock); + $mockedService->removeAliases($aliasList); } @@ -199,7 +215,20 @@ public function testRemoveAliases() { $aliasList = array(new UrlAlias(array('isCustom' => true))); $spiAliasList = array(new SPIUrlAlias(array('isCustom' => true))); + $permissionResolverMock = $this->createMock(PermissionResolver::class); + $permissionResolverMock + ->expects($this->once()) + ->method('hasAccess')->with( + $this->equalTo('content'), + $this->equalTo('urltranslator') + )->will($this->returnValue(true)); + $repositoryMock = $this->getRepositoryMock(); + + $repositoryMock + ->expects($this->atLeastOnce()) + ->method('getPermissionResolver') + ->willReturn($permissionResolverMock); $mockedService = $this->getPartlyMockedURLAliasServiceService(); /** @var \PHPUnit\Framework\MockObject\MockObject $urlAliasHandlerMock */ $urlAliasHandlerMock = $this->getPersistenceMock()->urlAliasHandler(); @@ -229,7 +258,20 @@ public function testRemoveAliasesWithRollback() { $aliasList = array(new UrlAlias(array('isCustom' => true))); $spiAliasList = array(new SPIUrlAlias(array('isCustom' => true))); + $permissionResolverMock = $this->createMock(PermissionResolver::class); + $permissionResolverMock + ->expects($this->once()) + ->method('hasAccess')->with( + $this->equalTo('content'), + $this->equalTo('urltranslator') + )->will($this->returnValue(true)); + $repositoryMock = $this->getRepositoryMock(); + + $repositoryMock + ->expects($this->atLeastOnce()) + ->method('getPermissionResolver') + ->willReturn($permissionResolverMock); $mockedService = $this->getPartlyMockedURLAliasServiceService(); /** @var \PHPUnit\Framework\MockObject\MockObject $urlAliasHandlerMock */ $urlAliasHandlerMock = $this->getPersistenceMock()->urlAliasHandler(); @@ -3174,7 +3216,21 @@ public function testReverseLookupWithShowAllTranslations() */ public function testCreateUrlAlias() { + $permissionResolverMock = $this->createMock(PermissionResolver::class); + $permissionResolverMock + ->expects($this->once()) + ->method('hasAccess')->with( + $this->equalTo('content'), + $this->equalTo('urltranslator') + )->will($this->returnValue(true)); + $repositoryMock = $this->getRepositoryMock(); + + $repositoryMock + ->expects($this->atLeastOnce()) + ->method('getPermissionResolver') + ->willReturn($permissionResolverMock); + $mockedService = $this->getPartlyMockedURLAliasServiceService(); /** @var \PHPUnit\Framework\MockObject\MockObject $urlAliasHandlerMock */ $urlAliasHandlerMock = $this->getPersistenceMock()->urlAliasHandler(); @@ -3220,7 +3276,21 @@ public function testCreateUrlAlias() */ public function testCreateUrlAliasWithRollback() { + $permissionResolverMock = $this->createMock(PermissionResolver::class); + $permissionResolverMock + ->expects($this->once()) + ->method('hasAccess')->with( + $this->equalTo('content'), + $this->equalTo('urltranslator') + )->will($this->returnValue(true)); + $repositoryMock = $this->getRepositoryMock(); + + $repositoryMock + ->expects($this->atLeastOnce()) + ->method('getPermissionResolver') + ->willReturn($permissionResolverMock); + $mockedService = $this->getPartlyMockedURLAliasServiceService(); /** @var \PHPUnit\Framework\MockObject\MockObject $urlAliasHandlerMock */ $urlAliasHandlerMock = $this->getPersistenceMock()->urlAliasHandler(); @@ -3264,10 +3334,26 @@ public function testCreateUrlAliasWithRollback() public function testCreateUrlAliasThrowsInvalidArgumentException() { $location = $this->getLocationStub(); - $urlAliasService = $this->getRepository()->getURLAliasService(); - $urlAliasHandler = $this->getPersistenceMockHandler('Content\\UrlAlias\\Handler'); - $urlAliasHandler->expects( + $mockedService = $this->getPartlyMockedURLAliasServiceService(); + /** @var \PHPUnit\Framework\MockObject\MockObject $handlerMock */ + $handlerMock = $this->getPersistenceMock()->urlAliasHandler(); + $permissionResolverMock = $this->createMock(PermissionResolver::class); + $permissionResolverMock + ->expects($this->once()) + ->method('hasAccess')->with( + $this->equalTo('content'), + $this->equalTo('urltranslator') + )->will($this->returnValue(true)); + + $repositoryMock = $this->getRepositoryMock(); + + $repositoryMock + ->expects($this->atLeastOnce()) + ->method('getPermissionResolver') + ->willReturn($permissionResolverMock); + + $handlerMock->expects( $this->once() )->method( 'createCustomUrlAlias' @@ -3281,7 +3367,7 @@ public function testCreateUrlAliasThrowsInvalidArgumentException() $this->throwException(new ForbiddenException('Forbidden!')) ); - $urlAliasService->createUrlAlias( + $mockedService->createUrlAlias( $location, 'path', 'languageCode', @@ -3296,7 +3382,20 @@ public function testCreateUrlAliasThrowsInvalidArgumentException() public function testCreateGlobalUrlAlias() { $resource = 'module:content/search'; + $permissionResolverMock = $this->createMock(PermissionResolver::class); + $permissionResolverMock + ->expects($this->once()) + ->method('hasAccess')->with( + $this->equalTo('content'), + $this->equalTo('urltranslator') + )->will($this->returnValue(true)); + $repositoryMock = $this->getRepositoryMock(); + + $repositoryMock + ->expects($this->atLeastOnce()) + ->method('getPermissionResolver') + ->willReturn($permissionResolverMock); $mockedService = $this->getPartlyMockedURLAliasServiceService(); /** @var \PHPUnit\Framework\MockObject\MockObject $urlAliasHandlerMock */ $urlAliasHandlerMock = $this->getPersistenceMock()->urlAliasHandler(); @@ -3342,7 +3441,20 @@ public function testCreateGlobalUrlAlias() public function testCreateGlobalUrlAliasWithRollback() { $resource = 'module:content/search'; + $permissionResolverMock = $this->createMock(PermissionResolver::class); + $permissionResolverMock + ->expects($this->once()) + ->method('hasAccess')->with( + $this->equalTo('content'), + $this->equalTo('urltranslator') + )->will($this->returnValue(true)); + $repositoryMock = $this->getRepositoryMock(); + + $repositoryMock + ->expects($this->atLeastOnce()) + ->method('getPermissionResolver') + ->willReturn($permissionResolverMock); $mockedService = $this->getPartlyMockedURLAliasServiceService(); /** @var \PHPUnit\Framework\MockObject\MockObject $urlAliasHandlerMock */ $urlAliasHandlerMock = $this->getPersistenceMock()->urlAliasHandler(); @@ -3384,8 +3496,23 @@ public function testCreateGlobalUrlAliasWithRollback() */ public function testCreateGlobalUrlAliasThrowsInvalidArgumentExceptionResource() { - $urlAliasService = $this->getRepository()->getURLAliasService(); - $urlAliasService->createGlobalUrlAlias( + $mockedService = $this->getPartlyMockedURLAliasServiceService(); + $permissionResolverMock = $this->createMock(PermissionResolver::class); + $permissionResolverMock + ->expects($this->once()) + ->method('hasAccess')->with( + $this->equalTo('content'), + $this->equalTo('urltranslator') + )->will($this->returnValue(true)); + + $repositoryMock = $this->getRepositoryMock(); + + $repositoryMock + ->expects($this->atLeastOnce()) + ->method('getPermissionResolver') + ->willReturn($permissionResolverMock); + + $mockedService->createGlobalUrlAlias( 'invalid/resource', 'path', 'languageCode', @@ -3402,7 +3529,21 @@ public function testCreateGlobalUrlAliasThrowsInvalidArgumentExceptionResource() public function testCreateGlobalUrlAliasThrowsInvalidArgumentExceptionPath() { $resource = 'module:content/search'; - $urlAliasService = $this->getRepository()->getURLAliasService(); + $mockedService = $this->getPartlyMockedURLAliasServiceService(); + $permissionResolverMock = $this->createMock(PermissionResolver::class); + $permissionResolverMock + ->expects($this->once()) + ->method('hasAccess')->with( + $this->equalTo('content'), + $this->equalTo('urltranslator') + )->will($this->returnValue(true)); + + $repositoryMock = $this->getRepositoryMock(); + + $repositoryMock + ->expects($this->atLeastOnce()) + ->method('getPermissionResolver') + ->willReturn($permissionResolverMock); $urlAliasHandler = $this->getPersistenceMockHandler('Content\\UrlAlias\\Handler'); $urlAliasHandler->expects( @@ -3419,7 +3560,7 @@ public function testCreateGlobalUrlAliasThrowsInvalidArgumentExceptionPath() $this->throwException(new ForbiddenException('Forbidden!')) ); - $urlAliasService->createGlobalUrlAlias( + $mockedService->createGlobalUrlAlias( $resource, 'path', 'languageCode', @@ -3460,6 +3601,19 @@ public function testCreateGlobalUrlAliasForLocation() $this->returnValue($locationServiceMock) ); + $permissionResolverMock = $this->createMock(PermissionResolver::class); + $permissionResolverMock + ->expects($this->exactly(2)) + ->method('hasAccess')->with( + $this->equalTo('content'), + $this->equalTo('urltranslator') + )->will($this->returnValue(true)); + + $repositoryMock + ->expects($this->atLeastOnce()) + ->method('getPermissionResolver') + ->willReturn($permissionResolverMock); + $mockedService->expects( $this->exactly(2) )->method( @@ -3552,4 +3706,96 @@ protected function getPartlyMockedURLAliasServiceService(array $methods = null) ) ->getMock(); } + + /** + * Test for the createUrlAlias() method. + * + * @depends testConstructor + * @covers \eZ\Publish\Core\Repository\URLAliasService::createUrlAlias + * @expectedException \eZ\Publish\API\Repository\Exceptions\UnauthorizedException + */ + public function testCreateUrlAliasThrowsUnauthorizedException() + { + $mockedService = $this->getPartlyMockedURLAliasServiceService(); + $repositoryMock = $this->getRepositoryMock(); + $location = $this->getLocationStub(); + $permissionResolverMock = $this->createMock(PermissionResolver::class); + $permissionResolverMock + ->expects($this->once()) + ->method('hasAccess')->with( + $this->equalTo('content'), + $this->equalTo('urltranslator') + )->will($this->returnValue(false)); + + $repositoryMock + ->expects($this->atLeastOnce()) + ->method('getPermissionResolver') + ->willReturn($permissionResolverMock); + + $mockedService->createUrlAlias( + $location, + 'path', + 'languageCode', + 'forwarding' + ); + } + + /** + * Test for the createGlobalUrlAlias() method. + * + * @depends testConstructor + * @covers \eZ\Publish\Core\Repository\URLAliasService::createGlobalUrlAlias + * @expectedException \eZ\Publish\API\Repository\Exceptions\UnauthorizedException + */ + public function testCreateGlobalUrlAliasThrowsUnauthorizedException() + { + $mockedService = $this->getPartlyMockedURLAliasServiceService(); + $repositoryMock = $this->getRepositoryMock(); + $permissionResolverMock = $this->createMock(PermissionResolver::class); + $permissionResolverMock + ->expects($this->once()) + ->method('hasAccess')->with( + $this->equalTo('content'), + $this->equalTo('urltranslator') + )->will($this->returnValue(false)); + + $repositoryMock + ->expects($this->atLeastOnce()) + ->method('getPermissionResolver') + ->willReturn($permissionResolverMock); + $mockedService->createGlobalUrlAlias( + 'eznode:42', + 'path', + 'languageCode', + 'forwarding', + 'alwaysAvailable' + ); + } + + /** + * Test for the removeAliases() method. + * + * @depends testConstructor + * @covers \eZ\Publish\Core\Repository\URLAliasService::removeAliases + * @expectedException \eZ\Publish\API\Repository\Exceptions\UnauthorizedException + */ + public function testRemoveAliasesThrowsUnauthorizedException() + { + $aliasList = array(new UrlAlias(array('isCustom' => true))); + $mockedService = $this->getPartlyMockedURLAliasServiceService(); + $repositoryMock = $this->getRepositoryMock(); + $permissionResolverMock = $this->createMock(PermissionResolver::class); + $permissionResolverMock + ->expects($this->once()) + ->method('hasAccess')->with( + $this->equalTo('content'), + $this->equalTo('urltranslator') + )->will($this->returnValue(false)); + + $repositoryMock + ->expects($this->atLeastOnce()) + ->method('getPermissionResolver') + ->willReturn($permissionResolverMock); + $mockedService->removeAliases($aliasList); + } } diff --git a/eZ/Publish/Core/Repository/URLAliasService.php b/eZ/Publish/Core/Repository/URLAliasService.php index b41a714f0ce..4bd98ac3dc2 100644 --- a/eZ/Publish/Core/Repository/URLAliasService.php +++ b/eZ/Publish/Core/Repository/URLAliasService.php @@ -16,6 +16,7 @@ use eZ\Publish\SPI\Persistence\Content\URLAlias as SPIURLAlias; use eZ\Publish\Core\Base\Exceptions\NotFoundException; use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException; +use eZ\Publish\Core\Base\Exceptions\UnauthorizedException; use eZ\Publish\API\Repository\Exceptions\ForbiddenException; use Exception; @@ -73,12 +74,17 @@ public function __construct(RepositoryInterface $repository, Handler $urlAliasHa * @param string $languageCode the languageCode for which this alias is valid * @param bool $alwaysAvailable * + * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create url alias * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the path already exists for the given language * * @return \eZ\Publish\API\Repository\Values\Content\URLAlias */ public function createUrlAlias(Location $location, $path, $languageCode, $forwarding = false, $alwaysAvailable = false) { + if ($this->repository->getPermissionResolver()->hasAccess('content', 'urltranslator') !== true) { + throw new UnauthorizedException('content', 'urltranslator'); + } + $path = $this->cleanUrl($path); $this->repository->beginTransaction(); @@ -116,6 +122,8 @@ public function createUrlAlias(Location $location, $path, $languageCode, $forwar * * $alwaysAvailable makes the alias available in all languages. * + * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create global + * url alias * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the path already exists for the given * language or if resource is not valid * @@ -129,6 +137,10 @@ public function createUrlAlias(Location $location, $path, $languageCode, $forwar */ public function createGlobalUrlAlias($resource, $path, $languageCode, $forwarding = false, $alwaysAvailable = false) { + if ($this->repository->getPermissionResolver()->hasAccess('content', 'urltranslator') !== true) { + throw new UnauthorizedException('content', 'urltranslator'); + } + if (!preg_match('#^([a-zA-Z0-9_]+):(.+)$#', $resource, $matches)) { throw new InvalidArgumentException('$resource', 'argument is not valid'); } @@ -557,6 +569,7 @@ public function listGlobalAliases($languageCode = null, $offset = 0, $limit = -1 * * This method does not remove autogenerated aliases for locations. * + * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove url alias * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if alias list contains * autogenerated alias * @@ -564,6 +577,10 @@ public function listGlobalAliases($languageCode = null, $offset = 0, $limit = -1 */ public function removeAliases(array $aliasList) { + if ($this->repository->getPermissionResolver()->hasAccess('content', 'urltranslator') !== true) { + throw new UnauthorizedException('content', 'urltranslator'); + } + $spiUrlAliasList = array(); foreach ($aliasList as $alias) { if (!$alias->isCustom) { diff --git a/phpunit-integration-legacy.xml b/phpunit-integration-legacy.xml index 1c0be2cc710..478e2bbc0fc 100644 --- a/phpunit-integration-legacy.xml +++ b/phpunit-integration-legacy.xml @@ -42,6 +42,7 @@ eZ/Publish/API/Repository/Tests/RoleServiceAuthorizationTest.php eZ/Publish/API/Repository/Tests/TrashServiceAuthorizationTest.php eZ/Publish/API/Repository/Tests/URLServiceAuthorizationTest.php + eZ/Publish/API/Repository/Tests/URLAliasServiceAuthorizationTest.php eZ/Publish/API/Repository/Tests/URLWildcardServiceAuthorizationTest.php eZ/Publish/API/Repository/Tests/ObjectStateServiceAuthorizationTest.php eZ/Publish/API/Repository/Tests/SearchServiceTest.php