src/Security/AttachmentVoter.php line 63

Open in your IDE?
  1. <?php
  2. //------------------------------------------------------------------------------
  3. // src/Security/AttachmentVoter.php
  4. //------------------------------------------------------------------------------
  5. namespace App\Security;
  6. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  7. use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
  8. use Symfony\Component\Security\Core\Authorization\Voter\Voter;
  9. use Doctrine\Common\Collections\ArrayCollection;
  10. use Symfony\Component\Security\Core\Security;
  11. use Doctrine\Persistence\ManagerRegistry;
  12. use App\Entity\Access;
  13. use App\Entity\APIRest\AccessAPI;
  14. use App\Entity\Config\Config;
  15. use App\Entity\Config\Module;
  16. use App\Entity\Common\Attachment;
  17. use App\Entity\HR\AccessFunction;
  18. use App\Entity\Ikea\ServiceOrder;
  19. use App\Entity\Security\Acl;
  20. use App\Entity\Security\AclPermission;
  21. use App\Services\LogTools;
  22. use App\Services\Config\ModuleTools;
  23. class AttachmentVoter extends Voter
  24. {
  25.     //--------------------------------------------------------------------------------
  26.     // is_granted constants
  27.     const VIEW "view_attachment";
  28.     const DELETE "delete_attachment";
  29.     // Plan.io Task #4084
  30.     const EDIT_CONFIDENTIALITY "edit_confidentiality";
  31.     const VIEW_CONFIDENTIAL_ATTACHMENTS_FOR_MISSIONS "view_confidential_attachments_for_missions";
  32.     const VIEW_CONFIDENTIAL_ATTACHMENTS_FOR_INDIVIDUALS "view_confidential_attachments_for_individuals";
  33.     const VIEW_CONFIDENTIAL_ATTACHMENTS_FOR_IKEA_OS "view_confidential_attachments_for_ikea_service_orders";
  34.     const IS_GRANTED_CONSTANTS = array(
  35.         self::VIEW,
  36.         self::DELETE,
  37.         self::EDIT_CONFIDENTIALITY,
  38.         self::VIEW_CONFIDENTIAL_ATTACHMENTS_FOR_MISSIONS,
  39.         self::VIEW_CONFIDENTIAL_ATTACHMENTS_FOR_INDIVIDUALS,
  40.         self::VIEW_CONFIDENTIAL_ATTACHMENTS_FOR_IKEA_OS,
  41.     );
  42.     //--------------------------------------------------------------------------------
  43.     // acl constants
  44.     const ACL_PERM_VIEW_MISSION_ATTACHMENT "mission_view_confidential_attachment";
  45.     const ACL_PERM_VIEW_INDIVIDUAL_ATTACHMENT "ind_view_confidential_attachment";
  46.     const ACL_PERM_VIEW_IKEA_OS_ATTACHMENT "ikea_service_order_view_confidential_attachment";
  47.     //--------------------------------------------------------------------------------
  48.     public function __construct(AccessDecisionManagerInterface $accessDecisionManagerManagerRegistry $doctrineModuleTools $moduleToolsLogTools $logTools)
  49.     {
  50.         $this->accessDecisionManager $accessDecisionManager;
  51.         $this->em $doctrine->getManager();
  52.         $this->moduleTools $moduleTools;
  53.         $this->logTools $logTools;
  54.         $this->aclRepository $this->em->getRepository(Acl::class);
  55.         $this->aclPermissionRepository $this->em->getRepository(AclPermission::class);
  56.     }
  57.     // Plan.io Task #4453 [See AccessVoter for details]
  58.     public function supportsAttribute(string $attribute): bool
  59.     {
  60.         return in_array($attributeself::IS_GRANTED_CONSTANTStrue);
  61.     }
  62.     protected function supports(string $attribute$subject): bool
  63.     {
  64.         // if the attribute isn't one we support, return false
  65.         if (!in_array($attributeself::IS_GRANTED_CONSTANTS))
  66.         {
  67.             return false;
  68.         }
  69.         // only vote on Attachment objects inside this voter
  70.         if ($subject !== null && !$subject instanceof Attachment)
  71.         {
  72.             return false;
  73.         }
  74.         return true;
  75.     }
  76.     protected function voteOnAttribute(string $attribute$subjectTokenInterface $token): bool
  77.     {
  78.         $user $token->getUser();
  79.         // Plan.io Task #3707
  80.         if ($user instanceof AccessAPI)
  81.         {
  82.             if ($user->getAccess() === null)
  83.             {
  84.                 return false;
  85.             }
  86.             $user $user->getAccess();
  87.         }
  88.         // Plan.io Task #3707
  89.         // At this point $user is an object of Access type
  90.         // even if the $token->getUser() is AccessAPI
  91.         if (!$user instanceof Access)
  92.         {
  93.             // the user must be logged in; if not, deny access
  94.             return false;
  95.         }
  96.         // The user must have a function; if not deny access
  97.         $function $user->getFunction();
  98.         if ($function === null)        return false;
  99.         // Plan.io Task #3710 : Get current group
  100.         $currentGroup $user->getSocietyGroup();
  101.         if ($currentGroup === null)
  102.             return false;
  103.         $this->currentGroup $currentGroup;
  104.         // Check current group affectation
  105.         // or mission sharing situation
  106.         if ($subject !== null)
  107.         {
  108.             if (!$this->checkSocietyGroupAffectation($subject$currentGroup))
  109.             {
  110.                 return false;
  111.             }
  112.             // Plan.io Task #3427
  113.             if (!$this->checkConfidentialityForAttachment($subject$user$function))
  114.             {
  115.                 return false;
  116.             }
  117.         }
  118.         // you know $subject is a Attachment object, thanks to supports
  119.         /** @var Attachment $attachment */
  120.         $attachment $subject;
  121.         switch ($attribute)
  122.         {
  123.             case self::VIEW:
  124.             {
  125.                 $answer $this->canView($token$attachment$user$function);
  126.                 return $answer;
  127.             }
  128.             case self::EDIT_CONFIDENTIALITY:
  129.                 return $this->canEditConfidentiality($token$attachment$user$function);
  130.             case self::DELETE:
  131.                 return $this->canDelete($token$attachment$user$function);
  132.             case self::VIEW_CONFIDENTIAL_ATTACHMENTS_FOR_MISSIONS:
  133.                 return $this->checkConfidentialityForMission($user$function);
  134.             case self::VIEW_CONFIDENTIAL_ATTACHMENTS_FOR_INDIVIDUALS:
  135.                 return $this->checkConfidentialityForIndividual($user$function);
  136.             case self::VIEW_CONFIDENTIAL_ATTACHMENTS_FOR_IKEA_OS:
  137.                 return $this->checkConfidentialityForIkeaServiceOrder($user$function);
  138.         }
  139.         throw new \LogicException('This code should not be reached!');
  140.     }
  141.     private function checkSocietyGroupAffectation($attachment$currentGroup)
  142.     {
  143.         // Several cases are possible
  144.         // 1. currentGroup = attachment.societyGroup
  145.         // 2. currentGroup = attachment.mission.societyGroupOwner
  146.         // 3. currentGroup = attachment.mission.societyGroupAuthor
  147.         // 4. currentGroup = attachment.mission.shareDemand.receiver
  148.         // Plan.io Task #4071
  149.         // Conditions 2, 3 and 4 should also be adapted
  150.         // to attachments shared from parent missions to children
  151.         // If attachment belongs to mission.parent
  152.         // then attachment.mission is not the correct mission being shared
  153.         // We need the child mission ...
  154.         // 2.bis. currentGroup = attachment.childMission.societyGroupOwner
  155.         // 3.bis. currentGroup = attachment.childMission.societyGroupAuthor
  156.         // 4.bis. currentGroup = attachment.childMission.shareDemand.receiver
  157.         $childMission null;
  158.         $mission $attachment->getMission();
  159.         if ($mission !== null && $mission->hasChildren())
  160.         {
  161.             foreach ($mission->getChildren() as $child)
  162.             {
  163.                 if ($child->hasParentAttachment($attachment))
  164.                 {
  165.                     $childMission $child;
  166.                     break;
  167.                 }
  168.             }
  169.         }
  170.         // Plan.io Task #4071
  171.         // And also adapted to attachments from children which are visible on parents
  172.         // We need the parent mission ...
  173.         // 2.bis.bis. currentGroup = attachment.parentMission.societyGroupOwner
  174.         // 3.bis.bis. currentGroup = attachment.parentMission.societyGroupAuthor
  175.         // 4.bis.bis. currentGroup = attachment.parentMission.shareDemand.receiver
  176.         $parentMission null;
  177.         $mission $attachment->getMission();
  178.         if ($mission !== null)
  179.         {
  180.             $parentMission $mission->getParent();
  181.         }
  182.         $attachmentSocietyGroup $attachment->getSocietyGroup();
  183.         if ($attachmentSocietyGroup === null)
  184.         {
  185.             return false;
  186.         }
  187.         // 1. currentGroup = attachment.societyGroup
  188.         if ($currentGroup->equals($attachmentSocietyGroup))
  189.         {
  190.             return true;
  191.         }
  192.         // 2. and 3. Check mission
  193.         $mission $attachment->getMission();
  194.         if ($mission === null)
  195.         {
  196.             return false;
  197.         }
  198.         // 2. currentGroup = attachment.mission.societyGroupOwner
  199.         if ($currentGroup->equals($mission->getSocietyGroupOwner()))
  200.         {
  201.             return true;
  202.         }
  203.         // 3. currentGroup = attachment.mission.societyGroupAuthor
  204.         if ($currentGroup->equals($mission->getSocietyGroupAuthor()))
  205.         {
  206.             return true;
  207.         }
  208.         if ($childMission !== null)
  209.         {
  210.             // 2.bis. currentGroup = attachment.childMission.societyGroupOwner
  211.             if ($currentGroup->equals($childMission->getSocietyGroupOwner()))
  212.             {
  213.                 return true;
  214.             }
  215.             // 3.bis. currentGroup = attachment.childMission.societyGroupAuthor
  216.             if ($currentGroup->equals($childMission->getSocietyGroupAuthor()))
  217.             {
  218.                 return true;
  219.             }
  220.         }
  221.         if ($parentMission !== null)
  222.         {
  223.             // 2.bis.bis. currentGroup = attachment.parentMission.societyGroupOwner
  224.             if ($currentGroup->equals($parentMission->getSocietyGroupOwner()))
  225.             {
  226.                 return true;
  227.             }
  228.             // 3.bis.bis. currentGroup = attachment.parentMission.societyGroupAuthor
  229.             if ($currentGroup->equals($parentMission->getSocietyGroupAuthor()))
  230.             {
  231.                 return true;
  232.             }
  233.         }
  234.         // The following conditions must be considered in OR case
  235.         // 4 || 4.bis || 4.bis.bis
  236.         $stayOnFour true;
  237.         $stayOnFourBis true;
  238.         $stayOnFourBisBis true;
  239.         // 4. currentGroup = attachment.mission.shareDemand.receiver
  240.         $demands $mission->getShareDemandsForReceiverSocietyGroup($currentGroup);
  241.         if (count($demands) < 1)
  242.         {
  243.             $stayOnFour false;
  244.         }
  245.         $childDemands = array();
  246.         if ($childMission !== null)
  247.         {
  248.             // 4.bis. currentGroup = attachment.childMission.shareDemand.receiver
  249.             $childDemands $childMission->getShareDemandsForReceiverSocietyGroup($currentGroup);
  250.             if (count($childDemands) < 1)
  251.             {
  252.                 $stayOnFourBis false;
  253.             }
  254.         }
  255.         $parentDemands = array();
  256.         if ($parentMission !== null)
  257.         {
  258.             // 4.bis.bis. currentGroup = attachment.parentMission.shareDemand.receiver
  259.             $parentDemands $parentMission->getShareDemandsForReceiverSocietyGroup($currentGroup);
  260.             if (count($parentDemands) < 1)
  261.             {
  262.                 $stayOnFourBisBis false;
  263.             }
  264.         }
  265.         // The following conditions must be considered in OR case
  266.         // 4 || 4.bis || 4.bis.bis
  267.         if (!($stayOnFour || $stayOnFourBis || $stayOnFourBisBis))
  268.         {
  269.             return false;
  270.         }
  271.         // Plan.io Task #4071 : Mix $demands and $childDemands and $parentDemands
  272.         if ($demands instanceof ArrayCollection)        $demands $demands->toArray();
  273.         if ($childDemands instanceof ArrayCollection)    $childDemands $childDemands->toArray();
  274.         if ($parentDemands instanceof ArrayCollection)    $parentDemands $parentDemands->toArray();
  275.         $demands array_merge($demands$childDemands);
  276.         $demands array_merge($demands$parentDemands);
  277.         // Only allow accessing files for share demands that are in the default status or accepted
  278.         // Expiration should be checked separetly, and before anything else
  279.         foreach ($demands as $demand)
  280.         {
  281.             if ($demand->isExpired())
  282.             {
  283.                 continue;
  284.             }
  285.             if ($demand->isAnnulled())
  286.             {
  287.                 continue;
  288.             }
  289.             if ($demand->isRefused())
  290.             {
  291.                 continue;
  292.             }
  293.             // If we are here it means that we have a default / accepted demand for this file
  294.             if ($demand->isDefault())
  295.             {
  296.                 return true;
  297.             }
  298.             if ($demand->isAccepted())
  299.             {
  300.                 return true;
  301.             }
  302.         }
  303.         // If we are here all hope is lost
  304.         return false;
  305.     }
  306.     // Plan.io Task #3427
  307.     // This checks if the user can access the confidential attachments for missions
  308.     private function checkConfidentialityForMission(Access $userAccessFunction $function)
  309.     {
  310.         // Get Acl_Permission
  311.         $aclPerm $this->aclPermissionRepository->findOneByName(self::ACL_PERM_VIEW_MISSION_ATTACHMENT);
  312.         if ($aclPerm === null)        return false;
  313.         // Get Acl
  314.         $acl $this->aclRepository->findOneBy(array(
  315.             'function'        =>    $function,
  316.             'permission'    =>    $aclPerm
  317.         ));
  318.         if ($acl === null)        return false;
  319.         // Since only one acl type can exist
  320.         // we can return the result of the acl_permission
  321.         return $acl->getValue();
  322.     }
  323.     // Plan.io Task #3427
  324.     // This checks if the user can access the confidential attachments for clients
  325.     private function checkConfidentialityForIndividual(Access $userAccessFunction $function)
  326.     {
  327.         // Get Acl_Permission
  328.         $aclPerm $this->aclPermissionRepository->findOneByName(self::ACL_PERM_VIEW_INDIVIDUAL_ATTACHMENT);
  329.         if ($aclPerm === null)        return false;
  330.         // Get Acl
  331.         $acl $this->aclRepository->findOneBy(array(
  332.             'function'        =>    $function,
  333.             'permission'    =>    $aclPerm
  334.         ));
  335.         if ($acl === null)        return false;
  336.         // Since only one acl type can exist
  337.         // we can return the result of the acl_permission
  338.         return $acl->getValue();
  339.     }
  340.     // Plan.io Task #3939
  341.     // This checks if the user can access the confidential attachments for ikea service orders
  342.     private function checkConfidentialityForIkeaServiceOrder(Access $userAccessFunction $function)
  343.     {
  344.         // Get Acl_Permission
  345.         $aclPerm $this->aclPermissionRepository->findOneByName(self::ACL_PERM_VIEW_IKEA_OS_ATTACHMENT);
  346.         if ($aclPerm === null)        return false;
  347.         // Get Acl
  348.         $acl $this->aclRepository->findOneBy(array(
  349.             'function'        =>    $function,
  350.             'permission'    =>    $aclPerm
  351.         ));
  352.         if ($acl === null)        return false;
  353.         // Since only one acl type can exist
  354.         // we can return the result of the acl_permission
  355.         return $acl->getValue();
  356.     }
  357.     // Plan.io Task #3427, modified by #3939
  358.     // This checks if a given attachment is available to the user
  359.     private function checkConfidentialityForAttachment(Attachment $attachmentAccess $userAccessFunction $function)
  360.     {
  361.         if ($attachment->isNotConfidential())
  362.         {
  363.             // Confidentiality does not apply
  364.             return true;
  365.         }
  366.         $mission $attachment->getMission();
  367.         $client $attachment->getClient();
  368.         // Plan.io Task #3939
  369.         // Only fetch the IkeaServiceOrder if both the client and the mission are null
  370.         $ikeaServiceOrder null;
  371.         if ($client === null && $mission === null)
  372.         {
  373.             $ikeaOrderNumber $attachment->getIkeaOrderNumber();
  374.             if (!empty($ikeaOrderNumber))
  375.             {
  376.                 $societyGroup $attachment->getSocietyGroup();
  377.                 if ($societyGroup !== null)
  378.                 {
  379.                     $ikeaServiceOrder $this->em->getRepository(ServiceOrder::class)
  380.                         ->findOneBy(array(
  381.                             'societyGroup'        =>    $societyGroup,
  382.                             'orderNumber'        =>    $ikeaOrderNumber,
  383.                         ));
  384.                 }
  385.             }
  386.         }
  387.         // Plan.io Task #3939
  388.         // Third possible case : IkeaServiceOrder :: client
  389.         // in this case the attachment does not have neither a client, nor a mission attached
  390.         // however it is fetchd when viewing a task for example
  391.         // Quick fix : fetch the client from the IkeaServiceOrder
  392.         // Longterm solution : add VIEW_CONFIDENTIAL_ATTACHMENTS_FOR_IKEA_SERVICE_ORDERS (TODO)
  393.         // if ($client === null)
  394.         // {
  395.         //     $ikeaOrderNumber = $attachment->getIkeaOrderNumber();
  396.         //     if (!empty($ikeaOrderNumber))
  397.         //     {
  398.         //         $societyGroup = $attachment->getSocietyGroup();
  399.         //         if ($societyGroup !== null)
  400.         //         {
  401.         //             $ikeaServiceOrder = $this->em->getRepository(ServiceOrder::class)
  402.         //                 ->findOneBy(array(
  403.         //                     'societyGroup'        =>    $societyGroup,
  404.         //                     'orderNumber'        =>    $ikeaOrderNumber,
  405.         //                 ));
  406.         //             if ($ikeaServiceOrder !== null)
  407.         //             {
  408.         //                 $client = $ikeaServiceOrder->getClient();
  409.         //             }
  410.         //         }
  411.         //     }
  412.         // }
  413.         if ($mission === null && $client === null && $ikeaServiceOrder === null)
  414.         {
  415.             // Confidentiality does not apply
  416.             return true;
  417.         }
  418.         // This is the special case when both the client and the mission are null
  419.         // This means that we are viewing files that are only affected to the Ikea::ServiceOrder
  420.         if ($ikeaServiceOrder !== null)
  421.         {
  422.             // Get Acl_Permission
  423.             $aclPerm $this->aclPermissionRepository->findOneByName(self::ACL_PERM_VIEW_IKEA_OS_ATTACHMENT);
  424.             if ($aclPerm === null)        return false;
  425.             // Get Acl
  426.             $acl $this->aclRepository->findOneBy(array(
  427.                 'function'        =>    $function,
  428.                 'permission'    =>    $aclPerm
  429.             ));
  430.             if ($acl === null)        return false;
  431.             // Since only one acl type can exist
  432.             // we can return the result of the acl_permission
  433.             return $acl->getValue();
  434.         }
  435.         if ($mission !== null)
  436.         {
  437.             // Get Acl_Permission
  438.             $aclPerm $this->aclPermissionRepository->findOneByName(self::ACL_PERM_VIEW_MISSION_ATTACHMENT);
  439.             if ($aclPerm === null)        return false;
  440.             // Get Acl
  441.             $acl $this->aclRepository->findOneBy(array(
  442.                 'function'        =>    $function,
  443.                 'permission'    =>    $aclPerm
  444.             ));
  445.             if ($acl === null)        return false;
  446.             // Since only one acl type can exist
  447.             // we can return the result of the acl_permission
  448.             return $acl->getValue();
  449.         }
  450.         if ($client !== null)
  451.         {
  452.             $individual $client->getIndividual();
  453.             if ($individual === null)
  454.             {
  455.                 // Confidentiality does not apply
  456.                 return true;
  457.             }
  458.             // Get Acl_Permission
  459.             $aclPerm $this->aclPermissionRepository->findOneByName(self::ACL_PERM_VIEW_INDIVIDUAL_ATTACHMENT);
  460.             if ($aclPerm === null)        return false;
  461.             // Get Acl
  462.             $acl $this->aclRepository->findOneBy(array(
  463.                 'function'        =>    $function,
  464.                 'permission'    =>    $aclPerm
  465.             ));
  466.             if ($acl === null)        return false;
  467.             // Since only one acl type can exist
  468.             // we can return the result of the acl_permission
  469.             return $acl->getValue();
  470.         }
  471.         // Actually this code should never be reached ;)
  472.         // Plan.io Task #3939
  473.         // If this code should never be reached, just return false
  474.         return false;
  475.         // return true;
  476.     }
  477.     private function canView($tokenAttachment $attachmentAccess $userAccessFunction $function)
  478.     {
  479.         // If the Attachment belongs to a HumanResource,
  480.         // check permission for that first
  481.         if ($attachment->getHumanResource() !== null)
  482.         {
  483.             if (!$this->accessDecisionManager->decide($token, ['list_human_resource_attachments'], $attachment->getHumanResource()))
  484.             {
  485.                 return false;
  486.             }
  487.         }
  488.         return $this->checkConfidentialityForAttachment($attachment$user$function);
  489.         return true;
  490.     }
  491.     // Plan.io Task #4084
  492.     private function canEditConfidentiality($tokenAttachment $attachmentAccess $userAccessFunction $function)
  493.     {
  494.         // First things first : in order to edit it, one should be able to view it
  495.         if (!$this->canView($token$attachment$user$function))
  496.         {
  497.             return false;
  498.         }
  499.         // Plan.io Task #4071 : Deny changing confidentiality on parentAttachments
  500.         if ($attachment->isParentAttachment())
  501.         {
  502.             return false;
  503.         }
  504.         // Deny changing confidentiality if the Attachment belongs to a shared Mission
  505.         // and the Attachment was created by the societyGroupAuthor
  506.         // $currentGroup is the one trying to change the confidentiality
  507.         // $societyGroup is the author of the Attachment
  508.         // They should match
  509.         $societyGroup $attachment->getSocietyGroup();
  510.         $mission $attachment->getMission();
  511.         if ($mission !== null && $mission->isShared())
  512.         {
  513.             if (!$this->currentGroup->equals($societyGroup))
  514.             {
  515.                 return false;
  516.             }
  517.         }
  518.         // All looks good
  519.         return true;
  520.     }
  521.     private function canDelete(TokenInterface $tokenAttachment $attachmentAccess $userAccessFunction $function)
  522.     {
  523.         // Plan.io Task #3621
  524.         if ($attachment->cannotBeDeleted())
  525.         {
  526.             return false;
  527.         }
  528.         if ($attachment->getMission() !== null)
  529.         {
  530.             if ($this->accessDecisionManager->decide($token, ['edit_mission'], $attachment->getMission()))
  531.             {
  532.                 return true;
  533.             }
  534.             // Plan.io Task #4071 : Make an exception for children of Mission
  535.             // No longer needed, but one never knows, so keep the code just in case
  536.             // $parent = $attachment->getMission()->getParent();
  537.             // if ($parent !== null)
  538.             // {
  539.             //     if ($this->accessDecisionManager->decide($token, ['edit_mission'], $parent))
  540.             //     {
  541.             //         return true;
  542.             //     }
  543.             // }
  544.         }
  545.         if ($attachment->getDevis() !== null)
  546.         {
  547.             if ($this->accessDecisionManager->decide($token, ['edit_devis'], $attachment->getDevis()))
  548.             {
  549.                 return true;
  550.             }
  551.         }
  552.         if ($attachment->getClient() !== null)
  553.         {
  554.             if ($this->accessDecisionManager->decide($token, ['edit_client'], $attachment->getClient()))
  555.             {
  556.                 return true;
  557.             }
  558.         }
  559.         if ($attachment->getSupplier() !== null)
  560.         {
  561.             if ($this->accessDecisionManager->decide($token, ['edit_supplier'], $attachment->getSupplier()))
  562.             {
  563.                 return true;
  564.             }
  565.         }
  566.         if ($attachment->getHumanResource() !== null)
  567.         {
  568.             if ($this->accessDecisionManager->decide($token, ['edit_human_resource'], $attachment->getHumanResource()))
  569.             {
  570.                 return true;
  571.             }
  572.         }
  573.         if ($attachment->getCost() !== null)
  574.         {
  575.             if ($this->accessDecisionManager->decide($token, ['edit_cost'], $attachment->getCost()))
  576.             {
  577.                 return true;
  578.             }
  579.         }
  580.         if ($attachment->getCommand() !== null)
  581.         {
  582.             // Plan.io Task #4036
  583.             // Deny Attachments for PunchOut Commands
  584.             if ($attachment->getCommand()->isPunchOut())
  585.             {
  586.                 return false;
  587.             }
  588.             if ($this->accessDecisionManager->decide($token, ['edit_command'], $attachment->getCommand()))
  589.             {
  590.                 return true;
  591.             }
  592.         }
  593.         if ($attachment->getApplication() !== null)
  594.         {
  595.             if ($this->accessDecisionManager->decide($token, ['edit_application'], $attachment->getApplication()))
  596.             {
  597.                 return true;
  598.             }
  599.         }
  600.         // Equimpment related stuff
  601.         if ($attachment->getEquipment() !== null)
  602.         {
  603.             if ($this->accessDecisionManager->decide($token, ['edit_equipment'], $attachment->getEquipment()))
  604.             {
  605.                 return true;
  606.             }
  607.         }
  608.         if ($attachment->getVehicleMaintenance() !== null)
  609.         {
  610.             $vehicle $attachment->getVehicleMaintenance()->getVehicle();
  611.             if ($vehicle !== null)
  612.             {
  613.                 $equipment $vehicle->getEquipment();
  614.                 if ($equipment !== null)
  615.                 {
  616.                     if ($this->accessDecisionManager->decide($token, ['edit_equipment'], $equipment))
  617.                     {
  618.                         return true;
  619.                     }
  620.                 }
  621.             }
  622.         }
  623.         if ($attachment->getDamage() !== null)
  624.         {
  625.             $equipment $attachment->getDamage()->getEquipment();
  626.             if ($equipment !== null)
  627.             {
  628.                 if ($this->accessDecisionManager->decide($token, ['edit_equipment'], $equipment))
  629.                 {
  630.                     return true;
  631.                 }
  632.             }
  633.         }
  634.         // Plan.io Task #3621
  635.         if (!empty($attachment->getIkeaOrderNumber()))
  636.         {
  637.             // Get one ServiceOrder to test the permissions
  638.             $serviceOrder $this->em->getRepository(ServiceOrder::class)->findOneBy(array(
  639.                 'orderNumber'        =>    $attachment->getIkeaOrderNumber(),
  640.                 'societyGroup'        =>    $attachment->getSocietyGroup(),
  641.             ));
  642.             if ($serviceOrder !== null)
  643.             {
  644.                 if ($this->accessDecisionManager->decide($token, ['edit_ikea_service_order'], $serviceOrder))
  645.                 {
  646.                     return true;
  647.                 }
  648.             }
  649.         }
  650.         // Plan.io Task #3592 #4177
  651.         if (!empty($attachment->getWebappDocumentMultiple()))
  652.         {
  653.             // Only check author at this point
  654.             $author $attachment->getAuthor();
  655.             if ($author === null)
  656.             {
  657.                 return false;
  658.             }
  659.             if ($author->equals($user))
  660.             {
  661.                 return true;
  662.             }
  663.         }
  664.         return false;
  665.     }
  666. }