public function UserFieldsEventSubscriber::onUserSync in SAML Authentication 8.3
Same name and namespace in other branches
- 4.x modules/samlauth_user_fields/src/EventSubscriber/UserFieldsEventSubscriber.php \Drupal\samlauth_user_fields\EventSubscriber\UserFieldsEventSubscriber::onUserSync()
Saves configured SAML attribute values into user fields.
Parameters
\Drupal\samlauth\Event\SamlauthUserSyncEvent $event: The event being dispatched.
File
- modules/
samlauth_user_fields/ src/ EventSubscriber/ UserFieldsEventSubscriber.php, line 229
Class
- UserFieldsEventSubscriber
- Synchronizes SAML attributes into user fields / links new users during login.
Namespace
Drupal\samlauth_user_fields\EventSubscriberCode
public function onUserSync(SamlauthUserSyncEvent $event) {
$account = $event
->getAccount();
$config = $this->configFactory
->get(static::CONFIG_OBJECT_NAME);
$mappings = $config
->get('field_mappings');
$validation_errors = [];
if (is_array($mappings)) {
$compound_field_values = [];
$changed_compound_field_values = [];
foreach ($mappings as $mapping) {
// If the attribute name is invalid, or the field does not exist, spam
// the logs on every login until the mapping is fixed.
if (empty($mapping['attribute_name']) || !is_string($mapping['attribute_name'])) {
$this->logger
->warning('Invalid SAML attribute %attribute detected in mapping; the mapping must be fixed.');
}
elseif (empty($mapping['field_name']) || !is_string($mapping['field_name'])) {
$this->logger
->warning('Invalid user field mapped from SAML attribute %attribute; the mapping must be fixed.', [
'%attribute' => $mapping['attribute_name'],
]);
}
// Skip silently if the configured attribute is not present in our
// data or if its value is considered 'empty / not updatable'.
$value = $this
->getUpdatableAttributeValue($mapping['attribute_name'], $event
->getAttributes());
if (isset($value)) {
$account_field_name = strstr($mapping['field_name'], ':', TRUE);
if ($account_field_name) {
$sub_field_name = substr($mapping['field_name'], strlen($account_field_name) + 1);
}
else {
$account_field_name = $mapping['field_name'];
$sub_field_name = '';
}
$field_definition = $account
->getFieldDefinition($account_field_name);
if (!$field_definition) {
$this->logger
->warning('User field %field is mapped from SAML attribute %attribute, but does not exist; the mapping must be fixed.', [
'%field' => $mapping['field_name'],
'%attribute' => $mapping['attribute_name'],
]);
}
elseif ($sub_field_name && $field_definition
->getType() !== 'address') {
// 'address' is the only compound field type we tested so far.
$this->logger
->warning('Unsuppoted user field type %type; skipping field mapping.', [
'%type' => $field_definition
->getType(),
]);
}
else {
if (!$sub_field_name) {
// Compare, validate, set single field.
if (!$this
->isInputValueEqual($value, $account
->get($account_field_name)->value, $account_field_name)) {
$valid = $this
->validateAccountFieldValue($value, $account, $mapping['field_name']);
if ($valid) {
$account
->set($mapping['field_name'], $value);
$event
->markAccountChanged();
}
else {
// Collect values to include below. Supposedly we have scalar
// values; var_export() shows their type. And identifier
// should include both source and destination because we can
// have multiple mappings defined for either.
$validation_errors[] = $mapping['attribute_name'] . ' (' . var_export($value, TRUE) . ') > ' . $mapping['field_name'];
}
}
}
else {
// Get/compare compound field; if it should be updated, set the
// changed field value aside for later validation, because
// validation needs to be done on the field as a whole, and other
// attributes may be mapped to other sub values.
if (!isset($compound_field_values[$account_field_name])) {
// TypedData: this only works with multivalue fields but I
// guess that's a given anyway. We can either get() the
// single value (specific object) or getValue() it, in which
// case we assume it's an array, for our purpose. In the former
// case, I guess
// - typedDataManager->create($field_definition, $input_value)
// would get us a new value if our field is NULL (which can
// happen)
// - validateAccountFieldValue() likely just works if we skip
// the create() call when $value is an object
// but I haven't tried that. So far we just work with arrays.
$compound_field_values[$account_field_name] = $account
->get($account_field_name)
->get(0)
->getValue() ?? [];
}
if (!$this
->isInputValueEqual($value, $compound_field_values[$account_field_name][$sub_field_name] ?? NULL, $mapping['field_name'])) {
$compound_field_values[$account_field_name][$sub_field_name] = $value;
// Just for logging if necessary:
$changed_compound_field_values[$account_field_name][] = $mapping['attribute_name'] . ' (' . var_export($value, TRUE) . ')';
}
// This would be a step toward working with objects - untested:
// TypedData uncertainty: get($sub_field_name) returns StringData
// for address subfields; get($sub_field_name)->getValue()
// returns the string. Both would be good for our current purpose
// provided that isInputValueEqual() could handle classes.
// if (!$this->isInputValueEqual($value, $account_field->get($sub_field_name)->getValue(), $mapping['field_name'])) {
// $account_field->setValue($sub_field_name, $value);
// $compound_field_values[$account_field_name] = $account_field;.
}
}
}
}
if ($compound_field_values) {
foreach ($compound_field_values as $field_name => $value) {
$valid = $this
->validateAccountFieldValue($value, $account, $field_name);
if ($valid) {
$account
->set($field_name, $value);
$event
->markAccountChanged();
}
else {
$validation_errors[] = implode(' + ', $changed_compound_field_values) . " > {$field_name}";
}
}
}
}
elseif (isset($mappings)) {
$this->logger
->warning('Invalid %name configuration value; skipping user synchronization.', [
'%name' => 'field_mappings',
]);
}
if ($validation_errors) {
// Log an extra message summarizing which values failed validation,
// because our field validation supposedly doesn't do that. The user is
// expected to see the correlation between the different log messages.
$this->logger
->warning('Validation errors were encountered while synchronizing SAML attributes into the user account: @values', [
'@values' => implode(', ', $validation_errors),
]);
}
}