From 38c5b336ad4880cff1ec156f82ad7fc84961a596 Mon Sep 17 00:00:00 2001 From: mattab Date: Mon, 8 Dec 2014 14:53:21 +1300 Subject: [PATCH] fixes #6824 Hash the fingerprint with the website ID to make it different on each website for a given user + tests --- config/global.ini.php | 6 + core/Tracker/Settings.php | 9 +- core/Tracker/Visit.php | 3 +- .../Integration/Tracker/SettingsTest.php | 159 ++++++++++++++++++ 4 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 tests/PHPUnit/Integration/Tracker/SettingsTest.php diff --git a/config/global.ini.php b/config/global.ini.php index b03a2dc978f..95f30a3d0e7 100644 --- a/config/global.ini.php +++ b/config/global.ini.php @@ -511,6 +511,12 @@ [Tracker] +; Piwik uses "Privacy by default" model. When one of your users visit multiple of your websites tracked in this Piwik, +; Piwik will create for this user a fingerprint that will be different across the multiple websites. +; If you want to track unique users across websites (for example when using the InterSites plugin) you may set this setting to 0. +; Note: setting this to 0 reduces your users' privacy. +enable_fingerprinting_across_websites = 0 + ; Piwik uses first party cookies by default. If set to 1, ; the visit ID cookie will be set on the Piwik server domain as well ; this is useful when you want to do cross websites analysis diff --git a/core/Tracker/Settings.php b/core/Tracker/Settings.php index 836d03650a6..74240718e72 100644 --- a/core/Tracker/Settings.php +++ b/core/Tracker/Settings.php @@ -8,6 +8,7 @@ */ namespace Piwik\Tracker; +use Piwik\Config; use Piwik\Tracker; use Piwik\DeviceDetectorFactory; use Piwik\SettingsPiwik; @@ -16,10 +17,11 @@ class Settings { const OS_BOT = 'BOT'; - function __construct(Request $request, $ip) + function __construct(Request $request, $ip, $isSameFingerprintsAcrossWebsites) { $this->request = $request; $this->ipAddress = $ip; + $this->isSameFingerprintsAcrossWebsites = $isSameFingerprintsAcrossWebsites; $this->configId = null; } @@ -110,8 +112,13 @@ protected function getConfigHash($os, $browserName, $browserVersion, $plugin_Fla . $browserLang . $salt; + if(!$this->isSameFingerprintsAcrossWebsites) { + $configString .= $this->request->getIdSite(); + } + $hash = md5($configString, $raw_output = true); return substr($hash, 0, Tracker::LENGTH_BINARY_ID); } + } \ No newline at end of file diff --git a/core/Tracker/Visit.php b/core/Tracker/Visit.php index 9dee4196b7c..ee4bd97a110 100644 --- a/core/Tracker/Visit.php +++ b/core/Tracker/Visit.php @@ -386,7 +386,8 @@ protected function getVisitorIp() protected function getSettingsObject() { if (is_null($this->userSettings)) { - $this->userSettings = new Settings( $this->request, $this->getVisitorIp() ); + $isSameFingerprintAcrossWebsites = (bool)Config::getInstance()->Tracker['enable_fingerprinting_across_websites']; + $this->userSettings = new Settings( $this->request, $this->getVisitorIp(), $isSameFingerprintAcrossWebsites ); } return $this->userSettings; diff --git a/tests/PHPUnit/Integration/Tracker/SettingsTest.php b/tests/PHPUnit/Integration/Tracker/SettingsTest.php new file mode 100644 index 00000000000..fa2dee2969d --- /dev/null +++ b/tests/PHPUnit/Integration/Tracker/SettingsTest.php @@ -0,0 +1,159 @@ +makeSettings(array('idsite' => 1)); + $settings2 = $this->makeSettings(array('idsite' => 1)); + + $this->assertEquals($settings1->getConfigId(), $settings2->getConfigId()); + } + + + public function test_getConfigId_isSame_whenConfiguredUserHasSameFingerprintAcrossWebsites() + { + $isSameFingerprintAcrossWebsites = true; + + $settingsSite1 = $this->makeSettings(array('idsite' => 1), $isSameFingerprintAcrossWebsites); + $settingsSite2 = $this->makeSettings(array('idsite' => 2), $isSameFingerprintAcrossWebsites); + + $this->assertEquals($settingsSite1->getConfigId(), $settingsSite2->getConfigId()); + } + + public function test_getConfigId_isDifferent_whenConfiguredUserHasDifferentFingerprintAcrossWebsites() + { + $isSameFingerprintAcrossWebsites = false; + + $settingsSite1 = $this->makeSettings(array('idsite' => 1), $isSameFingerprintAcrossWebsites); + $settingsSite2 = $this->makeSettings(array('idsite' => 2), $isSameFingerprintAcrossWebsites); + + $this->assertNotSame($settingsSite1->getConfigId(), $settingsSite2->getConfigId()); + } + + public function test_getConfigId_isSame_whenBrowserSamebutDifferentUserAgent() + { + $settingsFirefox = $this->makeSettings(array('ua' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10; rv:33.0) Gecko/20100101 Firefox/33.0')); + $settingsSlightlyDifferentUserAgent = $this->makeSettings(array('ua' => 'Mozilla/5.0 (Macintosh; Extra; string; here; Hello; world; Intel Mac OS X 10_10; rv:33.0) Gecko/20100101 Firefox/33.0')); + + $this->assertSame($settingsSlightlyDifferentUserAgent->getConfigId(), $settingsFirefox->getConfigId()); + } + + public function test_getConfigId_isDifferent_whenBrowserChanges() + { + $settingsFirefox = $this->makeSettings(array('ua' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10; rv:33.0) Gecko/20100101 Firefox/33.0')); + $settingsChrome = $this->makeSettings(array('ua' => 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19 ')); + + $this->assertNotSame($settingsChrome->getConfigId(), $settingsFirefox->getConfigId()); + } + + public function test_getConfigId_isDifferent_whenOSChanges() + { + $settingsFirefoxMac = $this->makeSettings(array('ua' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10; rv:33.0) Gecko/20100101 Firefox/33.0')); + $settingsFirefoxLinux = $this->makeSettings(array('ua' => 'Mozilla/5.0 (Linux; rv:33.0) Gecko/20100101 Firefox/33.0')); + + $this->assertNotSame($settingsFirefoxLinux->getConfigId(), $settingsFirefoxMac->getConfigId()); + } + + public function test_getConfigId_isDifferent_whenPluginChanges() + { + $params = array( + 'pdf' => 1, + 'cookie' => 1, + 'fla' => 0, + 'idsite' => 1, + ); + $settingsWithoutFlash = $this->makeSettings($params); + + // activate flash + $params['fla'] = 1; + $settingsWithFlash = $this->makeSettings($params); + + $this->assertNotSame($settingsWithoutFlash->getConfigId(), $settingsWithFlash->getConfigId()); + } + + public function test_getConfigId_isDifferent_whenIPIsAnonimised() + { + $settingsIpIsNotAnon = $this->makeSettings(array(), true, '125.1.55.55'); + $settingsIpIsAnon = $this->makeSettings(array(), true, '125.1.0.0'); + + $this->assertNotSame($settingsIpIsNotAnon->getConfigId(), $settingsIpIsAnon->getConfigId()); + } + + public function test_getConfigId_isSame_whenIPIsAnonimisedAndBothSame() + { + $settingsIpIsNotAnon = $this->makeSettings(array(), true, '125.2.0.0'); + $settingsIpIsAnon = $this->makeSettings(array(), true, '125.2.0.0'); + + $this->assertSame($settingsIpIsNotAnon->getConfigId(), $settingsIpIsAnon->getConfigId()); + } + + /** + * @param $params array + * @param $isSameFingerprintAcrossWebsites + * @param $ip + * @return Settings + */ + protected function makeSettings($params, $isSameFingerprintAcrossWebsites = false, $ip = null) + { + if(is_null($ip)) { + $ip = $this->ip; + } + $requestSite1 = $this->makeRequest($params); + $settingsSite1 = new Settings($requestSite1, $ip, $isSameFingerprintAcrossWebsites); + return $settingsSite1; + } + + /** + * @param $extraParams array + * @return array + */ + private function makeRequest($extraParams) + { + // default + $params = array( + 'pdf' => 1, + 'cookie' => 1, + 'fla' => 0, + 'idsite' => 1, + ); + $params = array_merge($params, $extraParams); + $request = new Request($params); + return $request; + } +}