ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
class.ilAuthProviderSaml.php
Go to the documentation of this file.
1<?php
2/* Copyright (c) 1998-2017 ILIAS open source, Extended GPL, see docs/LICENSE */
3
8{
12 protected $uid = '';
13
17 protected $attributes = array();
18
22 protected $return_to = '';
23
27 protected $idp;
28
32 protected $force_new_account = false;
33
37 protected $migration_account = '';
38
44 public function __construct(\ilAuthFrontendCredentials $credentials, $a_idp_id = null)
45 {
46 parent::__construct($credentials);
47
48 if (null === $a_idp_id) {
49 $this->idp = ilSamlIdp::getFirstActiveIdp();
50 } else {
51 $this->idp = ilSamlIdp::getInstanceByIdpId($a_idp_id);
52 }
53
55 $this->attributes = $credentials->getAttributes();
56 $this->return_to = $credentials->getReturnTo();
57 }
58 }
59
63 private function determineUidFromAttributes()
64 {
65 if (
66 !array_key_exists($this->idp->getUidClaim(), $this->attributes) ||
67 !is_array($this->attributes[$this->idp->getUidClaim()]) ||
68 !array_key_exists(0, $this->attributes[$this->idp->getUidClaim()]) ||
69 0 === strlen($this->attributes[$this->idp->getUidClaim()][0])
70 ) {
71 throw new \ilException(sprintf(
72 'Could not find unique SAML attribute for the configured identifier: %s',
73 var_export($this->idp->getUidClaim(), 1)
74 ));
75 }
76
77 $this->uid = $this->attributes[$this->idp->getUidClaim()][0];
78 }
79
84 {
85 if (!is_array($this->attributes) || 0 === count($this->attributes)) {
86 $this->getLogger()->warning('Could not parse any attributes from SAML response.');
87 $this->handleAuthenticationFail($status, 'err_wrong_login');
88 return false;
89 }
90
91 try {
93
94 return $this->handleSamlAuth($status);
95 } catch (\ilException $e) {
96 $this->getLogger()->warning($e->getMessage());
97 $this->handleAuthenticationFail($status, 'err_wrong_login');
98 return false;
99 }
100 }
101
107 {
108 $update_auth_mode = false;
109
110 ilLoggerFactory::getLogger('auth')->debug(sprintf('Login observer called for SAML authentication request of ext_account "%s" and auth_mode "%s".', $this->uid, $this->getUserAuthModeName()));
111 ilLoggerFactory::getLogger('auth')->debug(sprintf('Target set to: %s', var_export($this->return_to, 1)));
112 ilLoggerFactory::getLogger('auth')->debug(sprintf('Trying to find ext_account "%s" for auth_mode "%s".', $this->uid, $this->getUserAuthModeName()));
113
114 $internal_account = ilObjUser::_checkExternalAuthAccount(
115 $this->getUserAuthModeName(),
116 $this->uid,
117 false
118 );
119
120 if (strlen($internal_account) == 0) {
121 $update_auth_mode = true;
122
123 ilLoggerFactory::getLogger('auth')->debug(sprintf('Could not find ext_account "%s" for auth_mode "%s".', $this->uid, $this->getUserAuthModeName()));
124
125 $fallback_auth_mode = 'local';
126 ilLoggerFactory::getLogger('auth')->debug(sprintf('Trying to find ext_account "%s" for auth_mode "%s".', $this->uid, $fallback_auth_mode));
127 $internal_account = ilObjUser::_checkExternalAuthAccount($fallback_auth_mode, $this->uid, false);
128
129 if (!defined('AUTH_DEFAULT')) {
130 define('AUTH_DEFAULT', $GLOBALS['DIC']['ilSetting']->get('auth_mode') ? $GLOBALS['DIC']['ilSetting']->get('auth_mode') : AUTH_LOCAL);
131 }
132
133 if (strlen($internal_account) == 0 && (AUTH_DEFAULT == AUTH_LOCAL || AUTH_DEFAULT == $this->getTriggerAuthMode())) {
134 ilLoggerFactory::getLogger('auth')->debug(sprintf('Could not find ext_account "%s" for auth_mode "%s".', $this->uid, $fallback_auth_mode));
135
136 $fallback_auth_mode = 'default';
137 ilLoggerFactory::getLogger('auth')->debug(sprintf('Trying to find ext_account "%s" for auth_mode "%s".', $this->uid, $fallback_auth_mode));
138 $internal_account = ilObjUser::_checkExternalAuthAccount($fallback_auth_mode, $this->uid, false);
139 }
140 }
141
142 if (strlen($internal_account) > 0) {
143 ilLoggerFactory::getLogger('auth')->debug(sprintf('Found user "%s" for ext_account "%s" in ILIAS database.', $internal_account, $this->uid));
144
145 if ($this->idp->isSynchronizationEnabled()) {
146 ilLoggerFactory::getLogger('auth')->debug(sprintf('SAML user synchronisation is enabled, so update existing user "%s" with ext_account "%s".', $internal_account, $this->uid));
147 $internal_account = $this->importUser($internal_account, $this->uid, $this->attributes);
148 }
149
150 if ($update_auth_mode) {
151 $usr_id = ilObjUser::_loginExists($internal_account);
152 if ($usr_id > 0) {
154 ilLoggerFactory::getLogger('auth')->debug(sprintf('SAML Switched auth_mode of user with login "%s" and ext_account "%s" to "%s".', $internal_account, $this->uid, $this->getUserAuthModeName()));
155 } else {
156 ilLoggerFactory::getLogger('auth')->debug(sprintf('SAML Could not switch auth_mode of user with login "%s" and ext_account "%s" to "%s".', $internal_account, $this->uid, $this->getUserAuthModeName()));
157 }
158 }
159
160 ilLoggerFactory::getLogger('auth')->debug(sprintf('Authentication succeeded: Found internal login "%s for ext_account "%s" and auth_mode "%s".', $internal_account, $this->uid, $this->getUserAuthModeName()));
161
163 $status->setAuthenticatedUserId(ilObjUser::_lookupId($internal_account));
164 ilSession::set('used_external_auth', true);
165 return true;
166 } else {
167 ilLoggerFactory::getLogger('auth')->debug(sprintf('Could not find an existing user for ext_account "%s" for any relevant auth_mode.', $this->uid));
168 if ($this->idp->isSynchronizationEnabled()) {
169 ilLoggerFactory::getLogger('auth')->debug(sprintf('SAML user synchronisation is enabled, so determine action for ext_account "%s" and auth_mode "%s".', $this->uid, $this->getUserAuthModeName()));
170 if ($this->idp->isAccountMigrationEnabled() && !$this->force_new_account) {
171 ilSession::set('tmp_attributes', $this->attributes);
172 ilSession::set('tmp_return_to', $this->return_to);
173
174 ilLoggerFactory::getLogger('auth')->debug(sprintf('Account migration is enabled, so redirecting ext_account "%s" to account migration screen.', $this->uid));
175
176 $this->setExternalAccountName($this->uid);
178 return false;
179 }
180
181 $new_name = $this->importUser(null, $this->uid, $this->attributes);
182 ilLoggerFactory::getLogger('auth')->debug(sprintf('Created new user account with login "%s" and ext_account "%s".', $new_name, $this->uid));
183
184 ilSession::set('tmp_attributes', null);
185 ilSession::set('tmp_return_to', null);
186 ilSession::set('used_external_auth', true);
187
188 if (strlen($this->return_to)) {
189 $_GET['target'] = $this->return_to;
190 }
191
193 $status->setAuthenticatedUserId(ilObjUser::_lookupId($new_name));
194 return true;
195 } else {
196 ilLoggerFactory::getLogger('auth')->debug("SAML user synchronisation is not enabled, auth failed.");
197 $this->handleAuthenticationFail($status, 'err_auth_saml_no_ilias_user');
198 return false;
199 }
200 }
201 }
202
207 {
208 }
209
214 {
215 if (
216 0 === strlen($this->getCredentials()->getUsername()) ||
217 !is_array(ilSession::get('tmp_attributes')) ||
218 0 === count(ilSession::get('tmp_attributes'))
219 ) {
220 $this->getLogger()->warning('Cannot find user id for external account: ' . $this->getCredentials()->getUsername());
221 $this->handleAuthenticationFail($status, 'err_wrong_login');
222 return false;
223 }
224
225 $this->uid = $this->getCredentials()->getUsername();
226 $this->attributes = ilSession::get('tmp_attributes');
227 $this->return_to = ilSession::get('tmp_return_to');
228
229 $this->force_new_account = true;
230 return $this->handleSamlAuth($status);
231 }
232
237 public function setExternalAccountName($a_name)
238 {
239 $this->migration_account = $a_name;
240 }
241
245 public function getExternalAccountName()
246 {
248 }
249
253 public function getTriggerAuthMode()
254 {
255 return AUTH_SAML . '_' . $this->idp->getIdpId();
256 }
257
261 public function getUserAuthModeName()
262 {
263 return 'saml_' . $this->idp->getIdpId();
264 }
265
272 public function importUser($a_internal_login, $a_external_account, $a_user_data = array())
273 {
274 $mapping = new ilExternalAuthUserAttributeMapping('saml', $this->idp->getIdpId());
275
276 $xml_writer = new ilXmlWriter();
277 $xml_writer->xmlStartTag('Users');
278 if (null === $a_internal_login) {
279 $login = $a_user_data[$this->idp->getLoginClaim()][0];
280 $login = ilAuthUtils::_generateLogin($login);
281
282 $xml_writer->xmlStartTag('User', array('Action' => 'Insert'));
283 $xml_writer->xmlElement('Login', array(), $login);
284
285 $xml_writer->xmlElement('Role', array(
286 'Id' => $this->idp->getDefaultRoleId(),
287 'Type' => 'Global',
288 'Action' => 'Assign'
289 ));
290
291 $xml_writer->xmlElement('Active', array(), "true");
292 $xml_writer->xmlElement('TimeLimitOwner', array(), USER_FOLDER_ID);
293 $xml_writer->xmlElement('TimeLimitUnlimited', array(), 1);
294 $xml_writer->xmlElement('TimeLimitFrom', array(), time());
295 $xml_writer->xmlElement('TimeLimitUntil', array(), time());
296 $xml_writer->xmlElement('AuthMode', array('type' => $this->getUserAuthModeName()), $this->getUserAuthModeName());
297 $xml_writer->xmlElement('ExternalAccount', array(), $a_external_account);
298
300 } else {
301 $login = $a_internal_login;
302 $usr_id = ilObjUser::_lookupId($a_internal_login);
303
304 $xml_writer->xmlStartTag('User', array('Action' => 'Update', 'Id' => $usr_id));
305
306 $loginClaim = $a_user_data[$this->idp->getLoginClaim()][0];
307 if ($login != $loginClaim) {
308 $login = ilAuthUtils::_generateLogin($loginClaim);
309 $xml_writer->xmlElement('Login', array(), $login);
310 }
311
312 $mapping = new ilExternalAuthUserUpdateAttributeMappingFilter($mapping);
313 }
314
315 foreach ($mapping as $rule) {
316 try {
317 $attributeValueParser = new ilSamlMappedUserAttributeValueParser($rule, $a_user_data);
318 $value = $attributeValueParser->parse();
319 $this->buildUserAttributeXml($xml_writer, $rule, $value);
320 } catch (\ilSamlException $e) {
321 $this->getLogger()->warning($e->getMessage());
322 continue;
323 }
324 }
325
326 $xml_writer->xmlEndTag('User');
327 $xml_writer->xmlEndTag('Users');
328
329 ilLoggerFactory::getLogger('auth')->debug(sprintf('Started import of user "%s" with ext_account "%s" and auth_mode "%s".', $login, $a_external_account, $this->getUserAuthModeName()));
330 include_once './Services/User/classes/class.ilUserImportParser.php';
331 $importParser = new ilUserImportParser();
332 $importParser->setXMLContent($xml_writer->xmlDumpMem(false));
333 $importParser->setRoleAssignment(array(
334 $this->idp->getDefaultRoleId() => $this->idp->getDefaultRoleId()
335 ));
336 $importParser->setFolderId(USER_FOLDER_ID);
337 $importParser->setUserMappingMode(IL_USER_MAPPING_ID);
338 $importParser->startParsing();
339
340 return $login;
341 }
342
349 {
350 switch (strtolower($rule->getAttribute())) {
351 case 'gender':
352 switch (strtolower($value)) {
353 case 'n':
354 case 'neutral':
355 $xml_writer->xmlElement('Gender', array(), 'n');
356 break;
357
358 case 'm':
359 case 'male':
360 $xml_writer->xmlElement('Gender', array(), 'm');
361 break;
362
363 case 'f':
364 case 'female':
365 default:
366 $xml_writer->xmlElement('Gender', array(), 'f');
367 break;
368 }
369 break;
370
371 case 'firstname':
372 $xml_writer->xmlElement('Firstname', array(), $value);
373 break;
374
375 case 'lastname':
376 $xml_writer->xmlElement('Lastname', array(), $value);
377 break;
378
379 case 'email':
380 $xml_writer->xmlElement('Email', array(), $value);
381 break;
382
383 case 'institution':
384 $xml_writer->xmlElement('Institution', array(), $value);
385 break;
386
387 case 'department':
388 $xml_writer->xmlElement('Department', array(), $value);
389 break;
390
391 case 'hobby':
392 $xml_writer->xmlElement('Hobby', array(), $value);
393 break;
394
395 case 'title':
396 $xml_writer->xmlElement('Title', array(), $value);
397 break;
398
399 case 'street':
400 $xml_writer->xmlElement('Street', array(), $value);
401 break;
402
403 case 'city':
404 $xml_writer->xmlElement('City', array(), $value);
405 break;
406
407 case 'zipcode':
408 $xml_writer->xmlElement('PostalCode', array(), $value);
409 break;
410
411 case 'country':
412 $xml_writer->xmlElement('Country', array(), $value);
413 break;
414
415 case 'phone_office':
416 $xml_writer->xmlElement('PhoneOffice', array(), $value);
417 break;
418
419 case 'phone_home':
420 $xml_writer->xmlElement('PhoneHome', array(), $value);
421 break;
422
423 case 'phone_mobile':
424 $xml_writer->xmlElement('PhoneMobile', array(), $value);
425 break;
426
427 case 'fax':
428 $xml_writer->xmlElement('Fax', array(), $value);
429 break;
430
431 case 'referral_comment':
432 $xml_writer->xmlElement('Comment', array(), $value);
433 break;
434
435 case 'matriculation':
436 $xml_writer->xmlElement('Matriculation', array(), $value);
437 break;
438
439 case 'birthday':
440 $xml_writer->xmlElement('Birthday', array(), $value);
441 break;
442
443 default:
444 if (substr($rule->getAttribute(), 0, 4) != 'udf_') {
445 break;
446 }
447
448 $udf_data = explode('_', $rule->getAttribute());
449 if (!isset($udf_data[1])) {
450 break;
451 }
452
453 $definition = ilUserDefinedFields::_getInstance()->getDefinition($udf_data[1]);
454 $xml_writer->xmlElement(
455 'UserDefinedField',
456 array('Id' => $definition['il_id'], 'Name' => $definition['field_name']),
457 $value
458 );
459 break;
460 }
461 }
462}
sprintf('%.4f', $callTime)
$_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.
buildUserAttributeXml(\ilXmlWriter $xml_writer, \ilExternalAuthUserAttributeMappingRule $rule, $value)
__construct(\ilAuthFrontendCredentials $credentials, $a_idp_id=null)
ilAuthProviderSaml constructor.
setExternalAccountName($a_name)
Set external account name.
createNewAccount(\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.
doAuthentication(\ilAuthStatus $status)
Do authentication.bool
migrateAccount(ilAuthStatus $status)
Create new account.
importUser($a_internal_login, $a_external_account, $a_user_data=array())
handleSamlAuth(\ilAuthStatus $status)
getTriggerAuthMode()
Get auth mode which triggered the account migration 2_1 for ldap account migration with server id 1 1...
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($a_idp_id)
static getFirstActiveIdp()
static set($a_var, $a_val)
Set a value.
static get($a_var)
Get a value.
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)
$GLOBALS['loaded']
Global hash that tracks already loaded includes.
Standard interface for auth provider implementations.
defined( 'APPLICATION_ENV')||define( 'APPLICATION_ENV'
Definition: bootstrap.php:27
$rule
Definition: showstats.php:43