public function UserSyncEventSubscriber::onUserSync in SAML Authentication 4.x
Same name and namespace in other branches
- 8.3 src/EventSubscriber/UserSyncEventSubscriber.php \Drupal\samlauth\EventSubscriber\UserSyncEventSubscriber::onUserSync()
- 8.2 src/EventSubscriber/UserSyncEventSubscriber.php \Drupal\samlauth\EventSubscriber\UserSyncEventSubscriber::onUserSync()
Performs actions to synchronize users with SAML data on login.
Parameters
\Drupal\samlauth\Event\SamlauthUserSyncEvent $event: The event.
File
- src/
EventSubscriber/ UserSyncEventSubscriber.php, line 125
Class
- UserSyncEventSubscriber
- Event subscriber that synchronizes user properties on a user_sync event.
Namespace
Drupal\samlauth\EventSubscriberCode
public function onUserSync(SamlauthUserSyncEvent $event) {
// If the account is new, we are in the middle of a user save operation;
// the current user name is the authname as set by externalauth, and
// e-mail is not set yet.
$account = $event
->getAccount();
$fatal_errors = [];
// Synchronize username.
// @todo in v4, can/should we get rid of most of this validation code, and
// just call $account->validate() afterwards? (Because that supposedly
// also checks for duplicate e-mail addresses etc.) This should be in
// 'base' code, likely moved into externalauth if possible. (It's
// mentioned in #3132453. At the moment I think we should have an option
// for entity level validation, and keep field level validation in here.)
if ($account
->isNew() || $this->config
->get('sync_name')) {
// Get value from the SAML attribute whose name is configured in the
// samlauth module.
$name = $this
->getAttributeByConfig('user_name_attribute', $event);
if ($name && $name != $account
->getAccountName()) {
// Validate the username. This shouldn't be necessary to mitigate
// attacks; assuming our SAML setup is correct, noone can insert fake
// data here. It protects against SAML attribute misconfigurations.
// Invalid names will cancel the login / account creation. The code is
// copied from user_validate_name().
$definition = BaseFieldDefinition::create('string')
->addConstraint('UserName', []);
$data = $this->typedDataManager
->create($definition);
$data
->setValue($name);
$violations = $data
->validate();
if ($violations) {
foreach ($violations as $violation) {
$fatal_errors[] = $violation
->getMessage();
}
}
// Check if the username is not already taken by someone else. For new
// accounts this can happen if the 'map existing users' setting is off.
if (!$fatal_errors) {
$account_search = $this->entityTypeManager
->getStorage('user')
->loadByProperties([
'name' => $name,
]);
$existing_account = reset($account_search);
if (!$existing_account || $account
->id() == $existing_account
->id()) {
$account
->setUsername($name);
$event
->markAccountChanged();
}
else {
$error = 'An account with the username @username already exists.';
if ($account
->isNew()) {
$fatal_errors[] = $this
->t($error, [
'@username' => $name,
]);
}
else {
// We continue and keep the old name. A DSM should be OK here
// since login only happens interactively.
$error = "Error updating user name from SAML attribute: {$error}";
$this->logger
->error($error, [
'@username' => $name,
]);
$this->messenger
->addError($this
->t($error, [
'@username' => $name,
]));
}
}
}
}
}
// Synchronize e-mail.
if ($this->config
->get('user_mail_attribute') && ($account
->isNew() || $this->config
->get('sync_mail'))) {
$mail = $this
->getAttributeByConfig('user_mail_attribute', $event);
if ($mail) {
if ($mail != $account
->getEmail()) {
// Invalid e-mail cancels the login / account creation just like name.
if ($this->emailValidator
->isValid($mail)) {
$account
->setEmail($mail);
if ($account
->isNew()) {
// Externalauth sets init to a non e-mail value so we will fix it.
$account
->set('init', $mail);
}
$event
->markAccountChanged();
}
else {
$fatal_errors[] = $this
->t('Invalid e-mail address @mail', [
'@mail' => $mail,
]);
}
}
}
elseif ($account
->isNew() && !$account
->getEmail()) {
// We won't allow new accounts with empty e-mail. If a custom event
// subscriber wants to populate the e-mail, then (at least for now) it
// should be registered with a higher priority than this standard one.
$fatal_errors[] = $this
->t('Email address is not provided in SAML attribute.');
}
}
if ($fatal_errors) {
// Cancel the whole login process and/or account creation.
throw new \RuntimeException('Error(s) encountered during SAML attribute synchronization: ' . implode(' // ', $fatal_errors));
}
}