|
<?php |
|
|
|
namespace Drupal\symfony_mailer_office365; |
|
|
|
use Drupal\Core\Cache\CacheBackendInterface; |
|
use Psr\Http\Client\ClientInterface; |
|
use Psr\Http\Message\ServerRequestFactoryInterface; |
|
use Psr\Http\Message\StreamFactoryInterface; |
|
use Psr\Log\LoggerInterface; |
|
|
|
/** |
|
* Provides OAuth2 token retrieval functionality for Microsoft Office 365. |
|
* |
|
* This class handles the retrieval and caching of OAuth2 tokens for |
|
* authenticating with Microsoft Office 365 services. It fetches the token |
|
* using client credentials and caches the token for future use. |
|
*/ |
|
final class Office365OAuthTokenProvider { |
|
private const OAUTH_URL = 'https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token'; |
|
private const SCOPE = 'https://outlook.office.com/.default'; |
|
private const GRANT_TYPE = 'client_credentials'; |
|
private const CACHE_KEY = 'email-token'; |
|
|
|
/** |
|
* Constructs an Office365OAuthTokenProvider object. |
|
* |
|
* @param \Psr\Http\Client\ClientInterface $httpClient |
|
* The HTTP client used to make requests to the OAuth2 server. |
|
* @param \Psr\Http\Message\ServerRequestFactoryInterface $serverRequestFactory |
|
* The request factory for creating server requests. |
|
* @param \Psr\Http\Message\StreamFactoryInterface $streamFactory |
|
* The stream factory for creating the request body stream. |
|
* @param \Drupal\Core\Cache\CacheBackendInterface $cache |
|
* The cache backend used to store the access token. |
|
* @param \Psr\Log\LoggerInterface $logger |
|
* Logger interface for logging any issues. |
|
*/ |
|
public function __construct( |
|
protected readonly ClientInterface $httpClient, |
|
protected readonly ServerRequestFactoryInterface $serverRequestFactory, |
|
protected readonly StreamFactoryInterface $streamFactory, |
|
protected readonly CacheBackendInterface $cache, |
|
protected readonly LoggerInterface $logger, |
|
) { |
|
} |
|
|
|
/** |
|
* Retrieves the OAuth2 token. |
|
* |
|
* @param string $tenantId |
|
* The tenant ID for the Microsoft Office 365 instance. |
|
* @param string $clientId |
|
* The client ID for the OAuth2 application. |
|
* @param string $clientSecret |
|
* The client secret for the OAuth2 application. |
|
* |
|
* @return string |
|
* The OAuth2 access token. |
|
*/ |
|
public function getToken(string $tenantId, string $clientId, string $clientSecret): string { |
|
return $this->cache->get(self::CACHE_KEY)?->data ?? $this->fetchToken($tenantId, $clientId, $clientSecret); |
|
} |
|
|
|
/** |
|
* Fetches a new OAuth2 token from Microsoft Office 365. |
|
* |
|
* @param string $tenantId |
|
* The tenant ID for the Microsoft Office 365 instance. |
|
* @param string $clientId |
|
* The client ID for the OAuth2 application. |
|
* @param string $clientSecret |
|
* The client secret for the OAuth2 application. |
|
* |
|
* @return string |
|
* The newly fetched OAuth2 access token, |
|
* or an empty string if an error occurred. |
|
* |
|
* @throws \RuntimeException |
|
* Throws an exception if the request to fetch the token fails or |
|
* if an invalid token is returned. |
|
*/ |
|
public function fetchToken(string $tenantId, string $clientId, string $clientSecret): string { |
|
$data = [ |
|
'client_id' => $clientId, |
|
'client_secret' => $clientSecret, |
|
'scope' => self::SCOPE, |
|
'grant_type' => self::GRANT_TYPE, |
|
]; |
|
|
|
$oAuthUrl = str_replace('{tenant}', $tenantId, self::OAUTH_URL); |
|
|
|
$body = $this->streamFactory->createStream(http_build_query($data)); |
|
$request = $this->serverRequestFactory->createServerRequest('POST', $oAuthUrl) |
|
->withHeader('Content-Type', 'application/x-www-form-urlencoded') |
|
->withBody($body); |
|
|
|
try { |
|
$response = $this->httpClient->sendRequest($request); |
|
if (200 !== $response->getStatusCode()) { |
|
throw new \RuntimeException('Failed to fetch oauth token from Microsoft: ' . $response->getBody()); |
|
} |
|
|
|
$auth = json_decode((string) $response->getBody(), TRUE, 512, JSON_THROW_ON_ERROR); |
|
|
|
$accessToken = $auth['access_token'] ?? NULL; |
|
|
|
if (!$accessToken) { |
|
throw new \RuntimeException('Received empty access token from Microsoft: ' . $response->getBody()); |
|
} |
|
|
|
$expiresIn = $auth['expires_in'] ?? time() + 300; |
|
|
|
// Subtracting 60 seconds from the TTL |
|
// as a safety margin to certainly not use an expiring token. |
|
$expiryTime = time() + ($expiresIn - 60); |
|
$this->cache->set(self::CACHE_KEY, $accessToken, $expiryTime); |
|
|
|
return $accessToken; |
|
} |
|
catch (\Throwable $exception) { |
|
$this->logger->alert($exception->getMessage()); |
|
} |
|
|
|
return ''; |
|
} |
|
|
|
} |