%PDF- %PDF-
Direktori : /home/bitrix/www/bitrix/modules/ldap/classes/general/ |
Current File : /home/bitrix/www/bitrix/modules/ldap/classes/general/ldap.php |
<?php /** * Bitrix Framework * @package bitrix * @subpackage ldap * @copyright 2001-2016 Bitrix */ IncludeModuleLangFile(__FILE__); class CLDAP { var $arFields, $arGroupList = false; var $conn; protected static $PHOTO_ATTRIBS = array("thumbnailPhoto", "jpegPhoto"); protected $arGroupMaps; protected $groupsLists = array(); const CONNECTION_TYPE_SIMPLE = 0; const CONNECTION_TYPE_SSL = 1; const CONNECTION_TYPE_TLS = 2; protected $isTlsStarted = false; function Connect($arFields = Array()) { global $APPLICATION; if(!isset($this) || !is_object($this)) { $ldap = new CLDAP(); $ldap->arFields = $arFields; if($ldap->Connect()) return $ldap; return false; } if($this->conn = @ldap_connect($this->arFields["SERVER"], $this->arFields['PORT'])) { @ldap_set_option($this->conn, LDAP_OPT_PROTOCOL_VERSION, 3); @ldap_set_option($this->conn, LDAP_OPT_REFERRALS, 0); @ldap_set_option($this->conn, LDAP_OPT_SIZELIMIT, COption::GetOptionInt("ldap", "group_limit", 0)); @ldap_set_option($this->conn, LDAP_OPT_TIMELIMIT, 100); @ldap_set_option($this->conn, LDAP_OPT_TIMEOUT, 5); @ldap_set_option($this->conn, LDAP_OPT_NETWORK_TIMEOUT, 5); $login = isset($this->arFields["~ADMIN_LOGIN"]) ? $this->arFields["~ADMIN_LOGIN"] : $this->arFields["ADMIN_LOGIN"]; $pass = isset($this->arFields["~ADMIN_PASSWORD"]) ? $this->arFields["~ADMIN_PASSWORD"] : $this->arFields["ADMIN_PASSWORD"]; return $this->Bind($login, $pass); } else { $APPLICATION->ThrowException('ldap_connect() error. '.$this->getLastErrorDescription()); } return false; } function BindAdmin() { if(strlen($this->arFields["ADMIN_LOGIN"]) <= 0) return false; return $this->Bind( (isset($this->arFields["~ADMIN_LOGIN"])?$this->arFields["~ADMIN_LOGIN"]:$this->arFields["ADMIN_LOGIN"]), (isset($this->arFields["~ADMIN_PASSWORD"])?$this->arFields["~ADMIN_PASSWORD"]:$this->arFields["ADMIN_PASSWORD"]) ); } function Bind($login, $password) { if(!$this->conn) return false; global $APPLICATION; if($this->arFields["CONVERT_UTF8"] == "Y") { $login = $APPLICATION->ConvertCharset($login, SITE_CHARSET, "utf-8"); $password = $APPLICATION->ConvertCharset($password, SITE_CHARSET, "utf-8"); } if(strpos($password, "\0") !== false || strlen($password) <= 0) return false; if(intval($this->arFields["CONNECTION_TYPE"]) == CLDAP::CONNECTION_TYPE_TLS) if(!$this->startTls()) return false; if(!@ldap_bind($this->conn, $login, $password)) { $APPLICATION->ThrowException('ldap_bind() error. '.$this->getLastErrorDescription()); return false; } return true; } protected function startTls() { global $APPLICATION; if($this->isTlsStarted) return true; if(!@ldap_start_tls($this->conn)) { $APPLICATION->ThrowException('ldap_start_tls() error. '.$this->getLastErrorDescription()); return false; } $this->isTlsStarted = true; return true; } function Disconnect() { ldap_close($this->conn); } function RootDSE() { $values = $this->_RootDSE('namingcontexts'); if ($values == false) $values = $this->_RootDSE('namingContexts'); return $this->WorkAttr($values); } function _RootDSE($filtr) { $sr = ldap_read($this->conn, '', 'objectClass=*', Array($filtr), 0); //$sr = ldap_read($this->conn, '', 'objectClass=*'); $entry = ldap_first_entry($this->conn, $sr); $attributes = ldap_get_attributes($this->conn, $entry); $values = false; if ($attributes['count'] > 0) $values = @ldap_get_values_len($this->conn, $entry, $filtr); return $values; } function WorkAttr($values) { global $APPLICATION; if(is_array($values) && $values['count']==1) { if($this->arFields["CONVERT_UTF8"]=="Y" && strtolower(SITE_CHARSET) != "utf-8") return $APPLICATION->ConvertCharset($values[0], "utf-8", SITE_CHARSET); return $values[0]; } unset($values['count']); if($this->arFields["CONVERT_UTF8"]=="Y" && strtolower(SITE_CHARSET) != "utf-8") foreach($values as $key=>$val) $values[$key] = $APPLICATION->ConvertCharset($val, "utf-8", SITE_CHARSET); return $values; } function QueryArray($str = '(ObjectClass=*)', $fields = false) { global $APPLICATION; if(strlen($this->arFields['BASE_DN'])<=0) return false; $arBaseDNs = explode(";", $this->arFields['BASE_DN']); $info = false; $i=0; if($this->arFields["CONVERT_UTF8"] == "Y") $str = $APPLICATION->ConvertCharset($str, SITE_CHARSET, "utf-8"); foreach($arBaseDNs as $BaseDN) { global $APPLICATION; $BaseDN = trim($BaseDN); if($BaseDN == "") continue; if($this->arFields["CONVERT_UTF8"]=="Y") $BaseDN = $APPLICATION->ConvertCharset($BaseDN, SITE_CHARSET, "utf-8"); $defaultMaxPageSizeAD = 1000; $pageSize = isset($this->arFields['MAX_PAGE_SIZE']) && intval($this->arFields['MAX_PAGE_SIZE'] > 0) ? intval($this->arFields['MAX_PAGE_SIZE']) : $defaultMaxPageSizeAD; $cookie = ''; do { if(CLdapUtil::isLdapPaginationAviable()) ldap_control_paged_result($this->conn, $pageSize, false, $cookie); if($fields === false) $sr = @ldap_search($this->conn, $BaseDN, $str); else $sr = @ldap_search($this->conn, $BaseDN, $str, $fields); if($sr) { $entry = ldap_first_entry($this->conn, $sr); if($entry) { if(!is_array($info)) { $info = Array(); $i=0; } do { $attributes = ldap_get_attributes($this->conn, $entry); for($j=0; $j<$attributes['count']; $j++) { $values = @ldap_get_values_len($this->conn, $entry, $attributes[$j]); if($values === false) continue; $bPhotoAttr = in_array($attributes[$j], self::$PHOTO_ATTRIBS); $info[$i][strtolower($attributes[$j])] = $bPhotoAttr ? $values : $this->WorkAttr($values); } if(!is_set($info[$i], 'dn')) { if($this->arFields["CONVERT_UTF8"]=="Y") $info[$i]['dn'] = $APPLICATION->ConvertCharset(ldap_get_dn($this->conn, $entry), "utf-8", SITE_CHARSET); else $info[$i]['dn'] = ldap_get_dn($this->conn, $entry); } $i++; } while($entry = ldap_next_entry($this->conn, $entry)); } if(CLdapUtil::isLdapPaginationAviable()) @ldap_control_paged_result_response($this->conn, $sr, $cookie); } elseif($sr === false) { $APPLICATION->ThrowException("LDAP_SEARCH_ERROR"); } } while($cookie !== null && $cookie != ''); } return $info; } function Query($str = '(ObjectClass=*)', $fields = false) { $info = $this->QueryArray($str, $fields); $result = new CDBResult; $result->InitFromArray($info); return $result; } protected function setFieldAsAttr(array $attrArray, $fieldName) { $field = isset($this->arFields["~".$fieldName]) ? $this->arFields["~".$fieldName] : $this->arFields[$fieldName]; $field = strtolower($field); if(!in_array($field, $attrArray)) $attrArray[] = $field; return $attrArray; } // query for group list from AD - server function GetGroupListArray($query = '') { $group_filter = $this->arFields['GROUP_FILTER']; if(strlen(trim($group_filter))>0 && substr(trim($group_filter), 0, 1)!='(') $group_filter = '('.trim($group_filter).')'; $query = '(&'.$group_filter.$query.')'; if (!array_key_exists($query, $this->groupsLists)) { $this->BindAdmin(); $arGroupAttr = array( "name", "cn", "gidNumber", "description", "memberof", "primarygrouptoken", "primarygroupid", "samaccountname", "distinguishedname" ); foreach(array("GROUP_ID_ATTR", "GROUP_NAME_ATTR", "GROUP_MEMBERS_ATTR") as $fieldName) $arGroupAttr = $this->setFieldAsAttr($arGroupAttr, $fieldName); if ($this->arFields['USER_GROUP_ACCESSORY'] == 'Y') $arGroupAttr = $this->setFieldAsAttr($arGroupAttr, "USER_GROUP_ATTR"); $arGroupsTmp = $this->QueryArray($query, $arGroupAttr); if (!$arGroupsTmp) return false; $arGroups = array(); $group_id_attr = strtolower($this->arFields['GROUP_ID_ATTR']); if(is_set($this->arFields, 'GROUP_NAME_ATTR')) $group_name_attr = strtolower($this->arFields['GROUP_NAME_ATTR']); else $group_name_attr = false; foreach ($arGroupsTmp as $grp) { $grp['ID'] = $grp[$group_id_attr]; if ($group_name_attr && is_set($grp, $group_name_attr)) $grp['NAME'] = $grp[$group_name_attr]; $arGroups[$grp['ID']] = $grp; } $this->groupsLists[$query] = $arGroups; } return $this->groupsLists[$query]; } function GetGroupList($query = '') { $arGroups = $this->GetGroupListArray($query); $result = new CDBResult(); $result->InitFromArray($arGroups); return $result; } function OnUserLogin($arArgs) { global $APPLICATION; if(!function_exists("ldap_connect")) return false; $LOGIN = $arArgs["LOGIN"]; $PASSWORD = $arArgs["PASSWORD"]; if(strlen($LOGIN)<=0 || strlen($PASSWORD)<=0) return false; $arFilter = Array("ACTIVE"=>"Y"); $p = strpos($LOGIN, "\\"); if( $p===false && COption::GetOptionString("ldap", "ntlm_auth_without_prefix", "Y") != "Y") { return false; } elseif( $p > 0 ) { $arFilter["CODE"] = substr($LOGIN, 0, $p); $LOGIN = substr($LOGIN, $p+1); } $arParams = Array( "LOGIN" => &$LOGIN, "PASSWORD" => &$PASSWORD, "LDAP_FILTER" => &$arFilter, ); $APPLICATION->ResetException(); foreach(GetModuleEvents("ldap", "OnBeforeUserLogin", true) as $arEvent) { // TODO check whether wrapping of &$arParams into another array is reasonable as part of migration from ExecuteModuleEvent to ExecuteModuleEventEx if(ExecuteModuleEventEx($arEvent, array(&$arParams))===false) { if($err = $APPLICATION->GetException()) { $result_message = Array("MESSAGE"=>$err->GetString()."<br>", "TYPE"=>"ERROR"); } else { $APPLICATION->ThrowException("Unknown error"); $result_message = Array("MESSAGE"=>"Unknown error"."<br>", "TYPE"=>"ERROR"); } return false; } } $db_ldap_serv = CLdapServer::GetList(Array(), $arFilter); while($xLDAP = $db_ldap_serv->GetNextServer()) { if($xLDAP->Connect()) { // user AD parameters are queried here, inside FindUser function if(!$arLdapUser = $xLDAP->FindUser($LOGIN, $PASSWORD)) { if(isset($arArgs["OTP"]) && strlen($arArgs["OTP"]) > 0) if(substr($PASSWORD, -6) == $arArgs["OTP"]) $arLdapUser = $xLDAP->FindUser($LOGIN, substr($PASSWORD, 0, -6)); //It can be with otp } if($arLdapUser) { $ID = $xLDAP->SetUser($arLdapUser, (COption::GetOptionString("ldap", "add_user_when_auth", "Y")=="Y")); if($ID > 0) { $arArgs["STORE_PASSWORD"] = "N"; $xLDAP->Disconnect(); return $ID; } } $xLDAP->Disconnect(); } } return false; } // this function is called on user logon (either normal or ntlm) to find user in ldap function FindUser($LOGIN, $PASSWORD = false) { $login_field = $LOGIN; $password_field = $PASSWORD; $this->BindAdmin(); $user_filter = "(&".$this->arFields["~USER_FILTER"]."(".$this->arFields["~USER_ID_ATTR"]."=".$this->specialchars($login_field)."))"; $dbLdapUsers = $this->Query($user_filter); if (!$dbLdapUsers) return false; if($arLdapUser = $dbLdapUsers->Fetch()) { if($PASSWORD !== false) // also check auth { $user_dn = $arLdapUser['dn']; if (!$this->Bind($user_dn, $password_field)) return false; } return $this->GetUserFields($arLdapUser); } return false; } /** * Returns value of ldap user field mapped to bitrix field. * @param string $fieldName Name of user field in Bitrix system. * @param array $arLdapUser User params received from ldap. * @return mixed. */ function getLdapValueByBitrixFieldName($fieldName, $arLdapUser) { global $USER_FIELD_MANAGER; if(!isset($this->arFields["FIELD_MAP"][$fieldName])) return false; $attr = $this->arFields["FIELD_MAP"][$fieldName]; $arRes = $USER_FIELD_MANAGER->GetUserFields("USER", 0, LANGUAGE_ID); $result = false; if(is_array($arRes[$fieldName])) { if($arRes[$fieldName]["MULTIPLE"]=="Y") { if (is_array($arLdapUser[strtolower($attr)])) $result = array_values($arLdapUser[strtolower($attr)]); else $result = array($arLdapUser[strtolower($attr)]); } else if (!empty($arLdapUser[strtolower($attr)])) $result = $arLdapUser[strtolower($attr)]; else if (!empty($arRes[$fieldName]['SETTINGS']['DEFAULT_VALUE'])) { if (is_array($arRes[$fieldName]['SETTINGS']['DEFAULT_VALUE'])) { if (!empty($arRes[$fieldName]['SETTINGS']['DEFAULT_VALUE']['VALUE'])) $result = $arRes[$fieldName]['SETTINGS']['DEFAULT_VALUE']['VALUE']; } else $result = $arRes[$fieldName]['SETTINGS']['DEFAULT_VALUE']; } } elseif(preg_match("/(.*)&([0-9]+)/", $attr, $arMatch)) { if(intval($arLdapUser[strtolower($arMatch[1])]) & intval($arMatch[2])) $result = "N"; else $result = "Y"; } elseif ($fieldName == "PERSONAL_PHOTO") { if($arLdapUser[strtolower($attr)] == "") return false; $fExt = CLdapUtil::GetImgTypeBySignature($arLdapUser[strtolower($attr)][0]); if(!$fExt) return false; $tmpDir = CTempFile::GetDirectoryName(); CheckDirPath($tmpDir); $fname = "ad_".rand().".".$fExt; if(!file_put_contents($tmpDir.$fname,$arLdapUser[strtolower($attr)][0])) return false; $result = array( "name" => $fname, "type" => CFile::GetContentType($tmpDir.$fname), "tmp_name" => $tmpDir.$fname ); } else $result = $arLdapUser[strtolower($attr)]; if(is_null($result)) $result = false; return $result; } public static function OnFindExternalUser($login) { if(strlen($login) <= 0) return 0; $filter = array("ACTIVE" => "Y"); $p = strpos($login, "\\"); if($p === false && COption::GetOptionString("ldap", "ntlm_auth_without_prefix", "Y") != "Y") { return 0; } elseif( $p > 0 ) { $filter["CODE"] = substr($login, 0, $p); $login = substr($login, $p+1); } $dbServ = CLdapServer::GetList(array(), $filter); while($serv = $dbServ->GetNextServer()) { if($serv->Connect()) { if($arLdapUser = $serv->FindUser($login)) { $id = $serv->SetUser($arLdapUser, (COption::GetOptionString("ldap", "add_user_when_auth", "Y") == "Y")); if($id > 0) { $serv->Disconnect(); return $id; } } $serv->Disconnect(); } } return 0; } // converts LDAP values to those suitable for user fields function GetUserFields($arLdapUser, &$departmentCache=FALSE) { global $APPLICATION; $arFields = array( 'DN' => $arLdapUser['dn'], 'LOGIN' => $arLdapUser[strtolower($this->arFields['~USER_ID_ATTR'])], 'EXTERNAL_AUTH_ID' => 'LDAP#'.$this->arFields['ID'], 'LDAP_GROUPS' => $arLdapUser[strtolower($this->arFields['~USER_GROUP_ATTR'])], ); // for each field, do the conversion foreach($this->arFields["FIELD_MAP"] as $userField=>$attr) $arFields[$userField] = $this->getLdapValueByBitrixFieldName($userField, $arLdapUser); $APPLICATION->ResetException(); $db_events = GetModuleEvents("ldap", "OnLdapUserFields"); while($arEvent = $db_events->Fetch()) { $arParams = array(array(&$arFields, &$arLdapUser)); if(ExecuteModuleEventEx($arEvent, $arParams)===false) { if(!($err = $APPLICATION->GetException())) $APPLICATION->ThrowException("Unknown error"); return false; } $arFields = $arParams[0][0]; } // set a department field, if needed if (empty($arFields['UF_DEPARTMENT']) && isModuleInstalled('intranet') && $this->arFields['IMPORT_STRUCT'] && $this->arFields['IMPORT_STRUCT']=='Y') { //$arLdapUser[$this->arFields['USER_DN_ATTR']] $username = $arLdapUser[$this->arFields['USER_ID_ATTR']]; if ($arDepartment = $this->GetDepartmentIdForADUser($arLdapUser[$this->arFields['USER_DEPARTMENT_ATTR']],$arLdapUser[$this->arFields['USER_MANAGER_ATTR']],$username,$departmentCache)) { // fill in cache. it is done outside the function because it has many exit points if ($departmentCache) $departmentCache[$username] = $arDepartment; // this is not final assignment // $arFields['UF_DEPARTMENT'] sould contain array of department ids // but somehow we have to return an information whether this user is a department head // so we'll save this data here temporarily $arFields['UF_DEPARTMENT'] = $arDepartment; } else $arFields['UF_DEPARTMENT'] = array(); // at this point $arFields['UF_DEPARTMENT'] should be set to some value, even an empty array is ok } if (!is_array($arFields['LDAP_GROUPS'])) $arFields['LDAP_GROUPS'] = (!empty($arFields['LDAP_GROUPS']) ? array($arFields['LDAP_GROUPS']) : array()); $primarygroupid_name_attr = 'primarygroupid'; $primarygrouptoken_name_attr = 'primarygrouptoken'; $groupMemberAttr = null; $userIdAttr = null; if ($this->arFields['USER_GROUP_ACCESSORY'] == 'Y') { $primarygroupid_name_attr = strtolower($this->arFields['GROUP_ID_ATTR']); $primarygrouptoken_name_attr = strtolower($this->arFields['USER_GROUP_ATTR']); $userIdAttr = strtolower($this->arFields['USER_ID_ATTR']); $groupMemberAttr = strtolower($this->arFields['GROUP_MEMBERS_ATTR']); } $arAllGroups = $this->GetGroupListArray(); if (!is_array($arAllGroups) || count($arAllGroups) <= 0) return $arFields; $arGroup = reset($arAllGroups); do { if(in_array($arGroup['ID'], $arFields['LDAP_GROUPS'])) continue; if ( (is_set($arLdapUser, $primarygroupid_name_attr) && $arGroup[$primarygrouptoken_name_attr] == $arLdapUser[$primarygroupid_name_attr] ) || ($this->arFields['USER_GROUP_ACCESSORY'] == 'Y' && is_set($arGroup, $groupMemberAttr) && ( (is_array($arGroup[$groupMemberAttr]) && in_array($arLdapUser[$userIdAttr], $arGroup[$groupMemberAttr]) ) || $arLdapUser[$userIdAttr] == $arGroup[$groupMemberAttr] ) ) ) { $arFields['LDAP_GROUPS'][] = $arGroup['ID']; if ($this->arFields['USER_GROUP_ACCESSORY'] == 'N') break; } } while ($arGroup = next($arAllGroups)); return $arFields; } // Gets department ID for AD user. If department doesn't exist, creates a new one. Returns FALSE if there should be no department set. // returns array: // 'ID' - department id // 'IS_HEAD' - true if this user is head of the department, false if not function GetDepartmentIdForADUser($department, $managerDN, $username, &$cache=FALSE, $iblockId = FALSE, $names = FALSE) { global $USER_FIELD_MANAGER; // check for loops in manager structure, if loop is found - quit // should be done before cache lookup if ($names && isset($names[$username])) return false; // if department id for this user is already stored in cache if ($cache) { $departmentCached = $cache[$username]; // if user was not set as head earlier, then do not get his id from cache if ($departmentCached) return $departmentCached; } // if it is a first call in recursive chain if (!$iblockId) { // check module inclusions if (!IsModuleInstalled('intranet') || !CModule::IncludeModule('iblock')) return false; // get structure's iblock id $iblockId=COption::GetOptionInt("intranet", "iblock_structure", false, false); if (!$iblockId) return false; $names = array(); } // save current username as already visited $names[$username] = true; $arManagerDep = null; $mgrDepartment = null; // if there's a manager - query it if ($managerDN) { preg_match('/^((CN|uid)=.*?)(\,){1}([^\,])*(=){1}/i', $managerDN, $matches); //Extract "CN=User Name" from full name $user = isset($matches[1]) ? str_replace('\\', '',$matches[1]) : ""; $userArr = $this->GetUserArray($user); if (count($userArr)>0) { // contents of userArr are already in local encoding, no need for conversion here $mgrDepartment = $userArr[0][$this->arFields['USER_DEPARTMENT_ATTR']]; if ($mgrDepartment && trim($mgrDepartment)!='') { // if manager's department name is set - then get it's id $mgrManagerDN = $userArr[0][$this->arFields['USER_MANAGER_ATTR']]; $mgrUserName = $userArr[0][$this->arFields['USER_ID_ATTR']]; $arManagerDep = $this->GetDepartmentIdForADUser($mgrDepartment, $mgrManagerDN, $mgrUserName, $cache, $iblockId, $names); // fill in cache if ($cache && $arManagerDep) $cache[$mgrUserName] = $arManagerDep; } } } // prepare result and create department (if needed) $arResult = array('IS_HEAD'=>true); // by default, thinking of user as a head of the department if ($arManagerDep) { // if got manager's data correctly if ($department && trim($department)!='' && ($mgrDepartment!=$department)) { // if our department is set && differs from manager's, set manager's as parent $parentSectionId = $arManagerDep['ID']; } else { // - if user has no department, but somehow have manager - then he is assumed to be in manager's department // - if user has same department name as manager - then he is not head // here we can return manager's department id immediately $arResult = $arManagerDep; $arResult['IS_HEAD'] = false; return $arResult; } } else { // if there's no manager's data if ($department && trim($department)!='') { $parentSectionId = $this->arFields['ROOT_DEPARTMENT']; } else { // if have no manager's department and no own department: // - use default as our department and root as parent section if default is set // - or just root if default has empty value // - or return false, if setting of default department is turned off if ($this->arFields['STRUCT_HAVE_DEFAULT'] && $this->arFields['STRUCT_HAVE_DEFAULT'] == "Y") { // if can use default department $department = $this->arFields['DEFAULT_DEPARTMENT_NAME']; if ($department && trim($department)!='') { // if department is not empty $parentSectionId = $this->arFields['ROOT_DEPARTMENT']; } else { // if it is empty - return parent return array('ID' => $this->arFields['ROOT_DEPARTMENT']); } } else { // if have no department in AD and no default - then do not set a department return false; } } } // 3. if there's no department set for this user, this means there was no default department name (which substituted in *) - then there's no need to set department id for this user at all if (!$department || trim($department)=='') return false; // 4. detect this user's department ID, using parent id and department name string, which we certainly have now (these 2 parameters are required to get an ID) // see if this department already exists $bs = new CIBlockSection(); $dbExistingSections = GetIBlockSectionList( $iblockId, ($parentSectionId >= 0 ? $parentSectionId : false), $arOrder = Array("left_margin" => "asc"), $cnt = 0, $arFilter = Array('NAME' => $department) ); $departmentId = false; if($arItem = $dbExistingSections->GetNext()) $departmentId = $arItem['ID']; if (!$departmentId) { //create new department $arNewSectFields = Array( "ACTIVE" => "Y", "IBLOCK_ID" => $iblockId, "NAME" => $department ); if ($parentSectionId>=0) $arNewSectFields["IBLOCK_SECTION_ID"] = $parentSectionId; // and get it's Id $departmentId = $bs->Add($arNewSectFields); } $arElement = $USER_FIELD_MANAGER->GetUserFields( 'IBLOCK_'.$iblockId.'_SECTION', $departmentId ); // if the head of the department is already set, do not change it if (!empty($arElement['UF_HEAD']['VALUE'])) $arResult['IS_HEAD'] = false; $arResult['ID'] = $departmentId; return $arResult; } // get user list (with attributes) from AD server function GetUserList($arFilter = Array()) { $query = ''; foreach($arFilter as $key=>$value) { $key = strtoupper($key); switch($key) { case 'GROUP_ID': //"SELECT ". // "FROM " case 'GROUP_DN': $temp = ''; $temp_cnt = 0; if(!is_array($value)) $value = array($value); foreach($value as $group) { if(strlen($group)<=0) continue; $temp_cnt++; $temp .= '('.$this->arFields['USER_GROUP_ATTR'].'='.$this->specialchars($group).')'; } $query .= '(|'.$temp.')'; break; } } $user_filter = $this->arFields['USER_FILTER']; if(strlen(trim($user_filter))>0 && substr(trim($user_filter), 0, 1)!='(') $user_filter = '('.trim($user_filter).')'; $query = '(&'.$user_filter.$query.')'; $arResult = $this->Query($query); return $arResult; } function GetUserArray($cn) { $user_filter = $this->arFields['USER_FILTER']; if(strlen(trim($user_filter))>0 && substr(trim($user_filter), 0, 1)!='(') $user_filter = '('.trim($user_filter).')'; $query = '(&'.$user_filter.'('.$cn.'))'; return $this->QueryArray($query); } function specialchars($str) { $from = Array("\\", ',', '+', '"', '<', '>', ';', "\n", "\r", '=', '*'); $to = Array('\5C', '\2C', '\2B', '\22', '\3C', '\3E', '\3B', '\0A', '\0D', '\3D', '\*'); return str_replace($from, $to, $str); } function OnExternalAuthList() { $arResult = Array(); $db_ldap_serv = CLdapServer::GetList(); while($arLDAP = $db_ldap_serv->Fetch()) { $arResult[] = Array( 'ID' => 'LDAP#'.$arLDAP['ID'], 'NAME' => $arLDAP['NAME'] ); } return $arResult; } static function NTLMAuth() { global $USER; if ($USER->IsAuthorized()) return; if(!array_key_exists("AUTH_TYPE", $_SERVER) || ($_SERVER["AUTH_TYPE"] != "NTLM" && $_SERVER["AUTH_TYPE"] != "Negotiate")) return; $ntlm_varname = trim(COption::GetOptionString('ldap', 'ntlm_varname', 'REMOTE_USER')); if (array_key_exists($ntlm_varname, $_SERVER) && strlen($LOGIN = $_SERVER[$ntlm_varname]) > 0) { $DOMAIN = ""; if(($pos = strpos($LOGIN, "\\")) !== false) { $DOMAIN = substr($LOGIN, 0, $pos); $LOGIN = substr($LOGIN, $pos + 1); } elseif($_SERVER["AUTH_TYPE"] == "Negotiate" && (($pos = strpos($LOGIN, "@")) !== false)) { $LOGIN = substr($LOGIN, 0, $pos); $DOMAIN = substr($LOGIN, $pos + 1); } $arFilterServer = array('ACTIVE' => 'Y'); if(strlen($DOMAIN) > 0) { $arFilterServer['CODE'] = $DOMAIN; } else { $DEF_DOMAIN_ID = intval(COption::GetOptionInt('ldap', 'ntlm_default_server', 0)); if($DEF_DOMAIN_ID > 0) $arFilterServer['ID'] = $DEF_DOMAIN_ID; else return; } $db_ldap_serv = CLdapServer::GetList(Array(), $arFilterServer); /*@var $xLDAP CLDAP*/ while($xLDAP = $db_ldap_serv->GetNextServer()) { if($xLDAP->Connect()) { if($arLdapUser = $xLDAP->FindUser($LOGIN)) { $ID = $xLDAP->SetUser($arLdapUser, (COption::GetOptionString("ldap", "add_user_when_auth", "Y")=="Y")); if($ID > 0) { $USER->Authorize($ID); $xLDAP->Disconnect(); return; } } $xLDAP->Disconnect(); } } } } /** * * Recieves the users groups list includes all groups parents list * searching by memberOf in group properties * @param $arFindGroups - user groups * @param $arUserGroups - full array with uppergroups * @param $arAllGroups - list of all ldap groups */ function GetAllMemberOf($arFindGroups, &$arUserGroups, $arAllGroups) { if(!$arFindGroups || $arFindGroups=='') return; if(!is_array($arFindGroups)) $arFindGroups = Array($arFindGroups); foreach($arFindGroups as $group_id) { if(in_array($group_id, $arUserGroups)) continue; $arUserGroups[] = $group_id; $this->GetAllMemberOf($arAllGroups[$group_id]["memberof"], $arUserGroups, $arAllGroups); } } function GetGroupMaps() { global $DB; if(!is_array($this->arGroupMaps)) { $this->arGroupMaps = array(); $rsCorellations = $DB->Query("SELECT LDAP_GROUP_ID, GROUP_ID FROM b_ldap_group WHERE LDAP_SERVER_ID=".intval($this->arFields['ID'])); while ($arCorellation = $rsCorellations->Fetch()) { if(!is_array($this->arGroupMaps[$arCorellation["LDAP_GROUP_ID"]])) $this->arGroupMaps[$arCorellation["LDAP_GROUP_ID"]] = array(); $this->arGroupMaps[$arCorellation["LDAP_GROUP_ID"]][] = $arCorellation["GROUP_ID"]; } } return $this->arGroupMaps; } //Need this to delete old photo static function PrepareUserPhoto($uid, &$arLdapUser) { if(!isset($arLdapUser["PERSONAL_PHOTO"])) return false; $dbRes = CUser::GetById($uid); $arUser = $dbRes->Fetch(); if(!isset($arUser["PERSONAL_PHOTO"]) || is_null($arUser["PERSONAL_PHOTO"])) return false; if($arLdapUser["PERSONAL_PHOTO"] == "") $arLdapUser["PERSONAL_PHOTO"]["del"] = "Y"; $arLdapUser["PERSONAL_PHOTO"]["old_file"] = $arUser["PERSONAL_PHOTO"]; return true; } // update user info, using previously loaded data from AD, make additional calls to AD if needed function SetUser($arLdapUser, $bAddNew = true) { global $USER; $isHead = false; $bUSERGen = false; $userActive = ''; if(!is_object($USER)) { $USER = new CUser(); $bUSERGen = true; } // process previously saved department data if (IsModuleInstalled('intranet') && is_array($arLdapUser['UF_DEPARTMENT'])) { $isHead = $arLdapUser['UF_DEPARTMENT']['IS_HEAD']; // replace temporary value with a real one $arLdapUser['UF_DEPARTMENT'] = array($arLdapUser['UF_DEPARTMENT']['ID']); } if(isset($arLdapUser["ID"])) { $ID = intval($arLdapUser["ID"]); self::PrepareUserPhoto($ID,$arLdapUser); $USER->Update($ID, $arLdapUser); } else { $ldapUserID = 0; if (isset($_REQUEST['ldap_user_id']) && strlen($_REQUEST['ldap_user_id']) == 32) { $dbUser = CUser::GetList( $O='', $B='', array('XML_ID' => $_REQUEST['ldap_user_id'], 'EXTERNAL_AUTH_ID' => $arLdapUser['EXTERNAL_AUTH_ID']), array('FIELDS' => array('ID', 'XML_ID')) ); if ($arUser = $dbUser->Fetch()) { if($arUser['XML_ID']) $ldapUserID = $arUser['ID']; } } $bitrixUserId = 0; $res = CUser::GetList( $O="", $B="", array('LOGIN_EQUAL_EXACT' => $arLdapUser['LOGIN']), array('FIELDS' => array('ID', 'EXTERNAL_AUTH_ID', 'ACTIVE')) ); while($ar_res = $res->Fetch()) { if($ar_res['EXTERNAL_AUTH_ID'] == $arLdapUser['EXTERNAL_AUTH_ID']) { $bitrixUserId = $ar_res['ID']; $userActive = $ar_res['ACTIVE']; break; } else { $bAddNew = ($bAddNew && COption::GetOptionString("ldap", "ldap_create_duplicate_login_user", 'Y') == 'Y'); } } $arLdapUser['PASSWORD'] = uniqid(rand(), true); if($bitrixUserId <= 0 && $ldapUserID <= 0) { if($bAddNew) { if(strlen($arLdapUser["EMAIL"]) <= 0) { $arLdapUser["EMAIL"] = COption::GetOptionString("ldap", "default_email", 'no@email'); } $ID = $USER->Add($arLdapUser); } else { $ID = 0; } } else { $ID = ($ldapUserID > 1 ? $ldapUserID : $bitrixUserId); self::PrepareUserPhoto($ID,$arLdapUser); //Performance to skip \CIntranetEventHandlers::UpdateActivity(); if(isset($arLdapUser['ACTIVE']) && $userActive == $arLdapUser['ACTIVE']) unset($arLdapUser['ACTIVE']); $USER->Update($ID, $arLdapUser); } $ID = intval($ID); } // - add this user to groups if ($ID > 0) { // - set as head of department if (IsModuleInstalled('intranet') && $isHead) { CLdapUtil::SetDepartmentHead($ID,$arLdapUser['UF_DEPARTMENT'][0]); } $arGroupMaps = $this->GetGroupMaps(); if(!empty($arGroupMaps)) { // For each group finding all superior ones $arUserLdapGroups = Array(); $arLdapGroups = $this->GetGroupListArray(); $this->GetAllMemberOf($arLdapUser['LDAP_GROUPS'], $arUserLdapGroups, $arLdapGroups); $arGroupMaps = $this->GetGroupMaps(); $arUserBitrixGroups = $USER->GetUserGroup($ID); $arUserBitrixGroupsNew = array(); $prevGroups = $arUserBitrixGroups; sort($prevGroups); foreach($arGroupMaps as $fromLdapGroup=>$arToUserGroups) { foreach($arToUserGroups as $toUserGroup) { if (($k = array_search($toUserGroup, $arUserBitrixGroups)) !== false) { unset($arUserBitrixGroups[$k]); } // If there is such a group among user's if (in_array($fromLdapGroup, $arUserLdapGroups)) { $arUserBitrixGroupsNew[] = $toUserGroup; } } } $arUserBitrixGroups = array_merge($arUserBitrixGroups, array_unique($arUserBitrixGroupsNew)); sort($arUserBitrixGroups); if($arUserBitrixGroups <> $prevGroups) { $USER->SetUserGroup($ID, $arUserBitrixGroups); } } } if($bUSERGen) { unset($USER); } return $ID; } protected function getLastErrorDescription() { $result = ''; if($this->conn) { $ldapError = ldap_error($this->conn); if(strlen($ldapError) > 0) $result = "\nldap_error: '".$ldapError."'\nldap_errno: '".ldap_errno($this->conn)."'"; } return $result; } }