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:
-rw-r--r--.gitmodules9
-rw-r--r--CHANGELOG.md6
-rw-r--r--core/Access.php27
-rw-r--r--core/CliMulti.php16
-rw-r--r--core/Db/Schema/Mysql.php26
-rw-r--r--core/Piwik.php46
-rw-r--r--core/Session/SessionAuth.php25
-rw-r--r--core/Session/SessionFingerprint.php18
-rw-r--r--core/Session/SessionInitializer.php2
-rw-r--r--core/Tracker/Request.php2
-rw-r--r--core/Updater/Migration/Plugin/Uninstall.php4
-rw-r--r--core/Updates/2.0.4-b5.php4
-rw-r--r--core/Updates/4.0.0-b1.php44
-rw-r--r--lang/en.json2
-rw-r--r--misc/cron/updatetoken.php5
m---------misc/log-analytics0
-rw-r--r--plugins/API/Controller.php4
-rw-r--r--plugins/API/css/styles.css29
-rw-r--r--plugins/API/lang/en.json2
-rw-r--r--plugins/API/templates/listAllAPI.twig11
-rw-r--r--plugins/BulkTracking/tests/Integration/RequestsTest.php4
-rw-r--r--plugins/CoreHome/angularjs/common/services/piwik-api.js3
-rw-r--r--plugins/CoreHome/angularjs/common/services/piwik-api.spec.js2
-rw-r--r--plugins/CoreHome/angularjs/widget-loader/widgetloader.directive.js4
-rw-r--r--plugins/Dashboard/tests/UI/Dashboard_spec.js2
-rw-r--r--plugins/Feedback/tests/Integration/FeedbackTest.php1
-rw-r--r--plugins/Login/Auth.php21
-rw-r--r--plugins/Login/Login.php2
-rw-r--r--plugins/Login/SessionInitializer.php2
-rw-r--r--plugins/Login/tests/Integration/LoginTest.php26
-rw-r--r--plugins/Login/tests/Integration/SessionInitializerTest.php4
-rw-r--r--plugins/Login/tests/UI/expected-screenshots/Login_bruteforcelog_noentries.png4
-rw-r--r--plugins/Login/tests/UI/expected-screenshots/Login_bruteforcelog_withentries.png4
-rw-r--r--plugins/Morpheus/javascripts/ajaxHelper.js3
-rw-r--r--plugins/Overlay/javascripts/Overlay_Helper.js2
-rw-r--r--plugins/Overlay/templates/index.twig2
-rw-r--r--plugins/Overlay/templates/index_noframe.twig2
m---------plugins/Provider0
-rw-r--r--plugins/TwoFactorAuth/Controller.php2
-rw-r--r--plugins/TwoFactorAuth/TwoFactorAuth.php6
-rw-r--r--plugins/TwoFactorAuth/templates/setupFinished.twig2
-rw-r--r--plugins/TwoFactorAuth/tests/Fixtures/TwoFactorFixture.php2
-rw-r--r--plugins/TwoFactorAuth/tests/Integration/TwoFactorAuthTest.php36
-rw-r--r--plugins/TwoFactorAuth/tests/UI/TwoFactorAuth_spec.js2
-rw-r--r--plugins/UserCountry/Columns/City.php2
-rw-r--r--plugins/UserCountry/Columns/Country.php2
-rw-r--r--plugins/UserCountry/Columns/Region.php2
-rw-r--r--plugins/UserCountryMap/javascripts/realtime-map.js2
-rw-r--r--plugins/UserCountryMap/javascripts/visitor-map.js2
-rw-r--r--plugins/UsersManager/API.php63
-rw-r--r--plugins/UsersManager/Controller.php223
-rw-r--r--plugins/UsersManager/Menu.php1
-rw-r--r--plugins/UsersManager/Model.php214
-rw-r--r--plugins/UsersManager/Tasks.php6
-rw-r--r--plugins/UsersManager/UsersManager.php32
-rw-r--r--plugins/UsersManager/angularjs/personal-settings/personal-settings.controller.js29
-rw-r--r--plugins/UsersManager/lang/en.json17
-rw-r--r--plugins/UsersManager/stylesheets/usersManager.less7
-rw-r--r--plugins/UsersManager/templates/addNewToken.twig37
-rw-r--r--plugins/UsersManager/templates/addNewTokenSuccess.twig17
-rw-r--r--plugins/UsersManager/templates/userSecurity.twig121
-rw-r--r--plugins/UsersManager/templates/userSettings.twig80
-rw-r--r--plugins/UsersManager/tests/Fixtures/ManyUsers.php7
-rw-r--r--plugins/UsersManager/tests/Integration/ModelTest.php260
-rw-r--r--plugins/UsersManager/tests/Integration/UserAccessFilterTest.php18
-rw-r--r--plugins/UsersManager/tests/Integration/UsersManagerTest.php11
-rw-r--r--plugins/UsersManager/tests/System/ApiTest.php96
-rw-r--r--plugins/UsersManager/tests/UI/UserSettings_spec.js34
-rw-r--r--plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_add_token.png3
-rw-r--r--plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_add_token_check_password.png3
-rw-r--r--plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_add_token_success.png3
-rw-r--r--plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_load_security.png3
-rw-r--r--plugins/Widgetize/templates/index.twig4
-rw-r--r--tests/PHPUnit/Fixtures/OmniFixture.php25
-rw-r--r--tests/PHPUnit/Fixtures/SqlDump.php16
-rw-r--r--tests/PHPUnit/Fixtures/UITestFixture.php7
-rw-r--r--tests/PHPUnit/Framework/Fixture.php25
-rw-r--r--tests/PHPUnit/Integration/FrontControllerTest.php5
-rw-r--r--tests/PHPUnit/Integration/Session/SessionAuthTest.php2
-rw-r--r--tests/PHPUnit/Integration/Tracker/RequestTest.php7
-rw-r--r--tests/PHPUnit/System/AllWebsitesTest.php2
-rw-r--r--tests/PHPUnit/Unit/CommonTest.php1
-rw-r--r--tests/PHPUnit/Unit/Session/SessionFingerprintTest.php8
-rw-r--r--tests/UI/expected-screenshots/Menus_admin_changed.png4
-rw-r--r--tests/UI/expected-screenshots/Menus_admin_loaded.png4
-rw-r--r--tests/UI/expected-screenshots/OptOutForm_opted-out2.png3
-rw-r--r--tests/UI/expected-screenshots/UIIntegrationTest_api_listing.png4
-rw-r--r--tests/UI/expected-screenshots/UIIntegrationTest_widgets_listing.png4
-rw-r--r--tests/UI/specs/BarGraph_spec.js2
-rw-r--r--tests/UI/specs/Comparison_spec.js2
-rw-r--r--tests/resources/install-matomo.php10
91 files changed, 1463 insertions, 387 deletions
diff --git a/.gitmodules b/.gitmodules
index fefdf57df8..71eed63ea2 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -57,15 +57,6 @@
[submodule "plugins/DeviceDetectorCache"]
path = plugins/DeviceDetectorCache
url = https://github.com/matomo-org/plugin-DeviceDetectorCache.git
-[submodule "plugins/Provider"]
- path = plugins/Provider
- url = https://github.com/matomo-org/plugin-Provider.git
-
-# Add new Plugin submodule above this line ^^
-#
-# Note: when you add a submodule that SHOULD be left in the packaged release such as the few submodules below,
-# then you MUST add these submodules names in the SUBMODULES_PACKAGED_WITH_CORE variable in:
-# https://github.com/matomo-org/matomo-package/blob/master/scripts/build-package.sh
[submodule "misc/log-analytics"]
path = misc/log-analytics
url = https://github.com/matomo-org/matomo-log-analytics.git
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a3741a87be..a0efe87536 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,8 +6,12 @@ The Product Changelog at **[matomo.org/changelog](https://matomo.org/changelog)*
## Matomo 4.0.0
-### Breaking Changes
+### New API
+* A new API `UsersManager.createAppSpecificTokenAuth` has been added to create an app specific token for a user.
+### Breaking changes
+* The API `UsersManager.getTokenAuth` has been removed. Instead you need to use `UsersManager.createAppSpecificTokenAuth` and store this token in your application.
+* The API `UsersManager.createTokenAuth` has been removed. Instead you need to use `UsersManager.createAppSpecificTokenAuth` and store this token in your application.
* Deprecated `piwik` font was removed. Use `matomo` font instead
* The JavaScript AjaxHelper does not longer support synchronous requests. All requests will be sent async instead.
* The deprecated Platform API method `\Piwik\Plugin::getListHooksRegistered()` has been removed. Use `\Piwik\Plugin::registerEvents()` instead
diff --git a/core/Access.php b/core/Access.php
index d99551c219..7e4bcd59d8 100644
--- a/core/Access.php
+++ b/core/Access.php
@@ -15,6 +15,7 @@ use Piwik\Access\RolesProvider;
use Piwik\Container\StaticContainer;
use Piwik\Exception\InvalidRequestParameterException;
use Piwik\Plugins\SitesManager\API as SitesManagerApi;
+use Piwik\Session\SessionAuth;
/**
* Singleton that manages user access to Piwik resources.
@@ -155,8 +156,32 @@ class Access
return false;
}
+ $result = null;
+
+ $forceApiSession = Common::getRequestVar('force_api_session', 0, 'int', $_POST);
+ if ($forceApiSession && Piwik::getModule() === 'API' && (Piwik::getAction() === 'index' || !Piwik::getAction())) {
+ $tokenAuth = Common::getRequestVar('token_auth', '', 'string', $_POST);
+ if (!empty($tokenAuth)) {
+ Session::start();
+ $auth = StaticContainer::get(SessionAuth::class);
+ $auth->setTokenAuth($tokenAuth);
+ $result = $auth->authenticate();
+ if (!$result->wasAuthenticationSuccessful()) {
+ /**
+ * Ensures brute force logic to be executed
+ * @ignore
+ * @internal
+ */
+ Piwik::postEvent('API.Request.authenticate.failed');
+ }
+ // if not successful, we will fallback to regular auth
+ }
+ }
+
// access = array ( idsite => accessIdSite, idsite2 => accessIdSite2)
- $result = $this->auth->authenticate();
+ if (!$result || !$result->wasAuthenticationSuccessful()) {
+ $result = $this->auth->authenticate();
+ }
if (!$result->wasAuthenticationSuccessful()) {
return false;
diff --git a/core/CliMulti.php b/core/CliMulti.php
index 863bea1b3b..751c5fbd3e 100644
--- a/core/CliMulti.php
+++ b/core/CliMulti.php
@@ -365,8 +365,7 @@ class CliMulti
}
if ($this->runAsSuperUser) {
- $tokenAuths = self::getSuperUserTokenAuths();
- $tokenAuth = reset($tokenAuths);
+ $tokenAuth = self::getSuperUserTokenAuth();
if (strpos($url, '?') === false) {
$url .= '?';
@@ -433,18 +432,9 @@ class CliMulti
return $results;
}
- private static function getSuperUserTokenAuths()
+ private static function getSuperUserTokenAuth()
{
- $tokens = array();
-
- /**
- * Used to be in CronArchive, moved to CliMulti.
- *
- * @ignore
- */
- Piwik::postEvent('CronArchive.getTokenAuth', array(&$tokens));
-
- return $tokens;
+ return Piwik::requestTemporarySystemAuthToken('CliMultiNonAsyncArchive', 36);
}
public function setUrlToPiwik($urlToPiwik)
diff --git a/core/Db/Schema/Mysql.php b/core/Db/Schema/Mysql.php
index ffe4aa5b65..01ea82492a 100644
--- a/core/Db/Schema/Mysql.php
+++ b/core/Db/Schema/Mysql.php
@@ -16,6 +16,7 @@ use Piwik\Db\SchemaInterface;
use Piwik\Db;
use Piwik\DbHelper;
use Piwik\Option;
+use Piwik\Plugins\UsersManager\Model;
use Piwik\Version;
/**
@@ -45,12 +46,24 @@ class Mysql implements SchemaInterface
alias VARCHAR(45) NOT NULL,
email VARCHAR(100) NOT NULL,
twofactor_secret VARCHAR(40) NOT NULL DEFAULT '',
- token_auth CHAR(32) NOT NULL,
superuser_access TINYINT(2) unsigned NOT NULL DEFAULT '0',
date_registered TIMESTAMP NULL,
ts_password_modified TIMESTAMP NULL,
- PRIMARY KEY(login),
- UNIQUE KEY uniq_keytoken(token_auth)
+ PRIMARY KEY(login)
+ ) ENGINE=$engine DEFAULT CHARSET=utf8
+ ",
+ 'user_token_auth' => "CREATE TABLE {$prefixTables}user_token_auth (
+ idusertokenauth BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+ login VARCHAR(100) NOT NULL,
+ description VARCHAR(".Model::MAX_LENGTH_TOKEN_DESCRIPTION.") NOT NULL,
+ password VARCHAR(255) NOT NULL,
+ hash_algo VARCHAR(30) NOT NULL,
+ system_token TINYINT(1) NOT NULL DEFAULT 0,
+ last_used DATETIME NULL,
+ date_created DATETIME NOT NULL,
+ date_expired DATETIME NULL,
+ PRIMARY KEY(idusertokenauth),
+ UNIQUE KEY uniq_password(password)
) ENGINE=$engine DEFAULT CHARSET=utf8
",
@@ -504,12 +517,15 @@ class Mysql implements SchemaInterface
public function createAnonymousUser()
{
$now = Date::factory('now')->getDatetime();
-
// The anonymous user is the user that is assigned by default
// note that the token_auth value is anonymous, which is assigned by default as well in the Login plugin
$db = $this->getDb();
$db->query("INSERT IGNORE INTO " . Common::prefixTable("user") . "
- VALUES ( 'anonymous', '', 'anonymous', 'anonymous@example.org', '', 'anonymous', 0, '$now', '$now' );");
+ (`login`, `password`, `alias`, `email`, `twofactor_secret`, `superuser_access`, `date_registered`, `ts_password_modified`)
+ VALUES ( 'anonymous', '', 'anonymous', 'anonymous@example.org', '', 0, '$now', '$now' );");
+
+ $model = new Model();
+ $model->addTokenAuth('anonymous', 'anonymous', 'anonymous default token', $now);
}
/**
diff --git a/core/Piwik.php b/core/Piwik.php
index 32520ffec4..d16ffca97e 100644
--- a/core/Piwik.php
+++ b/core/Piwik.php
@@ -16,6 +16,7 @@ use Piwik\Period\Range;
use Piwik\Period\Week;
use Piwik\Period\Year;
use Piwik\Plugins\UsersManager\API as APIUsersManager;
+use Piwik\Plugins\UsersManager\Model;
use Piwik\Translation\Translator;
/**
@@ -258,6 +259,51 @@ class Piwik
}
/**
+ * Request a token auth to authenticate in a request.
+ *
+ * Note: During one request the token is only being requested once and used throughout the request. So you want to make
+ * sure the token is valid for enough time for the whole request to finish.
+ *
+ * @param string $reason some short string/text explaining the reason for the token generation, eg "CliMultiAsyncHttpArchiving"
+ * @param int $validForHours For how many hours the token should be valid. Should not be valid for more than 14 days.
+ * @return mixed
+ */
+ public static function requestTemporarySystemAuthToken($reason, $validForHours)
+ {
+ static $token = array();
+
+ if (isset($token[$reason])) {
+ // note: For now we do not increase the expire time when it is already requested
+ return $token[$reason];
+ }
+
+ $twoWeeksInHours = 14 * 24;
+ if ($validForHours > $twoWeeksInHours) {
+ throw new Exception('The token cannot be valid for so many hours: ' . $validForHours);
+ }
+
+ $model = new Model();
+ $users = $model->getUsersHavingSuperUserAccess();
+ if (!empty($users)) {
+ $user = reset($users);
+ $expireDate = Date::now()->addHour($validForHours)->getDatetime();
+
+ $token[$reason] = $model->generateRandomTokenAuth();
+
+ $model->addTokenAuth(
+ $user['login'],
+ $token[$reason],
+ 'System generated ' . $reason,
+ Date::now()->getDatetime(),
+ $expireDate,
+ true);
+
+ return $token[$reason];
+ }
+
+ }
+
+ /**
* Check whether the given user has superuser access.
*
* @param string $theUser A username.
diff --git a/core/Session/SessionAuth.php b/core/Session/SessionAuth.php
index 001b3d2bb0..da896b991e 100644
--- a/core/Session/SessionAuth.php
+++ b/core/Session/SessionAuth.php
@@ -11,8 +11,10 @@ namespace Piwik\Session;
use Piwik\Auth;
use Piwik\AuthResult;
+use Piwik\Common;
use Piwik\Config;
use Piwik\Date;
+use Piwik\Plugins\UsersManager\Model;
use Piwik\Plugins\UsersManager\Model as UsersModel;
use Piwik\Session;
@@ -42,6 +44,8 @@ class SessionAuth implements Auth
*/
private $user;
+ private $tokenAuth;
+
public function __construct(UsersModel $userModel = null, $shouldDestroySession = true)
{
$this->userModel = $userModel ?: new UsersModel();
@@ -55,7 +59,7 @@ class SessionAuth implements Auth
public function setTokenAuth($token_auth)
{
- // empty
+ $this->tokenAuth = $token_auth;
}
public function getLogin()
@@ -113,7 +117,17 @@ class SessionAuth implements Auth
$this->updateSessionExpireTime($sessionFingerprint);
- return $this->makeAuthSuccess($user);
+ if (!empty($this->tokenAuth) && $this->tokenAuth !== $sessionFingerprint->getSessionTokenAuth()) {
+ return $this->makeAuthFailure();
+ }
+
+ if ($sessionFingerprint->getSessionTokenAuth()) {
+ $tokenAuth = $sessionFingerprint->getSessionTokenAuth();
+ } else {
+ $tokenAuth = $this->userModel->generateRandomTokenAuth();
+ }
+
+ return $this->makeAuthSuccess($user, $tokenAuth);
}
private function isSessionStartedBeforePasswordChange(SessionFingerprint $sessionFingerprint, $tsPasswordModified)
@@ -137,14 +151,15 @@ class SessionAuth implements Auth
return new AuthResult(AuthResult::FAILURE, null, null);
}
- private function makeAuthSuccess($user)
+ private function makeAuthSuccess($user, $tokenAuth)
{
$this->user = $user;
+ $this->tokenAuth = $tokenAuth;
$isSuperUser = (int) $user['superuser_access'];
$code = $isSuperUser ? AuthResult::SUCCESS_SUPERUSER_AUTH_CODE : AuthResult::SUCCESS;
- return new AuthResult($code, $user['login'], $user['token_auth']);
+ return new AuthResult($code, $user['login'], $tokenAuth);
}
protected function initNewBlankSession(SessionFingerprint $sessionFingerprint)
@@ -178,7 +193,7 @@ class SessionAuth implements Auth
public function getTokenAuth()
{
- return $this->user['token_auth'];
+ return $this->tokenAuth;
}
private function updateSessionExpireTime(SessionFingerprint $sessionFingerprint)
diff --git a/core/Session/SessionFingerprint.php b/core/Session/SessionFingerprint.php
index df8a519e5e..3399fa7a0c 100644
--- a/core/Session/SessionFingerprint.php
+++ b/core/Session/SessionFingerprint.php
@@ -9,6 +9,7 @@
namespace Piwik\Session;
+use Piwik\Common;
use Piwik\Config;
use Piwik\Date;
@@ -42,6 +43,7 @@ class SessionFingerprint
const USER_NAME_SESSION_VAR_NAME = 'user.name';
const SESSION_INFO_SESSION_VAR_NAME = 'session.info';
const SESSION_INFO_TWO_FACTOR_AUTH_VERIFIED = 'twofactorauth.verified';
+ const SESSION_INFO_TEMP_TOKEN_AUTH = 'user.token_auth_temp';
public function getUser()
{
@@ -61,6 +63,15 @@ class SessionFingerprint
return null;
}
+ public function getSessionTokenAuth()
+ {
+ if (!empty($_SESSION[self::SESSION_INFO_TEMP_TOKEN_AUTH])) {
+ return $_SESSION[self::SESSION_INFO_TEMP_TOKEN_AUTH];
+ }
+
+ return null;
+ }
+
public function hasVerifiedTwoFactor()
{
if (isset($_SESSION[self::SESSION_INFO_TWO_FACTOR_AUTH_VERIFIED])) {
@@ -75,11 +86,12 @@ class SessionFingerprint
$_SESSION[self::SESSION_INFO_TWO_FACTOR_AUTH_VERIFIED] = 1;
}
- public function initialize($userName, $isRemembered = false, $time = null)
+ public function initialize($userName, $tokenAuth, $isRemembered = false, $time = null)
{
$time = $time ?: Date::now()->getTimestampUTC();
$_SESSION[self::USER_NAME_SESSION_VAR_NAME] = $userName;
$_SESSION[self::SESSION_INFO_TWO_FACTOR_AUTH_VERIFIED] = 0;
+ $_SESSION[self::SESSION_INFO_TEMP_TOKEN_AUTH] = $tokenAuth;
$_SESSION[self::SESSION_INFO_SESSION_VAR_NAME] = [
'ts' => $time,
'remembered' => $isRemembered,
@@ -100,6 +112,10 @@ class SessionFingerprint
if (isset($_SESSION[self::SESSION_INFO_TWO_FACTOR_AUTH_VERIFIED])) { // may not be available during tests
unset($_SESSION[self::SESSION_INFO_TWO_FACTOR_AUTH_VERIFIED]);
}
+
+ if (isset($_SESSION[self::SESSION_INFO_TEMP_TOKEN_AUTH])) { // may not be available during tests
+ unset($_SESSION[self::SESSION_INFO_TEMP_TOKEN_AUTH]);
+ }
}
public function getSessionStartTime()
diff --git a/core/Session/SessionInitializer.php b/core/Session/SessionInitializer.php
index 4021890167..11d753395d 100644
--- a/core/Session/SessionInitializer.php
+++ b/core/Session/SessionInitializer.php
@@ -83,7 +83,7 @@ class SessionInitializer
protected function processSuccessfulSession(AuthResult $authResult)
{
$sessionIdentifier = new SessionFingerprint();
- $sessionIdentifier->initialize($authResult->getIdentity(), $this->isRemembered());
+ $sessionIdentifier->initialize($authResult->getIdentity(), $authResult->getTokenAuth(), $this->isRemembered());
/**
* @ignore
diff --git a/core/Tracker/Request.php b/core/Tracker/Request.php
index 7da7f12287..3b9bae24d3 100644
--- a/core/Tracker/Request.php
+++ b/core/Tracker/Request.php
@@ -208,6 +208,8 @@ class Request
// Now checking the list of admin token_auth cached in the Tracker config file
if (!empty($idSite) && $idSite > 0) {
$website = Cache::getCacheWebsiteAttributes($idSite);
+ $userModel = new \Piwik\Plugins\UsersManager\Model();
+ $tokenAuth = $userModel->hashTokenAuth($tokenAuth);
$hashedToken = UsersManager::hashTrackingToken((string) $tokenAuth, $idSite);
if (array_key_exists('tracking_token_auth', $website)
diff --git a/core/Updater/Migration/Plugin/Uninstall.php b/core/Updater/Migration/Plugin/Uninstall.php
index 77e95d2f24..ed4b29a08f 100644
--- a/core/Updater/Migration/Plugin/Uninstall.php
+++ b/core/Updater/Migration/Plugin/Uninstall.php
@@ -48,6 +48,10 @@ class Uninstall extends Migration
public function exec()
{
$this->pluginManager->uninstallPlugin($this->pluginName);
+
+ // uninstallPlugin() loads all plugins in the filesystem, which we don't want for the rest of the updates
+ $this->pluginManager->unloadPlugins();
+ $this->pluginManager->loadActivatedPlugins();
}
}
diff --git a/core/Updates/2.0.4-b5.php b/core/Updates/2.0.4-b5.php
index 2282ca1dae..a96d9e4cc3 100644
--- a/core/Updates/2.0.4-b5.php
+++ b/core/Updates/2.0.4-b5.php
@@ -36,7 +36,7 @@ class Updates_2_0_4_b5 extends Updates
public function getMigrations(Updater $updater)
{
return array(
- $this->migration->db->addColumn('user', 'superuser_access', "TINYINT(2) UNSIGNED NOT NULL DEFAULT '0'", 'token_auth')
+ $this->migration->db->addColumn('user', 'superuser_access', "TINYINT(2) UNSIGNED NOT NULL DEFAULT '0'")
);
}
@@ -82,7 +82,7 @@ class Updates_2_0_4_b5 extends Updates
'password' => $superUser['password'],
'alias' => $superUser['login'],
'email' => $superUser['email'],
- 'token_auth' => $userApi->getTokenAuth($superUser['login'], $superUser['password']),
+ 'token_auth' => md5(Common::getRandomString(32)),
'date_registered' => Date::now()->getDatetime(),
'superuser_access' => 1
)
diff --git a/core/Updates/4.0.0-b1.php b/core/Updates/4.0.0-b1.php
index c38b1a1eb5..2b289f6a44 100644
--- a/core/Updates/4.0.0-b1.php
+++ b/core/Updates/4.0.0-b1.php
@@ -9,6 +9,9 @@
namespace Piwik\Updates;
+use Piwik\Date;
+use Piwik\DbHelper;
+use Piwik\Plugins\UsersManager\Model;
use Piwik\Common;
use Piwik\Config;
use Piwik\Plugins\UserCountry\LocationProvider;
@@ -33,10 +36,49 @@ class Updates_4_0_0_b1 extends PiwikUpdates
public function getMigrations(Updater $updater)
{
- $migrations = [];
+ $migrations = array();
$migrations[] = $this->migration->db->changeColumnType('log_action', 'name', 'VARCHAR(4096)');
$migrations[] = $this->migration->db->changeColumnType('log_conversion', 'url', 'VARCHAR(4096)');
+ /** APP SPECIFIC TOKEN START */
+ $migrations[] = $this->migration->db->createTable('user_token_auth', array(
+ 'idusertokenauth' => 'BIGINT UNSIGNED NOT NULL AUTO_INCREMENT',
+ 'login' => 'VARCHAR(100) NOT NULL',
+ 'description' => 'VARCHAR('.Model::MAX_LENGTH_TOKEN_DESCRIPTION.') NOT NULL',
+ 'password' => 'VARCHAR(255) NOT NULL',
+ 'system_token' => 'TINYINT(1) NOT NULL DEFAULT 0',
+ 'hash_algo' => 'VARCHAR(30) NOT NULL',
+ 'last_used' => 'DATETIME NULL',
+ 'date_created' => ' DATETIME NOT NULL',
+ 'date_expired' => ' DATETIME NULL',
+ ), 'idusertokenauth');
+ $migrations[] = $this->migration->db->addUniqueKey('user_token_auth', 'password', 'uniq_password');
+
+ $migrations[] = $this->migration->db->dropIndex('user', 'uniq_keytoken');
+
+ $userModel = new Model();
+ foreach ($userModel->getUsers(array()) as $user) {
+ if (!empty($user['token_auth'])) {
+ $migrations[] = $this->migration->db->insert('user_token_auth', array(
+ 'login' => $user['login'],
+ 'description' => 'Created by Matomo 4 migration',
+ 'password' => $userModel->hashTokenAuth($user['token_auth']),
+ 'date_created' => Date::now()->getDatetime()
+ ));
+ }
+ }
+
+ // we don't delete the token_auth column so users can still downgrade to 3.X if they want to. However, the original
+ // token_auth will be regenerated for security reasons to no longer have it in plain text. So this column will be no longer used
+ // unless someone downgrades to 3.x
+ $columns = DbHelper::getTableColumns(Common::prefixTable('user'));
+ if (isset($columns['token_auth'])) {
+ $sql = sprintf('UPDATE %s set token_auth = MD5(CONCAT(NOW(), UUID()))', Common::prefixTable('user'));
+ $migrations[] = $this->migration->db->sql($sql, Updater\Migration\Db::ERROR_CODE_UNKNOWN_COLUMN);
+ }
+
+ /** APP SPECIFIC TOKEN END */
+
$customTrackerPluginActive = false;
if (in_array('CustomPiwikJs', Config::getInstance()->Plugins['Plugins'])) {
$customTrackerPluginActive = true;
diff --git a/lang/en.json b/lang/en.json
index c3c715ea23..cda996a2a2 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -112,6 +112,7 @@
"Continue": "Continue",
"ContinueToPiwik": "Continue to Matomo",
"CurrentlyUsingUnsecureHttp": "You are currently using Matomo over unsecure HTTP. This can make your Matomo vulnerable to security exploits. You may also be in breach of privacy laws, as some features including opt-out cookies will not work. We recommend you set up Matomo to use SSL (HTTPS) for improved security.",
+ "CreationDate": "Creation date",
"CreatedByUser": "created by %s",
"CurrentMonth": "Current Month",
"CurrentWeek": "Current Week",
@@ -389,6 +390,7 @@
"Search": "Search",
"Clear": "Clear",
"SearchNoResults": "No results",
+ "Security": "Security",
"SeeAll": "see all",
"SeeTheOfficialDocumentationForMoreInformation": "See the %1$sofficial documentation%2$s for more information.",
"SeeThisFaq": "See %1$sthis faq%2$s.",
diff --git a/misc/cron/updatetoken.php b/misc/cron/updatetoken.php
index b10a21d347..f1c154705b 100644
--- a/misc/cron/updatetoken.php
+++ b/misc/cron/updatetoken.php
@@ -56,10 +56,7 @@ if($piwikDomain) {
$environment = new Environment('cli');
$environment->init();
-$token = Db::get()->fetchOne("SELECT token_auth
- FROM " . Common::prefixTable("user") . "
- WHERE superuser_access = 1
- ORDER BY date_registered ASC");
+$token = Piwik::requestTemporarySystemAuthToken('LogImporter', 48);
$filename = $environment->getContainer()->get('path.tmp') . '/cache/token.php';
diff --git a/misc/log-analytics b/misc/log-analytics
-Subproject b3973f3cd0cb773eb9cf3bfff439cc43640c5f1
+Subproject 4794df04cb9187031a044e79b77351c4f8e8ef0
diff --git a/plugins/API/Controller.php b/plugins/API/Controller.php
index 326b048926..d46bdf158a 100644
--- a/plugins/API/Controller.php
+++ b/plugins/API/Controller.php
@@ -26,7 +26,9 @@ class Controller extends \Piwik\Plugin\Controller
{
function index()
{
- $token = 'token_auth=' . Common::getRequestVar('token_auth', 'anonymous', 'string');
+ $tokenAuth = Common::getRequestVar('token_auth', 'anonymous', 'string');
+
+ $token = 'token_auth=' . $tokenAuth;
// when calling the API through http, we limit the number of returned results
if (!isset($_GET['filter_limit'])) {
diff --git a/plugins/API/css/styles.css b/plugins/API/css/styles.css
new file mode 100644
index 0000000000..3b52ba995a
--- /dev/null
+++ b/plugins/API/css/styles.css
@@ -0,0 +1,29 @@
+#token_auth {
+ background-color:#E8FFE9;
+ border-color:#00CC3A;
+ border-style: solid;
+ border-width: 1px;
+ margin: 0pt 0pt 16px 8px;
+ padding: 12px;
+ line-height:4em;
+}
+.example, .example A {
+ color:#9E9E9E;
+}
+
+.page_api{
+ padding:0px 15px 0 15px;
+ font-size:13px;
+}
+
+.page_api h2 {
+ border-bottom:1px solid #DADADA;
+ margin:10px -15px 15px 0px;
+ padding:0pt 0px 5px 0pt;
+ font-size:24px;
+}
+
+.page_api p {
+ line-height:140%;
+ padding-bottom:20px;
+}
diff --git a/plugins/API/lang/en.json b/plugins/API/lang/en.json
index 72dd837fae..cc43b92313 100644
--- a/plugins/API/lang/en.json
+++ b/plugins/API/lang/en.json
@@ -9,7 +9,7 @@
"ReportingApiReference": "Reporting API Reference",
"TopLinkTooltip": "Access your Web Analytics data programmatically through a simple API in json, xml, etc.",
"UserAuthentication": "User authentication",
- "UsingTokenAuth": "If you want to %1$s request data within a script, a crontab, etc. %2$s you need to add the parameter %3$s to the API calls URLs that require authentication.",
+ "UsingTokenAuth": "If you want to %1$s request data within a script, a crontab, etc. %2$s you need to add the URL parameter %3$s to the API calls URLs that require authentication.",
"Glossary": "Glossary",
"LearnAboutCommonlyUsedTerms2": "Learn about the commonly used terms to make the most of Matomo Analytics.",
"EvolutionMetricName": "%s Evolution"
diff --git a/plugins/API/templates/listAllAPI.twig b/plugins/API/templates/listAllAPI.twig
index 17da68de1d..9c9c2ea70f 100644
--- a/plugins/API/templates/listAllAPI.twig
+++ b/plugins/API/templates/listAllAPI.twig
@@ -19,13 +19,12 @@
</div>
<div piwik-content-block content-title="{{ 'API_UserAuthentication'|translate|e('html_attr') }}">
<p>
- {{ 'API_UsingTokenAuth'|translate('','',"")|raw }}<br/>
- <pre piwik-select-on-focus id='token_auth'>&amp;token_auth=<strong piwik-show-sensitive-data="{{ token_auth }}" data-click-element-selector="#token_auth"></strong></pre><br/>
- {{ 'API_KeepTokenSecret'|translate('<b>','</b>')|raw }}<br />
- {{ 'API_ChangeTokenHint'|translate('<a href="' ~ linkTo({
+ {{ 'API_UsingTokenAuth'|translate('','',"<code>token_auth</code>")|raw }} <a target="_blank" rel="noreferrer noopener" href="https://developer.matomo.org/api-reference/reporting-api#authenticate-to-the-api-via-token_auth-parameter">{{ 'CoreAdminHome_LearnMore'|translate }}</a><br/>
+ <br/>
+ <a href="{{ linkTo({
'module': 'UsersManager',
- 'action': 'userSettings',
- }) ~ '">', '</a>')|raw }}
+ 'action': 'userSecurity',
+ }) }}#/#authtokens">You can manage your authentication tokens on your security page.</a>
</p>
</div>
{{ list_api_methods_with_links|raw }}
diff --git a/plugins/BulkTracking/tests/Integration/RequestsTest.php b/plugins/BulkTracking/tests/Integration/RequestsTest.php
index 3e4e0fbc7e..6b67b88ae2 100644
--- a/plugins/BulkTracking/tests/Integration/RequestsTest.php
+++ b/plugins/BulkTracking/tests/Integration/RequestsTest.php
@@ -8,8 +8,10 @@
namespace Piwik\Plugins\BulkTracking\tests\Integration;
+use Piwik\Container\StaticContainer;
use Piwik\Plugins\BulkTracking\Tracker\Requests;
use Piwik\Plugins\UsersManager\API;
+use Piwik\Plugins\UsersManager\Model;
use Piwik\Plugins\UsersManager\UsersManager;
use Piwik\Tests\Framework\Fixture;
use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
@@ -88,7 +90,7 @@ class RequestsTest extends IntegrationTestCase
$this->expectException(\Exception::class);
$this->expectExceptionMessage('token_auth specified does not have Admin permission for idsite=1');
- $dummyToken = API::getInstance()->createTokenAuth('test');
+ $dummyToken = StaticContainer::get(Model::class)->generateRandomTokenAuth();
$superUserToken = $this->getSuperUserToken();
$requests = array($this->buildDummyRequest($superUserToken), $this->buildDummyRequest($dummyToken));
diff --git a/plugins/CoreHome/angularjs/common/services/piwik-api.js b/plugins/CoreHome/angularjs/common/services/piwik-api.js
index e2195f7ff1..e34ca41593 100644
--- a/plugins/CoreHome/angularjs/common/services/piwik-api.js
+++ b/plugins/CoreHome/angularjs/common/services/piwik-api.js
@@ -49,6 +49,7 @@ var hasBlockedContent = false;
function withTokenInUrl()
{
postParams['token_auth'] = piwik.token_auth;
+ postParams['force_api_session'] = '1';
}
function isRequestToApiMethod() {
@@ -191,6 +192,7 @@ var hasBlockedContent = false;
function getPostParams (params) {
if (isRequestToApiMethod() || piwik.shouldPropagateTokenAuth) {
params.token_auth = piwik.token_auth;
+ params.force_api_session = '1';
}
return params;
@@ -276,6 +278,7 @@ var hasBlockedContent = false;
if (_postParams_) {
if (postParams && postParams.token_auth && !_postParams_.token_auth) {
_postParams_.token_auth = postParams.token_auth;
+ _postParams_.force_api_session = '1';
}
postParams = _postParams_;
}
diff --git a/plugins/CoreHome/angularjs/common/services/piwik-api.spec.js b/plugins/CoreHome/angularjs/common/services/piwik-api.spec.js
index e33fa329e2..916c5b73e1 100644
--- a/plugins/CoreHome/angularjs/common/services/piwik-api.spec.js
+++ b/plugins/CoreHome/angularjs/common/services/piwik-api.spec.js
@@ -218,7 +218,7 @@
]).then(function (response) {
var restOfExpected = "index.php?method=API.getBulkRequest&module=API&format=JSON2&idSite=1&period=day&date= - "
+ "urls%5B%5D=%3Fmethod%3DSomePlugin.action%26param%3Dvalue&urls%5B%5D=%3Fmethod%3DSomeOtherPlugin.action"
- + "&token_auth=100bf5eeeed1468f3f9d93750044d3dd";
+ + "&token_auth=100bf5eeeed1468f3f9d93750044d3dd&force_api_session=1";
expect(response.length).to.equal(2);
expect(response[0]).to.equal("Response #1: " + restOfExpected);
diff --git a/plugins/CoreHome/angularjs/widget-loader/widgetloader.directive.js b/plugins/CoreHome/angularjs/widget-loader/widgetloader.directive.js
index 16a5a91105..d3c35b1115 100644
--- a/plugins/CoreHome/angularjs/widget-loader/widgetloader.directive.js
+++ b/plugins/CoreHome/angularjs/widget-loader/widgetloader.directive.js
@@ -113,7 +113,7 @@
}
if (piwik.shouldPropagateTokenAuth && broadcast.getValueFromUrl('token_auth')) {
- url += '&token_auth=' + broadcast.getValueFromUrl('token_auth');
+ url += '&force_api_session=1&token_auth=' + broadcast.getValueFromUrl('token_auth');
}
url += '&random=' + parseInt(Math.random() * 10000);
@@ -198,4 +198,4 @@
}
};
}
-})(); \ No newline at end of file
+})();
diff --git a/plugins/Dashboard/tests/UI/Dashboard_spec.js b/plugins/Dashboard/tests/UI/Dashboard_spec.js
index 6ac640b7a8..b3914f3bc3 100644
--- a/plugins/Dashboard/tests/UI/Dashboard_spec.js
+++ b/plugins/Dashboard/tests/UI/Dashboard_spec.js
@@ -299,7 +299,7 @@ describe("Dashboard", function () {
testEnvironment.testUseMockAuth = 0;
testEnvironment.save();
- var tokenAuth = "9ad1de7f8b329ab919d854c556f860c1";
+ var tokenAuth = "c4ca4238a0b923820dcc509a6f75849b";
await page.goto(url.replace("idDashboard=5", "idDashboard=1") + '&token_auth=' + tokenAuth);
expect(await page.screenshot({ fullPage: true })).to.matchImage('loaded_token_auth');
diff --git a/plugins/Feedback/tests/Integration/FeedbackTest.php b/plugins/Feedback/tests/Integration/FeedbackTest.php
index 072a12186b..33c88e1336 100644
--- a/plugins/Feedback/tests/Integration/FeedbackTest.php
+++ b/plugins/Feedback/tests/Integration/FeedbackTest.php
@@ -39,7 +39,6 @@ class FeedbackTest extends IntegrationTestCase
'a98732d98732',
'user1@example.com',
'user1',
- 'ab9879dc23876f19',
'2019-03-03'
);
FakeAccess::$identity = 'user1';
diff --git a/plugins/Login/Auth.php b/plugins/Login/Auth.php
index 3d572abcf9..a196b48b15 100644
--- a/plugins/Login/Auth.php
+++ b/plugins/Login/Auth.php
@@ -10,6 +10,7 @@ namespace Piwik\Plugins\Login;
use Piwik\AuthResult;
use Piwik\Auth\Password;
+use Piwik\Date;
use Piwik\Piwik;
use Piwik\Plugins\UsersManager\Model;
use Piwik\Plugins\UsersManager\UsersManager;
@@ -76,8 +77,9 @@ class Auth implements \Piwik\Auth
if ($this->passwordHelper->needsRehash($user['password'])) {
$newPasswordHash = $this->passwordHelper->hash($passwordHash);
- $this->userModel->updateUser($login, $newPasswordHash, $user['email'], $user['alias'], $user['token_auth']);
+ $this->userModel->updateUser($login, $newPasswordHash, $user['email'], $user['alias']);
}
+ $this->token_auth = null; // make sure to generate a random token
return $this->authenticationSuccess($user);
}
@@ -90,6 +92,7 @@ class Auth implements \Piwik\Auth
$user = $this->userModel->getUserByTokenAuth($token);
if (!empty($user['login'])) {
+ $this->userModel->setTokenAuthWasUsed($token, Date::now()->getDatetime());
return $this->authenticationSuccess($user);
}
@@ -98,13 +101,10 @@ class Auth implements \Piwik\Auth
private function authenticateWithLoginAndToken($token, $login)
{
- $user = $this->userModel->getUser($login);
+ $user = $this->userModel->getUserByTokenAuth($token);
- if (!empty($user['token_auth'])
- // authenticate either with the token or the "hash token"
- && ((SessionInitializer::getHashTokenAuth($login, $user['token_auth']) === $token)
- || $user['token_auth'] === $token)
- ) {
+ if (!empty($user['login']) && $user['login'] === $login) {
+ $this->userModel->setTokenAuthWasUsed($token, Date::now()->getDatetime());
return $this->authenticationSuccess($user);
}
@@ -113,12 +113,15 @@ class Auth implements \Piwik\Auth
private function authenticationSuccess(array $user)
{
- $this->setTokenAuth($user['token_auth']);
+ if (empty($this->token_auth)) {
+ $this->token_auth = $this->userModel->generateRandomTokenAuth();
+ // we generated one randomly which will then be stored in the session and used across the session
+ }
$isSuperUser = (int) $user['superuser_access'];
$code = $isSuperUser ? AuthResult::SUCCESS_SUPERUSER_AUTH_CODE : AuthResult::SUCCESS;
- return new AuthResult($code, $user['login'], $user['token_auth']);
+ return new AuthResult($code, $user['login'], $this->token_auth);
}
/**
diff --git a/plugins/Login/Login.php b/plugins/Login/Login.php
index d60358c709..9f6ed6f820 100644
--- a/plugins/Login/Login.php
+++ b/plugins/Login/Login.php
@@ -41,7 +41,7 @@ class Login extends \Piwik\Plugin
// for brute force prevention of all tracking + reporting api requests
'Request.initAuthenticationObject' => 'onInitAuthenticationObject',
- 'API.UsersManager.getTokenAuth' => 'beforeLoginCheckBruteForce', // doesn't require auth but can be used to authenticate
+ 'API.UsersManager.createAppSpecificTokenAuth' => 'beforeLoginCheckBruteForce', // doesn't require auth but can be used to authenticate
// for brute force prevention of all UI requests
'Controller.Login.logme' => 'beforeLoginCheckBruteForce',
diff --git a/plugins/Login/SessionInitializer.php b/plugins/Login/SessionInitializer.php
index 0b31f62114..b997358a4b 100644
--- a/plugins/Login/SessionInitializer.php
+++ b/plugins/Login/SessionInitializer.php
@@ -182,8 +182,6 @@ class SessionInitializer
protected function processSuccessfulSession(AuthResult $authResult, $rememberMe)
{
$cookie = $this->getAuthCookie($rememberMe);
- $cookie->set('login', $authResult->getIdentity());
- $cookie->set('token_auth', $this->getHashTokenAuth($authResult->getIdentity(), $authResult->getTokenAuth()));
$cookie->setSecure(ProxyHttp::isHttps());
$cookie->setHttpOnly(true);
$cookie->save();
diff --git a/plugins/Login/tests/Integration/LoginTest.php b/plugins/Login/tests/Integration/LoginTest.php
index a0351b5ea1..8a45a53096 100644
--- a/plugins/Login/tests/Integration/LoginTest.php
+++ b/plugins/Login/tests/Integration/LoginTest.php
@@ -11,6 +11,7 @@ namespace Piwik\Plugins\Login\tests\Integration;
use Piwik\AuthResult;
use Piwik\Common;
use Piwik\Config;
+use Piwik\Date;
use Piwik\DbHelper;
use Piwik\NoAccessException;
use Piwik\Plugins\Login\Auth;
@@ -243,6 +244,17 @@ class LoginTest extends IntegrationTestCase
$this->assertSuperUserLogin($rc, 'user');
}
+ public function test_authenticate_successUserLoginAndTokenAuthWithAnonymous()
+ {
+ DbHelper::createAnonymousUser();
+
+ $user = $this->_setUpUser();
+
+ // valid login & token auth
+ $rc = $this->authenticate('anonymous', 'anonymous');
+ $this->assertUserLogin($rc, 'anonymous', strlen('anonymous'));
+ }
+
public function test_authenticate_successUserLoginAndTokenAuth()
{
$user = $this->_setUpUser();
@@ -271,7 +283,8 @@ class LoginTest extends IntegrationTestCase
$rc = $this->auth->authenticate();
$this->assertUserLogin($rc);
// Check that the token auth is correct in the result
- $this->assertEquals($user['tokenAuth'], $rc->getTokenAuth());
+ $this->assertEquals(32, strlen($rc->getTokenAuth()));
+ $this->assertTrue(ctype_xdigit($rc->getTokenAuth()));
}
public function test_authenticate_successWithSuperUserPassword()
@@ -307,7 +320,8 @@ class LoginTest extends IntegrationTestCase
$rc = $this->auth->authenticate();
$this->assertUserLogin($rc);
// Check that the token auth is correct in the result
- $this->assertEquals($user['tokenAuth'], $rc->getTokenAuth());
+ $this->assertEquals(32, strlen($rc->getTokenAuth()));
+ $this->assertTrue(ctype_xdigit($rc->getTokenAuth()));
}
/**
@@ -324,7 +338,8 @@ class LoginTest extends IntegrationTestCase
$this->assertUserLogin($rc);
// Check that the login + token auth is correct in the result
$this->assertEquals($user['login'], $rc->getIdentity());
- $this->assertEquals($user['tokenAuth'], $rc->getTokenAuth());
+ $this->assertEquals(32, strlen($rc->getTokenAuth()));
+ $this->assertTrue(ctype_xdigit($rc->getTokenAuth()));
}
protected function _setUpUser()
@@ -338,9 +353,10 @@ class LoginTest extends IntegrationTestCase
API::getInstance()->addUser($user['login'], $user['password'], $user['email'], $user['alias']);
$model = new \Piwik\Plugins\UsersManager\Model();
- $dbUser = $model->getUser($user['login']);
+ $tokenAuth = $model->generateRandomTokenAuth();
+ $model->addTokenAuth($user['login'], $tokenAuth, 'many users test', Date::now()->getDatetime());
- $user['tokenAuth'] = $dbUser['token_auth'];
+ $user['tokenAuth'] = $tokenAuth;
return $user;
}
diff --git a/plugins/Login/tests/Integration/SessionInitializerTest.php b/plugins/Login/tests/Integration/SessionInitializerTest.php
index c2215de7a9..7be28c174c 100644
--- a/plugins/Login/tests/Integration/SessionInitializerTest.php
+++ b/plugins/Login/tests/Integration/SessionInitializerTest.php
@@ -36,6 +36,7 @@ class SessionInitializerTest extends IntegrationTestCase
$this->assertAuthCookieIsAbsent();
$sessionInitializer = new TestSessionInitializer();
+ $this->assertEmpty($sessionInitializer->cookie);
$sessionInitializer->initSession($this->makeMockAuth(AuthResult::SUCCESS), true);
$this->assertAuthCookieIsCreated($sessionInitializer->cookie);
@@ -69,8 +70,7 @@ class SessionInitializerTest extends IntegrationTestCase
private function assertAuthCookieIsCreated(Cookie $cookie)
{
- self::assertStringContainsString('login=czo5OiJ0ZXN0bG9naW4iOw==:token_auth=czozMjoiOWU5MDYxZjk2MDI0YTY3NWFmOGFkNWZmNmNiZGY2ZGMiOw==',
- $cookie->generateContentString());
+ $this->assertSame('', $cookie->generateContentString());
}
private function createAuthCookie()
diff --git a/plugins/Login/tests/UI/expected-screenshots/Login_bruteforcelog_noentries.png b/plugins/Login/tests/UI/expected-screenshots/Login_bruteforcelog_noentries.png
index 02fe11e89e..7c129e3f46 100644
--- a/plugins/Login/tests/UI/expected-screenshots/Login_bruteforcelog_noentries.png
+++ b/plugins/Login/tests/UI/expected-screenshots/Login_bruteforcelog_noentries.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:524375b5af2cbabc68f4973e66340276339deb907f9f0406a36f4fefc6fae014
-size 88970
+oid sha256:11249006c3f0eca81d59dc3b0334283d736d4d1e5c47ee698b53dc7a6b1acb2a
+size 89496
diff --git a/plugins/Login/tests/UI/expected-screenshots/Login_bruteforcelog_withentries.png b/plugins/Login/tests/UI/expected-screenshots/Login_bruteforcelog_withentries.png
index 93e8f1d9aa..0233d76503 100644
--- a/plugins/Login/tests/UI/expected-screenshots/Login_bruteforcelog_withentries.png
+++ b/plugins/Login/tests/UI/expected-screenshots/Login_bruteforcelog_withentries.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6fa4a84825aafc43395995ee62a3f7d4830506381d782ddbd841168fee818cc3
-size 106683
+oid sha256:3e698b7fa581131093a71889d3fecee464d1f1ecb830a058aad8fa1fb3db8765
+size 107286
diff --git a/plugins/Morpheus/javascripts/ajaxHelper.js b/plugins/Morpheus/javascripts/ajaxHelper.js
index 03d15be891..38ec2f287e 100644
--- a/plugins/Morpheus/javascripts/ajaxHelper.js
+++ b/plugins/Morpheus/javascripts/ajaxHelper.js
@@ -492,7 +492,8 @@ function ajaxHelper() {
this._getDefaultPostParams = function () {
if (this.withToken || this._isRequestToApiMethod() || piwik.shouldPropagateTokenAuth) {
return {
- token_auth: piwik.token_auth
+ token_auth: piwik.token_auth,
+ force_api_session: '1'
};
}
diff --git a/plugins/Overlay/javascripts/Overlay_Helper.js b/plugins/Overlay/javascripts/Overlay_Helper.js
index c7fe53e986..6f2208d494 100644
--- a/plugins/Overlay/javascripts/Overlay_Helper.js
+++ b/plugins/Overlay/javascripts/Overlay_Helper.js
@@ -29,7 +29,7 @@ var Overlay_Helper = {
var token_auth = piwik.broadcast.getValueFromUrl("token_auth");
if (token_auth.length && piwik.shouldPropagateTokenAuth) {
- url += '&token_auth=' + encodeURIComponent(token_auth);
+ url += '&force_api_session=1&token_auth=' + encodeURIComponent(token_auth);
}
if (link) {
diff --git a/plugins/Overlay/templates/index.twig b/plugins/Overlay/templates/index.twig
index c98d310787..42294e2ae4 100644
--- a/plugins/Overlay/templates/index.twig
+++ b/plugins/Overlay/templates/index.twig
@@ -69,7 +69,7 @@
var iframeSrc = 'index.php?module=Overlay&action=startOverlaySession&idSite={{ idSite }}&period={{ period }}&date={{ rawDate }}&segment={{ segment }}';
if (piwik.shouldPropagateTokenAuth) {
- iframeSrc += '&token_auth=' + piwik.token_auth;
+ iframeSrc += '&force_api_session=1&token_auth=' + piwik.token_auth;
}
Piwik_Overlay.init(iframeSrc, '{{ idSite }}', '{{ period }}', '{{ rawDate }}', '{{ segment }}');
diff --git a/plugins/Overlay/templates/index_noframe.twig b/plugins/Overlay/templates/index_noframe.twig
index 78c18bf281..c3f32be6b6 100644
--- a/plugins/Overlay/templates/index_noframe.twig
+++ b/plugins/Overlay/templates/index_noframe.twig
@@ -8,7 +8,7 @@
<script type="text/javascript">
var newLocation = 'index.php?module=Overlay&action=startOverlaySession&idSite={{ idSite }}&period={{ period }}&date={{ date }}&segment={{ segment }}';
if (piwik.shouldPropagateTokenAuth) {
- newLocation += '&token_auth=' + piwik.token_auth;
+ newLocation += '&force_api_session=1&token_auth=' + piwik.token_auth;
}
var locationParts = window.location.href.split('#');
diff --git a/plugins/Provider b/plugins/Provider
deleted file mode 160000
-Subproject 65ad2d63ab289ca3dc032a3e4dc4d28c35582cc
diff --git a/plugins/TwoFactorAuth/Controller.php b/plugins/TwoFactorAuth/Controller.php
index bb37cd5a64..cb9dedf7ab 100644
--- a/plugins/TwoFactorAuth/Controller.php
+++ b/plugins/TwoFactorAuth/Controller.php
@@ -148,7 +148,7 @@ class Controller extends \Piwik\Plugin\Controller
$this->twoFa->disable2FAforUser(Piwik::getCurrentUserLogin());
$this->passwordVerify->forgetVerifiedPassword();
- $this->redirectToIndex('UsersManager', 'userSettings', null, null, null, array(
+ $this->redirectToIndex('UsersManager', 'userSecurity', null, null, null, array(
'disableNonce' => false
));
}
diff --git a/plugins/TwoFactorAuth/TwoFactorAuth.php b/plugins/TwoFactorAuth/TwoFactorAuth.php
index 9b86925b36..f5d17aff94 100644
--- a/plugins/TwoFactorAuth/TwoFactorAuth.php
+++ b/plugins/TwoFactorAuth/TwoFactorAuth.php
@@ -32,9 +32,9 @@ class TwoFactorAuth extends \Piwik\Plugin
'AssetManager.getJavaScriptFiles' => 'getJsFiles',
'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
'API.UsersManager.deleteUser.end' => 'deleteRecoveryCodes',
- 'API.UsersManager.getTokenAuth.end' => 'onApiGetTokenAuth',
+ 'API.UsersManager.createAppSpecificTokenAuth.end' => 'onCreateAppSpecificTokenAuth',
'Request.dispatch.end' => array('function' => 'onRequestDispatchEnd', 'after' => true),
- 'Template.userSettings.afterTokenAuth' => 'render2FaUserSettings',
+ 'Template.userSecurity.afterPassword' => 'render2FaUserSettings',
'Login.authenticate.processSuccessfulSession.end' => 'onSuccessfulSession'
);
}
@@ -107,7 +107,7 @@ class TwoFactorAuth extends \Piwik\Plugin
return !empty($user);
}
- public function onApiGetTokenAuth($returnedValue, $params)
+ public function onCreateAppSpecificTokenAuth($returnedValue, $params)
{
if (!SettingsPiwik::isMatomoInstalled()) {
return;
diff --git a/plugins/TwoFactorAuth/templates/setupFinished.twig b/plugins/TwoFactorAuth/templates/setupFinished.twig
index 456e8f5189..02dec629f1 100644
--- a/plugins/TwoFactorAuth/templates/setupFinished.twig
+++ b/plugins/TwoFactorAuth/templates/setupFinished.twig
@@ -6,6 +6,6 @@
</h2>
<h3>{{ 'TwoFactorAuth_SetupFinishedSubtitle'|translate }}</h3>
<p><br />
- <a class="btn" href="{{ linkTo({'module': 'UsersManager', 'action': 'userSettings'}) }}">{{ 'General_Continue'|translate }}</a></p>
+ <a class="btn" href="{{ linkTo({'module': 'UsersManager', 'action': 'userSecurity'}) }}">{{ 'General_Continue'|translate }}</a></p>
</div>
{% endblock %}
diff --git a/plugins/TwoFactorAuth/tests/Fixtures/TwoFactorFixture.php b/plugins/TwoFactorAuth/tests/Fixtures/TwoFactorFixture.php
index f5b047ced5..181595f582 100644
--- a/plugins/TwoFactorAuth/tests/Fixtures/TwoFactorFixture.php
+++ b/plugins/TwoFactorAuth/tests/Fixtures/TwoFactorFixture.php
@@ -82,7 +82,7 @@ class TwoFactorFixture extends Fixture
if ($this->userWith2Fa === $user) {
$userModel = new Model();
- $userModel->updateUserTokenAuth($user, 'c4ca4238a0b923820dcc509a6f75849b');
+ $userModel->addTokenAuth($user, 'c4ca4238a0b923820dcc509a6f75849b', 'twofa test', Date::now()->getDatetime());
}
}
diff --git a/plugins/TwoFactorAuth/tests/Integration/TwoFactorAuthTest.php b/plugins/TwoFactorAuth/tests/Integration/TwoFactorAuthTest.php
index 797620443c..1df67e1e36 100644
--- a/plugins/TwoFactorAuth/tests/Integration/TwoFactorAuthTest.php
+++ b/plugins/TwoFactorAuth/tests/Integration/TwoFactorAuthTest.php
@@ -69,53 +69,59 @@ class TwoFactorAuthTest extends IntegrationTestCase
unset($_GET['authCode']);
}
- public function test_onApiGetTokenAuth_canAuthenticateWhenUserNotUsesTwoFA()
+ public function test_onCreateAppSpecificTokenAuth_canAuthenticateWhenUserNotUsesTwoFA()
{
- $token = Request::processRequest('UsersManager.getTokenAuth', array(
+ $token = Request::processRequest('UsersManager.createAppSpecificTokenAuth', array(
'userLogin' => $this->userWithout2Fa,
- 'md5Password' => md5($this->userPassword)
+ 'md5Password' => md5($this->userPassword),
+ 'description' => 'twofa test'
));
$this->assertEquals(32, strlen($token));
}
- public function test_onApiGetTokenAuth_returnsRandomTokenWhenNotAuthenticatedEvenWhen2FAenabled()
+ public function test_onCreateAppSpecificTokenAuth_returnsRandomTokenWhenNotAuthenticatedEvenWhen2FAenabled()
{
- $token = Request::processRequest('UsersManager.getTokenAuth', array(
+ $token = Request::processRequest('UsersManager.createAppSpecificTokenAuth', array(
'userLogin' => $this->userWith2Fa,
- 'md5Password' => md5('invalidPAssword')
+ 'md5Password' => md5('invalidPAssword'),
+ 'description' => 'twofa test'
));
$this->assertEquals(32, strlen($token));
}
- public function test_onApiGetTokenAuth_throwsErrorWhenMissingTokenWhenUsing2FaAndAuthenticatedCorrectly()
+ public function test_onCreateAppSpecificTokenAuth_throwsErrorWhenMissingTokenWhenUsing2FaAndAuthenticatedCorrectly()
{
$this->expectException(\Exception::class);
$this->expectExceptionMessage('TwoFactorAuth_MissingAuthCodeAPI');
- Request::processRequest('UsersManager.getTokenAuth', array(
+ Request::processRequest('UsersManager.createAppSpecificTokenAuth', array(
+
'userLogin' => $this->userWith2Fa,
- 'md5Password' => md5($this->userPassword)
+ 'md5Password' => md5($this->userPassword),
+ 'description' => 'twofa test'
));
}
- public function test_onApiGetTokenAuth_throwsErrorWhenInvalidTokenWhenUsing2FaAndAuthenticatedCorrectly()
+ public function test_onCreateAppSpecificTokenAuth_throwsErrorWhenInvalidTokenWhenUsing2FaAndAuthenticatedCorrectly()
{
$this->expectException(\Exception::class);
$this->expectExceptionMessage('TwoFactorAuth_InvalidAuthCode');
$_GET['authCode'] = '111222';
- Request::processRequest('UsersManager.getTokenAuth', array(
+ Request::processRequest('UsersManager.createAppSpecificTokenAuth', array(
'userLogin' => $this->userWith2Fa,
- 'md5Password' => md5($this->userPassword)
+ 'md5Password' => md5($this->userPassword),
+ 'description' => 'twofa test'
));
}
- public function test_onApiGetTokenAuth_returnsCorrectTokenWhenProvidingCorrectAuthTokenOnAuthentication()
+ public function test_onCreateAppSpecificTokenAuth_returnsCorrectTokenWhenProvidingCorrectAuthTokenOnAuthentication()
{
$_GET['authCode'] = $this->generateValidAuthCode($this->user2faSecret);
- $token = Request::processRequest('UsersManager.getTokenAuth', array(
+ $token = Request::processRequest('UsersManager.createAppSpecificTokenAuth', array(
'userLogin' => $this->userWith2Fa,
- 'md5Password' => md5($this->userPassword)
+ 'md5Password' => md5($this->userPassword),
+ 'description' => 'twofa test'
));
$this->assertEquals(32, strlen($token));
}
diff --git a/plugins/TwoFactorAuth/tests/UI/TwoFactorAuth_spec.js b/plugins/TwoFactorAuth/tests/UI/TwoFactorAuth_spec.js
index 368ec0632f..cdfcb10c95 100644
--- a/plugins/TwoFactorAuth/tests/UI/TwoFactorAuth_spec.js
+++ b/plugins/TwoFactorAuth/tests/UI/TwoFactorAuth_spec.js
@@ -13,7 +13,7 @@ describe("TwoFactorAuth", function () {
this.fixture = "Piwik\\Plugins\\TwoFactorAuth\\tests\\Fixtures\\TwoFactorFixture";
var generalParams = 'idSite=1&period=day&date=2010-01-03',
- userSettings = '?module=UsersManager&action=userSettings&' + generalParams,
+ userSettings = '?module=UsersManager&action=userSecurity&' + generalParams,
logoutUrl = '?module=Login&action=logout&period=day&date=yesterday';
diff --git a/plugins/UserCountry/Columns/City.php b/plugins/UserCountry/Columns/City.php
index 95bf56cb1c..b9f8d0a38a 100644
--- a/plugins/UserCountry/Columns/City.php
+++ b/plugins/UserCountry/Columns/City.php
@@ -33,8 +33,8 @@ class City extends Base
public function onNewVisit(Request $request, Visitor $visitor, $action)
{
$value = $this->getUrlOverrideValueIfAllowed('city', $request);
-
if ($value !== false) {
+ $value = substr($value, 0, 255);
return $value;
}
diff --git a/plugins/UserCountry/Columns/Country.php b/plugins/UserCountry/Columns/Country.php
index 608be68e6b..139e987746 100644
--- a/plugins/UserCountry/Columns/Country.php
+++ b/plugins/UserCountry/Columns/Country.php
@@ -82,8 +82,8 @@ class Country extends Base
public function onNewVisit(Request $request, Visitor $visitor, $action)
{
$value = $this->getUrlOverrideValueIfAllowed('country', $request);
-
if ($value !== false) {
+ $value = substr($value, 0, 3);
return $value;
}
diff --git a/plugins/UserCountry/Columns/Region.php b/plugins/UserCountry/Columns/Region.php
index 71af217b53..99f578ae73 100644
--- a/plugins/UserCountry/Columns/Region.php
+++ b/plugins/UserCountry/Columns/Region.php
@@ -33,8 +33,8 @@ class Region extends Base
public function onNewVisit(Request $request, Visitor $visitor, $action)
{
$value = $this->getUrlOverrideValueIfAllowed('region', $request);
-
if ($value !== false) {
+ $value = substr($value, 0, 3);
return $value;
}
diff --git a/plugins/UserCountryMap/javascripts/realtime-map.js b/plugins/UserCountryMap/javascripts/realtime-map.js
index 39d97a7497..2f42048818 100644
--- a/plugins/UserCountryMap/javascripts/realtime-map.js
+++ b/plugins/UserCountryMap/javascripts/realtime-map.js
@@ -148,7 +148,7 @@
return $.ajax({
url: 'index.php?' + $.param(params),
dataType: 'json',
- data: { token_auth: tokenAuth },
+ data: { token_auth: tokenAuth, force_api_session: '1' },
type: 'POST'
});
}
diff --git a/plugins/UserCountryMap/javascripts/visitor-map.js b/plugins/UserCountryMap/javascripts/visitor-map.js
index 396fac73a3..82bdb13852 100644
--- a/plugins/UserCountryMap/javascripts/visitor-map.js
+++ b/plugins/UserCountryMap/javascripts/visitor-map.js
@@ -122,7 +122,7 @@
return $.ajax({
url: 'index.php?' + $.param(params),
dataType: dataType,
- data: { token_auth: token_auth },
+ data: { token_auth: token_auth, force_api_session: '1' },
type: 'POST'
});
}
diff --git a/plugins/UsersManager/API.php b/plugins/UsersManager/API.php
index 08cf1312c0..c2d4dba6aa 100644
--- a/plugins/UsersManager/API.php
+++ b/plugins/UsersManager/API.php
@@ -684,9 +684,8 @@ class API extends \Piwik\Plugin\API
$alias = $this->getCleanAlias($alias, $userLogin);
$passwordTransformed = $this->password->hash($passwordTransformed);
- $token_auth = $this->createTokenAuth($userLogin);
- $this->model->addUser($userLogin, $passwordTransformed, $email, $alias, $token_auth, Date::now()->getDatetime());
+ $this->model->addUser($userLogin, $passwordTransformed, $email, $alias, Date::now()->getDatetime());
// we reload the access list which doesn't yet take in consideration this new user
Access::getInstance()->reloadAccess();
@@ -844,29 +843,6 @@ class API extends \Piwik\Plugin\API
}
/**
- * Regenerate the token_auth associated with a user.
- *
- * If the user currently logged in regenerates his own token, he will be logged out.
- * His previous token will be rendered invalid.
- *
- * @param string $userLogin
- * @throws Exception
- */
- public function regenerateTokenAuth($userLogin)
- {
- $this->checkUserIsNotAnonymous($userLogin);
-
- Piwik::checkUserHasSuperUserAccessOrIsTheUser($userLogin);
-
- $this->model->updateUserTokenAuth(
- $userLogin,
- $this->createTokenAuth($userLogin)
- );
-
- Cache::deleteTrackerCache();
- }
-
- /**
* Updates a user in the database.
* Only login and password are required (case when we update the password).
*
@@ -889,7 +865,6 @@ class API extends \Piwik\Plugin\API
$this->checkUserExists($userLogin);
$userInfo = $this->model->getUser($userLogin);
- $token_auth = $userInfo['token_auth'];
$changeShouldRequirePasswordConfirmation = false;
$passwordHasBeenUpdated = false;
@@ -936,7 +911,7 @@ class API extends \Piwik\Plugin\API
$alias = $this->getCleanAlias($alias, $userLogin);
- $this->model->updateUser($userLogin, $password, $email, $alias, $token_auth);
+ $this->model->updateUser($userLogin, $password, $email, $alias);
Cache::deleteTrackerCache();
@@ -1336,34 +1311,27 @@ class API extends \Piwik\Plugin\API
}
/**
- * Generates a new random authentication token.
- *
- * @param string $userLogin Login
- * @return string
- */
- public function createTokenAuth($userLogin)
- {
- return md5($userLogin . microtime(true) . Common::generateUniqId() . SettingsPiwik::getSalt());
- }
-
- /**
- * Returns the user's API token.
+ * Generates an app specific API token every time you call this method. You should ideally store this token securely
+ * in your app and not generate a new token every time.
*
* If the username/password combination is incorrect an invalid token will be returned.
*
* @param string $userLogin Login or Email address
* @param string $md5Password hashed string of the password (using current hash function; MD5-named for historical reasons)
+ * @param string $description The description for this app specific password, for example your app name. Max 100 characters are allowed
+ * @param string $expireDate Optionally a date when the token should expire
+ * @param string $expireHours Optionally number of hours for how long the token should be valid before it expires.
+ * If expireDate is set and expireHours, then expireDate will be used.
+ * If expireDate is set and expireHours, then expireDate will be used.
* @return string
*/
- public function getTokenAuth($userLogin, $md5Password)
+ public function createAppSpecificTokenAuth($userLogin, $md5Password, $description, $expireDate = null, $expireHours = 0)
{
UsersManager::checkPasswordHash($md5Password, Piwik::translate('UsersManager_ExceptionPasswordMD5HashExpected'));
$user = $this->model->getUser($userLogin);
-
if (empty($user) && Piwik::isValidEmailString($userLogin)) {
$user = $this->model->getUserByEmail($userLogin);
-
if (!empty($user['login'])) {
$userLogin = $user['login'];
}
@@ -1384,7 +1352,16 @@ class API extends \Piwik\Plugin\API
$userUpdater->updateUserWithoutCurrentPassword($userLogin, $this->password->hash($md5Password));
}
- return $user['token_auth'];
+ if (empty($expireDate) && !empty($expireHours) && is_numeric($expireHours)) {
+ $expireDate = Date::now()->addHour($expireHours)->getDatetime();
+ } elseif (!empty($expireDate)) {
+ $expireDate = Date::factory($expireDate)->getDatetime();
+ }
+
+ $generatedToken = $this->model->generateRandomTokenAuth();
+ $this->model->addTokenAuth($userLogin, $generatedToken, $description, Date::now()->getDatetime(), $expireDate);
+
+ return $generatedToken;
}
public function newsletterSignup()
diff --git a/plugins/UsersManager/Controller.php b/plugins/UsersManager/Controller.php
index 49fb7bd563..f75bb81b35 100644
--- a/plugins/UsersManager/Controller.php
+++ b/plugins/UsersManager/Controller.php
@@ -13,31 +13,55 @@ use Piwik\API\Request;
use Piwik\API\ResponseBuilder;
use Piwik\Common;
use Piwik\Container\StaticContainer;
+use Piwik\Date;
+use Piwik\Nonce;
+use Piwik\Notification;
use Piwik\Option;
use Piwik\Piwik;
use Piwik\Plugin;
use Piwik\Plugin\ControllerAdmin;
use Piwik\Plugins\LanguagesManager\API as APILanguagesManager;
use Piwik\Plugins\LanguagesManager\LanguagesManager;
+use Piwik\Plugins\Login\PasswordVerifier;
+use Piwik\Plugins\TagManager\Validators\TriggerIds;
use Piwik\Plugins\UsersManager\API as APIUsersManager;
use Piwik\SettingsPiwik;
use Piwik\Site;
use Piwik\Tracker\IgnoreCookie;
use Piwik\Translation\Translator;
use Piwik\Url;
+use Piwik\Validators\BaseValidator;
+use Piwik\Validators\CharacterLength;
+use Piwik\Validators\NotEmpty;
use Piwik\View;
use Piwik\Session\SessionInitializer;
class Controller extends ControllerAdmin
{
+ const NONCE_CHANGE_PASSWORD = 'changePasswordNonce';
+ const NONCE_ADD_AUTH_TOKEN = 'addAuthTokenNonce';
+ const NONCE_DELETE_AUTH_TOKEN = 'deleteAuthTokenNonce';
+
/**
* @var Translator
*/
private $translator;
- public function __construct(Translator $translator)
+ /**
+ * @var PasswordVerifier
+ */
+ private $passwordVerify;
+
+ /**
+ * @var Model
+ */
+ private $userModel;
+
+ public function __construct(Translator $translator, PasswordVerifier $passwordVerify, Model $userModel)
{
$this->translator = $translator;
+ $this->passwordVerify = $passwordVerify;
+ $this->userModel = $userModel;
parent::__construct();
}
@@ -249,6 +273,112 @@ class Controller extends ControllerAdmin
}
/**
+ * The "User Security" admin UI screen view
+ */
+ public function userSecurity()
+ {
+ Piwik::checkUserIsNotAnonymous();
+
+ $tokens = $this->userModel->getAllNonSystemTokensForLogin(Piwik::getCurrentUserLogin());
+ $tokens = array_map(function ($token){
+ foreach (['date_created', 'last_used', 'date_expired'] as $key) {
+ if (!empty($token[$key])) {
+ $token[$key] = Date::factory($token[$key])->getLocalized(Date::DATE_FORMAT_LONG);
+ }
+ }
+
+ return $token;
+ }, $tokens);
+ $hasTokensWithExpireDate = !empty(array_filter(array_column($tokens, 'date_expired')));
+
+ return $this->renderTemplate('userSecurity', array(
+ 'isUsersAdminEnabled' => UsersManager::isUsersAdminEnabled(),
+ 'changePasswordNonce' => Nonce::getNonce(self::NONCE_CHANGE_PASSWORD),
+ 'deleteTokenNonce' => Nonce::getNonce(self::NONCE_DELETE_AUTH_TOKEN),
+ 'hasTokensWithExpireDate' => $hasTokensWithExpireDate,
+ 'tokens' => $tokens
+ ));
+ }
+
+ /**
+ * The "User Security" admin UI screen view
+ */
+ public function deleteToken()
+ {
+ Piwik::checkUserIsNotAnonymous();
+
+ $idTokenAuth = Common::getRequestVar('idtokenauth', '', 'string', $_POST);
+
+ if (!empty($idTokenAuth)) {
+ $params = array(
+ 'module' => 'UsersManager',
+ 'action' => 'deleteToken',
+ 'idtokenauth' => $idTokenAuth,
+ 'nonce' => Nonce::getNonce(self::NONCE_DELETE_AUTH_TOKEN)
+ );
+
+ if (!$this->passwordVerify->requirePasswordVerifiedRecently($params)) {
+ throw new Exception('Not allowed');
+ }
+
+ Nonce::checkNonce(self::NONCE_DELETE_AUTH_TOKEN);
+
+ if ($idTokenAuth === 'all') {
+ $this->userModel->deleteAllTokensForUser(Piwik::getCurrentUserLogin());
+
+ $notification = new Notification(Piwik::translate('UsersManager_TokensSuccessfullyDeleted'));
+ $notification->context = Notification::CONTEXT_SUCCESS;
+ Notification\Manager::notify('successdeletetokens', $notification);
+
+ } elseif (is_numeric($idTokenAuth)) {
+ $this->userModel->deleteToken($idTokenAuth, Piwik::getCurrentUserLogin());
+
+ $notification = new Notification(Piwik::translate('UsersManager_TokenSuccessfullyDeleted'));
+ $notification->context = Notification::CONTEXT_SUCCESS;
+ Notification\Manager::notify('successdeletetoken', $notification);
+ }
+ }
+
+ $this->redirectToIndex('UsersManager', 'userSecurity');
+ }
+
+ /**
+ * The "User Security" admin UI screen view
+ */
+ public function addNewToken()
+ {
+ Piwik::checkUserIsNotAnonymous();
+
+ $params = array('module' => 'UsersManager', 'action' => 'addNewToken');
+
+ if (!$this->passwordVerify->requirePasswordVerifiedRecently($params)) {
+ throw new Exception('Not allowed');
+ }
+
+ $noDescription = false;
+
+ if (!empty($_POST['description'])) {
+ Nonce::checkNonce(self::NONCE_ADD_AUTH_TOKEN);
+
+ $description = Common::getRequestVar('description', '', 'string');
+ $login = Piwik::getCurrentUserLogin();
+
+ $generatedToken = $this->userModel->generateRandomTokenAuth();
+
+ $this->userModel->addTokenAuth($login, $generatedToken, $description, Date::now()->getDatetime());
+
+ return $this->renderTemplate('addNewTokenSuccess', array('generatedToken' => $generatedToken));
+ } elseif (isset($_POST['description'])) {
+ $noDescription = true;
+ }
+
+ return $this->renderTemplate('addNewToken', array(
+ 'nonce' => Nonce::getNonce(self::NONCE_ADD_AUTH_TOKEN),
+ 'noDescription' => $noDescription
+ ));
+ }
+
+ /**
* The "Anonymous Settings" admin UI screen view
*/
public function anonymousSettings()
@@ -389,9 +519,7 @@ class Controller extends ControllerAdmin
Piwik::checkUserHasSuperUserAccessOrIsTheUser($userLogin);
- if (UsersManager::isUsersAdminEnabled()) {
- $this->processPasswordChange($userLogin);
- }
+ $this->processEmailChange($userLogin);
LanguagesManager::setLanguageForSession($language);
@@ -418,6 +546,26 @@ class Controller extends ControllerAdmin
return $toReturn;
}
+
+ /**
+ * Records settings from the "User Settings" page
+ * @throws Exception
+ */
+ public function recordPasswordChange()
+ {
+ $userLogin = Piwik::getCurrentUserLogin();
+
+ Piwik::checkUserHasSuperUserAccessOrIsTheUser($userLogin);
+ Nonce::checkNonce(self::NONCE_CHANGE_PASSWORD);
+
+ $this->processPasswordChange($userLogin);
+
+ $notification = new Notification(Piwik::translate('CoreAdminHome_SettingsSaveSuccess'));
+ $notification->context = Notification::CONTEXT_SUCCESS;
+ Notification\Manager::notify('successpass', $notification);
+ $this->redirectToIndex('UsersManager', 'userSecurity');
+ }
+
private function noAdminAccessToWebsite($idSiteSelected, $defaultReportSiteName, $message)
{
$view = new View('@UsersManager/noWebsiteAdminAccess');
@@ -430,43 +578,60 @@ class Controller extends ControllerAdmin
return $view->render();
}
- private function processPasswordChange($userLogin)
+ private function processEmailChange($userLogin)
{
- $email = Common::getRequestVar('email');
- $password = Common::getRequestVar('password', false);
- $passwordBis = Common::getRequestVar('passwordBis', false);
- $passwordCurrent = Common::getRequestVar('passwordConfirmation', false);
-
- $newPassword = false;
- if (!empty($password) || !empty($passwordBis)) {
- if ($password != $passwordBis) {
- throw new Exception($this->translator->translate('Login_PasswordsDoNotMatch'));
- }
- $newPassword = $password;
+ if (!UsersManager::isUsersAdminEnabled()) {
+ return;
}
- if ($newPassword !== false && !Url::isValidHost()) {
- throw new Exception("Cannot change password or email with untrusted hostname!");
+ if (!Url::isValidHost()) {
+ throw new Exception("Cannot change email with untrusted hostname!");
}
-
+
+ $email = Common::getRequestVar('email');
+ $passwordCurrent = Common::getRequestvar('passwordConfirmation', false);
+
// UI disables password change on invalid host, but check here anyway
Request::processRequest('UsersManager.updateUser', [
'userLogin' => $userLogin,
- 'password' => $newPassword,
'email' => $email,
'passwordConfirmation' => $passwordCurrent
], $default = []);
+ }
+
+ private function processPasswordChange($userLogin)
+ {
+ if (!UsersManager::isUsersAdminEnabled()) {
+ return;
+ }
+
+ if (!Url::isValidHost()) {
+ // UI disables password change on invalid host, but check here anyway
+ throw new Exception("Cannot change password with untrusted hostname!");
+ }
- if ($newPassword !== false) {
- // logs the user in with the new password
- $newPassword = Common::unsanitizeInputValue($newPassword);
- $sessionInitializer = new SessionInitializer();
- $auth = StaticContainer::get('Piwik\Auth');
- $auth->setTokenAuth(null); // ensure authenticated through password
- $auth->setLogin($userLogin);
- $auth->setPassword($newPassword);
- $sessionInitializer->initSession($auth);
+ $newPassword = Common::getRequestvar('password', false);
+ $passwordBis = Common::getRequestvar('passwordBis', false);
+ $passwordCurrent = Common::getRequestvar('passwordConfirmation', false);
+
+ if ($newPassword !== $passwordBis) {
+ throw new Exception($this->translator->translate('Login_PasswordsDoNotMatch'));
}
+
+ Request::processRequest('UsersManager.updateUser', [
+ 'userLogin' => $userLogin,
+ 'password' => $newPassword,
+ 'passwordConfirmation' => $passwordCurrent
+ ], $default = []);
+
+ // logs the user in with the new password
+ $newPassword = Common::unsanitizeInputValue($newPassword);
+ $sessionInitializer = new SessionInitializer();
+ $auth = StaticContainer::get('Piwik\Auth');
+ $auth->setTokenAuth(null); // ensure authenticated through password
+ $auth->setLogin($userLogin);
+ $auth->setPassword($newPassword);
+ $sessionInitializer->initSession($auth);
}
/**
diff --git a/plugins/UsersManager/Menu.php b/plugins/UsersManager/Menu.php
index 7945480d4e..25d345f52c 100644
--- a/plugins/UsersManager/Menu.php
+++ b/plugins/UsersManager/Menu.php
@@ -25,6 +25,7 @@ class Menu extends \Piwik\Plugin\Menu
if (!Piwik::isUserIsAnonymous()) {
$menu->addItem('UsersManager_MenuPersonal', 'General_Settings', $this->urlForAction('userSettings'), 0);
+ $menu->addItem('UsersManager_MenuPersonal', 'General_Security', $this->urlForAction('userSecurity'), 1);
}
}
}
diff --git a/plugins/UsersManager/Model.php b/plugins/UsersManager/Model.php
index 839fdbd578..15dd22c40b 100644
--- a/plugins/UsersManager/Model.php
+++ b/plugins/UsersManager/Model.php
@@ -10,6 +10,7 @@ namespace Piwik\Plugins\UsersManager;
use Piwik\Auth\Password;
use Piwik\Common;
+use Piwik\Config;
use Piwik\Date;
use Piwik\Db;
use Piwik\Option;
@@ -17,6 +18,10 @@ use Piwik\Piwik;
use Piwik\Plugins\SitesManager\SitesManager;
use Piwik\Plugins\UsersManager\Sql\SiteAccessFilter;
use Piwik\Plugins\UsersManager\Sql\UserTableFilter;
+use Piwik\SettingsPiwik;
+use Piwik\Validators\BaseValidator;
+use Piwik\Validators\CharacterLength;
+use Piwik\Validators\NotEmpty;
/**
* The UsersManager API lets you Manage Users and their permissions to access specific websites.
@@ -32,8 +37,12 @@ use Piwik\Plugins\UsersManager\Sql\UserTableFilter;
*/
class Model
{
+ const MAX_LENGTH_TOKEN_DESCRIPTION = 100;
+ const TOKEN_HASH_ALGO = 'sha512';
+
private static $rawPrefix = 'user';
- private $table;
+ private $userTable;
+ private $tokenTable;
/**
* @var Password
@@ -43,7 +52,8 @@ class Model
public function __construct()
{
$this->passwordHelper = new Password();
- $this->table = Common::prefixTable(self::$rawPrefix);
+ $this->userTable = Common::prefixTable(self::$rawPrefix);
+ $this->tokenTable = Common::prefixTable('user_token_auth');
}
/**
@@ -63,7 +73,7 @@ class Model
}
$db = $this->getDb();
- $users = $db->fetchAll("SELECT * FROM " . $this->table . "
+ $users = $db->fetchAll("SELECT * FROM " . $this->userTable . "
$where
ORDER BY login ASC", $bind);
@@ -78,7 +88,7 @@ class Model
public function getUsersLogin()
{
$db = $this->getDb();
- $users = $db->fetchAll("SELECT login FROM " . $this->table . " ORDER BY login ASC");
+ $users = $db->fetchAll("SELECT login FROM " . $this->userTable . " ORDER BY login ASC");
$return = array();
foreach ($users as $login) {
@@ -229,7 +239,7 @@ class Model
{
$db = $this->getDb();
- $matchedUsers = $db->fetchAll("SELECT * FROM {$this->table} WHERE login = ?", $userLogin);
+ $matchedUsers = $db->fetchAll("SELECT * FROM {$this->userTable} WHERE login = ?", $userLogin);
// for BC in 2.15 LTS, if there is a user w/ an exact match to the requested login, return that user.
// this is done since before this change, login was case sensitive. until 3.0, we want to maintain
@@ -243,33 +253,190 @@ class Model
return reset($matchedUsers);
}
+ public function hashTokenAuth($tokenAuth)
+ {
+ $salt = SettingsPiwik::getSalt();
+ return hash(self::TOKEN_HASH_ALGO, $tokenAuth . $salt);
+ }
+
+ public function generateRandomTokenAuth()
+ {
+ $count = 0;
+
+ do {
+ $token = $this->generateTokenAuth();
+
+ $count++;
+ if ($count > 20) {
+ // something seems wrong as the odds of that happening is basically 0. Only catching it to prevent
+ // endless loop in case there is some bug somewhere
+ throw new \Exception('Failed to generate token');
+ }
+
+ } while ($this->getUserByTokenAuth($token));
+
+ return $token;
+ }
+
+ private function generateTokenAuth()
+ {
+ return md5(Common::getRandomString(32, 'abcdef1234567890') . microtime(true) . Common::generateUniqId() . SettingsPiwik::getSalt());
+ }
+
+ public function addTokenAuth($login, $tokenAuth, $description, $dateCreated, $dateExpired = null, $isSystemToken = false)
+ {
+ if (!$this->getUser($login)) {
+ throw new \Exception('User ' . $login . ' does not exist');
+ }
+
+ BaseValidator::check('Description', $description, [new NotEmpty(), new CharacterLength(1, self::MAX_LENGTH_TOKEN_DESCRIPTION)]);
+
+ if (empty($dateExpired)) {
+ $dateExpired = null;
+ }
+
+ $isSystemToken = (int) $isSystemToken;
+
+ $insertSql = "INSERT INTO " . $this->tokenTable . ' (login, description, password, date_created, date_expired, system_token, hash_algo) VALUES (?, ?, ?, ?, ?, ?, ?)';
+
+ $tokenAuth = $this->hashTokenAuth($tokenAuth);
+
+ $db = $this->getDb();
+ $db->query($insertSql, [$login, $description, $tokenAuth, $dateCreated, $dateExpired, $isSystemToken, self::TOKEN_HASH_ALGO]);
+
+ return $db->lastInsertId();
+ }
+
+ private function getTokenByTokenAuth($tokenAuth)
+ {
+ $tokenAuth = $this->hashTokenAuth($tokenAuth);
+ $db = $this->getDb();
+
+ return $db->fetchRow("SELECT * FROM " . $this->tokenTable . " WHERE `password` = ?", $tokenAuth);
+ }
+
+ private function getQueryNotExpiredToken()
+ {
+ return array(
+ 'sql' => ' (date_expired is null or date_expired > ?)',
+ 'bind' => array(Date::now()->getDatetime())
+ );
+ }
+
+ private function getTokenByTokenAuthIfNotExpired($tokenAuth)
+ {
+ $tokenAuth = $this->hashTokenAuth($tokenAuth);
+ $db = $this->getDb();
+
+ $expired = $this->getQueryNotExpiredToken();
+ $bind = array_merge(array($tokenAuth), $expired['bind']);
+
+ $token = $db->fetchRow("SELECT * FROM " . $this->tokenTable . " WHERE `password` = ? and " . $expired['sql'], $bind);
+
+ return $token;
+ }
+
+ public function deleteExpiredTokens($expiredSince)
+ {
+ $db = $this->getDb();
+
+ return $db->query("DELETE FROM " . $this->tokenTable . " WHERE `date_expired` is not null and date_expired < ?", $expiredSince);
+ }
+
+ public function deleteAllTokensForUser($login)
+ {
+ $db = $this->getDb();
+
+ return $db->query("DELETE FROM " . $this->tokenTable . " WHERE `login` = ?", $login);
+ }
+
+ public function getAllNonSystemTokensForLogin($login)
+ {
+ $db = $this->getDb();
+
+
+ $expired = $this->getQueryNotExpiredToken();
+ $bind = array_merge(array($login), $expired['bind']);
+
+ return $db->fetchAll("SELECT * FROM " . $this->tokenTable . " WHERE `login` = ? and system_token = 0 and " . $expired['sql'] . ' order by idusertokenauth ASC', $bind);
+ }
+
+ public function getAllHashedTokensForLogins($logins)
+ {
+ if (empty($logins)) {
+ return array();
+ }
+
+ $db = $this->getDb();
+ $placeholder = Common::getSqlStringFieldsArray($logins);
+
+ $expired = $this->getQueryNotExpiredToken();
+ $bind = array_merge($logins, $expired['bind']);
+
+ $tokens = $db->fetchAll("SELECT password FROM " . $this->tokenTable . " WHERE `login` IN (".$placeholder.") and " . $expired['sql'], $bind);
+ return array_column($tokens, 'password');
+ }
+
+ public function deleteToken($idTokenAuth, $login)
+ {
+ $db = $this->getDb();
+
+ return $db->query("DELETE FROM " . $this->tokenTable . " WHERE `idusertokenauth` = ? and login = ?", array($idTokenAuth, $login));
+ }
+
+ public function setTokenAuthWasUsed($tokenAuth, $dateLastUsed)
+ {
+ $token = $this->getTokenByTokenAuth($tokenAuth);
+ if (!empty($token)) {
+ $this->updateTokenAuthTable($token['idusertokenauth'], array(
+ 'last_used' => $dateLastUsed
+ ));
+ }
+ }
+
+ private function updateTokenAuthTable($idTokenAuth, $fields) {
+ $set = array();
+ $bind = array();
+ foreach ($fields as $key => $val) {
+ $set[] = "`$key` = ?";
+ $bind[] = $val;
+ }
+
+ $bind[] = $idTokenAuth;
+
+ $db = $this->getDb();
+ $db->query(sprintf('UPDATE `%s` SET %s WHERE `idusertokenauth` = ?', $this->tokenTable, implode(', ', $set)), $bind);
+ }
+
public function getUserByEmail($userEmail)
{
$db = $this->getDb();
- return $db->fetchRow("SELECT * FROM " . $this->table . " WHERE email = ?", $userEmail);
+ return $db->fetchRow("SELECT * FROM " . $this->userTable . " WHERE email = ?", $userEmail);
}
public function getUserByTokenAuth($tokenAuth)
{
- $db = $this->getDb();
- return $db->fetchRow('SELECT * FROM ' . $this->table . ' WHERE token_auth = ?', $tokenAuth);
+ $token = $this->getTokenByTokenAuthIfNotExpired($tokenAuth);
+ if (!empty($token)) {
+ $db = $this->getDb();
+ return $db->fetchRow("SELECT * FROM " . $this->userTable . " WHERE `login` = ?", $token['login']);
+ }
}
- public function addUser($userLogin, $hashedPassword, $email, $alias, $tokenAuth, $dateRegistered)
+ public function addUser($userLogin, $hashedPassword, $email, $alias, $dateRegistered)
{
$user = array(
'login' => $userLogin,
'password' => $hashedPassword,
'alias' => $alias,
'email' => $email,
- 'token_auth' => $tokenAuth,
'date_registered' => $dateRegistered,
'superuser_access' => 0,
'ts_password_modified' => Date::now()->getDatetime(),
);
$db = $this->getDb();
- $db->insert($this->table, $user);
+ $db->insert($this->userTable, $user);
}
public function setSuperUserAccess($userLogin, $hasSuperUserAccess)
@@ -297,7 +464,7 @@ class Model
$bind[] = $userLogin;
$db = $this->getDb();
- $db->query(sprintf('UPDATE `%s` SET %s WHERE `login` = ?', $this->table, implode(', ', $set)), $bind);
+ $db->query(sprintf('UPDATE `%s` SET %s WHERE `login` = ?', $this->userTable, implode(', ', $set)), $bind);
}
/**
@@ -308,7 +475,7 @@ class Model
public function getUsersHavingSuperUserAccess()
{
$db = $this->getDb();
- $users = $db->fetchAll("SELECT login, email, token_auth, superuser_access
+ $users = $db->fetchAll("SELECT login, email, superuser_access
FROM " . Common::prefixTable("user") . "
WHERE superuser_access = 1
ORDER BY date_registered ASC");
@@ -316,12 +483,11 @@ class Model
return $users;
}
- public function updateUser($userLogin, $hashedPassword, $email, $alias, $tokenAuth)
+ public function updateUser($userLogin, $hashedPassword, $email, $alias)
{
$fields = array(
'alias' => $alias,
'email' => $email,
- 'token_auth' => $tokenAuth
);
if (!empty($hashedPassword)) {
$fields['password'] = $hashedPassword;
@@ -329,17 +495,10 @@ class Model
$this->updateUserFields($userLogin, $fields);
}
- public function updateUserTokenAuth($userLogin, $tokenAuth)
- {
- $this->updateUserFields($userLogin, array(
- 'token_auth' => $tokenAuth
- ));
- }
-
public function userExists($userLogin)
{
$db = $this->getDb();
- $count = $db->fetchOne("SELECT count(*) FROM " . $this->table . " WHERE login = ?", $userLogin);
+ $count = $db->fetchOne("SELECT count(*) FROM " . $this->userTable . " WHERE login = ?", $userLogin);
return $count != 0;
}
@@ -347,7 +506,7 @@ class Model
public function userEmailExists($userEmail)
{
$db = $this->getDb();
- $count = $db->fetchOne("SELECT count(*) FROM " . $this->table . " WHERE email = ?", $userEmail);
+ $count = $db->fetchOne("SELECT count(*) FROM " . $this->userTable . " WHERE email = ?", $userEmail);
return $count != 0;
}
@@ -380,7 +539,8 @@ class Model
public function deleteUserOnly($userLogin)
{
$db = $this->getDb();
- $db->query("DELETE FROM " . $this->table . " WHERE login = ?", $userLogin);
+ $db->query("DELETE FROM " . $this->userTable . " WHERE login = ?", $userLogin);
+ $db->query("DELETE FROM " . $this->tokenTable . " WHERE login = ?", $userLogin);
/**
* Triggered after a user has been deleted.
@@ -428,7 +588,7 @@ class Model
$bind = array_merge($bind, $whereBind);
- $sql = 'SELECT u.login FROM ' . $this->table . " u $joins $where";
+ $sql = 'SELECT u.login FROM ' . $this->userTable . " u $joins $where";
$db = $this->getDb();
@@ -468,7 +628,7 @@ class Model
}
$sql = 'SELECT SQL_CALC_FOUND_ROWS u.*, GROUP_CONCAT(a.access SEPARATOR "|") as access
- FROM ' . $this->table . " u
+ FROM ' . $this->userTable . " u
$joins
$where
GROUP BY u.login
diff --git a/plugins/UsersManager/Tasks.php b/plugins/UsersManager/Tasks.php
index f3f6a35528..29ea9ed3ba 100644
--- a/plugins/UsersManager/Tasks.php
+++ b/plugins/UsersManager/Tasks.php
@@ -8,6 +8,7 @@
namespace Piwik\Plugins\UsersManager;
use Piwik\Access;
+use Piwik\Date;
class Tasks extends \Piwik\Plugin\Tasks
{
@@ -29,9 +30,14 @@ class Tasks extends \Piwik\Plugin\Tasks
public function schedule()
{
+ $this->daily("cleanupExpiredTokens");
$this->daily("setUserDefaultReportPreference");
}
+ public function cleanupExpiredTokens() {
+ $this->usersModel->deleteExpiredTokens(Date::now()->getDatetime());
+ }
+
public function setUserDefaultReportPreference()
{
// We initialize the default report user preference for each user (if it hasn't been inited before) for performance,
diff --git a/plugins/UsersManager/UsersManager.php b/plugins/UsersManager/UsersManager.php
index 0340acda20..405d17c1cf 100644
--- a/plugins/UsersManager/UsersManager.php
+++ b/plugins/UsersManager/UsersManager.php
@@ -43,7 +43,6 @@ class UsersManager extends \Piwik\Plugin
'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys',
'Platform.initialized' => 'onPlatformInitialized',
'System.addSystemSummaryItems' => 'addSystemSummaryItems',
- 'CronArchive.getTokenAuth' => 'getCronArchiveTokenAuth'
);
}
@@ -94,22 +93,17 @@ class UsersManager extends \Piwik\Plugin
public function recordAdminUsersInCache(&$attributes, $idSite)
{
$model = new Model();
- $adminLogins = $model->getUsersLoginWithSiteAccess($idSite, Admin::ID);
+ $logins = $model->getUsersLoginWithSiteAccess($idSite, Admin::ID);
$writeLogins = $model->getUsersLoginWithSiteAccess($idSite, Write::ID);
+ $logins = array_merge($logins, $writeLogins);
- $attributes['tracking_token_auth'] = array();
+ $token_auths = $model->getAllHashedTokensForLogins($logins);
- if (!empty($adminLogins)) {
- $users = $model->getUsers($adminLogins);
- foreach ($users as $user) {
- $attributes['tracking_token_auth'][] = self::hashTrackingToken($user['token_auth'], $idSite);
- }
- }
+ $attributes['tracking_token_auth'] = array();
- if (!empty($writeLogins)) {
- $users = $model->getUsers($writeLogins);
- foreach ($users as $user) {
- $attributes['tracking_token_auth'][] = self::hashTrackingToken($user['token_auth'], $idSite);
+ if (!empty($token_auths)) {
+ foreach ($token_auths as $token_auth) {
+ $attributes['tracking_token_auth'][] = self::hashTrackingToken($token_auth, $idSite);
}
}
}
@@ -119,16 +113,6 @@ class UsersManager extends \Piwik\Plugin
return sha1($idSite . $tokenAuth . SettingsPiwik::getSalt());
}
- public function getCronArchiveTokenAuth(&$tokens)
- {
- $model = new Model();
- $superUsers = $model->getUsersHavingSuperUserAccess();
-
- foreach($superUsers as $superUser) {
- $tokens[] = $superUser['token_auth'];
- }
- }
-
/**
* Delete user preferences associated with a particular site
*/
@@ -247,7 +231,7 @@ class UsersManager extends \Piwik\Plugin
*/
public static function checkPasswordHash($passwordHash, $exceptionMessage)
{
- if (strlen($passwordHash) != 32) { // MD5 hash length
+ if (strlen($passwordHash) != 32 || !ctype_xdigit($passwordHash)) { // MD5 hash length
throw new Exception($exceptionMessage);
}
}
diff --git a/plugins/UsersManager/angularjs/personal-settings/personal-settings.controller.js b/plugins/UsersManager/angularjs/personal-settings/personal-settings.controller.js
index 635d6e93db..047b08cb10 100644
--- a/plugins/UsersManager/angularjs/personal-settings/personal-settings.controller.js
+++ b/plugins/UsersManager/angularjs/personal-settings/personal-settings.controller.js
@@ -34,7 +34,7 @@
id: 'PersonalSettingsSuccess', context: 'success'});
notification.scrollToNotification();
- self.doesRequirePasswordConfirmation = !!self.password;
+ self.doesRequirePasswordConfirmation = false;
self.passwordCurrent = '';
self.loading = false;
}, function (errorMessage) {
@@ -74,25 +74,6 @@
});
};
- this.regenerateTokenAuth = function () {
- var parameters = { userLogin: piwik.userLogin };
-
- self.loading = true;
-
- piwikHelper.modalConfirm('#confirmTokenRegenerate', {yes: function () {
- piwikApi.withTokenInUrl();
- piwikApi.post({
- module: 'API',
- method: 'UsersManager.regenerateTokenAuth'
- }, parameters).then(function (success) {
- $window.location.reload();
- self.loading = false;
- }, function (errorMessage) {
- self.loading = false;
- });
- }});
- };
-
this.cancelSave = function () {
this.passwordCurrent = '';
};
@@ -116,14 +97,6 @@
timeformat: this.timeformat,
};
- if (this.password) {
- postParams.password = this.password;
- }
-
- if (this.passwordBis) {
- postParams.passwordBis = this.passwordBis;
- }
-
if (this.passwordCurrent) {
postParams.passwordConfirmation = this.passwordCurrent;
}
diff --git a/plugins/UsersManager/lang/en.json b/plugins/UsersManager/lang/en.json
index c8f7e4de47..8f23bae7c7 100644
--- a/plugins/UsersManager/lang/en.json
+++ b/plugins/UsersManager/lang/en.json
@@ -13,6 +13,16 @@
"SaveBasicInfo": "Save Basic Info",
"Alias": "Alias",
"AllWebsites": "All websites",
+ "LastUsed": "Last used",
+ "ExpireDate": "Expire date",
+ "AuthTokens": "Auth tokens",
+ "AuthTokenPurpose": "What are you using this token for?",
+ "NoTokenCreatedYetCreateNow": "No token created yet, %1$screate a new token now%2$s.",
+ "TokenSuccessfullyGenerated": "Token successfully generated",
+ "ConfirmTokenCopied": "I confirm I copied the token.",
+ "GoBackSecurityPage": "Go back to security page.",
+ "PleaseStoreToken": "Please store your token securely as you will not be able to access or see the token again.",
+ "CreateNewToken": "Create new token",
"AnonymousAccessConfirmation": "You are about to grant the anonymous user the 'view' access to this website. This means your analytics reports and your visitors information will be publicly viewable by anyone even without a login. Are you sure you want to proceed?",
"AnonymousUser": "Anonymous user",
"AnonymousUserHasViewAccess": "Note: the %1$s user has %2$s access to this website.",
@@ -90,7 +100,14 @@
"TokenAuth": "API Authentication Token",
"TokenRegenerateConfirmSelf": "Changing the API authentication token will invalidate your own token. If the current token is in use, you need to update all API clients with the newly generated token. Do you really want to change your authentication token?",
"TokenRegenerateTitle": "Regenerate",
+ "TokensSuccessfullyDeleted": "All tokens were successfully deleted",
+ "TokenSuccessfullyDeleted": "Token was successfully deleted",
+ "DeleteAllTokens": "Delete all tokens",
+ "ExpiredTokensDeleteAutomatically": "Tokens with an expire date will be deleted automatically.",
+ "TokensWithExpireDateCreationBySystem": "Tokens with expire date can currently only be created by the system",
+ "TokenAuthIntro": "Tokens you have generated can be used to access the Matomo reporting API, Matomo tracking API, and exported Matomo widgets and have the same permissions as your regular user login. You can use these tokens also for the Matomo Mobile app.",
"TypeYourPasswordAgain": "Type your new password again.",
+ "TypeYourCurrentPassword": "Please type your current password to confirm the password change.",
"User": "User",
"UserHasPermission":"%1$s currently has %2$s access for %3$s.",
"UserHasNoPermission":"%1$s currently has %2$s to %3$s",
diff --git a/plugins/UsersManager/stylesheets/usersManager.less b/plugins/UsersManager/stylesheets/usersManager.less
index 3def95dce6..c3a47dd7bf 100644
--- a/plugins/UsersManager/stylesheets/usersManager.less
+++ b/plugins/UsersManager/stylesheets/usersManager.less
@@ -97,4 +97,11 @@
margin-right: 1em;
margin-top: 1em;
}
+}
+
+.uiTest pre.generatedTokenAuth {
+ visibility: hidden;
+}
+.uiTest .listAuthTokens .creationDate {
+ visibility: hidden;
} \ No newline at end of file
diff --git a/plugins/UsersManager/templates/addNewToken.twig b/plugins/UsersManager/templates/addNewToken.twig
new file mode 100644
index 0000000000..fa04b2f26b
--- /dev/null
+++ b/plugins/UsersManager/templates/addNewToken.twig
@@ -0,0 +1,37 @@
+{% extends 'admin.twig' %}
+
+{% set title %}{{ 'General_Security'|translate }}{% endset %}
+
+{% block content %}
+
+ <div piwik-content-block content-title="{{ 'UsersManager_AuthTokens'|translate|e('html_attr') }}">
+ <p>
+ {{ 'UsersManager_TokenAuthIntro'|translate }}
+ </p>
+
+ {% if noDescription %}
+ <br>
+ <div class="alert alert-danger">
+ {{ 'General_Description'|translate }}: {{ 'General_ValidatorErrorEmptyValue'|translate }}
+ </div>
+ {% endif %}
+
+ <form action="{{ linkTo({'module': 'UsersManager', 'action': 'addNewToken'}) }}" method="post" class="addTokenForm">
+ <div piwik-field uicontrol="text" name="description"
+ data-title="{{ 'General_Description'|translate|e('html_attr') }}"
+ maxlength="100" required
+ inline-help="{{ 'UsersManager_AuthTokenPurpose'|translate|e('html_attr') }}">
+ </div>
+
+ <input type="hidden" value="{{ nonce|e('html_attr') }}" name="nonce">
+
+ <input type="submit"
+ value="{{ 'UsersManager_CreateNewToken'|translate|e('html_attr') }}"
+ class="btn"/>
+ {% set backlink = linkTo({'module': 'UsersManager', 'action': 'userSecurity'}) %}
+ {{ 'General_OrCancel'|translate("<a class='entityCancelLink' href='" ~ backlink ~ "'>","</a>")|raw }}
+
+ </form>
+ </div>
+
+{% endblock %}
diff --git a/plugins/UsersManager/templates/addNewTokenSuccess.twig b/plugins/UsersManager/templates/addNewTokenSuccess.twig
new file mode 100644
index 0000000000..ba0ac62b09
--- /dev/null
+++ b/plugins/UsersManager/templates/addNewTokenSuccess.twig
@@ -0,0 +1,17 @@
+{% extends 'admin.twig' %}
+
+{% set title %}{{ 'General_Security'|translate }}{% endset %}
+
+{% block content %}
+
+ <div piwik-content-block content-title="{{ 'UsersManager_TokenSuccessfullyGenerated'|translate|e('html_attr') }}">
+ <p>
+ {{ 'UsersManager_PleaseStoreToken'|translate }}
+ </p>
+ <pre piwik-select-on-focus style="font-size: 40px;" class="generatedTokenAuth"><code>{{ generatedToken }}</code></pre>
+
+ <a href="{{ linkTo({'module': 'UsersManager', 'action': 'userSecurity'}) }}" class="btn"
+ >{{ 'UsersManager_ConfirmTokenCopied'|translate }} {{ 'UsersManager_GoBackSecurityPage'|translate }}</a>
+ </div>
+
+{% endblock %}
diff --git a/plugins/UsersManager/templates/userSecurity.twig b/plugins/UsersManager/templates/userSecurity.twig
new file mode 100644
index 0000000000..58c03b571e
--- /dev/null
+++ b/plugins/UsersManager/templates/userSecurity.twig
@@ -0,0 +1,121 @@
+{% extends 'admin.twig' %}
+
+{% set title %}{{ 'General_Security'|translate }}{% endset %}
+
+{% block content %}
+{% if isUsersAdminEnabled %}
+ <div piwik-content-block content-title="{{ 'General_ChangePassword'|translate|e('html_attr') }}" feature="true">
+ <form id="userSettingsTable" method="post" action="{{ linkTo({'module': 'UsersManager', 'action': 'recordPasswordChange'}) }}">
+
+ <input type="hidden" value="{{ changePasswordNonce|e('html_attr') }}" name="nonce">
+
+ {% if isValidHost is defined and isValidHost %}
+
+ <div piwik-field uicontrol="password" name="password" autocomplete="off"
+ ng-model="personalSettings.password"
+ ng-change="personalSettings.requirePasswordConfirmation()"
+ data-title="{{ 'Login_NewPassword'|translate|e('html_attr') }}"
+ value="" inline-help="{{ 'UsersManager_IfYouWouldLikeToChangeThePasswordTypeANewOne'|translate|e('html_attr') }}">
+ </div>
+
+ <div piwik-field uicontrol="password" name="passwordBis" autocomplete="off"
+ ng-model="personalSettings.passwordBis"
+ ng-change="personalSettings.requirePasswordConfirmation()"
+ data-title="{{ 'Login_NewPasswordRepeat'|translate|e('html_attr') }}"
+ value="" inline-help="{{ 'UsersManager_TypeYourPasswordAgain'|translate|e('html_attr') }}">
+ </div>
+
+ <div piwik-field uicontrol="password" name="passwordConfirmation" autocomplete="off"
+ ng-model="personalSettings.current_password"
+ data-title="{{ 'UsersManager_YourCurrentPassword'|translate|e('html_attr') }}"
+ value="" inline-help="{{ 'UsersManager_TypeYourCurrentPassword'|translate|e('html_attr') }}">
+ </div>
+
+ <input type="submit"
+ value="{{ 'General_Save'|translate|e('html_attr') }}"
+ class="btn"/>
+ {% endif %}
+
+ {% if isValidHost is not defined or not isValidHost %}
+ <div class="alert alert-danger">
+ {{ 'UsersManager_InjectedHostCannotChangePwd'|translate(invalidHost) }}
+ {% if not isSuperUser %}{{ 'UsersManager_EmailYourAdministrator'|translate(invalidHostMailLinkStart,'</a>')|raw }}{% endif %}
+ </div>
+ {% endif %}
+
+ </form>
+ </div>
+
+ {{ postEvent('Template.userSecurity.afterPassword') }}
+{% endif %}
+
+ <a name="authtokens" id="authtokens"></a>
+ <div piwik-content-block content-title="{{ 'UsersManager_AuthTokens'|translate|e('html_attr') }}">
+ <p>
+ {{ 'UsersManager_TokenAuthIntro'|translate }}
+ {% if hasTokensWithExpireDate %}{{ 'UsersManager_ExpiredTokensDeleteAutomatically'|translate }}{% endif %}
+ </p>
+ <table piwik-content-table class="listAuthTokens">
+ <thead>
+ <tr>
+ <th>{{ 'General_CreationDate'|translate }}</th>
+ <th>{{ 'General_Description'|translate }}</th>
+ <th>{{ 'UsersManager_LastUsed'|translate }}</th>
+ {% if hasTokensWithExpireDate %}<th title="{{ 'UsersManager_TokensWithExpireDateCreationBySystem'|translate|e('html_attr') }}">{{ 'UsersManager_ExpireDate'|translate }}</th>{% endif %}
+ <th>{{ 'General_Actions'|translate }}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% if tokens is empty %}
+ <tr>
+ <td colspan="{% if hasTokensWithExpireDate %}5{% else %}4{% endif %}">
+ {{ 'UsersManager_NoTokenCreatedYetCreateNow'|translate('<a href="' ~ (linkTo({'module': 'UsersManager', 'action': 'addNewToken'})|e('html_attr'))~ '">', '</a>')|raw }}
+ </td></tr>
+ {% else %}
+ {% for theToken in tokens %}
+ <tr>
+ <td><span class="creationDate">{{ theToken.date_created }}</span></td>
+ <td>{{ theToken.description }}</td>
+ <td>{% if theToken.last_used %}{{ theToken.last_used }}{% else %}{{ 'General_Never'|translate }}{% endif %}</td>
+ {% if hasTokensWithExpireDate %}
+ <td title="{{ 'UsersManager_TokensWithExpireDateCreationBySystem'|translate|e('html_attr') }}">
+ {% if theToken.date_expired %}{{ theToken.date_expired }}{% else %}{{ 'General_Never'|translate }}{% endif %}
+ </td>
+ {% endif %}
+ <td>
+ <form method="post" action="{{ linkTo({'module': 'UsersManager', 'action': 'deleteToken'}) }}" style="display: inline">
+ <input name="nonce" type="hidden" value="{{ deleteTokenNonce|e('html_attr') }}">
+ <input name="idtokenauth" type="hidden" value="{{ theToken.idusertokenauth|e('html_attr') }}">
+ <button type="submit" class="table-action"
+ title="{{ 'General_Delete'|translate|e('html_attr') }}">
+ <span class="icon-delete"></span>
+ </button>
+ </form>
+ </td>
+ </tr>
+ {% endfor %}
+ {% endif %}
+ </tbody>
+ </table>
+
+ <div class="tableActionBar">
+ <a href="{{ linkTo({'module': 'UsersManager', 'action': 'addNewToken'})|e('html_attr') }}" class="addNewToken">
+ <span class="icon-add"></span>
+ {{ 'UsersManager_CreateNewToken'|translate }}
+ </a>
+
+ {% if tokens is not empty %}
+ <form method="post" action="{{ linkTo({'module': 'UsersManager', 'action': 'deleteToken'}) }}" style="display: inline">
+ <input name="nonce" type="hidden" value="{{ deleteTokenNonce|e('html_attr') }}">
+ <input name="idtokenauth" type="hidden" value="all">
+ <button type="submit" class="table-action">
+ <span class="icon-delete"></span> {{ 'UsersManager_DeleteAllTokens'|translate }}
+ </button>
+ </form>
+ {% endif %}
+ </div>
+
+ </div>
+
+
+{% endblock %}
diff --git a/plugins/UsersManager/templates/userSettings.twig b/plugins/UsersManager/templates/userSettings.twig
index 22c9a58ccb..370c710cc8 100644
--- a/plugins/UsersManager/templates/userSettings.twig
+++ b/plugins/UsersManager/templates/userSettings.twig
@@ -4,12 +4,6 @@
{% block content %}
-<div class="ui-confirm" id="confirmTokenRegenerate">
- <h2>{{ 'UsersManager_TokenRegenerateConfirmSelf'|translate }}</h2>
- <input role="yes" type="button" value="{{ 'General_Yes'|translate }}"/>
- <input role="no" type="button" value="{{ 'General_No'|translate }}"/>
-</div>
-
<div piwik-content-block content-title="{{ title|e('html_attr') }}" feature="true">
<form id="userSettingsTable" piwik-form ng-controller="PersonalSettingsController as personalSettings">
@@ -74,31 +68,6 @@
value="{{ defaultDate }}" options="{{ availableDefaultDates|json_encode }}">
</div>
- {% if isValidHost is defined and isValidHost and isUsersAdminEnabled %}
-
- <div piwik-field uicontrol="password" name="password" autocomplete="off"
- ng-model="personalSettings.password"
- ng-change="personalSettings.requirePasswordConfirmation()"
- introduction="{{ 'General_ChangePassword'|translate|e('html_attr') }}"
- data-title="{{ 'Login_NewPassword'|translate|e('html_attr') }}"
- value="" inline-help="{{ 'UsersManager_IfYouWouldLikeToChangeThePasswordTypeANewOne'|translate|e('html_attr') }}">
- </div>
-
- <div piwik-field uicontrol="password" name="passwordBis" autocomplete="off"
- ng-model="personalSettings.passwordBis"
- ng-change="personalSettings.requirePasswordConfirmation()"
- data-title="{{ 'Login_NewPasswordRepeat'|translate|e('html_attr') }}"
- value="" inline-help="{{ 'UsersManager_TypeYourPasswordAgain'|translate|e('html_attr') }}">
- </div>
- {% endif %}
-
- {% if isValidHost is not defined or not isValidHost %}
- <div class="alert alert-danger">
- {{ 'UsersManager_InjectedHostCannotChangePwd'|translate(invalidHost) }}
- {% if not isSuperUser %}{{ 'UsersManager_EmailYourAdministrator'|translate(invalidHostMailLinkStart,'</a>')|raw }}{% endif %}
- </div>
- {% endif %}
-
<div piwik-save-button onconfirm="personalSettings.save()"
saving="personalSettings.loading"></div>
@@ -122,40 +91,31 @@
</form>
</div>
+{% endblock %}
+
{% if showNewsletterSignup %}
-<div ng-controller="PersonalSettingsController as personalSettings">
- <div piwik-content-block id="newsletterSignup"
- ng-show="personalSettings.showNewsletterSignup"
- content-title="{{ 'UsersManager_NewsletterSignupTitle'|translate|e('html_attr') }}">
-
- <div piwik-field uicontrol="checkbox" name="newsletterSignupCheckbox"
- ng-model="personalSettings.newsletterSignupCheckbox"
- full-width="true"
- data-title="{{ 'UsersManager_NewsletterSignupMessage'|translate('<a href="https://matomo.org/privacy-policy/" target="_blank">', '</a>')|e('html_attr') }}"
- >
- </div>
+ <div ng-controller="PersonalSettingsController as personalSettings">
+ <div piwik-content-block id="newsletterSignup"
+ ng-show="personalSettings.showNewsletterSignup"
+ content-title="{{ 'UsersManager_NewsletterSignupTitle'|translate|e('html_attr') }}">
+
+ <div piwik-field uicontrol="checkbox" name="newsletterSignupCheckbox"
+ ng-model="personalSettings.newsletterSignupCheckbox"
+ full-width="true"
+ data-title="{{ 'UsersManager_NewsletterSignupMessage'|translate('<a href="https://matomo.org/privacy-policy/" target="_blank">', '</a>')|e('html_attr') }}"
+ >
+ </div>
- <div piwik-save-button id="newsletterSignupBtn"
- onconfirm="personalSettings.signupForNewsletter()"
- data-disabled="!personalSettings.newsletterSignupCheckbox"
- value="{{ '{{ personalSettings.newsletterSignupButtonTitle }}'|raw }}"
- saving="personalSettings.isProcessingNewsletterSignup">
+ <div piwik-save-button id="newsletterSignupBtn"
+ onconfirm="personalSettings.signupForNewsletter()"
+ data-disabled="!personalSettings.newsletterSignupCheckbox"
+ value="{{ '{{ personalSettings.newsletterSignupButtonTitle }}'|raw }}"
+ saving="personalSettings.isProcessingNewsletterSignup">
+ </div>
</div>
</div>
-</div>
{% endif %}
-<div piwik-content-block
- content-title="{{ 'UsersManager_TokenAuth'|translate|e('html_attr') }}">
- <pre piwik-select-on-focus id="token_auth_user" piwik-show-sensitive-data="{{ userTokenAuth }}"></pre>
-
- <button class="btn btn-link"
- ng-controller="PersonalSettingsController as personalSettings"
- ng-click="personalSettings.regenerateTokenAuth()">{{ 'UsersManager_TokenRegenerateTitle'|translate }}</button>
-</div>
-
-{{ postEvent('Template.userSettings.afterTokenAuth') }}
-
<div piwik-plugin-settings mode="user"></div>
<div piwik-content-block
@@ -174,4 +134,4 @@
</a></span>
</div>
-{% endblock %}
+{% endblock %} \ No newline at end of file
diff --git a/plugins/UsersManager/tests/Fixtures/ManyUsers.php b/plugins/UsersManager/tests/Fixtures/ManyUsers.php
index 9eba719d2a..4497ed319d 100644
--- a/plugins/UsersManager/tests/Fixtures/ManyUsers.php
+++ b/plugins/UsersManager/tests/Fixtures/ManyUsers.php
@@ -7,6 +7,7 @@
*/
namespace Piwik\Plugins\UsersManager\tests\Fixtures;
+use Piwik\Date;
use Piwik\Plugins\UsersManager\API;
use Piwik\Plugins\UsersManager\Model;
use Piwik\Plugins\UsersManager\UserUpdater;
@@ -27,6 +28,7 @@ class ManyUsers extends Fixture
public $siteCopyCount;
public $userCopyCount;
public $addTextSuffixes;
+ public $users = array();
public $baseUsers = array(
'login1' => array('superuser' => 1),
@@ -129,8 +131,11 @@ class ManyUsers extends Fixture
}
}
+ $tokenAuth = $model->generateRandomTokenAuth();
+ $model->addTokenAuth($login, $tokenAuth, 'many users test', Date::now()->getDatetime());
+
$user = $model->getUser($login);
- $this->users[$login]['token'] = $user['token_auth'];
+ $this->users[$login]['token'] = $tokenAuth;
}
}
}
diff --git a/plugins/UsersManager/tests/Integration/ModelTest.php b/plugins/UsersManager/tests/Integration/ModelTest.php
index be2c79d333..0a99be16de 100644
--- a/plugins/UsersManager/tests/Integration/ModelTest.php
+++ b/plugins/UsersManager/tests/Integration/ModelTest.php
@@ -43,6 +43,7 @@ class ModelTest extends IntegrationTestCase
private $model;
private $login = 'userLogin';
+ private $login2 = 'userLogin2';
public function setUp(): void
{
@@ -58,6 +59,7 @@ class ModelTest extends IntegrationTestCase
Fixture::createWebsite('2014-01-01 00:00:00');
Fixture::createWebsite('2014-01-01 00:00:00');
$this->api->addUser($this->login, 'password', 'userlogin@password.de');
+ $this->api->addUser($this->login2, 'password2', 'userlogin2@password.de');
}
public function test_getSitesAccessFromUser_noAccess()
@@ -109,4 +111,262 @@ class ModelTest extends IntegrationTestCase
), $this->model->getSitesAccessFromUser($this->login));
}
+ public function test_getAllNonSystemTokensForLogin_whenNoTokenConfigured()
+ {
+ $tokens = $this->model->getAllNonSystemTokensForLogin($this->login);
+ $this->assertSame(array(), $tokens);
+ }
+
+ public function test_addTokenAuth_minimal()
+ {
+ $this->model->addTokenAuth($this->login, 'token', 'MyDescription', '2020-01-02 03:04:05');
+ $tokens = $this->model->getAllNonSystemTokensForLogin($this->login);
+ $this->assertEquals(array(array(
+ 'idusertokenauth' => '1',
+ 'login' => 'userLogin',
+ 'description' => 'MyDescription',
+ 'password' => '2265daba0872fc3aef169d079365e590f0cbc8ed46c2a7984c8a642803cfd96cb47804a63cf22a79f6ca469268c29ee9e72a5059b62d0a598fe42dfc8dcc51bc',
+ 'hash_algo' => 'sha512',
+ 'system_token' => '0',
+ 'last_used' => null,
+ 'date_created' => '2020-01-02 03:04:05',
+ 'date_expired' => null
+ )), $tokens);
+ }
+
+ public function test_addTokenAuth_expire()
+ {
+ $id = $this->model->addTokenAuth($this->login, 'token', 'MyDescription', '2020-01-02 03:04:05', '2030-01-05 03:04:05');
+ $this->assertEquals(1, $id);
+ $tokens = $this->model->getAllNonSystemTokensForLogin($this->login);
+ $this->assertEquals(array(array(
+ 'idusertokenauth' => '1',
+ 'login' => 'userLogin',
+ 'description' => 'MyDescription',
+ 'password' => '2265daba0872fc3aef169d079365e590f0cbc8ed46c2a7984c8a642803cfd96cb47804a63cf22a79f6ca469268c29ee9e72a5059b62d0a598fe42dfc8dcc51bc',
+ 'hash_algo' => 'sha512',
+ 'system_token' => '0',
+ 'last_used' => null,
+ 'date_created' => '2020-01-02 03:04:05',
+ 'date_expired' => '2030-01-05 03:04:05'
+ )), $tokens);
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage does not exist
+ */
+ public function test_addTokenAuth_throwsException_ifUserNotExists()
+ {
+ $this->model->addTokenAuth('foobar', 'token', 'MyDescription', '2020-01-02 03:04:05', '2030-01-05 03:04:05');
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage Duplicate entry
+ */
+ public function test_addTokenAuth_throwsException_FailsAddingSameTwice()
+ {
+ $this->model->addTokenAuth($this->login, 'token', 'My description', '2020-01-02 03:04:05');
+ $this->model->addTokenAuth($this->login, 'token', 'My duplicate', '2020-01-03 03:04:05');
+ }
+
+ public function test_addTokenAuth_returnsId()
+ {
+ $id = $this->model->addTokenAuth($this->login, 'token', 'MyDescription', '2020-01-02 03:04:05');
+ $this->assertEquals(1, $id);
+ $id = $this->model->addTokenAuth($this->login, 'token2', 'MyDescription', '2020-01-02 03:04:05');
+ $this->assertEquals(2, $id);
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage General_ValidatorErrorEmptyValue
+ */
+ public function test_addTokenAuth_throwsException_NoDescription()
+ {
+ $this->model->addTokenAuth($this->login, 'token', '', '2020-01-02 03:04:05');
+ }
+
+ public function test_getAllNonSystemTokensForLogin_doesNotReturnSystemTokens()
+ {
+ $this->model->addTokenAuth($this->login, 'token2', 'api usage token', '2020-01-02 03:04:05', null, true);
+ $tokens = $this->model->getAllNonSystemTokensForLogin($this->login);
+ $this->assertEquals(array(), $tokens);
+ }
+
+ public function test_getAllNonSystemTokensForLogin_doesNotReturnExpiredTokens()
+ {
+ $this->model->addTokenAuth($this->login, 'token2', 'api usage token', '2020-01-02 03:04:05', '2019-01-05 03:04:05');
+ $tokens = $this->model->getAllNonSystemTokensForLogin($this->login);
+ $this->assertEquals(array(), $tokens);
+ }
+
+ public function test_getAllNonSystemTokensForLogin_returnsNotExpiredToken()
+ {
+ $this->model->addTokenAuth($this->login, 'token', 'MyDescription', '2020-01-02 03:04:05', '2030-01-05 03:04:05');
+ $tokens = $this->model->getAllNonSystemTokensForLogin($this->login);
+ $this->assertEquals(array(array(
+ 'idusertokenauth' => '1',
+ 'login' => 'userLogin',
+ 'description' => 'MyDescription',
+ 'password' => '2265daba0872fc3aef169d079365e590f0cbc8ed46c2a7984c8a642803cfd96cb47804a63cf22a79f6ca469268c29ee9e72a5059b62d0a598fe42dfc8dcc51bc',
+ 'hash_algo' => 'sha512',
+ 'system_token' => '0',
+ 'last_used' => null,
+ 'date_created' => '2020-01-02 03:04:05',
+ 'date_expired' => '2030-01-05 03:04:05'
+ )), $tokens);
+ }
+
+ public function test_getUserByTokenAuth_findsUserWhenTokenNotYetExpired()
+ {
+ $this->model->addTokenAuth($this->login, 'token', 'MyDescription', '2020-01-02 03:04:05', '2030-01-05 03:04:05');
+ $user = $this->model->getUserByTokenAuth('token');
+ $this->assertSame($this->login, $user['login']);
+ }
+
+ public function test_getUserByTokenAuth_findsUserWhenNoExpireDateSet()
+ {
+ $this->model->addTokenAuth($this->login, 'token', 'MyDescription', '2020-01-02 03:04:05');
+ $user = $this->model->getUserByTokenAuth('token');
+ $this->assertSame($this->login, $user['login']);
+ }
+
+ public function test_getUserByTokenAuth_notFindsUserWhenTokenIsExpired()
+ {
+ $this->model->addTokenAuth($this->login, 'token', 'MyDescription', '2020-01-02 03:04:05', '2019-03-04 00:05:06');
+ $user = $this->model->getUserByTokenAuth('token');
+ $this->assertEmpty($user);
+ }
+
+ public function test_getUserByTokenAuth_findsUserWhenTokenIsSystemToken()
+ {
+ $this->model->addTokenAuth($this->login, 'token', 'MyDescription', '2020-01-02 03:04:05', null, true);
+ $user = $this->model->getUserByTokenAuth('token');
+ $this->assertSame($this->login, $user['login']);
+ }
+
+ public function test_generateRandomTokenAuth_correctFormat()
+ {
+ $token = $this->model->generateRandomTokenAuth();
+ $this->assertSame(32, strlen($token));
+ $this->assertTrue(ctype_xdigit($token));
+ }
+
+ public function test_generateRandomTokenAuth_isAlwaysDifferent()
+ {
+ $this->assertNotEquals($this->model->generateRandomTokenAuth(), $this->model->generateRandomTokenAuth());
+ }
+
+ public function test_hashTokenAuth()
+ {
+ $this->assertSame('2265daba0872fc3aef169d079365e590f0cbc8ed46c2a7984c8a642803cfd96cb47804a63cf22a79f6ca469268c29ee9e72a5059b62d0a598fe42dfc8dcc51bc', $this->model->hashTokenAuth('token'));
+ $this->assertSame('02c2e43dcb393097a1221465812a4e9b1e1e80f16e92b313fd4ce8c5ee5b8272a17cd8cdc1ce63578494eaba739c6f7abba7890506ef6bf8d607538778f2a849', $this->model->hashTokenAuth('token2'));
+ }
+
+ public function test_getAllHashedTokensForLogins_noLoginsSet()
+ {
+ $this->assertSame(array(), $this->model->getAllHashedTokensForLogins(array()));
+ }
+
+ public function test_getAllHashedTokensForLogins_noTokensExist()
+ {
+ $this->assertSame(array(), $this->model->getAllHashedTokensForLogins(array('foo', 'bar')));
+ }
+
+ public function test_getAllHashedTokensForLogins()
+ {
+ $this->model->addTokenAuth($this->login, 'token', 'MyDescription', '2020-01-02 03:04:05', null, true);
+ $this->model->addTokenAuth($this->login, 'token2', 'MyDescription', '2020-01-02 03:04:05', null, false);
+ // does not return expired tokens
+ $this->model->addTokenAuth($this->login, 'token3', 'MyDescription', '2020-01-02 03:04:05', '2019-02-03 00:01:02', true);
+
+ $this->assertSame(array(), $this->model->getAllHashedTokensForLogins(array('foo', 'bar')));
+
+ $this->assertSame(array(
+ '2265daba0872fc3aef169d079365e590f0cbc8ed46c2a7984c8a642803cfd96cb47804a63cf22a79f6ca469268c29ee9e72a5059b62d0a598fe42dfc8dcc51bc',
+ '02c2e43dcb393097a1221465812a4e9b1e1e80f16e92b313fd4ce8c5ee5b8272a17cd8cdc1ce63578494eaba739c6f7abba7890506ef6bf8d607538778f2a849'
+ ), $this->model->getAllHashedTokensForLogins(array('foo', $this->login, 'bar')));
+ }
+
+ public function test_deleteToken()
+ {
+ $id1 = $this->model->addTokenAuth($this->login, 'token', 'MyDescription1', '2020-01-02 03:04:05');
+ $id2 = $this->model->addTokenAuth($this->login, 'token2', 'MyDescription2', '2020-01-03 03:04:05');
+
+ // should not have deleted anything as it doesn't match
+ $this->model->deleteToken(999, $this->login);
+ $this->model->deleteToken($id1, 'foobar');
+
+ $tokens = $this->model->getAllNonSystemTokensForLogin($this->login);
+ $this->assertCount(2, $tokens);
+ $this->assertEquals($id1, $tokens[0]['idusertokenauth']);
+ $this->assertEquals($id2, $tokens[1]['idusertokenauth']);
+
+ // should only delete that id
+ $this->model->deleteToken($id1, $this->login);
+
+ $tokens = $this->model->getAllNonSystemTokensForLogin($this->login);
+ $this->assertCount(1, $tokens);
+ $this->assertEquals($id2, $tokens[0]['idusertokenauth']);
+ }
+
+ public function test_deleteAllTokensForUser()
+ {
+ $this->model->addTokenAuth($this->login, 'token', 'MyDescription1', '2020-01-02 03:04:05');
+ $this->model->addTokenAuth($this->login, 'token2', 'MyDescription2', '2020-01-03 03:04:05');
+ $this->model->addTokenAuth($this->login2, 'token3', 'MyDescription2', '2020-01-03 03:04:05');
+
+ // should not have deleted anything as it doesn't match
+ $this->model->deleteAllTokensForUser('foobar');
+
+ $this->assertCount(2, $this->model->getAllNonSystemTokensForLogin($this->login));
+ $this->assertCount(1, $this->model->getAllNonSystemTokensForLogin($this->login2));
+
+ // should only delete tokens for that login
+ $this->model->deleteAllTokensForUser($this->login);
+
+ $tokens = $this->model->getAllNonSystemTokensForLogin($this->login);
+ $this->assertCount(0, $this->model->getAllNonSystemTokensForLogin($this->login));
+ $this->assertCount(1, $this->model->getAllNonSystemTokensForLogin($this->login2));
+ }
+
+ public function test_setTokenAuthWasUsed()
+ {
+ $this->model->addTokenAuth($this->login, 'token2', 'MyDescription', '2020-01-02 03:04:05');
+ $this->model->setTokenAuthWasUsed('token2', '2025-01-02 03:04:05');
+
+ $tokens = $this->model->getAllNonSystemTokensForLogin($this->login);
+ $this->assertSame('2025-01-02 03:04:05', $tokens[0]['last_used']);
+ }
+
+ public function test_setTokenAuthWasUsed_doesNotFailWhenTokenNotExists()
+ {
+ $this->model->setTokenAuthWasUsed('tokenFooBar', '2025-01-02 03:04:05');
+ }
+
+ public function test_deleteExpiredTokens()
+ {
+ $id1 = $this->model->addTokenAuth($this->login, 'token', 'MyDescription1', '2020-01-01 03:04:05', '2020-01-02 03:04:05');
+ $id2 = $this->model->addTokenAuth($this->login, 'token2', 'MyDescription2', '2020-01-02 03:04:05');
+ $id3 = $this->model->addTokenAuth($this->login, 'token3', 'MyDescription3', '2020-01-03 03:04:05', '2022-01-02 03:04:05');
+ $id4 = $this->model->addTokenAuth($this->login2, 'token4', 'MyDescription4', '2020-01-04 03:04:05', '2024-01-02 03:04:05');
+ $id5 = $this->model->addTokenAuth($this->login2, 'token5', 'MyDescription5', '2020-01-05 03:04:05');
+ $id6 = $this->model->addTokenAuth($this->login2, 'token6', 'MyDescription6', '2020-01-06 03:04:05', '2018-01-02 03:04:05');
+
+ // id1 and id6 are expired and should have been deleted
+ $this->model->deleteExpiredTokens('2021-01-02 03:04:05');
+
+ $tokens = $this->model->getAllNonSystemTokensForLogin($this->login);
+ $this->assertSame($id2, $tokens[0]['idusertokenauth']);
+ $this->assertSame($id3, $tokens[1]['idusertokenauth']);
+ $this->assertCount(2, $tokens);
+
+ $tokens = $this->model->getAllNonSystemTokensForLogin($this->login2);
+ $this->assertSame($id4, $tokens[0]['idusertokenauth']);
+ $this->assertSame($id5, $tokens[1]['idusertokenauth']);
+ $this->assertCount(2, $tokens);
+ }
+
}
diff --git a/plugins/UsersManager/tests/Integration/UserAccessFilterTest.php b/plugins/UsersManager/tests/Integration/UserAccessFilterTest.php
index 86c150e2ed..5dacff9e16 100644
--- a/plugins/UsersManager/tests/Integration/UserAccessFilterTest.php
+++ b/plugins/UsersManager/tests/Integration/UserAccessFilterTest.php
@@ -283,16 +283,16 @@ class UserAccessFilterTest extends IntegrationTestCase
private function createManyUsers()
{
- $this->model->addUser('login1', md5('pass'), 'email1@example.com', 'alias1', md5('token1'), '2008-01-01 00:00:00');
- $this->model->addUser('login2', md5('pass'), 'email2@example.com', 'alias2', md5('token2'), '2008-01-01 00:00:00');
+ $this->model->addUser('login1', md5('pass'), 'email1@example.com', 'alias1', '2008-01-01 00:00:00');
+ $this->model->addUser('login2', md5('pass'), 'email2@example.com', 'alias2', '2008-01-01 00:00:00');
// login3 won't have access to any site
- $this->model->addUser('login3', md5('pass'), 'email3@example.com', 'alias3', md5('token3'), '2008-01-01 00:00:00');
- $this->model->addUser('login4', md5('pass'), 'email4@example.com', 'alias4', md5('token4'), '2008-01-01 00:00:00');
- $this->model->addUser('login5', md5('pass'), 'email5@example.com', 'alias5', md5('token5'), '2008-01-01 00:00:00');
- $this->model->addUser('login6', md5('pass'), 'email6@example.com', 'alias6', md5('token6'), '2008-01-01 00:00:00');
- $this->model->addUser('login7', md5('pass'), 'email7@example.com', 'alias7', md5('token7'), '2008-01-01 00:00:00');
- $this->model->addUser('login8', md5('pass'), 'email8@example.com', 'alias8', md5('token8'), '2008-01-01 00:00:00');
- $this->model->addUser('anonymous', '', 'ano@example.com', 'anonymous', 'anonymous', '2008-01-01 00:00:00');
+ $this->model->addUser('login3', md5('pass'), 'email3@example.com', 'alias3', '2008-01-01 00:00:00');
+ $this->model->addUser('login4', md5('pass'), 'email4@example.com', 'alias4', '2008-01-01 00:00:00');
+ $this->model->addUser('login5', md5('pass'), 'email5@example.com', 'alias5', '2008-01-01 00:00:00');
+ $this->model->addUser('login6', md5('pass'), 'email6@example.com', 'alias6', '2008-01-01 00:00:00');
+ $this->model->addUser('login7', md5('pass'), 'email7@example.com', 'alias7', '2008-01-01 00:00:00');
+ $this->model->addUser('login8', md5('pass'), 'email8@example.com', 'alias8', '2008-01-01 00:00:00');
+ $this->model->addUser('anonymous', '', 'ano@example.com', 'anonymous', '2008-01-01 00:00:00');
$this->model->setSuperUserAccess('login1', true); // we treat this one as our superuser
diff --git a/plugins/UsersManager/tests/Integration/UsersManagerTest.php b/plugins/UsersManager/tests/Integration/UsersManagerTest.php
index d7ae784bf1..2a66f9dad2 100644
--- a/plugins/UsersManager/tests/Integration/UsersManagerTest.php
+++ b/plugins/UsersManager/tests/Integration/UsersManagerTest.php
@@ -109,11 +109,6 @@ class UsersManagerTest extends IntegrationTestCase
unset($userAfter['password']);
// implicitly checks password!
- $userModel = $this->model->getUser($user['login']);
- $userAfter['token_auth'] = $userModel['token_auth'];
-
- $user['token_auth'] = $this->api->getTokenAuth($user["login"], md5($newPassword));
-
$user['email'] = $newEmail;
$user['alias'] = $newAlias;
$user['superuser_access'] = 0;
@@ -282,12 +277,6 @@ class UsersManagerTest extends IntegrationTestCase
// check that password and token are properly set
$this->assertEquals(60, strlen($user['password']));
- $userModel = $this->model->getUser($login);
- $this->assertEquals(32, strlen($userModel['token_auth']));
-
- $userModel = $this->model->getUser($login);
- $this->assertEquals($userModel['token_auth'], $this->api->getTokenAuth($login, UsersManager::getPasswordHash($password)));
-
// check that all fields are the same
$this->assertEquals($login, $user['login']);
$this->assertEquals($email, $user['email']);
diff --git a/plugins/UsersManager/tests/System/ApiTest.php b/plugins/UsersManager/tests/System/ApiTest.php
index 0a7d808ffb..5908f9245c 100644
--- a/plugins/UsersManager/tests/System/ApiTest.php
+++ b/plugins/UsersManager/tests/System/ApiTest.php
@@ -8,6 +8,9 @@
namespace Piwik\Plugins\UsersManager\tests\System;
+use Piwik\Date;
+use Piwik\Plugins\UsersManager\API;
+use Piwik\Plugins\UsersManager\Model;
use Piwik\Plugins\UsersManager\tests\Fixtures\ManyUsers;
use Piwik\Tests\Framework\TestCase\SystemTestCase;
@@ -24,6 +27,24 @@ class ApiTest extends SystemTestCase
public static $fixture = null; // initialized below class definition
/**
+ * @var API
+ */
+ private $api;
+
+ /**
+ * @var Model
+ */
+ private $model;
+
+ public function setUp() : void
+ {
+ parent::setUp(); // TODO: Change the autogenerated stub
+
+ $this->api = API::getInstance();
+ $this->model = new Model();
+ }
+
+ /**
* @dataProvider getApiForTesting
*/
public function testApi($api, $params = array())
@@ -62,6 +83,81 @@ class ApiTest extends SystemTestCase
return $apiToTest;
}
+ public function test_createAppSpecificTokenAuth()
+ {
+ $this->model->deleteAllTokensForUser('login1');
+ $token = $this->api->createAppSpecificTokenAuth('login1', md5('password'), 'test');
+ $this->assertMd5($token);
+
+ $user = $this->model->getUserByTokenAuth($token);
+ $this->assertSame('login1', $user['login']);
+ }
+
+ public function test_createAppSpecificTokenAuth_canLoginByEmail()
+ {
+ $this->model->deleteAllTokensForUser('login1');
+ $token = $this->api->createAppSpecificTokenAuth('login1@example.com', md5('password'), 'test');
+ $this->assertMd5($token);
+
+ $user = $this->model->getUserByTokenAuth($token);
+ $this->assertSame('login1', $user['login']);
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage is expecting a MD5-hashed password
+ */
+ public function test_createAppSpecificTokenAuth_notValidPasswordFormat()
+ {
+ $this->api->createAppSpecificTokenAuth('login1', 'foobar', 'test');
+ }
+
+ public function test_createAppSpecificTokenAuth_generatesRandomTokenWhenPasswordNotValid()
+ {
+ $this->model->deleteAllTokensForUser('login1');
+ $token = $this->api->createAppSpecificTokenAuth('login1', md5('foooooo'), 'test');
+ $this->assertMd5($token);
+
+ $user = $this->model->getUserByTokenAuth($token);
+ $this->assertEmpty($user);
+ }
+
+ public function test_createAppSpecificTokenAuth_withExpireDate()
+ {
+ $this->model->deleteAllTokensForUser('login1');
+ $token = $this->api->createAppSpecificTokenAuth('login1', md5('password'), 'test', '2026-01-02 03:04:05');
+ $this->assertMd5($token);
+
+ $tokens = $this->model->getAllNonSystemTokensForLogin('login1');
+ $this->assertEquals($this->model->hashTokenAuth($token), $tokens[0]['password']);
+ $this->assertEquals('login1', $tokens[0]['login']);
+ $this->assertEquals('test', $tokens[0]['description']);
+ $this->assertEquals('2026-01-02 03:04:05', $tokens[0]['date_expired']);
+ }
+
+ public function test_createAppSpecificTokenAuth_withExpireHours()
+ {
+ $expireInHours = 48;
+ $this->model->deleteAllTokensForUser('login1');
+ $token = $this->api->createAppSpecificTokenAuth('login1', md5('password'), 'test', null, $expireInHours);
+ $this->assertMd5($token);
+
+ $tokens = $this->model->getAllNonSystemTokensForLogin('login1');
+ $this->assertEquals($this->model->hashTokenAuth($token), $tokens[0]['password']);
+ $this->assertEquals('login1', $tokens[0]['login']);
+ $this->assertNotEmpty($tokens[0]['date_expired']);
+
+ $dateExpired = Date::factory($tokens[0]['date_expired']);
+ $dateExpired->isLater(Date::now()->addHour($expireInHours - 1 ));
+ $dateExpired->isEarlier(Date::now()->addHour($expireInHours + 1));
+ }
+
+ private function assertMd5($string)
+ {
+ $this->assertSame(32, strlen($string));
+ $this->assertTrue(ctype_xdigit($string));
+ }
+
public static function getOutputPrefix()
{
return '';
diff --git a/plugins/UsersManager/tests/UI/UserSettings_spec.js b/plugins/UsersManager/tests/UI/UserSettings_spec.js
index 190673c6ca..1f4dc719b0 100644
--- a/plugins/UsersManager/tests/UI/UserSettings_spec.js
+++ b/plugins/UsersManager/tests/UI/UserSettings_spec.js
@@ -11,7 +11,8 @@ describe("UserSettings", function () {
this.timeout(0);
this.fixture = "Piwik\\Plugins\\UsersManager\\tests\\Fixtures\\ManyUsers";
- var url = "?module=UsersManager&action=userSettings";
+ var userSettingsUrl = "?module=UsersManager&action=userSettings";
+ var userSecurityUrl = "?module=UsersManager&action=userSecurity";
before(async function() {
await page.webpage.setViewport({
@@ -20,8 +21,35 @@ describe("UserSettings", function () {
});
});
+ it('should show user security page', async function () {
+ await page.goto(userSecurityUrl);
+ expect(await page.screenshotSelector('.admin')).to.matchImage('load_security');
+ });
+
+ it('should ask for password when trying to add token', async function () {
+ await page.click('.addNewToken');
+ await page.waitForNetworkIdle();
+ await page.waitForSelector('.loginSection');
+ expect(await page.screenshotSelector('.loginSection')).to.matchImage('add_token_check_password');
+ });
+
+ it('should accept correct password', async function () {
+ await page.type('#login_form_password', 'superUserPass');
+ await page.click('#login_form_submit');
+ await page.waitForNetworkIdle();
+ await page.waitForSelector('.addTokenForm');
+ expect(await page.screenshotSelector('.admin')).to.matchImage('add_token');
+ });
+
+ it('should create new token', async function () {
+ await page.type('.addTokenForm input[id=description]', 'test description');
+ await page.click('.addTokenForm .btn');
+ await page.waitForNetworkIdle();
+ expect(await page.screenshotSelector('.admin')).to.matchImage('add_token_success');
+ });
+
it('should show user settings page', async function () {
- await page.goto(url);
+ await page.goto(userSettingsUrl);
expect(await page.screenshotSelector('.admin')).to.matchImage('load');
});
@@ -34,7 +62,7 @@ describe("UserSettings", function () {
it('should not prompt user to subscribe to newsletter again', async function () {
// Assumes previous test has clicked on the signup button - so we shouldn't see it this time
- await page.goto(url);
+ await page.goto(userSettingsUrl);
expect(await page.screenshotSelector('.admin')).to.matchImage('already_signed_up');
});
diff --git a/plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_add_token.png b/plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_add_token.png
new file mode 100644
index 0000000000..8f8b683dac
--- /dev/null
+++ b/plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_add_token.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e0a217eaf00dd0e60598067698cdcff92c794433438122b07284c89093d84ae2
+size 30211
diff --git a/plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_add_token_check_password.png b/plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_add_token_check_password.png
new file mode 100644
index 0000000000..d511b3665a
--- /dev/null
+++ b/plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_add_token_check_password.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bf4e7b2dd1d68df9db0826eb9051f796bf3c25426659a9e84792ae4879835f17
+size 13422
diff --git a/plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_add_token_success.png b/plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_add_token_success.png
new file mode 100644
index 0000000000..45a8da92a3
--- /dev/null
+++ b/plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_add_token_success.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c2117369cad613d3fccc5848a29ec09212a49a355266db0f57058cd7af639544
+size 19921
diff --git a/plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_load_security.png b/plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_load_security.png
new file mode 100644
index 0000000000..f0312d5958
--- /dev/null
+++ b/plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_load_security.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:13053d0f3133b73a4f4c9cff719d1c6cd735dfffda162e7e775acb1db6b81162
+size 115909
diff --git a/plugins/Widgetize/templates/index.twig b/plugins/Widgetize/templates/index.twig
index 3c4d1529e0..2ab204877c 100644
--- a/plugins/Widgetize/templates/index.twig
+++ b/plugins/Widgetize/templates/index.twig
@@ -21,8 +21,8 @@
If you want your widgets to be viewable by everybody, you first have to set the 'view' permissions
to the anonymous user in the <a href='index.php?module=UsersManager' rel='noreferrer noopener' target='_blank'>Users Management section</a>.
<br/>Alternatively, if you are publishing widgets on a password protected or private page,
- you don't necessarily have to allow 'anonymous' to view your reports. In this case, you can add the secret token_auth parameter (found in the
- <a href='{{ linkTo({'module':'API','action':'listAllAPI'}) }}' rel='noreferrer noopener' target='_blank'>API page</a>) in the widget URL.
+ you don't necessarily have to allow 'anonymous' to view your reports. In this case, you can add the secret <code>token_auth</code> parameter in the widget URL.
+ You can manage your auth tokens on your <a href='{{ linkTo({'module':'UsersManager','action':'userSecurity'}) }}' rel='noreferrer noopener' target='_blank'>Security page</a>.
</p>
</div>
<div piwik-content-block content-title="Widgetize dashboards">
diff --git a/tests/PHPUnit/Fixtures/OmniFixture.php b/tests/PHPUnit/Fixtures/OmniFixture.php
index 626580574c..10730fc674 100644
--- a/tests/PHPUnit/Fixtures/OmniFixture.php
+++ b/tests/PHPUnit/Fixtures/OmniFixture.php
@@ -12,6 +12,7 @@ use Piwik\Common;
use Piwik\Date;
use Piwik\Db;
use Piwik\Option;
+use Piwik\Plugins\UsersManager\Model;
use ReflectionClass;
use Piwik\Plugins\SitesManager\API as SitesManagerAPI;
use Piwik\Tests\Framework\Fixture;
@@ -23,9 +24,11 @@ use Piwik\Tests\Framework\Fixture;
class OmniFixture extends Fixture
{
const DEFAULT_SEGMENT = "browserCode==FF";
+ const OMNIFIXTURE_SUPERUSER_TOKEN = '9ad1de7f8b329ab919d854c556f860c1';
public $month = '2012-01';
public $idSite = 'all';
+
public $dateTime = '2012-02-01';
/**
@@ -122,6 +125,19 @@ class OmniFixture extends Fixture
return $result;
}
+ public static function getTokenAuth()
+ {
+ $model = new \Piwik\Plugins\UsersManager\Model();
+ $user = $model->getUser(self::ADMIN_USER_LOGIN);
+
+ if (!empty($user)) {
+ if ($model->getUserByTokenAuth(self::OMNIFIXTURE_SUPERUSER_TOKEN)) {
+ return self::OMNIFIXTURE_SUPERUSER_TOKEN;
+ }
+ }
+ return parent::getTokenAuth();
+ }
+
public function setUp(): void
{
$firstFixture = array_shift($this->fixtures);
@@ -135,7 +151,14 @@ class OmniFixture extends Fixture
$this->setUpFixture($fixture);
}
- Db::query(sprintf('UPDATE %s SET token_auth = "9ad1de7f8b329ab919d854c556f860c1" WHERE login = "superUserLogin"', Common::prefixTable('user')));
+ $model = new Model();
+
+ if (!$model->getUserByTokenAuth(self::OMNIFIXTURE_SUPERUSER_TOKEN)) {
+ $model->addTokenAuth(self::ADMIN_USER_LOGIN, self::OMNIFIXTURE_SUPERUSER_TOKEN, 'omnifixture token', Date::now()->getDatetime());
+ }
+ if (!$model->getUserByTokenAuth(self::ADMIN_USER_TOKEN)) {
+ $model->addTokenAuth(self::ADMIN_USER_LOGIN, self::ADMIN_USER_TOKEN, 'omnifixture token default', Date::now()->getDatetime());
+ }
Option::set("Tests.forcedNowTimestamp", $this->now->getTimestamp());
}
diff --git a/tests/PHPUnit/Fixtures/SqlDump.php b/tests/PHPUnit/Fixtures/SqlDump.php
index 94cddf0bc5..abfcb31338 100644
--- a/tests/PHPUnit/Fixtures/SqlDump.php
+++ b/tests/PHPUnit/Fixtures/SqlDump.php
@@ -63,7 +63,9 @@ class SqlDump extends Fixture
$host = Config::getInstance()->database['host'];
Config::getInstance()->database['tables_prefix'] = $this->tablesPrefix;
- $cmd = "mysql -h \"$host\" -u \"$user\" \"--password=$password\" {$this->dbName} < \"" . $deflatedDumpPath . "\" 2>&1";
+ $defaultsFile = $this->makeMysqlDefaultsFile($user, $password);
+
+ $cmd = "mysql --defaults-extra-file=\"$defaultsFile\" -h \"$host\" {$this->dbName} < \"" . $deflatedDumpPath . "\" 2>&1";
exec($cmd, $output, $return);
if ($return !== 0) {
throw new Exception("Failed to load sql dump: " . implode("\n", $output));
@@ -105,4 +107,16 @@ class SqlDump extends Fixture
fclose($outfile);
return $bytesRead;
}
+
+ private function makeMysqlDefaultsFile($user, $password)
+ {
+ $contents = "[client]
+user=$user
+password=$password\n";
+
+ $path = PIWIK_INCLUDE_PATH . '/mysqldefaults.conf';
+ file_put_contents($path, $contents);
+
+ return $path;
+ }
} \ No newline at end of file
diff --git a/tests/PHPUnit/Fixtures/UITestFixture.php b/tests/PHPUnit/Fixtures/UITestFixture.php
index dd4dadea0a..1d0cc5ff5c 100644
--- a/tests/PHPUnit/Fixtures/UITestFixture.php
+++ b/tests/PHPUnit/Fixtures/UITestFixture.php
@@ -35,6 +35,7 @@ use Piwik\Plugins\SegmentEditor\API as APISegmentEditor;
use Piwik\Plugins\UserCountry\LocationProvider;
use Piwik\Plugins\UsersManager\API as UsersManagerAPI;
use Piwik\Plugins\SitesManager\API as SitesManagerAPI;
+use Piwik\Plugins\UsersManager\Model;
use Piwik\Plugins\UsersManager\UserUpdater;
use Piwik\Plugins\VisitsSummary\API as VisitsSummaryAPI;
use Piwik\ReportRenderer;
@@ -85,6 +86,8 @@ class UITestFixture extends SqlDump
LocationProvider::setCurrentProvider(GeoIp2\Php::ID);
IPAnonymizer::deactivate();
+ self::createSuperUser(false);
+
$this->addOverlayVisits();
$this->addNewSitesForSiteSelector();
@@ -137,6 +140,8 @@ class UITestFixture extends SqlDump
parent::performSetUp($setupEnvironmentOnly);
+ self::createSuperUser(false);
+
$this->createSegments();
$this->setupDashboards();
@@ -299,7 +304,7 @@ class UITestFixture extends SqlDump
$oldGet = $_GET;
$_GET['idSite'] = 1;
- $_GET['token_auth'] = Piwik::getCurrentUserTokenAuth();
+ $_GET['token_auth'] = \Piwik\Piwik::getCurrentUserTokenAuth();
// collect widgets & sort them so widget order is not important
$allWidgets = Request::processRequest('API.getWidgetMetadata', array(
diff --git a/tests/PHPUnit/Framework/Fixture.php b/tests/PHPUnit/Framework/Fixture.php
index 307daa838c..5fb6dae988 100644
--- a/tests/PHPUnit/Framework/Fixture.php
+++ b/tests/PHPUnit/Framework/Fixture.php
@@ -81,6 +81,7 @@ class Fixture extends \PHPUnit\Framework\Assert
const ADMIN_USER_LOGIN = 'superUserLogin';
const ADMIN_USER_PASSWORD = 'superUserPass';
+ const ADMIN_USER_TOKEN = 'c4ca4238a0b923820dcc509a6f75849b';
const PERSIST_FIXTURE_DATA_ENV = 'PERSIST_FIXTURE_DATA';
@@ -716,7 +717,9 @@ class Fixture extends \PHPUnit\Framework\Assert
$model = new \Piwik\Plugins\UsersManager\Model();
$user = $model->getUser(self::ADMIN_USER_LOGIN);
- return $user['token_auth'];
+ if (!empty($user)) {
+ return self::ADMIN_USER_TOKEN;
+ }
}
public static function createSuperUser($removeExisting = true)
@@ -725,7 +728,6 @@ class Fixture extends \PHPUnit\Framework\Assert
$login = self::ADMIN_USER_LOGIN;
$password = $passwordHelper->hash(UsersManager::getPasswordHash(self::ADMIN_USER_PASSWORD));
- $token = APIUsersManager::getInstance()->createTokenAuth($login);
$model = new \Piwik\Plugins\UsersManager\Model();
$user = $model->getUser($login);
@@ -734,19 +736,26 @@ class Fixture extends \PHPUnit\Framework\Assert
$model->deleteUserOnly($login);
}
- if (!empty($user) && !$removeExisting) {
- $token = $user['token_auth'];
- }
if (empty($user) || $removeExisting) {
- $model->addUser($login, $password, 'hello@example.org', $login, $token, Date::now()->getDatetime());
+ $model->addUser($login, $password, 'hello@example.org', $login, Date::now()->getDatetime());
} else {
- $model->updateUser($login, $password, 'hello@example.org', $login, $token);
+ $model->updateUser($login, $password, 'hello@example.org', $login);
+ }
+ try {
+ if (!$model->getUserByTokenAuth(self::ADMIN_USER_TOKEN)) {
+ $model->addTokenAuth($login,self::ADMIN_USER_TOKEN, 'Admin user token', Date::now()->getDatetime());
+ }
+ } catch (Exception $e) {
+ // duplicate entry errors are expected
+ if (strpos($e->getMessage(), 'Duplicate entry') === false) {
+ throw $e;
+ }
}
$setSuperUser = empty($user) || !empty($user['superuser_access']);
$model->setSuperUserAccess($login, $setSuperUser);
- return $model->getUserByTokenAuth($token);
+ return $model->getUser($login);
}
/**
diff --git a/tests/PHPUnit/Integration/FrontControllerTest.php b/tests/PHPUnit/Integration/FrontControllerTest.php
index ca666618d2..dd9afe0e79 100644
--- a/tests/PHPUnit/Integration/FrontControllerTest.php
+++ b/tests/PHPUnit/Integration/FrontControllerTest.php
@@ -48,9 +48,8 @@ FORMAT;
$this->assertEquals('error', $response['result']);
$expectedFormat = <<<FORMAT
-test message on {includePath}/tests/resources/trigger-fatal-exception.php(23) #0 [internal function]: {closure}('CoreHome', 'index', Array) #1 {includePath}/core/EventDispatcher.php(141): call_user_func_array(Object(Closure), Array) #2 {includePath}/core/Piwik.php(756): Piwik\EventDispatcher-&gt;postEvent('Request.dispatc...', Array, false, Array) #3 {includePath}/core/FrontController.php(570): Piwik\Piwik::postEvent('Request.dispatc...', Array) #4 {includePath}/core/FrontController.php(165): Piwik\FrontController-&gt;doDispatch('CoreHome', 'index', Array) #5 {includePath}/tests/resources/trigger-fatal-exception.php(31): Piwik\FrontController-&gt;dispatch('CoreHome', 'index') #6 {main}
+test message on {includePath}/tests/resources/trigger-fatal-exception.php(23) #0 [internal function]: {closure}('CoreHome', 'index', Array) #1 {includePath}/core/EventDispatcher.php(141): call_user_func_array(Object(Closure), Array) #2 {includePath}/core/Piwik.php(802): Piwik\EventDispatcher-&gt;postEvent('Request.dispatc...', Array, false, Array) #3 {includePath}/core/FrontController.php(570): Piwik\Piwik::postEvent('Request.dispatc...', Array) #4 {includePath}/core/FrontController.php(165): Piwik\FrontController-&gt;doDispatch('CoreHome', 'index', Array) #5 {includePath}/tests/resources/trigger-fatal-exception.php(31): Piwik\FrontController-&gt;dispatch('CoreHome', 'index') #6 {main}
FORMAT;
-
$this->assertStringMatchesFormat($expectedFormat, $response['message']);
}
@@ -64,7 +63,7 @@ FORMAT;
Access::getInstance()->setSuperUserAccess(false);
$sessionFingerprint = new SessionFingerprint();
- $sessionFingerprint->initialize('superUserLogin');
+ $sessionFingerprint->initialize('superUserLogin', Fixture::getTokenAuth());
FrontController::getInstance()->init();
diff --git a/tests/PHPUnit/Integration/Session/SessionAuthTest.php b/tests/PHPUnit/Integration/Session/SessionAuthTest.php
index 4f4c7eccb3..9225f6d425 100644
--- a/tests/PHPUnit/Integration/Session/SessionAuthTest.php
+++ b/tests/PHPUnit/Integration/Session/SessionAuthTest.php
@@ -135,7 +135,7 @@ class SessionAuthTest extends IntegrationTestCase
private function initializeSession($userLogin, $isRemembered = false)
{
$sessionFingerprint = new SessionFingerprint();
- $sessionFingerprint->initialize($userLogin, $isRemembered);
+ $sessionFingerprint->initialize($userLogin, Fixture::getTokenAuth(), $isRemembered);
}
protected static function configureFixture($fixture)
diff --git a/tests/PHPUnit/Integration/Tracker/RequestTest.php b/tests/PHPUnit/Integration/Tracker/RequestTest.php
index 2de6b0e92f..2323420e3d 100644
--- a/tests/PHPUnit/Integration/Tracker/RequestTest.php
+++ b/tests/PHPUnit/Integration/Tracker/RequestTest.php
@@ -395,11 +395,12 @@ class RequestTest extends IntegrationTestCase
$login = 'myadmin';
$passwordHash = UsersManager::getPasswordHash('password');
- $token = API::getInstance()->createTokenAuth($login);
-
$user = new Model();
- $user->addUser($login, $passwordHash, 'admin@piwik', 'alias', $token, '2014-01-01 00:00:00');
+ $token = $user->generateRandomTokenAuth();
+
+ $user->addUser($login, $passwordHash, 'admin@piwik', 'alias', '2014-01-01 00:00:00');
$user->addUserAccess($login, 'admin', array($idSite));
+ $user->addTokenAuth($login, $token, 'createAdminUserForSite', '2014-01-01 00:00:00');
return $token;
}
diff --git a/tests/PHPUnit/System/AllWebsitesTest.php b/tests/PHPUnit/System/AllWebsitesTest.php
index eaa6f08e16..4c936c01d8 100644
--- a/tests/PHPUnit/System/AllWebsitesTest.php
+++ b/tests/PHPUnit/System/AllWebsitesTest.php
@@ -35,7 +35,7 @@ class AllWebsitesTest extends SystemTestCase
UsersManagerAPI::getInstance()->addUser('limitedUser', 'smartypants', 'user@limited.com');
UsersManagerAPI::getInstance()->setUserAccess('limitedUser', 'view', array(2, 3));
$userModel = new UsersManagerModel();
- $userModel->updateUserTokenAuth('limitedUser', self::$userTokenAuth);
+ $userModel->addTokenAuth('limitedUser', self::$userTokenAuth, 'desc', '2020-01-02 03:04:05');
}
}
diff --git a/tests/PHPUnit/Unit/CommonTest.php b/tests/PHPUnit/Unit/CommonTest.php
index a09c68aa73..7a3975420d 100644
--- a/tests/PHPUnit/Unit/CommonTest.php
+++ b/tests/PHPUnit/Unit/CommonTest.php
@@ -24,6 +24,7 @@ use Piwik\Tests\Framework\Mock\FakeLogger;
*/
class CommonTest extends TestCase
{
+
public function test_getProcessId()
{
$this->assertEquals(getmypid(), Common::getProcessId());
diff --git a/tests/PHPUnit/Unit/Session/SessionFingerprintTest.php b/tests/PHPUnit/Unit/Session/SessionFingerprintTest.php
index 62c50bd062..3bc43a0a70 100644
--- a/tests/PHPUnit/Unit/Session/SessionFingerprintTest.php
+++ b/tests/PHPUnit/Unit/Session/SessionFingerprintTest.php
@@ -12,6 +12,7 @@ namespace Piwik\Tests\Unit\Session;
use Piwik\Date;
use Piwik\Session\SessionFingerprint;
+use Piwik\Tests\Framework\Fixture;
class SessionFingerprintTest extends \PHPUnit\Framework\TestCase
{
@@ -64,9 +65,10 @@ class SessionFingerprintTest extends \PHPUnit\Framework\TestCase
public function test_initialize_SetsSessionVarsToCurrentRequest()
{
- $this->testInstance->initialize('testuser', true, self::TEST_TIME_VALUE);
+ $this->testInstance->initialize('testuser', Fixture::ADMIN_USER_TOKEN, true, self::TEST_TIME_VALUE);
$this->assertEquals('testuser', $_SESSION[SessionFingerprint::USER_NAME_SESSION_VAR_NAME]);
+ $this->assertEquals(Fixture::ADMIN_USER_TOKEN, $_SESSION[SessionFingerprint::SESSION_INFO_TEMP_TOKEN_AUTH]);
$this->assertEquals(
['ts' => self::TEST_TIME_VALUE, 'remembered' => true, 'expiration' => self::TEST_TIME_VALUE + 3600],
$_SESSION[SessionFingerprint::SESSION_INFO_SESSION_VAR_NAME]
@@ -75,7 +77,7 @@ class SessionFingerprintTest extends \PHPUnit\Framework\TestCase
public function test_initialize_hasVerifiedTwoFactor()
{
- $this->testInstance->initialize('testuser', self::TEST_TIME_VALUE);
+ $this->testInstance->initialize('testuser', Fixture::ADMIN_USER_TOKEN, self::TEST_TIME_VALUE);
// after logging in, the user has by default not verified two factor, important
$this->assertFalse($this->testInstance->hasVerifiedTwoFactor());
@@ -87,7 +89,7 @@ class SessionFingerprintTest extends \PHPUnit\Framework\TestCase
public function test_updateSessionExpireTime_SetsANewExpirationTime()
{
- $this->testInstance->initialize('testuser', false, self::TEST_TIME_VALUE);
+ $this->testInstance->initialize('testuser', Fixture::ADMIN_USER_TOKEN, false, self::TEST_TIME_VALUE);
Date::$now = self::TEST_TIME_VALUE + 100;
diff --git a/tests/UI/expected-screenshots/Menus_admin_changed.png b/tests/UI/expected-screenshots/Menus_admin_changed.png
index 5878a7921f..3c5fed55d7 100644
--- a/tests/UI/expected-screenshots/Menus_admin_changed.png
+++ b/tests/UI/expected-screenshots/Menus_admin_changed.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:b7d4b7614c4f3a40b39951751a816bed9dcb047ab9ac54116a5c22ae6f0394bf
-size 58130
+oid sha256:59b7f618ddfa675ac6f7015e3a9e80126e174d0761ad874dc89725f36c943a22
+size 59289
diff --git a/tests/UI/expected-screenshots/Menus_admin_loaded.png b/tests/UI/expected-screenshots/Menus_admin_loaded.png
index ffe64f4708..6ef69198b9 100644
--- a/tests/UI/expected-screenshots/Menus_admin_loaded.png
+++ b/tests/UI/expected-screenshots/Menus_admin_loaded.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:7bc3958996fdec4854c6d6f744fdbcd68ec03be45463c12a949591632173048f
-size 58154
+oid sha256:d7c9df46e7a2ac1a4792ab4f12ba06ec42dc8e1d67f97beeee7f63f38ca506f7
+size 59317
diff --git a/tests/UI/expected-screenshots/OptOutForm_opted-out2.png b/tests/UI/expected-screenshots/OptOutForm_opted-out2.png
new file mode 100644
index 0000000000..152f97f608
--- /dev/null
+++ b/tests/UI/expected-screenshots/OptOutForm_opted-out2.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:375f8b477c06ee4477d7976f347dae645ea04a97c0acd21b9f154bd61edfc71b
+size 23291
diff --git a/tests/UI/expected-screenshots/UIIntegrationTest_api_listing.png b/tests/UI/expected-screenshots/UIIntegrationTest_api_listing.png
index 95c558ba9c..6bc95d9e0b 100644
--- a/tests/UI/expected-screenshots/UIIntegrationTest_api_listing.png
+++ b/tests/UI/expected-screenshots/UIIntegrationTest_api_listing.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:7260e83828e9510616e2786fad99724630a22013444115be8f8fa28c392d362c
-size 4944235
+oid sha256:6d9c9e496c7bc56ab968df2942dc566ffcb7c2b93dd2ef6b89acb81c0860c511
+size 4923271
diff --git a/tests/UI/expected-screenshots/UIIntegrationTest_widgets_listing.png b/tests/UI/expected-screenshots/UIIntegrationTest_widgets_listing.png
index 3da6cd00d5..21f91a9316 100644
--- a/tests/UI/expected-screenshots/UIIntegrationTest_widgets_listing.png
+++ b/tests/UI/expected-screenshots/UIIntegrationTest_widgets_listing.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5fbd69449c1d5c892d380fc4dd7e1c4f72c5b32ab6a8f73dde863ae0fce0a2a6
-size 187953
+oid sha256:1e5aba4ff1b36aeff4bcfeb2fca49525a174812e9d58a9590024a7bb41462408
+size 190316
diff --git a/tests/UI/specs/BarGraph_spec.js b/tests/UI/specs/BarGraph_spec.js
index 7b1e8ff54c..f9888a8225 100644
--- a/tests/UI/specs/BarGraph_spec.js
+++ b/tests/UI/specs/BarGraph_spec.js
@@ -8,7 +8,7 @@
*/
describe("BarGraph", function () {
- var tokenAuth = "9ad1de7f8b329ab919d854c556f860c1", // md5('superUserLogin' . md5('superUserPass'))
+ var tokenAuth = "c4ca4238a0b923820dcc509a6f75849b", // md5('superUserLogin' . md5('superUserPass'))
url = "?module=Widgetize&action=iframe&moduleToWidgetize=Referrers&idSite=1&period=year&date=2012-08-09&"
+ "actionToWidgetize=getKeywords&viewDataTable=graphVerticalBar&isFooterExpandedInDashboard=1&"
+ "token_auth=" + tokenAuth;
diff --git a/tests/UI/specs/Comparison_spec.js b/tests/UI/specs/Comparison_spec.js
index 220ed48a7a..6bb4d732ee 100644
--- a/tests/UI/specs/Comparison_spec.js
+++ b/tests/UI/specs/Comparison_spec.js
@@ -11,7 +11,7 @@ describe("Comparison", function () {
const generalParams = 'idSite=1&period=range&date=2012-01-12,2012-01-17',
urlBase = 'module=CoreHome&action=index&' + generalParams,
dashboardUrl = "?" + urlBase + "#?" + generalParams + "&category=Dashboard_Dashboard&subcategory=5",
- tokenAuth = "9ad1de7f8b329ab919d854c556f860c1", // md5('superUserLogin' . md5('superUserPass'))
+ tokenAuth = "c4ca4238a0b923820dcc509a6f75849b", // md5('superUserLogin' . md5('superUserPass'))
comparePeriod = "&compareDates[]=2012-01-01,2012-01-31&comparePeriods[]=range",
compareSegment = "&compareSegments[]=continentCode%3D%3Deur",
compareParams = comparePeriod + compareSegment,
diff --git a/tests/resources/install-matomo.php b/tests/resources/install-matomo.php
index d43cc699f6..741471c6bc 100644
--- a/tests/resources/install-matomo.php
+++ b/tests/resources/install-matomo.php
@@ -86,24 +86,20 @@ function createSuperUser() {
$login = 'superUserLogin';
$password = $passwordHelper->hash(UsersManager::getPasswordHash('superUserPass'));
- $token = UsersManagerAPI::getInstance()->createTokenAuth($login);
$model = new \Piwik\Plugins\UsersManager\Model();
$user = $model->getUser($login);
- if (!empty($user)) {
- $token = $user['token_auth'];
- }
if (empty($user)) {
- $model->addUser($login, $password, 'hello@example.org', $login, $token, Date::now()->getDatetime());
+ $model->addUser($login, $password, 'hello@example.org', $login, Date::now()->getDatetime());
} else {
- $model->updateUser($login, $password, 'hello@example.org', $login, $token);
+ $model->updateUser($login, $password, 'hello@example.org', $login);
}
$setSuperUser = empty($user) || !empty($user['superuser_access']);
$model->setSuperUserAccess($login, $setSuperUser);
- return $model->getUserByTokenAuth($token);
+ return $model->getUser($login);
}
function createWebsite($dateTime)