diff --git a/workflow/engine/classes/AuthSources.php b/workflow/engine/classes/AuthSources.php index efb4a62c2..24242a9d7 100644 --- a/workflow/engine/classes/AuthSources.php +++ b/workflow/engine/classes/AuthSources.php @@ -7,11 +7,43 @@ use ProcessMaker\Model\GroupUser; use ProcessMaker\Model\Groupwf; use ProcessMaker\Model\User; use ProcessMaker\Model\Department; -use Illuminate\Support\Facades\Log; -use Illuminate\Support\Facades\Cache; +/** + * AuthSources Class + * + * This class manages authentication sources for the Lurana workflow engine. + * It provides functionality to handle LDAP authentication sources including: + * - Listing and managing authentication sources + * - Testing LDAP connections + * - Importing users from LDAP + * - Managing groups and departments synchronization + * + * @package Lurana\Workflow\Engine\Classes + * @author Lurana Team + * @version 1.0 + */ class AuthSources { + /** + * Retrieves a paginated list of authentication sources + * + * This method fetches authentication sources with pagination support, + * filtering, and sorting capabilities. It also includes user count + * information for each authentication source. + * + * @param string $userUid The unique identifier of the requesting user + * @param int $start Starting position for pagination (default: 0) + * @param int $limit Maximum number of records to return (default: 25) + * @param string $orderBy Field name to sort by (optional) + * @param string $ascending Sort direction: 'asc' or 'desc' (default: 'asc', invalid values auto-convert to 'asc') + * @param string $filter Text filter to search authentication sources (optional) + * + * @return array Response array containing: + * - success: boolean indicating operation success + * - sources: array of authentication source data + * - total_sources: total count of authentication sources + * - message: error message if operation fails + */ public function getListAuthSources($userUid, $start = 0, $limit = 25, $orderBy = '', $ascending = 'asc' , $filter = '') { try { if ($limit == 0) { @@ -48,22 +80,21 @@ class AuthSources $rbacAuthenticationSource = new RbacAuthenticationSource(); $authSourceReturn = $rbacAuthenticationSource->show($filters); - global $RBAC; - $auth = $RBAC->getAllUsersByAuthSource(); + $rbacUsers = new RbacUsers(); + $usersByAuthSource = $rbacUsers->getAllUsersByAuthSource(); + $auth = []; + foreach (($usersByAuthSource['data'] ?? []) as $user) { + $auth[$user['UID_AUTH_SOURCE']] = $user['CNT']; + } $sources = []; - foreach ($authSourceReturn['data'] as $key => $authSourceRow) { - $values = explode('_', $authSourceRow['AUTH_SOURCE_PASSWORD']); - foreach ($values as $value) { - if ($value == '2NnV3ujj3w') { - $authSourceRow['AUTH_SOURCE_PASSWORD'] = G::decrypt($values[0], $authSourceRow['AUTH_SOURCE_SERVER_NAME']); - } - } + 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)) { @@ -71,7 +102,7 @@ class AuthSources } $authSourceRow['AUTH_ANONYMOUS'] = (string)$authSourceRow['AUTH_ANONYMOUS']; $sources[] = $authSourceRow; - $index = sizeof($sources) - 1; + $index = array_key_last($sources); $sources[$index]['CURRENT_USERS'] = isset($auth[$sources[$index]['AUTH_SOURCE_UID']]) ? $auth[$sources[$index]['AUTH_SOURCE_UID']] : 0; } @@ -86,9 +117,29 @@ class AuthSources } } + /** + * 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 { - $conditions = ['AUTH_SOURCE_UID'=> $authSourceUid]; + 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'] ]; @@ -97,6 +148,21 @@ class AuthSources } } + /** + * 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; @@ -108,6 +174,7 @@ class AuthSources $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]; @@ -135,10 +202,25 @@ class AuthSources } } + /** + * 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) { @@ -150,33 +232,83 @@ class AuthSources } } + /** + * 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 { - $authSourceData['AUTH_SOURCE_VERSION'] = 3; $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'] ); - $rbacAuthenticationSource = new RbacAuthenticationSource(); $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(); - foreach ($usersAuthSources['data'] as $row) { + $listUsers = []; + foreach (($usersAuthSources['data'] ?? []) as $row) { $listUsers[strtolower($row['USR_USERNAME'])] = $row['UID_AUTH_SOURCE']; } @@ -184,14 +316,15 @@ class AuthSources $ldapSource->authSourceUid = $authSourceUid; $result = $ldapSource->searchUsersLdap($filters['text'], $filters['start'], $filters['limit']); - $arrayData = array(); - foreach ($result['data'] as $value) { + $arrayData = []; + foreach (($result['data'] ?? []) as $value) { $listUsersData = $value; + $usernameLower = strtolower($listUsersData['sUsername']); - if (!isset($listUsers[strtolower($listUsersData['sUsername'])])) { + if (!isset($listUsers[$usernameLower])) { $listUsersData['STATUS'] = G::LoadTranslation('ID_NOT_IMPORTED'); $listUsersData['IMPORT'] = 1; - } elseif ($authSourceUid === $listUsers[strtolower($listUsersData['sUsername'])]) { + } elseif ($authSourceUid === $listUsers[$usernameLower]) { $listUsersData['STATUS'] = G::LoadTranslation('ID_IMPORTED'); $listUsersData['IMPORT'] = 0; } else { @@ -208,20 +341,40 @@ class AuthSources } } + /** + * 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); - $authSourceReturn = $authSourceReturn['data'][0]; - - $aAttributes = array(); - if (isset($authSourceReturn['AUTH_SOURCE_DATA']['AUTH_SOURCE_GRID_ATTRIBUTE'])) { - $aAttributes = $authSourceReturn['AUTH_SOURCE_DATA']['AUTH_SOURCE_GRID_ATTRIBUTE']; + + if (empty($authSourceReturn['data']) || !isset($authSourceReturn['data'][0])) { + throw new Exception('Authentication source not found'); } - $usersCreated = ''; - $countUsers = 0; + $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; @@ -229,9 +382,6 @@ class AuthSources $aData = array(); $aData['USR_USERNAME'] = str_replace('*', "'", $aUser['sUsername']); $aData['USR_PASSWORD'] = '00000000000000000000000000000000'; - // note added by gustavo gustavo-at-colosa.com - // asign the FirstName and LastName variables - // add replace to change D*Souza to D'Souza by krlos $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']); @@ -240,15 +390,14 @@ class AuthSources $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']; - // validating with regexp if there are some missing * inside the DN string - // if it's so the is changed to the ' character preg_match('/[a-zA-Z]\*[a-zA-Z]/', $aUser['sDN'], $matches); - foreach ($matches as $key => $match) { + foreach ($matches as $match) { $newMatch = str_replace('*', '\'', $match); $aUser['sDN'] = str_replace($match, $newMatch, $aUser['sDN']); } @@ -260,9 +409,8 @@ class AuthSources } $sUserUID = $RBAC->createUser($aData, $usrRole, $authSourceReturn['AUTH_SOURCE_NAME']); - $usersCreated .= $aData['USR_USERNAME'] . ' '; - $countUsers++; + // 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; @@ -275,7 +423,6 @@ class AuthSources if (isset($aUser[$value['attributeUser']])) { $aData[$value['attributeUser']] = str_replace('*', "'", $aUser[$value['attributeUser']]); if ($value['attributeUser'] == 'USR_STATUS') { - $evalValue = $aData[$value['attributeUser']]; $statusValue = $aData['USR_STATUS']; $aData[$value['attributeUser']] = $statusValue; } @@ -291,6 +438,19 @@ class AuthSources } } + /** + * 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(); @@ -299,7 +459,7 @@ class AuthSources $allGroupsLdap = []; foreach ($groupsLdap as $group) { - $node = array(); + $node = []; $node['GRP_UID'] = $group['cn']; $node['GRP_TITLE'] = $group['cn']; $node['GRP_USERS'] = $group['users']; @@ -310,7 +470,7 @@ class AuthSources $groupUser = new GroupUser(); $groupsNumberUsers = $groupUser->getNumberOfUsersByGroups(); $listGroupsNumberUsers = []; - foreach ($groupsNumberUsers['data'] as $group) { + foreach (($groupsNumberUsers['data'] ?? []) as $group) { $listGroupsNumberUsers[$group['GRP_UID']] = $group['NUM_REC']; } @@ -319,9 +479,9 @@ class AuthSources foreach ($allGroupsLdap as $group) { $groupObject = new TreeNodeAuthSource(); $groupObject->text = htmlentities($group['GRP_TITLE'], ENT_QUOTES, 'UTF-8'); - $groupUid = $groupwf->getGroupWithDN($group['GRP_DN']); - if (!empty($groupUid[0]['GRP_UID'])) { - $groupUid = $groupUid[0]['GRP_UID']; + $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 { @@ -337,18 +497,30 @@ class AuthSources } } - public function searchDepartaments($authSourceUid) { + /** + * 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(); - $departmentsObjects = array(); $user = new User(); $departmentsNumberUsers = $user->getNumberOfUsersByDepartments(); $listDepartmentsNumberUsers = []; - foreach ($departmentsNumberUsers['data'] as $group) { - $listDepartmentsNumberUsers[$group['DEP_UID']] = $group['NUM_REC']; + foreach (($departmentsNumberUsers['data'] ?? []) as $department) { + $listDepartmentsNumberUsers[$department['DEP_UID']] = $department['NUM_REC']; } $departmentsObject = $this->getChildrenDepartments($departments, '', $listDepartmentsNumberUsers, $ldapSource->terminatedOu); @@ -358,53 +530,43 @@ class AuthSources } } + /** + * 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) { - $groupsToCheck = explode('|', $groupsDN); - $groupsToCheck = array_map('urldecode', $groupsToCheck); - $groupsToUncheck = $this->getGroupsToUncheck($groupsToCheck); + 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); - $authenticationSourceData = $authSourceReturn['data'][0]; - $authenticationSourceData['AUTH_SOURCE_DATA'] = json_decode($authenticationSourceData['AUTH_SOURCE_DATA'], true); + $filters = ['conditions' => ['AUTH_SOURCE_UID'=> $authSourceUid]]; + $rbacAuthenticationSource = new RbacAuthenticationSource(); + $authSourceReturn = $rbacAuthenticationSource->show($filters); - $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; + if (empty($authSourceReturn['data']) || !isset($authSourceReturn['data'][0])) { + throw new Exception('Authentication source not found'); } - $filters = array( - 'fields' => ['GRP_UID'], - 'conditions' => ['GRP_TITLE' => $groupTitle, 'GRP_STATUS' => 'ACTIVE'] - ); - $allGroups = $groupwf->show($filters); - $groupUid = $allGroups['data'][0]['GRP_UID'] ?? ''; + $authenticationSourceData = $authSourceReturn['data'][0]; + $authenticationSourceData['AUTH_SOURCE_DATA'] = json_decode($authenticationSourceData['AUTH_SOURCE_DATA'], true); - if ($groupUid === '') { - $group = [ - 'GRP_TITLE' => $groupTitle, - 'GRP_LDAP_DN' => $groupDN - ]; - } else { - $group = $allGroups['data'][0]; - $group['GRP_LDAP_DN'] = $groupDN; - } + $ldapSource = new LdapSource(); + $ldapSource->authSourceUid = $authSourceUid; - $groupwf->saveData($group); - } - - if (count($groupsToUncheck) > 0) { - foreach ($groupsToUncheck as $groupDN) { + $groupwf = new Groupwf(); + foreach ($groupsToCheck as $groupDN) { $ous = $ldapSource->custom_ldap_explode_dn($groupDN); $currentGroup = array_shift($ous); $groupAux = explode('=', $currentGroup); @@ -419,311 +581,431 @@ class AuthSources 'conditions' => ['GRP_TITLE' => $groupTitle, 'GRP_STATUS' => 'ACTIVE'] ); $allGroups = $groupwf->show($filters); - $groupUid = $allGroups['data'][0]['GRP_UID'] ?? ''; + $groupUid = ''; + if (!empty($allGroups['data']) && isset($allGroups['data'][0]['GRP_UID'])) { + $groupUid = $allGroups['data'][0]['GRP_UID']; + } - if ($groupUid != '') { + if ($groupUid === '') { + $group = [ + 'GRP_TITLE' => $groupTitle, + 'GRP_LDAP_DN' => $groupDN + ]; + } else { $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'] = []; + $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; } - if (!in_array($groupUid, $authenticationSourceData['AUTH_SOURCE_DATA']['GROUPS_TO_UNASSIGN'])) { - $authenticationSourceData['AUTH_SOURCE_DATA']['GROUPS_TO_UNASSIGN'][] = $groupUid; + + $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); } - $authenticationSourceData['AUTH_SOURCE_DATA'] = json_encode($authenticationSourceData['AUTH_SOURCE_DATA']); - $rbacAuthenticationSource->saveData($authenticationSourceData); - } - $responseSaveGroups = [ - 'status' => 'OK', - 'success' => true - ]; - return $responseSaveGroups; - - if ($ldapSource->checkDuplicateTitles()) { - $response->warning = G::LoadTranslation('ID_IT_WAS_IDENTIFIED_DUPLICATED_GROUPS_PLEASE_REMOVE_THESE_GROUPS'); + 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) { - $depsToCheck = ($departmentsDN != '') ? explode('|', $departmentsDN) : []; - $depsToCheck = array_map('urldecode', $depsToCheck); + try { + $depsToCheck = ($departmentsDN != '') ? explode('|', $departmentsDN) : []; + $depsToCheck = array_map('urldecode', $depsToCheck); - $depsToUncheck = $this->getDepartmentsToUncheck($depsToCheck); + $depsToUncheck = $this->getDepartmentsToUncheck($depsToCheck); - $filters = ['conditions' => ['AUTH_SOURCE_UID'=> $authSourceUid]]; - $rbacAuthenticationSource = new RbacAuthenticationSource(); - $authSourceReturn = $rbacAuthenticationSource->show($filters); - $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 == '') { - $response = new stdClass(); - $response->status = 'ERROR'; - $response->message = G::LoadTranslation( - 'ID_DEPARTMENT_CHECK_PARENT_DEPARTMENT', - [$parentDn, $departmentTitle] - ); - echo json_encode($response); - exit(0); - } - } - - $filters = array( - 'conditions' => ['DEP_STATUS' => 'ACTIVE', 'DEP_TITLE' => $departmentTitle] - ); - $allDepartments = $department->show($filters); - $departmentUid = $allDepartments['data'][0]['DEP_UID'] ?? ''; - - if (empty($departmentUid)) { - $data = [ - 'DEP_TITLE' => stripslashes($departmentTitle), - 'DEP_PARENT' => $parentUid, - 'DEP_LDAP_DN' => $departmentDn, - 'DEP_REF_CODE' => '' - ]; - $saveDerpartment = $department->saveData($data); - - if (empty($saveDerpartment)) { - $response = new stdClass(); - $response->status = 'ERROR'; - $response->message = G::LoadTranslation('ID_DEPARTMENT_ERROR_CREATE'); - echo json_encode($response); - exit(0); - } - } else { - - $data = $allDepartments['data'][0]; - $data['DEP_LDAP_DN'] = $departmentDn; - $department->saveData($data); - } + $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); - if (count($depsToUncheck) > 0) { - $baseDnLength = strlen($authenticationSourceData['AUTH_SOURCE_BASE_DN']); - foreach ($depsToUncheck as $departmentDn) { + $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 != '' && - 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); - } - $responseSaveGroups = [ - 'status' => 'OK', - 'success' => true - ]; - return $responseSaveGroups; - - if ($ldapAdvanced->checkDuplicateDepartmentTitles()) { - $response->warning = G::LoadTranslation('ID_IT_WAS_IDENTIFIED_DUPLICATED_DEPARTMENTS_PLEASE_REMOVE_THESE_DEPARTMENTS'); - } - } - - private function getDepartments($departments, $parent, $terminatedOu) - { - $parentDepartments = $departments; - $childDepartments = $departments; - $currentDepartments = array(); - - foreach ($parentDepartments 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']; - $node['HAS_CHILDREN'] = false; - $departments[$key]['hasChildren'] = false; - - foreach ($childDepartments as $key2 => $val2) { - if (strtolower($val2['parent']) == strtolower($val['dn'])) { - $node['HAS_CHILDREN'] = true; - $departments[$key]['hasChildren'] = true; - break; + 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); } } - $node['DEP_LAST'] = false; - $currentDepartments[] = $node; + $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 (isset($currentDepartments[count($currentDepartments) - 1])) { - $currentDepartments[count($currentDepartments) - 1]['DEP_LAST'] = true; - } - - return $currentDepartments; - } - - private function getChildrenDepartments($departments, $parent, $listDepartmentsNumberUsers, $terminatedOu) { - $allDepartments = $this->getDepartments($departments, $parent, $terminatedOu); - - $department = new Department(); - 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] ?? '') . ')'; - $departmentObject->checked = true; - } else { - $departmentObject->checked = false; + 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); } - if ($departmentData['HAS_CHILDREN'] == 1) { - $departmentObject->children = $this->getChildrenDepartments($departments, $departmentData['DEP_DN'], $listDepartmentsNumberUsers, $terminatedOu); + 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; } - $departmentObject->id = urlencode($departmentData['DEP_DN']); - $departmentsObjects[] = $departmentObject; + 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 []; } - return $departmentsObjects; } + /** + * 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) { - $departament = new Department(); - $departmentsWithDN = $departament->getDepartmentsWithDN(); - $departmentsWithDN = $departmentsWithDN['data']; + try { + $department = new Department(); + $departmentsWithDN = $department->getDepartmentsWithDN(); + $departmentsWithDN = $departmentsWithDN['data'] ?? []; - $depsToUncheck = []; - foreach ($departmentsWithDN as $departmentWithDN) { - $found = false; - - foreach ($depsToCheck as $depToCheck) { - if ($departmentWithDN['DEP_LDAP_DN'] == $depToCheck) { - $found = true; + $depsToUncheck = []; + foreach ($departmentsWithDN as $departmentWithDN) { + if (!in_array($departmentWithDN['DEP_LDAP_DN'], $depsToCheck)) { + $depsToUncheck[] = $departmentWithDN['DEP_LDAP_DN']; } } - if (!$found) { - $depsToUncheck[] = $departmentWithDN['DEP_LDAP_DN']; - } + return $depsToUncheck; + } catch (Exception $exception) { + return []; } - - return $depsToUncheck; } + /** + * 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) { - $groupsWithDN = $this->getGroupsWithDN(); - $groupsToUncheck = array(); - - foreach ($groupsWithDN as $groupWithDN) { - $found = false; - - foreach ($groupsToCheck as $groupToCheck) { - if ($groupWithDN['GRP_LDAP_DN'] == $groupToCheck) { - $found = true; + 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']; } } - - if (!$found) { - $groupsToUncheck[] = $groupWithDN['GRP_LDAP_DN']; - } + return $groupsToUncheck; + } catch (Exception $e) { + return []; } - - return $groupsToUncheck; } + /** + * 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() { - $groupwf = new Groupwf(); - $filters = array('start' => 0, 'limit' => 1000); - $allGroups = $groupwf->show($filters); - $allGroups = $allGroups['data']; - $groupsWithDN = array(); - - foreach ($allGroups as $group) { - if ($group['GRP_LDAP_DN'] != '') { - $groupsWithDN[] = $group; - } + try { + $groupwf = new Groupwf(); + $filters = [ + 'start' => 0, 'limit' => 100000, + 'conditions' => ['GRP_LDAP_DN' => ['!=', '']] + ]; + $allGroups = $groupwf->show($filters); + return $allGroups['data'] ?? []; + } catch (Exception $exception) { + return []; } - - return $groupsWithDN; - } - - private static function encrypt($plaintext, $key) { - $cipher = 'AES-256-CBC'; - $ivlen = openssl_cipher_iv_length($cipher); - $iv = openssl_random_pseudo_bytes($ivlen); - - $ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, OPENSSL_RAW_DATA, $iv); - - $ciphertext = base64_encode($iv . $ciphertext_raw); - return $ciphertext; - } - - private static function decrypt($ciphertext_b64, $key) { - $cipher = 'AES-256-CBC'; - $ivlen = openssl_cipher_iv_length($cipher); - - $ciphertext = base64_decode($ciphertext_b64); - $iv = substr($ciphertext, 0, $ivlen); - $ciphertext_raw = substr($ciphertext, $ivlen); - - $plaintext = openssl_decrypt($ciphertext_raw, $cipher, $key, OPENSSL_RAW_DATA, $iv); - return $plaintext; } } -class TreeNodeAuthSource extends stdclass +/** + * 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 = ''; } \ No newline at end of file diff --git a/workflow/engine/classes/LdapSource.php b/workflow/engine/classes/LdapSource.php index f1f15ebd5..9d7a9dc68 100644 --- a/workflow/engine/classes/LdapSource.php +++ b/workflow/engine/classes/LdapSource.php @@ -8,10 +8,6 @@ use ProcessMaker\BusinessModel\User; use ProcessMaker\Model\Department; use ProcessMaker\Model\Groupwf; - -/** - * Class LdapAdvanced - */ class LdapSource { public $authSourceUid; @@ -31,19 +27,17 @@ class LdapSource private $arrayAttributesForUser = ["dn", "uid", "samaccountname", "givenname", "sn", "cn", "mail", "userprincipalname", "useraccountcontrol", "accountexpires", "manager"]; - public function ldapConnection($authSourceData) { - $pass = explode('_', $authSourceData['AUTH_SOURCE_PASSWORD']); + public function __destruct() { + if ($this->ldapcnn) { + @ldap_close($this->ldapcnn); + } + } + public function ldapConnection($authSourceData) { // Removing sensitive data $loggableAuthSource = $authSourceData; unset($loggableAuthSource['AUTH_SOURCE_PASSWORD']); - foreach ($pass as $index => $value) { - if ($value == '2NnV3ujj3w') { - $authSourceData['AUTH_SOURCE_PASSWORD'] = G::decrypt($pass[0], $authSourceData['AUTH_SOURCE_SERVER_NAME']); - } - } - $ldapcnn = ldap_connect($authSourceData['AUTH_SOURCE_SERVER_NAME'], $authSourceData['AUTH_SOURCE_PORT']); $this->stdLog($ldapcnn, 'ldap_connect', $loggableAuthSource); @@ -74,7 +68,7 @@ class LdapSource $message = 'Unable to bind to server: ' . $ldapServer . 'LDAP-Errno: ' . ldap_errno($ldapcnn) . ' : ' . ldap_error($ldapcnn) . " \n"; throw new Exception($message); } - + $this->ldapcnn = $ldapcnn; return ['connection' =>$ldapcnn, 'startTLS' => $resultLDAPStartTLS]; } @@ -82,22 +76,21 @@ class LdapSource try { $arrayGroup = []; - $rbac = RBAC::getSingleton(); - - if (is_null($rbac->authSourcesObj)) { - $rbac->authSourcesObj = new AuthenticationSource(); - } - - $arrayAuthenticationSourceData = $rbac->authSourcesObj->load($this->authSourceUid); + $filters = ['conditions' => ['AUTH_SOURCE_UID'=> $this->authSourceUid]]; + $rbacAuthenticationSource = new RbacAuthenticationSource(); + $authSourceReturn = $rbacAuthenticationSource->show($filters); + $authenticationSourceData = $authSourceReturn['data'][0]; + $authenticationSourceData['AUTH_SOURCE_DATA'] = json_decode($authenticationSourceData['AUTH_SOURCE_DATA'], true); + $authenticationSourceData['AUTH_SOURCE_PASSWORD'] = G::decrypt($authenticationSourceData['AUTH_SOURCE_PASSWORD'], URL_KEY); if (is_null($this->ldapcnn)) { - $ldapcnn = $this->ldapConnection($arrayAuthenticationSourceData); + $ldapcnn = $this->ldapConnection($authenticationSourceData); $this->ldapcnn = $ldapcnn['connection']; } $ldapcnn = $this->ldapcnn; // Get Groups - $limit = $this->getPageSizeLimitByData($arrayAuthenticationSourceData); + $limit = $this->getPageSizeLimitByData($authenticationSourceData); $flagError = false; $filter = '(' . $this->arrayObjectClassFilter['group'] . ')'; $this->log($ldapcnn, 'search groups with Filter: ' . $filter); @@ -106,7 +99,7 @@ class LdapSource do { $searchResult = @ldap_search( $ldapcnn, - $arrayAuthenticationSourceData['AUTH_SOURCE_BASE_DN'], + $authenticationSourceData['AUTH_SOURCE_BASE_DN'], $filter, ['dn', 'cn'], 0, @@ -119,7 +112,7 @@ class LdapSource $this->stdLog($ldapcnn, "ldap_search", ["filter" => $filter, "attributes" => ['dn', 'cn']]); $context = [ - "baseDN" => $arrayAuthenticationSourceData['AUTH_SOURCE_BASE_DN'], + "baseDN" => $authenticationSourceData['AUTH_SOURCE_BASE_DN'], "filter" => $filter, "attributes" => ['dn', 'cn'] ]; @@ -172,7 +165,6 @@ class LdapSource } $this->log($ldapcnn, 'found ' . count($arrayGroup) . ' groups: ' . $str); - return $arrayGroup; } catch (Exception $e) { throw $e; @@ -187,6 +179,8 @@ class LdapSource $rbacAuthenticationSource = new RbacAuthenticationSource(); $authSourceReturn = $rbacAuthenticationSource->show($filters); $authenticationSourceData = $authSourceReturn['data'][0]; + $authenticationSourceData['AUTH_SOURCE_DATA'] = json_decode($authenticationSourceData['AUTH_SOURCE_DATA'], true); + $authenticationSourceData['AUTH_SOURCE_PASSWORD'] = G::decrypt($authenticationSourceData['AUTH_SOURCE_PASSWORD'], URL_KEY); if (is_null($this->ldapcnn)) { $ldapcnn = $this->ldapConnection($authenticationSourceData); @@ -290,7 +284,6 @@ class LdapSource } $this->log($ldapcnn, 'found ' . count($arrayDepartment) . ' departments: ' . $str); - return $arrayDepartment; } catch (Exception $e) { throw $e; @@ -459,6 +452,7 @@ class LdapSource $authSourceReturn = $rbacAuthenticationSource->show($filters); $arrayAuthenticationSourceData = $authSourceReturn['data'][0]; $arrayAuthenticationSourceData['AUTH_SOURCE_DATA'] = json_decode($arrayAuthenticationSourceData['AUTH_SOURCE_DATA'], true); + $arrayAuthenticationSourceData['AUTH_SOURCE_PASSWORD'] = G::decrypt($arrayAuthenticationSourceData['AUTH_SOURCE_PASSWORD'], URL_KEY); $attributeUserSet = []; $attributeSetAdd = []; @@ -512,39 +506,6 @@ class LdapSource if ((is_array($sUsername) && !empty($sUsername)) || trim($sUsername) != '') { $countUser++; - - /* Active Directory userAccountControl Values - Normal Day to Day Values: - 512 - Enable Account - 514 - Disable account - 544 - Account Enabled - Require user to change password at first logon - 4096 - Workstation/server - 66048 - Enabled, password never expires - 66050 - Disabled, password never expires - 262656 - Smart Card Logon Required - 532480 - Domain controller - 1 - script - 2 - accountdisable - 8 - homedir_required - 16 - lockout - 32 - passwd_notreqd - 64 - passwd_cant_change - 128 - encrypted_text_pwd_allowed - 256 - temp_duplicate_account - 512 - normal_account - 2048 - interdomain_trust_account - 4096 - workstation_trust_account - 8192 - server_trust_account - 65536 - dont_expire_password - 131072 - mns_logon_account - 262144 - smartcard_required - 524288 - trusted_for_delegation - 1048576 - not_delegated - 2097152 - use_des_key_only - 4194304 - dont_req_preauth - 8388608 - password_expired - 16777216 - trusted_to_auth_for_delegation - */ $userCountControl = ''; //Active Directory, openLdap if (isset($aAttr['useraccountcontrol'])) { @@ -598,7 +559,6 @@ class LdapSource } } } - return ($paged) ? ['numRecTotal' => $totalUser, 'data' => $arrayUser] : $arrayUser; } diff --git a/workflow/engine/methods/authSources/authSourcesProxy.php b/workflow/engine/methods/authSources/authSourcesProxy.php index 14797c3b0..7cb62163b 100644 --- a/workflow/engine/methods/authSources/authSourcesProxy.php +++ b/workflow/engine/methods/authSources/authSourcesProxy.php @@ -1,19 +1,41 @@ true]; + // Process the requested action using a switch statement switch ($action) { + /** + * Retrieve a paginated list of authentication sources + * Parameters: start, limit, textFilter, orderBy, ascending + */ case 'authSourcesList': $start = $_REQUEST['start'] ?? 0; $limit = $_REQUEST['limit'] ?? 25; @@ -24,6 +46,10 @@ try { $authSources = new AuthSources(); $responseProxy = $authSources->getListAuthSources($userUid, $start, $limit, $orderBy, $ascending, $filter); break; + /** + * Delete an authentication source by its UID + * Required parameter: auth_uid + */ case 'authSourcesDelete': if (!isset($_REQUEST['auth_uid'])) { throw new Exception('No auth source UID was sent'); @@ -32,16 +58,30 @@ try { $authSources = new AuthSources(); $responseProxy = $authSources->removeAuthSource($authSourceUid); break; + /** + * Verify if an authentication source name is unique/available + * Required parameter: AUTH_SOURCE_NAME + */ case 'authSourcesVerifyName': if (empty($_REQUEST['AUTH_SOURCE_NAME'])) { - throw new Exception('No auth source UID was sent'); + throw new Exception('No auth source name was sent'); } $authSourceName = $_REQUEST['AUTH_SOURCE_NAME']; $authSources = new AuthSources(); $responseProxy = $authSources->verifyAuthSourceName($authSourceName); break; + /** + * Test connection to an authentication source (LDAP/AD) + * Required parameter: AUTH_ANONYMOUS + * If anonymous auth is enabled, clears username and password + */ case 'authSourcesTestConnection': + if (isset($_REQUEST['AUTH_ANONYMOUS']) === false) { + throw new Exception('No auth anonymous was sent'); + } + + // Clear credentials if anonymous authentication is enabled if ($_REQUEST['AUTH_ANONYMOUS'] == '1') { $_REQUEST['AUTH_SOURCE_SEARCH_USER'] = ''; $_REQUEST['AUTH_SOURCE_PASSWORD'] = ''; @@ -53,12 +93,19 @@ try { $authSources = new AuthSources(); $responseProxy = $authSources->testConnection($authSourceData); break; + /** + * Save (create or update) an authentication source configuration + * Processes form data, separates common fields from extra data, + * and handles grid attributes if enabled + */ case 'authSourcesSave': $temporalData = $_REQUEST; + // Process grid attributes if the show grid checkbox is enabled if (isset($temporalData['AUTH_SOURCE_SHOWGRID-checkbox'])) { if ($temporalData['AUTH_SOURCE_SHOWGRID-checkbox'] == 'on') { $temporalData['AUTH_SOURCE_SHOWGRID'] = 'on'; + // Parse JSON grid attributes and convert to array format $attributes = G::json_decode($temporalData['AUTH_SOURCE_GRID_TEXT']); $con = 1; foreach ($attributes as $value) { @@ -69,20 +116,19 @@ try { unset($temporalData['AUTH_SOURCE_SHOWGRID-checkbox']); } + // Clear credentials for anonymous authentication if ($temporalData['AUTH_ANONYMOUS'] == '1') { $temporalData['AUTH_SOURCE_SEARCH_USER'] = ''; $temporalData['AUTH_SOURCE_PASSWORD'] = ''; } unset($temporalData['AUTH_SOURCE_GRID_TEXT']); - unset($temporalData['DELETE1']); - unset($temporalData['DELETE2']); unset($temporalData['AUTH_SOURCE_ATTRIBUTE_IDS']); - unset($temporalData['AUTH_SOURCE_SHOWGRID_FLAG']); - unset($temporalData['AUTH_SOURCE_GRID_TEXT']); + // Define core authentication source fields $commonFields = array('AUTH_SOURCE_UID', 'AUTH_SOURCE_NAME', 'AUTH_SOURCE_PROVIDER', 'AUTH_SOURCE_SERVER_NAME', 'AUTH_SOURCE_PORT', 'AUTH_SOURCE_ENABLED_TLS', 'AUTH_ANONYMOUS', 'AUTH_SOURCE_SEARCH_USER', 'AUTH_SOURCE_PASSWORD', 'AUTH_SOURCE_VERSION', 'AUTH_SOURCE_BASE_DN', 'AUTH_SOURCE_OBJECT_CLASSES', 'AUTH_SOURCE_ATTRIBUTES'); + // Separate common fields from extra configuration data $authSourceData = $authSourceExtraData = array(); foreach ($temporalData as $sField => $sValue) { if (in_array($sField, $commonFields)) { @@ -92,6 +138,7 @@ try { } } + // Remove grid attributes if grid display is disabled if (!isset($authSourceExtraData['AUTH_SOURCE_SHOWGRID']) || $authSourceExtraData['AUTH_SOURCE_SHOWGRID'] == 'off') { unset($authSourceExtraData['AUTH_SOURCE_GRID_ATTRIBUTE']); unset($authSourceExtraData['AUTH_SOURCE_SHOWGRID']); @@ -102,12 +149,18 @@ try { $authSources = new AuthSources(); $responseProxy = $authSources->saveAuthSource($authSourceData); break; + /** + * Search for users in an authentication source for import + * Required parameter: sUID (auth source UID) + * Optional parameters: start, limit/pageSize, sKeyword + */ case 'authSourcesImportSearchUsers': - if (!isset($_REQUEST['sUID'])) { + if (!isset($_POST['sUID'])) { throw new Exception('No auth source UID was sent'); } $authSourceUid = $_POST['sUID']; + // Set up search filters with default values $filters = [ 'start'=> $_POST['start'] ?? 0, 'limit'=> $_POST['limit'] ?? ($_POST['pageSize'] ?? 10), @@ -117,6 +170,10 @@ try { $authSources = new AuthSources(); $responseProxy = $authSources->searchUsers($authSourceUid, $filters); break; + /** + * Import selected users from an authentication source + * Required parameters: UsersImport (JSON), AUTH_SOURCE_UID + */ case 'authSourcesImportUsers': if (!isset($_REQUEST['UsersImport'])) { throw new Exception('There are no users to import'); @@ -128,11 +185,16 @@ try { $authSourceUid = $_REQUEST['AUTH_SOURCE_UID']; $usersImport = $_REQUEST['UsersImport']; + // Decode JSON list of users to import $usersImport = json_decode($usersImport, true); $authSources = new AuthSources(); $responseProxy = $authSources->importUsers($authSourceUid, $usersImport); break; + /** + * Load/search departments from an authentication source + * Required parameter: authUid (auth source UID) + */ case 'authSourcesImportLoadDepartment': if (!isset($_REQUEST['authUid'])) { throw new Exception('No auth source UID was sent'); @@ -140,14 +202,22 @@ try { $authSourceUid = $_REQUEST['authUid']; $authSources = new AuthSources(); - $responseProxy = $authSources->searchDepartaments($authSourceUid); + $responseProxy = $authSources->searchDepartments($authSourceUid); break; + /** + * Save/import selected departments from an authentication source + * Required parameters: departmentsDN, authUid + */ case 'authSourcesImportSaveDepartment': $authSources = new AuthSources(); $departmentsDN = $_REQUEST['departmentsDN']; $authSourceUid = $_REQUEST['authUid']; $responseProxy = $authSources->saveDepartments($departmentsDN, $authSourceUid); break; + /** + * Load/search groups from an authentication source + * Required parameter: authUid (auth source UID) + */ case 'authSourcesImportLoadGroup': if (!isset($_REQUEST['authUid'])) { throw new Exception('No auth source UID was sent'); @@ -157,23 +227,31 @@ try { $authSources = new AuthSources(); $responseProxy = $authSources->searchGroups($authSourceUid); break; + /** + * Save/import selected groups from an authentication source + * Required parameters: groupsDN, authUid + */ case 'authSourcesImportSaveGroup': $authSources = new AuthSources(); $groupsDN = $_REQUEST['groupsDN']; $authSourceUid = $_REQUEST['authUid']; $responseProxy = $authSources->saveGroups($groupsDN, $authSourceUid); break; + /** + * Handle invalid/unknown actions + */ default: throw new Exception('The action "' . $action . '" is not allowed'); - break; } + // Return successful response as JSON header('Content-Type: application/json'); echo json_encode($responseProxy, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } catch (Exception $exception) { + // Handle any exceptions and return error response $responseProxy['success'] = false; - $responseProxy['message'] = htmlentities($exception->getMessage(), ENT_QUOTES, 'UTF-8'); - + $responseProxy['message'] = 'An error occurred while processing your request: '; + $responseProxy['message'] .= htmlentities($exception->getMessage(), ENT_QUOTES, 'UTF-8'); header('Content-Type: application/json'); echo json_encode($responseProxy, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } diff --git a/workflow/engine/src/ProcessMaker/Model/RbacUsers.php b/workflow/engine/src/ProcessMaker/Model/RbacUsers.php index 72cb07dc1..77403a041 100644 --- a/workflow/engine/src/ProcessMaker/Model/RbacUsers.php +++ b/workflow/engine/src/ProcessMaker/Model/RbacUsers.php @@ -6,6 +6,7 @@ use App\Factories\HasFactory; use Exception; use Illuminate\Database\Eloquent\Model; use ProcessMaker\Model\RbacUsersRoles; +use Illuminate\Support\Facades\DB; class RbacUsers extends Model { @@ -94,4 +95,17 @@ class RbacUsers extends Model ]; return $result; } + + public static function getAllUsersByAuthSource() + { + $query = static::query(); + $query->select('UID_AUTH_SOURCE', DB::raw('COUNT(*) AS CNT')); + $query->where('USR_STATUS', '!=', 'CLOSED'); + $query->where('USR_USERNAME', '!=', ''); + $query->groupBy('UID_AUTH_SOURCE'); + + $data =$query->get()->toArray(); + $result = ['data' => $data]; + return $result; + } }