mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 07:24:13 +01:00
N°2311 - User Provisioning API
This commit is contained in:
@@ -85,7 +85,7 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE;
|
||||
}
|
||||
|
||||
protected function OnCheckCredentials(&$iErrorCode)
|
||||
protected function OnCredentialsOk(&$iErrorCode)
|
||||
{
|
||||
if (!isset($_SESSION['login_mode']))
|
||||
{
|
||||
|
||||
@@ -361,7 +361,7 @@ EOF
|
||||
{
|
||||
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
$sToken = utils::ReadParam('token', '', false, 'raw_data');
|
||||
$sNewPwd = utils::ReadPostedParam('new_pwd', '', false, 'raw_data');
|
||||
$sNewPwd = utils::ReadPostedParam('new_pwd', '', 'raw_data');
|
||||
|
||||
UserRights::Login($sAuthUser); // Set the user's language
|
||||
$oUser = UserRights::GetUserObject();
|
||||
@@ -679,7 +679,28 @@ EOF
|
||||
}
|
||||
|
||||
/**
|
||||
* Store User info in the session when connection is OK
|
||||
* Login API: Check that credentials correspond to a valid user
|
||||
*
|
||||
* @param string $sName
|
||||
* @param string $sPassword
|
||||
* @param string $sAuthentication
|
||||
*
|
||||
* @return bool
|
||||
* @api
|
||||
*/
|
||||
public static function CheckUser($sName, $sPassword, $sAuthentication = 'external')
|
||||
{
|
||||
$oUser = self::FindUser($sName, true, ucfirst(strtolower($sAuthentication)));
|
||||
if (is_null($oUser))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return $oUser->CheckCredentials($sPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Login API: Store User info in the session when connection is OK
|
||||
*
|
||||
* @param $sAuthUser
|
||||
* @param $sAuthentication
|
||||
@@ -692,6 +713,7 @@ EOF
|
||||
* @throws CoreWarning
|
||||
* @throws MySQLException
|
||||
* @throws OQLException
|
||||
* @api
|
||||
*/
|
||||
public static function OnLoginSuccess($sAuthUser, $sAuthentication, $sLoginMode)
|
||||
{
|
||||
@@ -714,6 +736,14 @@ EOF
|
||||
UserRights::_InitSessionCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Login API: Check that an already logger User is still valid
|
||||
*
|
||||
* @param int $iErrorCode
|
||||
*
|
||||
* @return int LOGIN_FSM_RETURN_OK or LOGIN_FSM_RETURN_ERROR
|
||||
* @api
|
||||
*/
|
||||
public static function CheckLoggedUser(&$iErrorCode)
|
||||
{
|
||||
if (isset($_SESSION['auth_user']))
|
||||
@@ -744,6 +774,211 @@ EOF
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provisioning API: Find a User
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param bool $bMustBeValid
|
||||
* @param string $sType
|
||||
*
|
||||
* @param string $sLogin
|
||||
*
|
||||
* @return \User|null
|
||||
*/
|
||||
public static function FindUser($sLogin, $bMustBeValid = true, $sType = 'External')
|
||||
{
|
||||
try
|
||||
{
|
||||
$aArgs = array('login' => $sLogin);
|
||||
$sUserClass = "User$sType";
|
||||
$oSearch = DBObjectSearch::FromOQL("SELECT $sUserClass WHERE login = :login");
|
||||
if ($bMustBeValid)
|
||||
{
|
||||
$oSearch->AddCondition('status', 'enabled');
|
||||
}
|
||||
$oSet = new DBObjectSet($oSearch, array(), $aArgs);
|
||||
if ($oSet->CountExceeds(0))
|
||||
{
|
||||
/** @var User $oUser */
|
||||
$oUser = $oSet->Fetch();
|
||||
|
||||
return $oUser;
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provisioning API: Find a Person
|
||||
*
|
||||
* @param string $sEmail
|
||||
*
|
||||
* @return \DBObject
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
* @throws \Exception
|
||||
* @api
|
||||
*/
|
||||
public static function FindPerson($sEmail)
|
||||
{
|
||||
$oSearch = new DBObjectSearch('Person');
|
||||
$oSearch->AddCondition('email', $sEmail);
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
if ($oSet->CountExceeds(1))
|
||||
{
|
||||
throw new Exception(Dict::S('UI:Login:Error:MultipleContactsHaveSameEmail'));
|
||||
}
|
||||
return $oSet->Fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provisioning API: Create a person
|
||||
*
|
||||
* @param string $sFirstName
|
||||
* @param string $sLastName
|
||||
* @param string $sEmail
|
||||
* @param string $sOrganization
|
||||
* @param array $aAdditionalParams
|
||||
*
|
||||
* @return \Person
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
* @api
|
||||
*/
|
||||
public static function ProvisionPerson($sFirstName, $sLastName, $sEmail, $sOrganization, $aAdditionalParams = array())
|
||||
{
|
||||
/** @var Person $oPerson */
|
||||
$oPerson = MetaModel::NewObject('Person');
|
||||
$oPerson->Set('first_name', $sFirstName);
|
||||
$oPerson->Set('name', $sLastName);
|
||||
$oPerson->Set('email', $sEmail);
|
||||
$oOrg = MetaModel::GetObjectByName('Organization', $sOrganization, false);
|
||||
if (is_null($oOrg))
|
||||
{
|
||||
throw new Exception(Dict::S('UI:Login:Error:WrongOrganizationName'));
|
||||
}
|
||||
$oPerson->Set('org_id', $oOrg->GetKey());
|
||||
foreach ($aAdditionalParams as $sAttCode => $sValue)
|
||||
{
|
||||
$oPerson->Set($sAttCode, $sValue);
|
||||
}
|
||||
/** @var CMDBChange $oMyChange */
|
||||
$oMyChange = MetaModel::NewObject('CMDBChange');
|
||||
$oMyChange->Set("date", time());
|
||||
$sOrigin = 'External User provisioning';
|
||||
if (isset($_SESSION['login_mode']))
|
||||
{
|
||||
$sOrigin .= " ({$_SESSION['login_mode']})";
|
||||
}
|
||||
$oMyChange->Set('userinfo', $sOrigin);
|
||||
$oMyChange->DBInsert();
|
||||
$oPerson->DBInsertTracked($oMyChange);
|
||||
return $oPerson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provisioning API: Create or update a User
|
||||
*
|
||||
* @param string $sLogin
|
||||
* @param Person $oPerson
|
||||
* @param array $aRequestedProfiles
|
||||
*
|
||||
* @return \cmdbAbstractObject|\UserExternal
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \Exception
|
||||
* @api
|
||||
*/
|
||||
public static function ProvisionUser($sLogin, $oPerson, $aRequestedProfiles)
|
||||
{
|
||||
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 = MetaModel::GetObjectByName('UserExternal', $sLogin, false);
|
||||
if (is_null($oUser))
|
||||
{
|
||||
$oUser = MetaModel::NewObject('UserExternal');
|
||||
$oUser->Set('login', $sLogin);
|
||||
$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())
|
||||
{
|
||||
/** @var \CMDBChange $oMyChange */
|
||||
$oMyChange = MetaModel::NewObject("CMDBChange");
|
||||
$oMyChange->Set("date", time());
|
||||
$oMyChange->Set('userinfo', $sOrigin);
|
||||
$oMyChange->DBInsert();
|
||||
if ($oUser->IsNew())
|
||||
{
|
||||
$oUser->DBInsertTracked($oMyChange);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oUser->DBUpdateTracked($oMyChange);
|
||||
}
|
||||
}
|
||||
|
||||
return $oUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridable: depending on the user, head toward a dedicated portal
|
||||
* @param string|null $sRequestedPortalId
|
||||
|
||||
@@ -18,7 +18,6 @@ use phpCAS;
|
||||
use URP_UserProfile;
|
||||
use User;
|
||||
use UserExternal;
|
||||
use UserRights;
|
||||
use utils;
|
||||
|
||||
/**
|
||||
@@ -92,7 +91,7 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
if ($_SESSION['login_mode'] == 'cas')
|
||||
{
|
||||
$sAuthUser = $_SESSION['auth_user'];
|
||||
if (!UserRights::CheckCredentials($sAuthUser, '', $_SESSION['login_mode'], 'external'))
|
||||
if (!LoginWebPage::CheckUser($sAuthUser, '', 'external'))
|
||||
{
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_NOTAUTHORIZED;
|
||||
return LoginWebPage::LOGIN_FSM_RETURN_ERROR;
|
||||
@@ -184,16 +183,22 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
|
||||
|
||||
private function DoUserProvisioning($sLogin)
|
||||
{
|
||||
$aArgs = array('login' => $sLogin);
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserExternal WHERE login = :login"), array(), $aArgs);
|
||||
if ($oSet->CountExceeds(0))
|
||||
$bCASUserSynchro = Config::Get('cas_user_synchro');
|
||||
if (!$bCASUserSynchro)
|
||||
{
|
||||
/** @var User $oUser */
|
||||
$oUser = $oSet->Fetch();
|
||||
CASUserProvisioning::UpdateUser($oUser);
|
||||
return;
|
||||
}
|
||||
CASUserProvisioning::CreateUser($sLogin, '', 'cas', 'external');
|
||||
|
||||
$oUser = LoginWebPage::FindUser($sLogin, false);
|
||||
if ($oUser)
|
||||
{
|
||||
if ($oUser->Get('status') == 'enabled')
|
||||
{
|
||||
CASUserProvisioning::UpdateUser($oUser);
|
||||
}
|
||||
return;
|
||||
}
|
||||
CASUserProvisioning::CreateUser($sLogin, '', 'external');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,11 +212,6 @@ class CASUserProvisioning
|
||||
* 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 CAS authenticated user name
|
||||
* @param string $sPassword Ignored
|
||||
* @param string $sLoginMode The login mode used (cas|form|basic|url)
|
||||
* @param string $sAuthentication The authentication method used
|
||||
*
|
||||
* @return bool true if the user is a valid one, false otherwise
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
@@ -223,10 +223,9 @@ class CASUserProvisioning
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public static function CreateUser($sName, $sPassword, $sLoginMode, $sAuthentication)
|
||||
public static function CreateUser()
|
||||
{
|
||||
$bOk = true;
|
||||
if ($sLoginMode != 'cas') return false; // Must be authenticated via CAS
|
||||
|
||||
$sCASMemberships = Config::Get('cas_memberof');
|
||||
$bFound = false;
|
||||
@@ -271,25 +270,15 @@ class CASUserProvisioning
|
||||
}
|
||||
if ($bIsMember)
|
||||
{
|
||||
$bCASUserSynchro = Config::Get('cas_user_synchro');
|
||||
if ($bCASUserSynchro)
|
||||
// If needed create a new user for this email/profile
|
||||
$bOk = self::CreateCASUser(phpCAS::getUser(), $aMemberOf);
|
||||
if($bOk)
|
||||
{
|
||||
// If needed create a new user for this email/profile
|
||||
phpCAS::log('Info: cas_user_synchro is ON');
|
||||
$bOk = self::CreateCASUser(phpCAS::getUser(), $aMemberOf);
|
||||
if($bOk)
|
||||
{
|
||||
$bFound = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
phpCAS::log("User ".phpCAS::getUser()." cannot be created in iTop. Logging off...");
|
||||
}
|
||||
$bFound = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
phpCAS::log('Info: cas_user_synchro is OFF');
|
||||
$bFound = true;
|
||||
phpCAS::log("User ".phpCAS::getUser()." cannot be created in iTop. Logging off...");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -315,11 +304,10 @@ class CASUserProvisioning
|
||||
if (!$bFound)
|
||||
{
|
||||
// The user is not part of the allowed groups, => log out
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php';
|
||||
$sCASLogoutUrl = Config::Get('cas_logout_redirect_service');
|
||||
if (empty($sCASLogoutUrl))
|
||||
{
|
||||
$sCASLogoutUrl = $sUrl;
|
||||
$sCASLogoutUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php';
|
||||
}
|
||||
phpCAS::logoutWithRedirectService($sCASLogoutUrl); // Redirects to the CAS logout page
|
||||
// Will never return !
|
||||
@@ -355,21 +343,17 @@ class CASUserProvisioning
|
||||
/**
|
||||
* Helper method to create a CAS based user
|
||||
*
|
||||
* @param string $sEmail
|
||||
* @param string $sLogin
|
||||
* @param array $aGroups
|
||||
*
|
||||
* @return bool true on success, false otherwise
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
protected static function CreateCASUser($sEmail, $aGroups)
|
||||
protected static function CreateCASUser($sLogin, $aGroups)
|
||||
{
|
||||
if (!MetaModel::IsValidClass('URP_Profiles'))
|
||||
{
|
||||
@@ -377,15 +361,22 @@ class CASUserProvisioning
|
||||
return false;
|
||||
}
|
||||
|
||||
$oUser = MetaModel::GetObjectByName('UserExternal', $sEmail, false);
|
||||
$oUser = MetaModel::GetObjectByName('UserExternal', $sLogin, false);
|
||||
if ($oUser == null)
|
||||
{
|
||||
// Create the user, link it to a contact
|
||||
phpCAS::log("Info: the user '$sEmail' does not exist. A new UserExternal will be created.");
|
||||
if (phpCAS::hasAttribute('mail'))
|
||||
{
|
||||
$sEmail = phpCAS::getAttribute('mail');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sEmail = $sLogin;
|
||||
}
|
||||
phpCAS::log("Info: the user '$sLogin' does not exist. A new UserExternal will be created.");
|
||||
$oSearch = new DBObjectSearch('Person');
|
||||
$oSearch->AddCondition('email', $sEmail);
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
$iContactId = 0;
|
||||
switch($oSet->Count())
|
||||
{
|
||||
case 0:
|
||||
@@ -404,41 +395,17 @@ class CASUserProvisioning
|
||||
}
|
||||
|
||||
$oUser = new UserExternal();
|
||||
$oUser->Set('login', $sEmail);
|
||||
$oUser->Set('login', $sLogin);
|
||||
$oUser->Set('contactid', $iContactId);
|
||||
$oUser->Set('language', MetaModel::GetConfig()->GetDefaultLanguage());
|
||||
}
|
||||
else
|
||||
{
|
||||
phpCAS::log("Info: the user '$sEmail' already exists (id=".$oUser->GetKey().").");
|
||||
phpCAS::log("Info: the user '$sLogin' already exists (id=".$oUser->GetKey().").");
|
||||
}
|
||||
|
||||
// Now synchronize the profiles
|
||||
if (!self::SetProfilesFromCAS($oUser, $aGroups))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($oUser->IsNew() || $oUser->IsModified())
|
||||
{
|
||||
/** @var \CMDBChange $oMyChange */
|
||||
$oMyChange = MetaModel::NewObject("CMDBChange");
|
||||
$oMyChange->Set("date", time());
|
||||
$oMyChange->Set("userinfo", 'CAS/LDAP Synchro');
|
||||
$oMyChange->DBInsert();
|
||||
if ($oUser->IsNew())
|
||||
{
|
||||
$oUser->DBInsertTracked($oMyChange);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oUser->DBUpdateTracked($oMyChange);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return self::SetProfilesFromCAS($oUser, $aGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -562,6 +562,9 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'UI:Button:Login' => 'Enter iTop',
|
||||
'UI:Login:Error:AccessRestricted' => 'iTop access is restricted. Please, contact an iTop administrator.',
|
||||
'UI:Login:Error:AccessAdmin' => 'Access restricted to people having administrator privileges. Please, contact an iTop administrator.',
|
||||
'UI:Login:Error:WrongOrganizationName' => 'Unknown organization',
|
||||
'UI:Login:Error:MultipleContactsHaveSameEmail' => 'Multiple contacts have the same e-mail',
|
||||
'UI:Login:Error:NoValidProfiles' => 'No valid profile provided',
|
||||
'UI:CSVImport:MappingSelectOne' => '-- select one --',
|
||||
'UI:CSVImport:MappingNotApplicable' => '-- ignore this field --',
|
||||
'UI:CSVImport:NoData' => 'Empty data set..., please provide some data!',
|
||||
|
||||
@@ -545,6 +545,9 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'UI:Button:Login' => 'Entrer dans iTop',
|
||||
'UI:Login:Error:AccessRestricted' => 'L\'accès à iTop est soumis à autorisation. Merci de contacter votre administrateur iTop.',
|
||||
'UI:Login:Error:AccessAdmin' => 'Accès resreint aux utilisateurs possédant le profil Administrateur.',
|
||||
'UI:Login:Error:WrongOrganizationName' => 'Organisation inconnue',
|
||||
'UI:Login:Error:MultipleContactsHaveSameEmail' => 'Email partagé par plusieurs contacts',
|
||||
'UI:Login:Error:NoValidProfiles' => 'Pas de profil valide',
|
||||
'UI:CSVImport:MappingSelectOne' => '-- choisir une valeur --',
|
||||
'UI:CSVImport:MappingNotApplicable' => '-- ignorer ce champ --',
|
||||
'UI:CSVImport:NoData' => 'Aucune donnée... merci de fournir des données !',
|
||||
|
||||
Reference in New Issue
Block a user