[FEATURE] Allow anonymous logging
authorBjoern Pedersen <bjoern.pedersen@frm2.tum.de>
Fri, 18 Nov 2011 07:40:59 +0000 (08:40 +0100)
committerSteffen Ritter <info@rs-websystems.de>
Sun, 18 Dec 2011 12:49:47 +0000 (13:49 +0100)
    New options in config allow the anonymized storage of statistic log
    informations.
    (necessary e.g. in Germany: see e.g. http://www.saechsdsb.de/ipmask)

    config.stat_IP_anonymize:
      set to 1 to activate anonymized logging
    config.stat_IP_anonymize_mask_ipv4:
    config.stat_IP_anonymize_mask_ipv6:
      Prefix-mask (0..32 for IPv4, 0..128 for IPv6) to use for
      anonymisation. Setting this to will log an empty hostname.
      Recommendation for Germany: config.stat_IP_anonymize_ipv4 = 24
      if not set, defaults to 24  and 64, resp.

    config.stat_logUser: configure username logging

    CGL-cleanup in statistics function.

Change-Id: Id03841ad19343b7b6820d780d0769154c5a880b0
Resolves: #25404
Releases: 4.7
Reviewed-on: http://review.typo3.org/1289
Reviewed-by: Stefan Neufeind
Reviewed-by: Dominik Mathern
Tested-by: Dominik Mathern
Reviewed-by: Steffen Ritter
Tested-by: Steffen Ritter
tests/typo3/sysext/cms/tslib/class.tslib_feTest.php
typo3/sysext/cms/tslib/class.tslib_fe.php

index de91301..8281851 100644 (file)
@@ -43,9 +43,19 @@ class tslib_feTest extends tx_phpunit_testcase {
                eval(
                        'class ' . $className . ' extends tslib_fe {' .
                        'public function ' . $className . '() {}' .
+
                        'public function roundTripCryptString($string) {' .
                        'return parent::roundTripCryptString($string);' .
                        '}' .
+
+                       'public function stripIPv4($strIP) {' .
+                       'return parent::stripIPv4($strIP);' .
+                       '}' .
+
+                       'public function stripIPv6($strIP) {' .
+                       'return parent::stripIPv6($strIP);' .
+                       '}' .
+
                        '}'
                );
 
@@ -143,5 +153,87 @@ class tslib_feTest extends tx_phpunit_testcase {
                        )
                );
        }
+
+       //////////////////////////////////////
+       // Tests concerning stat-anonymization
+       //////////////////////////////////////
+
+       /**
+        * Data provider for stripIPv6Correct
+        *
+        * @return array Data sets
+        */
+       public static function stripIPv4DataProviderCorrect() {
+               return array(
+                       'empty address, prefix-length 24' => array('0.0.0.0', '24', '0.0.0.0'),
+                       'normal address 1, prefix-length 1' => array('1.2.3.4', '1', '0.0.0.0'),
+                       'normal address 2, prefix-length 24' => array('192.168.5.79', '24', '192.168.5.0'),
+                       'normal address 2, prefix-length 30' => array('192.168.5.79', '30', '192.168.5.76'),
+                               // test for no anonymization; full prefix-length
+                       'normal address 2, prefix-length 32' => array('192.168.5.79', '32', '192.168.5.79'),
+                               // test for full anonymization; full prefix-length
+                       'normal address 2, prefix-length 0' => array('192.168.5.79', '0', '0.0.0.0'),
+               );
+       }
+
+       /**
+        * @test
+        * @dataProvider stripIPv4DataProviderCorrect
+        */
+       public function stripIPv4Correct($address, $prefixLength, $anonymized) {
+               $oldConfig = $this->fixture->config;
+
+               $this->fixture->config = array('config' =>
+                       array('stat_IP_anonymize' => '1',
+                               'stat_IP_anonymize_mask_ipv4' => $prefixLength
+                       )
+               );
+
+               $this->assertEquals(
+                       $this->fixture->stripIPv4($address),
+                       $anonymized
+               );
+               $this->fixture->config = $oldConfig;
+       }
+
+       /**
+        * Data provider for stripIPv6Correct
+        *
+        * @return array Data sets
+        */
+       public static function stripIPv6DataProviderCorrect() {
+               return array(
+                       'empty address, prefix-length 96' => array('::', '96', '::'),
+                       'normal address 1, prefix-length 1' => array('1:2:3::4', '1', '::'),
+                       'normal address 2, prefix-length 4' => array('ffff::9876', '4', 'f000::'),
+                       'normal address 2, prefix-length 1' => array('ffff::9876', '1', '8000::'),
+                       'normal address 3, prefix-length 96' => array('abc:def::9876', '96', 'abc:def::'),
+                       'normal address 3, prefix-length 120' => array('abc:def::9876', '120', 'abc:def::9800'),
+                               // test for no anonymization; full prefix-length
+                       'normal address 3, prefix-length 128' => array('abc:def::9876', '128', 'abc:def::9876'),
+                               // test for full anonymization
+                       'normal address 3, prefix-length 0' => array('abc:def::9876', '0', '::'),
+               );
+       }
+
+       /**
+        * @test
+        * @dataProvider stripIPv6DataProviderCorrect
+        */
+       public function stripIPv6Correct($address, $prefixLength, $anonymized) {
+               $oldConfig = $this->fixture->config;
+
+               $this->fixture->config = array('config' =>
+                       array('stat_IP_anonymize' => '1',
+                               'stat_IP_anonymize_mask_ipv6' => $prefixLength
+                       )
+               );
+
+               $this->assertEquals(
+                       $this->fixture->stripIPv6($address),
+                       $anonymized
+               );
+               $this->fixture->config = $oldConfig;
+       }
 }
 ?>
