From f0c246cb3a4db3021da7552f6779d56613799414 Mon Sep 17 00:00:00 2001 From: Thomas Steur Date: Wed, 18 Mar 2020 16:04:12 +1300 Subject: [PATCH] App specific token_auths (#15410) * some initial work * add security page * backing up some code * more functionality * adjust more UI parts * adjust more code * more tweaks * add todo note * few tweaks * make sure date is in right format * fix not existing column * few fixes * available hashes * use different hash algo so tests run on php 5 * fix name of aglorithm * trying to fix some tests * another try to fix some tests * more fixes * more fixes * few fixes * update template * fix some tests * fix test * fixing some tests * various test fixes * more fixes * few more tests * more tests * various tweaks * add translations * add some ui tests * fix selector * tweaks * trying to fix some ui tests * fallback to regular authentication if needed * fix call authenticate on null * fix user settings * fix some tests * few fixes * fix more ui tests * update schema * Update plugins/CoreHome/angularjs/widget-loader/widgetloader.directive.js Co-Authored-By: Stefan Giehl * fix maps are not showing data * trying to fix some tests * set correct token * trying to fix tracking failure * minor tweaks and fixes * fix more tests * fix screenshot test * trigger event so brute force logic is executed * test no fallback to actual authentication * allow fallback * apply review feedback * fix some tests * fix tests * make sure location values from query params are limited properly before attempting a db insert * make sure plugin uninstall migration reloads plugins, make sure 4.0.0-b1 migration removes unique index that is no longer used, use defaults extra file in SqlDump to get test to run on travis * Fix UI tests. * update expected screenshot Co-authored-by: Stefan Giehl Co-authored-by: diosmosis --- .gitmodules | 9 - CHANGELOG.md | 6 +- core/Access.php | 27 +- core/CliMulti.php | 16 +- core/Db/Schema/Mysql.php | 26 +- core/Piwik.php | 46 ++++ core/Session/SessionAuth.php | 25 +- core/Session/SessionFingerprint.php | 18 +- core/Session/SessionInitializer.php | 2 +- core/Tracker/Request.php | 2 + core/Updater/Migration/Plugin/Uninstall.php | 4 + core/Updates/2.0.4-b5.php | 4 +- core/Updates/4.0.0-b1.php | 44 ++- lang/en.json | 2 + misc/cron/updatetoken.php | 5 +- misc/log-analytics | 2 +- plugins/API/Controller.php | 4 +- plugins/API/css/styles.css | 29 ++ plugins/API/lang/en.json | 2 +- plugins/API/templates/listAllAPI.twig | 11 +- .../tests/Integration/RequestsTest.php | 4 +- .../angularjs/common/services/piwik-api.js | 3 + .../common/services/piwik-api.spec.js | 2 +- .../widget-loader/widgetloader.directive.js | 4 +- plugins/Dashboard/tests/UI/Dashboard_spec.js | 2 +- .../tests/Integration/FeedbackTest.php | 1 - plugins/Login/Auth.php | 21 +- plugins/Login/Login.php | 2 +- plugins/Login/SessionInitializer.php | 2 - plugins/Login/tests/Integration/LoginTest.php | 26 +- .../Integration/SessionInitializerTest.php | 4 +- .../Login_bruteforcelog_noentries.png | 4 +- .../Login_bruteforcelog_withentries.png | 4 +- plugins/Morpheus/javascripts/ajaxHelper.js | 3 +- plugins/Overlay/javascripts/Overlay_Helper.js | 2 +- plugins/Overlay/templates/index.twig | 2 +- plugins/Overlay/templates/index_noframe.twig | 2 +- plugins/Provider | 1 - plugins/TwoFactorAuth/Controller.php | 2 +- plugins/TwoFactorAuth/TwoFactorAuth.php | 6 +- .../templates/setupFinished.twig | 2 +- .../tests/Fixtures/TwoFactorFixture.php | 2 +- .../tests/Integration/TwoFactorAuthTest.php | 36 ++- .../tests/UI/TwoFactorAuth_spec.js | 2 +- plugins/UserCountry/Columns/City.php | 2 +- plugins/UserCountry/Columns/Country.php | 2 +- plugins/UserCountry/Columns/Region.php | 2 +- .../javascripts/realtime-map.js | 2 +- .../UserCountryMap/javascripts/visitor-map.js | 2 +- plugins/UsersManager/API.php | 63 ++--- plugins/UsersManager/Controller.php | 223 +++++++++++++-- plugins/UsersManager/Menu.php | 1 + plugins/UsersManager/Model.php | 214 ++++++++++++-- plugins/UsersManager/Tasks.php | 6 + plugins/UsersManager/UsersManager.php | 32 +-- .../personal-settings.controller.js | 29 +- plugins/UsersManager/lang/en.json | 17 ++ .../stylesheets/usersManager.less | 7 + .../UsersManager/templates/addNewToken.twig | 37 +++ .../templates/addNewTokenSuccess.twig | 17 ++ .../UsersManager/templates/userSecurity.twig | 121 ++++++++ .../UsersManager/templates/userSettings.twig | 80 ++---- .../UsersManager/tests/Fixtures/ManyUsers.php | 7 +- .../tests/Integration/ModelTest.php | 260 ++++++++++++++++++ .../Integration/UserAccessFilterTest.php | 18 +- .../tests/Integration/UsersManagerTest.php | 11 - plugins/UsersManager/tests/System/ApiTest.php | 96 +++++++ .../tests/UI/UserSettings_spec.js | 34 ++- .../UserSettings_add_token.png | 3 + .../UserSettings_add_token_check_password.png | 3 + .../UserSettings_add_token_success.png | 3 + .../UserSettings_load_security.png | 3 + plugins/Widgetize/templates/index.twig | 4 +- tests/PHPUnit/Fixtures/OmniFixture.php | 25 +- tests/PHPUnit/Fixtures/SqlDump.php | 16 +- tests/PHPUnit/Fixtures/UITestFixture.php | 7 +- tests/PHPUnit/Framework/Fixture.php | 25 +- .../Integration/FrontControllerTest.php | 5 +- .../Integration/Session/SessionAuthTest.php | 2 +- .../Integration/Tracker/RequestTest.php | 7 +- tests/PHPUnit/System/AllWebsitesTest.php | 2 +- tests/PHPUnit/Unit/CommonTest.php | 1 + .../Unit/Session/SessionFingerprintTest.php | 8 +- .../Menus_admin_changed.png | 4 +- .../Menus_admin_loaded.png | 4 +- .../OptOutForm_opted-out2.png | 3 + .../UIIntegrationTest_api_listing.png | 4 +- .../UIIntegrationTest_widgets_listing.png | 4 +- tests/UI/specs/BarGraph_spec.js | 2 +- tests/UI/specs/Comparison_spec.js | 2 +- tests/resources/install-matomo.php | 10 +- 91 files changed, 1464 insertions(+), 389 deletions(-) create mode 100644 plugins/API/css/styles.css delete mode 160000 plugins/Provider create mode 100644 plugins/UsersManager/templates/addNewToken.twig create mode 100644 plugins/UsersManager/templates/addNewTokenSuccess.twig create mode 100644 plugins/UsersManager/templates/userSecurity.twig create mode 100644 plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_add_token.png create mode 100644 plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_add_token_check_password.png create mode 100644 plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_add_token_success.png create mode 100644 plugins/UsersManager/tests/UI/expected-screenshots/UserSettings_load_security.png create mode 100644 tests/UI/expected-screenshots/OptOutForm_opted-out2.png diff --git a/.gitmodules b/.gitmodules index fefdf57df8f..71eed63ea23 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 a3741a87bea..a0efe87536d 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 d99551c219c..7e4bcd59d85 100644 --- a/core/Access.php +++ b/core/Access.php @@ -15,6 +15,7 @@ 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 @@ public function reloadAccess(Auth $auth = null) 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 863bea1b3b9..751c5fbd3ee 100644 --- a/core/CliMulti.php +++ b/core/CliMulti.php @@ -365,8 +365,7 @@ private function executeNotAsyncHttp($url, Output $output) } if ($this->runAsSuperUser) { - $tokenAuths = self::getSuperUserTokenAuths(); - $tokenAuth = reset($tokenAuths); + $tokenAuth = self::getSuperUserTokenAuth(); if (strpos($url, '?') === false) { $url .= '?'; @@ -433,18 +432,9 @@ private function requestUrls(array $piwikUrls) 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 ffe4aa5b655..01ea82492ad 100644 --- a/core/Db/Schema/Mysql.php +++ b/core/Db/Schema/Mysql.php @@ -16,6 +16,7 @@ use Piwik\Db; use Piwik\DbHelper; use Piwik\Option; +use Piwik\Plugins\UsersManager\Model; use Piwik\Version; /** @@ -45,12 +46,24 @@ public function getTablesCreateSql() 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 @@ public function createTables() 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 32520ffec40..d16ffca97e3 100644 --- a/core/Piwik.php +++ b/core/Piwik.php @@ -16,6 +16,7 @@ use Piwik\Period\Week; use Piwik\Period\Year; use Piwik\Plugins\UsersManager\API as APIUsersManager; +use Piwik\Plugins\UsersManager\Model; use Piwik\Translation\Translator; /** @@ -257,6 +258,51 @@ public static function checkUserHasSuperUserAccessOrIsTheUser($theUser) } } + /** + * 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. * diff --git a/core/Session/SessionAuth.php b/core/Session/SessionAuth.php index 001b3d2bb09..da896b991eb 100644 --- a/core/Session/SessionAuth.php +++ b/core/Session/SessionAuth.php @@ -11,8 +11,10 @@ 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 @@ public function getName() public function setTokenAuth($token_auth) { - // empty + $this->tokenAuth = $token_auth; } public function getLogin() @@ -113,7 +117,17 @@ public function authenticate() $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 @@ private function makeAuthFailure() 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 @@ protected function destroyCurrentSession(SessionFingerprint $sessionFingerprint) 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 df8a519e5e3..3399fa7a0c0 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 @@ public function getUserInfo() 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 @@ public function setTwoFactorAuthenticationVerified() $_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 @@ public function clear() 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 40218901671..11d753395d0 100644 --- a/core/Session/SessionInitializer.php +++ b/core/Session/SessionInitializer.php @@ -83,7 +83,7 @@ protected function processFailedSession() 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 7da7f122876..3b9bae24d3f 100644 --- a/core/Tracker/Request.php +++ b/core/Tracker/Request.php @@ -208,6 +208,8 @@ public static function authenticateSuperUserOrAdminOrWrite($tokenAuth, $idSite) // 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 77e95d2f24e..ed4b29a08f4 100644 --- a/core/Updater/Migration/Plugin/Uninstall.php +++ b/core/Updater/Migration/Plugin/Uninstall.php @@ -48,6 +48,10 @@ public function shouldIgnoreError($exception) 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 2282ca1dae9..a96d9e4cc35 100644 --- a/core/Updates/2.0.4-b5.php +++ b/core/Updates/2.0.4-b5.php @@ -36,7 +36,7 @@ public function __construct(MigrationFactory $factory) 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 @@ private static function migrateConfigSuperUserToDb() '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 c38b1a1eb5b..2b289f6a449 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 @@ public function __construct(MigrationFactory $factory) 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 c3c715ea232..cda996a2a26 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 b10a21d3471..f1c154705b2 100644 --- a/misc/cron/updatetoken.php +++ b/misc/cron/updatetoken.php @@ -56,10 +56,7 @@ function getPiwikDomain() $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 index b3973f3cd0c..4794df04cb9 160000 --- a/misc/log-analytics +++ b/misc/log-analytics @@ -1 +1 @@ -Subproject commit b3973f3cd0cb773eb9cf3bfff439cc43640c5f15 +Subproject commit 4794df04cb9187031a044e79b77351c4f8e8ef08 diff --git a/plugins/API/Controller.php b/plugins/API/Controller.php index 326b048926c..d46bdf158a6 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 00000000000..3b52ba995a0 --- /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 72dd837fae9..cc43b92313f 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 17da68de1d6..9c9c2ea70f5 100644 --- a/plugins/API/templates/listAllAPI.twig +++ b/plugins/API/templates/listAllAPI.twig @@ -19,13 +19,12 @@

- {{ 'API_UsingTokenAuth'|translate('','',"")|raw }}
-

&token_auth=

- {{ 'API_KeepTokenSecret'|translate('','')|raw }}
- {{ 'API_ChangeTokenHint'|translate('token_auth")|raw }} {{ 'CoreAdminHome_LearnMore'|translate }}
+
+ ', '')|raw }} + 'action': 'userSecurity', + }) }}#/#authtokens">You can manage your authentication tokens on your security page.

{{ list_api_methods_with_links|raw }} diff --git a/plugins/BulkTracking/tests/Integration/RequestsTest.php b/plugins/BulkTracking/tests/Integration/RequestsTest.php index 3e4e0fbc7e0..6b67b88ae2e 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 @@ public function test_authenticateRequests_shouldThrowAnException_IfTokenIsNotVal $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 e2195f7ff1d..e34ca415932 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 e33fa329e22..916c5b73e10 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 16a5a91105b..d3c35b11155 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 6ac640b7a89..b3914f3bc3b 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 072a12186ba..33c88e13364 100644 --- a/plugins/Feedback/tests/Integration/FeedbackTest.php +++ b/plugins/Feedback/tests/Integration/FeedbackTest.php @@ -39,7 +39,6 @@ public function setUp(): void '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 3d572abcf92..a196b48b156 100644 --- a/plugins/Login/Auth.php +++ b/plugins/Login/Auth.php @@ -10,6 +10,7 @@ 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 @@ private function authenticateWithPassword($login, $passwordHash) 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 @@ private function authenticateWithToken($token) $user = $this->userModel->getUserByTokenAuth($token); if (!empty($user['login'])) { + $this->userModel->setTokenAuthWasUsed($token, Date::now()->getDatetime()); return $this->authenticationSuccess($user); } @@ -98,13 +101,10 @@ private function authenticateWithToken($token) 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 @@ private function authenticateWithLoginAndToken($token, $login) 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 d60358c7096..9f6ed6f8200 100644 --- a/plugins/Login/Login.php +++ b/plugins/Login/Login.php @@ -41,7 +41,7 @@ public function registerEvents() // 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 0b31f62114d..b997358a4b6 100644 --- a/plugins/Login/SessionInitializer.php +++ b/plugins/Login/SessionInitializer.php @@ -182,8 +182,6 @@ protected function processFailedSession($rememberMe) 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 a0351b5ea1f..8a45a53096c 100644 --- a/plugins/Login/tests/Integration/LoginTest.php +++ b/plugins/Login/tests/Integration/LoginTest.php @@ -11,6 +11,7 @@ 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 @@ public function test_authenticate_successUserWithSuperUserAccessByTokenAuth() $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 @@ public function test_authenticate_successWithValidPassword() $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 @@ public function test_authenticate_prioritizesPasswordAuthentication() $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 @@ public function test_authenticate_withPasswordIsCaseInsensitiveForLogin() $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 @@ protected function _setUpUser() 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 c2215de7a9a..7be28c174c8 100644 --- a/plugins/Login/tests/Integration/SessionInitializerTest.php +++ b/plugins/Login/tests/Integration/SessionInitializerTest.php @@ -36,6 +36,7 @@ public function test_initSession_CreatesCookie_WhenAuthenticationIsSuccessful() $this->assertAuthCookieIsAbsent(); $sessionInitializer = new TestSessionInitializer(); + $this->assertEmpty($sessionInitializer->cookie); $sessionInitializer->initSession($this->makeMockAuth(AuthResult::SUCCESS), true); $this->assertAuthCookieIsCreated($sessionInitializer->cookie); @@ -69,8 +70,7 @@ private function assertAuthCookieIsAbsent() 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 02fe11e89e1..7c129e3f469 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 93e8f1d9aac..0233d765039 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 03d15be8911..38ec2f287eb 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 c7fe53e986e..6f2208d4943 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 c98d310787e..42294e2ae4d 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 78c18bf2816..c3f32be6b6f 100644 --- a/plugins/Overlay/templates/index_noframe.twig +++ b/plugins/Overlay/templates/index_noframe.twig @@ -8,7 +8,7 @@