* @author Romain Quetiez * @author Denis Flaven * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ //////////////////////////////////////////////////////////////////////////////// // Configuration parameters: adjust them to connect to your AD server // And configure the mapping between AD groups and iTop profiles $aConfig = array( // Configuration of the Active Directory connection 'host' => 'your_ad_server', // IP or FQDN of your domain controller 'port' => '389', // LDAP port, 398=LDAP, 636= LDAPS 'dn' => 'DC=combodo,DC=net', // Domain DN 'username' => 'ad_user', // username with read access 'password' => 'ad_password', // password for above // Query to retrieve and filter the users from AD // Example: retrieve all users from the AD Group "iTop Users" 'ldap_query' => '(&(objectCategory=user)(memberOf=CN=iTop Users,CN=Users,DC=combodo,DC=net))', // Example 2: retrieves ALL the users from AD // 'ldap_query' => '(&(objectCategory=user))', // Retrieve all users // Which field to use as the iTop login samaccountname or userprincipalname ? 'login' => 'samaccountname', //'login' => 'userprincipalname', // Mapping between the AD groups and the iTop profiles 'profiles_mapping' => array( //AD Group Name => iTop Profile Name 'Administrators' => 'Administrator', ), // Since each iTop user must have at least one profile, assign the profile // Below to users for which there was no match in the above mapping 'default_profile' => 'Portal user', 'default_language' => 'EN US', // Default language for creating new users 'default_organization' => 2, // ID of the default organization for creating new contacts ); // End of configuration //////////////////////////////////////////////////////////////////////////////// if (file_exists('../approot.inc.php')) { // iTop 1.0.2 include('../approot.inc.php'); } else // iTop 1.0 & 1.0.1 { define('APPROOT', '../'); } require_once(APPROOT.'application/application.inc.php'); require_once(APPROOT.'application/webpage.class.inc.php'); require_once(APPROOT.'application/csvpage.class.inc.php'); require_once(APPROOT.'application/clipage.class.inc.php'); require_once(APPROOT.'application/startup.inc.php'); // List of attributes to retrieve $aAttribs = array( 'samaccountname', 'sn', 'givenname', 'userprincipalname', 'cn', 'memberof', 'mail', ); $g_aUsersCache = null; // Cache of all the iTop users to speed up searches $g_aProfilesCache = null; // Cache of all iTop profiles /** * Helper function to read attributes from LDAP data * @param hash The LDAP data for one item as returned by ldap_search * @param string The name of the attribute to retrieve * @return mixed null if no such attribute, a scalar or a array depending on the * number of values for the attribute. */ function ReadLdapValue($aEntry, $sValueName) { if (array_key_exists($sValueName, $aEntry)) { $iCount = $aEntry[$sValueName]['count']; switch($iCount) { case 0: // No value, return null return null; case 1: // Just one value, return it return $aEntry[$sValueName][0]; default: // Many values, return all of them as an array // except the 'count' entry $aValues = $aEntry[$sValueName]; unset($aValues['count']); return $aValues; } } return null; } /** * Helper function that processes 1 user at a time * @param $aData hash The input data from Active Directory * @param $index integer The index of the current user in the AD query (for reporting) * @param $aConfig hash The configuration parameter * @param $oChange CMDBChange Change to record all the changes or null if simulation mode * @return string The action undertaken 'created', 'synchronized', 'error' */ function ProcessUser($aData, $index, $aConfig, $oChange = null) { $sAction = 'error'; $sUserLogin = $aData['samaccountname']; if (!is_array($aData['memberof'])) { $aADGroups = array($aData['memberof']); } else { $aADGroups = $aData['memberof']; } $aITopProfiles = array(); foreach($aADGroups as $sGroupString) { $aMatches = array(); $sShortGroupString = ''; if (preg_match('/^CN=([^,]+)/', $sGroupString, $aMatches)) { $sShortGroupString = $aMatches[1]; } //echo "

GroupString: $sGroupString => $sShortGroupString

"; if (isset($aConfig['profiles_mapping'][$sShortGroupString])) { $aITopProfiles[] = $aConfig['profiles_mapping'][$sShortGroupString]; } } if (count($aITopProfiles) == 0) { // Each user must have at least one profile // Assign the 'default_profile' to this user $aITopProfiles[] = $aConfig['default_profile']; } echo "

User#{$index}: {$aData['cn']}

