Announcement

Collapse
No announcement yet.

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

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • 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...

  • #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.

    Comment


    • #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 $userEntity $entityScopeData $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 $userDocument $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 $userEntity $entityScopeData $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 $userDocument $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


      • #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

        Comment

        Working...
        X