From a02b9edc45c92bbef41cb931eeccf2d5b7beb250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 16 Apr 2018 15:09:50 +0200 Subject: [PATCH] first implementation of controller --- Controller/DefaultController.php | 13 -- Controller/SingleTaskController.php | 101 ++++++++++++++++ DataFixtures/ORM/LoadTaskACL.php | 86 ++++++++++++++ DependencyInjection/ChillTaskExtension.php | 3 +- Entity/AbstractTask.php | 56 ++++++++- Entity/SingleTask.php | 14 ++- Form/SingleTaskType.php | 74 ++++++++++++ Resources/config/routing.yml | 4 +- Resources/config/services.yml | 2 + Resources/config/services/controller.yml | 4 + Resources/config/services/security.yml | 10 ++ Resources/views/SingleTask/new.html.twig | 29 +++++ Security/Authorization/TaskVoter.php | 131 +++++++++++++++++++++ 13 files changed, 509 insertions(+), 18 deletions(-) delete mode 100644 Controller/DefaultController.php create mode 100644 Controller/SingleTaskController.php create mode 100644 DataFixtures/ORM/LoadTaskACL.php create mode 100644 Form/SingleTaskType.php create mode 100644 Resources/config/services.yml create mode 100644 Resources/config/services/controller.yml create mode 100644 Resources/config/services/security.yml create mode 100644 Resources/views/SingleTask/new.html.twig create mode 100644 Security/Authorization/TaskVoter.php diff --git a/Controller/DefaultController.php b/Controller/DefaultController.php deleted file mode 100644 index 74b3aa34e..000000000 --- a/Controller/DefaultController.php +++ /dev/null @@ -1,13 +0,0 @@ -?php - -namespace Chill\TaskBundle\Controller; - -use Symfony\Bundle\FrameworkBundle\Controller\Controller; - -class DefaultController extends Controller -{ - public function indexAction() - { - return $this->render('ChillTaskBundle:Default:index.html.twig'); - } -} diff --git a/Controller/SingleTaskController.php b/Controller/SingleTaskController.php new file mode 100644 index 000000000..b2158d0bd --- /dev/null +++ b/Controller/SingleTaskController.php @@ -0,0 +1,101 @@ +em = $em; + $this->formFactory = $formFactory; + }*/ + + /** + * @Route("/{_locale}/task/single-task/new") + */ + public function newAction(Request $request) + { + $personId = $request->query->getInt('person_id', null); + + if ($personId === null) { + return new Response("You must provide a person_id", Response::HTTP_BAD_REQUEST); + } + + $person = $this->getDoctrine()->getManager() + ->getRepository(Person::class) + ->find($personId); + + if ($person === null) { + throw $this->createNotFoundException("Invalid person id"); + } + + $task = (new SingleTask()) + ->setPerson($person) + ->setAssignee($this->getUser()) + ; + + $this->denyAccessUnlessGranted(TaskVoter::CREATE, $task, 'You are not ' + . 'allowed to create this task'); + + $form = $this->createCreateForm($task); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $em = $this->getDoctrine()->getManager(); + $em->persist($task); + + $this->addFlash('success', "The task is created"); + + $em->flush(); + } + + return $this->render('ChillTaskBundle:SingleTask:new.html.twig', array( + 'form' => $form->createView(), + 'task' => $task + )); + } + + /** + * + * @param SingleTask $task + * @return \Symfony\Component\Form\FormInterface + */ + protected function createCreateForm(SingleTask $task) + { + $form = $this->createForm(SingleTaskType::class, $task, [ + 'center' => $task->getCenter() + ]); + + $form->add('submit', SubmitType::class); + + return $form; + } + +} diff --git a/DataFixtures/ORM/LoadTaskACL.php b/DataFixtures/ORM/LoadTaskACL.php new file mode 100644 index 000000000..f7f88c79c --- /dev/null +++ b/DataFixtures/ORM/LoadTaskACL.php @@ -0,0 +1,86 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\TaskBundle\DataFixtures\ORM; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\Common\Persistence\ObjectManager; +use Chill\MainBundle\DataFixtures\ORM\LoadPermissionsGroup; +use Chill\MainBundle\Entity\RoleScope; +use Chill\MainBundle\DataFixtures\ORM\LoadScopes; +use Chill\TaskBundle\Security\Authorization\TaskVoter; + +/** + * Add a role UPDATE & CREATE for all groups except administrative, + * and a role SEE for administrative + * + * @author Julien Fastré + */ +class LoadTaskACL extends AbstractFixture implements OrderedFixtureInterface +{ + public function getOrder() + { + return 16000; + } + + + public function load(ObjectManager $manager) + { + foreach (LoadPermissionsGroup::$refs as $permissionsGroupRef) { + $permissionsGroup = $this->getReference($permissionsGroupRef); + foreach (LoadScopes::$references as $scopeRef){ + $scope = $this->getReference($scopeRef); + //create permission group + switch ($permissionsGroup->getName()) { + case 'social': + if ($scope->getName()['en'] === 'administrative') { + break 2; // we do not want any power on administrative + } + break; + case 'administrative': + case 'direction': + if (in_array($scope->getName()['en'], array('administrative', 'social'))) { + break 2; // we do not want any power on social or administrative + } + break; + } + + printf("Adding CHILL_TASK_TASK_UPDATE & CHILL_TASK_TASK_CREATE permissions to %s " + . "permission group, scope '%s' \n", + $permissionsGroup->getName(), $scope->getName()['en']); + $roleScopeUpdate = (new RoleScope()) + ->setRole(TaskVoter::UPDATE) + ->setScope($scope); + $permissionsGroup->addRoleScope($roleScopeUpdate); + $roleScopeCreate = (new RoleScope()) + ->setRole(TaskVoter::CREATE) + ->setScope($scope); + $permissionsGroup->addRoleScope($roleScopeCreate); + + $manager->persist($roleScopeUpdate); + $manager->persist($roleScopeCreate); + } + + } + + $manager->flush(); + } + +} diff --git a/DependencyInjection/ChillTaskExtension.php b/DependencyInjection/ChillTaskExtension.php index 0a933e247..2506082da 100644 --- a/DependencyInjection/ChillTaskExtension.php +++ b/DependencyInjection/ChillTaskExtension.php @@ -23,6 +23,7 @@ class ChillTaskExtension extends Extension $config = $this->processConfiguration($configuration, $configs); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); - //$loader->load('services.yml'); + $loader->load('services/controller.yml'); + $loader->load('services/security.yml'); } } diff --git a/Entity/AbstractTask.php b/Entity/AbstractTask.php index 3e65be165..4b5df1093 100644 --- a/Entity/AbstractTask.php +++ b/Entity/AbstractTask.php @@ -6,13 +6,15 @@ use Doctrine\ORM\Mapping as ORM; use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\Person; use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Entity\HasScopeInterface; +use Chill\MainBundle\Entity\HasCenterInterface; /** * AbstractTask * * @ORM\MappedSuperclass() */ -abstract class AbstractTask +abstract class AbstractTask implements HasScopeInterface, HasCenterInterface { /** @@ -27,7 +29,7 @@ abstract class AbstractTask * * @ORM\Column(name="current_states", type="json") */ - private $currentStates = ''; + private $currentStates = []; /** * @var string @@ -165,5 +167,55 @@ abstract class AbstractTask { return $this->description; } + + public function getAssignee(): ?User + { + return $this->assignee; + } + + public function getPerson(): ?Person + { + return $this->person; + } + + public function getCircle(): ?Scope + { + return $this->circle; + } + + public function setAssignee(User $assignee) + { + $this->assignee = $assignee; + return $this; + } + + public function setPerson(Person $person) + { + $this->person = $person; + return $this; + } + + public function setCircle(Scope $circle) + { + $this->circle = $circle; + return $this; + } + + public function getCenter(): ?\Chill\MainBundle\Entity\Center + { + if ($this->getPerson() instanceof Person) { + return $this->getPerson()->getCenter(); + } + + return null; + + } + + public function getScope(): ?\Chill\MainBundle\Entity\Scope + { + return $this->getCircle(); + } + + } diff --git a/Entity/SingleTask.php b/Entity/SingleTask.php index 81d987b5c..77627906d 100644 --- a/Entity/SingleTask.php +++ b/Entity/SingleTask.php @@ -130,9 +130,21 @@ class SingleTask extends AbstractTask * * @return \DateInterval */ - public function getWarningInterval(): ?\DateInterval + public function getWarningInterval() { return $this->warningInterval; } + + function getRecurringTask(): RecurringTask + { + return $this->recurringTask; + } + + function setRecurringTask(RecurringTask $recurringTask) + { + $this->recurringTask = $recurringTask; + } + + } diff --git a/Form/SingleTaskType.php b/Form/SingleTaskType.php new file mode 100644 index 000000000..93ed932ba --- /dev/null +++ b/Form/SingleTaskType.php @@ -0,0 +1,74 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\TaskBundle\Form; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Chill\MainBundle\Form\Type\ChillDateType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Extension\Core\Type\TextareaType; +use Chill\MainBundle\Form\Type\UserPickerType; +use Chill\MainBundle\Form\Type\ScopePickerType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Security\Core\Role\Role; + +/** + * + * + * @author Julien Fastré + */ +class SingleTaskType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('title', TextType::class) + ->add('description', TextareaType::class, [ + 'required' => false + ]) + ->add('assignee', UserPickerType::class, [ + 'required' => false, + 'center' => $options['center'], + 'role' => new Role(\Chill\PersonBundle\Security\Authorization\PersonVoter::UPDATE) + ]) + ->add('circle', ScopePickerType::class, [ + 'center' => $options['center'], + 'role' => new Role(\Chill\ActivityBundle\Security\Authorization\ActivityVoter::SEE) + ]) + ->add('startDate', ChillDateType::class, [ + 'required' => false + ]) + ->add('endDate', ChillDateType::class, [ + 'required' => false + ]) + ->add('warningInterval', TextType::class, [ + 'required' => false + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setRequired('center') + ->setAllowedTypes('center', [ \Chill\MainBundle\Entity\Center::class ]) + ; + } +} diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml index 8b1378917..53d5e4f81 100644 --- a/Resources/config/routing.yml +++ b/Resources/config/routing.yml @@ -1 +1,3 @@ - +chill_task_controllers: + resource: "@ChillTaskBundle/Controller" + type: annotation diff --git a/Resources/config/services.yml b/Resources/config/services.yml new file mode 100644 index 000000000..5779e425e --- /dev/null +++ b/Resources/config/services.yml @@ -0,0 +1,2 @@ +services: + diff --git a/Resources/config/services/controller.yml b/Resources/config/services/controller.yml new file mode 100644 index 000000000..9edb4e9ad --- /dev/null +++ b/Resources/config/services/controller.yml @@ -0,0 +1,4 @@ +services: + #chill_task.single_task_controller: + # class: Chill\TaskBundle\Controller\SingleTaskController + # autowire: true diff --git a/Resources/config/services/security.yml b/Resources/config/services/security.yml new file mode 100644 index 000000000..0acc6e56f --- /dev/null +++ b/Resources/config/services/security.yml @@ -0,0 +1,10 @@ +services: + chill_task.task_voter: + class: Chill\TaskBundle\Security\Authorization\TaskVoter + arguments: + - "@security.access.decision_manager" + - "@chill.main.security.authorization.helper" + - "@logger" + tags: + - { name: security.voter } + - { name: chill.role } diff --git a/Resources/views/SingleTask/new.html.twig b/Resources/views/SingleTask/new.html.twig new file mode 100644 index 000000000..e656a69fa --- /dev/null +++ b/Resources/views/SingleTask/new.html.twig @@ -0,0 +1,29 @@ +{# + * Copyright (C) 2014, Champs Libres Cooperative SCRLFS, + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . +#} +{% extends "ChillPersonBundle::layout.html.twig" %} + +{% set activeRouteKey = 'chill_task_single_task_new' %} +{% set person = task.person %} + +{% block title %}{{ 'New task'|trans }}{% endblock %} + +{% block personcontent %} +

{{ 'New task'|trans }}

+ +{{ form(form) }} + +{% endblock %} diff --git a/Security/Authorization/TaskVoter.php b/Security/Authorization/TaskVoter.php new file mode 100644 index 000000000..aacad0dad --- /dev/null +++ b/Security/Authorization/TaskVoter.php @@ -0,0 +1,131 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\TaskBundle\Security\Authorization; + +use Chill\MainBundle\Security\Authorization\AbstractChillVoter; +use Chill\TaskBundle\Entity\AbstractTask; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Chill\PersonBundle\Security\Authorization\PersonVoter; +use Psr\Log\LoggerInterface; +use Chill\MainBundle\Security\ProvideRoleHierarchyInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Chill\MainBundle\Entity\User; + +/** + * + * + * @author Julien Fastré + */ +class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface +{ + const CREATE = 'CHILL_TASK_TASK_CREATE'; + const UPDATE = 'CHILL_TASK_TASK_UPDATE'; + const SHOW = 'CHILL_TASK_TASK_SHOW'; + + const ROLES = [ + self::CREATE, + self::UPDATE, + self::SHOW + ]; + + /** + * + * @var AuthorizationHelper + */ + protected $authorizationHelper; + + /** + * + * @var AccessDecisionManagerInterface + */ + protected $accessDecisionManager; + + /** + * + * @var LoggerInterface + */ + protected $logger; + + public function __construct( + AccessDecisionManagerInterface $accessDecisionManager, + AuthorizationHelper $authorizationHelper, + LoggerInterface $logger + ) { + $this->accessDecisionManager = $accessDecisionManager; + $this->authorizationHelper = $authorizationHelper; + $this->logger = $logger; + } + + public function supports($attribute, $subject) + { + return $subject instanceof AbstractTask + && in_array($attribute, self::ROLES); + } + + /** + * + * @param string $attribute + * @param AbstractTask $subject + * @param TokenInterface $token + * @return boolean + */ + protected function voteOnAttribute($attribute, $subject, TokenInterface $token) + { + $this->logger->debug(sprintf("Voting from %s class", self::class)); + + if (!$token->getUser() instanceof User) { + return false; + } + + if ($subject->getPerson() === null) { + throw new \LogicException("You should associate a person with task " + . "in order to check autorizations"); + } + + if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $subject->getPerson())) { + + return false; + } + + return $this->authorizationHelper->userHasAccess( + $token->getUser(), + $subject, + $attribute + ); + } + + public function getRoles() + { + return self::ROLES; + } + + public function getRolesWithHierarchy(): array + { + return [ + 'Task' => self::ROLES + ]; + } + + public function getRolesWithoutScope() + { + return []; + } + +}