Skip to content

Commit

Permalink
Send email notification when user email changes. (#14136)
Browse files Browse the repository at this point in the history
* Send email notification when user email changes.

* Forgot to add file.

* Apply pr fixes + send email for password changes too.

* Add quick test for new emails.

* Translate text

* Refactor according to review.

* ucfirst device name

* Fixing integration test
  • Loading branch information
diosmosis authored Mar 7, 2019
1 parent 7153700 commit 0648513
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 7 deletions.
4 changes: 3 additions & 1 deletion lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,9 @@
"YouAreCurrentlyUsing": "You are currently using Matomo %s.",
"YouAreViewingDemoMessage": "You are viewing the demo of %1$sMatomo%2$s",
"YouMustBeLoggedIn": "You must be logged in to access this functionality.",
"YourChangesHaveBeenSaved": "Your changes have been saved."
"YourChangesHaveBeenSaved": "Your changes have been saved.",
"ThankYouForUsingMatomo": "Thank you for using Matomo",
"TheMatomoTeam": "The Matomo Team"
},
"Mobile": {
"AboutPiwikMobile": "About Matomo Mobile",
Expand Down
10 changes: 6 additions & 4 deletions plugins/Login/tests/Integration/PasswordResetterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,12 @@ public function provideContainerConfig()
'observers.global' => [
['Mail.send', function (Mail $mail) {
$body = $mail->getBodyHtml(true);
preg_match('/resetToken=3D([a-zA-Z0-9=\s]+?)<\/p>/', $body, $matches);
$capturedToken = $matches[1];
$capturedToken = preg_replace('/=\s*/', '', $capturedToken);
$this->capturedToken = $capturedToken;
preg_match('/resetToken=3D([a-zA-Z0-9=\s]+)<\/p>/', $body, $matches);
if (!empty($matches[1])) {
$capturedToken = $matches[1];
$capturedToken = preg_replace('/=\s*/', '', $capturedToken);
$this->capturedToken = $capturedToken;
}
}],
],
];
Expand Down
77 changes: 76 additions & 1 deletion plugins/UsersManager/API.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/
namespace Piwik\Plugins\UsersManager;

use DeviceDetector\DeviceDetector;
use Exception;
use Piwik\Access;
use Piwik\Access\CapabilitiesProvider;
Expand All @@ -17,6 +18,8 @@
use Piwik\Config;
use Piwik\Container\StaticContainer;
use Piwik\Date;
use Piwik\IP;
use Piwik\Mail;
use Piwik\Metrics\Formatter;
use Piwik\NoAccessException;
use Piwik\Option;
Expand All @@ -26,7 +29,7 @@
use Piwik\SettingsPiwik;
use Piwik\Site;
use Piwik\Tracker\Cache;
use Piwik\Url;
use Piwik\View;

