o365-2.0.3/src/AuthenticationService.php

src/AuthenticationService.php
<?php

namespace Drupal\o365;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Messenger\Messenger;
use Drupal\Core\Routing\RedirectDestinationTrait;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\Core\Url;
use Drupal\externalauth\AuthmapInterface;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Provider\GenericProvider;
use League\OAuth2\Client\Token\AccessTokenInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

/**
 * Service used to authenticate users between Microsoft 365 and Drupal.
 */
class AuthenticationService implements AuthenticationServiceInterface {

  use RedirectDestinationTrait;
  use StringTranslationTrait;

  /**
   * The config factory interface.
   */
  protected ConfigFactoryInterface $configFactory;

  /**
   * The modules base config.
   */
  protected ImmutableConfig $moduleConfig;

  /**
   * The o365_sso config.
   */
  protected ImmutableConfig $ssoConfig;

  /**
   * The private temp store.
   */
  protected PrivateTempStoreFactory $tempStore;

  /**
   * An oauth provider.
   */
  protected GenericProvider $oauthClient;

  /**
   * The ConstantsService implementation.
   */
  protected ConstantsService $constants;

  /**
   * The logger service.
   */
  protected O365LoggerServiceInterface $loggerService;

  /**
   * The current request.
   */
  protected Request $request;

  /**
   * If we want to add debug messages.
   */
  private bool $debug;

  /**
   * The auth data.
   */
  private array $authValues = [];

  /**
   * The current user account.
   */
  protected AccountProxyInterface $currentUser;

  /**
   * The auth map.
   */
  protected AuthmapInterface $authmap;

  /**
   * The o365 helper service.
   */
  protected HelperService $helperService;

  /**
   * The messenger.
   */
  protected Messenger $messenger;

  /**
   * The entity type manager.
   */
  protected EntityTypeManagerInterface $entityTypeManager;

  /**
   * Constructs a new AuthenticationService object.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   The config factory interface.
   * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempStoreFactory
   *   The private store factory.
   * @param \Drupal\o365\ConstantsService $constantsService
   *   The constants service from the o365 module.
   * @param \Drupal\o365\O365LoggerServiceInterface $loggerService
   *   The logger service from the o365 module.
   * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
   *   The request stack.
   * @param \Drupal\o365\HelperService $helperService
   *   The helper service used to get the api settings.
   * @param \Drupal\Core\Session\AccountProxyInterface $accountProxy
   *   The account proxy for the current user.
   * @param \Drupal\externalauth\AuthmapInterface $authmap
   *   The auth map.
   * @param \Drupal\Core\Messenger\Messenger $messenger
   *   The messenger class.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   */
  public function __construct(ConfigFactoryInterface $configFactory, PrivateTempStoreFactory $tempStoreFactory, ConstantsService $constantsService, O365LoggerServiceInterface $loggerService, RequestStack $requestStack, HelperService $helperService, AccountProxyInterface $accountProxy, AuthmapInterface $authmap, Messenger $messenger, EntityTypeManagerInterface $entityTypeManager) {
    $this->configFactory = $configFactory;
    $this->helperService = $helperService;
    $this->moduleConfig = $this->configFactory->get('o365.settings');
    $this->ssoConfig = $this->configFactory->get('o365_sso.settings');
    $this->tempStore = $tempStoreFactory;
    $this->constants = $constantsService;
    $this->loggerService = $loggerService;
    $this->request = $requestStack->getCurrentRequest();
    $this->currentUser = $accountProxy;
    $this->authmap = $authmap;
    $this->messenger = $messenger;
    $this->entityTypeManager = $entityTypeManager;

    $this->debug = !empty($this->moduleConfig->get('verbose_logging'));
  }

