next-1.0.0-alpha2/src/Plugin/Next/PreviewUrlGenerator/SimpleOauth.php
src/Plugin/Next/PreviewUrlGenerator/SimpleOauth.php
<?php
namespace Drupal\next\Plugin\Next\PreviewUrlGenerator;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Url;
use Drupal\next\Entity\NextSiteInterface;
use Drupal\next\Exception\InvalidPreviewUrlRequest;
use Drupal\next\Plugin\ConfigurablePreviewUrlGeneratorBase;
use Drupal\next\PreviewSecretGeneratorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides the preview_url_generator plugin based on simple_oauth.
*
* @PreviewUrlGenerator(
* id = "simple_oauth",
* label = "Simple OAuth",
* description = "This plugin generates token for role-based access control.
* Access control is handle using OAuth scopes."
* )
*/
class SimpleOauth extends ConfigurablePreviewUrlGeneratorBase {
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected ModuleHandlerInterface $moduleHandler;
/**
* SimpleOauth constructor.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
* The current user.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
* @param \Drupal\next\PreviewSecretGeneratorInterface $preview_secret_generator
* The preview secret generator.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, AccountProxyInterface $current_user, TimeInterface $time, PreviewSecretGeneratorInterface $preview_secret_generator, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $current_user, $time, $preview_secret_generator, $entity_type_manager);
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('current_user'),
$container->get('datetime.time'),
$container->get('next.preview_secret_generator'),
$container->get('entity_type.manager'),
$container->get('module_handler')
);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'secret_expiration' => NULL,
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['secret_expiration'] = [
'#title' => $this->t('Secret expiration time'),
'#description' => $this->t('The value, in seconds, to be used as expiration time for the validation secret. <strong>It is recommended to use short-lived secrets for increased security.</strong>'),
'#type' => 'number',
'#required' => TRUE,
'#default_value' => $this->configuration['secret_expiration'],
];
if ($this->moduleHandler->moduleExists('jwt')) {
$form['jwt_error'] = [
'#weight' => -100,
'#theme' => 'status_messages',
'#message_list' => [
MessengerInterface::TYPE_ERROR => [
$this->t('The JSON Web Tokens module is not compatible with the Simple OAuth module. You should uninstall it when using the Simple OAuth plugin.'),
],
],
];
}
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['secret_expiration'] = $form_state->getValue('secret_expiration');
}
/**
* {@inheritdoc}
*/
public function generate(NextSiteInterface $next_site, EntityInterface $entity, ?string $resource_version = NULL): ?Url {
$query = [];
$query['path'] = $path = $entity->toUrl()->toString();
// Create a secret based on the timestamp, path, scope and resource version.
$query['timestamp'] = $timestamp = $this->time->getRequestTime();
$query['secret'] = $this->previewSecretGenerator->generate($timestamp . $path . $resource_version);
return Url::fromUri($next_site->getPreviewUrl(), [
'query' => $query,
]);
}
/**
* {@inheritdoc}
*/
public function validate(Request $request) {
$body = Json::decode($request->getContent());
// Validate the path.
// We do not check for existing path. We let the next.js site handle this.
if (empty($body['path'])) {
throw new InvalidPreviewUrlRequest("Field 'path' is missing");
}
// Validate the timestamp.
if (empty($body['timestamp'])) {
throw new InvalidPreviewUrlRequest("Field 'timestamp' is missing");
}
$timestamp = (int) $body['timestamp'];
if ($this->time->getRequestTime() > $timestamp + (int) $this->configuration['secret_expiration']) {
throw new InvalidPreviewUrlRequest("The provided secret has expired.");
}
// Validate the secret.
if (empty($body['secret'])) {
throw new InvalidPreviewUrlRequest("Field 'secret' is missing");
}
if ($body['secret'] !== $this->previewSecretGenerator->generate($body['timestamp'] . $body['path'] . $body['resourceVersion'])) {
throw new InvalidPreviewUrlRequest("The provided secret is invalid.");
}
return [
'path' => $body['path'],
'maxAge' => (int) $this->configuration['secret_expiration'],
];
}
}
