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