data = (object) [ 'table' => (object) [], 'fieldTable' => (object) [], 'fieldTableQuickAccess' => (object) [], ]; if ($this->isStrictModeForced) { $this->isStrictMode = true; } else { $this->isStrictMode = $config->get('aclStrictMode', true); } $this->user = $user; $this->metadata = $metadata; if ($fieldManager) { $this->fieldManager = $fieldManager; } if (!$this->user->isFetched()) { throw new Error('User must be fetched before ACL check.'); } if ($fileManager) { $this->fileManager = $fileManager; } $this->valuePermissionList = $this->metadata->get(['app', $this->type, 'valuePermissionList'], []); $this->valuePermissionHighestLevels = $this->metadata->get(['app', $this->type, 'valuePermissionHighestLevels'], array()); $this->initCacheFilePath(); if ($config && $config->get('useCache') && file_exists($this->cacheFilePath)) { $cached = include $this->cacheFilePath; $this->data = $cached; } else { $this->load(); if ($config && $fileManager && $config->get('useCache')) { $this->buildCache(); } } } protected function initCacheFilePath() { $this->cacheFilePath = 'data/cache/application/acl/' . $this->getUser()->id . '.php'; } protected function getUser() { return $this->user; } protected function getMetadata() { return $this->metadata; } protected function getFieldManager() { return $this->fieldManager; } public function getMap() { return $this->data; } public function getScopeData($scope) { if (isset($this->data->table->$scope)) { $data = $this->data->table->$scope; if (is_string($data)) { $data = $this->getScopeData($data); return $data; } return $data; } return null; } public function get($permission) { if ($permission == 'table') { return null; } if (isset($this->data->$permission)) { return $this->data->$permission; } return 'no'; } public function getLevel($scope, $action) { if (isset($this->data->table->$scope)) { if (isset($this->data->table->$scope->$action)) { return $this->data->table->$scope->$action; } } return 'no'; } public function getHighestLevel($scope, $action) { if (in_array($action, $this->booleanActionList)) { return 'yes'; } else { $level = $this->metadata->get(['scopes', $scope, $this->type . 'HighestLevel']); return $level ?? 'all'; } } private function load() { $valuePermissionLists = (object)[]; foreach ($this->valuePermissionList as $permission) { $valuePermissionLists->$permission = []; } $aclTableList = []; $fieldTableList = []; if (!$this->getUser()->isAdmin()) { $roleList = $this->getRoleList(); foreach ($roleList as $role) { $aclTableList[] = $role->get('data'); $fieldTableList[] = $role->get('fieldData'); foreach ($this->valuePermissionList as $permission) { $valuePermissionLists->{$permission}[] = $role->get($permission); } } $aclTable = $this->mergeTableList($aclTableList); $fieldTable = $this->mergeFieldTableList($fieldTableList); $this->applyDefault($aclTable, $fieldTable); $this->applyDisabled($aclTable, $fieldTable); $this->applyMandatory($aclTable, $fieldTable); $this->applyAdditional($aclTable, $fieldTable, $valuePermissionLists); $this->applyReadOnlyFields($fieldTable); } else { $aclTable = (object) []; foreach ($this->getScopeList() as $scope) { if ($this->metadata->get("scopes.{$scope}.{$this->type}") === 'boolean') { $aclTable->$scope = true; } else { if ($this->metadata->get("scopes.{$scope}.entity")) { $aclTable->$scope = (object) []; foreach ($this->actionList as $action) { $aclTable->$scope->$action = 'all'; if (in_array($action, $this->booleanActionList)) { $aclTable->$scope->$action = 'yes'; } } } } } $fieldTable = (object) []; } foreach ($aclTable as $scope => $data) { if (is_string($data)) { if (isset($aclTable->$data)) { $aclTable->$scope = $aclTable->$data; } } } $this->data->table = $aclTable; $this->data->fieldTable = $fieldTable; $this->fillFieldTableQuickAccess(); if (!$this->getUser()->isAdmin()) { $permissionsDefaultsGroupName = 'permissionsDefaults'; if ($this->isStrictMode) { $permissionsDefaultsGroupName = 'permissionsStrictDefaults'; } foreach ($this->valuePermissionList as $permission) { $this->data->$permission = $this->mergeValueList($valuePermissionLists->$permission, $this->metadata->get(['app', $this->type, $permissionsDefaultsGroupName, $permission, 'yes'])); if ($this->metadata->get('app.'.$this->type.'.mandatory.' . $permission)) { $this->data->$permission = $this->metadata->get('app.'.$this->type.'.mandatory.' . $permission); } } } else { foreach ($this->valuePermissionList as $permission) { if (isset($this->valuePermissionHighestLevels[$permission])) { $this->data->$permission = $this->valuePermissionHighestLevels[$permission]; continue; } $this->data->$permission = 'all'; } } } protected function getRoleList() { $roleList = []; $userRoleList = $this->getUser()->get('roles'); if (!(is_array($userRoleList) || $userRoleList instanceof \Traversable)) { throw new Error(); } foreach ($userRoleList as $role) { $roleList[] = $role; } $teamList = $this->getUser()->get('teams'); if (!(is_array($teamList) || $teamList instanceof \Traversable)) { throw new Error(); } foreach ($teamList as $team) { $teamRoleList = $team->get('roles'); foreach ($teamRoleList as $role) { $roleList[] = $role; } } return $roleList; } public function getScopeForbiddenAttributeList($scope, $action = 'read', $thresholdLevel = 'no') { $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($scope, $action = 'read', $thresholdLevel = 'no') { $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() { $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->getFieldManager()->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; } protected function applyDefault(&$table, &$fieldTable) { if ($this->getUser()->isAdmin()) { return; } $defaultsGroupName = 'default'; if ($this->isStrictMode) { $defaultsGroupName = 'strictDefault'; } $data = $this->metadata->get(['app', $this->type, $defaultsGroupName, 'scopeLevel'], []); foreach ($data as $scope => $item) { if (isset($table->$scope)) continue; $value = $item; if (is_array($item)) { $value = (object) $item; } $table->$scope = $value; } $defaultFieldData = $this->metadata->get(['app', $this->type, $defaultsGroupName, 'fieldLevel'], []); foreach ($this->getScopeList() as $scope) { if (isset($table->$scope) && $table->$scope === false) continue; if (!$this->getMetadata()->get('scopes.' . $scope . '.entity')) continue; $fieldList = array_keys($this->getMetadata()->get("entityDefs.{$scope}.fields", [])); $defaultScopeFieldData = $this->metadata->get('app.'.$this->type.'.'.$defaultsGroupName.'.scopeFieldLevel.' . $scope, []); foreach (array_merge($defaultFieldData, $defaultScopeFieldData) as $field => $f) { if (!in_array($field, $fieldList)) continue; if (!isset($fieldTable->$scope)) { $fieldTable->$scope = (object) []; } if (isset($fieldTable->$scope->$field)) continue; $fieldTable->$scope->$field = (object) []; foreach ($this->fieldActionList as $action) { $level = 'no'; if ($f === true) { $level = 'yes'; } else { if (is_array($f) && isset($f[$action])) { $level = $f[$action]; } } $fieldTable->$scope->$field->$action = $level; } } } foreach ($this->getScopeWithAclList() as $scope) { if (!isset($table->$scope)) { $aclType = $this->metadata->get('scopes.' . $scope . '.' . $this->type); if ($aclType === true) { $aclType = $this->defaultAclType; } if (!empty($aclType)) { $paramDefaultsName = 'scopeLevelTypesDefaults'; if ($this->isStrictMode) { $paramDefaultsName = 'scopeLevelTypesStrictDefaults'; } $defaultValue = $this->metadata->get(['app', $this->type, $paramDefaultsName, $aclType], $this->metadata->get(['app', $this->type, $paramDefaultsName, 'record'])); if (is_array($defaultValue)) { $defaultValue = (object) $defaultValue; } $table->$scope = $defaultValue; if (is_object($table->$scope)) { $actionList = $this->getMetadata()->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); } } } } } } } } protected function applyMandatory(&$table, &$fieldTable) { if ($this->getUser()->isAdmin()) { return; } $data = $this->metadata->get('app.'.$this->type.'.mandatory.scopeLevel', array()); foreach ($data as $scope => $item) { $value = $item; if (is_array($item)) { $value = (object) $item; } $table->$scope = $value; } $mandatoryFieldData = $this->metadata->get('app.'.$this->type.'.mandatory.fieldLevel', array()); foreach ($this->getScopeList() as $scope) { if (isset($table->$scope) && $table->$scope === false) continue; if (!$this->getMetadata()->get('scopes.' . $scope . '.entity')) continue; $fieldList = array_keys($this->getMetadata()->get("entityDefs.{$scope}.fields", [])); $mandatoryScopeFieldData = $this->metadata->get('app.'.$this->type.'.mandatory.scopeFieldLevel.' . $scope, array()); foreach (array_merge($mandatoryFieldData, $mandatoryScopeFieldData) as $field => $f) { if (!in_array($field, $fieldList)) continue; if (!isset($fieldTable->$scope)) { $fieldTable->$scope = (object) []; } $fieldTable->$scope->$field = (object) []; foreach ($this->fieldActionList as $action) { $level = 'no'; if ($f === true) { $level = 'yes'; } else { if (is_array($f) && isset($f[$action])) { $level = $f[$action]; } } $fieldTable->$scope->$field->$action = $level; } } } } protected function applyDisabled(&$table, &$fieldTable) { if ($this->getUser()->isAdmin()) { return; } foreach ($this->getScopeList() as $scope) { if ($this->getMetadata()->get('scopes.' . $scope . '.disabled')) { $table->$scope = false; unset($fieldTable->$scope); } } } protected function applyAdditional(&$table, &$fieldTable, &$valuePermissionLists) { if ($this->getUser()->isPortal()) { foreach ($this->getScopeList() as $scope) { $table->$scope = false; unset($fieldTable->$scope); } foreach ($this->valuePermissionList as $permission) { $valuePermissionLists->{$permission}[] = 'no'; } } } private function mergeValueList(array $list, $defaultValue) { $result = null; foreach ($list as $level) { if ($level != 'not-set') { if (is_null($result)) { $result = $level; continue; } if (array_search($result, $this->levelList) > array_search($level, $this->levelList)) { $result = $level; } } } if (is_null($result)) { $result = $defaultValue; } return $result; } protected function getScopeWithAclList() { $scopeList = []; $scopes = $this->metadata->get('scopes'); foreach ($scopes as $scope => $d) { if (empty($d['acl'])) continue; $scopeList[] = $scope; } return $scopeList; } protected function getScopeList() { $scopeList = []; $scopes = $this->metadata->get('scopes'); foreach ($scopes as $scope => $d) { $scopeList[] = $scope; } return $scopeList; } private function mergeTableList(array $tableList) { $data = (object) []; $scopeList = $this->getScopeWithAclList(); foreach ($tableList as $table) { foreach ($scopeList as $scope) { if (!isset($table->$scope)) continue; $row = $table->$scope; if ($row == false) { if (!isset($data->$scope)) { $data->$scope = false; } } else if ($row === true) { $data->$scope = true; } else { if (!isset($data->$scope)) { $data->$scope = (object) []; } if ($data->$scope === false) { $data->$scope = (object) []; } if (!is_object($row)) continue; $actionList = $this->getMetadata()->get(['scopes', $scope, $this->type . 'ActionList'], $this->actionList); foreach ($actionList as $i => $action) { if (isset($row->$action)) { $level = $row->$action; if (!isset($data->$scope->$action)) { $data->$scope->$action = $level; } else { if (array_search($data->$scope->$action, $this->levelList) > array_search($level, $this->levelList)) { $data->$scope->$action = $level; } } } else { if ($i > 0) { // TODO remove it $previousAction = $this->actionList[$i - 1]; if (in_array($action, $this->booleanActionList)) { $data->$scope->$action = 'yes'; } else { if ($action === 'stream' && isset($data->$scope->$previousAction)) { $data->$scope->$action = $data->$scope->$previousAction; } } } } } } } } return $data; } private function mergeFieldTableList(array $tableList) { $data = (object) []; $scopeList = $this->getScopeWithAclList(); foreach ($tableList as $table) { foreach ($scopeList as $scope) { if (!isset($table->$scope)) continue; if (!isset($data->$scope)) { $data->$scope = (object) []; } if (!is_object($table->$scope)) continue; $fieldList = array_keys($this->getMetadata()->get("entityDefs.{$scope}.fields", [])); foreach (get_object_vars($table->$scope) as $field => $row) { if (!is_object($row)) continue; if (!in_array($field, $fieldList)) continue; if (!isset($data->$scope->$field)) { $data->$scope->$field = (object) []; } foreach ($this->fieldActionList as $i => $action) { if (!isset($row->$action)) continue; $level = $row->$action; if (!isset($data->$scope->$field->$action)) { $data->$scope->$field->$action = $level; } else { if (array_search($data->$scope->$field->$action, $this->fieldLevelList) > array_search($level, $this->fieldLevelList)) { $data->$scope->$field->$action = $level; } } } } } } return $data; } private function buildCache() { $this->fileManager->putPhpContents($this->cacheFilePath, $this->data, true); } protected function applyReadOnlyFields(&$fieldTable) { // TODO Enable in 5.4.0 return; $scopeList = $this->getScopeWithAclList(); foreach ($scopeList as $scope) { if (!property_exists($fieldTable, $scope)) continue; $fieldList = array_keys($this->getMetadata()->get(['entityDefs', $scope, 'fields'], [])); foreach ($fieldList as $field) { if ($this->getMetadata()->get(['entityDefs', $scope, 'fields', $field, 'readOnly'])) { if (property_exists($fieldTable->$scope, $field)) { $fieldTable->$scope->$field->edit = 'no'; } else { $fieldTable->$scope->$field = (object) []; foreach ($this->fieldActionList as $action) { $fieldTable->$scope->$field->$action = 'yes'; } $fieldTable->$scope->$field->edit = 'no'; } } } } } }