diff options
-rw-r--r-- | core/Db/Schema/Mysql.php | 2 | ||||
-rw-r--r-- | core/Notification/Manager.php | 26 | ||||
-rw-r--r-- | core/Session.php | 19 | ||||
-rw-r--r-- | core/Session/SaveHandler/DbTable.php | 2 | ||||
-rw-r--r-- | core/Session/SessionAuth.php | 12 | ||||
-rw-r--r-- | core/Updates/4.5.0-b1.php | 43 | ||||
-rw-r--r-- | core/Version.php | 3 | ||||
-rw-r--r-- | plugins/Monolog/Handler/WebNotificationHandler.php | 8 | ||||
-rw-r--r-- | tests/PHPUnit/Framework/Fixture.php | 3 | ||||
-rw-r--r-- | tests/PHPUnit/Integration/Session/SaveHandler/DbTableTest.php | 37 | ||||
-rw-r--r-- | tests/PHPUnit/Unit/Notification/ManagerTest.php | 84 |
11 files changed, 226 insertions, 13 deletions
diff --git a/core/Db/Schema/Mysql.php b/core/Db/Schema/Mysql.php index c7afaa6d87..940f694e70 100644 --- a/core/Db/Schema/Mysql.php +++ b/core/Db/Schema/Mysql.php @@ -275,7 +275,7 @@ class Mysql implements SchemaInterface id VARCHAR( 191 ) NOT NULL, modified INTEGER, lifetime INTEGER, - data TEXT, + data MEDIUMTEXT, PRIMARY KEY ( id ) ) ENGINE=$engine DEFAULT CHARSET=$charset ", diff --git a/core/Notification/Manager.php b/core/Notification/Manager.php index a621f27bfd..6e1924e873 100644 --- a/core/Notification/Manager.php +++ b/core/Notification/Manager.php @@ -18,6 +18,7 @@ use Piwik\Session\SessionNamespace; */ class Manager { + const MAX_NOTIFICATIONS_IN_SESSION = 30; /** * @var SessionNamespace */ @@ -36,6 +37,8 @@ class Manager * element ID. It can only contain alphanumeric characters (underscores can * be used). * @param Notification $notification The notification to post. + * @return bool true if the notification was added, false if it was ignored because there were too many + * pending ones. * @api */ public static function notify($id, Notification $notification) @@ -43,7 +46,7 @@ class Manager self::checkId($id); self::removeOldestNotificationsIfThereAreTooMany(); - self::addNotification($id, $notification); + return self::addNotification($id, $notification); } /** @@ -103,7 +106,7 @@ class Manager throw new \Exception('Notification ID is empty.'); } - if (!preg_match('/^(\w)*$/', $id)) { + if (!preg_match('/^\w*$/', $id)) { throw new \Exception('Invalid Notification ID given. Only word characters (AlNum + underscore) allowed.'); } } @@ -112,9 +115,15 @@ class Manager { self::saveNotificationAcrossUiRequestsIfNeeded($id, $notification); + if (count(self::$notifications) >= self::MAX_NOTIFICATIONS_IN_SESSION) { + return false; + } + // we store all kinda notifications here so in case the session is not enabled or disabled later there is still // a chance it gets delivered to the UI during the same request. self::$notifications[$id] = $notification; + + return true; } private static function saveNotificationAcrossUiRequestsIfNeeded($id, Notification $notification) @@ -133,7 +142,7 @@ class Manager return; } - $maxNotificationsInSession = 30; + $maxNotificationsInSession = self::MAX_NOTIFICATIONS_IN_SESSION; $session = static::getSession(); @@ -200,4 +209,15 @@ class Manager return static::$session; } + + public static function cancelAllNotifications() + { + self::$notifications = []; + } + + // for tests + public static function getPendingInMemoryNotifications() + { + return self::$notifications; + } } diff --git a/core/Session.php b/core/Session.php index 4c09c3b6cb..641334676c 100644 --- a/core/Session.php +++ b/core/Session.php @@ -100,13 +100,7 @@ class Session extends Zend_Session @ini_set('session.serialize_handler', 'php_serialize'); } - $config = array( - 'name' => Common::prefixTable(DbTable::TABLE_NAME), - 'primary' => 'id', - 'modifiedColumn' => 'modified', - 'dataColumn' => 'data', - 'lifetimeColumn' => 'lifetime', - ); + $config = self::getDbTableConfig(); $saveHandler = new DbTable($config); if ($saveHandler) { @@ -226,4 +220,15 @@ class Session extends Zend_Session Common::sendHeader($headerStr); return $headerStr; } + + public static function getDbTableConfig() + { + return array( + 'name' => Common::prefixTable(DbTable::TABLE_NAME), + 'primary' => 'id', + 'modifiedColumn' => 'modified', + 'dataColumn' => 'data', + 'lifetimeColumn' => 'lifetime', + ); + } } diff --git a/core/Session/SaveHandler/DbTable.php b/core/Session/SaveHandler/DbTable.php index 46012cc216..52bbeeb37f 100644 --- a/core/Session/SaveHandler/DbTable.php +++ b/core/Session/SaveHandler/DbTable.php @@ -23,6 +23,8 @@ use Zend_Session_SaveHandler_Interface; */ class DbTable implements Zend_Session_SaveHandler_Interface { + public static $wasSessionToLargeToRead = false; + protected $config; protected $maxLifetime; diff --git a/core/Session/SessionAuth.php b/core/Session/SessionAuth.php index 19e7e24e19..bff91c113e 100644 --- a/core/Session/SessionAuth.php +++ b/core/Session/SessionAuth.php @@ -13,10 +13,12 @@ use Piwik\Auth; use Piwik\AuthResult; use Piwik\Common; use Piwik\Config; +use Piwik\Container\StaticContainer; use Piwik\Date; use Piwik\Plugins\UsersManager\Model; use Piwik\Plugins\UsersManager\Model as UsersModel; use Piwik\Session; +use Psr\Log\LoggerInterface; /** * Validates already authenticated sessions. @@ -94,6 +96,8 @@ class SessionAuth implements Auth $sessionFingerprint = new SessionFingerprint(); $userModel = $this->userModel; + $this->checkIfSessionFailedToRead(); + if ($this->isExpiredSession($sessionFingerprint)) { $sessionFingerprint->clear(); return $this->makeAuthFailure(); @@ -231,4 +235,12 @@ class SessionAuth implements Auth $isExpired = Date::now()->getTimestampUTC() > $expirationTime; return $isExpired; } + + private function checkIfSessionFailedToRead() + { + if (Session\SaveHandler\DbTable::$wasSessionToLargeToRead) { + StaticContainer::get(LoggerInterface::class)->warning( + "Too much data stored in the session so it could not be read properly. If you were logged out, this is why."); + } + } } diff --git a/core/Updates/4.5.0-b1.php b/core/Updates/4.5.0-b1.php new file mode 100644 index 0000000000..e79ebe7107 --- /dev/null +++ b/core/Updates/4.5.0-b1.php @@ -0,0 +1,43 @@ +<?php +/** + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + */ + +namespace Piwik\Updates; + +use Piwik\Updater; +use Piwik\Updates as PiwikUpdates; +use Piwik\Updater\Migration; +use Piwik\Updater\Migration\Factory as MigrationFactory; + +class Updates_4_5_0_b1 extends PiwikUpdates +{ + + /** + * @var MigrationFactory + */ + private $migration; + + public function __construct(MigrationFactory $factory) + { + $this->migration = $factory; + } + + public function getMigrations(Updater $updater) + { + $migration1 = $this->migration->db->changeColumnType('session', 'data', 'MEDIUMTEXT'); + + return [ + $migration1, + ]; + } + + public function doUpdate(Updater $updater) + { + $updater->executeMigrations(__FILE__, $this->getMigrations($updater)); + } +} diff --git a/core/Version.php b/core/Version.php index 8c0ebd1d8d..cd40ab0d9e 100644 --- a/core/Version.php +++ b/core/Version.php @@ -20,7 +20,8 @@ final class Version * The current Matomo version. * @var string */ - const VERSION = '4.4.1'; + const VERSION = '4.5.0-b1'; + const MAJOR_VERSION = 4; public function isStableVersion($version) diff --git a/plugins/Monolog/Handler/WebNotificationHandler.php b/plugins/Monolog/Handler/WebNotificationHandler.php index 0269282c9f..c365403590 100644 --- a/plugins/Monolog/Handler/WebNotificationHandler.php +++ b/plugins/Monolog/Handler/WebNotificationHandler.php @@ -20,6 +20,8 @@ use Zend_Session_Exception; */ class WebNotificationHandler extends AbstractProcessingHandler { + const MAX_NOTIFICATION_MESSAGE_LENGTH = 512; + public function isHandling(array $record) { if (!empty($record['context']['ignoreInScreenWriter'])) { @@ -46,7 +48,11 @@ class WebNotificationHandler extends AbstractProcessingHandler break; } - $message = $record['level_name'] . ': ' . htmlentities($record['message'], ENT_COMPAT | ENT_HTML401, 'UTF-8'); + $recordMessage = $record['message']; + $recordMessage = str_replace(PIWIK_INCLUDE_PATH, '', $recordMessage); + $recordMessage = substr($recordMessage, 0, self::MAX_NOTIFICATION_MESSAGE_LENGTH); + + $message = $record['level_name'] . ': ' . htmlentities($recordMessage, ENT_COMPAT | ENT_HTML401, 'UTF-8'); $message .= $this->getLiteDebuggingInfo(); $notification = new Notification($message); diff --git a/tests/PHPUnit/Framework/Fixture.php b/tests/PHPUnit/Framework/Fixture.php index bb5e33a9bd..8050865eff 100644 --- a/tests/PHPUnit/Framework/Fixture.php +++ b/tests/PHPUnit/Framework/Fixture.php @@ -43,6 +43,7 @@ use Piwik\Plugins\UserCountry\LocationProvider; use Piwik\Plugins\UsersManager\API as APIUsersManager; use Piwik\Plugins\UsersManager\UsersManager; use Piwik\ReportRenderer; +use Piwik\Session\SaveHandler\DbTable; use Piwik\SettingsPiwik; use Piwik\SettingsServer; use Piwik\Singleton; @@ -392,6 +393,7 @@ class Fixture extends \PHPUnit\Framework\Assert public static function clearInMemoryCaches($resetTranslations = true) { + DbTable::$wasSessionToLargeToRead = false; Date::$now = null; FrontController::$requestId = null; Cache::$cache = null; @@ -411,6 +413,7 @@ class Fixture extends \PHPUnit\Framework\Assert \Piwik\Plugins\ScheduledReports\API::$cache = array(); Singleton::clearAll(); PluginsArchiver::$archivers = array(); + \Piwik\Notification\Manager::cancelAllNotifications(); Plugin\API::unsetAllInstances(); $_GET = $_REQUEST = array(); diff --git a/tests/PHPUnit/Integration/Session/SaveHandler/DbTableTest.php b/tests/PHPUnit/Integration/Session/SaveHandler/DbTableTest.php new file mode 100644 index 0000000000..e71af20a32 --- /dev/null +++ b/tests/PHPUnit/Integration/Session/SaveHandler/DbTableTest.php @@ -0,0 +1,37 @@ +<?php +/** + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + */ + +namespace Piwik\Tests\Integration\Session\SaveHandler; + +use Piwik\Session; +use Piwik\Session\SaveHandler\DbTable; +use Piwik\Tests\Framework\TestCase\IntegrationTestCase; + +class DbTableTest extends IntegrationTestCase +{ + /** + * @var DbTable + */ + private $testInstance; + + public function setUp(): void + { + parent::setUp(); + $this->testInstance = new DbTable(Session::getDbTableConfig()); + } + + public function test_read_returnsTheSessionDataCorrectly() + { + $this->testInstance->write('testid', 'testdata'); + + $result = $this->testInstance->read('testid'); + + $this->assertEquals('testdata', $result); + } +}
\ No newline at end of file diff --git a/tests/PHPUnit/Unit/Notification/ManagerTest.php b/tests/PHPUnit/Unit/Notification/ManagerTest.php new file mode 100644 index 0000000000..dd8673f1c0 --- /dev/null +++ b/tests/PHPUnit/Unit/Notification/ManagerTest.php @@ -0,0 +1,84 @@ +<?php +/** + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + */ + +namespace Piwik\Tests\Unit\Notification; + +use PHPUnit\Framework\TestCase; +use Piwik\Notification; +use Piwik\Notification\Manager; + +class ManagerTest extends TestCase +{ + protected function setUp(): void + { + parent::setUp(); + + Manager::cancelAllNotifications(); + } + + protected function tearDown(): void + { + parent::tearDown(); + + Manager::cancelAllNotifications(); + } + + public function test_notify_addsNotificationToNotificationArray() + { + $notification = new Notification('abcdefg'); + $result = Manager::notify('testid', $notification); + + $this->assertTrue($result); + + $notificationsInArray = Manager::getPendingInMemoryNotifications(); + + $expected = [ + 'testid' => $notification, + ]; + $this->assertEquals($expected, $notificationsInArray); + } + + /** + * @dataProvider getTestDataForNotify + */ + public function test_notify_throwsWhenAnInvalidIdIsUsed($id, $expectedMessage) + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage($expectedMessage); + + Manager::notify($id, new Notification('sldjfksdf')); + } + + public function getTestDataForNotify() + { + return [ + ['', 'Notification ID is empty.'], + ['aabcd a a k', 'Invalid Notification ID given. Only word characters (AlNum + underscore) allowed.'], + ['a23%$%', 'Invalid Notification ID given. Only word characters (AlNum + underscore) allowed.'], + ]; + } + + public function test_notify_doesNotAddNotificationIfThereAreAlreadyMoreThanThirty() + { + for ($i = 0; $i < Manager::MAX_NOTIFICATIONS_IN_SESSION; ++$i) { + Manager::notify('not' . $i, new Notification('message ' . $i)); + } + + $notificationsInArray = Manager::getPendingInMemoryNotifications(); + + $notification = new Notification('abcdefg'); + $result = Manager::notify('testid', $notification); + + $this->assertFalse($result); + + $notificationsInArray = Manager::getPendingInMemoryNotifications(); + $this->assertCount(30, $notificationsInArray); + $this->assertArrayNotHasKey('testid', $notificationsInArray); + } +}
\ No newline at end of file |