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 $neutral_other = AccessResult::neutral('other neutral message');
273 $neutral_reasonless = AccessResult::neutral();
274 $allowed = AccessResult::allowed();
275 $forbidden = AccessResult::forbidden('forbidden message');
276 $forbidden_other = AccessResult::forbidden('other forbidden message');
277 $forbidden_reasonless = AccessResult::forbidden();
278 $unused_access_result_due_to_lazy_evaluation = $this->getMock('\Drupal\Core\Access\AccessResultInterface');
279 $unused_access_result_due_to_lazy_evaluation->expects($this->never())
280 ->method($this->anything());
282 // ALLOWED || ALLOWED === ALLOWED.
283 $access = $allowed->orIf($allowed);
284 $this->assertTrue($access->isAllowed());
285 $this->assertFalse($access->isForbidden());
286 $this->assertFalse($access->isNeutral());
287 $this->assertDefaultCacheability($access);
289 // ALLOWED || NEUTRAL === ALLOWED.
290 $access = $allowed->orIf($neutral);
291 $this->assertTrue($access->isAllowed());
292 $this->assertFalse($access->isForbidden());
293 $this->assertFalse($access->isNeutral());
294 $this->assertDefaultCacheability($access);
296 // ALLOWED || FORBIDDEN === FORBIDDEN.
297 $access = $allowed->orIf($forbidden);
298 $this->assertFalse($access->isAllowed());
299 $this->assertTrue($access->isForbidden());
300 $this->assertFalse($access->isNeutral());
301 $this->assertEquals('forbidden message', $access->getReason());
302 $this->assertDefaultCacheability($access);
304 // NEUTRAL || NEUTRAL === NEUTRAL.
305 $access = $neutral->orIf($neutral);
306 $this->assertFalse($access->isAllowed());
307 $this->assertFalse($access->isForbidden());
308 $this->assertTrue($access->isNeutral());
309 $this->assertEquals('neutral message', $access->getReason());
310 $this->assertDefaultCacheability($access);
311 // Reason inheritance edge case: first reason is kept.
312 $access = $neutral->orIf($neutral_other);
313 $this->assertEquals('neutral message', $access->getReason());
314 $access = $neutral_other->orIf($neutral);
315 $this->assertEquals('other neutral message', $access->getReason());
316 // Reason inheritance edge case: one of the operands is reasonless.
317 $access = $neutral->orIf($neutral_reasonless);
318 $this->assertEquals('neutral message', $access->getReason());
319 $access = $neutral_reasonless->orIf($neutral);
320 $this->assertEquals('neutral message', $access->getReason());
321 $access = $neutral_reasonless->orIf($neutral_reasonless);
322 $this->assertNull($access->getReason());
324 // NEUTRAL || ALLOWED === ALLOWED.
325 $access = $neutral->orIf($allowed);
326 $this->assertTrue($access->isAllowed());
327 $this->assertFalse($access->isForbidden());
328 $this->assertFalse($access->isNeutral());
329 $this->assertDefaultCacheability($access);
331 // NEUTRAL || FORBIDDEN === FORBIDDEN.
332 $access = $neutral->orIf($forbidden);
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 || ALLOWED === 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 || NEUTRAL === FORBIDDEN.
348 $access = $forbidden->orIf($neutral);
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);
355 // FORBIDDEN || FORBIDDEN === FORBIDDEN.
356 $access = $forbidden->orIf($forbidden);
357 $this->assertFalse($access->isAllowed());
358 $this->assertTrue($access->isForbidden());
359 $this->assertFalse($access->isNeutral());
360 $this->assertEquals('forbidden message', $access->getReason());
361 $this->assertDefaultCacheability($access);
362 // Reason inheritance edge case: first reason is kept.
363 $access = $forbidden->orIf($forbidden_other);
364 $this->assertEquals('forbidden message', $access->getReason());
365 $access = $forbidden_other->orIf($forbidden);
366 $this->assertEquals('other forbidden message', $access->getReason());
367 // Reason inheritance edge case: one of the operands is reasonless.
368 $access = $forbidden->orIf($forbidden_reasonless);
369 $this->assertEquals('forbidden message', $access->getReason());
370 $access = $forbidden_reasonless->orIf($forbidden);
371 $this->assertEquals('forbidden message', $access->getReason());
372 $access = $forbidden_reasonless->orIf($forbidden_reasonless);
373 $this->assertNull($access->getReason());
375 // FORBIDDEN || * === FORBIDDEN.
376 $access = $forbidden->orIf($unused_access_result_due_to_lazy_evaluation);
377 $this->assertFalse($access->isAllowed());
378 $this->assertTrue($access->isForbidden());
379 $this->assertFalse($access->isNeutral());
380 $this->assertEquals('forbidden message', $access->getReason());
381 $this->assertDefaultCacheability($access);
385 * @covers ::setCacheMaxAge
386 * @covers ::getCacheMaxAge
388 public function testCacheMaxAge() {
389 $this->assertSame(Cache::PERMANENT, AccessResult::neutral()->getCacheMaxAge());
390 $this->assertSame(1337, AccessResult::neutral()->setCacheMaxAge(1337)->getCacheMaxAge());
394 * @covers ::addCacheContexts
395 * @covers ::resetCacheContexts
396 * @covers ::getCacheContexts
397 * @covers ::cachePerPermissions
398 * @covers ::cachePerUser
399 * @covers ::allowedIfHasPermission
401 public function testCacheContexts() {
402 $verify = function (AccessResult $access, array $contexts) {
403 $this->assertFalse($access->isAllowed());
404 $this->assertFalse($access->isForbidden());
405 $this->assertTrue($access->isNeutral());
406 $this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge());
407 $this->assertSame($contexts, $access->getCacheContexts());
408 $this->assertSame([], $access->getCacheTags());
411 $access = AccessResult::neutral()->addCacheContexts(['foo']);
412 $verify($access, ['foo']);
413 // Verify resetting works.
414 $access->resetCacheContexts();
415 $verify($access, []);
416 // Verify idempotency.
417 $access->addCacheContexts(['foo'])
418 ->addCacheContexts(['foo']);
419 $verify($access, ['foo']);
420 // Verify same values in different call order yields the same result.
421 $access->resetCacheContexts()
422 ->addCacheContexts(['foo'])
423 ->addCacheContexts(['bar']);
424 $verify($access, ['bar', 'foo']);
425 $access->resetCacheContexts()
426 ->addCacheContexts(['bar'])
427 ->addCacheContexts(['foo']);
428 $verify($access, ['bar', 'foo']);
430 // ::cachePerPermissions() convenience method.
431 $contexts = ['user.permissions'];
432 $a = AccessResult::neutral()->addCacheContexts($contexts);
433 $verify($a, $contexts);
434 $b = AccessResult::neutral()->cachePerPermissions();
435 $verify($b, $contexts);
436 $this->assertEquals($a, $b);
438 // ::cachePerUser() convenience method.
439 $contexts = ['user'];
440 $a = AccessResult::neutral()->addCacheContexts($contexts);
441 $verify($a, $contexts);
442 $b = AccessResult::neutral()->cachePerUser();
443 $verify($b, $contexts);
444 $this->assertEquals($a, $b);
447 $contexts = ['user', 'user.permissions'];
448 $a = AccessResult::neutral()->addCacheContexts($contexts);
449 $verify($a, $contexts);
450 $b = AccessResult::neutral()->cachePerPermissions()->cachePerUser();
451 $verify($b, $contexts);
452 $c = AccessResult::neutral()->cachePerUser()->cachePerPermissions();
453 $verify($c, $contexts);
454 $this->assertEquals($a, $b);
455 $this->assertEquals($a, $c);
457 // ::allowIfHasPermission and ::allowedIfHasPermission convenience methods.
458 $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
459 $account->expects($this->any())
460 ->method('hasPermission')
461 ->with('may herd llamas')
462 ->will($this->returnValue(FALSE));
463 $contexts = ['user.permissions'];
465 // Verify the object when using the ::allowedIfHasPermission() convenience
467 $b = AccessResult::allowedIfHasPermission($account, 'may herd llamas');
468 $verify($b, $contexts);
472 * @covers ::addCacheTags
473 * @covers ::addCacheableDependency
474 * @covers ::getCacheTags
475 * @covers ::resetCacheTags
477 public function testCacheTags() {
478 $verify = function (AccessResult $access, array $tags, array $contexts = [], $max_age = Cache::PERMANENT) {
479 $this->assertFalse($access->isAllowed());
480 $this->assertFalse($access->isForbidden());
481 $this->assertTrue($access->isNeutral());
482 $this->assertSame($max_age, $access->getCacheMaxAge());
483 $this->assertSame($contexts, $access->getCacheContexts());
484 $this->assertSame($tags, $access->getCacheTags());
487 $access = AccessResult::neutral()->addCacheTags(['foo:bar']);
488 $verify($access, ['foo:bar']);
489 // Verify resetting works.
490 $access->resetCacheTags();
491 $verify($access, []);
492 // Verify idempotency.
493 $access->addCacheTags(['foo:bar'])
494 ->addCacheTags(['foo:bar']);
495 $verify($access, ['foo:bar']);
496 // Verify same values in different call order yields the same result.
497 $access->resetCacheTags()
498 ->addCacheTags(['bar:baz'])
499 ->addCacheTags(['bar:qux'])
500 ->addCacheTags(['foo:bar'])
501 ->addCacheTags(['foo:baz']);
502 $verify($access, ['bar:baz', 'bar:qux', 'foo:bar', 'foo:baz']);
503 $access->resetCacheTags()
504 ->addCacheTags(['foo:bar'])
505 ->addCacheTags(['bar:qux'])
506 ->addCacheTags(['foo:baz'])
507 ->addCacheTags(['bar:baz']);
508 $verify($access, ['bar:baz', 'bar:qux', 'foo:bar', 'foo:baz']);
510 // ::addCacheableDependency() convenience method.
511 $node = $this->getMock('\Drupal\node\NodeInterface');
512 $node->expects($this->any())
513 ->method('getCacheTags')
514 ->will($this->returnValue(['node:20011988']));
515 $node->expects($this->any())
516 ->method('getCacheMaxAge')
518 $node->expects($this->any())
519 ->method('getCacheContexts')
520 ->willReturn(['user']);
521 $tags = ['node:20011988'];
522 $a = AccessResult::neutral()->addCacheTags($tags);
524 $b = AccessResult::neutral()->addCacheableDependency($node);
525 $verify($b, $tags, ['user'], 600);
527 $non_cacheable_dependency = new \stdClass();
528 $non_cacheable = AccessResult::neutral()->addCacheableDependency($non_cacheable_dependency);
529 $verify($non_cacheable, [], [], 0);
533 * @covers ::inheritCacheability
535 public function testInheritCacheability() {
536 // andIf(); 1st has defaults, 2nd has custom tags, contexts and max-age.
537 $access = AccessResult::allowed();
538 $other = AccessResult::allowed()->setCacheMaxAge(1500)->cachePerPermissions()->addCacheTags(['node:20011988']);
539 $this->assertTrue($access->inheritCacheability($other) instanceof AccessResult);
540 $this->assertSame(['user.permissions'], $access->getCacheContexts());
541 $this->assertSame(['node:20011988'], $access->getCacheTags());
542 $this->assertSame(1500, $access->getCacheMaxAge());
544 // andIf(); 1st has custom tags, max-age, 2nd has custom contexts and max-age.
545 $access = AccessResult::allowed()->cachePerUser()->setCacheMaxAge(43200);
546 $other = AccessResult::forbidden()->addCacheTags(['node:14031991'])->setCacheMaxAge(86400);
547 $this->assertTrue($access->inheritCacheability($other) instanceof AccessResult);
548 $this->assertSame(['user'], $access->getCacheContexts());
549 $this->assertSame(['node:14031991'], $access->getCacheTags());
550 $this->assertSame(43200, $access->getCacheMaxAge());
554 * Provides a list of access result pairs and operations to test.
556 * This tests the propagation of cacheability metadata. Rather than testing
557 * every single bit of cacheability metadata, which would lead to a mind-
558 * boggling number of permutations, in this test, we only consider the
559 * permutations of all pairs of the following set:
560 * - Allowed, implements CDI and is cacheable.
561 * - Allowed, implements CDI and is not cacheable.
562 * - Allowed, does not implement CDI (hence not cacheable).
563 * - Forbidden, implements CDI and is cacheable.
564 * - Forbidden, implements CDI and is not cacheable.
565 * - Forbidden, does not implement CDI (hence not cacheable).
566 * - Neutral, implements CDI and is cacheable.
567 * - Neutral, implements CDI and is not cacheable.
568 * - Neutral, does not implement CDI (hence not cacheable).
570 * (Where "CDI" is CacheableDependencyInterface.)
572 * This leads to 72 permutations (9!/(9-2)! = 9*8 = 72) per operation. There
573 * are two operations to test (AND and OR), so that leads to a grand total of
574 * 144 permutations, all of which are tested.
576 * There are two "contagious" patterns:
577 * - Any operation with a forbidden access result yields a forbidden result.
578 * This therefore also applies to the cacheability metadata associated with
579 * a forbidden result. This is the case for bullets 4, 5 and 6 in the set
581 * - Any operation yields an access result object that is of the same class
582 * (implementation) as the first operand. This is because operations are
583 * invoked on the first operand. Therefore, if the first implementation
584 * does not implement CacheableDependencyInterface, then the result won't
585 * either. This is the case for bullets 3, 6 and 9 in the set above.
587 public function andOrCacheabilityPropagationProvider() {
588 // ct: cacheable=true, cf: cacheable=false, un: uncacheable.
589 // Note: the test cases that have a "un" access result as the first operand
590 // test UncacheableTestAccessResult, not AccessResult. However, we
591 // definitely want to verify that AccessResult's orIf() and andIf() methods
592 // work correctly when given an AccessResultInterface implementation that
593 // does not implement CacheableDependencyInterface, and we want to test the
594 // full gamut of permutations, so that's not a problem.
595 $allowed_ct = AccessResult::allowed();
596 $allowed_cf = AccessResult::allowed()->setCacheMaxAge(0);
597 $allowed_un = new UncacheableTestAccessResult('ALLOWED');
598 $forbidden_ct = AccessResult::forbidden();
599 $forbidden_cf = AccessResult::forbidden()->setCacheMaxAge(0);
600 $forbidden_un = new UncacheableTestAccessResult('FORBIDDEN');
601 $neutral_ct = AccessResult::neutral();
602 $neutral_cf = AccessResult::neutral()->setCacheMaxAge(0);
603 $neutral_un = new UncacheableTestAccessResult('NEUTRAL');
606 // - First column: first access result.
607 // - Second column: operator ('OR' or 'AND').
608 // - Third column: second access result.
609 // - Fourth column: whether result implements CacheableDependencyInterface
610 // - Fifth column: whether the result is cacheable (if column 4 is TRUE)
612 // Allowed (ct) OR allowed (ct,cf,un).
613 [$allowed_ct, 'OR', $allowed_ct, TRUE, TRUE],
614 [$allowed_ct, 'OR', $allowed_cf, TRUE, TRUE],
615 [$allowed_ct, 'OR', $allowed_un, TRUE, TRUE],
616 // Allowed (cf) OR allowed (ct,cf,un).
617 [$allowed_cf, 'OR', $allowed_ct, TRUE, TRUE],
618 [$allowed_cf, 'OR', $allowed_cf, TRUE, FALSE],
619 [$allowed_cf, 'OR', $allowed_un, TRUE, FALSE],
620 // Allowed (un) OR allowed (ct,cf,un).
621 [$allowed_un, 'OR', $allowed_ct, FALSE, NULL],
622 [$allowed_un, 'OR', $allowed_cf, FALSE, NULL],
623 [$allowed_un, 'OR', $allowed_un, FALSE, NULL],
625 // Allowed (ct) OR forbidden (ct,cf,un).
626 [$allowed_ct, 'OR', $forbidden_ct, TRUE, TRUE],
627 [$allowed_ct, 'OR', $forbidden_cf, TRUE, FALSE],
628 [$allowed_ct, 'OR', $forbidden_un, TRUE, FALSE],
629 // Allowed (cf) OR forbidden (ct,cf,un).
630 [$allowed_cf, 'OR', $forbidden_ct, TRUE, TRUE],
631 [$allowed_cf, 'OR', $forbidden_cf, TRUE, FALSE],
632 [$allowed_cf, 'OR', $forbidden_un, TRUE, FALSE],
633 // Allowed (un) OR forbidden (ct,cf,un).
634 [$allowed_un, 'OR', $forbidden_ct, FALSE, NULL],
635 [$allowed_un, 'OR', $forbidden_cf, FALSE, NULL],
636 [$allowed_un, 'OR', $forbidden_un, FALSE, NULL],
638 // Allowed (ct) OR neutral (ct,cf,un).
639 [$allowed_ct, 'OR', $neutral_ct, TRUE, TRUE],
640 [$allowed_ct, 'OR', $neutral_cf, TRUE, TRUE],
641 [$allowed_ct, 'OR', $neutral_un, TRUE, TRUE],
642 // Allowed (cf) OR neutral (ct,cf,un).
643 [$allowed_cf, 'OR', $neutral_ct, TRUE, FALSE],
644 [$allowed_cf, 'OR', $neutral_cf, TRUE, FALSE],
645 [$allowed_cf, 'OR', $neutral_un, TRUE, FALSE],
646 // Allowed (un) OR neutral (ct,cf,un).
647 [$allowed_un, 'OR', $neutral_ct, FALSE, NULL],
648 [$allowed_un, 'OR', $neutral_cf, FALSE, NULL],
649 [$allowed_un, 'OR', $neutral_un, FALSE, NULL],
651 // Forbidden (ct) OR allowed (ct,cf,un).
652 [$forbidden_ct, 'OR', $allowed_ct, TRUE, TRUE],
653 [$forbidden_ct, 'OR', $allowed_cf, TRUE, TRUE],
654 [$forbidden_ct, 'OR', $allowed_un, TRUE, TRUE],
655 // Forbidden (cf) OR allowed (ct,cf,un).
656 [$forbidden_cf, 'OR', $allowed_ct, TRUE, FALSE],
657 [$forbidden_cf, 'OR', $allowed_cf, TRUE, FALSE],
658 [$forbidden_cf, 'OR', $allowed_un, TRUE, FALSE],
659 // Forbidden (un) OR allowed (ct,cf,un).
660 [$forbidden_un, 'OR', $allowed_ct, FALSE, NULL],
661 [$forbidden_un, 'OR', $allowed_cf, FALSE, NULL],
662 [$forbidden_un, 'OR', $allowed_un, FALSE, NULL],
664 // Forbidden (ct) OR neutral (ct,cf,un).
665 [$forbidden_ct, 'OR', $neutral_ct, TRUE, TRUE],
666 [$forbidden_ct, 'OR', $neutral_cf, TRUE, TRUE],
667 [$forbidden_ct, 'OR', $neutral_un, TRUE, TRUE],
668 // Forbidden (cf) OR neutral (ct,cf,un).
669 [$forbidden_cf, 'OR', $neutral_ct, TRUE, FALSE],
670 [$forbidden_cf, 'OR', $neutral_cf, TRUE, FALSE],
671 [$forbidden_cf, 'OR', $neutral_un, TRUE, FALSE],
672 // Forbidden (un) OR neutral (ct,cf,un).
673 [$forbidden_un, 'OR', $neutral_ct, FALSE, NULL],
674 [$forbidden_un, 'OR', $neutral_cf, FALSE, NULL],
675 [$forbidden_un, 'OR', $neutral_un, FALSE, NULL],
677 // Forbidden (ct) OR forbidden (ct,cf,un).
678 [$forbidden_ct, 'OR', $forbidden_ct, TRUE, TRUE],
679 [$forbidden_ct, 'OR', $forbidden_cf, TRUE, TRUE],
680 [$forbidden_ct, 'OR', $forbidden_un, TRUE, TRUE],
681 // Forbidden (cf) OR forbidden (ct,cf,un).
682 [$forbidden_cf, 'OR', $forbidden_ct, TRUE, TRUE],
683 [$forbidden_cf, 'OR', $forbidden_cf, TRUE, FALSE],
684 [$forbidden_cf, 'OR', $forbidden_un, TRUE, FALSE],
685 // Forbidden (un) OR forbidden (ct,cf,un).
686 [$forbidden_un, 'OR', $forbidden_ct, FALSE, NULL],
687 [$forbidden_un, 'OR', $forbidden_cf, FALSE, NULL],
688 [$forbidden_un, 'OR', $forbidden_un, FALSE, NULL],
690 // Neutral (ct) OR allowed (ct,cf,un).
691 [$neutral_ct, 'OR', $allowed_ct, TRUE, TRUE],
692 [$neutral_ct, 'OR', $allowed_cf, TRUE, FALSE],
693 [$neutral_ct, 'OR', $allowed_un, TRUE, FALSE],
694 // Neutral (cf) OR allowed (ct,cf,un).
695 [$neutral_cf, 'OR', $allowed_ct, TRUE, TRUE],
696 [$neutral_cf, 'OR', $allowed_cf, TRUE, FALSE],
697 [$neutral_cf, 'OR', $allowed_un, TRUE, FALSE],
698 // Neutral (un) OR allowed (ct,cf,un).
699 [$neutral_un, 'OR', $allowed_ct, FALSE, NULL],
700 [$neutral_un, 'OR', $allowed_cf, FALSE, NULL],
701 [$neutral_un, 'OR', $allowed_un, FALSE, NULL],
703 // Neutral (ct) OR neutral (ct,cf,un).
704 [$neutral_ct, 'OR', $neutral_ct, TRUE, TRUE],
705 [$neutral_ct, 'OR', $neutral_cf, TRUE, TRUE],
706 [$neutral_ct, 'OR', $neutral_un, TRUE, TRUE],
707 // Neutral (cf) OR neutral (ct,cf,un).
708 [$neutral_cf, 'OR', $neutral_ct, TRUE, TRUE],
709 [$neutral_cf, 'OR', $neutral_cf, TRUE, FALSE],
710 [$neutral_cf, 'OR', $neutral_un, TRUE, FALSE],
711 // Neutral (un) OR neutral (ct,cf,un).
712 [$neutral_un, 'OR', $neutral_ct, FALSE, NULL],
713 [$neutral_un, 'OR', $neutral_cf, FALSE, NULL],
714 [$neutral_un, 'OR', $neutral_un, FALSE, NULL],
716 // Neutral (ct) OR forbidden (ct,cf,un).
717 [$neutral_ct, 'OR', $forbidden_ct, TRUE, TRUE],
718 [$neutral_ct, 'OR', $forbidden_cf, TRUE, FALSE],
719 [$neutral_ct, 'OR', $forbidden_un, TRUE, FALSE],
720 // Neutral (cf) OR forbidden (ct,cf,un).
721 [$neutral_cf, 'OR', $forbidden_ct, TRUE, TRUE],
722 [$neutral_cf, 'OR', $forbidden_cf, TRUE, FALSE],
723 [$neutral_cf, 'OR', $forbidden_un, TRUE, FALSE],
724 // Neutral (un) OR forbidden (ct,cf,un).
725 [$neutral_un, 'OR', $forbidden_ct, FALSE, NULL],
726 [$neutral_un, 'OR', $forbidden_cf, FALSE, NULL],
727 [$neutral_un, 'OR', $forbidden_un, FALSE, NULL],
729 // Allowed (ct) AND allowed (ct,cf,un).
730 [$allowed_ct, 'AND', $allowed_ct, TRUE, TRUE],
731 [$allowed_ct, 'AND', $allowed_cf, TRUE, FALSE],
732 [$allowed_ct, 'AND', $allowed_un, TRUE, FALSE],
733 // Allowed (cf) AND allowed (ct,cf,un).
734 [$allowed_cf, 'AND', $allowed_ct, TRUE, FALSE],
735 [$allowed_cf, 'AND', $allowed_cf, TRUE, FALSE],
736 [$allowed_cf, 'AND', $allowed_un, TRUE, FALSE],
737 // Allowed (un) AND allowed (ct,cf,un).
738 [$allowed_un, 'AND', $allowed_ct, FALSE, NULL],
739 [$allowed_un, 'AND', $allowed_cf, FALSE, NULL],
740 [$allowed_un, 'AND', $allowed_un, FALSE, NULL],
742 // Allowed (ct) AND forbidden (ct,cf,un).
743 [$allowed_ct, 'AND', $forbidden_ct, TRUE, TRUE],
744 [$allowed_ct, 'AND', $forbidden_cf, TRUE, FALSE],
745 [$allowed_ct, 'AND', $forbidden_un, TRUE, FALSE],
746 // Allowed (cf) AND forbidden (ct,cf,un).
747 [$allowed_cf, 'AND', $forbidden_ct, TRUE, TRUE],
748 [$allowed_cf, 'AND', $forbidden_cf, TRUE, FALSE],
749 [$allowed_cf, 'AND', $forbidden_un, TRUE, FALSE],
750 // Allowed (un) AND forbidden (ct,cf,un).
751 [$allowed_un, 'AND', $forbidden_ct, FALSE, NULL],
752 [$allowed_un, 'AND', $forbidden_cf, FALSE, NULL],
753 [$allowed_un, 'AND', $forbidden_un, FALSE, NULL],
755 // Allowed (ct) AND neutral (ct,cf,un).
756 [$allowed_ct, 'AND', $neutral_ct, TRUE, TRUE],
757 [$allowed_ct, 'AND', $neutral_cf, TRUE, FALSE],
758 [$allowed_ct, 'AND', $neutral_un, TRUE, FALSE],
759 // Allowed (cf) AND neutral (ct,cf,un).
760 [$allowed_cf, 'AND', $neutral_ct, TRUE, FALSE],
761 [$allowed_cf, 'AND', $neutral_cf, TRUE, FALSE],
762 [$allowed_cf, 'AND', $neutral_un, TRUE, FALSE],
763 // Allowed (un) AND neutral (ct,cf,un).
764 [$allowed_un, 'AND', $neutral_ct, FALSE, NULL],
765 [$allowed_un, 'AND', $neutral_cf, FALSE, NULL],
766 [$allowed_un, 'AND', $neutral_un, FALSE, NULL],
768 // Forbidden (ct) AND allowed (ct,cf,un).
769 [$forbidden_ct, 'AND', $allowed_ct, TRUE, TRUE],
770 [$forbidden_ct, 'AND', $allowed_cf, TRUE, TRUE],
771 [$forbidden_ct, 'AND', $allowed_un, TRUE, TRUE],
772 // Forbidden (cf) AND allowed (ct,cf,un).
773 [$forbidden_cf, 'AND', $allowed_ct, TRUE, FALSE],
774 [$forbidden_cf, 'AND', $allowed_cf, TRUE, FALSE],
775 [$forbidden_cf, 'AND', $allowed_un, TRUE, FALSE],
776 // Forbidden (un) AND allowed (ct,cf,un).
777 [$forbidden_un, 'AND', $allowed_ct, FALSE, NULL],
778 [$forbidden_un, 'AND', $allowed_cf, FALSE, NULL],
779 [$forbidden_un, 'AND', $allowed_un, FALSE, NULL],
781 // Forbidden (ct) AND neutral (ct,cf,un).
782 [$forbidden_ct, 'AND', $neutral_ct, TRUE, TRUE],
783 [$forbidden_ct, 'AND', $neutral_cf, TRUE, TRUE],
784 [$forbidden_ct, 'AND', $neutral_un, TRUE, TRUE],
785 // Forbidden (cf) AND neutral (ct,cf,un).
786 [$forbidden_cf, 'AND', $neutral_ct, TRUE, FALSE],
787 [$forbidden_cf, 'AND', $neutral_cf, TRUE, FALSE],
788 [$forbidden_cf, 'AND', $neutral_un, TRUE, FALSE],
789 // Forbidden (un) AND neutral (ct,cf,un).
790 [$forbidden_un, 'AND', $neutral_ct, FALSE, NULL],
791 [$forbidden_un, 'AND', $neutral_cf, FALSE, NULL],
792 [$forbidden_un, 'AND', $neutral_un, FALSE, NULL],
794 // Forbidden (ct) AND forbidden (ct,cf,un).
795 [$forbidden_ct, 'AND', $forbidden_ct, TRUE, TRUE],
796 [$forbidden_ct, 'AND', $forbidden_cf, TRUE, TRUE],
797 [$forbidden_ct, 'AND', $forbidden_un, TRUE, TRUE],
798 // Forbidden (cf) AND forbidden (ct,cf,un).
799 [$forbidden_cf, 'AND', $forbidden_ct, TRUE, FALSE],
800 [$forbidden_cf, 'AND', $forbidden_cf, TRUE, FALSE],
801 [$forbidden_cf, 'AND', $forbidden_un, TRUE, FALSE],
802 // Forbidden (un) AND forbidden (ct,cf,un).
803 [$forbidden_un, 'AND', $forbidden_ct, FALSE, NULL],
804 [$forbidden_un, 'AND', $forbidden_cf, FALSE, NULL],
805 [$forbidden_un, 'AND', $forbidden_un, FALSE, NULL],
807 // Neutral (ct) AND allowed (ct,cf,un).
808 [$neutral_ct, 'AND', $allowed_ct, TRUE, TRUE],
809 [$neutral_ct, 'AND', $allowed_cf, TRUE, TRUE],
810 [$neutral_ct, 'AND', $allowed_un, TRUE, TRUE],
811 // Neutral (cf) AND allowed (ct,cf,un).
812 [$neutral_cf, 'AND', $allowed_ct, TRUE, FALSE],
813 [$neutral_cf, 'AND', $allowed_cf, TRUE, FALSE],
814 [$neutral_cf, 'AND', $allowed_un, TRUE, FALSE],
815 // Neutral (un) AND allowed (ct,cf,un).
816 [$neutral_un, 'AND', $allowed_ct, FALSE, NULL],
817 [$neutral_un, 'AND', $allowed_cf, FALSE, NULL],
818 [$neutral_un, 'AND', $allowed_un, FALSE, NULL],
820 // Neutral (ct) AND neutral (ct,cf,un).
821 [$neutral_ct, 'AND', $neutral_ct, TRUE, TRUE],
822 [$neutral_ct, 'AND', $neutral_cf, TRUE, TRUE],
823 [$neutral_ct, 'AND', $neutral_un, TRUE, TRUE],
824 // Neutral (cf) AND neutral (ct,cf,un).
825 [$neutral_cf, 'AND', $neutral_ct, TRUE, FALSE],
826 [$neutral_cf, 'AND', $neutral_cf, TRUE, FALSE],
827 [$neutral_cf, 'AND', $neutral_un, TRUE, FALSE],
828 // Neutral (un) AND neutral (ct,cf,un).
829 [$neutral_un, 'AND', $neutral_ct, FALSE, NULL],
830 [$neutral_un, 'AND', $neutral_cf, FALSE, NULL],
831 [$neutral_un, 'AND', $neutral_un, FALSE, NULL],
833 // Neutral (ct) AND forbidden (ct,cf,un).
834 [$neutral_ct, 'AND', $forbidden_ct, TRUE, TRUE],
835 [$neutral_ct, 'AND', $forbidden_cf, TRUE, FALSE],
836 [$neutral_ct, 'AND', $forbidden_un, TRUE, FALSE],
837 // Neutral (cf) AND forbidden (ct,cf,un).
838 [$neutral_cf, 'AND', $forbidden_ct, TRUE, TRUE],
839 [$neutral_cf, 'AND', $forbidden_cf, TRUE, FALSE],
840 [$neutral_cf, 'AND', $forbidden_un, TRUE, FALSE],
841 // Neutral (un) AND forbidden (ct,cf,un).
842 [$neutral_un, 'AND', $forbidden_ct, FALSE, NULL],
843 [$neutral_un, 'AND', $forbidden_cf, FALSE, NULL],
844 [$neutral_un, 'AND', $forbidden_un, FALSE, NULL],
851 * @covers ::inheritCacheability
853 * @dataProvider andOrCacheabilityPropagationProvider
855 public function testAndOrCacheabilityPropagation(AccessResultInterface $first, $op, AccessResultInterface $second, $implements_cacheable_dependency_interface, $is_cacheable) {
857 $result = $first->orIf($second);
859 elseif ($op === 'AND') {
860 $result = $first->andIf($second);
863 throw new \LogicException('Invalid operator specified');
865 if ($implements_cacheable_dependency_interface) {
866 $this->assertTrue($result instanceof CacheableDependencyInterface, 'Result is an instance of CacheableDependencyInterface.');
867 if ($result instanceof CacheableDependencyInterface) {
868 $this->assertSame($is_cacheable, $result->getCacheMaxAge() !== 0, 'getCacheMaxAge() matches expectations.');
872 $this->assertFalse($result instanceof CacheableDependencyInterface, 'Result is not an instance of CacheableDependencyInterface.');
879 * Tests the special case of ORing non-forbidden access results that are both
880 * cacheable but have different cacheability metadata.
881 * This is only the case for non-forbidden access results; we still abort the
882 * ORing process as soon as a forbidden access result is encountered. This is
883 * tested in ::testOrIf().
885 public function testOrIfCacheabilityMerging() {
886 $merge_both_directions = function (AccessResult $a, AccessResult $b) {
887 // A globally cacheable access result.
888 $a->setCacheMaxAge(3600);
889 // Another access result that is cacheable per permissions.
890 $b->setCacheMaxAge(86400)->cachePerPermissions();
893 $this->assertTrue($r1->getCacheMaxAge() === 3600);
894 $this->assertSame(['user.permissions'], $r1->getCacheContexts());
896 $this->assertTrue($r2->getCacheMaxAge() === 3600);
897 $this->assertSame(['user.permissions'], $r2->getCacheContexts());
900 // Merge either direction, get the same result.
901 $merge_both_directions(AccessResult::allowed(), AccessResult::allowed());
902 $merge_both_directions(AccessResult::allowed(), AccessResult::neutral());
903 $merge_both_directions(AccessResult::neutral(), AccessResult::neutral());
904 $merge_both_directions(AccessResult::neutral(), AccessResult::allowed());
908 * Tests allowedIfHasPermissions().
910 * @covers ::allowedIfHasPermissions
912 * @dataProvider providerTestAllowedIfHasPermissions
914 * @param string[] $permissions
915 * The permissions to check for.
916 * @param string $conjunction
917 * The conjunction to use when checking for permission. 'AND' or 'OR'.
918 * @param \Drupal\Core\Access\AccessResult $expected_access
919 * The expected access check result.
921 public function testAllowedIfHasPermissions($permissions, $conjunction, AccessResult $expected_access) {
922 $account = $this->getMock('\Drupal\Core\Session\AccountInterface');
923 $account->expects($this->any())
924 ->method('hasPermission')
931 $expected_access->cachePerPermissions();
934 $access_result = AccessResult::allowedIfHasPermissions($account, $permissions, $conjunction);
935 $this->assertEquals($expected_access, $access_result);
939 * Provides data for the testAllowedIfHasPermissions() method.
943 public function providerTestAllowedIfHasPermissions() {
944 $access_result = AccessResult::allowedIf(FALSE);
945 $data[] = [[], 'AND', $access_result];
946 $data[] = [[], 'OR', $access_result];
948 $access_result = AccessResult::allowedIf(TRUE);
949 $data[] = [['allowed'], 'OR', $access_result];
950 $data[] = [['allowed'], 'AND', $access_result];
952 $access_result = AccessResult::allowedIf(FALSE);
953 $access_result->setReason("The 'denied' permission is required.");
954 $data[] = [['denied'], 'OR', $access_result];
955 $data[] = [['denied'], 'AND', $access_result];
957 $access_result = AccessResult::allowedIf(TRUE);
958 $data[] = [['allowed', 'denied'], 'OR', $access_result];
959 $data[] = [['denied', 'allowed'], 'OR', $access_result];
961 $access_result = AccessResult::allowedIf(TRUE);
962 $data[] = [['allowed', 'denied', 'other'], 'OR', $access_result];
964 $access_result = AccessResult::allowedIf(FALSE);
965 $access_result->setReason("The following permissions are required: 'allowed' AND 'denied'.");
966 $data[] = [['allowed', 'denied'], 'AND', $access_result];
973 class UncacheableTestAccessResult implements AccessResultInterface {
976 * The access result value. 'ALLOWED', 'FORBIDDEN' or 'NEUTRAL'.
983 * Constructs a new UncacheableTestAccessResult object.
985 public function __construct($value) {
986 $this->value = $value;
992 public function isAllowed() {
993 return $this->value === 'ALLOWED';
999 public function isForbidden() {
1000 return $this->value === 'FORBIDDEN';
1006 public function isNeutral() {
1007 return $this->value === 'NEUTRAL';
1013 public function orIf(AccessResultInterface $other) {
1014 if ($this->isForbidden() || $other->isForbidden()) {
1015 return new static('FORBIDDEN');
1017 elseif ($this->isAllowed() || $other->isAllowed()) {
1018 return new static('ALLOWED');
1021 return new static('NEUTRAL');
1028 public function andIf(AccessResultInterface $other) {
1029 if ($this->isForbidden() || $other->isForbidden()) {
1030 return new static('FORBIDDEN');
1032 elseif ($this->isAllowed() && $other->isAllowed()) {
1033 return new static('ALLOWED');
1036 return new static('NEUTRAL');