OauthTokenFileStorage.php in Apigee Edge 8
Namespace
Drupal\apigee_edgeFile
src/OauthTokenFileStorage.phpView source
<?php
/**
* Copyright 2018 Google Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License version 2 as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
* License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
namespace Drupal\apigee_edge;
use Drupal\apigee_edge\Exception\OauthTokenStorageException;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\File\Exception\FileException;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
/**
* Stores OAuth token data in a file.
*
* @todo: move to \Drupal\apigee_edge\Connector namespace.
*/
final class OauthTokenFileStorage implements OauthTokenStorageInterface {
/**
* Default directory of the oauth.dat file.
*
* @var string
*/
public const DEFAULT_DIRECTORY = 'private://.apigee_edge';
/**
* Ensures that token gets refreshed earlier than it expires.
*
* Number of seconds extracted from token's expiration date when
* hasExpired() calculates.
*
* @var int
*/
protected $leeway = 30;
/**
* Internal cache for token data.
*
* @var array
*
* @see getTokenData()
*/
private $tokenData = [];
/**
* Path of the token file.
*
* @var string
*/
private $tokenFilePath;
/**
* The logger service.
*
* @var \Drupal\Core\Logger\LoggerChannelInterface
*/
private $logger;
/**
* The file system service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
private $fileSystem;
/**
* OauthTokenFileStorage constructor.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config
* The config factory service.
* @param \Drupal\Core\File\FileSystemInterface $file_system
* The file system service.
* @param \Drupal\Core\Logger\LoggerChannelInterface $logger
* The logger service.
*/
public function __construct(ConfigFactoryInterface $config, FileSystemInterface $file_system, LoggerChannelInterface $logger) {
$custom_path = $config
->get('apigee_edge.auth')
->get('oauth_token_storage_location');
$this->tokenFilePath = empty($custom_path) ? static::DEFAULT_DIRECTORY : rtrim(trim($custom_path), " \\/");
$this->tokenFilePath .= '/oauth.dat';
$this->fileSystem = $file_system;
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public function getAccessToken() : ?string {
$token_data = $this
->getTokenData();
return $token_data['access_token'] ?? NULL;
}
/**
* {@inheritdoc}
*/
public function getTokenType() : ?string {
$token_data = $this
->getTokenData();
return $token_data['token_type'] ?? NULL;
}
/**
* {@inheritdoc}
*/
public function getRefreshToken() : ?string {
$token_data = $this
->getTokenData();
return $token_data['refresh_token'] ?? NULL;
}
/**
* {@inheritdoc}
*/
public function getScope() : string {
$token_data = $this
->getTokenData();
return $token_data['scope'] ?? '';
}
/**
* {@inheritdoc}
*/
public function getExpires() : int {
$token_data = $this
->getTokenData();
return $token_data['expires'] ?? time() - 1;
}
/**
* {@inheritdoc}
*/
public function hasExpired() : bool {
$expires = $this
->getExpires();
return !($expires - $this->leeway > time());
}
/**
* {@inheritdoc}
*/
public function markExpired() : void {
// Gets token data.
$token_data = $this
->getTokenData();
// Expire in the past.
$token_data['expires_in'] = -1;
// Save the token data.
$this
->saveToken($token_data);
}
/**
* {@inheritdoc}
*/
public function saveToken(array $data) : void {
// Calculate the cache expiration.
if (isset($data['expires_in'])) {
$data['expires'] = $data['expires_in'] + time();
}
// Do not save the expires_in data to the storage.
unset($data['expires_in']);
try {
$this
->checkRequirements();
// Write the obfuscated token data to a private file.
$this->fileSystem
->saveData(base64_encode(serialize($data)), $this->tokenFilePath, FileSystemInterface::EXISTS_REPLACE);
} catch (FileException $e) {
$this->logger
->critical('Error saving OAuth token file.');
} catch (OauthTokenStorageException $exception) {
$this->logger
->critical('OAuth token file storage: %error.', [
'%error' => $exception
->getMessage(),
]);
} finally {
// Even if an error occurs here token data can be still served from the
// internal cache in this page request.
$this->tokenData = $data;
}
}
/**
* {@inheritdoc}
*/
public function checkRequirements() : void {
if ($this
->isTokenFileInPrivateFileSystem() && !$this
->isPrivateFileSystemConfigured()) {
throw new OauthTokenStorageException('Unable to save token data to private filesystem because it has not been configured yet.');
}
// Gets the file directory so we can make sure it exists.
$token_directory = $this->fileSystem
->dirname($this->tokenFilePath);
if (!$this->fileSystem
->prepareDirectory($token_directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) {
throw new OauthTokenStorageException("Unable to set up {$token_directory} directory for token file.");
}
}
/**
* {@inheritdoc}
*/
public function removeToken() : void {
// Do not try to remove token from the token file if private filesystem
// has not been configured yet. Also, do not create the token file
// if it has not existed yet.
if ($this
->isTokenFileInPrivateFileSystem() && (!$this
->isPrivateFileSystemConfigured() || $this
->isPrivateFileSystemConfigured() && !file_exists($this->tokenFilePath))) {
$this->tokenData = [];
return;
}
else {
$this
->saveToken([]);
}
}
/**
* Removes the file in which the OAuth token data is stored.
*/
public function removeTokenFile() : void {
if ($this
->isTokenFileInPrivateFileSystem() && !$this
->isPrivateFileSystemConfigured() || !file_exists($this->tokenFilePath)) {
// Do not try to delete the file if private filesystem has not been
// configured because in that cause "private://" scheme is not
// registered. Also do nothing if token file does not exist.
return;
}
try {
$this->fileSystem
->delete($this->tokenFilePath);
} catch (FileException $e) {
// Do nothing.
}
}
/**
* Gets the token data from the cache or the file.
*
* @param bool $reset
* Whether or not to reload the token data.
*
* @return array
* The token data from the internal cache or the token file. Returned array
* could be empty!
*/
private function getTokenData(bool $reset = FALSE) : array {
// Load from storage if the cached value is empty.
if ($reset || empty($this->tokenData)) {
$this->tokenData = $this
->getFromStorage();
}
return $this->tokenData;
}
/**
* Reads the token data from the file.
*
* @return array
* The token data from the file or an empty array if file does not exist.
*/
private function getFromStorage() : array {
$data = [];
// Get the token data from the file store.
if (file_exists($this->tokenFilePath) && ($raw_data = file_get_contents($this->tokenFilePath))) {
$data = unserialize(base64_decode($raw_data));
}
return is_array($data) ? $data : [];
}
/**
* Checks whether the token file's location in the private filesystem.
*
* @return bool
* True if the token file's location is in the private filesystem, false
* otherwise.
*/
private function isTokenFileInPrivateFileSystem() : bool {
return strpos($this->tokenFilePath, 'private://') === 0;
}
/**
* Checks whether the private filesystem is configured.
*
* @return bool
* True if configured, FALSE otherwise.
*/
private function isPrivateFileSystemConfigured() : bool {
return (bool) $this->fileSystem
->realpath('private://');
}
}
Classes
Name | Description |
---|---|
OauthTokenFileStorage | Stores OAuth token data in a file. |