Initial commit

This commit is contained in:
root
2026-01-19 17:44:46 +01:00
commit 823af8b11d
8721 changed files with 1130846 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl\AccessChecker;
use Espo\Core\Acl\AccessChecker;
use Espo\Core\Acl\Exceptions\NotImplemented;
use Espo\Core\AclManager;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\BindingContainer;
use Espo\Core\Binding\BindingData;
use Espo\Core\InjectableFactory;
use Espo\Core\Portal\Acl\DefaultAccessChecker;
use Espo\Core\Portal\AclManager as PortalAclManager;
use Espo\Core\Utils\Metadata;
class AccessCheckerFactory
{
/** @var class-string<AccessChecker> */
private $defaultClassName = DefaultAccessChecker::class;
public function __construct(
private Metadata $metadata,
private InjectableFactory $injectableFactory
) {}
/**
* Create an access checker.
*
* @throws NotImplemented
*/
public function create(string $scope, PortalAclManager $aclManager): AccessChecker
{
$className = $this->getClassName($scope);
$bindingContainer = $this->createBindingContainer($className, $aclManager, $scope);
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
}
/**
* @return class-string<AccessChecker>
*/
private function getClassName(string $scope): string
{
/** @var ?class-string<AccessChecker> $className1 */
$className1 = $this->metadata->get(['aclDefs', $scope, 'portalAccessCheckerClassName']);
if ($className1) {
return $className1;
}
if (!$this->metadata->get(['scopes', $scope])) {
throw new NotImplemented();
}
return $this->defaultClassName;
}
/**
* @param class-string<AccessChecker> $className
*/
private function createBindingContainer(
string $className,
PortalAclManager $aclManager,
string $scope
): BindingContainer {
$bindingData = new BindingData();
$binder = new Binder($bindingData);
$binder
->bindInstance(PortalAclManager::class, $aclManager)
->bindInstance(AclManager::class, $aclManager);
$binder
->for($className)
->bindValue('$entityType', $scope);
return new BindingContainer($bindingData);
}
}

View File

