public function SamlService::acs in SAML Authentication 8.2
Same name and namespace in other branches
- 8.3 src/SamlService.php \Drupal\samlauth\SamlService::acs()
- 8 src/SamlService.php \Drupal\samlauth\SamlService::acs()
- 4.x src/SamlService.php \Drupal\samlauth\SamlService::acs()
Processes a SAML response (Assertion Consumer Service).
First checks whether the SAML request is OK, then takes action on the Drupal user (logs in / maps existing / create new) depending on attributes sent in the request and our module configuration.
Throws
Exception
File
- src/
SamlService.php, line 166
Class
- SamlService
- Governs communication between the SAML toolkit and the IDP / login behavior.
Namespace
Drupal\samlauthCode
public function acs() {
// This call can either set an error condition or throw a
// \OneLogin_Saml2_Error exception, depending on whether or not we are
// processing a POST request. Don't catch the exception.
$this
->getSamlAuth()
->processResponse();
// Now look if there were any errors and also throw.
$errors = $this
->getSamlAuth()
->getErrors();
if (!empty($errors)) {
// We have one or multiple error types / short descriptions, and one
// 'reason' for the last error.
throw new RuntimeException('Error(s) encountered during processing of ACS response. Type(s): ' . implode(', ', array_unique($errors)) . '; reason given for last error: ' . $this
->getSamlAuth()
->getLastErrorReason());
}
if (!$this
->isAuthenticated()) {
throw new RuntimeException('Could not authenticate.');
}
$unique_id = $this
->getAttributeByConfig('unique_id_attribute');
if (!$unique_id) {
throw new Exception('Configured unique ID is not present in SAML response.');
}
$account = $this->externalAuth
->load($unique_id, 'samlauth');
if (!$account) {
$this->logger
->debug('No matching local users found for unique SAML ID @saml_id.', [
'@saml_id' => $unique_id,
]);
// Try to link an existing user: first through a custom event handler,
// then by name, then by e-mail.
if ($this->config
->get('map_users')) {
$event = new SamlauthUserLinkEvent($this
->getAttributes());
$this->eventDispatcher
->dispatch(SamlauthEvents::USER_LINK, $event);
$account = $event
->getLinkedAccount();
if (!$account) {
// The linking by name / e-mail cannot be bypassed at this point
// because it makes no sense to create a new account from the SAML
// attributes if one of these two basic properties is already in use.
// (In this case a newly created and logged-in account would get a
// cryptic machine name because synchronizeUserAttributes() cannot
// assign the proper name while saving.)
$name = $this
->getAttributeByConfig('user_name_attribute');
if ($name && ($account_search = $this->entityTypeManager
->getStorage('user')
->loadByProperties([
'name' => $name,
]))) {
$account = reset($account_search);
$this->logger
->info('Matching local user @uid found for name @name (as provided in a SAML attribute); associating user and logging in.', [
'@name' => $name,
'@uid' => $account
->id(),
]);
}
else {
$mail = $this
->getAttributeByConfig('user_mail_attribute');
if ($mail && ($account_search = $this->entityTypeManager
->getStorage('user')
->loadByProperties([
'mail' => $mail,
]))) {
$account = reset($account_search);
$this->logger
->info('Matching local user @uid found for e-mail @mail (as provided in a SAML attribute); associating user and logging in.', [
'@mail' => $mail,
'@uid' => $account
->id(),
]);
}
}
}
}
if ($account) {
// There is a chance that the following call will not actually link the
// account (if a mapping to this account already exists from another
// unique ID). If that happens, it does not matter much to us; we will
// just log the account in anyway. Next time the same not-yet-linked
// user logs in, we will again try to link the account in the same way
// and (falsely) log that we are associating the user.
$this->externalAuth
->linkExistingAccount($unique_id, 'samlauth', $account);
}
}
// If we haven't found an account to link, create one from the SAML
// attributes.
if (!$account) {
if ($this->config
->get('create_users')) {
// The register() call will save the account. We want to:
// - add values from the SAML response into the user account;
// - not save the account twice (because if the second save fails we do
// not want to end up with a user account in an undetermined state);
// - reuse code (i.e. call synchronizeUserAttributes() with its current
// signature, which is also done when an existing user logs in).
// Because of the third point, we are not passing the necessary SAML
// attributes into register()'s $account_data parameter, but we want to
// hook into the save operation of the user account object that is
// created by register(). It seems we can only do this by implementing
// hook_user_presave() - which calls our synchronizeUserAttributes().
$account = $this->externalAuth
->register($unique_id, 'samlauth');
$this->externalAuth
->userLoginFinalize($account, $unique_id, 'samlauth');
}
else {
throw new RuntimeException('No existing user account matches the SAML ID provided. This authentication service is not configured to create new accounts.');
}
}
elseif ($account
->isBlocked()) {
throw new RuntimeException('Requested account is blocked.');
}
else {
// Synchronize the user account with SAML attributes if needed.
$this
->synchronizeUserAttributes($account);
$this->externalAuth
->userLoginFinalize($account, $unique_id, 'samlauth');
}
// Set some request properties in local private storage. We can use these on
// logout.
foreach ([
'session_index' => $this->samlAuth
->getSessionIndex(),
'session_expiration' => $this->samlAuth
->getSessionExpiration(),
'name_id' => $this->samlAuth
->getNameId(),
'name_id_format' => $this->samlAuth
->getNameIdFormat(),
] as $key => $value) {
if (isset($value)) {
$this->privateTempStore
->set($key, $value);
}
else {
$this->privateTempStore
->delete($key);
}
}
}