/**
* The UsersManager API lets you Manage Users and their permissions to access specific websites.
Expand Down Expand Up @@ -915,6 +918,14 @@ public function updateUser($userLogin, $password = false, $email = false, $alias

Cache::deleteTrackerCache();

if ($email != $userInfo['email']) {
$this->sendEmailChangedEmail($userInfo, $email);
}

if ($passwordHasBeenUpdated) {
$this->sendPasswordChangedEmail($userInfo);
}

/**
* Triggered after an existing user has been updated.
* Event notify about password change.
Expand Down Expand Up @@ -1379,4 +1390,68 @@ private function getRoleAndCapabilitiesFromAccess($access)
}
return [$roles, $capabilities];
}

private function sendEmailChangedEmail($user, $newEmail)
{
// send the mail to both the old email and the new email
foreach ([$newEmail, $user['email']] as $emailTo) {
$this->sendUserInfoChangedEmail('email', $user, $newEmail, $emailTo, 'UsersManager_EmailChangeNotificationSubject');
}
}

private function sendUserInfoChangedEmail($type, $user, $newValue, $emailTo, $subject)
{
$deviceDescription = $this->getDeviceDescription();

$view = new View('@UsersManager/_userInfoChangedEmail.twig');
$view->type = $type;
$view->accountName = Common::sanitizeInputValue($user['login']);
$view->newEmail = Common::sanitizeInputValue($newValue);
$view->ipAddress = IP::getIpFromHeader();
$view->deviceDescription = $deviceDescription;

$mail = new Mail();

$mail->addTo($emailTo, $user['login']);
$mail->setSubject(Piwik::translate($subject));
$mail->setDefaultFromPiwik();
$mail->setWrappedHtmlBody($view);

$replytoEmailName = Config::getInstance()->General['login_password_recovery_replyto_email_name'];
$replytoEmailAddress = Config::getInstance()->General['login_password_recovery_replyto_email_address'];
$mail->setReplyTo($replytoEmailAddress, $replytoEmailName);

$mail->send();
}

private function sendPasswordChangedEmail($user)
{
$this->sendUserInfoChangedEmail('password', $user, null, $user['email'], 'UsersManager_PasswordChangeNotificationSubject');
}

private function getDeviceDescription()
{
$userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';

$uaParser = new DeviceDetector($userAgent);
$uaParser->parse();

$deviceName = ucfirst($uaParser->getDeviceName());
if (!empty($deviceName)) {
$description = $deviceName;
} else {
$description = Piwik::translate('General_Unknown');
}

$deviceBrand = $uaParser->getBrandName();
$deviceModel = $uaParser->getModel();
if (!empty($deviceBrand)
|| !empty($deviceModel)
) {
$parts = array_filter([$deviceBrand, $deviceModel]);
$description .= ' (' . implode(' ', $parts) . ')';
}

return $description;
}
}
8 changes: 7 additions & 1 deletion plugins/UsersManager/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@
"AreYouSureAddCapability": "Are you sure you want to give %1$s the %2$s capability for %3$s?",
"AreYouSureRemoveCapability": "Are you sure you want to remove the %1$s capability from %2$s for %3$s?",
"IncludedInUsersRole": "Included in this user's role.",
"Capability": "Capability"
"Capability": "Capability",
"EmailChangeNotificationSubject": "Your Matomo account's email address has just been changed",
"EmailChangedEmail1": "The email address associated with your account has been changed to %1$s",
"EmailChangedEmail2": "This change was initiated from the following device: %1$s (IP address = %2$s).",
"IfThisWasYouIgnoreIfNot": "If this was you, feel free to ignore this email. If this was not you, please login, correct your email address, change your password and contact your Matomo administrator.",
"PasswordChangeNotificationSubject": "Your Matomo account's password has just been changed",
"PasswordChangedEmail": "Your password has just been changed. The change was initiated from the following device: %1$s (IP address = %2$s)."
}
}
14 changes: 14 additions & 0 deletions plugins/UsersManager/templates/_userInfoChangedEmail.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<p>{{ 'General_HelloUser'|translate('<strong>' ~ accountName ~ '</strong>')|raw }}</p>

{% if type == 'email' %}
<p>{{ 'UsersManager_EmailChangedEmail1'|translate('<strong>' ~ newEmail ~ '</strong>')|raw }}.</p>

<p>{{ 'UsersManager_EmailChangedEmail2'|translate(deviceDescription, ipAddress) }} {{ 'UsersManager_IfThisWasYouIgnoreIfNot'|translate }}</p>
{% elseif type == 'password' %}
<p>{{ 'UsersManager_PasswordChangedEmail'|translate(deviceDescription, ipAddress) }}</p>

<p>{{ 'UsersManager_IfThisWasYouIgnoreIfNot'|translate }}</p>
{% endif %}

<p>{{ 'General_ThankYouForUsingMatomo'|translate }}!
<br/>{{ 'General_TheMatomoTeam'|translate }}</p>
13 changes: 13 additions & 0 deletions plugins/UsersManager/tests/Integration/APITest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Piwik\Access\Role\Write;
use Piwik\Auth\Password;
use Piwik\Container\StaticContainer;
use Piwik\Mail;
use Piwik\Option;
use Piwik\Piwik;
use Piwik\Plugins\SitesManager\API as SitesManagerAPI;
Expand Down Expand Up @@ -293,6 +294,11 @@ public function test_setUserPreference_throws_whenPreferenceNameContainsUndersco

public function test_updateUser()
{
$capturedMails = [];
Piwik::addAction('Mail.send', function (Mail $mail) use (&$capturedMails) {
$capturedMails[] = $mail;
});

$identity = FakeAccess::$identity;
FakeAccess::$identity = $this->login; // ensure password will be checked against this user
$this->api->updateUser($this->login, 'newPassword', '[email protected]', 'newAlias', false, $this->password);
Expand All @@ -307,6 +313,13 @@ public function test_updateUser()
$passwordHelper = new Password();

$this->assertTrue($passwordHelper->verify(UsersManager::getPasswordHash('newPassword'), $user['password']));

$subjects = array_map(function (Mail $mail) { return $mail->getSubject(); }, $capturedMails);
$this->assertEquals([
'UsersManager_EmailChangeNotificationSubject', // sent twice to old email and new
'UsersManager_EmailChangeNotificationSubject',
'UsersManager_PasswordChangeNotificationSubject',
], $subjects);
}

public function test_updateUser_doesNotChangePasswordIfFalsey()
Expand Down

0 comments on commit 0648513

Please sign in to comment.