diff --git a/application/logindefault.class.inc.php b/application/logindefault.class.inc.php index 6df6d5b0f..9ef5d1329 100644 --- a/application/logindefault.class.inc.php +++ b/application/logindefault.class.inc.php @@ -37,6 +37,20 @@ class LoginDefaultBefore extends AbstractLoginFSMExtension return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE; } + protected function OnReadCredentials(&$iErrorCode) + { + // Check if proposed login mode is present and allowed + $aAllowedLoginTypes = MetaModel::GetConfig()->GetAllowedLoginTypes(); + $sProposedLoginMode = utils::ReadParam('login_mode', ''); + $index = array_search($sProposedLoginMode, $aAllowedLoginTypes); + if ($index !== false) + { + // Force login mode + $_SESSION['login_mode'] = $sProposedLoginMode; + } + return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE; + } + protected function OnError(&$iErrorCode) { $_SESSION['login_error_count'] = (isset($_SESSION['login_error_count']) ? $_SESSION['login_error_count'] : 0) + 1; diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index 1ee222aca..07739d837 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -129,24 +129,7 @@ class LoginWebPage extends NiceWebPage { switch($sLoginType) { - case 'cas': - utils::InitCASClient(); - // force CAS authentication - phpCAS::forceAuthentication(); // Will redirect the user and exit since the user is not yet authenticated - break; - - case 'basic': - case 'url': - $this->add_header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION)); - $this->add_header('HTTP/1.0 401 Unauthorized'); - $this->add_header('Content-type: text/html; charset=iso-8859-1'); - // Note: displayed when the user will click on Cancel - $this->add('

'.Dict::S('UI:Login:Error:AccessRestricted').'

'); - break; - - case 'external': case 'form': - default: // In case the settings get messed up... $sAuthUser = utils::ReadParam('auth_user', '', true, 'raw_data'); $sAuthPwd = utils::ReadParam('suggest_pwd', '', true, 'raw_data'); diff --git a/application/utils.inc.php b/application/utils.inc.php index 33d4a043c..322d2424e 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -77,7 +77,6 @@ class FileUploadException extends Exception class utils { private static $oConfig = null; - private static $m_bCASClient = false; // Parameters loaded from a file, parameters of the page/command line still have precedence private static $m_aParamsFromFile = null; @@ -904,45 +903,6 @@ class utils return $sSessionLog; } - /** - * Initializes the CAS client - */ - static function InitCASClient() - { - $sCASIncludePath = self::GetConfig()->Get('cas_include_path'); - include_once($sCASIncludePath.'/CAS.php'); - - $bCASDebug = self::GetConfig()->Get('cas_debug'); - if ($bCASDebug) - { - phpCAS::setDebug(APPROOT.'log/error.log'); - } - - if (!self::$m_bCASClient) - { - // Initialize phpCAS - $sCASVersion = self::GetConfig()->Get('cas_version'); - $sCASHost = self::GetConfig()->Get('cas_host'); - $iCASPort = self::GetConfig()->Get('cas_port'); - $sCASContext = self::GetConfig()->Get('cas_context'); - phpCAS::client($sCASVersion, $sCASHost, $iCASPort, $sCASContext, false /* session already started */); - self::$m_bCASClient = true; - $sCASCACertPath = self::GetConfig()->Get('cas_server_ca_cert_path'); - if (empty($sCASCACertPath)) - { - // If no certificate authority is provided, do not attempt to validate - // the server's certificate - // THIS SETTING IS NOT RECOMMENDED FOR PRODUCTION. - // VALIDATING THE CAS SERVER IS CRUCIAL TO THE SECURITY OF THE CAS PROTOCOL! - phpCAS::setNoCasServerValidation(); - } - else - { - phpCAS::setCasServerCACert($sCASCACertPath); - } - } - } - static function DebugBacktrace($iLimit = 5) { $aFullTrace = debug_backtrace(); diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index 5c746bbb1..1eba71157 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -1544,339 +1544,3 @@ class StimulusChecker extends ActionChecker return $this->iState; } } - -/** - * Self-register extension to allow the automatic creation & update of CAS users - * - * @package iTopORM - * - */ -class CAS_SelfRegister implements iSelfRegister -{ - /** - * 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 - */ - public static function CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication) - { - $bOk = true; - if ($sLoginMode != 'cas') return false; // Must be authenticated via CAS - - $sCASMemberships = MetaModel::GetConfig()->Get('cas_memberof'); - $bFound = false; - if (!empty($sCASMemberships)) - { - if (phpCAS::hasAttribute('memberOf')) - { - // A list of groups is specified, the user must a be member of (at least) one of them to pass - $aCASMemberships = array(); - $aTmp = explode(';', $sCASMemberships); - setlocale(LC_ALL, "en_US.utf8"); // !!! WARNING: this is needed to have the iconv //TRANSLIT working fine below !!! - foreach($aTmp as $sGroupName) - { - $aCASMemberships[] = trim(iconv('UTF-8', 'ASCII//TRANSLIT', $sGroupName)); // Just in case remove accents and spaces... - } - - $aMemberOf = phpCAS::getAttribute('memberOf'); - if (!is_array($aMemberOf)) $aMemberOf = array($aMemberOf); // Just one entry, turn it into an array - $aFilteredGroupNames = array(); - foreach($aMemberOf as $sGroupName) - { - phpCAS::log("Info: user if a member of the group: ".$sGroupName); - $sGroupName = trim(iconv('UTF-8', 'ASCII//TRANSLIT', $sGroupName)); // Remove accents and spaces as well - $aFilteredGroupNames[] = $sGroupName; - $bIsMember = false; - foreach($aCASMemberships as $sCASPattern) - { - if (self::IsPattern($sCASPattern)) - { - if (preg_match($sCASPattern, $sGroupName)) - { - $bIsMember = true; - break; - } - } - else if ($sCASPattern == $sGroupName) - { - $bIsMember = true; - break; - } - } - if ($bIsMember) - { - $bCASUserSynchro = MetaModel::GetConfig()->Get('cas_user_synchro'); - if ($bCASUserSynchro) - { - // 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..."); - } - } - else - { - phpCAS::log('Info: cas_user_synchro is OFF'); - $bFound = true; - } - break; - } - } - if($bOk && !$bFound) - { - phpCAS::log("User ".phpCAS::getUser().", none of his/her groups (".implode('; ', $aFilteredGroupNames).") match any of the required groups: ".implode('; ', $aCASMemberships)); - } - } - else - { - // Too bad, the user is not part of any of the group => not allowed - phpCAS::log("No 'memberOf' attribute found for user ".phpCAS::getUser().". Are you using the SAML protocol (S1) ?"); - } - } - else - { - // No membership: no way to create the user that should exist prior to authentication - phpCAS::log("User ".phpCAS::getUser().": missing user account in iTop (or iTop badly configured, Cf setting cas_memberof)"); - $bFound = false; - } - - if (!$bFound) - { - // The user is not part of the allowed groups, => log out - $sUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php'; - $sCASLogoutUrl = MetaModel::GetConfig()->Get('cas_logout_redirect_service'); - if (empty($sCASLogoutUrl)) - { - $sCASLogoutUrl = $sUrl; - } - phpCAS::logoutWithRedirectService($sCASLogoutUrl); // Redirects to the CAS logout page - // Will never return ! - } - return $bFound; - } - - /** - * Called after the user has been authenticated and found in iTop. This method can - * Update the user's definition (profiles...) on the fly 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) - { - $bCASUpdateProfiles = MetaModel::GetConfig()->Get('cas_update_profiles'); - if (($sLoginMode == 'cas') && $bCASUpdateProfiles && (phpCAS::hasAttribute('memberOf'))) - { - $aMemberOf = phpCAS::getAttribute('memberOf'); - if (!is_array($aMemberOf)) $aMemberOf = array($aMemberOf); // Just one entry, turn it into an array - - return self::SetProfilesFromCAS($oUser, $aMemberOf); - } - // No groups defined in CAS or not CAS at all: do nothing... - return true; - } - - /** - * Helper method to create a CAS based user - * @param string $sEmail - * @param array $aGroups - * @return bool true on success, false otherwise - */ - protected static function CreateCASUser($sEmail, $aGroups) - { - if (!MetaModel::IsValidClass('URP_Profiles')) - { - phpCAS::log("URP_Profiles is not a valid class. Automatic creation of Users is not supported in this context, sorry."); - return false; - } - - $oUser = MetaModel::GetObjectByName('UserExternal', $sEmail, 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."); - $oSearch = new DBObjectSearch('Person'); - $oSearch->AddCondition('email', $sEmail); - $oSet = new DBObjectSet($oSearch); - $iContactId = 0; - switch($oSet->Count()) - { - case 0: - phpCAS::log("Error: found no contact with the email: '$sEmail'. Cannot create the user in iTop."); - return false; - - case 1: - $oContact = $oSet->Fetch(); - $iContactId = $oContact->GetKey(); - phpCAS::log("Info: Found 1 contact '".$oContact->GetName()."' (id=$iContactId) corresponding to the email '$sEmail'."); - break; - - default: - phpCAS::log("Error: ".$oSet->Count()." contacts have the same email: '$sEmail'. Cannot create a user for this email."); - return false; - } - - $oUser = new UserExternal(); - $oUser->Set('login', $sEmail); - $oUser->Set('contactid', $iContactId); - $oUser->Set('language', MetaModel::GetConfig()->GetDefaultLanguage()); - } - else - { - phpCAS::log("Info: the user '$sEmail' already exists (id=".$oUser->GetKey().")."); - } - - // Now synchronize the profiles - if (!self::SetProfilesFromCAS($oUser, $aGroups)) - { - return false; - } - else - { - if ($oUser->IsNew() || $oUser->IsModified()) - { - $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; - } - } - - protected static function SetProfilesFromCAS($oUser, $aGroups) - { - if (!MetaModel::IsValidClass('URP_Profiles')) - { - phpCAS::log("URP_Profiles is not a valid class. Automatic creation of Users is not supported in this context, sorry."); - return false; - } - - // 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(); - } - - // Translate the CAS/LDAP group names into iTop profile names - $aProfiles = array(); - $sPattern = MetaModel::GetConfig()->Get('cas_profile_pattern'); - foreach($aGroups as $sGroupName) - { - if (preg_match($sPattern, $sGroupName, $aMatches)) - { - if (array_key_exists(strtolower($aMatches[1]), $aAllProfiles)) - { - $aProfiles[] = $aAllProfiles[strtolower($aMatches[1])]; - phpCAS::log("Info: Adding the profile '{$aMatches[1]}' from CAS."); - } - else - { - phpCAS::log("Warning: {$aMatches[1]} is not a valid iTop profile (extracted from group name: '$sGroupName'). Ignored."); - } - } - else - { - phpCAS::log("Info: The CAS group '$sGroupName' does not seem to match an iTop pattern. Ignored."); - } - } - if (count($aProfiles) == 0) - { - phpCAS::log("Info: The user '".$oUser->GetName()."' has no profiles retrieved from CAS. Default profile(s) will be used."); - - // Second attempt: check if there is/are valid default profile(s) - $sCASDefaultProfiles = MetaModel::GetConfig()->Get('cas_default_profiles'); - $aCASDefaultProfiles = explode(';', $sCASDefaultProfiles); - foreach($aCASDefaultProfiles as $sDefaultProfileName) - { - if (array_key_exists(strtolower($sDefaultProfileName), $aAllProfiles)) - { - $aProfiles[] = $aAllProfiles[strtolower($sDefaultProfileName)]; - phpCAS::log("Info: Adding the default profile '".$aAllProfiles[strtolower($sDefaultProfileName)]."' from CAS."); - } - else - { - phpCAS::log("Warning: the default profile {$sDefaultProfileName} is not a valid iTop profile. Ignored."); - } - } - - if (count($aProfiles) == 0) - { - phpCAS::log("Error: The user '".$oUser->GetName()."' has no profiles in iTop, and therefore cannot be created."); - return false; - } - } - - // Now synchronize the profiles - $oProfilesSet = DBObjectSet::FromScratch('URP_UserProfile'); - foreach($aProfiles as $iProfileId) - { - $oLink = new URP_UserProfile(); - $oLink->Set('profileid', $iProfileId); - $oLink->Set('reason', 'CAS/LDAP Synchro'); - $oProfilesSet->AddObject($oLink); - } - $oUser->Set('profile_list', $oProfilesSet); - phpCAS::log("Info: the user '".$oUser->GetName()."' (id=".$oUser->GetKey().") now has the following profiles: '".implode("', '", $aProfiles)."'."); - if ($oUser->IsModified()) - { - $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; - } - /** - * Helper function to check if the supplied string is a litteral string or a regular expression pattern - * @param string $sCASPattern - * @return bool True if it's a regular expression pattern, false otherwise - */ - protected static function IsPattern($sCASPattern) - { - if ((substr($sCASPattern, 0, 1) == '/') && (substr($sCASPattern, -1) == '/')) - { - // the string is enclosed by slashes, let's assume it's a pattern - return true; - } - else - { - return false; - } - } -} - -// By default enable the 'CAS_SelfRegister' defined above -UserRights::SelectSelfRegister('CAS_SelfRegister'); \ No newline at end of file