diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 7466a8e..c64a55c 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -1,39 +1,133 @@ security: + access_decision_manager: + strategy: unanimous + allow_if_all_abstain: false role_hierarchy: - ROLE_VIEWER: + + #PERMISSONS + CAN_MANAGE_FOLDERS: + - CAN_SHOW_FOLDER + - CAN_CREATE_FOLDER + - CAN_EDIT_FOLDER + - CAN_DELETE_FOLDER + + CAN_MANAGE_STRUCTURES: + - CAN_SHOW_STRUCTURE + - CAN_CREATE_STRUCTURE + - CAN_EDIT_STRUCTURE + - CAN_DELETE_STRUCTURE + + CAN_MANAGE_STRUCTURE_TYPES: + - CAN_SHOW_STRUCTURE_TYPE + - CAN_CREATE_STRUCTURE_TYPE + - CAN_EDIT_STRUCTURE_TYPE + - CAN_DELETE_STRUCTURE_TYPE + + CAN_MANAGE_APPLICATIONS: + - CAN_SHOW_APPLICATION + - CAN_CREATE_APPLICATION + - CAN_EDIT_APPLICATION + - CAN_DELETE_APPLICATION + + CAN_MANAGE_PIA_TEMPLATES: + - CAN_SHOW_PIA_TEMPLATE + - CAN_CREATE_PIA_TEMPLATE + - CAN_EDIT_PIA_TEMPLATE + - CAN_DELETE_PIA_TEMPLATE + + CAN_EXPLORE_PIAS: + - CAN_SHOW_PIA + - CAN_SHOW_FOLDER + - CAN_SHOW_MEASURE + - CAN_SHOW_EVALUATION + - CAN_SHOW_ANSWER + - CAN_SHOW_COMMENT + + CAN_MANAGE_PIAS: + - CAN_SHOW_PIA + - CAN_CREATE_PIA + - CAN_EDIT_PIA + - CAN_DELETE_PIA + + CAN_MANAGE_ANSWERS: + - CAN_SHOW_ANSWER + - CAN_CREATE_ANSWER + - CAN_EDIT_ANSWER + - CAN_DELETE_ANSWER + + CAN_MANAGE_EVALUATIONS: + - CAN_SHOW_EVALUATION + - CAN_CREATE_EVALUATION + - CAN_EDIT_EVALUATION + - CAN_DELETE_EVALUATION + + CAN_MANAGE_MEASURES: + - CAN_SHOW_MEASURE + - CAN_CREATE_MEASURE + - CAN_EDIT_MEASURE + - CAN_DELETE_MEASURE + + CAN_MANAGE_COMMENTS: + - CAN_SHOW_COMMENT + - CAN_CREATE_COMMENT + - CAN_EDIT_COMMENT + - CAN_DELETE_COMMENT + + CAN_MANAGE_ATTACHMENTS: + - CAN_SHOW_ATTACHMENT + - CAN_CREATE_ATTACHMENT + - CAN_EDIT_ATTACHMENT + - CAN_DELETE_ATTACHMENT + + + CAN_MANAGE_USERS: + - CAN_SHOW_USER + - CAN_CREATE_USER + - CAN_EDIT_USER + - CAN_DELETE_USER + + #Special permission which limit to owned users (from the same structure) + CAN_MANAGE_ONLY_OWNED_USERS: + - CAN_SHOW_USER + - CAN_CREATE_USER + - CAN_EDIT_USER + - CAN_DELETE_USER + + #ROLE DEFINITIONS + ROLE_USER: + - CAN_EXPLORE_PIAS + + ROLE_CONTROLLER: - ROLE_USER - - ROLE_PIA_LIST - - ROLE_PIA_VIEW - - ROLE_MEASURE_LIST - - ROLE_MEASURE_VIEW - - ROLE_EVALUATION_LIST - - ROLE_EVALUATION_VIEW - - ROLE_ANSWER_LIST - - ROLE_ANSWER_VIEW + - CAN_MANAGE_MEASURES + - CAN_MANAGE_ANSWERS + - CAN_MANAGE_COMMENTS + - CAN_MANAGE_ATTACHMENTS + - CAN_MANAGE_EVALUATIONS + ROLE_DPO: - - ROLE_VIEWER - - ROLE_PIA_CREATE - - ROLE_PIA_EDIT - - ROLE_PIA_DELETE - - ROLE_MEASURE_DELETE - - ROLE_EVALUATION_CREATE - - ROLE_EVALUATION_EDIT - - ROLE_EVALUATION_DELETE - - ROLE_ANSWER_DELETE - ROLE_CONTROLLER: - - ROLE_VIEWER - - ROLE_PIA_EDIT - - ROLE_MEASURE_CREATE - - ROLE_MEASURE_EDIT - - ROLE_ANSWER_CREATE - - ROLE_ANSWER_EDIT - - ROLE_ANSWER_DELETE + - ROLE_CONTROLLER + - CAN_MANAGE_PIAS + - CAN_SHOW_PIA_TEMPLATE + - CAN_IMPORT_PIA + - CAN_MANAGE_FOLDERS + ROLE_ADMIN: - ROLE_DPO - - ROLE_CONTROLLER - ROLE_SUPER_ADMIN: + - CAN_MANAGE_ONLY_OWNED_USERS + + ROLE_TECHNICAL_ADMIN: - ROLE_ADMIN + - CAN_MANAGE_USERS + - CAN_EXPORT_PIA + - CAN_MANAGE_STRUCTURES + - CAN_MANAGE_STRUCTURE_TYPES + - CAN_MANAGE_PIA_TEMPLATES + - CAN_MANAGE_APPLICATIONS + ROLE_SUPER_ADMIN: + - ROLE_TECHNICAL_ADMIN + encoders: FOS\UserBundle\Model\UserInterface: @@ -53,7 +147,7 @@ security: # # login_path: /login # # check_path: /login_check # provider: fos_userbundle - + api: pattern: ^/(pias|profile|pia-templates|folders) fos_oauth: true diff --git a/config/pialab/parameters.yaml b/config/pialab/parameters.yaml new file mode 100644 index 0000000..12f86c2 --- /dev/null +++ b/config/pialab/parameters.yaml @@ -0,0 +1,5 @@ +parameters: + + pialab.user_creation_limit: 10 + pialab.mono_structure: true + diff --git a/config/services.yaml b/config/services.yaml index 98a16e4..5d6c680 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -1,7 +1,10 @@ # Put parameters here that don't need to change on each machine where the app is deployed # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration +imports: + - { resource: pialab/parameters.yaml } + parameters: - locale: 'en' + locale: 'fr' services: _defaults: @@ -31,13 +34,27 @@ services: tags: - { name: kernel.event_subscriber } + + PiaApi\Form\Type\RolesType: + tags: [form.type] + + PiaApi\Security\Voter\PermissionVoter: + arguments: ['@security.role_hierarchy'] + tags: [security.voter] + PiaApi\Security\Voter\CanManageUsersVoter: + tags: [security.voter] + + PiaApi\Security\Role\RoleHierarchy: + arguments: ['@security.token_storage', '%security.role_hierarchy.roles%', '@security.role_hierarchy'] + + PiaApi\Auth\ClientManager: arguments: - '@fos_oauth_server.entity_manager' - '%fos_oauth_server.model.client.class%' FOS\OAuthServerBundle\Model\ClientManagerInterface: '@PiaApi\Auth\ClientManager' - + # Used to fix bug when updating and entity througth REST api # jms_serializer.object_constructor: # alias: jms_serializer.doctrine_object_constructor diff --git a/public/assets/js/modal.js b/public/assets/js/modal.js new file mode 100644 index 0000000..3b104a6 --- /dev/null +++ b/public/assets/js/modal.js @@ -0,0 +1,35 @@ +$(document).on('click', 'a[data-modal]', (e) => { + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + + let button = $(e.currentTarget); + let url = button.attr('href'); + + let modal = $('.ui.modal'); + modal.hide(); + + $.ajax({ + url: url, + success: (html) => { + let modal = $('.ui.modal'); + + modal.find('.header').html(button.attr('title')); + + modal + .find('.content') + .html(html); + + modal.find('.content .negative, .content .deny, .content .cancel').on('click', (e) => { + $(e.currentTarget).closest('.ui.modal').modal('hide'); + }); + + modal.find('.actions').remove(); + + $('.ui.checkbox').checkbox(); + $('.ui.dropdown').dropdown(); + + modal.modal('show'); + } + }) +}); diff --git a/src/Controller/BackOffice/OauthController.php b/src/Controller/BackOffice/OauthController.php index 60d3712..f58c819 100644 --- a/src/Controller/BackOffice/OauthController.php +++ b/src/Controller/BackOffice/OauthController.php @@ -13,12 +13,12 @@ use FOS\OAuthServerBundle\Model\ClientManagerInterface; use OAuth2\OAuth2; use PiaApi\Entity\Oauth\Client; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use PiaApi\Form\Application\CreateApplicationForm; use PiaApi\Form\Application\EditApplicationForm; use PiaApi\Form\Application\RemoveApplicationForm; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class OauthController extends BackOfficeAbstractController @@ -35,15 +35,10 @@ public function __construct(ClientManagerInterface $fosOauthClientManager) /** * @Route("/manageApplications", name="manage_applications") + * @Security("is_granted('CAN_SHOW_APPLICATION')") */ public function manageApplicationsAction(Request $request) { - if (!$this->isGranted('IS_AUTHENTICATED_REMEMBERED')) { - return $this->redirect($this->generateUrl('fos_user_security_login')); - } - - $this->canAccess(); - $pagerfanta = $this->buildPager($request, Client::class); return $this->render('pia/Application/manageApplications.html.twig', [ @@ -53,13 +48,12 @@ public function manageApplicationsAction(Request $request) /** * @Route("/manageApplications/addApplication", name="manage_applications_add_application") + * @Security("is_granted('CAN_CREATE_APPLICATION')") * * @param Request $request */ public function addApplicationAction(Request $request) { - $this->canAccess(); - $form = $this->createForm(CreateApplicationForm::class, [ 'allowedGrantTypes' => [ OAuth2::GRANT_TYPE_IMPLICIT => OAuth2::GRANT_TYPE_IMPLICIT, @@ -95,13 +89,12 @@ public function addApplicationAction(Request $request) /** * @Route("/manageApplications/editApplication/{applicationId}", name="manage_applications_edit_application") + * @Security("is_granted('CAN_EDIT_APPLICATION')") * * @param Request $request */ public function editApplicationAction(Request $request) { - $this->canAccess(); - $userId = $request->get('applicationId'); $user = $this->getDoctrine()->getRepository(Client::class)->find($userId); @@ -131,13 +124,12 @@ public function editApplicationAction(Request $request) /** * @Route("/manageApplications/removeApplication/{applicationId}", name="manage_applications_remove_application") + * @Security("is_granted('CAN_DELETE_APPLICATION')") * * @param Request $request */ public function removeApplicationAction(Request $request) { - $this->canAccess(); - $applicationId = $request->get('applicationId'); $user = $this->getDoctrine()->getRepository(Client::class)->find($applicationId); @@ -168,11 +160,4 @@ public function removeApplicationAction(Request $request) 'form' => $form->createView(), ]); } - - protected function canAccess() - { - if (!$this->isGranted('ROLE_SUPER_ADMIN')) { - throw new AccessDeniedHttpException(); - } - } } diff --git a/src/Controller/BackOffice/PiaTemplateController.php b/src/Controller/BackOffice/PiaTemplateController.php index d454e23..0f4dc96 100644 --- a/src/Controller/BackOffice/PiaTemplateController.php +++ b/src/Controller/BackOffice/PiaTemplateController.php @@ -12,7 +12,7 @@ use Symfony\Component\HttpFoundation\Request; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use PiaApi\Form\PiaTemplate\CreatePiaTemplateForm; use PiaApi\Form\PiaTemplate\EditPiaTemplateForm; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -24,15 +24,12 @@ class PiaTemplateController extends BackOfficeAbstractController { /** * @Route("/managePiaTemplates", name="manage_pia_templates") + * @Security("is_granted('CAN_SHOW_PIA_TEMPLATE')") + * + * @param Request $request */ public function managePiaTemplatesAction(Request $request) { - if (!$this->isGranted('IS_AUTHENTICATED_REMEMBERED')) { - return $this->redirect($this->generateUrl('fos_user_security_login')); - } - - $this->canAccess(); - $pagerfanta = $this->buildPager($request, PiaTemplate::class); return $this->render('pia/PiaTemplate/managePiaTemplates.html.twig', [ @@ -42,13 +39,12 @@ public function managePiaTemplatesAction(Request $request) /** * @Route("/managePiaTemplates/addPiaTemplate", name="manage_pia_templates_add_pia_template") + * @Security("is_granted('CAN_CREATE_PIA_TEMPLATE')") * * @param Request $request */ public function addPiaTemplateAction(Request $request) { - $this->canAccess(); - $form = $this->createForm(CreatePiaTemplateForm::class, [], [ 'action' => $this->generateUrl('manage_pia_templates_add_pia_template'), ]); @@ -83,13 +79,12 @@ public function addPiaTemplateAction(Request $request) /** * @Route("/managePiaTemplates/editPiaTemplate/{piaTemplateId}", name="manage_pia_templates_edit_pia_template") + * @Security("is_granted('CAN_EDIT_PIA_TEMPLATE')") * * @param Request $request */ public function editPiaTemplateAction(Request $request) { - $this->canAccess(); - $piaTemplateId = $request->get('piaTemplateId'); $piaTemplate = $this->getDoctrine()->getRepository(PiaTemplate::class)->find($piaTemplateId); @@ -125,13 +120,12 @@ public function editPiaTemplateAction(Request $request) /** * @Route("/managePiaTemplates/removePiaTemplate/{piaTemplateId}", name="manage_pia_templates_remove_pia_template") + * @Security("is_granted('CAN_REMOVE_PIA_TEMPLATE')") * * @param Request $request */ public function removePiaTemplateAction(Request $request) { - $this->canAccess(); - $piaTemplateId = $request->get('piaTemplateId'); $piaTemplate = $this->getDoctrine()->getRepository(PiaTemplate::class)->find($piaTemplateId); @@ -158,11 +152,4 @@ public function removePiaTemplateAction(Request $request) 'form' => $form->createView(), ]); } - - protected function canAccess() - { - if (!$this->isGranted('ROLE_SUPER_ADMIN')) { - throw new AccessDeniedHttpException(); - } - } } diff --git a/src/Controller/BackOffice/StructureController.php b/src/Controller/BackOffice/StructureController.php index 1398d77..dc04f00 100644 --- a/src/Controller/BackOffice/StructureController.php +++ b/src/Controller/BackOffice/StructureController.php @@ -12,31 +12,28 @@ use Symfony\Component\HttpFoundation\Request; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; -use PiaApi\Form\Structure\CreateStructureForm; -use PiaApi\Form\Structure\EditStructureForm; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use PiaApi\Entity\Pia\Structure; +use PiaApi\Entity\Pia\Folder; +use PiaApi\Entity\Pia\StructureType; +use PiaApi\Entity\OAuth\User; +use PiaApi\Form\Structure\CreateStructureForm; +use PiaApi\Form\Structure\EditStructureForm; use PiaApi\Form\Structure\RemoveStructureForm; use PiaApi\Form\Structure\CreateStructureTypeForm; -use PiaApi\Entity\Pia\StructureType; use PiaApi\Form\Structure\EditStructureTypeForm; use PiaApi\Form\Structure\RemoveStructureTypeForm; -use PiaApi\Entity\Pia\Folder; +use PiaApi\Form\User\CreateUserForm; class StructureController extends BackOfficeAbstractController { /** * @Route("/manageStructures", name="manage_structures") + * @Security("is_granted('CAN_SHOW_STRUCTURE')") */ public function manageStructuresAction(Request $request) { - if (!$this->isGranted('IS_AUTHENTICATED_REMEMBERED')) { - return $this->redirect($this->generateUrl('fos_user_security_login')); - } - - $this->canAccess(); - $pagerfanta = $this->buildPager($request, Structure::class); $pagerfantaSt = $this->buildPager($request, StructureType::class, 20, 'pageSt', 'limitSt'); @@ -46,15 +43,49 @@ public function manageStructuresAction(Request $request) ]); } + /** + * @Route("/showStructure/{structureId}", name="manage_structures_show_structure") + * @Security("is_granted('CAN_SHOW_STRUCTURE')") + */ + public function showStructureAction(Request $request) + { + $structureId = $request->get('structureId'); + $structure = $this->getDoctrine()->getRepository(Structure::class)->find($structureId); + + if ($structure === null) { + throw new NotFoundHttpException(sprintf('Structure « %s » does not exist', $structureId)); + } + + $userPager = $this->getDoctrine() + ->getRepository(User::class) + ->getPaginatedUsersByStructure($structure); + + $userPage = $request->get('page', 1); + $userLimit = $request->get('limit', $userPager->getMaxPerPage()); + + $userPager->setMaxPerPage($userLimit); + $userPager->setCurrentPage($userPager->getNbPages() < $userPage ? $userPager->getNbPages() : $userPage); + + $userForm = $this->createForm(CreateUserForm::class, ['roles' => ['ROLE_USER']], [ + 'action' => $this->generateUrl('manage_users_add_user'), + 'structure' => $structure, + ]); + + return $this->render('pia/Structure/showStructure.html.twig', [ + 'structure' => $structure, + 'users' => $userPager, + 'userForm' => $userForm->createView(), + ]); + } + /** * @Route("/manageStructures/addStructure", name="manage_structures_add_structure") + * @Security("is_granted('CAN_CREATE_STRUCTURE')") * * @param Request $request */ public function addStructureAction(Request $request) { - $this->canAccess(); - $form = $this->createForm(CreateStructureForm::class, [], [ 'action' => $this->generateUrl('manage_structures_add_structure'), ]); @@ -83,13 +114,12 @@ public function addStructureAction(Request $request) /** * @Route("/manageStructures/addStructureType", name="manage_structures_add_structure_type") + * @Security("is_granted('CAN_CREATE_STRUCTURE_TYPE')") * * @param Request $request */ public function addStructureTypeAction(Request $request) { - $this->canAccess(); - $form = $this->createForm(CreateStructureTypeForm::class, [], [ 'action' => $this->generateUrl('manage_structures_add_structure_type'), ]); @@ -114,13 +144,12 @@ public function addStructureTypeAction(Request $request) /** * @Route("/manageStructures/editStructure/{structureId}", name="manage_structures_edit_structure") + * @Security("is_granted('CAN_EDIT_STRUCTURE')") * * @param Request $request */ public function editStructureAction(Request $request) { - $this->canAccess(); - $structureId = $request->get('structureId'); $structure = $this->getDoctrine()->getRepository(Structure::class)->find($structureId); @@ -150,13 +179,12 @@ public function editStructureAction(Request $request) /** * @Route("/manageStructures/editStructureType/{structureTypeId}", name="manage_structures_edit_structure_type") + * @Security("is_granted('CAN_EDIT_STRUCTURE_TYPE')") * * @param Request $request */ public function editStructureTypeAction(Request $request) { - $this->canAccess(); - $structureTypeId = $request->get('structureTypeId'); $structureType = $this->getDoctrine()->getRepository(StructureType::class)->find($structureTypeId); @@ -186,13 +214,12 @@ public function editStructureTypeAction(Request $request) /** * @Route("/manageStructures/removeStructure/{structureId}", name="manage_structures_remove_structure") + * @Security("is_granted('CAN_DELETE_STRUCTURE')") * * @param Request $request */ public function removeStructureAction(Request $request) { - $this->canAccess(); - $structureId = $request->get('structureId'); $structure = $this->getDoctrine()->getRepository(Structure::class)->find($structureId); @@ -226,13 +253,12 @@ public function removeStructureAction(Request $request) /** * @Route("/manageStructures/removeStructureType/{structureTypeId}", name="manage_structures_remove_structure_type") + * @Security("is_granted('CAN_DELETE_STRUCTURE_TYPE')") * * @param Request $request */ public function removeStructureTypeAction(Request $request) { - $this->canAccess(); - $structureTypeId = $request->get('structureTypeId'); $structureType = $this->getDoctrine()->getRepository(StructureType::class)->find($structureTypeId); @@ -263,11 +289,4 @@ public function removeStructureTypeAction(Request $request) 'form' => $form->createView(), ]); } - - protected function canAccess() - { - if (!$this->isGranted('ROLE_SUPER_ADMIN')) { - throw new AccessDeniedHttpException(); - } - } } diff --git a/src/Controller/BackOffice/UserController.php b/src/Controller/BackOffice/UserController.php index c76bf90..223b4b9 100644 --- a/src/Controller/BackOffice/UserController.php +++ b/src/Controller/BackOffice/UserController.php @@ -20,12 +20,13 @@ use PiaApi\Form\User\SendResetPasswordEmailForm; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; use PiaApi\Entity\Pia\UserProfile; use Symfony\Component\Security\Core\User\UserInterface; +use PiaApi\Security\Role\RoleHierarchy; class UserController extends BackOfficeAbstractController { @@ -59,7 +60,19 @@ class UserController extends BackOfficeAbstractController */ private $tokenGenerator; - public function __construct(EncoderFactoryInterface $encoderFactory, TokenStorageInterface $tokenStorage, MailerInterface $mailer, int $FOSUserResettingRetryTTL, UserManagerInterface $userManager, TokenGeneratorInterface $tokenGenerator) + /** + * @var RoleHierarchy + */ + private $roleHierarchy; + + public function __construct( + EncoderFactoryInterface $encoderFactory, + TokenStorageInterface $tokenStorage, + MailerInterface $mailer, + int $FOSUserResettingRetryTTL, + UserManagerInterface $userManager, + TokenGeneratorInterface $tokenGenerator, + RoleHierarchy $roleHierarchy) { $this->encoderFactory = $encoderFactory; $this->tokenStorage = $tokenStorage; @@ -67,39 +80,49 @@ public function __construct(EncoderFactoryInterface $encoderFactory, TokenStorag $this->mailer = $mailer; $this->userManager = $userManager; $this->tokenGenerator = $tokenGenerator; + $this->roleHierarchy = $roleHierarchy; } /** * @Route("/manageUsers", name="manage_users") + * @Security("is_granted('CAN_SHOW_USER')") * * @param Request $request */ public function manageUsersAction(Request $request) { - if (!$this->isGranted('IS_AUTHENTICATED_REMEMBERED')) { - return $this->redirect($this->generateUrl('fos_user_security_login')); + if ($this->isGranted('CAN_MANAGE_ONLY_OWNED_USERS')) { + $structure = $this->getUser()->getStructure(); + $userPager = $this->getDoctrine() + ->getRepository(User::class) + ->getPaginatedUsersByStructure($structure); + + $userPage = $request->get('page', 1); + $userLimit = $request->get('limit', $userPager->getMaxPerPage()); + + $userPager->setMaxPerPage($userLimit); + $userPager->setCurrentPage($userPager->getNbPages() < $userPage ? $userPager->getNbPages() : $userPage); + } else { + $userPager = $this->buildPager($request, User::class); } - $this->canAccess(); - - $pagerfanta = $this->buildPager($request, User::class); - return $this->render('pia/User/manageUsers.html.twig', [ - 'users' => $pagerfanta, + 'users' => $userPager, ]); } /** * @Route("/manageUsers/addUser", name="manage_users_add_user") + * @Security("is_granted('CAN_CREATE_USER')") * * @param Request $request */ public function addUserAction(Request $request) { - $this->canAccess(); - $form = $this->createForm(CreateUserForm::class, ['roles' => ['ROLE_USER']], [ - 'action' => $this->generateUrl('manage_users_add_user'), + 'action' => $this->generateUrl('manage_users_add_user'), + 'structure' => $this->isGranted('CAN_MANAGE_STRUCTURES') ? false : $this->getUser()->getStructure(), + 'application' => $this->isGranted('CAN_MANAGE_APPLICATIONS') ? false : $this->getUser()->getApplication(), ]); $form->handleRequest($request); @@ -112,10 +135,23 @@ public function addUserAction(Request $request) $user->addRole($role); } + $profile = new UserProfile(); + $profile->setUser($user); + $user->setProfile($profile); + $profile->setFirstName($userData['profile']['firstName']); + $profile->setLastName($userData['profile']['lastName']); + + $user->setUsername($this->generateUsername($user)); + $encoder = $this->encoderFactory->getEncoder($user); $user->setPassword($encoder->encodePassword($userData['password'], $user->getSalt())); $user->setApplication($userData['application']); + + //a ROLE_ADMIN (which contains CAN_MANAGE_ONLY_OWNED_USERS) must have a structure + if (!$userData['structure'] && $user->hasRole('ROLE_ADMIN')) { + throw new \DomainException('A Functional Administrator must be assigned to a Structure'); + } $user->setStructure($userData['structure']); $this->getDoctrine()->getManager()->persist($user); @@ -135,13 +171,12 @@ public function addUserAction(Request $request) /** * @Route("/manageUsers/editUser/{userId}", name="manage_users_edit_user") + * @Security("is_granted('CAN_EDIT_USER')") * * @param Request $request */ public function editUserAction(Request $request) { - $this->canAccess(); - $userId = $request->get('userId'); $user = $this->getDoctrine()->getRepository(User::class)->find($userId); @@ -155,7 +190,9 @@ public function editUserAction(Request $request) } $form = $this->createForm(EditUserForm::class, $user, [ - 'action' => $this->generateUrl('manage_users_edit_user', ['userId' => $user->getId()]), + 'action' => $this->generateUrl('manage_users_edit_user', ['userId' => $user->getId()]), + 'structure' => $this->isGranted('CAN_MANAGE_STRUCTURES') ? false : $this->getUser()->getStructure(), + 'application' => $this->isGranted('CAN_MANAGE_APPLICATIONS') ? false : $this->getUser()->getApplication(), ]); $form->handleRequest($request); @@ -176,13 +213,12 @@ public function editUserAction(Request $request) /** * @Route("/manageUsers/removeUser/{userId}", name="manage_users_remove_user") + * @Security("is_granted('CAN_DELETE_USER')") * * @param Request $request */ public function removeUserAction(Request $request) { - $this->canAccess(); - $userId = $request->get('userId'); $user = $this->getDoctrine()->getRepository(User::class)->find($userId); @@ -216,6 +252,7 @@ public function removeUserAction(Request $request) /** * @Route("/manageUsers/sendResetPasswordEmail/{userId}", name="manage_users_send_reset_password_email") + * @Security("is_granted('CAN_SHOW_USER')") * * @param Request $request * @param string $username @@ -270,10 +307,11 @@ private function sendResetingEmail(UserInterface $user): void $this->userManager->updateUser($user); } - protected function canAccess() + protected function generateUsername(User $user) { - if (!$this->isGranted('ROLE_SUPER_ADMIN')) { - throw new AccessDeniedHttpException(); - } + $emailParts = explode('@', $user->getEmail()); + $str = preg_replace('/[^a-z0-9]+/i', ' ', $emailParts[0]); + + return '@' . str_replace(' ', '', ucwords($str)); } } diff --git a/src/Controller/Pia/AnswerController.php b/src/Controller/Pia/AnswerController.php index 85b812e..078f3d6 100644 --- a/src/Controller/Pia/AnswerController.php +++ b/src/Controller/Pia/AnswerController.php @@ -20,7 +20,7 @@ class AnswerController extends PiaSubController { /** * @FOSRest\Get("/pias/{piaId}/answers") - * @Security("is_granted('ROLE_ANSWER_LIST')") + * @Security("is_granted('CAN_SHOW_ANSWER')") */ public function listAction(Request $request, $piaId) { @@ -29,7 +29,7 @@ public function listAction(Request $request, $piaId) /** * @FOSRest\Get("/pias/{piaId}/answers/{id}") - * @Security("is_granted('ROLE_ANSWER_VIEW") + * @Security("is_granted('CAN_SHOW_ANSWER") */ public function showAction(Request $request, $piaId, $id) { @@ -38,7 +38,7 @@ public function showAction(Request $request, $piaId, $id) /** * @FOSRest\Post("/pias/{piaId}/answers") - * @Security("is_granted('ROLE_ANSWER_CREATE')") + * @Security("is_granted('CAN_CREATE_ANSWER')") */ public function createAction(Request $request, $piaId) { @@ -49,7 +49,7 @@ public function createAction(Request $request, $piaId) * @FOSRest\Put("/pias/{piaId}/answers/{id}") * @FOSRest\Patch("/pias/{piaId}/answers/{id}") * @FOSRest\Post("/pias/{piaId}/answers/{id}") - * @Security("is_granted('ROLE_ANSWER_EDIT')") + * @Security("is_granted('CAN_EDIT_ANSWER')") */ public function updateAction(Request $request, $piaId, $id) { @@ -58,7 +58,7 @@ public function updateAction(Request $request, $piaId, $id) /** * @FOSRest\Delete("pias/{piaId}/answers/{id}") - * @Security("is_granted('ROLE_ANSWER_DELETE')") + * @Security("is_granted('CAN_DELETE_ANSWER')") * * @return array */ diff --git a/src/Controller/Pia/AttachmentController.php b/src/Controller/Pia/AttachmentController.php index 7a6b561..98da884 100644 --- a/src/Controller/Pia/AttachmentController.php +++ b/src/Controller/Pia/AttachmentController.php @@ -13,13 +13,14 @@ use Symfony\Component\HttpFoundation\Request; use FOS\RestBundle\View\View; use FOS\RestBundle\Controller\Annotations as FOSRest; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use PiaApi\Entity\Pia\Attachment; class AttachmentController extends PiaSubController { - /** * @FOSRest\Get("/pias/{piaId}/attachments") + * @Security("is_granted('CAN_SHOW_PIA')") */ public function listAction(Request $request, $piaId) { @@ -28,6 +29,7 @@ public function listAction(Request $request, $piaId) /** * @FOSRest\Get("/pias/{piaId}/attachments/{id}") + * @Security("is_granted('CAN_SHOW_PIA')") */ public function showAction(Request $request, $piaId, $id) { @@ -36,6 +38,7 @@ public function showAction(Request $request, $piaId, $id) /** * @FOSRest\Post("/pias/{piaId}/attachments") + * @Security("is_granted('CAN_EDIT_PIA')") */ public function createAction(Request $request, $piaId) { @@ -46,6 +49,7 @@ public function createAction(Request $request, $piaId) * @FOSRest\Put("/pias/{piaId}/attachments/{id}") * @FOSRest\Patch("/pias/{piaId}/attachments/{id}") * @FOSRest\Post("/pias/{piaId}/attachments/{id}") + * @Security("is_granted('CAN_EDIT_PIA')") */ public function updateAction(Request $request, $piaId, $id) { @@ -54,6 +58,7 @@ public function updateAction(Request $request, $piaId, $id) /** * @FOSRest\Delete("pias/{piaId}/attachments/{id}") + * @Security("is_granted('CAN_EDIT_PIA')") * * @return array */ diff --git a/src/Controller/Pia/CommentController.php b/src/Controller/Pia/CommentController.php index f464524..6e72075 100644 --- a/src/Controller/Pia/CommentController.php +++ b/src/Controller/Pia/CommentController.php @@ -1,4 +1,5 @@ canAccessRouteOr403(); - $structure = $this->getUser()->getStructure(); $collection = $this->getRepository()->findBy(['structure' => $structure, 'parent' => null]); @@ -38,14 +36,12 @@ public function listAction(Request $request) /** * @FOSRest\Get("/folders/{id}") - * @Security("is_granted('ROLE_PIA_VIEW')") + * @Security("is_granted('CAN_SHOW_FOLDER')") * * @return View */ public function showAction(Request $request, $id) { - $this->canAccessRouteOr403(); - $folder = $this->getResource($id); if ($folder === null) { return $this->view($folder, Response::HTTP_NOT_FOUND); @@ -58,14 +54,12 @@ public function showAction(Request $request, $id) /** * @FOSRest\Post("/folders") - * @Security("is_granted('ROLE_PIA_CREATE')") + * @Security("is_granted('CAN_CREATE_FOLDER')") * * @return View */ public function createAction(Request $request) { - $this->canAccessRouteOr403(); - if ($request->get('parent_id') === null && $request->get('parent') === null) { return $this->view('Missing parent identification', Response::HTTP_BAD_REQUEST); } @@ -86,14 +80,12 @@ public function createAction(Request $request) /** * @FOSRest\Put("/folders/{id}", requirements={"id"="\d+"}) * @FOSRest\Post("/folders/{id}", requirements={"id"="\d+"}) - * @Security("is_granted('ROLE_PIA_EDIT')") + * @Security("is_granted('CAN_EDIT_FOLDER')") * * @return View */ public function updateAction(Request $request, $id) { - $this->canAccessRouteOr403(); - $folder = $this->getResource($id); $this->canAccessResourceOr403($folder); @@ -111,14 +103,12 @@ public function updateAction(Request $request, $id) /** * @FOSRest\Delete("/folders/{id}", requirements={"id"="\d+"}) - * @Security("is_granted('ROLE_PIA_DELETE')") + * @Security("is_granted('CAN_DELETE_FOLDER')") * * @return View */ public function deleteAction(Request $request, $id) { - $this->canAccessRouteOr403(); - $folder = $this->getResource($id); $this->canAccessResourceOr403($folder); $this->remove($folder); diff --git a/src/Controller/Pia/MeasureController.php b/src/Controller/Pia/MeasureController.php index f1ffe57..09e77cd 100644 --- a/src/Controller/Pia/MeasureController.php +++ b/src/Controller/Pia/MeasureController.php @@ -20,7 +20,7 @@ class MeasureController extends PiaSubController { /** * @FOSRest\Get("/pias/{piaId}/measures") - * @Security("is_granted('ROLE_MEASURE_LIST')") + * @Security("is_granted('CAN_SHOW_MEASURE')") */ public function listAction(Request $request, $piaId) { @@ -29,7 +29,7 @@ public function listAction(Request $request, $piaId) /** * @FOSRest\Get("/pias/{piaId}/measures/{id}") - * @Security("is_granted('ROLE_MEASURE_VIEW')") + * @Security("is_granted('CAN_SHOW_MEASURE')") */ public function showAction(Request $request, $piaId, $id) { @@ -38,7 +38,7 @@ public function showAction(Request $request, $piaId, $id) /** * @FOSRest\Post("/pias/{piaId}/measures") - * @Security("is_granted('ROLE_MEASURE_CREATE')") + * @Security("is_granted('CAN_CREATE_MEASURE')") */ public function createAction(Request $request, $piaId) { @@ -49,7 +49,7 @@ public function createAction(Request $request, $piaId) * @FOSRest\Put("/pias/{piaId}/measures/{id}") * @FOSRest\Patch("/pias/{piaId}/measures/{id}") * @FOSRest\Post("/pias/{piaId}/measures/{id}") - * @Security("is_granted('ROLE_MEASURE_EDIT')") + * @Security("is_granted('CAN_EDIT_MEASURE')") */ public function updateAction(Request $request, $piaId, $id) { @@ -58,7 +58,7 @@ public function updateAction(Request $request, $piaId, $id) /** * @FOSRest\Delete("pias/{piaId}/measures/{id}") - * @Security("is_granted('ROLE_MEASURE_DELETE')") + * @Security("is_granted('CAN_DELETE_MEASURE')") * * @return array */ diff --git a/src/Controller/Pia/PiaController.php b/src/Controller/Pia/PiaController.php index 4e6c374..34fc5d4 100644 --- a/src/Controller/Pia/PiaController.php +++ b/src/Controller/Pia/PiaController.php @@ -37,14 +37,12 @@ public function __construct(JsonToEntityTransformer $jsonToEntityTransformer, Pr /** * @FOSRest\Get("/pias") - * @Security("is_granted('ROLE_PIA_LIST')") + * @Security("is_granted('CAN_SHOW_PIA')") * * @return array */ public function listAction(Request $request) { - $this->canAccessRouteOr403(); - $structure = $this->getUser()->getStructure(); $criteria = array_merge($this->extractCriteria($request), ['structure' => $structure]); @@ -55,14 +53,12 @@ public function listAction(Request $request) /** * @FOSRest\Get("/pias/{id}", requirements={"id"="\d+"}) - * @Security("is_granted('ROLE_PIA_VIEW')") + * @Security("is_granted('CAN_SHOW_PIA')") * * @return array */ public function showAction(Request $request, $id) { - $this->canAccessRouteOr403(); - $pia = $this->getRepository()->find($id); if ($pia === null) { return $this->view($pia, Response::HTTP_NOT_FOUND); @@ -75,14 +71,12 @@ public function showAction(Request $request, $id) /** * @FOSRest\Post("/pias") - * @Security("is_granted('ROLE_PIA_CREATE')") + * @Security("is_granted('CAN_CREATE_PIA')") * * @return array */ public function createAction(Request $request) { - $this->canAccessRouteOr403(); - $pia = $this->newFromRequest($request); if ($request->get('folder') !== null) { @@ -91,9 +85,7 @@ public function createAction(Request $request) } else { $folder = $this->getUser()->getStructure()->getRootFolder(); } - $pia->setFolder($folder); - $pia->setStructure($this->getUser()->getStructure()); $this->persist($pia); @@ -102,13 +94,12 @@ public function createAction(Request $request) /** * @FOSRest\Post("/pias/new-from-template/{id}") - * @Security("is_granted('ROLE_PIA_VIEW')") + * @Security("is_granted('CAN_CREATE_PIA')") * * @return array */ public function createFromTemplateAction(Request $request, $id) { - $this->canAccessRouteOr403(); /** @var PiaTemplate $piaTemplate */ $piaTemplate = $this->getDoctrine()->getRepository(PiaTemplate::class)->find($id); if ($piaTemplate === null) { @@ -135,35 +126,33 @@ public function createFromTemplateAction(Request $request, $id) /** * @FOSRest\Put("/pias/{id}", requirements={"id"="\d+"}) * @FOSRest\Post("/pias/{id}", requirements={"id"="\d+"}) - * @Security("is_granted('ROLE_PIA_EDIT')") + * @Security("is_granted('CAN_EDIT_PIA')") * * @return array */ public function updateAction(Request $request, $id) { - $this->canAccessRouteOr403(); - $pia = $this->getResource($id); $this->canAccessResourceOr403($pia); $updatableAttributes = [ - 'name' => 'string', - 'author_name' => 'string', - 'evaluator_name' => 'string', - 'validator_name' => 'string', - 'folder' => Folder::class, - 'dpo_status' => 'int', - 'concerned_people_status' => 'int', - 'status' => 'int', - 'dpo_opinion' => 'string', - 'concerned_people_opinion' => 'string', - 'concerned_people_searched_opinion' => 'boolean', - 'concerned_people_searched_content' => 'string', - 'rejection_reason' => 'string', - 'applied_adjustments' => 'string', - 'dpos_names' => 'string', - 'people_names' => 'sring', - ]; + 'name' => 'string', + 'author_name' => 'string', + 'evaluator_name' => 'string', + 'validator_name' => 'string', + 'folder' => Folder::class, + 'dpo_status' => 'int', + 'concerned_people_status' => 'int', + 'status' => 'int', + 'dpo_opinion' => 'string', + 'concerned_people_opinion' => 'string', + 'concerned_people_searched_opinion' => 'boolean', + 'concerned_people_searched_content' => 'string', + 'rejection_reason' => 'string', + 'applied_adjustments' => 'string', + 'dpos_names' => 'string', + 'people_names' => 'sring', + ]; $this->mergeFromRequest($pia, $updatableAttributes, $request); @@ -174,14 +163,12 @@ public function updateAction(Request $request, $id) /** * @FOSRest\Delete("/pias/{id}", requirements={"id"="\d+"}) - * @Security("is_granted('ROLE_PIA_DELETE')") + * @Security("is_granted('CAN_DELETE_PIA')") * * @return array */ public function deleteAction(Request $request, $id) { - $this->canAccessRouteOr403(); - $pia = $this->getRepository()->find($id); $this->canAccessResourceOr403($pia); $this->remove($pia); @@ -191,14 +178,12 @@ public function deleteAction(Request $request, $id) /** * @FOSRest\Post("/pias/import") - * @Security("is_granted('ROLE_PIA_CREATE')") + * @Security("is_granted('CAN_IMPORT_PIA')") * * @return array */ public function importAction(Request $request) { - $this->canAccessRouteOr403(); - $importData = $request->get('data', null); if ($importData === null) { return $this->view($importData, Response::HTTP_BAD_REQUEST); @@ -213,7 +198,7 @@ public function importAction(Request $request) /** * @FOSRest\Get("/pias/export/{id}", requirements={"id"="\d+"}) - * @Security("is_granted('ROLE_PIA_VIEW')") + * @Security("is_granted('CAN_EXPORT_PIA')") * * @return array */ diff --git a/src/Controller/Pia/PiaTemplateController.php b/src/Controller/Pia/PiaTemplateController.php index 43e6822..964cc69 100644 --- a/src/Controller/Pia/PiaTemplateController.php +++ b/src/Controller/Pia/PiaTemplateController.php @@ -33,30 +33,32 @@ public function __construct(JsonToEntityTransformer $jsonToEntityTransformer) /** * @FOSRest\Get("/pia-templates") - * @Security("is_granted('ROLE_PIA_LIST')") + * @Security("is_granted('CAN_SHOW_PIA_TEMPLATE')") * * @return array */ public function listAction(Request $request) { - $this->canAccessRouteOr403(); - $structure = $this->getUser()->getStructure(); - $collection = $this->getRepository()->findAvailablePiaTemplatesForStructure($structure); + if ($structure !== null) { + $collection = $this->getRepository()->findAvailablePiaTemplatesForStructure($structure); + } elseif ($this->isGranted('ROLE_TECHNICAL_ADMIN')) { + $collection = $this->getRepository()->findAll(); + } else { + $collection = []; + } return $this->view($collection, Response::HTTP_OK); } /** * @FOSRest\Get("/pia-templates/{id}") - * @Security("is_granted('ROLE_PIA_VIEW')") + * @Security("is_granted('CAN_SHOW_PIA_TEMPLATE')") * * @return array */ public function showAction(Request $request, $id) { - $this->canAccessRouteOr403(); - $piaTemplate = $this->getRepository()->find($id); if ($piaTemplate === null) { return $this->view($piaTemplate, Response::HTTP_NOT_FOUND); diff --git a/src/Entity/Oauth/User.php b/src/Entity/Oauth/User.php index b73eaf6..51ed918 100644 --- a/src/Entity/Oauth/User.php +++ b/src/Entity/Oauth/User.php @@ -148,7 +148,7 @@ public function getRoles() public function addRole($role) { - if (!in_array($role, $this->roles)) { + if (!$this->hasRole($role)) { $this->roles[] = $role; } diff --git a/src/Form/Type/RolesType.php b/src/Form/Type/RolesType.php new file mode 100644 index 0000000..0dc9189 --- /dev/null +++ b/src/Form/Type/RolesType.php @@ -0,0 +1,46 @@ +getUserAccessibleRoles(); + $roleLabels = array_map(function ($roleName) { + return 'pia.users.labels.roles.' . $roleName; + }, $roleNames); + $this->roles = array_combine($roleLabels, $roleNames); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults(array( + 'required' => false, + 'multiple' => true, + 'expanded' => true, + 'choices' => $this->roles, + 'label' => 'Rôles', + )); + } + + public function getParent() + { + return ChoiceType::class; + } +} diff --git a/src/Form/User/CreateUserForm.php b/src/Form/User/CreateUserForm.php index f15cd7f..393a679 100644 --- a/src/Form/User/CreateUserForm.php +++ b/src/Form/User/CreateUserForm.php @@ -10,17 +10,20 @@ namespace PiaApi\Form\User; +use Symfony\Component\OptionsResolver\OptionsResolver; use PiaApi\Form\BaseForm; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Bridge\Doctrine\RegistryInterface; -use PiaApi\Entity\Oauth\Client; use PiaApi\Form\Application\Transformer\ApplicationTransformer; use PiaApi\Form\Structure\Transformer\StructureTransformer; use PiaApi\Entity\Pia\Structure; +use PiaApi\Entity\Oauth\Client; +use PiaApi\Form\Type\RolesType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; class CreateUserForm extends BaseForm @@ -40,15 +43,15 @@ class CreateUserForm extends BaseForm */ protected $structureTransformer; - protected $userRoles = [ - 'ROLE_USER' => 'ROLE_USER', - 'ROLE_ADMIN' => 'ROLE_ADMIN', - 'ROLE_SUPER_ADMIN' => 'ROLE_SUPER_ADMIN', - 'DPO' => 'ROLE_DPO', - 'Data controller' => 'ROLE_CONTROLLER', - ]; + /** + * @var Security + */ + protected $security; - public function __construct(RegistryInterface $doctrine, ApplicationTransformer $applicationTransformer, StructureTransformer $structureTransformer) + public function __construct( + RegistryInterface $doctrine, + ApplicationTransformer $applicationTransformer, + StructureTransformer $structureTransformer) { $this->doctrine = $doctrine; $this->applicationTransformer = $applicationTransformer; @@ -57,20 +60,43 @@ public function __construct(RegistryInterface $doctrine, ApplicationTransformer public function buildForm(FormBuilderInterface $builder, array $options) { - $builder + if (!$options['application']) { + $builder ->add('application', ChoiceType::class, [ 'required' => true, 'multiple' => false, 'expanded' => false, - 'choices' => $this->getApplications(), + 'choices' => $this->getApplications($options), 'label' => 'pia.users.forms.create.application', - ]) + ]); + } else { + $builder + ->add('application', HiddenType::class, [ + 'required' => true, + 'data' => $options['application'], + 'data_class' => null, + ]); + } + if (!$options['structure']) { + $builder ->add('structure', ChoiceType::class, [ 'required' => false, 'multiple' => false, 'expanded' => false, - 'choices' => $this->getStructures(), + 'choices' => $this->getStructures($options), 'label' => 'pia.users.forms.create.structure', + ]); + } else { + $builder + ->add('structure', HiddenType::class, [ + 'required' => true, + 'data' => $options['structure'], + 'data_class' => null, + ]); + } + $builder + ->add('profile', UserProfileForm::class, [ + 'label' => false, ]) ->add('email', EmailType::class, [ 'label' => 'pia.users.forms.create.email', @@ -78,11 +104,11 @@ public function buildForm(FormBuilderInterface $builder, array $options) ->add('password', PasswordType::class, [ 'label' => 'pia.users.forms.create.password', ]) - ->add('roles', ChoiceType::class, [ + ->add('roles', RolesType::class, [ 'required' => false, 'multiple' => true, 'expanded' => true, - 'choices' => $this->userRoles, + 'label' => 'pia.users.forms.create.roles', ]) ->add('sendResetingEmail', CheckboxType::class, [ @@ -104,25 +130,43 @@ public function buildForm(FormBuilderInterface $builder, array $options) $builder->get('structure')->addModelTransformer($this->structureTransformer); } - private function getApplications(): array + private function getApplications(array $options): array { $applications = []; - - foreach ($this->doctrine->getManager()->getRepository(Client::class)->findAll() as $application) { - $applications[$application->getId()] = $application->getName() ?? $application->getId(); + if (!$options['application']) { + foreach ($this->doctrine->getManager()->getRepository(Client::class)->findAll() as $application) { + $applications[$application->getId()] = $application->getName() ?? $application->getId(); + } + } else { + $app = $options['application']; + $applications[$app->getId()] = $app->getName(); } return array_flip($applications); } - private function getStructures(): array + private function getStructures(array $options): array { $structures = []; - - foreach ($this->doctrine->getManager()->getRepository(Structure::class)->findAll() as $structure) { - $structures[$structure->getId()] = $structure->getName() ?? $structure->getId(); + if (!$options['structure']) { + foreach ($this->doctrine->getManager()->getRepository(Structure::class)->findAll() as $structure) { + $structures[$structure->getId()] = $structure->getName() ?? $structure->getId(); + } + } else { + $struct = $options['structure']; + $structures[$struct->getId()] = $struct->getName(); } return array_flip($structures); } + + public function configureOptions(OptionsResolver $resolver) + { + parent::configureOptions($resolver); + + $resolver->setDefaults([ + 'application' => false, + 'structure' => false, + ]); + } } diff --git a/src/Form/User/EditUserForm.php b/src/Form/User/EditUserForm.php index cf508bd..d46a983 100644 --- a/src/Form/User/EditUserForm.php +++ b/src/Form/User/EditUserForm.php @@ -13,7 +13,6 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\ButtonType; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; @@ -21,6 +20,7 @@ use PiaApi\Form\Application\Transformer\ApplicationTransformer; use PiaApi\Form\Structure\Transformer\StructureTransformer; use PiaApi\Form\User\Transformer\UserProfileTransformer; +use PiaApi\Form\Type\RolesType; class EditUserForm extends CreateUserForm { @@ -29,7 +29,8 @@ class EditUserForm extends CreateUserForm */ protected $profileTransformer; - public function __construct(RegistryInterface $doctrine, + public function __construct( + RegistryInterface $doctrine, UserProfileTransformer $profileTransformer, ApplicationTransformer $applicationTransformer, StructureTransformer $structureTransformer @@ -53,14 +54,12 @@ public function buildForm(FormBuilderInterface $builder, array $options) ->add('profile', UserProfileForm::class, [ 'label' => false, ]) - ->add('roles', ChoiceType::class, [ + ->add('roles', RolesType::class, [ 'required' => false, 'multiple' => true, 'expanded' => true, - 'choices' => $this->userRoles, 'label' => 'pia.users.forms.edit.roles', ]) - ->add('expirationDate', DateType::class, [ 'widget' => 'single_text', 'label' => 'pia.users.forms.edit.expirationDate', @@ -86,8 +85,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'style' => 'width: 48%;', ], 'label' => 'pia.users.forms.edit.submit', - ]) - ; + ]); $builder->get('profile')->addModelTransformer($this->profileTransformer); } diff --git a/src/Form/User/UserProfileForm.php b/src/Form/User/UserProfileForm.php index 11b1eda..976489c 100644 --- a/src/Form/User/UserProfileForm.php +++ b/src/Form/User/UserProfileForm.php @@ -23,12 +23,12 @@ public function buildForm(FormBuilderInterface $builder, array $options) ->add('user', HiddenType::class) ->add('firstName', TextType::class, [ - 'required' => false, + 'required' => true, 'label' => 'pia.users.forms.profile.firstName', ]) ->add('lastName', TextType::class, [ - 'required' => false, + 'required' => true, 'label' => 'pia.users.forms.profile.lastName', ]) ; diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index e7e0ca8..7f64c4b 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -12,6 +12,10 @@ use Doctrine\ORM\EntityRepository; use Symfony\Component\Security\Core\User\UserInterface; +use PiaApi\Entity\Pia\Structure; +use Pagerfanta\PagerfantaInterface; +use Pagerfanta\Adapter\DoctrineORMAdapter; +use Pagerfanta\Pagerfanta; class UserRepository extends EntityRepository { @@ -28,4 +32,29 @@ public function findUserByUsernameOrEmail(string $usernameOrEmail): ?UserInterfa return $qb->getQuery()->getOneOrNullResult(); } + + /** + * @param Structure $structure + * @param int $defaultLimit + * + * @return PagerfantaInterface + */ + public function getPaginatedUsersByStructure( + Structure $structure, + ?int $defaultLimit = 20 + ): PagerfantaInterface { + $queryBuilder = $this->createQueryBuilder('e'); + + $queryBuilder + ->orderBy('e.id', 'DESC') + ->where('e.structure = :structure') + ->setParameter('structure', $structure); + + $adapter = new DoctrineORMAdapter($queryBuilder); + + $pagerfanta = new Pagerfanta($adapter); + $pagerfanta->setMaxPerPage($defaultLimit); + + return $pagerfanta; + } } diff --git a/src/Security/Role/RoleHierarchy.php b/src/Security/Role/RoleHierarchy.php new file mode 100644 index 0000000..024da66 --- /dev/null +++ b/src/Security/Role/RoleHierarchy.php @@ -0,0 +1,76 @@ +rawRoles = $roles; + + $this->definedRoles = array_filter(array_keys($roles), function ($role) { + return substr($role, 0, 5) == 'ROLE_'; + }); + //special case when logout + if ($tokenStorage->getToken() !== null) { + $this->user = $tokenStorage->getToken()->getUser(); + } + $this->roleHierarchy = $roleHierarchy; + } + + public function getUserAccessibleRoles(): array + { + $userRoleNames = $this->user->getRoles(); + + $userRoles = array_map(function ($roleName) { + return new Role($roleName); + }, $userRoleNames); + + $reachableRoleNames = array_map(function ($role) { + return $role->getRole(); + }, $this->roleHierarchy->getReachableRoles($userRoles)); + + $roleNames = array_filter($this->definedRoles, function ($roleName) use ($reachableRoleNames) { + return in_array($roleName, $reachableRoleNames); + }); + + return $roleNames; + } + + public function isGranted(User $user, string $roleOrPermission) + { + $userRoles = array_map(function ($roleName) { + return new Role($roleName); + }, $user->getRoles()); + + $reachableRoleNames = array_map(function ($role) { + return $role->getRole(); + }, $this->roleHierarchy->getReachableRoles($userRoles)); + + return in_array($roleOrPermission, $reachableRoleNames); + } +} diff --git a/src/Security/Voter/CanManageUsersVoter.php b/src/Security/Voter/CanManageUsersVoter.php new file mode 100644 index 0000000..59870f5 --- /dev/null +++ b/src/Security/Voter/CanManageUsersVoter.php @@ -0,0 +1,42 @@ +roleHierarchy = $roleHierarchy; + } + + protected function supports($attribute, $subject) + { + return $attribute === self::CAN_MANAGE_ONLY_OWNED_USERS; + } + + protected function voteOnAttribute($attribute, $subject, TokenInterface $token) + { + //if the user CAN_MANAGE_USERS he is not granted for CAN_MANAGE_ONLY_OWNED_USERS + return !$this->roleHierarchy->isGranted($token->getUser(), self::CAN_MANAGE_USERS); + } +} diff --git a/src/Security/Voter/PermissionVoter.php b/src/Security/Voter/PermissionVoter.php new file mode 100644 index 0000000..4e03d25 --- /dev/null +++ b/src/Security/Voter/PermissionVoter.php @@ -0,0 +1,22 @@ + +