src/Services/Common/AclServiceV2.php line 100

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