Chris@17
|
1 <?php
|
Chris@17
|
2
|
Chris@17
|
3 namespace Drupal\Tests\contextual\Functional;
|
Chris@17
|
4
|
Chris@17
|
5 use Drupal\Component\Serialization\Json;
|
Chris@17
|
6 use Drupal\Component\Utility\Crypt;
|
Chris@17
|
7 use Drupal\Core\Site\Settings;
|
Chris@17
|
8 use Drupal\Core\Url;
|
Chris@17
|
9 use Drupal\language\Entity\ConfigurableLanguage;
|
Chris@17
|
10 use Drupal\Tests\BrowserTestBase;
|
Chris@17
|
11
|
Chris@17
|
12 /**
|
Chris@17
|
13 * Tests if contextual links are showing on the front page depending on
|
Chris@17
|
14 * permissions.
|
Chris@17
|
15 *
|
Chris@17
|
16 * @group contextual
|
Chris@17
|
17 */
|
Chris@17
|
18 class ContextualDynamicContextTest extends BrowserTestBase {
|
Chris@17
|
19
|
Chris@17
|
20 /**
|
Chris@17
|
21 * A user with permission to access contextual links and edit content.
|
Chris@17
|
22 *
|
Chris@17
|
23 * @var \Drupal\user\UserInterface
|
Chris@17
|
24 */
|
Chris@17
|
25 protected $editorUser;
|
Chris@17
|
26
|
Chris@17
|
27 /**
|
Chris@17
|
28 * An authenticated user with permission to access contextual links.
|
Chris@17
|
29 *
|
Chris@17
|
30 * @var \Drupal\user\UserInterface
|
Chris@17
|
31 */
|
Chris@17
|
32 protected $authenticatedUser;
|
Chris@17
|
33
|
Chris@17
|
34 /**
|
Chris@17
|
35 * A simulated anonymous user with access only to node content.
|
Chris@17
|
36 *
|
Chris@17
|
37 * @var \Drupal\user\UserInterface
|
Chris@17
|
38 */
|
Chris@17
|
39 protected $anonymousUser;
|
Chris@17
|
40
|
Chris@17
|
41 /**
|
Chris@17
|
42 * Modules to enable.
|
Chris@17
|
43 *
|
Chris@17
|
44 * @var array
|
Chris@17
|
45 */
|
Chris@17
|
46 public static $modules = ['contextual', 'node', 'views', 'views_ui', 'language', 'menu_test'];
|
Chris@17
|
47
|
Chris@17
|
48 protected function setUp() {
|
Chris@17
|
49 parent::setUp();
|
Chris@17
|
50
|
Chris@17
|
51 $this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
|
Chris@17
|
52 $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
|
Chris@17
|
53
|
Chris@17
|
54 ConfigurableLanguage::createFromLangcode('it')->save();
|
Chris@17
|
55 $this->rebuildContainer();
|
Chris@17
|
56
|
Chris@17
|
57 $this->editorUser = $this->drupalCreateUser(['access content', 'access contextual links', 'edit any article content']);
|
Chris@17
|
58 $this->authenticatedUser = $this->drupalCreateUser(['access content', 'access contextual links']);
|
Chris@17
|
59 $this->anonymousUser = $this->drupalCreateUser(['access content']);
|
Chris@17
|
60 }
|
Chris@17
|
61
|
Chris@17
|
62 /**
|
Chris@17
|
63 * Tests contextual links with different permissions.
|
Chris@17
|
64 *
|
Chris@17
|
65 * Ensures that contextual link placeholders always exist, even if the user is
|
Chris@17
|
66 * not allowed to use contextual links.
|
Chris@17
|
67 */
|
Chris@17
|
68 public function testDifferentPermissions() {
|
Chris@17
|
69 $this->drupalLogin($this->editorUser);
|
Chris@17
|
70
|
Chris@17
|
71 // Create three nodes in the following order:
|
Chris@17
|
72 // - An article, which should be user-editable.
|
Chris@17
|
73 // - A page, which should not be user-editable.
|
Chris@17
|
74 // - A second article, which should also be user-editable.
|
Chris@17
|
75 $node1 = $this->drupalCreateNode(['type' => 'article', 'promote' => 1]);
|
Chris@17
|
76 $node2 = $this->drupalCreateNode(['type' => 'page', 'promote' => 1]);
|
Chris@17
|
77 $node3 = $this->drupalCreateNode(['type' => 'article', 'promote' => 1]);
|
Chris@17
|
78
|
Chris@17
|
79 // Now, on the front page, all article nodes should have contextual links
|
Chris@17
|
80 // placeholders, as should the view that contains them.
|
Chris@17
|
81 $ids = [
|
Chris@17
|
82 'node:node=' . $node1->id() . ':changed=' . $node1->getChangedTime() . '&langcode=en',
|
Chris@17
|
83 'node:node=' . $node2->id() . ':changed=' . $node2->getChangedTime() . '&langcode=en',
|
Chris@17
|
84 'node:node=' . $node3->id() . ':changed=' . $node3->getChangedTime() . '&langcode=en',
|
Chris@17
|
85 'entity.view.edit_form:view=frontpage:location=page&name=frontpage&display_id=page_1&langcode=en',
|
Chris@17
|
86 ];
|
Chris@17
|
87
|
Chris@17
|
88 // Editor user: can access contextual links and can edit articles.
|
Chris@17
|
89 $this->drupalGet('node');
|
Chris@17
|
90 for ($i = 0; $i < count($ids); $i++) {
|
Chris@17
|
91 $this->assertContextualLinkPlaceHolder($ids[$i]);
|
Chris@17
|
92 }
|
Chris@17
|
93 $response = $this->renderContextualLinks([], 'node');
|
Chris@17
|
94 $this->assertSame(400, $response->getStatusCode());
|
Chris@17
|
95 $this->assertContains('No contextual ids specified.', (string) $response->getBody());
|
Chris@17
|
96 $response = $this->renderContextualLinks($ids, 'node');
|
Chris@17
|
97 $this->assertSame(200, $response->getStatusCode());
|
Chris@17
|
98 $json = Json::decode((string) $response->getBody());
|
Chris@17
|
99 $this->assertIdentical($json[$ids[0]], '<ul class="contextual-links"><li class="entitynodeedit-form"><a href="' . base_path() . 'node/1/edit">Edit</a></li></ul>');
|
Chris@17
|
100 $this->assertIdentical($json[$ids[1]], '');
|
Chris@17
|
101 $this->assertIdentical($json[$ids[2]], '<ul class="contextual-links"><li class="entitynodeedit-form"><a href="' . base_path() . 'node/3/edit">Edit</a></li></ul>');
|
Chris@17
|
102 $this->assertIdentical($json[$ids[3]], '');
|
Chris@17
|
103
|
Chris@17
|
104 // Verify that link language is properly handled.
|
Chris@17
|
105 $node3->addTranslation('it')->set('title', $this->randomString())->save();
|
Chris@17
|
106 $id = 'node:node=' . $node3->id() . ':changed=' . $node3->getChangedTime() . '&langcode=it';
|
Chris@17
|
107 $this->drupalGet('node', ['language' => ConfigurableLanguage::createFromLangcode('it')]);
|
Chris@17
|
108 $this->assertContextualLinkPlaceHolder($id);
|
Chris@17
|
109
|
Chris@17
|
110 // Authenticated user: can access contextual links, cannot edit articles.
|
Chris@17
|
111 $this->drupalLogin($this->authenticatedUser);
|
Chris@17
|
112 $this->drupalGet('node');
|
Chris@17
|
113 for ($i = 0; $i < count($ids); $i++) {
|
Chris@17
|
114 $this->assertContextualLinkPlaceHolder($ids[$i]);
|
Chris@17
|
115 }
|
Chris@17
|
116 $response = $this->renderContextualLinks([], 'node');
|
Chris@17
|
117 $this->assertSame(400, $response->getStatusCode());
|
Chris@17
|
118 $this->assertContains('No contextual ids specified.', (string) $response->getBody());
|
Chris@17
|
119 $response = $this->renderContextualLinks($ids, 'node');
|
Chris@17
|
120 $this->assertSame(200, $response->getStatusCode());
|
Chris@17
|
121 $json = Json::decode((string) $response->getBody());
|
Chris@17
|
122 $this->assertIdentical($json[$ids[0]], '');
|
Chris@17
|
123 $this->assertIdentical($json[$ids[1]], '');
|
Chris@17
|
124 $this->assertIdentical($json[$ids[2]], '');
|
Chris@17
|
125 $this->assertIdentical($json[$ids[3]], '');
|
Chris@17
|
126
|
Chris@17
|
127 // Anonymous user: cannot access contextual links.
|
Chris@17
|
128 $this->drupalLogin($this->anonymousUser);
|
Chris@17
|
129 $this->drupalGet('node');
|
Chris@17
|
130 for ($i = 0; $i < count($ids); $i++) {
|
Chris@17
|
131 $this->assertNoContextualLinkPlaceHolder($ids[$i]);
|
Chris@17
|
132 }
|
Chris@17
|
133 $response = $this->renderContextualLinks([], 'node');
|
Chris@17
|
134 $this->assertSame(403, $response->getStatusCode());
|
Chris@17
|
135 $this->renderContextualLinks($ids, 'node');
|
Chris@17
|
136 $this->assertSame(403, $response->getStatusCode());
|
Chris@17
|
137
|
Chris@17
|
138 // Get a page where contextual links are directly rendered.
|
Chris@17
|
139 $this->drupalGet(Url::fromRoute('menu_test.contextual_test'));
|
Chris@17
|
140 $this->assertEscaped("<script>alert('Welcome to the jungle!')</script>");
|
Chris@17
|
141 $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>');
|
Chris@17
|
142 }
|
Chris@17
|
143
|
Chris@17
|
144 /**
|
Chris@17
|
145 * Tests the contextual placeholder content is protected by a token.
|
Chris@17
|
146 */
|
Chris@17
|
147 public function testTokenProtection() {
|
Chris@17
|
148 $this->drupalLogin($this->editorUser);
|
Chris@17
|
149
|
Chris@17
|
150 // Create a node that will have a contextual link.
|
Chris@17
|
151 $node1 = $this->drupalCreateNode(['type' => 'article', 'promote' => 1]);
|
Chris@17
|
152
|
Chris@17
|
153 // Now, on the front page, all article nodes should have contextual links
|
Chris@17
|
154 // placeholders, as should the view that contains them.
|
Chris@17
|
155 $id = 'node:node=' . $node1->id() . ':changed=' . $node1->getChangedTime() . '&langcode=en';
|
Chris@17
|
156
|
Chris@17
|
157 // Editor user: can access contextual links and can edit articles.
|
Chris@17
|
158 $this->drupalGet('node');
|
Chris@17
|
159 $this->assertContextualLinkPlaceHolder($id);
|
Chris@17
|
160
|
Chris@17
|
161 $http_client = $this->getHttpClient();
|
Chris@17
|
162 $url = Url::fromRoute('contextual.render', [], [
|
Chris@17
|
163 'query' => [
|
Chris@17
|
164 '_format' => 'json',
|
Chris@17
|
165 'destination' => 'node',
|
Chris@17
|
166 ],
|
Chris@17
|
167 ])->setAbsolute()->toString();
|
Chris@17
|
168
|
Chris@17
|
169 $response = $http_client->request('POST', $url, [
|
Chris@17
|
170 'cookies' => $this->getSessionCookies(),
|
Chris@17
|
171 'form_params' => ['ids' => [$id], 'tokens' => []],
|
Chris@17
|
172 'http_errors' => FALSE,
|
Chris@17
|
173 ]);
|
Chris@17
|
174 $this->assertEquals('400', $response->getStatusCode());
|
Chris@17
|
175 $this->assertContains('No contextual ID tokens specified.', (string) $response->getBody());
|
Chris@17
|
176
|
Chris@17
|
177 $response = $http_client->request('POST', $url, [
|
Chris@17
|
178 'cookies' => $this->getSessionCookies(),
|
Chris@17
|
179 'form_params' => ['ids' => [$id], 'tokens' => ['wrong_token']],
|
Chris@17
|
180 'http_errors' => FALSE,
|
Chris@17
|
181 ]);
|
Chris@17
|
182 $this->assertEquals('400', $response->getStatusCode());
|
Chris@17
|
183 $this->assertContains('Invalid contextual ID specified.', (string) $response->getBody());
|
Chris@17
|
184
|
Chris@17
|
185 $response = $http_client->request('POST', $url, [
|
Chris@17
|
186 'cookies' => $this->getSessionCookies(),
|
Chris@17
|
187 'form_params' => ['ids' => [$id], 'tokens' => ['wrong_key' => $this->createContextualIdToken($id)]],
|
Chris@17
|
188 'http_errors' => FALSE,
|
Chris@17
|
189 ]);
|
Chris@17
|
190 $this->assertEquals('400', $response->getStatusCode());
|
Chris@17
|
191 $this->assertContains('Invalid contextual ID specified.', (string) $response->getBody());
|
Chris@17
|
192
|
Chris@17
|
193 $response = $http_client->request('POST', $url, [
|
Chris@17
|
194 'cookies' => $this->getSessionCookies(),
|
Chris@17
|
195 'form_params' => ['ids' => [$id], 'tokens' => [$this->createContextualIdToken($id)]],
|
Chris@17
|
196 'http_errors' => FALSE,
|
Chris@17
|
197 ]);
|
Chris@17
|
198 $this->assertEquals('200', $response->getStatusCode());
|
Chris@17
|
199 }
|
Chris@17
|
200
|
Chris@17
|
201 /**
|
Chris@17
|
202 * Asserts that a contextual link placeholder with the given id exists.
|
Chris@17
|
203 *
|
Chris@17
|
204 * @param string $id
|
Chris@17
|
205 * A contextual link id.
|
Chris@17
|
206 */
|
Chris@17
|
207 protected function assertContextualLinkPlaceHolder($id) {
|
Chris@17
|
208 $this->assertSession()->elementAttributeContains(
|
Chris@17
|
209 'css',
|
Chris@17
|
210 'div[data-contextual-id="' . $id . '"]',
|
Chris@17
|
211 'data-contextual-token',
|
Chris@17
|
212 $this->createContextualIdToken($id)
|
Chris@17
|
213 );
|
Chris@17
|
214 }
|
Chris@17
|
215
|
Chris@17
|
216 /**
|
Chris@17
|
217 * Asserts that a contextual link placeholder with the given id does not exist.
|
Chris@17
|
218 *
|
Chris@17
|
219 * @param string $id
|
Chris@17
|
220 * A contextual link id.
|
Chris@17
|
221 */
|
Chris@17
|
222 protected function assertNoContextualLinkPlaceHolder($id) {
|
Chris@17
|
223 $this->assertSession()->elementNotExists('css', 'div[data-contextual-id="' . $id . '"]');
|
Chris@17
|
224 }
|
Chris@17
|
225
|
Chris@17
|
226 /**
|
Chris@17
|
227 * Get server-rendered contextual links for the given contextual link ids.
|
Chris@17
|
228 *
|
Chris@17
|
229 * @param array $ids
|
Chris@17
|
230 * An array of contextual link ids.
|
Chris@17
|
231 * @param string $current_path
|
Chris@17
|
232 * The Drupal path for the page for which the contextual links are rendered.
|
Chris@17
|
233 *
|
Chris@17
|
234 * @return \Psr\Http\Message\ResponseInterface
|
Chris@17
|
235 * The response object.
|
Chris@17
|
236 */
|
Chris@17
|
237 protected function renderContextualLinks($ids, $current_path) {
|
Chris@17
|
238 $tokens = array_map([$this, 'createContextualIdToken'], $ids);
|
Chris@17
|
239 $http_client = $this->getHttpClient();
|
Chris@17
|
240 $url = Url::fromRoute('contextual.render', [], [
|
Chris@17
|
241 'query' => [
|
Chris@17
|
242 '_format' => 'json',
|
Chris@17
|
243 'destination' => $current_path,
|
Chris@17
|
244 ],
|
Chris@17
|
245 ]);
|
Chris@17
|
246
|
Chris@17
|
247 return $http_client->request('POST', $this->buildUrl($url), [
|
Chris@17
|
248 'cookies' => $this->getSessionCookies(),
|
Chris@17
|
249 'form_params' => ['ids' => $ids, 'tokens' => $tokens],
|
Chris@17
|
250 'http_errors' => FALSE,
|
Chris@17
|
251 ]);
|
Chris@17
|
252 }
|
Chris@17
|
253
|
Chris@17
|
254 /**
|
Chris@17
|
255 * Creates a contextual ID token.
|
Chris@17
|
256 *
|
Chris@17
|
257 * @param string $id
|
Chris@17
|
258 * The contextual ID to create a token for.
|
Chris@17
|
259 *
|
Chris@17
|
260 * @return string
|
Chris@17
|
261 * The contextual ID token.
|
Chris@17
|
262 */
|
Chris@17
|
263 protected function createContextualIdToken($id) {
|
Chris@17
|
264 return Crypt::hmacBase64($id, Settings::getHashSalt() . $this->container->get('private_key')->get());
|
Chris@17
|
265 }
|
Chris@17
|
266
|
Chris@17
|
267 }
|