ILIAS  release_8 Revision v8.19
All Data Structures Namespaces Files Functions Variables Modules Pages
class.ilSessionControl.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
25 {
30  public const DEFAULT_MAX_COUNT = 0;
31  public const DEFAULT_MIN_IDLE = 15;
32  public const DEFAULT_MAX_IDLE = 30;
35 
41  private static array $setting_fields = array(
42  'session_max_count',
43  'session_min_idle',
44  'session_max_idle',
45  'session_max_idle_after_first_request',
46  'session_allow_client_maintenance',
47  'session_handling_type'
48  );
49 
54  private const SESSION_TYPE_UNKNOWN = 0;
55  private const SESSION_TYPE_SYSTEM = 1;
56  private const SESSION_TYPE_ADMIN = 2;
57  private const SESSION_TYPE_USER = 3;
58  private const SESSION_TYPE_ANONYM = 4;
59 
60  private const SESSION_TYPE_KEY = "SessionType";
67  public static array $session_types_controlled = array(
68  self::SESSION_TYPE_USER,
69  self::SESSION_TYPE_ANONYM
70  );
71 
78  private static array $session_types_not_controlled = array(
79  self::SESSION_TYPE_UNKNOWN,
80  self::SESSION_TYPE_SYSTEM,
81  self::SESSION_TYPE_ADMIN
82  );
83 
89  public static function handleLoginEvent(string $a_login, ilAuthSession $auth_session): bool
90  {
91  global $DIC;
92 
93  $ilSetting = $DIC['ilSetting'];
94 
95  $user_id = ilObjUser::_lookupId($a_login);
96 
97  // we need the session type for the session statistics
98  // regardless of the current session handling type
99  switch (true) {
100  case isset($_ENV['SHELL']):
101  $type = self::SESSION_TYPE_SYSTEM;
102  break;
103 
104  case $user_id === ANONYMOUS_USER_ID:
105  $type = self::SESSION_TYPE_ANONYM;
106  break;
107 
108  case self::checkAdministrationPermission($user_id):
109  $type = self::SESSION_TYPE_ADMIN;
110  break;
111 
112  default:
113  $type = self::SESSION_TYPE_USER;
114  break;
115  }
116 
117  ilSession::set(self::SESSION_TYPE_KEY, $type);
118  self::debug(__METHOD__ . " --> update sessions type to (" . $type . ")");
119 
120  // do not handle login event in fixed duration mode
121  if ((int) $ilSetting->get('session_handling_type', (string) ilSession::SESSION_HANDLING_FIXED) !== ilSession::SESSION_HANDLING_LOAD_DEPENDENT) {
122  return true;
123  }
124 
125  if (in_array($type, self::$session_types_controlled, true)) {
126  //TODO rework this, as it did return value of a void method call
127  self::checkCurrentSessionIsAllowed($auth_session, $user_id);
128  return true;
129  }
130  return false;
131  }
132 
136  public static function handleLogoutEvent(): void
137  {
138  global $DIC;
139 
140  $ilSetting = $DIC['ilSetting'];
141 
142  // do not handle logout event in fixed duration mode
143  if ((int) $ilSetting->get('session_handling_type', '0') !== 1) {
144  return;
145  }
146 
147  ilSession::set('SessionType', self::SESSION_TYPE_UNKNOWN);
148  self::debug(__METHOD__ . " --> reset sessions type to (" . ilSession::get('SessionType') . ")");
149 
150  // session_destroy() is called in auth, so raw data will be updated
151 
152  self::removeSessionCookie();
153  }
154 
161  private static function checkCurrentSessionIsAllowed(ilAuthSession $auth, int $a_user_id): void
162  {
163  global $DIC;
164 
165  $ilSetting = $DIC['ilSetting'];
166 
167  $max_sessions = (int) $ilSetting->get('session_max_count', (string) self::DEFAULT_MAX_COUNT);
168 
169  if ($max_sessions > 0) {
170  // get total number of sessions
171  $num_sessions = self::getExistingSessionCount(self::$session_types_controlled);
172 
173  self::debug(__METHOD__ . "--> total existing sessions (" . $num_sessions . ")");
174 
175  if (($num_sessions + 1) > $max_sessions) {
176  self::debug(__METHOD__ . ' --> limit for session pool reached, but try kicking some first request abidencer');
177 
178  self::kickFirstRequestAbidencer(self::$session_types_controlled);
179 
180  // get total number of sessions again
181  $num_sessions = self::getExistingSessionCount(self::$session_types_controlled);
182 
183  if (($num_sessions + 1) > $max_sessions) {
184  self::debug(__METHOD__ . ' --> limit for session pool still reached so try kick one min idle session');
185 
186  self::kickOneMinIdleSession(self::$session_types_controlled);
187 
188  // get total number of sessions again
189  $num_sessions = self::getExistingSessionCount(self::$session_types_controlled);
190 
191  if (($num_sessions + 1) > $max_sessions) {
192  self::debug(__METHOD__ . ' --> limit for session pool still reached so logout session (' . session_id() . ') and trigger event');
193 
195 
196  // as the session is opened and closed in one request, there
197  // is no proper session yet and we have to do this ourselves
199  session_id(),
200  ilSession::get(self::SESSION_TYPE_KEY),
201  time(),
202  $a_user_id
203  );
204 
205  $auth->logout();
206 
207  // Trigger reachedSessionPoolLimit Event
208  $ilAppEventHandler = $DIC['ilAppEventHandler'];
209  $ilAppEventHandler->raise(
210  'Services/Authentication',
211  'reachedSessionPoolLimit',
212  array()
213  );
214 
215  // auth won't do this, we need to close session properly
216  // already done in new implementation
217  // session_destroy();
218 
219  ilUtil::redirect('login.php?reached_session_limit=true');
220  } else {
221  self::debug(__METHOD__ . ' --> limit of session pool not reached anymore after kicking one min idle session');
222  }
223  } else {
224  self::debug(__METHOD__ . ' --> limit of session pool not reached anymore after kicking some first request abidencer');
225  }
226  } else {
227  self::debug(__METHOD__ . ' --> limit for session pool not reached yet');
228  }
229  } else {
230  self::debug(__METHOD__ . ' --> limit for session pool not set so check is bypassed');
231  }
232  }
233 
237  public static function getExistingSessionCount(array $a_types): int
238  {
239  global $DIC;
240 
241  $ilDB = $DIC['ilDB'];
242 
243  $ts = time();
244 
245  $query = "SELECT count(session_id) AS num_sessions FROM usr_session " .
246  "WHERE expires > %s " .
247  "AND " . $ilDB->in('type', $a_types, false, 'integer');
248 
249  $res = $ilDB->queryF($query, array('integer'), array($ts));
250  return (int) $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)->num_sessions;
251  }
252 
258  private static function kickOneMinIdleSession(array $a_types): void
259  {
260  global $DIC;
261 
262  $ilDB = $DIC['ilDB'];
263  $ilSetting = $DIC['ilSetting'];
264 
265  $ts = time();
266  $min_idle = (int) $ilSetting->get('session_min_idle', (string) self::DEFAULT_MIN_IDLE) * 60;
267  $max_idle = (int) $ilSetting->get('session_max_idle', (string) self::DEFAULT_MAX_IDLE) * 60;
268 
269  $query = "SELECT session_id,expires FROM usr_session WHERE expires >= %s " .
270  "AND (expires - %s) < (%s - %s) " .
271  "AND " . $ilDB->in('type', $a_types, false, 'integer') . " ORDER BY expires";
272 
273  $res = $ilDB->queryF(
274  $query,
275  array('integer', 'integer', 'integer', 'integer'),
276  array($ts, $ts, $max_idle, $min_idle)
277  );
278 
279  if ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
280  ilSession::_destroy($row->session_id, ilSession::SESSION_CLOSE_IDLE, $row->expires);
281 
282  self::debug(__METHOD__ . ' --> successfully deleted one min idle session');
283 
284  return;
285  }
286  self::debug(__METHOD__ . ' --> no min idle session available for deletion');
287  }
288 
293  private static function kickFirstRequestAbidencer(array $a_types): void
294  {
295  global $DIC;
296 
297  $ilDB = $DIC['ilDB'];
298  $ilSetting = $DIC['ilSetting'];
299 
300  $max_idle_after_first_request = (int) $ilSetting->get('session_max_idle_after_first_request') * 60;
301 
302  if ((int) $max_idle_after_first_request === 0) {
303  return;
304  }
305 
306  $query = "SELECT session_id,expires FROM usr_session WHERE " .
307  "(ctime - createtime) < %s " .
308  "AND (%s - createtime) > %s " .
309  "AND " . $ilDB->in('type', $a_types, false, 'integer');
310 
311  $res = $ilDB->queryF(
312  $query,
313  array('integer', 'integer', 'integer'),
314  array($max_idle_after_first_request, time(), $max_idle_after_first_request)
315  );
316 
317  $session_ids = array();
318  while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
319  $session_ids[$row->session_id] = $row->expires;
320  }
322 
323  self::debug(__METHOD__ . ' --> Finished kicking first request abidencer');
324  }
325 
332  private static function isValidSession(string $a_sid): bool
333  {
334  global $DIC;
335 
336  $ilDB = $DIC['ilDB'];
337 
338  $query = "SELECT session_id, expires FROM usr_session " .
339  "WHERE session_id = %s";
340 
341  $res = $ilDB->queryF($query, array('text'), array($a_sid));
342 
343  $ts = time();
344 
345  $sessions = array();
346 
347  while ($row = $ilDB->fetchAssoc($res)) {
348  if ($row['expires'] > $ts) {
349  self::debug(__METHOD__ . ' --> Found a valid session with id (' . $a_sid . ')');
350  $sessions[] = $row;
351  } else {
352  self::debug(__METHOD__ . ' --> Found an expired session with id (' . $a_sid . ')');
353  }
354  }
355 
356  if (count($sessions) === 1) {
357  self::debug(__METHOD__ . ' --> Exact one valid session found for session id (' . $a_sid . ')');
358 
359  return true;
360  }
361 
362  if (count($sessions) > 1) {
363  self::debug(__METHOD__ . ' --> Strange!!! More than one sessions found for given session id! (' . $a_sid . ')');
364  } else {
365  self::debug(__METHOD__ . ' --> No valid session found for session id (' . $a_sid . ')');
366  }
367 
368  return false;
369  }
370 
374  private static function removeSessionCookie(): void
375  {
376  ilUtil::setCookie(session_name(), 'deleted', true, true);
377  self::debug('Session cookie has been removed');
378  }
379 
387  private static function checkAdministrationPermission(int $a_user_id): bool
388  {
389  if (!$a_user_id) {
390  return false;
391  }
392 
393  global $DIC;
394 
395  $rbacsystem = $DIC['rbacsystem'];
396 
397  $access = $rbacsystem->checkAccessOfUser(
398  $a_user_id,
399  'read,visible',
401  );
402 
403  return $access;
404  }
405 
411  private static function debug(string $a_debug_log_message): void
412  {
413  global $DIC;
414 
415  $logger = $DIC->logger()->auth();
416 
417  $logger->debug($a_debug_log_message);
418  }
419 
425  public static function getSettingFields(): array
426  {
427  return self::$setting_fields;
428  }
429 }
const SESSION_CLOSE_IDLE
const DEFAULT_MAX_COUNT
default value for settings that have not been defined in setup or administration yet ...
static array $setting_fields
all fieldnames that are saved in settings table
static get(string $a_var)
logout()
Logout user => stop session.
$res
Definition: ltiservices.php:69
static createRawEntry(string $a_session_id, int $a_session_type, int $a_timestamp, int $a_user_id)
Create raw data entry.
const ANONYMOUS_USER_ID
Definition: constants.php:27
$type
static removeSessionCookie()
removes a session cookie, so it is not sent by browser anymore
static checkAdministrationPermission(int $a_user_id)
checks wether a given user login relates to an user with administrative permissions ...
static _lookupId($a_user_str)
static getSettingFields()
returns the array of setting fields
const SESSION_HANDLING_FIXED
const SYSTEM_FOLDER_ID
Definition: constants.php:35
global $DIC
Definition: feed.php:28
$auth
Definition: metadata.php:76
static setCookie(string $a_cookie_name, string $a_cookie_value='', bool $a_also_set_super_global=true, bool $a_set_cookie_invalid=false)
$query
static isValidSession(string $a_sid)
checks if session exists for given id and if it is still valid
const SESSION_CLOSE_LIMIT
static redirect(string $a_script)
static getExistingSessionCount(array $a_types)
returns number of valid sessions relating to given session types
static handleLoginEvent(string $a_login, ilAuthSession $auth_session)
when current session is allowed to be created it marks it with type regarding to the sessions user co...
static _destroy($a_session_id, ?int $a_closing_context=null, $a_expired_at=null)
Destroy session.
static array $session_types_controlled
static array $session_types_not_controlled
all session types that will be involved when count of sessions will be determined or when idleing ses...
global $ilSetting
Definition: privfeed.php:17
static setClosingContext(int $a_context)
set closing context (for statistics)
const SESSION_HANDLING_LOAD_DEPENDENT
static handleLogoutEvent()
reset sessions type to unknown
static kickFirstRequestAbidencer(array $a_types)
kicks sessions of users that abidence after login so people could not login and go for coffe break ;-...
const SESSION_CLOSE_FIRST
static kickOneMinIdleSession(array $a_types)
if sessions exist that relates to given session types and idled longer than min idle parameter...
static set(string $a_var, $a_val)
Set a value.
static debug(string $a_debug_log_message)
logs the given debug message in
const SESSION_TYPE_UNKNOWN
session types from which one is assigned to each session
static checkCurrentSessionIsAllowed(ilAuthSession $auth, int $a_user_id)
checks wether the current session exhaust the limit of sessions when limit is reached it deletes "fir...