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 
54  if ($credentials instanceof \ilAuthFrontendCredentialsSaml) {
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 
299  $mapping = new ilExternalAuthUserCreationAttributeMappingFilter($mapping);
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 }
buildUserAttributeXml(\ilXmlWriter $xml_writer, \ilExternalAuthUserAttributeMappingRule $rule, $value)
__construct(\ilAuthFrontendCredentials $credentials, $a_idp_id=null)
ilAuthProviderSaml constructor.
createNewAccount(\ilAuthStatus $status)
Class ilSamlException.
static _getInstance()
Get instance.
$_GET["client_id"]
importUser($a_internal_login, $a_external_account, $a_user_data=array())
doAuthentication(\ilAuthStatus $status)
Do authentication.Authentication status bool
getExternalAccountName()
Get external account name.string
static _generateLogin($a_login)
generate free login by starting with a default string and adding postfix numbers
$GLOBALS['loaded']
Global hash that tracks already loaded includes.
static get($a_var)
Get a value.
XML writer class.
static set($a_var, $a_val)
Set a value.
static getInstanceByIdpId($a_idp_id)
static _lookupId($a_user_str)
Lookup id by login.
const AUTH_SAML
setAuthenticatedUserId($a_id)
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...
setExternalAccountName($a_name)
Set external account name.
Base class for authentication providers (radius, ldap, apache, ...)
Class ilAuthFrontendCredentialsSaml.
Standard interface for auth provider implementations.
Class ilExternalAuthUserAttributeMapping.
setStatus($a_status)
Set auth status.
$rule
Definition: showstats.php:43
const IL_USER_MAPPING_ID
migrateAccount(ilAuthStatus $status)
Create new account.
getUserAuthModeName()
Get user auth mode name ldap_1 for ldap account migration with server id 1 apache for apache auth...
const AUTH_LOCAL
getTriggerAuthMode()
Get auth mode which triggered the account migration 2_1 for ldap account migration with server id 1 1...
Create styles array
The data for the language used.
static _checkExternalAuthAccount($a_auth, $a_account, $tryFallback=true)
check whether external account and authentication method matches with a user
getLogger()
Get logger.
Class ilAuthProviderSaml.
xmlElement($tag, $attrs=null, $data=null, $encode=true, $escape=true)
Writes a basic element (no children, just textual content)
handleSamlAuth(\ilAuthStatus $status)
handleAuthenticationFail(ilAuthStatus $status, $a_reason)
Handle failed authentication.
const USER_FOLDER_ID
Class ilObjUserFolder.
static getLogger($a_component_id)
Get component logger.
Add data(end) time
Method that wraps PHPs time in order to allow simulations with the workflow.
static _writeAuthMode($a_usr_id, $a_auth_mode)
Class ilSamlMappedUserAttributeValueParser.
defined( 'APPLICATION_ENV')||define( 'APPLICATION_ENV'
Definition: bootstrap.php:27
Auth status implementation.
const STATUS_ACCOUNT_MIGRATION_REQUIRED
static getFirstActiveIdp()