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 @@ + + {% spaceless %} - {{ 'pia.app_name'|trans({}, 'Pia') }} - {{ 'pia.layout.title'|trans({}, 'Pia') }} | + {{ 'pia.app_name'|trans({}, 'Pia') }} - {{ 'pia.layout.title'|trans({}, 'Pia') }} | {% block title %} - + {% endblock %} {% endspaceless %} @@ -29,6 +30,7 @@ {% block javascripts %} + {% endblock %} - \ No newline at end of file + diff --git a/templates/pia/Layout/navbar.html.twig b/templates/pia/Layout/navbar.html.twig index 6d3a0a8..9eb4715 100644 --- a/templates/pia/Layout/navbar.html.twig +++ b/templates/pia/Layout/navbar.html.twig @@ -3,40 +3,49 @@ {% import _self as menu %} {% if activeItem is not defined %} - {% set activeItem = null %} + {% set activeItem = null %} {% endif %} {% macro activeMenu(activeItem, menuName) %} - {% if activeItem == menuName %}active{% endif %} -{% endmacro %} \ No newline at end of file + {% if activeItem == menuName %}active{% endif %} +{% endmacro %} diff --git a/templates/pia/Structure/manageStructures.html.twig b/templates/pia/Structure/manageStructures.html.twig index dccf71c..aa42358 100644 --- a/templates/pia/Structure/manageStructures.html.twig +++ b/templates/pia/Structure/manageStructures.html.twig @@ -5,199 +5,49 @@ {% import 'pia/Layout/_macros.html.twig' as macro %} {% block title %} - {{ 'pia.structures.title'|trans }} + {{ 'pia.structures.title'|trans }} {% endblock title %} {% block navbar %} - {% include 'pia/Layout/navbar.html.twig' with { - 'activeItem' : 'structures' - } %} + {% include 'pia/Layout/navbar.html.twig' with { + 'activeItem' : 'structures' + } %} {% endblock navbar %} {% block body %} -
- -
- -
- -
- -
- -

- {{ 'pia.actions.display'|trans }} 1000 - {{ 'pia.structures.title'|trans }} -

- -
- - - - - - - - - - - - - - - {% for structure in structures %} - - - - - - - - - - {% endfor %} - - -
{{ 'pia.structures.list.id'|trans }}{{ 'pia.structures.list.name'|trans }}{{ 'pia.structures.list.type'|trans }}{{ 'pia.structures.list.users_number'|trans }}{{ 'pia.structures.list.pias_number'|trans }}{{ 'pia.structures.list.created_at'|trans }} - -
{{ structure.id }}{{ structure.name }} - {% if structure.type is not null %} - {{ structure.type.name }} - {% else %} - {{ 'pia.labels.n_a'|trans }} - {% endif %} - - {{ structure.users|length }} - - {{ structure.pias|length }} - - {{ structure.createdAt|date('d/m/Y')}} - - -
-
- {{ pagerfanta(structures, 'semantic_ui') }} -
- -
- -
- -
- -

- Voir 1000 - {{ 'pia.structure_types.title'|trans }} -

- - - - - - - - - - - - - {% for structureType in structureTypes %} - - - - - - - {% endfor %} - - -
{{ 'pia.structure_types.list.id'|trans }}{{ 'pia.structure_types.list.name'|trans }}{{ 'pia.structure_types.list.structures_number'|trans }} - -
{{ structureType.id }}{{ structureType.name }} - {{ structureType.structures|length }} - - -
- - {{ pagerfanta(structureTypes, 'semantic_ui', {'pageParameter': 'pageSt'}) }} -
-
-
-
- -
-
- -

{{ 'pia.structures.add.title'|trans }}

+
- {{ render(controller('PiaApi\\Controller\\BackOffice\\StructureController:addStructureAction')) }} +
-
-
+
-

{{ 'pia.structure_types.add.title'|trans }}

