Skip to content

Commit 41c1445

Browse files
authored
[Improvement] Add voter for voting on having one permission from a set of permissions (#1538)
* Added voter for voting on having one permission in a set of permissions * Apply php-cs-fixer changes
1 parent 8123289 commit 41c1445

File tree

4 files changed

+106
-7
lines changed

4 files changed

+106
-7
lines changed

config/security.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ services:
1313
tags:
1414
- { name: security.voter }
1515

16+
Pimcore\Bundle\StudioBackendBundle\Security\Voter\HasOneOfUserPermissionVoter:
17+
tags:
18+
- { name: security.voter }
19+
1620
Pimcore\Bundle\StudioBackendBundle\Security\Voter\PublicAuthorizationVoter:
1721
arguments: [ '@request_stack' ]
1822
tags:

src/Bundle/CustomReport/Controller/Config/GetController.php

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@
2525
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\DefaultResponses;
2626
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attribute\Response\SuccessResponse;
2727
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Config\Tags;
28+
use Pimcore\Bundle\StudioBackendBundle\Security\PermissionsToCheck;
29+
use Pimcore\Bundle\StudioBackendBundle\Util\Constant\CustomReportPermissions;
2830
use Pimcore\Bundle\StudioBackendBundle\Util\Constant\HttpResponseCodes;
29-
use Symfony\Component\ExpressionLanguage\Expression;
3031
use Symfony\Component\HttpFoundation\JsonResponse;
3132
use Symfony\Component\Routing\Attribute\Route;
32-
use Symfony\Component\Security\Http\Attribute\IsGranted;
3333
use Symfony\Component\Serializer\SerializerInterface;
3434

3535
/**
@@ -51,11 +51,6 @@ public function __construct(
5151
* @throws Exception
5252
*/
5353
#[Route(self::ROUTE, name: 'pimcore_studio_api_custom_reports_report', methods: ['GET'])]
54-
#[IsGranted(
55-
new Expression(
56-
'is_granted("reports") or is_granted("reports_permissions")'
57-
)
58-
)]
5954
#[Get(
6055
path: self::PREFIX . self::ROUTE,
6156
operationId: 'custom_reports_report',
@@ -76,6 +71,14 @@ public function __construct(
7671
])]
7772
public function getByName(string $name): JsonResponse
7873
{
74+
$this->denyAccessUnlessGranted(
75+
'HasOneOf',
76+
new PermissionsToCheck([
77+
CustomReportPermissions::REPORTS_CONFIG->value,
78+
CustomReportPermissions::REPORTS->value,
79+
])
80+
);
81+
7982
return $this->jsonResponse(
8083
$this->customReportService->getCustomReportDetails($name)
8184
);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* This source file is available under the terms of the
6+
* Pimcore Open Core License (POCL)
7+
* Full copyright and license information is available in
8+
* LICENSE.md which is distributed with this source code.
9+
*
10+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
11+
* @license Pimcore Open Core License (POCL)
12+
*/
13+
14+
namespace Pimcore\Bundle\StudioBackendBundle\Security;
15+
16+
use InvalidArgumentException;
17+
18+
final readonly class PermissionsToCheck
19+
{
20+
public function __construct(
21+
private array $permissionsToCheck
22+
) {
23+
if (empty($this->permissionsToCheck)) {
24+
throw new InvalidArgumentException('Permissions to check must not be empty');
25+
}
26+
}
27+
28+
public function getPermissionsToCheck(): array
29+
{
30+
return $this->permissionsToCheck;
31+
}
32+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* This source file is available under the terms of the
6+
* Pimcore Open Core License (POCL)
7+
* Full copyright and license information is available in
8+
* LICENSE.md which is distributed with this source code.
9+
*
10+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
11+
* @license Pimcore Open Core License (POCL)
12+
*/
13+
14+
namespace Pimcore\Bundle\StudioBackendBundle\Security\Voter;
15+
16+
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\ForbiddenException;
17+
use Pimcore\Bundle\StudioBackendBundle\Security\Service\SecurityServiceInterface;
18+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
19+
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
20+
21+
/**
22+
* @internal
23+
*/
24+
final class HasOneOfUserPermissionVoter extends Voter
25+
{
26+
private const string SUPPORTED_ATTRIBUTE = 'HasOneOf';
27+
28+
public function __construct(
29+
private readonly SecurityServiceInterface $securityService
30+
31+
) {
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
protected function supports(string $attribute, mixed $subject): bool
38+
{
39+
return $attribute === self::SUPPORTED_ATTRIBUTE;
40+
}
41+
42+
/**
43+
* @throws ForbiddenException
44+
*/
45+
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
46+
{
47+
foreach ($subject->getPermissionsToCheck() as $permissionToCheck) {
48+
if ($this->securityService->getCurrentUser()->isAllowed($permissionToCheck)) {
49+
return true;
50+
}
51+
}
52+
53+
throw new ForbiddenException(
54+
'Access denied. User does not have one of the following permissions: '
55+
. implode(
56+
', ', $subject->getPermissionsToCheck()
57+
)
58+
);
59+
}
60+
}

0 commit comments

Comments
 (0)