diff --git a/phpstan.neon b/phpstan.neon index f88ac00d..f6d70c65 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -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 diff --git a/src/Controller/PermissionController.php b/src/Controller/PermissionController.php index fe300314..24bfaa98 100644 --- a/src/Controller/PermissionController.php +++ b/src/Controller/PermissionController.php @@ -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 diff --git a/src/Repository/RolePermissionRepository.php b/src/Repository/RolePermissionRepository.php index 4ac331e2..d3623d2f 100644 --- a/src/Repository/RolePermissionRepository.php +++ b/src/Repository/RolePermissionRepository.php @@ -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); diff --git a/src/Security/RolePermissionManager.php b/src/Security/RolePermissionManager.php index ace5cd31..448c57c4 100644 --- a/src/Security/RolePermissionManager.php +++ b/src/Security/RolePermissionManager.php @@ -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> $permissions as defined in kimai.yaml * @param array $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']; diff --git a/src/User/PermissionService.php b/src/User/PermissionService.php new file mode 100644 index 00000000..f722247f --- /dev/null +++ b/src/User/PermissionService.php @@ -0,0 +1,62 @@ +> + */ + 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> + */ + 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; + } +} diff --git a/tests/Security/RolePermissionManagerTest.php b/tests/Security/RolePermissionManagerTest.php index 388bebbf..52af7aff 100644 --- a/tests/Security/RolePermissionManagerTest.php +++ b/tests/Security/RolePermissionManagerTest.php @@ -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] diff --git a/tests/Voter/AbstractVoterTest.php b/tests/Voter/AbstractVoterTest.php index ffb1635d..62884c40 100644 --- a/tests/Voter/AbstractVoterTest.php +++ b/tests/Voter/AbstractVoterTest.php @@ -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); } }