idSite = Fixture::createWebsite('2018-01-02 03:04:05'); Fixture::createWebsite('2018-01-02 03:04:05'); $this->now = Date::factory('2018-09-07 01:02:03'); $this->failures = new Failures(); $this->failures->setNow($this->now); } public function testLogFailureGetAllFailures() { $this->logFailure(1, array()); $this->logFailure(1, array('idsite' => 9999999)); // unknown idsite $this->logFailure(9999, array()); // unknown failure $this->logFailure(2, array('url' => '')); $this->logFailure(1, array('url' => 'https://www.example.com/page')); $failures = $this->failures->getAllFailures(); $this->assertEquals(array ( array ( 'idsite' => '1', 'idfailure' => '1', 'date_first_occurred' => '2018-09-07 01:02:03', 'request_url' => 'rec=1&idsite=1', 'site_name' => 'Piwik test', 'pretty_date_first_occurred' => 'Intl_1or02Intl_Time_AMt_250Intl_Time_AMtTi02_S1ort', 'url' => '', 'solution_url' => 'https://matomo.org/faq/how-to/faq_30838/', 'problem' => 'CoreAdminHome_TrackingFailureInvalidSiteProblem', 'solution' => 'CoreAdminHome_TrackingFailureInvalidSiteSolution', ), array ( 'idsite' => '1', 'idfailure' => '2', 'date_first_occurred' => '2018-09-07 01:02:03', 'request_url' => 'url=&rec=1&idsite=1', 'site_name' => 'Piwik test', 'pretty_date_first_occurred' => 'Intl_1or02Intl_Time_AMt_250Intl_Time_AMtTi02_S1ort', 'url' => '', 'solution_url' => 'https://matomo.org/faq/how-to/faq_30835/', 'problem' => 'CoreAdminHome_TrackingFailureAuthenticationProblem', 'solution' => 'CoreAdminHome_TrackingFailureAuthenticationSolution', ), array ( 'idsite' => '1', 'idfailure' => '9999', 'date_first_occurred' => '2018-09-07 01:02:03', 'request_url' => 'rec=1&idsite=1', 'site_name' => 'Piwik test', 'pretty_date_first_occurred' => 'Intl_1or02Intl_Time_AMt_250Intl_Time_AMtTi02_S1ort', 'url' => '', 'problem' => '', 'solution' => '', 'solution_url' => '', ), array ( 'idsite' => '9999999', 'idfailure' => '1', 'date_first_occurred' => '2018-09-07 01:02:03', 'request_url' => 'idsite=9999999&rec=1', 'site_name' => 'General_Unknown', 'pretty_date_first_occurred' => 'Intl_1or02Intl_Time_AMt_250Intl_Time_AMtTi02_S1ort', 'url' => '', 'solution_url' => 'https://matomo.org/faq/how-to/faq_30838/', 'problem' => 'CoreAdminHome_TrackingFailureInvalidSiteProblem', 'solution' => 'CoreAdminHome_TrackingFailureInvalidSiteSolution', ), ), $failures); } public function testLogFailureDoesNotLogSameFailureTwice() { $expected = array ( array ( 'idsite' => '1', 'idfailure' => '1', 'date_first_occurred' => '2018-09-07 01:02:03', 'request_url' => 'rec=1&idsite=1', 'site_name' => 'Piwik test', 'pretty_date_first_occurred' => 'Intl_1or02Intl_Time_AMt_250Intl_Time_AMtTi02_S1ort', 'url' => '', 'solution_url' => 'https://matomo.org/faq/how-to/faq_30838/', 'problem' => 'CoreAdminHome_TrackingFailureInvalidSiteProblem', 'solution' => 'CoreAdminHome_TrackingFailureInvalidSiteSolution', ), ); $this->logFailure(1, array()); $failures = $this->failures->getAllFailures(); $this->assertEquals($expected, $failures); $this->logFailure(1, array()); $failures = $this->failures->getAllFailures(); $this->assertEquals($expected, $failures); // does log a different problem for same site $this->logFailure(2, array()); $failures = $this->failures->getAllFailures(); $this->assertCount(2, $failures); // does log a same problem for different site $this->logFailure(1, array('idsite' => 999)); $failures = $this->failures->getAllFailures(); $this->assertCount(3, $failures); } public function testLogFailureAnonymizesTokenWhenParamUsed() { $this->logFailure(1, array('token_auth' => 'foobar', 'token' => 'bar', 'tokenauth' => 'baz')); $failures = $this->failures->getAllFailures(); $this->assertEquals('token_auth=__TOKEN_AUTH__&token=__TOKEN_AUTH__&tokenauth=__TOKEN_AUTH__&rec=1&idsite=1', $failures[0]['request_url']); } public function testLogFailureAnonymizesTokenWhenMd5ValueUsed() { $this->logFailure(1, array('foo' => md5('foo'))); $failures = $this->failures->getAllFailures(); $this->assertEquals('foo=__TOKEN_AUTH__&rec=1&idsite=1', $failures[0]['request_url']); } public function testLogFailureAnonymizesTokenWhenMd5SimilarValueUsed() { $this->logFailure(1, array('foo' => md5('foo') . 'ff')); $failures = $this->failures->getAllFailures(); $this->assertEquals('foo=__TOKEN_AUTH__&rec=1&idsite=1', $failures[0]['request_url']); } public function testLogFailureDoesNotLogExcludedRequest() { $this->logFailure(1, array('rec' => '0')); $this->assertEquals(array(), $this->failures->getAllFailures()); } public function testLogFailureDoesNotLogAnyUnusualHighSiteId() { $this->logFailure(1, array('idsite' => '99999999999')); $this->assertEquals(array(), $this->failures->getAllFailures()); } public function testLogFailureDoesNotLogAnyUnusualLowSiteId() { try { $this->logFailure(1, array('idsite' => '-1')); } catch (UnexpectedWebsiteFoundException $e) { // triggered by $request->getIdSite() in visits excluded... we ignore this error in this test // as it is fine to have this error as long as the failure is not recorded } $this->assertEquals(array(), $this->failures->getAllFailures()); } public function testLogFailureCanLogEntryForIdSite0() { $this->logFailure(1, array('idsite' => '0')); $this->assertCount(1, $this->failures->getAllFailures()); } public function testGetAllFailuresNoFailuresByDefault() { $this->assertSame(array(), $this->failures->getAllFailures()); } public function testGetFailuresForSitesNoFailuresByDefault() { $this->assertSame(array(), $this->failures->getAllFailures()); } public function testGetFailuresForSitesReturnsOnlyFailuresForGivenSite() { $this->logFailure(1, array('idsite' => 2)); $this->logFailure(2, array('idsite' => 2)); $this->logFailure(1, array('idsite' => 3)); $this->logFailure(2, array('idsite' => 3)); $this->logFailure(3, array('idsite' => 3)); $this->logFailure(1, array('idsite' => 4)); $this->logFailure(2, array('idsite' => 4)); $this->logFailure(3, array('idsite' => 4)); $this->logFailure(4, array('idsite' => 4)); $this->logFailure(1, array('idsite' => 5)); $this->logFailure(2, array('idsite' => 5)); $this->logFailure(3, array('idsite' => 5)); $this->logFailure(4, array('idsite' => 5)); $this->logFailure(5, array('idsite' => 5)); $this->assertSame(array(), $this->failures->getFailuresForSites(array())); $this->assertCount(2, $this->failures->getFailuresForSites(array(2))); $this->assertCount(3, $this->failures->getFailuresForSites(array(3))); $this->assertCount(7, $this->failures->getFailuresForSites(array(2,5))); $this->assertCount(12, $this->failures->getFailuresForSites(array(4,3,5))); } public function testDeleteTrackingFailure() { $this->logFailure(1, array('idsite' => 2)); $this->logFailure(2, array('idsite' => 2)); $this->logFailure(1, array('idsite' => 3)); $this->logFailure(2, array('idsite' => 3)); $this->logFailure(3, array('idsite' => 3)); $this->assertCount(5, $this->failures->getAllFailures()); $this->failures->deleteTrackingFailure(3, 2); $summary = $this->getFailureSummary(); $this->assertEquals(array( array(2,1), array(2,2), array(3,1), array(3,3), // 3,2 is not returned ), $summary); } public function testDeleteTrackingFailureWhenWrongIdAllAreKept() { $this->logFailure(1, array('idsite' => 2)); $this->logFailure(2, array('idsite' => 2)); $this->logFailure(1, array('idsite' => 3)); $this->logFailure(2, array('idsite' => 3)); $this->logFailure(3, array('idsite' => 3)); $this->assertCount(5, $this->failures->getAllFailures()); $this->failures->deleteTrackingFailure(99999, 2); $this->assertCount(5, $this->failures->getAllFailures()); $this->failures->deleteTrackingFailure(2, 9999); $this->assertCount(5, $this->failures->getAllFailures()); } public function testDeleteAllTrackingFailures() { $this->logFailure(1, array('idsite' => 2)); $this->logFailure(2, array('idsite' => 2)); $this->logFailure(1, array('idsite' => 3)); $this->logFailure(2, array('idsite' => 3)); $this->logFailure(3, array('idsite' => 3)); $this->assertCount(5, $this->failures->getAllFailures()); $this->failures->deleteAllTrackingFailures(); $this->assertSame([], $this->failures->getAllFailures()); } public function testDeleteTrackingFailures() { $this->logFailure(1, array('idsite' => 1)); $this->logFailure(1, array('idsite' => 2)); $this->logFailure(2, array('idsite' => 2)); $this->logFailure(1, array('idsite' => 3)); $this->logFailure(2, array('idsite' => 3)); $this->logFailure(3, array('idsite' => 3)); $this->assertCount(6, $this->failures->getAllFailures()); $this->failures->deleteTrackingFailures(array(1,3)); $this->assertEquals([array(2,1), array(2,2)], $this->getFailureSummary()); } public function testRemoveFailuresOlderThanDays() { $this->logFailure(1, array('idsite' => 2)); $this->logFailure(2, array('idsite' => 2)); $this->logFailure(3, array('idsite' => 2), 1); $this->logFailure(1, array('idsite' => 3), 2); $this->logFailure(2, array('idsite' => 3), 2); $this->logFailure(3, array('idsite' => 3), 3); $this->logFailure(4, array('idsite' => 3), 3); $this->logFailure(5, array('idsite' => 3), 3); $this->logFailure(6, array('idsite' => 3), 4); $this->failures->removeFailuresOlderThanDays(2); $summary = $this->getFailureSummary(); $this->assertEquals(array( array(2,1), array(2,2), array(2,3), array(3,1), array(3,2), ), $summary); } /** * @dataProvider getInvalidSiteIds */ public function testProvidingInvalidSiteIdForTrackingDoesLogFailure(string $idsite) { try { $request = new Request(['idsite' => $idsite, 'rec' => '1', 'url' => 'https://matomo.org/index']); $visit = new Visit(); $visit->setRequest($request); $visit->handle(); self::fail('expected exception not raised'); } catch (UnexpectedWebsiteFoundException $e) { // ignore, as we expect a UnexpectedWebsiteFoundException to be thrown } self::assertCount(1, $this->failures->getAllFailures()); } public function getInvalidSiteIds(): array { return [ ['4'], ['0'], ['1234'], ]; } /** * @dataProvider getMalFormedSiteIds */ public function testProvidingMalformedSiteIdForTrackingDoesNotLogFailure(string $idsite) { try { $request = new Request(['idsite' => $idsite, 'rec' => '1', 'url' => 'https://matomo.org/index']); $visit = new Visit(); $visit->setRequest($request); $visit->handle(); self::fail('expected exception not raised'); } catch (UnexpectedWebsiteFoundException $e) { // ignore, as we expect it to be thrown } self::assertCount(0, $this->failures->getAllFailures()); } public function getMalFormedSiteIds(): array { return [ [''], ['-4'], ['1"; DROP TABLE'], ['5,6'], ['nan'], ['test5'], ]; } public function testProvidingInvalidTokenAuthForTrackingDoesLogFailure() { try { $request = new Request(['idsite' => '1', 'rec' => '1', 'url' => 'https://matomo.org/index', 'city' => 'Berlin'], '1d34ghdrg6j33uersadfg34defg342vs'); $visit = new Visit(); $visit->setRequest($request); $visit->handle(); self::fail('expected exception not raised'); } catch (InvalidRequestParameterException $e) { // ignore, as we expect that exception } self::assertCount(1, $this->failures->getAllFailures()); } /** * @dataProvider getMalFormedTokenAuths */ public function testProvidingMalformedTokenAuthForTrackingDoesNotLogFailure(string $tokenAuth) { try { $request = new Request(['idsite' => '1', 'rec' => '1', 'url' => 'https://matomo.org/index', 'city' => 'Berlin'], $tokenAuth); $visit = new Visit(); $visit->setRequest($request); $visit->handle(); self::fail('expected exception not raised'); } catch (InvalidRequestParameterException $e) { // ignore, as we expect that exception } self::assertCount(0, $this->failures->getAllFailures()); } public function getMalFormedTokenAuths(): iterable { yield 'too short token' => [ 'abc', ]; yield 'too long token' => [ 'abc2435tgadetb356z2wq3er4gbrnz367634rahne735e5wqergwert245hw45h3gq45h', ]; yield 'token with invalid chars' => [ '33dc3f!536d302$974cccb4b4d2-98f4', ]; } private function getFailureSummary() { $failures = $this->failures->getAllFailures(); $summary = array(); foreach ($failures as $failure) { $summary[] = array($failure['idsite'], $failure['idfailure']); } return $summary; } private function logFailure($idFailure, $params, $daysAgo = null) { if (!isset($params['rec'])) { $params['rec'] = 1; } if (!isset($params['idsite'])) { $params['idsite'] = $this->idSite; } $request = new Request($params); if (isset($daysAgo)) { $this->failures->setNow($this->now->subDay($daysAgo)->addPeriod(1, 'minute')); } $this->failures->logFailure($idFailure, $request); $this->failures->setNow($this->now); } }