Skip to content

Commit

Permalink
Add Voter for permissions (#56)
Browse files Browse the repository at this point in the history
* Add Voter for permissions, rename Permission constants, refactor openapi crud operations to small case

* Load permissions from database

* Deactivate voter in controller

* Remove test 4

* use interface
  • Loading branch information
mattamon authored May 13, 2024
1 parent 7ee01fa commit 642aa2b
Show file tree
Hide file tree
Showing 16 changed files with 145 additions and 17 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"minimum-stability": "dev",
"require": {
"php": "~8.2",
"pimcore/static-resolver-bundle": "^1.3",
"pimcore/static-resolver-bundle": "1.x-dev",
"pimcore/generic-data-index-bundle": "1.x-dev",
"pimcore/pimcore": "^11.0",
"zircote/swagger-php": "^4.8"
Expand Down
4 changes: 4 additions & 0 deletions config/security.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ services:
tags:
- { name: security.voter }

Pimcore\Bundle\StudioBackendBundle\Security\Voter\UserPermissionVoter:
tags:
- { name: security.voter }

Pimcore\Bundle\StudioBackendBundle\Security\Voter\PublicAuthorizationVoter:
arguments: [ '@request_stack' ]
tags:
Expand Down
3 changes: 2 additions & 1 deletion src/Asset/Controller/CollectionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public function __construct(
*/
#[Route('/assets', name: 'pimcore_studio_api_assets', methods: ['GET'])]
//#[IsGranted('STUDIO_API')]
#[GET(
//#[IsGranted(UserPermissions::ASSETS->value)]
#[Get(
path: self::API_PATH . '/assets',
operationId: 'getAssets',
description: 'Get paginated assets',
Expand Down
3 changes: 2 additions & 1 deletion src/Asset/Controller/GetController.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ public function __construct(

#[Route('/assets/{id}', name: 'pimcore_studio_api_get_asset', methods: ['GET'])]
//#[IsGranted('STUDIO_API')]
#[GET(
//#[IsGranted(UserPermissions::ASSETS->value)]
#[Get(
path: self::API_PATH . '/assets/{id}',
operationId: 'getAssetById',
description: 'Get assets by id by path parameter',
Expand Down
2 changes: 1 addition & 1 deletion src/Authorization/Controller/AuthorizationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public function __construct(
* @throws AccessDeniedException
*/
#[Route('/login', name: 'pimcore_studio_api_login', methods: ['POST'])]
#[POST(
#[Post(
path: self::API_PATH . '/login',
operationId: 'login',
summary: 'Login with user credentials and get access token',
Expand Down
2 changes: 1 addition & 1 deletion src/DataObject/Controller/CollectionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public function __construct(
*/
#[Route('/data-objects', name: 'pimcore_studio_api_data_objects', methods: ['GET'])]
//#[IsGranted(self::VOTER_STUDIO_API)]
#[GET(
#[Get(
path: self::API_PATH . '/data-objects',
operationId: 'getDataObjects',
description: 'Get paginated data objects',
Expand Down
2 changes: 1 addition & 1 deletion src/DataObject/Controller/GetController.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function __construct(

#[Route('/data-objects/{id}', name: 'pimcore_studio_api_get_data_object', methods: ['GET'])]
//#[IsGranted('STUDIO_API')]
#[GET(
#[Get(
path: self::API_PATH . '/data-objects/{id}',
operationId: 'getDataObjectById',
description: 'Get data object by id by path parameter',
Expand Down
95 changes: 95 additions & 0 deletions src/Security/Voter/UserPermissionVoter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioBackendBundle\Security\Voter;

use Doctrine\DBAL\Exception;
use Pimcore\Bundle\StaticResolverBundle\Db\DbResolverInterface;
use Pimcore\Bundle\StaticResolverBundle\Lib\CacheResolverInterface;
use Pimcore\Bundle\StudioBackendBundle\Exception\AccessDeniedException;
use Pimcore\Bundle\StudioBackendBundle\Security\Service\SecurityServiceInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;

/**
* @internal
*/
final class UserPermissionVoter extends Voter
{
private const USER_PERMISSIONS_CACHE_KEY = 'studio_backend_user_permissions';

private array $userPermissions;

public function __construct(
private readonly CacheResolverInterface $cacheResolver,
private readonly DbResolverInterface $dbResolver,
private readonly SecurityServiceInterface $securityService

) {
$this->getUserPermissions();
}

/**
* @inheritDoc
*/
protected function supports(string $attribute, mixed $subject): bool
{
return in_array($attribute, $this->userPermissions, true);
}

protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
if (!$this->securityService->getCurrentUser()->isAllowed($attribute)) {
throw new AccessDeniedException(sprintf('User does not have permission: %s', $attribute));
}

return true;
}

/**
* @throws AccessDeniedException
*/
private function getUserPermissions(): void
{
$userPermissions = $this->cacheResolver->load(self::USER_PERMISSIONS_CACHE_KEY);

if($userPermissions !== false && is_array($userPermissions)) {
$this->userPermissions = $userPermissions;
return;
}

$userPermissions = $this->getUserPermissionsFromDataBase();

$this->cacheResolver->save(
$userPermissions,
self::USER_PERMISSIONS_CACHE_KEY
);

$this->userPermissions = $userPermissions;
}

private function getUserPermissionsFromDataBase(): array
{
try {
$userPermissions = $this->dbResolver->getConnection()->fetchFirstColumn(
'SELECT `key` FROM users_permission_definitions'
);
} catch (Exception) {
throw new AccessDeniedException('Cannot resolve user permissions');
}
return $userPermissions;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
/**
* @internal
*/
final class Permissions
final class ElementPermissions
{
public const LIST_PERMISSION = 'list';

Expand Down
27 changes: 27 additions & 0 deletions src/Util/Constants/UserPermissions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioBackendBundle\Util\Constants;

/**
* @internal
*/
enum UserPermissions: string
{
case ASSETS = 'assets';
case DOCUMENTS = 'documents';
case OBJECTS = 'objects';
}
2 changes: 1 addition & 1 deletion src/Version/Controller/CollectionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public function __construct(

#[Route('/versions', name: 'pimcore_studio_api_versions', methods: ['GET'])]
//#[IsGranted('STUDIO_API')]
#[GET(
#[Get(
path: self::API_PATH . '/versions',
operationId: 'getVersions',
description: 'Get paginated versions',
Expand Down
4 changes: 2 additions & 2 deletions src/Version/Controller/DeleteController.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attributes\Response\SuccessResponse;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Config\Tags;
use Pimcore\Bundle\StudioBackendBundle\Security\Service\SecurityServiceInterface;
use Pimcore\Bundle\StudioBackendBundle\Util\Constants\Permissions;
use Pimcore\Bundle\StudioBackendBundle\Util\Constants\ElementPermissions;
use Pimcore\Bundle\StudioBackendBundle\Version\RepositoryInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
Expand Down Expand Up @@ -74,7 +74,7 @@ public function deleteVersion(int $id): JsonResponse
$this->securityService->hasElementPermission(
$version->getData(),
$user,
Permissions::VERSIONS_PERMISSION
ElementPermissions::VERSIONS_PERMISSION
);
$version->delete();

Expand Down
2 changes: 1 addition & 1 deletion src/Version/Controller/GetController.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public function __construct(

#[Route('/versions/{id}', name: 'pimcore_studio_api_get_version', methods: ['GET'])]
//#[IsGranted('STUDIO_API')]
#[GET(
#[Get(
path: self::API_PATH . '/versions/{id}',
operationId: 'getVersionById',
description: 'Get version based on the version ID',
Expand Down
2 changes: 1 addition & 1 deletion src/Version/Controller/PublishController.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public function __construct(

#[Route('/versions/{id}', name: 'pimcore_studio_api_publish_version', methods: ['POST'])]
//#[IsGranted('STUDIO_API')]
#[POST(
#[Post(
path: self::API_PATH . '/versions/{id}',
operationId: 'publishVersion',
description: 'Publish element based on the version ID',
Expand Down
4 changes: 2 additions & 2 deletions src/Version/Publisher/VersionPublisherService.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
use Pimcore\Bundle\StudioBackendBundle\Exception\ElementPublishingFailedException;
use Pimcore\Bundle\StudioBackendBundle\Exception\InvalidElementTypeException;
use Pimcore\Bundle\StudioBackendBundle\Security\Service\SecurityServiceInterface;
use Pimcore\Bundle\StudioBackendBundle\Util\Constants\Permissions;
use Pimcore\Bundle\StudioBackendBundle\Util\Constants\ElementPermissions;
use Pimcore\Bundle\StudioBackendBundle\Util\Traits\ElementProviderTrait;
use Pimcore\Bundle\StudioBackendBundle\Version\RepositoryInterface;
use Pimcore\Model\UserInterface;
Expand Down Expand Up @@ -61,7 +61,7 @@ public function publishVersion(
$this->securityService->hasElementPermission(
$currentElement,
$user,
Permissions::PUBLISH_PERMISSION
ElementPermissions::PUBLISH_PERMISSION
);

$class = $this->getElementClass($currentElement);
Expand Down
6 changes: 3 additions & 3 deletions src/Version/Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
use Pimcore\Bundle\StaticResolverBundle\Models\Version\VersionResolver;
use Pimcore\Bundle\StudioBackendBundle\Exception\ElementNotFoundException;
use Pimcore\Bundle\StudioBackendBundle\Security\Service\SecurityServiceInterface;
use Pimcore\Bundle\StudioBackendBundle\Util\Constants\Permissions;
use Pimcore\Bundle\StudioBackendBundle\Util\Constants\ElementPermissions;
use Pimcore\Bundle\StudioBackendBundle\Util\Traits\ElementProviderTrait;
use Pimcore\Bundle\StudioBackendBundle\Version\Request\VersionCleanupParameters;
use Pimcore\Bundle\StudioBackendBundle\Version\Request\VersionParameters;
Expand Down Expand Up @@ -50,7 +50,7 @@ public function listVersions(
$this->securityService->hasElementPermission(
$element,
$user,
Permissions::VERSIONS_PERMISSION
ElementPermissions::VERSIONS_PERMISSION
);

$limit = $parameters->getPageSize();
Expand Down Expand Up @@ -95,7 +95,7 @@ public function getElementFromVersion(
$this->securityService->hasElementPermission(
$element,
$user,
Permissions::VERSIONS_PERMISSION
ElementPermissions::VERSIONS_PERMISSION
);

return $element;
Expand Down

0 comments on commit 642aa2b

Please sign in to comment.