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],
621 // Forbidden (ct) OR allowed (ct,cf,un).
622 [$forbidden_ct, 'OR', $allowed_ct, TRUE, TRUE],
623 [$forbidden_ct, 'OR', $allowed_cf, TRUE, TRUE],
624 [$forbidden_ct, 'OR', $allowed_un, TRUE, TRUE],
625 // Forbidden (cf) OR allowed (ct,cf,un).
626 [$forbidden_cf, 'OR', $allowed_ct, TRUE, FALSE],
627 [$forbidden_cf, 'OR', $allowed_cf, TRUE, FALSE],
628 [$forbidden_cf, 'OR', $allowed_un, TRUE, FALSE],
629 // Forbidden (un) OR allowed (ct,cf,un).
630 [$forbidden_un, 'OR', $allowed_ct, FALSE, NULL],
631 [$forbidden_un, 'OR', $allowed_cf, FALSE, NULL],
632 [$forbidden_un, 'OR', $allowed_un, FALSE, NULL],
634 // Forbidden (ct) OR neutral (ct,cf,un).
635 [$forbidden_ct, 'OR', $neutral_ct, TRUE, TRUE],
636 [$forbidden_ct, 'OR', $neutral_cf, TRUE, TRUE],
637 [$forbidden_ct, 'OR', $neutral_un, TRUE, TRUE],
638 // Forbidden (cf) OR neutral (ct,cf,un).
639 [$forbidden_cf, 'OR', $neutral_ct, TRUE, FALSE],
640 [$forbidden_cf, 'OR', $neutral_cf, TRUE, FALSE],
641 [$forbidden_cf, 'OR', $neutral_un, TRUE, FALSE],
642 // Forbidden (un) OR neutral (ct,cf,un).
643 [$forbidden_un, 'OR', $neutral_ct, FALSE, NULL],
644 [$forbidden_un, 'OR', $neutral_cf, FALSE, NULL],
645 [$forbidden_un, 'OR', $neutral_un, FALSE, NULL],
647 // Forbidden (ct) OR forbidden (ct,cf,un).
648 [$forbidden_ct, 'OR', $forbidden_ct, TRUE, TRUE],
649 [$forbidden_ct, 'OR', $forbidden_cf, TRUE, TRUE],
650 [$forbidden_ct, 'OR', $forbidden_un, TRUE, TRUE],
651 // Forbidden (cf) OR forbidden (ct,cf,un).
652 [$forbidden_cf, 'OR', $forbidden_ct, TRUE, TRUE],
653 [$forbidden_cf, 'OR', $forbidden_cf, TRUE, FALSE],
654 [$forbidden_cf, 'OR', $forbidden_un, TRUE, FALSE],
655 // Forbidden (un) OR forbidden (ct,cf,un).
656 [$forbidden_un, 'OR', $forbidden_ct, FALSE, NULL],
657 [$forbidden_un, 'OR', $forbidden_cf, FALSE, NULL],
658 [$forbidden_un, 'OR', $forbidden_un, FALSE, NULL],
661 // Neutral (ct) OR allowed (ct,cf,un).
662 [$neutral_ct, 'OR', $allowed_ct, TRUE, TRUE],
663 [$neutral_ct, 'OR', $allowed_cf, TRUE, FALSE],
664 [$neutral_ct, 'OR', $allowed_un, TRUE, FALSE],
665 // Neutral (cf) OR allowed (ct,cf,un).
666 [$neutral_cf, 'OR', $allowed_ct, TRUE, TRUE],
667 [$neutral_cf, 'OR', $allowed_cf, TRUE, FALSE],
668 [$neutral_cf, 'OR', $allowed_un, TRUE, FALSE],
669 // Neutral (un) OR allowed (ct,cf,un).
670 [$neutral_un, 'OR', $allowed_ct, FALSE, NULL],
671 [$neutral_un, 'OR', $allowed_cf, FALSE, NULL],
672 [$neutral_un, 'OR', $allowed_un, FALSE, NULL],
674 // Neutral (ct) OR neutral (ct,cf,un).
675 [$neutral_ct, 'OR', $neutral_ct, TRUE, TRUE],
676 [$neutral_ct, 'OR', $neutral_cf, TRUE, TRUE],
677 [$neutral_ct, 'OR', $neutral_un, TRUE, TRUE],
678 // Neutral (cf) OR neutral (ct,cf,un).
679 [$neutral_cf, 'OR', $neutral_ct, TRUE, TRUE],
680 [$neutral_cf, 'OR', $neutral_cf, TRUE, FALSE],
681 [$neutral_cf, 'OR', $neutral_un, TRUE, FALSE],
682 // Neutral (un) OR neutral (ct,cf,un).
683 [$neutral_un, 'OR', $neutral_ct, FALSE, NULL],
684 [$neutral_un, 'OR', $neutral_cf, FALSE, NULL],
685 [$neutral_un, 'OR', $neutral_un, FALSE, NULL],
687 // Neutral (ct) OR forbidden (ct,cf,un).
688 [$neutral_ct, 'OR', $forbidden_ct, TRUE, TRUE],
689 [$neutral_ct, 'OR', $forbidden_cf, TRUE, FALSE],
690 [$neutral_ct, 'OR', $forbidden_un, TRUE, FALSE],
691 // Neutral (cf) OR forbidden (ct,cf,un).
692 [$neutral_cf, 'OR', $forbidden_ct, TRUE, TRUE],
693 [$neutral_cf, 'OR', $forbidden_cf, TRUE, FALSE],
694 [$neutral_cf, 'OR', $forbidden_un, TRUE, FALSE],
695 // Neutral (un) OR forbidden (ct,cf,un).
696 [$neutral_un, 'OR', $forbidden_ct, FALSE, NULL],
697 [$neutral_un, 'OR', $forbidden_cf, FALSE, NULL],
698 [$neutral_un, 'OR', $forbidden_un, FALSE, NULL],
703 // Allowed (ct) AND allowed (ct,cf,un).
704 [$allowed_ct, 'AND', $allowed_ct, TRUE, TRUE],
705 [$allowed_ct, 'AND', $allowed_cf, TRUE, FALSE],
706 [$allowed_ct, 'AND', $allowed_un, TRUE, FALSE],
707 // Allowed (cf) AND allowed (ct,cf,un).
708 [$allowed_cf, 'AND', $allowed_ct, TRUE, FALSE],
709 [$allowed_cf, 'AND', $allowed_cf, TRUE, FALSE],
710 [$allowed_cf, 'AND', $allowed_un, TRUE, FALSE],
711 // Allowed (un) AND allowed (ct,cf,un).
712 [$allowed_un, 'AND', $allowed_ct, FALSE, NULL],
713 [$allowed_un, 'AND', $allowed_cf, FALSE, NULL],
714 [$allowed_un, 'AND', $allowed_un, FALSE, NULL],
716 // Allowed (ct) AND forbidden (ct,cf,un).
717 [$allowed_ct, 'AND', $forbidden_ct, TRUE, TRUE],
718 [$allowed_ct, 'AND', $forbidden_cf, TRUE, FALSE],
719 [$allowed_ct, 'AND', $forbidden_un, TRUE, FALSE],
720 // Allowed (cf) AND forbidden (ct,cf,un).
721 [$allowed_cf, 'AND', $forbidden_ct, TRUE, TRUE],
722 [$allowed_cf, 'AND', $forbidden_cf, TRUE, FALSE],
723 [$allowed_cf, 'AND', $forbidden_un, TRUE, FALSE],
724 // Allowed (un) AND forbidden (ct,cf,un).
725 [$allowed_un, 'AND', $forbidden_ct, FALSE, NULL],
726 [$allowed_un, 'AND', $forbidden_cf, FALSE, NULL],
727 [$allowed_un, 'AND', $forbidden_un, FALSE, NULL],
729 // Allowed (ct) AND neutral (ct,cf,un).
730 [$allowed_ct, 'AND', $neutral_ct, TRUE, TRUE],
731 [$allowed_ct, 'AND', $neutral_cf, TRUE, FALSE],
732 [$allowed_ct, 'AND', $neutral_un, TRUE, FALSE],
733 // Allowed (cf) AND neutral (ct,cf,un).
734 [$allowed_cf, 'AND', $neutral_ct, TRUE, FALSE],
735 [$allowed_cf, 'AND', $neutral_cf, TRUE, FALSE],
736 [$allowed_cf, 'AND', $neutral_un, TRUE, FALSE],
737 // Allowed (un) AND neutral (ct,cf,un).
738 [$allowed_un, 'AND', $neutral_ct, FALSE, NULL],
739 [$allowed_un, 'AND', $neutral_cf, FALSE, NULL],
740 [$allowed_un, 'AND', $neutral_un, FALSE, NULL],
743 // Forbidden (ct) AND allowed (ct,cf,un).
744 [$forbidden_ct, 'AND', $allowed_ct, TRUE, TRUE],
745 [$forbidden_ct, 'AND', $allowed_cf, TRUE, TRUE],
746 [$forbidden_ct, 'AND', $allowed_un, TRUE, TRUE],
747 // Forbidden (cf) AND allowed (ct,cf,un).
748 [$forbidden_cf, 'AND', $allowed_ct, TRUE, FALSE],
749 [$forbidden_cf, 'AND', $allowed_cf, TRUE, FALSE],
750 [$forbidden_cf, 'AND', $allowed_un, TRUE, FALSE],
751 // Forbidden (un) AND allowed (ct,cf,un).
752 [$forbidden_un, 'AND', $allowed_ct, FALSE, NULL],
753 [$forbidden_un, 'AND', $allowed_cf, FALSE, NULL],
754 [$forbidden_un, 'AND', $allowed_un, FALSE, NULL],
756 // Forbidden (ct) AND neutral (ct,cf,un).
757 [$forbidden_ct, 'AND', $neutral_ct, TRUE, TRUE],
758 [$forbidden_ct, 'AND', $neutral_cf, TRUE, TRUE],
759 [$forbidden_ct, 'AND', $neutral_un, TRUE, TRUE],
760 // Forbidden (cf) AND neutral (ct,cf,un).
761 [$forbidden_cf, 'AND', $neutral_ct, TRUE, FALSE],
762 [$forbidden_cf, 'AND', $neutral_cf, TRUE, FALSE],
763 [$forbidden_cf, 'AND', $neutral_un, TRUE, FALSE],
764 // Forbidden (un) AND neutral (ct,cf,un).
765 [$forbidden_un, 'AND', $neutral_ct, FALSE, NULL],
766 [$forbidden_un, 'AND', $neutral_cf, FALSE, NULL],
767 [$forbidden_un, 'AND', $neutral_un, FALSE, NULL],
769 // Forbidden (ct) AND forbidden (ct,cf,un).
770 [$forbidden_ct, 'AND', $forbidden_ct, TRUE, TRUE],
771 [$forbidden_ct, 'AND', $forbidden_cf, TRUE, TRUE],
772 [$forbidden_ct, 'AND', $forbidden_un, TRUE, TRUE],
773 // Forbidden (cf) AND forbidden (ct,cf,un).
774 [$forbidden_cf, 'AND', $forbidden_ct, TRUE, FALSE],
775 [$forbidden_cf, 'AND', $forbidden_cf, TRUE, FALSE],
776 [$forbidden_cf, 'AND', $forbidden_un, TRUE, FALSE],
777 // Forbidden (un) AND forbidden (ct,cf,un).
778 [$forbidden_un, 'AND', $forbidden_ct, FALSE, NULL],
779 [$forbidden_un, 'AND', $forbidden_cf, FALSE, NULL],
780 [$forbidden_un, 'AND', $forbidden_un, FALSE, NULL],
783 // Neutral (ct) AND allowed (ct,cf,un).
784 [$neutral_ct, 'AND', $allowed_ct, TRUE, TRUE],
785 [$neutral_ct, 'AND', $allowed_cf, TRUE, TRUE],
786 [$neutral_ct, 'AND', $allowed_un, TRUE, TRUE],
787 // Neutral (cf) AND allowed (ct,cf,un).
788 [$neutral_cf, 'AND', $allowed_ct, TRUE, FALSE],
789 [$neutral_cf, 'AND', $allowed_cf, TRUE, FALSE],
790 [$neutral_cf, 'AND', $allowed_un, TRUE, FALSE],
791 // Neutral (un) AND allowed (ct,cf,un).
792 [$neutral_un, 'AND', $allowed_ct, FALSE, NULL],
793 [$neutral_un, 'AND', $allowed_cf, FALSE, NULL],
794 [$neutral_un, 'AND', $allowed_un, FALSE, NULL],
796 // Neutral (ct) AND neutral (ct,cf,un).
797 [$neutral_ct, 'AND', $neutral_ct, TRUE, TRUE],
798 [$neutral_ct, 'AND', $neutral_cf, TRUE, TRUE],
799 [$neutral_ct, 'AND', $neutral_un, TRUE, TRUE],
800 // Neutral (cf) AND neutral (ct,cf,un).
801 [$neutral_cf, 'AND', $neutral_ct, TRUE, FALSE],
802 [$neutral_cf, 'AND', $neutral_cf, TRUE, FALSE],
803 [$neutral_cf, 'AND', $neutral_un, TRUE, FALSE],
804 // Neutral (un) AND neutral (ct,cf,un).
805 [$neutral_un, 'AND', $neutral_ct, FALSE, NULL],
806 [$neutral_un, 'AND', $neutral_cf, FALSE, NULL],
807 [$neutral_un, 'AND', $neutral_un, FALSE, NULL],
809 // Neutral (ct) AND forbidden (ct,cf,un).
810 [$neutral_ct, 'AND', $forbidden_ct, TRUE, TRUE],
811 [$neutral_ct, 'AND', $forbidden_cf, TRUE, FALSE],
812 [$neutral_ct, 'AND', $forbidden_un, TRUE, FALSE],
813 // Neutral (cf) AND forbidden (ct,cf,un).
814 [$neutral_cf, 'AND', $forbidden_ct, TRUE, TRUE],
815 [$neutral_cf, 'AND', $forbidden_cf, TRUE, FALSE],
816 [$neutral_cf, 'AND', $forbidden_un, TRUE, FALSE],
817 // Neutral (un) AND forbidden (ct,cf,un).
818 [$neutral_un, 'AND', $forbidden_ct, FALSE, NULL],
819 [$neutral_un, 'AND', $forbidden_cf, FALSE, NULL],
820 [$neutral_un, 'AND', $forbidden_un, FALSE, NULL],
827 * @covers ::inheritCacheability
829 * @dataProvider andOrCacheabilityPropagationProvider
831 public function testAndOrCacheabilityPropagation(AccessResultInterface $first, $op, AccessResultInterface $second, $implements_cacheable_dependency_interface, $is_cacheable) {
833 $result = $first->orIf($second);
835 elseif ($op === 'AND') {
836 $result = $first->andIf($second);
839 throw new \LogicException('Invalid operator specified');
841 if ($implements_cacheable_dependency_interface) {
842 $this->assertTrue($result instanceof CacheableDependencyInterface, 'Result is an instance of CacheableDependencyInterface.');
843 if ($result instanceof CacheableDependencyInterface) {
844 $this->assertSame($is_cacheable, $result->getCacheMaxAge() !== 0, 'getCacheMaxAge() matches expectations.');
848 $this->assertFalse($result instanceof CacheableDependencyInterface, 'Result is not an instance of CacheableDependencyInterface.');
855 * Tests the special case of ORing non-forbidden access results that are both
856 * cacheable but have different cacheability metadata.
857 * This is only the case for non-forbidden access results; we still abort the
858 * ORing process as soon as a forbidden access result is encountered. This is
859 * tested in ::testOrIf().
861 public function testOrIfCacheabilityMerging() {
862 $merge_both_directions = function(AccessResult $a, AccessResult $b) {
863 // A globally cacheable access result.
864 $a->setCacheMaxAge(3600);
865 // Another access result that is cacheable per permissions.
866 $b->setCacheMaxAge(86400)->cachePerPermissions();
869 $this->assertTrue($r1->getCacheMaxAge() === 3600);
870 $this->assertSame(['user.permissions'], $r1->getCacheContexts());
872 $this->assertTrue($r2->getCacheMaxAge() === 3600);
873 $this->assertSame(['user.permissions'], $r2->getCacheContexts());
876 // Merge either direction, get the same result.
877 $merge_both_directions(AccessResult::allowed(), AccessResult::allowed());
878 $merge_both_directions(AccessResult::allowed(), AccessResult::neutral());
879 $merge_both_directions(AccessResult::neutral(), AccessResult::neutral());
880 $merge_both_directions(AccessResult::neutral(), AccessResult::allowed());
884 * Tests allowedIfHasPermissions().
886 * @covers ::allowedIfHasPermissions
888 * @dataProvider providerTestAllowedIfHasPermissions
890 * @param string[] $permissions
891 * The permissions to check for.
892 * @param string $conjunction
893 * The conjunction to use when checking for permission. 'AND' or 'OR'.
894 * @param \Drupal\Core\Access\AccessResult $expected_access
895 * The expected access check result.
897 public function testAllowedIfHasPermissions($permissions, $conjunction, AccessResult $expected_access) {
898 $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
899 $account->expects($this->any())
900 ->method('hasPermission')
907 $expected_access->cachePerPermissions();
910 $access_result = AccessResult::allowedIfHasPermissions($account, $permissions, $conjunction);
911 $this->assertEquals($expected_access, $access_result);
915 * Provides data for the testAllowedIfHasPermissions() method.
919 public function providerTestAllowedIfHasPermissions() {
920 $access_result = AccessResult::allowedIf(FALSE);
921 $data[] = [[], 'AND', $access_result];
922 $data[] = [[], 'OR', $access_result];
924 $access_result = AccessResult::allowedIf(TRUE);
925 $data[] = [['allowed'], 'OR', $access_result];
926 $data[] = [['allowed'], 'AND', $access_result];
928 $access_result = AccessResult::allowedIf(FALSE);
929 $access_result->setReason("The 'denied' permission is required.");
930 $data[] = [['denied'], 'OR', $access_result];
931 $data[] = [['denied'], 'AND', $access_result];
933 $access_result = AccessResult::allowedIf(TRUE);
934 $data[] = [['allowed', 'denied'], 'OR', $access_result];
935 $data[] = [['denied', 'allowed'], 'OR', $access_result];
937 $access_result = AccessResult::allowedIf(TRUE);
938 $data[] = [['allowed', 'denied', 'other'], 'OR', $access_result];
940 $access_result = AccessResult::allowedIf(FALSE);
941 $access_result->setReason("The following permissions are required: 'allowed' AND 'denied'.");
942 $data[] = [['allowed', 'denied'], 'AND', $access_result];
949 class UncacheableTestAccessResult implements AccessResultInterface {
952 * The access result value. 'ALLOWED', 'FORBIDDEN' or 'NEUTRAL'.
959 * Constructs a new UncacheableTestAccessResult object.
961 public function __construct($value) {
962 $this->value = $value;
967 public function isAllowed() {
968 return $this->value === 'ALLOWED';
974 public function isForbidden() {
975 return $this->value === 'FORBIDDEN';
981 public function isNeutral() {
982 return $this->value === 'NEUTRAL';
988 public function orIf(AccessResultInterface $other) {
989 if ($this->isForbidden() || $other->isForbidden()) {
990 return new static('FORBIDDEN');
992 elseif ($this->isAllowed() || $other->isAllowed()) {
993 return new static('ALLOWED');
996 return new static('NEUTRAL');
1003 public function andIf(AccessResultInterface $other) {
1004 if ($this->isForbidden() || $other->isForbidden()) {
1005 return new static('FORBIDDEN');
1007 elseif ($this->isAllowed() && $other->isAllowed()) {
1008 return new static('ALLOWED');
1011 return new static('NEUTRAL');