Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Zhang <peter@innocraft.com>2022-07-08 14:24:54 +0300
committerGitHub <noreply@github.com>2022-07-08 14:24:54 +0300
commit5df7397b4276a8f23e5537b7ba84394f4238dbed (patch)
treef38e8706090a4fb6d687415a89b29377d9bfda11 /plugins/Login
parent49adc21534740eeb62a700562cdde095c7fa9589 (diff)
update invite user (#19366)4.11.0-rc2
* update invite update invite * update tests and VUE error update tests and VUE error * built vue files * update ui update ui * update vue update vue * update vue update vue * built vue files * Update UsersManager_spec.js update tests * update clean update clean * Update UsersManager_spec.js update success * Update UsersManager_spec.js remove popup error * update tests order update tests order * Update UsersManager_next_click.png updateui * revert screenshot revert screenshot * delete if user if declined delete use if declined add privacy split templated into 3 parts * remove decline remove decline * add invited by column add invited by column * Revert "add invited by column" This reverts commit e2358cb493684dc26fdf9a436d00a29981365861. * Update UserRepository.php add view user display * add email tests and update admin email add email tests and update admin email * update php cs update php cs * add expire task and default setting add expire task and default setting * add api tests add api tests * add filter add filter * update ui tests update ui tests * built vue files * update tests update tests * update signup and tests update signup and tests * update tests update tests * update screenshots update screenshots * update screenshots update screenshots * update ui update ui * update typo update typo * built vue files * Update core/Updates/4.12.0-b1.php Co-authored-by: Ben Burgess <88810029+bx80@users.noreply.github.com> * Update plugins/UsersManager/API.php Co-authored-by: Ben Burgess <88810029+bx80@users.noreply.github.com> * Update plugins/UsersManager/UsersManager.php Co-authored-by: Ben Burgess <88810029+bx80@users.noreply.github.com> * Update plugins/UsersManager/lang/en.json Co-authored-by: Ben Burgess <88810029+bx80@users.noreply.github.com> * Update config/global.ini.php Co-authored-by: Ben Burgess <88810029+bx80@users.noreply.github.com> * update email date update email date * update typo update typo * update UI update UI * Updated UI screenshot * Update plugins/Login/lang/en.json Co-authored-by: Stefan Giehl <stefan@matomo.org> * Update plugins/Login/templates/invitationDecline.twig Co-authored-by: Stefan Giehl <stefan@matomo.org> * update some feedbacks update some feedbacks * built vue files * update feedbacks update feedbacks * update filter update filter * pending your can't reset password pending your can't reset password * fix php cs fix php cs * update column update column * built vue files * set up terms in tests set up terms in tests * Update PendingUsers.php setup website * update tests and broken template update tests * update password update password * update screenshots update screenshots * add delete to admin add delete to admin * update invite success notification update invite success notification * add tests add tests * built vue files * update tests update tests * update UI and checkbox update UI and checkbox * Update Invite_spec.js show error screen * update tests update tests * update lang update lang * Update OmniFixture-dump.sql revert OmniFixture * update wording update wording * update version update version * fix change column fix change column * Update UsersManagerTest.php update tests * Update Model.php update tests * Update Model.php update tests * remove fixes remove fixes * Revert "remove fixes" This reverts commit f8fe33706513a32bf3386bc6b52800d68ec58562. * update tests update tests * revert password reset revert password reset * update tests update tests * update resend update resend * update tests update tests * Update PasswordResetter.php update tests * correct pending user from last time correct pending user from last time * apply some fixes /improvements * Apply suggestions from code review Co-authored-by: Ben Burgess <88810029+bx80@users.noreply.github.com> * Allow composer plugins for dev dependency codesniffer to fix travis builds. (#19468) Co-authored-by: peterhashair <peterhashair@users.noreply.github.com> Co-authored-by: Ben Burgess <88810029+bx80@users.noreply.github.com> Co-authored-by: Ben <ben.burgess@innocraft.com> Co-authored-by: Stefan Giehl <stefan@matomo.org> Co-authored-by: dizzy <diosmosis@users.noreply.github.com>
Diffstat (limited to 'plugins/Login')
-rw-r--r--plugins/Login/Controller.php257
-rw-r--r--plugins/Login/PasswordResetter.php9
-rw-r--r--plugins/Login/lang/en.json16
-rw-r--r--plugins/Login/templates/invitation.twig136
-rw-r--r--plugins/Login/templates/invitationDecline.twig23
-rw-r--r--plugins/Login/templates/invitationDeclineSuccess.twig11
-rw-r--r--plugins/Login/templates/inviteLayout.twig46
-rw-r--r--plugins/Login/tests/Fixtures/PendingUsers.php38
-rw-r--r--plugins/Login/tests/Integration/LoginTest.php14
-rw-r--r--plugins/Login/tests/Integration/PasswordResetterTest.php21
-rw-r--r--plugins/Login/tests/UI/Decline_spec.js18
-rw-r--r--plugins/Login/tests/UI/Invite_spec.js21
-rw-r--r--plugins/Login/tests/UI/expected-screenshots/Decline_default.png4
-rw-r--r--plugins/Login/tests/UI/expected-screenshots/Decline_success.png3
-rw-r--r--plugins/Login/tests/UI/expected-screenshots/Invite_error.png4
-rw-r--r--plugins/Login/tests/UI/expected-screenshots/Invite_set_password.png4
-rw-r--r--plugins/Login/tests/UI/expected-screenshots/Invite_wrong_password.png4
17 files changed, 366 insertions, 263 deletions
diff --git a/plugins/Login/Controller.php b/plugins/Login/Controller.php
index 98b0dd23e9..08c657827a 100644
--- a/plugins/Login/Controller.php
+++ b/plugins/Login/Controller.php
@@ -1,4 +1,5 @@
<?php
+
/**
* Matomo - free/libre analytics platform
*
@@ -6,6 +7,7 @@
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
+
namespace Piwik\Plugins\Login;
use Exception;
@@ -13,12 +15,14 @@ use Piwik\Auth\Password;
use Piwik\Common;
use Piwik\Config;
use Piwik\Container\StaticContainer;
+use Piwik\Date;
use Piwik\Log;
use Piwik\Nonce;
use Piwik\Piwik;
use Piwik\Plugins\CoreAdminHome\Emails\UserAcceptInvitationEmail;
use Piwik\Plugins\CoreAdminHome\Emails\UserDeclinedInvitationEmail;
use Piwik\Plugins\Login\Security\BruteForceDetection;
+use Piwik\Plugins\PrivacyManager\SystemSettings;
use Piwik\Plugins\UsersManager\Model as UsersModel;
use Piwik\Plugins\UsersManager\UsersManager;
use Piwik\QuickForm2;
@@ -70,19 +74,19 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
* Constructor.
*
* @param PasswordResetter $passwordResetter
- * @param AuthInterface $auth
+ * @param \Piwik\Auth $auth
* @param SessionInitializer $sessionInitializer
* @param PasswordVerifier $passwordVerify
* @param BruteForceDetection $bruteForceDetection
* @param SystemSettings $systemSettings
*/
public function __construct(
- $passwordResetter = null,
- $auth = null,
- $sessionInitializer = null,
- $passwordVerify = null,
- $bruteForceDetection = null,
- $systemSettings = null
+ $passwordResetter = null,
+ $auth = null,
+ $sessionInitializer = null,
+ $passwordVerify = null,
+ $bruteForceDetection = null,
+ $systemSettings = null
) {
parent::__construct();
@@ -122,7 +126,7 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
*
* @return string
*/
- function index()
+ public function index()
{
return $this->login();
}
@@ -135,7 +139,7 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
* @return string
* @internal param string $currentUrl Current URL
*/
- function login($messageNoAccess = null, $infoMessage = false)
+ public function login($messageNoAccess = null, $infoMessage = false)
{
$form = new FormLogin();
if ($form->validate()) {
@@ -231,20 +235,20 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
}
}
- return $this->renderTemplate('@Login/confirmPassword', array(
+ return $this->renderTemplate('@Login/confirmPassword', [
'nonce' => Nonce::getNonce($nonceKey),
'AccessErrorString' => $messageNoAccess,
'loginPlugin' => Piwik::getLoginPluginName(),
- ));
+ ]);
}
/**
* Form-less login
- * @see how to use it on http://piwik.org/faq/how-to/#faq_30
+ * @see how to use it on https://matomo.org/faq/how-to/faq_30
* @throws Exception
* @return void
*/
- function logme()
+ public function logme()
{
if (Config::getInstance()->General['login_allow_logme'] == 0) {
throw new Exception('This functionality has been disabled in config');
@@ -254,8 +258,9 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
$login = Common::getRequestVar('login', null, 'string');
if (Piwik::hasTheUserSuperUserAccess($login)) {
- throw new Exception(Piwik::translate('Login_ExceptionInvalidSuperUserAccessAuthenticationMethod',
- array("logme")));
+ throw new Exception(
+ Piwik::translate('Login_ExceptionInvalidSuperUserAccessAuthenticationMethod', ["logme"])
+ );
}
$currentUrl = 'index.php';
@@ -274,10 +279,10 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
{
Piwik::checkUserHasSuperUserAccess();
- return $this->renderTemplate('bruteForceLog', array(
+ return $this->renderTemplate('bruteForceLog', [
'blockedIps' => $this->bruteForceDetection->getCurrentlyBlockedIps(),
'blacklistedIps' => $this->systemSettings->blacklistedBruteForceIps->getValue()
- ));
+ ]);
}
/**
@@ -289,14 +294,14 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
public function ajaxNoAccess($errorMessage)
{
return sprintf(
- '<div class="alert alert-danger">
+ '<div class="alert alert-danger">
<p><strong>%s:</strong> %s</p>
<p><a href="%s">%s</a></p>
</div>',
- Piwik::translate('General_Error'),
- htmlentities($errorMessage, Common::HTML_ENCODING_QUOTE_STYLE, 'UTF-8', $doubleEncode = false),
- 'index.php?module=' . Piwik::getLoginPluginName(),
- Piwik::translate('Login_LogIn')
+ Piwik::translate('General_Error'),
+ htmlentities($errorMessage, Common::HTML_ENCODING_QUOTE_STYLE, 'UTF-8', $doubleEncode = false),
+ 'index.php?module=' . Piwik::getLoginPluginName(),
+ Piwik::translate('Login_LogIn')
);
}
@@ -345,7 +350,8 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
$currentHost = explode(':', $currentHost, 2)[0];
// we only redirect to a trusted host
- if (!empty($host) && !empty($currentHost) && $host == $currentHost && Url::isValidHost($host)
+ if (
+ !empty($host) && !empty($currentHost) && $host == $currentHost && Url::isValidHost($host)
) {
$urlToRedirect = $redirect;
}
@@ -362,9 +368,8 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
/**
* Reset password action. Stores new password as hash and sends email
* to confirm use.
- *
*/
- function resetPassword()
+ public function resetPassword()
{
$infoMessage = null;
$formErrors = null;
@@ -379,7 +384,7 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
$infoMessage = Piwik::translate('Login_ConfirmationLinkSent');
}
} else {
- $formErrors = array($errorMessage);
+ $formErrors = [$errorMessage];
}
} else {
// if invalid, display error
@@ -410,7 +415,7 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
} catch (Exception $ex) {
Log::debug($ex);
- return array($ex->getMessage());
+ return [$ex->getMessage()];
}
return null;
@@ -444,15 +449,21 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
return $this->login($errorMessage);
}
- if (!empty($_POST['nonce'])
- && !empty($_POST['mtmpasswordconfirm'])
- && !empty($resetToken)
- && !empty($login)
- && !empty($passwordHash)
- && empty($errorMessage)) {
+ if (
+ !empty($_POST['nonce'])
+ && !empty($_POST['mtmpasswordconfirm'])
+ && !empty($resetToken)
+ && !empty($login)
+ && !empty($passwordHash)
+ && empty($errorMessage)
+ ) {
Nonce::checkNonce(self::NONCE_CONFIRMRESETPASSWORD, $_POST['nonce']);
- if ($this->passwordResetter->doesResetPasswordHashMatchesPassword($_POST['mtmpasswordconfirm'],
- $passwordHash)) {
+ if (
+ $this->passwordResetter->doesResetPasswordHashMatchesPassword(
+ $_POST['mtmpasswordconfirm'],
+ $passwordHash
+ )
+ ) {
$this->passwordResetter->setHashedPasswordForLogin($login, $passwordHash);
return $this->resetPasswordSuccess();
} else {
@@ -462,10 +473,10 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
$nonce = Nonce::getNonce(self::NONCE_CONFIRMRESETPASSWORD);
- return $this->renderTemplateAs('confirmResetPassword', array(
+ return $this->renderTemplateAs('confirmResetPassword', [
'nonce' => $nonce,
'errorMessage' => $errorMessage
- ), 'basic');
+ ], 'basic');
}
/**
@@ -475,7 +486,7 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
*/
public function resetPasswordSuccess()
{
- $_POST = array(); // prevent showing error message username and password is missing
+ $_POST = []; // prevent showing error message username and password is missing
return $this->login($errorMessage = null, $infoMessage = Piwik::translate('Login_PasswordChanged'));
}
@@ -499,7 +510,7 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
*/
public function logout()
{
- Piwik::postEvent('Login.logout', array(Piwik::getCurrentUserLogin()));
+ Piwik::postEvent('Login.logout', [Piwik::getCurrentUserLogin()]);
self::clearSession();
@@ -524,79 +535,99 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
$token = Common::getRequestVar('token', null, 'string');
$form = Common::getRequestVar('invitation_form', false, 'string');
- //check token is valid
- $user = $model->getUserByTokenAuth($token);
- if ($user['invite_status'] !== 'pending') {
- throw new Exception(Piwik::translate('Login_InvalidOrExpiredToken'));
- }
+ $settings = new SystemSettings();
+ $termsAndConditionUrl = $settings->termsAndConditionUrl->getValue();
+ $privacyPolicyUrl = $settings->privacyPolicyUrl->getValue();
+ $user = $model->getUserByInviteToken($token);
- //if user not match the invite user
+ // if no user matches the invite token
if (!$user) {
throw new Exception(Piwik::translate('Login_InvalidUsernameEmail'));
}
- //if form is blank
+ if (!empty($user['invite_expired_at']) && Date::factory($user['invite_expired_at'])->isEarlier(Date::now())) {
+ throw new Exception(Piwik::translate('Login_InvalidOrExpiredToken'));
+ }
+
+ // if form was sent
if (!empty($form)) {
$error = null;
$password = Common::getRequestVar('password', false, 'string');
$passwordConfirmation = Common::getRequestVar('passwordConfirmation', false, 'string');
- $terms = Common::getRequestVar('terms', false, 'string');
+ $conditionCheck = Common::getRequestVar('conditionCheck', false, 'string');
+
if (!$password) {
$error = Piwik::translate('Login_PasswordRequired');
}
- //not accept terms
- if (!$terms) {
- $error = Piwik::translate('Login_TermsRequired');
+ // check if terms accepted and privacy
+ if (!$conditionCheck && ($privacyPolicyUrl || $termsAndConditionUrl)) {
+ if ($privacyPolicyUrl && $termsAndConditionUrl) {
+ $error = Piwik::translate('Login_AcceptPrivacyPolicyAndTermsAndCondition');
+ } elseif ($privacyPolicyUrl) {
+ $error = Piwik::translate('Login_AcceptPrivacyPolicy');
+ } elseif ($termsAndConditionUrl) {
+ $error = Piwik::translate('Login_AcceptTermsAndCondition');
+ }
}
- //valid password
+ // validate password
if (!UsersManager::isValidPasswordString($password)) {
- $error = Piwik::translate('UsersManager_ExceptionInvalidPassword',
- array(UsersManager::PASSWORD_MIN_LENGTH));
+ $error = Piwik::translate('UsersManager_ExceptionInvalidPassword', [UsersManager::PASSWORD_MIN_LENGTH]);
}
- //confirm matching password
+
+ // confirm matching passwords
if ($password !== $passwordConfirmation) {
$error = Piwik::translate('Login_PasswordsDoNotMatch');
}
if (!$error) {
$password = UsersManager::getPasswordHash($password);
- $passwordInfo = $passwordHelper->info($password);
-
- if (!isset($passwordInfo['algo']) || 0 >= $passwordInfo['algo']) {
- // password may have already been fully hashed
- $password = $passwordHelper->hash($password);
+ $password = $passwordHelper->hash($password);
+
+ // update pending user to active user
+ $model->updateUserFields(
+ $user['login'],
+ [
+ 'password' => $password,
+ 'invite_token' => null,
+ 'invite_accept_at' => Date::now()->getDatetime(),
+ 'invite_expired_at' => null,
+ ]
+ );
+
+ // send e-mail to inviter
+ if (!empty($user['invited_by'])) {
+ $invitedBy = $model->getUser($user['invited_by']);
+ if ($invitedBy) {
+ $mail = StaticContainer::getContainer()->make(UserAcceptInvitationEmail::class, [
+ 'login' => $user['invited_by'],
+ 'emailAddress' => $invitedBy['email'],
+ 'userLogin' => $user['login'],
+ ]);
+ $mail->safeSend();
+ }
}
- //update pending user to active user
- $model->updateUserFields($user['login'], ['password' => $password, 'invite_status' => 'accept']);
- $sessionInitializer = new SessionInitializer();
- $auth = StaticContainer::get('Piwik\Auth');
- $auth->setTokenAuth(null); // ensure authenticated through password
- $auth->setLogin($user['login']);
- $auth->setPassword($passwordConfirmation);
- $sessionInitializer->initSession($auth);
-
- //send Admin Email
- try {
- $mail = StaticContainer::getContainer()->make(UserAcceptInvitationEmail::class, array(
- 'login' => $user['login'],
- 'emailAddress' => $user['email'],
- 'userLogin' => $user['login'],
- ));
- $mail->safeSend();
- } catch (\Exception $e) {
-
- }
+ /**
+ * Triggered after a user accepted an invite
+ *
+ * @param string $userLogin The invited user's login.
+ * @param string $email The invited user's e-mail.
+ * @param string $inviterLogin The login of the user, who invited this user
+ */
+ Piwik::postEvent('UsersManager.inviteUser.accepted', [$user['login'], $user['email'], $user['invited_by']]);
- $this->redirectToIndex('CoreHome', 'index');
+ $this->authenticateAndRedirect($user['login'], $passwordConfirmation);
}
+
$view->AccessErrorString = $error;
}
+
$view->user = $user;
+ $view->termsAndCondition = $termsAndConditionUrl;
+ $view->privacyPolicyUrl = $privacyPolicyUrl;
$view->token = $token;
- $view->declined = false;
$this->configureView($view);
self::setHostValidationVariablesView($view);
return $view->render();
@@ -609,42 +640,56 @@ class Controller extends \Piwik\Plugin\ControllerAdmin
$token = Common::getRequestVar('token', null, 'string');
$form = Common::getRequestVar('invitation_form', false, 'string');
- $user = $model->getUserByTokenAuth($token);
- if ($user['invite_status'] !== 'pending') {
+ $user = $model->getUserByInviteToken($token);
+
+ // if no user matches the invite token
+ if (!$user) {
throw new Exception(Piwik::translate('Login_InvalidOrExpiredToken'));
}
- //if user not match the invite user
- if (!$user) {
- throw new Exception(Piwik::translate('Login_InvalidUsernameEmail'));
+
+ if (!empty($user['invite_expired_at']) && Date::factory($user['invite_expired_at'])->isEarlier(Date::now())) {
+ throw new Exception(Piwik::translate('Login_InvalidOrExpiredToken'));
}
- if ($form) {
- $model->deleteAllTokensForUser($user['login']);
- $model->updateUserFields($user['login'], ['invite_status' => 'decline']);
- $this->redirectToIndex('Login', 'index');
+ $view = new View('@Login/invitationDecline');
- }
+ if ($form) {
+ // remove user
+ try {
+ $model->deleteUser($user['login']);
+ } catch (\Exception $e) {
+ // deleting the user triggers an event, which might call methods that require a user to be logged in
+ // as those operations might not be needed for a pending user, we simply ignore any errors here
+ }
- $view = new View('@Login/invitation');
- $view->declined = true;
- $view->token = $token;
+ // send e-mail to inviter
+ if (!empty($user['invited_by'])) {
+ $invitedBy = $model->getUser($user['invited_by']);
+ if ($invitedBy) {
+ $mail = StaticContainer::getContainer()->make(UserDeclinedInvitationEmail::class, [
+ 'login' => $user['invited_by'],
+ 'emailAddress' => $invitedBy['email'],
+ 'userLogin' => $user['login'],
+ ]);
+ $mail->safeSend();
+ }
+ }
- //send Admin Email
- try {
- $mail = StaticContainer::getContainer()->make(UserDeclinedInvitationEmail::class, array(
- 'login' => $user['login'],
- 'emailAddress' => $user['email'],
- 'userLogin' => $user['login'],
- ));
- $mail->safeSend();
- } catch (\Exception $e) {
+ $view = new View('@Login/invitationDeclineSuccess');
+ /**
+ * Triggered after a user accepted an invite
+ *
+ * @param string $userLogin The invited user's login.
+ * @param string $email The invited user's e-mail.
+ * @param string $inviterLogin The login of the user, who invited this user
+ */
+ Piwik::postEvent('UsersManager.inviteUser.declined', [$user['login'], $user['email'], $user['invited_by']]);
}
+
+ $view->token = $token;
$this->configureView($view);
self::setHostValidationVariablesView($view);
return $view->render();
-
-
}
-
}
diff --git a/plugins/Login/PasswordResetter.php b/plugins/Login/PasswordResetter.php
index 45854f4601..3d0039554c 100644
--- a/plugins/Login/PasswordResetter.php
+++ b/plugins/Login/PasswordResetter.php
@@ -15,9 +15,9 @@ use Piwik\IP;
use Piwik\Option;
use Piwik\Piwik;
use Piwik\Plugins\Login\Emails\PasswordResetEmail;
+use Piwik\Plugins\UsersManager\API as UsersManagerAPI;
use Piwik\Plugins\UsersManager\Model;
use Piwik\Plugins\UsersManager\UsersManager;
-use Piwik\Plugins\UsersManager\API as UsersManagerAPI;
use Piwik\Plugins\UsersManager\UserUpdater;
use Piwik\SettingsPiwik;
use Piwik\Url;
@@ -379,6 +379,8 @@ class PasswordResetter
/**
* Returns user information based on a login or email.
*
+ * If user is pending, return null
+ *
* Derived classes can override this method to provide custom user querying logic.
*
* @param string $loginMail user login or email address
@@ -388,7 +390,12 @@ class PasswordResetter
{
$userModel = new Model();
+ if ($userModel->isPendingUser($loginOrMail)) {
+ return null;
+ }
+
$user = null;
+
if ($userModel->userExists($loginOrMail)) {
$user = $userModel->getUser($loginOrMail);
} else if ($userModel->userEmailExists($loginOrMail)) {
diff --git a/plugins/Login/lang/en.json b/plugins/Login/lang/en.json
index 9f97cad22b..92ff4bc518 100644
--- a/plugins/Login/lang/en.json
+++ b/plugins/Login/lang/en.json
@@ -55,14 +55,18 @@
"SuspiciousLoginAttemptsInLastHourEmail4": "Setup two-factor auth so attackers will need more information than just your password in order to login.",
"SuspiciousLoginAttemptsInLastHourEmail5": "Additionally, if your Matomo has a limited set of users or IPs through which users will access it, it may be beneficial to setup a IP address allowlist. %1$sRead our docs for more information.%2$s",
"LoginNotAllowedBecauseUserLoginBlocked": "Login functionality is temporarily disabled since we've a suspicious amount of failed login attempts in the last hour.",
- "InvitationTitle": "Accept Invitation",
- "InvitationDeclineTitle": "Decline Invitation",
- "InvitationDeclineBody": "Are you sure you want to decline this Invitation?",
+ "InvitationTitle": "Accept invitation",
+ "InvitationDeclineTitle": "Decline invitation",
+ "InvitationDeclineBody": "Are you sure you want to decline this invitation?",
"InvitationHints": "(to cancel this action, just leave the page)",
"Accept": "Accept",
- "Yes": "Yes",
- "TermsRequired": "Please Accept the terms and conditions",
"PasswordRequired": "Please enter password to continue",
- "declineInvitationInfo": "Your invitation has been decline."
+ "DeclineInvitationInfo": "Your invitation was successfully declined.",
+ "BySigningUpPrivacyPolicy": "By signing up, I accept the %1$sprivacy policy%2$s",
+ "BySigningUpTermsAndCondition": "By signing up, I accept the %1$sterms &amp; conditions%2$s",
+ "BySigningUpPrivacyPolicyAndTermsAndCondition": "By signing up, I accept the %1$sprivacy policy%2$s and the %3$sterms &amp; conditions%4$s",
+ "AcceptPrivacyPolicy": "You need to accept the privacy policy.",
+ "AcceptTermsAndCondition": "You need to accept the terms &amp; conditions.",
+ "AcceptPrivacyPolicyAndTermsAndCondition": "You need to accept the privacy policy and the terms &amp; conditions."
}
}
diff --git a/plugins/Login/templates/invitation.twig b/plugins/Login/templates/invitation.twig
index adcba7d505..a559a9aab4 100644
--- a/plugins/Login/templates/invitation.twig
+++ b/plugins/Login/templates/invitation.twig
@@ -1,83 +1,83 @@
-{% extends '@Login/inviteLayout.twig' %}
+{% extends '@Login/loginLayout.twig' %}
{% block loginContent %}
- {% if not declined %}
- <div class="contentForm invitationForm">
- <div class="card">
- <div class="card-content">
- <div class="card-title">
- {{ "Login_InvitationTitle"|translate }}
- </div>
+ <div class="contentForm invitationForm">
+ <div class="card">
+ <div class="card-content">
+ <div class="card-title">
+ {{ "Login_InvitationTitle"|translate }}
+ </div>
- {% if AccessErrorString is defined %}
- <div piwik-notification
- noclear="true"
- context="error">
- <strong>{{ 'General_Error'|translate }}</strong>: {{ AccessErrorString|raw }}<br/>
- </div>
- {% endif %}
- {% block content %}
- <form method="post" action="?module=Login&action=acceptInvitation">
- <input type="hidden" name="token" value="{{ token }}"/>
- <div class="row">
- <div class="col s12 input-field">
- <input type="text" name="login" value="{{ user.login }}" size="20" readonly
- tabindex="0"/>
- <label><i class="icon-user icon"></i> {{ 'Login_LoginOrEmail'|translate }}</label>
- </div>
- <div class="col s12 input-field">
- <input type="password" placeholder="" name="password" id="password" class="input" value="" size="20"
- autocorrect="off" autocapitalize="none"
- tabindex="1" required/>
- <label for="password"><i class="icon-locked icon"></i> {{ 'Login_NewPassword'|translate }}</label>
- </div>
- <div class="col s12 input-field">
- <input type="password" placeholder="" name="passwordConfirmation" id="password_confirm" class="input" value="" size="20"
- autocorrect="off" autocapitalize="none"
- tabindex="2"/>
- <label for="password_confirm"><i class="icon-locked icon"></i> {{ 'Login_NewPasswordRepeat'|translate }}</label>
- </div>
+ {% if AccessErrorString is defined %}
+ <div piwik-notification
+ noclear="true"
+ context="error">
+ <strong>{{ 'General_Error'|translate }}</strong>: {{ AccessErrorString|raw }}<br/>
+ </div>
+ {% endif %}
+ {% block content %}
+ <form method="post" action="?module=Login&action=acceptInvitation">
+ <input type="hidden" name="token" value="{{ token }}"/>
+ <div class="row">
+ <div class="col s12 input-field">
+ <input type="text" name="login" value="{{ user.login }}" size="20" readonly
+ tabindex="0"/>
+ <label><i class="icon-user icon"></i> {{ 'Login_LoginOrEmail'|translate }}</label>
+ </div>
+ <div class="col s12 input-field">
+ <input type="password" placeholder="" name="password" id="password" class="input" value="" size="20"
+ autocorrect="off" autocapitalize="none"
+ tabindex="1" required/>
+ <label for="password"><i class="icon-locked icon"></i> {{ 'Login_NewPassword'|translate }}</label>
+ </div>
+ <div class="col s12 input-field">
+ <input type="password" placeholder="" name="passwordConfirmation" id="password_confirm" class="input" value="" size="20"
+ autocorrect="off" autocapitalize="none"
+ tabindex="2" required/>
+ <label for="password_confirm"><i class="icon-locked icon"></i> {{ 'Login_NewPasswordRepeat'|translate }}</label>
</div>
- <div class="row actions">
+ </div>
+ <div class="row actions">
+ {% if privacyPolicyUrl|default('') is not empty or termsAndCondition|default('') is not empty %}
<div class="col s12">
<label>
- <input name="terms" type="checkbox" id="terms" value="1" tabindex="90"/>
- <span>{{ 'PrivacyManager_TermsAndConditions'|translate }}</span>
+ <input name="conditionCheck" type="checkbox" id="conditionCheck" value="1" tabindex="89"/>
+ <span>
+ {% if privacyPolicyUrl|default('') is not empty and termsAndCondition|default('') is empty %}
+ {{ 'Login_BySigningUpPrivacyPolicy'|translate(
+ '<a target="_blank" rel="noreferrer noopener" href="' ~ privacyPolicyUrl|safelink|e('html_attr') ~ '">',
+ '</a>'
+ )|raw }}
+ {% elseif privacyPolicyUrl|default('') is empty and termsAndCondition|default('') is not empty %}
+ {{ 'Login_BySigningUpTermsAndCondition'|translate(
+ '<a target="_blank" rel="noreferrer noopener" href="' ~ termsAndCondition|safelink|e('html_attr') ~ '">',
+ '</a>'
+ )|raw }}
+ {% elseif privacyPolicyUrl|default('') is not empty and termsAndCondition|default('') is not empty %}
+ {{ 'Login_BySigningUpPrivacyPolicyAndTermsAndCondition'|translate(
+ '<a target="_blank" rel="noreferrer noopener" href="' ~ privacyPolicyUrl|safelink|e('html_attr') ~ '">',
+ '</a>',
+ '<a target="_blank" rel="noreferrer noopener" href="' ~ termsAndCondition|safelink|e('html_attr') ~ '">',
+ '</a>'
+ )|raw }}
+ {% endif %}
+ </span>
</label>
- <input class="submit btn" name="invitation_form" id="login_form_submit" type="submit" value="{{ 'Login_Accept'|translate }}"
- tabindex="100"/>
</div>
- </div>
- </form>
- {% if isCustomLogo %}
- <p id="piwik">
- <i><a href="https://matomo.org/" rel="noreferrer noopener" target="_blank">{{ linkTitle }}</a></i>
- </p>
- {% endif %}
- {% endblock %}
- </div>
- </div>
- </div>
- {% else %}
- <div class="contentForm invitationForm">
- <div class="card">
- <div class="card-content">
- <div class="card-title">
- {{ "Login_InvitationDeclineTitle"|translate }}
- </div>
- <p> {{ "Login_InvitationDeclineBody"|translate }}</p>
- <p class="hints"> {{ "Login_InvitationHints"|translate }}</p>
- <form method="post" action="?module=Login&action=declineInvitation">
- <input type="hidden" name="token" value="{{ token }}"/>
- <div class="row actions">
- <div class="col s12">
- <input class="submit btn" name="invitation_form" id="login_form_submit" type="submit" value="{{ 'Login_Yes'|translate }}"
+ {% endif %}
+ <div style="margin-top:10px" class="col s12">
+ <input class="submit btn" name="invitation_form" id="login_form_submit" type="submit" value="{{ 'Login_Accept'|translate }}"
tabindex="100"/>
</div>
</div>
</form>
- </div>
+ {% if isCustomLogo %}
+ <p id="piwik">
+ <i><a href="https://matomo.org/" rel="noreferrer noopener" target="_blank">{{ linkTitle }}</a></i>
+ </p>
+ {% endif %}
+ {% endblock %}
</div>
</div>
- {% endif %}
+ </div>
{% endblock %} \ No newline at end of file
diff --git a/plugins/Login/templates/invitationDecline.twig b/plugins/Login/templates/invitationDecline.twig
new file mode 100644
index 0000000000..0e72c6d874
--- /dev/null
+++ b/plugins/Login/templates/invitationDecline.twig
@@ -0,0 +1,23 @@
+{% extends '@Login/loginLayout.twig' %}
+
+{% block loginContent %}
+ <div class="contentForm invitationForm">
+ <div class="card">
+ <div class="card-content">
+ <div class="card-title">
+ {{ "Login_InvitationDeclineTitle"|translate }}
+ </div>
+ <p> {{ "Login_InvitationDeclineBody"|translate }}</p>
+ <p class="hints"> {{ "Login_InvitationHints"|translate }}</p>
+ <form method="post" action="?module=Login&action=declineInvitation">
+ <input type="hidden" name="token" value="{{ token }}"/>
+ <div class="row actions">
+ <div class="col s12">
+ <input class="submit btn" name="invitation_form" id="login_form_submit" type="submit" value="{{ 'General_Yes'|translate }}"
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+{% endblock %} \ No newline at end of file
diff --git a/plugins/Login/templates/invitationDeclineSuccess.twig b/plugins/Login/templates/invitationDeclineSuccess.twig
new file mode 100644
index 0000000000..cf4f94dbce
--- /dev/null
+++ b/plugins/Login/templates/invitationDeclineSuccess.twig
@@ -0,0 +1,11 @@
+{% extends '@Login/loginLayout.twig' %}
+
+{% block loginContent %}
+ <div class="contentForm invitationForm">
+ <div class="card">
+ <div class="notification system notification-success">
+ <p> {{ "Login_DeclineInvitationInfo"|translate }}</p>
+ </div>
+ </div>
+ </div>
+{% endblock %} \ No newline at end of file
diff --git a/plugins/Login/templates/inviteLayout.twig b/plugins/Login/templates/inviteLayout.twig
deleted file mode 100644
index 3db4ed5be6..0000000000
--- a/plugins/Login/templates/inviteLayout.twig
+++ /dev/null
@@ -1,46 +0,0 @@
-{% extends '@Morpheus/layout.twig' %}
-
-{% block meta %}
- <meta name="robots" content="noindex,nofollow">
-{% endblock %}
-
-{% block head %}
- {{ parent() }}
-{% endblock %}
-
-{% set title %}{{ 'Login_InvitationTitle'|translate }}{% endset %}
-
-{% block pageDescription %}{{ 'General_OpenSourceWebAnalytics'|translate }}{% endblock %}
-
-{% set bodyId = 'loginPage' %}
-
-{% block body %}
-
- {{ postEvent("Template.beforeTopBar", "login") }}
- {{ postEvent("Template.beforeContent", "login") }}
-
- {% include "_iframeBuster.twig" %}
-
- <div id="notificationContainer">
- </div>
- <nav>
- <div class="nav-wrapper">
- {% include "@CoreHome/_logo.twig" with { 'logoLink': 'https://matomo.org', 'centeredLogo': true, 'useLargeLogo': false } %}
- </div>
- </nav>
-
- <section class="loginSection row">
- <div class="col s12 m6 push-m3 l4 push-l4">
-
- {# untrusted host warning #}
- {% if (isValidHost is defined and invalidHostMessage is defined and isValidHost == false) %}
- {% include '@CoreHome/_warningInvalidHost.twig' %}
- {% else %}
- {% block loginContent %}
- {% endblock %}
- {% endif %}
-
- </div>
- </section>
-
-{% endblock %}
diff --git a/plugins/Login/tests/Fixtures/PendingUsers.php b/plugins/Login/tests/Fixtures/PendingUsers.php
index 4be64378ac..fea4f24000 100644
--- a/plugins/Login/tests/Fixtures/PendingUsers.php
+++ b/plugins/Login/tests/Fixtures/PendingUsers.php
@@ -1,39 +1,40 @@
<?php
+
/**
* Matomo - free/libre analytics platform
*
* @link https://matomo.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
+
namespace Piwik\Plugins\Login\tests\Fixtures;
-use Piwik\Date;
+use Piwik\Plugins\PrivacyManager\SystemSettings;
use Piwik\Plugins\UsersManager\Model;
use Piwik\Tests\Framework\Fixture;
/**
- * Generates tracker testing data for our APITest
- *
- * This Simple fixture adds one website and tracks one visit with couple pageviews and an ecommerce conversion
+ * Simple fixture that creates a user with pending invitation
*/
class PendingUsers extends Fixture
{
-
public $dateTime = '2013-01-23 01:23:45';
public $idSite = 1;
- public $users = array();
+ public $users = [];
- public $pendingUser = array(
+ public $pendingUser = [
'login' => '000pendingUser',
'email' => 'pendinguser2light@example.com'
- );
+ ];
public $token = "13cb9dcef6cc70b02a640cee30dc8ce9";
public function setUp(): void
{
+ $this->setUpWebsite();
$this->setUpUser();
+ $this->setUpTermsAndPrivacy();
}
public function tearDown(): void
@@ -45,10 +46,23 @@ class PendingUsers extends Fixture
{
$model = new Model();
$model->addUser($this->pendingUser['login'], '', $this->pendingUser['email'], $this->dateTime, 1);
+ $model->attachInviteToken($this->pendingUser['login'], $this->token, 7);
+ }
- $model->addTokenAuth($this->pendingUser['login'], $this->token, "Invite Token",
- Date::now()->getDatetime(),
- Date::now()->addDay(7)->getDatetime());
+ private function setUpWebsite()
+ {
+ if (!self::siteCreated($this->idSite)) {
+ $idSite = self::createWebsite($this->dateTime, $ecommerce = 1);
+ $this->assertSame($this->idSite, $idSite);
+ }
+ }
+
+ private function setUpTermsAndPrivacy()
+ {
+ $settings = new SystemSettings();
+ $settings->termsAndConditionUrl->setValue('matomo.org');
+ $settings->privacyPolicyUrl->setValue('matomo.org');
+ $settings->save();
}
-} \ No newline at end of file
+}
diff --git a/plugins/Login/tests/Integration/LoginTest.php b/plugins/Login/tests/Integration/LoginTest.php
index 39f2cc3e76..533a5747d4 100644
--- a/plugins/Login/tests/Integration/LoginTest.php
+++ b/plugins/Login/tests/Integration/LoginTest.php
@@ -341,14 +341,16 @@ class LoginTest extends IntegrationTestCase
protected function _setUpUser()
{
- $user = array('login' => 'user',
- 'password' => 'geqgeagae',
- 'email' => 'test@test.com',
- 'superuser_access' => 0);
+ $user = array(
+ 'login' => 'user',
+ 'password' => 'geqgeagae',
+ 'email' => 'test@test.com',
+ 'superuser_access' => 0
+ );
API::getInstance()->addUser($user['login'], $user['password'], $user['email']);
- $model = new \Piwik\Plugins\UsersManager\Model();
+ $model = new \Piwik\Plugins\UsersManager\Model();
$tokenAuth = $model->generateRandomTokenAuth();
$model->addTokenAuth($user['login'], $tokenAuth, 'many users test', Date::now()->getDatetime());
@@ -393,7 +395,7 @@ class LoginTest extends IntegrationTestCase
public function provideContainerConfig()
{
return array(
- 'Piwik\Access' => new FakeAccess()
+ 'Piwik\Access' => new FakeAccess()
);
}
}
diff --git a/plugins/Login/tests/Integration/PasswordResetterTest.php b/plugins/Login/tests/Integration/PasswordResetterTest.php
index b6014aecff..b8f8af336e 100644
--- a/plugins/Login/tests/Integration/PasswordResetterTest.php
+++ b/plugins/Login/tests/Integration/PasswordResetterTest.php
@@ -11,6 +11,7 @@ namespace Piwik\Plugins\Login\tests\Integration;
use PHPMailer\PHPMailer\PHPMailer;
use Piwik\Access;
+use Piwik\API\Request;
use Piwik\Auth;
use Piwik\Container\StaticContainer;
use Piwik\Option;
@@ -166,6 +167,26 @@ class PasswordResetterTest extends IntegrationTestCase
$this->passwordResetter->checkValidConfirmPasswordToken('superUserLogin', $oldCapturedToken);
}
+ public function testPasswordResetShouldNotWorkForPendingUser()
+ {
+ self::expectException(\Exception::class);
+ self::expectExceptionMessage('Invalid username or e-mail address.');
+
+ Request::processRequest(
+ 'UsersManager.inviteUser',
+ [
+ 'userLogin' => 'pendingUser',
+ 'email' => 'pending@user.io',
+ 'idSite' => 1,
+ 'expiryInDays' => 7
+ ]
+ );
+
+ self::assertTrue($this->userModel->isPendingUser('pendingUser'));
+
+ $this->passwordResetter->initiatePasswordResetProcess('pendingUser', self::NEWPASSWORD);
+ }
+
/**
* @param Fixture $fixture
*/
diff --git a/plugins/Login/tests/UI/Decline_spec.js b/plugins/Login/tests/UI/Decline_spec.js
index b2b6893784..515a44a6a3 100644
--- a/plugins/Login/tests/UI/Decline_spec.js
+++ b/plugins/Login/tests/UI/Decline_spec.js
@@ -1,7 +1,7 @@
/*!
* Matomo - free/libre analytics platform
*
- * login & password reset screenshot tests.
+ * Decline invitation UI tests
*
* @link https://matomo.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@@ -10,16 +10,22 @@
describe('Decline', function () {
this.timeout(0);
this.fixture = 'Piwik\\Plugins\\Login\\tests\\Fixtures\\PendingUsers';
+ this.optionsOverride = {
+ 'persist-fixture-data': false
+ };
var pendingUserUrl = '?module=Login&action=declineInvitation&token=13cb9dcef6cc70b02a640cee30dc8ce9';
-
-
it('should display decline invite page', async function () {
await page.goto(pendingUserUrl);
expect(await page.screenshot({ fullPage: true })).to.matchImage('default');
});
-
-
-}); \ No newline at end of file
+ it('should display decline success page', async function () {
+ await page.evaluate(function(){
+ $('#login_form_submit').click();
+ });
+ await page.waitForNetworkIdle();
+ expect(await page.screenshot({ fullPage: true })).to.matchImage('success');
+ });
+});
diff --git a/plugins/Login/tests/UI/Invite_spec.js b/plugins/Login/tests/UI/Invite_spec.js
index d937f2c891..decb080ff0 100644
--- a/plugins/Login/tests/UI/Invite_spec.js
+++ b/plugins/Login/tests/UI/Invite_spec.js
@@ -1,7 +1,7 @@
/*!
* Matomo - free/libre analytics platform
*
- * login & password reset screenshot tests.
+ * Accept invitation UI tests
*
* @link https://matomo.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@@ -10,11 +10,13 @@
describe('Invite', function () {
this.timeout(0);
this.fixture = 'Piwik\\Plugins\\Login\\tests\\Fixtures\\PendingUsers';
+ this.optionsOverride = {
+ 'persist-fixture-data': false
+ };
var pendingUserUrl = '?module=Login&action=acceptInvitation&token=13cb9dcef6cc70b02a640cee30dc8ce9';
var wrongUserUrl = '?module=Login&action=acceptInvitation&token=123';
-
it('should display error page', async function (){
await page.goto(wrongUserUrl);
expect(await page.screenshot({ fullPage: true })).to.matchImage('error');
@@ -31,8 +33,19 @@ describe('Invite', function () {
await page.evaluate(function(){
$('#login_form_submit').click();
});
+ await page.waitForNetworkIdle();
expect(await page.screenshot({ fullPage: true })).to.matchImage('wrong_password');
-
});
-}); \ No newline at end of file
+ it('it should login success', async function () {
+ await page.type('#password', 'abcd1234');
+ await page.type('#password_confirm', 'abcd1234');
+ await page.evaluate(function(){
+ $('#conditionCheck').prop('checked', true);
+ $('#login_form_submit').click();
+ });
+ // should show site without data page
+ await page.waitForNetworkIdle();
+ await page.waitForSelector('.site-without-data');
+ });
+});
diff --git a/plugins/Login/tests/UI/expected-screenshots/Decline_default.png b/plugins/Login/tests/UI/expected-screenshots/Decline_default.png
index fa9acf274f..a2e9c2522b 100644
--- a/plugins/Login/tests/UI/expected-screenshots/Decline_default.png
+++ b/plugins/Login/tests/UI/expected-screenshots/Decline_default.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b4f71261c0c6e74cae66aba9d6749eb2e0852acb802349e573d35f38ad6a7861
-size 24210
+oid sha256:41aedf3327800c766eb323de2239e78a8854f73ed18bacad5a0e3c243aa4407f
+size 24267
diff --git a/plugins/Login/tests/UI/expected-screenshots/Decline_success.png b/plugins/Login/tests/UI/expected-screenshots/Decline_success.png
new file mode 100644
index 0000000000..e88135cd2e
--- /dev/null
+++ b/plugins/Login/tests/UI/expected-screenshots/Decline_success.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d92554f925821c014c4ec38647342c14595d07d060dc761256983838dd426518
+size 15961
diff --git a/plugins/Login/tests/UI/expected-screenshots/Invite_error.png b/plugins/Login/tests/UI/expected-screenshots/Invite_error.png
index 5d5cc8fcd4..e9775cca1e 100644
--- a/plugins/Login/tests/UI/expected-screenshots/Invite_error.png
+++ b/plugins/Login/tests/UI/expected-screenshots/Invite_error.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c273f68c05b5c8398aa2248a92633bc83b5209188b859d173d76be5a3d96f630
-size 40102
+oid sha256:6510477474c2b1bd247bb61dedb0b5d381306e1f647772628a712b1ef5361dd3
+size 40268
diff --git a/plugins/Login/tests/UI/expected-screenshots/Invite_set_password.png b/plugins/Login/tests/UI/expected-screenshots/Invite_set_password.png
index 4979ed5066..cf6230982b 100644
--- a/plugins/Login/tests/UI/expected-screenshots/Invite_set_password.png
+++ b/plugins/Login/tests/UI/expected-screenshots/Invite_set_password.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:d59911d79ef8cf203efa7a7d64024fe66c9c655dcf6ec32900954be7d9e7b494
-size 27166
+oid sha256:2928cffb6651bf7304f3fe2b99f53570a7cba38655df2b24eac99b3a4e720c36
+size 33403
diff --git a/plugins/Login/tests/UI/expected-screenshots/Invite_wrong_password.png b/plugins/Login/tests/UI/expected-screenshots/Invite_wrong_password.png
index ac1265c02a..9563f4704c 100644
--- a/plugins/Login/tests/UI/expected-screenshots/Invite_wrong_password.png
+++ b/plugins/Login/tests/UI/expected-screenshots/Invite_wrong_password.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9a8c371e544e1964269b1d6200e933a719a47a6b3dcba01549588dc67bb2f3ea
-size 32681
+oid sha256:e984c2c1337416b377b6c1195696ad126028e9b2aa5d6ddbad4d2c3a8d2bbb8a
+size 39188