src/Twig/Runtime/AclRuntime.php line 240

Open in your IDE?
  1. <?php
  2. /** @noinspection ALL */
  3. namespace App\Twig\Runtime;
  4. use App\Constants\ACL;
  5. use App\Entity\Parameter;
  6. use App\Entity\SliderItem;
  7. use App\Entity\User;
  8. use App\Model\Product;
  9. use App\Services\Back\ParameterService;
  10. use App\Services\Common\AclServiceV2;
  11. use Doctrine\ORM\EntityManagerInterface;
  12. use Psr\Cache\InvalidArgumentException;
  13. use Symfony\Component\HttpFoundation\RequestStack;
  14. use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
  15. use Symfony\Component\Security\Core\Security;
  16. use Symfony\Component\Security\Core\User\UserInterface;
  17. use Twig\Extension\RuntimeExtensionInterface;
  18. use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
  19. /**
  20. * Rassemble toute les fonction disponible dans twig qui touchent aux ACL et à l'affichage des données via les règles acl ou les systèmes paralèlles (roles, jobs sur les entités comme pour les slider ou les documents)
  21. */
  22. class AclRuntime implements RuntimeExtensionInterface
  23. {
  24. private AclServiceV2 $aclService;
  25. private RequestStack $requestStack;
  26. private ParameterService $parameterService;
  27. private Security $security;
  28. private RoleHierarchyInterface $roleHierarchy;
  29. private EntityManagerInterface $em;
  30. private array $userIsGrantedCache = [];
  31. private array $componentVisibilityCache = [];
  32. private array $displayConfigCache = [];
  33. public function __construct(
  34. AclServiceV2 $aclService,
  35. RequestStack $requestStack,
  36. ParameterService $parameterService,
  37. Security $security,
  38. RoleHierarchyInterface $roleHierarchy,
  39. EntityManagerInterface $em
  40. )
  41. {
  42. $this->aclService = $aclService;
  43. $this->requestStack = $requestStack;
  44. $this->parameterService = $parameterService;
  45. $this->security = $security;
  46. $this->roleHierarchy = $roleHierarchy;
  47. $this->em = $em;
  48. }
  49. /**
  50. * Permet de savoir si le user a le droit d'accéder à la route
  51. * @param User|null $user
  52. * @param string $route
  53. * @param array $params
  54. * @param string $env
  55. * @return bool
  56. */
  57. public function userIsGrantedRoute(
  58. ?User $user,
  59. string $route,
  60. array $params = [],
  61. string $env = ACL::FRONT_ENV,
  62. bool $debug = FALSE
  63. )
  64. {
  65. $config = [
  66. 'route' => $route,
  67. 'params' => $params,
  68. 'component' => ACL::ACL_NO_COMPONENT,
  69. 'slug' => ACL::ACL_NO_SLUG,
  70. 'action' => ACL::READ,
  71. 'env' => $env,
  72. ];
  73. return $this->aclService->userIsGranted( $user, $config, $debug );
  74. }
  75. /**
  76. * Permet de savoir si un user peut faire une action en fonction de son rôle ou de son job
  77. *
  78. * @param User|null $user
  79. * @param string $slug
  80. * @param string $action
  81. * @param string $env
  82. *
  83. * @return bool
  84. *
  85. * @throws InvalidArgumentException
  86. */
  87. public function userIsGranted(
  88. ?User $user,
  89. string $component,
  90. string $slug = ACL::ACL_NO_SLUG,
  91. string $action = ACL::READ,
  92. string $env = ACL::FRONT_ENV,
  93. string $route = NULL,
  94. string $params = NULL,
  95. bool $debug = FALSE
  96. ): bool
  97. {
  98. $currentRoute = $route ?? $this->requestStack->getCurrentRequest()->get( '_route' );
  99. $currentParams = $params ?? $this->requestStack->getCurrentRequest()->get( '_route_params' );
  100. if ($currentRoute === NULL){
  101. return $this->checkWhenRouteIsNull();
  102. }
  103. $config = [
  104. 'route' => $currentRoute,
  105. 'params' => $this->aclService->getRouteParamsForAcl($currentRoute, $currentParams),
  106. 'component' => $component,
  107. 'slug' => $slug,
  108. 'action' => $action,
  109. 'env' => $env,
  110. ];
  111. $cacheKey = implode('|', [
  112. $user instanceof User ? $user->getId() : 'anonymous',
  113. $currentRoute,
  114. $config['params'],
  115. $component,
  116. $slug,
  117. $action,
  118. $env,
  119. ]);
  120. if (array_key_exists($cacheKey, $this->userIsGrantedCache)) {
  121. return $this->userIsGrantedCache[$cacheKey];
  122. }
  123. return $this->userIsGrantedCache[$cacheKey] = $this->aclService->userIsGranted( $user, $config, $debug );
  124. }
  125. /**
  126. * Logique pour éviter des erreurs 500 si jamais une route est évaluée à NULL
  127. * Si on est dans le cas d'une exception on autorise la visualition
  128. * @return true
  129. * @throws \Exception
  130. */
  131. private function checkWhenRouteIsNull()
  132. {
  133. $request = $this->requestStack->getCurrentRequest();
  134. $exeption = $request->attributes->get('exception');
  135. if ($exeption !== null) {
  136. return TRUE;
  137. }
  138. throw new \Exception('Une erreur est survenue, la route ne peut pas être null et ne pas être une exception');
  139. }
  140. /**
  141. * Permet de savoir si le current user à le droit de voir le catalogue par son slug
  142. * @param $catalogue
  143. * @return bool
  144. * @throws InvalidArgumentException
  145. */
  146. public function userIsGrantedCatalogue($catalogue)
  147. {
  148. $currentUser = $this->security->getUser();
  149. return $this->aclService->userIsGrantedCatalogue($currentUser, $catalogue);
  150. }
  151. /**
  152. * Permet de savoir si le current user à le droit de voir le produit
  153. * @param Product $product
  154. *
  155. * @return bool
  156. * @throws \JsonException
  157. */
  158. public function userIsGrantedProduct(Product $product)
  159. {
  160. $currentUser = $this->security->getUser();
  161. return $this->aclService->userIsGrantedProduct($currentUser, $product);
  162. }
  163. /**
  164. * Retourne le slug du premier catalogue qui contient le produit et qui est accèssible au user courant
  165. * @param Product $product
  166. *
  167. * @return mixed|null
  168. * @throws \JsonException
  169. */
  170. public function getUserFirstGrantedCatalogSlugForProduct(Product $product)
  171. {
  172. $currentUser = $this->security->getUser();
  173. return $this->aclService->getUserFirstGrantedCatalogSlugForProduct($currentUser, $product);
  174. }
  175. /**
  176. * Défini si l'utilisateur à le droit de voir le document
  177. *
  178. * @param User|null $user
  179. * @param Parameter $document
  180. *
  181. * @return bool
  182. *
  183. * @throws \JsonException
  184. */
  185. public function canDisplayDocument( ?User $user, Parameter $document ): bool
  186. {
  187. return $this->aclService->userIsGrantedOnDocument( $user, $document );
  188. }
  189. /**
  190. * Lors des documents personnalisé, permet de savoit si le user peut voir le document
  191. *
  192. * @param User|null $user
  193. * @param $id
  194. *
  195. * @return bool
  196. *
  197. * @throws \JsonException
  198. */
  199. public function isDocumentSelectedForUser( ?User $user, $id ): bool
  200. {
  201. return $this->parameterService->isDocumentSelectedForUser( $user, $id );
  202. }
  203. /**
  204. * Permet de savoir si on component est visible par le user courant
  205. *
  206. * Ne doit être utilisé que pour l'affichage twig des components en front
  207. *
  208. * @param array|null $componentOptions
  209. * @param bool $debug
  210. * @return bool
  211. * @throws InvalidArgumentException
  212. */
  213. public function canDisplayComponentByAcl(?array $componentOptions, bool $debug = FALSE)
  214. {
  215. if ($componentOptions === NULL || $componentOptions === []){
  216. return TRUE;
  217. }
  218. $request = $this->requestStack->getCurrentRequest();
  219. $cacheKey = md5(json_encode([
  220. 'route' => $request?->get('_route'),
  221. 'user' => $this->security->getUser() instanceof User ? $this->security->getUser()->getId() : 'anonymous',
  222. 'componentOptions' => $componentOptions,
  223. ]));
  224. if (array_key_exists($cacheKey, $this->componentVisibilityCache)) {
  225. return $this->componentVisibilityCache[$cacheKey];
  226. }
  227. $item = $componentOptions['item'] ?? $componentOptions;
  228. // Le component est actif sur la page ?
  229. $canDisplay = (bool)$item[ 'enabled' ] ?? TRUE;
  230. if (!$canDisplay) {
  231. return FALSE;
  232. }
  233. // on regarde s'il peut s'afficher sur la page via la clef display
  234. $canDisplay = $this->canDisplayOnPageByConfig($item['display'], $debug);
  235. if (!$canDisplay) {
  236. return FALSE;
  237. }
  238. /** @var User $currentUser */
  239. $currentUser = $this->security->getUser();
  240. // on recherche l'acl si on peut récupérer son slug data-component-acl
  241. if ( isset($item['data']['data-component-acl'])){
  242. $canDisplay = $this->userIsGranted($currentUser, $item['data']['data-component-acl'] ?? ACL::ACL_NO_COMPONENT);
  243. }
  244. if (!$canDisplay) {
  245. return FALSE;
  246. }
  247. // on regarde s'il y a des univers... on verifie le currentUser car le component peut être utilisé en partie security
  248. if ($currentUser !== NULL && isset($item['univers'])){
  249. if ( $canDisplay && !$currentUser->isDeveloper() && !$currentUser->isSuperAdmin() && isset($item['univers'])){
  250. $canDisplay = $this->aclService->canDisplayByUniverses($currentUser, $item['univers']);
  251. }
  252. }
  253. return $this->componentVisibilityCache[$cacheKey] = $canDisplay;
  254. }
  255. /**
  256. * Défini si un component s'affiche via la clef display
  257. *
  258. * Elle contient 2 enfants:
  259. * enabled_on : array (tableau de route)|null
  260. * disabled_on : array (tableau de route)|null
  261. * Si disabled_on est a autre chose que null, alors c'est lui qui prend l'ascendant
  262. * Afficher partout => enabled_on: null + disabled_on: [] ou null
  263. *
  264. * @param array $display
  265. *
  266. * @return bool
  267. */
  268. private function canDisplayOnPageByConfig(array $display, bool $debug = FALSE)
  269. {
  270. $currentRoute = $this->requestStack->getCurrentRequest()->get( '_route' );
  271. if ($currentRoute === NULL){
  272. return $this->checkWhenRouteIsNull();
  273. }
  274. $cacheKey = md5(json_encode([$currentRoute, $display]));
  275. if (array_key_exists($cacheKey, $this->displayConfigCache)) {
  276. return $this->displayConfigCache[$cacheKey];
  277. }
  278. $canDisplay = TRUE; // par défaut on affiche
  279. // On prend la config du disabled_on si elle n'est pas nulle
  280. $displayByDisabled = FALSE;
  281. if (isset($display['disabled_on']) && $display['disabled_on'] !== NULL){
  282. $displayByDisabled = TRUE;
  283. }
  284. // On prend la config enbaled_on
  285. if (isset($display['enabled_on']) && !$displayByDisabled){
  286. $arrayRoute = $display['enabled_on'];
  287. if(gettype($display['enabled_on']) === "string") {
  288. $arrayRoute = json_decode($display['enabled_on'], true);
  289. }
  290. switch (TRUE){
  291. // tableau vide, ce n'est pas visible
  292. case $arrayRoute === []:
  293. $canDisplay = FALSE;
  294. break;
  295. // NULL ou valeur qui n'est pas un tableau, on considère que c'est visible
  296. // ainsi si enabled_on et disabled_on sont NULL, on affiche
  297. case $arrayRoute === NULL:
  298. case !is_array($arrayRoute):
  299. $canDisplay = TRUE;
  300. break;
  301. // on regarde si la route actuelle est dans le tableau pour l'afficher
  302. default:
  303. $canDisplay = in_array( $currentRoute, $arrayRoute, TRUE );
  304. break;
  305. }
  306. }
  307. // on prend la config disabled_on
  308. if (isset($display['disabled_on']) && $displayByDisabled){
  309. $arrayRoute = $display['disabled_on'];
  310. if(gettype($display['disabled_on']) === "string") {
  311. $arrayRoute = json_decode($display['disabled_on'], true);
  312. }
  313. switch (TRUE){
  314. // tableau vide, c'est visible partout
  315. case $arrayRoute === []:
  316. $canDisplay = TRUE;
  317. break;
  318. // NULL ou valeur qui n'est pas un tableau, on refuse l'affichage
  319. // Normalement on ne tombe pas dans cette configuration puisque $displayByDisabled est FALSE
  320. case $arrayRoute === NULL:
  321. case !is_array($arrayRoute):
  322. $canDisplay = FALSE;
  323. break;
  324. // on regarde si la route actuelle est dans le tableau pour refuser l'affichage
  325. default:
  326. $canDisplay = !in_array( $currentRoute, $arrayRoute, TRUE );
  327. break;
  328. }
  329. }
  330. return $this->displayConfigCache[$cacheKey] = $canDisplay;
  331. }
  332. /**
  333. * Permet de savoir si le user peut voir le slider
  334. *
  335. * Les acl du slider sont intégré à l'édition de l'entité
  336. *
  337. * @param User $user
  338. * @param SliderItem $item
  339. *
  340. * @return bool
  341. */
  342. public function canDisplaySliderItem(?User $user, SliderItem $item): bool
  343. {
  344. if(!$user) return FALSE;
  345. $jobs = $item->getDisplayJob();
  346. $universes = $item->getDisplayUniverses();
  347. // Le superadmin et dev doivent pouvoir tout voir...
  348. if ( $user->isDeveloper() || $user->isSuperAdmin() ) {
  349. return TRUE;
  350. }
  351. // Par défaut, tout s'affiche
  352. $canDisplay = TRUE;
  353. // SI config par job on regarde si ça match
  354. if ( $jobs !== NULL && !in_array( $user->getJob(), $jobs, TRUE ) ) {
  355. $canDisplay = FALSE;
  356. }
  357. // Si config par univers on regarde si ça match
  358. if ( $universes !== NULL ) {
  359. foreach ( $user->getUniverses() as $userUnivers ) {
  360. if ( in_array( $userUnivers->getSlug(), $universes, TRUE ) ) {
  361. $canDisplay = TRUE;
  362. break;
  363. }
  364. $canDisplay = FALSE;
  365. }
  366. }
  367. return $canDisplay;
  368. }
  369. /**
  370. * Normalise la transformation de l'array qui contient les params d'une route pour la transformer en string
  371. * @param array $params
  372. *
  373. * @return false|string
  374. * @throws \JsonException
  375. */
  376. public function formatParamsToString(array $params)
  377. {
  378. return $this->aclService->formatArrayParamsToString($params);
  379. }
  380. /**
  381. * Retourne un tableau avec la liste de roles et les jobs relatif à ces roles
  382. *
  383. * @return array[]
  384. */
  385. public function getDefaultRolesAndJobs()
  386. {
  387. return $this->aclService->getDefaultRoleAndJob();
  388. }
  389. /**
  390. * @return UserInterface|null
  391. */
  392. public function getOriginalUser(): ?UserInterface
  393. {
  394. $token = $this->security->getToken();
  395. if ($token instanceof SwitchUserToken)
  396. {
  397. $user = $token->getOriginalToken()->getUser();
  398. $user = $this->em->getRepository(User::class)->findOneBy(['email' => $user->getEmail()]);
  399. return $user;
  400. }
  401. return $this->security->getUser();
  402. }
  403. /**
  404. * @param User $user
  405. * @param string $role
  406. *
  407. * @return bool
  408. */
  409. public function hasRole(User $user, string $role): bool
  410. {
  411. $reachableRoles = $this->roleHierarchy->getReachableRoleNames($user->getRoles());
  412. return in_array($role, $reachableRoles);
  413. }
  414. }