diff --git a/application/Espo/Core/Acl.php b/application/Espo/Core/Acl.php index d2979825e4..83ae0904ea 100644 --- a/application/Espo/Core/Acl.php +++ b/application/Espo/Core/Acl.php @@ -81,12 +81,12 @@ class Acl public function check($subject, $action = null) { - return $this->getAclManager()->check($this->getUser(), $subject, $action) ; + return $this->getAclManager()->check($this->getUser(), $subject, $action); } public function checkScope($scope, $action = null) { - return $this->getAclManager()->checkScope($this->getUser(), $scope, $action) ; + return $this->getAclManager()->checkScope($this->getUser(), $scope, $action); } public function checkEntity(Entity $entity, $action) diff --git a/application/Espo/Core/Acl/Base.php b/application/Espo/Core/Acl/Base.php index 6b1801ac29..6f5db87b32 100644 --- a/application/Espo/Core/Acl/Base.php +++ b/application/Espo/Core/Acl/Base.php @@ -135,8 +135,8 @@ class Base implements Injectable } if (!is_null($action)) { - if (isset($data->action)) { - $value = $data->action; + if (isset($data->$action)) { + $value = $data->$action; if ($value === 'all' || $value === true) { return true; diff --git a/application/Espo/Modules/Crm/Services/CaseObj.php b/application/Espo/Modules/Crm/Services/CaseObj.php index 9c2745a6d8..87ee37c5d6 100644 --- a/application/Espo/Modules/Crm/Services/CaseObj.php +++ b/application/Espo/Modules/Crm/Services/CaseObj.php @@ -38,7 +38,7 @@ class CaseObj extends \Espo\Services\Record 'meetings', 'calls', 'emails' - ] + ]; protected $readOnlyAttributeList = [ 'inboundEmailId' diff --git a/application/Espo/Modules/Crm/Services/TargetList.php b/application/Espo/Modules/Crm/Services/TargetList.php index af875b368f..0743ce1429 100644 --- a/application/Espo/Modules/Crm/Services/TargetList.php +++ b/application/Espo/Modules/Crm/Services/TargetList.php @@ -35,6 +35,8 @@ use \Espo\Core\Exceptions\NotFound; class TargetList extends \Espo\Services\Record { + protected $noEditAccessRequiredLinkList = ['accounts', 'contacts', 'leads', 'users']; + public function loadAdditionalFields(Entity $entity) { parent::loadAdditionalFields($entity); @@ -60,13 +62,18 @@ class TargetList extends \Espo\Services\Record public function unlinkAll($id, $link) { $entity = $this->getRepository()->get($id); - - $foreignEntityType = $entity->relations[$link]['entity']; - + if (!$entity) { + throw new NotFound(); + } if (!$this->getAcl()->check($entity, 'edit')) { throw new Forbidden(); } + $foreignEntityType = $entity->getRelationParam($link, 'entity'); + if (!$foreignEntityType) { + throw new Error(); + } + if (empty($foreignEntityType)) { throw new Error(); } diff --git a/application/Espo/ORM/Entity.php b/application/Espo/ORM/Entity.php index c5e11b4d79..5e706de2da 100644 --- a/application/Espo/ORM/Entity.php +++ b/application/Espo/ORM/Entity.php @@ -313,10 +313,10 @@ abstract class Entity implements IEntity return $this->relations; } - public function getFieldType($field) + public function getAttributeType($attribute) { - if (isset($this->fields[$field]) && isset($this->fields[$field]['type'])) { - return $this->fields[$field]['type']; + if (isset($this->fields[$attribute]) && isset($this->fields[$attribute]['type'])) { + return $this->fields[$attribute]['type']; } return null; } @@ -329,6 +329,22 @@ abstract class Entity implements IEntity return null; } + public function getAttributeParam($attribute, $name) + { + if (isset($this->fields[$attribute]) && isset($this->fields[$attribute][$name])) { + return $this->fields[$attribute][$name]; + } + return null; + } + + public function getRelationParam($relation, $name) + { + if (isset($this->relations[$relation]) && isset($this->relations[$relation][$name])) { + return $this->relations[$relation][$name]; + } + return null; + } + public function isFetched() { return $this->isFetched; diff --git a/application/Espo/Services/Record.php b/application/Espo/Services/Record.php index fa6bc2e325..b6ca539bb8 100644 --- a/application/Espo/Services/Record.php +++ b/application/Espo/Services/Record.php @@ -68,10 +68,14 @@ class Record extends \Espo\Core\Services\Base protected $readOnlyAttributeList = []; + protected $readOnlyLinkList = []; + protected $linkSelectParams = []; protected $mergeLinkList = []; + protected $noEditAccessRequiredLinkList = []; + protected $exportSkipAttributeList = []; protected $exportAdditionalAttributeList = []; @@ -748,70 +752,111 @@ class Record extends \Espo\Core\Services\Base public function linkEntity($id, $link, $foreignId) { + if (empty($id) || empty($link) || empty($foreignId)) { + throw new BadRequest; + } + + if (in_array($link, $this->readOnlyLinkList)) { + throw new Forbidden(); + } + $entity = $this->getRepository()->get($id); - - $foreignEntityName = $entity->relations[$link]['entity']; - + if (!$entity) { + throw new NotFound(); + } if (!$this->getAcl()->check($entity, 'edit')) { throw new Forbidden(); } - if (empty($foreignEntityName)) { - throw new Error(); + $foreignEntityType = $entity->getRelationParam($link, 'entity'); + if (!$foreignEntityType) { + throw new Error("Entity '{$this->entityType}' has not relation '{$link}'."); } - $foreignEntity = $this->getEntityManager()->getEntity($foreignEntityName, $foreignId); + $foreignEntity = $this->getEntityManager()->getEntity($foreignEntityType, $foreignId); + if (!$foreignEntity) { + throw new NotFound(); + } - if (!$this->getAcl()->check($foreignEntity, 'edit')) { + $accessActionRequired = 'edit'; + if (in_array($link, $this->noEditAccessRequiredLinkList)) { + $accessActionRequired = 'read'; + } + if (!$this->getAcl()->check($foreignEntity, $accessActionRequired)) { throw new Forbidden(); } - if (!empty($foreignEntity)) { - $this->getRepository()->relate($entity, $link, $foreignEntity); - return true; - } + $this->getRepository()->relate($entity, $link, $foreignEntity); + return true; } public function unlinkEntity($id, $link, $foreignId) { + if (empty($id) || empty($link) || empty($foreignId)) { + throw new BadRequest; + } + + if (in_array($link, $this->readOnlyLinkList)) { + throw new Forbidden(); + } + $entity = $this->getRepository()->get($id); - - $foreignEntityName = $entity->relations[$link]['entity']; - + if (!$entity) { + throw new NotFound(); + } if (!$this->getAcl()->check($entity, 'edit')) { throw new Forbidden(); } - if (empty($foreignEntityName)) { - throw new Error(); + $foreignEntityType = $entity->getRelationParam($link, 'entity'); + if (!$foreignEntityType) { + throw new Error("Entity '{$this->entityType}' has not relation '{$link}'."); } - $foreignEntity = $this->getEntityManager()->getEntity($foreignEntityName, $foreignId); + $foreignEntity = $this->getEntityManager()->getEntity($foreignEntityType, $foreignId); + if (!$foreignEntity) { + throw new NotFound(); + } - if (!$this->getAcl()->check($foreignEntity, 'edit')) { + $accessActionRequired = 'edit'; + if (in_array($link, $this->noEditAccessRequiredLinkList)) { + $accessActionRequired = 'read'; + } + if (!$this->getAcl()->check($foreignEntity, $accessActionRequired)) { throw new Forbidden(); } - if (!empty($foreignEntity)) { - $this->getRepository()->unrelate($entity, $link, $foreignEntity); - return true; - } + $this->getRepository()->unrelate($entity, $link, $foreignEntity); + return true; } public function linkEntityMass($id, $link, $where) { + if (empty($id) || empty($link)) { + throw new BadRequest; + } + $entity = $this->getRepository()->get($id); - - $entityType = $entity->getEntityType(); - $foreignEntityType = $entity->relations[$link]['entity']; - + if (!$entity) { + throw new NotFound(); + } if (!$this->getAcl()->check($entity, 'edit')) { throw new Forbidden(); } + + $entityType = $entity->getEntityType(); + $foreignEntityType = $entity->getRelationParam($link, 'entity'); + if (empty($foreignEntityType)) { throw new Error(); } - if (!$this->getAcl()->check($foreignEntityType, 'edit')) { + + $accessActionRequired = 'edit'; + if (in_array($link, $this->noEditAccessRequiredLinkList)) { + $accessActionRequired = 'read'; + } + + if (!$this->getAcl()->check($foreignEntityType, $accessActionRequired)) { throw new Forbidden(); } @@ -822,7 +867,22 @@ class Record extends \Espo\Core\Services\Base $selectParams = $this->getRecordService($foreignEntityType)->getSelectParams($params); - return $this->getRepository()->massRelate($entity, $link, $selectParams); + if ($this->getAcl()->getLevel($foreignEntityType, $accessActionRequired) === 'all') { + return $this->getRepository()->massRelate($entity, $link, $selectParams); + } else { + $foreignEntityList = $this->getEntityManager()->getRepository($foreignEntityType)->find($selectParams); + $countRelated = 0; + foreach ($foreignEntityList as $foreignEntity) { + if (!$this->getAcl()->check($foreignEntity, $accessActionRequired)) { + continue; + } + $this->getRepository()->relate($entity, $link, $foreignEntity); + $countRelated++; + } + if ($countRelated) { + return true; + } + } } public function massUpdate($attributes = array(), array $params)