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