src/Services/Common/AclServiceV2.php line 149

Open in your IDE?
  1. <?php
  2. namespace App\Services\Common;
  3. use App\Constants\ACL;
  4. use App\Constants\UserExtension;
  5. use App\Entity\AclSetting;
  6. use App\Entity\Parameter;
  7. use App\Entity\Univers;
  8. use App\Entity\User;
  9. use App\Model\AclSettingConfig;
  10. use App\Model\Product;
  11. use App\Services\Back\ParameterService;
  12. use App\Services\Front\Catalogue\JsonCatalogueService;
  13. use Doctrine\ORM\EntityManagerInterface;
  14. use JsonException;
  15. use League\Csv\Exception;
  16. use Psr\Log\LoggerInterface;
  17. use Symfony\Component\Routing\RouterInterface;
  18. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  19. /**
  20. * Service pour la gestion des ACL V2 coté Plateforme
  21. */
  22. class AclServiceV2
  23. {
  24. private RouterInterface $router;
  25. private TokenStorageInterface $tokenStorage;
  26. private ParameterService $parameterService;
  27. private EntityManagerInterface $em;
  28. private ModuleSettingService $moduleSettingService;
  29. private JsonCatalogueService $jsonCatalogueService;
  30. private CommunityService $communityService;
  31. private LoggerInterface $logger;
  32. private array $aclRouteOptionsCache = [];
  33. private array $aclItemCache = [];
  34. private array $userIsGrantedCache = [];
  35. private array $normalizedParamsCache = [];
  36. private array $firstGrantedCatalogCache = [];
  37. private ?array $defaultRoleAndJobCache = NULL;
  38. public function __construct(RouterInterface $router, ParameterService $parameterService, TokenStorageInterface $tokenStorage, EntityManagerInterface $em, ModuleSettingService $moduleSettingService, JsonCatalogueService $jsonCatalogueService, CommunityService $communityService, LoggerInterface $logger)
  39. {
  40. $this->router = $router;
  41. $this->parameterService = $parameterService;
  42. $this->tokenStorage = $tokenStorage;
  43. $this->em = $em;
  44. $this->moduleSettingService = $moduleSettingService;
  45. $this->jsonCatalogueService = $jsonCatalogueService;
  46. $this->communityService = $communityService;
  47. $this->logger = $logger;
  48. }
  49. /**
  50. * @param $config
  51. *
  52. * @return false|mixed|string
  53. *
  54. * @throws JsonException
  55. */
  56. public function getNormalizedAclSettingConfigParams($config)
  57. {
  58. $config = $config instanceof AclSettingConfig ? $config->toArray() : $config;
  59. $params = $config['params'] ?? ACL::ACL_NO_PARAMS;
  60. if(is_array($params))
  61. {
  62. if($config['route'] !== NULL)
  63. {
  64. $params = $this->getRouteParamsForAcl($config['route'], $params);
  65. }
  66. else
  67. {
  68. $params = $this->formatArrayParamsToString($params);
  69. }
  70. }
  71. elseif(in_array($params, [NULL, ''], TRUE))
  72. {
  73. $params = ACL::ACL_NO_PARAMS;
  74. }
  75. return $params;
  76. }
  77. /**
  78. * Retourne les params sous forme de string pour les ACL
  79. *
  80. * Supprime les params inutiles comme "_env"
  81. * Supprime les params qui ne sont pas configurés pour être pris en compte dans la route
  82. *
  83. * @param string|null $routeName
  84. * @param array|string|null $params
  85. *
  86. * @return string
  87. *
  88. */
  89. public function getRouteParamsForAcl(?string $routeName, array|string|null $params): string
  90. {
  91. $cacheKey = $routeName . '|' . (is_array($params) ? $this->formatArrayParamsToString($params) : (string)$params);
  92. if(array_key_exists($cacheKey, $this->normalizedParamsCache))
  93. {
  94. return $this->normalizedParamsCache[$cacheKey];
  95. }
  96. // cette fonction doit être capable de normalizer les params qui sont en string ou en array
  97. if(is_string($params))
  98. {
  99. try
  100. {
  101. $params = json_decode($params, TRUE, 512, JSON_THROW_ON_ERROR);
  102. } catch(JsonException $e)
  103. {
  104. $this->logger->error($e->getMessage());
  105. $params = [];
  106. }
  107. }
  108. //unset de la clef _env présente en back_office
  109. if(isset($params['_env']))
  110. {
  111. unset($params['_env']);
  112. }
  113. $paramsOptions = $this->getAclRouteOptions($routeName);
  114. foreach($paramsOptions as $key => $value)
  115. {
  116. if(isset($params[$key]) && !$value)
  117. {
  118. unset($params[$key]);
  119. }
  120. }
  121. return $this->normalizedParamsCache[$cacheKey] = $this->formatArrayParamsToString($params);
  122. }
  123. /**
  124. * Retourne les informations de la clef acl dans les options de la route
  125. *
  126. * Certains params ne doivent pas être pris en compte pour la création des ACL (ex id d'une commande)
  127. * Il faut ajouter dans les options de la route le tableau suivant
  128. * acl :
  129. * id : false <=== le params ID ne doit pas peser dans la règle des ACL
  130. *
  131. * @param string|null $routeName
  132. *
  133. * @return array|mixed|null
  134. */
  135. public function getAclRouteOptions(?string $routeName): mixed
  136. {
  137. if($routeName === NULL)
  138. {
  139. return [];
  140. }
  141. if(array_key_exists($routeName, $this->aclRouteOptionsCache))
  142. {
  143. return $this->aclRouteOptionsCache[$routeName];
  144. }
  145. // Récupère les informations complètes de la route courante
  146. $route = $this->router->getRouteCollection()->get($routeName);
  147. if($route === NULL)
  148. {
  149. return $this->aclRouteOptionsCache[$routeName] = [];
  150. }
  151. return $this->aclRouteOptionsCache[$routeName] = ($route->getOption('acl') ?? []);
  152. }
  153. /**
  154. * Normalise le json_encode des params pour la transformation array to string
  155. *
  156. * @param array|null $params
  157. *
  158. * @return string
  159. */
  160. public function formatArrayParamsToString(?array $params): string
  161. {
  162. if($params === NULL) return ACL::ACL_NO_PARAMS;
  163. try
  164. {
  165. return json_encode($params, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
  166. } catch(JsonException $e)
  167. {
  168. $this->logger->error($e->getMessage());
  169. return ACL::ACL_NO_PARAMS;
  170. }
  171. }
  172. /**
  173. * Récupère l'environnement d'une route (front ou back) par le début du routename
  174. *
  175. * @param string $routeName
  176. *
  177. * @return string
  178. */
  179. public function getEnvByRoute(string $routeName): string
  180. {
  181. if(str_contains($routeName, ACL::START_ROUTE_BACK))
  182. {
  183. return ACL::BACK_ENV;
  184. }
  185. return ACL::FRONT_ENV;
  186. }
  187. /**
  188. * Remplit le fichier d'ACL avec les données lors du POST de la modale
  189. *
  190. * @param AclSettingConfig $config
  191. * @param array $acl
  192. *
  193. * @return array
  194. */
  195. public function setAllData(AclSettingConfig $config, array $acl = []): array
  196. {
  197. $acls = [];
  198. foreach($acl as $role => $jobs)
  199. {
  200. $config->setRole($role);
  201. foreach($jobs as $job => $value)
  202. {
  203. $config->setJob($job);
  204. $acls[] = $this->setData($config, $value, FALSE);
  205. }
  206. }
  207. // reset de la config si on en a besoin ultérieurement
  208. $config->setRole(NULL)->setJob(NULL);
  209. $this->em->flush();
  210. return $acls;
  211. }
  212. /**
  213. * Set 1 ligne d'acl uniquement
  214. *
  215. * @param AclSettingConfig $config
  216. * @param bool $value
  217. * @param bool $withFlush
  218. *
  219. * @return AclSetting|float|int|mixed|string|null
  220. */
  221. public function setData(AclSettingConfig $config, bool $value, bool $withFlush = TRUE): mixed
  222. {
  223. // SI pas de role set, on met par défaut le ROLE_USER
  224. // TODO voir si on retourne une erreur à la place
  225. if($config->getRole() === NULL) $config->setRole('ROLE_USER');
  226. // Si pas de job set, on met la constant ACL_NO_JOB
  227. if($config->getJob() === NULL) $config->setRole(ACL::ACL_NO_JOB);
  228. // vérifications des params en fonction de la configuration des routes
  229. $params = $this->getRouteParamsForAcl($config->getRoute(), $config->getParams());
  230. $config->setParams($params);
  231. $registeredAcl = $this->em->getRepository(AclSetting::class)->getAclSettingsFor($config, TRUE);
  232. if($registeredAcl === NULL)
  233. {
  234. $aclSetting = (new AclSetting())
  235. ->setFromAclSettingConfig($config)->setConcatKey($config->getConcatKey())
  236. ;
  237. $this->em->persist($aclSetting);
  238. }
  239. else
  240. {
  241. $aclSetting = $registeredAcl;
  242. }
  243. $aclSetting->setValue($value);
  244. if($withFlush) $this->em->flush();
  245. $this->aclItemCache[$config->getConcatKey()] = $aclSetting;
  246. $this->userIsGrantedCache = [];
  247. return $aclSetting;
  248. }
  249. /**
  250. * @param $config
  251. *
  252. * @return string
  253. * @deprecated
  254. */
  255. private function getConcatKey($config): string
  256. {
  257. $default = [
  258. 'env' => ACL::FRONT_ENV,
  259. 'route' => NULL,
  260. 'params' => ACL::ACL_NO_PARAMS,
  261. 'component' => ACL::ACL_NO_COMPONENT,
  262. 'slug' => ACL::ACL_NO_SLUG,
  263. 'action' => ACL::READ,
  264. ];
  265. $config = array_merge($default, $config);
  266. return $config['env'] . ACL::ACL_KEY_SEPARATOR . ($config['route'] ?? '') . ACL::ACL_KEY_SEPARATOR . $config['params'] . ACL::ACL_KEY_SEPARATOR . $config['component'] . ACL::ACL_KEY_SEPARATOR . $config['slug'] . ACL::ACL_KEY_SEPARATOR . $config['action'];
  267. }
  268. /**
  269. * Retourne l'objet utilisé pour construire le tableau d'ACL dans la modale
  270. *
  271. * @param AclSettingConfig $config
  272. *
  273. * @return array
  274. */
  275. public function getAclConfig(AclSettingConfig $config): array
  276. {
  277. // on garde la route d'origine en mémoire
  278. $routeName = $config->getRoute();
  279. if(NULL === $routeName) return [];
  280. // on transforme les valeurs pour correspondre aux différents cas
  281. $config = $this->transformAclVariables($config);
  282. $aclItems = $this->getAclItems($config);
  283. $tables = [];
  284. foreach($aclItems as $role => $jobs)
  285. {
  286. $rows = [];
  287. $rows[] = array_merge([$role], array_values($jobs));
  288. $tables[$role] = [
  289. 'header' => [array_keys($jobs)],
  290. 'row' => $rows,
  291. ];
  292. // Ajoute au début du tableau $jobs le $role
  293. $rows = [array_merge([$role], array_values($jobs))];
  294. $tables[$role] = [
  295. 'rows' => $rows,
  296. 'header' => array_keys($jobs),
  297. ];
  298. }
  299. $data = [
  300. 'method' => 'NaN',
  301. 'env' => $config->getEnv(),
  302. 'route' => $config->getRoute(),
  303. 'params' => $config->getParams(),
  304. 'component' => $config->getComponent(),
  305. 'slug' => $config->getSlug(),
  306. 'action' => $config->getAction(),
  307. 'tables' => $tables,
  308. ];
  309. $route = $this->router->getRouteCollection()->get($routeName);
  310. if($route !== NULL)
  311. {
  312. $defaults = $route->getDefaults();
  313. // On remplace ':' par '::' pour pouvoir utiliser la reflection
  314. $re = '/(.*\w):(\w.*)/m';
  315. $subst = "$1::$2";
  316. $defaults['_controller'] = preg_replace($re, $subst, $defaults['_controller']);
  317. $method = explode('::', $defaults['_controller']);
  318. if(method_exists($method[0], $method[1]))
  319. {
  320. $data['method'] = $defaults['_controller'];
  321. }
  322. }
  323. $data['routeName'] = $routeName;
  324. return $data;
  325. }
  326. /**
  327. * Prépare les variables d'ACL en fonction de cas particulier
  328. *
  329. * - slug pas toujours obligatoire
  330. * - les composants "commun" (header, footer) ne dépendent pas de la route
  331. * - les catalogues ne dépendent pas des routes
  332. *
  333. * @param AclSettingConfig $config
  334. *
  335. * @return AclSettingConfig
  336. */
  337. private function transformAclVariables(AclSettingConfig $config): AclSettingConfig
  338. {
  339. // le slug n'est pas toujours obligatoire
  340. if(in_array($config->getSlug(), ['', NULL], TRUE))
  341. {
  342. $config->setSlug(ACL::ACL_NO_SLUG);
  343. }
  344. // les components dans la partie common ne sont pas dépendant de la page
  345. if(str_starts_with($config->getComponent(), 'common.'))
  346. {
  347. $config->setRoute(ACL::ACL_ROUTE_FRONT_ALL);
  348. $config->setParams(ACL::ACL_NO_PARAMS);
  349. }
  350. // même principe pour le header dans le back office
  351. if($config->getEnv() === ACL::BACK_ENV && str_starts_with($config->getComponent(), 'header.'))
  352. {
  353. $config->setRoute(ACL::ACL_ROUTE_BACK_ALL);
  354. $config->setParams(ACL::ACL_NO_PARAMS);
  355. }
  356. // on s'occupe d'un acl global de catalogue
  357. if(str_starts_with($config->getSlug(), ACL::ACL_KEY_SLUG_SHOP_CATALOG))
  358. {
  359. $config->setRoute(ACL::ACL_ROUTE_SHOP_CONFIG);
  360. $config->setParams(ACL::ACL_NO_PARAMS);
  361. $config->setEnv(ACL::FRONT_ENV);
  362. }
  363. return $config;
  364. }
  365. /**
  366. * Donne l'ACL correspondant à l'environnement, la route, au composant et à l'action demandés et retourne un
  367. * tableau formaté pour l'affichage de la modale d'édition
  368. *
  369. * @param AclSettingConfig $config
  370. *
  371. * @return array
  372. */
  373. private function getAclItems(AclSettingConfig $config): array
  374. {
  375. $currentUser = $this->tokenStorage->getToken() !== NULL ? $this->tokenStorage->getToken()->getUser() : NULL;
  376. // on est obligé de transformer les clefs en premier en fonction des conditions
  377. $config = $this->transformAclVariables($config);
  378. $rolesAndJobs = $this->getDefaultRoleAndJob();
  379. $acls = [];
  380. foreach($rolesAndJobs as $role => $jobs)
  381. {
  382. $config->setRole($role);
  383. foreach($jobs as $job => $value)
  384. {
  385. $config->setJob($job);
  386. $acls[] = $this->getAclItemForRoleAndJob($config, TRUE, FALSE);
  387. }
  388. }
  389. $formattedResult = [];
  390. // Pour chaque acl, injecte dans $formattedResult les roles et jobs voulus
  391. /** @var AclSetting $acl */
  392. foreach($acls as $acl)
  393. {
  394. $formattedResult[$acl->getRole()][$acl->getJob()] = $acl->getValue();
  395. }
  396. // Si on est ROLE_ADMIN, on a accès au tableau pour les ROLE_USER
  397. if($currentUser instanceof User && $currentUser->isAdmin())
  398. {
  399. unset($formattedResult['ROLE_SUPER_ADMIN'], $formattedResult['ROLE_DEVELOPER']);
  400. }
  401. // Si on est ROLE_SUPER_ADMIN, on a accès au tableau pour les ROLE_USER, et ROLE_ADMIN
  402. if($currentUser instanceof User && $currentUser->isSuperAdmin())
  403. {
  404. unset($formattedResult['ROLE_DEVELOPER']);
  405. }
  406. return $formattedResult;
  407. }
  408. /**
  409. * Retourne le tableau des roles et job en fonction de la configuration YAML
  410. *
  411. * Si un role n'a pas de job, le système des acls le considère avec le job ACL::ACL_NO_JOB
  412. *
  413. * @return array[]
  414. */
  415. public function getDefaultRoleAndJob(bool $debug = FALSE): array
  416. {
  417. if($this->defaultRoleAndJobCache !== NULL)
  418. {
  419. return $this->defaultRoleAndJobCache;
  420. }
  421. $tree = $this->communityService->getTreeJobs();
  422. $roles = [
  423. 'ROLE_USER' => array_merge(array_keys(array_filter($tree, static function($item)
  424. {
  425. return !isset($item['role']) || $item['role'] === 'ROLE_USER';
  426. })), [ACL::ACL_NO_JOB]),
  427. 'ROLE_ADMIN' => array_merge(array_keys(array_filter($tree, static function($item)
  428. {
  429. return isset($item['role']) && $item['role'] === 'ROLE_ADMIN';
  430. })), [ACL::ACL_NO_JOB]),
  431. 'ROLE_SUPER_ADMIN' => array_merge(array_keys(array_filter($tree, static function($item)
  432. {
  433. return isset($item['role']) && $item['role'] === 'ROLE_SUPER_ADMIN';
  434. })), [ACL::ACL_NO_JOB]),
  435. ];
  436. if(isset($roles['ROLE_DEVELOPER']))
  437. {
  438. unset($roles['ROLE_DEVELOPER']);
  439. }
  440. return $this->defaultRoleAndJobCache = array_map(static function($role)
  441. {
  442. // Si $role est vide, ajoute une clé 'ACL::ACL_NO_JOB'
  443. if(count($role) === 0)
  444. {
  445. return [ACL::ACL_NO_JOB => TRUE];
  446. }
  447. return array_map(static function()
  448. {
  449. return TRUE;
  450. },
  451. array_flip($role));
  452. }, $roles);
  453. }
  454. /**
  455. * Retourne une règle d'ACL complète
  456. *
  457. * Retourne une règle existante ou en créé une nouvelle par rapport au contexte
  458. * Force la valeur à FALSE pour la création d'une règle qui concerne le back + un ROLE_USER
  459. *
  460. * @param AclSettingConfig $config config complète contexte + ROLE + JOB
  461. * @param bool $withFlush ajoute un flush dans la fonction pour enregistrer AclSetting
  462. * @param bool $debug
  463. *
  464. * @return AclSetting|float|int|mixed|string|null
  465. */
  466. public function getAclItemForRoleAndJob(AclSettingConfig $config, bool $withFlush = TRUE, bool $debug = FALSE): mixed
  467. {
  468. $cacheKey = $config->getConcatKey();
  469. if(array_key_exists($cacheKey, $this->aclItemCache))
  470. {
  471. return $this->aclItemCache[$cacheKey];
  472. }
  473. $acl = $this->em->getRepository(AclSetting::class)->getAclSettingsFor($config, TRUE, TRUE);
  474. // si les clefs n'existent pas, on les set pour chaque role/job
  475. if($acl === NULL)
  476. {
  477. // Si la règle concerne le back pour un ROLE_USER, on set à FALSE par défaut
  478. if($config->getEnv() === ACL::BACK_ENV && $config->getRole() === "ROLE_USER")
  479. {
  480. $defaultValue = FALSE;
  481. }
  482. else
  483. {
  484. $defaultValues = $this->getDefaultRoleAndJob();
  485. try
  486. {
  487. $defaultValue = (bool)$defaultValues[$config->getRole()][$config->getJob()];
  488. }
  489. catch(\Exception $e)
  490. {
  491. $this->logger->error($e->getMessage());
  492. $defaultValue = FALSE;
  493. }
  494. }
  495. $acl = $this->setData($config, $defaultValue, $withFlush, $debug);
  496. }
  497. return $this->aclItemCache[$cacheKey] = $acl;
  498. }
  499. /**
  500. * Vérifie si le user à le droit de voir le produit
  501. *
  502. * Vérifie les catalogues où est présent le produit et recherche les droits d'accès du user sur ces catalogues
  503. * Retourne TRUE au premier qui match
  504. *
  505. * @param User|null $user
  506. * @param Product $product
  507. *
  508. * @return bool
  509. *
  510. * @throws JsonException
  511. */
  512. public function userIsGrantedProduct(?User $user, Product $product): bool
  513. {
  514. foreach($product->getCatalogues() as $catalogue)
  515. {
  516. //if ($this->userIsGrantedCatalogue($user, $catalogue) && $this->jsonCatalogueService->isProductInCatalogue($product->getSku(), $catalogue)) {
  517. //TODO @Manu la vérification pour savoir si le produit est dans le catalogue est lourde, sans doute redondante si on a déjà récupéré le produit via le JSON pour obtenir la variable $product
  518. if($this->userIsGrantedCatalogue($user, $catalogue)) return TRUE;
  519. }
  520. return FALSE;
  521. }
  522. /**
  523. * Retourne si l'utilisateur peut voir ou non le catalogue via son slug, prend en compte les Univers si
  524. * l'option est active
  525. *
  526. * @param User|null $user
  527. * @param string $catalogueSlug
  528. * @param bool $debug
  529. *
  530. * @return bool
  531. *
  532. * @throws JsonException
  533. */
  534. public function userIsGrantedCatalogue(?User $user, string $catalogueSlug, bool $debug = FALSE): bool
  535. {
  536. $isGranted = $this->userIsGranted($user, [
  537. 'route' => ACL::ACL_ROUTE_SHOP_CONFIG,
  538. 'params' => ACL::ACL_NO_PARAMS,
  539. 'component' => ACL::ACL_NO_COMPONENT,
  540. 'slug' => ACL::ACL_KEY_SLUG_SHOP_CATALOG . '.' . $catalogueSlug,
  541. 'env' => ACL::FRONT_ENV,
  542. ]);
  543. // en cas d'univers, il faut vérifier si on a le droit de voir quand on est ni dev, ni super admin
  544. $universActive = $this->moduleSettingService->isModuleActive('univers');
  545. if($universActive && $user !== NULL && !$user->isSuperAdmin() && !$user->isDeveloper())
  546. {
  547. $catalogueHasUnivers = $this->em->getRepository(Univers::class)->findUniversForUserAndCatalogSlug($user, $catalogueSlug);
  548. $isGranted = $catalogueHasUnivers !== [];
  549. }
  550. return $isGranted;
  551. }
  552. /**
  553. * Indique si un utilisateur a les droits d'accès de la page ou du composant avec son action
  554. *
  555. * Retourne TRUE si on est sur un component qui s'affiche coté security (non logué)
  556. * Retourne TRUE si on est ROLE_DEVELOPER
  557. * Retourne FALSE si la règle n'est pas trouvée ou qu'aucune règle n'est TRUE
  558. * Retourne TRUE à la première règle dont la valeur est TRUE (si le user à plusieurs roles par exemple)
  559. *
  560. * @param User|null $user
  561. * @param array $config
  562. * @param bool $debug
  563. *
  564. * @return bool
  565. * @throws JsonException
  566. */
  567. public function userIsGranted(?User $user, array $config, bool $debug = FALSE): bool
  568. {
  569. $default = [
  570. 'route' => NULL,
  571. 'params' => ACL::ACL_NO_PARAMS,
  572. 'component' => ACL::ACL_NO_COMPONENT,
  573. 'slug' => ACL::ACL_NO_SLUG,
  574. 'action' => ACL::READ,
  575. 'env' => ACL::FRONT_ENV,
  576. ];
  577. $config = array_merge($default, $config);
  578. //Si c'est un component qui vient de la clef security alors on autorise
  579. // @todo passer par l'array des routes concernée par la security ?
  580. // security_path dans twig.yaml
  581. if(str_contains($config['component'], 'security.') || in_array($config['route'], ACL::ACL_SECURITY_ROUTES, TRUE))
  582. {
  583. return TRUE;
  584. }
  585. // Utilisateur non connecté
  586. if(!$user instanceof User) return FALSE;
  587. // Aucune restriction pour le ROLE_DEVELOPER
  588. if($user->isDeveloper()) return TRUE;
  589. // cette étape normalise la variable params que ça soit une string ou un array.
  590. $config['params'] = $this->getRouteParamsForAcl($config['route'], $config['params']);
  591. $cacheKey = implode('|', [
  592. $user->getId() ?? 'anon',
  593. $user->getJob() ?? ACL::ACL_NO_JOB,
  594. implode(',', $user->getRoles()),
  595. $config['env'],
  596. $config['route'] ?? '',
  597. $config['params'],
  598. $config['component'],
  599. $config['slug'],
  600. $config['action'],
  601. ]);
  602. if(array_key_exists($cacheKey, $this->userIsGrantedCache))
  603. {
  604. return $this->userIsGrantedCache[$cacheKey];
  605. }
  606. $isGranted = FALSE;
  607. $job = $user->getJob() ?? ACL::ACL_NO_JOB;
  608. $baseAclConfig = (new AclSettingConfig())->setFromArray($config)->setJob($job);
  609. $baseAclConfig = $this->transformAclVariables($baseAclConfig);
  610. foreach($user->getRoles() as $role)
  611. {
  612. $aclConfig = (clone $baseAclConfig)->setRole($role);
  613. $aclItem = $this->getAclItemForRoleAndJob($aclConfig, TRUE, $debug);
  614. $isGranted = $aclItem->getValue();
  615. if($isGranted) break;
  616. }
  617. return $this->userIsGrantedCache[$cacheKey] = $isGranted;
  618. }
  619. /**
  620. * Retourne le slug du premier catalogue où le user a les accès ACL
  621. *
  622. * @param User|null $user
  623. * @param Product $product
  624. *
  625. * @return mixed|null
  626. * @throws JsonException
  627. */
  628. public function getUserFirstGrantedCatalogSlugForProduct(?User $user, Product $product): mixed
  629. {
  630. $cacheKey = ($user?->getId() ?? 'anonymous') . '|' . $product->getSku();
  631. if (array_key_exists($cacheKey, $this->firstGrantedCatalogCache)) {
  632. return $this->firstGrantedCatalogCache[$cacheKey];
  633. }
  634. foreach($product->getCatalogues() as $catalogue)
  635. {
  636. //TODO @manu la vérification pour savoir si le produit est dans le catalogue est lourde, sans doute redondante si on a déjà récupéré le produit via le JSON pour obtenir la variable $product
  637. if($this->userIsGrantedCatalogue($user, $catalogue) /*&& $this->jsonCatalogueService->isProductInCatalogue($product->getSku(), $catalogue)*/)
  638. {
  639. return $this->firstGrantedCatalogCache[$cacheKey] = $catalogue;
  640. }
  641. }
  642. return $this->firstGrantedCatalogCache[$cacheKey] = NULL;
  643. }
  644. /**
  645. * Indique si un user a les droits d'accès au document
  646. *
  647. * Les ACL du document se font lors de la création de l'entité
  648. * Choix des roles => si pas de choix tout le monde voit
  649. * Choix des jobs => si pas de choix tout le monde voit
  650. * Choix des univers => si pas de choix tout le monde voit
  651. *
  652. * @param User|null $user
  653. * @param Parameter $document
  654. *
  655. * @return bool
  656. *
  657. * @throws JsonException
  658. * @throws Exception
  659. */
  660. public function userIsGrantedOnDocument(?User $user, Parameter $document): bool
  661. {
  662. if($user === NULL)
  663. {
  664. return TRUE;
  665. }
  666. $isGranted = TRUE;
  667. //Check sur job
  668. if($document->getDisplayJob() !== NULL && !in_array($user->getJob(), $document->getDisplayJob(), TRUE))
  669. {
  670. return FALSE;
  671. }
  672. //Check sur le role
  673. if($document->getDisplayRole() !== NULL && array_diff($user->getRoles(), $document->getDisplayRole()) !== [])
  674. {
  675. return FALSE;
  676. }
  677. $universes = $document->getDisplayUniverses();
  678. // Si config par univers on regarde si ça match
  679. if($universes !== NULL)
  680. {
  681. $isGranted = FALSE;
  682. foreach($user->getUniverses() as $userUnivers)
  683. {
  684. if(in_array($userUnivers->getSlug(), $universes, TRUE))
  685. {
  686. $isGranted = TRUE;
  687. break;
  688. }
  689. }
  690. if(!$isGranted) return FALSE;
  691. }
  692. // Check si la selection des documents est activé sur des parents dont il dépend et qui ont une extension
  693. if($user->getParents()->getValues() !== [])
  694. {
  695. $isGranted = $this->parameterService->isDocumentSelectedByUserParents($user, $document->getId());
  696. }
  697. // sur le user lui-même si une extension existe
  698. $currentUserDocumentSelected = $user->getExtensionBySlug(UserExtension::DOCUMENT_SELECTION);
  699. if($currentUserDocumentSelected !== NULL)
  700. {
  701. $isGranted = $this->parameterService->isDocumentSelectedForUser($user, $document->getId());
  702. }
  703. return $isGranted;
  704. }
  705. /**
  706. * Définit si un element d'un formType est visible en fonction de ses droits configurés dans le yaml
  707. *
  708. * @param AclSettingConfig|null $aclConfig
  709. * @param array $config
  710. * @param bool $debug
  711. *
  712. * @return bool
  713. * @throws JsonException
  714. */
  715. public function currentUserIsGrantedByConfigFormType(?AclSettingConfig $aclConfig, array $config = [], bool $debug = FALSE): bool
  716. {
  717. // Pas de config, tout le monde voit
  718. if($aclConfig === NULL && !array_key_exists('jobs', $config) && !array_key_exists('roles', $config) && !array_key_exists('univers', $config))
  719. {
  720. return TRUE;
  721. }
  722. $tokenStorage = $this->tokenStorage->getToken();
  723. $currentUser = $tokenStorage ? $tokenStorage->getUser() : NULL;
  724. if(!$currentUser instanceof User) return FALSE;
  725. if($currentUser->isDeveloper() || $currentUser->isSuperAdmin()) return TRUE;
  726. if(array_key_exists('jobs', $config) && $config['jobs'] !== null)
  727. {
  728. return $this->canDisplayByJobs($currentUser, $config['jobs']);
  729. }
  730. if(array_key_exists('roles', $config) && $config['roles'] !== null)
  731. {
  732. return $this->canDisplayByRoles($currentUser, $config['roles']);
  733. }
  734. $universesConfig = isset($config['univers']) && count($config['univers']) > 0;
  735. // cas 2 $aclConfig + config => on ne garde que les univers pour le moment, $aclConfig prend le dessus.
  736. $isGranted = !$aclConfig || $this->userIsGranted($currentUser, $aclConfig->toArray(), $debug);
  737. if($isGranted && $universesConfig)
  738. {
  739. return $this->canDisplayByUniverses($currentUser, $config['univers']);
  740. }
  741. return $isGranted;
  742. }
  743. /**
  744. * Vérifie si on peut afficher un élément en fonction du roles
  745. *
  746. * @param User $user
  747. * @param array $roles
  748. *
  749. * @return bool
  750. */
  751. public function canDisplayByRoles(User $user, array $roles): bool
  752. {
  753. $userRole = $user->getRoles();
  754. if(array_diff($userRole, $roles) === []) return TRUE;
  755. return FALSE;
  756. }
  757. /**
  758. * Vérifie si on peut afficher un élément en fonction du jobs
  759. *
  760. * @param User $user
  761. * @param array $jobs
  762. *
  763. * @return bool
  764. */
  765. public function canDisplayByJobs(User $user, array $jobs): bool
  766. {
  767. $userJob = $user->getJob();
  768. if(in_array($userJob, $jobs)) return TRUE;
  769. return FALSE;
  770. }
  771. /**
  772. * Vérifie si on peut afficher un élément en fonction des univers de l'utilisateur
  773. *
  774. * @param User $user
  775. * @param array $universes
  776. *
  777. * @return bool
  778. */
  779. public function canDisplayByUniverses(User $user, array $universes): bool
  780. {
  781. $userUniverses = $user->getUniverses();
  782. foreach($userUniverses as $univers)
  783. {
  784. if(in_array($univers->getSlug(), $universes, TRUE))
  785. {
  786. return TRUE;
  787. }
  788. }
  789. return FALSE;
  790. }
  791. }