  /**
   * {@inheritdoc}
   */
  public function redirectToAuthorizationUrl(O365ConnectorInterface $o365_connector) {
    if ($this->currentUser->isAnonymous() && !$this->helperService->strStartsWith($this->request->getPathInfo(), '/o365/login')) {
      $message = $this->t('An anonymous user tried to log in using o365, this is not allowed.');
      $this->loggerService->debug($message);

      $url = Url::fromRoute('user.login')->toString(TRUE);
      $response = new TrustedRedirectResponse($url->getGeneratedUrl());
      $response->addCacheableDependency($url);
      return $response;
    }

    if ($this->debug) {
      $message = $this->t('-- redirectToAuthorizationUrl()');
      $this->loggerService->debug($message);
    }

    // Save the destination.
    $this->saveDestination();

    $clientId = $o365_connector->getClientId();

    if ($this->debug) {
      $message = $this->t('clientId: @client', ['@client' => $clientId]);
      $this->loggerService->debug($message);
    }

    $this->generateProvider($o365_connector);

    $authUrl = $this->oauthClient->getAuthorizationUrl() . '&client_id=' . $clientId;
    $prompt = $this->ssoConfig->get('prompt') ?? FALSE;

    if ($prompt && $prompt !== '<noprompt>') {
      $authUrl .= '&prompt=' . $prompt;
    }

    $authMapUser = $this->authmap->get($this->currentUser->getAccount()->id(), 'o365_sso');

    if ($this->currentUser->isAnonymous() || $authMapUser) {
      return new TrustedRedirectResponse($authUrl);
    }

    throw new AccessDeniedHttpException('User is not authorized to access this page.');
  }