- - {{ render(controller('PiaApi\\Controller\\BackOffice\\StructureController:addStructureTypeAction')) }} +
+ {{ include('pia/Structure/partials/list.html.twig',{'structures':structures}) }} +
-
+
+ {{ include('pia/StructureType/partials/list.html.twig',{'structureTypes':structureTypes}) }}
+
-{% endblock %} - -{% block javascripts %} - {{ parent() }} - - -{% endblock javascripts %} \ No newline at end of file +{% endblock %} diff --git a/templates/pia/Structure/partials/list.html.twig b/templates/pia/Structure/partials/list.html.twig new file mode 100644 index 0000000..ec7ce47 --- /dev/null +++ b/templates/pia/Structure/partials/list.html.twig @@ -0,0 +1,68 @@ +{% trans_default_domain 'Pia' %} +{% import 'pia/Layout/_macros.html.twig' as macro %} + +
+ +

+ {{ 'pia.actions.display'|trans }} 1000 + {{ 'pia.structures.title'|trans }} +

+ +
+ + + + + + + + + + + + + + + {% for structure in structures %} + + + + + + + + + + {% endfor %} + + +
{{ 'pia.structures.list.id'|trans }}{{ 'pia.structures.list.name'|trans }}{{ 'pia.structures.list.type'|trans }}{{ 'pia.structures.list.users_number'|trans }}{{ 'pia.structures.list.pias_number'|trans }}{{ 'pia.structures.list.created_at'|trans }} + +
{{ structure.id }}{{ structure.name }} + {% if structure.type is not null %} + {{ structure.type.name }} + {% else %} + {{ 'pia.labels.n_a'|trans }} + {% endif %} + + {{ structure.users|length }} + + {{ structure.pias|length }} + + {{ structure.createdAt|date('d/m/Y')}} + + +
+
+ {{ pagerfanta(structures, 'semantic_ui') }} +
diff --git a/templates/pia/Structure/showStructure.html.twig b/templates/pia/Structure/showStructure.html.twig new file mode 100644 index 0000000..8e6d7f3 --- /dev/null +++ b/templates/pia/Structure/showStructure.html.twig @@ -0,0 +1,44 @@ +{% extends 'pia/Layout/base.html.twig' %} + +{% import 'pia/Layout/_macros.html.twig' as macro %} + +{% block title %}PIA User Management{% endblock title %} + +{% block navbar %} + {% include 'pia/Layout/navbar.html.twig' with { + 'activeItem' : 'my_structure' + } %} +{% endblock navbar %} + +{% block body %} + +
+
+
+

+ {{structure.name}} +
+ ({{structure.type.name}}) +

+
+
+ +
+ {{ include('pia/User/partials/list.html.twig',{'users':users}) }} +
+ +
+
+

Ajouter un utilisateur

+ {{ include('pia/Layout/form.html.twig',{'form':userForm}) }} + + +
+
+
+ +{% endblock %} + +{% block javascripts %} + {{ parent() }} +{% endblock javascripts %} diff --git a/templates/pia/StructureType/partials/list.html.twig b/templates/pia/StructureType/partials/list.html.twig new file mode 100644 index 0000000..2eabd47 --- /dev/null +++ b/templates/pia/StructureType/partials/list.html.twig @@ -0,0 +1,47 @@ +{% trans_default_domain 'Pia' %} +{% import 'pia/Layout/_macros.html.twig' as macro %} + +
+ +

+ Voir 1000 + {{ 'pia.structure_types.title'|trans }} +

+ + + + + + + + + + + + + {% for structureType in structureTypes %} + + + + + + + {% endfor %} + +
{{ 'pia.structure_types.list.id'|trans }}{{ 'pia.structure_types.list.name'|trans }}{{ 'pia.structure_types.list.structures_number'|trans }} + +
{{ structureType.id }}{{ structureType.name }} + {{ structureType.structures|length }} + + +
+ + {{ pagerfanta(structureTypes, 'semantic_ui', {'pageParameter': 'pageSt'}) }} +
diff --git a/templates/pia/User/manageUsers.html.twig b/templates/pia/User/manageUsers.html.twig index 36b0df2..f1e04a9 100644 --- a/templates/pia/User/manageUsers.html.twig +++ b/templates/pia/User/manageUsers.html.twig @@ -19,106 +19,7 @@
- -
- -

- {{ 'pia.actions.display'|trans }} 1000 - {{ 'pia.users.title'|trans }} -

- -
- - - - - - - - - - - - - - - - - - - {% for user in users %} - - - - - - - - - - - - - - {% endfor %} - - -
{{ 'pia.users.list.id'|trans }}{{ 'pia.users.list.application'|trans }}{{ 'pia.users.list.structure'|trans }}{{ 'pia.users.list.username'|trans }}{{ 'pia.users.list.email'|trans }}{{ 'pia.users.list.roles'|trans }}{{ 'pia.users.list.enabled'|trans }}{{ 'pia.users.list.locked'|trans }}{{ 'pia.users.list.created_at'|trans }}{{ 'pia.users.list.expires_at'|trans }} - -
{{ user.id }} - {% if user.application is not null %} - - {{ user.application.name }} - - {% else %} - {{ 'pia.labels.n_a'|trans }} - {% endif %} - - {% if user.structure is not null %} - - {{ user.structure.name }} - - {% else %} - {{ 'pia.labels.n_a'|trans }} - {% endif %} - {{ user.username }}{{ user.email }} - {% for role in user.roles if role != 'ROLE_USER' %} - {{ role }} - {% endfor %} - - {{ macro.boolLabel(user.enabled) }} - - {{ macro.boolLabel(user.locked) }} - - {{ user.creationDate|date('d/m/Y')}} - - {{ user.expirationDate|date('d/m/Y')}} - -
- - - - - - - - - - {% if app.user != user %} - - - - {% else %} - - - - {% endif %} -
-
-
- - {{ pagerfanta(users, 'semantic_ui') }} -
+ {{ include('pia/User/partials/list.html.twig',{'users':users}) }}
@@ -136,42 +37,4 @@ {% block javascripts %} {{ parent() }} - - -{% endblock javascripts %} \ No newline at end of file +{% endblock javascripts %} diff --git a/templates/pia/User/partials/list.html.twig b/templates/pia/User/partials/list.html.twig new file mode 100644 index 0000000..fe6e117 --- /dev/null +++ b/templates/pia/User/partials/list.html.twig @@ -0,0 +1,104 @@ +{% trans_default_domain 'Pia' %} +{% import 'pia/Layout/_macros.html.twig' as macro %} + +
+ +

+ {{ 'pia.actions.display'|trans }} 1000 + {{ 'pia.users.title'|trans }} +

