From 2773e3be838a3fea777c83d9b3b2f5bc6f623895 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Thu, 10 Mar 2022 21:52:14 +0100 Subject: [PATCH] backports murph-skeleton --- core/Controller/Site/NodeAdminController.php | 5 ++ core/Controller/User/UserAdminController.php | 7 +- core/DependencyInjection/Configuration.php | 16 +++++ core/Entity/Site/Node.php | 34 +++++++++ .../PasswordRequestEventSubscriber.php | 2 +- .../EntityManagerEventSubscriber.php | 2 +- .../NavigationSettingEventSubscriber.php | 2 +- .../RequestSecurityEventSubscriber.php | 71 +++++++++++++++++++ .../SettingEventSubscriber.php | 2 +- .../Site/ForcedDomainEventSubscriber.php | 2 +- .../Site/MenuEventSubscriber.php | 4 +- .../Site/NavigationEventSubscriber.php | 4 +- .../Site/NodeEventSubscriber.php | 4 +- .../Site/Page/BlockEventSubscriber.php | 4 +- .../Site/Page/PageEventSubscriber.php | 4 +- .../Site/SiteEventSubscriber.php | 4 +- .../Task/CacheCleanTaskEventSubscriber.php | 2 +- .../Task/TaskEventSubscriber.php | 2 +- core/Factory/UserFactory.php | 2 +- core/Form/Site/NodeType.php | 36 ++++++++++ core/Resources/translations/messages.fr.yaml | 3 + .../views/site/node_admin/_form.html.twig | 32 ++++++++- core/Site/ControllerLocator.php | 6 +- core/Site/RoleConfiguration.php | 38 ++++++++++ core/Site/RoleLocator.php | 42 +++++++++++ .../Blog/CategoryEventSubscriber.php | 4 +- .../Blog/CommentEventSubscriber.php | 4 +- .../Blog/PostEventSubscriber.php | 4 +- .../Blog/PostFollowEventSubscriber.php | 4 +- .../SettingEventSubscriber.php | 4 +- 30 files changed, 310 insertions(+), 40 deletions(-) rename core/{EventSuscriber => EventSubscriber}/Account/PasswordRequestEventSubscriber.php (98%) rename core/{EventSuscriber => EventSubscriber}/EntityManagerEventSubscriber.php (97%) rename core/{EventSuscriber => EventSubscriber}/NavigationSettingEventSubscriber.php (95%) create mode 100644 core/EventSubscriber/RequestSecurityEventSubscriber.php rename core/{EventSuscriber => EventSubscriber}/SettingEventSubscriber.php (94%) rename core/{EventSuscriber => EventSubscriber}/Site/ForcedDomainEventSubscriber.php (97%) rename core/{EventSuscriber => EventSubscriber}/Site/MenuEventSubscriber.php (96%) rename core/{EventSuscriber => EventSubscriber}/Site/NavigationEventSubscriber.php (91%) rename core/{EventSuscriber => EventSubscriber}/Site/NodeEventSubscriber.php (98%) rename core/{EventSuscriber => EventSubscriber}/Site/Page/BlockEventSubscriber.php (93%) rename core/{EventSuscriber => EventSubscriber}/Site/Page/PageEventSubscriber.php (92%) rename core/{EventSuscriber => EventSubscriber}/Site/SiteEventSubscriber.php (92%) rename core/{EventSuscriber => EventSubscriber}/Task/CacheCleanTaskEventSubscriber.php (95%) rename core/{EventSuscriber => EventSubscriber}/Task/TaskEventSubscriber.php (94%) create mode 100644 core/Site/RoleConfiguration.php create mode 100644 core/Site/RoleLocator.php rename src/{EventSuscriber => EventSubscriber}/Blog/CategoryEventSubscriber.php (91%) rename src/{EventSuscriber => EventSubscriber}/Blog/CommentEventSubscriber.php (97%) rename src/{EventSuscriber => EventSubscriber}/Blog/PostEventSubscriber.php (95%) rename src/{EventSuscriber => EventSubscriber}/Blog/PostFollowEventSubscriber.php (96%) rename src/{EventSuscriber => EventSubscriber}/SettingEventSubscriber.php (96%) diff --git a/core/Controller/Site/NodeAdminController.php b/core/Controller/Site/NodeAdminController.php index 327bd34..5149078 100644 --- a/core/Controller/Site/NodeAdminController.php +++ b/core/Controller/Site/NodeAdminController.php @@ -14,6 +14,7 @@ use App\Core\Form\Site\NodeType as EntityType; use App\Core\Manager\EntityManager; use App\Core\Repository\Site\NodeRepository; use App\Core\Site\ControllerLocator; +use App\Core\Site\RoleLocator; use App\Core\Site\PageLocator; use App\Core\Sitemap\SitemapBuilder; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -39,12 +40,14 @@ class NodeAdminController extends AbstractController NodeRepository $nodeRepository, PageLocator $pageLocator, ControllerLocator $controllerLocator, + RoleLocator $roleLocator, Request $request ): Response { $entity = $factory->create($node->getMenu()); $form = $this->createForm(EntityType::class, $entity, [ 'pages' => $pageLocator->getPages(), 'controllers' => $controllerLocator->getControllers(), + 'roles' => $roleLocator->getRoles(), 'navigation' => $node->getMenu()->getNavigation(), ]); @@ -109,12 +112,14 @@ class NodeAdminController extends AbstractController PageFactory $pageFactory, PageLocator $pageLocator, ControllerLocator $controllerLocator, + RoleLocator $roleLocator, Request $request, string $tab = 'content' ): Response { $form = $this->createForm(EntityType::class, $entity, [ 'pages' => $pageLocator->getPages(), 'controllers' => $controllerLocator->getControllers(), + 'roles' => $roleLocator->getRoles(), 'navigation' => $entity->getMenu()->getNavigation(), ]); diff --git a/core/Controller/User/UserAdminController.php b/core/Controller/User/UserAdminController.php index 7fdeea9..b63b29a 100644 --- a/core/Controller/User/UserAdminController.php +++ b/core/Controller/User/UserAdminController.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\Routing\Annotation\Route; +use App\Core\Security\TokenGenerator; class UserAdminController extends CrudController { @@ -30,11 +31,9 @@ class UserAdminController extends CrudController /** * @Route("/admin/user/new", name="admin_user_new", methods={"GET", "POST"}) */ - public function new(Factory $factory, EntityManager $entityManager, Request $request): Response + public function new(Factory $factory, EntityManager $entityManager, Request $request, TokenGenerator $tokenGenerator): Response { - $entity = $factory->create($this->getUser()); - - return $this->doNew($factory->create(), $entityManager, $request); + return $this->doNew($factory->create(null, $tokenGenerator->generateToken()), $entityManager, $request); } /** diff --git a/core/DependencyInjection/Configuration.php b/core/DependencyInjection/Configuration.php index 8334f69..9d6b419 100644 --- a/core/DependencyInjection/Configuration.php +++ b/core/DependencyInjection/Configuration.php @@ -69,6 +69,22 @@ class Configuration implements ConfigurationInterface ->end() ->end() ->end() + ->arrayNode('security') + ->children() + ->arrayNode('roles') + ->prototype('array') + ->children() + ->scalarNode('name') + ->cannotBeEmpty() + ->end() + ->scalarNode('role') + ->cannotBeEmpty() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() ->arrayNode('pages') ->prototype('array') ->children() diff --git a/core/Entity/Site/Node.php b/core/Entity/Site/Node.php index 4196966..6c9b77e 100644 --- a/core/Entity/Site/Node.php +++ b/core/Entity/Site/Node.php @@ -157,6 +157,16 @@ class Node implements EntityInterface */ protected $analyticReferers; + /** + * @ORM\Column(type="array") + */ + private $securityRoles = []; + + /** + * @ORM\Column(type="string", length=3, options={"default"="or"}) + */ + private $securityOperator; + public function __construct() { $this->children = new ArrayCollection(); @@ -640,4 +650,28 @@ class Node implements EntityInterface return $this; } + + public function getSecurityRoles(): array + { + return !is_array($this->securityRoles) ? [] : $this->securityRoles; + } + + public function setSecurityRoles(array $securityRoles): self + { + $this->securityRoles = $securityRoles; + + return $this; + } + + public function getSecurityOperator(): ?string + { + return $this->securityOperator; + } + + public function setSecurityOperator(string $securityOperator): self + { + $this->securityOperator = $securityOperator; + + return $this; + } } diff --git a/core/EventSuscriber/Account/PasswordRequestEventSubscriber.php b/core/EventSubscriber/Account/PasswordRequestEventSubscriber.php similarity index 98% rename from core/EventSuscriber/Account/PasswordRequestEventSubscriber.php rename to core/EventSubscriber/Account/PasswordRequestEventSubscriber.php index 909cb22..8f2c5d2 100644 --- a/core/EventSuscriber/Account/PasswordRequestEventSubscriber.php +++ b/core/EventSubscriber/Account/PasswordRequestEventSubscriber.php @@ -1,6 +1,6 @@ + */ +class RequestSecurityEventSubscriber implements EventSubscriberInterface +{ + protected NodeRepository $nodeRepository; + protected AuthorizationChecker $authorizationChecker; + + public function __construct(NodeRepository $nodeRepository, ContainerInterface $container) + { + $this->nodeRepository = $nodeRepository; + $this->authorizationChecker = $container->get('security.authorization_checker'); + } + + public function onKernelRequest(RequestEvent $event) + { + $request = $event->getRequest(); + + if (!$request->attributes->has('_node')) { + return; + } + + $node = $this->nodeRepository->findOneBy([ + 'id' => $request->attributes->get('_node'), + ]); + + $roles = $node->getSecurityRoles(); + + if (empty($roles)) { + return; + } + + $operator = $node->getSecurityOperator(); + $exception = new AccessDeniedException('Access denied'); + $isAuthorized = false; + + foreach ($roles as $role) { + $isGranted = $this->authorizationChecker->isGranted($role); + + if ('or' === $operator && $isGranted) { + $isAuthorized = true; + } elseif ('and' === $operator && !$isGranted) { + throw $exception; + } + } + + if (!$isAuthorized) { + throw $exception; + } + } + + public static function getSubscribedEvents(): array + { + return [ + RequestEvent::class => ['onKernelRequest', 1], + ]; + } +} diff --git a/core/EventSuscriber/SettingEventSubscriber.php b/core/EventSubscriber/SettingEventSubscriber.php similarity index 94% rename from core/EventSuscriber/SettingEventSubscriber.php rename to core/EventSubscriber/SettingEventSubscriber.php index d8db765..c0ff1c4 100644 --- a/core/EventSuscriber/SettingEventSubscriber.php +++ b/core/EventSubscriber/SettingEventSubscriber.php @@ -1,6 +1,6 @@ setEmail($email); } - if (null !== $email) { + if (null !== $password) { $entity->setPassword($this->encoder->encodePassword($entity, $password)); } diff --git a/core/Form/Site/NodeType.php b/core/Form/Site/NodeType.php index 284d0f9..923e7e1 100644 --- a/core/Form/Site/NodeType.php +++ b/core/Form/Site/NodeType.php @@ -119,6 +119,41 @@ class NodeType extends AbstractType ] ); + if (count($options['roles']) > 0) { + $builder->add( + 'securityRoles', + ChoiceType::class, + [ + 'label' => 'Roles', + 'required' => false, + 'multiple' => true, + 'expanded' => true, + 'choices' => call_user_func(function () use ($options) { + $choices = []; + + foreach ($options['roles'] as $role) { + $choices[$role->getName()] = $role->getRole(); + } + + return $choices; + }), + ] + ); + + $builder->add( + 'securityOperator', + ChoiceType::class, + [ + 'label' => 'Condition', + 'required' => true, + 'choices' => [ + 'At least one role' => 'or', + 'All roles' => 'and', + ], + ] + ); + } + $actions = [ 'New page' => 'new', 'Use an existing page' => 'existing', @@ -278,6 +313,7 @@ class NodeType extends AbstractType 'data_class' => Node::class, 'pages' => [], 'controllers' => [], + 'roles' => [], 'navigation' => null, ]); } diff --git a/core/Resources/translations/messages.fr.yaml b/core/Resources/translations/messages.fr.yaml index c2f6d2b..cdba966 100644 --- a/core/Resources/translations/messages.fr.yaml +++ b/core/Resources/translations/messages.fr.yaml @@ -217,3 +217,6 @@ "Disable": "Désactiver" "Reuse the query string": "Réutiliser la chaîne de requête" "First element": "Premier élément" +"Roles": "Rôles" +"Condition": "Condition" +"Security": "Sécurité" diff --git a/core/Resources/views/site/node_admin/_form.html.twig b/core/Resources/views/site/node_admin/_form.html.twig index 42079fd..0496509 100644 --- a/core/Resources/views/site/node_admin/_form.html.twig +++ b/core/Resources/views/site/node_admin/_form.html.twig @@ -173,6 +173,7 @@ {% endif %} {{ form_row(form.url) }} + {{ form_row(form.code) }}
@@ -206,9 +207,34 @@ {{ form_row(form.disableUrl) }} {{ form_row(form.enableAnalytics) }} - {{ form_row(form.code) }} - {{ form_row(form.contentType) }} - {{ form_row(form.controller) }} + +
+
+ {{ form_row(form.controller) }} +
+
+ {{ form_row(form.contentType) }} +
+
+ + {% if form.securityRoles is defined %} +
+
+ + {{ 'Security'|trans }} + + +
+
+ {{ form_row(form.securityRoles) }} +
+
+ {{ form_row(form.securityOperator) }} +
+
+
+
+ {% endif %}
{% for item in form.parameters %} diff --git a/core/Site/ControllerLocator.php b/core/Site/ControllerLocator.php index 5956ca7..2991e67 100644 --- a/core/Site/ControllerLocator.php +++ b/core/Site/ControllerLocator.php @@ -30,13 +30,13 @@ class ControllerLocator $params = $this->params['site']['controllers'] ?? []; foreach ($params as $conf) { - $controllerConfiguration = new ControllerConfiguration(); - $controllerConfiguration + $configuration = new ControllerConfiguration(); + $configuration ->setName($conf['name']) ->setAction($conf['action']) ; - $this->controllers[$conf['action']] = $controllerConfiguration; + $this->controllers[$conf['action']] = $configuration; } } } diff --git a/core/Site/RoleConfiguration.php b/core/Site/RoleConfiguration.php new file mode 100644 index 0000000..c15f4f8 --- /dev/null +++ b/core/Site/RoleConfiguration.php @@ -0,0 +1,38 @@ + + */ +class RoleConfiguration +{ + protected string $name; + protected string $role; + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function getName(): string + { + return $this->name; + } + + public function setRole(string $role): self + { + $this->role = $role; + + return $this; + } + + public function getRole(): string + { + return $this->role; + } +} diff --git a/core/Site/RoleLocator.php b/core/Site/RoleLocator.php new file mode 100644 index 0000000..5dcbdd5 --- /dev/null +++ b/core/Site/RoleLocator.php @@ -0,0 +1,42 @@ + + */ +class RoleLocator +{ + protected array $params; + protected array $roles = []; + + public function __construct(ParameterBagInterface $bag) + { + $this->params = $bag->get('core'); + $this->loadRoles(); + } + + public function getRoles(): array + { + return $this->roles; + } + + protected function loadRoles(): void + { + $params = $this->params['site']['security']['roles'] ?? []; + + foreach ($params as $conf) { + $configuration = new RoleConfiguration(); + $configuration + ->setName($conf['name']) + ->setRole($conf['role']) + ; + + $this->roles[$conf['name']] = $configuration; + } + } +} diff --git a/src/EventSuscriber/Blog/CategoryEventSubscriber.php b/src/EventSubscriber/Blog/CategoryEventSubscriber.php similarity index 91% rename from src/EventSuscriber/Blog/CategoryEventSubscriber.php rename to src/EventSubscriber/Blog/CategoryEventSubscriber.php index 50f5837..7a0ac45 100644 --- a/src/EventSuscriber/Blog/CategoryEventSubscriber.php +++ b/src/EventSubscriber/Blog/CategoryEventSubscriber.php @@ -1,10 +1,10 @@