Project

General

Profile

« Previous | Next » 

Revision 2137

Added by darkviper about 9 years ago

! class SecureTokens: added easy handling of service requests on checkFTAN()/checkIDKEY()

View differences:

SecureTokens.php
45 45

  
46 46
class SecureTokens
47 47
{
48
/** character string for 64char encoding */
49
    const CHAR64 = 'Zabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZa';
50
/** possible settings for TokenLifeTime in seconds */
51
    const LIFETIMES = [1800, 2700, 3600, 7200];  // seconds for 30min / 45min / 1h / 2h
48
/**
49
 * possible settings for TokenLifeTime in seconds
50
 * @description seconds for 30min / 45min / 1h / 75min / 90min / 105min / 2h
51
 */
52
/** minimum lifetime in seconds */
53
    const LIFETIME_MIN  = 1800; // 30min
54
/** maximum lifetime in seconds */
55
    const LIFETIME_MAX  = 7200; // 120min (2h)
56
/** stepwidth between min and max */
57
    const LIFETIME_STEP =  900; // 15min
52 58
/** lifetime in seconds to use in DEBUG mode if negative value is given (-1) */
53
    const DEBUG_LIFETIME = 300;
54

  
59
    const DEBUG_LIFETIME = 300; // 5
55 60
/** array to hold all tokens from the session */
56 61
    private $aTokens = array(
57
        'default' => array('value' => 0, 'expire' => 0)
62
        'default' => array('value' => 0, 'expire' => 0, 'instance' => 0)
58 63
    );
59
/** the salt for this run */
64
/** the salt for this instance */
60 65
    private $sSalt            = '';
61 66
/** fingerprint of the current connection */
62 67
    private $sFingerprint     = '';
63
/** the FTAN token which is valid for this run */
68
/** the FTAN token which is valid for this instance */
64 69
    private $aLastCreatedFtan = null;
70
/** the time when tokens expired if they created in this instance */
71
    private $iExpireTime      = 0;
72
/** remove selected tokens only and update all others */
73
    private $bPreserveAllOtherTokens = false;
74
/** id of the current instance */
75
    private $sCurrentInstance  = null;
76
/** id of the instance to remove */
77
    private $sInstanceToDelete = null;
78
/** id of the instance to update expire time */
79
    private $sInstanceToUpdate = null;
65 80
/* --- settings for SecureTokens ------------------------------------------------------ */
66 81
/** use fingerprinting to encode */
67 82
    private $bUseFingerprint = true;
68 83
/** maximum lifetime of a token in seconds */
69
    private $iTokenLifeTime   = 1800; // 30min / 45min / 1h / 2h (default = 30min)
84
    private $iTokenLifeTime   = 1800; // between LIFETIME_MIN and LIFETIME_MAX (default = 30min)
70 85
/** bit length of the IPv4 Netmask (0-32 // 0 = off  default = 24) */
71 86
    private $iNetmaskLengthV4 = 0;
72 87
/** bit length of the IPv6 Netmask (0-128 // 0 = off  default = 64) */
......
76 91
 * constructor
77 92
 * @param (void)
78 93
 */
79
    protected function __construct() {
94
    protected function __construct()
95
    {
80 96
    // load settings if available
81 97
        $this->getSettings();
98
    // generate salt for calculations in this instance
99
        $this->sSalt        = $this->generateSalt();
100
    // generate fingerprint for the current connection
101
        $this->sFingerprint = $this->buildFingerprint();
102
    // define the expiretime for this instance
103
        $this->iExpireTime  = time() + $this->iTokenLifeTime;
104
    // calculate the instance id for this instance
105
        $this->sCurrentInstance = $this->encode64(md5($this->iExpireTime.$this->sSalt));
82 106
    // load array of tokens from session
83 107
        $this->loadTokens();
84 108
    // at first of all remove expired tokens
85 109
        $this->removeExpiredTokens();
86
    // generate salt for calculations in this run
87
        $this->sSalt        = $this->generateSalt();
88
    // generate fingerprint for the current connection
89
        $this->sFingerprint = $this->buildFingerprint();
90 110
    }
91 111

  
92 112
/**
93
 * @param (void)
94
 * @return integer
113
 * destructor
95 114
 */
96
    public function getTokenLifeTime()
115
    final public function __destruct()
97 116
    {
98
        // return 5 seconds less then lifetime to compensate delay of transfer to the client
99
        return $this->iTokenLifeTime - 5;
117
        foreach ($this->aTokens as $sKey => $aToken) {
118
            if ($aToken['instance'] == $this->sInstanceToUpdate) {
119
                $this->aTokens[$sKey]['instance'] = $this->sCurrentInstance;
120
                $this->aTokens[$sKey]['expire']   = $this->iExpireTime;
121
            } elseif ($aToken['instance'] == $this->sInstanceToDelete) {
122
                unset($this->aTokens[$sKey]);
123
            }
124
        }
125
        $this->saveTokens();
100 126
    }
127

  
101 128
/**
129
 * returns all TokenLifeTime values
130
 * @return array
131
 */
132
    final public function getTokenLifeTime()
133
    {
134
        return array(
135
            'min'   => self::LIFETIME_MIN,
136
            'max'   => self::LIFETIME_MAX,
137
            'step'  => self::LIFETIME_STEP,
138
            'value' => $this->iTokenLifeTime
139
        );
140
    }
141

  
142
/**
102 143
 * Dummy method for backward compatibility
103 144
 * @return void
104 145
 * @deprecated from WB-2.8.3-SP5
......
153 194
/**
154 195
 * checks received form-transactionnumbers against session-stored one
155 196
 * @param string $mode: requestmethode POST(default) or GET
197
 * @param bool $bPreserve (default=false)
156 198
 * @return bool:    true if numbers matches against stored ones
157 199
 *
158 200
 * requirements: an active session must be available
159 201
 * this check will prevent from multiple sending a form. history.back() also will never work
160 202
 */
161
    final public function checkFTAN($mMode = 'POST')
203
    final public function checkFTAN($mMode = 'POST', $bPreserve = false)
162 204
    {
163 205
        $bRetval = false;
206
        $this->bPreserveAllOtherTokens = $bPreserve ?: $this->bPreserveAllOtherTokens;
164 207
        // get the POST/GET arguments
165 208
        $aArguments = (strtoupper($mMode) == 'POST' ? $_POST : $_GET);
166 209
        // encode the value of all matching tokens
......
205 248
 * @param string $sFieldname: name of the POST/GET-Field containing the key or hex-key itself
206 249
 * @param mixed $mDefault: returnvalue if key not exist (default 0)
207 250
 * @param string $sRequest: requestmethode can be POST or GET or '' (default POST)
251
 * @param bool $bPreserve (default=false)
208 252
 * @return mixed: the original value (string, numeric, array) or DEFAULT if request fails
209 253
 * @description: each IDKEY can be checked only once. Unused Keys stay in list until they expire
210 254
 */
211
    final public function checkIDKEY( $sFieldname, $mDefault = 0, $sRequest = 'POST' )
255
    final public function checkIDKEY($sFieldname, $mDefault = 0, $sRequest = 'POST', $bPreserve = false)
212 256
    {
213 257
        $mReturnValue = $mDefault; // set returnvalue to default
258
        $this->bPreserveAllOtherTokens = $bPreserve ?: $this->bPreserveAllOtherTokens;
214 259
        $sRequest = strtoupper($sRequest);
215 260
        switch ($sRequest) {
216 261
            case 'POST':
......
234 279
        }
235 280
        return $mReturnValue;
236 281
    }
237
// *** from here private methods only ****************************************************
238 282

  
239 283
/**
284
 * make a valid LifeTime value from given integer on the rules of class SecureTokens
285
 * @param integer  $iLifeTime
286
 * @return integer
287
 */
288
    final public function sanitizeLifeTime($iLifeTime)
289
    {
290
        $iLifeTime = intval($iLifeTime);
291
        for ($i = self::LIFETIME_MIN; $i <= self::LIFETIME_MAX; $i += self::LIFETIME_STEP) {
292
            $aLifeTimes[] = $i;
293
        }
294
        $iRetval = array_pop($aLifeTimes);
295
        foreach ($aLifeTimes as $iValue) {
296
            if ($iLifeTime <= $iValue) {
297
                $iRetval = $iValue;
298
                break;
299
            }
300
        }
301
        return $iRetval;
302
    }
303
/* ************************************************************************************ */
304
/* *** from here private methods only                                               *** */
305
/* ************************************************************************************ */
306
/**
240 307
 * load all tokens from session
241 308
 */
242 309
    private function loadTokens()
......
270 337
            $sTokenName = sprintf('%16x', hexdec($sTokenName)+1);
271 338
        }
272 339
        $this->aTokens[$sTokenName] = array(
273
            'value'  => $sValue,
274
            'expire' => time()+$this->iTokenLifeTime
340
            'value'    => $sValue,
341
            'expire'   => $this->iExpireTime,
342
            'instance' => $this->sCurrentInstance
275 343
        );
276
        $this->saveTokens();
277 344
        return $sTokenName;
278 345
    }
279 346

  
......
284 351
    private function removeToken($sTokenName)
285 352
    {
286 353
        if (isset($this->aTokens[$sTokenName])) {
354
            if ($this->bPreserveAllOtherTokens) {
355
                $this->sInstanceToUpdate = $this->aTokens[$sTokenName]['instance'];
356
            } else {
357
                $this->sInstanceToDelete = $this->aTokens[$sTokenName]['instance'];
358
            }
287 359
            unset($this->aTokens[$sTokenName]);
288 360
        }
289
        $this->saveTokens();
290 361
    }
291 362

  
292 363
/**
......
300 371
                unset($this->aTokens[$sTokenName]);
301 372
            }
302 373
        }
303
        $this->saveTokens();
304 374
    }
305 375

  
306 376
/**
......
323 393
    private function buildFingerprint()
324 394
    {
325 395
        if (!$this->bUseFingerprint) { return md5('dummy'); }
326
        $sClientIp = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
396
        $sClientIp = '127.0.0.1';
397
        if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)){
398
            $sClientIp = array_pop(preg_split('/\s*?,\s*?/', $_SERVER['HTTP_X_FORWARDED_FOR'], null, PREG_SPLIT_NO_EMPTY));
399
        }else if (array_key_exists('REMOTE_ADDR', $_SERVER)) {
400
            $sClientIp = $_SERVER['REMOTE_ADDR'];
401
        }else if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) {
402
            $sClientIp = $_SERVER['HTTP_CLIENT_IP'];
403
        }
327 404
        $sFingerprint = __FILE__.PHP_VERSION
328 405
                      . isset($_SERVER['SERVER_SIGNATURE']) ? $_SERVER['SERVER_SIGNATURE'] : 'unknown'
329 406
                      . isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'AGENT'
......
390 467
 * encode a hex string into a 64char based string
391 468
 * @param string $sMd5Hash
392 469
 * @return string
393
 * @description reduce the 32char length of a MD5 to 21 chars
470
 * @description reduce the 32char length of a MD5 to 22 chars
394 471
 */
395 472
    private function encode64($sMd5Hash)
396 473
    {
397
        $sCharList = self::CHAR64;
398
        $sBin = implode(
399
            '',
400
            array_map(
401
                function($sPart) {
402
                    return sprintf('%032b', hexdec($sPart));
403
                },
404
                str_split($sMd5Hash, 8)
405
            )
406
        );
407
        $sResult = implode(
408
            '',
409
            array_map(
410
                function($sPart) use ($sCharList) {
411
                    $sPart = str_pad($sPart, 6, '0', STR_PAD_RIGHT);
412
                    return $sCharList[bindec($sPart)];
413
                },
414
                str_split($sBin, 6)
415
            )
416
        );
417
        return $sResult;
474
        return rtrim(base64_encode(pack('h*',$sMd5Hash)), '+-= ');
418 475
    }
419 476

  
420 477
/**
......
422 479
 */
423 480
    private function getSettings()
424 481
    {
425
        $this->bUseFingerprint  = isset($this->oReg->SecTokenFingerprint)
426
                                  ? $this->oReg->SecTokenFingerprint
427
                                  : $this->bUseFingerprint;
428
        $this->iNetmaskLengthV4 = isset($this->oReg->SecTokenNetmask4)
429
                                  ? $this->oReg->SecTokenNetmask4
430
                                  : $this->iNetmaskLengthV4;
431
        $this->iNetmaskLengthV6 = isset($this->oReg->SecTokenNetmask6)
432
                                  ? $this->oReg->SecTokenNetmask6
433
                                  : $this->iNetmaskLengthV6;
434
        $this->iTokenLifeTime   = isset($this->oReg->SecTokenLifeTime)
435
                                  ? $this->oReg->SecTokenLifeTime
436
                                  : $this->iTokenLifeTime;
482
        if (!class_exists('WbAdaptor', false)) {
483
        // for WB before 2.8.4
484
            $this->bUseFingerprint  = defined('SEC_TOKEN_FINGERPRINT')
485
                                      ? SEC_TOKEN_FINGERPRINT
486
                                      : $this->bUseFingerprint;
487
            $this->iNetmaskLengthV4 = defined('SEC_TOKEN_NETMASK4')
488
                                      ? SEC_TOKEN_NETMASK4
489
                                      : $this->iNetmaskLengthV4;
490
            $this->iNetmaskLengthV6 = defined('SEC_TOKEN_NETMASK6')
491
                                      ? SEC_TOKEN_NETMASK6
492
                                      : $this->iNetmaskLengthV6;
493
            $this->iTokenLifeTime   = defined('SEC_TOKEN_LIFE_TIME')
494
                                      ? SEC_TOKEN_LIFE_TIME
495
                                      : $this->iTokenLifeTime;
496
        } else {
497
        // for WB from 2.8.4 and up
498
            $this->bUseFingerprint  = isset($this->oReg->SecTokenFingerprint)
499
                                      ? $this->oReg->SecTokenFingerprint
500
                                      : $this->bUseFingerprint;
501
            $this->iNetmaskLengthV4 = isset($this->oReg->SecTokenNetmask4)
502
                                      ? $this->oReg->SecTokenNetmask4
503
                                      : $this->iNetmaskLengthV4;
504
            $this->iNetmaskLengthV6 = isset($this->oReg->SecTokenNetmask6)
505
                                      ? $this->oReg->SecTokenNetmask6
506
                                      : $this->iNetmaskLengthV6;
507
            $this->iTokenLifeTime   = isset($this->oReg->SecTokenLifeTime)
508
                                      ? $this->oReg->SecTokenLifeTime
509
                                      : $this->iTokenLifeTime;
510
        }
437 511
        $this->iNetmaskLengthV4 = ($this->iNetmaskLengthV4 < 1 || $this->iNetmaskLengthV4 > 32)
438 512
                                  ? 0 :$this->iNetmaskLengthV4;
439 513
        $this->iNetmaskLengthV6 = ($this->iNetmaskLengthV6 < 1 || $this->iNetmaskLengthV6 > 128)
440 514
                                  ? 0 :$this->iNetmaskLengthV6;
441
        $aLifeTimes = self::LIFETIMES;
442
        sort($aLifeTimes);
443
        if ($this->iTokenLifeTime < 0 && DEBUG) {
515
        $this->iTokenLifeTime   = $this->sanitizeLifeTime($this->iTokenLifeTime);
516
        if ($this->iTokenLifeTime <= self::LIFETIME_MIN && DEBUG) {
444 517
            $this->iTokenLifeTime = self::DEBUG_LIFETIME;
445
        } else {
446
            $iRetval = array_pop($aLifeTimes);
447
            foreach ($aLifeTimes as $iValue) {
448
                if ($this->iTokenLifeTime <= $iValue) {
449
                    $iRetval = $iValue;
450
                    break;
451
                }
452
            }
453
            $this->iTokenLifeTime = $iRetval;
454 518
        }
455

  
456 519
    }
457 520

  
521

  
458 522
} // end of class SecureTokens

Also available in: Unified diff