Skip to content

Add a mechanism to remind users to rotate personal auth tokens #23172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Apr 14, 2025
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
dab3b6c
Add initial code for a mechanism to remind users to rotate personal a…
michalkleiner Mar 26, 2025
738b03c
Fix CS
michalkleiner Mar 26, 2025
e45fcf2
Adjust token provider interface, make it more reusable, adjust respon…
michalkleiner Mar 28, 2025
8860c8a
Mark class as final
michalkleiner Mar 28, 2025
7f0bf88
Add migration to create new column and bump core version
michalkleiner Mar 28, 2025
d279dd4
Rework responsibilities, abstract token notification for allow other …
michalkleiner Mar 31, 2025
a2ccb88
Merge branch '5.x-dev' into dev-18658
michalkleiner Mar 31, 2025
68fa801
Remove unnecessary use statement
michalkleiner Mar 31, 2025
2010972
Fix typo
michalkleiner Apr 4, 2025
17ed08b
Rename interfaces, classes and methods to better suit their intended …
michalkleiner Apr 4, 2025
1d938bd
Ensure TokenNotifierTask is scheduled
michalkleiner Apr 4, 2025
4dfe986
Merge branch '5.x-dev' into dev-18658
michalkleiner Apr 4, 2025
747f3b8
Correctly use array access instead of object access to db row data
michalkleiner Apr 4, 2025
8963a6e
Tweaks from further local testing
michalkleiner Apr 7, 2025
41b7800
Allow to disable auth token notifications
michalkleiner Apr 7, 2025
06cf8c2
Exclude system tokens
michalkleiner Apr 7, 2025
7953e0b
Add auth token notification email tests
michalkleiner Apr 7, 2025
e8cbaee
Merge branch '5.x-dev' into dev-18658
michalkleiner Apr 7, 2025
b195c3b
Fix CS
michalkleiner Apr 7, 2025
2f834d9
Add log info about number of notifications sent
michalkleiner Apr 8, 2025
edc40a6
Declare test fixture variable
michalkleiner Apr 8, 2025
d5867ef
Merge branch '5.x-dev' into dev-18658
michalkleiner Apr 8, 2025
35a037e
Use strings in Tokens fixture
michalkleiner Apr 9, 2025
c2f1050
Add ts_rotation_notified field to expected token test data
michalkleiner Apr 9, 2025
42f53e0
Update UI test screenshot
michalkleiner Apr 10, 2025
03ad180
Merge branch '5.x-dev' into dev-18658
michalkleiner Apr 10, 2025
61af2b3
Use DATETIME column type in migration
michalkleiner Apr 11, 2025
30bc5c4
Update Manage auth token link URL to behave correctly when task run f…
michalkleiner Apr 11, 2025
223943f
Exclude anonymous user default token from token notifications
michalkleiner Apr 11, 2025
791d191
Store current datetime when token notification sent
michalkleiner Apr 11, 2025
6ac11f1
Merge branch '5.x-dev' into dev-18658
michalkleiner Apr 11, 2025
4913893
Exclude anonymous user by login
michalkleiner Apr 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/global.ini.php
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,10 @@
; Recommended for best security.
only_allow_secure_auth_tokens = 0

; Number of days after which a personal auth token is recommended to be rotated
; and an email notification will be sent to the user
auth_token_rotation_notification_days = 180

; language cookie name for session
language_cookie_name = matomo_lang

Expand Down
39 changes: 39 additions & 0 deletions core/Updates/5.4.0-b1.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/**
* Matomo - free/libre analytics platform
*
* @link https://matomo.org
* @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/

namespace Piwik\Updates;

use Piwik\Updater;
use Piwik\Updater\Migration\Factory as MigrationFactory;
use Piwik\Updates;

class Updates_5_4_0_b1 extends Updates
{
/**
* @var MigrationFactory
*/
private $migration;

public function __construct(MigrationFactory $factory)
{
$this->migration = $factory;
}

public function getMigrations(Updater $updater)
{
return [
$this->migration->db->addColumn('user_token_auth', 'ts_rotation_notified', 'TIMESTAMP NULL'),
];
}

public function doUpdate(Updater $updater)
{
$updater->executeMigrations(__FILE__, $this->getMigrations($updater));
}
}
2 changes: 1 addition & 1 deletion core/Version.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class Version
* The current Matomo version.
* @var string
*/
public const VERSION = '5.4.0-alpha';
public const VERSION = '5.4.0-b1';

public const MAJOR_VERSION = 5;

Expand Down
21 changes: 21 additions & 0 deletions plugins/UsersManager/AuthTokenEmailNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?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\UsersManager;

use Piwik\Plugins\UsersManager\Emails\AuthTokenNotificationEmail;
use Piwik\Plugins\UsersManager\TokenNotifications\TokenEmailNotification;

final class AuthTokenEmailNotification extends TokenEmailNotification
{
public function getEmailClass(): string
{
return AuthTokenNotificationEmail::class;
}
}
103 changes: 103 additions & 0 deletions plugins/UsersManager/Emails/AuthTokenNotificationEmail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

