N°2311 - Login Page extensibility

This commit is contained in:
Eric
2019-08-29 10:59:12 +02:00
parent 9bd1da95e0
commit b2ab07aa69
35 changed files with 974 additions and 291 deletions

View File

@@ -178,6 +178,14 @@ interface iLogoutExtension extends iLoginExtension
public function LogoutAction();
}
interface iLoginDataExtension extends iLoginExtension
{
/**
* @return LoginTwigData
*/
public function GetLoginData();
}
/**
* Implement this interface to change the behavior of the GUI for some objects.
*

View File

@@ -21,6 +21,7 @@ class LoginDefaultBefore extends AbstractLoginFSMExtension
protected function OnStart(&$iErrorCode)
{
$iErrorCode = LoginWebPage::EXIT_CODE_OK;
// Check if proposed login mode is present and allowed
$aAllowedLoginTypes = MetaModel::GetConfig()->GetAllowedLoginTypes();
$sProposedLoginMode = utils::ReadParam('login_mode', '');
@@ -46,22 +47,10 @@ class LoginDefaultBefore extends AbstractLoginFSMExtension
if ($index !== false)
{
// Force login mode
$_SESSION['login_mode'] = $sProposedLoginMode;
LoginWebPage::SetLoginModeAndReload($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;
return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE;
}
protected function OnConnected(&$iErrorCode)
{
unset($_SESSION['login_error_count']);
return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE;
}
}
/**
@@ -69,6 +58,8 @@ class LoginDefaultBefore extends AbstractLoginFSMExtension
*/
class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExtension
{
/**
* Must be executed after the other login plugins
*
@@ -81,7 +72,7 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
protected function OnError(&$iErrorCode)
{
unset($_SESSION['login_mode']);
self::ResetLoginSession();
return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE;
}
@@ -89,7 +80,8 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
{
if (!isset($_SESSION['login_mode']))
{
LoginWebPage::ResetSession();
// If no plugin validated the user, exit
self::ResetLoginSession();
exit();
}
return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE;
@@ -100,6 +92,19 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
*/
public function LogoutAction()
{
unset($_SESSION['login_mode']);
self::ResetLoginSession();
}
// Hard reset of the session
private static function ResetLoginSession()
{
LoginWebPage::ResetSession();
foreach (array_keys($_SESSION) as $sKey)
{
if (utils::StartsWith($sKey, 'login_'))
{
unset($_SESSION[$sKey]);
}
}
}
}

View File

@@ -7,7 +7,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
class LoginForm extends AbstractLoginFSMExtension
class LoginForm extends AbstractLoginFSMExtension implements iLoginDataExtension
{
private $bForceFormOnError = false;
@@ -21,24 +21,12 @@ class LoginForm extends AbstractLoginFSMExtension
return array('form');
}
protected function OnModeDetection(&$iErrorCode)
{
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
if (!empty($sAuthUser) && !empty($sAuthPwd))
{
$_SESSION['login_mode'] = 'form';
}
return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE;
}
protected function OnReadCredentials(&$iErrorCode)
{
if (!isset($_SESSION['login_mode']) || ($_SESSION['login_mode'] == 'form'))
{
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
$_SESSION['login_mode'] = 'form';
if ($this->bForceFormOnError || empty($sAuthUser) || empty($sAuthPwd))
{
if (array_key_exists('HTTP_X_COMBODO_AJAX', $_SERVER))
@@ -51,11 +39,12 @@ class LoginForm extends AbstractLoginFSMExtension
// No credentials yet, display the form
$oPage = LoginWebPage::NewLoginWebPage();
$oPage->DisplayLoginForm('form', $this->bForceFormOnError);
$oPage->DisplayLoginForm($this->bForceFormOnError);
$oPage->output();
$this->bForceFormOnError = false;
exit;
}
$_SESSION['login_mode'] = 'form';
}
return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE;
}
@@ -103,4 +92,36 @@ class LoginForm extends AbstractLoginFSMExtension
}
return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE;
}
/**
* @return LoginTwigData
* @throws \Exception
*/
public function GetLoginData()
{
$aPostedVars = array('auth_user', 'auth_pwd');
$oLoginData = new LoginTwigData($aPostedVars);
$sAuthUser = utils::ReadParam('auth_user', '', true, 'raw_data');
$sAuthPwd = utils::ReadParam('suggest_pwd', '', true, 'raw_data');
$aData = array(
'sAuthUser' => $sAuthUser,
'sAuthPwd' => $sAuthPwd,
);
$oLoginData->AddBlockData('login_input', new LoginBlockData('loginforminput.html.twig', $aData));
$oLoginData->AddBlockData('login_submit', new LoginBlockData('loginformsubmit.html.twig'));
$bEnableResetPassword = empty(MetaModel::GetConfig()->Get('forgot_password')) ? true : MetaModel::GetConfig()->Get('forgot_password');
$sResetPasswordUrl = utils::GetAbsoluteUrlAppRoot() . 'pages/UI.php?loginop=forgot_pwd';
$aData = array(
'bEnableResetPassword' => $bEnableResetPassword,
'sResetPasswordUrl' => $sResetPasswordUrl,
);
$oLoginData->AddBlockData('login_links', new LoginBlockData('loginformlinks.html.twig', $aData));
return $oLoginData;
}
}

View File

@@ -0,0 +1,222 @@
<?php
/**
*
* @copyright Copyright (C) 2010-2019 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\TwigExtension;
class LoginTwigData
{
private $aBlockData;
private $aPostedVars;
private $sTwigLoaderPath;
private $sCSSFile;
/**
* LoginTwigData constructor.
*
* @param array $aPostedVars
* @param string $sLoaderPath
* @param string $sCSSFile
*/
public function __construct($aPostedVars = array(), $sLoaderPath = null, $sCSSFile = null)
{
$this->aBlockData = array();
$this->aPostedVars = $aPostedVars;
$this->sTwigLoaderPath = $sLoaderPath;
$this->sCSSFile = $sCSSFile;
}
/**
* @param string $sBlockName
* @param LoginBlockData $oBlockData
*/
public final function AddBlockData($sBlockName, $oBlockData)
{
$this->aBlockData[$sBlockName] = $oBlockData;
}
public final function GetBlockData($sBlockName)
{
/** @var LoginBlockData $oBlockData */
$oBlockData = isset($this->aBlockData[$sBlockName]) ? $this->aBlockData[$sBlockName] : null;
return $oBlockData;
}
public final function GetPostedVars()
{
return $this->aPostedVars;
}
public final function GetTwigLoaderPath()
{
return $this->sTwigLoaderPath;
}
public final function GetCSSFile()
{
return $this->sCSSFile;
}
}
class LoginBlockData
{
private $sTwig;
private $aData;
/**
* LoginBlockData constructor.
*
* @param string $sTwig
* @param array $aData
*/
public function __construct($sTwig, $aData = array())
{
$this->sTwig = $sTwig;
$this->aData = $aData;
}
public final function GetTwig()
{
return $this->sTwig;
}
public final function GetData()
{
return $this->aData;
}
}
class LoginTwigContext
{
private $aLoginPluginList;
private $aPluginFormData;
private $aPostedVars;
private $oTwig;
public function __construct()
{
$this->aLoginPluginList = LoginWebPage::GetLoginPluginList('iLoginDataExtension', false);
$this->aPluginFormData = array();
$aTwigLoaders = array();
$this->aPostedVars = array();
foreach ($this->aLoginPluginList as $oLoginPlugin)
{
/** @var \iLoginDataExtension $oLoginPlugin */
$oLoginData = $oLoginPlugin->GetLoginData();
$this->aPluginFormData[] = $oLoginData;
$sTwigLoaderPath = $oLoginData->GetTwigLoaderPath();
if ($sTwigLoaderPath != null)
{
$aTwigLoaders[] = new Twig_Loader_Filesystem($sTwigLoaderPath);
}
$this->aPostedVars = array_merge($this->aPostedVars, $oLoginData->GetPostedVars());
}
$oCoreLoader = new Twig_Loader_Filesystem(array(), APPROOT.'templates');
$aCoreTemplatesPaths = array('login', 'login/password');
// Having this path declared after the plugins let the plugins replace the core templates
$oCoreLoader->setPaths($aCoreTemplatesPaths);
// Having the core templates accessible within a different namespace offer the possibility to extend them while replacing them
$oCoreLoader->setPaths($aCoreTemplatesPaths, 'ItopCore');
$aTwigLoaders[] = $oCoreLoader;
$oLoader = new Twig_Loader_Chain($aTwigLoaders);
$this->oTwig = new Twig_Environment($oLoader);
TwigExtension::RegisterTwigExtensions($this->oTwig);
}
public function GetDefaultVars()
{
$sLogo = 'itop-logo-external.png';
$sBrandingLogo = 'login-logo.png';
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/'.$sLogo.'?t='.utils::GetCacheBusterTimestamp();
if (file_exists(MODULESROOT.'branding/'.$sBrandingLogo))
{
$sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/'.$sBrandingLogo.'?t='.utils::GetCacheBusterTimestamp();
}
$aVars = array(
'sAppRootUrl' => utils::GetAbsoluteUrlAppRoot(),
'aPluginFormData' => $this->GetPluginFormData(),
'sItopVersion' => ITOP_VERSION,
'sVersionShort' => $sVersionShort,
'sIconUrl' => $sIconUrl,
'sDisplayIcon' => $sDisplayIcon,
);
return $aVars;
}
public function Render(NiceWebPage $oPage, $sTwigFile, $aVars = array())
{
$sType = 'html';
if (preg_match('/.*\.(html|ready\.js|js)\.twig/U', $sTwigFile, $matches))
{
$sType = $matches[1];
}
switch ($sType)
{
case 'html':
$oPage->add($this->GetTwig()->render($sTwigFile, $aVars));
// Render CSS links
foreach ($this->aPluginFormData as $oFormData)
{
/** @var \LoginTwigData $oFormData */
$sCSSFile = $oFormData->GetCSSFile();
if (!empty($sCSSFile))
{
$oPage->add_linked_stylesheet($sCSSFile);
}
}
break;
case 'js':
$oPage->add_script($this->GetTwig()->render($sTwigFile, $aVars));
break;
case 'ready.js':
$oPage->add_ready_script($this->GetTwig()->render($sTwigFile, $aVars));
break;
}
}
/**
* @return mixed
*/
public function GetLoginPluginList()
{
return $this->aLoginPluginList;
}
/**
* @return array
*/
public function GetPluginFormData()
{
return $this->aPluginFormData;
}
/**
* @return array
*/
public function GetPostedVars()
{
return $this->aPostedVars;
}
/**
* @return \Twig_Environment
*/
public function GetTwig()
{
return $this->oTwig;
}
}