  /**
   * {@inheritdoc}
   */
  public function setAccessToken(string $code, O365ConnectorInterface $o365_connector, $redirect = '') {
    try {
      // Make the token request.
      $this->generateProvider($o365_connector);
      $accessToken = $this->oauthClient->getAccessToken('authorization_code', [
        'code' => $code,
        'client_id' => $o365_connector->getClientId(),
        'client_secret' => $o365_connector->getClientSecret(),
      ]);

      $this->saveAuthData($accessToken, $o365_connector);

      if ($redirect) {
        $redirect .= '?access_token=' . $accessToken->getToken();
        $redirect .= '&refresh_token=' . $accessToken->getRefreshToken();
        $redirect .= '&expires_on=' . $accessToken->getExpires();
        $redirect .= '&connector_id=' . $o365_connector->id();

        return new TrustedRedirectResponse($redirect);
      }
    }
    catch (IdentityProviderException $e) {
      $error = $e->getResponseBody();
      $message = $this->t('Error description: @error', ['@error' => $error['error_description']]);
      $this->loggerService->log($message, 'error');
    }

    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function getAccessToken($login = TRUE) {
    $authData = $this->getAuthData();

    if (!empty($authData)) {
      $now = time();

      if ($authData['expires_on'] > $now) {
        return $authData['access_token'];
      }

      if ($this->debug) {
        $message = $this->t('accessToken is expired, refresh the token');
        $this->loggerService->debug($message);
      }

      return $this->refreshToken($authData['refresh_token'], $authData['connector_id']);
    }

    // We don't have any auth data, so return a redirect response to log in
    // and back.
    if ($login) {
      $connector = $this->request->get('o365_connector');
      return $this->redirectToAuthorizationUrl($connector);
    }

    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function saveAuthDataFromUrl(): void {
    $this->authValues = [
      'access_token' => $this->request->get('access_token'),
      'refresh_token' => $this->request->get('refresh_token'),
      'expires_on' => $this->request->get('expires_on'),
      'connector_id' => $this->request->get('connector_id'),
    ];

    $this->saveDataToTempStore($this->constants->getUserTempStoreDataName(), $this->authValues);
  }

  /**
   * {@inheritdoc}
   */
  public function checkForOfficeLogin() {
    if (!empty($this->getAuthData())) {
      return $this->authmap->get($this->currentUser->id(), 'o365_sso');
    }

    return FALSE;
  }

  /**
   * Generate a new access token based on the refresh token.
   *
   * @param string $refreshToken
   *   The refresh token.
   * @param string $connector_id
   *   The connector entity ID.
   *
   * @return string
   *   The new access token.
   */
  private function refreshToken(string $refreshToken, string $connector_id): string {
    /** @var \Drupal\o365\O365ConnectorInterface $o365_connector */
    $o365_connector = $this->entityTypeManager->getStorage('o365_connector')
      ->load($connector_id);
    $this->generateProvider($o365_connector);

    $accessToken = $this->oauthClient->getAccessToken('refresh_token', [
      'refresh_token' => $refreshToken,
      'client_id' => $o365_connector->getClientId(),
      'client_secret' => $o365_connector->getClientSecret(),
    ]);

    $this->saveAuthData($accessToken, $o365_connector);

    return $accessToken->getToken();
  }

  /**
   * Generate a basic oAuth2 provider.
   *
   * @param \Drupal\o365\O365ConnectorInterface $o365_connector
   *   The o365_connector entity type.
   */
  private function generateProvider(O365ConnectorInterface $o365_connector): void {
    $scopes = $this->helperService->getAuthScopes($o365_connector);
    $redirect_uri = Url::fromRoute('o365_sso.login_callback_controller_callback', ['o365_connector' => $o365_connector->id()], ['absolute' => TRUE])
      ->toString();

    $providerData = [
      'client_id' => $o365_connector->getClientId(),
      'client_secret' => $o365_connector->getClientSecret(),
      'redirectUri' => $redirect_uri,
      'urlAuthorize' => $this->constants->getAuthorizeUrl($o365_connector),
      'urlAccessToken' => $this->constants->getTokenUrl($o365_connector),
      'urlResourceOwnerDetails' => '',
      'scopes' => $scopes,
    ];

    if ($this->debug) {
      $message = $this->t('providerData: @data', ['@data' => print_r($providerData, TRUE)]);
      $this->loggerService->debug($message);
    }

    $this->oauthClient = new GenericProvider($providerData);
  }

  /**
   * Get the auth data from temp store or cookie.
   *
   * @return mixed
   *   The saved auth data.
   */
  private function getAuthData(): mixed {
    // Check if there is some auth data on the url, if so, save it.
    if (!empty($this->request->get('access_token'))) {
      $this->saveAuthDataFromUrl();
    }

    $tempstore = $this->tempStore->get($this->constants->getUserTempStoreName());
    return $tempstore->get($this->constants->getUserTempStoreDataName());
  }

  /**
   * Save the auth data to the temp store.
   *
   * @param \League\OAuth2\Client\Token\AccessTokenInterface $accessToken
   *   The access token object.
   * @param \Drupal\o365\O365ConnectorInterface $o365_connector
   *   The o365_connector entity type.
   *
   * @throws \Drupal\Core\TempStore\TempStoreException
   */
  private function saveAuthData(AccessTokenInterface $accessToken, O365ConnectorInterface $o365_connector): void {
    $this->authValues = [
      'access_token' => $accessToken->getToken(),
      'refresh_token' => $accessToken->getRefreshToken(),
      'expires_on' => $accessToken->getExpires(),
      'connector_id' => $o365_connector->id(),
    ];

    if ($this->debug) {
      $message = $this->t('Saving authData: @data', ['@data' => print_r($this->authValues, TRUE)]);
      $this->loggerService->debug($message);
    }

    $this->saveDataToTempStore($this->constants->getUserTempStoreDataName(), $this->authValues);
  }

  /**
   * Save data to the tempstore.
   *
   * @param string $name
   *   The name of the store.
   * @param mixed $value
   *   The value.
   *
   * @throws \Drupal\Core\TempStore\TempStoreException
   */
  private function saveDataToTempStore(string $name, mixed $value): void {
    $tempstore = $this->tempStore->get($this->constants->getUserTempStoreName());
    $tempstore->set($name, $value);
  }

  /**
   * Get data from the temp store.
   *
   * @param string $name
   *   The name of the temp store value.
   *
   * @return mixed
   *   The value.
   */
  public function getDataFromTempStore(string $name): mixed {
    $tempstore = $this->tempStore->get($this->constants->getUserTempStoreName());
    return $tempstore->get($name);
  }

  /**
   * Save the destination that is part of the url.
   *
   * @throws \Drupal\Core\TempStore\TempStoreException
   */
  private function saveDestination() {
    $destination = NULL;
    if ($this->request->query->has('destination')) {
      $destination = $this->getRedirectDestination()->get();
      // Remove the destination to stop the RedirectResponseSubscriber from
      // picking it up.
      $this->request->query->remove('destination');
    }
    $this->saveDataToTempStore('destination', $destination);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc