src/Services/Common/AclServiceV2.php line 157

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