Skip to content

Commit

Permalink
Removed scope from the preview URL generation and validation process
Browse files Browse the repository at this point in the history
  • Loading branch information
Bojan Bogdanovic committed Dec 25, 2023
1 parent ccb42de commit 715d03d
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 121 deletions.
75 changes: 14 additions & 61 deletions modules/next/src/Plugin/Next/PreviewUrlGenerator/SimpleOauth.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,16 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
* {@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'));
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')
);
}

/**
Expand Down Expand Up @@ -119,18 +128,9 @@ public function generate(NextSiteInterface $next_site, EntityInterface $entity,
$query = [];
$query['slug'] = $slug = $entity->toUrl()->toString();

// Send the current user roles as scope.
$scopes = $this->getScopesForCurrentUser();

if (!count($scopes)) {
return NULL;
}

$query['scope'] = $scope = implode(' ', $scopes);

// Create a secret based on the timestamp, slug, scope and resource version.
$query['timestamp'] = $timestamp = $this->time->getRequestTime();
$query['secret'] = $this->previewSecretGenerator->generate($timestamp . $slug . $scope . $resource_version);
$query['secret'] = $this->previewSecretGenerator->generate($timestamp . $slug . $resource_version);

return Url::fromUri($next_site->getPreviewUrl(), [
'query' => $query,
Expand Down Expand Up @@ -159,66 +159,19 @@ public function validate(Request $request) {
throw new InvalidPreviewUrlRequest("The provided secret has expired.");
}

if (empty($body['scope'])) {
throw new InvalidPreviewUrlRequest("Field 'scope' is missing");
}

// Validate the secret.
if (empty($body['secret'])) {
throw new InvalidPreviewUrlRequest("Field 'secret' is missing");
}

if ($body['secret'] !== $this->previewSecretGenerator->generate($body['timestamp'] . $body['slug'] . $body['scope'] . $body['resourceVersion'])) {
if ($body['secret'] !== $this->previewSecretGenerator->generate($body['timestamp'] . $body['slug'] . $body['resourceVersion'])) {
throw new InvalidPreviewUrlRequest("The provided secret is invalid.");
}

return [
'scope' => $body['scope'],
'path' => $body['slug'],
'maxAge' => (int) $this->configuration['secret_expiration'],
];
}

/**
* Returns scope for the current user.
*
* @return array|mixed
* An array of roles as scopes.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function getScopesForCurrentUser(): array {
$roles = $this->currentUser->getRoles(TRUE);
$admin_role = $this->getAdminRole();

// Return the admin role for administrators.
if ((int) $this->currentUser->id() === 1 || in_array($admin_role, $roles)) {
return [$admin_role];
}

return $roles;
}

/**
* Returns an array of admin roles.
*
* @return string|null
* The admin role.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function getAdminRole(): ?string {
$admin_roles = $this->entityTypeManager->getStorage('user_role')
->getQuery()
->accessCheck(FALSE)
->condition('is_admin', TRUE)
->execute();

if (!$admin_roles) {
return NULL;
}

return reset($admin_roles);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public function testValidate() {

$controller = NextPreviewUrlController::create($this->container);
$response = $controller->validate($request);
$this->assertSame(['scope' => $user->getRoles(TRUE)[0]], Json::decode($response->getContent()));
$this->assertSame(['path' => $page->toUrl()->toString()], Json::decode($response->getContent()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ class SimpleOauthPreviewUrlGeneratorTest extends KernelTestBase {
*/
protected $nextSite;

/**
* The next settings manager.
*
* @var \Drupal\next\NextSettingsManagerInterface
*/
protected $nextSettingsManager;