\ No newline at end of file
index 9ce8101..6770a3c 100644 (file)
@@ -3716,6 +3716,113 @@ if (version == "n3") {
        }
 
        /**
+        * Get the (partially) anonymized IP address for the log file
+        *      configure: set set config.stat_IP_anonymize=1
+        *
+        *  @return string the IP to log
+        */
+       public function getLogIPAddress(){
+               $result = t3lib_div::getIndpEnv('REMOTE_ADDR');
+               if ($this->config['config']['stat_IP_anonymize']) {
+                       if (strpos($result, ':')) {
+                               $result = $this->stripIPv6($result);
+                       } else {
+                               $result = $this->stripIPv4($result);
+                       }
+               }
+               return $result;
+       }
+
+       /**
+        * Strip parts from a IPv6 address
+        *
+        * configure: set config.stat_IP_anonymize_mask_ipv6 to a prefix-length (0 to 128)
+        *                        defaults to 64  if not set
+        *
+        * @param string raw IPv6 address
+        * @return string stripped address
+        */
+       protected function stripIPv6($strIP) {
+               if(isset($this->config['config']['stat_IP_anonymize_mask_ipv6'])) {
+                       $netPrefix = intval($this->config['config']['stat_IP_anonymize_mask_ipv6']);
+               } else {
+                       $netPrefix = 64;
+               }
+               $bytesIP = t3lib_div::IPv6Hex2Bin($strIP);
+
+               $bitsToStrip = (128 - $netPrefix);
+
+               for($counter = 15; $counter >= 0; $counter--)
+               {
+                       $bitsToStripPart = min($bitsToStrip, 8);
+                               // TODO find a nicer solution for bindec and chr/ord below - but it works :-)
+                       $mask = bindec(str_pad('', 8 - $bitsToStripPart, '1') . str_pad('', $bitsToStripPart, '0'));
+                       $bytesIP[$counter] = chr(ord($bytesIP[$counter]) & $mask);
+                       $bitsToStrip -= $bitsToStripPart;
+               }
+               $strIP = inet_ntop($bytesIP);
+               return $strIP;
+       }
+
+       /**
+        * Strip parts from IPv4 addresses
+        *
+        * configure: set config.stat_IP_anonymize_mask_ipv4 to a prefix-length (0 to 32)
+        *                        defaults to 24, if not set
+        *
+        * @param string IPv4 address
+        * @return string  stripped IP address
+        */
+       protected function stripIPv4($strIP) {
+               if(isset($this->config['config']['stat_IP_anonymize_mask_ipv4'])) {
+                       $netPrefix = intval($this->config['config']['stat_IP_anonymize_mask_ipv4']);
+               } else {
+                       $netPrefix = 24;
+               }
+
+               $bitsToStrip = (32 - $netPrefix);
+               $ip = ip2long($strIP);
+                       // shift right
+               $ip = $ip >> $bitsToStrip;
+                       // shift left; last bytes will be zero now
+               $ip = $ip << $bitsToStrip;
+               $strIP = long2ip($ip);
+               return $strIP;
+       }
+
+       /**
+        * Get the (possibly) anonymized host name for the log file
+        *      configure: set config.stat_IP_anonymize=1
+        *
+        * @return the host name to log
+        */
+       public function getLogHostName(){
+               if($this->config['config']['stat_IP_anonymize']) {
+                               // ignore hostname if IP anonymized
+                       $hostName = '<anonymized>';
+               } else {
+                       $hostName = t3lib_div::getIndpEnv('REMOTE_HOST');
+               }
+               return $hostName;
+       }
+
+       /**
+        * Get the (possibly) anonymized username or user id for the log file
+        *      configure: set config.stat_IP_anonymize=1
+        *
+        * @return the user name /uid to log
+        */
+       public function getLogUserName(){
+               $logUser = (isset($this->config['config']['stat_logUser'])) ? $this->config['config']['stat_logUser'] : TRUE;
+               if ($this->loginUser && $logUser) {
+                       $userName =  $this->fe_user->user['username'];
+               } else {
+                       $userName = '-';
+               }
+               return $userName;
+       }
+
+       /**
         * Saves hit statistics
         *
         * @return      void
@@ -3765,8 +3872,8 @@ if (version == "n3") {
                                                'client_os' => $GLOBALS['CLIENT']['SYSTEM'],                    // Client Operating system (win, mac, unix)
                                                'parsetime' => intval($this->scriptParseTime),                  // Parsetime for the page.
                                                'flags' => $flags,                                                                              // Flags: Is be user logged in? Is page cached?
-                                               'IP' => t3lib_div::getIndpEnv('REMOTE_ADDR'),                   // Remote IP address
-                                               'host' => t3lib_div::getIndpEnv('REMOTE_HOST'),                 // Remote Host Address
+                                               'IP' => $this->getLogIPAddress(),                                               // Remote IP address
+                                               'host' => $this->getLogHostName(),                                              // Remote Host Address
                                                'referer' => $refUrl,                                                                   // Referer URL
                                                'browser' => t3lib_div::getIndpEnv('HTTP_USER_AGENT'),  // User Agent Info.
                                                'tstamp' => $GLOBALS['EXEC_TIME']                                               // Time stamp
@@ -3791,21 +3898,21 @@ if (version == "n3") {
                                        if (@is_file($this->config['stat_vars']['logFile'])) {
                                                        // Build a log line (format is derived from the NCSA extended/combined log format)
                                                        // Log part 1: Remote hostname / address
-                                               $LogLine = (t3lib_div::getIndpEnv('REMOTE_HOST') && empty($this->config['config']['stat_apache_noHost'])) ? t3lib_div::getIndpEnv('REMOTE_HOST') : t3lib_div::getIndpEnv('REMOTE_ADDR');
+                                               $LogLine = (t3lib_div::getIndpEnv('REMOTE_HOST') && empty($this->config['config']['stat_apache_noHost'])) ? $this->getLogHostName() : $this->getLogIPAddress();
                                                        // Log part 2: Fake the remote logname
-                                               $LogLine.= ' -';
+                                               $LogLine .= ' -';
                                                        // Log part 3: Remote username
-                                               $LogLine.= ' '.($this->loginUser ? $this->fe_user->user['username'] : '-');
+                                               $LogLine .= ' ' . $this->getLogUserName();
                                                        // Log part 4: Time
-                                               $LogLine.= ' '.date('[d/M/Y:H:i:s +0000]',$GLOBALS['EXEC_TIME']);
+                                               $LogLine .= ' ' . date('[d/M/Y:H:i:s +0000]',$GLOBALS['EXEC_TIME']);
                                                        // Log part 5: First line of request (the request filename)
-                                               $LogLine.= ' "GET '.$this->config['stat_vars']['pageName'].' HTTP/1.1"';
+                                               $LogLine .= ' "GET ' . $this->config['stat_vars']['pageName'].' HTTP/1.1"';
                                                        // Log part 6: Status and content length (ignores special content like admin panel!)
-                                               $LogLine.= ' 200 '.strlen($this->content);
+                                               $LogLine .= ' 200 ' . strlen($this->content);
 
                                                if (empty($this->config['config']['stat_apache_notExtended'])) {
                                                        $referer = t3lib_div::getIndpEnv('HTTP_REFERER');
-                                                       $LogLine.= ' "'.($referer ? $referer : '-').'" "'.t3lib_div::getIndpEnv('HTTP_USER_AGENT').'"';
+                                                       $LogLine .= ' "' . ($referer ? $referer : '-') . '" "' . t3lib_div::getIndpEnv('HTTP_USER_AGENT') . '"';
                                                }
 
                                                $GLOBALS['TT']->push('Write to log file (fputs)');