OK, I have more than 30.000 ldap users and it's a non sense to import all of them because probably only 5% will ever open a ticket for support. So I manged to implement auto creation of ldap users. The first attempt was to make a new external authentication extension. But I'm not familiar with PHP and I've not an IDE installed. So I managed on modifying some files. Probably not the best solution but it's functional, so.....
Here are the files modified. I've tested on env-production but they must be in the datamodel, of course.
It requires some new config:
'authent-ldap' => array ( 'host' => 'ldap server', 'port' => 389, 'default_user' => 'ldap search user', 'default_pwd' => 'password', 'base_dn' => 'base dn', 'user_query' => '(&(objectCategory=person)(objectClass=user)(sAMAccountName=%1$s))', 'options' => array ( 17 => 3, 8 => 0, ), 'start_tls' => true, 'create_user' => true, 'default_organization' => 'default organization name', 'default_profile' => array ( 0 => 'Portal user', ), 'debug' => false, ),
and here the snip of modification made:
/var/www/html/application# vi loginform.class.inc.php protected function OnCheckCredentials(&$iErrorCode) { if ($_SESSION['login_mode'] == 'form') { $sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data'); $sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data'); if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal')) { IssueLog::Error("QUAT '$sAuthUser'"); if (!UserLDAP::CheckCredentialsAndCreateUser($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal')) { $iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS; return LoginWebPage::LOGIN_FSM_ERROR; } } } return LoginWebPage::LOGIN_FSM_CONTINUE; } ------------------------------------------------------------------------------------- /var/www/html/env-production/authent-ldap# vi model.authent-ldap.php class UserLDAP extends UserInternal implements iSelfRegister { .... public function CheckCredentials($sPassword) { $sLDAPHost = MetaModel::GetModuleSetting('authent-ldap', 'host', 'localhost'); $iLDAPPort = MetaModel::GetModuleSetting('authent-ldap', 'port', 389); $sDefaultLDAPUser = MetaModel::GetModuleSetting('authent-ldap', 'default_user', ''); $sDefaultLDAPPwd = MetaModel::GetModuleSetting('authent-ldap', 'default_pwd', ''); $bLDAPStartTLS = MetaModel::GetModuleSetting('authent-ldap', 'start_tls', false); $bCreateUser = MetaModel::GetModuleSetting('authent-ldap', 'create_user', false); $sDefaultOrganization = MetaModel::GetModuleSetting('authent-ldap', 'default_organization', ''); $aRequestedProfiles = MetaModel::GetModuleSetting('authent-ldap', 'default_profile', ''); $aOptions = MetaModel::GetModuleSetting('authent-ldap', 'options', array()); if (array_key_exists(LDAP_OPT_DEBUG_LEVEL, $aOptions)) { // Set debug level before trying to connect, so that debug info appear in the PHP error log if ldap_connect goes wrong $bRet = ldap_set_option($hDS, LDAP_OPT_DEBUG_LEVEL, $aOptions[LDAP_OPT_DEBUG_LEVEL]); $this->LogMessage("ldap_set_option('$name', '$value') returned ".($bRet ? 'true' : 'false')); } $hDS = @ldap_connect($sLDAPHost, $iLDAPPort); if ($hDS === false) { $this->LogMessage("ldap_authentication: can not connect to the LDAP server '$sLDAPHost' (port: $iLDAPPort). Check the configuration file config-itop.php."); return false; } foreach($aOptions as $name => $value) { $bRet = ldap_set_option($hDS, $name, $value); $this->LogMessage("ldap_set_option('$name', '$value') returned ".($bRet ? 'true' : 'false')); } if ($bLDAPStartTLS) { $this->LogMessage("ldap_authentication: start tls required."); $hStartTLS = ldap_start_tls($hDS); //$this->LogMessage("ldap_authentication: hStartTLS = '$hStartTLS'"); if (!$hStartTLS) { $this->LogMessage("ldap_authentication: start tls failed."); return false; } } if ($bind = @ldap_bind($hDS, $sDefaultLDAPUser, $sDefaultLDAPPwd)) { // Search for the person, using the specified query expression $sLDAPUserQuery = MetaModel::GetModuleSetting('authent-ldap', 'user_query', ''); $sBaseDN = MetaModel::GetModuleSetting('authent-ldap', 'base_dn', ''); $sLogin = $this->Get('login'); $iContactId = $this->Get('contactid'); $sFirstName = ''; $sLastName = ''; $sEMail = ''; if ($iContactId > 0) { $oPerson = MetaModel::GetObject('Person', $iContactId); if (is_object($oPerson)) { $sFirstName = $oPerson->Get('first_name'); $sLastName = $oPerson->Get('name'); $sEMail = $oPerson->Get('email'); } } // %1$s => login // %2$s => first name // %3$s => last name // %4$s => email $sQuery = sprintf($sLDAPUserQuery, $sLogin, $sFirstName, $sLastName, $sEMail); $hSearchResult = @ldap_search($hDS, $sBaseDN, $sQuery); $iCountEntries = ($hSearchResult !== false) ? @ldap_count_entries($hDS, $hSearchResult) : 0; switch($iCountEntries) { case 1: // Exactly one entry found, let's check the password by trying to bind with this user $aEntry = ldap_get_entries($hDS, $hSearchResult); $sUserDN = $aEntry[0]['dn']; $bUserBind = @ldap_bind($hDS, $sUserDN, $sPassword); if (($bUserBind !== false) && !empty($sPassword)) { $sFirstName = $aEntry[0]['sn'][0]; $sLastName = $aEntry[0]['givenname'][0]; $sEMail = $aEntry[0]['mail'][0]; ldap_unbind($hDS); $sAuthentication = 'internal'; $oUser = LoginWebPage::FindUser($sLogin, true, ucfirst(strtolower($sAuthentication))); if($bCreateUser && is_null($oUser)) { $oPerson=LoginWebPage::ProvisionPerson($sFirstName, $sLastName, $sEMail, $sDefaultOrganization); LoginWebPage::ProvisionUser($sLogin, $oPerson, $aRequestedProfiles, 'UserLDAP'); } return true; // Password Ok } $this->LogMessage("ldap_authentication: wrong password for user: '$sUserDN'."); return false; // Wrong password break; case 0: // User not found... $this->LogMessage("ldap_authentication: no entry found with the query '$sQuery', base_dn = '$sBaseDN'. User not found in LDAP."); break; default: // More than one entry... maybe the query is not specific enough... $this->LogMessage("ldap_authentication: several (".ldap_count_entries($hDS, $hSearchResult).") entries match the query '$sQuery', base_dn = '$sBaseDN', check that the query defined in config-itop.php is specific enough."); } return false; } else { // Trace: invalid default user for LDAP initial binding $this->LogMessage("ldap_authentication: can not bind to the LDAP server '$sLDAPHost' (port: $iLDAPPort), user='$sDefaultLDAPUser', pwd='$sDefaultLDAPPwd'. Error: '".ldap_error($hDS)."'. Check the configuration file config-itop.php."); return false; } } /** * Called when no user is found in iTop for the corresponding 'name'. This method * can create/synchronize the User in iTop with an external source (such as AD/LDAP) on the fly * @param string $sName The typed-in user name * @param string $sPassword The typed-in password * @param string $sLoginMode The login method used (cas|form|basic|url) * @param string $sAuthentication The authentication method used (any|internal|external) * @return bool true if the user is a valid one, false otherwise */ public static function CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication) { IssueLog::Error("TRE '$sName'"); $oUser = MetaModel::NewObject('UserLDAP'); $oUser->Set('login', $sName); $oUser->Set('language', MetaModel::GetConfig()->GetDefaultLanguage()); return $oUser->CheckCredentials($sPassword); } /** * Called after the user has been authenticated and found in iTop. This method can * Update the user's definition on the fly (profiles...) to keep it in sync with an external source * @param User $oUser The user to update/synchronize * @param string $sLoginMode The login mode used (cas|form|basic|url) * @param string $sAuthentication The authentication method used * @return void */ public static function UpdateUser(User $oUser, $sLoginMode, $sAuthentication) { return false; } ------------------------------------------------------------------------------------------------------------------------------- /var/www/html/application# vi loginwebpage.class.inc.php /** * Provisioning API: Create or update a User * * @api * * @param string $sAuthUser * @param Person $oPerson * @param array $aRequestedProfiles profiles to add to the new user * * @return \UserExternal|\UserLdap|null */ public static function ProvisionUser($sAuthUser, $oPerson, $aRequestedProfiles, $sUserType = 'UserExternal') { if (!MetaModel::IsValidClass('URP_Profiles')) { IssueLog::Error("URP_Profiles is not a valid class. Automatic creation of Users is not supported in this context, sorry."); return null; } /** @var UserExternal $oUser */ $oUser = null; try { CMDBObject::SetTrackOrigin('custom-extension'); $sInfo = 'External User provisioning'; if (isset($_SESSION['login_mode'])) { $sInfo .= " ({$_SESSION['login_mode']})"; } CMDBObject::SetTrackInfo($sInfo); $oUser = MetaModel::GetObjectByName($sUserType, $sAuthUser, false); if (is_null($oUser)) { $oUser = MetaModel::NewObject($sUserType); $oUser->Set('login', $sAuthUser); $oUser->Set('contactid', $oPerson->GetKey()); $oUser->Set('language', MetaModel::GetConfig()->GetDefaultLanguage()); } // read all the existing profiles $oProfilesSearch = new DBObjectSearch('URP_Profiles'); $oProfilesSet = new DBObjectSet($oProfilesSearch); $aAllProfiles = array(); while ($oProfile = $oProfilesSet->Fetch()) { $aAllProfiles[strtolower($oProfile->GetName())] = $oProfile->GetKey(); } $aProfiles = array(); foreach ($aRequestedProfiles as $sRequestedProfile) { $sRequestedProfile = strtolower($sRequestedProfile); if (isset($aAllProfiles[$sRequestedProfile])) { $aProfiles[] = $aAllProfiles[$sRequestedProfile]; } } if (empty($aProfiles)) { throw new Exception(Dict::S('UI:Login:Error:NoValidProfiles')); } // Now synchronize the profiles $oProfilesSet = DBObjectSet::FromScratch('URP_UserProfile'); $sOrigin = 'External User provisioning'; if (isset($_SESSION['login_mode'])) { $sOrigin .= " ({$_SESSION['login_mode']})"; } foreach ($aProfiles as $iProfileId) { $oLink = new URP_UserProfile(); $oLink->Set('profileid', $iProfileId); $oLink->Set('reason', $sOrigin); $oProfilesSet->AddObject($oLink); } $oUser->Set('profile_list', $oProfilesSet); if ($oUser->IsModified()) { $oUser->DBWrite(); } } catch (Exception $e) { IssueLog::Error($e->getMessage()); } return $oUser; } ----------------------------------------------------------------------------------------------------------
Hello Romano,
Thanks for sharing !
But... Are you asking something ?
Next time please use the forums ("discussion" tab, then "new topic" button on the top left) : we prefer tickets to be used only for qualified bug reports and enhancements requests.
In consequence I'm closing this.
Also, we strongly advise against modifying iTop core code : that will complicate a lot iTop upgrades, cause you'll have to report all of those modifications each times !
iTop provides a complete customization system that should be used instead !
About user provisionning, all is explained here : Login and User Provisioning API
This custom code can be added to iTop using modules, see Customizing iTop [iTop Documentation]
Last edit: Romano Trampus 2021-01-25
Hello Romano, I've received an email with your answer but when opening this ticket your message is blank ?!??
In the email I received you were asking if it's ok to contribute and yes of course it is ! See the CONTRIBUTING.md files in our public repos.
Last edit: Pierre Goiffon 2021-02-24
Hi Pierre, I moved to a discussion, and probably it's still there :-) Thank
you,
romano
Last edit: Pierre Goiffon 2021-02-24