source: Dev/branches/cakephp/cake/libs/cake_session.php @ 126

Last change on this file since 126 was 126, checked in by fpvanagthoven, 14 years ago

Cakephp branch.

File size: 18.5 KB
Line 
1<?php
2/**
3 * Session class for Cake.
4 *
5 * Cake abstracts the handling of sessions.
6 * There are several convenient methods to access session information.
7 * This class is the implementation of those methods.
8 * They are mostly used by the Session Component.
9 *
10 * PHP versions 4 and 5
11 *
12 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
13 * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
14 *
15 * Licensed under The MIT License
16 * Redistributions of files must retain the above copyright notice.
17 *
18 * @copyright     Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
19 * @link          http://cakephp.org CakePHP(tm) Project
20 * @package       cake
21 * @subpackage    cake.cake.libs
22 * @since         CakePHP(tm) v .0.10.0.1222
23 * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
24 */
25if (!class_exists('Security')) {
26        App::import('Core', 'Security');
27}
28
29/**
30 * Session class for Cake.
31 *
32 * Cake abstracts the handling of sessions. There are several convenient methods to access session information.
33 * This class is the implementation of those methods. They are mostly used by the Session Component.
34 *
35 * @package       cake
36 * @subpackage    cake.cake.libs
37 */
38class CakeSession extends Object {
39
40/**
41 * True if the Session is still valid
42 *
43 * @var boolean
44 * @access public
45 */
46        var $valid = false;
47
48/**
49 * Error messages for this session
50 *
51 * @var array
52 * @access public
53 */
54        var $error = false;
55
56/**
57 * User agent string
58 *
59 * @var string
60 * @access protected
61 */
62        var $_userAgent = '';
63
64/**
65 * Path to where the session is active.
66 *
67 * @var string
68 * @access public
69 */
70        var $path = '/';
71
72/**
73 * Error number of last occurred error
74 *
75 * @var integer
76 * @access public
77 */
78        var $lastError = null;
79
80/**
81 * 'Security.level' setting, "high", "medium", or "low".
82 *
83 * @var string
84 * @access public
85 */
86        var $security = null;
87
88/**
89 * Start time for this session.
90 *
91 * @var integer
92 * @access public
93 */
94        var $time = false;
95
96/**
97 * Time when this session becomes invalid.
98 *
99 * @var integer
100 * @access public
101 */
102        var $sessionTime = false;
103
104/**
105 * The number of seconds to set for session.cookie_lifetime.  0 means
106 * at browser close.
107 *
108 * @var integer
109 */
110        var $cookieLifeTime = false;
111
112/**
113 * Keeps track of keys to watch for writes on
114 *
115 * @var array
116 * @access public
117 */
118        var $watchKeys = array();
119
120/**
121 * Current Session id
122 *
123 * @var string
124 * @access public
125 */
126        var $id = null;
127
128/**
129 * Hostname
130 *
131 * @var string
132 * @access public
133 */
134        var $host = null;
135
136/**
137 * Session timeout multiplier factor
138 *
139 * @var integer
140 * @access public
141 */
142        var $timeout = null;
143
144/**
145 * Constructor.
146 *
147 * @param string $base The base path for the Session
148 * @param boolean $start Should session be started right now
149 * @access public
150 */
151        function __construct($base = null, $start = true) {
152                App::import('Core', array('Set', 'Security'));
153                $this->time = time();
154
155                if (Configure::read('Session.checkAgent') === true || Configure::read('Session.checkAgent') === null) {
156                        if (env('HTTP_USER_AGENT') != null) {
157                                $this->_userAgent = md5(env('HTTP_USER_AGENT') . Configure::read('Security.salt'));
158                        }
159                }
160                if (Configure::read('Session.save') === 'database') {
161                        $modelName = Configure::read('Session.model');
162                        $database = Configure::read('Session.database');
163                        $table = Configure::read('Session.table');
164
165                        if (empty($database)) {
166                                $database = 'default';
167                        }
168                        $settings = array(
169                                'class' => 'Session',
170                                'alias' => 'Session',
171                                'table' => 'cake_sessions',
172                                'ds' => $database
173                        );
174                        if (!empty($modelName)) {
175                                $settings['class'] = $modelName;
176                        }
177                        if (!empty($table)) {
178                                $settings['table'] = $table;
179                        }
180                        ClassRegistry::init($settings);
181                }
182                if ($start === true) {
183                        if (!empty($base)) {
184                                $this->path = $base;
185                                if (strpos($base, 'index.php') !== false) {
186                                   $this->path = str_replace('index.php', '', $base);
187                                }
188                                if (strpos($base, '?') !== false) {
189                                   $this->path = str_replace('?', '', $base);
190                                }
191                        }
192                        $this->host = env('HTTP_HOST');
193
194                        if (strpos($this->host, ':') !== false) {
195                                $this->host = substr($this->host, 0, strpos($this->host, ':'));
196                        }
197                }
198                if (isset($_SESSION) || $start === true) {
199                        $this->sessionTime = $this->time + (Security::inactiveMins() * Configure::read('Session.timeout'));
200                        $this->security = Configure::read('Security.level');
201                }
202                parent::__construct();
203        }
204
205/**
206 * Starts the Session.
207 *
208 * @return boolean True if session was started
209 * @access public
210 */
211        function start() {
212                if ($this->started()) {
213                        return true;
214                }
215                if (function_exists('session_write_close')) {
216                        session_write_close();
217                }
218                $this->__initSession();
219                $this->__startSession();
220                return $this->started();
221        }
222
223/**
224 * Determine if Session has been started.
225 *
226 * @access public
227 * @return boolean True if session has been started.
228 */
229        function started() {
230                if (isset($_SESSION) && session_id()) {
231                        return true;
232                }
233                return false;
234        }
235
236/**
237 * Returns true if given variable is set in session.
238 *
239 * @param string $name Variable name to check for
240 * @return boolean True if variable is there
241 * @access public
242 */
243        function check($name) {
244                if (empty($name)) {
245                        return false;
246                }
247                $result = Set::classicExtract($_SESSION, $name);
248                return isset($result);
249        }
250
251/**
252 * Returns the Session id
253 *
254 * @param id $name string
255 * @return string Session id
256 * @access public
257 */
258        function id($id = null) {
259                if ($id) {
260                        $this->id = $id;
261                        session_id($this->id);
262                }
263                if ($this->started()) {
264                        return session_id();
265                } else {
266                        return $this->id;
267                }
268        }
269
270/**
271 * Removes a variable from session.
272 *
273 * @param string $name Session variable to remove
274 * @return boolean Success
275 * @access public
276 */
277        function delete($name) {
278                if ($this->check($name)) {
279                        if (in_array($name, $this->watchKeys)) {
280                                trigger_error(sprintf(__('Deleting session key {%s}', true), $name), E_USER_NOTICE);
281                        }
282                        $this->__overwrite($_SESSION, Set::remove($_SESSION, $name));
283                        return ($this->check($name) == false);
284                }
285                $this->__setError(2, sprintf(__("%s doesn't exist", true), $name));
286                return false;
287        }
288
289/**
290 * Used to write new data to _SESSION, since PHP doesn't like us setting the _SESSION var itself
291 *
292 * @param array $old Set of old variables => values
293 * @param array $new New set of variable => value
294 * @access private
295 */
296        function __overwrite(&$old, $new) {
297                if (!empty($old)) {
298                        foreach ($old as $key => $var) {
299                                if (!isset($new[$key])) {
300                                        unset($old[$key]);
301                                }
302                        }
303                }
304                foreach ($new as $key => $var) {
305                        $old[$key] = $var;
306                }
307        }
308
309/**
310 * Return error description for given error number.
311 *
312 * @param integer $errorNumber Error to set
313 * @return string Error as string
314 * @access private
315 */
316        function __error($errorNumber) {
317                if (!is_array($this->error) || !array_key_exists($errorNumber, $this->error)) {
318                        return false;
319                } else {
320                        return $this->error[$errorNumber];
321                }
322        }
323
324/**
325 * Returns last occurred error as a string, if any.
326 *
327 * @return mixed Error description as a string, or false.
328 * @access public
329 */
330        function error() {
331                if ($this->lastError) {
332                        return $this->__error($this->lastError);
333                } else {
334                        return false;
335                }
336        }
337
338/**
339 * Returns true if session is valid.
340 *
341 * @return boolean Success
342 * @access public
343 */
344        function valid() {
345                if ($this->read('Config')) {
346                        if ((Configure::read('Session.checkAgent') === false || $this->_userAgent == $this->read('Config.userAgent')) && $this->time <= $this->read('Config.time')) {
347                                if ($this->error === false) {
348                                        $this->valid = true;
349                                }
350                        } else {
351                                $this->valid = false;
352                                $this->__setError(1, 'Session Highjacking Attempted !!!');
353                        }
354                }
355                return $this->valid;
356        }
357
358/**
359 * Returns given session variable, or all of them, if no parameters given.
360 *
361 * @param mixed $name The name of the session variable (or a path as sent to Set.extract)
362 * @return mixed The value of the session variable
363 * @access public
364 */
365        function read($name = null) {
366                if (is_null($name)) {
367                        return $this->__returnSessionVars();
368                }
369                if (empty($name)) {
370                        return false;
371                }
372                $result = Set::classicExtract($_SESSION, $name);
373
374                if (!is_null($result)) {
375                        return $result;
376                }
377                $this->__setError(2, "$name doesn't exist");
378                return null;
379        }
380
381/**
382 * Returns all session variables.
383 *
384 * @return mixed Full $_SESSION array, or false on error.
385 * @access private
386 */
387        function __returnSessionVars() {
388                if (!empty($_SESSION)) {
389                        return $_SESSION;
390                }
391                $this->__setError(2, "No Session vars set");
392                return false;
393        }
394
395/**
396 * Tells Session to write a notification when a certain session path or subpath is written to
397 *
398 * @param mixed $var The variable path to watch
399 * @return void
400 * @access public
401 */
402        function watch($var) {
403                if (empty($var)) {
404                        return false;
405                }
406                if (!in_array($var, $this->watchKeys, true)) {
407                        $this->watchKeys[] = $var;
408                }
409        }
410
411/**
412 * Tells Session to stop watching a given key path
413 *
414 * @param mixed $var The variable path to watch
415 * @return void
416 * @access public
417 */
418        function ignore($var) {
419                if (!in_array($var, $this->watchKeys)) {
420                        return;
421                }
422                foreach ($this->watchKeys as $i => $key) {
423                        if ($key == $var) {
424                                unset($this->watchKeys[$i]);
425                                $this->watchKeys = array_values($this->watchKeys);
426                                return;
427                        }
428                }
429        }
430
431/**
432 * Writes value to given session variable name.
433 *
434 * @param mixed $name Name of variable
435 * @param string $value Value to write
436 * @return boolean True if the write was successful, false if the write failed
437 * @access public
438 */
439        function write($name, $value) {
440                if (empty($name)) {
441                        return false;
442                }
443                if (in_array($name, $this->watchKeys)) {
444                        trigger_error(sprintf(__('Writing session key {%s}: %s', true), $name, Debugger::exportVar($value)), E_USER_NOTICE);
445                }
446                $this->__overwrite($_SESSION, Set::insert($_SESSION, $name, $value));
447                return (Set::classicExtract($_SESSION, $name) === $value);
448        }
449
450/**
451 * Helper method to destroy invalid sessions.
452 *
453 * @return void
454 * @access public
455 */
456        function destroy() {
457                if ($this->started()) {
458                        session_destroy();
459                }
460                $_SESSION = null;
461                $this->__construct($this->path);
462                $this->start();
463                $this->renew();
464                $this->_checkValid();
465        }
466
467/**
468 * Helper method to initialize a session, based on Cake core settings.
469 *
470 * @access private
471 */
472        function __initSession() {
473                $iniSet = function_exists('ini_set');
474                if ($iniSet && env('HTTPS')) {
475                        ini_set('session.cookie_secure', 1);
476                }
477                if ($iniSet && ($this->security === 'high' || $this->security === 'medium')) {
478                        ini_set('session.referer_check', $this->host);
479                }
480
481                if ($this->security == 'high') {
482                        $this->cookieLifeTime = 0;
483                } else {
484                        $this->cookieLifeTime = Configure::read('Session.timeout') * (Security::inactiveMins() * 60);
485                }
486
487                switch (Configure::read('Session.save')) {
488                        case 'cake':
489                                if (empty($_SESSION)) {
490                                        if ($iniSet) {
491                                                ini_set('session.use_trans_sid', 0);
492                                                ini_set('url_rewriter.tags', '');
493                                                ini_set('session.serialize_handler', 'php');
494                                                ini_set('session.use_cookies', 1);
495                                                ini_set('session.name', Configure::read('Session.cookie'));
496                                                ini_set('session.cookie_lifetime', $this->cookieLifeTime);
497                                                ini_set('session.cookie_path', $this->path);
498                                                ini_set('session.auto_start', 0);
499                                                ini_set('session.save_path', TMP . 'sessions');
500                                        }
501                                }
502                        break;
503                        case 'database':
504                                if (empty($_SESSION)) {
505                                        if (Configure::read('Session.model') === null) {
506                                                trigger_error(__("You must set the all Configure::write('Session.*') in core.php to use database storage"), E_USER_WARNING);
507                                                $this->_stop();
508                                        }
509                                        if ($iniSet) {
510                                                ini_set('session.use_trans_sid', 0);
511                                                ini_set('url_rewriter.tags', '');
512                                                ini_set('session.save_handler', 'user');
513                                                ini_set('session.serialize_handler', 'php');
514                                                ini_set('session.use_cookies', 1);
515                                                ini_set('session.name', Configure::read('Session.cookie'));
516                                                ini_set('session.cookie_lifetime', $this->cookieLifeTime);
517                                                ini_set('session.cookie_path', $this->path);
518                                                ini_set('session.auto_start', 0);
519                                        }
520                                }
521                                session_set_save_handler(
522                                        array('CakeSession','__open'),
523                                        array('CakeSession', '__close'),
524                                        array('CakeSession', '__read'),
525                                        array('CakeSession', '__write'),
526                                        array('CakeSession', '__destroy'),
527                                        array('CakeSession', '__gc')
528                                );
529                        break;
530                        case 'php':
531                                if (empty($_SESSION)) {
532                                        if ($iniSet) {
533                                                ini_set('session.use_trans_sid', 0);
534                                                ini_set('session.name', Configure::read('Session.cookie'));
535                                                ini_set('session.cookie_lifetime', $this->cookieLifeTime);
536                                                ini_set('session.cookie_path', $this->path);
537                                        }
538                                }
539                        break;
540                        case 'cache':
541                                if (empty($_SESSION)) {
542                                        if (!class_exists('Cache')) {
543                                                require LIBS . 'cache.php';
544                                        }
545                                        if ($iniSet) {
546                                                ini_set('session.use_trans_sid', 0);
547                                                ini_set('url_rewriter.tags', '');
548                                                ini_set('session.save_handler', 'user');
549                                                ini_set('session.use_cookies', 1);
550                                                ini_set('session.name', Configure::read('Session.cookie'));
551                                                ini_set('session.cookie_lifetime', $this->cookieLifeTime);
552                                                ini_set('session.cookie_path', $this->path);
553                                        }
554                                }
555                                session_set_save_handler(
556                                        array('CakeSession','__open'),
557                                        array('CakeSession', '__close'),
558                                        array('Cache', 'read'),
559                                        array('Cache', 'write'),
560                                        array('Cache', 'delete'),
561                                        array('Cache', 'gc')
562                                );
563                        break;
564                        default:
565                                $config = CONFIGS . Configure::read('Session.save') . '.php';
566
567                                if (is_file($config)) {
568                                        require($config);
569                                }
570                        break;
571                }
572        }
573
574/**
575 * Helper method to start a session
576 *
577 * @access private
578 */
579        function __startSession() {
580                if (headers_sent()) {
581                        if (empty($_SESSION)) {
582                                $_SESSION = array();
583                        }
584                        return true;
585                } elseif (!isset($_SESSION)) {
586                        session_cache_limiter ("must-revalidate");
587                        session_start();
588                        header ('P3P: CP="NOI ADM DEV PSAi COM NAV OUR OTRo STP IND DEM"');
589                        return true;
590                } else {
591                        session_start();
592                        return true;
593                }
594        }
595
596/**
597 * Helper method to create a new session.
598 *
599 * @return void
600 * @access protected
601 */
602        function _checkValid() {
603                if ($this->read('Config')) {
604                        if ((Configure::read('Session.checkAgent') === false || $this->_userAgent == $this->read('Config.userAgent')) && $this->time <= $this->read('Config.time')) {
605                                $time = $this->read('Config.time');
606                                $this->write('Config.time', $this->sessionTime);
607                                if (Configure::read('Security.level') === 'high') {
608                                        $check = $this->read('Config.timeout');
609                                        $check -= 1;
610                                        $this->write('Config.timeout', $check);
611
612                                        if (time() > ($time - (Security::inactiveMins() * Configure::read('Session.timeout')) + 2) || $check < 1) {
613                                                $this->renew();
614                                                $this->write('Config.timeout', 10);
615                                        }
616                                }
617                                $this->valid = true;
618                        } else {
619                                $this->destroy();
620                                $this->valid = false;
621                                $this->__setError(1, 'Session Highjacking Attempted !!!');
622                        }
623                } else {
624                        $this->write('Config.userAgent', $this->_userAgent);
625                        $this->write('Config.time', $this->sessionTime);
626                        $this->write('Config.timeout', 10);
627                        $this->valid = true;
628                        $this->__setError(1, 'Session is valid');
629                }
630        }
631
632/**
633 * Helper method to restart a session.
634 *
635 * @return void
636 * @access private
637 */
638        function __regenerateId() {
639                $oldSessionId = session_id();
640                if ($oldSessionId) {
641                        if (session_id() != ''|| isset($_COOKIE[session_name()])) {
642                                setcookie(Configure::read('Session.cookie'), '', time() - 42000, $this->path);
643                        }
644                        session_regenerate_id(true);
645                        if (PHP_VERSION < 5.1) {
646                                $sessionPath = session_save_path();
647                                if (empty($sessionPath)) {
648                                        $sessionPath = '/tmp';
649                                }
650                                $newSessid = session_id();
651
652                                if (function_exists('session_write_close')) {
653                                        session_write_close();
654                                }
655                                $this->__initSession();
656                                session_id($oldSessionId);
657                                session_start();
658                                session_destroy();
659                                $file = $sessionPath . DS . 'sess_' . $oldSessionId;
660                                @unlink($file);
661                                $this->__initSession();
662                                session_id($newSessid);
663                                session_start();
664                        }
665                }
666        }
667
668/**
669 * Restarts this session.
670 *
671 * @access public
672 */
673        function renew() {
674                $this->__regenerateId();
675        }
676
677/**
678 * Helper method to set an internal error message.
679 *
680 * @param integer $errorNumber Number of the error
681 * @param string $errorMessage Description of the error
682 * @return void
683 * @access private
684 */
685        function __setError($errorNumber, $errorMessage) {
686                if ($this->error === false) {
687                        $this->error = array();
688                }
689                $this->error[$errorNumber] = $errorMessage;
690                $this->lastError = $errorNumber;
691        }
692
693/**
694 * Method called on open of a database session.
695 *
696 * @return boolean Success
697 * @access private
698 */
699        function __open() {
700                return true;
701        }
702
703/**
704 * Method called on close of a database session.
705 *
706 * @return boolean Success
707 * @access private
708 */
709        function __close() {
710                $probability = mt_rand(1, 150);
711                if ($probability <= 3) {
712                        switch (Configure::read('Session.save')) {
713                                case 'cache':
714                                        Cache::gc();
715                                break;
716                                default:
717                                        CakeSession::__gc();
718                                break;
719                        }
720                }
721                return true;
722        }
723
724/**
725 * Method used to read from a database session.
726 *
727 * @param mixed $id The key of the value to read
728 * @return mixed The value of the key or false if it does not exist
729 * @access private
730 */
731        function __read($id) {
732                $model =& ClassRegistry::getObject('Session');
733
734                $row = $model->find('first', array(
735                        'conditions' => array($model->primaryKey => $id)
736                ));
737
738                if (empty($row[$model->alias]['data'])) {
739                        return false;
740                }
741
742                return $row[$model->alias]['data'];
743        }
744
745/**
746 * Helper function called on write for database sessions.
747 *
748 * @param integer $id ID that uniquely identifies session in database
749 * @param mixed $data The value of the data to be saved.
750 * @return boolean True for successful write, false otherwise.
751 * @access private
752 */
753        function __write($id, $data) {
754                if (!$id) {
755                        return false;
756                }
757                $expires = time() + Configure::read('Session.timeout') * Security::inactiveMins();
758                $model =& ClassRegistry::getObject('Session');
759                $return = $model->save(array($model->primaryKey => $id) + compact('data', 'expires'));
760                return $return;
761        }
762
763/**
764 * Method called on the destruction of a database session.
765 *
766 * @param integer $id ID that uniquely identifies session in database
767 * @return boolean True for successful delete, false otherwise.
768 * @access private
769 */
770        function __destroy($id) {
771                $model =& ClassRegistry::getObject('Session');
772                $return = $model->delete($id);
773
774                return $return;
775        }
776
777/**
778 * Helper function called on gc for database sessions.
779 *
780 * @param integer $expires Timestamp (defaults to current time)
781 * @return boolean Success
782 * @access private
783 */
784        function __gc($expires = null) {
785                $model =& ClassRegistry::getObject('Session');
786
787                if (!$expires) {
788                        $expires = time();
789                }
790
791                $return = $model->deleteAll(array($model->alias . ".expires <" => $expires), false, false);
792                return $return;
793        }
794}
Note: See TracBrowser for help on using the repository browser.