class UserSyncEventSubscriber in SAML Authentication 8.2
Same name and namespace in other branches
- 8.3 src/EventSubscriber/UserSyncEventSubscriber.php \Drupal\samlauth\EventSubscriber\UserSyncEventSubscriber
- 4.x src/EventSubscriber/UserSyncEventSubscriber.php \Drupal\samlauth\EventSubscriber\UserSyncEventSubscriber
Event subscriber that synchronizes user properties on a user_sync event.
This is basic module functionality, partially driven by config options. It's split out into an event subscriber so that the logic is easier to tweak for individual sites. (Set message or not? Completely break off login if an account with the same name is found, or continue with a non-renamed account? etc.)
Hierarchy
- class \Drupal\samlauth\EventSubscriber\UserSyncEventSubscriber implements \Symfony\Component\EventDispatcher\EventSubscriberInterface
Expanded class hierarchy of UserSyncEventSubscriber
1 string reference to 'UserSyncEventSubscriber'
1 service uses UserSyncEventSubscriber
File
- src/
EventSubscriber/ UserSyncEventSubscriber.php, line 24
Namespace
Drupal\samlauth\EventSubscriberView source
class UserSyncEventSubscriber implements EventSubscriberInterface {
/**
* The EntityTypeManager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The typed data manager.
*
* @var \Drupal\Core\TypedData\TypedDataManagerInterface
*/
protected $typedDataManager;
/**
* The email validator.
*
* @var \Egulias\EmailValidator\EmailValidator
*/
protected $emailValidator;
/**
* A logger instance.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* A configuration object containing samlauth settings.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected $config;
/**
* Construct a new SamlauthUserSyncSubscriber.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The EntityTypeManager service.
* @param \Egulias\EmailValidator\EmailValidator $email_validator
* The email validator.
* @param \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager
* The typed data manager.
* @param \Psr\Log\LoggerInterface $logger
* A logger instance.
*/
public function __construct(ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, TypedDataManagerInterface $typed_data_manager, EmailValidator $email_validator, LoggerInterface $logger) {
$this->entityTypeManager = $entity_type_manager;
$this->emailValidator = $email_validator;
$this->logger = $logger;
$this->typedDataManager = $typed_data_manager;
$this->config = $config_factory
->get('samlauth.authentication');
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[SamlauthEvents::USER_SYNC][] = [
'onUserSync',
];
return $events;
}
/**
* Performs actions to synchronize users with Factory data on login.
*
* @param \Drupal\samlauth\Event\SamlauthUserSyncEvent $event
* The event.
*/
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 'samlauth_AUTHNAME' (as set by externalauth) and
// e-mail is not set yet.
$account = $event
->getAccount();
$fatal_errors = [];
// Synchronize username.
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 = \Drupal::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(array(
'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[] = t($error, [
'@username' => $name,
]);
}
else {
// We continue and keep the old name. A DSM should be OK here
// since login only happens interactively. (And we're ignoring
// the law of dependency injection for this.)
$error = "Error updating user name from SAML attribute: {$error}";
$this->logger
->error($error, [
'@username' => $name,
]);
drupal_set_message(t($error, [
'@username' => $name,
]), 'error');
}
}
}
}
}
// Synchronize e-mail.
if ($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[] = t('Invalid e-mail address @mail', [
'@mail' => $mail,
]);
}
}
}
elseif ($account
->isNew()) {
// We won't allow new accounts with empty e-mail.
$fatal_errors[] = 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: ' . join(' // ', $fatal_errors));
}
}
/**
* Returns value from a SAML attribute whose name is configured in our module.
*
* This is suitable for single-value attributes. (Most values are.)
*
* @param string $config_key
* A key in the module's configuration, containing the name of a SAML
* attribute.
* @param \Drupal\samlauth\Event\SamlauthUserSyncEvent $event
* The event, which holds the attributes from the SAML response.
*
* @return mixed|null
* The SAML attribute value; NULL if the attribute value was not found.
*/
public function getAttributeByConfig($config_key, SamlauthUserSyncEvent $event) {
$attributes = $event
->getAttributes();
$attribute_name = $this->config
->get($config_key);
return $attribute_name && !empty($attributes[$attribute_name][0]) ? $attributes[$attribute_name][0] : NULL;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
UserSyncEventSubscriber:: |
protected | property | A configuration object containing samlauth settings. | |
UserSyncEventSubscriber:: |
protected | property | The email validator. | |
UserSyncEventSubscriber:: |
protected | property | The EntityTypeManager service. | |
UserSyncEventSubscriber:: |
protected | property | A logger instance. | |
UserSyncEventSubscriber:: |
protected | property | The typed data manager. | |
UserSyncEventSubscriber:: |
public | function | Returns value from a SAML attribute whose name is configured in our module. | |
UserSyncEventSubscriber:: |
public static | function | Returns an array of event names this subscriber wants to listen to. | |
UserSyncEventSubscriber:: |
public | function | Performs actions to synchronize users with Factory data on login. | |
UserSyncEventSubscriber:: |
public | function | Construct a new SamlauthUserSyncSubscriber. |