diff --git a/config/global.ini.php b/config/global.ini.php index b03a2dc978f46c16a6c17f3d5df0028eeec8dbb7..95f30a3d0e73f6811b2e5b95e8d313cc6b129b05 100644 --- a/config/global.ini.php +++ b/config/global.ini.php @@ -511,6 +511,12 @@ pivot_by_filter_default_column_limit = 10 [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 836d03650a64ee0a99e56307ab0cf15fb20c0a21..74240718e720ffd7f90e3a1570a10f05c08dc85d 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 @@ class Settings . $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 9dee4196b7c8a7bf59cf51697b90c8699b46f6c2..ee4bd97a11032a71b0617c73127bb73164a5beab 100644 --- a/core/Tracker/Visit.php +++ b/core/Tracker/Visit.php @@ -386,7 +386,8 @@ class Visit implements VisitInterface 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 0000000000000000000000000000000000000000..fa2dee2969d7797a3139bf359d4d0d568717f599 --- /dev/null +++ b/tests/PHPUnit/Integration/Tracker/SettingsTest.php @@ -0,0 +1,159 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Tests\Integration\Tracker; + +use Piwik\Tests\Framework\Fixture; +use Piwik\Tracker\Cache; +use Piwik\Tests\Framework\TestCase\IntegrationTestCase; +use Piwik\Tracker\Request; +use Piwik\Tracker\Settings; + +/** + * @group SettingsTest + * @group TrackerVisitSettingsTest + * @group Tracker + */ +class SettingsTest extends IntegrationTestCase +{ + /** + * @var string + */ + protected $ip = '123.30.30.30'; + + public function setUp() + { + parent::setUp(); + + Fixture::createWebsite('2014-01-01 00:00:00'); + Fixture::createWebsite('2014-01-01 00:00:00'); + Cache::deleteTrackerCache(); + + $_SERVER['HTTP_USER_AGENT'] = ''; + } + + public function test_getConfigId_isSame() + { + $settings1 = $this->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; + } +}