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 2 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?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\AuthTokenNotifications;

use Piwik\Config;
use Piwik\Container\StaticContainer;
use Piwik\Plugins\UsersManager\Emails\AuthTokenNotificationEmail;

final class AuthTokenNotification
{
/** @var string */
private $tokenId;

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

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

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

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

/** @var callable */
private $onNotificationSent;

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

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

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

public function sendNotification(): void
{
// send email
$email = StaticContainer::getContainer()->make(
AuthTokenNotificationEmail::class,
[
'notification' => $this,
'rotationPeriodDays' => Config::getInstance()->General['auth_token_rotation_notification_days'],
]
);
$email->safeSend();

if (is_callable($this->onNotificationSent)) {
call_user_func($this->onNotificationSent, $this->tokenId);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?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\AuthTokenNotifications;

use Exception;
use Piwik\Container\StaticContainer;
use Piwik\Date;
use Piwik\Log;
use Piwik\Option;
use Piwik\Plugin\Manager as PluginManager;
use Piwik\Plugins\UsersManager\AuthTokenProvider;
use Piwik\Scheduler\Schedule\Daily;
use Piwik\Scheduler\Scheduler;
use Piwik\Scheduler\Task;

/**
* Used to automatically update installed GeoIP 2 databases, and manages the updater's
* scheduled task.
*/
class AuthTokenNotifierTask extends Task
{
public const LAST_RUN_TIME_OPTION_NAME = 'AuthTokenNotifier.lastRunTime';

public function __construct()
{
// all checks whether emails can be sent are done in the actual Mail class

parent::__construct($this, 'sendEmails', null, new Daily());
}

/**
* @return AuthTokenNotification[]
*/
private function getAuthTokensToNotify(): array
{
$tokensToNotify = [];
$providers = PluginManager::getInstance()->findComponents(
'AuthTokenProvider',
AuthTokenProviderInterface::class
);

/** @var AuthTokenProvider $provider */
foreach ($providers as $provider) {
array_push($tokensToNotify, ...$provider->getAuthTokensToNotify());
}

return $tokensToNotify;
}

/**
* Attempts to download new location & ISP GeoIP databases and
* replace the existing ones w/ them.
*/
public function sendEmails()
{
try {
Option::set(self::LAST_RUN_TIME_OPTION_NAME, Date::factory('today')->getTimestamp());

$tokensToNotify = $this->getAuthTokensToNotify();

// notification emails should be using `safeSend()` method so we don't do try/catch here
foreach ($tokensToNotify as $tokenToNotify) {
$tokenToNotify->sendNotification();
}

// reschedule for next run
/** @var Scheduler $scheduler */
$scheduler = StaticContainer::getContainer()->get(Scheduler::class);
// reschedule to ensure it's not run again in an hour
$scheduler->rescheduleTask(new AuthTokenNotifierTask());
} catch (Exception $ex) {
// message will already be prefixed w/ 'GeoIP2AutoUpdater: '
Log::error($ex);
throw $ex;
}
}
}
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\AuthTokenNotifications;

interface AuthTokenProviderInterface
{
/**
* Provide a list of auth tokens to be notified, each with their information
* that can be used to populate the notification email
*
* @return AuthTokenNotification[]
*/
public function getAuthTokensToNotify(): array;
}
76 changes: 76 additions & 0 deletions plugins/UsersManager/AuthTokenProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?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\Common;
use Piwik\Config;
use Piwik\Date;
use Piwik\Db;
use Piwik\Plugins\UsersManager\AuthTokenNotifications\AuthTokenNotification;
use Piwik\Plugins\UsersManager\AuthTokenNotifications\AuthTokenProviderInterface;
use Piwik\Plugins\UsersManager\Model as UserModel;

class AuthTokenProvider implements AuthTokenProviderInterface
{
/** @var Model */
private $userModel;

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

public function __construct()
{
$this->userModel = new UserModel();
$this->today = Date::factory('today')->getDatetime();
}

private function getRotationPeriodThreshold(): string
{
$periodDays = Config::getInstance()->General['auth_token_rotation_notification_days'];
return Date::factory('today')->subDay($periodDays)->getDateTime();
}

public function setTokenNotified(string $tokenId): void
{
$this->userModel->setRotationNotificationWasSentForToken($tokenId, $this->today);
}

public function getAuthTokensToNotify(): array
{
$db = Db::get();
$sql = "SELECT * FROM " . Common::prefixTable('user_token_auth')
. " WHERE (date_expired is null or date_expired > ?)"
. " AND (date_created <= ?)"
. " AND ts_rotation_notified is null";

$tokensToNotify = $db->fetchAll($sql, [
$this->today,
$this->getRotationPeriodThreshold()
]);

$notifications = [];

foreach ($tokensToNotify as $t) {
$user = $this->userModel->getUser($t->login);
$email = $user['email'];

$notifications[] = new AuthTokenNotification(
$t->idusertokenauth,
$t->description,
$t->date_created,
$t->login,
$email,
[static::class, 'setTokenNotified']
);
}

return $notifications;
}
}
Loading
Loading