/** * Class LoginWebPage * * @copyright Copyright (C) 2010-2017 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 */ require_once(APPROOT."/application/nicewebpage.class.inc.php"); require_once(APPROOT.'/application/portaldispatcher.class.inc.php'); /** * Web page used for displaying the login form */ class LoginWebPage extends NiceWebPage { const EXIT_PROMPT = 0; const EXIT_HTTP_401 = 1; const EXIT_RETURN = 2; const EXIT_CODE_OK = 0; const EXIT_CODE_MISSINGLOGIN = 1; const EXIT_CODE_MISSINGPASSWORD = 2; const EXIT_CODE_WRONGCREDENTIALS = 3; const EXIT_CODE_MUSTBEADMIN = 4; const EXIT_CODE_PORTALUSERNOTAUTHORIZED = 5; protected static $sHandlerClass = __class__; public static function RegisterHandler($sClass) { self::$sHandlerClass = $sClass; } public static function NewLoginWebPage() { return new self::$sHandlerClass; } protected static $m_sLoginFailedMessage = ''; public function __construct($sTitle = 'iTop Login') { parent::__construct($sTitle); $this->SetStyleSheet(); $this->add_header("Cache-control: no-cache"); } public function SetStyleSheet() { $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/login.css'); } public static function SetLoginFailedMessage($sMessage) { self::$m_sLoginFailedMessage = $sMessage; } public function EnableResetPassword() { return MetaModel::GetConfig()->Get('forgot_password'); } public function DisplayLoginHeader($bMainAppLogo = false) { if ($bMainAppLogo) { $sLogo = 'itop-logo.png'; $sBrandingLogo = 'main-logo.png'; } else { $sLogo = 'itop-logo-external.png'; $sBrandingLogo = 'login-logo.png'; } $sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION); $sIconUrl = Utils::GetConfig()->Get('app_icon_url'); $sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/'.$sLogo.'?itopversion='.ITOP_VERSION; if (file_exists(MODULESROOT.'branding/'.$sBrandingLogo)) { $sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/'.$sBrandingLogo.'?itopversion='.ITOP_VERSION; } $this->add("
\n"); } public function DisplayLoginForm($sLoginType, $bFailedLogin = false) { 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_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'); $this->DisplayLoginHeader(); $this->add("
\n"); $this->add("

".Dict::S('UI:Login:Welcome')."

\n"); if ($bFailedLogin) { if (self::$m_sLoginFailedMessage != '') { $this->add("

".self::$m_sLoginFailedMessage."

\n"); } else { $this->add("

".Dict::S('UI:Login:IncorrectLoginPassword')."

\n"); } } else { $this->add("

".Dict::S('UI:Login:IdentifyYourself')."

\n"); } $this->add("
\n"); $this->add("\n"); $sForgotPwd = $this->EnableResetPassword() ? $this->ForgotPwdLink() : ''; $this->add("\n"); $this->add("\n"); $this->add("\n"); if (strlen($sForgotPwd) > 0) { $this->add("\n"); } $this->add("
$sForgotPwd
\n"); $this->add("\n"); $this->add_ready_script('$("#user").focus();'); // Keep the OTHER parameters posted foreach($_POST as $sPostedKey => $postedValue) { if (!in_array($sPostedKey, array('auth_user', 'auth_pwd'))) { if (is_array($postedValue)) { foreach($postedValue as $sKey => $sValue) { $this->add("\n"); } } else { $this->add("\n"); } } } $this->add("
\n"); $this->add(Dict::S('UI:Login:About')); $this->add("
\n"); break; } } /** * Return '' to disable this feature */ public function ForgotPwdLink() { $sUrl = '?loginop=forgot_pwd'; $sHtml = "".Dict::S('UI:Login:ForgotPwd').""; return $sHtml; } public function DisplayForgotPwdForm($bFailedToReset = false, $sFailureReason = null) { $this->DisplayLoginHeader(); $this->add("
\n"); $this->add("

".Dict::S('UI:Login:ForgotPwdForm')."

\n"); $this->add("

".Dict::S('UI:Login:ForgotPwdForm+')."

\n"); if ($bFailedToReset) { $this->add("

".Dict::Format('UI:Login:ResetPwdFailed', htmlentities($sFailureReason, ENT_QUOTES, 'UTF-8'))."

\n"); } $sAuthUser = utils::ReadParam('auth_user', '', true, 'raw_data'); $this->add("
\n"); $this->add("\n"); $this->add("\n"); $this->add("\n"); $this->add("
  
\n"); $this->add("\n"); $this->add("
\n"); $this->add("
\n"); $this->add_ready_script('$("#user").focus();'); } protected function ForgotPwdGo() { $sAuthUser = utils::ReadParam('auth_user', '', true, 'raw_data'); try { UserRights::Login($sAuthUser); // Set the user's language (if possible!) $oUser = UserRights::GetUserObject(); if ($oUser == null) { throw new Exception(Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser)); } if (!MetaModel::IsValidAttCode(get_class($oUser), 'reset_pwd_token')) { throw new Exception(Dict::S('UI:ResetPwd-Error-NotPossible')); } if (!$oUser->CanChangePassword()) { throw new Exception(Dict::S('UI:ResetPwd-Error-FixedPwd')); } $sTo = $oUser->GetResetPasswordEmail(); // throws Exceptions if not allowed if ($sTo == '') { throw new Exception(Dict::S('UI:ResetPwd-Error-NoEmail')); } // This token allows the user to change the password without knowing the previous one $sToken = substr(md5(APPROOT.uniqid()), 0, 16); $oUser->Set('reset_pwd_token', $sToken); CMDBObject::SetTrackInfo('Reset password'); $oUser->DBUpdate(); $oEmail = new Email(); $oEmail->SetRecipientTO($sTo); $sFrom = MetaModel::GetConfig()->Get('forgot_password_from'); if ($sFrom == '') { $sFrom = $sTo; } $oEmail->SetRecipientFrom($sFrom); $oEmail->SetSubject(Dict::S('UI:ResetPwd-EmailSubject')); $sResetUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?loginop=reset_pwd&auth_user='.urlencode($oUser->Get('login')).'&token='.urlencode($sToken); $oEmail->SetBody(Dict::Format('UI:ResetPwd-EmailBody', $sResetUrl)); $iRes = $oEmail->Send($aIssues, true /* force synchronous exec */); switch ($iRes) { //case EMAIL_SEND_PENDING: case EMAIL_SEND_OK: break; case EMAIL_SEND_ERROR: default: IssueLog::Error('Failed to send the email with the NEW password for '.$oUser->Get('friendlyname').': '.implode(', ', $aIssues)); throw new Exception(Dict::S('UI:ResetPwd-Error-Send')); } $this->DisplayLoginHeader(); $this->add("
\n"); $this->add("

".Dict::S('UI:Login:ForgotPwdForm')."

\n"); $this->add("

".Dict::S('UI:ResetPwd-EmailSent')."

"); $this->add("
\n"); $this->add("\n"); $this->add("\n"); $this->add("
\n"); $this->add("
\n"); $this->add("DisplayForgotPwdForm(true, $e->getMessage()); } } public function DisplayResetPwdForm() { $sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data'); $sToken = utils::ReadParam('token', '', false, 'raw_data'); UserRights::Login($sAuthUser); // Set the user's language $oUser = UserRights::GetUserObject(); $this->DisplayLoginHeader(); $this->add("
\n"); $this->add("

".Dict::S('UI:ResetPwd-Title')."

\n"); if ($oUser == null) { $this->add("

".Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser)."

\n"); } else { $oEncryptedToken = $oUser->Get('reset_pwd_token'); if (!$oEncryptedToken->CheckPassword($sToken)) { $this->add("

".Dict::S('UI:ResetPwd-Error-InvalidToken')."

\n"); } else { $this->add("

".Dict::Format('UI:ResetPwd-Error-EnterPassword', $oUser->GetFriendlyName())."

\n"); $sInconsistenPwdMsg = Dict::S('UI:Login:RetypePwdDoesNotMatch'); $this->add_script( <<add("
\n"); $this->add("\n"); $this->add("\n"); $this->add("\n"); $this->add("\n"); $this->add("
\n"); $this->add("\n"); $this->add("\n"); $this->add("\n"); $this->add("
\n"); $this->add("DisplayLoginHeader(); $this->add("
\n"); $this->add("

".Dict::S('UI:ResetPwd-Title')."

\n"); if ($oUser == null) { $this->add("

".Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser)."

\n"); } else { $oEncryptedPassword = $oUser->Get('reset_pwd_token'); if (!$oEncryptedPassword->CheckPassword($sToken)) { $this->add("

".Dict::S('UI:ResetPwd-Error-InvalidToken')."

\n"); } else { // Trash the token and change the password $oUser->Set('reset_pwd_token', ''); $oUser->SetPassword($sNewPwd); // Does record the change into the DB $this->add("

".Dict::S('UI:ResetPwd-Ready')."

"); $sUrl = utils::GetAbsoluteUrlAppRoot(); $this->add("

".Dict::S('UI:ResetPwd-Login')."

"); } $this->add("add_script(<<DisplayLoginHeader(); $this->add("
\n"); $this->add("

".Dict::S('UI:Login:ChangeYourPassword')."

\n"); if ($bFailedLogin) { $this->add("

".Dict::S('UI:Login:IncorrectOldPassword')."

\n"); } $this->add("
\n"); $this->add("\n"); $this->add("\n"); $this->add("\n"); $this->add("\n"); $this->add("\n"); $this->add("
  
\n"); $this->add("\n"); $this->add("
\n"); $this->add("
\n"); } static function ResetSession() { // Unset all of the session variables. unset($_SESSION['auth_user']); unset($_SESSION['login_mode']); unset($_SESSION['archive_mode']); unset($_SESSION['archive_allowed']); UserRights::_ResetSessionCache(); // If it's desired to kill the session, also delete the session cookie. // Note: This will destroy the session, and not just the session data! } static function SecureConnectionRequired() { return MetaModel::GetConfig()->GetSecureConnectionRequired(); } /** * Guess if a string looks like an UTF-8 string based on some ranges of multi-bytes encoding * @param string $sString * @return bool True if the string contains some typical UTF-8 multi-byte sequences */ static function LooksLikeUTF8($sString) { return preg_match('%(?: [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte |\xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs |[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte |\xED[\x80-\x9F][\x80-\xBF] # excluding surrogates |\xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 |[\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 |\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_... */ protected static function Login($iOnExit) { if (self::SecureConnectionRequired() && !utils::IsConnectionSecure()) { // Non secured URL... request for a secure connection throw new Exception('Secure connection required!'); } $aAllowedLoginTypes = MetaModel::GetConfig()->GetAllowedLoginTypes(); if (isset($_SESSION['auth_user'])) { //echo "User: ".$_SESSION['auth_user']."\n"; // Already authentified $bRet = UserRights::Login($_SESSION['auth_user']); // Login & set the user's language if ($bRet) { return self::EXIT_CODE_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; 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_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_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; } /** * Overridable: depending on the user, head toward a dedicated portal * @param string|null $sRequestedPortalId * @param int $iOnExit How to complete the call: redirect or return a code */ 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) { return self::EXIT_CODE_OK; } else if($ret === false) { throw new Exception('Nowhere to go??'); } else { if ($iOnExit == self::EXIT_RETURN) { return self::EXIT_CODE_PORTALUSERNOTAUTHORIZED; } else { // No rights to be here, redirect to the portal header('Location: '.$ret); } } } /** * Check if the user is already authentified, if yes, then performs some additional validations: * - if $bMustBeAdmin is true, then the user must be an administrator, otherwise an error is displayed * - if $bIsAllowedToPortalUsers is false and the user has only access to the portal, then the user is redirected to the portal * @param bool $bMustBeAdmin Whether or not the user must be an admin to access the current page * @param bool $bIsAllowedToPortalUsers Whether or not the current page is considered as part of the portal * @param int iOnExit What action to take if the user is not logged on (one of the class constants EXIT_...) */ static function DoLogin($bMustBeAdmin = false, $bIsAllowedToPortalUsers = false, $iOnExit = self::EXIT_PROMPT) { $sRequestedPortalId = $bIsAllowedToPortalUsers ? 'legacy_portal' : 'backoffice'; return self::DoLoginEx($sRequestedPortalId, $bMustBeAdmin, $iOnExit); } /** * Check if the user is already authentified, if yes, then performs some additional validations to redirect towards the desired "portal" * @param string|null $sRequestedPortalId The requested "portal" interface, null for any * @param bool $bMustBeAdmin Whether or not the user must be an admin to access the current page * @param int iOnExit What action to take if the user is not logged on (one of the class constants EXIT_...) */ static function DoLoginEx($sRequestedPortalId = null, $bMustBeAdmin = false, $iOnExit = self::EXIT_PROMPT) { $operation = utils::ReadParam('loginop', ''); $sMessage = self::HandleOperations($operation); // May exit directly $iRet = self::Login($iOnExit); if ($iRet == self::EXIT_CODE_OK) { if ($bMustBeAdmin && !UserRights::IsAdministrator()) { if ($iOnExit == self::EXIT_RETURN) { return self::EXIT_CODE_MUSTBEADMIN; } else { require_once(APPROOT.'/setup/setuppage.class.inc.php'); $oP = new SetupPage(Dict::S('UI:PageTitle:FatalError')); $oP->add("

".Dict::S('UI:Login:Error:AccessAdmin')."

\n"); $oP->p("".Dict::S('UI:LogOffMenu').""); $oP->output(); exit; } } $iRet = call_user_func(array(self::$sHandlerClass, 'ChangeLocation'), $sRequestedPortalId, $iOnExit); } if ($iOnExit == self::EXIT_RETURN) { return $iRet; } else { return $sMessage; } } protected static function HandleOperations($operation) { $sMessage = ''; // most of the operations never return, but some can return a message to be displayed if ($operation == 'logoff') { if (isset($_SESSION['login_mode'])) { $sLoginMode = $_SESSION['login_mode']; } else { $aAllowedLoginTypes = MetaModel::GetConfig()->GetAllowedLoginTypes(); if (count($aAllowedLoginTypes) > 0) { $sLoginMode = $aAllowedLoginTypes[0]; } else { $sLoginMode = 'form'; } } self::ResetSession(); $oPage = self::NewLoginWebPage(); $oPage->DisplayLoginForm( $sLoginMode, false /* not a failed attempt */); $oPage->output(); exit; } else if ($operation == 'forgot_pwd') { $oPage = self::NewLoginWebPage(); $oPage->DisplayForgotPwdForm(); $oPage->output(); exit; } else if ($operation == 'forgot_pwd_go') { $oPage = self::NewLoginWebPage(); $oPage->ForgotPwdGo(); $oPage->output(); exit; } else if ($operation == 'reset_pwd') { $oPage = self::NewLoginWebPage(); $oPage->DisplayResetPwdForm(); $oPage->output(); exit; } else if ($operation == 'do_reset_pwd') { $oPage = self::NewLoginWebPage(); $oPage->DoResetPassword(); $oPage->output(); exit; } else if ($operation == 'change_pwd') { $sAuthUser = $_SESSION['auth_user']; UserRights::Login($sAuthUser); // Set the user's language $oPage = self::NewLoginWebPage(); $oPage->DisplayChangePwdForm(); $oPage->output(); exit; } if ($operation == 'do_change_pwd') { $sAuthUser = $_SESSION['auth_user']; UserRights::Login($sAuthUser); // Set the user's language $sOldPwd = utils::ReadPostedParam('old_pwd', '', false, 'raw_data'); $sNewPwd = utils::ReadPostedParam('new_pwd', '', false, 'raw_data'); if (UserRights::CanChangePassword() && ((!UserRights::CheckCredentials($sAuthUser, $sOldPwd)) || (!UserRights::ChangePassword($sOldPwd, $sNewPwd)))) { $oPage = self::NewLoginWebPage(); $oPage->DisplayChangePwdForm(true); // old pwd was wrong $oPage->output(); exit; } $sMessage = Dict::S('UI:Login:PasswordChanged'); } return $sMessage; } protected static function Dispatch($sRequestedPortalId) { if ($sRequestedPortalId === null) return true; // allowed to any portal => return true $aPortalsConf = PortalDispatcherData::GetData(); $aDispatchers = array(); foreach($aPortalsConf as $sPortalId => $aConf) { $sHandlerClass = $aConf['handler']; $aDispatchers[$sPortalId] = new $sHandlerClass($sPortalId); } if (array_key_exists($sRequestedPortalId, $aDispatchers) && $aDispatchers[$sRequestedPortalId]->IsUserAllowed()) { return true; } foreach($aDispatchers as $sPortalId => $oDispatcher) { if ($oDispatcher->IsUserAllowed()) return $oDispatcher->GetUrl(); } return false; // nothing matched !! } } // End of class