Index: branches/2.8.x/CHANGELOG
===================================================================
--- branches/2.8.x/CHANGELOG	(revision 2136)
+++ branches/2.8.x/CHANGELOG	(revision 2137)
@@ -10,6 +10,8 @@
 # = Bugfix
 ! = Update/Change
 ===============================================================================
+30 Oct -2015 Build 2137 Manuela v.d.Decken(DarkViper)
+! class SecureTokens: added easy handling of service requests on checkFTAN()/checkIDKEY()
 22 Oct -2015 Build 2136 Manuela v.d.Decken(DarkViper)
 + class SecureTokens: as a replacement for former class SecureForm in SecureForm.php and SecureForm.mtab.php
 ! framework/initialize.php change bindings of SecureForm to SecureTokens
Index: branches/2.8.x/wb/admin/interface/version.php
===================================================================
--- branches/2.8.x/wb/admin/interface/version.php	(revision 2136)
+++ branches/2.8.x/wb/admin/interface/version.php	(revision 2137)
@@ -51,5 +51,5 @@
 
 // check if defined to avoid errors during installation (redirect to admin panel fails if PHP error/warnings are enabled)
 if(!defined('VERSION')) define('VERSION', '2.8.4');
-if(!defined('REVISION')) define('REVISION', '2136');
+if(!defined('REVISION')) define('REVISION', '2137');
 if(!defined('SP')) define('SP', '');