View File

@@ -24,8 +24,6 @@
* @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
*/
@@ -91,6 +89,8 @@ class LoginWebPage extends NiceWebPage
public function SetStyleSheet()
{
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/login.css');
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/all.min.css');
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/v4-shims.min.css');
}
public static function SetLoginFailedMessage($sMessage)
@@ -98,23 +98,11 @@ class LoginWebPage extends NiceWebPage
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';
}
$sLogo = 'itop-logo-external.png';
$sBrandingLogo = 'login-logo.png';
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/'.$sLogo.'?t='.utils::GetCacheBusterTimestamp();
@@ -125,104 +113,69 @@ class LoginWebPage extends NiceWebPage
$this->add("<div id=\"login-logo\"><a href=\"".htmlentities($sIconUrl, ENT_QUOTES, 'UTF-8')."\"><img title=\"$sVersionShort\" src=\"$sDisplayIcon\"></a></div>\n");
}
public function DisplayLoginForm($sLoginType, $bFailedLogin = false)
public function DisplayLoginForm($bFailedLogin = false)
{
switch($sLoginType)
$oTwigContext = new LoginTwigContext();
$aPostedVars = array_merge(array('login_mode', 'loginop'), $oTwigContext->GetPostedVars());
$sMessage = Dict::S('UI:Login:IdentifyYourself');
// Error message
if ($bFailedLogin)
{
case 'form':
$sAuthUser = utils::ReadParam('auth_user', '', true, 'raw_data');
$sAuthPwd = utils::ReadParam('suggest_pwd', '', true, 'raw_data');
$this->DisplayLoginHeader();
$this->add("<div id=\"login\">\n");
$this->add("<h1>".Dict::S('UI:Login:Welcome')."</h1>\n");
if ($bFailedLogin)
if (self::$m_sLoginFailedMessage != '')
{
if (self::$m_sLoginFailedMessage != '')
{
$this->add("<p class=\"hilite\">".self::$m_sLoginFailedMessage."</p>\n");
}
else
{
$this->add("<p class=\"hilite\">".Dict::S('UI:Login:IncorrectLoginPassword')."</p>\n");
}
$sMessage = self::$m_sLoginFailedMessage;
}
else
{
$this->add("<p>".Dict::S('UI:Login:IdentifyYourself')."</p>\n");
$sMessage = Dict::S('UI:Login:IncorrectLoginPassword');
}
$this->add("<form method=\"post\">\n");
$this->add("<table>\n");
$sForgotPwd = $this->EnableResetPassword() ? $this->ForgotPwdLink() : '';
$this->add("<tr><td style=\"text-align:right\"><label for=\"user\">".Dict::S('UI:Login:UserNamePrompt').":</label></td><td style=\"text-align:left\"><input id=\"user\" type=\"text\" name=\"auth_user\" value=\"".htmlentities($sAuthUser, ENT_QUOTES, 'UTF-8')."\" /></td></tr>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"pwd\">".Dict::S('UI:Login:PasswordPrompt').":</label></td><td style=\"text-align:left\"><input id=\"pwd\" type=\"password\" name=\"auth_pwd\" value=\"".htmlentities($sAuthPwd, ENT_QUOTES, 'UTF-8')."\" /></td></tr>\n");
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><span class=\"btn_border\"><input type=\"submit\" value=\"".Dict::S('UI:Button:Login')."\" /></span></td></tr>\n");
if (strlen($sForgotPwd) > 0)
{
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\">$sForgotPwd</td></tr>\n");
}
$this->add("</table>\n");
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"login\" />\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("<input type=\"hidden\" name=\"".htmlentities($sPostedKey, ENT_QUOTES, 'UTF-8')."[".htmlentities($sKey, ENT_QUOTES, 'UTF-8')."]\" value=\"".htmlentities($sValue, ENT_QUOTES, 'UTF-8')."\" />\n");
}
}
else
{
$this->add("<input type=\"hidden\" name=\"".htmlentities($sPostedKey, ENT_QUOTES, 'UTF-8')."\" value=\"".htmlentities($postedValue, ENT_QUOTES, 'UTF-8')."\" />\n");
}
}
}
$this->add("</form>\n");
$this->add(Dict::S('UI:Login:About'));
$this->add("</div>\n");
break;
}
}
/**
* Return '' to disable this feature
*/
public function ForgotPwdLink()
{
$sUrl = utils::GetAbsoluteUrlAppRoot() . 'pages/UI.php?loginop=forgot_pwd';
$sHtml = "<a href=\"$sUrl\" target=\"_blank\">".Dict::S('UI:Login:ForgotPwd')."</a>";
return $sHtml;
// Keep the OTHER parameters posted
$aPreviousPostedVars = array();
foreach($_POST as $sPostedKey => $postedValue)
{
if (!in_array($sPostedKey, $aPostedVars))
{
if (is_array($postedValue))
{
foreach($postedValue as $sKey => $sValue)
{
$sName = "{$sPostedKey}[{$sKey}]";
$aPreviousPostedVars[$sName] = $sValue;
}
}
else
{
$aPreviousPostedVars[$sPostedKey] = $postedValue;
}
}
}
$aVars = array(
'bFailedLogin' => $bFailedLogin,
'sMessage' => $sMessage,
'aPreviousPostedVars' => $aPreviousPostedVars,
);
$aVars = array_merge($aVars, $oTwigContext->GetDefaultVars());
$oTwigContext->Render($this, 'login.html.twig', $aVars);
}
public function DisplayForgotPwdForm($bFailedToReset = false, $sFailureReason = null)
{
$this->DisplayLoginHeader();
$this->add("<div id=\"login\">\n");
$this->add("<h1>".Dict::S('UI:Login:ForgotPwdForm')."</h1>\n");
$this->add("<p>".Dict::S('UI:Login:ForgotPwdForm+')."</p>\n");
if ($bFailedToReset)
{
$this->add("<p class=\"hilite\">".Dict::Format('UI:Login:ResetPwdFailed', htmlentities($sFailureReason, ENT_QUOTES, 'UTF-8'))."</p>\n");
}
$sAuthUser = utils::ReadParam('auth_user', '', true, 'raw_data');
$this->add("<form method=\"post\">\n");
$this->add("<table>\n");
$this->add("<tr><td colspan=\"2\" class=\"center\"><label for=\"user\">".Dict::S('UI:Login:UserNamePrompt').":</label><input id=\"user\" type=\"text\" name=\"auth_user\" value=\"".htmlentities($sAuthUser, ENT_QUOTES, 'UTF-8')."\" /></td></tr>\n");
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><span class=\"btn_border\"><input type=\"button\" onClick=\"window.close();\" value=\"".Dict::S('UI:Button:Cancel')."\" /></span>&nbsp;&nbsp;<span class=\"btn_border\"><input type=\"submit\" value=\"".Dict::S('UI:Login:ResetPassword')."\" /></span></td></tr>\n");
$this->add("</table>\n");
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"forgot_pwd_go\" />\n");
$this->add("</form>\n");
$this->add("</div>\n");
$this->add_ready_script('$("#user").focus();');
$oTwigContext = new LoginTwigContext();
$aVars = $oTwigContext->GetDefaultVars();
$aVars['sAuthUser'] = $sAuthUser;
$aVars['bFailedToReset'] = $bFailedToReset;
$aVars['sFailureReason'] = $sFailureReason;
$oTwigContext->Render($this, 'forgotpwdform.html.twig', $aVars);
$oTwigContext->Render($this, 'forgotpwdform.ready.js.twig');
}
protected function ForgotPwdGo()
@@ -280,16 +233,9 @@ class LoginWebPage extends NiceWebPage
throw new Exception(Dict::S('UI:ResetPwd-Error-Send'));
}
$this->DisplayLoginHeader();
$this->add("<div id=\"login\">\n");
$this->add("<h1>".Dict::S('UI:Login:ForgotPwdForm')."</h1>\n");
$this->add("<p>".Dict::S('UI:ResetPwd-EmailSent')."</p>");
$this->add("<form method=\"post\">\n");
$this->add("<table>\n");
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><input type=\"button\" onClick=\"window.close();\" value=\"".Dict::S('UI:Button:Done')."\" /></td></tr>\n");
$this->add("</table>\n");
$this->add("</form>\n");
$this->add("</div\n");
$oTwigContext = new LoginTwigContext();
$aVars = $oTwigContext->GetDefaultVars();
$oTwigContext->Render($this, 'forgotpwdsent.html.twig', $aVars);
}
catch(Exception $e)
{
@@ -302,59 +248,36 @@ class LoginWebPage extends NiceWebPage
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sToken = utils::ReadParam('token', '', false, 'raw_data');
$sAuthUserForDisplay = utils::HtmlEntities($sAuthUser);
$sTokenForDisplay = utils::HtmlEntities($sToken);
UserRights::Login($sAuthUser); // Set the user's language
$oUser = UserRights::GetUserObject();
$this->DisplayLoginHeader();
$this->add("<div id=\"login\">\n");
$this->add("<h1>".Dict::S('UI:ResetPwd-Title')."</h1>\n");
if ($oUser == null)
$oTwigContext = new LoginTwigContext();
$aVars = $oTwigContext->GetDefaultVars();
$aVars['sAuthUser'] = $sAuthUser;
$aVars['sToken'] = $sToken;
if (($oUser == null))
{
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUserForDisplay)."</p>\n");
$aVars['bNoUser'] = true;
}
else
{
$aVars['bNoUser'] = false;
$oEncryptedToken = $oUser->Get('reset_pwd_token');
if (!$oEncryptedToken->CheckPassword($sToken))
{
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
$aVars['bBadToken'] = true;
}
else
{
$sUserNameForDisplay = utils::HtmlEntities($oUser->GetFriendlyName());
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-EnterPassword', $sUserNameForDisplay)."</p>\n");
$sInconsistenPwdMsg = Dict::S('UI:Login:RetypePwdDoesNotMatch');
$this->add_script(
<<<EOF
function DoCheckPwd()
{
if ($('#new_pwd').val() != $('#retype_new_pwd').val())
{
alert('$sInconsistenPwdMsg');
return false;
}
return true;
}
EOF
);
$this->add("<form method=\"post\">\n");
$this->add("<table>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"new_pwd\">".Dict::S('UI:Login:NewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"new_pwd\" name=\"new_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"retype_new_pwd\">".Dict::S('UI:Login:RetypeNewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"retype_new_pwd\" name=\"retype_new_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><span class=\"btn_border\"><input type=\"submit\" onClick=\"return DoCheckPwd();\" value=\"".Dict::S('UI:Button:ChangePassword')."\" /></span></td></tr>\n");
$this->add("</table>\n");
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"do_reset_pwd\" />\n");
$this->add("<input type=\"hidden\" name=\"auth_user\" value=\"".$sAuthUserForDisplay."\" />\n");
$this->add("<input type=\"hidden\" name=\"token\" value=\"".$sTokenForDisplay."\" />\n");
$this->add("</form>\n");
$this->add("</div\n");
$aVars['bBadToken'] = false;
$aVars['sUserName'] = $oUser->GetFriendlyName();
}
}
$oTwigContext->Render($this, 'resetpwdform.html.twig', $aVars);
$oTwigContext->Render($this, 'resetpwdform.js.twig');
}
public function DoResetPassword()
@@ -366,102 +289,61 @@ EOF
UserRights::Login($sAuthUser); // Set the user's language
$oUser = UserRights::GetUserObject();
$this->DisplayLoginHeader();
$this->add("<div id=\"login\">\n");
$this->add("<h1>".Dict::S('UI:ResetPwd-Title')."</h1>\n");
if ($oUser == null)
$oTwigContext = new LoginTwigContext();
$aVars = $oTwigContext->GetDefaultVars();
$aVars['sAuthUser'] = $sAuthUser;
$aVars['sToken'] = $sToken;
if (($oUser == null))
{
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser)."</p>\n");
$aVars['bNoUser'] = true;
}
else
{
$oEncryptedPassword = $oUser->Get('reset_pwd_token');
if (!$oEncryptedPassword->CheckPassword($sToken))
$aVars['bNoUser'] = false;
$oEncryptedToken = $oUser->Get('reset_pwd_token');
if (!$oEncryptedToken->CheckPassword($sToken))
{
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
$aVars['bBadToken'] = true;
}
else
{
$aVars['bBadToken'] = false;
// Trash the token and change the password
$oUser->Set('reset_pwd_token', '');
$oUser->AllowWrite(true);
$oUser->SetPassword($sNewPwd); // Does record the change into the DB
$this->add("<p>".Dict::S('UI:ResetPwd-Ready')."</p>");
$sUrl = utils::GetAbsoluteUrlAppRoot();
$this->add("<p><a href=\"$sUrl\">".Dict::S('UI:ResetPwd-Login')."</a></p>");
$aVars['sUrl'] = utils::GetAbsoluteUrlAppRoot();
}
$this->add("</div\n");
}
$oTwigContext->Render($this, 'resetpwddone.html.twig', $aVars);
}
public function DisplayChangePwdForm($bFailedLogin = false)
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sInconsistenPwdMsg = Dict::S('UI:Login:RetypePwdDoesNotMatch');
$this->add_script(<<<EOF
function GoBack()
{
window.history.back();
}
function DoCheckPwd()
{
if ($('#new_pwd').val() != $('#retype_new_pwd').val())
{
alert('$sInconsistenPwdMsg');
return false;
}
return true;
}
EOF
);
$this->DisplayLoginHeader();
$this->add("<div id=\"login\">\n");
$this->add("<h1>".Dict::S('UI:Login:ChangeYourPassword')."</h1>\n");
if ($bFailedLogin)
{
$this->add("<p class=\"hilite\">".Dict::S('UI:Login:IncorrectOldPassword')."</p>\n");
}
$this->add("<form method=\"post\">\n");
$this->add("<table>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"old_pwd\">".Dict::S('UI:Login:OldPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"old_pwd\" name=\"old_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"new_pwd\">".Dict::S('UI:Login:NewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"new_pwd\" name=\"new_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"retype_new_pwd\">".Dict::S('UI:Login:RetypeNewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"retype_new_pwd\" name=\"retype_new_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><span class=\"btn_border\"><input type=\"button\" onClick=\"GoBack();\" value=\"".Dict::S('UI:Button:Cancel')."\" /></span>&nbsp;&nbsp;<span class=\"btn_border\"><input type=\"submit\" onClick=\"return DoCheckPwd();\" value=\"".Dict::S('UI:Button:ChangePassword')."\" /></span></td></tr>\n");
$this->add("</table>\n");
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"do_change_pwd\" />\n");
$this->add("</form>\n");
$this->add("</div>\n");
$oTwigContext = new LoginTwigContext();
$aVars = $oTwigContext->GetDefaultVars();
$aVars['bFailedLogin'] = $bFailedLogin;
$oTwigContext->Render($this, 'changepwdform.js.twig');
$oTwigContext->Render($this, 'changepwdform.html.twig', $aVars);
}
public function DisplayLogoutPage($bPortal, $sTitle = null)
{
$sUrl = utils::GetAbsoluteUrlAppRoot();
if ($bPortal)
{
$sUrl .= 'portal/';
}
else
{
$sUrl .= 'pages/UI.php';
}
if (empty($sTitle))
{
$sTitle = Dict::S('UI:LogOff:ThankYou');
}
$sUrl .= $bPortal ? 'portal/' : 'pages/UI.php';
$sTitle = empty($sTitle) ? Dict::S('UI:LogOff:ThankYou') : $sTitle;
$sMessage = Dict::S('UI:LogOff:ClickHereToLoginAgain');
$this->no_cache();
$this->DisplayLoginHeader();
$this->add("<div id=\"login\">\n");
$this->add("<h1>$sTitle</h1>\n");
$oTwigContext = new LoginTwigContext();
$aVars = $oTwigContext->GetDefaultVars();
$aVars['sUrl'] = $sUrl;
$aVars['sTitle'] = $sTitle;
$aVars['sMessage'] = $sMessage;
$this->add("<p><a href=\"$sUrl\">$sMessage</a></p>");
$this->add("</div>\n");
$oTwigContext->Render($this, 'logout.html.twig', $aVars);
$this->output();
}
@@ -532,6 +414,8 @@ EOF
IssueLog::Info("SESSION: $sSessionLog");
}
$iErrorCode = self::EXIT_CODE_OK;
// Finite state machine loop
while (true)
{
@@ -556,7 +440,6 @@ EOF
}
IssueLog::Info("Login: state: [$sLoginState] call: ".get_class($oLoginFSMExtensionInstance));
}
$iErrorCode = self::EXIT_CODE_OK;
$iResponse = $oLoginFSMExtensionInstance->LoginAction($sLoginState, $iErrorCode);
if ($iResponse == self::LOGIN_FSM_RETURN_OK)
{
@@ -597,14 +480,22 @@ EOF
* Use the login mode to filter plugins
*
* @param string $sInterface 'iLoginFSMExtension' or 'iLogoutExtension'
* @param bool $bFilterWithMode if false do not filter the plugin list with login mode
*
* @return array of plugins
*/
public static function GetLoginPluginList($sInterface = 'iLoginFSMExtension')
public static function GetLoginPluginList($sInterface = 'iLoginFSMExtension', $bFilterWithMode = true)
{
$aAllPlugins = array();
$sCurrentLoginMode = isset($_SESSION['login_mode']) ? $_SESSION['login_mode'] : '';
if ($bFilterWithMode)
{
$sCurrentLoginMode = isset($_SESSION['login_mode']) ? $_SESSION['login_mode'] : '';
}
else
{
$sCurrentLoginMode = '';
}
/** @var iLoginExtension $oLoginExtensionInstance */
foreach (MetaModel::EnumPlugins($sInterface) as $oLoginExtensionInstance)
@@ -620,6 +511,7 @@ EOF
$aAllPlugins[$sLoginMode] = array();
}
$aAllPlugins[$sLoginMode][] = $oLoginExtensionInstance;
break; // Stop here to avoid registering a plugin twice
}
}
}
@@ -780,6 +672,35 @@ EOF
exit;
}
public static function SetLoginModeAndReload($sNewLoginMode)
{
if (isset($_SESSION['login_mode']) && ($_SESSION['login_mode'] == $sNewLoginMode))
{
return;
}
$_SESSION['login_mode'] = $sNewLoginMode;
self::HTTPReload();
}
public static function HTTPReload()
{
$sOriginURL = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
if (!utils::StartsWith($sOriginURL, utils::GetAbsoluteUrlAppRoot()))
{
// If the found URL does not start with the configured AppRoot URL
$sOriginURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php';
}
self::HTTPRedirect($sOriginURL);
}
public static function HTTPRedirect($sURL)
{
header('HTTP/1.1 307 Temporary Redirect');
header('Location: '.$sURL);
exit;
}
/**
* Provisioning API: Find a User
*
@@ -1118,7 +1039,7 @@ EOF
}
self::ResetSession();
$oPage = self::NewLoginWebPage();
$oPage->DisplayLoginForm( $sLoginMode, false /* not a failed attempt */);
$oPage->DisplayLoginForm(false /* not a failed attempt */);
$oPage->output();
exit;
}

