ILIAS  Release_4_4_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
class.ilSessionControl.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (c) 1998-2009 ILIAS open source, Extended GPL, see docs/LICENSE */
3 
4 
12 {
17  const INTERNAL_DEBUG = false;
18 
23  const DEFAULT_MAX_COUNT = 0;
24  const DEFAULT_MIN_IDLE = 15;
25  const DEFAULT_MAX_IDLE = 30;
28 
34  private static $setting_fields = array(
35  'session_max_count',
36  'session_min_idle',
37  'session_max_idle',
38  'session_max_idle_after_first_request',
39  'session_allow_client_maintenance',
40  'session_handling_type'
41  );
42 
49  const SESSION_TYPE_ADMIN = 2;
50  const SESSION_TYPE_USER = 3;
52 
59  public static $session_types_controlled = array(
60  self::SESSION_TYPE_USER,
61  self::SESSION_TYPE_ANONYM
62  );
63 
70  private static $session_types_not_controlled = array(
71  self::SESSION_TYPE_UNKNOWN,
72  self::SESSION_TYPE_SYSTEM,
73  self::SESSION_TYPE_ADMIN
74  );
75 
85  public static function checkExpiredSession()
86  {
87  global $ilSetting;
88 
89  // do not check session in fixed duration mode
90  if( $ilSetting->get('session_handling_type', 0) != 1 )
91  return;
92 
93  // check for expired sessions makes sense
94  // only when public section is not enabled
95  // because it is not possible to determine
96  // wether the sid cookie relates to a session of an
97  // authenticated user or a anonymous user
98  // when the session dataset has allready been deleted
99 
100  if(!$ilSetting->get("pub_section"))
101  {
102  global $lng;
103 
104  $sid = null;
105 
106  if( !isset($_COOKIE[session_name()]) || !strlen($_COOKIE[session_name()]) )
107  {
108  self::debug('Browser did not send a sid cookie');
109  }
110  else
111  {
112  $sid = $_COOKIE[session_name()];
113 
114  self::debug('Browser sent sid cookie with value ('.$sid.')');
115 
116  if(!self::isValidSession($sid))
117  {
118  self::debug('remove session cookie for ('.$sid.') and trigger event');
119 
120  // raw data will be updated (later) with garbage collection [destroyExpired()]
121 
123 
124  // Trigger expiredSessionDetected Event
125  global $ilAppEventHandler;
126  $ilAppEventHandler->raise(
127  'Services/Authentication', 'expiredSessionDetected', array()
128  );
129 
130  ilUtil::redirect('login.php?expired=true'.'&target='.$_GET['target']);
131  }
132  }
133  }
134  }
135 
140  public static function initSession()
141  {
142  global $ilSetting;
143 
144  // do not init session type in fixed duration mode
145  if( $ilSetting->get('session_handling_type', 0) != 1 )
146  return;
147 
148  if( !isset($_SESSION['SessionType']) )
149  {
150  $_SESSION['SessionType'] = self::SESSION_TYPE_UNKNOWN;
151  self::debug(__METHOD__." --> init session with type (".$_SESSION['SessionType'].")");
152  }
153  else
154  {
155  self::debug(__METHOD__." --> keep sessions type on (".$_SESSION['SessionType'].")");
156  }
157  }
158 
164  public static function handleLoginEvent($a_login, $a_auth)
165  {
166  global $ilSetting;
167 
168  require_once 'Services/User/classes/class.ilObjUser.php';
169  $user_id = ilObjUser::_lookupId($a_login);
170 
171  // we need the session type for the session statistics
172  // regardless of the current session handling type
173  switch(true)
174  {
175  case isset($_ENV['SHELL']):
176  $type = self::SESSION_TYPE_SYSTEM;
177  break;
178 
179  case $user_id == ANONYMOUS_USER_ID:
181  break;
182 
184  $type = self::SESSION_TYPE_ADMIN;
185  break;
186 
187  default:
188  $type = self::SESSION_TYPE_USER;
189  break;
190  }
191 
192  $_SESSION['SessionType'] = $type;
193  self::debug(__METHOD__." --> update sessions type to (".$type.")");
194 
195  // do not handle login event in fixed duration mode
196  if( $ilSetting->get('session_handling_type', 0) != 1 )
197  return;
198 
199  if(in_array($type, self::$session_types_controlled))
200  {
201  self::checkCurrentSessionIsAllowed($a_auth, $user_id);
202  }
203  }
204 
208  public static function handleLogoutEvent()
209  {
210  global $ilSetting;
211 
212  // do not handle logout event in fixed duration mode
213  if( $ilSetting->get('session_handling_type', 0) != 1 )
214  return;
215 
216  $_SESSION['SessionType'] = self::SESSION_TYPE_UNKNOWN;
217  self::debug(__METHOD__." --> reset sessions type to (".$_SESSION['SessionType'].")");
218 
219  // session_destroy() is called in auth, so raw data will be updated
220 
222  }
223 
234  private static function checkCurrentSessionIsAllowed(Auth $a_auth, $a_user_id)
235  {
236  global $ilSetting;
237 
238  $max_sessions = (int)$ilSetting->get('session_max_count', DEFAULT_MAX_COUNT);
239 
240  if($max_sessions > 0)
241  {
242  // get total number of sessions
243  $num_sessions = self::getExistingSessionCount(self::$session_types_controlled);
244 
245  self::debug(__METHOD__."--> total existing sessions (".$num_sessions.")");
246 
247  if(($num_sessions + 1) > $max_sessions)
248  {
249  self::debug(__METHOD__.' --> limit for session pool reached, but try kicking some first request abidencer');
250 
251  self::kickFirstRequestAbidencer(self::$session_types_controlled);
252 
253  // get total number of sessions again
254  $num_sessions = self::getExistingSessionCount(self::$session_types_controlled);
255 
256  if(($num_sessions + 1) > $max_sessions)
257  {
258  self::debug(__METHOD__.' --> limit for session pool still reached so try kick one min idle session');
259 
260  self::kickOneMinIdleSession(self::$session_types_controlled);
261 
262  // get total number of sessions again
263  $num_sessions = self::getExistingSessionCount(self::$session_types_controlled);
264 
265  if(($num_sessions + 1) > $max_sessions)
266  {
267  self::debug(__METHOD__.' --> limit for session pool still reached so logout session ('.session_id().') and trigger event');
268 
270 
271  // as the session is opened and closed in one request, there
272  // is no proper session yet and we have to do this ourselves
273  ilSessionStatistics::createRawEntry(session_id(), $_SESSION['SessionType'],
274  time(), $a_user_id);
275 
276  $a_auth->logout();
277 
278  // Trigger reachedSessionPoolLimit Event
279  global $ilAppEventHandler;
280  $ilAppEventHandler->raise(
281  'Services/Authentication', 'reachedSessionPoolLimit', array()
282  );
283 
284  // auth won't do this, we need to close session properly
285  session_destroy();
286 
287  ilUtil::redirect('login.php?reached_session_limit=true');
288  }
289  else
290  {
291  self::debug(__METHOD__.' --> limit of session pool not reached anymore after kicking one min idle session');
292  }
293  }
294  else
295  {
296  self::debug(__METHOD__.' --> limit of session pool not reached anymore after kicking some first request abidencer');
297  }
298  }
299  else
300  {
301  self::debug(__METHOD__.' --> limit for session pool not reached yet');
302  }
303  }
304  else
305  {
306  self::debug(__METHOD__.' --> limit for session pool not set so check is bypassed');
307  }
308  }
309 
317  public static function getExistingSessionCount(array $a_types)
318  {
319  global $ilDB;
320 
321  $ts = time();
322 
323  $query = "SELECT count(session_id) AS num_sessions FROM usr_session ".
324  "WHERE expires > %s ".
325  "AND ".$ilDB->in('type', $a_types, false, 'integer');
326 
327  $res = $ilDB->queryF($query, array('integer'), array($ts));
328  $row = $res->fetchRow(DB_FETCHMODE_OBJECT);
329 
330  return $row->num_sessions;
331  }
332 
343  private static function kickOneMinIdleSession(array $a_types)
344  {
345  global $ilDB, $ilSetting;
346 
347  $ts = time();
348  $min_idle = (int)$ilSetting->get('session_min_idle', self::DEFAULT_MIN_IDLE) * 60;
349  $max_idle = (int)$ilSetting->get('session_max_idle', self::DEFAULT_MAX_IDLE) * 60;
350 
351  $query = "SELECT session_id,expires FROM usr_session WHERE expires >= %s " .
352  "AND (expires - %s) < (%s - %s) " .
353  "AND ".$ilDB->in('type', $a_types, false, 'integer');
354  "ORDER BY expires";
355 
356  $res = $ilDB->queryF(
357  $query,
358  array('integer', 'integer', 'integer', 'integer'),
359  array($ts, $ts, $max_idle, $min_idle)
360  );
361 
362  while( $row = $res->fetchRow(DB_FETCHMODE_OBJECT) )
363  {
365 
366  self::debug(__METHOD__.' --> successfully deleted one min idle session');
367 
368  return true;
369  }
370 
371  self::debug(__METHOD__.' --> no min idle session available for deletion');
372 
373  return false;
374  }
375 
384  private static function kickFirstRequestAbidencer(array $a_types)
385  {
386  global $ilDB, $ilSetting;
387 
388  $max_idle_after_first_request = (int)$ilSetting->get('session_max_idle_after_first_request') * 60;
389 
390  if((int)$max_idle_after_first_request == 0) return;
391 
392  $query = "SELECT session_id,expires FROM usr_session WHERE " .
393  "(ctime - createtime) < %s " .
394  "AND (%s - createtime) > %s " .
395  "AND ".$ilDB->in('type', $a_types, false, 'integer');
396 
397  $res = $ilDB->queryF( $query,
398  array('integer', 'integer', 'integer'),
399  array($max_idle_after_first_request, time(), $max_idle_after_first_request)
400  );
401 
402  $session_ids = array();
403  while( $row = $res->fetchRow(DB_FETCHMODE_OBJECT) )
404  {
405  $session_ids[$row->session_id] = $row->expires;
406  }
408 
409  self::debug(__METHOD__.' --> Finished kicking first request abidencer');
410  }
411 
421  private static function isValidSession($a_sid)
422  {
423  global $ilDB, $ilSetting;
424 
425  $query = "SELECT session_id, expires FROM usr_session ".
426  "WHERE session_id = %s";
427 
428  $res = $ilDB->queryF($query, array('text'), array($a_sid));
429 
430  $ts = time();
431 
432  $sessions = array();
433 
434  while( $row = $ilDB->fetchAssoc($res) )
435  {
436  if( $row['expires'] > $ts )
437  {
438  self::debug(__METHOD__.' --> Found a valid session with id ('.$a_sid.')');
439  $sessions[] = $row;
440  }
441  else
442  {
443  self::debug(__METHOD__.' --> Found an expired session with id ('.$a_sid.')');
444  }
445  }
446 
447  if(count($sessions) == 1)
448  {
449  self::debug(__METHOD__.' --> Exact one valid session found for session id ('.$a_sid.')');
450 
451  return true;
452  }
453  else
454  {
455  if(count($sessions) > 1)
456  self::debug(__METHOD__.' --> Strange!!! More than one sessions found for given session id! ('.$a_sid.')');
457  else self::debug(__METHOD__.' --> No valid session found for session id ('.$a_sid.')');
458 
459  return false;
460  }
461  }
462 
466  private static function removeSessionCookie()
467  {
468  ilUtil::setCookie(session_name(),'deleted',true,true);
469  self::debug('Session cookie has been removed');
470  }
471 
480  private static function checkAdministrationPermission($a_user_id)
481  {
482  if( !(int)$a_user_id ) return false;
483 
484  global $rbacsystem;
485 
486  $access = $rbacsystem->checkAccessOfUser(
487  $a_user_id, 'read,visible', SYSTEM_FOLDER_ID
488  );
489 
490  return $access;
491  }
492 
499  private static function debug($a_debug_log_message)
500  {
501  global $ilLog;
502 
503  if(DEVMODE) $ilLog->write($a_debug_log_message, 'message');
504 
505  if(self::INTERNAL_DEBUG) error_log($a_debug_log_message."\n", 3, 'session.log');
506  }
507 
513  public static function getSettingFields()
514  {
515  return self::$setting_fields;
516  }
517 
518 }
519 
520 
521 ?>