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