diff --git a/application/applicationextension.inc.php b/application/applicationextension.inc.php index 9b65f1474..db12a40aa 100644 --- a/application/applicationextension.inc.php +++ b/application/applicationextension.inc.php @@ -30,6 +30,30 @@ require_once(APPROOT.'application/newsroomprovider.class.inc.php'); * @api */ +interface iLoginFSMExtension +{ + /** + * Return the list of supported login modes for this plugin + * + * @return array of supported login modes + */ + public function ListSupportedLoginModes(); + + /** + * Execute action for this login state + * If a page is displayed, the action must exit at this point + * if LoginWebPage::LOGIN_FSM_RETURN_ERROR is returned $iErrorCode must be set + * if LoginWebPage::LOGIN_FSM_RETURN_OK is returned then the login is OK and terminated + * if LoginWebPage::LOGIN_FSM_RETURN_IGNORE is returned then the FSM will proceed to next plugin or state + * + * @param string $sLoginState (see LoginWebPage::LOGIN_STATE_...) + * @param int $iErrorCode (see LoginWebPage::EXIT_CODE_...) + * + * @return int LoginWebPage::LOGIN_FSM_RETURN_ERROR, LoginWebPage::LOGIN_FSM_RETURN_OK or LoginWebPage::LOGIN_FSM_RETURN_IGNORE + */ + public function LoginAction($sLoginState, &$iErrorCode); +} + /** * Implement this interface to change the behavior of the GUI for some objects. * diff --git a/application/loginbasic.class.inc.php b/application/loginbasic.class.inc.php new file mode 100644 index 000000000..0fe1cfac8 --- /dev/null +++ b/application/loginbasic.class.inc.php @@ -0,0 +1,129 @@ +GetAuthUserAndPassword(); + if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal')) + { + $iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS; + + return LoginWebPage::LOGIN_FSM_RETURN_ERROR; + } + } + break; + + case LoginWebPage::LOGIN_STATE_CREDENTIAL_OK: + if ($_SESSION['login_mode'] == 'basic') + { + list($sAuthUser) = $this->GetAuthUserAndPassword(); + LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']); + } + break; + + case LoginWebPage::LOGIN_STATE_ERROR: + if ($_SESSION['login_mode'] == 'basic') + { + LoginWebPage::HTTP401Error(); + } + break; + + case LoginWebPage::LOGIN_STATE_CONNECTED: + if ($_SESSION['login_mode'] == 'basic') + { + $_SESSION['can_logoff'] = true; + return LoginWebPage::CheckLoggedUser($iErrorCode); + } + break; + } + + return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE; + } + + /** + * Return the list of supported login modes for this plugin + * + * @return array of supported login modes + */ + public function ListSupportedLoginModes() + { + return array('basic'); + } + + private function GetAuthUserAndPassword() + { + $sAuthUser = ''; + $sAuthPwd = null; + if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION'])) + { + list($sAuthUser, $sAuthPwd) = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6))); + } + else + { + if (isset($_SERVER['PHP_AUTH_USER'])) + { + $sAuthUser = $_SERVER['PHP_AUTH_USER']; + // Unfortunately, the RFC is not clear about the encoding... + // IE and FF supply the user and password encoded in ISO-8859-1 whereas Chrome provides them encoded in UTF-8 + // So let's try to guess if it's an UTF-8 string or not... fortunately all encodings share the same ASCII base + if (!LoginWebPage::LooksLikeUTF8($sAuthUser)) + { + // Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8 + // Supposed to be harmless in case of a plain ASCII string... + $sAuthUser = iconv('iso-8859-1', 'utf-8', $sAuthUser); + } + $sAuthPwd = $_SERVER['PHP_AUTH_PW']; + if (!LoginWebPage::LooksLikeUTF8($sAuthPwd)) + { + // Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8 + // Supposed to be harmless in case of a plain ASCII string... + $sAuthPwd = iconv('iso-8859-1', 'utf-8', $sAuthPwd); + } + } + } + return array($sAuthUser, $sAuthPwd); + } +} \ No newline at end of file diff --git a/application/logindefault.class.inc.php b/application/logindefault.class.inc.php new file mode 100644 index 000000000..ebaf1a20e --- /dev/null +++ b/application/logindefault.class.inc.php @@ -0,0 +1,65 @@ +GetAllowedLoginTypes(); + $sProposedLoginMode = utils::ReadParam('login_mode', ''); + $index = array_search($sProposedLoginMode, $aAllowedLoginTypes); + if ($index !== false) + { + // Force login mode + $_SESSION['login_mode'] = $sProposedLoginMode; + } + else + { + unset($_SESSION['login_mode']); + } + break; + + case LoginWebPage::LOGIN_STATE_ERROR: + $_SESSION['login_error_count'] = (isset($_SESSION['login_error_count']) ? $_SESSION['login_error_count'] : 0) + 1; + break; + + case LoginWebPage::LOGIN_STATE_CONNECTED: + unset($_SESSION['login_error_count']); + break; + } + + return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE; + } +} \ No newline at end of file diff --git a/application/loginexternal.class.inc.php b/application/loginexternal.class.inc.php new file mode 100644 index 000000000..e918c3354 --- /dev/null +++ b/application/loginexternal.class.inc.php @@ -0,0 +1,98 @@ +GetAuthUser(); + if ($sAuthUser && (strlen($sAuthUser) > 0)) + { + $_SESSION['login_mode'] = 'external'; + } + } + break; + + case LoginWebPage::LOGIN_STATE_CHECK_CREDENTIALS: + if ($_SESSION['login_mode'] == 'external') + { + $sAuthUser = $this->GetAuthUser(); + if (!UserRights::CheckCredentials($sAuthUser, '', $_SESSION['login_mode'], 'external')) + { + $iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS; + + return LoginWebPage::LOGIN_FSM_RETURN_ERROR; + } + } + break; + + case LoginWebPage::LOGIN_STATE_CREDENTIAL_OK: + if ($_SESSION['login_mode'] == 'external') + { + $sAuthUser = $this->GetAuthUser(); + LoginWebPage::OnLoginSuccess($sAuthUser, 'external', $_SESSION['login_mode']); + } + break; + + case LoginWebPage::LOGIN_STATE_CONNECTED: + if ($_SESSION['login_mode'] == 'external') + { + $_SESSION['can_logoff'] = false; + return LoginWebPage::CheckLoggedUser($iErrorCode); + } + break; + } + + return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE; + } + + /** + * @return bool + */ + private function GetAuthUser() + { + $sExtAuthVar = MetaModel::GetConfig()->GetExternalAuthenticationVariable(); // In which variable is the info passed ? + eval('$sAuthUser = isset('.$sExtAuthVar.') ? '.$sExtAuthVar.' : false;'); // Retrieve the value + /** @var string $sAuthUser */ + return $sAuthUser; // Retrieve the value + } +} \ No newline at end of file diff --git a/application/loginform.class.inc.php b/application/loginform.class.inc.php new file mode 100644 index 000000000..819bd5b17 --- /dev/null +++ b/application/loginform.class.inc.php @@ -0,0 +1,120 @@ +bForceFormOnError || empty($sAuthUser) || empty($sAuthPwd)) + { + if (array_key_exists('HTTP_X_COMBODO_AJAX', $_SERVER)) + { + // X-Combodo-Ajax is a special header automatically added to all ajax requests + // Let's reply that we're currently logged-out + header('HTTP/1.0 401 Unauthorized'); + exit; + } + + // No credentials yet, display the form + $oPage = LoginWebPage::NewLoginWebPage(); + $oPage->DisplayLoginForm('form', $this->bForceFormOnError); + $oPage->output(); + $this->bForceFormOnError = false; + exit; + } + } + break; + + case LoginWebPage::LOGIN_STATE_CHECK_CREDENTIALS: + 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')) + { + $iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS; + + return LoginWebPage::LOGIN_FSM_RETURN_ERROR; + } + } + break; + + case LoginWebPage::LOGIN_STATE_CREDENTIAL_OK: + if ($_SESSION['login_mode'] == 'form') + { + $sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data'); + LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']); + } + break; + + case LoginWebPage::LOGIN_STATE_ERROR: + if ($_SESSION['login_mode'] == 'form') + { + $this->bForceFormOnError = true; + } + break; + + case LoginWebPage::LOGIN_STATE_CONNECTED: + if ($_SESSION['login_mode'] == 'form') + { + $_SESSION['can_logoff'] = true; + return LoginWebPage::CheckLoggedUser($iErrorCode); + } + break; + } + + return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE; + } + + /** + * Return the list of supported login modes for this plugin + * + * @return array of supported login modes + */ + public function ListSupportedLoginModes() + { + return array('form'); + } +} diff --git a/application/loginurl.class.inc.php b/application/loginurl.class.inc.php new file mode 100644 index 000000000..040b75360 --- /dev/null +++ b/application/loginurl.class.inc.php @@ -0,0 +1,100 @@ +bErrorOccurred) + { + $sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data'); + $sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data'); + if (!empty($sAuthUser) && !empty($sAuthPwd)) + { + $_SESSION['login_mode'] = 'url'; + } + } + break; + + case LoginWebPage::LOGIN_STATE_CHECK_CREDENTIALS: + if ($_SESSION['login_mode'] == 'url') + { + $sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data'); + $sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data'); + if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal')) + { + $iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS; + + return LoginWebPage::LOGIN_FSM_RETURN_ERROR; + } + } + break; + + case LoginWebPage::LOGIN_STATE_CREDENTIAL_OK: + if ($_SESSION['login_mode'] == 'url') + { + $sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data'); + LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']); + } + break; + + case LoginWebPage::LOGIN_STATE_ERROR: + if ($_SESSION['login_mode'] == 'url') + { + $this->bErrorOccurred = true; + } + break; + + case LoginWebPage::LOGIN_STATE_CONNECTED: + if ($_SESSION['login_mode'] == 'url') + { + $_SESSION['can_logoff'] = true; + return LoginWebPage::CheckLoggedUser($iErrorCode); + } + break; + } + + return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE; + } +} \ No newline at end of file diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index 475cc4a2d..bb22a1c0d 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -43,7 +43,23 @@ class LoginWebPage extends NiceWebPage const EXIT_CODE_MUSTBEADMIN = 4; const EXIT_CODE_PORTALUSERNOTAUTHORIZED = 5; const EXIT_CODE_NOTAUTHORIZED = 6; - + + // Login FSM States + const LOGIN_STATE_START = 'start'; // Entry state + const LOGIN_STATE_MODE_DETECTION = 'login mode detection'; // Detect which login plugin to use + const LOGIN_STATE_READ_CREDENTIALS = 'read credentials'; // Read the credentials + const LOGIN_STATE_CHECK_CREDENTIALS = 'check credentials'; // Check if the credentials are valid + const LOGIN_STATE_CREDENTIAL_OK = 'credentials ok'; // User provisioning + const LOGIN_STATE_USER_OK = 'user ok'; // Additional check (2FA) + const LOGIN_STATE_CONNECTED = 'connected'; // User connected + const LOGIN_STATE_SET_ERROR = 'prepare for error'; // Internal state to trigger ERROR state + const LOGIN_STATE_ERROR = 'error'; // An error occurred, next state will be NONE + + // Login FSM Returns + const LOGIN_FSM_RETURN_OK = 0; // End the FSM OK (connected) + const LOGIN_FSM_RETURN_ERROR = 1; // Error signaled + const LOGIN_FSM_RETURN_CONTINUE = 2; // Continue FSM + protected static $sHandlerClass = __class__; public static function RegisterHandler($sClass) { @@ -441,7 +457,8 @@ EOF { // Unset all of the session variables. unset($_SESSION['auth_user']); - unset($_SESSION['login_mode']); + unset($_SESSION['login_state']); + unset($_SESSION['can_logoff']); unset($_SESSION['archive_mode']); unset($_SESSION['impersonate_user']); UserRights::_ResetSessionCache(); @@ -471,215 +488,228 @@ EOF |\xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 )+%xs', $sString); } - /** * Attempt a login - * + * * @param int iOnExit What action to take if the user is not logged on (one of the class constants EXIT_...) - * @return int One of the class constants EXIT_CODE_... - */ + * + * @return int|void One of the class constants EXIT_CODE_... + * @throws \Exception + */ protected static function Login($iOnExit) { if (self::SecureConnectionRequired() && !utils::IsConnectionSecure()) { // Non secured URL... request for a secure connection - throw new Exception('Secure connection required!'); + throw new Exception('Secure connection required!'); } - $aAllowedLoginTypes = MetaModel::GetConfig()->GetAllowedLoginTypes(); + if (!isset($_SESSION['login_state']) || ($_SESSION['login_state'] == self::LOGIN_STATE_ERROR)) + { + $_SESSION['login_state'] = self::LOGIN_STATE_START; + } + $sLoginState = $_SESSION['login_state']; + // Finite state machine loop + while (true) + { + try + { + $aLoginPlugins = self::GetLoginPluginList(); + if (empty($aLoginPlugins)) + { + throw new Exception("Missing login classes"); + } + + /** @var iLoginFSMExtension $oLoginFSMExtensionInstance */ + foreach ($aLoginPlugins as $oLoginFSMExtensionInstance) + { + $iErrorCode = self::EXIT_CODE_OK; + $iResponse = $oLoginFSMExtensionInstance->LoginAction($sLoginState, $iErrorCode); + if ($iResponse == self::LOGIN_FSM_RETURN_OK) + { + return self::EXIT_CODE_OK; // login OK, exit FSM + } + if ($iResponse == self::LOGIN_FSM_RETURN_ERROR) + { + static::ResetSession(); + if ($iOnExit == self::EXIT_RETURN) + { + return $iErrorCode; // Error, exit FSM + } + elseif ($iOnExit == self::EXIT_HTTP_401) + { + self::HTTP401Error(); // Error, exit + } + $sLoginState = self::LOGIN_STATE_SET_ERROR; // Next state will be error + break; + } + // The plugin has nothing to do for this state, continue to the next plugin + } + + // Every plugin has nothing else to do in this state, go forward + $sLoginState = self::AdvanceLoginFSMState($sLoginState); + $_SESSION['login_state'] = $sLoginState; + } + catch (Exception $e) + { + IssueLog::Error($e->getTraceAsString()); + static::ResetSession(); + die($e->getMessage()); + } + } + } + + /** + * Get plugins list ordered by config 'allowed_login_types' + * Use the login mode to filter plugins + * + * @return array of plugins + */ + private static function GetLoginPluginList() + { + $aAllPlugins = array(); + + // Keep only the plugins for the current login mode + $sCurrentLoginMode = isset($_SESSION['login_mode']) ? $_SESSION['login_mode'] : ''; + + /** @var iLoginFSMExtension $oLoginFSMExtensionInstance */ + foreach (MetaModel::EnumPlugins('iLoginFSMExtension') as $oLoginFSMExtensionInstance) + { + $aLoginModes = $oLoginFSMExtensionInstance->ListSupportedLoginModes(); + foreach ($aLoginModes as $sLoginMode) + { + if (empty($sCurrentLoginMode) || ($sLoginMode == 'default') || ($sLoginMode == $sCurrentLoginMode)) + { + if (!isset($aAllPlugins[$sLoginMode])) + { + $aAllPlugins[$sLoginMode] = array(); + } + $aAllPlugins[$sLoginMode][] = $oLoginFSMExtensionInstance; + } + } + } + + // Order by the config list of allowed types + $aAllowedLoginModes = array_merge(array('default'), MetaModel::GetConfig()->GetAllowedLoginTypes()); + $aPlugins = array(); + foreach ($aAllowedLoginModes as $sAllowedMode) + { + if (isset($aAllPlugins[$sAllowedMode])) + { + $aPlugins = array_merge($aPlugins, $aAllPlugins[$sAllowedMode]); + } + } + return $aPlugins; + } + + /** + * Advance Login Finite State Machine to the next step + * + * @param string $sLoginState Current step + * + * @return string next step + */ + private static function AdvanceLoginFSMState($sLoginState) + { + switch ($sLoginState) + { + case self::LOGIN_STATE_START: + return self::LOGIN_STATE_MODE_DETECTION; + + case self::LOGIN_STATE_MODE_DETECTION: + return self::LOGIN_STATE_READ_CREDENTIALS; + + case self::LOGIN_STATE_READ_CREDENTIALS: + return self::LOGIN_STATE_CHECK_CREDENTIALS; + + case self::LOGIN_STATE_CHECK_CREDENTIALS: + return self::LOGIN_STATE_CREDENTIAL_OK; + + case self::LOGIN_STATE_CREDENTIAL_OK: + return self::LOGIN_STATE_USER_OK; + + case self::LOGIN_STATE_USER_OK: + return self::LOGIN_STATE_CONNECTED; + + case self::LOGIN_STATE_CONNECTED: + case self::LOGIN_STATE_ERROR: + return self::LOGIN_STATE_START; + + case self::LOGIN_STATE_SET_ERROR: + return self::LOGIN_STATE_ERROR; + } + + // Default reset to NONE + return self::LOGIN_STATE_START; + } + + /** + * Store User info in the session when connection is OK + * + * @param $sAuthUser + * @param $sAuthentication + * @param $sLoginMode + * + * @throws ArchivedObjectException + * @throws CoreCannotSaveObjectException + * @throws CoreException + * @throws CoreUnexpectedValue + * @throws CoreWarning + * @throws MySQLException + * @throws OQLException + */ + public static function OnLoginSuccess($sAuthUser, $sAuthentication, $sLoginMode) + { + // User is Ok, let's save it in the session and proceed with normal login + $bLoginSuccess = UserRights::Login($sAuthUser, $sAuthentication); // Login & set the user's language + if (!$bLoginSuccess) + { + throw new Exception("Bad user"); + } + if (MetaModel::GetConfig()->Get('log_usage')) { + $oLog = new EventLoginUsage(); + $oLog->Set('userinfo', UserRights::GetUser()); + $oLog->Set('user_id', UserRights::GetUserObject()->GetKey()); + $oLog->Set('message', 'Successful login'); + $oLog->DBInsertNoReload(); + } + + $_SESSION['auth_user'] = $sAuthUser; + $_SESSION['login_mode'] = $sLoginMode; + UserRights::_InitSessionCache(); + } + + public static function CheckLoggedUser(&$iErrorCode) + { if (isset($_SESSION['auth_user'])) { - //echo "User: ".$_SESSION['auth_user']."\n"; - // Already authentified + // Already authenticated $bRet = UserRights::Login($_SESSION['auth_user']); // Login & set the user's language if ($bRet) { - return self::EXIT_CODE_OK; + return self::LOGIN_FSM_RETURN_OK; } - // The user account is no longer valid/enabled - static::ResetSession(); } - - $index = 0; - $sLoginMode = ''; - $sAuthentication = 'internal'; - while(($sLoginMode == '') && ($index < count($aAllowedLoginTypes))) - { - $sLoginType = $aAllowedLoginTypes[$index]; - switch($sLoginType) - { - case 'cas': - utils::InitCASClient(); - // check CAS authentication - if (phpCAS::isAuthenticated()) - { - $sAuthUser = phpCAS::getUser(); - $sAuthPwd = ''; - $sLoginMode = 'cas'; - $sAuthentication = 'external'; - } - break; - - case 'form': - // iTop standard mode: form based authentication - $sAuthUser = utils::ReadPostedParam('auth_user', '', false, 'raw_data'); - $sAuthPwd = utils::ReadPostedParam('auth_pwd', null, false, 'raw_data'); - if (($sAuthUser != '') && ($sAuthPwd !== null)) - { - $sLoginMode = 'form'; - } - break; - - case 'basic': - // Standard PHP authentication method, works with Apache... - // Case 1) Apache running in CGI mode + rewrite rules in .htaccess - if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION'])) - { - list($sAuthUser, $sAuthPwd) = explode(':' , base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6))); - $sLoginMode = 'basic'; - } - else if (isset($_SERVER['PHP_AUTH_USER'])) - { - $sAuthUser = $_SERVER['PHP_AUTH_USER']; - // Unfortunately, the RFC is not clear about the encoding... - // IE and FF supply the user and password encoded in ISO-8859-1 whereas Chrome provides them encoded in UTF-8 - // So let's try to guess if it's an UTF-8 string or not... fortunately all encodings share the same ASCII base - if (!self::LooksLikeUTF8($sAuthUser)) - { - // Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8 - // Supposed to be harmless in case of a plain ASCII string... - $sAuthUser = iconv('iso-8859-1', 'utf-8', $sAuthUser); - } - $sAuthPwd = $_SERVER['PHP_AUTH_PW']; - if (!self::LooksLikeUTF8($sAuthPwd)) - { - // Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8 - // Supposed to be harmless in case of a plain ASCII string... - $sAuthPwd = iconv('iso-8859-1', 'utf-8', $sAuthPwd); - } - $sLoginMode = 'basic'; - } - break; + // The user account is no longer valid/enabled + $iErrorCode = self::EXIT_CODE_WRONGCREDENTIALS; - case 'external': - // Web server supplied authentication - $bExternalAuth = false; - $sExtAuthVar = MetaModel::GetConfig()->GetExternalAuthenticationVariable(); // In which variable is the info passed ? - eval('$sAuthUser = isset('.$sExtAuthVar.') ? '.$sExtAuthVar.' : false;'); // Retrieve the value - if ($sAuthUser && (strlen($sAuthUser) > 0)) - { - $sAuthPwd = ''; // No password in this case the web server already authentified the user... - $sLoginMode = 'external'; - $sAuthentication = 'external'; - } - break; - - case 'url': - // Credentials passed directly in the url - $sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data'); - $sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data'); - if (($sAuthUser != '') && ($sAuthPwd !== null)) - { - $sLoginMode = 'url'; - } - break; - } - $index++; - } - //echo "\nsLoginMode: $sLoginMode (user: $sAuthUser / pwd: $sAuthPwd\n)"; - if ($sLoginMode == '') - { - // First connection - $sDesiredLoginMode = utils::ReadParam('login_mode'); - if (in_array($sDesiredLoginMode, $aAllowedLoginTypes)) - { - $sLoginMode = $sDesiredLoginMode; - } - else - { - $sLoginMode = $aAllowedLoginTypes[0]; // First in the list... - } - if (array_key_exists('HTTP_X_COMBODO_AJAX', $_SERVER)) - { - // X-Combodo-Ajax is a special header automatically added to all ajax requests - // Let's reply that we're currently logged-out - header('HTTP/1.0 401 Unauthorized'); - exit; - } - if (($iOnExit == self::EXIT_HTTP_401) || ($sLoginMode == 'basic')) - { - header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION)); - header('HTTP/1.0 401 Unauthorized'); - header('Content-type: text/html; charset=iso-8859-1'); - exit; - } - else if($iOnExit == self::EXIT_RETURN) - { - if (($sAuthUser !== '') && ($sAuthPwd === null)) - { - return self::EXIT_CODE_MISSINGPASSWORD; - } - else - { - return self::EXIT_CODE_MISSINGLOGIN; - } - } - else - { - $oPage = self::NewLoginWebPage(); - $oPage->DisplayLoginForm( $sLoginMode, false /* no previous failed attempt */); - $oPage->output(); - exit; - } - } - else - { - if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $sLoginMode, $sAuthentication)) - { - //echo "Check Credentials returned false for user $sAuthUser!"; - self::ResetSession(); - if (($iOnExit == self::EXIT_HTTP_401) || ($sLoginMode == 'basic')) - { - header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION)); - header('HTTP/1.0 401 Unauthorized'); - header('Content-type: text/html; charset=iso-8859-1'); - exit; - } - else if($iOnExit == self::EXIT_RETURN) - { - return self::EXIT_CODE_WRONGCREDENTIALS; - } - else - { - $oPage = self::NewLoginWebPage(); - $oPage->DisplayLoginForm( $sLoginMode, true /* failed attempt */); - $oPage->output(); - exit; - } - } - else - { - // User is Ok, let's save it in the session and proceed with normal login - UserRights::Login($sAuthUser, $sAuthentication); // Login & set the user's language - - if (MetaModel::GetConfig()->Get('log_usage')) - { - $oLog = new EventLoginUsage(); - $oLog->Set('userinfo', UserRights::GetUser()); - $oLog->Set('user_id', UserRights::GetUserObject()->GetKey()); - $oLog->Set('message', 'Successful login'); - $oLog->DBInsertNoReload(); - } - - $_SESSION['auth_user'] = $sAuthUser; - $_SESSION['login_mode'] = $sLoginMode; - UserRights::_InitSessionCache(); - } - } - return self::EXIT_CODE_OK; + return self::LOGIN_FSM_RETURN_ERROR; } - + + /** + * Exit with an HTTP 401 error + */ + public static function HTTP401Error() + { + header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION)); + header('HTTP/1.0 401 Unauthorized'); + header('Content-type: text/html; charset=iso-8859-1'); + // Note: displayed when the user will click on Cancel + echo '

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

'; + exit; + } + /** * Overridable: depending on the user, head toward a dedicated portal * @param string|null $sRequestedPortalId @@ -687,7 +717,6 @@ EOF */ protected static function ChangeLocation($sRequestedPortalId = null, $iOnExit = self::EXIT_PROMPT) { - $fStart = microtime(true); $ret = call_user_func(array(self::$sHandlerClass, 'Dispatch'), $sRequestedPortalId); if ($ret === true) { @@ -709,6 +738,7 @@ EOF header('Location: '.$ret); } } + return self::EXIT_CODE_OK; } /** diff --git a/application/utils.inc.php b/application/utils.inc.php index a19b1c072..1b39d5aa0 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -50,6 +50,14 @@ require_once(APPROOT.'application/transaction.class.inc.php'); require_once(APPROOT.'application/Html2Text.php'); require_once(APPROOT.'application/Html2TextException.php'); +require_once(APPROOT.'application/applicationextension.inc.php'); +require_once(APPROOT.'application/loginform.class.inc.php'); +require_once(APPROOT.'application/loginbasic.class.inc.php'); +require_once(APPROOT.'application/logindefault.class.inc.php'); +require_once(APPROOT.'application/loginexternal.class.inc.php'); +require_once(APPROOT.'application/loginurl.class.inc.php'); + + define('ITOP_CONFIG_FILE', 'config-itop.php'); define('ITOP_DEFAULT_CONFIG_FILE', APPCONF.ITOP_DEFAULT_ENV.'/'.ITOP_CONFIG_FILE); @@ -879,26 +887,7 @@ class utils */ static function CanLogOff() { - $bResult = false; - if(isset($_SESSION['login_mode'])) - { - $sLoginMode = $_SESSION['login_mode']; - switch($sLoginMode) - { - case 'external': - $bResult = false; - break; - - case 'form': - case 'basic': - case 'url': - case 'cas': - default: - $bResult = true; - - } - } - return $bResult; + return (isset($_SESSION['can_logoff']) ? $_SESSION['can_logoff'] : false); } /** diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 977741c2a..3e3e75e53 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -66,7 +66,7 @@ define('DEFAULT_MAX_DISPLAY_LIMIT', 15); define('DEFAULT_STANDARD_RELOAD_INTERVAL', 5 * 60); define('DEFAULT_FAST_RELOAD_INTERVAL', 1 * 60); define('DEFAULT_SECURE_CONNECTION_REQUIRED', false); -define('DEFAULT_ALLOWED_LOGIN_TYPES', 'form|basic|external'); +define('DEFAULT_ALLOWED_LOGIN_TYPES', 'form|external|basic'); define('DEFAULT_EXT_AUTH_VARIABLE', '$_SERVER[\'REMOTE_USER\']'); define('DEFAULT_ENCRYPTION_KEY', '@iT0pEncr1pti0n!'); // We'll use a random generated key later (if possible) define('DEFAULT_ENCRYPTION_LIB', 'Mcrypt'); // We'll define the best encryption available later diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 6a2feca1c..07d17252b 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -2780,7 +2780,7 @@ abstract class MetaModel // Build the list of available extensions // - $aInterfaces = array('iApplicationUIExtension', 'iApplicationObjectExtension', 'iQueryModifier', 'iOnClassInitialization', 'iPopupMenuExtension', 'iPageUIExtension', 'iPortalUIExtension', 'ModuleHandlerApiInterface', 'iNewsroomProvider'); + $aInterfaces = array('iApplicationUIExtension', 'iApplicationObjectExtension', 'iLoginFSMExtension', 'iQueryModifier', 'iOnClassInitialization', 'iPopupMenuExtension', 'iPageUIExtension', 'iPortalUIExtension', 'ModuleHandlerApiInterface', 'iNewsroomProvider'); foreach($aInterfaces as $sInterface) { self::$m_aExtensionClasses[$sInterface] = array();