src/Tuer24/Security/Voter/Tuer24OrderVoter.php line 25

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Tuer24\Security\Voter;
  4. use App\Entity\Company;
  5. use App\Tuer24\Config\Tuer24Constants;
  6. use Roothirsch\CoreBundle\Behaviors\Attributable\MappedSuperclass\AbstractAttributeValue;
  7. use Roothirsch\ShopBundle\Entity\Order;
  8. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  9. use Symfony\Component\Security\Core\Authorization\Voter\Voter;
  10. use Symfony\Component\Security\Core\User\UserInterface;
  11. /**
  12. * Voter for TÜR24 Order permissions
  13. *
  14. * Handles authorization for:
  15. * - VIEW: Order owner OR assigned distributor
  16. * - ACCEPT: Assigned distributor only (order must be in 'submitted' state)
  17. * - REJECT: Assigned distributor only (order must be in 'submitted' state)
  18. * - CONFIRM: Order owner who IS a distributor (order must be in 'placed' state)
  19. * - SUBMIT: Order owner who is NOT a distributor + distributor assigned (order must be in 'placed' state)
  20. */
  21. class Tuer24OrderVoter extends Voter
  22. {
  23. public const VIEW = 'TUER24_ORDER_VIEW';
  24. public const EDIT = 'TUER24_ORDER_EDIT';
  25. public const ACCEPT = 'TUER24_ORDER_ACCEPT';
  26. public const REJECT = 'TUER24_ORDER_REJECT';
  27. public const CONFIRM = 'TUER24_ORDER_CONFIRM';
  28. public const SUBMIT = 'TUER24_ORDER_SUBMIT';
  29. protected function supports(string $attribute, mixed $subject): bool
  30. {
  31. if (!in_array($attribute, [self::VIEW, self::ACCEPT, self::REJECT, self::CONFIRM, self::SUBMIT, self::EDIT])) {
  32. return false;
  33. }
  34. if (!$subject instanceof Order) {
  35. return false;
  36. }
  37. return $subject->getType() === Tuer24Constants::ORDER_TYPE;
  38. }
  39. protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
  40. {
  41. $user = $token->getUser();
  42. if (!$user instanceof UserInterface) {
  43. return false;
  44. }
  45. /** @var Order $order */
  46. $order = $subject;
  47. return match ($attribute) {
  48. self::VIEW => $this->canView($order, $user, $token),
  49. self::EDIT => $this->canEdit($order, $user, $token),
  50. self::ACCEPT => $this->canAccept($order, $user, $token),
  51. self::REJECT => $this->canReject($order, $user, $token),
  52. self::CONFIRM => $this->canConfirm($order, $user, $token),
  53. self::SUBMIT => $this->canSubmit($order, $user, $token),
  54. default => false,
  55. };
  56. }
  57. private function canEdit(Order $order, UserInterface $user, TokenInterface $token): bool
  58. {
  59. return $this->isAssignedDistributor($order, $user, $token);
  60. }
  61. /**
  62. * Check if user can view the order
  63. * Allowed: Order owner OR assigned distributor
  64. */
  65. private function canView(Order $order, UserInterface $user, TokenInterface $token): bool
  66. {
  67. // Order owner can always view
  68. if ($this->isOrderOwner($order, $user)) {
  69. return true;
  70. }
  71. // Distributor assigned to this order can view
  72. if ($this->isAssignedDistributor($order, $user, $token)) {
  73. return true;
  74. }
  75. return false;
  76. }
  77. /**
  78. * Check if user can accept the order
  79. * Allowed: Assigned distributor only, order must be in 'submitted' state
  80. */
  81. private function canAccept(Order $order, UserInterface $user, TokenInterface $token): bool
  82. {
  83. // Only submitted orders can be accepted
  84. if ($order->getState() !== 'submitted') {
  85. return false;
  86. }
  87. // Only assigned distributors can accept
  88. return $this->isAssignedDistributor($order, $user, $token);
  89. }
  90. /**
  91. * Check if user can reject the order
  92. * Allowed: Assigned distributor only, order must be in 'submitted' state
  93. */
  94. private function canReject(Order $order, UserInterface $user, TokenInterface $token): bool
  95. {
  96. // Only submitted orders can be rejected
  97. if ($order->getState() !== 'submitted') {
  98. return false;
  99. }
  100. // Only assigned distributors can reject
  101. return $this->isAssignedDistributor($order, $user, $token);
  102. }
  103. private function canConfirm(Order $order, UserInterface $user, TokenInterface $token): bool
  104. {
  105. // Only submitted orders can be rejected
  106. if ($order->getState() !== 'placed') {
  107. return false;
  108. }
  109. if(!$this->isOrderOwner($order, $user)) {
  110. return false;
  111. }
  112. return $this->hasRole($token, Tuer24Constants::ROLE_DISTRIBUTOR);
  113. }
  114. /**
  115. * Check if user can submit the order
  116. * Allowed: Order owner who is NOT a distributor, with a distributor assigned
  117. */
  118. private function canSubmit(Order $order, UserInterface $user, TokenInterface $token): bool
  119. {
  120. // Only placed orders can be submitted
  121. if ($order->getState() !== 'placed') {
  122. return false;
  123. }
  124. // Must have a distributor assigned
  125. if ($this->getDistributorId($order) === null) {
  126. return false;
  127. }
  128. // Must be order owner
  129. if (!$this->isOrderOwner($order, $user)) {
  130. return false;
  131. }
  132. // Only non-distributors submit (distributors use confirm instead)
  133. return !$this->hasRole($token, Tuer24Constants::ROLE_DISTRIBUTOR);
  134. }
  135. /**
  136. * Check if user is the order owner
  137. */
  138. private function isOrderOwner(Order $order, UserInterface $user): bool
  139. {
  140. $orderOwner = $order->getOwner();
  141. if ($orderOwner === null) {
  142. return false;
  143. }
  144. if (!method_exists($user, 'getId') || !method_exists($orderOwner, 'getId')) {
  145. return false;
  146. }
  147. return $user->getId() === $orderOwner->getId();
  148. }
  149. /**
  150. * Check if user is an assigned distributor for this order
  151. */
  152. private function isAssignedDistributor(Order $order, UserInterface $user, TokenInterface $token): bool
  153. {
  154. // Must have distributor role
  155. if (!$this->hasRole($token, Tuer24Constants::ROLE_DISTRIBUTOR)) {
  156. return false;
  157. }
  158. // Must have a company
  159. if (!method_exists($user, 'getCompany')) {
  160. return false;
  161. }
  162. $userCompany = $user->getCompany();
  163. if ($userCompany === null) {
  164. return false;
  165. }
  166. $distributorCompanyId = $this->getDistributorId($order);
  167. if($distributorCompanyId === NULL) {
  168. return false;
  169. }
  170. return (string) $userCompany->getId() === (string) $distributorCompanyId;
  171. }
  172. private function getDistributorId(Order $order): null|int
  173. {
  174. $distributorAttribute = $order->getAttribute('distributor');
  175. if ($distributorAttribute === null) {
  176. return null;
  177. }
  178. /** @var AbstractAttributeValue $attributeValue */
  179. $attributeValue = $order->getAttributeValue($distributorAttribute);
  180. if ($attributeValue === null) {
  181. return null;
  182. }
  183. if($attributeValue->getEntity()) {
  184. return $attributeValue->getEntity()->getId();
  185. }
  186. return $attributeValue->getEntityId();
  187. }
  188. /**
  189. * Check if token has a specific role
  190. */
  191. private function hasRole(TokenInterface $token, string $role): bool
  192. {
  193. foreach ($token->getRoleNames() as $roleName) {
  194. if ($roleName === $role) {
  195. return true;
  196. }
  197. }
  198. return false;
  199. }
  200. }