View File

@@ -0,0 +1,126 @@
<?php
namespace Combodo\iTop;
use AttributeDateTime;
use Dict;
use Exception;
use MetaModel;
use Twig_Environment;
use Twig_SimpleFilter;
use Twig_SimpleFunction;
use utils;
class TwigExtension
{
/**
* Registers Twig extensions such as filters or functions.
* It allows us to access some stuff directly in twig.
*
* @param \Twig_Environment $oTwigEnv
*/
public static function RegisterTwigExtensions(Twig_Environment &$oTwigEnv)
{
// Filter to translate a string via the Dict::S function
// Usage in twig: {{ 'String:ToTranslate'|dict_s }}
$oTwigEnv->addFilter(new Twig_SimpleFilter('dict_s',
function ($sStringCode, $sDefault = null, $bUserLanguageOnly = false) {
return Dict::S($sStringCode, $sDefault, $bUserLanguageOnly);
})
);
// Filter to format a string via the Dict::Format function
// Usage in twig: {{ 'String:ToTranslate'|dict_format() }}
$oTwigEnv->addFilter(new Twig_SimpleFilter('dict_format',
function ($sStringCode, $sParam01 = null, $sParam02 = null, $sParam03 = null, $sParam04 = null) {
return Dict::Format($sStringCode, $sParam01, $sParam02, $sParam03, $sParam04);
})
);
// Filter to format output
// example a DateTime is converted to user format
// Usage in twig: {{ 'String:ToFormat'|output_format }}
$oTwigEnv->addFilter(new Twig_SimpleFilter('date_format',
function ($sDate) {
try
{
if (preg_match('@^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$@', trim($sDate)))
{
return AttributeDateTime::GetFormat()->Format($sDate);
}
}
catch (Exception $e)
{
}
return $sDate;
})
);
// Filter to format output
// example a DateTime is converted to user format
// Usage in twig: {{ 'String:ToFormat'|output_format }}
$oTwigEnv->addFilter(new Twig_SimpleFilter('size_format',
function ($sSize) {
return utils::BytesToFriendlyFormat($sSize);
})
);
// Filter to enable base64 encode/decode
// Usage in twig: {{ 'String to encode'|base64_encode }}
$oTwigEnv->addFilter(new Twig_SimpleFilter('base64_encode', 'base64_encode'));
$oTwigEnv->addFilter(new Twig_SimpleFilter('base64_decode', 'base64_decode'));
// Filter to enable json decode (encode already exists)
// Usage in twig: {{ aSomeArray|json_decode }}
$oTwigEnv->addFilter(new Twig_SimpleFilter('json_decode', function ($sJsonString, $bAssoc = false) {
return json_decode($sJsonString, $bAssoc);
})
);
// Filter to add itopversion to an url
$oTwigEnv->addFilter(new Twig_SimpleFilter('add_itop_version', function ($sUrl) {
if (strpos($sUrl, '?') === false)
{
$sUrl = $sUrl."?itopversion=".ITOP_VERSION;
}
else
{
$sUrl = $sUrl."&itopversion=".ITOP_VERSION;
}
return $sUrl;
}));
// Filter to add a module's version to an url
$oTwigEnv->addFilter(new Twig_SimpleFilter('add_module_version', function ($sUrl, $sModuleName) {
$sModuleVersion = utils::GetCompiledModuleVersion($sModuleName);
if (strpos($sUrl, '?') === false)
{
$sUrl = $sUrl."?moduleversion=".$sModuleVersion;
}
else
{
$sUrl = $sUrl."&moduleversion=".$sModuleVersion;
}
return $sUrl;
}));
// Function to check our current environment
// Usage in twig: {% if is_development_environment() %}
$oTwigEnv->addFunction(new Twig_SimpleFunction('is_development_environment', function()
{
return utils::IsDevelopmentEnvironment();
}));
// Function to get configuration parameter
// Usage in twig: {{ get_config_parameter('foo') }}
$oTwigEnv->addFunction(new Twig_SimpleFunction('get_config_parameter', function($sParamName)
{
$oConfig = MetaModel::GetConfig();
return $oConfig->Get($sParamName);
}));
}
}

