acl manager code improvements

This commit is contained in:
Yuri Kuznetsov
2020-06-24 12:48:08 +03:00
parent 8bfec9ec7e
commit cb55cec3f0
13 changed files with 291 additions and 133 deletions

View File

@@ -29,9 +29,12 @@
namespace Espo\Core;
use \Espo\ORM\Entity;
use \Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Entities\User;
/**
* A wrapper for AclManager. To check access for a current user.
*/
class Acl
{
private $user;

View File

@@ -29,6 +29,12 @@
namespace Espo\Core\Acl;
use Espo\Core\{
Utils\Metadata,
Utils\File\Manager as FileManager,
Utils\FieldManagerUtil,
};
class GlobalRestricton
{
protected $fieldTypeList = [
@@ -58,12 +64,8 @@ class GlobalRestricton
private $data;
public function __construct(
\Espo\Core\Utils\Metadata $metadata,
\Espo\Core\Utils\File\Manager $fileManager,
\Espo\Core\Utils\FieldManagerUtil $fieldManagerUtil,
bool $useCache = true
)
{
Metadata $metadata, FileManager $fileManager, FieldManagerUtil $fieldManagerUtil, bool $useCache = true
) {
$this->metadata = $metadata;
$this->fileManager = $fileManager;
$this->fieldManagerUtil = $fieldManagerUtil;

View File

@@ -29,15 +29,15 @@
namespace Espo\Core\Acl;
use \Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Error;
use \Espo\ORM\Entity;
use \Espo\Entities\User;
use Espo\ORM\Entity;
use Espo\Entities\User;
use \Espo\Core\Utils\Config;
use \Espo\Core\Utils\Metadata;
use \Espo\Core\Utils\FieldManagerUtil;
use \Espo\Core\Utils\File\Manager as FileManager;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\FieldManagerUtil;
use Espo\Core\Utils\File\Manager as FileManager;
class Table
{
@@ -59,7 +59,7 @@ class Table
protected $fieldLevelList = ['yes', 'no'];
protected $valuePermissionHighestLevels = array();
protected $valuePermissionHighestLevels = [];
protected $valuePermissionList = [];
@@ -69,16 +69,18 @@ class Table
private $fieldManager;
protected $forbiddenAttributesCache = array();
protected $forbiddenAttributesCache = [];
protected $forbiddenFieldsCache = array();
protected $forbiddenFieldsCache = [];
protected $isStrictModeForced = false;
protected $isStrictMode = false;
public function __construct(User $user, Config $config = null, FileManager $fileManager = null, Metadata $metadata = null, FieldManagerUtil $fieldManager = null)
{
public function __construct(
User $user, Config $config = null, FileManager $fileManager = null, Metadata $metadata = null,
FieldManagerUtil $fieldManagerUtil = null
) {
$this->data = (object) [
'table' => (object) [],
'fieldTable' => (object) [],
@@ -95,8 +97,8 @@ class Table
$this->metadata = $metadata;
if ($fieldManager) {
$this->fieldManager = $fieldManager;
if ($fieldManagerUtil) {
$this->fieldManager = $fieldManagerUtil;
}
if (!$this->user->isFetched()) {
@@ -220,7 +222,6 @@ class Table
$this->applyDisabled($aclTable, $fieldTable);
$this->applyMandatory($aclTable, $fieldTable);
$this->applyAdditional($aclTable, $fieldTable, $valuePermissionLists);
$this->applyReadOnlyFields($fieldTable);
} else {
$aclTable = (object) [];
foreach ($this->getScopeList() as $scope) {
@@ -260,7 +261,10 @@ class Table
$permissionsDefaultsGroupName = 'permissionsStrictDefaults';
}
foreach ($this->valuePermissionList as $permission) {
$this->data->$permission = $this->mergeValueList($valuePermissionLists->$permission, $this->metadata->get(['app', $this->type, $permissionsDefaultsGroupName, $permission, 'yes']));
$this->data->$permission = $this->mergeValueList(
$valuePermissionLists->$permission,
$this->metadata->get(['app', $this->type, $permissionsDefaultsGroupName, $permission, 'yes'])
);
if ($this->metadata->get('app.'.$this->type.'.mandatory.' . $permission)) {
$this->data->$permission = $this->metadata->get('app.'.$this->type.'.mandatory.' . $permission);
}
@@ -312,7 +316,10 @@ class Table
$fieldTableQuickAccess = $this->data->fieldTableQuickAccess;
if (!isset($fieldTableQuickAccess->$scope) || !isset($fieldTableQuickAccess->$scope->attributes) || !isset($fieldTableQuickAccess->$scope->attributes->$action)) {
if (
!isset($fieldTableQuickAccess->$scope) || !isset($fieldTableQuickAccess->$scope->attributes) ||
!isset($fieldTableQuickAccess->$scope->attributes->$action)
) {
$this->forbiddenAttributesCache[$key] = [];
return [];
}
@@ -348,7 +355,10 @@ class Table
$fieldTableQuickAccess = $this->data->fieldTableQuickAccess;
if (!isset($fieldTableQuickAccess->$scope) || !isset($fieldTableQuickAccess->$scope->fields) || !isset($fieldTableQuickAccess->$scope->fields->$action)) {
if (
!isset($fieldTableQuickAccess->$scope) || !isset($fieldTableQuickAccess->$scope->fields) ||
!isset($fieldTableQuickAccess->$scope->fields->$action)
) {
$this->forbiddenFieldsCache[$key] = [];
return [];
}
@@ -446,7 +456,8 @@ class Table
$fieldList = array_keys($this->getMetadata()->get("entityDefs.{$scope}.fields", []));
$defaultScopeFieldData = $this->metadata->get('app.'.$this->type.'.'.$defaultsGroupName.'.scopeFieldLevel.' . $scope, []);
$defaultScopeFieldData = $this->metadata->get(
'app.'.$this->type.'.'.$defaultsGroupName.'.scopeFieldLevel.' . $scope, []);
foreach (array_merge($defaultFieldData, $defaultScopeFieldData) as $field => $f) {
if (!in_array($field, $fieldList)) continue;
@@ -480,7 +491,10 @@ class Table
if ($this->isStrictMode) {
$paramDefaultsName = 'scopeLevelTypesStrictDefaults';
}
$defaultValue = $this->metadata->get(['app', $this->type, $paramDefaultsName, $aclType], $this->metadata->get(['app', $this->type, $paramDefaultsName, 'record']));
$defaultValue = $this->metadata->get(
['app', $this->type, $paramDefaultsName, $aclType],
$this->metadata->get(['app', $this->type, $paramDefaultsName, 'record'])
);
if (is_array($defaultValue)) {
$defaultValue = (object) $defaultValue;
}
@@ -651,7 +665,9 @@ class Table
if (!isset($data->$scope->$action)) {
$data->$scope->$action = $level;
} else {
if (array_search($data->$scope->$action, $this->levelList) > array_search($level, $this->levelList)) {
if (
array_search($data->$scope->$action, $this->levelList) > array_search($level, $this->levelList)
) {
$data->$scope->$action = $level;
}
}
@@ -709,7 +725,12 @@ class Table
if (!isset($data->$scope->$field->$action)) {
$data->$scope->$field->$action = $level;
} else {
if (array_search($data->$scope->$field->$action, $this->fieldLevelList) > array_search($level, $this->fieldLevelList)) {
if (
array_search(
$data->$scope->$field->$action,
$this->fieldLevelList
) > array_search($level, $this->fieldLevelList)
) {
$data->$scope->$field->$action = $level;
}
}
@@ -726,27 +747,4 @@ class Table
$this->fileManager->putPhpContents($this->cacheFilePath, $this->data, true);
}
protected function applyReadOnlyFields(&$fieldTable)
{
// TODO Enable in 5.4.0
return;
$scopeList = $this->getScopeWithAclList();
foreach ($scopeList as $scope) {
if (!property_exists($fieldTable, $scope)) continue;
$fieldList = array_keys($this->getMetadata()->get(['entityDefs', $scope, 'fields'], []));
foreach ($fieldList as $field) {
if ($this->getMetadata()->get(['entityDefs', $scope, 'fields', $field, 'readOnly'])) {
if (property_exists($fieldTable->$scope, $field)) {
$fieldTable->$scope->$field->edit = 'no';
} else {
$fieldTable->$scope->$field = (object) [];
foreach ($this->fieldActionList as $action) {
$fieldTable->$scope->$field->$action = 'yes';
}
$fieldTable->$scope->$field->edit = 'no';
}
}
}
}
}
}

View File

@@ -37,12 +37,17 @@ use Espo\Core\Utils\Util;
use Espo\Core\Acl\GlobalRestricton;
use Espo\Core\{
Utils\ClassFinder,
Utils\Config,
ORM\EntityManager,
};
/**
* Used to check access for a specific user.
*/
class AclManager
{
private $container;
private $metadata;
private $implementationHashMap = [];
private $tableHashMap = [];
@@ -55,33 +60,28 @@ class AclManager
protected $globalRestricton;
public function __construct(Container $container)
{
$this->container = $container;
$this->metadata = $container->get('metadata');
protected $injectableFactory;
protected $classFinder;
protected $config;
protected $entityManager;
$this->globalRestricton = new GlobalRestricton(
$container->get('metadata'),
$container->get('fileManager'),
$container->get('fieldManagerUtil'),
$container->get('config')->get('useCache')
);
}
public function __construct(
InjectableFactory $injectableFactory, ClassFinder $classFinder, Config $config, EntityManager $entityManager
) {
$this->injectableFactory = $injectableFactory;
$this->classFinder = $classFinder;
$this->config = $config;
$this->entityManager = $entityManager;
protected function getContainer()
{
return $this->container;
}
protected function getMetadata()
{
return $this->metadata;
$this->globalRestricton = $this->injectableFactory->createWith(GlobalRestricton::class, [
'useCache' => $config->get('useCache'),
]);
}
public function getImplementation(string $scope)
{
if (empty($this->implementationHashMap[$scope])) {
$className = $this->getContainer()->get('classFinder')->find('Acl', $scope);
$className = $this->classFinder->find('Acl', $scope);
if (!$className) {
$className = $this->baseImplementationClassName;
@@ -91,7 +91,7 @@ class AclManager
throw new Error("{$className} does not exist.");
}
$acl = $this->getContainer()->get('injectableFactory')->createWith($className, [
$acl = $this->injectableFactory->createWith($className, [
'scope' => $scope,
]);
@@ -109,12 +109,9 @@ class AclManager
}
if (empty($this->tableHashMap[$key])) {
$config = $this->getContainer()->get('config');
$fileManager = $this->getContainer()->get('fileManager');
$metadata = $this->getContainer()->get('metadata');
$fieldManager = $this->getContainer()->get('fieldManagerUtil');
$this->tableHashMap[$key] = new $this->tableClassName($user, $config, $fileManager, $metadata, $fieldManager);
$this->tableHashMap[$key] = $this->injectableFactory->createWith($this->tableClassName, [
'user' => $user,
]);
}
return $this->tableHashMap[$key];
@@ -342,7 +339,7 @@ class AclManager
if ($permission === 'team') {
$teamIdList = $user->getLinkMultipleIdList('teams');
if (!$this->getContainer()->get('entityManager')->getRepository('User')->checkBelongsToAnyOfTeams($userId, $teamIdList)) {
if (!$this->entityManager->getRepository('User')->checkBelongsToAnyOfTeams($userId, $teamIdList)) {
return false;
}
}

View File

@@ -29,16 +29,16 @@
namespace Espo\Core\AclPortal;
use \Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Error;
use \Espo\ORM\Entity;
use \Espo\Entities\User;
use \Espo\Entities\Portal;
use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\Entities\Portal;
use \Espo\Core\Utils\Config;
use \Espo\Core\Utils\Metadata;
use \Espo\Core\Utils\FieldManagerUtil;
use \Espo\Core\Utils\File\Manager as FileManager;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\FieldManagerUtil;
use Espo\Core\Utils\File\Manager as FileManager;
class Table extends \Espo\Core\Acl\Table
{
@@ -52,13 +52,16 @@ class Table extends \Espo\Core\Acl\Table
protected $isStrictModeForced = true;
public function __construct(User $user, Portal $portal, Config $config = null, FileManager $fileManager = null, Metadata $metadata = null, FieldManagerUtil $fieldManager = null)
{
public function __construct(
User $user, Portal $portal, Config $config = null, FileManager $fileManager = null,
Metadata $metadata = null, FieldManagerUtil $fieldManagerUtil = null
) {
if (empty($portal)) {
throw new Error("No portal was passed to AclPortal\\Table constructor.");
}
$this->portal = $portal;
parent::__construct($user, $config, $fileManager, $metadata, $fieldManager);
parent::__construct($user, $config, $fileManager, $metadata, $fieldManagerUtil);
}
protected function getPortal()
@@ -132,4 +135,3 @@ class Table extends \Espo\Core\Acl\Table
{
}
}

View File

@@ -30,20 +30,31 @@
namespace Espo\Core\Loaders;
use Espo\Core\{
Container,
InjectableFactory,
Utils\ClassFinder,
Utils\Config,
ORM\EntityManager,
AclManager as AclManagerService,
};
class AclManager implements Loader
{
protected $container;
protected $injectableFactory;
protected $classFinder;
protected $config;
protected $entityManager;
public function __construct(Container $container)
{
$this->container = $container;
public function __construct(
InjectableFactory $injectableFactory, ClassFinder $classFinder, Config $config, EntityManager $entityManager
) {
$this->injectableFactory = $injectableFactory;
$this->classFinder = $classFinder;
$this->config = $config;
$this->entityManager = $entityManager;
}
public function load()
{
return new \Espo\Core\AclManager($this->container);
return new AclManagerService($this->injectableFactory, $this->classFinder, $this->config, $this->entityManager);
}
}

View File

@@ -0,0 +1,49 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://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 General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Loaders;
use Espo\Core\{
InjectableFactory,
Portal\AclManagerContainer as PortalAclManagerContainerService
};
class PortalAclManagerContainer implements Loader
{
protected $injectableFactory;
public function __construct(InjectableFactory $injectableFactory) {
$this->injectableFactory = $injectableFactory;
}
public function load()
{
return new PortalAclManagerContainerService($this->injectableFactory);
}
}

View File

@@ -33,6 +33,8 @@ use Espo\ORM\Entity;
use Espo\Entities\User;
use Espo\Core\Utils\Util;
use Espo\Entities\Portal;
class AclManager extends \Espo\Core\AclManager
{
protected $tableClassName = 'Espo\\Core\\AclPortal\\Table';
@@ -48,7 +50,7 @@ class AclManager extends \Espo\Core\AclManager
public function getImplementation(string $scope)
{
if (empty($this->implementationHashMap[$scope])) {
$className = $this->getContainer()->get('classFinder')->find('AclPortal', $scope);
$className = $this->classFinder->find('AclPortal', $scope);
if (!$className) {
$className = $this->baseImplementationClassName;
@@ -58,7 +60,7 @@ class AclManager extends \Espo\Core\AclManager
throw new Error("{$className} does not exist.");
}
$acl = $this->getContainer()->get('injectableFactory')->createWith($className, [
$acl = $this->injectableFactory->createWith($className, [
'scope' => $scope,
]);
@@ -78,17 +80,14 @@ class AclManager extends \Espo\Core\AclManager
return $this->mainManager;
}
public function setPortal($portal)
public function setPortal(Portal $portal)
{
$this->portal = $portal;
}
protected function getPortal()
protected function getPortal() : Portal
{
if ($this->portal) {
return $this->portal;
}
return $this->getContainer()->get('portal');
return $this->portal ?? null;
}
protected function getTable(User $user)
@@ -99,14 +98,10 @@ class AclManager extends \Espo\Core\AclManager
}
if (empty($this->tableHashMap[$key])) {
$config = $this->getContainer()->get('config');
$fileManager = $this->getContainer()->get('fileManager');
$metadata = $this->getContainer()->get('metadata');
$fieldManager = $this->getContainer()->get('fieldManagerUtil');
$portal = $this->getPortal();
$this->tableHashMap[$key] = new $this->tableClassName(
$user, $portal, $config, $fileManager, $metadata, $fieldManager);
$this->tableHashMap[$key] = $this->injectableFactory->createWith($this->tableClassName, [
'user' => $user,
'portal' => $this->getPortal(),
]);
}
return $this->tableHashMap[$key];
@@ -259,5 +254,4 @@ class AclManager extends \Espo\Core\AclManager
{
return !$user->isPortal();
}
}

View File

@@ -0,0 +1,69 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://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 General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Portal;
use Espo\Entities\Portal;
use Espo\Core\{
InjectableFactory,
Portal\AclManager,
};
use Espo\Core\Exceptions\Error;
/** Used when logged to CRM (not to portal) to provide an access checking ability for a specific portal.
* E.g. check whether a portal user has an access to some record within a specific portal.
*/
class AclManagerContainer
{
protected $data = [];
protected $injectableFactory;
public function __construct(InjectableFactory $injectableFactory) {
$this->injectableFactory = $injectableFactory;
}
public function get(Portal $portal)
{
$id = $portal->id;
if (!$id) throw new Error("AclManagerContainer: portal should have ID.");
if (!isset($this->data[$id])) {
$aclManager = $this->injectableFactory->create(AclManager::class);
$aclManager->setPortal($portal);
$this->data[$id] = $aclManager;
}
return $this->data[$id];
}
}

View File

@@ -75,5 +75,7 @@ class Container extends BaseContainer
foreach ($data as $attribute => $value) {
$this->get('config')->set($attribute, $value, true);
}
$this->get('aclManager')->setPortal($portal);
}
}

View File

@@ -30,22 +30,37 @@
namespace Espo\Core\Portal\Loaders;
use Espo\Core\{
Container,
InjectableFactory,
Utils\ClassFinder,
Utils\Config,
ORM\EntityManager,
AclManager as InternalAclManager,
Loaders\Loader,
Portal\AclManager as PortalAclManager,
};
class AclManager implements Loader
{
public function __construct(Container $container, InternalAclManager $internalAclManager)
{
$this->container = $container;
protected $injectableFactory;
protected $classFinder;
protected $config;
protected $entityManager;
protected $internalAclManager;
public function __construct(
InjectableFactory $injectableFactory, ClassFinder $classFinder, Config $config, EntityManager $entityManager,
InternalAclManager $internalAclManager
) {
$this->injectableFactory = $injectableFactory;
$this->classFinder = $classFinder;
$this->config = $config;
$this->entityManager = $entityManager;
$this->internalAclManager = $internalAclManager;
}
public function load()
{
$aclMenager = new \Espo\Core\Portal\AclManager($this->container);
$aclMenager = new PortalAclManager($this->injectableFactory, $this->classFinder, $this->config, $this->entityManager);
$aclMenager->setMainManager($this->internalAclManager);
return $aclMenager;
}

View File

@@ -30,19 +30,35 @@
namespace Espo\Core\Portal\Loaders;
use Espo\Core\{
Container,
Loaders\Loader,
};
use Espo\Core\{
InjectableFactory,
Utils\ClassFinder,
Utils\Config,
ORM\EntityManager,
AclManager as InternalAclManagerService,
};
class InternalAclManager implements Loader
{
public function __construct(Container $container)
{
$this->container = $container;
protected $injectableFactory;
protected $classFinder;
protected $config;
protected $entityManager;
public function __construct(
InjectableFactory $injectableFactory, ClassFinder $classFinder, Config $config, EntityManager $entityManager
) {
$this->injectableFactory = $injectableFactory;
$this->classFinder = $classFinder;
$this->config = $config;
$this->entityManager = $entityManager;
}
public function load()
{
return new \Espo\Core\AclManager($this->container);
return new InternalAclManagerService($this->injectableFactory, $this->classFinder, $this->config, $this->entityManager);
}
}

View File

@@ -55,7 +55,8 @@ class Stream extends \Espo\Core\Services\Base
'metadata',
'acl',
'aclManager',
'container'
'container',
'portalAclManagerContainer',
]);
}
@@ -1725,10 +1726,9 @@ class Stream extends \Espo\Core\Services\Base
$aclManager = $this->getAclManager();
if ($user->isPortal() && !$this->getUser()->isPortal()) {
$aclManager = new \Espo\Core\Portal\AclManager($this->getInjection('container'));
$portals = $user->get('portals');
if (count($portals)) {
$aclManager->setPortal($portals[0]);
$aclManager = $this->getInjection('portalAclManagerContainer')->get($portals[0]);
} else {
$aclManager = null;
}