o365-2.0.3/modules/o365_sso/src/Controller/UserLoginController.php
modules/o365_sso/src/Controller/UserLoginController.php
<?php
namespace Drupal\o365_sso\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Url;
use Drupal\externalauth\ExternalAuth;
use Drupal\o365\AuthenticationService;
use Drupal\o365\GraphService;
use Drupal\o365\O365ConnectorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* UserLoginController. Used when returned from the callback.
*
* This creates/updates the user and logs in the Drupal user.
*/
class UserLoginController extends ControllerBase {
/**
* Drupal\o365\GraphService definition.
*
* @var \Drupal\o365\GraphService
*/
protected GraphService $graphService;
/**
* Drupal\o365\AuthenticationService definition.
*
* @var \Drupal\o365\AuthenticationService
*/
protected AuthenticationService $authenticationService;
/**
* The external auth service.
*
* @var \Drupal\externalauth\ExternalAuth
*/
protected ExternalAuth $externalAuth;
/**
* Constructs a new UserLoginController object.
*
* @param \Drupal\o365\GraphService $o365_graph
* The GraphService definition.
* @param \Drupal\o365\AuthenticationService $authenticationService
* The AuthenticationService definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The EntityTypeManager definition.
* @param \Drupal\externalauth\ExternalAuth $externalAuth
* The ExternalAuth definition.
*/
public function __construct(GraphService $o365_graph, AuthenticationService $authenticationService, EntityTypeManagerInterface $entity_type_manager, ExternalAuth $externalAuth) {
$this->graphService = $o365_graph;
$this->authenticationService = $authenticationService;
$this->entityTypeManager = $entity_type_manager;
$this->externalAuth = $externalAuth;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
// @phpstan-ignore-next-line
return new static($container->get('o365.graph'), $container->get('o365.authentication'), $container->get('entity_type.manager'), $container->get('externalauth.externalauth'));
}
/**
* Login a user.
*
* @param \Drupal\o365\O365ConnectorInterface $o365_connector
* The o365 connector interface.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* The redirect to the set URL in config.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\TempStore\TempStoreException
* @throws \League\OAuth2\Client\Provider\Exception\IdentityProviderException|\Drupal\externalauth\Exception\ExternalAuthRegisterException|\GuzzleHttp\Exception\GuzzleException
* @throws \Drupal\Core\Entity\EntityStorageException|\Microsoft\Graph\Exception\GraphException
*/
public function login(O365ConnectorInterface $o365_connector): RedirectResponse {
// Get config objects.
$ssoConfig = $this->config('o365_sso.settings');
// Get the mail property.
$mailProperty = $ssoConfig->get('email_property') ?? 'userPrincipalName';
if ($mailProperty === 'other') {
$mailProperty = $ssoConfig->get('email_property_other');
}
// Determine the fields we want to get for a user.
$select = $this->getFieldsForUser($mailProperty, TRUE);
// Get user data.
$userData = $this->graphService->getGraphData('/me?$select=' . $select);
// Get set config.
$useRedirectDestination = $ssoConfig->get('redirect_login_destination');
$onlyExistingUsers = $ssoConfig->get('only_existing_users');
$nameAttribute = $ssoConfig->get('username_attribute') ?? 'displayName';
$updateUsername = $ssoConfig->get('update_username') ?? FALSE;
// Get redirect url after login.
$redirectUrl = $o365_connector->getRedirectLogin();
// Check if we want to use the redirect destination.
if ($useRedirectDestination) {
$savedDestination = $this->authenticationService->getDataFromTempStore('destination');
$savedDestination = $savedDestination ?? '';
$firstChar = $savedDestination[0] ?? '';
$mandatoryChars = ['/', '?', '#'];
if (!empty($savedDestination) && in_array($firstChar, $mandatoryChars, TRUE)) {
$url = Url::fromUserInput($savedDestination, ['absolute' => TRUE]);
$redirectUrl = $url->toString();
}
}
// Get user unique identifier.
$o365_id = $userData['id'];
$account = $this->externalAuth->load($o365_id, 'o365_sso');
$email = strtolower($userData[$mailProperty]);
// Check for email match.
if (!$account) {
$account = $this->entityTypeManager->getStorage('user')
->loadByProperties(['mail' => $email]);
$account = !empty($account) ? reset($account) : FALSE;
}
if ($account) {
$this->externalAuth->linkExistingAccount($o365_id, 'o365_sso', $account);
}
// If we haven't found an account to link, create one from the data.
if (!$account && empty($onlyExistingUsers)) {
$accountData = [
'name' => $this->sanitizeUsername($userData[$nameAttribute]),
'mail' => $email,
];
$account = $this->externalAuth->register($o365_id, 'o365_sso', $accountData);
}
elseif (!$account && !empty($onlyExistingUsers)) {
$this->messenger()->addError($this->t('You do not have an account on this site. Please contact a administrator.'));
if (empty($ssoConfig->get('auto_redirect'))) {
return $this->redirect('user.login');
}
else {
return $this->redirect('<front>');
}
}
elseif ($account->isBlocked()) {
$this->messenger()->addError($this->t('The requested account is blocked'));
return $this->redirect('user.login');
}
// Log the user in.
$this->externalAuth->userLoginFinalize($account, $o365_id, 'o365_sso');
// Update the username if requested.
$newUsername = $this->sanitizeUsername($userData[$nameAttribute]);
if (!empty($updateUsername) && $account->getAccountName() != $newUsername) {
$account->setUsername($newUsername);
$account->save();
}
// Save the data from the url.
$this->authenticationService->saveAuthDataFromUrl();
// Return the redirect.
return new RedirectResponse($redirectUrl);
}
/**
* Sanitize username.
*
* @param string $username
* The username coming from o365.
*
* @return string
* The sanitized username.
*/
public function sanitizeUsername($username): string {
// Strip illegal characters.
return preg_replace('/[^\\x{80}-\\x{F7} a-zA-Z0-9@_.\'-]/', '', $username);
}
/**
* Get a list of the fields we need to retrieve during login for a user.
*
* @param string $mailProperty
* The mail property name.
* @param bool $implode
* If we want to implode the array.
*
* @return string[]|string
* The list of fields as an array or string.
*/
private function getFieldsForUser(string $mailProperty, bool $implode = TRUE): array|string {
$fields = [
'businessPhones',
'displayName',
'givenName',
'jobTitle',
'mail',
'mobilePhone',
'officeLocation',
'preferredLanguage',
'userPrincipalName',
'id',
];
if (!in_array($mailProperty, $fields)) {
$fields[] = $mailProperty;
}
if ($implode) {
return implode(',', $fields);
}
return $fields;
}
}
