mirror of
https://github.com/espocrm/espocrm.git
synced 2026-06-28 06:56:05 +00:00
acl recactoring
This commit is contained in:
@@ -54,7 +54,7 @@ class User extends \Espo\Core\Controllers\Record
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
return $this->getAclManager()->getMap($user);
|
||||
return $this->getAclManager()->getMapData($user);
|
||||
}
|
||||
|
||||
public function postActionChangeOwnPassword($params, $data, $request)
|
||||
|
||||
@@ -57,9 +57,9 @@ class Acl
|
||||
/**
|
||||
* Get a full access data map.
|
||||
*/
|
||||
public function getMap(): StdClass
|
||||
public function getMapData(): StdClass
|
||||
{
|
||||
return $this->aclManager->getMap($this->user);
|
||||
return $this->aclManager->getMapData($this->user);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,16 +29,14 @@
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\{
|
||||
ORM\EntityManager,
|
||||
Acl\Table\RoleListProvider,
|
||||
Acl\Table\CacheKeyProvider,
|
||||
Utils\Config,
|
||||
Utils\Metadata,
|
||||
Utils\FieldUtil,
|
||||
Utils\DataCache,
|
||||
Utils\ObjectUtil,
|
||||
};
|
||||
|
||||
use StdClass;
|
||||
@@ -50,17 +48,13 @@ use RuntimeException;
|
||||
*/
|
||||
class DefaultTable implements Table
|
||||
{
|
||||
protected const LEVEL_NOT_SET = 'not-set';
|
||||
private const LEVEL_NOT_SET = 'not-set';
|
||||
|
||||
protected $type = 'acl';
|
||||
|
||||
protected $defaultAclType = 'recordAllTeamOwnNo';
|
||||
|
||||
private $data = null;
|
||||
|
||||
protected $cacheKey;
|
||||
|
||||
protected $actionList = [
|
||||
private $actionList = [
|
||||
self::ACTION_READ,
|
||||
self::ACTION_STREAM,
|
||||
self::ACTION_EDIT,
|
||||
@@ -68,7 +62,7 @@ class DefaultTable implements Table
|
||||
self::ACTION_CREATE,
|
||||
];
|
||||
|
||||
protected $booleanActionList = [
|
||||
private $booleanActionList = [
|
||||
self::ACTION_CREATE,
|
||||
];
|
||||
|
||||
@@ -80,7 +74,7 @@ class DefaultTable implements Table
|
||||
self::LEVEL_NO,
|
||||
];
|
||||
|
||||
protected $fieldActionList = [
|
||||
private $fieldActionList = [
|
||||
self::ACTION_READ,
|
||||
self::ACTION_EDIT,
|
||||
];
|
||||
@@ -90,44 +84,36 @@ class DefaultTable implements Table
|
||||
self::LEVEL_NO,
|
||||
];
|
||||
|
||||
protected $valuePermissionHighestLevels = [];
|
||||
|
||||
protected $valuePermissionList = [];
|
||||
|
||||
protected $forbiddenAttributesCache = [];
|
||||
|
||||
protected $forbiddenFieldsCache = [];
|
||||
|
||||
protected $isStrictModeForced = false;
|
||||
|
||||
protected $isStrictMode = false;
|
||||
private $isStrictMode = false;
|
||||
|
||||
protected $entityManager;
|
||||
private $data = null;
|
||||
|
||||
private $cacheKey;
|
||||
|
||||
private $valuePermissionList = [];
|
||||
|
||||
private $roleListProvider;
|
||||
|
||||
protected $user;
|
||||
|
||||
protected $config;
|
||||
|
||||
protected $metadata;
|
||||
|
||||
protected $fieldUtil;
|
||||
|
||||
protected $dataCache;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
RoleListProvider $roleListProvider,
|
||||
CacheKeyProvider $cacheKeyProvider,
|
||||
User $user,
|
||||
Config $config,
|
||||
Metadata $metadata,
|
||||
FieldUtil $fieldUtil,
|
||||
DataCache $dataCache
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->roleListProvider = $roleListProvider;
|
||||
|
||||
$this->data = (object) [
|
||||
'table' => (object) [],
|
||||
'fieldTable' => (object) [],
|
||||
'fieldTableQuickAccess' => (object) [],
|
||||
'scopes' => (object) [],
|
||||
'fields' => (object) [],
|
||||
'permissions' => (object) [],
|
||||
];
|
||||
|
||||
if ($this->isStrictModeForced) {
|
||||
@@ -139,8 +125,6 @@ class DefaultTable implements Table
|
||||
|
||||
$this->user = $user;
|
||||
$this->metadata = $metadata;
|
||||
$this->fieldUtil = $fieldUtil;
|
||||
$this->dataCache = $dataCache;
|
||||
|
||||
if (!$this->user->isFetched()) {
|
||||
throw new RuntimeException('User must be fetched before ACL check.');
|
||||
@@ -149,46 +133,30 @@ class DefaultTable implements Table
|
||||
$this->valuePermissionList = $this->metadata
|
||||
->get(['app', $this->type, 'valuePermissionList'], []);
|
||||
|
||||
$this->valuePermissionHighestLevels = $this->metadata
|
||||
->get(['app', $this->type, 'valuePermissionHighestLevels'], []);
|
||||
$this->cacheKey = $cacheKeyProvider->get();
|
||||
|
||||
$this->initCacheKey();
|
||||
|
||||
if ($config && $config->get('useCache') && $this->dataCache->has($this->cacheKey)) {
|
||||
$this->data = $this->dataCache->get($this->cacheKey);
|
||||
if ($config->get('useCache') && $dataCache->has($this->cacheKey)) {
|
||||
$this->data = $dataCache->get($this->cacheKey);
|
||||
}
|
||||
else {
|
||||
$this->load();
|
||||
|
||||
if ($config && $config->get('useCache')) {
|
||||
$this->buildCache();
|
||||
if ($config->get('useCache')) {
|
||||
$dataCache->store($this->cacheKey, $this->data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function initCacheKey(): void
|
||||
{
|
||||
$this->cacheKey = 'acl/' . $this->user->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a full map.
|
||||
*/
|
||||
public function getMap(): StdClass
|
||||
{
|
||||
return ObjectUtil::clone($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get scope data.
|
||||
*/
|
||||
public function getScopeData(string $scope): ScopeData
|
||||
{
|
||||
if (!isset($this->data->table->$scope)) {
|
||||
if (!isset($this->data->scopes->$scope)) {
|
||||
return ScopeData::fromRaw(false);
|
||||
}
|
||||
|
||||
$data = $this->data->table->$scope;
|
||||
$data = $this->data->scopes->$scope;
|
||||
|
||||
if (is_string($data)) {
|
||||
return $this->getScopeData($data);
|
||||
@@ -197,14 +165,32 @@ class DefaultTable implements Table
|
||||
return ScopeData::fromRaw($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field data.
|
||||
*/
|
||||
public function getFieldData(string $scope, string $field): FieldData
|
||||
{
|
||||
if (!isset($this->data->fields->$scope)) {
|
||||
return FieldData::fromRaw((object) [
|
||||
self::ACTION_READ => self::LEVEL_YES,
|
||||
self::ACTION_EDIT => self::LEVEL_YES,
|
||||
]);
|
||||
}
|
||||
|
||||
$data = $this->data->fields->$scope->$field ?? (object) [
|
||||
self::ACTION_READ => self::LEVEL_YES,
|
||||
self::ACTION_EDIT => self::LEVEL_YES,
|
||||
];
|
||||
|
||||
return FieldData::fromRaw($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a permission level.
|
||||
*/
|
||||
public function getPermissionLevel(string $permission): string
|
||||
{
|
||||
$key = $permission . 'Permission';
|
||||
|
||||
return $this->data->$key ?? self::LEVEL_NO;
|
||||
return $this->data->permissions->$permission ?? self::LEVEL_NO;
|
||||
}
|
||||
|
||||
private function load(): void
|
||||
@@ -219,14 +205,16 @@ class DefaultTable implements Table
|
||||
$fieldTableList = [];
|
||||
|
||||
if (!$this->user->isAdmin()) {
|
||||
$roleList = $this->getRoleList();
|
||||
$roleList = $this->roleListProvider->get();
|
||||
|
||||
foreach ($roleList as $role) {
|
||||
$aclTableList[] = $role->get('data');
|
||||
$fieldTableList[] = $role->get('fieldData');
|
||||
$aclTableList[] = $role->getScopeTableData();
|
||||
$fieldTableList[] = $role->getFieldTableData();
|
||||
|
||||
foreach ($this->valuePermissionList as $permission) {
|
||||
$valuePermissionLists->{$permission}[] = $role->get($permission);
|
||||
foreach ($this->valuePermissionList as $permissionKey) {
|
||||
$permission = $this->normilizePermissionName($permissionKey);
|
||||
|
||||
$valuePermissionLists->{$permissionKey}[] = $role->getPermissionLevel($permission);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,10 +240,8 @@ class DefaultTable implements Table
|
||||
}
|
||||
}
|
||||
|
||||
$this->data->table = $aclTable;
|
||||
$this->data->fieldTable = $fieldTable;
|
||||
|
||||
$this->fillFieldTableQuickAccess();
|
||||
$this->data->scopes = $aclTable;
|
||||
$this->data->fields = $fieldTable;
|
||||
|
||||
if (!$this->user->isAdmin()) {
|
||||
$permissionsDefaultsGroupName = 'permissionsDefaults';
|
||||
@@ -264,221 +250,53 @@ class DefaultTable implements Table
|
||||
$permissionsDefaultsGroupName = 'permissionsStrictDefaults';
|
||||
}
|
||||
|
||||
foreach ($this->valuePermissionList as $permission) {
|
||||
$this->data->$permission = $this->mergeValueList(
|
||||
$valuePermissionLists->$permission,
|
||||
$this->metadata
|
||||
->get(['app', $this->type, $permissionsDefaultsGroupName, $permission, self::LEVEL_YES])
|
||||
foreach ($this->valuePermissionList as $permissionKey) {
|
||||
$permission = $this->normilizePermissionName($permissionKey);
|
||||
|
||||
$defaultLevel = $this->metadata
|
||||
->get(['app', $this->type, $permissionsDefaultsGroupName, $permissionKey]) ??
|
||||
($this->isStrictMode ? self::LEVEL_NO : self::LEVEL_YES);
|
||||
|
||||
$this->data->permissions->$permission = $this->mergeValueList(
|
||||
$valuePermissionLists->$permissionKey,
|
||||
$defaultLevel
|
||||
);
|
||||
|
||||
if ($this->metadata->get('app.'.$this->type.'.mandatory.' . $permission)) {
|
||||
$this->data->$permission = $this->metadata
|
||||
->get('app.'.$this->type.'.mandatory.' . $permission);
|
||||
$mandatoryLevel = $this->metadata->get(['app', $this->type, 'mandatory', $permissionKey]);
|
||||
|
||||
if ($mandatoryLevel !== null) {
|
||||
$this->data->permissions->$permission = $mandatoryLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->user->isAdmin()) {
|
||||
foreach ($this->valuePermissionList as $permission) {
|
||||
if (isset($this->valuePermissionHighestLevels[$permission])) {
|
||||
$this->data->$permission = $this->valuePermissionHighestLevels[$permission];
|
||||
foreach ($this->valuePermissionList as $permissionKey) {
|
||||
$permission = $this->normilizePermissionName($permissionKey);
|
||||
|
||||
$highestLevel = $this->metadata
|
||||
->get(['app', $this->type, 'valuePermissionHighestLevels', $permissionKey]);
|
||||
|
||||
if ($highestLevel !== null) {
|
||||
$this->data->permissions->$permission = $highestLevel;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->data->$permission = self::LEVEL_ALL;
|
||||
$this->data->permissions->$permission = self::LEVEL_ALL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getRoleList(): array
|
||||
private function normilizePermissionName(string $permissionKey): string
|
||||
{
|
||||
$roleList = [];
|
||||
$permission = $permissionKey;
|
||||
|
||||
$userRoleList = $this->entityManager
|
||||
->getRepository('User')
|
||||
->getRelation($this->user, 'roles')
|
||||
->find();
|
||||
|
||||
foreach ($userRoleList as $role) {
|
||||
$roleList[] = $role;
|
||||
if (substr($permissionKey, -10) === 'Permission') {
|
||||
$permission = substr($permissionKey, 0, -10);
|
||||
}
|
||||
|
||||
$teamList = $this->entityManager
|
||||
->getRepository('User')
|
||||
->getRelation($this->user, 'teams')
|
||||
->find();
|
||||
|
||||
foreach ($teamList as $team) {
|
||||
$teamRoleList = $this->entityManager
|
||||
->getRepository('Team')
|
||||
->getRelation($team, 'roles')
|
||||
->find();
|
||||
|
||||
foreach ($teamRoleList as $role) {
|
||||
$roleList[] = $role;
|
||||
}
|
||||
}
|
||||
|
||||
return $roleList;
|
||||
}
|
||||
|
||||
public function getScopeForbiddenAttributeList(
|
||||
string $scope,
|
||||
string $action = self::ACTION_READ,
|
||||
string $thresholdLevel = self::LEVEL_NO
|
||||
): array {
|
||||
|
||||
if (!in_array($thresholdLevel, $this->fieldLevelList) || $thresholdLevel === self::LEVEL_YES) {
|
||||
throw new RuntimeException("Bad threshold level.");
|
||||
}
|
||||
|
||||
$key = $scope . '_'. $action . '_' . $thresholdLevel;
|
||||
|
||||
if (isset($this->forbiddenAttributesCache[$key])) {
|
||||
return $this->forbiddenAttributesCache[$key];
|
||||
}
|
||||
|
||||
$fieldTableQuickAccess = $this->data->fieldTableQuickAccess;
|
||||
|
||||
if (
|
||||
!isset($fieldTableQuickAccess->$scope) || !isset($fieldTableQuickAccess->$scope->attributes) ||
|
||||
!isset($fieldTableQuickAccess->$scope->attributes->$action)
|
||||
) {
|
||||
$this->forbiddenAttributesCache[$key] = [];
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$levelList = [];
|
||||
|
||||
foreach ($this->fieldLevelList as $level) {
|
||||
if (array_search($level, $this->fieldLevelList) >= array_search($thresholdLevel, $this->fieldLevelList)) {
|
||||
$levelList[] = $level;
|
||||
}
|
||||
}
|
||||
|
||||
$attributeList = [];
|
||||
|
||||
foreach ($levelList as $level) {
|
||||
if (!isset($fieldTableQuickAccess->$scope->attributes->$action->$level)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($fieldTableQuickAccess->$scope->attributes->$action->$level as $attribute) {
|
||||
if (in_array($attribute, $attributeList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributeList[] = $attribute;
|
||||
}
|
||||
}
|
||||
|
||||
$this->forbiddenAttributesCache[$key] = $attributeList;
|
||||
|
||||
return $attributeList;
|
||||
}
|
||||
|
||||
public function getScopeForbiddenFieldList(
|
||||
string $scope,
|
||||
string $action = self::ACTION_READ,
|
||||
string $thresholdLevel = self::LEVEL_NO
|
||||
): array {
|
||||
|
||||
if (!in_array($thresholdLevel, $this->fieldLevelList) || $thresholdLevel === self::LEVEL_YES) {
|
||||
throw new RuntimeException("Bad threshold level.");
|
||||
}
|
||||
|
||||
$key = $scope . '_'. $action . '_' . $thresholdLevel;
|
||||
|
||||
if (isset($this->forbiddenFieldsCache[$key])) {
|
||||
return $this->forbiddenFieldsCache[$key];
|
||||
}
|
||||
|
||||
$fieldTableQuickAccess = $this->data->fieldTableQuickAccess;
|
||||
|
||||
if (
|
||||
!isset($fieldTableQuickAccess->$scope) || !isset($fieldTableQuickAccess->$scope->fields) ||
|
||||
!isset($fieldTableQuickAccess->$scope->fields->$action)
|
||||
) {
|
||||
$this->forbiddenFieldsCache[$key] = [];
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$levelList = [];
|
||||
|
||||
foreach ($this->fieldLevelList as $level) {
|
||||
if (array_search($level, $this->fieldLevelList) >= array_search($thresholdLevel, $this->fieldLevelList)) {
|
||||
$levelList[] = $level;
|
||||
}
|
||||
}
|
||||
|
||||
$fieldList = [];
|
||||
|
||||
foreach ($levelList as $level) {
|
||||
if (!isset($fieldTableQuickAccess->$scope->fields->$action->$level)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($fieldTableQuickAccess->$scope->fields->$action->$level as $field) {
|
||||
if (in_array($field, $fieldList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldList[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
$this->forbiddenFieldsCache[$key] = $fieldList;
|
||||
|
||||
return $fieldList;
|
||||
}
|
||||
|
||||
protected function fillFieldTableQuickAccess(): void
|
||||
{
|
||||
$fieldTable = $this->data->fieldTable;
|
||||
|
||||
$fieldTableQuickAccess = (object) [];
|
||||
|
||||
foreach (get_object_vars($fieldTable) as $scope => $scopeData) {
|
||||
$fieldTableQuickAccess->$scope = (object) [
|
||||
'attributes' => (object) [],
|
||||
'fields' => (object) []
|
||||
];
|
||||
|
||||
foreach ($this->fieldActionList as $action) {
|
||||
$fieldTableQuickAccess->$scope->attributes->$action = (object) [];
|
||||
$fieldTableQuickAccess->$scope->fields->$action = (object) [];
|
||||
|
||||
foreach ($this->fieldLevelList as $level) {
|
||||
$fieldTableQuickAccess->$scope->attributes->$action->$level = [];
|
||||
$fieldTableQuickAccess->$scope->fields->$action->$level = [];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (get_object_vars($scopeData) as $field => $fieldData) {
|
||||
$attributeList = $this->fieldUtil->getAttributeList($scope, $field);
|
||||
|
||||
foreach ($this->fieldActionList as $action) {
|
||||
if (!isset($fieldData->$action)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->fieldLevelList as $level) {
|
||||
if ($fieldData->$action === $level) {
|
||||
$fieldTableQuickAccess->$scope->fields->$action->{$level}[] = $field;
|
||||
|
||||
foreach ($attributeList as $attribute) {
|
||||
$fieldTableQuickAccess->$scope->attributes->$action->{$level}[] = $attribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->data->fieldTableQuickAccess = $fieldTableQuickAccess;
|
||||
return $permission;
|
||||
}
|
||||
|
||||
protected function applyHighest(StdClass &$table, StdClass &$fieldTable): void
|
||||
@@ -544,21 +362,22 @@ class DefaultTable implements Table
|
||||
$table->$scope = $value;
|
||||
}
|
||||
|
||||
$defaultFieldData = $this->metadata->get(['app', $this->type, $defaultsGroupName, 'fieldLevel'], []);
|
||||
$defaultFieldData = $this->metadata
|
||||
->get(['app', $this->type, $defaultsGroupName, 'fieldLevel']) ?? [];
|
||||
|
||||
foreach ($this->getScopeList() as $scope) {
|
||||
if (isset($table->$scope) && $table->$scope === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->metadata->get('scopes.' . $scope . '.entity')) {
|
||||
if (!$this->metadata->get(['scopes', $scope, 'entity'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldList = array_keys($this->metadata->get("entityDefs.{$scope}.fields", []));
|
||||
$fieldList = array_keys($this->metadata->get(['entityDefs', $scope, 'fields']) ?? []);
|
||||
|
||||
$defaultScopeFieldData = $this->metadata
|
||||
->get('app.'.$this->type.'.'.$defaultsGroupName.'.scopeFieldLevel.' . $scope, []);
|
||||
->get(['app', $this->type, $defaultsGroupName, 'scopeFieldLevel', $scope]) ?? [];
|
||||
|
||||
foreach (array_merge($defaultFieldData, $defaultScopeFieldData) as $field => $f) {
|
||||
if (!in_array($field, $fieldList)) {
|
||||
@@ -593,41 +412,43 @@ class DefaultTable implements Table
|
||||
}
|
||||
|
||||
foreach ($this->getScopeWithAclList() as $scope) {
|
||||
if (!isset($table->$scope)) {
|
||||
$aclType = $this->metadata->get('scopes.' . $scope . '.' . $this->type);
|
||||
if (isset($table->$scope)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($aclType === true) {
|
||||
$aclType = $this->defaultAclType;
|
||||
}
|
||||
$aclType = $this->metadata->get(['scopes', $scope, $this->type]);
|
||||
|
||||
if (!empty($aclType)) {
|
||||
$paramDefaultsName = 'scopeLevelTypesDefaults';
|
||||
if ($aclType === true) {
|
||||
$aclType = $this->defaultAclType;
|
||||
}
|
||||
|
||||
if ($this->isStrictMode) {
|
||||
$paramDefaultsName = 'scopeLevelTypesStrictDefaults';
|
||||
}
|
||||
if (empty($aclType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$defaultValue = $this->metadata
|
||||
->get(
|
||||
['app', $this->type, $paramDefaultsName, $aclType],
|
||||
$this->metadata->get(['app', $this->type, $paramDefaultsName, 'record'])
|
||||
);
|
||||
$paramDefaultsName = 'scopeLevelTypesDefaults';
|
||||
|
||||
if (is_array($defaultValue)) {
|
||||
$defaultValue = (object) $defaultValue;
|
||||
}
|
||||
if ($this->isStrictMode) {
|
||||
$paramDefaultsName = 'scopeLevelTypesStrictDefaults';
|
||||
}
|
||||
|
||||
$table->$scope = $defaultValue;
|
||||
$defaultValue =
|
||||
$this->metadata->get(['app', $this->type, $paramDefaultsName, $aclType]) ??
|
||||
$this->metadata->get(['app', $this->type, $paramDefaultsName, 'record']);
|
||||
|
||||
if (is_object($table->$scope)) {
|
||||
$actionList = $this->metadata->get(['scopes', $scope, $this->type . 'ActionList']);
|
||||
if (is_array($defaultValue)) {
|
||||
$defaultValue = (object) $defaultValue;
|
||||
}
|
||||
|
||||
if ($actionList) {
|
||||
foreach (get_object_vars($table->$scope) as $action => $level) {
|
||||
if (!in_array($action, $actionList)) {
|
||||
unset($table->$scope->$action);
|
||||
}
|
||||
}
|
||||
$table->$scope = $defaultValue;
|
||||
|
||||
if (is_object($table->$scope)) {
|
||||
$actionList = $this->metadata->get(['scopes', $scope, $this->type . 'ActionList']);
|
||||
|
||||
if ($actionList) {
|
||||
foreach (get_object_vars($table->$scope) as $action => $level) {
|
||||
if (!in_array($action, $actionList)) {
|
||||
unset($table->$scope->$action);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -641,7 +462,7 @@ class DefaultTable implements Table
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $this->metadata->get('app.'.$this->type.'.mandatory.scopeLevel', []);
|
||||
$data = $this->metadata->get(['app', $this->type, 'mandatory', 'scopeLevel']) ?? [];
|
||||
|
||||
foreach ($data as $scope => $item) {
|
||||
$value = $item;
|
||||
@@ -653,23 +474,23 @@ class DefaultTable implements Table
|
||||
$table->$scope = $value;
|
||||
}
|
||||
|
||||
$mandatoryFieldData = $this->metadata->get('app.'.$this->type.'.mandatory.fieldLevel', []);
|
||||
$mandatoryFieldData = $this->metadata->get(['app', $this->type, 'mandatory', 'fieldLevel']) ?? [];
|
||||
|
||||
foreach ($this->getScopeList() as $scope) {
|
||||
if (isset($table->$scope) && $table->$scope === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->metadata->get('scopes.' . $scope . '.entity')) {
|
||||
if (!$this->metadata->get(['scopes', $scope, 'entity'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldList = array_keys($this->metadata->get("entityDefs.{$scope}.fields", []));
|
||||
$fieldList = array_keys($this->metadata->get(['entityDefs', $scope, 'fields']) ?? []);
|
||||
|
||||
$mandatoryScopeFieldData = $this->metadata
|
||||
->get('app.'.$this->type.'.mandatory.scopeFieldLevel.' . $scope, []);
|
||||
->get(['app', $this->type, 'mandatory', 'scopeFieldLevel', $scope]) ?? [];
|
||||
|
||||
foreach (array_merge($mandatoryFieldData, $mandatoryScopeFieldData) as $field => $f) {
|
||||
foreach (array_merge($mandatoryFieldData, $mandatoryScopeFieldData) as $field => $item) {
|
||||
if (!in_array($field, $fieldList)) {
|
||||
continue;
|
||||
}
|
||||
@@ -683,12 +504,12 @@ class DefaultTable implements Table
|
||||
foreach ($this->fieldActionList as $action) {
|
||||
$level = self::LEVEL_NO;
|
||||
|
||||
if ($f === true) {
|
||||
if ($item === true) {
|
||||
$level = self::LEVEL_YES;
|
||||
}
|
||||
else {
|
||||
if (is_array($f) && isset($f[$action])) {
|
||||
$level = $f[$action];
|
||||
if (is_array($item) && isset($item[$action])) {
|
||||
$level = $item[$action];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,7 +526,7 @@ class DefaultTable implements Table
|
||||
}
|
||||
|
||||
foreach ($this->getScopeList() as $scope) {
|
||||
if ($this->metadata->get('scopes.' . $scope . '.disabled')) {
|
||||
if ($this->metadata->get(['scopes', $scope, 'disabled'])) {
|
||||
$table->$scope = false;
|
||||
|
||||
unset($fieldTable->$scope);
|
||||
@@ -713,6 +534,9 @@ class DefaultTable implements Table
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Revise usage of this method.
|
||||
*/
|
||||
protected function applyAdditional(&$table, &$fieldTable, &$valuePermissionLists): void
|
||||
{
|
||||
if ($this->user->isPortal()) {
|
||||
@@ -836,7 +660,7 @@ class DefaultTable implements Table
|
||||
}
|
||||
|
||||
$actionList = $this->metadata
|
||||
->get(['scopes', $scope, $this->type . 'ActionList'], $this->actionList);
|
||||
->get(['scopes', $scope, $this->type . 'ActionList']) ?? $this->actionList;
|
||||
|
||||
foreach ($actionList as $i => $action) {
|
||||
if (isset($row->$action)) {
|
||||
@@ -891,7 +715,7 @@ class DefaultTable implements Table
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldList = array_keys($this->metadata->get("entityDefs.{$scope}.fields", []));
|
||||
$fieldList = array_keys($this->metadata->get(['entityDefs', $scope, 'fields']) ?? []);
|
||||
|
||||
foreach (get_object_vars($table->$scope) as $field => $row) {
|
||||
if (!is_object($row)) {
|
||||
@@ -933,9 +757,4 @@ class DefaultTable implements Table
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function buildCache(): void
|
||||
{
|
||||
$this->dataCache->store($this->cacheKey, $this->data);
|
||||
}
|
||||
}
|
||||
|
||||
96
application/Espo/Core/Acl/FieldData.php
Normal file
96
application/Espo/Core/Acl/FieldData.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl;
|
||||
|
||||
use StdClass;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Field data.
|
||||
*/
|
||||
class FieldData
|
||||
{
|
||||
private $raw;
|
||||
|
||||
private $actionData = [];
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function __get(string $name)
|
||||
{
|
||||
throw new RuntimeException("Accessing ScopeData properties is not allowed.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a level for an action.
|
||||
*/
|
||||
public function get(string $action): string
|
||||
{
|
||||
return $this->actionData[$action] ?? Table::LEVEL_NO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a 'read' level.
|
||||
*/
|
||||
public function getRead(): string
|
||||
{
|
||||
return $this->get(Table::ACTION_READ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an 'edit' level.
|
||||
*/
|
||||
public function getEdit(): string
|
||||
{
|
||||
return $this->get(Table::ACTION_EDIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from a raw table value.
|
||||
*/
|
||||
public static function fromRaw(StdClass $raw): self
|
||||
{
|
||||
$obj = new self();
|
||||
|
||||
$obj->actionData = get_object_vars($raw);
|
||||
|
||||
foreach ($obj->actionData as $item) {
|
||||
if (!is_string($item)) {
|
||||
throw new RuntimeException("Bad raw scope data.");
|
||||
}
|
||||
}
|
||||
|
||||
$obj->raw = $raw;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
35
application/Espo/Core/Acl/Map/CacheKeyProvider.php
Normal file
35
application/Espo/Core/Acl/Map/CacheKeyProvider.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl\Map;
|
||||
|
||||
interface CacheKeyProvider
|
||||
{
|
||||
public function get(): string;
|
||||
}
|
||||
194
application/Espo/Core/Acl/Map/DataBuilder.php
Normal file
194
application/Espo/Core/Acl/Map/DataBuilder.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl\Map;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\Table,
|
||||
Utils\FieldUtil,
|
||||
};
|
||||
|
||||
use StdClass;
|
||||
|
||||
class DataBuilder
|
||||
{
|
||||
private $actionList = [
|
||||
Table::ACTION_READ,
|
||||
Table::ACTION_STREAM,
|
||||
Table::ACTION_EDIT,
|
||||
Table::ACTION_DELETE,
|
||||
Table::ACTION_CREATE,
|
||||
];
|
||||
|
||||
private $fieldActionList = [
|
||||
Table::ACTION_READ,
|
||||
Table::ACTION_EDIT,
|
||||
];
|
||||
|
||||
private $fieldLevelList = [
|
||||
Table::LEVEL_YES,
|
||||
Table::LEVEL_NO,
|
||||
];
|
||||
|
||||
private $metadataProvider;
|
||||
|
||||
private $fieldUtil;
|
||||
|
||||
public function __construct(MetadataProvider $metadataProvider, FieldUtil $fieldUtil)
|
||||
{
|
||||
$this->metadataProvider = $metadataProvider;
|
||||
$this->fieldUtil = $fieldUtil;
|
||||
}
|
||||
|
||||
public function build(Table $table): StdClass
|
||||
{
|
||||
$data = (object) [
|
||||
'table' => (object) [],
|
||||
'fieldTable' => (object) [],
|
||||
];
|
||||
|
||||
foreach ($this->metadataProvider->getScopeList() as $scope) {
|
||||
$data->table->$scope = $this->getScopeRawData($table, $scope);
|
||||
|
||||
$fieldData = $this->getScopeFieldData($table, $scope);
|
||||
|
||||
if ($fieldData !== null) {
|
||||
$data->fieldTable->$scope = $fieldData;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->metadataProvider->getPermissionList() as $permission) {
|
||||
$data->{$permission . 'Permission'} = $table->getPermissionLevel($permission);
|
||||
}
|
||||
|
||||
$data->fieldTableQuickAccess = $this->buildFieldTableQuickAccess($data->fieldTable);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|StdClass
|
||||
*/
|
||||
private function getScopeRawData(Table $table, string $scope)
|
||||
{
|
||||
$data = $table->getScopeData($scope);
|
||||
|
||||
if ($data->isBoolean()) {
|
||||
return $data->isTrue();
|
||||
}
|
||||
|
||||
$rawData = (object) [];
|
||||
|
||||
foreach ($this->actionList as $action) {
|
||||
$rawData->$action = $data->get($action);
|
||||
}
|
||||
|
||||
return $rawData;
|
||||
}
|
||||
|
||||
private function getScopeFieldData(Table $table, string $scope): ?StdClass
|
||||
{
|
||||
if (!$this->metadataProvider->isScopeEntity($scope)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$fieldList = $this->metadataProvider->getScopeFieldList($scope);
|
||||
|
||||
$rawData = (object) [];
|
||||
|
||||
foreach ($fieldList as $field) {
|
||||
$data = $table->getFieldData($scope, $field);
|
||||
|
||||
if (
|
||||
$data->getRead() === Table::LEVEL_YES &&
|
||||
$data->getEdit() === Table::LEVEL_YES
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rawData->$field = (object) [
|
||||
Table::ACTION_READ => $data->getRead(),
|
||||
Table::ACTION_EDIT => $data->getEdit(),
|
||||
];
|
||||
}
|
||||
|
||||
return $rawData;
|
||||
}
|
||||
|
||||
protected function buildFieldTableQuickAccess(StdClass $fieldTable): StdClass
|
||||
{
|
||||
$quickAccess = (object) [];
|
||||
|
||||
foreach (get_object_vars($fieldTable) as $scope => $scopeData) {
|
||||
$quickAccess->$scope = $this->buildFieldTableQuickAccessScope($scope, $scopeData);
|
||||
}
|
||||
|
||||
return $quickAccess;
|
||||
}
|
||||
|
||||
private function buildFieldTableQuickAccessScope(string $scope, StdClass $data): StdClass
|
||||
{
|
||||
$quickAccess = (object) [
|
||||
'attributes' => (object) [],
|
||||
'fields' => (object) [],
|
||||
];
|
||||
|
||||
foreach ($this->fieldActionList as $action) {
|
||||
$quickAccess->attributes->$action = (object) [];
|
||||
$quickAccess->fields->$action = (object) [];
|
||||
|
||||
foreach ($this->fieldLevelList as $level) {
|
||||
$quickAccess->attributes->$action->$level = [];
|
||||
$quickAccess->fields->$action->$level = [];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (get_object_vars($data) as $field => $fieldData) {
|
||||
$attributeList = $this->fieldUtil->getAttributeList($scope, $field);
|
||||
|
||||
foreach ($this->fieldActionList as $action) {
|
||||
if (!isset($fieldData->$action)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->fieldLevelList as $level) {
|
||||
if ($fieldData->$action === $level) {
|
||||
$quickAccess->fields->$action->{$level}[] = $field;
|
||||
|
||||
foreach ($attributeList as $attribute) {
|
||||
$quickAccess->attributes->$action->{$level}[] = $attribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $quickAccess;
|
||||
}
|
||||
}
|
||||
47
application/Espo/Core/Acl/Map/DefaultCacheKeyProvider.php
Normal file
47
application/Espo/Core/Acl/Map/DefaultCacheKeyProvider.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl\Map;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
class DefaultCacheKeyProvider implements CacheKeyProvider
|
||||
{
|
||||
private $user;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function get(): string
|
||||
{
|
||||
return 'aclMap/' . $this->user->getId();
|
||||
}
|
||||
}
|
||||
251
application/Espo/Core/Acl/Map/Map.php
Normal file
251
application/Espo/Core/Acl/Map/Map.php
Normal file
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl\Map;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\Table,
|
||||
Utils\Config,
|
||||
Utils\DataCache,
|
||||
Utils\ObjectUtil,
|
||||
};
|
||||
|
||||
use StdClass;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Provides quick access to ACL data.
|
||||
*/
|
||||
class Map
|
||||
{
|
||||
private $data;
|
||||
|
||||
private $cacheKey;
|
||||
|
||||
private $forbiddenFieldsCache = [];
|
||||
|
||||
private $forbiddenAttributesCache;
|
||||
|
||||
private $fieldLevelList = [
|
||||
Table::LEVEL_YES,
|
||||
Table::LEVEL_NO,
|
||||
];
|
||||
|
||||
private $user;
|
||||
|
||||
private $table;
|
||||
|
||||
private $config;
|
||||
|
||||
private $dataCache;
|
||||
|
||||
private $dataBuilder;
|
||||
|
||||
public function __construct(
|
||||
User $user,
|
||||
Table $table,
|
||||
DataBuilder $dataBuilder,
|
||||
Config $config,
|
||||
DataCache $dataCache,
|
||||
CacheKeyProvider $cacheKeyProvider
|
||||
) {
|
||||
$this->user = $user;
|
||||
$this->table = $table;
|
||||
$this->dataBuilder = $dataBuilder;
|
||||
$this->config = $config;
|
||||
$this->dataCache = $dataCache;
|
||||
|
||||
$this->cacheKey = $cacheKeyProvider->get();
|
||||
|
||||
if ($this->config->get('useCache') && $this->dataCache->has($this->cacheKey)) {
|
||||
$this->data = $this->dataCache->get($this->cacheKey);
|
||||
}
|
||||
else {
|
||||
$this->data = $this->dataBuilder->build($table);
|
||||
|
||||
if ($this->config->get('useCache')) {
|
||||
$this->dataCache->store($this->cacheKey, $this->data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw data (for front-end).
|
||||
*/
|
||||
public function getData(): StdClass
|
||||
{
|
||||
return ObjectUtil::clone($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of forbidden attributes for a scope and action.
|
||||
*
|
||||
* @param $scope A scope.
|
||||
* @param $action An action.
|
||||
* @param $thresholdLevel An attribute will be treated as forbidden if the level is
|
||||
* equal to or lower than the threshold.
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getScopeForbiddenAttributeList(
|
||||
string $scope,
|
||||
string $action = Table::ACTION_READ,
|
||||
string $thresholdLevel = Table::LEVEL_NO
|
||||
): array {
|
||||
|
||||
if (
|
||||
!in_array($thresholdLevel, $this->fieldLevelList) ||
|
||||
$thresholdLevel === Table::LEVEL_YES
|
||||
) {
|
||||
throw new RuntimeException("Bad threshold level.");
|
||||
}
|
||||
|
||||
$key = $scope . '_'. $action . '_' . $thresholdLevel;
|
||||
|
||||
if (isset($this->forbiddenAttributesCache[$key])) {
|
||||
return $this->forbiddenAttributesCache[$key];
|
||||
}
|
||||
|
||||
$fieldTableQuickAccess = $this->data->fieldTableQuickAccess;
|
||||
|
||||
if (
|
||||
!isset($fieldTableQuickAccess->$scope) ||
|
||||
!isset($fieldTableQuickAccess->$scope->attributes) ||
|
||||
!isset($fieldTableQuickAccess->$scope->attributes->$action)
|
||||
) {
|
||||
$this->forbiddenAttributesCache[$key] = [];
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$levelList = [];
|
||||
|
||||
foreach ($this->fieldLevelList as $level) {
|
||||
if (
|
||||
array_search($level, $this->fieldLevelList) >=
|
||||
array_search($thresholdLevel, $this->fieldLevelList)
|
||||
) {
|
||||
$levelList[] = $level;
|
||||
}
|
||||
}
|
||||
|
||||
$attributeList = [];
|
||||
|
||||
foreach ($levelList as $level) {
|
||||
if (!isset($fieldTableQuickAccess->$scope->attributes->$action->$level)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($fieldTableQuickAccess->$scope->attributes->$action->$level as $attribute) {
|
||||
if (in_array($attribute, $attributeList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributeList[] = $attribute;
|
||||
}
|
||||
}
|
||||
|
||||
$this->forbiddenAttributesCache[$key] = $attributeList;
|
||||
|
||||
return $attributeList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of forbidden fields for a scope and action.
|
||||
*
|
||||
* @param $scope A scope.
|
||||
* @param $action An action.
|
||||
* @param $thresholdLevel An attribute will be treated as forbidden if the level is
|
||||
* equal to or lower than the threshold.
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getScopeForbiddenFieldList(
|
||||
string $scope,
|
||||
string $action = Table::ACTION_READ,
|
||||
string $thresholdLevel = Table::LEVEL_NO
|
||||
): array {
|
||||
|
||||
if (
|
||||
!in_array($thresholdLevel, $this->fieldLevelList) ||
|
||||
$thresholdLevel === Table::LEVEL_YES
|
||||
) {
|
||||
throw new RuntimeException("Bad threshold level.");
|
||||
}
|
||||
|
||||
$key = $scope . '_'. $action . '_' . $thresholdLevel;
|
||||
|
||||
if (isset($this->forbiddenFieldsCache[$key])) {
|
||||
return $this->forbiddenFieldsCache[$key];
|
||||
}
|
||||
|
||||
$fieldTableQuickAccess = $this->data->fieldTableQuickAccess;
|
||||
|
||||
if (
|
||||
!isset($fieldTableQuickAccess->$scope) ||
|
||||
!isset($fieldTableQuickAccess->$scope->fields) ||
|
||||
!isset($fieldTableQuickAccess->$scope->fields->$action)
|
||||
) {
|
||||
$this->forbiddenFieldsCache[$key] = [];
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$levelList = [];
|
||||
|
||||
foreach ($this->fieldLevelList as $level) {
|
||||
if (
|
||||
array_search($level, $this->fieldLevelList) >=
|
||||
array_search($thresholdLevel, $this->fieldLevelList)
|
||||
) {
|
||||
$levelList[] = $level;
|
||||
}
|
||||
}
|
||||
|
||||
$fieldList = [];
|
||||
|
||||
foreach ($levelList as $level) {
|
||||
if (!isset($fieldTableQuickAccess->$scope->fields->$action->$level)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($fieldTableQuickAccess->$scope->fields->$action->$level as $field) {
|
||||
if (in_array($field, $fieldList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldList[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
$this->forbiddenFieldsCache[$key] = $fieldList;
|
||||
|
||||
return $fieldList;
|
||||
}
|
||||
}
|
||||
82
application/Espo/Core/Acl/Map/MapFactory.php
Normal file
82
application/Espo/Core/Acl/Map/MapFactory.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl\Map;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\{
|
||||
InjectableFactory,
|
||||
Acl\Table,
|
||||
Acl\Map\Map,
|
||||
Binding\BindingContainer,
|
||||
Binding\Binder,
|
||||
Binding\BindingData,
|
||||
};
|
||||
|
||||
class MapFactory
|
||||
{
|
||||
private $injectableFactory;
|
||||
|
||||
public function __construct(InjectableFactory $injectableFactory)
|
||||
{
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
}
|
||||
|
||||
public function create(User $user, Table $table): Map
|
||||
{
|
||||
$bindingContainer = $this->createBindingContainer($user, $table);
|
||||
|
||||
return $this->injectableFactory->createWithBinding(Map::class, $bindingContainer);
|
||||
}
|
||||
|
||||
private function createBindingContainer(User $user, Table $table): BindingContainer
|
||||
{
|
||||
$bindingData = new BindingData();
|
||||
|
||||
$binder = new Binder($bindingData);
|
||||
|
||||
$binder
|
||||
->bindCallback(
|
||||
User::class,
|
||||
function () use ($user): User {
|
||||
return $user;
|
||||
}
|
||||
)
|
||||
->bindCallback(
|
||||
Table::class,
|
||||
function () use ($table): Table {
|
||||
return $table;
|
||||
}
|
||||
)
|
||||
->bindImplementation(CacheKeyProvider::class, DefaultCacheKeyProvider::class);
|
||||
|
||||
return new BindingContainer($bindingData);
|
||||
}
|
||||
}
|
||||
86
application/Espo/Core/Acl/Map/MetadataProvider.php
Normal file
86
application/Espo/Core/Acl/Map/MetadataProvider.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl\Map;
|
||||
|
||||
use Espo\Core\{
|
||||
Utils\Metadata,
|
||||
};
|
||||
|
||||
class MetadataProvider
|
||||
{
|
||||
protected $type = 'acl';
|
||||
|
||||
private $metadata;
|
||||
|
||||
public function __construct(Metadata $metadata)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getScopeList(): array
|
||||
{
|
||||
return array_keys($this->metadata->get('scopes') ?? []);
|
||||
}
|
||||
|
||||
public function isScopeEntity(string $scope): bool
|
||||
{
|
||||
return (bool) $this->metadata->get(['scopes', $scope, 'entity']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getScopeFieldList(string $scope): array
|
||||
{
|
||||
return array_keys($this->metadata->get(['entityDefs', $scope, 'fields']) ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getPermissionList(): array
|
||||
{
|
||||
$itemList = $this->metadata->get(['app', $this->type, 'valuePermissionList']) ?? [];
|
||||
|
||||
return array_map(
|
||||
function (string $item): string {
|
||||
if (substr($item, -10) === 'Permission') {
|
||||
return substr($item, 0, -10);
|
||||
}
|
||||
|
||||
return $item;
|
||||
},
|
||||
$itemList
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -29,10 +29,8 @@
|
||||
|
||||
namespace Espo\Core\Acl;
|
||||
|
||||
use StdClass;
|
||||
|
||||
/**
|
||||
* Contains access levels for a user.
|
||||
* Access levels for a user.
|
||||
*/
|
||||
interface Table
|
||||
{
|
||||
@@ -56,40 +54,18 @@ interface Table
|
||||
|
||||
public const ACTION_CREATE = 'create';
|
||||
|
||||
/**
|
||||
* Get a full map.
|
||||
*/
|
||||
public function getMap(): StdClass;
|
||||
|
||||
/**
|
||||
* Get scope data.
|
||||
*/
|
||||
public function getScopeData(string $scope): ScopeData;
|
||||
|
||||
/**
|
||||
* Get field data.
|
||||
*/
|
||||
public function getFieldData(string $scope, string $field): FieldData;
|
||||
|
||||
/**
|
||||
* Get a permission level.
|
||||
*/
|
||||
public function getPermissionLevel(string $permission): string;
|
||||
|
||||
/**
|
||||
* Get a list of forbidden attributes for a scope and action.
|
||||
*
|
||||
* @param $scope A scope.
|
||||
* $param $action An action.
|
||||
* @param $thresholdLevel An attribute will be treated as forbidden if the level is
|
||||
* equal to or lower than the threshold.
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getScopeForbiddenAttributeList(string $scope, string $action, string $thresholdLevel): array;
|
||||
|
||||
/**
|
||||
* Get a list of forbidden fields for a scope and action.
|
||||
*
|
||||
* @param $scope A scope.
|
||||
* $param $action An action.
|
||||
* @param $thresholdLevel An attribute will be treated as forbidden if the level is
|
||||
* equal to or lower than the threshold.
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getScopeForbiddenFieldList(string $scope, string $action, string $thresholdLevel): array;
|
||||
}
|
||||
|
||||
35
application/Espo/Core/Acl/Table/CacheKeyProvider.php
Normal file
35
application/Espo/Core/Acl/Table/CacheKeyProvider.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl\Table;
|
||||
|
||||
interface CacheKeyProvider
|
||||
{
|
||||
public function get(): string;
|
||||
}
|
||||
47
application/Espo/Core/Acl/Table/DefaultCacheKeyProvider.php
Normal file
47
application/Espo/Core/Acl/Table/DefaultCacheKeyProvider.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl\Table;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
class DefaultCacheKeyProvider implements CacheKeyProvider
|
||||
{
|
||||
private $user;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function get(): string
|
||||
{
|
||||
return 'acl/' . $this->user->getId();
|
||||
}
|
||||
}
|
||||
90
application/Espo/Core/Acl/Table/DefaultRoleListProvider.php
Normal file
90
application/Espo/Core/Acl/Table/DefaultRoleListProvider.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl\Table;
|
||||
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
use Espo\Entities\{
|
||||
User,
|
||||
Role as RoleEntity,
|
||||
};
|
||||
|
||||
class DefaultRoleListProvider implements RoleListProvider
|
||||
{
|
||||
private $user;
|
||||
|
||||
private $entityManager;
|
||||
|
||||
public function __construct(User $user, EntityManager $entityManager)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Role>
|
||||
*/
|
||||
public function get(): array
|
||||
{
|
||||
$roleList = [];
|
||||
|
||||
$userRoleList = $this->entityManager
|
||||
->getRepository('User')
|
||||
->getRelation($this->user, 'roles')
|
||||
->find();
|
||||
|
||||
foreach ($userRoleList as $role) {
|
||||
$roleList[] = $role;
|
||||
}
|
||||
|
||||
$teamList = $this->entityManager
|
||||
->getRepository('User')
|
||||
->getRelation($this->user, 'teams')
|
||||
->find();
|
||||
|
||||
foreach ($teamList as $team) {
|
||||
$teamRoleList = $this->entityManager
|
||||
->getRepository('Team')
|
||||
->getRelation($team, 'roles')
|
||||
->find();
|
||||
|
||||
foreach ($teamRoleList as $role) {
|
||||
$roleList[] = $role;
|
||||
}
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function (RoleEntity $role): RoleEntityWrapper {
|
||||
return new RoleEntityWrapper($role);
|
||||
},
|
||||
$roleList
|
||||
);
|
||||
}
|
||||
}
|
||||
41
application/Espo/Core/Acl/Table/Role.php
Normal file
41
application/Espo/Core/Acl/Table/Role.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl\Table;
|
||||
|
||||
use StdClass;
|
||||
|
||||
interface Role
|
||||
{
|
||||
public function getScopeTableData(): StdClass;
|
||||
|
||||
public function getFieldTableData(): StdClass;
|
||||
|
||||
public function getPermissionLevel(string $permission): ?string;
|
||||
}
|
||||
59
application/Espo/Core/Acl/Table/RoleEntityWrapper.php
Normal file
59
application/Espo/Core/Acl/Table/RoleEntityWrapper.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl\Table;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use StdClass;
|
||||
|
||||
class RoleEntityWrapper implements Role
|
||||
{
|
||||
private $entity;
|
||||
|
||||
public function __construct(Entity $entity)
|
||||
{
|
||||
$this->entity = $entity;
|
||||
}
|
||||
|
||||
public function getScopeTableData(): StdClass
|
||||
{
|
||||
return $this->entity->get('data') ?? (object) [];
|
||||
}
|
||||
|
||||
public function getFieldTableData(): StdClass
|
||||
{
|
||||
return $this->entity->get('fieldData') ?? (object) [];
|
||||
}
|
||||
|
||||
public function getPermissionLevel(string $permission): ?string
|
||||
{
|
||||
return $this->entity->get($permission . 'Permission');
|
||||
}
|
||||
}
|
||||
38
application/Espo/Core/Acl/Table/RoleListProvider.php
Normal file
38
application/Espo/Core/Acl/Table/RoleListProvider.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl\Table;
|
||||
|
||||
interface RoleListProvider
|
||||
{
|
||||
/**
|
||||
* @return array<Role>
|
||||
*/
|
||||
public function get(): array;
|
||||
}
|
||||
@@ -33,6 +33,13 @@ use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\{
|
||||
InjectableFactory,
|
||||
Acl\Table\CacheKeyProvider,
|
||||
Acl\Table\DefaultCacheKeyProvider,
|
||||
Acl\Table\RoleListProvider,
|
||||
Acl\Table\DefaultRoleListProvider,
|
||||
Binding\BindingContainer,
|
||||
Binding\Binder,
|
||||
Binding\BindingData,
|
||||
};
|
||||
|
||||
class TableFactory
|
||||
@@ -51,8 +58,27 @@ class TableFactory
|
||||
*/
|
||||
public function create(User $user): Table
|
||||
{
|
||||
return $this->injectableFactory->createWith(DefaultTable::class, [
|
||||
'user' => $user,
|
||||
]);
|
||||
$bindingContainer = $this->createBindingContainer($user);
|
||||
|
||||
return $this->injectableFactory->createWithBinding(DefaultTable::class, $bindingContainer);
|
||||
}
|
||||
|
||||
private function createBindingContainer(User $user): BindingContainer
|
||||
{
|
||||
$bindingData = new BindingData();
|
||||
|
||||
$binder = new Binder($bindingData);
|
||||
|
||||
$binder
|
||||
->bindCallback(
|
||||
User::class,
|
||||
function () use ($user): User {
|
||||
return $user;
|
||||
}
|
||||
)
|
||||
->bindImplementation(RoleListProvider::class, DefaultRoleListProvider::class)
|
||||
->bindImplementation(CacheKeyProvider::class, DefaultCacheKeyProvider::class);
|
||||
|
||||
return new BindingContainer($bindingData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ use Espo\Core\{
|
||||
Acl\OwnerUserFieldProvider,
|
||||
Acl\TableFactory,
|
||||
Acl\Table,
|
||||
Acl\Map\Map,
|
||||
Acl\Map\MapFactory,
|
||||
Acl\OwnershipCheckerFactory,
|
||||
Acl\OwnershipChecker,
|
||||
Acl\OwnershipOwnChecker,
|
||||
@@ -73,6 +75,8 @@ class AclManager
|
||||
|
||||
protected $tableHashMap = [];
|
||||
|
||||
protected $mapHashMap = [];
|
||||
|
||||
protected $userAclClassName = Acl::class;
|
||||
|
||||
protected const PERMISSION_ASSIGNMENT = 'assignment';
|
||||
@@ -99,6 +103,8 @@ class AclManager
|
||||
|
||||
protected $tableFactory;
|
||||
|
||||
protected $mapFactory;
|
||||
|
||||
protected $globalRestricton;
|
||||
|
||||
protected $ownerUserFieldProvider;
|
||||
@@ -109,6 +115,7 @@ class AclManager
|
||||
AccessCheckerFactory $accessCheckerFactory,
|
||||
OwnershipCheckerFactory $ownershipCheckerFactory,
|
||||
TableFactory $tableFactory,
|
||||
MapFactory $mapFactory,
|
||||
GlobalRestricton $globalRestricton,
|
||||
OwnerUserFieldProvider $ownerUserFieldProvider,
|
||||
EntityManager $entityManager
|
||||
@@ -116,6 +123,7 @@ class AclManager
|
||||
$this->accessCheckerFactory = $accessCheckerFactory;
|
||||
$this->ownershipCheckerFactory = $ownershipCheckerFactory;
|
||||
$this->tableFactory = $tableFactory;
|
||||
$this->mapFactory = $mapFactory;
|
||||
$this->globalRestricton = $globalRestricton;
|
||||
$this->ownerUserFieldProvider = $ownerUserFieldProvider;
|
||||
$this->entityManager = $entityManager;
|
||||
@@ -160,12 +168,27 @@ class AclManager
|
||||
return $this->tableHashMap[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a full access data map.
|
||||
*/
|
||||
public function getMap(User $user): StdClass
|
||||
protected function getMap(User $user): Map
|
||||
{
|
||||
return $this->getTable($user)->getMap();
|
||||
$key = $user->getId();
|
||||
|
||||
if (!$key) {
|
||||
$key = spl_object_hash($user);
|
||||
}
|
||||
|
||||
if (!array_key_exists($key, $this->mapHashMap)) {
|
||||
$this->mapHashMap[$key] = $this->mapFactory->create($user, $this->getTable($user));
|
||||
}
|
||||
|
||||
return $this->mapHashMap[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a full access data map (for front-end).
|
||||
*/
|
||||
public function getMapData(User $user): StdClass
|
||||
{
|
||||
return $this->getMap($user)->getData();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -437,7 +460,7 @@ class AclManager
|
||||
): array {
|
||||
|
||||
$list = array_merge(
|
||||
$this->getTable($user)->getScopeForbiddenAttributeList(
|
||||
$this->getMap($user)->getScopeForbiddenAttributeList(
|
||||
$scope,
|
||||
$action,
|
||||
$thresholdLevel
|
||||
@@ -465,7 +488,7 @@ class AclManager
|
||||
): array {
|
||||
|
||||
$list = array_merge(
|
||||
$this->getTable($user)->getScopeForbiddenFieldList(
|
||||
$this->getMap($user)->getScopeForbiddenFieldList(
|
||||
$scope,
|
||||
$action,
|
||||
$thresholdLevel
|
||||
|
||||
57
application/Espo/Core/Portal/Acl/Map/CacheKeyProvider.php
Normal file
57
application/Espo/Core/Portal/Acl/Map/CacheKeyProvider.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl\Map;
|
||||
|
||||
use Espo\Entities\{
|
||||
User,
|
||||
Portal,
|
||||
};
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\Map\CacheKeyProvider as CacheKeyProviderInterface,
|
||||
};
|
||||
|
||||
class CacheKeyProvider implements CacheKeyProviderInterface
|
||||
{
|
||||
private $user;
|
||||
|
||||
private $portal;
|
||||
|
||||
public function __construct(User $user, Portal $portal)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->portal = $portal;
|
||||
}
|
||||
|
||||
public function get(): string
|
||||
{
|
||||
return 'aclPortalMap/' . $this->portal->getId() . '/' . $this->user->getId();
|
||||
}
|
||||
}
|
||||
97
application/Espo/Core/Portal/Acl/Map/MapFactory.php
Normal file
97
application/Espo/Core/Portal/Acl/Map/MapFactory.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl\Map;
|
||||
|
||||
use Espo\Entities\{
|
||||
User,
|
||||
Portal,
|
||||
};
|
||||
|
||||
use Espo\Core\{
|
||||
InjectableFactory,
|
||||
Portal\Acl\Table as PortalTable,
|
||||
Portal\Acl\Map\MetadataProvider as PortalMetadataProvider,
|
||||
Portal\Acl\Map\CacheKeyProvider as PortalCacheKeyProvider,
|
||||
Acl\Map\MetadataProvider,
|
||||
Acl\Map\CacheKeyProvider,
|
||||
Acl\Map\Map,
|
||||
Acl\Table,
|
||||
Binding\BindingContainer,
|
||||
Binding\Binder,
|
||||
Binding\BindingData,
|
||||
};
|
||||
|
||||
class MapFactory
|
||||
{
|
||||
private $injectableFactory;
|
||||
|
||||
public function __construct(InjectableFactory $injectableFactory)
|
||||
{
|
||||
$this->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
|
||||
->bindCallback(
|
||||
User::class,
|
||||
function () use ($user): User {
|
||||
return $user;
|
||||
}
|
||||
)
|
||||
->bindCallback(
|
||||
Table::class,
|
||||
function () use ($table): PortalTable {
|
||||
return $table;
|
||||
}
|
||||
)
|
||||
->bindCallback(
|
||||
Portal::class,
|
||||
function () use ($portal): Portal {
|
||||
return $portal;
|
||||
}
|
||||
)
|
||||
->bindImplementation(MetadataProvider::class, PortalMetadataProvider::class)
|
||||
->bindImplementation(CacheKeyProvider::class, PortalCacheKeyProvider::class);
|
||||
|
||||
return new BindingContainer($bindingData);
|
||||
}
|
||||
}
|
||||
39
application/Espo/Core/Portal/Acl/Map/MetadataProvider.php
Normal file
39
application/Espo/Core/Portal/Acl/Map/MetadataProvider.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl\Map;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\Map\MetadataProvider as BaseMetadataProvider,
|
||||
};
|
||||
|
||||
class MetadataProvider extends BaseMetadataProvider
|
||||
{
|
||||
protected $type = 'aclPortal';
|
||||
}
|
||||
@@ -29,22 +29,10 @@
|
||||
|
||||
namespace Espo\Core\Portal\Acl;
|
||||
|
||||
use Espo\Entities\{
|
||||
User,
|
||||
Portal,
|
||||
};
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\DefaultTable as BaseTable,
|
||||
ORM\EntityManager,
|
||||
Utils\Config,
|
||||
Utils\Metadata,
|
||||
Utils\FieldUtil,
|
||||
Utils\DataCache,
|
||||
};
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class Table extends BaseTable
|
||||
{
|
||||
public const LEVEL_ACCOUNT = 'account';
|
||||
@@ -53,8 +41,6 @@ class Table extends BaseTable
|
||||
|
||||
protected $type = 'aclPortal';
|
||||
|
||||
protected $portal;
|
||||
|
||||
protected $defaultAclType = 'recordAllOwnNo';
|
||||
|
||||
protected $levelList = [
|
||||
@@ -68,54 +54,6 @@ class Table extends BaseTable
|
||||
|
||||
protected $isStrictModeForced = true;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
User $user,
|
||||
Portal $portal,
|
||||
Config $config,
|
||||
Metadata $metadata,
|
||||
FieldUtil $fieldUtil,
|
||||
DataCache $dataCache
|
||||
) {
|
||||
if (empty($portal)) {
|
||||
throw new RuntimeException("No portal was passed to AclPortal\\Table constructor.");
|
||||
}
|
||||
|
||||
$this->portal = $portal;
|
||||
|
||||
parent::__construct($entityManager, $user, $config, $metadata, $fieldUtil, $dataCache);
|
||||
}
|
||||
|
||||
protected function initCacheKey(): void
|
||||
{
|
||||
$this->cacheKey = 'aclPortal/' . $this->portal->getId() . '/' . $this->user->getId();
|
||||
}
|
||||
|
||||
protected function getRoleList(): array
|
||||
{
|
||||
$roleList = [];
|
||||
|
||||
$userRoleList = $this->entityManager
|
||||
->getRepository('User')
|
||||
->getRelation($this->user, 'portalRoles')
|
||||
->find();
|
||||
|
||||
foreach ($userRoleList as $role) {
|
||||
$roleList[] = $role;
|
||||
}
|
||||
|
||||
$portalRoleList = $this->entityManager
|
||||
->getRepository('Portal')
|
||||
->getRelation($this->portal, 'portalRoles')
|
||||
->find();
|
||||
|
||||
foreach ($portalRoleList as $role) {
|
||||
$roleList[] = $role;
|
||||
}
|
||||
|
||||
return $roleList;
|
||||
}
|
||||
|
||||
protected function getScopeWithAclList(): array
|
||||
{
|
||||
$scopeList = [];
|
||||
@@ -151,9 +89,9 @@ class Table extends BaseTable
|
||||
protected function applyDisabled(&$table, &$fieldTable): void
|
||||
{
|
||||
foreach ($this->getScopeList() as $scope) {
|
||||
$d = $this->metadata->get('scopes.' . $scope);
|
||||
$item = $this->metadata->get(['scopes', $scope]) ?? [];
|
||||
|
||||
if (!empty($d['disabled']) || !empty($d['portalDisabled'])) {
|
||||
if (!empty($item['disabled']) || !empty($item['portalDisabled'])) {
|
||||
$table->$scope = false;
|
||||
|
||||
unset($fieldTable->$scope);
|
||||
|
||||
57
application/Espo/Core/Portal/Acl/Table/CacheKeyProvider.php
Normal file
57
application/Espo/Core/Portal/Acl/Table/CacheKeyProvider.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl\Table;
|
||||
|
||||
use Espo\Entities\{
|
||||
User,
|
||||
Portal,
|
||||
};
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\Table\CacheKeyProvider as CacheKeyProviderInterface,
|
||||
};
|
||||
|
||||
class CacheKeyProvider implements CacheKeyProviderInterface
|
||||
{
|
||||
private $user;
|
||||
|
||||
private $portal;
|
||||
|
||||
public function __construct(User $user, Portal $portal)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->portal = $portal;
|
||||
}
|
||||
|
||||
public function get(): string
|
||||
{
|
||||
return 'aclPortal/' . $this->portal->getId() . '/' . $this->user->getId();
|
||||
}
|
||||
}
|
||||
93
application/Espo/Core/Portal/Acl/Table/RoleListProvider.php
Normal file
93
application/Espo/Core/Portal/Acl/Table/RoleListProvider.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\Acl\Table;
|
||||
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
use Espo\Entities\{
|
||||
User,
|
||||
Portal,
|
||||
PortalRole,
|
||||
};
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\Table\RoleListProvider as RoleListProviderInterface,
|
||||
Acl\Table\RoleEntityWrapper,
|
||||
Acl\Table\Role,
|
||||
};
|
||||
|
||||
class RoleListProvider implements RoleListProviderInterface
|
||||
{
|
||||
private $user;
|
||||
|
||||
private $portal;
|
||||
|
||||
private $entityManager;
|
||||
|
||||
public function __construct(User $user, Portal $portal, EntityManager $entityManager)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->portal = $portal;
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Role>
|
||||
*/
|
||||
public function get(): array
|
||||
{
|
||||
$roleList = [];
|
||||
|
||||
$userRoleList = $this->entityManager
|
||||
->getRepository('User')
|
||||
->getRelation($this->user, 'portalRoles')
|
||||
->find();
|
||||
|
||||
foreach ($userRoleList as $role) {
|
||||
$roleList[] = $role;
|
||||
}
|
||||
|
||||
$portalRoleList = $this->entityManager
|
||||
->getRepository('Portal')
|
||||
->getRelation($this->portal, 'portalRoles')
|
||||
->find();
|
||||
|
||||
foreach ($portalRoleList as $role) {
|
||||
$roleList[] = $role;
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function (PortalRole $role): RoleEntityWrapper {
|
||||
return new RoleEntityWrapper($role);
|
||||
},
|
||||
$roleList
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,13 @@ use Espo\Entities\{
|
||||
|
||||
use Espo\Core\{
|
||||
InjectableFactory,
|
||||
Acl\Table\CacheKeyProvider,
|
||||
Portal\Acl\Table\CacheKeyProvider as PortalCacheKeyProvider,
|
||||
Acl\Table\RoleListProvider,
|
||||
Portal\Acl\Table\RoleListProvider as PortalRoleListProvider,
|
||||
Binding\BindingContainer,
|
||||
Binding\Binder,
|
||||
Binding\BindingData,
|
||||
};
|
||||
|
||||
class TableFactory
|
||||
@@ -52,9 +59,33 @@ class TableFactory
|
||||
*/
|
||||
public function create(User $user, Portal $portal): Table
|
||||
{
|
||||
return $this->injectableFactory->createWith(Table::class, [
|
||||
'user' => $user,
|
||||
'portal' => $portal,
|
||||
]);
|
||||
$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
|
||||
->bindCallback(
|
||||
User::class,
|
||||
function () use ($user): User {
|
||||
return $user;
|
||||
}
|
||||
)
|
||||
->bindCallback(
|
||||
Portal::class,
|
||||
function () use ($portal): Portal {
|
||||
return $portal;
|
||||
}
|
||||
)
|
||||
->bindImplementation(RoleListProvider::class, PortalRoleListProvider::class)
|
||||
->bindImplementation(CacheKeyProvider::class, PortalCacheKeyProvider::class);
|
||||
|
||||
return new BindingContainer($bindingData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,9 +47,11 @@ use Espo\Core\{
|
||||
Portal\Acl\OwnershipContactChecker,
|
||||
Portal\Acl\TableFactory,
|
||||
Portal\Acl,
|
||||
Portal\Acl\Map\MapFactory,
|
||||
Acl\GlobalRestricton,
|
||||
Acl\OwnerUserFieldProvider,
|
||||
Acl\Table as TableBase,
|
||||
Acl\Map\Map,
|
||||
AclManager as InternalAclManager,
|
||||
};
|
||||
|
||||
@@ -68,6 +70,7 @@ class AclManager extends InternalAclManager
|
||||
AccessCheckerFactory $accessCheckerFactory,
|
||||
OwnershipCheckerFactory $ownershipCheckerFactory,
|
||||
TableFactory $tableFactory,
|
||||
MapFactory $mapFactory,
|
||||
GlobalRestricton $globalRestricton,
|
||||
OwnerUserFieldProvider $ownerUserFieldProvider,
|
||||
EntityManager $entityManager,
|
||||
@@ -76,6 +79,7 @@ class AclManager extends InternalAclManager
|
||||
$this->accessCheckerFactory = $accessCheckerFactory;
|
||||
$this->ownershipCheckerFactory = $ownershipCheckerFactory;
|
||||
$this->tableFactory = $tableFactory;
|
||||
$this->mapFactory = $mapFactory;
|
||||
$this->globalRestricton = $globalRestricton;
|
||||
$this->ownerUserFieldProvider = $ownerUserFieldProvider;
|
||||
$this->entityManager = $entityManager;
|
||||
@@ -111,13 +115,29 @@ class AclManager extends InternalAclManager
|
||||
return $this->tableHashMap[$key];
|
||||
}
|
||||
|
||||
public function getMap(User $user): StdClass
|
||||
protected function getMap(User $user): Map
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->internalAclManager->getMap($user);
|
||||
$key = $user->getId();
|
||||
|
||||
if (!$key) {
|
||||
$key = spl_object_hash($user);
|
||||
}
|
||||
|
||||
return parent::getMap($user);
|
||||
if (!array_key_exists($key, $this->mapHashMap)) {
|
||||
$this->mapHashMap[$key] = $this->mapFactory
|
||||
->create($user, $this->getTable($user), $this->getPortal());
|
||||
}
|
||||
|
||||
return $this->mapHashMap[$key];
|
||||
}
|
||||
|
||||
public function getMapData(User $user): StdClass
|
||||
{
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->internalAclManager->getMapData($user);
|
||||
}
|
||||
|
||||
return parent::getMapData($user);
|
||||
}
|
||||
|
||||
public function getLevel(User $user, string $scope, string $action): string
|
||||
@@ -285,7 +305,8 @@ class AclManager extends InternalAclManager
|
||||
): array {
|
||||
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->internalAclManager->getScopeForbiddenAttributeList($user, $scope, $action, $thresholdLevel);
|
||||
return $this->internalAclManager
|
||||
->getScopeForbiddenAttributeList($user, $scope, $action, $thresholdLevel);
|
||||
}
|
||||
|
||||
return parent::getScopeForbiddenAttributeList($user, $scope, $action, $thresholdLevel);
|
||||
@@ -299,7 +320,8 @@ class AclManager extends InternalAclManager
|
||||
): array {
|
||||
|
||||
if ($this->checkUserIsNotPortal($user)) {
|
||||
return $this->internalAclManager->getScopeForbiddenFieldList($user, $scope, $action, $thresholdLevel);
|
||||
return $this->internalAclManager
|
||||
->getScopeForbiddenFieldList($user, $scope, $action, $thresholdLevel);
|
||||
}
|
||||
|
||||
return parent::getScopeForbiddenFieldList($user, $scope, $action, $thresholdLevel);
|
||||
|
||||
@@ -214,7 +214,7 @@ class App
|
||||
|
||||
protected function getAclDataForFrontend()
|
||||
{
|
||||
$data = $this->acl->getMap();
|
||||
$data = $this->acl->getMapData();
|
||||
|
||||
if (!$this->user->isAdmin()) {
|
||||
$data = unserialize(serialize($data));
|
||||
|
||||
@@ -77,6 +77,8 @@ class Portal extends Record implements
|
||||
protected function clearRolesCache()
|
||||
{
|
||||
$this->fileManager->removeInDir('data/cache/application/aclPortal');
|
||||
$this->fileManager->removeInDir('data/cache/application/aclPortalMap');
|
||||
|
||||
$this->dataManager->updateCacheTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@ class PortalRole extends Record implements
|
||||
protected function clearRolesCache()
|
||||
{
|
||||
$this->fileManager->removeInDir('data/cache/application/aclPortal');
|
||||
$this->fileManager->removeInDir('data/cache/application/aclPortalMap');
|
||||
|
||||
$this->dataManager->updateCacheTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@ class Role extends Record implements
|
||||
protected function clearRolesCache()
|
||||
{
|
||||
$this->fileManager->removeInDir('data/cache/application/acl');
|
||||
$this->fileManager->removeInDir('data/cache/application/aclMap');
|
||||
|
||||
$this->dataManager->updateCacheTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ class Team extends Record implements
|
||||
public function afterUpdateEntity(Entity $entity, $data)
|
||||
{
|
||||
parent::afterUpdateEntity($entity, $data);
|
||||
|
||||
if (property_exists($data, 'rolesIds')) {
|
||||
$this->clearRolesCache();
|
||||
}
|
||||
@@ -52,6 +53,8 @@ class Team extends Record implements
|
||||
protected function clearRolesCache()
|
||||
{
|
||||
$this->fileManager->removeInDir('data/cache/application/acl');
|
||||
$this->fileManager->removeInDir('data/cache/application/aclMap');
|
||||
|
||||
$this->dataManager->updateCacheTimestamp();
|
||||
}
|
||||
|
||||
@@ -61,6 +64,8 @@ class Team extends Record implements
|
||||
|
||||
if ($link === 'users') {
|
||||
$this->fileManager->removeFile('data/cache/application/acl/' . $foreignId . '.php');
|
||||
$this->fileManager->removeFile('data/cache/application/aclMap/' . $foreignId . '.php');
|
||||
|
||||
$this->dataManager->updateCacheTimestamp();
|
||||
}
|
||||
}
|
||||
@@ -71,6 +76,8 @@ class Team extends Record implements
|
||||
|
||||
if ($link === 'users') {
|
||||
$this->fileManager->removeFile('data/cache/application/acl/' . $foreignId . '.php');
|
||||
$this->fileManager->removeFile('data/cache/application/aclMap/' . $foreignId . '.php');
|
||||
|
||||
$this->dataManager->updateCacheTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -771,6 +771,7 @@ class User extends Record implements
|
||||
protected function clearRoleCache(string $id)
|
||||
{
|
||||
$this->fileManager->removeFile('data/cache/application/acl/' . $id . '.php');
|
||||
$this->fileManager->removeFile('data/cache/application/aclMap/' . $id . '.php');
|
||||
|
||||
$this->dataManager->updateCacheTimestamp();
|
||||
}
|
||||
@@ -778,11 +779,11 @@ class User extends Record implements
|
||||
protected function clearPortalRolesCache()
|
||||
{
|
||||
$this->fileManager->removeInDir('data/cache/application/aclPortal');
|
||||
$this->fileManager->removeInDir('data/cache/application/aclPortalMap');
|
||||
|
||||
$this->dataManager->updateCacheTimestamp();
|
||||
}
|
||||
|
||||
|
||||
public function loadAdditionalFields(Entity $entity)
|
||||
{
|
||||
parent::loadAdditionalFields($entity);
|
||||
|
||||
@@ -96,13 +96,36 @@ define('acl-manager', ['acl'], function (Acl) {
|
||||
this.data.attributeTable = this.data.attributeTable || {};
|
||||
},
|
||||
|
||||
/**
|
||||
* @deprecated Use `getPermissionLevel`.
|
||||
* @returns string|null
|
||||
*/
|
||||
get: function (name) {
|
||||
return this.data[name] || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param string permission
|
||||
* @returns string
|
||||
*/
|
||||
getPermissionLevel: function (permission) {
|
||||
var permissionKey = permission;
|
||||
|
||||
if (permission.substr(-10) !== 'Permission') {
|
||||
permissionKey = permission + 'Permission';
|
||||
}
|
||||
|
||||
return this.data[permissionKey] || 'no';
|
||||
},
|
||||
|
||||
getLevel: function (scope, action) {
|
||||
if (!(scope in this.data.table)) return;
|
||||
if (typeof this.data.table[scope] !== 'object' || !(action in this.data.table[scope])) return;
|
||||
if (!(scope in this.data.table)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof this.data.table[scope] !== 'object' || !(action in this.data.table[scope])) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.data.table[scope][action];
|
||||
},
|
||||
@@ -113,17 +136,21 @@ define('acl-manager', ['acl'], function (Acl) {
|
||||
|
||||
checkScopeHasAcl: function (scope) {
|
||||
var data = (this.data.table || {})[scope];
|
||||
|
||||
if (typeof data === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
checkScope: function (scope, action, precise) {
|
||||
var data = (this.data.table || {})[scope];
|
||||
|
||||
if (typeof data === 'undefined') {
|
||||
data = null;
|
||||
}
|
||||
|
||||
return this.getImplementation(scope).checkScope(data, action, precise);
|
||||
},
|
||||
|
||||
@@ -143,6 +170,7 @@ define('acl-manager', ['acl'], function (Acl) {
|
||||
}
|
||||
|
||||
var data = (this.data.table || {})[scope];
|
||||
|
||||
if (typeof data === 'undefined') {
|
||||
data = null;
|
||||
}
|
||||
@@ -160,7 +188,8 @@ define('acl-manager', ['acl'], function (Acl) {
|
||||
check: function (subject, action, precise) {
|
||||
if (typeof subject === 'string') {
|
||||
return this.checkScope(subject, action, precise);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return this.checkModel(subject, action, precise);
|
||||
}
|
||||
},
|
||||
@@ -230,6 +259,7 @@ define('acl-manager', ['acl'], function (Acl) {
|
||||
thresholdLevel = thresholdLevel || 'no';
|
||||
|
||||
var key = scope + '_' + action + '_' + thresholdLevel;
|
||||
|
||||
if (key in this.forbiddenFieldsCache) {
|
||||
return this.forbiddenFieldsCache[key];
|
||||
}
|
||||
@@ -242,10 +272,15 @@ define('acl-manager', ['acl'], function (Acl) {
|
||||
var actionData = fieldsData[action] || {};
|
||||
|
||||
var fieldList = [];
|
||||
|
||||
levelList.forEach(function (level) {
|
||||
var list = actionData[level] || [];
|
||||
|
||||
list.forEach(function (field) {
|
||||
if (~fieldList.indexOf(field)) return;
|
||||
if (~fieldList.indexOf(field)) {
|
||||
return;
|
||||
}
|
||||
|
||||
fieldList.push(field);
|
||||
}, this);
|
||||
}, this);
|
||||
@@ -260,6 +295,7 @@ define('acl-manager', ['acl'], function (Acl) {
|
||||
thresholdLevel = thresholdLevel || 'no';
|
||||
|
||||
var key = scope + '_' + action + '_' + thresholdLevel;
|
||||
|
||||
if (key in this.forbiddenAttributesCache) {
|
||||
return this.forbiddenAttributesCache[key];
|
||||
}
|
||||
@@ -273,10 +309,15 @@ define('acl-manager', ['acl'], function (Acl) {
|
||||
var actionData = attributesData[action] || {};
|
||||
|
||||
var attributeList = [];
|
||||
|
||||
levelList.forEach(function (level) {
|
||||
var list = actionData[level] || [];
|
||||
|
||||
list.forEach(function (attribute) {
|
||||
if (~attributeList.indexOf(attribute)) return;
|
||||
if (~attributeList.indexOf(attribute)) {
|
||||
return;
|
||||
}
|
||||
|
||||
attributeList.push(attribute);
|
||||
}, this);
|
||||
}, this);
|
||||
@@ -287,7 +328,10 @@ define('acl-manager', ['acl'], function (Acl) {
|
||||
},
|
||||
|
||||
checkTeamAssignmentPermission: function (teamId) {
|
||||
if (this.get('assignmentPermission') === 'all') return true;
|
||||
if (this.get('assignmentPermission') === 'all') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ~this.getUser().getLinkMultipleIdList('teams').indexOf(teamId);
|
||||
},
|
||||
|
||||
|
||||
79
tests/unit/Espo/Core/Acl/FieldDataTest.php
Normal file
79
tests/unit/Espo/Core/Acl/FieldDataTest.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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 tests\unit\Espo\Core\Acl;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\FieldData,
|
||||
Acl\Table,
|
||||
};
|
||||
|
||||
class FieldDataTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
protected function setUp() : void
|
||||
{
|
||||
}
|
||||
|
||||
public function testGet1(): void
|
||||
{
|
||||
$raw = (object) [
|
||||
Table::ACTION_EDIT => Table::LEVEL_YES,
|
||||
Table::ACTION_READ => Table::LEVEL_NO,
|
||||
];
|
||||
|
||||
$data = FieldData::fromRaw($raw);
|
||||
|
||||
$this->assertEquals(Table::LEVEL_NO, $data->getRead());
|
||||
$this->assertEquals(Table::LEVEL_YES, $data->getEdit());
|
||||
}
|
||||
|
||||
public function testGet2(): void
|
||||
{
|
||||
$raw = (object) [
|
||||
Table::ACTION_EDIT => Table::LEVEL_NO,
|
||||
Table::ACTION_READ => Table::LEVEL_YES,
|
||||
];
|
||||
|
||||
$data = FieldData::fromRaw($raw);
|
||||
|
||||
$this->assertEquals(Table::LEVEL_YES, $data->getRead());
|
||||
$this->assertEquals(Table::LEVEL_NO, $data->getEdit());
|
||||
}
|
||||
|
||||
public function testGetEmpty(): void
|
||||
{
|
||||
$raw = (object) [
|
||||
];
|
||||
|
||||
$data = FieldData::fromRaw($raw);
|
||||
|
||||
$this->assertEquals(Table::LEVEL_NO, $data->getRead());
|
||||
$this->assertEquals(Table::LEVEL_NO, $data->getEdit());
|
||||
}
|
||||
}
|
||||
442
tests/unit/Espo/Core/Acl/Map/MapTest.php
Normal file
442
tests/unit/Espo/Core/Acl/Map/MapTest.php
Normal file
@@ -0,0 +1,442 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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 tests\unit\Espo\Core\Acl;
|
||||
|
||||
use Espo\Entities\User;
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\Map\Map,
|
||||
Acl\Map\DataBuilder,
|
||||
Acl\Map\MetadataProvider,
|
||||
Acl\Map\CacheKeyProvider,
|
||||
Acl\Table,
|
||||
Acl\ScopeData,
|
||||
Acl\FieldData,
|
||||
Utils\Config,
|
||||
Utils\FieldUtil,
|
||||
Utils\DataCache,
|
||||
};
|
||||
|
||||
use StdClass;
|
||||
|
||||
class MapTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
private $fieldUtil;
|
||||
|
||||
private $config;
|
||||
|
||||
private $table;
|
||||
|
||||
private $user;
|
||||
|
||||
private $metadataProvider;
|
||||
|
||||
private $cacheKeyProvider;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->config = $this->createMock(Config::class);
|
||||
$this->fieldUtil = $this->createMock(FieldUtil::class);
|
||||
$this->table = $this->createMock(Table::class);
|
||||
$this->user = $this->createMock(User::class);
|
||||
$this->dataCache = $this->createMock(DataCache::class);
|
||||
$this->metadataProvider = $this->createMock(MetadataProvider::class);
|
||||
$this->cacheKeyProvider = $this->createMock(CacheKeyProvider::class);
|
||||
|
||||
$this->config
|
||||
->expects($this->any())
|
||||
->method('get')
|
||||
->willReturnMap([
|
||||
['useCache', false]
|
||||
]);
|
||||
|
||||
$this->user
|
||||
->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn('user-id');
|
||||
}
|
||||
|
||||
private function mockTableData(array $scopeData, array $fieldData, array $permissionData): void
|
||||
{
|
||||
$returnMap1 = [];
|
||||
|
||||
foreach ($scopeData as $scope => $item) {
|
||||
$returnMap1[] = [$scope, ScopeData::fromRaw($item)];
|
||||
}
|
||||
|
||||
$this->table
|
||||
->expects($this->any())
|
||||
->method('getScopeData')
|
||||
->willReturnMap($returnMap1);
|
||||
|
||||
$returnMap2 = [];
|
||||
|
||||
foreach ($fieldData as $scope => $item1) {
|
||||
foreach ($item1 as $field => $item2) {
|
||||
$returnMap2[] = [$scope, $field, FieldData::fromRaw($item2)];
|
||||
}
|
||||
}
|
||||
|
||||
$this->table
|
||||
->expects($this->any())
|
||||
->method('getFieldData')
|
||||
->willReturnMap($returnMap2);
|
||||
|
||||
$returnMap3 = [];
|
||||
|
||||
foreach ($permissionData as $permission => $level) {
|
||||
$returnMap3[] = [$permission, $level];
|
||||
}
|
||||
|
||||
$this->table
|
||||
->expects($this->any())
|
||||
->method('getPermissionLevel')
|
||||
->willReturnMap($returnMap3);
|
||||
}
|
||||
|
||||
public function testMap1(): void
|
||||
{
|
||||
$dataBuilder = new DataBuilder($this->metadataProvider, $this->fieldUtil);
|
||||
|
||||
$this->metadataProvider
|
||||
->expects($this->any())
|
||||
->method('getScopeList')
|
||||
->willReturn(['Test1', 'Test2', 'Test3', 'Test4', 'Test5']);
|
||||
|
||||
$this->metadataProvider
|
||||
->expects($this->any())
|
||||
->method('getPermissionList')
|
||||
->willReturn(['assignment', 'portal']);
|
||||
|
||||
$this->metadataProvider
|
||||
->expects($this->any())
|
||||
->method('isScopeEntity')
|
||||
->willReturnMap([
|
||||
['Test1', true],
|
||||
['Test2', true],
|
||||
['Test3', true],
|
||||
['Test4', true],
|
||||
['Test5', false],
|
||||
]);
|
||||
|
||||
$this->metadataProvider
|
||||
->expects($this->any())
|
||||
->method('getScopeFieldList')
|
||||
->willReturnMap([
|
||||
['Test1', ['field1', 'field2', 'field3', 'field4']],
|
||||
['Test2', ['field1']],
|
||||
['Test3', []],
|
||||
['Test4', []],
|
||||
['Test5', []],
|
||||
]);
|
||||
|
||||
$this->fieldUtil
|
||||
->expects($this->any())
|
||||
->method('getAttributeList')
|
||||
->willReturnMap([
|
||||
['Test1', 'field1', ['attr1a', 'attr1b']],
|
||||
['Test1', 'field2', ['field2']],
|
||||
['Test1', 'field3', ['field3']],
|
||||
['Test1', 'field4', ['field4']],
|
||||
['Test2', 'field1', ['field1']],
|
||||
]);
|
||||
|
||||
$this->mockTableData(
|
||||
[
|
||||
'Test1' => (object) [
|
||||
Table::ACTION_CREATE => Table::LEVEL_YES,
|
||||
Table::ACTION_READ => Table::LEVEL_TEAM,
|
||||
],
|
||||
'Test2' => (object) [
|
||||
Table::ACTION_CREATE => Table::LEVEL_YES,
|
||||
Table::ACTION_READ => Table::LEVEL_TEAM,
|
||||
Table::ACTION_EDIT => Table::LEVEL_OWN,
|
||||
],
|
||||
'Test3' => false,
|
||||
'Test4' => true,
|
||||
'Test5' => true,
|
||||
],
|
||||
[
|
||||
'Test1' => [
|
||||
'field1' => (object) [
|
||||
Table::ACTION_READ => Table::LEVEL_YES,
|
||||
Table::ACTION_EDIT => Table::LEVEL_NO,
|
||||
],
|
||||
'field2' => (object) [
|
||||
Table::ACTION_READ => Table::LEVEL_NO,
|
||||
Table::ACTION_EDIT => Table::LEVEL_YES,
|
||||
],
|
||||
'field3' => (object) [
|
||||
Table::ACTION_READ => Table::LEVEL_NO,
|
||||
Table::ACTION_EDIT => Table::LEVEL_NO,
|
||||
],
|
||||
'field4' => (object) [
|
||||
Table::ACTION_READ => Table::LEVEL_YES,
|
||||
Table::ACTION_EDIT => Table::LEVEL_YES,
|
||||
],
|
||||
],
|
||||
'Test2' => [
|
||||
'field1' => (object) [
|
||||
Table::ACTION_READ => Table::LEVEL_NO,
|
||||
Table::ACTION_EDIT => Table::LEVEL_NO,
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'assignment' => Table::LEVEL_YES,
|
||||
'portal' => Table::LEVEL_NO,
|
||||
],
|
||||
);
|
||||
|
||||
$expectedData = $this->getExpectedRawData();
|
||||
|
||||
$map = new Map(
|
||||
$this->user,
|
||||
$this->table,
|
||||
$dataBuilder,
|
||||
$this->config,
|
||||
$this->dataCache,
|
||||
$this->cacheKeyProvider
|
||||
);
|
||||
|
||||
$this->assertEquals($expectedData, $map->getData());
|
||||
|
||||
$this->assertEquals(
|
||||
['field2', 'field3'],
|
||||
$map->getScopeForbiddenFieldList('Test1', Table::ACTION_READ)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
['field1', 'field3'],
|
||||
$map->getScopeForbiddenFieldList('Test1', Table::ACTION_EDIT)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
['field2', 'field3'],
|
||||
$map->getScopeForbiddenAttributeList('Test1', Table::ACTION_READ)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
['attr1a', 'attr1b', 'field3'],
|
||||
$map->getScopeForbiddenAttributeList('Test1', Table::ACTION_EDIT)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
['attr1a', 'attr1b', 'field3'],
|
||||
$map->getScopeForbiddenAttributeList('Test1', Table::ACTION_EDIT)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
['field1'],
|
||||
$map->getScopeForbiddenFieldList('Test2', Table::ACTION_READ)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
['field1'],
|
||||
$map->getScopeForbiddenFieldList('Test2', Table::ACTION_READ)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
[],
|
||||
$map->getScopeForbiddenFieldList('Test3', Table::ACTION_READ)
|
||||
);
|
||||
}
|
||||
|
||||
private function getExpectedRawData(): StdClass
|
||||
{
|
||||
return (object) [
|
||||
'table' => (object) [
|
||||
'Test1' => (object) [
|
||||
'read' => 'team',
|
||||
'stream' => 'no',
|
||||
'edit' => 'no',
|
||||
'delete' => 'no',
|
||||
'create' => 'yes'
|
||||
],
|
||||
'Test2' => (object) [
|
||||
'read' => 'team',
|
||||
'stream' => 'no',
|
||||
'edit' => 'own',
|
||||
'delete' => 'no',
|
||||
'create' => 'yes'
|
||||
],
|
||||
'Test3' => false,
|
||||
'Test4' => true,
|
||||
'Test5' => true
|
||||
],
|
||||
'fieldTable' => (object) [
|
||||
'Test1' => (object) [
|
||||
'field1' => (object) [
|
||||
'read' => 'yes',
|
||||
'edit' => 'no'
|
||||
],
|
||||
'field2' => (object) [
|
||||
'read' => 'no',
|
||||
'edit' => 'yes'
|
||||
],
|
||||
'field3' => (object) [
|
||||
'read' => 'no',
|
||||
'edit' => 'no'
|
||||
]
|
||||
],
|
||||
'Test2' => (object) [
|
||||
'field1' => (object) [
|
||||
'read' => 'no',
|
||||
'edit' => 'no'
|
||||
]
|
||||
],
|
||||
'Test3' => (object) [],
|
||||
'Test4' => (object) []
|
||||
],
|
||||
'assignmentPermission' => 'yes',
|
||||
'portalPermission' => 'no',
|
||||
'fieldTableQuickAccess' => (object) [
|
||||
'Test1' => (object) [
|
||||
'attributes' => (object) [
|
||||
'read' => (object) [
|
||||
'yes' => [
|
||||
'attr1a',
|
||||
'attr1b'
|
||||
],
|
||||
'no' => [
|
||||
'field2',
|
||||
'field3'
|
||||
]
|
||||
],
|
||||
'edit' => (object) [
|
||||
'yes' => [
|
||||
'field2'
|
||||
],
|
||||
'no' => [
|
||||
'attr1a',
|
||||
'attr1b',
|
||||
'field3'
|
||||
]
|
||||
]
|
||||
],
|
||||
'fields' => (object) [
|
||||
'read' => (object) [
|
||||
'yes' => [
|
||||
'field1'
|
||||
],
|
||||
'no' => [
|
||||
'field2',
|
||||
'field3'
|
||||
]
|
||||
],
|
||||
'edit' => (object) [
|
||||
'yes' => [
|
||||
'field2'
|
||||
],
|
||||
'no' => [
|
||||
'field1',
|
||||
'field3'
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'Test2' => (object) [
|
||||
'attributes' => (object) [
|
||||
'read' => (object) [
|
||||
'yes' => [],
|
||||
'no' => [
|
||||
'field1'
|
||||
]
|
||||
],
|
||||
'edit' => (object) [
|
||||
'yes' => [],
|
||||
'no' => [
|
||||
'field1'
|
||||
]
|
||||
]
|
||||
],
|
||||
'fields' => (object) [
|
||||
'read' => (object) [
|
||||
'yes' => [],
|
||||
'no' => [
|
||||
'field1'
|
||||
]
|
||||
],
|
||||
'edit' => (object) [
|
||||
'yes' => [],
|
||||
'no' => [
|
||||
'field1'
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'Test3' => (object) [
|
||||
'attributes' => (object) [
|
||||
'read' => (object) [
|
||||
'yes' => [],
|
||||
'no' => []
|
||||
],
|
||||
'edit' => (object) [
|
||||
'yes' => [],
|
||||
'no' => []
|
||||
]
|
||||
],
|
||||
'fields' => (object) [
|
||||
'read' => (object) [
|
||||
'yes' => [],
|
||||
'no' => []
|
||||
],
|
||||
'edit' => (object) [
|
||||
'yes' => [],
|
||||
'no' => []
|
||||
]
|
||||
]
|
||||
],
|
||||
'Test4' => (object) [
|
||||
'attributes' => (object) [
|
||||
'read' => (object) [
|
||||
'yes' => [],
|
||||
'no' => []
|
||||
],
|
||||
'edit' => (object) [
|
||||
'yes' => [],
|
||||
'no' => []
|
||||
]
|
||||
],
|
||||
'fields' => (object) [
|
||||
'read' => (object) [
|
||||
'yes' => [],
|
||||
'no' => []
|
||||
],
|
||||
'edit' => (object) [
|
||||
'yes' => [],
|
||||
'no' => []
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
59
tests/unit/Espo/Core/Acl/Map/MetadataProviderTest.php
Normal file
59
tests/unit/Espo/Core/Acl/Map/MetadataProviderTest.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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 tests\unit\Espo\Core\Acl;
|
||||
|
||||
|
||||
use Espo\Core\{
|
||||
Acl\Map\MetadataProvider,
|
||||
Utils\Metadata,
|
||||
};
|
||||
|
||||
class MetadataProviderTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
private $metadata;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->metadata = $this->createMock(Metadata::class);
|
||||
}
|
||||
|
||||
public function testGetPermissionList(): void
|
||||
{
|
||||
$provider = new MetadataProvider($this->metadata);
|
||||
|
||||
$this->metadata
|
||||
->expects($this->once())
|
||||
->method('get')
|
||||
->with(['app', 'acl', 'valuePermissionList'])
|
||||
->willReturn(['assignmentPermission', 'portalPermission']);
|
||||
|
||||
$this->assertEquals(['assignment', 'portal'], $provider->getPermissionList());
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ use Espo\Core\{
|
||||
Acl\OwnerUserFieldProvider,
|
||||
Acl\TableFactory,
|
||||
Acl\GlobalRestricton,
|
||||
Acl\Map\MapFactory,
|
||||
ORM\EntityManager,
|
||||
};
|
||||
|
||||
@@ -72,10 +73,12 @@ class AclManagerTest extends \PHPUnit\Framework\TestCase
|
||||
private $globalRestriction;
|
||||
|
||||
/**
|
||||
* @var USer
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
|
||||
private $mapFactory;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->user = $this->createMock(User::class);
|
||||
@@ -84,12 +87,14 @@ class AclManagerTest extends \PHPUnit\Framework\TestCase
|
||||
$this->accessCheckerFactory = $this->createMock(AccessCheckerFactory::class);
|
||||
$this->ownershipCheckerFactory = $this->createMock(OwnershipCheckerFactory::class);
|
||||
$this->tableFactory = $this->createMock(TableFactory::class);
|
||||
$this->mapFactory = $this->createMock(MapFactory::class);
|
||||
$this->globalRestriction = $this->createMock(GlobalRestricton::class);
|
||||
|
||||
$this->aclManager = new AclManager(
|
||||
$this->accessCheckerFactory,
|
||||
$this->ownershipCheckerFactory,
|
||||
$this->tableFactory,
|
||||
$this->mapFactory,
|
||||
$this->globalRestriction,
|
||||
$this->createMock(OwnerUserFieldProvider::class),
|
||||
$this->createMock(EntityManager::class)
|
||||
|
||||
Reference in New Issue
Block a user