Automatically upgrade permission version schema in the background when needed on a request.

This commit is contained in:
Jan Böhmer 2023-01-08 00:35:43 +01:00
parent 0e56f11b6b
commit 7aa88a8dbd
6 changed files with 130 additions and 5 deletions

View file

@ -27,7 +27,7 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
'currencies.read', 'attachment_types.read', 'measurement_units.read'] 'currencies.read', 'attachment_types.read', 'measurement_units.read']
edit: edit:
label: "perm.edit" label: "perm.edit"
alsoSet: ['read', 'parts_stock.withdraw', 'parts_stock.add', 'parts_stock.move'] alsoSet: ['read'] #['read', 'parts_stock.withdraw', 'parts_stock.add', 'parts_stock.move']
create: create:
label: "perm.create" label: "perm.create"
alsoSet: ['read', 'edit'] alsoSet: ['read', 'edit']

View file

@ -32,6 +32,7 @@ use App\Services\ImportExportSystem\EntityExporter;
use App\Services\ImportExportSystem\EntityImporter; use App\Services\ImportExportSystem\EntityImporter;
use App\Services\Trees\StructuralElementRecursionHelper; use App\Services\Trees\StructuralElementRecursionHelper;
use App\Services\UserSystem\PermissionPresetsHelper; use App\Services\UserSystem\PermissionPresetsHelper;
use App\Services\UserSystem\PermissionSchemaUpdater;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@ -54,8 +55,11 @@ class GroupController extends BaseAdminController
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="group_edit") * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="group_edit")
* @Route("/{id}/", requirements={"id"="\d+"}) * @Route("/{id}/", requirements={"id"="\d+"})
*/ */
public function edit(Group $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper, ?string $timestamp = null): Response public function edit(Group $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper, PermissionSchemaUpdater $permissionSchemaUpdater, ?string $timestamp = null): Response
{ {
//Do an upgrade of the permission schema if needed (so the user can see the permissions a user get on next request (even if it was not done yet)
$permissionSchemaUpdater->groupUpgradeSchemaRecursively($entity);
//Handle permissions presets //Handle permissions presets
if ($request->request->has('permission_preset')) { if ($request->request->has('permission_preset')) {
$this->denyAccessUnlessGranted('edit_permissions', $entity); $this->denyAccessUnlessGranted('edit_permissions', $entity);

View file

@ -35,6 +35,7 @@ use App\Services\ImportExportSystem\EntityExporter;
use App\Services\ImportExportSystem\EntityImporter; use App\Services\ImportExportSystem\EntityImporter;
use App\Services\Trees\StructuralElementRecursionHelper; use App\Services\Trees\StructuralElementRecursionHelper;
use App\Services\UserSystem\PermissionPresetsHelper; use App\Services\UserSystem\PermissionPresetsHelper;
use App\Services\UserSystem\PermissionSchemaUpdater;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Exception; use Exception;
use InvalidArgumentException; use InvalidArgumentException;
@ -82,10 +83,12 @@ class UserController extends AdminPages\BaseAdminController
* *
* @throws Exception * @throws Exception
*/ */
public function edit(User $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper, ?string $timestamp = null): Response public function edit(User $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper, PermissionSchemaUpdater $permissionSchemaUpdater, ?string $timestamp = null): Response
{ {
//Handle 2FA disabling //Do an upgrade of the permission schema if needed (so the user can see the permissions a user get on next request (even if it was not done yet)
$permissionSchemaUpdater->userUpgradeSchemaRecursively($entity);
//Handle 2FA disabling
if ($request->request->has('reset_2fa')) { if ($request->request->has('reset_2fa')) {
//Check if the admin has the needed permissions //Check if the admin has the needed permissions
$this->denyAccessUnlessGranted('set_password', $entity); $this->denyAccessUnlessGranted('set_password', $entity);

View file

@ -0,0 +1,73 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\EventSubscriber\UserSystem;
use App\Entity\UserSystem\User;
use App\Services\UserSystem\PermissionSchemaUpdater;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Security;
/**
* The purpose of this event subscriber is to check if the permission schema of the current user is up to date and upgrade it automatically if needed.
*/
class UpgradePermissionsSchemaSubscriber implements EventSubscriberInterface
{
private Security $security;
private PermissionSchemaUpdater $permissionSchemaUpdater;
private EntityManagerInterface $entityManager;
private FlashBagInterface $flashBag;
public function __construct(Security $security, PermissionSchemaUpdater $permissionSchemaUpdater, EntityManagerInterface $entityManager, FlashBagInterface $flashBag)
{
$this->security = $security;
$this->permissionSchemaUpdater = $permissionSchemaUpdater;
$this->entityManager = $entityManager;
$this->flashBag = $flashBag;
}
public function onRequest(RequestEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
$user = $this->security->getUser();
if (null === $user) {
//Retrieve anonymous user
$user = $this->entityManager->getRepository(User::class)->getAnonymousUser();
}
if ($this->permissionSchemaUpdater->isSchemaUpdateNeeded($user)) {
$this->permissionSchemaUpdater->userUpgradeSchemaRecursively($user);
$this->entityManager->flush();
$this->flashBag->add('notice', 'user.permissions_schema_updated');
}
}
public static function getSubscribedEvents(): array
{
return [KernelEvents::REQUEST => 'onRequest'];
}
}

View file

@ -20,7 +20,9 @@
namespace App\Services\UserSystem; namespace App\Services\UserSystem;
use App\Entity\UserSystem\Group;
use App\Entity\UserSystem\PermissionData; use App\Entity\UserSystem\PermissionData;
use App\Entity\UserSystem\User;
use App\Security\Interfaces\HasPermissionsInterface; use App\Security\Interfaces\HasPermissionsInterface;
class PermissionSchemaUpdater class PermissionSchemaUpdater
@ -42,7 +44,8 @@ class PermissionSchemaUpdater
} }
/** /**
* Upgrades the permission schema of the given user/group to the chosen version * Upgrades the permission schema of the given user/group to the chosen version.
* Please note that this function does not flush the changes to DB!
* @param HasPermissionsInterface $holder * @param HasPermissionsInterface $holder
* @param int $target_version * @param int $target_version
* @return bool True, if an upgrade was done, false if it was not needed. * @return bool True, if an upgrade was done, false if it was not needed.
@ -76,6 +79,42 @@ class PermissionSchemaUpdater
return true; return true;
} }
/**
* Upgrades the permission schema of the given group and all of its parent groups to the chosen version.
* Please note that this function does not flush the changes to DB!
* @param Group $group
* @param int $target_version
* @return bool True if an upgrade was done, false if it was not needed.
*/
public function groupUpgradeSchemaRecursively(Group $group, int $target_version = PermissionData::CURRENT_SCHEMA_VERSION): bool
{
$updated = $this->upgradeSchema($group, $target_version);
/** @var Group $parent */
$parent = $group->getParent();
while ($parent) {
$updated = $this->upgradeSchema($parent, $target_version) || $updated;
$parent = $parent->getParent();
}
return $updated;
}
/**
* Upgrades the permissions schema of the given users and its parent (including parent groups) to the chosen version.
* Please note that this function does not flush the changes to DB!
* @param User $user
* @param int $target_version
* @return bool True if an upgrade was done, false if it was not needed.
*/
public function userUpgradeSchemaRecursively(User $user, int $target_version = PermissionData::CURRENT_SCHEMA_VERSION): bool
{
$updated = $this->upgradeSchema($user, $target_version);
$updated = $this->groupUpgradeSchemaRecursively($user->getGroup(), $target_version) || $updated;
return $updated;
}
private function upgradeSchemaToVersion1(HasPermissionsInterface $holder): void private function upgradeSchemaToVersion1(HasPermissionsInterface $holder): void
{ {
//Use the part edit permission to set the preset value for the new part stock permission //Use the part edit permission to set the preset value for the new part stock permission

View file

@ -10217,5 +10217,11 @@ Element 3</target>
<target>Move parts between lots</target> <target>Move parts between lots</target>
</segment> </segment>
</unit> </unit>
<unit id="DQ_FAfi" name="user.permissions_schema_updated">
<segment>
<source>user.permissions_schema_updated</source>
<target>The permission schema of your user were upgraded to the latest version.</target>
</segment>
</unit>
</file> </file>
</xliff> </xliff>