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);
134 * @covers ::allowedIf
135 * @covers ::isAllowed
136 * @covers ::isForbidden
137 * @covers ::isNeutral
139 public function testAccessConditionallyAllowed() {
140 $verify = function (AccessResult $access, $allowed) {
141 $this->assertSame($allowed, $access->isAllowed());
142 $this->assertFalse($access->isForbidden());
143 $this->assertSame(!$allowed, $access->isNeutral());
144 $this->assertDefaultCacheability($access);
147 $b1 = AccessResult::allowedIf(TRUE);
149 $b2 = AccessResult::allowedIf(FALSE);
154 * @covers ::forbiddenIf
155 * @covers ::isAllowed
156 * @covers ::isForbidden
157 * @covers ::isNeutral
159 public function testAccessConditionallyForbidden() {
160 $verify = function (AccessResult $access, $forbidden) {
161 $this->assertFalse($access->isAllowed());
162 $this->assertSame($forbidden, $access->isForbidden());
163 $this->assertSame(!$forbidden, $access->isNeutral());
164 $this->assertDefaultCacheability($access);
167 $b1 = AccessResult::forbiddenIf(TRUE);
169 $b2 = AccessResult::forbiddenIf(FALSE);
176 public function testAndIf() {
177 $neutral = AccessResult::neutral('neutral message');
178 $allowed = AccessResult::allowed();
179 $forbidden = AccessResult::forbidden('forbidden message');
180 $unused_access_result_due_to_lazy_evaluation = $this->getMock('\Drupal\Core\Access\AccessResultInterface');
181 $unused_access_result_due_to_lazy_evaluation->expects($this->never())
182 ->method($this->anything());
184 // ALLOWED && ALLOWED === ALLOWED.
185 $access = $allowed->andIf($allowed);
186 $this->assertTrue($access->isAllowed());
187 $this->assertFalse($access->isForbidden());
188 $this->assertFalse($access->isNeutral());
189 $this->assertDefaultCacheability($access);
191 // ALLOWED && NEUTRAL === NEUTRAL.
192 $access = $allowed->andIf($neutral);
193 $this->assertFalse($access->isAllowed());
194 $this->assertFalse($access->isForbidden());
195 $this->assertTrue($access->isNeutral());
196 $this->assertEquals('neutral message', $access->getReason());
197 $this->assertDefaultCacheability($access);
199 // ALLOWED && FORBIDDEN === FORBIDDEN.
200 $access = $allowed->andIf($forbidden);
201 $this->assertFalse($access->isAllowed());
202 $this->assertTrue($access->isForbidden());
203 $this->assertFalse($access->isNeutral());
204 $this->assertEquals('forbidden message', $access->getReason());
205 $this->assertDefaultCacheability($access);
207 // NEUTRAL && ALLOW == NEUTRAL
208 $access = $neutral->andIf($allowed);
209 $this->assertFalse($access->isAllowed());
210 $this->assertFalse($access->isForbidden());
211 $this->assertTrue($access->isNeutral());
212 $this->assertEquals('neutral message', $access->getReason());
213 $this->assertDefaultCacheability($access);
215 // NEUTRAL && NEUTRAL === NEUTRAL.
216 $access = $neutral->andIf($neutral);
217 $this->assertFalse($access->isAllowed());
218 $this->assertFalse($access->isForbidden());
219 $this->assertTrue($access->isNeutral());
220 $this->assertEquals('neutral message', $access->getReason());
221 $this->assertDefaultCacheability($access);
223 // NEUTRAL && FORBIDDEN === FORBIDDEN.
224 $access = $neutral->andIf($forbidden);
225 $this->assertFalse($access->isAllowed());
226 $this->assertTrue($access->isForbidden());
227 $this->assertFalse($access->isNeutral());
228 $this->assertEquals('forbidden message', $access->getReason());
229 $this->assertDefaultCacheability($access);
231 // FORBIDDEN && ALLOWED = FORBIDDEN
232 $access = $forbidden->andif($allowed);
233 $this->assertFalse($access->isAllowed());
234 $this->assertTrue($access->isForbidden());
235 $this->assertFalse($access->isNeutral());
236 $this->assertEquals('forbidden message', $access->getReason());
237 $this->assertDefaultCacheability($access);
239 // FORBIDDEN && NEUTRAL = FORBIDDEN
240 $access = $forbidden->andif($neutral);
241 $this->assertFalse($access->isAllowed());
242 $this->assertTrue($access->isForbidden());
243 $this->assertFalse($access->isNeutral());
244 $this->assertEquals('forbidden message', $access->getReason());
245 $this->assertDefaultCacheability($access);
247 // FORBIDDEN && FORBIDDEN = FORBIDDEN
248 $access = $forbidden->andif($forbidden);
249 $this->assertFalse($access->isAllowed());
250 $this->assertTrue($access->isForbidden());
251 $this->assertFalse($access->isNeutral());
252 $this->assertEquals('forbidden message', $access->getReason());
253 $this->assertDefaultCacheability($access);
255 // FORBIDDEN && * === FORBIDDEN: lazy evaluation verification.
256 $access = $forbidden->andIf($unused_access_result_due_to_lazy_evaluation);
257 $this->assertFalse($access->isAllowed());
258 $this->assertTrue($access->isForbidden());
259 $this->assertFalse($access->isNeutral());
260 $this->assertEquals('forbidden message', $access->getReason());
261 $this->assertDefaultCacheability($access);
267 public function testOrIf() {
268 $neutral = AccessResult::neutral('neutral message');
269 $allowed = AccessResult::allowed();
270 $forbidden = AccessResult::forbidden('forbidden message');
271 $unused_access_result_due_to_lazy_evaluation = $this->getMock('\Drupal\Core\Access\AccessResultInterface');
272 $unused_access_result_due_to_lazy_evaluation->expects($this->never())
273 ->method($this->anything());
275 // ALLOWED || ALLOWED === ALLOWED.
276 $access = $allowed->orIf($allowed);
277 $this->assertTrue($access->isAllowed());
278 $this->assertFalse($access->isForbidden());
279 $this->assertFalse($access->isNeutral());
280 $this->assertDefaultCacheability($access);
282 // ALLOWED || NEUTRAL === ALLOWED.
283 $access = $allowed->orIf($neutral);
284 $this->assertTrue($access->isAllowed());
285 $this->assertFalse($access->isForbidden());
286 $this->assertFalse($access->isNeutral());
287 $this->assertDefaultCacheability($access);
289 // ALLOWED || FORBIDDEN === FORBIDDEN.
290 $access = $allowed->orIf($forbidden);
291 $this->assertFalse($access->isAllowed());
292 $this->assertTrue($access->isForbidden());
293 $this->assertFalse($access->isNeutral());
294 $this->assertEquals('forbidden message', $access->getReason());
295 $this->assertDefaultCacheability($access);
297 // NEUTRAL || NEUTRAL === NEUTRAL.
298 $access = $neutral->orIf($neutral);
299 $this->assertFalse($access->isAllowed());
300 $this->assertFalse($access->isForbidden());
301 $this->assertTrue($access->isNeutral());
302 $this->assertEquals('neutral message', $access->getReason());
303 $this->assertDefaultCacheability($access);
305 // NEUTRAL || ALLOWED === ALLOWED.
306 $access = $neutral->orIf($allowed);
307 $this->assertTrue($access->isAllowed());
308 $this->assertFalse($access->isForbidden());
309 $this->assertFalse($access->isNeutral());
310 $this->assertDefaultCacheability($access);
312 // NEUTRAL || FORBIDDEN === FORBIDDEN.
313 $access = $neutral->orIf($forbidden);
314 $this->assertFalse($access->isAllowed());
315 $this->assertTrue($access->isForbidden());
316 $this->assertFalse($access->isNeutral());
317 $this->assertEquals('forbidden message', $access->getReason());
318 $this->assertDefaultCacheability($access);
320 // FORBIDDEN || ALLOWED === FORBIDDEN.
321 $access = $forbidden->orIf($allowed);
322 $this->assertFalse($access->isAllowed());
323 $this->assertTrue($access->isForbidden());
324 $this->assertFalse($access->isNeutral());
325 $this->assertEquals('forbidden message', $access->getReason());
326 $this->assertDefaultCacheability($access);
328 // FORBIDDEN || NEUTRAL === FORBIDDEN.
329 $access = $forbidden->orIf($allowed);
330 $this->assertFalse($access->isAllowed());
331 $this->assertTrue($access->isForbidden());
332 $this->assertFalse($access->isNeutral());
333 $this->assertEquals('forbidden message', $access->getReason());
334 $this->assertDefaultCacheability($access);
336 // FORBIDDEN || FORBIDDEN === FORBIDDEN.
337 $access = $forbidden->orIf($allowed);
338 $this->assertFalse($access->isAllowed());
339 $this->assertTrue($access->isForbidden());
340 $this->assertFalse($access->isNeutral());
341 $this->assertEquals('forbidden message', $access->getReason());
342 $this->assertDefaultCacheability($access);
344 // FORBIDDEN || * === FORBIDDEN.
345 $access = $forbidden->orIf($unused_access_result_due_to_lazy_evaluation);
346 $this->assertFalse($access->isAllowed());
347 $this->assertTrue($access->isForbidden());
348 $this->assertFalse($access->isNeutral());
349 $this->assertEquals('forbidden message', $access->getReason());
350 $this->assertDefaultCacheability($access);
354 * @covers ::setCacheMaxAge
355 * @covers ::getCacheMaxAge
357 public function testCacheMaxAge() {
358 $this->assertSame(Cache::PERMANENT, AccessResult::neutral()->getCacheMaxAge());
359 $this->assertSame(1337, AccessResult::neutral()->setCacheMaxAge(1337)->getCacheMaxAge());
363 * @covers ::addCacheContexts
364 * @covers ::resetCacheContexts
365 * @covers ::getCacheContexts
366 * @covers ::cachePerPermissions
367 * @covers ::cachePerUser
368 * @covers ::allowedIfHasPermission
370 public function testCacheContexts() {
371 $verify = function (AccessResult $access, array $contexts) {
372 $this->assertFalse($access->isAllowed());
373 $this->assertFalse($access->isForbidden());
374 $this->assertTrue($access->isNeutral());
375 $this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge());
376 $this->assertSame($contexts, $access->getCacheContexts());
377 $this->assertSame([], $access->getCacheTags());
380 $access = AccessResult::neutral()->addCacheContexts(['foo']);
381 $verify($access, ['foo']);
382 // Verify resetting works.
383 $access->resetCacheContexts();
384 $verify($access, []);
385 // Verify idempotency.
386 $access->addCacheContexts(['foo'])
387 ->addCacheContexts(['foo']);
388 $verify($access, ['foo']);
389 // Verify same values in different call order yields the same result.
390 $access->resetCacheContexts()
391 ->addCacheContexts(['foo'])
392 ->addCacheContexts(['bar']);
393 $verify($access, ['bar', 'foo']);
394 $access->resetCacheContexts()
395 ->addCacheContexts(['bar'])
396 ->addCacheContexts(['foo']);
397 $verify($access, ['bar', 'foo']);
399 // ::cachePerPermissions() convenience method.
400 $contexts = ['user.permissions'];
401 $a = AccessResult::neutral()->addCacheContexts($contexts);
402 $verify($a, $contexts);
403 $b = AccessResult::neutral()->cachePerPermissions();
404 $verify($b, $contexts);
405 $this->assertEquals($a, $b);
407 // ::cachePerUser() convenience method.
408 $contexts = ['user'];
409 $a = AccessResult::neutral()->addCacheContexts($contexts);
410 $verify($a, $contexts);
411 $b = AccessResult::neutral()->cachePerUser();
412 $verify($b, $contexts);
413 $this->assertEquals($a, $b);
416 $contexts = ['user', 'user.permissions'];
417 $a = AccessResult::neutral()->addCacheContexts($contexts);
418 $verify($a, $contexts);
419 $b = AccessResult::neutral()->cachePerPermissions()->cachePerUser();
420 $verify($b, $contexts);
421 $c = AccessResult::neutral()->cachePerUser()->cachePerPermissions();
422 $verify($c, $contexts);
423 $this->assertEquals($a, $b);
424 $this->assertEquals($a, $c);
426 // ::allowIfHasPermission and ::allowedIfHasPermission convenience methods.
427 $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
428 $account->expects($this->any())
429 ->method('hasPermission')
430 ->with('may herd llamas')
431 ->will($this->returnValue(FALSE));
432 $contexts = ['user.permissions'];
434 // Verify the object when using the ::allowedIfHasPermission() convenience
436 $b = AccessResult::allowedIfHasPermission($account, 'may herd llamas');
437 $verify($b, $contexts);
441 * @covers ::addCacheTags
442 * @covers ::addCacheableDependency
443 * @covers ::getCacheTags
444 * @covers ::resetCacheTags
446 public function testCacheTags() {
447 $verify = function (AccessResult $access, array $tags, array $contexts = [], $max_age = Cache::PERMANENT) {
448 $this->assertFalse($access->isAllowed());
449 $this->assertFalse($access->isForbidden());
450 $this->assertTrue($access->isNeutral());
451 $this->assertSame($max_age, $access->getCacheMaxAge());
452 $this->assertSame($contexts, $access->getCacheContexts());
453 $this->assertSame($tags, $access->getCacheTags());
456 $access = AccessResult::neutral()->addCacheTags(['foo:bar']);
457 $verify($access, ['foo:bar']);
458 // Verify resetting works.
459 $access->resetCacheTags();
460 $verify($access, []);
461 // Verify idempotency.
462 $access->addCacheTags(['foo:bar'])
463 ->addCacheTags(['foo:bar']);
464 $verify($access, ['foo:bar']);
465 // Verify same values in different call order yields the same result.
466 $access->resetCacheTags()
467 ->addCacheTags(['bar:baz'])
468 ->addCacheTags(['bar:qux'])
469 ->addCacheTags(['foo:bar'])
470 ->addCacheTags(['foo:baz']);
471 $verify($access, ['bar:baz', 'bar:qux', 'foo:bar', 'foo:baz']);
472 $access->resetCacheTags()
473 ->addCacheTags(['foo:bar'])
474 ->addCacheTags(['bar:qux'])
475 ->addCacheTags(['foo:baz'])
476 ->addCacheTags(['bar:baz']);
477 $verify($access, ['bar:baz', 'bar:qux', 'foo:bar', 'foo:baz']);
479 // ::addCacheableDependency() convenience method.
480 $node = $this->getMock('\Drupal\node\NodeInterface');
481 $node->expects($this->any())
482 ->method('getCacheTags')
483 ->will($this->returnValue(['node:20011988']));
484 $node->expects($this->any())
485 ->method('getCacheMaxAge')
487 $node->expects($this->any())
488 ->method('getCacheContexts')
489 ->willReturn(['user']);
490 $tags = ['node:20011988'];
491 $a = AccessResult::neutral()->addCacheTags($tags);
493 $b = AccessResult::neutral()->addCacheableDependency($node);
494 $verify($b, $tags, ['user'], 600);
496 $non_cacheable_dependency = new \stdClass();
497 $non_cacheable = AccessResult::neutral()->addCacheableDependency($non_cacheable_dependency);
498 $verify($non_cacheable, [], [], 0);
502 * @covers ::inheritCacheability
504 public function testInheritCacheability() {
505 // andIf(); 1st has defaults, 2nd has custom tags, contexts and max-age.
506 $access = AccessResult::allowed();
507 $other = AccessResult::allowed()->setCacheMaxAge(1500)->cachePerPermissions()->addCacheTags(['node:20011988']);
508 $this->assertTrue($access->inheritCacheability($other) instanceof AccessResult);
509 $this->assertSame(['user.permissions'], $access->getCacheContexts());
510 $this->assertSame(['node:20011988'], $access->getCacheTags());
511 $this->assertSame(1500, $access->getCacheMaxAge());
513 // andIf(); 1st has custom tags, max-age, 2nd has custom contexts and max-age.
514 $access = AccessResult::allowed()->cachePerUser()->setCacheMaxAge(43200);
515 $other = AccessResult::forbidden()->addCacheTags(['node:14031991'])->setCacheMaxAge(86400);
516 $this->assertTrue($access->inheritCacheability($other) instanceof AccessResult);
517 $this->assertSame(['user'], $access->getCacheContexts());
518 $this->assertSame(['node:14031991'], $access->getCacheTags());
519 $this->assertSame(43200, $access->getCacheMaxAge());
523 * Provides a list of access result pairs and operations to test.
525 * This tests the propagation of cacheability metadata. Rather than testing
526 * every single bit of cacheability metadata, which would lead to a mind-
527 * boggling number of permutations, in this test, we only consider the
528 * permutations of all pairs of the following set:
529 * - Allowed, implements CDI and is cacheable.
530 * - Allowed, implements CDI and is not cacheable.
531 * - Allowed, does not implement CDI (hence not cacheable).
532 * - Forbidden, implements CDI and is cacheable.
533 * - Forbidden, implements CDI and is not cacheable.
534 * - Forbidden, does not implement CDI (hence not cacheable).
535 * - Neutral, implements CDI and is cacheable.
536 * - Neutral, implements CDI and is not cacheable.
537 * - Neutral, does not implement CDI (hence not cacheable).
539 * (Where "CDI" is CacheableDependencyInterface.)
541 * This leads to 72 permutations (9!/(9-2)! = 9*8 = 72) per operation. There
542 * are two operations to test (AND and OR), so that leads to a grand total of
543 * 144 permutations, all of which are tested.
545 * There are two "contagious" patterns:
546 * - Any operation with a forbidden access result yields a forbidden result.
547 * This therefore also applies to the cacheability metadata associated with
548 * a forbidden result. This is the case for bullets 4, 5 and 6 in the set
550 * - Any operation yields an access result object that is of the same class
551 * (implementation) as the first operand. This is because operations are
552 * invoked on the first operand. Therefore, if the first implementation
553 * does not implement CacheableDependencyInterface, then the result won't
554 * either. This is the case for bullets 3, 6 and 9 in the set above.
556 public function andOrCacheabilityPropagationProvider() {
557 // ct: cacheable=true, cf: cacheable=false, un: uncacheable.
558 // Note: the test cases that have a "un" access result as the first operand
559 // test UncacheableTestAccessResult, not AccessResult. However, we
560 // definitely want to verify that AccessResult's orIf() and andIf() methods
561 // work correctly when given an AccessResultInterface implementation that
562 // does not implement CacheableDependencyInterface, and we want to test the
563 // full gamut of permutations, so that's not a problem.
564 $allowed_ct = AccessResult::allowed();
565 $allowed_cf = AccessResult::allowed()->setCacheMaxAge(0);
566 $allowed_un = new UncacheableTestAccessResult('ALLOWED');
567 $forbidden_ct = AccessResult::forbidden();
568 $forbidden_cf = AccessResult::forbidden()->setCacheMaxAge(0);
569 $forbidden_un = new UncacheableTestAccessResult('FORBIDDEN');
570 $neutral_ct = AccessResult::neutral();
571 $neutral_cf = AccessResult::neutral()->setCacheMaxAge(0);
572 $neutral_un = new UncacheableTestAccessResult('NEUTRAL');
575 // - First column: first access result.
576 // - Second column: operator ('OR' or 'AND').
577 // - Third column: second access result.
578 // - Fourth column: whether result implements CacheableDependencyInterface
579 // - Fifth column: whether the result is cacheable (if column 4 is TRUE)
581 // Allowed (ct) OR allowed (ct,cf,un).
582 [$allowed_ct, 'OR', $allowed_ct, TRUE, TRUE],
583 [$allowed_ct, 'OR', $allowed_cf, TRUE, TRUE],
584 [$allowed_ct, 'OR', $allowed_un, TRUE, TRUE],
585 // Allowed (cf) OR allowed (ct,cf,un).
586 [$allowed_cf, 'OR', $allowed_ct, TRUE, TRUE],
587 [$allowed_cf, 'OR', $allowed_cf, TRUE, FALSE],
588 [$allowed_cf, 'OR', $allowed_un, TRUE, FALSE],
589 // Allowed (un) OR allowed (ct,cf,un).
590 [$allowed_un, 'OR', $allowed_ct, FALSE, NULL],
591 [$allowed_un, 'OR', $allowed_cf, FALSE, NULL],
592 [$allowed_un, 'OR', $allowed_un, FALSE, NULL],
594 // Allowed (ct) OR forbidden (ct,cf,un).
595 [$allowed_ct, 'OR', $forbidden_ct, TRUE, TRUE],
596 [$allowed_ct, 'OR', $forbidden_cf, TRUE, FALSE],
597 [$allowed_ct, 'OR', $forbidden_un, TRUE, FALSE],
598 // Allowed (cf) OR forbidden (ct,cf,un).
599 [$allowed_cf, 'OR', $forbidden_ct, TRUE, TRUE],
600 [$allowed_cf, 'OR', $forbidden_cf, TRUE, FALSE],
601 [$allowed_cf, 'OR', $forbidden_un, TRUE, FALSE],
602 // Allowed (un) OR forbidden (ct,cf,un).
603 [$allowed_un, 'OR', $forbidden_ct, FALSE, NULL],
604 [$allowed_un, 'OR', $forbidden_cf, FALSE, NULL],
605 [$allowed_un, 'OR', $forbidden_un, FALSE, NULL],
607 // Allowed (ct) OR neutral (ct,cf,un).
608 [$allowed_ct, 'OR', $neutral_ct, TRUE, TRUE],
609 [$allowed_ct, 'OR', $neutral_cf, TRUE, TRUE],
610 [$allowed_ct, 'OR', $neutral_un, TRUE, TRUE],
611 // Allowed (cf) OR neutral (ct,cf,un).
612 [$allowed_cf, 'OR', $neutral_ct, TRUE, FALSE],
613 [$allowed_cf, 'OR', $neutral_cf, TRUE, FALSE],
614 [$allowed_cf, 'OR', $neutral_un, TRUE, FALSE],
615 // Allowed (un) OR neutral (ct,cf,un).
616 [$allowed_un, 'OR', $neutral_ct, FALSE, NULL],
617 [$allowed_un, 'OR', $neutral_cf, FALSE, NULL],
618 [$allowed_un, 'OR', $neutral_un, FALSE, NULL],
620 // Forbidden (ct) OR allowed (ct,cf,un).
621 [$forbidden_ct, 'OR', $allowed_ct, TRUE, TRUE],
622 [$forbidden_ct, 'OR', $allowed_cf, TRUE, TRUE],
623 [$forbidden_ct, 'OR', $allowed_un, TRUE, TRUE],
624 // Forbidden (cf) OR allowed (ct,cf,un).
625 [$forbidden_cf, 'OR', $allowed_ct, TRUE, FALSE],
626 [$forbidden_cf, 'OR', $allowed_cf, TRUE, FALSE],
627 [$forbidden_cf, 'OR', $allowed_un, TRUE, FALSE],
628 // Forbidden (un) OR allowed (ct,cf,un).
629 [$forbidden_un, 'OR', $allowed_ct, FALSE, NULL],
630 [$forbidden_un, 'OR', $allowed_cf, FALSE, NULL],
631 [$forbidden_un, 'OR', $allowed_un, FALSE, NULL],
633 // Forbidden (ct) OR neutral (ct,cf,un).
634 [$forbidden_ct, 'OR', $neutral_ct, TRUE, TRUE],
635 [$forbidden_ct, 'OR', $neutral_cf, TRUE, TRUE],
636 [$forbidden_ct, 'OR', $neutral_un, TRUE, TRUE],
637 // Forbidden (cf) OR neutral (ct,cf,un).
638 [$forbidden_cf, 'OR', $neutral_ct, TRUE, FALSE],
639 [$forbidden_cf, 'OR', $neutral_cf, TRUE, FALSE],
640 [$forbidden_cf, 'OR', $neutral_un, TRUE, FALSE],
641 // Forbidden (un) OR neutral (ct,cf,un).
642 [$forbidden_un, 'OR', $neutral_ct, FALSE, NULL],
643 [$forbidden_un, 'OR', $neutral_cf, FALSE, NULL],
644 [$forbidden_un, 'OR', $neutral_un, FALSE, NULL],
646 // Forbidden (ct) OR forbidden (ct,cf,un).
647 [$forbidden_ct, 'OR', $forbidden_ct, TRUE, TRUE],
648 [$forbidden_ct, 'OR', $forbidden_cf, TRUE, TRUE],
649 [$forbidden_ct, 'OR', $forbidden_un, TRUE, TRUE],
650 // Forbidden (cf) OR forbidden (ct,cf,un).
651 [$forbidden_cf, 'OR', $forbidden_ct, TRUE, TRUE],
652 [$forbidden_cf, 'OR', $forbidden_cf, TRUE, FALSE],
653 [$forbidden_cf, 'OR', $forbidden_un, TRUE, FALSE],
654 // Forbidden (un) OR forbidden (ct,cf,un).
655 [$forbidden_un, 'OR', $forbidden_ct, FALSE, NULL],
656 [$forbidden_un, 'OR', $forbidden_cf, FALSE, NULL],
657 [$forbidden_un, 'OR', $forbidden_un, FALSE, NULL],
659 // Neutral (ct) OR allowed (ct,cf,un).
660 [$neutral_ct, 'OR', $allowed_ct, TRUE, TRUE],
661 [$neutral_ct, 'OR', $allowed_cf, TRUE, FALSE],
662 [$neutral_ct, 'OR', $allowed_un, TRUE, FALSE],
663 // Neutral (cf) OR allowed (ct,cf,un).
664 [$neutral_cf, 'OR', $allowed_ct, TRUE, TRUE],
665 [$neutral_cf, 'OR', $allowed_cf, TRUE, FALSE],
666 [$neutral_cf, 'OR', $allowed_un, TRUE, FALSE],
667 // Neutral (un) OR allowed (ct,cf,un).
668 [$neutral_un, 'OR', $allowed_ct, FALSE, NULL],
669 [$neutral_un, 'OR', $allowed_cf, FALSE, NULL],
670 [$neutral_un, 'OR', $allowed_un, FALSE, NULL],
672 // Neutral (ct) OR neutral (ct,cf,un).
673 [$neutral_ct, 'OR', $neutral_ct, TRUE, TRUE],
674 [$neutral_ct, 'OR', $neutral_cf, TRUE, TRUE],
675 [$neutral_ct, 'OR', $neutral_un, TRUE, TRUE],
676 // Neutral (cf) OR neutral (ct,cf,un).
677 [$neutral_cf, 'OR', $neutral_ct, TRUE, TRUE],
678 [$neutral_cf, 'OR', $neutral_cf, TRUE, FALSE],
679 [$neutral_cf, 'OR', $neutral_un, TRUE, FALSE],
680 // Neutral (un) OR neutral (ct,cf,un).
681 [$neutral_un, 'OR', $neutral_ct, FALSE, NULL],
682 [$neutral_un, 'OR', $neutral_cf, FALSE, NULL],
683 [$neutral_un, 'OR', $neutral_un, FALSE, NULL],
685 // Neutral (ct) OR forbidden (ct,cf,un).
686 [$neutral_ct, 'OR', $forbidden_ct, TRUE, TRUE],
687 [$neutral_ct, 'OR', $forbidden_cf, TRUE, FALSE],
688 [$neutral_ct, 'OR', $forbidden_un, TRUE, FALSE],
689 // Neutral (cf) OR forbidden (ct,cf,un).
690 [$neutral_cf, 'OR', $forbidden_ct, TRUE, TRUE],
691 [$neutral_cf, 'OR', $forbidden_cf, TRUE, FALSE],
692 [$neutral_cf, 'OR', $forbidden_un, TRUE, FALSE],
693 // Neutral (un) OR forbidden (ct,cf,un).
694 [$neutral_un, 'OR', $forbidden_ct, FALSE, NULL],
695 [$neutral_un, 'OR', $forbidden_cf, FALSE, NULL],
696 [$neutral_un, 'OR', $forbidden_un, FALSE, NULL],
698 // Allowed (ct) AND allowed (ct,cf,un).
699 [$allowed_ct, 'AND', $allowed_ct, TRUE, TRUE],
700 [$allowed_ct, 'AND', $allowed_cf, TRUE, FALSE],
701 [$allowed_ct, 'AND', $allowed_un, TRUE, FALSE],
702 // Allowed (cf) AND allowed (ct,cf,un).
703 [$allowed_cf, 'AND', $allowed_ct, TRUE, FALSE],
704 [$allowed_cf, 'AND', $allowed_cf, TRUE, FALSE],
705 [$allowed_cf, 'AND', $allowed_un, TRUE, FALSE],
706 // Allowed (un) AND allowed (ct,cf,un).
707 [$allowed_un, 'AND', $allowed_ct, FALSE, NULL],
708 [$allowed_un, 'AND', $allowed_cf, FALSE, NULL],
709 [$allowed_un, 'AND', $allowed_un, FALSE, NULL],
711 // Allowed (ct) AND forbidden (ct,cf,un).
712 [$allowed_ct, 'AND', $forbidden_ct, TRUE, TRUE],
713 [$allowed_ct, 'AND', $forbidden_cf, TRUE, FALSE],
714 [$allowed_ct, 'AND', $forbidden_un, TRUE, FALSE],
715 // Allowed (cf) AND forbidden (ct,cf,un).
716 [$allowed_cf, 'AND', $forbidden_ct, TRUE, TRUE],
717 [$allowed_cf, 'AND', $forbidden_cf, TRUE, FALSE],
718 [$allowed_cf, 'AND', $forbidden_un, TRUE, FALSE],
719 // Allowed (un) AND forbidden (ct,cf,un).
720 [$allowed_un, 'AND', $forbidden_ct, FALSE, NULL],
721 [$allowed_un, 'AND', $forbidden_cf, FALSE, NULL],
722 [$allowed_un, 'AND', $forbidden_un, FALSE, NULL],
724 // Allowed (ct) AND neutral (ct,cf,un).
725 [$allowed_ct, 'AND', $neutral_ct, TRUE, TRUE],
726 [$allowed_ct, 'AND', $neutral_cf, TRUE, FALSE],
727 [$allowed_ct, 'AND', $neutral_un, TRUE, FALSE],
728 // Allowed (cf) AND neutral (ct,cf,un).
729 [$allowed_cf, 'AND', $neutral_ct, TRUE, FALSE],
730 [$allowed_cf, 'AND', $neutral_cf, TRUE, FALSE],
731 [$allowed_cf, 'AND', $neutral_un, TRUE, FALSE],
732 // Allowed (un) AND neutral (ct,cf,un).
733 [$allowed_un, 'AND', $neutral_ct, FALSE, NULL],
734 [$allowed_un, 'AND', $neutral_cf, FALSE, NULL],
735 [$allowed_un, 'AND', $neutral_un, FALSE, NULL],
737 // Forbidden (ct) AND allowed (ct,cf,un).
738 [$forbidden_ct, 'AND', $allowed_ct, TRUE, TRUE],
739 [$forbidden_ct, 'AND', $allowed_cf, TRUE, TRUE],
740 [$forbidden_ct, 'AND', $allowed_un, TRUE, TRUE],
741 // Forbidden (cf) AND allowed (ct,cf,un).
742 [$forbidden_cf, 'AND', $allowed_ct, TRUE, FALSE],
743 [$forbidden_cf, 'AND', $allowed_cf, TRUE, FALSE],
744 [$forbidden_cf, 'AND', $allowed_un, TRUE, FALSE],
745 // Forbidden (un) AND allowed (ct,cf,un).
746 [$forbidden_un, 'AND', $allowed_ct, FALSE, NULL],
747 [$forbidden_un, 'AND', $allowed_cf, FALSE, NULL],
748 [$forbidden_un, 'AND', $allowed_un, FALSE, NULL],
750 // Forbidden (ct) AND neutral (ct,cf,un).
751 [$forbidden_ct, 'AND', $neutral_ct, TRUE, TRUE],
752 [$forbidden_ct, 'AND', $neutral_cf, TRUE, TRUE],
753 [$forbidden_ct, 'AND', $neutral_un, TRUE, TRUE],
754 // Forbidden (cf) AND neutral (ct,cf,un).
755 [$forbidden_cf, 'AND', $neutral_ct, TRUE, FALSE],
756 [$forbidden_cf, 'AND', $neutral_cf, TRUE, FALSE],
757 [$forbidden_cf, 'AND', $neutral_un, TRUE, FALSE],
758 // Forbidden (un) AND neutral (ct,cf,un).
759 [$forbidden_un, 'AND', $neutral_ct, FALSE, NULL],
760 [$forbidden_un, 'AND', $neutral_cf, FALSE, NULL],
761 [$forbidden_un, 'AND', $neutral_un, FALSE, NULL],
763 // Forbidden (ct) AND forbidden (ct,cf,un).
764 [$forbidden_ct, 'AND', $forbidden_ct, TRUE, TRUE],
765 [$forbidden_ct, 'AND', $forbidden_cf, TRUE, TRUE],
766 [$forbidden_ct, 'AND', $forbidden_un, TRUE, TRUE],
767 // Forbidden (cf) AND forbidden (ct,cf,un).
768 [$forbidden_cf, 'AND', $forbidden_ct, TRUE, FALSE],
769 [$forbidden_cf, 'AND', $forbidden_cf, TRUE, FALSE],
770 [$forbidden_cf, 'AND', $forbidden_un, TRUE, FALSE],
771 // Forbidden (un) AND forbidden (ct,cf,un).
772 [$forbidden_un, 'AND', $forbidden_ct, FALSE, NULL],
773 [$forbidden_un, 'AND', $forbidden_cf, FALSE, NULL],
774 [$forbidden_un, 'AND', $forbidden_un, FALSE, NULL],
776 // Neutral (ct) AND allowed (ct,cf,un).
777 [$neutral_ct, 'AND', $allowed_ct, TRUE, TRUE],
778 [$neutral_ct, 'AND', $allowed_cf, TRUE, TRUE],
779 [$neutral_ct, 'AND', $allowed_un, TRUE, TRUE],
780 // Neutral (cf) AND allowed (ct,cf,un).
781 [$neutral_cf, 'AND', $allowed_ct, TRUE, FALSE],
782 [$neutral_cf, 'AND', $allowed_cf, TRUE, FALSE],
783 [$neutral_cf, 'AND', $allowed_un, TRUE, FALSE],
784 // Neutral (un) AND allowed (ct,cf,un).
785 [$neutral_un, 'AND', $allowed_ct, FALSE, NULL],
786 [$neutral_un, 'AND', $allowed_cf, FALSE, NULL],
787 [$neutral_un, 'AND', $allowed_un, FALSE, NULL],
789 // Neutral (ct) AND neutral (ct,cf,un).
790 [$neutral_ct, 'AND', $neutral_ct, TRUE, TRUE],
791 [$neutral_ct, 'AND', $neutral_cf, TRUE, TRUE],
792 [$neutral_ct, 'AND', $neutral_un, TRUE, TRUE],
793 // Neutral (cf) AND neutral (ct,cf,un).
794 [$neutral_cf, 'AND', $neutral_ct, TRUE, FALSE],
795 [$neutral_cf, 'AND', $neutral_cf, TRUE, FALSE],
796 [$neutral_cf, 'AND', $neutral_un, TRUE, FALSE],
797 // Neutral (un) AND neutral (ct,cf,un).
798 [$neutral_un, 'AND', $neutral_ct, FALSE, NULL],
799 [$neutral_un, 'AND', $neutral_cf, FALSE, NULL],
800 [$neutral_un, 'AND', $neutral_un, FALSE, NULL],
802 // Neutral (ct) AND forbidden (ct,cf,un).
803 [$neutral_ct, 'AND', $forbidden_ct, TRUE, TRUE],
804 [$neutral_ct, 'AND', $forbidden_cf, TRUE, FALSE],
805 [$neutral_ct, 'AND', $forbidden_un, TRUE, FALSE],
806 // Neutral (cf) AND forbidden (ct,cf,un).
807 [$neutral_cf, 'AND', $forbidden_ct, TRUE, TRUE],
808 [$neutral_cf, 'AND', $forbidden_cf, TRUE, FALSE],
809 [$neutral_cf, 'AND', $forbidden_un, TRUE, FALSE],
810 // Neutral (un) AND forbidden (ct,cf,un).
811 [$neutral_un, 'AND', $forbidden_ct, FALSE, NULL],
812 [$neutral_un, 'AND', $forbidden_cf, FALSE, NULL],
813 [$neutral_un, 'AND', $forbidden_un, FALSE, NULL],
820 * @covers ::inheritCacheability
822 * @dataProvider andOrCacheabilityPropagationProvider
824 public function testAndOrCacheabilityPropagation(AccessResultInterface $first, $op, AccessResultInterface $second, $implements_cacheable_dependency_interface, $is_cacheable) {
826 $result = $first->orIf($second);
828 elseif ($op === 'AND') {
829 $result = $first->andIf($second);
832 throw new \LogicException('Invalid operator specified');
834 if ($implements_cacheable_dependency_interface) {
835 $this->assertTrue($result instanceof CacheableDependencyInterface, 'Result is an instance of CacheableDependencyInterface.');
836 if ($result instanceof CacheableDependencyInterface) {
837 $this->assertSame($is_cacheable, $result->getCacheMaxAge() !== 0, 'getCacheMaxAge() matches expectations.');
841 $this->assertFalse($result instanceof CacheableDependencyInterface, 'Result is not an instance of CacheableDependencyInterface.');
848 * Tests the special case of ORing non-forbidden access results that are both
849 * cacheable but have different cacheability metadata.
850 * This is only the case for non-forbidden access results; we still abort the
851 * ORing process as soon as a forbidden access result is encountered. This is
852 * tested in ::testOrIf().
854 public function testOrIfCacheabilityMerging() {
855 $merge_both_directions = function (AccessResult $a, AccessResult $b) {
856 // A globally cacheable access result.
857 $a->setCacheMaxAge(3600);
858 // Another access result that is cacheable per permissions.
859 $b->setCacheMaxAge(86400)->cachePerPermissions();
862 $this->assertTrue($r1->getCacheMaxAge() === 3600);
863 $this->assertSame(['user.permissions'], $r1->getCacheContexts());
865 $this->assertTrue($r2->getCacheMaxAge() === 3600);
866 $this->assertSame(['user.permissions'], $r2->getCacheContexts());
869 // Merge either direction, get the same result.
870 $merge_both_directions(AccessResult::allowed(), AccessResult::allowed());
871 $merge_both_directions(AccessResult::allowed(), AccessResult::neutral());
872 $merge_both_directions(AccessResult::neutral(), AccessResult::neutral());
873 $merge_both_directions(AccessResult::neutral(), AccessResult::allowed());
877 * Tests allowedIfHasPermissions().
879 * @covers ::allowedIfHasPermissions
881 * @dataProvider providerTestAllowedIfHasPermissions
883 * @param string[] $permissions
884 * The permissions to check for.
885 * @param string $conjunction
886 * The conjunction to use when checking for permission. 'AND' or 'OR'.
887 * @param \Drupal\Core\Access\AccessResult $expected_access
888 * The expected access check result.
890 public function testAllowedIfHasPermissions($permissions, $conjunction, AccessResult $expected_access) {
891 $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
892 $account->expects($this->any())
893 ->method('hasPermission')
900 $expected_access->cachePerPermissions();
903 $access_result = AccessResult::allowedIfHasPermissions($account, $permissions, $conjunction);
904 $this->assertEquals($expected_access, $access_result);
908 * Provides data for the testAllowedIfHasPermissions() method.
912 public function providerTestAllowedIfHasPermissions() {
913 $access_result = AccessResult::allowedIf(FALSE);
914 $data[] = [[], 'AND', $access_result];
915 $data[] = [[], 'OR', $access_result];
917 $access_result = AccessResult::allowedIf(TRUE);
918 $data[] = [['allowed'], 'OR', $access_result];
919 $data[] = [['allowed'], 'AND', $access_result];
921 $access_result = AccessResult::allowedIf(FALSE);
922 $access_result->setReason("The 'denied' permission is required.");
923 $data[] = [['denied'], 'OR', $access_result];
924 $data[] = [['denied'], 'AND', $access_result];
926 $access_result = AccessResult::allowedIf(TRUE);
927 $data[] = [['allowed', 'denied'], 'OR', $access_result];
928 $data[] = [['denied', 'allowed'], 'OR', $access_result];
930 $access_result = AccessResult::allowedIf(TRUE);
931 $data[] = [['allowed', 'denied', 'other'], 'OR', $access_result];
933 $access_result = AccessResult::allowedIf(FALSE);
934 $access_result->setReason("The following permissions are required: 'allowed' AND 'denied'.");
935 $data[] = [['allowed', 'denied'], 'AND', $access_result];
942 class UncacheableTestAccessResult implements AccessResultInterface {
945 * The access result value. 'ALLOWED', 'FORBIDDEN' or 'NEUTRAL'.
952 * Constructs a new UncacheableTestAccessResult object.
954 public function __construct($value) {
955 $this->value = $value;
960 public function isAllowed() {
961 return $this->value === 'ALLOWED';
967 public function isForbidden() {
968 return $this->value === 'FORBIDDEN';
974 public function isNeutral() {
975 return $this->value === 'NEUTRAL';
981 public function orIf(AccessResultInterface $other) {
982 if ($this->isForbidden() || $other->isForbidden()) {
983 return new static('FORBIDDEN');
985 elseif ($this->isAllowed() || $other->isAllowed()) {
986 return new static('ALLOWED');
989 return new static('NEUTRAL');
996 public function andIf(AccessResultInterface $other) {
997 if ($this->isForbidden() || $other->isForbidden()) {
998 return new static('FORBIDDEN');
1000 elseif ($this->isAllowed() && $other->isAllowed()) {
1001 return new static('ALLOWED');
1004 return new static('NEUTRAL');