\n"; echo ""; foreach($aData as $sAttrib => $value) { echo ""; echo "\n"; } echo ""; echo "\n"; echo "
$sAttrib"; if (is_array($value)) { echo implode('
', $value); } else { echo htmlentities($value); } echo "
iTop Profiles"; echo implode('
', $aITopProfiles); echo "
"; $sLogin = $aData[$aConfig['login']]; $oITopUser = GetUserByLogin($sLogin); if ($oITopUser == null) { // Check if a contact needs to be created or not $oPerson = GetPersonByEmail($aData['mail']); if (is_object($oPerson)) { echo "

A person with the email='{$aData['mail']}' was found ".$oPerson->GetHyperlink().". This person will be used when creating the account.

"; } else if ($oPerson == null) { echo "

A new person will be created.

"; $oPerson = new Person(); $oPerson->Set('name', $aData['sn']); $oPerson->Set('first_name', $aData['givenname']); $oPerson->Set('email', $aData['mail']); $oPerson->Set('org_id', $aConfig['default_organization']); if ($oChange != null) { $oPerson->DBInsertTracked($oChange); } } else { // Error ! Several matches found ?? throw new Exception($oPerson); } $sAction = 'created'; echo "

User $sLogin will be created in iTop

"; $oITopUser = new UserLDAP; $oITopUser->Set('login', $sLogin); $oITopUser->Set('contactid', $oPerson->GetKey()); $oITopUser->Set('language', $aConfig['default_language']); // Update the profiles $oLinkSet = DBObjectSet::FromScratch('URP_UserProfile'); foreach($aITopProfiles as $sProfile) { $oLink = new URP_UserProfile; $iProfileId = GetProfileByName($sProfile); if ($iProfileId != null) { $oLink->Set('profileid', $iProfileId); $oLinkSet->AddObject($oLink); } else { echo "

Error: the profile '$sProfile' does not exist in iTop, verify the profiles_mapping configuration!

"; } } $oITopUser->Set('profile_list', $oLinkSet); if ($oChange != null) { $oITopUser->DBInsertTracked($oChange); } } else if(is_object($oITopUser)) { $sAction = 'synchronized'; echo "

User $sLogin (UserLDAP::".$oITopUser->GetKey().") will be synchronized in iTop

"; // Update the profiles $oLinkSet = DBObjectSet::FromScratch('URP_UserProfile'); $oITopUser->Set('login', $sLogin); foreach($aITopProfiles as $sProfile) { $oLink = new URP_UserProfile; $iProfileId = GetProfileByName($sProfile); if ($iProfileId != null) { $oLink->Set('profileid', $iProfileId); $oLinkSet->AddObject($oLink); } else { echo "

Error: the profile '$sProfile' does not exist in iTop, verify the profiles_mapping configuration!

"; } } $oITopUser->Set('profile_list', $oLinkSet); if ($oChange != null) { $oITopUser->DBUpdateTracked($oChange); } } else { // Error, another kind of user already exists with the same login ? echo "

Error: $oITopUser

"; } return $sAction; } /** * Search the given user (identified by its login) in the iTop database * @param $sLogin string The login of the user * @return mixed null => nothing found, object => the user to synchronize, string => error message */ function GetUserByLogin($sLogin) { global $g_aUsersCache; $result = null; if ($g_aUsersCache == null) InitUsersCache(); if (isset($g_aUsersCache[$sLogin])) { $oUser = $g_aUsersCache[$sLogin]; if (get_class($oUser) != 'UserLDAP') { $result = "A user with the same login ($sLogin), but not managed by LDAP already exists in iTop, the AD record will be ignored."; } else { $result = $oUser; } } return $result; } /** * Initializes the cache for quickly searching iTop users * @param none * @return integer Number of users fetched from iTop */ function InitUsersCache() { global $g_aUsersCache; $sOQL = "SELECT User"; $oSearch = DBObjectSearch::FromOQL($sOQL); $oSet = new CMDBObjectSet($oSearch); $iRet = $oSet->Count(); while($oUser = $oSet->Fetch()) { $g_aUsersCache[$oUser->Get('login')] = $oUser; } return $iRet; } /** * Retrieves the ID of a profile (in iTop) base on its name * @param $sProfile string Name of the profile * @return integer ID of the profile, or null is not found */ function GetProfileByName($sProfileName) { global $g_aProfilesCache; $iRet = null; if ($g_aProfilesCache == null) InitProfilesCache(); if (isset($g_aProfilesCache[$sProfileName])) { $iRet = $g_aProfilesCache[$sProfileName]; } return $iRet; } /** * Initializes the cache of the iTop profiles * @param none * @return void */ function InitProfilesCache() { global $g_aProfilesCache; $sOQL = "SELECT URP_Profiles"; $oSearch = DBObjectSearch::FromOQL($sOQL); $oSet = new CMDBObjectSet($oSearch); while($oProfile = $oSet->Fetch()) { $g_aProfilesCache[$oProfile->GetName()] = $oProfile->GetKey(); } } /** * Search for a Person by email address * @param $sEmail string * @return mixed Person (if found) or null (not found) or string (error) */ function GetPersonByEmail($sEmail) { static $oSearch = null; // OQL Query cache $person = null; if ($oSearch == null) { $sOQL = 'SELECT Person WHERE email = :email'; $oSearch = DBObjectSearch::FromOQL($sOQL); } $oSet = new CMDBObjectSet($oSearch, array(), array('email' => $sEmail)); switch($oSet->Count()) { case 0: $person = null; break; case 1: $person = $oSet->Fetch(); break; default: $person = ' several matches found: '.$oSet->Count()." persons have the email address '$sEmail'"; } return $person; } /****************************************************************************** * * Main program * ******************************************************************************/ if (utils::IsModeCLI()) { $sAuthUser = utils::ReadParam('auth_user', '', true); $sAuthPwd = utils::ReadParam('auth_pwd', '', true); if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { UserRights::Login($sAuthUser); // Login & set the user's language } else { echo "Access restricted or wrong credentials ('$sAuthUser')"; exit; } } else { $_SESSION['login_mode'] = 'basic'; require_once('../application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed $sCSVData = utils::ReadPostedParam('csvdata'); } if (!UserRights::IsAdministrator()) { echo '