/**
* Matomo - free/libre analytics platform
*
* @link https://matomo.org
* @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/

namespace Piwik\Plugins\UsersManager\Emails;

use Piwik\Config;
use Piwik\Mail;
use Piwik\Piwik;
use Piwik\Plugins\UsersManager\TokenNotifications\TokenNotification;
use Piwik\Url;
use Piwik\View;

class AuthTokenNotificationEmail extends Mail
{
/**
* @var TokenNotification
*/
private $notification;

/** @var string */
private $recipient;

/** @var array */
private $emailData;

public function __construct(TokenNotification $notification, string $recipient, array $emailData)
{
parent::__construct();

$this->notification = $notification;
$this->recipient = $recipient;
$this->emailData = $emailData;

$this->setUpEmail();
}

private function setUpEmail(): void
{
$this->setDefaultFromPiwik();
$this->addTo($this->recipient);
$this->setSubject($this->getDefaultSubject());
$this->addReplyTo($this->getFrom(), $this->getFromName());
$this->setBodyText($this->getDefaultBodyText());
$this->setWrappedHtmlBody($this->getDefaultBodyView());
}

private function getRotationPeriodPretty(): string
{
$rotationPeriodDays = Config::getInstance()->General['auth_token_rotation_notification_days'];

return Piwik::translate('Intl_PeriodDay' . ($rotationPeriodDays === 1 ? '' : 's'));
}

protected function getManageAuthTokensLink(): string
{
return Url::getCurrentUrlWithoutQueryString()
. '?module=UsersManager'
. '&action=userSecurity'
. '#authtokens';
}
protected function getDefaultSubject(): string
{
return Piwik::translate('UsersManager_AuthTokenNotificationEmailSubject');
}

protected function getDefaultBodyText(): string
{
$view = new View('@UsersManager/_authTokenNotificationTextEmail.twig');
$view->setContentType('text/plain');

$this->assignCommonParameters($view);

return $view->render();
}

protected function getDefaultBodyView(): View
{
$view = new View('@UsersManager/_authTokenNotificationHtmlEmail.twig');

$this->assignCommonParameters($view);

return $view;
}

protected function assignCommonParameters(View $view): void
{
$view->tokenName = $this->notification->getTokenName();
$view->tokenCreationDate = $this->notification->getTokenCreationDate();

$view->rotationPeriod = $this->getRotationPeriodPretty();
$view->manageAuthTokensLink = $this->getManageAuthTokensLink();

foreach ($this->emailData as $item => $value) {
$view->assign($item, $value);
}
}
}
5 changes: 5 additions & 0 deletions plugins/UsersManager/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,11 @@ public function setTokenAuthWasUsed($tokenAuth, $dateLastUsed)
}
}

public function setRotationNotificationWasSentForToken(string $tokenId, string $tsRotation)
{
$this->updateTokenAuthTable($tokenId, ['ts_rotation_notified' => $tsRotation]);
}

private function updateTokenAuthTable($idTokenAuth, $fields)
{
$set = array();
Expand Down
61 changes: 61 additions & 0 deletions plugins/UsersManager/TokenNotifications/TokenEmailNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?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\UsersManager\TokenNotifications;

use Piwik\Container\StaticContainer;

abstract class TokenEmailNotification extends TokenNotification
{
/**
* A list of recipient emails
*
* @var array
*/
private $recipients;

/**
* Data in the format of ['[email protected]' => ['item1' => 'value1'], ...] that will be passed to the email class
*
* @var array
*/
private $emailData;

public function __construct(
string $tokenId,
string $tokenName,
string $tokenCreationDate,
array $recipients,
array $emailData
) {
parent::__construct($tokenId, $tokenName, $tokenCreationDate);

$this->recipients = $recipients;
$this->emailData = $emailData;
}

abstract public function getEmailClass(): string;

public function dispatch(): bool
{
foreach ($this->recipients as $recipient) {
$email = StaticContainer::getContainer()->make(
$this->getEmailClass(),
[
'notification' => $this,
'recipient' => $recipient,
'emailData' => $this->emailData[$recipient] ?? [],
]
);
$email->safeSend();
}

return true;
}
}
49 changes: 49 additions & 0 deletions plugins/UsersManager/TokenNotifications/TokenNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?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\UsersManager\TokenNotifications;

abstract class TokenNotification implements TokenNotificationInterface
{
/** @var string */
private $tokenId;

/** @var string */
private $tokenName;

/** @var string */
private $tokenCreationDate;

public function __construct(
string $tokenId,
string $tokenName,
string $tokenCreationDate
) {
$this->tokenId = $tokenId;
$this->tokenName = $tokenName;
$this->tokenCreationDate = $tokenCreationDate;
}

public function getTokenId(): string
{
return $this->tokenId;
}

public function getTokenName(): string
{
return $this->tokenName;
}

public function getTokenCreationDate(): string
{
return $this->tokenCreationDate;
}

abstract public function dispatch(): bool;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?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\UsersManager\TokenNotifications;

interface TokenNotificationInterface
{
public function getTokenId(): string;

public function getTokenName(): string;

public function getTokenCreationDate(): string;

public function dispatch(): bool;
}
Loading
Loading