Merge branch 'fix'
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\Acl\Email\LinkCheckers;
|
||||
|
||||
use Espo\Core\Acl\LinkChecker;
|
||||
use Espo\Core\AclManager;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @implements LinkChecker<Email, Entity>
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class ParentLinkChecker implements LinkChecker
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private AclManager $aclManager
|
||||
) {}
|
||||
|
||||
public function check(User $user, Entity $entity, Entity $foreignEntity): bool
|
||||
{
|
||||
if ($this->aclManager->checkEntityRead($user, $foreignEntity)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$entity->getReplied()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$replied = $this->entityManager
|
||||
->getRepositoryByClass(Email::class)
|
||||
->getById($entity->getReplied()->getId());
|
||||
|
||||
if (!$replied) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$parentLink = $replied->getParent();
|
||||
|
||||
if (
|
||||
!$parentLink ||
|
||||
$parentLink->getId() !== $foreignEntity->getId() ||
|
||||
$parentLink->getEntityType() !== $foreignEntity->getEntityType()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->aclManager->checkEntityRead($user, $replied);
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
/**
|
||||
* Check access for record linking.
|
||||
* Check access for record linking. When linking directly through relationships or via link fields.
|
||||
*/
|
||||
class LinkCheck
|
||||
{
|
||||
@@ -223,16 +223,32 @@ class LinkCheck
|
||||
);
|
||||
}
|
||||
|
||||
if ($isOne) {
|
||||
$this->linkForeignAccessCheckOne($entityType, $link, $foreignEntity);
|
||||
$toSkip = $this->linkForeignAccessCheck($isOne, $entityType, $link, $foreignEntity);
|
||||
|
||||
if ($toSkip) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->linkForeignAccessCheck($entityType, $link, $foreignEntity, true);
|
||||
$this->linkEntityAccessCheck($entity, $foreignEntity, $link);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function linkForeignAccessCheck(
|
||||
bool $isOne,
|
||||
string $entityType,
|
||||
string $link,
|
||||
Entity $foreignEntity
|
||||
): bool {
|
||||
|
||||
if ($isOne) {
|
||||
return $this->linkForeignAccessCheckOne($entityType, $link, $foreignEntity);
|
||||
}
|
||||
|
||||
return $this->linkForeignAccessCheckMany($entityType, $link, $foreignEntity, true);
|
||||
}
|
||||
|
||||
private function getParam(string $entityType, string $link, string $param): mixed
|
||||
{
|
||||
return $this->metadata->get(['recordDefs', $entityType, 'relationships', $link, $param]);
|
||||
@@ -283,7 +299,12 @@ class LinkCheck
|
||||
*/
|
||||
public function processLinkForeign(Entity $entity, string $link, Entity $foreignEntity): void
|
||||
{
|
||||
$this->linkForeignAccessCheck($entity->getEntityType(), $link, $foreignEntity);
|
||||
$toSkip = $this->linkForeignAccessCheckMany($entity->getEntityType(), $link, $foreignEntity);
|
||||
|
||||
if ($toSkip) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->linkEntityAccessCheck($entity, $foreignEntity, $link);
|
||||
}
|
||||
|
||||
@@ -299,14 +320,17 @@ class LinkCheck
|
||||
}
|
||||
|
||||
/**
|
||||
* Check access to foreign record for has-many and many-many links.
|
||||
*
|
||||
* @return bool True indicates that the link checker should be bypassed.
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function linkForeignAccessCheck(
|
||||
private function linkForeignAccessCheckMany(
|
||||
string $entityType,
|
||||
string $link,
|
||||
Entity $foreignEntity,
|
||||
bool $fromUpdate = false
|
||||
): void {
|
||||
): bool {
|
||||
|
||||
$action = in_array($link, $this->noEditAccessRequiredLinkList) ?
|
||||
AclTable::ACTION_READ :
|
||||
@@ -318,7 +342,7 @@ class LinkCheck
|
||||
}
|
||||
|
||||
if ($this->getParam($entityType, $link, 'linkForeignAccessCheckDisabled')) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
$fieldDefs = $fromUpdate ?
|
||||
@@ -336,19 +360,23 @@ class LinkCheck
|
||||
$action = AclTable::ACTION_READ;
|
||||
|
||||
if ($this->checkInDefaults($fieldDefs, $link, $foreignEntity)) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->acl->check($foreignEntity, $action)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
$action === AclTable::ACTION_READ &&
|
||||
$this->checkIsAllowedForPortal($foreignEntity)
|
||||
) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->acl->check($foreignEntity, $action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->getLinkChecker($entityType, $link)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = ErrorBody::create();
|
||||
@@ -409,8 +437,10 @@ class LinkCheck
|
||||
throw ForbiddenSilent::createWithBody(
|
||||
"No access for link operation ($entityType:$link).",
|
||||
ErrorBody::create()
|
||||
->withMessageTranslation('noLinkAccess')
|
||||
->encode()
|
||||
->withMessageTranslation('noLinkAccess', null, [
|
||||
'foreignEntityType' => $foreignEntity->getEntityType(),
|
||||
'link' => $link,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -537,12 +567,15 @@ class LinkCheck
|
||||
}
|
||||
|
||||
/**
|
||||
* Check access to foreign record for belongs-to, has-one and belongs-to-parent links.
|
||||
*
|
||||
* @return bool True indicates that the link checker should be bypassed.
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function linkForeignAccessCheckOne(string $entityType, string $link, Entity $foreignEntity): void
|
||||
private function linkForeignAccessCheckOne(string $entityType, string $link, Entity $foreignEntity): bool
|
||||
{
|
||||
if ($this->getParam($entityType, $link, 'linkForeignAccessCheckDisabled')) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
$fieldDefs = $this->entityManager
|
||||
@@ -555,16 +588,20 @@ class LinkCheck
|
||||
in_array($fieldDefs->getType(), $this->oneFieldTypeList)
|
||||
) {
|
||||
if ($this->checkIsDefault($fieldDefs, $link, $foreignEntity)) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->acl->check($foreignEntity, AclTable::ACTION_READ)) {
|
||||
return;
|
||||
if ($this->checkIsAllowedForPortal($foreignEntity)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->checkIsAllowedForPortal($foreignEntity)) {
|
||||
return;
|
||||
if ($this->acl->check($foreignEntity, AclTable::ACTION_READ)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->getLinkChecker($entityType, $link)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw ForbiddenSilent::createWithBody(
|
||||
@@ -590,7 +627,6 @@ class LinkCheck
|
||||
{
|
||||
return $foreignEntity->getId() === $this->getDefault($fieldDefs, $link . 'Id');
|
||||
}
|
||||
|
||||
private function getDefault(FieldDefs $fieldDefs, string $attribute): mixed
|
||||
{
|
||||
$defaultAttributes = (object) ($fieldDefs->getParam('defaultAttributes') ?? []);
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Crm\Classes\Acl\Case\LinkCheckers;
|
||||
|
||||
use Espo\Core\Acl\LinkChecker;
|
||||
use Espo\Core\AclManager;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Crm\Entities\Account;
|
||||
use Espo\Modules\Crm\Entities\CaseObj;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @implements LinkChecker<CaseObj, Account>
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class AccountLinkChecker implements LinkChecker
|
||||
{
|
||||
public function __construct(
|
||||
private AclManager $aclManager,
|
||||
private EntityManager $entityManager
|
||||
) {}
|
||||
|
||||
public function check(User $user, Entity $entity, Entity $foreignEntity): bool
|
||||
{
|
||||
if ($this->aclManager->checkEntityRead($user, $foreignEntity)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$entity->isNew()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$emailIds = $entity->getLinkMultipleIdList('emails');
|
||||
|
||||
if (count($emailIds) === 0 || count($emailIds) > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$email = $this->entityManager
|
||||
->getRepositoryByClass(Email::class)
|
||||
->getById($emailIds[0]);
|
||||
|
||||
if (!$email) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$parent = $email->getParent();
|
||||
|
||||
if (!$parent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
$parent->getEntityType() !== Account::ENTITY_TYPE ||
|
||||
$parent->getId() !== $foreignEntity->getId()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->aclManager->checkEntityRead($user, $email);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Crm\Classes\Acl\Case\LinkCheckers;
|
||||
|
||||
use Espo\Core\Acl\LinkChecker;
|
||||
use Espo\Core\AclManager;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Crm\Entities\CaseObj;
|
||||
use Espo\Modules\Crm\Entities\Contact;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @implements LinkChecker<CaseObj, Contact>
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class ContactLinkChecker implements LinkChecker
|
||||
{
|
||||
public function __construct(
|
||||
private AclManager $aclManager,
|
||||
private EntityManager $entityManager
|
||||
) {}
|
||||
|
||||
public function check(User $user, Entity $entity, Entity $foreignEntity): bool
|
||||
{
|
||||
if ($this->aclManager->checkEntityRead($user, $foreignEntity)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$entity->isNew()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$emailIds = $entity->getLinkMultipleIdList('emails');
|
||||
|
||||
if (count($emailIds) === 0 || count($emailIds) > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$email = $this->entityManager
|
||||
->getRepositoryByClass(Email::class)
|
||||
->getById($emailIds[0]);
|
||||
|
||||
if (!$email) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$parent = $email->getParent();
|
||||
|
||||
if (!$parent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
$parent->getEntityType() !== Contact::ENTITY_TYPE ||
|
||||
$parent->getId() !== $foreignEntity->getId()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->aclManager->checkEntityRead($user, $email);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Crm\Classes\Acl\Case\LinkCheckers;
|
||||
|
||||
use Espo\Core\Acl\LinkChecker;
|
||||
use Espo\Core\AclManager;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Crm\Entities\CaseObj;
|
||||
use Espo\Modules\Crm\Entities\Lead;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @implements LinkChecker<CaseObj, Lead>
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class LeadLinkChecker implements LinkChecker
|
||||
{
|
||||
public function __construct(
|
||||
private AclManager $aclManager,
|
||||
private EntityManager $entityManager
|
||||
) {}
|
||||
|
||||
public function check(User $user, Entity $entity, Entity $foreignEntity): bool
|
||||
{
|
||||
if ($this->aclManager->checkEntityRead($user, $foreignEntity)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$entity->isNew()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$emailIds = $entity->getLinkMultipleIdList('emails');
|
||||
|
||||
if (count($emailIds) === 0 || count($emailIds) > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$email = $this->entityManager
|
||||
->getRepositoryByClass(Email::class)
|
||||
->getById($emailIds[0]);
|
||||
|
||||
if (!$email) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$parent = $email->getParent();
|
||||
|
||||
if (!$parent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
$parent->getEntityType() !== Lead::ENTITY_TYPE ||
|
||||
$parent->getId() !== $foreignEntity->getId()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->aclManager->checkEntityRead($user, $email);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"linkCheckerClassNameMap": {
|
||||
"lead": "Espo\\Modules\\Crm\\Classes\\Acl\\Case\\LinkCheckers\\LeadLinkChecker",
|
||||
"account": "Espo\\Modules\\Crm\\Classes\\Acl\\Case\\LinkCheckers\\AccountLinkChecker",
|
||||
"contact": "Espo\\Modules\\Crm\\Classes\\Acl\\Case\\LinkCheckers\\ContactLinkChecker",
|
||||
"contacts": "Espo\\Modules\\Crm\\Classes\\Acl\\Case\\LinkCheckers\\ContactLinkChecker"
|
||||
}
|
||||
}
|
||||
@@ -376,7 +376,7 @@
|
||||
"loggedOutLeaveOut": "Logged out. The session is inactive. You may lose unsaved form data after page refresh. You may need to make a copy.",
|
||||
"noAccessToRecord": "Operation requires `{action}` access to record.",
|
||||
"noAccessToForeignRecord": "Operation requires `{action}` access to foreign record.",
|
||||
"noLinkAccess": "No access to link operation for a specific record.",
|
||||
"noLinkAccess": "Can't relate with {foreignEntityType} record through the link '{link}'. No access.",
|
||||
"cannotUnrelateRequiredLink": "Can't unrelate required link.",
|
||||
"cannotRelateNonExisting": "Can't relate with non-existing {foreignEntityType} record.",
|
||||
"cannotRelateForbidden": "Can't relate with forbidden {foreignEntityType} record. `{action}` access required.",
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"portalAccessCheckerClassName": "Espo\\Classes\\AclPortal\\Email\\AccessChecker",
|
||||
"portalOwnershipCheckerClassName": "Espo\\Classes\\AclPortal\\Email\\OwnershipChecker",
|
||||
"assignmentCheckerClassName": "Espo\\Classes\\Acl\\Email\\AssignmentChecker",
|
||||
"readOwnerUserField": "users"
|
||||
"readOwnerUserField": "users",
|
||||
"linkCheckerClassNameMap": {
|
||||
"parent": "Espo\\Classes\\Acl\\Email\\LinkCheckers\\ParentLinkChecker"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<p class="credit small">© 2023
|
||||
<p class="credit small">© 2024
|
||||
<a
|
||||
href="https://www.espocrm.com"
|
||||
title="Powered by EspoCRM"
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@
|
||||
<body>
|
||||
<div class="container content"></div>
|
||||
<footer>
|
||||
<p class="credit small">© 2023
|
||||
<p class="credit small">© 2024
|
||||
<a href="https://www.espocrm.com" title="Powered by EspoCRM" rel="noopener" target="_blank">EspoCRM</a></p>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
<p class="credit small">© 2023 <a href="https://www.espocrm.com">EspoCRM</a></p>
|
||||
<p class="credit small">© 2024 <a href="https://www.espocrm.com">EspoCRM</a></p>
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"linkCheckerClassNameMap": {
|
||||
"description": "Link checker classes for specific links.",
|
||||
"description": "Link checker classes for specific links. Should implement `Espo\\Core\\Acl\\LinkChecker`.",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
|
||||
@@ -29,13 +29,17 @@
|
||||
|
||||
namespace tests\integration\Espo\Portal;
|
||||
|
||||
use Espo\Core\{
|
||||
Select\SearchParams,
|
||||
};
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Record\CreateParams;
|
||||
use Espo\Core\Record\Service;
|
||||
use Espo\Core\Record\ServiceContainer;
|
||||
use Espo\Core\Select\SearchParams;
|
||||
use Espo\Modules\Crm\Entities\CaseObj;
|
||||
use tests\integration\Core\BaseTestCase;
|
||||
|
||||
class AclTest extends \tests\integration\Core\BaseTestCase
|
||||
class AclTest extends BaseTestCase
|
||||
{
|
||||
public function testAccessContact()
|
||||
public function testAccessContact(): void
|
||||
{
|
||||
$app = $this->createApplication();
|
||||
|
||||
@@ -101,7 +105,7 @@ class AclTest extends \tests\integration\Core\BaseTestCase
|
||||
$this->assertFalse(in_array($case3->id, $idList));
|
||||
}
|
||||
|
||||
public function testAccessAccount()
|
||||
public function testAccessAccount(): void
|
||||
{
|
||||
$app = $this->createApplication();
|
||||
|
||||
@@ -173,7 +177,7 @@ class AclTest extends \tests\integration\Core\BaseTestCase
|
||||
$this->assertTrue(in_array($case4->id, $idList));
|
||||
}
|
||||
|
||||
public function testAccessOwn()
|
||||
public function testAccessOwn(): void
|
||||
{
|
||||
$app = $this->createApplication();
|
||||
|
||||
@@ -252,4 +256,70 @@ class AclTest extends \tests\integration\Core\BaseTestCase
|
||||
$this->assertFalse(in_array($case4->id, $idList));
|
||||
$this->assertTrue(in_array($case5->id, $idList));
|
||||
}
|
||||
|
||||
public function testCreateCase(): void
|
||||
{
|
||||
$em = $this->getEntityManager();
|
||||
|
||||
$contact = $em->createEntity('Contact');
|
||||
$account = $em->createEntity('Account');
|
||||
|
||||
$contactNotOwn = $em->createEntity('Contact');
|
||||
$accountNotOwn = $em->createEntity('Account');
|
||||
|
||||
$portal = $em->createEntity('Portal', [
|
||||
'name' => 'Portal',
|
||||
]);
|
||||
|
||||
$this->createUser([
|
||||
'userName' => 'tester',
|
||||
'portalsIds' => [$portal->getId()],
|
||||
'contactId' => $contact->getId(),
|
||||
'accountsIds' => [$account->getId()],
|
||||
], [
|
||||
'data' => [
|
||||
'Case' => [
|
||||
'create' => 'yes',
|
||||
'read' => 'own',
|
||||
'edit' => 'no',
|
||||
'delete' => 'no',
|
||||
'stream' => 'own',
|
||||
]
|
||||
],
|
||||
], true);
|
||||
|
||||
$this->auth('tester', null, $portal->getId());
|
||||
|
||||
$app = $this->createApplication(true, $portal->getId());
|
||||
|
||||
/** @var Service<CaseObj> $caseService */
|
||||
$caseService = $app->getContainer()
|
||||
->getByClass(ServiceContainer::class)
|
||||
->getByClass(CaseObj::class);
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$caseService->create((object) [
|
||||
'name' => 'Test 1',
|
||||
'accountId' => $account->getId(),
|
||||
'contactId' => $contact->getId(),
|
||||
'contactsIds' => [$contact->getId()],
|
||||
], CreateParams::create());
|
||||
|
||||
$isThrown = false;
|
||||
|
||||
try {
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$caseService->create((object)[
|
||||
'name' => 'Test 1',
|
||||
'accountId' => $accountNotOwn->getId(),
|
||||
'contactId' => $contactNotOwn->getId(),
|
||||
'contactsIds' => [$contactNotOwn->getId()],
|
||||
], CreateParams::create());
|
||||
}
|
||||
catch (Forbidden) {
|
||||
$isThrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($isThrown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,11 @@ use Espo\Core\Record\CreateParams;
|
||||
use Espo\Core\Record\ServiceContainer;
|
||||
use Espo\Core\Record\UpdateParams;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\Modules\Crm\Entities\Account;
|
||||
use Espo\Modules\Crm\Entities\CaseObj;
|
||||
use Espo\Modules\Crm\Entities\Contact;
|
||||
use Espo\Modules\Crm\Entities\Lead;
|
||||
use Espo\Modules\Crm\Entities\Opportunity;
|
||||
use Espo\Modules\Crm\Entities\Task;
|
||||
use Espo\ORM\EntityManager;
|
||||
@@ -232,4 +235,169 @@ class LinkTest extends BaseTestCase
|
||||
'closeDate' => Date::createToday()->toString(),
|
||||
], CreateParams::create());
|
||||
}
|
||||
|
||||
public function testLinkCheckEmailToCase(): void
|
||||
{
|
||||
$user = $this->createUser('test', [
|
||||
'data' => [
|
||||
'Account' => [
|
||||
'create' => 'no',
|
||||
'read' => 'own',
|
||||
'edit' => 'no',
|
||||
'delete' => 'no',
|
||||
],
|
||||
'Case' => [
|
||||
'create' => 'yes',
|
||||
'read' => 'own',
|
||||
'edit' => 'own',
|
||||
'delete' => 'no',
|
||||
],
|
||||
'Contact' => [
|
||||
'create' => 'yes',
|
||||
'read' => 'own',
|
||||
'edit' => 'own',
|
||||
'delete' => 'no',
|
||||
],
|
||||
'Lead' => [
|
||||
'create' => 'yes',
|
||||
'read' => 'own',
|
||||
'edit' => 'own',
|
||||
'delete' => 'no',
|
||||
],
|
||||
'Email' => [
|
||||
'create' => 'yes',
|
||||
'read' => 'own',
|
||||
'edit' => 'own',
|
||||
'delete' => 'no',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$em = $this->getEntityManager();
|
||||
|
||||
$account = $em->createEntity(Account::ENTITY_TYPE, ['assignedUserId' => $user->getId()]);
|
||||
$lead = $em->createEntity(Lead::ENTITY_TYPE);
|
||||
$contact = $em->createEntity(Contact::ENTITY_TYPE);
|
||||
$email = $em->createEntity(Email::ENTITY_TYPE, [
|
||||
'assignedUserId' => $user->getId(),
|
||||
'parentId' => $lead->getId(),
|
||||
'parentType' => Lead::ENTITY_TYPE,
|
||||
]);
|
||||
|
||||
$this->auth('test');
|
||||
$this->reCreateApplication();
|
||||
|
||||
$caseService = $this->getContainer()
|
||||
->getByClass(ServiceContainer::class)
|
||||
->getByClass(CaseObj::class);
|
||||
|
||||
// Should not create if no access to the Lead.
|
||||
|
||||
$isThrown = false;
|
||||
|
||||
try {
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$caseService->create((object) [
|
||||
'name' => '1',
|
||||
'assignedUserId' => $user->getId(),
|
||||
'leadId' => $lead->getId(),
|
||||
'accountId' => $account->getId(),
|
||||
], CreateParams::create());
|
||||
}
|
||||
catch (Forbidden) {
|
||||
$isThrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($isThrown);
|
||||
|
||||
// Should create if an email with a Lead parent is passed.
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$caseService->create((object) [
|
||||
'name' => '1',
|
||||
'assignedUserId' => $user->getId(),
|
||||
'leadId' => $lead->getId(),
|
||||
'accountId' => $account->getId(),
|
||||
'emailsIds' => [$email->getId()]
|
||||
], CreateParams::create());
|
||||
|
||||
//
|
||||
|
||||
$account = $em->createEntity(Account::ENTITY_TYPE);
|
||||
$email = $em->createEntity(Email::ENTITY_TYPE, [
|
||||
'assignedUserId' => $user->getId(),
|
||||
'parentId' => $account->getId(),
|
||||
'parentType' => Account::ENTITY_TYPE,
|
||||
]);
|
||||
|
||||
// Should not create if no access to the Account.
|
||||
|
||||
$isThrown = false;
|
||||
|
||||
try {
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$caseService->create((object) [
|
||||
'name' => '1',
|
||||
'assignedUserId' => $user->getId(),
|
||||
'accountId' => $account->getId(),
|
||||
], CreateParams::create());
|
||||
}
|
||||
catch (Forbidden) {
|
||||
$isThrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($isThrown);
|
||||
|
||||
// Should create if an email with an Account parent is passed.
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$caseService->create((object) [
|
||||
'name' => '1',
|
||||
'assignedUserId' => $user->getId(),
|
||||
'accountId' => $account->getId(),
|
||||
'emailsIds' => [$email->getId()]
|
||||
], CreateParams::create());
|
||||
|
||||
//
|
||||
|
||||
$account = $em->createEntity(Account::ENTITY_TYPE, ['assignedUserId' => $user->getId()]);
|
||||
|
||||
$email = $em->createEntity(Email::ENTITY_TYPE, [
|
||||
'assignedUserId' => $user->getId(),
|
||||
'parentId' => $contact->getId(),
|
||||
'parentType' => Contact::ENTITY_TYPE,
|
||||
]);
|
||||
|
||||
// Should not create if no access to the Contact.
|
||||
|
||||
$isThrown = false;
|
||||
|
||||
try {
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$caseService->create((object) [
|
||||
'name' => '1',
|
||||
'assignedUserId' => $user->getId(),
|
||||
'accountId' => $account->getId(),
|
||||
'contactId' => $contact->getId(),
|
||||
'contactsIds' => [$contact->getId()],
|
||||
], CreateParams::create());
|
||||
}
|
||||
catch (Forbidden) {
|
||||
$isThrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($isThrown);
|
||||
|
||||
// Should create if an email with a Contact parent is passed.
|
||||
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$caseService->create((object) [
|
||||
'name' => '1',
|
||||
'assignedUserId' => $user->getId(),
|
||||
'accountId' => $account->getId(),
|
||||
'contactId' => $contact->getId(),
|
||||
'contactsIds' => [$contact->getId()],
|
||||
'emailsIds' => [$email->getId()]
|
||||
], CreateParams::create());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,9 @@ class SanitizeTest extends BaseTestCase
|
||||
|
||||
$numbers = $account->getPhoneNumberGroup()->getNumberList();
|
||||
$this->assertCount(2, $numbers);
|
||||
|
||||
sort($numbers);
|
||||
|
||||
$this->assertEquals('+380904443322', $numbers[0]);
|
||||
$this->assertEquals('+380904443333', $numbers[1]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user