Newer
Older
<?php
/**
* Piwik - Open source web analytics
mattab
a validé
* @package Login
mattab
a validé
namespace Piwik\Plugins\Login;
use Exception;
use Piwik\Common;
use Piwik\Cookie;
use Piwik\IP;
use Piwik\Mail;
use Piwik\Nonce;
use Piwik\Piwik;
use Piwik\Plugins\UsersManager\API;
use Piwik\Plugins\UsersManager\UsersManager;
use Piwik\ProxyHttp;
use Piwik\QuickForm2;
use Piwik\Session;
use Piwik\SettingsPiwik;
require_once PIWIK_INCLUDE_PATH . '/core/Config.php';
* Login controller
mattab
a validé
* @package Login
class Controller extends \Piwik\Plugin\Controller
/**
* Generate hash on user info and password
*
* @param string $password
* @return string
*/
private function generateHash($userInfo, $password)
{
// mitigate rainbow table attack
$passwordLen = strlen($password) / 2;
$userInfo . substr($password, 0, $passwordLen)
. SettingsPiwik::getSalt() . substr($password, $passwordLen)
);
return $hash;
}
/**
* Default action
*
* @param none
* @return void
*/
function index()
{
}
/**
* Login form
*
* @param string $messageNoAccess Access error message
* @param bool $infoMessage
* @internal param string $currentUrl Current URL
* @return void
*/
function login($messageNoAccess = null, $infoMessage = false)
{
self::checkForceSslLogin();
mattab
a validé
$form = new FormLogin();
if ($form->validate()) {
$nonce = $form->getSubmitValue('form_nonce');
mattab
a validé
if (Nonce::verifyNonce('Login.login', $nonce)) {
$login = $form->getSubmitValue('form_login');
$password = $form->getSubmitValue('form_password');
$rememberMe = $form->getSubmitValue('form_rememberme') == '1';
$md5Password = md5($password);
try {
$this->authenticateAndRedirect($login, $md5Password, $rememberMe);
} catch (Exception $e) {
$messageNoAccess = $e->getMessage();
}
} else {
$messageNoAccess = $this->getMessageExceptionNoAccess();
}
}
$view = new View('@Login/login');
$view->AccessErrorString = $messageNoAccess;
$view->infoMessage = nl2br($infoMessage);
$view->addForm($form);
$this->configureView($view);
self::setHostValidationVariablesView($view);
}
/**
* Configure common view properties
*
* @param View $view
*/
private function configureView($view)
{
$this->setBasicVariablesView($view);
$view->linkTitle = Piwik::getRandomTitle();
$view->forceSslLogin = Config::getInstance()->General['force_ssl_login'];
// crsf token: don't trust the submitted value; generate/fetch it from session data
mattab
a validé
$view->nonce = Nonce::getNonce('Login.login');
}
/**
* Form-less login
* @see how to use it on http://piwik.org/faq/how-to/#faq_30
* @throws Exception
* @return void
*/
function logme()
{
self::checkForceSslLogin();
$password = Common::getRequestVar('password', null, 'string');
if (strlen($password) != 32) {
throw new Exception(Piwik::translate('Login_ExceptionPasswordMD5HashExpected'));
$login = Common::getRequestVar('login', null, 'string');
if ($login == Config::getInstance()->superuser['login']) {
throw new Exception(Piwik::translate('Login_ExceptionInvalidSuperUserAuthenticationMethod', array("logme")));
}
$currentUrl = 'index.php';
if (($idSite = Common::getRequestVar('idSite', false, 'int')) !== false) {
$currentUrl .= '?idSite=' . $idSite;
}
$urlToRedirect = Common::getRequestVar('url', $currentUrl, 'string');
$urlToRedirect = Common::unsanitizeInputValue($urlToRedirect);
$this->authenticateAndRedirect($login, $password, false, $urlToRedirect);
}
/**
* Authenticate user and password. Redirect if successful.
*
* @param string $login user name
* @param string $md5Password md5 hash of password
* @param bool $rememberMe Remember me?
* @param string $urlToRedirect URL to redirect to, if successfully authenticated
* @return string failure message if unable to authenticate
*/
protected function authenticateAndRedirect($login, $md5Password, $rememberMe, $urlToRedirect = 'index.php')
{
mattab
a validé
Nonce::discardNonce('Login.login');
\Piwik\Registry::get('auth')->initSession($login, $md5Password, $rememberMe);
Url::redirectToUrl($urlToRedirect);
}
protected function getMessageExceptionNoAccess()
{
$message = Piwik::translate('Login_InvalidNonceOrHeadersOrReferrer', array('<a href="?module=Proxy&action=redirect&url=' . urlencode('http://piwik.org/faq/how-to-install/#faq_98') . '" target="_blank">', '</a>'));
// Should mention trusted_hosts or link to FAQ
return $message;
}
/**
* Reset password action. Stores new password as hash and sends email
* to confirm use.
*
* @param none
* @return void
*/
function resetPassword()
{
self::checkForceSslLogin();
$infoMessage = null;
$formErrors = null;
mattab
a validé
$form = new FormResetPassword();
if ($form->validate()) {
$nonce = $form->getSubmitValue('form_nonce');
mattab
a validé
if (Nonce::verifyNonce('Login.login', $nonce)) {
$formErrors = $this->resetPasswordFirstStep($form);
if (empty($formErrors)) {
$infoMessage = Piwik::translate('Login_ConfirmationLinkSent');
}
} else {
$formErrors = array($this->getMessageExceptionNoAccess());
}
} else {
// if invalid, display error
$formData = $form->getFormData();
$formErrors = $formData['errors'];
}
$view = new View('@Login/resetPassword');
$view->infoMessage = $infoMessage;
$view->formErrors = $formErrors;
}
/**
* Saves password reset info and sends confirmation email.
*
* @param QuickForm2 $form
* @return array Error message(s) if an error occurs.
*/
private function resetPasswordFirstStep($form)
{
$loginMail = $form->getSubmitValue('form_login');
$token = $form->getSubmitValue('form_token');
$password = $form->getSubmitValue('form_password');
// check the password
try {
mattab
a validé
UsersManager::checkPassword($password);
} catch (Exception $ex) {
return array($ex->getMessage());
}
// get the user's login
if ($loginMail === 'anonymous') {
return array(Piwik::translate('Login_InvalidUsernameEmail'));
}
$user = self::getUserInformation($loginMail);
if ($user === null) {
return array(Piwik::translate('Login_InvalidUsernameEmail'));
}
$login = $user['login'];
// if valid, store password information in options table, then...
mattab
a validé
Login::savePasswordResetInfo($login, $password);
// ... send email with confirmation link
try {
$this->sendEmailConfirmationLink($user);
} catch (Exception $ex) {
// remove password reset info
mattab
a validé
Login::removePasswordResetInfo($login);
return array($ex->getMessage() . Piwik::translate('Login_ContactAdmin'));
}
return null;
}
/**
* Sends email confirmation link for a password reset request.
*
* @param array $user User info for the requested password reset.
*/
private function sendEmailConfirmationLink($user)
{
$login = $user['login'];
$email = $user['email'];
// construct a password reset token from user information
$resetToken = self::generatePasswordResetToken($user);
$ip = IP::getIpFromHeader();
$url = Url::getCurrentUrlWithoutQueryString()
. "?module=Login&action=confirmResetPassword&login=" . urlencode($login)
. "&resetToken=" . urlencode($resetToken);
// send email with new password
$mail = new Mail();
$mail->addTo($email, $login);
$mail->setSubject(Piwik::translate('Login_MailTopicPasswordChange'));
$bodyText = str_replace(
sprintf(Piwik::translate('Login_MailPasswordChangeBody'), $login, $ip, $url)
$mail->setBodyText($bodyText);
$fromEmailName = Config::getInstance()->General['login_password_recovery_email_name'];
$fromEmailAddress = Config::getInstance()->General['login_password_recovery_email_address'];
$mail->setFrom($fromEmailAddress, $fromEmailName);
@$mail->send();
}
/**
* Password reset confirmation action. Finishes the password reset process.
* Users visit this action from a link supplied in an email.
*/
public function confirmResetPassword()
{
$errorMessage = null;
$login = Common::getRequestVar('login', '');
$resetToken = Common::getRequestVar('resetToken', '');
try {
// get password reset info & user info
$user = self::getUserInformation($login);
if ($user === null) {
throw new Exception(Piwik::translate('Login_InvalidUsernameEmail'));
}
// check that the reset token is valid
mattab
a validé
$resetPassword = Login::getPasswordToResetTo($login);
if ($resetPassword === false || !self::isValidToken($resetToken, $user)) {
throw new Exception(Piwik::translate('Login_InvalidOrExpiredToken'));
}
// reset password of user
$this->setNewUserPassword($user, $resetPassword);
} catch (Exception $ex) {
$errorMessage = $ex->getMessage();
}
if (is_null($errorMessage)) // if success, show login w/ success message
{
$this->redirectToIndex('Login', 'resetPasswordSuccess');
return;
} else {
// show login page w/ error. this will keep the token in the URL
}
}
/**
* Sets the password for a user.
*
* @param array $user User info.
* @param string $passwordHash The hashed password to use.
*/
private function setNewUserPassword($user, $passwordHash)
{
if (strlen($passwordHash) !== 32) // sanity check
{
throw new Exception(
"setNewUserPassword called w/ incorrect password hash. Something has gone terribly wrong.");
}
if ($user['email'] == Piwik::getSuperUserEmail()) {
$user['password'] = $passwordHash;
Config::getInstance()->superuser = $user;
Config::getInstance()->forceSave();
} else {
mattab
a validé
API::getInstance()->updateUser(
$user['login'], $passwordHash, $email = false, $alias = false, $isPasswordHashed = true);
}
}
/**
* The action used after a password is successfully reset. Displays the login
* screen with an extra message. A separate action is used instead of returning
* the HTML in confirmResetPassword so the resetToken won't be in the URL.
*/
public function resetPasswordSuccess()
{
return $this->login($errorMessage = null, $infoMessage = Piwik::translate('Login_PasswordChanged'));
}
/**
* Get user information
*
* @param string $loginMail user login or email address
* @return array ("login" => '...', "email" => '...', "password" => '...') or null, if user not found
*/
protected function getUserInformation($loginMail)
{
Piwik::setUserIsSuperUser();
$user = null;
if ($loginMail == Piwik::getSuperUserEmail()
|| $loginMail == Config::getInstance()->superuser['login']
) {
$user = array(
'login' => Config::getInstance()->superuser['login'],
'email' => Piwik::getSuperUserEmail(),
'password' => Config::getInstance()->superuser['password'],
mattab
a validé
} else if (API::getInstance()->userExists($loginMail)) {
$user = API::getInstance()->getUser($loginMail);
} else if (API::getInstance()->userEmailExists($loginMail)) {
$user = API::getInstance()->getUserByEmail($loginMail);
}
return $user;
}
/**
* Generate a password reset token. Expires in (roughly) 24 hours.
*
* @param array $user user information
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
* @param int $timestamp Unix timestamp
* @return string generated token
*/
protected function generatePasswordResetToken($user, $timestamp = null)
{
/*
* Piwik does not store the generated password reset token.
* This avoids a database schema change and SQL queries to store, retrieve, and purge (expired) tokens.
*/
if (!$timestamp) {
$timestamp = time() + 24 * 60 * 60; /* +24 hrs */
}
$expiry = strftime('%Y%m%d%H', $timestamp);
$token = $this->generateHash(
$expiry . $user['login'] . $user['email'],
$user['password']
);
return $token;
}
/**
* Validate token.
*
* @param string $token
* @param array $user user information
* @return bool true if valid, false otherwise
*/
protected function isValidToken($token, $user)
{
$now = time();
// token valid for 24 hrs (give or take, due to the coarse granularity in our strftime format string)
for ($i = 0; $i <= 24; $i++) {
$generatedToken = self::generatePasswordResetToken($user, $now + $i * 60 * 60);
if ($generatedToken === $token) {
return true;
}
}
// fails if token is invalid, expired, password already changed, other user information has changed, ...
return false;
}
/**
* Clear session information
*
* @param none
* @return void
*/
static public function clearSession()
{
$authCookieName = Config::getInstance()->General['login_cookie_name'];
$cookie = new Cookie($authCookieName);
$cookie->delete();
Session::expireSessionCookie();
}
/**
* Logout current user
*
* @param none
* @return void
*/
public function logout()
{
self::clearSession();
$logoutUrl = @Config::getInstance()->General['login_logout_url'];
if (empty($logoutUrl)) {
Piwik::redirectToModule('CoreHome');
} else {
Url::redirectToUrl($logoutUrl);
}
}
/**
* Check force_ssl_login and redirect if connection isn't secure and not using a reverse proxy
*
* @param none
* @return void
*/
protected function checkForceSslLogin()
{
$forceSslLogin = Config::getInstance()->General['force_ssl_login'];
if ($forceSslLogin
) {
$url = 'https://'
. Url::getCurrentHost()
. Url::getCurrentScriptName()
. Url::getCurrentQueryString();
Url::redirectToUrl($url);
}
}