diff --git a/src/Controller/GroupController.php b/src/Controller/GroupController.php index 2b6cca8a..d13454f4 100644 --- a/src/Controller/GroupController.php +++ b/src/Controller/GroupController.php @@ -51,6 +51,7 @@ use App\Form\AdminPages\GroupAdminForm; use App\Services\EntityExporter; use App\Services\EntityImporter; use App\Services\StructuralElementRecursionHelper; +use App\Services\UserSystem\PermissionPresetsHelper; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; @@ -73,8 +74,27 @@ class GroupController extends BaseAdminController * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="group_edit") * @Route("/{id}/", requirements={"id"="\d+"}) */ - public function edit(Group $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response + public function edit(Group $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper, ?string $timestamp = null): Response { + //Handle permissions presets + if ($request->request->has('permission_preset')) { + $this->denyAccessUnlessGranted('edit_permissions', $entity); + if ($this->isCsrfTokenValid('group'.$entity->getId(), $request->request->get('_token'))) { + $preset = $request->request->get('permission_preset'); + + $permissionPresetsHelper->applyPreset($entity, $preset); + + $em->flush(); + + $this->addFlash('success', 'user.edit.permission_success'); + + //We need to stop the execution here, or our permissions changes will be overwritten by the form values + return $this->redirectToRoute('group_edit', ['id' => $entity->getID()]); + } else { + $this->addFlash('danger', 'csfr_invalid'); + } + } + return $this->_edit($entity, $request, $em, $timestamp); } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index a8a2b860..1b53255b 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -54,6 +54,7 @@ use App\Form\UserAdminForm; use App\Services\EntityExporter; use App\Services\EntityImporter; use App\Services\StructuralElementRecursionHelper; +use App\Services\UserSystem\PermissionPresetsHelper; use Doctrine\ORM\EntityManagerInterface; use Exception; use InvalidArgumentException; @@ -101,7 +102,7 @@ class UserController extends AdminPages\BaseAdminController * * @throws Exception */ - public function edit(User $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response + public function edit(User $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper, ?string $timestamp = null): Response { //Handle 2FA disabling @@ -132,6 +133,25 @@ class UserController extends AdminPages\BaseAdminController } } + //Handle permissions presets + if ($request->request->has('permission_preset')) { + $this->denyAccessUnlessGranted('edit_permissions', $entity); + if ($this->isCsrfTokenValid('reset_2fa'.$entity->getId(), $request->request->get('_token'))) { + $preset = $request->request->get('permission_preset'); + + $permissionPresetsHelper->applyPreset($entity, $preset); + + $em->flush(); + + $this->addFlash('success', 'user.edit.permission_success'); + + //We need to stop the execution here, or our permissions changes will be overwritten by the form values + return $this->redirectToRoute('user_edit', ['id' => $entity->getID()]); + } else { + $this->addFlash('danger', 'csfr_invalid'); + } + } + return $this->_edit($entity, $request, $em, $timestamp); } diff --git a/src/Entity/UserSystem/PermissionData.php b/src/Entity/UserSystem/PermissionData.php index 91611dc9..dd468ce0 100644 --- a/src/Entity/UserSystem/PermissionData.php +++ b/src/Entity/UserSystem/PermissionData.php @@ -88,6 +88,16 @@ final class PermissionData implements \JsonSerializable return $this; } + /** + * Resets the saved permissions and set all operations to inherit (which means they are not defined). + * @return $this + */ + public function resetPermissions(): self + { + $this->data = []; + return $this; + } + /** * Creates a new Permission Data Instance using the given JSON encoded data * @param string $json diff --git a/src/Form/AdminPages/GroupAdminForm.php b/src/Form/AdminPages/GroupAdminForm.php index b8e9cfd1..2234a270 100644 --- a/src/Form/AdminPages/GroupAdminForm.php +++ b/src/Form/AdminPages/GroupAdminForm.php @@ -64,6 +64,7 @@ class GroupAdminForm extends BaseEntityAdminForm 'mapped' => false, 'data' => $builder->getData(), 'disabled' => !$this->security->isGranted('edit_permissions', $entity), + 'show_presets' => $this->security->isGranted('edit_permissions', $entity) && !$is_new, ]); } } diff --git a/src/Form/Permissions/PermissionsType.php b/src/Form/Permissions/PermissionsType.php index b16e76e4..ae5d0727 100644 --- a/src/Form/Permissions/PermissionsType.php +++ b/src/Form/Permissions/PermissionsType.php @@ -66,6 +66,7 @@ class PermissionsType extends AbstractType { $resolver->setDefaults([ 'show_legend' => true, + 'show_presets' => false, 'constraints' => static function (Options $options) { if (!$options['disabled']) { return [new NoLockout()]; @@ -80,6 +81,7 @@ class PermissionsType extends AbstractType public function buildView(FormView $view, FormInterface $form, array $options): void { $view->vars['show_legend'] = $options['show_legend']; + $view->vars['show_presets'] = $options['show_presets']; } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Form/UserAdminForm.php b/src/Form/UserAdminForm.php index 2c3d07c7..535bb240 100644 --- a/src/Form/UserAdminForm.php +++ b/src/Form/UserAdminForm.php @@ -227,6 +227,7 @@ class UserAdminForm extends AbstractType 'mapped' => false, 'data' => $builder->getData(), 'disabled' => !$this->security->isGranted('edit_permissions', $entity), + 'show_presets' => $this->security->isGranted('edit_permissions', $entity) && !$is_new, ]) ; /*->add('comment', CKEditorType::class, ['required' => false, diff --git a/src/Services/PermissionResolver.php b/src/Services/PermissionResolver.php index ca885008..3e41da9d 100644 --- a/src/Services/PermissionResolver.php +++ b/src/Services/PermissionResolver.php @@ -206,7 +206,7 @@ class PermissionResolver /** * This functions sets all operations mentioned in the alsoSet value of a permission, so that the structure is always valid. - * @param User $user + * @param HasPermissionsInterface $user * @return void */ public function ensureCorrectSetOperations(HasPermissionsInterface $user): void @@ -261,6 +261,26 @@ class PermissionResolver } } + /** + * Sets all operations of the given permissions to the given value. + * Please note that you have to call ensureCorrectSetOperations() after this function, to ensure that all alsoSet values are set. + * + * @param HasPermissionsInterface $perm_holder + * @param string $permission + * @param bool|null $new_value + * @return void + */ + public function setAllOperationsOfPermission(HasPermissionsInterface $perm_holder, string $permission, ?bool $new_value): void + { + if (!$this->isValidPermission($permission)) { + throw new InvalidArgumentException(sprintf('A permission with that name is not existing! Got %s.', $permission)); + } + + foreach ($this->permission_structure['perms'][$permission]['operations'] as $op_key => $op) { + $this->setPermission($perm_holder, $permission, $op_key, $new_value); + } + } + protected function generatePermissionStructure() { $cache = new ConfigCache($this->cache_file, $this->is_debug); diff --git a/src/Services/UserSystem/PermissionPresetsHelper.php b/src/Services/UserSystem/PermissionPresetsHelper.php new file mode 100644 index 00000000..bd0c70a2 --- /dev/null +++ b/src/Services/UserSystem/PermissionPresetsHelper.php @@ -0,0 +1,143 @@ +permissionResolver = $permissionResolver; + } + + /** + * Apply the given preset to the permission holding entity (like a user) + * The permission data will be reset during the process and then the preset will be applied. + * + * @param + * @param string $preset_name The name of the preset to use + * @return PermissionData + */ + public function applyPreset(HasPermissionsInterface $perm_holder, string $preset_name): HasPermissionsInterface + { + //We need to reset the permission data first (afterwards all values are inherit) + $perm_holder->getPermissions()->resetPermissions(); + + switch($preset_name) { + case 'all_inherit': + //Do nothing, all values are inherit after reset + break; + case 'all_forbid': + $this->allForbid($perm_holder); + break; + case 'all_allow': + $this->allAllow($perm_holder); + break; + case 'read_only': + $this->readOnly($perm_holder); + break; + case 'editor': + $this->editor($perm_holder); + break; + case 'admin': + $this->admin($perm_holder); + break; + + default: + throw new \InvalidArgumentException('Unknown permission preset name: '.$preset_name); + } + + //Ensure that permissions are valid (alsoSet values are set), this allows us to use the permission inheritance system to keep the presets short + $this->permissionResolver->ensureCorrectSetOperations($perm_holder); + + return $perm_holder; + } + + private function admin(HasPermissionsInterface $perm_holder): void + { + //Apply everything from editor permission + $this->editor($perm_holder); + + //Allow user and group access + $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'users', PermissionData::ALLOW); + $this->permissionResolver->setAllOperationsOfPermission($perm_holder, 'groups', PermissionData::ALLOW); + + //Allow access to system log and server infos + $this->permissionResolver->setPermission($perm_holder, 'system', 'show_logs', PermissionData::ALLOW); + $this->permissionResolver->setPermission($perm_holder, 'system', 'server_infos', PermissionData::ALLOW); + } + + private function editor(HasPermissionsInterface $permHolder): HasPermissionsInterface + { + //Apply everything from read-only + $this->readOnly($permHolder); + + //Set datastructures + $this->permissionResolver->setAllOperationsOfPermission($permHolder, 'parts', PermissionData::ALLOW); + $this->permissionResolver->setAllOperationsOfPermission($permHolder, 'categories', PermissionData::ALLOW); + $this->permissionResolver->setAllOperationsOfPermission($permHolder, 'storelocations', PermissionData::ALLOW); + $this->permissionResolver->setAllOperationsOfPermission($permHolder, 'footprints', PermissionData::ALLOW); + $this->permissionResolver->setAllOperationsOfPermission($permHolder, 'manufacturers', PermissionData::ALLOW); + $this->permissionResolver->setAllOperationsOfPermission($permHolder, 'attachment_types', PermissionData::ALLOW); + $this->permissionResolver->setAllOperationsOfPermission($permHolder, 'currencies', PermissionData::ALLOW); + $this->permissionResolver->setAllOperationsOfPermission($permHolder, 'measurement_units', PermissionData::ALLOW); + $this->permissionResolver->setAllOperationsOfPermission($permHolder, 'suppliers', PermissionData::ALLOW); + + //Attachments permissions + $this->permissionResolver->setPermission($permHolder, 'attachments', 'show_private', PermissionData::ALLOW); + + //Labels permissions (allow all except use twig) + $this->permissionResolver->setAllOperationsOfPermission($permHolder, 'labels', PermissionData::ALLOW); + $this->permissionResolver->setPermission($permHolder,'labels', 'use_twig', PermissionData::INHERIT); + + //Self permissions + $this->permissionResolver->setPermission($permHolder, 'self', 'edit_infos', PermissionData::ALLOW); + + //Various other permissions + $this->permissionResolver->setPermission($permHolder, 'tools', 'lastActivity', PermissionData::ALLOW); + + + return $permHolder; + } + + private function readOnly(HasPermissionsInterface $perm_holder): HasPermissionsInterface + { + //It is sufficient to only set the read operation to allow, read operations for datastructures are inherited + $this->permissionResolver->setPermission($perm_holder, 'parts', 'read', PermissionData::ALLOW); + + //Set tools permissions + $this->permissionResolver->setPermission($perm_holder, 'tools', 'statistics', PermissionData::ALLOW); + $this->permissionResolver->setPermission($perm_holder, 'tools', 'label_scanner', PermissionData::ALLOW); + $this->permissionResolver->setPermission($perm_holder, 'tools', 'reel_calculator', PermissionData::ALLOW); + + //Set attachments permissions + $this->permissionResolver->setPermission($perm_holder, 'attachments', 'list_attachments', PermissionData::ALLOW); + + //Set user (self) permissions + $this->permissionResolver->setPermission($perm_holder, 'self', 'show_permissions', PermissionData::ALLOW); + + //Label permissions + $this->permissionResolver->setPermission($perm_holder, 'labels', 'create_labels', PermissionData::ALLOW); + $this->permissionResolver->setPermission($perm_holder, 'labels', 'edit_options', PermissionData::ALLOW); + + return $perm_holder; + } + + private function AllForbid(HasPermissionsInterface $perm_holder): HasPermissionsInterface + { + $this->permissionResolver->setAllPermissions($perm_holder, PermissionData::DISALLOW); + return $perm_holder; + } + + private function AllAllow(HasPermissionsInterface $perm_holder): HasPermissionsInterface + { + $this->permissionResolver->setAllPermissions($perm_holder, PermissionData::ALLOW); + return $perm_holder; + } +} \ No newline at end of file diff --git a/templates/AdminPages/GroupAdmin.html.twig b/templates/AdminPages/GroupAdmin.html.twig index 409cb6d0..de88aab3 100644 --- a/templates/AdminPages/GroupAdmin.html.twig +++ b/templates/AdminPages/GroupAdmin.html.twig @@ -12,6 +12,7 @@ {% block additional_panes %}