fe0c4488bc6d8ea448feb502391a8c767bf098c5
[moodle.git] / lib / sessionlib.php
1 <?php  //$Id$
3 /**
4  * Factory method returning moodle_session object.
5  * @return moodle_session
6  */
7 function session_get_instance() {
8     static $session = null;
10     if (is_null($session)) {
11         $session = new moodle_session();
12     }
14     return $session;
15 }
17 /**
18  * Class handling all session and cookies related stuff.
19  */
20 class moodle_session {
21     public function __construct() {
22         global $CFG;
23         $this->prepare_cookies();
24         $this->init_session_storage();
26         if (!empty($CFG->usesid) && empty($_COOKIE['MoodleSession'.$CFG->sessioncookie])) {
27             sid_start_ob();
28         }
30         if (NO_MOODLE_COOKIES) {
31             $_SESSION = array();
32             $_SESSION['SESSION'] = new object();
33             $_SESSION['USER'] = new object();
35         } else {
36             session_name('MoodleSession'.$CFG->sessioncookie);
37             session_set_cookie_params(0, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
38             @session_start();
39             if (!isset($_SESSION['SESSION'])) {
40                 $_SESSION['SESSION'] = new object();
41             }
42             if (!isset($_SESSION['USER'])) {
43                 $_SESSION['USER'] = new object();
44             }
45         }
47         $this->check_user_initialised();
48     }
50     /**
51      * Initialise $USER object, handles google access.
52      *
53      * @return void
54      */
55     protected function check_user_initialised() {
56         if (isset($_SESSION['USER']->id)) {
57             // already set up $USER
58             return;
59         }
61         $user = null;
63         if (!empty($CFG->opentogoogle) and !NO_MOODLE_COOKIES) {
64             if (!empty($_SERVER['HTTP_USER_AGENT'])) {
65                 // allow web spiders in as guest users
66                 if (strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false ) {
67                     $user = guest_user();
68                 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'google.com') !== false ) { // Google
69                     $user = guest_user();
70                 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'Yahoo! Slurp') !== false ) {  // Yahoo
71                     $user = guest_user();
72                 } else if (strpos($_SERVER['HTTP_USER_AGENT'], '[ZSEBOT]') !== false ) {  // Zoomspider
73                     $user = guest_user();
74                 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSNBOT') !== false ) {  // MSN Search
75                     $user = guest_user();
76                 }
77             }
78             if (!$user and !empty($_SERVER['HTTP_REFERER'])) {
79                 // automaticaly log in users coming from search engine results
80                 if (strpos($_SERVER['HTTP_REFERER'], 'google') !== false ) {
81                     $user = guest_user();
82                 } else if (strpos($_SERVER['HTTP_REFERER'], 'altavista') !== false ) {
83                     $user = guest_user();
84                 }
85             }
86         }
88         if (!$user) {
89             $user = new object();
90             $user->id = 0; // to enable proper function of $CFG->notloggedinroleid hack
91             if (isset($CFG->mnet_localhost_id)) {
92                 $user->mnethostid = $CFG->mnet_localhost_id;
93             }
94         }
95         session_set_user($user);
96     }
98     /**
99      * Terminates active moodle session
100      */
101     public function terminate() {
102         global $CFG, $SESSION, $USER;
104         $_SESSION = array();
106         $SESSION  = new object();
107         $USER     = new object();
108         $USER->id = 0;
109         if (isset($CFG->mnet_localhost_id)) {
110             $USER->mnethostid = $CFG->mnet_localhost_id;
111         }
113         // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
114         $file = null;
115         $line = null;
116         if (headers_sent($file, $line)) {
117             error_log('Can not terminate session properly - headers were already sent in file: '.$file.' on line '.$line);
118         } else {
119             // TODO: regenerate session ID here
121         }
123         @session_write_close();
124     }
126     /**
127      * Prepare cookies and varions system settings
128      */
129     protected function prepare_cookies() {
130         global $CFG, $nomoodlecookie;
132         if (!defined('NO_MOODLE_COOKIES')) {
133             if (isset($nomoodlecookie)) {
134                 // backwards compatibility only
135                 define('NO_MOODLE_COOKIES', $nomoodlecookie);
136                 unset($nomoodlecookie);
137             } else {
138                 define('NO_MOODLE_COOKIES', false);
139             }
140         }
142         if (!isset($CFG->cookiesecure) or strpos($CFG->wwwroot, 'https://') !== 0) {
143             $CFG->cookiesecure = 0;
144         }
146         if (!isset($CFG->cookiehttponly)) {
147             $CFG->cookiehttponly = 0;
148         }
150     /// Set sessioncookie and sessioncookiepath variable if it isn't already
151         if (!isset($CFG->sessioncookie)) {
152             $CFG->sessioncookie = '';
153         }
154         if (!isset($CFG->sessioncookiedomain)) {
155             $CFG->sessioncookiedomain = '';
156         }
157         if (!isset($CFG->sessioncookiepath)) {
158             $CFG->sessioncookiepath = '/';
159         }
161         //discard session ID from POST, GET and globals to tighten security,
162         //this session fixation prevention can not be used in cookieless mode
163         if (empty($CFG->usesid)) {
164             unset(${'MoodleSession'.$CFG->sessioncookie});
165             unset($_GET['MoodleSession'.$CFG->sessioncookie]);
166             unset($_POST['MoodleSession'.$CFG->sessioncookie]);
167             unset($_REQUEST['MoodleSession'.$CFG->sessioncookie]);
168         }
169         //compatibility hack for Moodle Cron, cookies not deleted, but set to "deleted" - should not be needed with NO_MOODLE_COOKIES in cron.php now
170         if (!empty($_COOKIE['MoodleSession'.$CFG->sessioncookie]) && $_COOKIE['MoodleSession'.$CFG->sessioncookie] == "deleted") {
171             unset($_COOKIE['MoodleSession'.$CFG->sessioncookie]);
172         }
173     }
175     /**
176      * Inits session storage.
177      */
178     protected function init_session_storage() {
179         global $CFG;
181     /// Set up session handling
182         if(empty($CFG->respectsessionsettings)) {
183             if (true) {   /// File-based sessions
184                 // Some distros disable GC by setting probability to 0
185                 // overriding the PHP default of 1
186                 // (gc_probability is divided by gc_divisor, which defaults to 1000)
187                 if (ini_get('session.gc_probability') == 0) {
188                     ini_set('session.gc_probability', 1);
189                 }
191                 if (!empty($CFG->sessiontimeout)) {
192                     ini_set('session.gc_maxlifetime', $CFG->sessiontimeout);
193                 }
195                 if (!file_exists($CFG->dataroot .'/sessions')) {
196                     make_upload_directory('sessions');
197                 }
198                 ini_set('session.save_path', $CFG->dataroot .'/sessions');
200             } else {                         /// Database sessions
201                 // TODO: implement proper database session storage
202             }
203         }
204     }
207 /**
208  * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
209  * if one does not already exist, but does not overwrite existing sesskeys. Returns the
210  * sesskey string if $USER exists, or boolean false if not.
211  *
212  * @uses $USER
213  * @return string
214  */
215 function sesskey() {
216     global $USER;
218     if(!isset($USER)) {
219         return false;
220     }
222     if (empty($USER->sesskey)) {
223         $USER->sesskey = random_string(10);
224     }
226     return $USER->sesskey;
230 /**
231  * For security purposes, this function will check that the currently
232  * given sesskey (passed as a parameter to the script or this function)
233  * matches that of the current user.
234  *
235  * @param string $sesskey optionally provided sesskey
236  * @return bool
237  */
238 function confirm_sesskey($sesskey=NULL) {
239     global $USER;
241     if (!empty($USER->ignoresesskey) || !empty($CFG->ignoresesskey)) {
242         return true;
243     }
245     if (empty($sesskey)) {
246         $sesskey = required_param('sesskey', PARAM_RAW);  // Check script parameters
247     }
249     if (!isset($USER->sesskey)) {
250         return false;
251     }
253     return ($USER->sesskey === $sesskey);
256 /**
257  * Sets a moodle cookie with a weakly encrypted string
258  *
259  * @uses $CFG
260  * @uses DAYSECS
261  * @uses HOURSECS
262  * @param string $thing The string to encrypt and place in a cookie
263  */
264 function set_moodle_cookie($thing) {
265     global $CFG;
267     if ($thing == 'guest') {  // Ignore guest account
268         return;
269     }
271     $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
273     $days = 60;
274     $seconds = DAYSECS*$days;
276     // no need to set secure or http cookie only here - it is not secret
277     setcookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath, $CFG->sessioncookiedomain);
278     setcookie($cookiename, rc4encrypt($thing), time()+$seconds, $CFG->sessioncookiepath, $CFG->sessioncookiedomain);
281 /**
282  * Gets a moodle cookie with a weakly encrypted string
283  *
284  * @uses $CFG
285  * @return string
286  */
287 function get_moodle_cookie() {
288     global $CFG;
290     $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
292     if (empty($_COOKIE[$cookiename])) {
293         return '';
294     } else {
295         $thing = rc4decrypt($_COOKIE[$cookiename]);
296         return ($thing == 'guest') ? '': $thing;  // Ignore guest account
297     }
300 /**
301  * Setup $USER object - called during login, loginas, etc.
302  * Preloads capabilities and checks enrolment plugins
303  *
304  * @param object $user full user record object
305  * @return void
306  */
307 function session_set_user($user) {
308     $_SESSION['USER'] = $user;
309     check_enrolment_plugins($_SESSION['USER']);
310     load_all_capabilities();
313 /**
314  * Is current $USER logged-in-as somebody else?
315  * @return bool
316  */
317 function session_is_loggedinas() {
318     return !empty($_SESSION['USER']->realuser);
321 /**
322  * Returns the $USER object ignoring current login-as session
323  * @return object user object
324  */
325 function session_get_realuser() {
326     if (session_is_loggedinas()) {
327         return $_SESSION['REALUSER'];
328     } else {
329         return $_SESSION['USER'];
330     }
333 /**
334  * Login as another user - no security checks here.
335  * @param int $userid
336  * @param object $context
337  * @return void
338  */
339 function session_loginas($userid, $context) {
340     if (session_is_loggedinas()) {
341         return;
342     }
344     // switch to fresh new $SESSION
345     $_SESSION['REALSESSION'] = $_SESSION['SESSION'];
346     $_SESSION['SESSION']     = new object();
348     /// Create the new $USER object with all details and reload needed capabilitites
349     $_SESSION['REALUSER'] = $_SESSION['USER'];
350     $user = get_complete_user_data('id', $userid);
351     $user->realuser       = $_SESSION['REALUSER']->id;
352     $user->loginascontext = $context;
353     session_set_user($user);
356 /**
357  * Terminate login-as session
358  * @return void
359  */
360 function session_unloginas() {
361     if (!session_is_loggedinas()) {
362         return;
363     }
365     $_SESSION['SESSION'] = $_SESSION['REALSESSION'];
366     unset($_SESSION['REALSESSION']);
368     $_SESSION['USER'] = $_SESSION['REALUSER'];
369     unset($_SESSION['REALUSER']);
372 /**
373 * Enable cookieless sessions by including $CFG->usesid=true;
374 * in config.php.
375 * Based on code from php manual by Richard at postamble.co.uk
376 * Attempts to use cookies if cookies not present then uses session ids attached to all urls and forms to pass session id from page to page.
377 * If site is open to google, google is given guest access as usual and there are no sessions. No session ids will be attached to urls for googlebot.
378 * This doesn't require trans_sid to be turned on but this is recommended for better performance
379 * you should put :
380 * session.use_trans_sid = 1
381 * in your php.ini file and make sure that you don't have a line like this in your php.ini
382 * session.use_only_cookies = 1
383 * @author Richard at postamble.co.uk and Jamie Pratt
384 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
385 */
386 /**
387 * You won't call this function directly. This function is used to process
388 * text buffered by php in an output buffer. All output is run through this function
389 * before it is ouput.
390 * @param string $buffer is the output sent from php
391 * @return string the output sent to the browser
392 */
393 function sid_ob_rewrite($buffer){
394     $replacements = array(
395         '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")([^"]*)(")/i',
396         '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')([^\']*)(\')/i');
398     $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
399     $buffer = preg_replace('/<form\s[^>]*>/i',
400         '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
402       return $buffer;
404 /**
405 * You won't call this function directly. This function is used to process
406 * text buffered by php in an output buffer. All output is run through this function
407 * before it is ouput.
408 * This function only processes absolute urls, it is used when we decide that
409 * php is processing other urls itself but needs some help with internal absolute urls still.
410 * @param string $buffer is the output sent from php
411 * @return string the output sent to the browser
412 */
413 function sid_ob_rewrite_absolute($buffer){
414     $replacements = array(
415         '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")((?:http|https)[^"]*)(")/i',
416         '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')((?:http|https)[^\']*)(\')/i');
418     $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
419     $buffer = preg_replace('/<form\s[^>]*>/i',
420         '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
421     return $buffer;
424 /**
425 * A function to process link, a and script tags found
426 * by preg_replace_callback in {@link sid_ob_rewrite($buffer)}.
427 */
428 function sid_rewrite_link_tag($matches){
429     $url = $matches[4];
430     $url = sid_process_url($url);
431     return $matches[1].$url.$matches[5];
434 /**
435 * You can call this function directly. This function is used to process
436 * urls to add a moodle session id to the url for internal links.
437 * @param string $url is a url
438 * @return string the processed url
439 */
440 function sid_process_url($url) {
441     global $CFG;
443     if ((preg_match('/^(http|https):/i', $url)) // absolute url
444         &&  ((stripos($url, $CFG->wwwroot)!==0) && stripos($url, $CFG->httpswwwroot)!==0)) { // and not local one
445         return $url; //don't attach sessid to non local urls
446     }
447     if ($url[0]=='#' || (stripos($url, 'javascript:')===0)) {
448         return $url; //don't attach sessid to anchors
449     }
450     if (strpos($url, session_name())!==FALSE) {
451         return $url; //don't attach sessid to url that already has one sessid
452     }
453     if (strpos($url, "?")===FALSE) {
454         $append = "?".strip_tags(session_name() . '=' . session_id());
455     }    else {
456         $append = "&amp;".strip_tags(session_name() . '=' . session_id());
457     }
458     //put sessid before any anchor
459     $p = strpos($url, "#");
460     if ($p!==FALSE){
461         $anch = substr($url, $p);
462         $url = substr($url, 0, $p).$append.$anch ;
463     } else  {
464         $url .= $append ;
465     }
466     return $url;
469 /**
470 * Call this function before there has been any output to the browser to
471 * buffer output and add session ids to all internal links.
472 */
473 function sid_start_ob(){
474     global $CFG;
475     //don't attach sess id for bots
477     if (!empty($_SERVER['HTTP_USER_AGENT'])) {
478         if (!empty($CFG->opentogoogle)) {
479             if (strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false) {
480                 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
481                 $CFG->usesid=false;
482                 return;
483             }
484             if (strpos($_SERVER['HTTP_USER_AGENT'], 'google.com') !== false) {
485                 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
486                 $CFG->usesid=false;
487                 return;
488             }
489         }
490         if (strpos($_SERVER['HTTP_USER_AGENT'], 'W3C_Validator') !== false) {
491             @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
492             $CFG->usesid=false;
493             return;
494         }
495     }
497     @ini_set('session.use_trans_sid', '1'); // try and turn on trans_sid
499     if (ini_get('session.use_trans_sid') != 0) {
500         // use trans sid as its available
501         ini_set('url_rewriter.tags', 'a=href,area=href,script=src,link=href,frame=src,form=fakeentry');
502         ob_start('sid_ob_rewrite_absolute');
503     } else {
504         //rewrite all links ourselves
505         ob_start('sid_ob_rewrite');
506     }