ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
class.ilAuthFrontend.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 {
23  public const string MIG_EXTERNAL_ACCOUNT = 'mig_ext_account';
24  public const string MIG_TRIGGER_AUTHMODE = 'mig_trigger_auth_mode';
25  public const string MIG_DESIRED_AUTHMODE = 'mig_desired_auth_mode';
26 
27  private ilLogger $logger;
29  private ilLanguage $lng;
30 
34  private array $providers;
37 
39 
43  public function __construct(ilAuthSession $session, ilAuthStatus $status, ilAuthCredentials $credentials, array $providers)
44  {
45  global $DIC;
46  $this->logger = $DIC->logger()->auth();
47  $this->settings = $DIC->settings();
48  $this->lng = $DIC->language();
49  $this->ilAppEventHandler = $DIC->event();
50 
51  $this->auth_session = $session;
52  $this->credentials = $credentials;
53  $this->status = $status;
54  $this->providers = $providers;
55 
56  $this->user_profile = new ilUserProfile();
57  }
58 
59  public function getAuthSession(): ilAuthSession
60  {
61  return $this->auth_session;
62  }
63 
64  public function getCredentials(): ilAuthCredentials
65  {
66  return $this->credentials;
67  }
68 
72  public function getProviders(): array
73  {
74  return $this->providers;
75  }
76 
77  public function getStatus(): ilAuthStatus
78  {
79  return $this->status;
80  }
81 
82  public function resetStatus(): void
83  {
84  $this->getStatus()->setStatus(ilAuthStatus::STATUS_UNDEFINED);
85  $this->getStatus()->setReason('');
86  $this->getStatus()->setAuthenticatedUserId(ANONYMOUS_USER_ID);
87  }
88 
89  public function migrateAccount(ilAuthSession $session): bool
90  {
91  if (!$session->isAuthenticated()) {
92  $this->logger->warning('Desired user account is not authenticated');
93  return false;
94  }
95  $user = ilObjectFactory::getInstanceByObjId($session->getUserId(), false);
96 
97  if (!$user instanceof ilObjUser) {
98  $this->logger->info('Cannot instantiate user account for account migration: ' . $session->getUserId());
99  return false;
100  }
101 
102  $user->setAuthMode(ilSession::get(static::MIG_DESIRED_AUTHMODE));
103 
104  $this->logger->debug('new auth mode is: ' . ilSession::get(self::MIG_DESIRED_AUTHMODE));
105 
106  $user->setExternalAccount(ilSession::get(static::MIG_EXTERNAL_ACCOUNT));
107  $user->update();
108 
109  foreach ($this->getProviders() as $provider) {
110  if (!$provider instanceof ilAuthProviderAccountMigrationInterface) {
111  $this->logger->warning('Provider: ' . get_class($provider) . ' does not support account migration.');
112  throw new InvalidArgumentException('Invalid auth provider given.');
113  }
114  $this->getCredentials()->setUsername(ilSession::get(static::MIG_EXTERNAL_ACCOUNT));
115  $provider->migrateAccount($this->getStatus());
117  return $this->handleAuthenticationSuccess($provider);
118  }
119  }
120  return $this->handleAuthenticationFail();
121  }
122 
123  public function migrateAccountNew(): bool
124  {
125  foreach ($this->providers as $provider) {
126  if (!$provider instanceof ilAuthProviderAccountMigrationInterface) {
127  $this->logger->warning('Provider: ' . get_class($provider) . ' does not support account migration.');
128  throw new InvalidArgumentException('Invalid auth provider given.');
129  }
130  $provider->createNewAccount($this->getStatus());
131 
132  if ($provider instanceof ilAuthProviderInterface &&
134  return $this->handleAuthenticationSuccess($provider);
135  }
136  }
137  return $this->handleAuthenticationFail();
138  }
139 
140 
141  public function authenticate(): bool
142  {
143  foreach ($this->getProviders() as $provider) {
144  $this->resetStatus();
145 
146  $this->logger->debug('Trying authentication against: ' . get_class($provider));
147 
148  $provider->doAuthentication($this->getStatus());
149 
150  $this->logger->debug('Authentication user id: ' . $this->getStatus()->getAuthenticatedUserId());
151 
152  switch ($this->getStatus()->getStatus()) {
154  return $this->handleAuthenticationSuccess($provider);
155 
157  $this->logger->notice('Account migration required.');
158  if ($provider instanceof ilAuthProviderAccountMigrationInterface) {
159  return $this->handleAccountMigration($provider);
160  }
161 
162  $this->logger->error('Authentication migratittion required but provider does not support interface' . get_class($provider));
163  break;
165  default:
166  $this->logger->debug('Authentication failed against: ' . get_class($provider));
167  break;
168  }
169  }
170  return $this->handleAuthenticationFail();
171  }
172 
174  {
175  $this->logger->debug('Trigger auth mode: ' . $provider->getTriggerAuthMode());
176  $this->logger->debug('Desired auth mode: ' . $provider->getUserAuthModeName());
177  $this->logger->debug('External account: ' . $provider->getExternalAccountName());
178 
179  $this->getStatus()->setAuthenticatedUserId(ANONYMOUS_USER_ID);
180  #$this->getStatus()->setStatus(ilAuthStatus::STATUS_AUTHENTICATED);
181 
182  ilSession::set(static::MIG_TRIGGER_AUTHMODE, $provider->getTriggerAuthMode());
183  ilSession::set(static::MIG_DESIRED_AUTHMODE, $provider->getUserAuthModeName());
184  ilSession::set(static::MIG_EXTERNAL_ACCOUNT, $provider->getExternalAccountName());
185 
187 
188  return true;
189  }
190 
192  {
193  $user = ilObjectFactory::getInstanceByObjId($this->getStatus()->getAuthenticatedUserId(), false);
194 
195  $this->getStatus()->setReason('auth_err_invalid_user_account');
196  // reset expired status
197  $this->getAuthSession()->setExpired(false);
198 
199  if (!$user instanceof ilObjUser) {
200  $this->logger->error('Cannot instantiate user account with id: ' . $this->getStatus()->getAuthenticatedUserId());
202  $this->getStatus()->setAuthenticatedUserId(ANONYMOUS_USER_ID);
203  return false;
204  }
205 
206  if (!$this->checkExceededLoginAttempts($user)) {
207  $this->logger->info('Authentication failed for inactive user with id and too may login attempts: ' . $this->getStatus()->getAuthenticatedUserId());
209  $this->getStatus()->setAuthenticatedUserId(ANONYMOUS_USER_ID);
210  return false;
211  }
212 
213  if (!$this->checkActivation($user)) {
214  $this->logger->info('Authentication failed for inactive user with id: ' . $this->getStatus()->getAuthenticatedUserId());
216  $this->getStatus()->setAuthenticatedUserId(ANONYMOUS_USER_ID);
217  return false;
218  }
219 
220  // time limit
221  if (!$this->checkTimeLimit($user)) {
222  $this->logger->info('Authentication failed (time limit restriction) for user with id: ' . $this->getStatus()->getAuthenticatedUserId());
223 
224  if ($this->settings->get('user_reactivate_code')) {
225  $this->logger->debug('Accout reactivation codes are active');
227  } else {
228  $this->logger->debug('Accout reactivation codes are inactive');
230  $this->getStatus()->setAuthenticatedUserId(ANONYMOUS_USER_ID);
231  }
232  return false;
233  }
234 
235  // ip check
236  if (!$this->checkIp($user)) {
237  $this->logger->info('Authentication failed (wrong ip) for user with id: ' . $this->getStatus()->getAuthenticatedUserId());
239  $this->getStatus()->setAuthenticatedUserId(ANONYMOUS_USER_ID);
240  return false;
241  }
242 
243  // check simultaneos logins
244  $this->logger->debug('Check simutaneous login');
245  if (!$this->checkSimultaneousLogins($user)) {
246  $this->logger->info('Authentication failed: simultaneous logins forbidden for user: ' . $this->getStatus()->getAuthenticatedUserId());
248  $this->getStatus()->setAuthenticatedUserId(ANONYMOUS_USER_ID);
249  return false;
250  }
251 
252  // check if profile is complete
253  if (
254  $this->user_profile->isProfileIncomplete($user) &&
257  ) {
258  ilLoggerFactory::getLogger('auth')->info('User profile is incomplete.');
259  $user->setProfileIncomplete(true);
260  $user->update();
261  }
262 
263  // redirects in case of error (session pool limit reached)
264  ilSessionControl::handleLoginEvent($user->getLogin(), $this->getAuthSession());
265 
266 
267  // @todo move to event handling
268  ilOnlineTracking::addUser($user->getId());
269 
270  $security_settings = ilSecuritySettings::_getInstance();
271 
272  // determine first login of user for setting an indicator
273  // which still is available in PersonalDesktop, Repository, ...
274  // (last login date is set to current date in next step)
275  if (
276  $security_settings->isPasswordChangeOnFirstLoginEnabled() &&
277  $user->getLastLogin() === ''
278  ) {
279  $user->resetLastPasswordChange();
280  }
281  $user->refreshLogin();
282 
283  if ($user->getLoginAttempts() > 0) {
284  $user->setLoginAttempts(0);
285  $user->update();
286  }
287 
288 
289  $this->logger->info('Successfully authenticated: ' . ilObjUser::_lookupLogin($this->getStatus()->getAuthenticatedUserId()));
290  $this->getAuthSession()->setAuthenticated(true, $this->getStatus()->getAuthenticatedUserId());
291 
293 
294  ilSession::set('orig_request_target', '');
295 
296 
297  // --- anonymous/registered user
298  if (PHP_SAPI !== 'cli') {
299  $this->logger->info(
300  'logged in as ' . $user->getLogin() .
301  ', remote:' . $_SERVER['REMOTE_ADDR'] . ':' . $_SERVER['REMOTE_PORT'] .
302  ', server:' . $_SERVER['SERVER_ADDR'] . ':' . $_SERVER['SERVER_PORT']
303  );
304  } else {
305  $this->logger->info(
306  'logged in as ' . $user->getLogin() . ' from CLI'
307  );
308  }
309 
310  // finally raise event
311  $this->ilAppEventHandler->raise(
312  'components/ILIAS/Authentication',
313  'afterLogin',
314  [
315  'username' => $user->getLogin()
316  ]
317  );
318 
319  $this->getStatus()->setReason('');
320  return true;
321  }
322 
323  protected function checkActivation(ilObjUser $user): bool
324  {
325  return $user->getActive();
326  }
327 
328  protected function checkExceededLoginAttempts(ilObjUser $user): bool
329  {
330  if ($user->getId() === ANONYMOUS_USER_ID) {
331  return true;
332  }
333 
334  $isInactive = !$user->getActive();
335  if (!$isInactive) {
336  return true;
337  }
338 
339  $security = ilSecuritySettings::_getInstance();
340  $maxLoginAttempts = $security->getLoginMaxAttempts();
341 
342  if (!$maxLoginAttempts) {
343  return true;
344  }
345 
346  $numLoginAttempts = \ilObjUser::_getLoginAttempts($user->getId());
347 
348  return $numLoginAttempts < $maxLoginAttempts;
349  }
350 
351  protected function checkTimeLimit(ilObjUser $user): bool
352  {
353  return $user->checkTimeLimit();
354  }
355 
356  protected function checkIp(ilObjUser $user): bool
357  {
358  $clientip = $user->getClientIP();
359  if (trim($clientip) !== '') {
360  $clientip = preg_replace('/[^0-9.?*,:]+/', '', $clientip);
361  $clientip = str_replace(['.', '?', '*', ','], ["\\.", '[0-9]', '[0-9]*', '|'], $clientip);
362 
363  ilLoggerFactory::getLogger('auth')->debug('Check ip ' . $clientip . ' against ' . $_SERVER['REMOTE_ADDR']);
364 
365  if (!preg_match('/^' . $clientip . '$/', $_SERVER['REMOTE_ADDR'])) {
366  return false;
367  }
368  }
369  return true;
370  }
371 
372  protected function checkSimultaneousLogins(ilObjUser $user): bool
373  {
374  $this->logger->debug('Setting prevent simultaneous session is: ' . $this->settings->get('ps_prevent_simultaneous_logins'));
375  return !($this->settings->get('ps_prevent_simultaneous_logins') &&
376  ilObjUser::hasActiveSession($user->getId(), $this->getAuthSession()->getId()));
377  }
378 
379  protected function handleAuthenticationFail(): bool
380  {
381  $this->logger->debug('Authentication failed for all authentication methods.');
382 
383  $this->handleLoginAttempts();
384 
385  return false;
386  }
387 
388  protected function handleLoginAttempts(): void
389  {
390  $security = ilSecuritySettings::_getInstance();
391  $max_attempts = $security->getLoginMaxAttempts();
392  if ($max_attempts < 1) {
393  return;
394  }
395 
396  $auth_determination = ilAuthModeDetermination::_getInstance();
397  if ($this->getCredentials()->getAuthMode() !== '') {
398  $auth_modes = [
399  $this->getCredentials()->getAuthMode()
400  ];
401  } else {
402  $auth_modes = $auth_determination->getAuthModeSequence($this->getCredentials()->getUsername());
403  }
404 
405  $usr_id_candidates = [];
406  foreach (array_filter($auth_modes) as $auth_mode) {
407  if ((int) $auth_mode === ilAuthUtils::AUTH_LOCAL) {
408  $usr_id_candidates[] = ilObjUser::_lookupId($this->getCredentials()->getUsername());
409  continue;
410  }
411 
413  ilAuthUtils::_getAuthModeName($auth_mode),
414  $this->getCredentials()->getUsername(),
415  false
416  );
417  if (!is_string($login) || $login === '') {
418  continue;
419  }
420 
421  $usr_id_candidates[] = ilObjUser::_lookupId($login);
422  }
423 
424  $usr_id_candidates = array_values(array_unique(array_filter($usr_id_candidates, intval(...))));
425  $num_deacticated_accounts = 0;
426  foreach ($usr_id_candidates as $usr_id) {
427  if ($usr_id === ANONYMOUS_USER_ID) {
428  continue;
429  }
430 
431  $num_login_attempts = ilObjUser::_getLoginAttempts($usr_id);
432 
433  if ($num_login_attempts <= $max_attempts) {
435  $this->logger->notice(
436  sprintf(
437  'Incremented login attempts for user %s with id %s.',
438  $this->getCredentials()->getUsername(),
439  $usr_id
440  )
441  );
442  }
443 
444  if ($num_login_attempts >= $max_attempts) {
446 
447  ++$num_deacticated_accounts;
448  $this->logger->warning(
449  sprintf(
450  'User account %s with id %s set to inactive due to exceeded login attempts.',
451  $this->getCredentials()->getUsername(),
452  $usr_id
453  )
454  );
455  }
456  }
457 
458  if ($num_deacticated_accounts > 0) {
459  $this->getStatus()->setReason('auth_err_login_attempts_deactivation');
460  }
461  }
462 }
static hasActiveSession(int $a_user_id, string $a_session_id)
const int CONTEXT_ECS
Calendar authentication with auth token.
migrateAccount(ilAuthSession $session)
static get(string $a_var)
Global event handler.
static _getLoginAttempts(int $a_usr_id)
static dumpToString()
checkIp(ilObjUser $user)
const ANONYMOUS_USER_ID
Definition: constants.php:27
static getLogger(string $a_component_id)
Get component logger.
handleAuthenticationSuccess(ilAuthProviderInterface $provider)
const string MIG_DESIRED_AUTHMODE
const string MIG_TRIGGER_AUTHMODE
const int STATUS_AUTHENTICATED
checkSimultaneousLogins(ilObjUser $user)
Class ilUserProfile.
const CONTEXT_LTI_PROVIDER
isAuthenticated()
Check if session is authenticated.
static _lookupId($a_user_str)
handleAccountMigration(ilAuthProviderAccountMigrationInterface $provider)
checkExceededLoginAttempts(ilObjUser $user)
static _checkExternalAuthAccount(string $a_auth, string $a_account, bool $tryFallback=true)
check whether external account and authentication method matches with a user
static _getAuthModeName($a_auth_key)
ilAuthSession $auth_session
getUserId()
Get authenticated user id.
$provider
Definition: ltitoken.php:80
static addUser(int $a_user_id)
const int AUTH_LOCAL
checkTimeLimit(ilObjUser $user)
checkActivation(ilObjUser $user)
static _setUserInactive(int $a_usr_id)
$_SERVER['HTTP_HOST']
Definition: raiseError.php:26
getTriggerAuthMode()
Get auth mode which triggered the account migration 2_1 for ldap account migration with server id 1 1...
global $DIC
Definition: shib_login.php:26
const int STATUS_CODE_ACTIVATION_REQUIRED
ilUserProfile $user_profile
const int STATUS_AUTHENTICATION_FAILED
ilAuthCredentials $credentials
static handleLoginEvent(string $a_login, ilAuthSession $auth_session)
static getInstanceByObjId(?int $obj_id, bool $stop_on_error=true)
get an instance of an Ilias object by object id
ilAppEventHandler $ilAppEventHandler
static _incrementLoginAttempts(int $a_usr_id)
__construct(ilAuthSession $session, ilAuthStatus $status, ilAuthCredentials $credentials, array $providers)
const int STATUS_UNDEFINED
static getType()
Get context type.
static initUserAccount()
Init user with current account id.
getExternalAccountName()
Get external account name.
const string MIG_EXTERNAL_ACCOUNT
static _getInstance()
Get instance of ilSecuritySettings.
static set(string $a_var, $a_val)
Set a value.
getUserAuthModeName()
Get user auth mode name ldap_1 for ldap account migration with server id 1 apache for apache auth...
raise(string $a_component, string $a_event, array $a_parameter=[])
Raise an event.
static _lookupLogin(int $a_user_id)
const int STATUS_ACCOUNT_MIGRATION_REQUIRED