Upgraded drupal core with security updates
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Access / AccessResultTest.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\Tests\Core\Access\AccessResultTest.
6  */
7
8 namespace Drupal\Tests\Core\Access;
9
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;
18
19 /**
20  * @coversDefaultClass \Drupal\Core\Access\AccessResult
21  * @group Access
22  */
23 class AccessResultTest extends UnitTestCase {
24
25   /**
26    * The cache contexts manager.
27    *
28    * @var \Drupal\Core\Cache\Context\CacheContextsManager|\PHPUnit_Framework_MockObject_MockObject
29    */
30   protected $cacheContextsManager;
31
32   /**
33    * {@inheritdoc}
34    */
35   protected function setUp() {
36     parent::setUp();
37
38     $this->cacheContextsManager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
39       ->disableOriginalConstructor()
40       ->getMock();
41
42     $this->cacheContextsManager->method('assertValidTokens')->willReturn(TRUE);
43     $container = new ContainerBuilder();
44     $container->set('cache_contexts_manager', $this->cacheContextsManager);
45     \Drupal::setContainer($container);
46   }
47
48   protected function assertDefaultCacheability(AccessResult $access) {
49     $this->assertSame([], $access->getCacheContexts());
50     $this->assertSame([], $access->getCacheTags());
51     $this->assertSame(Cache::PERMANENT, $access->getCacheMaxAge());
52   }
53
54   /**
55    * Tests the construction of an AccessResult object.
56    *
57    * @covers ::neutral
58    */
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);
65     };
66
67     // Verify the object when using the constructor.
68     $a = new AccessResultNeutral();
69     $verify($a);
70
71     // Verify the object when using the ::create() convenience method.
72     $b = AccessResult::neutral();
73     $verify($b);
74
75     $this->assertEquals($a, $b);
76   }
77
78   /**
79    * @covers ::allowed
80    * @covers ::isAllowed
81    * @covers ::isForbidden
82    * @covers ::isNeutral
83    */
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);
90     };
91
92     // Verify the object when using the ::allowed() convenience static method.
93     $b = AccessResult::allowed();
94     $verify($b);
95   }
96
97   /**
98    * @covers ::forbidden
99    * @covers ::isAllowed
100    * @covers ::isForbidden
101    * @covers ::isNeutral
102    */
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);
109     };
110
111     // Verify the object when using the ::forbidden() convenience static method.
112     $b = AccessResult::forbidden();
113     $verify($b);
114   }
115
116   /**
117    * @covers ::forbidden
118    */
119   public function testAccessForbiddenReason() {
120     $verify = function (AccessResult $access, $reason) {
121       $this->assertInstanceOf(AccessResultReasonInterface::class, $access);
122       $this->assertSame($reason, $access->getReason());
123     };
124
125     $b = AccessResult::forbidden();
126     $verify($b, NULL);
127
128     $reason = $this->getRandomGenerator()->string();
129     $b = AccessResult::forbidden($reason);
130     $verify($b, $reason);
131   }
132
133   /**
134    * @covers ::allowedIf
135    * @covers ::isAllowed
136    * @covers ::isForbidden
137    * @covers ::isNeutral
138    */
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);
145     };
146
147     $b1 = AccessResult::allowedIf(TRUE);
148     $verify($b1, TRUE);
149     $b2 = AccessResult::allowedIf(FALSE);
150     $verify($b2, FALSE);
151   }
152
153   /**
154    * @covers ::forbiddenIf
155    * @covers ::isAllowed
156    * @covers ::isForbidden
157    * @covers ::isNeutral
158    */
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);
165     };
166
167     $b1 = AccessResult::forbiddenIf(TRUE);
168     $verify($b1, TRUE);
169     $b2 = AccessResult::forbiddenIf(FALSE);
170     $verify($b2, FALSE);
171   }
172
173   /**
174    * @covers ::andIf
175    */
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());
183
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);
190
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);
198
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);
206
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);
214
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);
222
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);
230
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);
238
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);
246
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);
254
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);
262   }
263
264   /**
265    * @covers ::orIf
266    */
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());
274
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);
281
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);
288
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);
296
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);
304
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);
311
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);
319
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);
327
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);
335
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);
343
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);
351   }
352
353   /**
354    * @covers ::setCacheMaxAge
355    * @covers ::getCacheMaxAge
356    */
357   public function testCacheMaxAge() {
358     $this->assertSame(Cache::PERMANENT, AccessResult::neutral()->getCacheMaxAge());
359     $this->assertSame(1337, AccessResult::neutral()->setCacheMaxAge(1337)->getCacheMaxAge());
360   }
361
362   /**
363    * @covers ::addCacheContexts
364    * @covers ::resetCacheContexts
365    * @covers ::getCacheContexts
366    * @covers ::cachePerPermissions
367    * @covers ::cachePerUser
368    * @covers ::allowedIfHasPermission
369    */
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());
378     };
379
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']);
398
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);
406
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);
414
415     // Both.
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);
425
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'];
433
434     // Verify the object when using the ::allowedIfHasPermission() convenience
435     // static method.
436     $b = AccessResult::allowedIfHasPermission($account, 'may herd llamas');
437     $verify($b, $contexts);
438   }
439
440   /**
441    * @covers ::addCacheTags
442    * @covers ::addCacheableDependency
443    * @covers ::getCacheTags
444    * @covers ::resetCacheTags
445    */
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());
454     };
455
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']);
478
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')
486       ->willReturn(600);
487     $node->expects($this->any())
488       ->method('getCacheContexts')
489       ->willReturn(['user']);
490     $tags = ['node:20011988'];
491     $a = AccessResult::neutral()->addCacheTags($tags);
492     $verify($a, $tags);
493     $b = AccessResult::neutral()->addCacheableDependency($node);
494     $verify($b, $tags, ['user'], 600);
495
496     $non_cacheable_dependency = new \stdClass();
497     $non_cacheable = AccessResult::neutral()->addCacheableDependency($non_cacheable_dependency);
498     $verify($non_cacheable, [], [], 0);
499   }
500
501   /**
502    * @covers ::inheritCacheability
503    */
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());
512
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());
520   }
521
522   /**
523    * Provides a list of access result pairs and operations to test.
524    *
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).
538    *
539    * (Where "CDI" is CacheableDependencyInterface.)
540    *
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.
544    *
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
549    *   above.
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.
555    */
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');
573
574     // Structure:
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)
580     return [
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],
593
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],
606
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],
619
620
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],
633
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],
646
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],
659
660
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],
673
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],
686
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],
699
700
701
702
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],
715
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],
728
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],
741
742
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],
755
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],
768
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],
781
782
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],
795
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],
808
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],
821     ];
822   }
823
824   /**
825    * @covers ::andIf
826    * @covers ::orIf
827    * @covers ::inheritCacheability
828    *
829    * @dataProvider andOrCacheabilityPropagationProvider
830    */
831   public function testAndOrCacheabilityPropagation(AccessResultInterface $first, $op, AccessResultInterface $second, $implements_cacheable_dependency_interface, $is_cacheable) {
832     if ($op === 'OR') {
833       $result = $first->orIf($second);
834     }
835     elseif ($op === 'AND') {
836       $result = $first->andIf($second);
837     }
838     else {
839       throw new \LogicException('Invalid operator specified');
840     }
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.');
845       }
846     }
847     else {
848       $this->assertFalse($result instanceof CacheableDependencyInterface, 'Result is not an instance of CacheableDependencyInterface.');
849     }
850   }
851
852   /**
853    * @covers ::orIf
854    *
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().
860    */
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();
867
868       $r1 = $a->orIf($b);
869       $this->assertTrue($r1->getCacheMaxAge() === 3600);
870       $this->assertSame(['user.permissions'], $r1->getCacheContexts());
871       $r2 = $b->orIf($a);
872       $this->assertTrue($r2->getCacheMaxAge() === 3600);
873       $this->assertSame(['user.permissions'], $r2->getCacheContexts());
874     };
875
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());
881   }
882
883   /**
884    * Tests allowedIfHasPermissions().
885    *
886    * @covers ::allowedIfHasPermissions
887    *
888    * @dataProvider providerTestAllowedIfHasPermissions
889    *
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.
896    */
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')
901       ->willReturnMap([
902         ['allowed', TRUE],
903         ['denied', FALSE],
904       ]);
905
906     if ($permissions) {
907       $expected_access->cachePerPermissions();
908     }
909
910     $access_result = AccessResult::allowedIfHasPermissions($account, $permissions, $conjunction);
911     $this->assertEquals($expected_access, $access_result);
912   }
913
914   /**
915    * Provides data for the testAllowedIfHasPermissions() method.
916    *
917    * @return array
918    */
919   public function providerTestAllowedIfHasPermissions() {
920     $access_result = AccessResult::allowedIf(FALSE);
921     $data[] = [[], 'AND', $access_result];
922     $data[] = [[], 'OR', $access_result];
923
924     $access_result = AccessResult::allowedIf(TRUE);
925     $data[] = [['allowed'], 'OR', $access_result];
926     $data[] = [['allowed'], 'AND', $access_result];
927
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];
932
933     $access_result = AccessResult::allowedIf(TRUE);
934     $data[] = [['allowed', 'denied'], 'OR', $access_result];
935     $data[] = [['denied', 'allowed'], 'OR', $access_result];
936
937     $access_result = AccessResult::allowedIf(TRUE);
938     $data[] = [['allowed', 'denied', 'other'], 'OR', $access_result];
939
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];
943
944     return $data;
945   }
946
947 }
948
949 class UncacheableTestAccessResult implements AccessResultInterface {
950
951   /**
952    * The access result value. 'ALLOWED', 'FORBIDDEN' or 'NEUTRAL'.
953    *
954    * @var string
955    */
956   protected $value;
957
958   /**
959    * Constructs a new UncacheableTestAccessResult object.
960    */
961   public function __construct($value) {
962     $this->value = $value;
963   }
964   /**
965    * {@inheritdoc}
966    */
967   public function isAllowed() {
968     return $this->value === 'ALLOWED';
969   }
970
971   /**
972    * {@inheritdoc}
973    */
974   public function isForbidden() {
975     return $this->value === 'FORBIDDEN';
976   }
977
978   /**
979    * {@inheritdoc}
980    */
981   public function isNeutral() {
982     return $this->value === 'NEUTRAL';
983   }
984
985   /**
986    * {@inheritdoc}
987    */
988   public function orIf(AccessResultInterface $other) {
989     if ($this->isForbidden() || $other->isForbidden()) {
990       return new static('FORBIDDEN');
991     }
992     elseif ($this->isAllowed() || $other->isAllowed()) {
993       return new static('ALLOWED');
994     }
995     else {
996       return new static('NEUTRAL');
997     }
998   }
999
1000   /**
1001    * {@inheritdoc}
1002    */
1003   public function andIf(AccessResultInterface $other) {
1004     if ($this->isForbidden() || $other->isForbidden()) {
1005       return new static('FORBIDDEN');
1006     }
1007     elseif ($this->isAllowed() && $other->isAllowed()) {
1008       return new static('ALLOWED');
1009     }
1010     else {
1011       return new static('NEUTRAL');
1012     }
1013   }
1014
1015 }