/**
* {@inheritdoc}
*/
Expand All @@ -46,6 +53,8 @@ protected function setUp(): void {
$this->installSchema('system', ['sequences']);
$this->installSchema('node', ['node_access']);

$this->nextSettingsManager = $this->container->get('next.settings.manager');

// Create NextSite entities.
$this->nextSite = NextSite::create([
'label' => 'Blog',
Expand Down Expand Up @@ -91,44 +100,11 @@ public function testGenerate() {
$this->assertNotEmpty($query['timestamp']);
$this->assertNotEmpty($query['secret']);
$this->assertSame($query['plugin'], 'simple_oauth');
$this->assertContains($query['scope'], $user->getRoles());

// Test the secret.
/** @var \Drupal\next\PreviewSecretGeneratorInterface $secret_generator */
$secret_generator = \Drupal::service('next.preview_secret_generator');
$this->assertSame($query['secret'], $secret_generator->generate($query['timestamp'] . $query['slug'] . $query['scope'] . $query['resourceVersion']));
}

/**
* @covers ::getScopesForCurrentUser
* @covers ::getAdminRole
*/
public function testCurrentUserScopes() {
/** @var \Drupal\next\NextSettingsManagerInterface $next_settings_manager */
$next_settings_manager = $this->container->get('next.settings.manager');
/** @var \Drupal\next\Plugin\Next\PreviewUrlGenerator\SimpleOauth $preview_url_generator */
$preview_url_generator = $next_settings_manager->getPreviewUrlGenerator();

$page = $this->createNode(['type' => 'page']);

// Log in as anonymous user.
$this->setCurrentUser(User::load(0));
$url = $preview_url_generator->generate($this->nextSite, $page);
$this->assertNull($url);

// Log in as user 1.
$admin_role = $this->createAdminRole();
$this->setCurrentUser(User::load(1));
$url = $preview_url_generator->generate($this->nextSite, $page);
$query = $url->getOption('query');
$this->assertSame($query['scope'], $admin_role);

// Log in as admin user.
$admin_user = $this->createUser([], NULL, TRUE);
$this->setCurrentUser($admin_user);
$url = $preview_url_generator->generate($this->nextSite, $page);
$query = $url->getOption('query');
$this->assertSame($query['scope'], $admin_user->getRoles(TRUE)[0]);
$this->assertSame($query['secret'], $secret_generator->generate($query['timestamp'] . $query['slug'] . $query['resourceVersion']));
}

/**
Expand All @@ -137,10 +113,7 @@ public function testCurrentUserScopes() {
*/
public function testValidateForInvalidBody($body, $message, $is_valid = FALSE) {
$request = Request::create('/', 'POST', [], [], [], [], Json::encode($body));

/** @var \Drupal\next\NextSettingsManagerInterface $next_settings_manager */
$next_settings_manager = $this->container->get('next.settings.manager');
$preview_url_generator = $next_settings_manager->getPreviewUrlGenerator();
$preview_url_generator = $this->nextSettingsManager->getPreviewUrlGenerator();

if (!$is_valid) {
$this->expectExceptionMessage($message);
Expand All @@ -159,15 +132,9 @@ public function testValidateSecret() {
$query = $preview_url->getOption('query');

$request = Request::create('/', 'POST', [], [], [], [], Json::encode($query));
$preview_url_generator = $this->nextSettingsManager->getPreviewUrlGenerator();

/** @var \Drupal\next\NextSettingsManagerInterface $next_settings_manager */
$next_settings_manager = $this->container->get('next.settings.manager');
$preview_url_generator = $next_settings_manager->getPreviewUrlGenerator();

$response = $preview_url_generator->validate($request);
$role = $user->getRoles(TRUE)[0];
$this->assertSame(['scope' => $role], $response);

$preview_url_generator->validate($request);
$this->expectExceptionMessage('The provided secret is invalid.');
$query = $preview_url->getOption('query');
$query['timestamp'] = strtotime('+60seconds');
Expand All @@ -185,12 +152,6 @@ public function testValidateSecret() {
$query['resourceVersion'] = 'rel:23';
$request = Request::create('/', 'POST', [], [], [], [], Json::encode($query));
$preview_url_generator->validate($request);

$this->expectExceptionMessage('The provided secret is invalid.');
$query = $preview_url->getOption('query');
$query['scope'] = 'editor';
$request = Request::create('/', 'POST', [], [], [], [], Json::encode($query));
$preview_url_generator->validate($request);
}

/**
Expand All @@ -203,23 +164,17 @@ public function providerValidateForInvalidBody() {
return [
[[], "Field 'slug' is missing"],
[['slug' => '/node/1'], "Field 'timestamp' is missing"],
[
['slug' => '/node/1', 'timestamp' => strtotime('now')],
"Field 'scope' is missing",
],
[
[
'slug' => '/node/1',
'timestamp' => strtotime('now'),
'scope' => 'llama',
],
"Field 'secret' is missing",
],
[
[
'slug' => '/node/1',
'timestamp' => strtotime('-60 seconds'),
'scope' => 'llama',
'secret' => 'secret',
],
"The provided secret has expired.",
Expand All @@ -228,7 +183,6 @@ public function providerValidateForInvalidBody() {
[
'slug' => '/node/1',
'timestamp' => strtotime('60 seconds'),
'scope' => 'llama',
'secret' => 'secret',
],
"",
Expand Down

0 comments on commit 715d03d

Please sign in to comment.