@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class PhabricatorPolicyTestCase extends PhabricatorTestCase {
4
5 /**
6 * Verify that any user can view an object with POLICY_PUBLIC.
7 */
8 public function testPublicPolicyEnabled() {
9 $env = PhabricatorEnv::beginScopedEnv();
10 $env->overrideEnvConfig('policy.allow-public', true);
11
12 $this->expectVisibility(
13 $this->buildObject(PhabricatorPolicies::POLICY_PUBLIC),
14 array(
15 'public' => true,
16 'user' => true,
17 'admin' => true,
18 ),
19 pht('Public Policy (Enabled in Config)'));
20 }
21
22
23 /**
24 * Verify that POLICY_PUBLIC is interpreted as POLICY_USER when public
25 * policies are disallowed.
26 */
27 public function testPublicPolicyDisabled() {
28 $env = PhabricatorEnv::beginScopedEnv();
29 $env->overrideEnvConfig('policy.allow-public', false);
30
31 $this->expectVisibility(
32 $this->buildObject(PhabricatorPolicies::POLICY_PUBLIC),
33 array(
34 'public' => false,
35 'user' => true,
36 'admin' => true,
37 ),
38 pht('Public Policy (Disabled in Config)'));
39 }
40
41
42 /**
43 * Verify that any logged-in user can view an object with POLICY_USER, but
44 * logged-out users can not.
45 */
46 public function testUsersPolicy() {
47 $this->expectVisibility(
48 $this->buildObject(PhabricatorPolicies::POLICY_USER),
49 array(
50 'public' => false,
51 'user' => true,
52 'admin' => true,
53 ),
54 pht('User Policy'));
55 }
56
57
58 /**
59 * Verify that only administrators can view an object with POLICY_ADMIN.
60 */
61 public function testAdminPolicy() {
62 $this->expectVisibility(
63 $this->buildObject(PhabricatorPolicies::POLICY_ADMIN),
64 array(
65 'public' => false,
66 'user' => false,
67 'admin' => true,
68 ),
69 pht('Admin Policy'));
70 }
71
72
73 /**
74 * Verify that no one can view an object with POLICY_NOONE.
75 */
76 public function testNoOnePolicy() {
77 $this->expectVisibility(
78 $this->buildObject(PhabricatorPolicies::POLICY_NOONE),
79 array(
80 'public' => false,
81 'user' => false,
82 'admin' => false,
83 ),
84 pht('No One Policy'));
85 }
86
87
88 /**
89 * Test offset-based filtering.
90 */
91 public function testOffsets() {
92 $results = array(
93 $this->buildObject(PhabricatorPolicies::POLICY_NOONE),
94 $this->buildObject(PhabricatorPolicies::POLICY_NOONE),
95 $this->buildObject(PhabricatorPolicies::POLICY_NOONE),
96 $this->buildObject(PhabricatorPolicies::POLICY_USER),
97 $this->buildObject(PhabricatorPolicies::POLICY_USER),
98 $this->buildObject(PhabricatorPolicies::POLICY_USER),
99 );
100
101 $query = new PhabricatorPolicyAwareTestQuery();
102 $query->setResults($results);
103 $query->setViewer($this->buildUser('user'));
104
105 $this->assertEqual(
106 3,
107 count($query->setLimit(3)->setOffset(0)->execute()),
108 pht('Invisible objects are ignored.'));
109
110 $this->assertEqual(
111 0,
112 count($query->setLimit(3)->setOffset(3)->execute()),
113 pht('Offset pages through visible objects only.'));
114
115 $this->assertEqual(
116 2,
117 count($query->setLimit(3)->setOffset(1)->execute()),
118 pht('Offsets work correctly.'));
119
120 $this->assertEqual(
121 2,
122 count($query->setLimit(0)->setOffset(1)->execute()),
123 pht('Offset with no limit works.'));
124 }
125
126
127 /**
128 * Test limits.
129 */
130 public function testLimits() {
131 $results = array(
132 $this->buildObject(PhabricatorPolicies::POLICY_USER),
133 $this->buildObject(PhabricatorPolicies::POLICY_USER),
134 $this->buildObject(PhabricatorPolicies::POLICY_USER),
135 $this->buildObject(PhabricatorPolicies::POLICY_USER),
136 $this->buildObject(PhabricatorPolicies::POLICY_USER),
137 $this->buildObject(PhabricatorPolicies::POLICY_USER),
138 );
139
140 $query = new PhabricatorPolicyAwareTestQuery();
141 $query->setResults($results);
142 $query->setViewer($this->buildUser('user'));
143
144 $this->assertEqual(
145 3,
146 count($query->setLimit(3)->setOffset(0)->execute()),
147 pht('Limits work.'));
148
149 $this->assertEqual(
150 2,
151 count($query->setLimit(3)->setOffset(4)->execute()),
152 pht('Limit + offset work.'));
153 }
154
155
156 /**
157 * Test that omnipotent users bypass policies.
158 */
159 public function testOmnipotence() {
160 $results = array(
161 $this->buildObject(PhabricatorPolicies::POLICY_NOONE),
162 );
163
164 $query = new PhabricatorPolicyAwareTestQuery();
165 $query->setResults($results);
166 $query->setViewer(PhabricatorUser::getOmnipotentUser());
167
168 $this->assertEqual(
169 1,
170 count($query->execute()));
171 }
172
173
174 /**
175 * Test that invalid policies reject viewers of all types.
176 */
177 public function testRejectInvalidPolicy() {
178 $invalid_policy = 'the duck goes quack';
179 $object = $this->buildObject($invalid_policy);
180
181 $this->expectVisibility(
182 $object = $this->buildObject($invalid_policy),
183 array(
184 'public' => false,
185 'user' => false,
186 'admin' => false,
187 ),
188 pht('Invalid Policy'));
189 }
190
191
192 /**
193 * Test that extended policies work.
194 */
195 public function testExtendedPolicies() {
196 $object = $this->buildObject(PhabricatorPolicies::POLICY_USER)
197 ->setPHID('PHID-TEST-1');
198
199 $this->expectVisibility(
200 $object,
201 array(
202 'public' => false,
203 'user' => true,
204 'admin' => true,
205 ),
206 pht('No Extended Policy'));
207
208 // Add a restrictive extended policy.
209 $extended = $this->buildObject(PhabricatorPolicies::POLICY_ADMIN)
210 ->setPHID('PHID-TEST-2');
211 $object->setExtendedPolicies(
212 array(
213 PhabricatorPolicyCapability::CAN_VIEW => array(
214 array($extended, PhabricatorPolicyCapability::CAN_VIEW),
215 ),
216 ));
217
218 $this->expectVisibility(
219 $object,
220 array(
221 'public' => false,
222 'user' => false,
223 'admin' => true,
224 ),
225 pht('With Extended Policy'));
226
227 // Depend on a different capability.
228 $object->setExtendedPolicies(
229 array(
230 PhabricatorPolicyCapability::CAN_VIEW => array(
231 array($extended, PhabricatorPolicyCapability::CAN_EDIT),
232 ),
233 ));
234
235 $extended->setCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT));
236 $extended->setPolicies(
237 array(
238 PhabricatorPolicyCapability::CAN_EDIT =>
239 PhabricatorPolicies::POLICY_NOONE,
240 ));
241
242 $this->expectVisibility(
243 $object,
244 array(
245 'public' => false,
246 'user' => false,
247 'admin' => false,
248 ),
249 pht('With Extended Policy + Edit'));
250 }
251
252
253 /**
254 * Test that cyclic extended policies are arrested properly.
255 */
256 public function testExtendedPolicyCycles() {
257 $object = $this->buildObject(PhabricatorPolicies::POLICY_USER)
258 ->setPHID('PHID-TEST-1');
259
260 $this->expectVisibility(
261 $object,
262 array(
263 'public' => false,
264 'user' => true,
265 'admin' => true,
266 ),
267 pht('No Extended Policy'));
268
269 // Set a self-referential extended policy on the object. This should
270 // make it fail all policy checks.
271 $object->setExtendedPolicies(
272 array(
273 PhabricatorPolicyCapability::CAN_VIEW => array(
274 array($object, PhabricatorPolicyCapability::CAN_VIEW),
275 ),
276 ));
277
278 $this->expectVisibility(
279 $object,
280 array(
281 'public' => false,
282 'user' => false,
283 'admin' => false,
284 ),
285 pht('Extended Policy with Cycle'));
286 }
287
288
289 /**
290 * Test bulk checks of extended policies.
291 *
292 * This is testing an issue with extended policy filtering which allowed
293 * unusual inputs to slip objects through the filter. See D14993.
294 */
295 public function testBulkExtendedPolicies() {
296 $object1 = $this->buildObject(PhabricatorPolicies::POLICY_USER)
297 ->setPHID('PHID-TEST-1');
298 $object2 = $this->buildObject(PhabricatorPolicies::POLICY_USER)
299 ->setPHID('PHID-TEST-2');
300 $object3 = $this->buildObject(PhabricatorPolicies::POLICY_USER)
301 ->setPHID('PHID-TEST-3');
302
303 $extended = $this->buildObject(PhabricatorPolicies::POLICY_ADMIN)
304 ->setPHID('PHID-TEST-999');
305
306 $object1->setExtendedPolicies(
307 array(
308 PhabricatorPolicyCapability::CAN_VIEW => array(
309 array(
310 $extended,
311 array(
312 PhabricatorPolicyCapability::CAN_VIEW,
313 PhabricatorPolicyCapability::CAN_EDIT,
314 ),
315 ),
316 ),
317 ));
318
319 $object2->setExtendedPolicies(
320 array(
321 PhabricatorPolicyCapability::CAN_VIEW => array(
322 array($extended, PhabricatorPolicyCapability::CAN_VIEW),
323 ),
324 ));
325
326 $object3->setExtendedPolicies(
327 array(
328 PhabricatorPolicyCapability::CAN_VIEW => array(
329 array(
330 $extended,
331 array(
332 PhabricatorPolicyCapability::CAN_VIEW,
333 PhabricatorPolicyCapability::CAN_EDIT,
334 ),
335 ),
336 ),
337 ));
338
339 $user = $this->buildUser('user');
340
341 $visible = id(new PhabricatorPolicyFilter())
342 ->setViewer($user)
343 ->requireCapabilities(
344 array(
345 PhabricatorPolicyCapability::CAN_VIEW,
346 ))
347 ->apply(
348 array(
349 $object1,
350 $object2,
351 $object3,
352 ));
353
354 $this->assertEqual(array(), $visible);
355 }
356
357
358 /**
359 * An omnipotent user should be able to see even objects with invalid
360 * policies.
361 */
362 public function testInvalidPolicyVisibleByOmnipotentUser() {
363 $invalid_policy = 'the cow goes moo';
364 $object = $this->buildObject($invalid_policy);
365
366 $results = array(
367 $object,
368 );
369
370 $query = new PhabricatorPolicyAwareTestQuery();
371 $query->setResults($results);
372 $query->setViewer(PhabricatorUser::getOmnipotentUser());
373
374 $this->assertEqual(
375 1,
376 count($query->execute()));
377 }
378
379 public function testAllQueriesBelongToActualApplications() {
380 $queries = id(new PhutilClassMapQuery())
381 ->setAncestorClass(PhabricatorPolicyAwareQuery::class)
382 ->execute();
383
384 foreach ($queries as $qclass => $query) {
385 $class = $query->getQueryApplicationClass();
386 if (!$class) {
387 continue;
388 }
389 $this->assertTrue(
390 (bool)PhabricatorApplication::getByClass($class),
391 pht(
392 "Application class '%s' for query '%s'.",
393 $class,
394 $qclass));
395 }
396 }
397
398 public function testMultipleCapabilities() {
399 $object = new PhabricatorPolicyTestObject();
400 $object->setCapabilities(
401 array(
402 PhabricatorPolicyCapability::CAN_VIEW,
403 PhabricatorPolicyCapability::CAN_EDIT,
404 ));
405 $object->setPolicies(
406 array(
407 PhabricatorPolicyCapability::CAN_VIEW
408 => PhabricatorPolicies::POLICY_USER,
409 PhabricatorPolicyCapability::CAN_EDIT
410 => PhabricatorPolicies::POLICY_NOONE,
411 ));
412
413 $filter = new PhabricatorPolicyFilter();
414 $filter->requireCapabilities(
415 array(
416 PhabricatorPolicyCapability::CAN_VIEW,
417 PhabricatorPolicyCapability::CAN_EDIT,
418 ));
419 $filter->setViewer($this->buildUser('user'));
420
421 $result = $filter->apply(array($object));
422
423 $this->assertEqual(array(), $result);
424 }
425
426 public function testPolicyStrength() {
427 $public = PhabricatorPolicyQuery::getGlobalPolicy(
428 PhabricatorPolicies::POLICY_PUBLIC);
429 $user = PhabricatorPolicyQuery::getGlobalPolicy(
430 PhabricatorPolicies::POLICY_USER);
431 $admin = PhabricatorPolicyQuery::getGlobalPolicy(
432 PhabricatorPolicies::POLICY_ADMIN);
433 $noone = PhabricatorPolicyQuery::getGlobalPolicy(
434 PhabricatorPolicies::POLICY_NOONE);
435
436 $this->assertFalse($public->isStrongerThan($public));
437 $this->assertFalse($public->isStrongerThan($user));
438 $this->assertFalse($public->isStrongerThan($admin));
439 $this->assertFalse($public->isStrongerThan($noone));
440
441 $this->assertTrue($user->isStrongerThan($public));
442 $this->assertFalse($user->isStrongerThan($user));
443 $this->assertFalse($user->isStrongerThan($admin));
444 $this->assertFalse($user->isStrongerThan($noone));
445
446 $this->assertTrue($admin->isStrongerThan($public));
447 $this->assertTrue($admin->isStrongerThan($user));
448 $this->assertFalse($admin->isStrongerThan($admin));
449 $this->assertFalse($admin->isStrongerThan($noone));
450
451 $this->assertTrue($noone->isStrongerThan($public));
452 $this->assertTrue($noone->isStrongerThan($user));
453 $this->assertTrue($noone->isStrongerThan($admin));
454 $this->assertFalse($admin->isStrongerThan($noone));
455 }
456
457
458 /**
459 * Test an object for visibility across multiple user specifications.
460 */
461 private function expectVisibility(
462 PhabricatorPolicyTestObject $object,
463 array $map,
464 $description) {
465
466 foreach ($map as $spec => $expect) {
467 $viewer = $this->buildUser($spec);
468
469 $query = new PhabricatorPolicyAwareTestQuery();
470 $query->setResults(array($object));
471 $query->setViewer($viewer);
472
473 $caught = null;
474 $result = null;
475 try {
476 $result = $query->executeOne();
477 } catch (PhabricatorPolicyException $ex) {
478 $caught = $ex;
479 }
480
481 if ($expect) {
482 $this->assertEqual(
483 $object,
484 $result,
485 pht('%s with user %s should succeed.', $description, $spec));
486 } else {
487 $this->assertTrue(
488 $caught instanceof PhabricatorPolicyException,
489 pht('%s with user %s should fail.', $description, $spec));
490 }
491 }
492 }
493
494
495 /**
496 * Build a test object to spec.
497 */
498 private function buildObject($policy) {
499 $object = new PhabricatorPolicyTestObject();
500 $object->setCapabilities(
501 array(
502 PhabricatorPolicyCapability::CAN_VIEW,
503 PhabricatorPolicyCapability::CAN_EDIT,
504 ));
505 $object->setPolicies(
506 array(
507 PhabricatorPolicyCapability::CAN_VIEW => $policy,
508 PhabricatorPolicyCapability::CAN_EDIT => $policy,
509 ));
510
511 return $object;
512 }
513
514
515 /**
516 * Build a test user to spec.
517 */
518 private function buildUser($spec) {
519 $user = new PhabricatorUser();
520
521 switch ($spec) {
522 case 'public':
523 break;
524 case 'user':
525 $user->setPHID(1);
526 break;
527 case 'admin':
528 $user->setPHID(1);
529 $user->setIsAdmin(true);
530 break;
531 default:
532 throw new Exception(pht("Unknown user spec '%s'.", $spec));
533 }
534
535 return $user;
536 }
537
538}