Mercurial > hg > isophonics-drupal-site
diff core/modules/contextual/tests/src/Functional/ContextualDynamicContextTest.php @ 17:129ea1e6d783
Update, including to Drupal core 8.6.10
author | Chris Cannam |
---|---|
date | Thu, 28 Feb 2019 13:21:36 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/modules/contextual/tests/src/Functional/ContextualDynamicContextTest.php Thu Feb 28 13:21:36 2019 +0000 @@ -0,0 +1,267 @@ +<?php + +namespace Drupal\Tests\contextual\Functional; + +use Drupal\Component\Serialization\Json; +use Drupal\Component\Utility\Crypt; +use Drupal\Core\Site\Settings; +use Drupal\Core\Url; +use Drupal\language\Entity\ConfigurableLanguage; +use Drupal\Tests\BrowserTestBase; + +/** + * Tests if contextual links are showing on the front page depending on + * permissions. + * + * @group contextual + */ +class ContextualDynamicContextTest extends BrowserTestBase { + + /** + * A user with permission to access contextual links and edit content. + * + * @var \Drupal\user\UserInterface + */ + protected $editorUser; + + /** + * An authenticated user with permission to access contextual links. + * + * @var \Drupal\user\UserInterface + */ + protected $authenticatedUser; + + /** + * A simulated anonymous user with access only to node content. + * + * @var \Drupal\user\UserInterface + */ + protected $anonymousUser; + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['contextual', 'node', 'views', 'views_ui', 'language', 'menu_test']; + + protected function setUp() { + parent::setUp(); + + $this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']); + $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']); + + ConfigurableLanguage::createFromLangcode('it')->save(); + $this->rebuildContainer(); + + $this->editorUser = $this->drupalCreateUser(['access content', 'access contextual links', 'edit any article content']); + $this->authenticatedUser = $this->drupalCreateUser(['access content', 'access contextual links']); + $this->anonymousUser = $this->drupalCreateUser(['access content']); + } + + /** + * Tests contextual links with different permissions. + * + * Ensures that contextual link placeholders always exist, even if the user is + * not allowed to use contextual links. + */ + public function testDifferentPermissions() { + $this->drupalLogin($this->editorUser); + + // Create three nodes in the following order: + // - An article, which should be user-editable. + // - A page, which should not be user-editable. + // - A second article, which should also be user-editable. + $node1 = $this->drupalCreateNode(['type' => 'article', 'promote' => 1]); + $node2 = $this->drupalCreateNode(['type' => 'page', 'promote' => 1]); + $node3 = $this->drupalCreateNode(['type' => 'article', 'promote' => 1]); + + // Now, on the front page, all article nodes should have contextual links + // placeholders, as should the view that contains them. + $ids = [ + 'node:node=' . $node1->id() . ':changed=' . $node1->getChangedTime() . '&langcode=en', + 'node:node=' . $node2->id() . ':changed=' . $node2->getChangedTime() . '&langcode=en', + 'node:node=' . $node3->id() . ':changed=' . $node3->getChangedTime() . '&langcode=en', + 'entity.view.edit_form:view=frontpage:location=page&name=frontpage&display_id=page_1&langcode=en', + ]; + + // Editor user: can access contextual links and can edit articles. + $this->drupalGet('node'); + for ($i = 0; $i < count($ids); $i++) { + $this->assertContextualLinkPlaceHolder($ids[$i]); + } + $response = $this->renderContextualLinks([], 'node'); + $this->assertSame(400, $response->getStatusCode()); + $this->assertContains('No contextual ids specified.', (string) $response->getBody()); + $response = $this->renderContextualLinks($ids, 'node'); + $this->assertSame(200, $response->getStatusCode()); + $json = Json::decode((string) $response->getBody()); + $this->assertIdentical($json[$ids[0]], '<ul class="contextual-links"><li class="entitynodeedit-form"><a href="' . base_path() . 'node/1/edit">Edit</a></li></ul>'); + $this->assertIdentical($json[$ids[1]], ''); + $this->assertIdentical($json[$ids[2]], '<ul class="contextual-links"><li class="entitynodeedit-form"><a href="' . base_path() . 'node/3/edit">Edit</a></li></ul>'); + $this->assertIdentical($json[$ids[3]], ''); + + // Verify that link language is properly handled. + $node3->addTranslation('it')->set('title', $this->randomString())->save(); + $id = 'node:node=' . $node3->id() . ':changed=' . $node3->getChangedTime() . '&langcode=it'; + $this->drupalGet('node', ['language' => ConfigurableLanguage::createFromLangcode('it')]); + $this->assertContextualLinkPlaceHolder($id); + + // Authenticated user: can access contextual links, cannot edit articles. + $this->drupalLogin($this->authenticatedUser); + $this->drupalGet('node'); + for ($i = 0; $i < count($ids); $i++) { + $this->assertContextualLinkPlaceHolder($ids[$i]); + } + $response = $this->renderContextualLinks([], 'node'); + $this->assertSame(400, $response->getStatusCode()); + $this->assertContains('No contextual ids specified.', (string) $response->getBody()); + $response = $this->renderContextualLinks($ids, 'node'); + $this->assertSame(200, $response->getStatusCode()); + $json = Json::decode((string) $response->getBody()); + $this->assertIdentical($json[$ids[0]], ''); + $this->assertIdentical($json[$ids[1]], ''); + $this->assertIdentical($json[$ids[2]], ''); + $this->assertIdentical($json[$ids[3]], ''); + + // Anonymous user: cannot access contextual links. + $this->drupalLogin($this->anonymousUser); + $this->drupalGet('node'); + for ($i = 0; $i < count($ids); $i++) { + $this->assertNoContextualLinkPlaceHolder($ids[$i]); + } + $response = $this->renderContextualLinks([], 'node'); + $this->assertSame(403, $response->getStatusCode()); + $this->renderContextualLinks($ids, 'node'); + $this->assertSame(403, $response->getStatusCode()); + + // Get a page where contextual links are directly rendered. + $this->drupalGet(Url::fromRoute('menu_test.contextual_test')); + $this->assertEscaped("<script>alert('Welcome to the jungle!')</script>"); + $this->assertRaw('<li class="menu-testcontextual-hidden-manage-edit"><a href="' . base_path() . 'menu-test-contextual/1/edit" class="use-ajax" data-dialog-type="modal" data-is-something>Edit menu - contextual</a></li>'); + } + + /** + * Tests the contextual placeholder content is protected by a token. + */ + public function testTokenProtection() { + $this->drupalLogin($this->editorUser); + + // Create a node that will have a contextual link. + $node1 = $this->drupalCreateNode(['type' => 'article', 'promote' => 1]); + + // Now, on the front page, all article nodes should have contextual links + // placeholders, as should the view that contains them. + $id = 'node:node=' . $node1->id() . ':changed=' . $node1->getChangedTime() . '&langcode=en'; + + // Editor user: can access contextual links and can edit articles. + $this->drupalGet('node'); + $this->assertContextualLinkPlaceHolder($id); + + $http_client = $this->getHttpClient(); + $url = Url::fromRoute('contextual.render', [], [ + 'query' => [ + '_format' => 'json', + 'destination' => 'node', + ], + ])->setAbsolute()->toString(); + + $response = $http_client->request('POST', $url, [ + 'cookies' => $this->getSessionCookies(), + 'form_params' => ['ids' => [$id], 'tokens' => []], + 'http_errors' => FALSE, + ]); + $this->assertEquals('400', $response->getStatusCode()); + $this->assertContains('No contextual ID tokens specified.', (string) $response->getBody()); + + $response = $http_client->request('POST', $url, [ + 'cookies' => $this->getSessionCookies(), + 'form_params' => ['ids' => [$id], 'tokens' => ['wrong_token']], + 'http_errors' => FALSE, + ]); + $this->assertEquals('400', $response->getStatusCode()); + $this->assertContains('Invalid contextual ID specified.', (string) $response->getBody()); + + $response = $http_client->request('POST', $url, [ + 'cookies' => $this->getSessionCookies(), + 'form_params' => ['ids' => [$id], 'tokens' => ['wrong_key' => $this->createContextualIdToken($id)]], + 'http_errors' => FALSE, + ]); + $this->assertEquals('400', $response->getStatusCode()); + $this->assertContains('Invalid contextual ID specified.', (string) $response->getBody()); + + $response = $http_client->request('POST', $url, [ + 'cookies' => $this->getSessionCookies(), + 'form_params' => ['ids' => [$id], 'tokens' => [$this->createContextualIdToken($id)]], + 'http_errors' => FALSE, + ]); + $this->assertEquals('200', $response->getStatusCode()); + } + + /** + * Asserts that a contextual link placeholder with the given id exists. + * + * @param string $id + * A contextual link id. + */ + protected function assertContextualLinkPlaceHolder($id) { + $this->assertSession()->elementAttributeContains( + 'css', + 'div[data-contextual-id="' . $id . '"]', + 'data-contextual-token', + $this->createContextualIdToken($id) + ); + } + + /** + * Asserts that a contextual link placeholder with the given id does not exist. + * + * @param string $id + * A contextual link id. + */ + protected function assertNoContextualLinkPlaceHolder($id) { + $this->assertSession()->elementNotExists('css', 'div[data-contextual-id="' . $id . '"]'); + } + + /** + * Get server-rendered contextual links for the given contextual link ids. + * + * @param array $ids + * An array of contextual link ids. + * @param string $current_path + * The Drupal path for the page for which the contextual links are rendered. + * + * @return \Psr\Http\Message\ResponseInterface + * The response object. + */ + protected function renderContextualLinks($ids, $current_path) { + $tokens = array_map([$this, 'createContextualIdToken'], $ids); + $http_client = $this->getHttpClient(); + $url = Url::fromRoute('contextual.render', [], [ + 'query' => [ + '_format' => 'json', + 'destination' => $current_path, + ], + ]); + + return $http_client->request('POST', $this->buildUrl($url), [ + 'cookies' => $this->getSessionCookies(), + 'form_params' => ['ids' => $ids, 'tokens' => $tokens], + 'http_errors' => FALSE, + ]); + } + + /** + * Creates a contextual ID token. + * + * @param string $id + * The contextual ID to create a token for. + * + * @return string + * The contextual ID token. + */ + protected function createContextualIdToken($id) { + return Crypt::hmacBase64($id, Settings::getHashSalt() . $this->container->get('private_key')->get()); + } + +}