['CFG_VALUE'], 'conditions' => ['CFG_UID' => 'authSourcesList', 'OBJ_UID' => 'pageSize', 'USR_UID' => $userUid] ); $configuration = new Configuration(); $configurationReturn = $configuration->show($filters); if ($configurationReturn['total'] > 0) { $configValue = unserialize($configurationReturn['data'][0]['CFG_VALUE']); $limit = $configValue['pageSize'] ?? $limit; } } $filters = array( 'fields' => ['*'], 'start' => $start, 'limit'=> $limit ); if ($orderBy != '') { if (!in_array($ascending, ['asc', 'desc'])) { $ascending = 'asc'; } $filters['orderBy'] = [$orderBy, $ascending]; } if ($filter != '') { $filters['conditions'] = ['text' => $filter]; } $rbacAuthenticationSource = new RbacAuthenticationSource(); $authSourceReturn = $rbacAuthenticationSource->show($filters); $rbacUsers = new RbacUsers(); $usersByAuthSource = $rbacUsers->getAllUsersByAuthSource(); $auth = []; foreach (($usersByAuthSource['data'] ?? []) as $user) { $auth[$user['UID_AUTH_SOURCE']] = $user['CNT']; } $sources = []; foreach (($authSourceReturn['data'] ?? []) as $authSourceRow) { $label = G::LoadTranslation('ID_DISABLE'); if ($authSourceRow['AUTH_SOURCE_ENABLED_TLS'] === '1') { $label = G::LoadTranslation('ID_ENABLE'); } $authSourceRow['AUTH_SOURCE_ENABLED_TLS_LABEL'] = $label; $authSourceRow['AUTH_SOURCE_PASSWORD'] = ''; //additional information $authSourceData = json_decode($authSourceRow['AUTH_SOURCE_DATA'], true); if (is_array($authSourceData)) { $authSourceRow = array_merge($authSourceRow, $authSourceData); } $authSourceRow['AUTH_ANONYMOUS'] = (string)$authSourceRow['AUTH_ANONYMOUS']; $sources[] = $authSourceRow; $index = array_key_last($sources); $sources[$index]['CURRENT_USERS'] = isset($auth[$sources[$index]['AUTH_SOURCE_UID']]) ? $auth[$sources[$index]['AUTH_SOURCE_UID']] : 0; } $response = [ 'success' => true, 'sources' => $sources, 'total_sources' => $authSourceReturn['total'] ]; return $response; } catch (Exception $exception) { return ['success' => false, 'message' => $exception->getMessage()]; } } /** * Removes an authentication source from the system * * This method permanently deletes an authentication source * identified by its unique identifier. It validates that the * authentication source exists before attempting removal. * * @param string $authSourceUid The unique identifier of the authentication source to remove * * @return array Response array containing: * - success: boolean indicating operation success * - deleteRows: number of rows deleted (if successful) * - message: error message if operation fails * * @throws Exception When authSourceUid is empty or authentication source not found */ public function removeAuthSource($authSourceUid) { try { if (empty($authSourceUid)) { throw new Exception('Authentication source UID is required'); } $conditions = ['AUTH_SOURCE_UID' => $authSourceUid]; $rbacAuthenticationSource = new RbacAuthenticationSource(); $removeResponse = $rbacAuthenticationSource->remove($conditions); return ['success' => true, 'deleteRows' => $removeResponse['deleteRows'] ]; } catch (Exception $exception) { return ['success' => false, 'message' => $exception->getMessage()]; } } /** * Verifies if an authentication source name already exists and suggests alternatives * * This method checks if a given authentication source name is already in use. * If the name exists, it generates a suggested alternative name by appending * a number in parentheses (e.g., "Source Name (1)"). * * @param string $authSourceName The authentication source name to verify * * @return array Response array containing: * - success: boolean indicating operation success * - row: existing authentication source data (false if not found) * - suggestName: suggested alternative name if original exists * - message: error message if operation fails */ public function verifyAuthSourceName($authSourceName) { try { $row = false; $suggestName = ''; $filters = [ 'fields' => ['AUTH_SOURCE_UID', 'AUTH_SOURCE_NAME'], 'conditions' => ['AUTH_SOURCE_NAME' => $authSourceName] ]; $rbacAuthenticationSource = new RbacAuthenticationSource(); $authSourceReturn = $rbacAuthenticationSource->show($filters); $authSourceReturn['AUTH_SOURCE_DATA'] = json_decode($authSourceReturn['AUTH_SOURCE_DATA'], true); if ($authSourceReturn['total'] > 0) { $row = $authSourceReturn['data'][0]; $filters['fields'] = ['AUTH_SOURCE_NAME']; $filters['conditions'] = ['text' => $authSourceName]; $filters['orderBy'] = ['AUTH_SOURCE_NAME', 'desc']; $lastAuthSource = $rbacAuthenticationSource->show($filters); if ($lastAuthSource['total'] > 0) { $name = $lastAuthSource['data'][0]['AUTH_SOURCE_NAME']; //get suggest name $pieces = explode( ' ', $name); $last = array_pop($pieces); $number = trim($last, '()'); if ("({$number})" === $last) { $number = intval($number) + 1; $suggestName = implode('', $pieces) . " ({$number})"; } else { $suggestName = $name . ' (1)'; } } } return ['success' => true, 'row' => $row, 'suggestName' => $suggestName]; } catch (Exception $exception) { return ['success' => false, 'message' => $exception->getMessage()]; } } /** * Tests the connection to an LDAP authentication source * * This method validates the connection parameters for an LDAP server * and checks if TLS encryption is properly configured. * * @param array $authSourceData Array containing LDAP connection parameters: * - server, port, username, password, base DN, etc. * * @return array Response array containing: * - success: boolean indicating connection success * - status: connection status ('OK' if successful) * - message: warning/error message (e.g., TLS certificate issues) */ public function testConnection($authSourceData) { try { $ldapSource = new LdapSource(); $authSourceConnectionData = $ldapSource->ldapConnection($authSourceData); $connectionEstablished = isset($authSourceConnectionData['connection']) && $authSourceConnectionData['connection']; $response = ['success' => true, 'status' => 'OK']; if ($authSourceConnectionData['startTLS'] === false) { $response['message'] = G::LoadTranslation('ID_TLS_CERTIFICATE_IS_NOT_INSTALLED_IN_THE_SERVER'); } return $response; } catch (Exception $exception) { return ['success' => false, 'message' => $exception->getMessage()]; } } /** * Saves or updates an authentication source configuration * * This method creates a new authentication source or updates an existing one. * It establishes an LDAP connection to validate the configuration and * determines the LDAP page size limit for optimal performance. * * @param array $authSourceData Complete authentication source configuration including: * - AUTH_SOURCE_UID: unique identifier (empty for new sources) * - AUTH_SOURCE_DATA: LDAP connection parameters * - AUTH_SOURCE_BASE_DN: base distinguished name * * @return array Response array containing: * - success: boolean indicating operation success * - saveData: saved authentication source data * - message: error message if operation fails */ public function saveAuthSource($authSourceData) { try { $ldapSource = new LdapSource(); $authSourceData['AUTH_SOURCE_VERSION'] = 3; $ldapConnection = $ldapSource->ldapConnection($authSourceData); if (!isset($ldapConnection['connection']) || !$ldapConnection['connection']) { throw new Exception('LDAP connection failed'); } $authSourceData['AUTH_SOURCE_DATA']['LDAP_PAGE_SIZE_LIMIT'] = $ldapSource->getPageSizeLimit( $ldapConnection['connection'], $authSourceData['AUTH_SOURCE_BASE_DN'] ); $authSourceData['AUTH_SOURCE_UID'] = $authSourceData['AUTH_SOURCE_UID'] ?? ''; if (!empty($authSourceData['AUTH_SOURCE_UID']) && empty($authSourceData['AUTH_SOURCE_PASSWORD'])) { unset($authSourceData['AUTH_SOURCE_PASSWORD']); } else { $authSourceData['AUTH_SOURCE_PASSWORD'] = G::encrypt($authSourceData['AUTH_SOURCE_PASSWORD'], URL_KEY); } $authSourceData['AUTH_SOURCE_DATA'] = json_encode($authSourceData['AUTH_SOURCE_DATA']); $rbacAuthenticationSource = new RbacAuthenticationSource(); $saveDataResponse = $rbacAuthenticationSource->saveData($authSourceData); return ['success' => true, 'saveData' => $saveDataResponse]; } catch (Exception $exception) { return ['success' => false, 'message' => $exception->getMessage()]; } } /** * Searches for users in an LDAP authentication source * * This method queries an LDAP server to find users based on search criteria. * It compares found users with existing system users to determine import status. * * @param string $authSourceUid The unique identifier of the authentication source * @param array $filters Search parameters containing: * - text: search term for user lookup * - start: pagination start position * - limit: maximum number of results * * @return array Response array containing: * - success: boolean indicating operation success * - status: operation status ('OK' if successful) * - resultTotal: total number of users found * - resultRoot: array of user data with import status * - message: error message if operation fails */ public function searchUsers($authSourceUid, $filters) { try { $rbacUsers = new RbacUsers(); $usersAuthSources = $rbacUsers->listUsersAuthSources(); $listUsers = []; foreach (($usersAuthSources['data'] ?? []) as $row) { $listUsers[strtolower($row['USR_USERNAME'])] = $row['UID_AUTH_SOURCE']; } $ldapSource = new LdapSource(); $ldapSource->authSourceUid = $authSourceUid; $result = $ldapSource->searchUsersLdap($filters['text'], $filters['start'], $filters['limit']); $arrayData = []; foreach (($result['data'] ?? []) as $value) { $listUsersData = $value; $usernameLower = strtolower($listUsersData['sUsername']); if (!isset($listUsers[$usernameLower])) { $listUsersData['STATUS'] = G::LoadTranslation('ID_NOT_IMPORTED'); $listUsersData['IMPORT'] = 1; } elseif ($authSourceUid === $listUsers[$usernameLower]) { $listUsersData['STATUS'] = G::LoadTranslation('ID_IMPORTED'); $listUsersData['IMPORT'] = 0; } else { $listUsersData['STATUS'] = G::LoadTranslation('ID_CANNOT_IMPORT'); $listUsersData['IMPORT'] = 0; } $arrayData[] = $listUsersData; } return ['success' => true, 'status' => 'OK', 'resultTotal' => $result['numRecTotal'], 'resultRoot' => $arrayData]; } catch (Exception $exception) { return ['success' => false, 'message' => $exception->getMessage()]; } } /** * Imports users from an LDAP authentication source into the system * * This method creates new user accounts in the system based on LDAP user data. * It handles user attribute mapping, role assignment, and calendar assignment. * * @param string $authSourceUid The unique identifier of the authentication source * @param array $usersImport Array of user objects to import, each containing: * - sUsername: LDAP username * - sFirstname: user's first name * - sLastname: user's last name * - sEmail: user's email address * - sDN: user's distinguished name * - USR_STATUS: user status (optional) * * @return array Response array containing: * - success: boolean indicating operation success * - message: error message if operation fails */ public function importUsers($authSourceUid, $usersImport) { try { $filters = ['conditions' => ['AUTH_SOURCE_UID'=> $authSourceUid]]; $rbacAuthenticationSource = new RbacAuthenticationSource(); $authSourceReturn = $rbacAuthenticationSource->show($filters); if (empty($authSourceReturn['data']) || !isset($authSourceReturn['data'][0])) { throw new Exception('Authentication source not found'); } $authSourceReturn = $authSourceReturn['data'][0]; $authSourceReturn['AUTH_SOURCE_DATA'] = json_decode($authSourceReturn['AUTH_SOURCE_DATA'], true); $aAttributes = $authSourceReturn['AUTH_SOURCE_DATA']['AUTH_SOURCE_GRID_ATTRIBUTE'] ?? []; global $RBAC; foreach ($usersImport as $sUser) { $aUser = (array) $sUser; $matches = array(); $aData = array(); $aData['USR_USERNAME'] = str_replace('*', "'", $aUser['sUsername']); $aData['USR_PASSWORD'] = '00000000000000000000000000000000'; $aData['USR_FIRSTNAME'] = str_replace('*', "'", $aUser['sFirstname']); $aData['USR_FIRSTNAME'] = ($aData['USR_FIRSTNAME'] == '') ? $aData['USR_USERNAME'] : $aData['USR_FIRSTNAME']; $aData['USR_LASTNAME'] = str_replace('*', "'", $aUser['sLastname']); $aData['USR_EMAIL'] = $aUser['sEmail']; $aData['USR_DUE_DATE'] = date('Y-m-d', mktime(0, 0, 0, date('m'), date('d'), date('Y') + 2)); $aData['USR_CREATE_DATE'] = date('Y-m-d H:i:s'); $aData['USR_UPDATE_DATE'] = date('Y-m-d H:i:s'); $aData['USR_BIRTHDAY'] = date('Y-m-d'); // Set USR_STATUS for RBAC (numeric format) $aData['USR_STATUS'] = (isset($aUser['USR_STATUS'])) ? (($aUser['USR_STATUS'] == 'ACTIVE') ? 1 : 0) : 1; $aData['USR_AUTH_TYPE'] = strtolower($authSourceReturn['AUTH_SOURCE_PROVIDER']); $aData['UID_AUTH_SOURCE'] = $authSourceReturn['AUTH_SOURCE_UID']; preg_match('/[a-zA-Z]\*[a-zA-Z]/', $aUser['sDN'], $matches); foreach ($matches as $match) { $newMatch = str_replace('*', '\'', $match); $aUser['sDN'] = str_replace($match, $newMatch, $aUser['sDN']); } $aData['USR_AUTH_USER_DN'] = $aUser['sDN']; $usrRole = 'LURANA_OPERATOR'; if (!empty($authSourceReturn['AUTH_SOURCE_DATA']['USR_ROLE'])) { $usrRole = $authSourceReturn['AUTH_SOURCE_DATA']['USR_ROLE']; } $sUserUID = $RBAC->createUser($aData, $usrRole, $authSourceReturn['AUTH_SOURCE_NAME']); // Set USR_STATUS for User model (string format) $aData['USR_STATUS'] = (isset($aUser['USR_STATUS'])) ? $aUser['USR_STATUS'] : 'ACTIVE'; $aData['USR_UID'] = $sUserUID; $aData['USR_ROLE'] = $usrRole; $calendarObj = new Calendar(); $calendarObj->assignCalendarTo($sUserUID, '00000000000000000000000000000001', 'USER'); if (count($aAttributes)) { foreach ($aAttributes as $value) { if (isset($aUser[$value['attributeUser']])) { $aData[$value['attributeUser']] = str_replace('*', "'", $aUser[$value['attributeUser']]); if ($value['attributeUser'] == 'USR_STATUS') { $statusValue = $aData['USR_STATUS']; $aData[$value['attributeUser']] = $statusValue; } } } } $oUser = new Users(); $oUser->create($aData); } return ['success' => true]; } catch (Exception $exception) { return ['success' => false, 'message' => $exception->getMessage()]; } } /** * Searches for groups in an LDAP authentication source * * This method retrieves all available groups from an LDAP server * and compares them with existing system groups to show synchronization status. * * @param string $authSourceUid The unique identifier of the authentication source * * @return array|TreeNodeAuthSource[] Array of TreeNodeAuthSource objects representing groups, or * error array containing: * - success: false * - message: error description */ public function searchGroups($authSourceUid) { try { $ldapSource = new LdapSource(); $ldapSource->authSourceUid = $authSourceUid; $groupsLdap = $ldapSource->searchGroups(); $allGroupsLdap = []; foreach ($groupsLdap as $group) { $node = []; $node['GRP_UID'] = $group['cn']; $node['GRP_TITLE'] = $group['cn']; $node['GRP_USERS'] = $group['users']; $node['GRP_DN'] = $group['dn']; $allGroupsLdap[] = $node; } $groupUser = new GroupUser(); $groupsNumberUsers = $groupUser->getNumberOfUsersByGroups(); $listGroupsNumberUsers = []; foreach (($groupsNumberUsers['data'] ?? []) as $group) { $listGroupsNumberUsers[$group['GRP_UID']] = $group['NUM_REC']; } $groupwf = new Groupwf(); $groupsObjects = []; foreach ($allGroupsLdap as $group) { $groupObject = new TreeNodeAuthSource(); $groupObject->text = htmlentities($group['GRP_TITLE'], ENT_QUOTES, 'UTF-8'); $groupResult = $groupwf->getGroupWithDN($group['GRP_DN']); if (!empty($groupResult) && !empty($groupResult[0]['GRP_UID'])) { $groupUid = $groupResult[0]['GRP_UID']; $groupObject->text .= ' (' . ($listGroupsNumberUsers[$groupUid] ?? 0) . ')'; $groupObject->checked = true; } else { $groupObject->checked = false; } $groupObject->id = urlencode($group['GRP_DN']); $groupsObjects[] = $groupObject; } return $groupsObjects; } catch (Exception $exception) { return ['success' => false, 'message' => $exception->getMessage()]; } } /** * Searches for departments (organizational units) in an LDAP authentication source * * This method retrieves the organizational unit structure from an LDAP server * and builds a hierarchical tree representation with user count information. * * @param string $authSourceUid The unique identifier of the authentication source * * @return array|TreeNodeAuthSource[] Hierarchical array of department objects, or * error array containing: * - success: false * - message: error description */ public function searchDepartments($authSourceUid) { try { $ldapSource = new LdapSource(); $ldapSource->authSourceUid = $authSourceUid; $departments = $ldapSource->searchDepartments(); $user = new User(); $departmentsNumberUsers = $user->getNumberOfUsersByDepartments(); $listDepartmentsNumberUsers = []; foreach (($departmentsNumberUsers['data'] ?? []) as $department) { $listDepartmentsNumberUsers[$department['DEP_UID']] = $department['NUM_REC']; } $departmentsObject = $this->getChildrenDepartments($departments, '', $listDepartmentsNumberUsers, $ldapSource->terminatedOu); return $departmentsObject; } catch (Exception $exception) { return ['success' => false, 'message' => $exception->getMessage()]; } } /** * Saves group synchronization settings for an authentication source * * This method processes a list of LDAP group distinguished names to synchronize * with the system. It creates new groups or updates existing ones and handles * unsynchronization of previously synchronized groups. * * @param string $groupsDN Pipe-separated list of URL-encoded group distinguished names * @param string $authSourceUid The unique identifier of the authentication source * * @return array Response array containing: * - success: boolean indicating operation success * - status: operation status ('OK' if successful) * - message: error message if operation fails */ public function saveGroups($groupsDN, $authSourceUid) { try { $groupsToCheck = explode('|', $groupsDN); $groupsToCheck = array_map('urldecode', $groupsToCheck); $groupsToUncheck = $this->getGroupsToUncheck($groupsToCheck); $filters = ['conditions' => ['AUTH_SOURCE_UID'=> $authSourceUid]]; $rbacAuthenticationSource = new RbacAuthenticationSource(); $authSourceReturn = $rbacAuthenticationSource->show($filters); if (empty($authSourceReturn['data']) || !isset($authSourceReturn['data'][0])) { throw new Exception('Authentication source not found'); } $authenticationSourceData = $authSourceReturn['data'][0]; $authenticationSourceData['AUTH_SOURCE_DATA'] = json_decode($authenticationSourceData['AUTH_SOURCE_DATA'], true); $ldapSource = new LdapSource(); $ldapSource->authSourceUid = $authSourceUid; $groupwf = new Groupwf(); foreach ($groupsToCheck as $groupDN) { $ous = $ldapSource->custom_ldap_explode_dn($groupDN); $currentGroup = array_shift($ous); $groupAux = explode('=', $currentGroup); $groupTitle = isset($groupAux[1]) ? trim($groupAux[1]) : ''; $groupTitle = stripslashes($groupTitle); if (empty($groupTitle)) { continue; } $filters = array( 'fields' => ['GRP_UID'], 'conditions' => ['GRP_TITLE' => $groupTitle, 'GRP_STATUS' => 'ACTIVE'] ); $allGroups = $groupwf->show($filters); $groupUid = ''; if (!empty($allGroups['data']) && isset($allGroups['data'][0]['GRP_UID'])) { $groupUid = $allGroups['data'][0]['GRP_UID']; } if ($groupUid === '') { $group = [ 'GRP_TITLE' => $groupTitle, 'GRP_LDAP_DN' => $groupDN ]; } else { $group = $allGroups['data'][0]; $group['GRP_LDAP_DN'] = $groupDN; } $groupwf->saveData($group); } if (count($groupsToUncheck) > 0) { foreach ($groupsToUncheck as $groupDN) { $ous = $ldapSource->custom_ldap_explode_dn($groupDN); $currentGroup = array_shift($ous); $groupAux = explode('=', $currentGroup); $groupTitle = isset($groupAux[1]) ? trim($groupAux[1]) : ''; $groupTitle = stripslashes($groupTitle); if (empty($groupTitle)) { continue; } $filters = array( 'fields' => ['GRP_UID'], 'conditions' => ['GRP_TITLE' => $groupTitle, 'GRP_STATUS' => 'ACTIVE'] ); $allGroups = $groupwf->show($filters); $groupUid = ''; if (!empty($allGroups['data']) && isset($allGroups['data'][0]['GRP_UID'])) { $groupUid = $allGroups['data'][0]['GRP_UID']; } if ($groupUid != '' && !empty($allGroups['data'])) { $group = $allGroups['data'][0]; $group['GRP_LDAP_DN'] = ''; $groupwf->saveData($group); if (!isset($authenticationSourceData['AUTH_SOURCE_DATA']['GROUPS_TO_UNASSIGN'])) { $authenticationSourceData['AUTH_SOURCE_DATA']['GROUPS_TO_UNASSIGN'] = []; } if (!in_array($groupUid, $authenticationSourceData['AUTH_SOURCE_DATA']['GROUPS_TO_UNASSIGN'])) { $authenticationSourceData['AUTH_SOURCE_DATA']['GROUPS_TO_UNASSIGN'][] = $groupUid; } } } $authenticationSourceData['AUTH_SOURCE_DATA'] = json_encode($authenticationSourceData['AUTH_SOURCE_DATA']); $rbacAuthenticationSource->saveData($authenticationSourceData); } return [ 'status' => 'OK', 'success' => true ]; } catch (Exception $exception) { return ['success' => false, 'message' => $exception->getMessage()]; } } /** * Saves department synchronization settings for an authentication source * * This method processes a list of LDAP organizational unit distinguished names * to synchronize with the system. It creates new departments or updates existing * ones while maintaining the hierarchical structure. * * @param string $departmentsDN Pipe-separated list of URL-encoded department distinguished names * @param string $authSourceUid The unique identifier of the authentication source * * @return array Response array containing: * - success: boolean indicating operation success * - status: operation status ('OK' if successful) * - message: error message if operation fails */ public function saveDepartments($departmentsDN, $authSourceUid) { try { $depsToCheck = ($departmentsDN != '') ? explode('|', $departmentsDN) : []; $depsToCheck = array_map('urldecode', $depsToCheck); $depsToUncheck = $this->getDepartmentsToUncheck($depsToCheck); $filters = ['conditions' => ['AUTH_SOURCE_UID'=> $authSourceUid]]; $rbacAuthenticationSource = new RbacAuthenticationSource(); $authSourceReturn = $rbacAuthenticationSource->show($filters); if (empty($authSourceReturn['data']) || !isset($authSourceReturn['data'][0])) { throw new Exception('Authentication source not found'); } $authenticationSourceData = $authSourceReturn['data'][0]; $authenticationSourceData['AUTH_SOURCE_DATA'] = json_decode($authenticationSourceData['AUTH_SOURCE_DATA'], true); $ldapSource = new LdapSource(); $ldapSource->authSourceUid = $authSourceUid; $department = new Department(); foreach ($depsToCheck as $departmentDn) { $departmentUid = $department->getDepUidIfExistsDN($departmentDn); $departmentUid = $departmentUid['data'][0]['DEP_UID'] ?? ''; if ($departmentUid == '') { if (strcasecmp($departmentDn, $authenticationSourceData['AUTH_SOURCE_BASE_DN']) == 0) { $departmentTitle = 'ROOT (' . $authenticationSourceData['AUTH_SOURCE_BASE_DN'] . ')'; $parentUid = ''; } else { $ous = $ldapSource->custom_ldap_explode_dn($departmentDn); $departmentCurrent = array_shift($ous); $parentDn = implode(',', $ous); $ous = explode('=', $departmentCurrent); $departmentTitle = trim($ous[1]); $parentUid = $department->getDepUidIfExistsDN($parentDn); $parentUid = $parentUid['data'][0]['DEP_UID'] ?? ''; if (str_ireplace($authenticationSourceData['AUTH_SOURCE_BASE_DN'], '', $parentDn) != '' && $parentUid == '') { $errorMessage = G::LoadTranslation( 'ID_DEPARTMENT_CHECK_PARENT_DEPARTMENT', [$parentDn, $departmentTitle] ); throw new Exception($errorMessage); } } $filters = array( 'conditions' => ['DEP_STATUS' => 'ACTIVE', 'DEP_TITLE' => $departmentTitle] ); $allDepartments = $department->show($filters); $departmentUid = ''; if (!empty($allDepartments['data']) && isset($allDepartments['data'][0]['DEP_UID'])) { $departmentUid = $allDepartments['data'][0]['DEP_UID']; } if (empty($departmentUid)) { $data = [ 'DEP_TITLE' => stripslashes($departmentTitle), 'DEP_PARENT' => $parentUid, 'DEP_LDAP_DN' => $departmentDn, 'DEP_REF_CODE' => '' ]; $saveDepartment = $department->saveData($data); if (empty($saveDepartment)) { throw new Exception(G::LoadTranslation('ID_DEPARTMENT_ERROR_CREATE')); } } else { if (!empty($allDepartments['data'])) { $data = $allDepartments['data'][0]; $data['DEP_LDAP_DN'] = $departmentDn; $department->saveData($data); } } } } if (count($depsToUncheck) > 0) { $baseDnLength = strlen($authenticationSourceData['AUTH_SOURCE_BASE_DN']); foreach ($depsToUncheck as $departmentDn) { $departmentUid = $department->getDepUidIfExistsDN($departmentDn); $departmentUid = $departmentUid['data'][0]['DEP_UID'] ?? ''; if ($departmentUid != '' && strcasecmp( substr($departmentDn, strlen($departmentDn) - $baseDnLength), $authenticationSourceData['AUTH_SOURCE_BASE_DN'] ) == 0 ) { $filters = array( 'conditions' => ['DEP_UID' => $departmentUid] ); $allDepartments = $department->show($filters); $data = $allDepartments['data'][0] ?? []; $data['DEP_LDAP_DN'] = ''; $department->saveData($data); if (!isset($authenticationSourceData['AUTH_SOURCE_DATA']['DEPARTMENTS_TO_UNASSIGN'])) { $authenticationSourceData['AUTH_SOURCE_DATA']['DEPARTMENTS_TO_UNASSIGN'] = []; } if (!in_array($departmentUid, $authenticationSourceData['AUTH_SOURCE_DATA']['DEPARTMENTS_TO_UNASSIGN'])) { $authenticationSourceData['AUTH_SOURCE_DATA']['DEPARTMENTS_TO_UNASSIGN'][] = $departmentUid; } } } $authenticationSourceData['AUTH_SOURCE_DATA'] = json_encode($authenticationSourceData['AUTH_SOURCE_DATA']); $rbacAuthenticationSource->saveData($authenticationSourceData); } return [ 'status' => 'OK', 'success' => true ]; } catch (Exception $exception) { return ['success' => false, 'message' => $exception->getMessage()]; } } /** * Filters and organizes departments based on parent-child relationships * * This private method processes a flat array of departments and returns * only those that are direct children of the specified parent, excluding * terminated organizational units. Returns empty array on error. * * @param array $departments Complete list of department data from LDAP * @param string $parent Parent department distinguished name * @param string $terminatedOu Name of the terminated organizational unit to exclude * * @return array Array of department objects that are children of the specified parent, * empty array on error */ private function getDepartments($departments, $parent, $terminatedOu) { try { $currentDepartments = array(); // Create hash map for O(1) parent-child lookups $parentChildMap = []; foreach ($departments as $dept) { $parentKey = strtolower($dept['parent']); if (!isset($parentChildMap[$parentKey])) { $parentChildMap[$parentKey] = []; } $parentChildMap[$parentKey][] = $dept; } foreach ($departments as $key => $val) { if (strtolower($val['dn']) != strtolower($parent)) { if ((strtolower($val['parent']) == strtolower($parent)) && (strtolower($val['ou']) != strtolower($terminatedOu))) { $node = array(); $node['DEP_UID'] = $val['ou']; $node['DEP_TITLE'] = $val['ou']; $node['DEP_USERS'] = $val['users']; $node['DEP_DN'] = $val['dn']; // Use hash map for O(1) lookup instead of nested loop $valDnKey = strtolower($val['dn']); $node['HAS_CHILDREN'] = isset($parentChildMap[$valDnKey]); $node['DEP_LAST'] = false; $currentDepartments[] = $node; } } } $lastIndex = count($currentDepartments) - 1; if (isset($currentDepartments[$lastIndex])) { $currentDepartments[$lastIndex]['DEP_LAST'] = true; } return $currentDepartments; } catch (Exception $exception) { return []; } } /** * Recursively builds a hierarchical tree structure of departments * * This private method creates a tree representation of departments with * user count information and synchronization status. It includes recursion * depth protection to prevent infinite loops. Returns empty array on error. * * @param array $departments Complete list of department data from LDAP * @param string $parent Parent department distinguished name (empty for root level) * @param array $listDepartmentsNumberUsers Associative array of department UIDs and user counts * @param string $terminatedOu Name of the terminated organizational unit to exclude * @param int $depth Current recursion depth (default: 0, max: 50) * * @return TreeNodeAuthSource[] Array of TreeNodeAuthSource objects representing the department hierarchy, * empty array on error or max depth reached */ private function getChildrenDepartments($departments, $parent, $listDepartmentsNumberUsers, $terminatedOu, $depth = 0) { try { if ($depth >= 50) { return []; } $allDepartments = $this->getDepartments($departments, $parent, $terminatedOu); $department = new Department(); $departmentsObjects = []; foreach ($allDepartments as $departmentData) { $departmentObject = new TreeNodeAuthSource(); $departmentObject->text = htmlentities($departmentData['DEP_TITLE'], ENT_QUOTES, 'UTF-8'); $departmentDNData = $department->getDepUidIfExistsDN($departmentData['DEP_DN']); $departmentUid = $departmentDNData['data'][0]['DEP_UID'] ?? ''; if ($departmentUid != '') { $departmentObject->text .= ' (' . ($listDepartmentsNumberUsers[$departmentUid] ?? '0') . ')'; $departmentObject->checked = true; } else { $departmentObject->checked = false; } if ((int)$departmentData['HAS_CHILDREN'] == 1) { $departmentObject->children = $this->getChildrenDepartments($departments, $departmentData['DEP_DN'], $listDepartmentsNumberUsers, $terminatedOu, $depth + 1); } $departmentObject->id = urlencode($departmentData['DEP_DN']); $departmentsObjects[] = $departmentObject; } return $departmentsObjects; } catch (Exception $exception) { return []; } } /** * Identifies departments that should be unsynchronized * * This private method compares currently synchronized departments with * the new list of departments to synchronize, returning those that should * be removed from synchronization. Returns empty array on error. * * @param array $depsToCheck Array of department distinguished names to keep synchronized * * @return array Array of department distinguished names to unsynchronize, * empty array on error */ private function getDepartmentsToUncheck($depsToCheck) { try { $department = new Department(); $departmentsWithDN = $department->getDepartmentsWithDN(); $departmentsWithDN = $departmentsWithDN['data'] ?? []; $depsToUncheck = []; foreach ($departmentsWithDN as $departmentWithDN) { if (!in_array($departmentWithDN['DEP_LDAP_DN'], $depsToCheck)) { $depsToUncheck[] = $departmentWithDN['DEP_LDAP_DN']; } } return $depsToUncheck; } catch (Exception $exception) { return []; } } /** * Identifies groups that should be unsynchronized * * This private method compares currently synchronized groups with * the new list of groups to synchronize, returning those that should * be removed from synchronization. Returns empty array on error. * * @param array $groupsToCheck Array of group distinguished names to keep synchronized * * @return array Array of group distinguished names to unsynchronize, * empty array on error */ private function getGroupsToUncheck($groupsToCheck) { try { $groupsWithDN = $this->getGroupsWithDN(); if (!is_array($groupsWithDN)) { return []; } $groupsToUncheck = []; foreach ($groupsWithDN as $groupWithDN) { if (!in_array($groupWithDN['GRP_LDAP_DN'], $groupsToCheck)) { $groupsToUncheck[] = $groupWithDN['GRP_LDAP_DN']; } } return $groupsToUncheck; } catch (Exception $e) { return []; } } /** * Retrieves all groups that have LDAP distinguished names * * This private method fetches all system groups that are currently * synchronized with LDAP (have non-empty LDAP DN values). * Uses a high limit (100,000) which may cause performance issues with large datasets. * * @return array Array of group data with LDAP distinguished names, * empty array on error * * @warning High limit of 100,000 records may cause memory issues */ private function getGroupsWithDN() { try { $groupwf = new Groupwf(); $filters = [ 'start' => 0, 'limit' => 100000, 'conditions' => ['GRP_LDAP_DN' => ['!=', '']] ]; $allGroups = $groupwf->show($filters); return $allGroups['data'] ?? []; } catch (Exception $exception) { return []; } } } /** * TreeNodeAuthSource Class * * This class represents a node in a tree structure used for displaying * authentication source hierarchical data (groups and departments). * It extends stdClass to provide a flexible object structure for tree rendering. * * @package Lurana\Workflow\Engine\Classes */ class TreeNodeAuthSource extends stdClass { /** @var string Display text for the tree node */ public $text = ''; /** @var string CSS class for styling the tree node */ public $cls = ''; /** @var bool Indicates if this is a leaf node (no children) */ public $leaf = false; /** @var bool Indicates if this node is selected/checked */ public $checked = false; /** @var array Array of child TreeNodeAuthSource objects */ public $children = array(); /** @var string Unique identifier for the tree node */ public $id = ''; }