ILIAS  release_6 Revision v6.24-5-g0c8bfefb3b8
class.ilAuthProviderSaml.php
Go to the documentation of this file.
1<?php declare(strict_types=1);
2/* Copyright (c) 1998-2017 ILIAS open source, Extended GPL, see docs/LICENSE */
3
8{
10 protected $uid = '';
12 protected $attributes = [];
14 protected $return_to = '';
16 protected $idp;
18 protected $force_new_account = false;
20 protected $migration_account = '';
21
28 public function __construct(ilAuthFrontendCredentials $credentials, ?int $a_idp_id = null)
29 {
31
32 if (null === $a_idp_id || 0 === $a_idp_id) {
33 $this->idp = ilSamlIdp::getFirstActiveIdp();
34 } else {
35 $this->idp = ilSamlIdp::getInstanceByIdpId($a_idp_id);
36 }
37
39 $this->attributes = $credentials->getAttributes();
40 $this->return_to = $credentials->getReturnTo();
41 }
42 }
43
47 private function determineUidFromAttributes() : void
48 {
49 if (
50 !array_key_exists($this->idp->getUidClaim(), $this->attributes) ||
51 !is_array($this->attributes[$this->idp->getUidClaim()]) ||
52 !array_key_exists(0, $this->attributes[$this->idp->getUidClaim()]) ||
53 0 === strlen($this->attributes[$this->idp->getUidClaim()][0])
54 ) {
55 throw new ilException(sprintf(
56 'Could not find unique SAML attribute for the configured identifier: %s',
57 print_r($this->idp->getUidClaim(), true)
58 ));
59 }
60
61 $this->uid = $this->attributes[$this->idp->getUidClaim()][0];
62 }
63
68 {
69 if (!is_array($this->attributes) || 0 === count($this->attributes)) {
70 $this->getLogger()->warning('Could not parse any attributes from SAML response.');
71 $this->handleAuthenticationFail($status, 'err_wrong_login');
72
73 return false;
74 }
75
76 try {
78
79 return $this->handleSamlAuth($status);
80 } catch (ilException $e) {
81 $this->getLogger()->warning($e->getMessage());
82 $this->handleAuthenticationFail($status, 'err_wrong_login');
83
84 return false;
85 }
86 }
87
92 public function handleSamlAuth(ilAuthStatus $status) : bool
93 {
94 $update_auth_mode = false;
95
96 ilLoggerFactory::getLogger('auth')->debug(sprintf(
97 'Login observer called for SAML authentication request of ext_account "%s" and auth_mode "%s".',
98 $this->uid,
99 $this->getUserAuthModeName()
100 ));
101 ilLoggerFactory::getLogger('auth')->debug(sprintf('Target set to: %s', print_r($this->return_to, true)));
102 ilLoggerFactory::getLogger('auth')->debug(sprintf(
103 'Trying to find ext_account "%s" for auth_mode "%s".',
104 $this->uid,
105 $this->getUserAuthModeName()
106 ));
107
108 $internal_account = ilObjUser::_checkExternalAuthAccount(
109 $this->getUserAuthModeName(),
110 $this->uid,
111 false
112 );
113
114 if (!is_string($internal_account) || $internal_account === '') {
115 $update_auth_mode = true;
116
117 ilLoggerFactory::getLogger('auth')->debug(sprintf(
118 'Could not find ext_account "%s" for auth_mode "%s".',
119 $this->uid,
120 $this->getUserAuthModeName()
121 ));
122
123 $fallback_auth_mode = 'local';
124 ilLoggerFactory::getLogger('auth')->debug(sprintf(
125 'Trying to find ext_account "%s" for auth_mode "%s".',
126 $this->uid,
127 $fallback_auth_mode
128 ));
129 $internal_account = ilObjUser::_checkExternalAuthAccount($fallback_auth_mode, $this->uid, false);
130
131 $defaultAuth = AUTH_LOCAL;
132 if ($GLOBALS['DIC']['ilSetting']->get('auth_mode')) {
133 $defaultAuth = $GLOBALS['DIC']['ilSetting']->get('auth_mode');
134 }
135
136 if ((!is_string($internal_account) || 0 === strlen($internal_account)) && ($defaultAuth == AUTH_LOCAL || $defaultAuth == $this->getTriggerAuthMode())) {
137 ilLoggerFactory::getLogger('auth')->debug(sprintf(
138 'Could not find ext_account "%s" for auth_mode "%s".',
139 $this->uid,
140 $fallback_auth_mode
141 ));
142
143 $fallback_auth_mode = 'default';
144 ilLoggerFactory::getLogger('auth')->debug(sprintf(
145 'Trying to find ext_account "%s" for auth_mode "%s".',
146 $this->uid,
147 $fallback_auth_mode
148 ));
149 $internal_account = ilObjUser::_checkExternalAuthAccount($fallback_auth_mode, $this->uid, false);
150 }
151 }
152
153 if (is_string($internal_account) && $internal_account !== '') {
154 ilLoggerFactory::getLogger('auth')->debug(sprintf(
155 'Found user "%s" for ext_account "%s" in ILIAS database.',
156 $internal_account,
157 $this->uid
158 ));
159
160 if ($this->idp->isSynchronizationEnabled()) {
161 ilLoggerFactory::getLogger('auth')->debug(sprintf(
162 'SAML user synchronisation is enabled, so update existing user "%s" with ext_account "%s".',
163 $internal_account,
164 $this->uid
165 ));
166 $internal_account = $this->importUser($internal_account, $this->uid, $this->attributes);
167 }
168
169 if ($update_auth_mode) {
170 $usr_id = ilObjUser::_loginExists($internal_account);
171 if ($usr_id > 0) {
173 ilLoggerFactory::getLogger('auth')->debug(sprintf(
174 'SAML Switched auth_mode of user with login "%s" and ext_account "%s" to "%s".',
175 $internal_account,
176 $this->uid,
177 $this->getUserAuthModeName()
178 ));
179 } else {
180 ilLoggerFactory::getLogger('auth')->debug(sprintf(
181 'SAML Could not switch auth_mode of user with login "%s" and ext_account "%s" to "%s".',
182 $internal_account,
183 $this->uid,
184 $this->getUserAuthModeName()
185 ));
186 }
187 }
188
189 ilLoggerFactory::getLogger('auth')->debug(sprintf(
190 'Authentication succeeded: Found internal login "%s for ext_account "%s" and auth_mode "%s".',
191 $internal_account,
192 $this->uid,
193 $this->getUserAuthModeName()
194 ));
195
197 $status->setAuthenticatedUserId(ilObjUser::_lookupId($internal_account));
198 ilSession::set('used_external_auth', true);
199
200 return true;
201 }
202
203 ilLoggerFactory::getLogger('auth')->debug(sprintf(
204 'Could not find an existing user for ext_account "%s" for any relevant auth_mode.',
205 $this->uid
206 ));
207 if ($this->idp->isSynchronizationEnabled()) {
208 ilLoggerFactory::getLogger('auth')->debug(sprintf(
209 'SAML user synchronisation is enabled, so determine action for ext_account "%s" and auth_mode "%s".',
210 $this->uid,
211 $this->getUserAuthModeName()
212 ));
213 if ($this->idp->isAccountMigrationEnabled() && !$this->force_new_account) {
214 ilSession::set('tmp_attributes', $this->attributes);
215 ilSession::set('tmp_return_to', $this->return_to);
216
217 ilLoggerFactory::getLogger('auth')->debug(sprintf(
218 'Account migration is enabled, so redirecting ext_account "%s" to account migration screen.',
219 $this->uid
220 ));
221
222 $this->setExternalAccountName($this->uid);
224
225 return false;
226 }
227
228 $new_name = $this->importUser(null, $this->uid, $this->attributes);
229 ilLoggerFactory::getLogger('auth')->debug(sprintf(
230 'Created new user account with login "%s" and ext_account "%s".',
231 $new_name,
232 $this->uid
233 ));
234
235 ilSession::set('tmp_attributes', null);
236 ilSession::set('tmp_return_to', null);
237 ilSession::set('used_external_auth', true);
238
239 if (strlen($this->return_to)) {
240 $_GET['target'] = $this->return_to;
241 }
242
244 $status->setAuthenticatedUserId(ilObjUser::_lookupId($new_name));
245
246 return true;
247 }
248
249 ilLoggerFactory::getLogger('auth')->debug("SAML user synchronisation is not enabled, auth failed.");
250 $this->handleAuthenticationFail($status, 'err_auth_saml_no_ilias_user');
251
252 return false;
253 }
254
259 {
260 }
261
266 {
267 if (
268 0 === strlen($this->getCredentials()->getUsername()) ||
269 !is_array(ilSession::get('tmp_attributes')) ||
270 0 === count(ilSession::get('tmp_attributes'))
271 ) {
272 $this->getLogger()->warning('Cannot find user id for external account: ' . $this->getCredentials()->getUsername());
273 $this->handleAuthenticationFail($status, 'err_wrong_login');
274
275 return false;
276 }
277
278 $this->uid = $this->getCredentials()->getUsername();
279 $this->attributes = ilSession::get('tmp_attributes');
280 $this->return_to = ilSession::get('tmp_return_to');
281
282 $this->force_new_account = true;
283
284 return $this->handleSamlAuth($status);
285 }
286
291 public function setExternalAccountName(string $a_name) : void
292 {
293 $this->migration_account = $a_name;
294 }
295
299 public function getExternalAccountName()
300 {
302 }
303
307 public function getTriggerAuthMode()
308 {
309 return AUTH_SAML . '_' . $this->idp->getIdpId();
310 }
311
315 public function getUserAuthModeName()
316 {
317 return 'saml_' . $this->idp->getIdpId();
318 }
319
326 public function importUser(?string $a_internal_login, string $a_external_account, array $a_user_data = [])
327 {
328 $mapping = new ilExternalAuthUserAttributeMapping('saml', $this->idp->getIdpId());
329
330 $xml_writer = new ilXmlWriter();
331 $xml_writer->xmlStartTag('Users');
332 if (null === $a_internal_login) {
333 $login = $a_user_data[$this->idp->getLoginClaim()][0];
335
336 $xml_writer->xmlStartTag('User', ['Action' => 'Insert']);
337 $xml_writer->xmlElement('Login', [], $login);
338
339 $xml_writer->xmlElement('Role', [
340 'Id' => $this->idp->getDefaultRoleId(),
341 'Type' => 'Global',
342 'Action' => 'Assign'
343 ]);
344
345 $xml_writer->xmlElement('Active', [], "true");
346 $xml_writer->xmlElement('TimeLimitOwner', [], USER_FOLDER_ID);
347 $xml_writer->xmlElement('TimeLimitUnlimited', [], 1);
348 $xml_writer->xmlElement('TimeLimitFrom', [], time());
349 $xml_writer->xmlElement('TimeLimitUntil', [], time());
350 $xml_writer->xmlElement(
351 'AuthMode',
352 ['type' => $this->getUserAuthModeName()],
353 $this->getUserAuthModeName()
354 );
355 $xml_writer->xmlElement('ExternalAccount', [], $a_external_account);
356
358 } else {
359 $login = $a_internal_login;
360 $usr_id = ilObjUser::_lookupId($a_internal_login);
361
362 $xml_writer->xmlStartTag('User', ['Action' => 'Update', 'Id' => $usr_id]);
363
364 $loginClaim = $a_user_data[$this->idp->getLoginClaim()][0];
365 if (ilStr::strToLower($login) !== ilStr::strToLower($loginClaim)) {
366 $login = ilAuthUtils::_generateLogin($loginClaim);
367 $xml_writer->xmlElement('Login', [], $login);
368 }
369
370 $mapping = new ilExternalAuthUserUpdateAttributeMappingFilter($mapping);
371 }
372
373 foreach ($mapping as $rule) {
374 try {
375 $attributeValueParser = new ilSamlMappedUserAttributeValueParser($rule, $a_user_data);
376 $value = $attributeValueParser->parse();
377 $this->buildUserAttributeXml($xml_writer, $rule, $value);
378 } catch (ilSamlException $e) {
379 $this->getLogger()->warning($e->getMessage());
380 continue;
381 }
382 }
383
384 $xml_writer->xmlEndTag('User');
385 $xml_writer->xmlEndTag('Users');
386
387 ilLoggerFactory::getLogger('auth')->debug(sprintf(
388 'Started import of user "%s" with ext_account "%s" and auth_mode "%s".',
389 $login,
390 $a_external_account,
391 $this->getUserAuthModeName()
392 ));
393 $importParser = new ilUserImportParser();
394 $importParser->setXMLContent($xml_writer->xmlDumpMem(false));
395 $importParser->setRoleAssignment([
396 $this->idp->getDefaultRoleId() => $this->idp->getDefaultRoleId(),
397 ]);
398 $importParser->setFolderId(USER_FOLDER_ID);
399 $importParser->setUserMappingMode(IL_USER_MAPPING_ID);
400 $importParser->startParsing();
401
402 return $login;
403 }
404
410 protected function buildUserAttributeXml(
411 ilXmlWriter $xml_writer,
413 string $value
414 ) {
415 switch (strtolower($rule->getAttribute())) {
416 case 'gender':
417 switch (strtolower($value)) {
418 case 'n':
419 case 'neutral':
420 $xml_writer->xmlElement('Gender', [], 'n');
421 break;
422
423 case 'm':
424 case 'male':
425 $xml_writer->xmlElement('Gender', [], 'm');
426 break;
427
428 case 'f':
429 case 'female':
430 default:
431 $xml_writer->xmlElement('Gender', [], 'f');
432 break;
433 }
434 break;
435
436 case 'firstname':
437 $xml_writer->xmlElement('Firstname', [], $value);
438 break;
439
440 case 'lastname':
441 $xml_writer->xmlElement('Lastname', [], $value);
442 break;
443
444 case 'email':
445 $xml_writer->xmlElement('Email', [], $value);
446 break;
447
448 case 'institution':
449 $xml_writer->xmlElement('Institution', [], $value);
450 break;
451
452 case 'department':
453 $xml_writer->xmlElement('Department', [], $value);
454 break;
455
456 case 'hobby':
457 $xml_writer->xmlElement('Hobby', [], $value);
458 break;
459
460 case 'title':
461 $xml_writer->xmlElement('Title', [], $value);
462 break;
463
464 case 'street':
465 $xml_writer->xmlElement('Street', [], $value);
466 break;
467
468 case 'city':
469 $xml_writer->xmlElement('City', [], $value);
470 break;
471
472 case 'zipcode':
473 $xml_writer->xmlElement('PostalCode', [], $value);
474 break;
475
476 case 'country':
477 $xml_writer->xmlElement('Country', [], $value);
478 break;
479
480 case 'phone_office':
481 $xml_writer->xmlElement('PhoneOffice', [], $value);
482 break;
483
484 case 'phone_home':
485 $xml_writer->xmlElement('PhoneHome', [], $value);
486 break;
487
488 case 'phone_mobile':
489 $xml_writer->xmlElement('PhoneMobile', [], $value);
490 break;
491
492 case 'fax':
493 $xml_writer->xmlElement('Fax', [], $value);
494 break;
495
496 case 'referral_comment':
497 $xml_writer->xmlElement('Comment', [], $value);
498 break;
499
500 case 'matriculation':
501 $xml_writer->xmlElement('Matriculation', [], $value);
502 break;
503
504 case 'birthday':
505 $xml_writer->xmlElement('Birthday', [], $value);
506 break;
507
508 default:
509 if (substr($rule->getAttribute(), 0, 4) !== 'udf_') {
510 break;
511 }
512
513 $udf_data = explode('_', $rule->getAttribute());
514 if (!isset($udf_data[1])) {
515 break;
516 }
517
518 $definition = ilUserDefinedFields::_getInstance()->getDefinition($udf_data[1]);
519 $xml_writer->xmlElement(
520 'UserDefinedField',
521 ['Id' => $definition['il_id'], 'Name' => $definition['field_name']],
522 $value
523 );
524 break;
525 }
526 }
527}
if(!defined('PATH_SEPARATOR')) $GLOBALS['_PEAR_default_error_mode']
Definition: PEAR.php:64
$_GET["client_id"]
An exception for terminatinating execution or to throw for unit testing.
const AUTH_SAML
const AUTH_LOCAL
const USER_FOLDER_ID
Class ilObjUserFolder.
const IL_USER_MAPPING_ID
Class ilAuthFrontendCredentialsSaml.
Class ilAuthProviderSaml.
createNewAccount(ilAuthStatus $status)
Create new ILIAS account for external_account.
importUser(?string $a_internal_login, string $a_external_account, array $a_user_data=[])
setExternalAccountName(string $a_name)
Set external account name.
buildUserAttributeXml(ilXmlWriter $xml_writer, ilExternalAuthUserAttributeMappingRule $rule, string $value)
handleSamlAuth(ilAuthStatus $status)
getExternalAccountName()
Get external account name.string
getUserAuthModeName()
Get user auth mode name ldap_1 for ldap account migration with server id 1 apache for apache auth.
migrateAccount(ilAuthStatus $status)
Create new account.
doAuthentication(ilAuthStatus $status)
getTriggerAuthMode()
Get auth mode which triggered the account migration 2_1 for ldap account migration with server id 1 1...
__construct(ilAuthFrontendCredentials $credentials, ?int $a_idp_id=null)
ilAuthProviderSaml constructor.
Base class for authentication providers (radius, ldap, apache, ...)
getLogger()
Get logger.
handleAuthenticationFail(ilAuthStatus $status, $a_reason)
Handle failed authentication.
Auth status implementation.
setStatus($a_status)
Set auth status.
const STATUS_ACCOUNT_MIGRATION_REQUIRED
static _generateLogin($a_login)
generate free login by starting with a default string and adding postfix numbers
Base class for ILIAS Exception handling.
static getLogger($a_component_id)
Get component logger.
static _checkExternalAuthAccount($a_auth, $a_account, $tryFallback=true)
check whether external account and authentication method matches with a user
static _writeAuthMode($a_usr_id, $a_auth_mode)
static _lookupId($a_user_str)
Lookup id by login.
static _loginExists($a_login, $a_user_id=0)
check if a login name already exists You may exclude a user from the check by giving his user id as 2...
Class ilSamlException.
static getInstanceByIdpId(int $a_idp_id)
static getFirstActiveIdp()
static set($a_var, $a_val)
Set a value.
static get($a_var)
Get a value.
static strToLower($a_string)
Definition: class.ilStr.php:87
static _getInstance()
Get instance.
XML writer class.
xmlElement($tag, $attrs=null, $data=null, $encode=true, $escape=true)
Writes a basic element (no children, just textual content)
$login
Definition: cron.php:13
Standard interface for auth provider implementations.
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc