added permission caching (#3877)
This commit is contained in:
@@ -6515,11 +6515,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Repository/Result/TimesheetResult.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Repository\\\\RolePermissionRepository\\:\\:saveRolePermission\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Repository/RolePermissionRepository.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Repository\\\\RoleRepository\\:\\:deleteRole\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
|
||||
@@ -16,11 +16,11 @@ use App\Event\PermissionSectionsEvent;
|
||||
use App\Event\PermissionsEvent;
|
||||
use App\Form\RoleType;
|
||||
use App\Model\PermissionSection;
|
||||
use App\Repository\RolePermissionRepository;
|
||||
use App\Repository\RoleRepository;
|
||||
use App\Repository\UserRepository;
|
||||
use App\Security\RolePermissionManager;
|
||||
use App\Security\RoleService;
|
||||
use App\User\PermissionService;
|
||||
use App\Utils\PageSetup;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -227,7 +227,7 @@ final class PermissionController extends AbstractController
|
||||
|
||||
#[Route(path: '/roles/{id}/{name}/{value}/{csrfToken}', name: 'admin_user_permission_save', methods: ['POST'])]
|
||||
#[IsGranted('role_permissions')]
|
||||
public function savePermission(Role $role, string $name, bool $value, string $csrfToken, RolePermissionRepository $rolePermissionRepository, CsrfTokenManagerInterface $csrfTokenManager): Response
|
||||
public function savePermission(Role $role, string $name, bool $value, string $csrfToken, PermissionService $permissionService, CsrfTokenManagerInterface $csrfTokenManager): Response
|
||||
{
|
||||
if (!$this->isCsrfTokenValid(self::TOKEN_NAME, $csrfToken)) {
|
||||
throw new BadRequestHttpException('Invalid CSRF token');
|
||||
@@ -242,7 +242,7 @@ final class PermissionController extends AbstractController
|
||||
}
|
||||
|
||||
try {
|
||||
$permission = $rolePermissionRepository->findRolePermission($role, $name);
|
||||
$permission = $permissionService->findRolePermission($role, $name);
|
||||
if (null === $permission) {
|
||||
$permission = new RolePermission();
|
||||
$permission->setRole($role);
|
||||
@@ -250,7 +250,7 @@ final class PermissionController extends AbstractController
|
||||
}
|
||||
$permission->setAllowed($value);
|
||||
|
||||
$rolePermissionRepository->saveRolePermission($permission);
|
||||
$permissionService->saveRolePermission($permission);
|
||||
|
||||
// refreshToken instead of getToken for more security but worse UX
|
||||
// fast clicking with slow response times would fail, as the token cannot be replaced fast enough
|
||||
|
||||
@@ -18,7 +18,7 @@ use Doctrine\ORM\EntityRepository;
|
||||
*/
|
||||
class RolePermissionRepository extends EntityRepository
|
||||
{
|
||||
public function saveRolePermission(RolePermission $permission)
|
||||
public function saveRolePermission(RolePermission $permission): void
|
||||
{
|
||||
$entityManager = $this->getEntityManager();
|
||||
$entityManager->persist($permission);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
namespace App\Security;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Repository\RolePermissionRepository;
|
||||
use App\User\PermissionService;
|
||||
|
||||
final class RolePermissionManager
|
||||
{
|
||||
@@ -29,11 +29,11 @@ final class RolePermissionManager
|
||||
private bool $isInitialized = false;
|
||||
|
||||
/**
|
||||
* @param RolePermissionRepository $repository
|
||||
* @param PermissionService $service
|
||||
* @param array<string, array<string, bool>> $permissions as defined in kimai.yaml
|
||||
* @param array<string, bool> $permissionNames as defined in kimai.yaml
|
||||
*/
|
||||
public function __construct(private RolePermissionRepository $repository, private array $permissions, private array $permissionNames)
|
||||
public function __construct(private PermissionService $service, private array $permissions, private array $permissionNames)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ final class RolePermissionManager
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->repository->getAllAsArray() as $item) {
|
||||
foreach ($this->service->getPermissions() as $item) {
|
||||
$perm = (string) $item['permission'];
|
||||
$role = (string) $item['role'];
|
||||
|
||||
|
||||
62
src/User/PermissionService.php
Normal file
62
src/User/PermissionService.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\User;
|
||||
|
||||
use App\Entity\Role;
|
||||
use App\Entity\RolePermission;
|
||||
use App\Repository\RolePermissionRepository;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
|
||||
/**
|
||||
* Caches permissions, which rarely change once Kimai is setup.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class PermissionService
|
||||
{
|
||||
/**
|
||||
* @var null|array<int, array<string, string|bool>>
|
||||
*/
|
||||
private ?array $cacheAll = null;
|
||||
|
||||
public function __construct(
|
||||
private RolePermissionRepository $repository,
|
||||
private CacheInterface $cache
|
||||
) {
|
||||
}
|
||||
|
||||
public function saveRolePermission(RolePermission $permission): void
|
||||
{
|
||||
$this->repository->saveRolePermission($permission);
|
||||
$this->cache->delete('permissions');
|
||||
}
|
||||
|
||||
public function findRolePermission(Role $role, string $permission): ?RolePermission
|
||||
{
|
||||
return $this->repository->findRolePermission($role, $permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, string|bool>>
|
||||
*/
|
||||
public function getPermissions(): array
|
||||
{
|
||||
if ($this->cacheAll === null) {
|
||||
$this->cacheAll = $this->cache->get('permissions', function (ItemInterface $item) {
|
||||
$item->expiresAfter(86400); // one day
|
||||
|
||||
return $this->repository->getAllAsArray();
|
||||
});
|
||||
}
|
||||
|
||||
return $this->cacheAll;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,9 @@ namespace App\Tests\Security;
|
||||
use App\Entity\User;
|
||||
use App\Repository\RolePermissionRepository;
|
||||
use App\Security\RolePermissionManager;
|
||||
use App\User\PermissionService;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
|
||||
/**
|
||||
* @covers \App\Security\RolePermissionManager
|
||||
@@ -23,9 +25,10 @@ class RolePermissionManagerTest extends TestCase
|
||||
{
|
||||
$repository = $this->getMockBuilder(RolePermissionRepository::class)->onlyMethods(['getAllAsArray'])->disableOriginalConstructor()->getMock();
|
||||
$repository->method('getAllAsArray')->willReturn([]);
|
||||
|
||||
/** @var RolePermissionRepository $repository */
|
||||
$sut = new RolePermissionManager($repository, [], []);
|
||||
$service = new PermissionService($repository, new ArrayAdapter());
|
||||
|
||||
$sut = new RolePermissionManager($service, [], []);
|
||||
self::assertFalse($sut->isRegisteredPermission('foo'));
|
||||
self::assertEquals([], $sut->getPermissions());
|
||||
self::assertFalse($sut->hasPermission('TEST_ROLE', 'foo'));
|
||||
@@ -39,9 +42,10 @@ class RolePermissionManagerTest extends TestCase
|
||||
['permission' => 'bar', 'role' => 'USER_ROLE', 'allowed' => true],
|
||||
['permission' => 'foo', 'role' => 'USER_ROLE', 'allowed' => false],
|
||||
]);
|
||||
|
||||
/** @var RolePermissionRepository $repository */
|
||||
$sut = new RolePermissionManager($repository, [], []);
|
||||
$service = new PermissionService($repository, new ArrayAdapter());
|
||||
|
||||
$sut = new RolePermissionManager($service, [], []);
|
||||
|
||||
// only data injected through the config will be registered as "known"
|
||||
self::assertFalse($sut->isRegisteredPermission('foo'));
|
||||
@@ -57,9 +61,10 @@ class RolePermissionManagerTest extends TestCase
|
||||
{
|
||||
$repository = $this->getMockBuilder(RolePermissionRepository::class)->onlyMethods(['getAllAsArray'])->disableOriginalConstructor()->getMock();
|
||||
$repository->method('getAllAsArray')->willReturn([]);
|
||||
|
||||
/** @var RolePermissionRepository $repository */
|
||||
$sut = new RolePermissionManager($repository, ['TEST_ROLE' => ['foo' => true], 'USER_ROLE' => ['bar' => true]], ['foo' => true, 'bar' => true]);
|
||||
$service = new PermissionService($repository, new ArrayAdapter());
|
||||
|
||||
$sut = new RolePermissionManager($service, ['TEST_ROLE' => ['foo' => true], 'USER_ROLE' => ['bar' => true]], ['foo' => true, 'bar' => true]);
|
||||
|
||||
self::assertTrue($sut->isRegisteredPermission('foo'));
|
||||
self::assertTrue($sut->isRegisteredPermission('bar'));
|
||||
@@ -82,9 +87,10 @@ class RolePermissionManagerTest extends TestCase
|
||||
['permission' => 'view_user', 'role' => 'ROLE_SUPER_ADMIN', 'allowed' => false],
|
||||
['permission' => 'create_user', 'role' => 'ROLE_SUPER_ADMIN', 'allowed' => false],
|
||||
]);
|
||||
|
||||
/** @var RolePermissionRepository $repository */
|
||||
$sut = new RolePermissionManager($repository, [
|
||||
$service = new PermissionService($repository, new ArrayAdapter());
|
||||
|
||||
$sut = new RolePermissionManager($service, [
|
||||
'ROLE_SUPER_ADMIN' => ['role_permissions' => true, 'view_user' => true, 'create_user' => true],
|
||||
'TEST_ROLE' => ['foo2' => true, 'foo' => true],
|
||||
'USER_ROLE' => ['foo' => true, 'bar' => true]
|
||||
|
||||
@@ -12,7 +12,9 @@ namespace App\Tests\Voter;
|
||||
use App\Entity\User;
|
||||
use App\Repository\RolePermissionRepository;
|
||||
use App\Security\RolePermissionManager;
|
||||
use App\User\PermissionService;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
|
||||
abstract class AbstractVoterTest extends TestCase
|
||||
@@ -101,8 +103,9 @@ abstract class AbstractVoterTest extends TestCase
|
||||
$names[$name] = true;
|
||||
}
|
||||
}
|
||||
/** @var RolePermissionRepository $repository */
|
||||
$service = new PermissionService($repository, new ArrayAdapter());
|
||||
|
||||
/* @var RolePermissionRepository $repository */
|
||||
return new RolePermissionManager($repository, $perms, $names);
|
||||
return new RolePermissionManager($service, $perms, $names);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user