From 9ea66b21a14eb0b9488f28fb8705afffe9b0b79f Mon Sep 17 00:00:00 2001 From: Brayan Pereyra Date: Tue, 16 Sep 2025 16:04:49 +0000 Subject: [PATCH] TASK-222 Add LdapSource Class to Core --- workflow/engine/classes/LdapSource.php | 441 +++++++++++++++++++++++++ 1 file changed, 441 insertions(+) create mode 100644 workflow/engine/classes/LdapSource.php diff --git a/workflow/engine/classes/LdapSource.php b/workflow/engine/classes/LdapSource.php new file mode 100644 index 000000000..8eb9c5226 --- /dev/null +++ b/workflow/engine/classes/LdapSource.php @@ -0,0 +1,441 @@ + "|(objectclass=inetorgperson)(objectclass=organizationalperson)(objectclass=person)(objectclass=user)", + "group" => "|(objectclass=posixgroup)(objectclass=group)(objectclass=groupofuniquenames)", + "department" => "|(objectclass=organizationalunit)" + ]; + private $arrayAttributes = [ + "ldap" => ["uid" => "uid", "member" => "memberuid"], //OpenLDAP + "ad" => ["uid" => "samaccountname", "member" => "member"], //Active Directory + "ds" => ["uid" => "uid", "member" => "uniquemember"] //389 DS + ]; + + private $arrayAttributesForUser = ["dn", "uid", "samaccountname", "givenname", "sn", "cn", "mail", "userprincipalname", "useraccountcontrol", "accountexpires", "manager"]; + + public function ldapConnection($authSourceData) { + $pass = explode('_', $authSourceData['AUTH_SOURCE_PASSWORD']); + + // 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); + + $ldapServer = $authSourceData['AUTH_SOURCE_SERVER_NAME'] . ':' . $authSourceData['AUTH_SOURCE_PORT']; + + ldap_set_option($ldapcnn, LDAP_OPT_PROTOCOL_VERSION, 3); + $this->stdLog($ldapcnn, 'ldap_set_option', $loggableAuthSource); + ldap_set_option($ldapcnn, LDAP_OPT_REFERRALS, 0); + $this->stdLog($ldapcnn, 'ldap_set_option', $loggableAuthSource); + + $resultLDAPStartTLS = true; + if (isset($authSourceData['AUTH_SOURCE_ENABLED_TLS']) && $authSourceData['AUTH_SOURCE_ENABLED_TLS']) { + $resultLDAPStartTLS = @ldap_start_tls($ldapcnn); + $this->stdLog($ldapcnn, 'ldap_start_tls', $loggableAuthSource); + $ldapServer = 'TLS ' . $ldapServer; + } + + if ($authSourceData['AUTH_ANONYMOUS'] == '1') { + $bBind = ldap_bind($ldapcnn); + $this->log($ldapcnn, 'bind $ldapServer like anonymous user'); + } else { + $bBind = ldap_bind($ldapcnn, $authSourceData['AUTH_SOURCE_SEARCH_USER'], $authSourceData['AUTH_SOURCE_PASSWORD']); + $this->log($ldapcnn, 'bind $ldapServer with user ' . $loggableAuthSource['AUTH_SOURCE_SEARCH_USER']); + } + $this->stdLog($ldapcnn, 'ldap_bind', $loggableAuthSource); + $this->getDiagnosticMessage($ldapcnn); + if (!$bBind) { + $message = 'Unable to bind to server: ' . $ldapServer . 'LDAP-Errno: ' . ldap_errno($ldapcnn) . ' : ' . ldap_error($ldapcnn) . " \n"; + throw new Exception($message); + } + + return ['connection' =>$ldapcnn, 'startTLS' => $resultLDAPStartTLS]; + } + + public function getPageSizeLimit($ldapcnn, $baseDn = '') + { + try { + $limit = 1000; + + if ($ldapcnn === false) { + return $limit; + } + + $searchResult = @ldap_search($ldapcnn, $baseDn, '(|(objectclass=*))', ['dn']); + $context = [ + 'baseDN' => $baseDn, + 'filter' => '(|(objectclass=*))', + 'attributes' => ['dn'] + ]; + $this->stdLog($ldapcnn, 'ldap_search', $context); + + if ($searchResult) { + $countEntries = ldap_count_entries($ldapcnn, $searchResult); + $this->stdLog($ldapcnn, 'ldap_count_entries'); + + if ($countEntries > 0) { + $limit = ($countEntries > $limit) ? $limit : $countEntries; + } + } + + return $limit; + } catch (Exception $e) { + throw $e; + } + } + + private function getDiagnosticMessage($linkIdentifier) + { + //specific message + $keysError = [ + [ + 'key' => 'USER_NOT_FOUND', + 'code' => 525, + 'message' => G::LoadTranslation('ID_LDAP_USER_NOT_FOUND_INVALID'), + ], [ + 'key' => 'NOT_PERMITTED_TO_LOGON_AT_THIS_TIME', + 'code' => 530, + 'message' => G::LoadTranslation('ID_LDAP_NOT_PERMITTED_TO_LOGON_AT_THIS_TIME'), + ], [ + 'key' => 'RESTRICTED_TO_SPECIFIC_MACHINES', + 'code' => 531, + 'message' => G::LoadTranslation('ID_LDAP_RESTRICTED_TO_SPECIFIC_MACHINES'), + ], [ + 'key' => 'PASSWORD_EXPIRED', + 'code' => 532, + 'message' => G::LoadTranslation('ID_LDAP_PASSWORD_EXPIRED'), + ], [ + 'key' => 'ACCOUNT_DISABLED', + 'code' => 533, + 'message' => G::LoadTranslation('ID_LDAP_ACCOUNT_DISABLED'), + ], [ + 'key' => 'ACCOUNT_EXPIRED', + 'code' => 701, + 'message' => G::LoadTranslation('ID_LDAP_ACCOUNT_EXPIRED'), + ], [ + 'key' => 'USER_MUST_RESET_PASSWORD', + 'code' => 773, + 'message' => G::LoadTranslation('ID_LDAP_USER_MUST_RESET_PASSWORD'), + ] + ]; + $message = ''; + ldap_get_option($linkIdentifier, LDAP_OPT_DIAGNOSTIC_MESSAGE, $messageError); + $this->stdLog($linkIdentifier, 'ldap_get_option', ['error' => $messageError]); + foreach ($keysError as $key => $value) { + if (strpos($messageError, (string) $value['code']) !== false) { + $message = $value['message']; + break; + } + } + //standard message + if (empty($message)) { + $errorNumber = ldap_errno($linkIdentifier); + $message = ldap_err2str($errorNumber) . '.'; + } + if (empty($message)) { + $message = G::LoadTranslation('ID_LDAP_ERROR_CONNECTION'); + } + Cache::put('ldapMessageError', $message, 120); //laravel 8.x the time parameter is in seconds. + $this->log($linkIdentifier, $messageError); + } + + private function log($link, $text) + { + $logFile = PATH_DATA . 'logs/ldap.log'; + + if (!file_exists($logFile)) { + file_put_contents($logFile, "Start\n"); + @chmod($logFile, 0644); + } + + if (!is_writable($logFile)) { + error_log('Log file is not writable: ' . $logFile); + throw new Exception('Log file is not writable: ' . $logFile); + } + + $fpt = fopen($logFile, 'a'); + $ldapErrorMsg = ''; + $ldapErrorNr = 0; + + if ($link != null) { + $ldapErrorNr = ldap_errno($link); + + if ($ldapErrorNr != 0) { + $ldapErrorMsg = ldap_error($link); + $text = $ldapErrorMsg . ' : ' . $text; + } + } + + // Log format: date hour ipaddress workspace ldapErrorNr + fwrite($fpt, sprintf("%s %s %s %s %s \n", date('Y-m-d H:i:s'), getenv('REMOTE_ADDR'), config('system.workspace'), $ldapErrorNr, $text)); + fclose($fpt); + } + + private function stdLog($link, $message = "", $context = [], $level = "info") + { + try { + if (empty($link)) { + switch ($level) { + case "error": + Log::channel(':ldapAdvanced')->error($message, Bootstrap::context($context)); + break; + case "info": + default: + Log::channel(':ldapAdvanced')->info($message, Bootstrap::context($context)); + break; + } + return; + } + $code = ldap_errno($link); + $detail = ldap_err2str($code); + $context["detail"] = $detail; + if ($code === 0) { + Log::channel(':ldapAdvanced')->info($message, Bootstrap::context($context)); + } else { + Log::channel(':ldapAdvanced')->error($message, Bootstrap::context($context)); + } + } catch (Exception $exception) { + return ['success' => false, 'message' => $exception->getMessage()]; + } + } + + + public function searchUsersLdap($keyword, $start = null, $limit = null) { + $arrayUser = []; + $totalUser = 0; + $countUser = 0; + + $paged = !is_null($start) && !is_null($limit); + + $rbac = RBAC::getSingleton(); + + if (is_null($rbac->authSourcesObj)) { + $rbac->authSourcesObj = new AuthenticationSource(); + } + + $arrayAuthenticationSourceData = $rbac->authSourcesObj->load($this->authSourceUid); + $attributeUserSet = []; + $attributeSetAdd = []; + + if ( + isset($arrayAuthenticationSourceData['AUTH_SOURCE_DATA']['AUTH_SOURCE_GRID_ATTRIBUTE']) && !empty($arrayAuthenticationSourceData['AUTH_SOURCE_DATA']['AUTH_SOURCE_GRID_ATTRIBUTE']) + ) { + foreach ($arrayAuthenticationSourceData['AUTH_SOURCE_DATA']['AUTH_SOURCE_GRID_ATTRIBUTE'] as $value) { + $attributeSetAdd[] = $value['attributeLdap']; + $attributeUserSet[$value['attributeUser']] = $value['attributeLdap']; + } + } + + $ldapcnn = $this->ldapConnection($arrayAuthenticationSourceData); + $ldapcnn = $ldapcnn['connection']; + + //Get Users + if (!isset($arrayAuthenticationSourceData['AUTH_SOURCE_DATA']['AUTH_SOURCE_USERS_FILTER'])) { + $arrayAuthenticationSourceData['AUTH_SOURCE_DATA']['AUTH_SOURCE_USERS_FILTER'] = ''; + } + + $uidUserIdentifier = (isset($arrayAuthenticationSourceData['AUTH_SOURCE_DATA']['AUTH_SOURCE_IDENTIFIER_FOR_USER'])) ? $arrayAuthenticationSourceData['AUTH_SOURCE_DATA']['AUTH_SOURCE_IDENTIFIER_FOR_USER'] : 'uid'; + $filterUsers = trim($arrayAuthenticationSourceData['AUTH_SOURCE_DATA']['AUTH_SOURCE_USERS_FILTER']); + $filter = ($filterUsers != '') ? $filterUsers : '(' . $this->arrayObjectClassFilter['user'] . ')'; + $filter = "(&$filter(|(dn=$keyword)(uid=$keyword)(samaccountname=$keyword)(givenname=$keyword)(sn=$keyword)(cn=$keyword)(mail=$keyword)(userprincipalname=$keyword)))"; + $oSearch = @ldap_search($ldapcnn, $arrayAuthenticationSourceData['AUTH_SOURCE_BASE_DN'], $filter, array_merge($this->arrayAttributesForUser, $attributeSetAdd)); + $context = [ + 'baseDN' => $arrayAuthenticationSourceData['AUTH_SOURCE_BASE_DN'], + 'filter' => $filter, + 'attribute' => array_merge($this->arrayAttributesForUser, $attributeSetAdd) + ]; + $this->stdLog($ldapcnn, 'ldap_search', $context); + + if ($oError = ldap_errno($ldapcnn)) { + $this->log($ldapcnn, 'Error in Search users'); + } else { + if ($oSearch) { + $entries = ldap_count_entries($ldapcnn, $oSearch); + $this->stdLog($ldapcnn, 'ldap_count_entries'); + $totalUser = $entries; + + if ($entries > 0) { + $oEntry = ldap_first_entry($ldapcnn, $oSearch); + $this->stdLog($ldapcnn, 'ldap_first_entry'); + $countEntries = 0; + $flagNextRecord = true; + + do { + $aAttr = $this->ldapGetAttributes($ldapcnn, $oEntry); + $sUsername = (isset($aAttr[$uidUserIdentifier])) ? $aAttr[$uidUserIdentifier] : ''; + + 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'])) { + switch ($aAttr['useraccountcontrol']) { + case '512': + case '544': + case '66048': + case '66080': + $userCountControl = 'ACTIVE'; + break; + case '514': + case '546': + case '66050': + case '66082': + case '2': + case '16': + case '8388608': + default: + $userCountControl = 'INACTIVE'; + break; + } + } + //apache ldap + if (isset($aAttr['status'])) { + $userCountControl = strtoupper($aAttr['status']); + } + $aUserAttributes = []; + foreach ($attributeUserSet as $key => $value) { + if ($key == 'USR_STATUS') { + $aUserAttributes[$key] = ($userCountControl != '') ? $userCountControl : 'ACTIVE'; + } elseif (isset($aAttr[$value])) { + $aUserAttributes[$key] = $aAttr[$value]; + } + } + + if ($paged) { + if ($countUser - 1 <= $start + $limit - 1) { + if ($start <= $countUser - 1) { + $arrayUser[] = array_merge($this->getUserDataFromAttribute($sUsername, $aAttr), $aUserAttributes); + } + } else { + $flagNextRecord = false; + } + } else { + $arrayUser[] = array_merge($this->getUserDataFromAttribute($sUsername, $aAttr), $aUserAttributes); + } + + $countEntries++; + } + } while (($oEntry = ldap_next_entry($ldapcnn, $oEntry)) && $flagNextRecord); + } + } + } + + return ($paged) ? ['numRecTotal' => $totalUser, 'data' => $arrayUser] : $arrayUser; + } + + private function getUserDataFromAttribute($username, array $arrayAttributes) + { + try { + $keyMail = (isset($arrayAttributes['mail'])) ? 'mail' : ((isset($arrayAttributes['userprincipalname'])) ? 'userprincipalname' : 'nomail'); + + return [ + 'sUsername' => trim((is_array($username)) ? $username[0] : $username), + 'sPassword' => trim((isset($arrayAttributes['userpassword'])) ? ((is_array($arrayAttributes['userpassword'])) ? $arrayAttributes['userpassword'][0] : $arrayAttributes['userpassword']) : ''), + 'sFullname' => trim((isset($arrayAttributes['cn'])) ? ((is_array($arrayAttributes['cn'])) ? $arrayAttributes['cn'][0] : $arrayAttributes['cn']) : ''), + 'sFirstname' => trim((isset($arrayAttributes['givenname'])) ? ((is_array($arrayAttributes['givenname'])) ? $arrayAttributes['givenname'][0] : $arrayAttributes['givenname']) : ''), + 'sLastname' => trim((isset($arrayAttributes['sn'])) ? ((is_array($arrayAttributes['sn'])) ? $arrayAttributes['sn'][0] : $arrayAttributes['sn']) : ''), + 'sEmail' => trim((isset($arrayAttributes[$keyMail])) ? ((is_array($arrayAttributes[$keyMail])) ? $arrayAttributes[$keyMail][0] : $arrayAttributes[$keyMail]) : ''), + 'sDN' => trim($arrayAttributes['dn']), + 'sManagerDN' => trim((isset($arrayAttributes['manager'])) ? ((is_array($arrayAttributes['manager'])) ? $arrayAttributes['manager'][0] : $arrayAttributes['manager']) : '') + ]; + } catch (Exception $e) { + throw $e; + } + } + + private function ldapGetAttributes($ldapcnn, $entry) + { + try { + $arrayAttributes = []; + + $arrayAttributes['dn'] = ldap_get_dn($ldapcnn, $entry); + $this->stdLog($ldapcnn, 'ldap_get_dn'); + + $arrayAux = ldap_get_attributes($ldapcnn, $entry); + $this->stdLog($ldapcnn, 'ldap_get_attributes'); + + for ($i = 0; $i <= $arrayAux['count'] - 1; $i++) { + $key = strtolower($arrayAux[$i]); + + switch ($arrayAux[$arrayAux[$i]]['count']) { + case 0: + $arrayAttributes[$key] = ''; + break; + case 1: + $arrayAttributes[$key] = $arrayAux[$arrayAux[$i]][0]; + break; + default: + $arrayAttributes[$key] = $arrayAux[$arrayAux[$i]]; + + unset($arrayAttributes[$key]['count']); + break; + } + } + + if (!isset($arrayAttributes['mail']) && isset($arrayAttributes['userprincipalname'])) { + $arrayAttributes['mail'] = $arrayAttributes['userprincipalname']; + } + + return $arrayAttributes; + } catch (Exception $e) { + throw $e; + } + } +} \ No newline at end of file