+ +
+ + + + {% if is_granted('CAN_MANAGE_USERS') %} + + + {% endif %} + + + + + + + + + + + + + {% for user in users %} + + {% if is_granted('CAN_MANAGE_USERS') %} + + + {% endif %} + + + + + + + + + + {% endfor %} + + +
{{ 'pia.users.list.application'|trans }}{{ 'pia.users.list.structure'|trans }}{{ 'pia.users.list.username'|trans }}{{ 'pia.users.list.email'|trans }}{{ 'pia.users.list.roles'|trans }}{{ 'pia.users.list.enabled'|trans }}{{ 'pia.users.list.locked'|trans }}{{ 'pia.users.list.created_at'|trans }}{{ 'pia.users.list.expires_at'|trans }} + +
+ {% if user.application is not null %} + + {{ user.application.name }} + + {% else %} + {{ 'pia.labels.n_a'|trans }} + {% endif %} + + {% if user.structure is not null %} + + {{ user.structure.name }} + + {% else %} + {{ 'pia.labels.n_a'|trans }} + {% endif %} + {{ user.username }}{{ user.email }} + {% for role in user.roles if role != 'ROLE_USER' %} + {{ role }} + {% endfor %} + + {{ macro.boolLabel(user.enabled) }} + + {{ macro.boolLabel(user.locked) }} + + {{ user.creationDate|date('d/m/Y')}} + + {{ user.expirationDate|date('d/m/Y')}} + +
+ + + + + + + + + + {% if app.user != user %} + + + + {% else %} + + + + {% endif %} +
+
+
+ + {{ pagerfanta(users, 'semantic_ui') }} +
diff --git a/tests/_support/Page/StructurePage.php b/tests/_support/Page/StructurePage.php new file mode 100644 index 0000000..e8c8169 --- /dev/null +++ b/tests/_support/Page/StructurePage.php @@ -0,0 +1,82 @@ +tester = $I; + } + + public function createStructureType($structureType) + { + $I = $this->tester; + $I->wantTo(sprintf('Create a new structure type named "%s"', $structureType)); + $I->amOnPage('/manageStructures'); + $I->fillField('input[name="create_structure_type_form[name]"]', $structureType); + $I->click('[name="create_structure_type_form[submit]"]'); + $I->canSee($structureType, '//td'); + + return $this; + } + + public function createStructure($structure, $structureType) + { + $I = $this->tester; + $I->wantTo(sprintf('Create a new structure named "%s" of type "%s"', $structure, $structureType)); + $I->amOnPage('/manageStructures'); + $I->fillField('input[name="create_structure_form[name]"]', $structure); + $I->selectOptionFromSUISelect('create_structure_form[type]', $structureType); + $I->click('[name="create_structure_form[submit]"]'); + + $I->canSee($structure, '//td'); + + return $this; + } + + public function removeStructure($structure) + { + $I = $this->tester; + $I->wantTo(sprintf('Remove the structure named "%s"', $structure)); + $I->amOnPage('/manageStructures'); + $I->click('//td[contains(text(), "' . $structure . '")]/ancestor::tr/descendant::a[contains(@href,"/manageStructures/removeStructure/")]'); + + $formName = 'form[name="remove_structure_form"]'; + $I->waitForElementVisible($formName . ' input[type="submit"]'); + $I->canSeeNumberOfElements('//table[@class="ui single line table"]/descendant-or-self::b[contains(text(), "Nom")]/ancestor::tr/descendant::td[contains(text(), "' . $structure . '")]', 1); + + $I->click($formName . ' input[type="submit"]'); + + $I->dontSee($structure, '//td'); + } + + public function removeStructureType($structureType) + { + $I = $this->tester; + $I->wantTo(sprintf('Remove the structure type named "%s"', $structureType)); + $I->amOnPage('/manageStructures'); + $I->click('//td[contains(text(), "' . $structureType . '")]/ancestor::tr/descendant::a[contains(@href,"/manageStructures/removeStructureType/")]'); + + $formName = 'form[name="remove_structure_type_form"]'; + $I->waitForElementVisible($formName . ' input[type="submit"]'); + $I->canSeeNumberOfElements('//table[@class="ui single line table"]/descendant-or-self::b[contains(text(), "Nom")]/ancestor::tr/descendant::td[contains(text(), "' . $structureType . '")]', 1); + + $I->click($formName . ' input[type="submit"]'); + + $I->dontSee($structureType, '//td'); + } +} diff --git a/tests/_support/WebGuy.php b/tests/_support/WebGuy.php index e877f6a..5d4507d 100644 --- a/tests/_support/WebGuy.php +++ b/tests/_support/WebGuy.php @@ -147,4 +147,9 @@ public function selectOptionFromSUISelect($selectName, $optionLabel) $this->click('//select[@name="' . $selectName . '"]/ancestor::div[contains(@class,"ui dropdown")]'); $this->click('//select[@name="' . $selectName . '"]/ancestor::div[contains(@class,"ui dropdown")]/div[contains(@class, "menu")]/div[contains(@class, "item")][contains(text(), "' . str_replace('"', '\"', $optionLabel) . '")]'); } + + public function dontSeeNavMenuWithHref($href) + { + return $this->dontSeeElement('//body/div[contains(@class, "ui menu")]/descendant::a[contains(@href,"' . $href . '")]'); + } } diff --git a/tests/acceptance.suite.dist.yml b/tests/acceptance.suite.dist.yml index 0e656ef..340dafb 100644 --- a/tests/acceptance.suite.dist.yml +++ b/tests/acceptance.suite.dist.yml @@ -13,4 +13,4 @@ modules: url: '%TEST_SERVER_URL%' wait: 5 browser: 'firefox' - - \Helper\Acceptance \ No newline at end of file + - \Helper\Acceptance diff --git a/tests/acceptance/StructuresCest.php b/tests/acceptance/StructuresCest.php index 96c806a..a0dd4ee 100644 --- a/tests/acceptance/StructuresCest.php +++ b/tests/acceptance/StructuresCest.php @@ -17,30 +17,26 @@ class StructuresCest private $structure = 'selenium'; private $structureType = 'seleniumType'; + public function init_variables(Webguy $I) + { + $this->structure = $this->structure . rand(100, 999); + $this->structureType = $this->structureType . rand(100, 999); + } + public function create_new_structure_type_test(Webguy $I) { $I->login(); - $I->wantTo('Create a new structure type'); - $I->amOnPage('/manageStructures'); - - $I->fillField('input[name="create_structure_type_form[name]"]', $this->structureType); - - $I->click('[name="create_structure_type_form[submit]"]'); + $structurePage = new \Page\StructurePage($I); + $structurePage->createStructureType($this->structureType); } public function create_new_structure_test(Webguy $I) { $I->login(); - $I->wantTo('Create a new structure'); - $I->amOnPage('/manageStructures'); - - $I->fillField('input[name="create_structure_form[name]"]', $this->structure); - - $I->selectOptionFromSUISelect('create_structure_form[type]', $this->structureType); - - $I->click('[name="create_structure_form[submit]"]'); + $structurePage = new \Page\StructurePage($I); + $structurePage->createStructure($this->structure, $this->structureType); } public function edit_newly_created_structure_type_test(Webguy $I) @@ -95,35 +91,15 @@ public function remove_newly_created_structure_test(Webguy $I) { $I->login(); - $I->wantTo('Remove newly created structure'); - $I->amOnPage('/manageStructures'); - - $I->click('//td[contains(text(), "' . $this->structure . '")]/ancestor::tr/descendant::a[contains(@href,"/manageStructures/removeStructure/")]'); - - $formName = 'form[name="remove_structure_form"]'; - - $I->waitForElementVisible($formName . ' input[type="submit"]'); - - $I->canSeeNumberOfElements('//table[@class="ui single line table"]/descendant-or-self::b[contains(text(), "Nom")]/ancestor::tr/descendant::td[contains(text(), "' . $this->structure . '")]', 1); - - $I->click($formName . ' input[type="submit"]'); + $structurePage = new \Page\StructurePage($I); + $structurePage->removeStructure($this->structure); } public function remove_newly_created_structure_type_test(Webguy $I) { $I->login(); - $I->wantTo('Remove newly created structure type'); - $I->amOnPage('/manageStructures'); - - $I->click('//td[contains(text(), "' . $this->structure . '")]/ancestor::tr/descendant::a[contains(@href,"/manageStructures/removeStructureType/")]'); - - $formName = 'form[name="remove_structure_type_form"]'; - - $I->waitForElementVisible($formName . ' input[type="submit"]'); - - $I->canSeeNumberOfElements('//table[@class="ui single line table"]/descendant-or-self::b[contains(text(), "Nom")]/ancestor::tr/descendant::td[contains(text(), "' . $this->structureType . '")]', 1); - - $I->click($formName . ' input[type="submit"]'); + $structurePage = new \Page\StructurePage($I); + $structurePage->removeStructureType($this->structureType); } } diff --git a/tests/acceptance/UsersCest.php b/tests/acceptance/UsersCest.php index 45fd288..4351176 100644 --- a/tests/acceptance/UsersCest.php +++ b/tests/acceptance/UsersCest.php @@ -14,6 +14,8 @@ */ class UsersCest { + private $firstname = 'John'; + private $lastname = 'Doe'; private $email = 'selenium@pialab.io'; private $password = 'kFR5C1EGaPZDFJ1A'; @@ -35,13 +37,15 @@ public function create_new_super_admin_user_test(Webguy $I) } catch (\Exception $e) { // This part is optionnal } - + $I->fillField('input[name="create_user_form[profile][firstName]"]', $this->firstname); + $I->fillField('input[name="create_user_form[profile][lastName]"]', $this->lastname); $I->fillField('input[name="create_user_form[email]"]', $this->email); $I->fillField('input[name="create_user_form[password]"]', $this->password); $I->checkSUIOption('input[name="create_user_form[roles][]"][value="ROLE_SUPER_ADMIN"]'); $I->click('[name="create_user_form[submit]"]'); + $I->canSee($this->email, '//td'); } public function login_with_newly_created_user(Webguy $I) diff --git a/tests/acceptance/UsersFunctionalAdminCest.php b/tests/acceptance/UsersFunctionalAdminCest.php new file mode 100644 index 0000000..cfd9b0e --- /dev/null +++ b/tests/acceptance/UsersFunctionalAdminCest.php @@ -0,0 +1,170 @@ +structure = $this->structure . rand(100, 999); + $this->structureType = $this->structureType . rand(100, 999); + } + + public function create_new_structure(Webguy $I) + { + $I->login(); + + $structurePage = new \Page\StructurePage($I); + $structurePage->createStructureType($this->structureType); + $structurePage->createStructure($this->structure, $this->structureType); + } + + public function create_new_functional_admin(Webguy $I) + { + $I->login(); + + $I->wantTo('Create a new functional admin'); + $I->amOnPage('/manageUsers'); + + // Select Application + $application = $I->grabTextFrom('//select[@name="create_user_form[application]"]/ancestor::div[contains(@class,"ui dropdown")]/div[contains(@class, "menu")]/div[contains(@class,"item")][1]'); + $I->selectOptionFromSUISelect('create_user_form[application]', $application); + + try { + // Select Structure + $structure = $I->grabTextFrom('//select[@name="create_user_form[structure]"]/ancestor::div[contains(@class,"ui dropdown")]/div[contains(@class, "menu")]/div[contains(@class,"item")][1]'); + $I->selectOptionFromSUISelect('create_user_form[structure]', $structure); + } catch (\Exception $e) { + // This part is optionnal + } + $I->fillField('input[name="create_user_form[profile][firstName]"]', $this->firstname); + $I->fillField('input[name="create_user_form[profile][lastName]"]', $this->lastname); + $I->fillField('input[name="create_user_form[email]"]', $this->email); + $I->fillField('input[name="create_user_form[password]"]', $this->password); + + $I->checkSUIOption('input[name="create_user_form[roles][]"][value="ROLE_ADMIN"]'); + + $I->click('[name="create_user_form[submit]"]'); + + $I->seeElement('//td[contains(text(), "' . $this->email . '")]'); + $I->logout(); + } + + public function login_with_newly_created_functional_admin(Webguy $I) + { + $I->wantTo('Log-in with newly created function admin'); + $I->login($this->email, $this->password); + $I->amOnPage('/manageUsers'); + + //Function Admin should not see other menus + $I->expect('All super_admin menus are not visible'); + $I->dontSeeNavMenuWithHref('/manageStructures'); + $I->dontSeeNavMenuWithHref('/managePiaTemplates'); + $I->dontSeeNavMenuWithHref('/manageApplications'); + + $I->logout(); + } + + public function create_new_dpo_with_functional_admin(Webguy $I) + { + $I->login($this->email, $this->password); + + $I->wantTo('Create a new dpo'); + $I->amOnPage('/manageUsers'); + + // No Application Choice + $I->expect('Application and Structure are not selectable'); + $I->dontSeeElement('//select[@name="create_user_form[application]"]'); + $I->dontSeeElement('//select[@name="create_user_form[structure]"]'); + + $I->fillField('input[name="create_user_form[profile][firstName]"]', $this->dpoFirstname); + $I->fillField('input[name="create_user_form[profile][lastName]"]', $this->dpoLastname); + $I->fillField('input[name="create_user_form[email]"]', $this->dpoEmail); + $I->fillField('input[name="create_user_form[password]"]', $this->dpoPassword); + + $I->checkSUIOption('input[name="create_user_form[roles][]"][value="ROLE_DPO"]'); + + $I->click('[name="create_user_form[submit]"]'); + + $I->canSee($this->dpoEmail, '//td'); + $I->logout(); + } + + public function remove_newly_created_dpo_with_functional_admin(Webguy $I) + { + $I->login($this->email, $this->password); + + $I->wantTo('Remove newly created dpo'); + $I->amOnPage('/manageUsers'); + + $I->click('//td[contains(text(), "' . $this->dpoEmail . '")]/ancestor::tr/descendant::a[contains(@href,"/manageUsers/removeUser/")]'); + + $formName = 'form[name="remove_user_form"]'; + + $I->waitForElementVisible($formName . ' input[type="submit"]'); + + $I->canSeeNumberOfElements('//table[@class="ui single line table"]/descendant-or-self::b[contains(text(), "E-mail")]/ancestor::tr/descendant::td[contains(text(), "' . $this->email . '")]', 1); + + $I->click($formName . ' input[type="submit"]'); + + $I->expect('DPO is removed from the list'); + $I->dontSee($this->dpoEmail, '//td'); + + $I->logout(); + } + + public function remove_newly_created_funtional_admin(Webguy $I) + { + $I->login(); + + $I->wantTo('Remove newly created function admin'); + $I->amOnPage('/manageUsers'); + + $I->click('//td[contains(text(), "' . $this->email . '")]/ancestor::tr/descendant::a[contains(@href,"/manageUsers/removeUser/")]'); + + $formName = 'form[name="remove_user_form"]'; + + $I->waitForElementVisible($formName . ' input[type="submit"]'); + + $I->canSeeNumberOfElements('//table[@class="ui single line table"]/descendant-or-self::b[contains(text(), "E-mail")]/ancestor::tr/descendant::td[contains(text(), "' . $this->email . '")]', 1); + + $I->click($formName . ' input[type="submit"]'); + + $I->expect('Functional Admin is removed from the list'); + $I->dontSee($this->email, '//td'); + + $I->logout(); + } + + public function remove_structure(Webguy $I) + { + $I->login(); + + $structurePage = new \Page\StructurePage($I); + $structurePage->removeStructure($this->structure); + $structurePage->removeStructureType($this->structureType); + } +} diff --git a/translations/Pia.fr.yml b/translations/Pia.fr.yml index db84e37..7b7af80 100644 --- a/translations/Pia.fr.yml +++ b/translations/Pia.fr.yml @@ -25,6 +25,7 @@ pia: logout: Déconnexion actions: + show: Voir add: Ajouter display: Afficher edit: Modifier @@ -84,7 +85,7 @@ pia: client_credentials: Identifiants client refresh_token: Jeton de rafraichissement extensions: Extension - + templates: title: Gabarits de PIA @@ -103,7 +104,7 @@ pia: add: title: Ajouter une application - + modals: remove: title: Êtes vous certain de vouloir supprimer ce gabarit ? @@ -264,11 +265,12 @@ pia: labels: roles: - ROLE_USER: - ROLE_ADMIN: - ROLE_SUPER_ADMIN: - ROLE_DPO: - ROLE_CONTROLLER: + ROLE_USER : Invité + ROLE_CONTROLLER : Responsable de Traitement + ROLE_DPO : DPO + ROLE_ADMIN : Administrateur Fonctionnel + ROLE_TECHNICAL_ADMIN : Administrateur Technique + ROLE_SUPER_ADMIN : Super Administrateur reseting: send_email: @@ -292,4 +294,4 @@ pia: Vous pouvez retourner sur l'application pour vous connecter return_to_application: Retouner vers l'application request: - back: Retour \ No newline at end of file + back: Retour