View File

@@ -2781,7 +2781,7 @@ abstract class MetaModel
// Build the list of available extensions
//
$aInterfaces = array('iApplicationUIExtension', 'iApplicationObjectExtension', 'iLoginFSMExtension', 'iLogoutExtension', 'iQueryModifier', 'iOnClassInitialization', 'iPopupMenuExtension', 'iPageUIExtension', 'iPortalUIExtension', 'ModuleHandlerApiInterface', 'iNewsroomProvider');
$aInterfaces = array('iApplicationUIExtension', 'iApplicationObjectExtension', 'iLoginFSMExtension', 'iLoginDataExtension', 'iLogoutExtension', 'iQueryModifier', 'iOnClassInitialization', 'iPopupMenuExtension', 'iPageUIExtension', 'iPortalUIExtension', 'ModuleHandlerApiInterface', 'iNewsroomProvider');
foreach($aInterfaces as $sInterface)
{
self::$m_aExtensionClasses[$sInterface] = array();

View File

@@ -48,14 +48,12 @@ body {
#login table {
width: 100%;
}
#pwd, #user,#old_pwd, #new_pwd, #retype_new_pwd {
#pwd, #user, #old_pwd, #new_pwd, #retype_new_pwd {
width: 10em;
}
.center {
text-align: center;
}
h1 {
color: #1C94C4;
font-size: 16pt;
@@ -63,3 +61,8 @@ h1 {
.v-spacer {
padding-top: 1em;
}
.sso-button {
width: 100%;
margin-bottom: 0.5em;
}

View File

@@ -8,4 +8,6 @@
Dict::Add('EN US', 'English', 'English', array(
'CAS:Error:UserNotAllowed' => 'User not allowed',
'CAS:Login:SignIn' => 'Sign in with CAS',
'CAS:Login:SignInTooltip' => 'Click here to authenticate yourself with the CAS server',
));

View File

@@ -8,4 +8,6 @@
Dict::Add('FR FR', 'French', 'Français', array(
'CAS:Error:UserNotAllowed' => 'Utilisateur non autorisé',
'CAS:Login:SignIn' => 'S\'identifier avec CAS',
'CAS:Login:SignInTooltip' => 'Cliquer ici pour s\'identifier avec le serveur CAS',
));

View File

@@ -11,7 +11,10 @@ use AbstractLoginFSMExtension;
use DBObjectSearch;
use DBObjectSet;
use Dict;
use iLoginDataExtension;
use iLogoutExtension;
use LoginBlockData;
use LoginTwigData;
use LoginWebPage;
use MetaModel;
use phpCAS;
@@ -23,7 +26,7 @@ use utils;
/**
* Class CASLoginExtension
*/
class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExtension
class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExtension, iLoginDataExtension
{
/**
* Return the list of supported login modes for this plugin
@@ -45,24 +48,29 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
{
if (!isset($_SESSION['login_mode']) || ($_SESSION['login_mode'] == 'cas'))
{
$_SESSION['login_mode'] = 'cas';
static::InitCASClient();
if (phpCAS::isAuthenticated())
{
$_SESSION['login_mode'] = 'cas';
$_SESSION['auth_user'] = phpCAS::getUser();
unset($_SESSION['login_will_redirect']);
}
else
{
if (!isset($_SESSION['cas_count']))
if ($_SESSION['login_mode'] == 'cas')
{
$_SESSION['cas_count'] = 1;
}
else
{
unset($_SESSION['cas_count']);
$iErrorCode = LoginWebPage::EXIT_CODE_MISSINGLOGIN;
return LoginWebPage::LOGIN_FSM_RETURN_ERROR;
if (!isset($_SESSION['login_will_redirect']))
{
$_SESSION['login_will_redirect'] = true;
}
else
{
unset($_SESSION['login_will_redirect']);
$iErrorCode = LoginWebPage::EXIT_CODE_MISSINGLOGIN;
return LoginWebPage::LOGIN_FSM_RETURN_ERROR;
}
}
$_SESSION['login_mode'] = 'cas';
phpCAS::forceAuthentication(); // Redirect to CAS and exit
}
}
@@ -110,8 +118,8 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
{
$oLoginWebPage = new LoginWebPage();
$oLoginWebPage->DisplayLogoutPage(false, Dict::S('CAS:Error:UserNotAllowed'));
exit();
}
exit();
}
return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE;
}
@@ -169,18 +177,6 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
}
}
public function GetSocialButtons()
{
return array(
array(
'login_mode' => 'cas',
'label' => 'Sign in with CAS',
'tooltip' => 'Click here to authenticate yourself with the CAS server',
'twig' => 'cas_button.twig',
),
);
}
private function DoUserProvisioning($sLogin)
{
$bCASUserSynchro = Config::Get('cas_user_synchro');
@@ -200,6 +196,26 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
}
CASUserProvisioning::CreateUser($sLogin, '', 'external');
}
/**
* @return LoginTwigData
*/
public function GetLoginData()
{
$sPath = APPROOT.'env-'.utils::GetCurrentEnvironment().'/authent-cas/view';
$oLoginData = new LoginTwigData(array(), $sPath);
$aData = array(
'sLoginMode' => 'cas',
'sLabel' => Dict::S('CAS:Login:SignIn'),
'sTooltip' => Dict::S('CAS:Login:SignInTooltip'),
);
$oBlockData = new LoginBlockData('cas_sso_button.html.twig', $aData);
$oLoginData->AddBlockData('login_sso_buttons', $oBlockData);
return $oLoginData;
}
}
/**

View File

@@ -0,0 +1,33 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
<tr>
<td style="text-align:center" colspan="2">
<div class="sso-button">
<div style="width:19em; cursor: pointer; margin-left: auto; margin-right: auto; padding: 0.5em; background-color: #eee; border-radius: 0.25em;" title="{{ aData.sTooltip }}" onclick="$('#login_mode').val('{{ aData.sLoginMode }}'); $('#login_form').submit(); return false;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 336.82 167.83" class="logo" style="height: 1em; vertical-align: middle; margin-right: 1em;">
<defs>
<style>
.cls-1 {
fill: #024d71;
}
.cls-2 {
fill: #74c163;
}
</style>
</defs>
<title>{{ aData.sTooltip }}</title>
<g id="Layer_2" data-name="Layer 2">
<path class="cls-1" d="M217.72,469.85c-17.68,1.31-38.31,1-54.18-7.87-12.59-7-23.71-18.52-28.72-32.14a82,82,0,0,1-4.49-27.49c0-36.26,16.11-64.23,40.29-80.34,16.59-11.38,36-16.83,58.54-16.83,17.3,0,30.34,4,35.08,6.64l-9.48,27.73c-4.5-2.38-15.17-5.22-28.92-5.22s-26.78,4.27-36.5,12.33c-13.27,11.13-22,29.39-22,51.66,0,25.6,14.46,42.67,42.43,42.67a161.48,161.48,0,0,0,22.38-1.19Z" transform="translate(-130.33 -305.18)"></path>
<path class="cls-1" d="M322.77,398.75l-2.72-30.4c-.71-8.76-1.42-21.56-2.14-31.28h-.71c-4,9.72-8.53,22-12.8,31.28l-13.69,30.08-13.53,33.43-17.33,38.41h-36.5l78-159.75h43.85l18.49,159.75H327.87l-3.05-38.12Z" transform="translate(-130.33 -305.18)"></path>
<path class="cls-1" d="M364.31,438.85c12.27,3.81,21.29,5.19,36.22,5.19,13,0,20.87-6.71,20.87-19,0-9.24-6.87-14.93-21.57-22.75-16.82-9-32.94-21.8-32.94-42.42,0-32.24,28-51.91,62.81-51.91,19.19,0,30.57,4.27,37.44,7.82l-10.66,28.44a62.94,62.94,0,0,0-29.63-6.87c-15.88,0-24.17,7.82-24.17,16.59,0,9.48,9.72,15.17,23.23,22.75,19.43,10.19,31.52,23.23,31.52,42.43,0,35.55-29.44,55.72-64.71,53.8a231.52,231.52,0,0,1-24.78-2.61Z" transform="translate(-130.33 -305.18)"></path>
</g>
<g id="swoosh">
<path class="cls-2" d="M257.45,432.24a41.64,41.64,0,0,1,13.11-.32,39.17,39.17,0,0,1,16.89,6.2l0,0c10.55-15,29.11-25,50.24-25,19.75,0,37.34,8.85,48.16,22.28l1-.77c-12.85-17.31-37.44-29-65.67-29-26.92,0-50.54,10.65-63.81,26.65Z" transform="translate(-130.33 -305.18)"></path>
</g>
</svg>
{{ aData.sLabel }}
</div>
</div>
</td>
</tr>

View File

@@ -526,6 +526,7 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:Login:ForgotPwdForm+' => 'iTop can send you an email in which you will find instructions to follow to reset your account.',
'UI:Login:ResetPassword' => 'Send now!',
'UI:Login:ResetPwdFailed' => 'Failed to send an email: %1$s',
'UI:Login:SeparatorOr' => 'Or',
'UI:ResetPwd-Error-WrongLogin' => '\'%1$s\' is not a valid login',
'UI:ResetPwd-Error-NotPossible' => 'external accounts do not allow password reset.',

View File

@@ -509,6 +509,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'UI:Login:ForgotPwdForm+' => 'Vous pouvez demander à saisir un nouveau mot de passe. Vous allez recevoir un email et vous pourrez suivre les instructions.',
'UI:Login:ResetPassword' => 'Envoyer le message',
'UI:Login:ResetPwdFailed' => 'Impossible de vous faire parvenir le message: %1$s',
'UI:Login:SeparatorOr' => 'Ou',
'UI:ResetPwd-Error-WrongLogin' => 'le compte \'%1$s\' est inconnu.',
'UI:ResetPwd-Error-NotPossible' => 'les comptes "externes" ne permettent pas la saisie d\'un mot de passe dans iTop.',

View File

@@ -377,11 +377,11 @@ class ClassLoader
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
$search = $subPath.'\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
$length = $this->prefixLengthsPsr4[$first][$search];
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
return $file;
}
}

View File

@@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'AbstractLoginFSMExtension' => $baseDir . '/application/applicationextension.inc.php',
'AbstractPortalUIExtension' => $baseDir . '/application/applicationextension.inc.php',
'Action' => $baseDir . '/core/action.class.inc.php',
'ActionChecker' => $baseDir . '/core/userrights.class.inc.php',
@@ -90,7 +91,6 @@ return array(
'BulkExportMissingParameterException' => $baseDir . '/core/bulkexport.class.inc.php',
'BulkExportResult' => $baseDir . '/core/bulkexport.class.inc.php',
'BulkExportResultGC' => $baseDir . '/core/bulkexport.class.inc.php',
'CAS_SelfRegister' => $baseDir . '/core/userrights.class.inc.php',
'CLIPage' => $baseDir . '/application/clipage.class.inc.php',
'CMDBChange' => $baseDir . '/core/cmdbchange.class.inc.php',
'CMDBChangeOp' => $baseDir . '/core/cmdbchangeop.class.inc.php',
@@ -133,6 +133,7 @@ return array(
'CheckableExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php',
'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php',
'Combodo\\iTop\\DesignElement' => $baseDir . '/core/designdocument.class.inc.php',
'Combodo\\iTop\\TwigExtension' => $baseDir . '/application/twigextension.class.inc.php',
'Config' => $baseDir . '/core/config.class.inc.php',
'ConfigException' => $baseDir . '/core/config.class.inc.php',
'Console_Getopt' => $vendorDir . '/pear/console_getopt/Console/Getopt.php',
@@ -257,6 +258,15 @@ return array(
'ListExpression' => $baseDir . '/core/oql/expression.class.inc.php',
'ListOqlExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php',
'LogAPI' => $baseDir . '/core/log.class.inc.php',
'LoginBasic' => $baseDir . '/application/loginbasic.class.inc.php',
'LoginBlockData' => $baseDir . '/application/logintwig.class.inc.php',
'LoginDefaultAfter' => $baseDir . '/application/logindefault.class.inc.php',
'LoginDefaultBefore' => $baseDir . '/application/logindefault.class.inc.php',
'LoginExternal' => $baseDir . '/application/loginexternal.class.inc.php',
'LoginForm' => $baseDir . '/application/loginform.class.inc.php',
'LoginTwigContext' => $baseDir . '/application/logintwig.class.inc.php',
'LoginTwigData' => $baseDir . '/application/logintwig.class.inc.php',
'LoginURL' => $baseDir . '/application/loginurl.class.inc.php',
'LoginWebPage' => $baseDir . '/application/loginwebpage.class.inc.php',
'MatchExpression' => $baseDir . '/core/oql/expression.class.inc.php',
'MatchOqlExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php',
@@ -274,6 +284,7 @@ return array(
'MyHelpers' => $baseDir . '/core/MyHelpers.class.inc.php',
'MySQLException' => $baseDir . '/core/cmdbsource.class.inc.php',
'MySQLHasGoneAwayException' => $baseDir . '/core/cmdbsource.class.inc.php',
'MySQLNoTransactionException' => $baseDir . '/core/cmdbsource.class.inc.php',
'MySQLQueryHasNoResultException' => $baseDir . '/core/cmdbsource.class.inc.php',
'NewObjectMenuNode' => $baseDir . '/application/menunode.class.inc.php',
'NewsroomProviderBase' => $baseDir . '/application/newsroomprovider.class.inc.php',
@@ -1885,6 +1896,10 @@ return array(
'iDBObjectSetIterator' => $baseDir . '/core/dbobjectiterator.php',
'iDBObjectURLMaker' => $baseDir . '/application/applicationcontext.class.inc.php',
'iDisplay' => $baseDir . '/core/dbobject.class.php',
'iLoginDataExtension' => $baseDir . '/application/applicationextension.inc.php',
'iLoginExtension' => $baseDir . '/application/applicationextension.inc.php',
'iLoginFSMExtension' => $baseDir . '/application/applicationextension.inc.php',
'iLogoutExtension' => $baseDir . '/application/applicationextension.inc.php',
'iMetricComputer' => $baseDir . '/core/computing.inc.php',
'iNewsroomProvider' => $baseDir . '/application/newsroomprovider.class.inc.php',
'iOnClassInitialization' => $baseDir . '/core/metamodelmodifier.inc.php',

View File

@@ -24,7 +24,7 @@ class ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b
spl_autoload_unregister(array('ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b', 'loadClassLoader'));
$includePaths = require __DIR__ . '/include_paths.php';
$includePaths[] = get_include_path();
array_push($includePaths, get_include_path());
set_include_path(implode(PATH_SEPARATOR, $includePaths));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());

View File

@@ -227,6 +227,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
);
public static $classMap = array (
'AbstractLoginFSMExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'AbstractPortalUIExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'Action' => __DIR__ . '/../..' . '/core/action.class.inc.php',
'ActionChecker' => __DIR__ . '/../..' . '/core/userrights.class.inc.php',
@@ -311,7 +312,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'BulkExportMissingParameterException' => __DIR__ . '/../..' . '/core/bulkexport.class.inc.php',
'BulkExportResult' => __DIR__ . '/../..' . '/core/bulkexport.class.inc.php',
'BulkExportResultGC' => __DIR__ . '/../..' . '/core/bulkexport.class.inc.php',
'CAS_SelfRegister' => __DIR__ . '/../..' . '/core/userrights.class.inc.php',
'CLIPage' => __DIR__ . '/../..' . '/application/clipage.class.inc.php',
'CMDBChange' => __DIR__ . '/../..' . '/core/cmdbchange.class.inc.php',
'CMDBChangeOp' => __DIR__ . '/../..' . '/core/cmdbchangeop.class.inc.php',
@@ -354,6 +354,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'CheckableExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php',
'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
'Combodo\\iTop\\DesignElement' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
'Combodo\\iTop\\TwigExtension' => __DIR__ . '/../..' . '/application/twigextension.class.inc.php',
'Config' => __DIR__ . '/../..' . '/core/config.class.inc.php',
'ConfigException' => __DIR__ . '/../..' . '/core/config.class.inc.php',
'Console_Getopt' => __DIR__ . '/..' . '/pear/console_getopt/Console/Getopt.php',
@@ -478,6 +479,15 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'ListExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php',
'ListOqlExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php',
'LogAPI' => __DIR__ . '/../..' . '/core/log.class.inc.php',
'LoginBasic' => __DIR__ . '/../..' . '/application/loginbasic.class.inc.php',
'LoginBlockData' => __DIR__ . '/../..' . '/application/logintwig.class.inc.php',
'LoginDefaultAfter' => __DIR__ . '/../..' . '/application/logindefault.class.inc.php',
'LoginDefaultBefore' => __DIR__ . '/../..' . '/application/logindefault.class.inc.php',
'LoginExternal' => __DIR__ . '/../..' . '/application/loginexternal.class.inc.php',
'LoginForm' => __DIR__ . '/../..' . '/application/loginform.class.inc.php',
'LoginTwigContext' => __DIR__ . '/../..' . '/application/logintwig.class.inc.php',
'LoginTwigData' => __DIR__ . '/../..' . '/application/logintwig.class.inc.php',
'LoginURL' => __DIR__ . '/../..' . '/application/loginurl.class.inc.php',
'LoginWebPage' => __DIR__ . '/../..' . '/application/loginwebpage.class.inc.php',
'MatchExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php',
'MatchOqlExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php',
@@ -495,6 +505,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'MyHelpers' => __DIR__ . '/../..' . '/core/MyHelpers.class.inc.php',
'MySQLException' => __DIR__ . '/../..' . '/core/cmdbsource.class.inc.php',
'MySQLHasGoneAwayException' => __DIR__ . '/../..' . '/core/cmdbsource.class.inc.php',
'MySQLNoTransactionException' => __DIR__ . '/../..' . '/core/cmdbsource.class.inc.php',
'MySQLQueryHasNoResultException' => __DIR__ . '/../..' . '/core/cmdbsource.class.inc.php',
'NewObjectMenuNode' => __DIR__ . '/../..' . '/application/menunode.class.inc.php',
'NewsroomProviderBase' => __DIR__ . '/../..' . '/application/newsroomprovider.class.inc.php',
@@ -2106,6 +2117,10 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b
'iDBObjectSetIterator' => __DIR__ . '/../..' . '/core/dbobjectiterator.php',
'iDBObjectURLMaker' => __DIR__ . '/../..' . '/application/applicationcontext.class.inc.php',
'iDisplay' => __DIR__ . '/../..' . '/core/dbobject.class.php',
'iLoginDataExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'iLoginExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'iLoginFSMExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'iLogoutExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'iMetricComputer' => __DIR__ . '/../..' . '/core/computing.inc.php',
'iNewsroomProvider' => __DIR__ . '/../..' . '/application/newsroomprovider.class.inc.php',
'iOnClassInitialization' => __DIR__ . '/../..' . '/core/metamodelmodifier.inc.php',

View File

@@ -117,8 +117,7 @@ class ApplicationInstaller
try
{
$fStart = microtime(true);
// Enter in maintenance mode
@touch(APPROOT.'.maintenance');
SetupUtils::EnterMaintenanceMode();
switch ($sStep)
{
case '':
@@ -341,15 +340,13 @@ class ApplicationInstaller
'percentage-completed' => 100,
);
}
// Exit maintenance mode
@unlink(APPROOT.'.maintenance');
SetupUtils::ExitMaintenanceMode();
$fDuration = round(microtime(true) - $fStart, 2);
SetupPage::log_info("##### STEP {$sStep} duration: {$fDuration}s");
}
catch (Exception $e)
{
// Exit maintenance mode
@unlink(APPROOT.'.maintenance');
SetupUtils::ExitMaintenanceMode();
$aResult = array(
'status' => self::ERROR,

View File

@@ -1835,6 +1835,22 @@ EOF
{
return APPROOT.'log/setup-queries-'.strftime('%Y-%m-%d_%H_%M').'.sql';
}
public final static function EnterMaintenanceMode($bCheckBackgroundTask = false)
{
@touch(APPROOT.'.maintenance');
if ($bCheckBackgroundTask)
{
// Assume database is OK but datamodel is not usable
$iCount = CMDBSource::QueryToScalar('SELECT COUNT(*) FROM priv_backgroundtask WHERE running=1');
}
}
public final static function ExitMaintenanceMode()
{
@unlink(APPROOT.'.maintenance');
}
}
/**

View File

@@ -0,0 +1,17 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{% block body %}
{% block login_logo %}
<div id="login-logo">
<a href="{{ sIconUrl }}">
<img title="{{ sVersionShort }}" src="{{ sDisplayIcon }}" alt="logo"/>
</a>
</div>
{% endblock login_logo %}
{% block login_content %}
{% endblock login_content %}
{% block login_footer %}
<div id="login-footer"></div>
{% endblock login_footer %}
{% endblock body %}

View File

@@ -0,0 +1,54 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{% extends "base.html.twig" %}
{% import "macros.twig" as Macro %}
{% block login_content %}
<div id="login">
{% block login_title %}
<div id="login-title">
<h1>{{ 'UI:Login:Welcome'|dict_s }}</h1>
{% if bFailedLogin %}
<p class="hilite">{{ sMessage }}</p>
{% else %}
<p>{{ sMessage }}</p>
{% endif %}
</div>
{% endblock login_title %}
{% block login_form %}
<div id="login-form">
<form id="login_form" method="post">
<table>
{% block login_sso_buttons %}
{{ Macro.BlockData(aPluginFormData, 'login_sso_buttons', '<tr><td class="center" colspan="2">' ~ 'UI:Login:SeparatorOr'|dict_s ~ '</td></tr>') }}
{% endblock login_sso_buttons %}
{% block login_input %}
{{ Macro.BlockData(aPluginFormData, 'login_input') }}
{% endblock login_input %}
{% block login_submit %}
{{ Macro.BlockData(aPluginFormData, 'login_submit') }}
{% endblock login_submit %}
{% block login_additional_controls %}
{{ Macro.BlockData(aPluginFormData, 'login_additional_controls') }}
{% endblock login_additional_controls %}
{% block login_links %}
{{ Macro.BlockData(aPluginFormData, 'login_links') }}
{% endblock login_links %}
</table>
<input type="hidden" id="login_mode" name="login_mode" value="form" />
<input type="hidden" id="login_op" name="loginop" value="login" />
{% for sName, sValue in aPreviousPostedVars %}
<input type="hidden" name="{{ sName }}" value="{{ sValue }}" />
{% endfor %}
</form>
</div>
{% endblock login_form %}
</div>
{% endblock login_content %}
{% block login_footer %}
{{ Macro.BlockData(aPluginFormData, 'login_footer') }}
{% endblock login_footer %}

View File

@@ -0,0 +1,19 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
<tr>
<td class="v-spacer" style="text-align:right">
<label for="user">{{ 'UI:Login:UserNamePrompt'|dict_s }}</label>
</td>
<td style="text-align:left">
<input id="user" type="text" name="auth_user" value="{{ aData.sAuthUser }}" />
</td>
</tr>
<tr>
<td style="text-align:right">
<label for="pwd">{{ 'UI:Login:PasswordPrompt'|dict_s }}</label>
</td>
<td style="text-align:left">
<input id="pwd" type="password" name="auth_pwd" value="{{ aData.sAuthPwd }}" />
</td>
</tr>

View File

@@ -0,0 +1,10 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{% if aData.bEnableResetPassword %}
<tr>
<td colspan="2" class="center v-spacer">
<a href="{{ aData.sResetPasswordUrl }}" target="_blank">{{ 'UI:Login:ForgotPwd'|dict_s }}</a>
</td>
</tr>
{% endif %}

View File

@@ -0,0 +1,10 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
<tr>
<td colspan="2" class="center v-spacer">
<span class="btn_border">
<input type="submit" value="{{ 'UI:Button:Login'|dict_s }}" />
</span>
</td>
</tr>

View File

@@ -0,0 +1,13 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{% extends "base.html.twig" %}
{% block login_content %}
<div id="login">
<div id="login-title">
<h1>{{ sTitle }}</h1>
<p><a href="{{ sUrl }}">{{ sMessage }}</a></p>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,17 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{% macro BlockData(aPluginFormData, sBlockName, sText = '') %}
{% set bHaveBlock = 'false' %}
{% for oLoginData in aPluginFormData if oLoginData.GetBlockData(sBlockName) %}
{% set oBlockData = oLoginData.GetBlockData(sBlockName) %}
{% set bHaveBlock = 'true' %}
{% set sTwig = oBlockData.GetTwig() %}
{% set aData = oBlockData.GetData() %}
{% set sContent = include(sTwig ? sTwig : '') %}
{{ sContent|raw }}
{% endfor %}
{% if bHaveBlock == 'true' %}
{{ sText|raw }}
{% endif %}
{% endmacro %}

View File

@@ -0,0 +1,22 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{% extends "base.html.twig" %}
{% block login_content %}
<div id="login">
<h1>{{ 'UI:Login:ChangeYourPassword'|dict_s }}</h1>
{% if bFailedLogin %}
<p class="hilite">{{ 'UI:Login:IncorrectOldPassword'|dict_s }}</p>
{% endif %}
<form method="post">
<table>
<tr><td style="text-align:right"><label for="old_pwd">{{ 'UI:Login:OldPasswordPrompt'|dict_s }}:</label></td><td style="text-align:left"><input type="password" id="old_pwd" name="old_pwd" value="" /></td></tr>
<tr><td style="text-align:right"><label for="new_pwd">{{ 'UI:Login:NewPasswordPrompt'|dict_s }}:</label></td><td style="text-align:left"><input type="password" id="new_pwd" name="new_pwd" value="" /></td></tr>
<tr><td style="text-align:right"><label for="retype_new_pwd">{{ 'UI:Login:RetypeNewPasswordPrompt'|dict_s }}:</label></td><td style="text-align:left"><input type="password" id="retype_new_pwd" name="retype_new_pwd" value="" /></td></tr>
<tr><td colspan="2" class="center v-spacer"><span class="btn_border"><input type="button" onClick="GoBack();" value="{{ 'UI:Button:Cancel'|dict_s }}" /></span>&nbsp;&nbsp;<span class="btn_border"><input type="submit" onClick="return DoCheckPwd();" value="{{ 'UI:Button:ChangePassword'|dict_s }}" /></span></td></tr>
</table>
<input type="hidden" name="loginop" value="do_change_pwd" />
</form>
</div>
{% endblock %}

View File

@@ -0,0 +1,17 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
function GoBack()
{
window.history.back();
}
function DoCheckPwd()
{
if ($('#new_pwd').val() != $('#retype_new_pwd').val())
{
alert('{{ 'UI:Login:RetypePwdDoesNotMatch'|dict_s }}');
return false;
}
return true;
}

View File

@@ -0,0 +1,21 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{% extends "base.html.twig" %}
{% block login_content %}
<div id="login">
<h1>{{ 'UI:Login:ForgotPwdForm'|dict_s }}</h1>
<p>{{ 'UI:Login:ForgotPwdForm+'|dict_s }}</p>
{% if bFailedToReset %}
<p class="hilite">{{ 'UI:Login:ResetPwdFailed'|dict_format(sFailureReason) }}</p>
{% endif %}
<form method="post">
<table>
<tr><td colspan="2" class="center"><label for="user">{{ 'UI:Login:UserNamePrompt'|dict_s }}:</label><input id="user" type="text" name="auth_user" value="{{ sAuthUser }}" /></td></tr>
<tr><td colspan="2" class="center v-spacer"><span class="btn_border"><input type="button" onClick="window.close();" value="{{ 'UI:Button:Cancel'|dict_s }}" /></span>&nbsp;&nbsp;<span class="btn_border"><input type="submit" value="{{ 'UI:Login:ResetPassword'|dict_s }}" /></span></td></tr>
</table>
<input type="hidden" name="loginop" value="forgot_pwd_go" />
</form>
</div>
{% endblock %}

View File

@@ -0,0 +1,4 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
$("#user").focus();

View File

@@ -0,0 +1,18 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{% extends "base.html.twig" %}
{% block login_content %}
<div id="login">
<h1>{{ 'UI:Login:ForgotPwdForm'|dict_s }}</h1>
<p>{{ 'UI:ResetPwd-EmailSent'|dict_s }}</p>
<form method="post">
<table>
<tr>
<td colspan="2" class="center v-spacer"><input type="button" onClick="window.close();" value="{{ 'UI:Button:Done'|dict_s }}"/></td>
</tr>
</table>
</form>
</div>
{% endblock %}

View File

@@ -0,0 +1,18 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{% extends "base.html.twig" %}
{% block login_content %}
<div id="login">
<h1>{{ 'UI:ResetPwd-Title'|dict_s }}</h1>
{% if bNoUser %}
<p>{{ 'UI:ResetPwd-Error-WrongLogin'|dict_format(sAuthUser) }}</p>
{% elseif bBadToken %}
<p>{{ 'UI:ResetPwd-Error-InvalidToken'|dict_s }}</p>
{% else %}
<p>{{ 'UI:ResetPwd-Ready'|dict_s }}</p>
<p><a href="{{ sUrl }}">{{ 'UI:ResetPwd-Login'|dict_s }}</a></p>
{% endif %}
</div>
{% endblock %}

View File

@@ -0,0 +1,27 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{% extends "base.html.twig" %}
{% block login_content %}
<div id="login">
<h1>{{ 'UI:ResetPwd-Title'|dict_s }}</h1>
{% if bNoUser %}
<p>{{ 'UI:ResetPwd-Error-WrongLogin'|dict_format(sAuthUser) }}</p>
{% elseif bBadToken %}
<p>{{ 'UI:ResetPwd-Error-InvalidToken'|dict_s }}</p>
{% else %}
<p>{{ 'UI:ResetPwd-Error-EnterPassword'|dict_format(sUserName) }}</p>
<form method="post">
<table>
<tr><td style="text-align:right"><label for="new_pwd">{{ 'UI:Login:NewPasswordPrompt'|dict_s }}:</label></td><td style="text-align:left"><input type="password" id="new_pwd" name="new_pwd" value="" /></td></tr>
<tr><td style="text-align:right"><label for="retype_new_pwd">{{ 'UI:Login:RetypeNewPasswordPrompt'|dict_s }}:</label></td><td style="text-align:left"><input type="password" id="retype_new_pwd" name="retype_new_pwd" value="" /></td></tr>
<tr><td colspan="2" class="center v-spacer"><span class="btn_border"><input type="submit" onClick="return DoCheckPwd();" value="{{ 'UI:Button:ChangePassword'|dict_s }}" /></span></td></tr>
</table>
<input type="hidden" name="loginop" value="do_reset_pwd" />
<input type="hidden" name="auth_user" value="{{ sAuthUser }}" />
<input type="hidden" name="token" value="{{ sToken }}" />
</form>
{% endif %}
</div
{% endblock %}

View File

@@ -0,0 +1,12 @@
{# @copyright Copyright (C) 2010-2019 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
function DoCheckPwd()
{
if ($('#new_pwd').val() != $('#retype_new_pwd').val())
{
alert('{{ 'UI:Login:RetypePwdDoesNotMatch'|dict_s }}');
return false;
}
return true;
}