@@ -0,0 +1,91 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl\AccessChecker;
use Espo\Core\Acl\ScopeData;
use Espo\Core\Portal\Acl\Table;
/**
* Checks scope access.
*/
class ScopeChecker
{
public function __construct()
{}
public function check(ScopeData $data, ?string $action = null, ?ScopeCheckerData $checkerData = null): bool
{
if ($data->isFalse()) {
return false;
}
if ($data->isTrue()) {
return true;
}
if ($action === null) {
return true;
}
$level = $data->get($action);
if ($level === Table::LEVEL_ALL || $level === Table::LEVEL_YES) {
return true;
}
if ($level === Table::LEVEL_NO) {
return false;
}
if (!$checkerData) {
return false;
}
if ($level === Table::LEVEL_OWN || $level === Table::LEVEL_ACCOUNT || $level === Table::LEVEL_CONTACT) {
if ($checkerData->isOwn()) {
return true;
}
}
if ($level === Table::LEVEL_ACCOUNT || $level === Table::LEVEL_CONTACT) {
if ($checkerData->inContact()) {
return true;
}
}
if ($level === Table::LEVEL_ACCOUNT) {
if ($checkerData->inAccount()) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,64 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl\AccessChecker;
use Closure;
/**
* Scope checker data.
*/
class ScopeCheckerData
{
public function __construct(
private Closure $isOwnChecker,
private Closure $inAccountChecker,
private Closure $inContactChecker
) {}
public function isOwn(): bool
{
return ($this->isOwnChecker)();
}
public function inAccount(): bool
{
return ($this->inAccountChecker)();
}
public function inContact(): bool
{
return ($this->inContactChecker)();
}
public static function createBuilder(): ScopeCheckerDataBuilder
{
return new ScopeCheckerDataBuilder();
}
}

View File

@@ -0,0 +1,143 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl\AccessChecker;
use Closure;
/**
* Builds scope checker data.
*/
class ScopeCheckerDataBuilder
{
private Closure $isOwnChecker;
private Closure $inAccountChecker;
private Closure $inContactChecker;
public function __construct()
{
$this->isOwnChecker = function (): bool {
return false;
};
$this->inAccountChecker = function (): bool {
return false;
};
$this->inContactChecker = function (): bool {
return false;
};
}
public function setIsOwn(bool $value): self
{
if ($value) {
$this->isOwnChecker = function (): bool {
return true;
};
return $this;
}
$this->isOwnChecker = function (): bool {
return false;
};
return $this;
}
public function setInAccount(bool $value): self
{
if ($value) {
$this->inAccountChecker = function (): bool {
return true;
};
return $this;
}
$this->inAccountChecker = function (): bool {
return false;
};
return $this;
}
public function setInContact(bool $value): self
{
if ($value) {
$this->inContactChecker = function (): bool {
return true;
};
return $this;
}
$this->inContactChecker = function (): bool {
return false;
};
return $this;
}
/**
* @param Closure(): bool $checker
*/
public function setIsOwnChecker(Closure $checker): self
{
$this->isOwnChecker = $checker;
return $this;
}
/**
* @param Closure(): bool $checker
*/
public function setInAccountChecker(Closure $checker): self
{
$this->inAccountChecker = $checker;
return $this;
}
/**
* @param Closure(): bool $checker
*/
public function setInContactChecker(Closure $checker): self
{
$this->inContactChecker = $checker;
return $this;
}
public function build(): ScopeCheckerData
{
return new ScopeCheckerData($this->isOwnChecker, $this->inAccountChecker, $this->inContactChecker);
}
}

View File

@@ -0,0 +1,157 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl;
use Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Core\Acl\AccessEntityCreateChecker;
use Espo\Core\Acl\AccessEntityDeleteChecker;
use Espo\Core\Acl\AccessEntityEditChecker;
use Espo\Core\Acl\AccessEntityReadChecker;
use Espo\Core\Acl\AccessEntityStreamChecker;
use Espo\Core\Acl\ScopeData;
use Espo\Core\Portal\Acl\AccessChecker\ScopeChecker;
use Espo\Core\Portal\Acl\AccessChecker\ScopeCheckerData;
use Espo\Core\Portal\AclManager as PortalAclManager;
/**
* A default implementation for access checking for portal.
*
* @implements AccessEntityCreateChecker<Entity>
* @implements AccessEntityReadChecker<Entity>
* @implements AccessEntityEditChecker<Entity>
* @implements AccessEntityDeleteChecker<Entity>
* @implements AccessEntityStreamChecker<Entity>
*/
class DefaultAccessChecker implements
AccessEntityCreateChecker,
AccessEntityReadChecker,
AccessEntityEditChecker,
AccessEntityDeleteChecker,
AccessEntityStreamChecker
{
public function __construct(
private PortalAclManager $aclManager,
private ScopeChecker $scopeChecker
) {}
private function checkEntity(User $user, Entity $entity, ScopeData $data, string $action): bool
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwnChecker(
function () use ($user, $entity): bool {
return $this->aclManager->checkOwnershipOwn($user, $entity);
}
)
->setInAccountChecker(
function () use ($user, $entity): bool {
return $this->aclManager->checkOwnershipAccount($user, $entity);
}
)
->setInContactChecker(
function () use ($user, $entity): bool {
return $this->aclManager->checkOwnershipContact($user, $entity);
}
)
->build();
return $this->scopeChecker->check($data, $action, $checkerData);
}
private function checkScope(User $user, ScopeData $data, ?string $action = null): bool
{
$checkerData = ScopeCheckerData
::createBuilder()
->setIsOwn(true)
->setInAccount(true)
->setInContact(true)
->build();
return $this->scopeChecker->check($data, $action, $checkerData);
}
public function check(User $user, ScopeData $data): bool
{
return $this->checkScope($user, $data);
}
public function checkCreate(User $user, ScopeData $data): bool
{
return $this->checkScope($user, $data, Table::ACTION_CREATE);
}
public function checkRead(User $user, ScopeData $data): bool
{
return $this->checkScope($user, $data, Table::ACTION_READ);
}
public function checkEdit(User $user, ScopeData $data): bool
{
return $this->checkScope($user, $data, Table::ACTION_EDIT);
}
public function checkDelete(User $user, ScopeData $data): bool
{
return $this->checkScope($user, $data, Table::ACTION_DELETE);
}
public function checkStream(User $user, ScopeData $data): bool
{
return $this->checkScope($user, $data, Table::ACTION_STREAM);
}
public function checkEntityCreate(User $user, Entity $entity, ScopeData $data): bool
{
return $this->checkEntity($user, $entity, $data, Table::ACTION_CREATE);
}
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
{
return $this->checkEntity($user, $entity, $data, Table::ACTION_READ);
}
public function checkEntityEdit(User $user, Entity $entity, ScopeData $data): bool
{
return $this->checkEntity($user, $entity, $data, Table::ACTION_EDIT);
}
public function checkEntityStream(User $user, Entity $entity, ScopeData $data): bool
{
return $this->checkEntity($user, $entity, $data, Table::ACTION_STREAM);
}
public function checkEntityDelete(User $user, Entity $entity, ScopeData $data): bool
{
return $this->checkEntity($user, $entity, $data, Table::ACTION_DELETE);
}
}

View File

@@ -0,0 +1,189 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl;
use Espo\Core\Field\Link;
use Espo\Core\Field\LinkParent;
use Espo\Core\Name\Field;
use Espo\Core\ORM\Type\FieldType;
use Espo\Core\Portal\Acl\OwnershipChecker\MetadataProvider;
use Espo\Modules\Crm\Entities\Account;
use Espo\Modules\Crm\Entities\Contact;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
use Espo\Entities\User;
use Espo\Core\Acl\OwnershipOwnChecker;
use Espo\ORM\Type\RelationType;
/**
* A default implementation for ownership checking for portal.
*
* @implements OwnershipOwnChecker<\Espo\Core\ORM\Entity>
* @implements OwnershipAccountChecker<\Espo\Core\ORM\Entity>
* @implements OwnershipContactChecker<\Espo\Core\ORM\Entity>
*/
class DefaultOwnershipChecker implements
OwnershipOwnChecker,
OwnershipAccountChecker,
OwnershipContactChecker
{
private const ATTR_CREATED_BY_ID = Field::CREATED_BY . 'Id';
public function __construct(
private EntityManager $entityManager,
private MetadataProvider $metadataProvider,
) {}
public function checkOwn(User $user, Entity $entity): bool
{
if ($entity->hasAttribute(self::ATTR_CREATED_BY_ID)) {
if (
$entity->has(self::ATTR_CREATED_BY_ID) &&
$user->getId() === $entity->get(self::ATTR_CREATED_BY_ID)
) {
return true;
}
}
return false;
}
public function checkAccount(User $user, Entity $entity): bool
{
$linkDefs = $this->metadataProvider->getAccountLink($entity->getEntityType());
if (!$linkDefs) {
return false;
}
$link = $linkDefs->getName();
$accountIds = $user->getAccounts()->getIdList();
if ($accountIds === []) {
return false;
}
$fieldDefs = $this->entityManager
->getDefs()
->getEntity($entity->getEntityType())
->tryGetField($link);
if (
$linkDefs->getType() === RelationType::BELONGS_TO &&
$fieldDefs?->getType() === FieldType::LINK
) {
$setAccountLink = $entity->getValueObject($link);
if (!$setAccountLink instanceof Link) {
return false;
}
return in_array($setAccountLink->getId(), $accountIds);
}
if (
$linkDefs->getType() === RelationType::BELONGS_TO_PARENT &&
$fieldDefs?->getType() === FieldType::LINK_PARENT
) {
$setLink = $entity->getValueObject($link);
if (!$setLink instanceof LinkParent || $setLink->getEntityType() !== Account::ENTITY_TYPE) {
return false;
}
return in_array($setLink->getId(), $accountIds);
}
foreach ($accountIds as $accountId) {
$isRelated = $this->entityManager
->getRelation($entity, $link)
->isRelatedById($accountId);
if ($isRelated) {
return true;
}
}
return false;
}
public function checkContact(User $user, Entity $entity): bool
{
$linkDefs = $this->metadataProvider->getContactLink($entity->getEntityType());
if (!$linkDefs) {
return false;
}
$link = $linkDefs->getName();
$contactId = $user->getContactId();
if (!$contactId) {
return false;
}
$fieldDefs = $this->entityManager
->getDefs()
->getEntity($entity->getEntityType())
->tryGetField($link);
if (
$linkDefs->getType() === RelationType::BELONGS_TO &&
$fieldDefs?->getType() === FieldType::LINK
) {
$setContactLink = $entity->getValueObject($link);
if (!$setContactLink instanceof Link) {
return false;
}
return $setContactLink->getId() === $contactId;
}
if (
$linkDefs->getType() === RelationType::BELONGS_TO_PARENT &&
$fieldDefs?->getType() === FieldType::LINK_PARENT
) {
$setLink = $entity->getValueObject($link);
if (!$setLink instanceof LinkParent || $setLink->getEntityType() !== Contact::ENTITY_TYPE) {
return false;
}
return $setLink->getId() === $contactId;
}
return$this->entityManager
->getRelation($entity, $link)
->isRelatedById($contactId);
}
}

View File

@@ -0,0 +1,45 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl\Map;
use Espo\Entities\Portal;
use Espo\Entities\User;
use Espo\Core\Acl\Map\CacheKeyProvider as CacheKeyProviderInterface;
class CacheKeyProvider implements CacheKeyProviderInterface
{
public function __construct(private User $user, private Portal $portal)
{}
public function get(): string
{
return 'aclPortalMap/' . $this->portal->getId() . '/' . $this->user->getId();
}
}

View File

@@ -0,0 +1,74 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl\Map;
use Espo\Entities\Portal;
use Espo\Entities\User;
use Espo\Core\Acl\Map\CacheKeyProvider;
use Espo\Core\Acl\Map\Map;
use Espo\Core\Acl\Map\MetadataProvider;
use Espo\Core\Acl\Table;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\BindingContainer;
use Espo\Core\Binding\BindingData;
use Espo\Core\InjectableFactory;
use Espo\Core\Portal\Acl\Map\CacheKeyProvider as PortalCacheKeyProvider;
use Espo\Core\Portal\Acl\Map\MetadataProvider as PortalMetadataProvider;
use Espo\Core\Portal\Acl\Table as PortalTable;
class MapFactory
{
public function __construct(private InjectableFactory $injectableFactory)
{}
public function create(User $user, PortalTable $table, Portal $portal): Map
{
$bindingContainer = $this->createBindingContainer($user, $table, $portal);
return $this->injectableFactory->createWithBinding(Map::class, $bindingContainer);
}
private function createBindingContainer(User $user, PortalTable $table, Portal $portal): BindingContainer
{
$bindingData = new BindingData();
$binder = new Binder($bindingData);
$binder
->bindInstance(User::class, $user)
->bindInstance(Table::class, $table)
->bindInstance(Portal::class, $portal)
->bindImplementation(MetadataProvider::class, PortalMetadataProvider::class)
->bindImplementation(CacheKeyProvider::class, PortalCacheKeyProvider::class);
return new BindingContainer($bindingData);
}
}

View File

@@ -0,0 +1,37 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl\Map;
use Espo\Core\Acl\Map\MetadataProvider as BaseMetadataProvider;
class MetadataProvider extends BaseMetadataProvider
{
protected string $type = 'aclPortal';
}

View File

@@ -0,0 +1,48 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl;
use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\Core\Acl\OwnershipChecker;
/**
* @template TEntity of Entity
*/
interface OwnershipAccountChecker extends OwnershipChecker
{
/**
* Check whether an entity belongs to a portal user account.
*
* @param TEntity $entity
*/
public function checkAccount(User $user, Entity $entity): bool;
}

View File

@@ -0,0 +1,64 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl\OwnershipChecker;
use Espo\Core\Utils\Metadata;
use Espo\ORM\Defs;
use Espo\ORM\Defs\RelationDefs;
class MetadataProvider
{
public function __construct(
private Metadata $metadata,
private Defs $defs,
) {}
public function getAccountLink(string $entityType): ?RelationDefs
{
$link = $this->metadata->get("aclDefs.$entityType.accountLink");
if (!$link) {
return null;
}
return $this->defs->getEntity($entityType)->tryGetRelation($link);
}
public function getContactLink(string $entityType): ?RelationDefs
{
$link = $this->metadata->get("aclDefs.$entityType.contactLink");
if (!$link) {
return null;
}
return $this->defs->getEntity($entityType)->tryGetRelation($link);
}
}

View File

@@ -0,0 +1,109 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl\OwnershipChecker;
use Espo\Core\Acl\Exceptions\NotImplemented;
use Espo\Core\Acl\OwnershipChecker;
use Espo\Core\AclManager;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\BindingContainer;
use Espo\Core\Binding\BindingData;
use Espo\Core\InjectableFactory;
use Espo\Core\Portal\Acl\DefaultOwnershipChecker;
use Espo\Core\Portal\AclManager as PortalAclManager;
use Espo\Core\Utils\Metadata;
class OwnershipCheckerFactory
{
/** @var class-string<OwnershipChecker> */
private $defaultClassName = DefaultOwnershipChecker::class;
public function __construct(
private Metadata $metadata,
private InjectableFactory $injectableFactory
) {}
/**
* Create an ownership checker.
*
* @throws NotImplemented
*/
public function create(string $scope, PortalAclManager $aclManager): OwnershipChecker
{
$className = $this->getClassName($scope);
$bindingContainer = $this->createBindingContainer($className, $aclManager, $scope);
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
}
/**
* @return class-string<OwnershipChecker>
*/
private function getClassName(string $scope): string
{
$className = $this->metadata->get(['aclDefs', $scope, 'portalOwnershipCheckerClassName']);
if ($className) {
/** @var class-string<OwnershipChecker> */
return $className;
}
if (!$this->metadata->get(['scopes', $scope])) {
throw new NotImplemented();
}
return $this->defaultClassName;
}
/**
* @param class-string<OwnershipChecker> $className
*/
private function createBindingContainer(
string $className,
PortalAclManager $aclManager,
string $scope
): BindingContainer {
$bindingData = new BindingData();
$binder = new Binder($bindingData);
$binder
->bindInstance(PortalAclManager::class, $aclManager)
->bindInstance(AclManager::class, $aclManager);
$binder
->for($className)
->bindValue('$entityType', $scope);
return new BindingContainer($bindingData);
}
}

View File

@@ -0,0 +1,48 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl;
use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\Core\Acl\OwnershipChecker;
/**
* @template TEntity of Entity
*/
interface OwnershipContactChecker extends OwnershipChecker
{
/**
* Check whether an entity belongs to a portal user contact.
*
* @param TEntity $entity
*/
public function checkContact(User $user, Entity $entity): bool;
}

View File

@@ -0,0 +1,102 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl;
use Espo\Core\Acl\Table\DefaultTable as BaseTable;
use stdClass;
class Table extends BaseTable
{
public const LEVEL_ACCOUNT = 'account';
public const LEVEL_CONTACT = 'contact';
protected string $type = 'aclPortal';
/**
* @var string[]
*/
protected $levelList = [
self::LEVEL_YES,
self::LEVEL_ALL,
self::LEVEL_ACCOUNT,
self::LEVEL_CONTACT,
self::LEVEL_OWN,
self::LEVEL_NO,
];
/**
* @return string[]
*/
protected function getScopeWithAclList(): array
{
$scopeList = [];
$scopes = $this->metadata->get('scopes');
foreach ($scopes as $scope => $item) {
if (empty($item['acl'])) {
continue;
}
if (empty($item['aclPortal'])) {
continue;
}
$scopeList[] = $scope;
}
return $scopeList;
}
protected function applyDefault(stdClass &$table, stdClass &$fieldTable): void
{
parent::applyDefault($table, $fieldTable);
foreach ($this->getScopeList() as $scope) {
if (!isset($table->$scope)) {
$table->$scope = false;
}
}
}
protected function applyDisabled(stdClass $table, stdClass $fieldTable): void
{
foreach ($this->getScopeList() as $scope) {
$item = $this->metadata->get(['scopes', $scope]) ?? [];
if (!empty($item['disabled']) || !empty($item['portalDisabled'])) {
$table->$scope = false;
unset($fieldTable->$scope);
}
}
}
}

View File

@@ -0,0 +1,46 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl\Table;
use Espo\Entities\Portal;
use Espo\Entities\User;
use Espo\Core\Acl\Table\CacheKeyProvider as CacheKeyProviderInterface;
class CacheKeyProvider implements CacheKeyProviderInterface
{
public function __construct(private User $user, private Portal $portal)
{}
public function get(): string
{
return 'aclPortal/' . $this->portal->getId() . '/' . $this->user->getId();
}
}

View File

@@ -0,0 +1,83 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl\Table;
use Espo\ORM\EntityManager;
use Espo\Entities\Portal;
use Espo\Entities\PortalRole;
use Espo\Entities\User;
use Espo\Core\Acl\Table\Role;
use Espo\Core\Acl\Table\RoleEntityWrapper;
use Espo\Core\Acl\Table\RoleListProvider as RoleListProviderInterface;
class RoleListProvider implements RoleListProviderInterface
{
public function __construct(
private User $user,
private Portal $portal,
private EntityManager $entityManager
) {}
/**
* @return Role[]
*/
public function get(): array
{
$roleList = [];
/** @var iterable<PortalRole> $userRoleList */
$userRoleList = $this->entityManager
->getRDBRepository(User::ENTITY_TYPE)
->getRelation($this->user, 'portalRoles')
->find();
foreach ($userRoleList as $role) {
$roleList[] = $role;
}
/** @var iterable<PortalRole> $portalRoleList */
$portalRoleList = $this->entityManager
->getRDBRepository(Portal::ENTITY_TYPE)
->getRelation($this->portal, 'portalRoles')
->find();
foreach ($portalRoleList as $role) {
$roleList[] = $role;
}
return array_map(
function (PortalRole $role): RoleEntityWrapper {
return new RoleEntityWrapper($role);
},
$roleList
);
}
}

View File

@@ -0,0 +1,74 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl\Table;
use Espo\Entities\Portal;
use Espo\Entities\User;
use Espo\Core\Acl\Table\CacheKeyProvider;
use Espo\Core\Acl\Table\RoleListProvider;
use Espo\Core\Binding\Binder;
use Espo\Core\Binding\BindingContainer;
use Espo\Core\Binding\BindingData;
use Espo\Core\InjectableFactory;
use Espo\Core\Portal\Acl\Table;
use Espo\Core\Portal\Acl\Table\CacheKeyProvider as PortalCacheKeyProvider;
use Espo\Core\Portal\Acl\Table\RoleListProvider as PortalRoleListProvider;
class TableFactory
{
public function __construct(private InjectableFactory $injectableFactory)
{}
/**
* Create a table.
*/
public function create(User $user, Portal $portal): Table
{
$bindingContainer = $this->createBindingContainer($user, $portal);
return $this->injectableFactory->createWithBinding(Table::class, $bindingContainer);
}
private function createBindingContainer(User $user, Portal $portal): BindingContainer
{
$bindingData = new BindingData();
$binder = new Binder($bindingData);
$binder
->bindInstance(User::class, $user)
->bindInstance(Portal::class, $portal)
->bindImplementation(RoleListProvider::class, PortalRoleListProvider::class)
->bindImplementation(CacheKeyProvider::class, PortalCacheKeyProvider::class);
return new BindingContainer($bindingData);
}
}

View File

@@ -0,0 +1,95 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* 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\Core\Portal\Acl\Traits;
use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\Core\Acl\ScopeData;
use Espo\Core\Portal\Acl\DefaultAccessChecker;
trait DefaultAccessCheckerDependency
{
private DefaultAccessChecker $defaultAccessChecker;
public function check(User $user, ScopeData $data): bool
{
return $this->defaultAccessChecker->check($user, $data);
}
public function checkCreate(User $user, ScopeData $data): bool
{
return $this->defaultAccessChecker->checkCreate($user, $data);
}
public function checkRead(User $user, ScopeData $data): bool
{
return $this->defaultAccessChecker->checkRead($user, $data);
}
public function checkEdit(User $user, ScopeData $data): bool
{
return $this->defaultAccessChecker->checkEdit($user, $data);
}
public function checkDelete(User $user, ScopeData $data): bool
{
return $this->defaultAccessChecker->checkDelete($user, $data);
}
public function checkStream(User $user, ScopeData $data): bool
{
return $this->defaultAccessChecker->checkStream($user, $data);
}
public function checkEntityCreate(User $user, Entity $entity, ScopeData $data): bool
{
return $this->defaultAccessChecker->checkEntityCreate($user, $entity, $data);
}
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
{
return $this->defaultAccessChecker->checkEntityRead($user, $entity, $data);
}
public function checkEntityEdit(User $user, Entity $entity, ScopeData $data): bool
{
return $this->defaultAccessChecker->checkEntityEdit($user, $entity, $data);
}
public function checkEntityDelete(User $user, Entity $entity, ScopeData $data): bool
{
return $this->defaultAccessChecker->checkEntityDelete($user, $entity, $data);
}
public function checkEntityStream(User $user, Entity $entity, ScopeData $data): bool
{
return $this->defaultAccessChecker->checkEntityStream($user, $entity, $data);
}
}