Index: branches/2.8.x/wb/framework/SecureTokens.php
===================================================================
--- branches/2.8.x/wb/framework/SecureTokens.php	(revision 2136)
+++ branches/2.8.x/wb/framework/SecureTokens.php	(revision 2137)
@@ -45,28 +45,43 @@
 
 class SecureTokens
 {
-/** character string for 64char encoding */
-    const CHAR64 = 'Zabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZa';
-/** possible settings for TokenLifeTime in seconds */
-    const LIFETIMES = [1800, 2700, 3600, 7200];  // seconds for 30min / 45min / 1h / 2h
+/**
+ * possible settings for TokenLifeTime in seconds
+ * @description seconds for 30min / 45min / 1h / 75min / 90min / 105min / 2h
+ */
+/** minimum lifetime in seconds */
+    const LIFETIME_MIN  = 1800; // 30min
+/** maximum lifetime in seconds */
+    const LIFETIME_MAX  = 7200; // 120min (2h)
+/** stepwidth between min and max */
+    const LIFETIME_STEP =  900; // 15min
 /** lifetime in seconds to use in DEBUG mode if negative value is given (-1) */
-    const DEBUG_LIFETIME = 300;
-
+    const DEBUG_LIFETIME = 300; // 5
 /** array to hold all tokens from the session */
     private $aTokens = array(
-        'default' => array('value' => 0, 'expire' => 0)
+        'default' => array('value' => 0, 'expire' => 0, 'instance' => 0)
     );
-/** the salt for this run */
+/** the salt for this instance */
     private $sSalt            = '';
 /** fingerprint of the current connection */
     private $sFingerprint     = '';
-/** the FTAN token which is valid for this run */
+/** the FTAN token which is valid for this instance */
     private $aLastCreatedFtan = null;
+/** the time when tokens expired if they created in this instance */
+    private $iExpireTime      = 0;
+/** remove selected tokens only and update all others */
+    private $bPreserveAllOtherTokens = false;
+/** id of the current instance */
+    private $sCurrentInstance  = null;
+/** id of the instance to remove */
+    private $sInstanceToDelete = null;
+/** id of the instance to update expire time */
+    private $sInstanceToUpdate = null;
 /* --- settings for SecureTokens ------------------------------------------------------ */
 /** use fingerprinting to encode */
     private $bUseFingerprint = true;
 /** maximum lifetime of a token in seconds */
-    private $iTokenLifeTime   = 1800; // 30min / 45min / 1h / 2h (default = 30min)
+    private $iTokenLifeTime   = 1800; // between LIFETIME_MIN and LIFETIME_MAX (default = 30min)
 /** bit length of the IPv4 Netmask (0-32 // 0 = off  default = 24) */
     private $iNetmaskLengthV4 = 0;
 /** bit length of the IPv6 Netmask (0-128 // 0 = off  default = 64) */
@@ -76,29 +91,55 @@
  * constructor
  * @param (void)
  */
-    protected function __construct() {
+    protected function __construct()
+    {
     // load settings if available
         $this->getSettings();
+    // generate salt for calculations in this instance
+        $this->sSalt        = $this->generateSalt();
+    // generate fingerprint for the current connection
+        $this->sFingerprint = $this->buildFingerprint();
+    // define the expiretime for this instance
+        $this->iExpireTime  = time() + $this->iTokenLifeTime;
+    // calculate the instance id for this instance
+        $this->sCurrentInstance = $this->encode64(md5($this->iExpireTime.$this->sSalt));
     // load array of tokens from session
         $this->loadTokens();
     // at first of all remove expired tokens
         $this->removeExpiredTokens();
-    // generate salt for calculations in this run
-        $this->sSalt        = $this->generateSalt();
-    // generate fingerprint for the current connection
-        $this->sFingerprint = $this->buildFingerprint();
     }
 
 /**
- * @param (void)
- * @return integer
+ * destructor
  */
-    public function getTokenLifeTime()
+    final public function __destruct()
     {
-        // return 5 seconds less then lifetime to compensate delay of transfer to the client
-        return $this->iTokenLifeTime - 5;
+        foreach ($this->aTokens as $sKey => $aToken) {
+            if ($aToken['instance'] == $this->sInstanceToUpdate) {
+                $this->aTokens[$sKey]['instance'] = $this->sCurrentInstance;
+                $this->aTokens[$sKey]['expire']   = $this->iExpireTime;
+            } elseif ($aToken['instance'] == $this->sInstanceToDelete) {
+                unset($this->aTokens[$sKey]);
+            }
+        }
+        $this->saveTokens();
     }
+
 /**
+ * returns all TokenLifeTime values
+ * @return array
+ */
+    final public function getTokenLifeTime()
+    {
+        return array(
+            'min'   => self::LIFETIME_MIN,
+            'max'   => self::LIFETIME_MAX,
+            'step'  => self::LIFETIME_STEP,
+            'value' => $this->iTokenLifeTime
+        );
+    }
+
+/**
  * Dummy method for backward compatibility
  * @return void
  * @deprecated from WB-2.8.3-SP5
@@ -153,14 +194,16 @@
 /**
  * checks received form-transactionnumbers against session-stored one
  * @param string $mode: requestmethode POST(default) or GET
+ * @param bool $bPreserve (default=false)
  * @return bool:    true if numbers matches against stored ones
  *
  * requirements: an active session must be available
  * this check will prevent from multiple sending a form. history.back() also will never work
  */
-    final public function checkFTAN($mMode = 'POST')
+    final public function checkFTAN($mMode = 'POST', $bPreserve = false)
     {
         $bRetval = false;
+        $this->bPreserveAllOtherTokens = $bPreserve ?: $this->bPreserveAllOtherTokens;
         // get the POST/GET arguments
         $aArguments = (strtoupper($mMode) == 'POST' ? $_POST : $_GET);
         // encode the value of all matching tokens
@@ -205,12 +248,14 @@
  * @param string $sFieldname: name of the POST/GET-Field containing the key or hex-key itself
  * @param mixed $mDefault: returnvalue if key not exist (default 0)
  * @param string $sRequest: requestmethode can be POST or GET or '' (default POST)
+ * @param bool $bPreserve (default=false)
  * @return mixed: the original value (string, numeric, array) or DEFAULT if request fails
  * @description: each IDKEY can be checked only once. Unused Keys stay in list until they expire
  */
-    final public function checkIDKEY( $sFieldname, $mDefault = 0, $sRequest = 'POST' )
+    final public function checkIDKEY($sFieldname, $mDefault = 0, $sRequest = 'POST', $bPreserve = false)
     {
         $mReturnValue = $mDefault; // set returnvalue to default
+        $this->bPreserveAllOtherTokens = $bPreserve ?: $this->bPreserveAllOtherTokens;
         $sRequest = strtoupper($sRequest);
         switch ($sRequest) {
             case 'POST':
@@ -234,9 +279,31 @@
         }
         return $mReturnValue;
     }
-// *** from here private methods only ****************************************************
 
 /**
+ * make a valid LifeTime value from given integer on the rules of class SecureTokens
+ * @param integer  $iLifeTime
+ * @return integer
+ */
+    final public function sanitizeLifeTime($iLifeTime)
+    {
+        $iLifeTime = intval($iLifeTime);
+        for ($i = self::LIFETIME_MIN; $i <= self::LIFETIME_MAX; $i += self::LIFETIME_STEP) {
+            $aLifeTimes[] = $i;
+        }
+        $iRetval = array_pop($aLifeTimes);
+        foreach ($aLifeTimes as $iValue) {
+            if ($iLifeTime <= $iValue) {
+                $iRetval = $iValue;
+                break;
+            }
+        }
+        return $iRetval;
+    }
+/* ************************************************************************************ */
+/* *** from here private methods only                                               *** */
+/* ************************************************************************************ */
+/**
  * load all tokens from session
  */
     private function loadTokens()
@@ -270,10 +337,10 @@
             $sTokenName = sprintf('%16x', hexdec($sTokenName)+1);
         }
         $this->aTokens[$sTokenName] = array(
-            'value'  => $sValue,
-            'expire' => time()+$this->iTokenLifeTime
+            'value'    => $sValue,
+            'expire'   => $this->iExpireTime,
+            'instance' => $this->sCurrentInstance
         );
-        $this->saveTokens();
         return $sTokenName;
     }
 
@@ -284,9 +351,13 @@
     private function removeToken($sTokenName)
     {
         if (isset($this->aTokens[$sTokenName])) {
+            if ($this->bPreserveAllOtherTokens) {
+                $this->sInstanceToUpdate = $this->aTokens[$sTokenName]['instance'];
+            } else {
+                $this->sInstanceToDelete = $this->aTokens[$sTokenName]['instance'];
+            }
             unset($this->aTokens[$sTokenName]);
         }
-        $this->saveTokens();
     }
 
 /**
@@ -300,7 +371,6 @@
                 unset($this->aTokens[$sTokenName]);
             }
         }
-        $this->saveTokens();
     }
 
 /**
@@ -323,7 +393,14 @@
     private function buildFingerprint()
     {
         if (!$this->bUseFingerprint) { return md5('dummy'); }
-        $sClientIp = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
+        $sClientIp = '127.0.0.1';
+        if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)){
+            $sClientIp = array_pop(preg_split('/\s*?,\s*?/', $_SERVER['HTTP_X_FORWARDED_FOR'], null, PREG_SPLIT_NO_EMPTY));
+        }else if (array_key_exists('REMOTE_ADDR', $_SERVER)) {
+            $sClientIp = $_SERVER['REMOTE_ADDR'];
+        }else if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) {
+            $sClientIp = $_SERVER['HTTP_CLIENT_IP'];
+        }
         $sFingerprint = __FILE__.PHP_VERSION
                       . isset($_SERVER['SERVER_SIGNATURE']) ? $_SERVER['SERVER_SIGNATURE'] : 'unknown'
                       . isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'AGENT'
@@ -390,31 +467,11 @@
  * encode a hex string into a 64char based string
  * @param string $sMd5Hash
  * @return string
- * @description reduce the 32char length of a MD5 to 21 chars
+ * @description reduce the 32char length of a MD5 to 22 chars
  */
     private function encode64($sMd5Hash)
     {
-        $sCharList = self::CHAR64;
-        $sBin = implode(
-            '',
-            array_map(
-                function($sPart) {
-                    return sprintf('%032b', hexdec($sPart));
-                },
-                str_split($sMd5Hash, 8)
-            )
-        );
-        $sResult = implode(
-            '',
-            array_map(
-                function($sPart) use ($sCharList) {
-                    $sPart = str_pad($sPart, 6, '0', STR_PAD_RIGHT);
-                    return $sCharList[bindec($sPart)];
-                },
-                str_split($sBin, 6)
-            )
-        );
-        return $sResult;
+        return rtrim(base64_encode(pack('h*',$sMd5Hash)), '+-= ');
     }
 
 /**
@@ -422,37 +479,44 @@
  */
     private function getSettings()
     {
-        $this->bUseFingerprint  = isset($this->oReg->SecTokenFingerprint)
-                                  ? $this->oReg->SecTokenFingerprint
-                                  : $this->bUseFingerprint;
-        $this->iNetmaskLengthV4 = isset($this->oReg->SecTokenNetmask4)
-                                  ? $this->oReg->SecTokenNetmask4
-                                  : $this->iNetmaskLengthV4;
-        $this->iNetmaskLengthV6 = isset($this->oReg->SecTokenNetmask6)
-                                  ? $this->oReg->SecTokenNetmask6
-                                  : $this->iNetmaskLengthV6;
-        $this->iTokenLifeTime   = isset($this->oReg->SecTokenLifeTime)
-                                  ? $this->oReg->SecTokenLifeTime
-                                  : $this->iTokenLifeTime;
+        if (!class_exists('WbAdaptor', false)) {
+        // for WB before 2.8.4
+            $this->bUseFingerprint  = defined('SEC_TOKEN_FINGERPRINT')
+                                      ? SEC_TOKEN_FINGERPRINT
+                                      : $this->bUseFingerprint;
+            $this->iNetmaskLengthV4 = defined('SEC_TOKEN_NETMASK4')
+                                      ? SEC_TOKEN_NETMASK4
+                                      : $this->iNetmaskLengthV4;
+            $this->iNetmaskLengthV6 = defined('SEC_TOKEN_NETMASK6')
+                                      ? SEC_TOKEN_NETMASK6
+                                      : $this->iNetmaskLengthV6;
+            $this->iTokenLifeTime   = defined('SEC_TOKEN_LIFE_TIME')
+                                      ? SEC_TOKEN_LIFE_TIME
+                                      : $this->iTokenLifeTime;
+        } else {
+        // for WB from 2.8.4 and up
+            $this->bUseFingerprint  = isset($this->oReg->SecTokenFingerprint)
+                                      ? $this->oReg->SecTokenFingerprint
+                                      : $this->bUseFingerprint;
+            $this->iNetmaskLengthV4 = isset($this->oReg->SecTokenNetmask4)
+                                      ? $this->oReg->SecTokenNetmask4
+                                      : $this->iNetmaskLengthV4;
+            $this->iNetmaskLengthV6 = isset($this->oReg->SecTokenNetmask6)
+                                      ? $this->oReg->SecTokenNetmask6
+                                      : $this->iNetmaskLengthV6;
+            $this->iTokenLifeTime   = isset($this->oReg->SecTokenLifeTime)
+                                      ? $this->oReg->SecTokenLifeTime
+                                      : $this->iTokenLifeTime;
+        }
         $this->iNetmaskLengthV4 = ($this->iNetmaskLengthV4 < 1 || $this->iNetmaskLengthV4 > 32)
                                   ? 0 :$this->iNetmaskLengthV4;
         $this->iNetmaskLengthV6 = ($this->iNetmaskLengthV6 < 1 || $this->iNetmaskLengthV6 > 128)
                                   ? 0 :$this->iNetmaskLengthV6;
-        $aLifeTimes = self::LIFETIMES;
-        sort($aLifeTimes);
-        if ($this->iTokenLifeTime < 0 && DEBUG) {
+        $this->iTokenLifeTime   = $this->sanitizeLifeTime($this->iTokenLifeTime);
+        if ($this->iTokenLifeTime <= self::LIFETIME_MIN && DEBUG) {
             $this->iTokenLifeTime = self::DEBUG_LIFETIME;
-        } else {
-            $iRetval = array_pop($aLifeTimes);
-            foreach ($aLifeTimes as $iValue) {
-                if ($this->iTokenLifeTime <= $iValue) {
-                    $iRetval = $iValue;
-                    break;
-                }
-            }
-            $this->iTokenLifeTime = $iRetval;
         }
-
     }
 
+
 } // end of class SecureTokens