Access is restricted to administrators

'; exit; } // By default, run in simulation mode (i.e do nothing) $bSimulationMode = utils::ReadParam('simulation', 1, true); $oMyChange = null; if (!$bSimulationMode) { $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); if (UserRights::IsImpersonated()) { $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); } else { $sUserString = UserRights::GetUser(); } $oMyChange->Set("userinfo", $sUserString); $oMyChange->DBInsert(); } else { echo "

Simulation mode -- no action will be performed

"; echo "

Set the parameter simulation=0 to trigger the actual execution.

"; } $ad = ldap_connect($aConfig['host'], $aConfig['port']) or die( "Could not connect to {$aConfig['host']} on port {$aConfig['port']}!" ); echo "

Connected to {$aConfig['host']} on port {$aConfig['port']}

\n"; // Set version number ldap_set_option($ad, LDAP_OPT_PROTOCOL_VERSION, 3) or die ("Could not set ldap protocol"); ldap_set_option($ad, LDAP_OPT_REFERRALS,0) or die ("could no se the ldap referrals"); // Binding to ldap server $bd = ldap_bind($ad, $aConfig['username'], $aConfig['password']) or die ("Could not bind"); echo "

Identified as {$aConfig['username']}

\n"; $sLdapSearch = $aConfig['ldap_query']; echo "

LDAP Query: '$sLdapSearch'

"; $search = ldap_search($ad, $aConfig['dn'], $sLdapSearch /*, $aAttribs*/) or die ("ldap search failed"); $entries = ldap_get_entries($ad, $search); $index = 1; $aStatistics = array( 'created' => 0, 'synchronized' => 0, 'error' => 0, ); $iCreated = 0; $iSynchronized = 0; $iErrors = 0; if ($entries["count"] > 0) { $iITopUsers = InitUsersCache(); echo "

{$entries["count"]} user(s) found in Active Directory, $iITopUsers (including non-LDAP users) found in iTop.

\n"; foreach($entries as $key => $aEntry) { //echo "
$key\n";
		//print_r($aEntry);
		//echo "
\n"; if (strcmp($key,'count') != 0) { $aData = array(); foreach($aAttribs as $sName) { $aData[$sName] = ReadLdapValue($aEntry, $sName); } if (empty($aData['mail'])) { $aData['mail'] = $aData['userprincipalname']; } try { $sAction = ProcessUser($aData, $index, $aConfig, $oMyChange); } catch(Exception $e) { echo "

An error occured while processing $index: ".$e->getMessage()."

"; $sAction = 'error'; } echo "
\n"; $aStatistics[$sAction]++; $index++; } } } else { echo "

Nothing found !

\n"; echo "

LDAP query was: $sLdapSearch

\n"; } ldap_unbind($ad); if ($bSimulationMode) { echo "

Simulation mode -- no action was performed

"; } echo "

Statistics:

"; echo ""; foreach($aStatistics as $sKey => $iValue) { echo "\n"; echo "\n"; } echo "
$sKey$iValue
"; ?>