5 * Contains \Drupal\Tests\Core\Access\AccessResultTest.
8 namespace Drupal\Tests\Core\Access;
10 use Drupal\Core\Access\AccessResult;
11 use Drupal\Core\Access\AccessResultInterface;
12 use Drupal\Core\Access\AccessResultNeutral;
13 use Drupal\Core\Access\AccessResultReasonInterface;
14 use Drupal\Core\Cache\Cache;
15 use Drupal\Core\Cache\CacheableDependencyInterface;
16 use Drupal\Core\DependencyInjection\ContainerBuilder;
17 use Drupal\Tests\UnitTestCase;
20 * @coversDefaultClass \Drupal\Core\Access\AccessResult
23 class AccessResultTest extends UnitTestCase {
26 * The cache contexts manager.
28 * @var \Drupal\Core\Cache\Context\CacheContextsManager|\PHPUnit_Framework_MockObject_MockObject
30 protected $cacheContextsManager;
35 protected function setUp() {
38 $this->cacheContextsManager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
39 ->disableOriginalConstructor()
42 $this->cacheContextsManager->method('assertValidTokens')->willReturn(TRUE);
43 $container = new ContainerBuilder();
44 $container->set('cache_contexts_manager', $this->cacheContextsManager);
45 \Drupal::setContainer($container);
48 protected function assertDefaultCacheability(AccessResult $access) {
49 $this->assertSame([], $access->getCacheContexts());
50 $this->assertSame([], $access->getCacheTags());
51 $this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge());
55 * Tests the construction of an AccessResult object.
59 public function testConstruction() {
60 $verify = function (AccessResult $access) {
61 $this->assertFalse($access->isAllowed());
62 $this->assertFalse($access->isForbidden());
63 $this->assertTrue($access->isNeutral());
64 $this->assertDefaultCacheability($access);
67 // Verify the object when using the constructor.
68 $a = new AccessResultNeutral();
71 // Verify the object when using the ::create() convenience method.
72 $b = AccessResult::neutral();
75 $this->assertEquals($a, $b);
81 * @covers ::isForbidden
84 public function testAccessAllowed() {
85 $verify = function (AccessResult $access) {
86 $this->assertTrue($access->isAllowed());
87 $this->assertFalse($access->isForbidden());
88 $this->assertFalse($access->isNeutral());
89 $this->assertDefaultCacheability($access);
92 // Verify the object when using the ::allowed() convenience static method.
93 $b = AccessResult::allowed();
100 * @covers ::isForbidden
101 * @covers ::isNeutral
103 public function testAccessForbidden() {
104 $verify = function (AccessResult $access) {
105 $this->assertFalse($access->isAllowed());
106 $this->assertTrue($access->isForbidden());
107 $this->assertFalse($access->isNeutral());
108 $this->assertDefaultCacheability($access);
111 // Verify the object when using the ::forbidden() convenience static method.
112 $b = AccessResult::forbidden();
117 * @covers ::forbidden
119 public function testAccessForbiddenReason() {
120 $verify = function (AccessResult $access, $reason) {
121 $this->assertInstanceOf(AccessResultReasonInterface::class, $access);
122 $this->assertSame($reason, $access->getReason());
125 $b = AccessResult::forbidden();
128 $reason = $this->getRandomGenerator()->string();
129 $b = AccessResult::forbidden($reason);
130 $verify($b, $reason);
132 $b = AccessResult::forbiddenIf(TRUE, $reason);
133 $verify($b, $reason);
137 * @covers ::allowedIf
138 * @covers ::isAllowed
139 * @covers ::isForbidden
140 * @covers ::isNeutral
142 public function testAccessConditionallyAllowed() {
143 $verify = function (AccessResult $access, $allowed) {
144 $this->assertSame($allowed, $access->isAllowed());
145 $this->assertFalse($access->isForbidden());
146 $this->assertSame(!$allowed, $access->isNeutral());
147 $this->assertDefaultCacheability($access);
150 $b1 = AccessResult::allowedIf(TRUE);
152 $b2 = AccessResult::allowedIf(FALSE);
157 * @covers ::forbiddenIf
158 * @covers ::isAllowed
159 * @covers ::isForbidden
160 * @covers ::isNeutral
162 public function testAccessConditionallyForbidden() {
163 $verify = function (AccessResult $access, $forbidden) {
164 $this->assertFalse($access->isAllowed());
165 $this->assertSame($forbidden, $access->isForbidden());
166 $this->assertSame(!$forbidden, $access->isNeutral());
167 $this->assertDefaultCacheability($access);
170 $b1 = AccessResult::forbiddenIf(TRUE);
172 $b2 = AccessResult::forbiddenIf(FALSE);
179 public function testAndIf() {
180 $neutral = AccessResult::neutral('neutral message');
181 $allowed = AccessResult::allowed();
182 $forbidden = AccessResult::forbidden('forbidden message');
183 $unused_access_result_due_to_lazy_evaluation = $this->getMock('\Drupal\Core\Access\AccessResultInterface');
184 $unused_access_result_due_to_lazy_evaluation->expects($this->never())
185 ->method($this->anything());
187 // ALLOWED && ALLOWED === ALLOWED.
188 $access = $allowed->andIf($allowed);
189 $this->assertTrue($access->isAllowed());
190 $this->assertFalse($access->isForbidden());
191 $this->assertFalse($access->isNeutral());
192 $this->assertDefaultCacheability($access);
194 // ALLOWED && NEUTRAL === NEUTRAL.
195 $access = $allowed->andIf($neutral);
196 $this->assertFalse($access->isAllowed());
197 $this->assertFalse($access->isForbidden());
198 $this->assertTrue($access->isNeutral());
199 $this->assertEquals('neutral message', $access->getReason());
200 $this->assertDefaultCacheability($access);
202 // ALLOWED && FORBIDDEN === FORBIDDEN.
203 $access = $allowed->andIf($forbidden);
204 $this->assertFalse($access->isAllowed());
205 $this->assertTrue($access->isForbidden());
206 $this->assertFalse($access->isNeutral());
207 $this->assertEquals('forbidden message', $access->getReason());
208 $this->assertDefaultCacheability($access);
210 // NEUTRAL && ALLOW == NEUTRAL
211 $access = $neutral->andIf($allowed);
212 $this->assertFalse($access->isAllowed());
213 $this->assertFalse($access->isForbidden());
214 $this->assertTrue($access->isNeutral());
215 $this->assertEquals('neutral message', $access->getReason());
216 $this->assertDefaultCacheability($access);
218 // NEUTRAL && NEUTRAL === NEUTRAL.
219 $access = $neutral->andIf($neutral);
220 $this->assertFalse($access->isAllowed());
221 $this->assertFalse($access->isForbidden());
222 $this->assertTrue($access->isNeutral());
223 $this->assertEquals('neutral message', $access->getReason());
224 $this->assertDefaultCacheability($access);
226 // NEUTRAL && FORBIDDEN === FORBIDDEN.
227 $access = $neutral->andIf($forbidden);
228 $this->assertFalse($access->isAllowed());
229 $this->assertTrue($access->isForbidden());
230 $this->assertFalse($access->isNeutral());
231 $this->assertEquals('forbidden message', $access->getReason());
232 $this->assertDefaultCacheability($access);
234 // FORBIDDEN && ALLOWED = FORBIDDEN
235 $access = $forbidden->andif($allowed);
236 $this->assertFalse($access->isAllowed());
237 $this->assertTrue($access->isForbidden());
238 $this->assertFalse($access->isNeutral());
239 $this->assertEquals('forbidden message', $access->getReason());
240 $this->assertDefaultCacheability($access);
242 // FORBIDDEN && NEUTRAL = FORBIDDEN
243 $access = $forbidden->andif($neutral);
244 $this->assertFalse($access->isAllowed());
245 $this->assertTrue($access->isForbidden());
246 $this->assertFalse($access->isNeutral());
247 $this->assertEquals('forbidden message', $access->getReason());
248 $this->assertDefaultCacheability($access);
250 // FORBIDDEN && FORBIDDEN = FORBIDDEN
251 $access = $forbidden->andif($forbidden);
252 $this->assertFalse($access->isAllowed());
253 $this->assertTrue($access->isForbidden());
254 $this->assertFalse($access->isNeutral());
255 $this->assertEquals('forbidden message', $access->getReason());
256 $this->assertDefaultCacheability($access);
258 // FORBIDDEN && * === FORBIDDEN: lazy evaluation verification.
259 $access = $forbidden->andIf($unused_access_result_due_to_lazy_evaluation);
260 $this->assertFalse($access->isAllowed());
261 $this->assertTrue($access->isForbidden());
262 $this->assertFalse($access->isNeutral());
263 $this->assertEquals('forbidden message', $access->getReason());
264 $this->assertDefaultCacheability($access);
270 public function testOrIf() {
271 $neutral = AccessResult::neutral('neutral message');
272 $allowed = AccessResult::allowed();
273 $forbidden = AccessResult::forbidden('forbidden message');
274 $unused_access_result_due_to_lazy_evaluation = $this->getMock('\Drupal\Core\Access\AccessResultInterface');
275 $unused_access_result_due_to_lazy_evaluation->expects($this->never())
276 ->method($this->anything());
278 // ALLOWED || ALLOWED === ALLOWED.
279 $access = $allowed->orIf($allowed);
280 $this->assertTrue($access->isAllowed());
281 $this->assertFalse($access->isForbidden());
282 $this->assertFalse($access->isNeutral());
283 $this->assertDefaultCacheability($access);
285 // ALLOWED || NEUTRAL === ALLOWED.
286 $access = $allowed->orIf($neutral);
287 $this->assertTrue($access->isAllowed());
288 $this->assertFalse($access->isForbidden());
289 $this->assertFalse($access->isNeutral());
290 $this->assertDefaultCacheability($access);
292 // ALLOWED || FORBIDDEN === FORBIDDEN.
293 $access = $allowed->orIf($forbidden);
294 $this->assertFalse($access->isAllowed());
295 $this->assertTrue($access->isForbidden());
296 $this->assertFalse($access->isNeutral());
297 $this->assertEquals('forbidden message', $access->getReason());
298 $this->assertDefaultCacheability($access);
300 // NEUTRAL || NEUTRAL === NEUTRAL.
301 $access = $neutral->orIf($neutral);
302 $this->assertFalse($access->isAllowed());
303 $this->assertFalse($access->isForbidden());
304 $this->assertTrue($access->isNeutral());
305 $this->assertEquals('neutral message', $access->getReason());
306 $this->assertDefaultCacheability($access);
308 // NEUTRAL || ALLOWED === ALLOWED.
309 $access = $neutral->orIf($allowed);
310 $this->assertTrue($access->isAllowed());
311 $this->assertFalse($access->isForbidden());
312 $this->assertFalse($access->isNeutral());
313 $this->assertDefaultCacheability($access);
315 // NEUTRAL || FORBIDDEN === FORBIDDEN.
316 $access = $neutral->orIf($forbidden);
317 $this->assertFalse($access->isAllowed());
318 $this->assertTrue($access->isForbidden());
319 $this->assertFalse($access->isNeutral());
320 $this->assertEquals('forbidden message', $access->getReason());
321 $this->assertDefaultCacheability($access);
323 // FORBIDDEN || ALLOWED === FORBIDDEN.
324 $access = $forbidden->orIf($allowed);
325 $this->assertFalse($access->isAllowed());
326 $this->assertTrue($access->isForbidden());
327 $this->assertFalse($access->isNeutral());
328 $this->assertEquals('forbidden message', $access->getReason());
329 $this->assertDefaultCacheability($access);
331 // FORBIDDEN || NEUTRAL === FORBIDDEN.
332 $access = $forbidden->orIf($allowed);
333 $this->assertFalse($access->isAllowed());
334 $this->assertTrue($access->isForbidden());
335 $this->assertFalse($access->isNeutral());
336 $this->assertEquals('forbidden message', $access->getReason());
337 $this->assertDefaultCacheability($access);
339 // FORBIDDEN || FORBIDDEN === FORBIDDEN.
340 $access = $forbidden->orIf($allowed);
341 $this->assertFalse($access->isAllowed());
342 $this->assertTrue($access->isForbidden());
343 $this->assertFalse($access->isNeutral());
344 $this->assertEquals('forbidden message', $access->getReason());
345 $this->assertDefaultCacheability($access);
347 // FORBIDDEN || * === FORBIDDEN.
348 $access = $forbidden->orIf($unused_access_result_due_to_lazy_evaluation);
349 $this->assertFalse($access->isAllowed());
350 $this->assertTrue($access->isForbidden());
351 $this->assertFalse($access->isNeutral());
352 $this->assertEquals('forbidden message', $access->getReason());
353 $this->assertDefaultCacheability($access);
357 * @covers ::setCacheMaxAge
358 * @covers ::getCacheMaxAge
360 public function testCacheMaxAge() {
361 $this->assertSame(Cache::PERMANENT, AccessResult::neutral()->getCacheMaxAge());
362 $this->assertSame(1337, AccessResult::neutral()->setCacheMaxAge(1337)->getCacheMaxAge());
366 * @covers ::addCacheContexts
367 * @covers ::resetCacheContexts
368 * @covers ::getCacheContexts
369 * @covers ::cachePerPermissions
370 * @covers ::cachePerUser
371 * @covers ::allowedIfHasPermission
373 public function testCacheContexts() {
374 $verify = function (AccessResult $access, array $contexts) {
375 $this->assertFalse($access->isAllowed());
376 $this->assertFalse($access->isForbidden());
377 $this->assertTrue($access->isNeutral());
378 $this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge());
379 $this->assertSame($contexts, $access->getCacheContexts());
380 $this->assertSame([], $access->getCacheTags());
383 $access = AccessResult::neutral()->addCacheContexts(['foo']);
384 $verify($access, ['foo']);
385 // Verify resetting works.
386 $access->resetCacheContexts();
387 $verify($access, []);
388 // Verify idempotency.
389 $access->addCacheContexts(['foo'])
390 ->addCacheContexts(['foo']);
391 $verify($access, ['foo']);
392 // Verify same values in different call order yields the same result.
393 $access->resetCacheContexts()
394 ->addCacheContexts(['foo'])
395 ->addCacheContexts(['bar']);
396 $verify($access, ['bar', 'foo']);
397 $access->resetCacheContexts()
398 ->addCacheContexts(['bar'])
399 ->addCacheContexts(['foo']);
400 $verify($access, ['bar', 'foo']);
402 // ::cachePerPermissions() convenience method.
403 $contexts = ['user.permissions'];
404 $a = AccessResult::neutral()->addCacheContexts($contexts);
405 $verify($a, $contexts);
406 $b = AccessResult::neutral()->cachePerPermissions();
407 $verify($b, $contexts);
408 $this->assertEquals($a, $b);
410 // ::cachePerUser() convenience method.
411 $contexts = ['user'];
412 $a = AccessResult::neutral()->addCacheContexts($contexts);
413 $verify($a, $contexts);
414 $b = AccessResult::neutral()->cachePerUser();
415 $verify($b, $contexts);
416 $this->assertEquals($a, $b);
419 $contexts = ['user', 'user.permissions'];
420 $a = AccessResult::neutral()->addCacheContexts($contexts);
421 $verify($a, $contexts);
422 $b = AccessResult::neutral()->cachePerPermissions()->cachePerUser();
423 $verify($b, $contexts);
424 $c = AccessResult::neutral()->cachePerUser()->cachePerPermissions();
425 $verify($c, $contexts);
426 $this->assertEquals($a, $b);
427 $this->assertEquals($a, $c);
429 // ::allowIfHasPermission and ::allowedIfHasPermission convenience methods.
430 $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
431 $account->expects($this->any())
432 ->method('hasPermission')
433 ->with('may herd llamas')
434 ->will($this->returnValue(FALSE));
435 $contexts = ['user.permissions'];
437 // Verify the object when using the ::allowedIfHasPermission() convenience
439 $b = AccessResult::allowedIfHasPermission($account, 'may herd llamas');
440 $verify($b, $contexts);
444 * @covers ::addCacheTags
445 * @covers ::addCacheableDependency
446 * @covers ::getCacheTags
447 * @covers ::resetCacheTags
449 public function testCacheTags() {
450 $verify = function (AccessResult $access, array $tags, array $contexts = [], $max_age = Cache::PERMANENT) {
451 $this->assertFalse($access->isAllowed());
452 $this->assertFalse($access->isForbidden());
453 $this->assertTrue($access->isNeutral());
454 $this->assertSame($max_age, $access->getCacheMaxAge());
455 $this->assertSame($contexts, $access->getCacheContexts());
456 $this->assertSame($tags, $access->getCacheTags());
459 $access = AccessResult::neutral()->addCacheTags(['foo:bar']);
460 $verify($access, ['foo:bar']);
461 // Verify resetting works.
462 $access->resetCacheTags();
463 $verify($access, []);
464 // Verify idempotency.
465 $access->addCacheTags(['foo:bar'])
466 ->addCacheTags(['foo:bar']);
467 $verify($access, ['foo:bar']);
468 // Verify same values in different call order yields the same result.
469 $access->resetCacheTags()
470 ->addCacheTags(['bar:baz'])
471 ->addCacheTags(['bar:qux'])
472 ->addCacheTags(['foo:bar'])
473 ->addCacheTags(['foo:baz']);
474 $verify($access, ['bar:baz', 'bar:qux', 'foo:bar', 'foo:baz']);
475 $access->resetCacheTags()
476 ->addCacheTags(['foo:bar'])
477 ->addCacheTags(['bar:qux'])
478 ->addCacheTags(['foo:baz'])
479 ->addCacheTags(['bar:baz']);
480 $verify($access, ['bar:baz', 'bar:qux', 'foo:bar', 'foo:baz']);
482 // ::addCacheableDependency() convenience method.
483 $node = $this->getMock('\Drupal\node\NodeInterface');
484 $node->expects($this->any())
485 ->method('getCacheTags')
486 ->will($this->returnValue(['node:20011988']));
487 $node->expects($this->any())
488 ->method('getCacheMaxAge')
490 $node->expects($this->any())
491 ->method('getCacheContexts')
492 ->willReturn(['user']);
493 $tags = ['node:20011988'];
494 $a = AccessResult::neutral()->addCacheTags($tags);
496 $b = AccessResult::neutral()->addCacheableDependency($node);
497 $verify($b, $tags, ['user'], 600);
499 $non_cacheable_dependency = new \stdClass();
500 $non_cacheable = AccessResult::neutral()->addCacheableDependency($non_cacheable_dependency);
501 $verify($non_cacheable, [], [], 0);
505 * @covers ::inheritCacheability
507 public function testInheritCacheability() {
508 // andIf(); 1st has defaults, 2nd has custom tags, contexts and max-age.
509 $access = AccessResult::allowed();
510 $other = AccessResult::allowed()->setCacheMaxAge(1500)->cachePerPermissions()->addCacheTags(['node:20011988']);
511 $this->assertTrue($access->inheritCacheability($other) instanceof AccessResult);
512 $this->assertSame(['user.permissions'], $access->getCacheContexts());
513 $this->assertSame(['node:20011988'], $access->getCacheTags());
514 $this->assertSame(1500, $access->getCacheMaxAge());
516 // andIf(); 1st has custom tags, max-age, 2nd has custom contexts and max-age.
517 $access = AccessResult::allowed()->cachePerUser()->setCacheMaxAge(43200);
518 $other = AccessResult::forbidden()->addCacheTags(['node:14031991'])->setCacheMaxAge(86400);
519 $this->assertTrue($access->inheritCacheability($other) instanceof AccessResult);
520 $this->assertSame(['user'], $access->getCacheContexts());
521 $this->assertSame(['node:14031991'], $access->getCacheTags());
522 $this->assertSame(43200, $access->getCacheMaxAge());
526 * Provides a list of access result pairs and operations to test.
528 * This tests the propagation of cacheability metadata. Rather than testing
529 * every single bit of cacheability metadata, which would lead to a mind-
530 * boggling number of permutations, in this test, we only consider the
531 * permutations of all pairs of the following set:
532 * - Allowed, implements CDI and is cacheable.
533 * - Allowed, implements CDI and is not cacheable.
534 * - Allowed, does not implement CDI (hence not cacheable).
535 * - Forbidden, implements CDI and is cacheable.
536 * - Forbidden, implements CDI and is not cacheable.
537 * - Forbidden, does not implement CDI (hence not cacheable).
538 * - Neutral, implements CDI and is cacheable.
539 * - Neutral, implements CDI and is not cacheable.
540 * - Neutral, does not implement CDI (hence not cacheable).
542 * (Where "CDI" is CacheableDependencyInterface.)
544 * This leads to 72 permutations (9!/(9-2)! = 9*8 = 72) per operation. There
545 * are two operations to test (AND and OR), so that leads to a grand total of
546 * 144 permutations, all of which are tested.
548 * There are two "contagious" patterns:
549 * - Any operation with a forbidden access result yields a forbidden result.
550 * This therefore also applies to the cacheability metadata associated with
551 * a forbidden result. This is the case for bullets 4, 5 and 6 in the set
553 * - Any operation yields an access result object that is of the same class
554 * (implementation) as the first operand. This is because operations are
555 * invoked on the first operand. Therefore, if the first implementation
556 * does not implement CacheableDependencyInterface, then the result won't
557 * either. This is the case for bullets 3, 6 and 9 in the set above.
559 public function andOrCacheabilityPropagationProvider() {
560 // ct: cacheable=true, cf: cacheable=false, un: uncacheable.
561 // Note: the test cases that have a "un" access result as the first operand
562 // test UncacheableTestAccessResult, not AccessResult. However, we
563 // definitely want to verify that AccessResult's orIf() and andIf() methods
564 // work correctly when given an AccessResultInterface implementation that
565 // does not implement CacheableDependencyInterface, and we want to test the
566 // full gamut of permutations, so that's not a problem.
567 $allowed_ct = AccessResult::allowed();
568 $allowed_cf = AccessResult::allowed()->setCacheMaxAge(0);
569 $allowed_un = new UncacheableTestAccessResult('ALLOWED');
570 $forbidden_ct = AccessResult::forbidden();
571 $forbidden_cf = AccessResult::forbidden()->setCacheMaxAge(0);
572 $forbidden_un = new UncacheableTestAccessResult('FORBIDDEN');
573 $neutral_ct = AccessResult::neutral();
574 $neutral_cf = AccessResult::neutral()->setCacheMaxAge(0);
575 $neutral_un = new UncacheableTestAccessResult('NEUTRAL');
578 // - First column: first access result.
579 // - Second column: operator ('OR' or 'AND').
580 // - Third column: second access result.
581 // - Fourth column: whether result implements CacheableDependencyInterface
582 // - Fifth column: whether the result is cacheable (if column 4 is TRUE)
584 // Allowed (ct) OR allowed (ct,cf,un).
585 [$allowed_ct, 'OR', $allowed_ct, TRUE, TRUE],
586 [$allowed_ct, 'OR', $allowed_cf, TRUE, TRUE],
587 [$allowed_ct, 'OR', $allowed_un, TRUE, TRUE],
588 // Allowed (cf) OR allowed (ct,cf,un).
589 [$allowed_cf, 'OR', $allowed_ct, TRUE, TRUE],
590 [$allowed_cf, 'OR', $allowed_cf, TRUE, FALSE],
591 [$allowed_cf, 'OR', $allowed_un, TRUE, FALSE],
592 // Allowed (un) OR allowed (ct,cf,un).
593 [$allowed_un, 'OR', $allowed_ct, FALSE, NULL],
594 [$allowed_un, 'OR', $allowed_cf, FALSE, NULL],
595 [$allowed_un, 'OR', $allowed_un, FALSE, NULL],
597 // Allowed (ct) OR forbidden (ct,cf,un).
598 [$allowed_ct, 'OR', $forbidden_ct, TRUE, TRUE],
599 [$allowed_ct, 'OR', $forbidden_cf, TRUE, FALSE],
600 [$allowed_ct, 'OR', $forbidden_un, TRUE, FALSE],
601 // Allowed (cf) OR forbidden (ct,cf,un).
602 [$allowed_cf, 'OR', $forbidden_ct, TRUE, TRUE],
603 [$allowed_cf, 'OR', $forbidden_cf, TRUE, FALSE],
604 [$allowed_cf, 'OR', $forbidden_un, TRUE, FALSE],
605 // Allowed (un) OR forbidden (ct,cf,un).
606 [$allowed_un, 'OR', $forbidden_ct, FALSE, NULL],
607 [$allowed_un, 'OR', $forbidden_cf, FALSE, NULL],
608 [$allowed_un, 'OR', $forbidden_un, FALSE, NULL],
610 // Allowed (ct) OR neutral (ct,cf,un).
611 [$allowed_ct, 'OR', $neutral_ct, TRUE, TRUE],
612 [$allowed_ct, 'OR', $neutral_cf, TRUE, TRUE],
613 [$allowed_ct, 'OR', $neutral_un, TRUE, TRUE],
614 // Allowed (cf) OR neutral (ct,cf,un).
615 [$allowed_cf, 'OR', $neutral_ct, TRUE, FALSE],
616 [$allowed_cf, 'OR', $neutral_cf, TRUE, FALSE],
617 [$allowed_cf, 'OR', $neutral_un, TRUE, FALSE],
618 // Allowed (un) OR neutral (ct,cf,un).
619 [$allowed_un, 'OR', $neutral_ct, FALSE, NULL],
620 [$allowed_un, 'OR', $neutral_cf, FALSE, NULL],
621 [$allowed_un, 'OR', $neutral_un, FALSE, NULL],
623 // Forbidden (ct) OR allowed (ct,cf,un).
624 [$forbidden_ct, 'OR', $allowed_ct, TRUE, TRUE],
625 [$forbidden_ct, 'OR', $allowed_cf, TRUE, TRUE],
626 [$forbidden_ct, 'OR', $allowed_un, TRUE, TRUE],
627 // Forbidden (cf) OR allowed (ct,cf,un).
628 [$forbidden_cf, 'OR', $allowed_ct, TRUE, FALSE],
629 [$forbidden_cf, 'OR', $allowed_cf, TRUE, FALSE],
630 [$forbidden_cf, 'OR', $allowed_un, TRUE, FALSE],
631 // Forbidden (un) OR allowed (ct,cf,un).
632 [$forbidden_un, 'OR', $allowed_ct, FALSE, NULL],
633 [$forbidden_un, 'OR', $allowed_cf, FALSE, NULL],
634 [$forbidden_un, 'OR', $allowed_un, FALSE, NULL],
636 // Forbidden (ct) OR neutral (ct,cf,un).
637 [$forbidden_ct, 'OR', $neutral_ct, TRUE, TRUE],
638 [$forbidden_ct, 'OR', $neutral_cf, TRUE, TRUE],
639 [$forbidden_ct, 'OR', $neutral_un, TRUE, TRUE],
640 // Forbidden (cf) OR neutral (ct,cf,un).
641 [$forbidden_cf, 'OR', $neutral_ct, TRUE, FALSE],
642 [$forbidden_cf, 'OR', $neutral_cf, TRUE, FALSE],
643 [$forbidden_cf, 'OR', $neutral_un, TRUE, FALSE],
644 // Forbidden (un) OR neutral (ct,cf,un).
645 [$forbidden_un, 'OR', $neutral_ct, FALSE, NULL],
646 [$forbidden_un, 'OR', $neutral_cf, FALSE, NULL],
647 [$forbidden_un, 'OR', $neutral_un, FALSE, NULL],
649 // Forbidden (ct) OR forbidden (ct,cf,un).
650 [$forbidden_ct, 'OR', $forbidden_ct, TRUE, TRUE],
651 [$forbidden_ct, 'OR', $forbidden_cf, TRUE, TRUE],
652 [$forbidden_ct, 'OR', $forbidden_un, TRUE, TRUE],
653 // Forbidden (cf) OR forbidden (ct,cf,un).
654 [$forbidden_cf, 'OR', $forbidden_ct, TRUE, TRUE],
655 [$forbidden_cf, 'OR', $forbidden_cf, TRUE, FALSE],
656 [$forbidden_cf, 'OR', $forbidden_un, TRUE, FALSE],
657 // Forbidden (un) OR forbidden (ct,cf,un).
658 [$forbidden_un, 'OR', $forbidden_ct, FALSE, NULL],
659 [$forbidden_un, 'OR', $forbidden_cf, FALSE, NULL],
660 [$forbidden_un, 'OR', $forbidden_un, FALSE, NULL],
662 // Neutral (ct) OR allowed (ct,cf,un).
663 [$neutral_ct, 'OR', $allowed_ct, TRUE, TRUE],
664 [$neutral_ct, 'OR', $allowed_cf, TRUE, FALSE],
665 [$neutral_ct, 'OR', $allowed_un, TRUE, FALSE],
666 // Neutral (cf) OR allowed (ct,cf,un).
667 [$neutral_cf, 'OR', $allowed_ct, TRUE, TRUE],
668 [$neutral_cf, 'OR', $allowed_cf, TRUE, FALSE],
669 [$neutral_cf, 'OR', $allowed_un, TRUE, FALSE],
670 // Neutral (un) OR allowed (ct,cf,un).
671 [$neutral_un, 'OR', $allowed_ct, FALSE, NULL],
672 [$neutral_un, 'OR', $allowed_cf, FALSE, NULL],
673 [$neutral_un, 'OR', $allowed_un, FALSE, NULL],
675 // Neutral (ct) OR neutral (ct,cf,un).
676 [$neutral_ct, 'OR', $neutral_ct, TRUE, TRUE],
677 [$neutral_ct, 'OR', $neutral_cf, TRUE, TRUE],
678 [$neutral_ct, 'OR', $neutral_un, TRUE, TRUE],
679 // Neutral (cf) OR neutral (ct,cf,un).
680 [$neutral_cf, 'OR', $neutral_ct, TRUE, TRUE],
681 [$neutral_cf, 'OR', $neutral_cf, TRUE, FALSE],
682 [$neutral_cf, 'OR', $neutral_un, TRUE, FALSE],
683 // Neutral (un) OR neutral (ct,cf,un).
684 [$neutral_un, 'OR', $neutral_ct, FALSE, NULL],
685 [$neutral_un, 'OR', $neutral_cf, FALSE, NULL],
686 [$neutral_un, 'OR', $neutral_un, FALSE, NULL],
688 // Neutral (ct) OR forbidden (ct,cf,un).
689 [$neutral_ct, 'OR', $forbidden_ct, TRUE, TRUE],
690 [$neutral_ct, 'OR', $forbidden_cf, TRUE, FALSE],
691 [$neutral_ct, 'OR', $forbidden_un, TRUE, FALSE],
692 // Neutral (cf) OR forbidden (ct,cf,un).
693 [$neutral_cf, 'OR', $forbidden_ct, TRUE, TRUE],
694 [$neutral_cf, 'OR', $forbidden_cf, TRUE, FALSE],
695 [$neutral_cf, 'OR', $forbidden_un, TRUE, FALSE],
696 // Neutral (un) OR forbidden (ct,cf,un).
697 [$neutral_un, 'OR', $forbidden_ct, FALSE, NULL],
698 [$neutral_un, 'OR', $forbidden_cf, FALSE, NULL],
699 [$neutral_un, 'OR', $forbidden_un, FALSE, NULL],
701 // Allowed (ct) AND allowed (ct,cf,un).
702 [$allowed_ct, 'AND', $allowed_ct, TRUE, TRUE],
703 [$allowed_ct, 'AND', $allowed_cf, TRUE, FALSE],
704 [$allowed_ct, 'AND', $allowed_un, TRUE, FALSE],
705 // Allowed (cf) AND allowed (ct,cf,un).
706 [$allowed_cf, 'AND', $allowed_ct, TRUE, FALSE],
707 [$allowed_cf, 'AND', $allowed_cf, TRUE, FALSE],
708 [$allowed_cf, 'AND', $allowed_un, TRUE, FALSE],
709 // Allowed (un) AND allowed (ct,cf,un).
710 [$allowed_un, 'AND', $allowed_ct, FALSE, NULL],
711 [$allowed_un, 'AND', $allowed_cf, FALSE, NULL],
712 [$allowed_un, 'AND', $allowed_un, FALSE, NULL],
714 // Allowed (ct) AND forbidden (ct,cf,un).
715 [$allowed_ct, 'AND', $forbidden_ct, TRUE, TRUE],
716 [$allowed_ct, 'AND', $forbidden_cf, TRUE, FALSE],
717 [$allowed_ct, 'AND', $forbidden_un, TRUE, FALSE],
718 // Allowed (cf) AND forbidden (ct,cf,un).
719 [$allowed_cf, 'AND', $forbidden_ct, TRUE, TRUE],
720 [$allowed_cf, 'AND', $forbidden_cf, TRUE, FALSE],
721 [$allowed_cf, 'AND', $forbidden_un, TRUE, FALSE],
722 // Allowed (un) AND forbidden (ct,cf,un).
723 [$allowed_un, 'AND', $forbidden_ct, FALSE, NULL],
724 [$allowed_un, 'AND', $forbidden_cf, FALSE, NULL],
725 [$allowed_un, 'AND', $forbidden_un, FALSE, NULL],
727 // Allowed (ct) AND neutral (ct,cf,un).
728 [$allowed_ct, 'AND', $neutral_ct, TRUE, TRUE],
729 [$allowed_ct, 'AND', $neutral_cf, TRUE, FALSE],
730 [$allowed_ct, 'AND', $neutral_un, TRUE, FALSE],
731 // Allowed (cf) AND neutral (ct,cf,un).
732 [$allowed_cf, 'AND', $neutral_ct, TRUE, FALSE],
733 [$allowed_cf, 'AND', $neutral_cf, TRUE, FALSE],
734 [$allowed_cf, 'AND', $neutral_un, TRUE, FALSE],
735 // Allowed (un) AND neutral (ct,cf,un).
736 [$allowed_un, 'AND', $neutral_ct, FALSE, NULL],
737 [$allowed_un, 'AND', $neutral_cf, FALSE, NULL],
738 [$allowed_un, 'AND', $neutral_un, FALSE, NULL],
740 // Forbidden (ct) AND allowed (ct,cf,un).
741 [$forbidden_ct, 'AND', $allowed_ct, TRUE, TRUE],
742 [$forbidden_ct, 'AND', $allowed_cf, TRUE, TRUE],
743 [$forbidden_ct, 'AND', $allowed_un, TRUE, TRUE],
744 // Forbidden (cf) AND allowed (ct,cf,un).
745 [$forbidden_cf, 'AND', $allowed_ct, TRUE, FALSE],
746 [$forbidden_cf, 'AND', $allowed_cf, TRUE, FALSE],
747 [$forbidden_cf, 'AND', $allowed_un, TRUE, FALSE],
748 // Forbidden (un) AND allowed (ct,cf,un).
749 [$forbidden_un, 'AND', $allowed_ct, FALSE, NULL],
750 [$forbidden_un, 'AND', $allowed_cf, FALSE, NULL],
751 [$forbidden_un, 'AND', $allowed_un, FALSE, NULL],
753 // Forbidden (ct) AND neutral (ct,cf,un).
754 [$forbidden_ct, 'AND', $neutral_ct, TRUE, TRUE],
755 [$forbidden_ct, 'AND', $neutral_cf, TRUE, TRUE],
756 [$forbidden_ct, 'AND', $neutral_un, TRUE, TRUE],
757 // Forbidden (cf) AND neutral (ct,cf,un).
758 [$forbidden_cf, 'AND', $neutral_ct, TRUE, FALSE],
759 [$forbidden_cf, 'AND', $neutral_cf, TRUE, FALSE],
760 [$forbidden_cf, 'AND', $neutral_un, TRUE, FALSE],
761 // Forbidden (un) AND neutral (ct,cf,un).
762 [$forbidden_un, 'AND', $neutral_ct, FALSE, NULL],
763 [$forbidden_un, 'AND', $neutral_cf, FALSE, NULL],
764 [$forbidden_un, 'AND', $neutral_un, FALSE, NULL],
766 // Forbidden (ct) AND forbidden (ct,cf,un).
767 [$forbidden_ct, 'AND', $forbidden_ct, TRUE, TRUE],
768 [$forbidden_ct, 'AND', $forbidden_cf, TRUE, TRUE],
769 [$forbidden_ct, 'AND', $forbidden_un, TRUE, TRUE],
770 // Forbidden (cf) AND forbidden (ct,cf,un).
771 [$forbidden_cf, 'AND', $forbidden_ct, TRUE, FALSE],
772 [$forbidden_cf, 'AND', $forbidden_cf, TRUE, FALSE],
773 [$forbidden_cf, 'AND', $forbidden_un, TRUE, FALSE],
774 // Forbidden (un) AND forbidden (ct,cf,un).
775 [$forbidden_un, 'AND', $forbidden_ct, FALSE, NULL],
776 [$forbidden_un, 'AND', $forbidden_cf, FALSE, NULL],
777 [$forbidden_un, 'AND', $forbidden_un, FALSE, NULL],
779 // Neutral (ct) AND allowed (ct,cf,un).
780 [$neutral_ct, 'AND', $allowed_ct, TRUE, TRUE],
781 [$neutral_ct, 'AND', $allowed_cf, TRUE, TRUE],
782 [$neutral_ct, 'AND', $allowed_un, TRUE, TRUE],
783 // Neutral (cf) AND allowed (ct,cf,un).
784 [$neutral_cf, 'AND', $allowed_ct, TRUE, FALSE],
785 [$neutral_cf, 'AND', $allowed_cf, TRUE, FALSE],
786 [$neutral_cf, 'AND', $allowed_un, TRUE, FALSE],
787 // Neutral (un) AND allowed (ct,cf,un).
788 [$neutral_un, 'AND', $allowed_ct, FALSE, NULL],
789 [$neutral_un, 'AND', $allowed_cf, FALSE, NULL],
790 [$neutral_un, 'AND', $allowed_un, FALSE, NULL],
792 // Neutral (ct) AND neutral (ct,cf,un).
793 [$neutral_ct, 'AND', $neutral_ct, TRUE, TRUE],
794 [$neutral_ct, 'AND', $neutral_cf, TRUE, TRUE],
795 [$neutral_ct, 'AND', $neutral_un, TRUE, TRUE],
796 // Neutral (cf) AND neutral (ct,cf,un).
797 [$neutral_cf, 'AND', $neutral_ct, TRUE, FALSE],
798 [$neutral_cf, 'AND', $neutral_cf, TRUE, FALSE],
799 [$neutral_cf, 'AND', $neutral_un, TRUE, FALSE],
800 // Neutral (un) AND neutral (ct,cf,un).
801 [$neutral_un, 'AND', $neutral_ct, FALSE, NULL],
802 [$neutral_un, 'AND', $neutral_cf, FALSE, NULL],
803 [$neutral_un, 'AND', $neutral_un, FALSE, NULL],
805 // Neutral (ct) AND forbidden (ct,cf,un).
806 [$neutral_ct, 'AND', $forbidden_ct, TRUE, TRUE],
807 [$neutral_ct, 'AND', $forbidden_cf, TRUE, FALSE],
808 [$neutral_ct, 'AND', $forbidden_un, TRUE, FALSE],
809 // Neutral (cf) AND forbidden (ct,cf,un).
810 [$neutral_cf, 'AND', $forbidden_ct, TRUE, TRUE],
811 [$neutral_cf, 'AND', $forbidden_cf, TRUE, FALSE],
812 [$neutral_cf, 'AND', $forbidden_un, TRUE, FALSE],
813 // Neutral (un) AND forbidden (ct,cf,un).
814 [$neutral_un, 'AND', $forbidden_ct, FALSE, NULL],
815 [$neutral_un, 'AND', $forbidden_cf, FALSE, NULL],
816 [$neutral_un, 'AND', $forbidden_un, FALSE, NULL],
823 * @covers ::inheritCacheability
825 * @dataProvider andOrCacheabilityPropagationProvider
827 public function testAndOrCacheabilityPropagation(AccessResultInterface $first, $op, AccessResultInterface $second, $implements_cacheable_dependency_interface, $is_cacheable) {
829 $result = $first->orIf($second);
831 elseif ($op === 'AND') {
832 $result = $first->andIf($second);
835 throw new \LogicException('Invalid operator specified');
837 if ($implements_cacheable_dependency_interface) {
838 $this->assertTrue($result instanceof CacheableDependencyInterface, 'Result is an instance of CacheableDependencyInterface.');
839 if ($result instanceof CacheableDependencyInterface) {
840 $this->assertSame($is_cacheable, $result->getCacheMaxAge() !== 0, 'getCacheMaxAge() matches expectations.');
844 $this->assertFalse($result instanceof CacheableDependencyInterface, 'Result is not an instance of CacheableDependencyInterface.');
851 * Tests the special case of ORing non-forbidden access results that are both
852 * cacheable but have different cacheability metadata.
853 * This is only the case for non-forbidden access results; we still abort the
854 * ORing process as soon as a forbidden access result is encountered. This is
855 * tested in ::testOrIf().
857 public function testOrIfCacheabilityMerging() {
858 $merge_both_directions = function (AccessResult $a, AccessResult $b) {
859 // A globally cacheable access result.
860 $a->setCacheMaxAge(3600);
861 // Another access result that is cacheable per permissions.
862 $b->setCacheMaxAge(86400)->cachePerPermissions();
865 $this->assertTrue($r1->getCacheMaxAge() === 3600);
866 $this->assertSame(['user.permissions'], $r1->getCacheContexts());
868 $this->assertTrue($r2->getCacheMaxAge() === 3600);
869 $this->assertSame(['user.permissions'], $r2->getCacheContexts());
872 // Merge either direction, get the same result.
873 $merge_both_directions(AccessResult::allowed(), AccessResult::allowed());
874 $merge_both_directions(AccessResult::allowed(), AccessResult::neutral());
875 $merge_both_directions(AccessResult::neutral(), AccessResult::neutral());
876 $merge_both_directions(AccessResult::neutral(), AccessResult::allowed());
880 * Tests allowedIfHasPermissions().
882 * @covers ::allowedIfHasPermissions
884 * @dataProvider providerTestAllowedIfHasPermissions
886 * @param string[] $permissions
887 * The permissions to check for.
888 * @param string $conjunction
889 * The conjunction to use when checking for permission. 'AND' or 'OR'.
890 * @param \Drupal\Core\Access\AccessResult $expected_access
891 * The expected access check result.
893 public function testAllowedIfHasPermissions($permissions, $conjunction, AccessResult $expected_access) {
894 $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
895 $account->expects($this->any())
896 ->method('hasPermission')
903 $expected_access->cachePerPermissions();
906 $access_result = AccessResult::allowedIfHasPermissions($account, $permissions, $conjunction);
907 $this->assertEquals($expected_access, $access_result);
911 * Provides data for the testAllowedIfHasPermissions() method.
915 public function providerTestAllowedIfHasPermissions() {
916 $access_result = AccessResult::allowedIf(FALSE);
917 $data[] = [[], 'AND', $access_result];
918 $data[] = [[], 'OR', $access_result];
920 $access_result = AccessResult::allowedIf(TRUE);
921 $data[] = [['allowed'], 'OR', $access_result];
922 $data[] = [['allowed'], 'AND', $access_result];
924 $access_result = AccessResult::allowedIf(FALSE);
925 $access_result->setReason("The 'denied' permission is required.");
926 $data[] = [['denied'], 'OR', $access_result];
927 $data[] = [['denied'], 'AND', $access_result];
929 $access_result = AccessResult::allowedIf(TRUE);
930 $data[] = [['allowed', 'denied'], 'OR', $access_result];
931 $data[] = [['denied', 'allowed'], 'OR', $access_result];
933 $access_result = AccessResult::allowedIf(TRUE);
934 $data[] = [['allowed', 'denied', 'other'], 'OR', $access_result];
936 $access_result = AccessResult::allowedIf(FALSE);
937 $access_result->setReason("The following permissions are required: 'allowed' AND 'denied'.");
938 $data[] = [['allowed', 'denied'], 'AND', $access_result];
945 class UncacheableTestAccessResult implements AccessResultInterface {
948 * The access result value. 'ALLOWED', 'FORBIDDEN' or 'NEUTRAL'.
955 * Constructs a new UncacheableTestAccessResult object.
957 public function __construct($value) {
958 $this->value = $value;
963 public function isAllowed() {
964 return $this->value === 'ALLOWED';
970 public function isForbidden() {
971 return $this->value === 'FORBIDDEN';
977 public function isNeutral() {
978 return $this->value === 'NEUTRAL';
984 public function orIf(AccessResultInterface $other) {
985 if ($this->isForbidden() || $other->isForbidden()) {
986 return new static('FORBIDDEN');
988 elseif ($this->isAllowed() || $other->isAllowed()) {
989 return new static('ALLOWED');
992 return new static('NEUTRAL');
999 public function andIf(AccessResultInterface $other) {
1000 if ($this->isForbidden() || $other->isForbidden()) {
1001 return new static('FORBIDDEN');
1003 elseif ($this->isAllowed() && $other->isAllowed()) {
1004 return new static('ALLOWED');
1007 return new static('NEUTRAL');