EntryPoints/Download ACL Global Read for Specific document or set of documents

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • czcpf
    Senior Member
    • Aug 2022
    • 160

    EntryPoints/Download ACL Global Read for Specific document or set of documents

    Hello,

    If we want a specific document attachment to be read accessible by any user with role X what would the best way to accomplish this? Right now, an attachment is only downloadable through EntryPoints/Download if the following is true:

    PHP Code:
    $this->acl->checkEntity($attachment) 
    
    For my application, I have a template that I want all users to be able to download say if they have a specific role. Right now, I have to add each account to the document which is cumbersome as my account/user base grows. Can someone offer another approach? I thought maybe one approach might be to extend or create a new EntryPoint/Download script and do my own acl check if the user has role...
  • yuri
    Member
    • Mar 2014
    • 8440

    #2
    Hi,

    Options:

    1. Define custom access checker for the Attachment entity type https://docs.espocrm.com/development...eckerclassname. Call the existing implementation in your custom to preserve the current logic: https://github.com/espocrm/espocrm/b...essChecker.php

    Code:
    <?php
    namespace Espo\Custom\Classes\Acl\Attachment;
    
    use Espo\Classes\Acl\Attachment\AccessChecker as AttachmentAccessChecker;
    use Espo\Core\Acl\Traits\DefaultAccessCheckerDependency;
    use Espo\Core\Acl\AccessEntityCREDChecker;
    use Espo\Core\Acl\DefaultAccessChecker;
    use Espo\ORM\Entity;
    use Espo\Entities\User;
    use Espo\Core\Acl\ScopeData;​
    
    class MyAccessChecker implements AccessEntityCREDChecker
    {
        use DefaultAccessCheckerDependency;
    
        private AttachmentAccessChecker $parentAccessChecker;
    
        public function __construct(
            AttachmentAccessChecker $parentAccessChecker,
            DefaultAccessChecker $defaultAccessChecker,
            // here add needed additional dependencies
        ) {
            $this->parentAccessChecker = $parentAccessChecker;
            $this->defaultAccessChecker = $defaultAccessChecker;
        }​
    
        public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
        {
            // here add your custom check that returns true if some conditions met
    
            return $this->parentAccessChecker->checkEntityRead($user, $entity, $data);
        }​
    }
    2. If it's the Document entity type, define access checker for the Document entity instead.

    3. Create new entry point, but this would mean that links to your attachments need to be changed.
    Last edited by yuri; 04-10-2023, 11:33 AM.
    If you find EspoCRM good, we would greatly appreciate if you could give the project a star on GitHub. We believe our work truly deserves more recognition. Thanks.

    Comment

    • czcpf
      Senior Member
      • Aug 2022
      • 160

      #3
      Thank you for the detailed explanation and options. It worked perfectly. I did have to add both lines below to custom/Espo/Custom/Resources/metadata/aclDefs/Attachment.json to have Espo load the custom access class checker for normal users and portal users.

      Code:
      {
      "accessCheckerClassName": "Espo\\Custom\\Classes\\Acl\\Attachment\\AccessChecker",
      "portalAccessCheckerClassName": "Espo\\Custom\\Classes\\AclPortal\\Attachment\\AccessChecker"
      }​
      Here is my portal AccessChecker.php for those interested. Here, I'm allowing read access to attachments for any portal user provided attachment is related to a 'Document' and the document type is 'PDF Template'.

      PHP Code:
      <?php
      
      
      namespace Espo\Custom\Classes\AclPortal\Attachment;
      
      use Espo\Classes\AclPortal\Attachment\AccessChecker as AttachmentAccessChecker;
      use Espo\Entities\Attachment;
      use Espo\Modules\Crm\Entities\Document;
      use Espo\ORM\Entity;
      use Espo\Entities\User;
      
      use Espo\Core\{
      ORM\EntityManager,
      Portal\AclManager,
      Acl\ScopeData,
      Acl\AccessEntityCREDChecker,
      Portal\Acl\DefaultAccessChecker,
      Portal\Acl\Traits\DefaultAccessCheckerDependency,
      };
      
      /**
      * @implements AccessEntityCREDChecker<Attachment>
      */
      class AccessChecker implements AccessEntityCREDChecker
      {
      use DefaultAccessCheckerDependency;
      
      private AttachmentAccessChecker $parentAccessChecker;
      private EntityManager $entityManager;
      
      public function __construct(
      AttachmentAccessChecker $parentAccessChecker,
      DefaultAccessChecker $defaultAccessChecker,
      EntityManager $entityManager,
      // here add needed additional dependencies
      )
      {
      $this->parentAccessChecker = $parentAccessChecker;
      $this->defaultAccessChecker = $defaultAccessChecker;
      $this->entityManager = $entityManager;
      }
      
      public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
      {
      // here add your custom check that returns true if some conditions met
      
      //$GLOBALS['log']->warning('AccessChecker.php Classes - checkEntityRead() #40 $data->getRead() = ',[$data->getRead()]);
      //$GLOBALS['log']->warning('AccessChecker.php Classes - checkEntityRead() #41 $entityType = ',[$entity->getEntityType()]);
      //$GLOBALS['log']->warning('AccessChecker.php Classes - checkEntityRead() #42 $entityValueMap = ',[$entity->getValueMap()]);
      //$GLOBALS['log']->warning('AccessChecker.php Classes - checkEntityRead() #43 $user->getPortalId() = ',[$user->getPortalId()]);
      //$GLOBALS['log']->warning('AccessChecker.php Classes - checkEntityRead() #44 $user->getType() = ',[$user->getType()]);
      //$GLOBALS['log']->warning('AccessChecker.php Classes - checkEntityRead() #45 $user->getRoles() = ',[$user->getRoles()]);
      //$GLOBALS['log']->warning('AccessChecker.php Classes - checkEntityRead() #46 $user->getValueMap() = ',[$user->getValueMap()]);
      //$GLOBALS['log']->warning('AccessChecker.php Classes - checkEntityRead() #47 $user->getLinkMultipleIdList(portals) = ',[$user->getLinkMultipleIdList('portals')]);
      
      
      /** @var Attachment $entity */
      $parent = $this->getParent($entity);
      
      if ($parent->getEntityType() === 'Document') {
      /** @var Document $parent */
      $result = $this->checkEntityReadDocumentParent($user, $parent);
      
      if ($result !== null) {return $result;}
      }
      
      return $this->parentAccessChecker->checkEntityRead($user, $entity, $data);
      }
      
      private function getParent(Attachment $entity) : ?Entity {
      
      $parent = null;
      
      $parentType = $entity->get('parentType');
      $parentId = $entity->get('parentId');
      
      $relatedType = $entity->get('relatedType');
      $relatedId = $entity->get('relatedId');
      
      if ($parentId && $parentType) {
      $parent = $this->entityManager->getEntityById($parentType, $parentId);
      }
      else if ($relatedId && $relatedType) {
      $parent = $this->entityManager->getEntityById($relatedType, $relatedId);
      }
      return $parent;
      }
      
      private function checkEntityReadDocumentParent(User $user, Document $entity): ?bool {
      
      if ($entity->get('createdById') === $user->getId()) {return true;}
      
      if( $entity->get('type') === 'PDF Template' &&
      in_array($user->getPortalId(), $user->getLinkMultipleIdList('portals'))
      ){
      return true;
      }
      
      return false;
      
      }
      
      }
      And here is my accessChecker.php for non-portal
      PHP Code:
      <?php
      
      
      namespace Espo\Custom\Classes\Acl\Attachment;
      
      use Espo\Classes\Acl\Attachment\AccessChecker as AttachmentAccessChecker;
      use Espo\Core\Acl\Traits\DefaultAccessCheckerDependenc y;
      use Espo\Core\Acl\AccessEntityCREDChecker;
      use Espo\Core\Acl\DefaultAccessChecker;
      use Espo\Entities\Attachment;
      use Espo\Modules\Crm\Entities\Document;
      use Espo\ORM\Entity;
      use Espo\Entities\User;
      use Espo\Core\Acl\ScopeData;
      use Espo\ORM\EntityManager;
      
      class AccessChecker implements AccessEntityCREDChecker
      {
      use DefaultAccessCheckerDependency;
      
      private AttachmentAccessChecker $parentAccessChecker;
      private EntityManager $entityManager;
      
      public function __construct(
      AttachmentAccessChecker $parentAccessChecker,
      DefaultAccessChecker $defaultAccessChecker,
      EntityManager $entityManager,
      // here add needed additional dependencies
      )
      {
      $this->parentAccessChecker = $parentAccessChecker;
      $this->defaultAccessChecker = $defaultAccessChecker;
      $this->entityManager = $entityManager;
      }
      
      public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
      {
      // here add your custom check that returns true if some conditions met
      
      //$GLOBALS['log']->warning('AccessChecker.php Classes - checkEntityRead() $data->getRead() = ',[$data->getRead()]);
      //$GLOBALS['log']->warning('AccessChecker.php Classes - checkEntityRead() $entityType = ',[$entity->getEntityType()]);
      //$GLOBALS['log']->warning('AccessChecker.php Classes - checkEntityRead() $entityValueMap = ',[$entity->getValueMap()]);
      //$GLOBALS['log']->warning('AccessChecker.php Classes - checkEntityRead() $user->getPortalId() = ',[$user->getPortalId()]);
      //$GLOBALS['log']->warning('AccessChecker.php Classes - checkEntityRead() Upload is not working $user->getType() = ',[$user->getType()]);
      //$GLOBALS['log']->warning('AccessChecker.php Classes - checkEntityRead() $user->getRoles() = ',[$user->getRoles()]);
      //$GLOBALS['log']->warning('AccessChecker.php Classes - checkEntityRead() #46 $user->getValueMap() = ',[$user->getValueMap()]);
      //$GLOBALS['log']->warning('AccessChecker.php Classes - checkEntityRead() Ajax failed $user->getLinkMultipleIdList(portals) = ',[$user->getLinkMultipleIdList('portals')]);
      
      
      /** @var Attachment $entity */
      $parent = $this->getParent($entity);
      
      if ($parent->getEntityType() === 'Document') {
      /** @var Document $parent */
      $result = $this->checkEntityReadDocumentParent($user, $parent);
      
      if ($result !== null) {return $result;}
      }
      
      return $this->parentAccessChecker->checkEntityRead($user, $entity, $data);
      }
      
      private function getParent(Attachment $entity) : ?Entity {
      
      $parent = null;
      
      $parentType = $entity->get('parentType');
      $parentId = $entity->get('parentId');
      
      $relatedType = $entity->get('relatedType');
      $relatedId = $entity->get('relatedId');
      
      if ($parentId && $parentType) {
      $parent = $this->entityManager->getEntityById($parentType, $parentId);
      }
      else if ($relatedId && $relatedType) {
      $parent = $this->entityManager->getEntityById($relatedType, $relatedId);
      }
      return $parent;
      }
      
      private function checkEntityReadDocumentParent(User $user, Document $entity): ?bool {
      
      if ($entity->get('createdById') === $user->getId()) {return true;}
      
      if( $entity->get('type') === 'PDF Template' ){ return true; }
      
      return false;
      
      }
      }


      yuri I also had one other question for you regarding this when you have time. In the future, I may want to modify this TemplateAccessChecker to provide more granular access based on user role and/or portal user role. In the above, I don't seem to get anything from this line in my log file. I'm opening the attachment as a portal user and that portal user has two portal roles defined. How can I get these roles ?

      PHP Code:
      $GLOBALS['log']->warning('AccessChecker.php Classes - checkEntityRead() #43 $user->getRoles() = ',[$user->getRoles()]); 
      
      log output
      [2023-04-10 22:58:39] WARNING: AccessChecker.php Classes - checkEntityRead() $user->getRoles() = [{"Espo\\Core\\Field\\LinkMultiple":[]}] []
      Last edited by czcpf; 04-11-2023, 06:48 PM.

      Comment

      • item
        Active Community Member
        • Mar 2017
        • 1476

        #4
        Hi czcpf

        i play with aclDefs in my develop instance, but it's strange, have you this step too ? :

        step : admin, espo7.5.5, cache enable
        Account .. checkEntityRead .. i return false..
        listView : i see all account. (it's normal)
        detailView :
        - first step , i have a top access error as notification but i see some field content, sample : name, industry, .. but not see somes fields like address, email and assignedUser and team is a ID (not name), i see activity panel but empty

        - second step : when i only refresh browser​, i have a Access Forbiden 403 page. (this is correct view for me, not see anything other).

        Do you have this behaviour ? do you need refresh browser in your case ?

        Second question :
        do you know what do theses files in client src/acl ? or how is called (in clientDefs) for custom ?
        EspoCRM – Open Source CRM Application. Contribute to espocrm/espocrm development by creating an account on GitHub.


        If some one have response

        If you could give the project a star on GitHub. EspoCrm believe our work truly deserves more recognition. Thanks.​

        Comment

        Working...