Merge in 5.16
[civicrm-core.git] / tests / phpunit / CRM / ACL / ListTest.php
CommitLineData
ea8011f6 1<?php
2
3/**
4 * Class CRM_ACL_Test
5 *
6 * This test focuses on testing the (new) ID list-based functions:
7 * CRM_Contact_BAO_Contact_Permission::allowList()
8 * CRM_Contact_BAO_Contact_Permission::relationshipList()
9 * @group headless
10 */
11class CRM_ACL_ListTest extends CiviUnitTestCase {
12
13 /**
14 * Set up function.
15 */
16 public function setUp() {
17 parent::setUp();
3c645834 18 // $this->quickCleanup(array('civicrm_acl_contact_cache'), TRUE);
ea8011f6 19 $this->useTransaction(TRUE);
9099cab3 20 $this->allowedContactsACL = [];
ea8011f6 21 }
22
23 /**
24 * general test for the 'view all contacts' permission
25 */
26 public function testViewAllPermission() {
e4541c56 27 // create test contacts
134b2b64 28 $contacts = $this->createScenarioPlain();
ea8011f6 29
134b2b64 30 // test WITH all permissions
39b959db
SL
31 // NULL means 'all permissions' in UnitTests environment
32 CRM_Core_Config::singleton()->userPermissionClass->permissions = NULL;
ea8011f6 33 $result = CRM_Contact_BAO_Contact_Permission::allowList($contacts);
134b2b64 34 sort($result);
35 $this->assertEquals($result, $contacts, "Contacts should be viewable when 'view all contacts'");
ea8011f6 36
ea8011f6 37 // test WITH explicit permission
9099cab3 38 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['view all contacts'];
ea8011f6 39 $result = CRM_Contact_BAO_Contact_Permission::allowList($contacts, CRM_Core_Permission::VIEW);
134b2b64 40 sort($result);
41 $this->assertEquals($result, $contacts, "Contacts should be viewable when 'view all contacts'");
ea8011f6 42
340be2e7 43 // test WITH EDIT permissions (should imply VIEW)
9099cab3 44 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['edit all contacts'];
340be2e7 45 $result = CRM_Contact_BAO_Contact_Permission::allowList($contacts, CRM_Core_Permission::VIEW);
46 sort($result);
47 $this->assertEquals($result, $contacts, "Contacts should be viewable when 'edit all contacts'");
48
ea8011f6 49 // test WITHOUT permission
9099cab3 50 CRM_Core_Config::singleton()->userPermissionClass->permissions = [];
ea8011f6 51 $result = CRM_Contact_BAO_Contact_Permission::allowList($contacts);
134b2b64 52 sort($result);
ea8011f6 53 $this->assertEmpty($result, "Contacts should NOT be viewable when 'view all contacts' is not set");
54 }
55
ea8011f6 56 /**
57 * general test for the 'view all contacts' permission
58 */
59 public function testEditAllPermission() {
60 // create test contacts
134b2b64 61 $contacts = $this->createScenarioPlain();
ea8011f6 62
63 // test WITH explicit permission
9099cab3 64 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['edit all contacts'];
ea8011f6 65 $result = CRM_Contact_BAO_Contact_Permission::allowList($contacts, CRM_Core_Permission::EDIT);
134b2b64 66 sort($result);
67 $this->assertEquals($result, $contacts, "Contacts should be viewable when 'edit all contacts'");
ea8011f6 68
ea8011f6 69 // test WITHOUT permission
9099cab3 70 CRM_Core_Config::singleton()->userPermissionClass->permissions = [];
ea8011f6 71 $result = CRM_Contact_BAO_Contact_Permission::allowList($contacts);
134b2b64 72 sort($result);
ea8011f6 73 $this->assertEmpty($result, "Contacts should NOT be viewable when 'edit all contacts' is not set");
74 }
75
ea8011f6 76 /**
134b2b64 77 * Test access related to the 'access deleted contact' permission
ea8011f6 78 */
79 public function testViewEditDeleted() {
134b2b64 80 // create test contacts
81 $contacts = $this->createScenarioPlain();
82
83 // delete one contact
84 $deleted_contact_id = $contacts[2];
9099cab3
CW
85 $this->callAPISuccess('Contact', 'create', ['id' => $deleted_contact_id, 'contact_is_deleted' => 1]);
86 $deleted_contact = $this->callAPISuccess('Contact', 'getsingle', ['id' => $deleted_contact_id]);
134b2b64 87 $this->assertEquals($deleted_contact['contact_is_deleted'], 1, "Contact should've been deleted");
88
89 // test WITH explicit permission
9099cab3 90 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['edit all contacts', 'view all contacts'];
134b2b64 91 $result = CRM_Contact_BAO_Contact_Permission::allowList($contacts, CRM_Core_Permission::EDIT);
92 sort($result);
93 $this->assertNotContains($deleted_contact_id, $result, "Deleted contacts should be excluded");
e4541c56 94 $this->assertEquals(count($result), count($contacts) - 1, "Only deleted contacts should be excluded");
134b2b64 95 }
96
134b2b64 97 /**
c1ebd31f 98 * Test access based on relations
e4541c56 99 *
134b2b64 100 * There should be the following permission-relationship
101 * contact[0] -> contact[1] -> contact[2]
102 */
103 public function testPermissionByRelation() {
104 // create test scenario
c1ebd31f 105 $contacts = $this->createScenarioRelations();
134b2b64 106
107 // remove all permissions
108 $config = CRM_Core_Config::singleton();
9099cab3
CW
109 $config->userPermissionClass->permissions = [];
110 $permissions_to_check = [CRM_Core_Permission::VIEW => 'View', CRM_Core_Permission::EDIT => 'Edit'];
134b2b64 111
112 // run this for SIMPLE relations
113 $config->secondDegRelPermissions = FALSE;
114 $this->assertFalse($config->secondDegRelPermissions);
115 foreach ($permissions_to_check as $permission => $permission_label) {
116 $result = CRM_Contact_BAO_Contact_Permission::allowList($contacts, $permission);
117 sort($result);
118
c1ebd31f 119 $this->assertNotContains($contacts[0], $result, "User[0] should NOT have $permission_label permission on contact[0].");
e4541c56 120 $this->assertContains($contacts[1], $result, "User[0] should have $permission_label permission on contact[1].");
c1ebd31f 121 $this->assertNotContains($contacts[2], $result, "User[0] should NOT have $permission_label permission on contact[2].");
122 $this->assertNotContains($contacts[3], $result, "User[0] should NOT have $permission_label permission on contact[3].");
123 $this->assertNotContains($contacts[4], $result, "User[0] should NOT have $permission_label permission on contact[4].");
aade61cd
AS
124 // view (b_a)
125 if ($permission == CRM_Core_Permission::VIEW) {
126 $this->assertContains($contacts[5], $result, "User[0] should have $permission_label permission on contact[5].");
127 }
128 else {
129 $this->assertNotContains($contacts[5], $result, "User[0] should NOT have $permission_label permission on contact[5].");
130 }
131 $this->assertNotContains($contacts[6], $result, "User[0] should NOT have $permission_label permission on contact[6].");
132 $this->assertNotContains($contacts[7], $result, "User[0] should NOT have $permission_label permission on contact[7].");
133 // edit (a_b)
134 $this->assertContains($contacts[8], $result, "User[0] should have $permission_label permission on contact[8].");
135 $this->assertNotContains($contacts[9], $result, "User[0] should NOT have $permission_label permission on contact[9].");
134b2b64 136 }
e4541c56 137
134b2b64 138 // run this for SECOND DEGREE relations
139 $config->secondDegRelPermissions = TRUE;
140 $this->assertTrue($config->secondDegRelPermissions);
141 foreach ($permissions_to_check as $permission => $permission_label) {
142 $result = CRM_Contact_BAO_Contact_Permission::allowList($contacts, $permission);
143 sort($result);
144
aade61cd
AS
145 $this->assertNotContains($contacts[0], $result, "User[0] should NOT have second degree $permission_label permission on contact[0].");
146 $this->assertContains($contacts[1], $result, "User[0] should have second degree $permission_label permission on contact[1].");
147 // Edit then edit -> edit
e4541c56 148 $this->assertContains($contacts[2], $result, "User[0] should have second degree $permission_label permission on contact[2].");
aade61cd
AS
149 $this->assertNotContains($contacts[3], $result, "User[0] should NOT have second degree $permission_label permission on contact[3].");
150 $this->assertNotContains($contacts[4], $result, "User[0] should NOT have second degree $permission_label permission on contact[4].");
151 // View then Edit -> View
152 if ($permission == CRM_Core_Permission::VIEW) {
153 $this->assertContains($contacts[5], $result, "User[0] should have second degree $permission_label permission on contact[5].");
154 $this->assertContains($contacts[6], $result, "User[0] should have second degree $permission_label permission on contact[6].");
155 }
156 else {
157 $this->assertNotContains($contacts[5], $result, "User[0] should NOT have second degree $permission_label permission on contact[5].");
158 $this->assertNotContains($contacts[6], $result, "User[0] should NOT have second degree $permission_label permission on contact[6].");
159 }
160 // View then Edit -> View
161 if ($permission == CRM_Core_Permission::VIEW) {
162 $this->assertContains($contacts[7], $result, "User[0] should have second degree $permission_label permission on contact[7].");
163 }
164 else {
165 $this->assertNotContains($contacts[7], $result, "User[0] should NOT have second degree $permission_label permission on contact[7].");
166 }
167 // Edit then View -> View
168 $this->assertContains($contacts[8], $result, "User[0] should have second degree $permission_label permission on contact[8].");
169 if ($permission == CRM_Core_Permission::VIEW) {
170 $this->assertContains($contacts[9], $result, "User[0] should have second degree $permission_label permission on contact[9].");
171 }
172 else {
173 $this->assertNotContains($contacts[9], $result, "User[0] should NOT have second degree $permission_label permission on contact[9].");
174 }
134b2b64 175 }
ea8011f6 176 }
177
134b2b64 178 /**
c1ebd31f 179 * Test access based on ACL
134b2b64 180 */
c1ebd31f 181 public function testPermissionByACL() {
182 $contacts = $this->createScenarioPlain();
183
184 // set custom hook
9099cab3 185 $this->hookClass->setHook('civicrm_aclWhereClause', [$this, 'hook_civicrm_aclWhereClause']);
c1ebd31f 186
187 // run simple test
9099cab3 188 $permissions_to_check = [CRM_Core_Permission::VIEW => 'View', CRM_Core_Permission::EDIT => 'Edit'];
c1ebd31f 189
9099cab3
CW
190 $this->allowedContactsACL = [$contacts[0], $contacts[1], $contacts[4]];
191 CRM_Core_Config::singleton()->userPermissionClass->permissions = [];
c1ebd31f 192 $result = CRM_Contact_BAO_Contact_Permission::allowList($contacts);
193 sort($result);
194
e4541c56 195 $this->assertContains($contacts[0], $result, "User[0] should NOT have an ACL permission on contact[0].");
196 $this->assertContains($contacts[1], $result, "User[0] should have an ACL permission on contact[1].");
c1ebd31f 197 $this->assertNotContains($contacts[2], $result, "User[0] should NOT have an ACL permission on contact[2].");
198 $this->assertNotContains($contacts[3], $result, "User[0] should NOT have an RELATION permission on contact[3].");
e4541c56 199 $this->assertContains($contacts[4], $result, "User[0] should NOT have an ACL permission on contact[4].");
134b2b64 200 }
ea8011f6 201
134b2b64 202 /**
c1ebd31f 203 * Test access with a mix of ACL and relationship
134b2b64 204 */
c1ebd31f 205 public function testPermissionACLvsRelationship() {
206 $contacts = $this->createScenarioRelations();
207
208 // set custom hook
9099cab3 209 $this->hookClass->setHook('civicrm_aclWhereClause', [$this, 'hook_civicrm_aclWhereClause']);
c1ebd31f 210
211 $config = CRM_Core_Config::singleton();
9099cab3 212 $config->userPermissionClass->permissions = [];
c1ebd31f 213 $config->secondDegRelPermissions = TRUE;
214
9099cab3
CW
215 $this->allowedContactsACL = [$contacts[0], $contacts[1], $contacts[4]];
216 CRM_Core_Config::singleton()->userPermissionClass->permissions = [];
c1ebd31f 217 $result = CRM_Contact_BAO_Contact_Permission::allowList($contacts);
218 sort($result);
219
e4541c56 220 $this->assertContains($contacts[0], $result, "User[0] should have an ACL permission on contact[0].");
221 $this->assertContains($contacts[1], $result, "User[0] should have an ACL permission on contact[1].");
222 $this->assertContains($contacts[2], $result, "User[0] should have second degree an relation permission on contact[2].");
c1ebd31f 223 $this->assertNotContains($contacts[3], $result, "User[0] should NOT have an ACL permission on contact[3].");
e4541c56 224 $this->assertContains($contacts[4], $result, "User[0] should have an ACL permission on contact[4].");
134b2b64 225 }
ea8011f6 226
134b2b64 227 /**
228 * Test access related to the 'access deleted contact' permission
229 */
3c645834 230 public function testPermissionCompare() {
231 $contacts = $this->createScenarioRelations();
232 $contact_index = array_flip($contacts);
233
234 // set custom hook
9099cab3 235 $this->hookClass->setHook('civicrm_aclWhereClause', [$this, 'hook_civicrm_aclWhereClause']);
3c645834 236
237 $config = CRM_Core_Config::singleton();
9099cab3 238 $this->allowedContactsACL = [$contacts[0], $contacts[1], $contacts[4]];
3c645834 239 $config->secondDegRelPermissions = TRUE;
240
241 // test configurations
9099cab3
CW
242 $permissions_to_check = [CRM_Core_Permission::VIEW => 'View', CRM_Core_Permission::EDIT => 'Edit'];
243 $user_permission_options = [/*ALL*/ NULL, /*NONE*/ [], ['view all contacts'], ['edit all contacts'], ['view all contacts', 'edit all contacts']];
3c645834 244
245 // run all combinations of those
246 foreach ($permissions_to_check as $permission_to_check => $permission_label) {
247 foreach ($user_permission_options as $user_permissions) {
248 // select the contact range
249 $contact_range = $contacts;
e4541c56 250 if (is_array($user_permissions) && count($user_permissions) == 0) {
3c645834 251 // slight (explainable) deviation on the own contact
252 unset($contact_range[0]);
253 }
254
255 $config->userPermissionClass->permissions = $user_permissions;
256 $user_permissions_label = json_encode($user_permissions);
257
258 // get the list result
259 $list_result = CRM_Contact_BAO_Contact_Permission::allowList($contact_range, $permission_to_check);
260 $this->assertTrue(count($list_result) <= count($contact_range), "Permission::allowList should return a subset of the contats.");
261 foreach ($list_result as $contact_id) {
262 $this->assertContains($contact_id, $contact_range, "Permission::allowList should return a subset of the contats.");
263 }
264
265 // now compare the results
266 foreach ($contact_range as $contact_id) {
267 $individual_result = CRM_Contact_BAO_Contact_Permission::allow($contact_id, $permission_to_check);
268
269 if (in_array($contact_id, $list_result)) {
270 // listPermission reports PERMISSION GRANTED
271 $this->assertTrue($individual_result, "Permission::allow denies {$permission_label} access for contact[{$contact_index[$contact_id]}], while Permission::allowList grants it. User permission: '{$user_permissions_label}'");
272
e4541c56 273 }
274 else {
3c645834 275 // listPermission reports PERMISSION DENIED
276 $this->assertFalse($individual_result, "Permission::allow grantes {$permission_label} access for contact[{$contact_index[$contact_id]}], while Permission::allowList denies it. User permission: '{$user_permissions_label}'");
277
278 }
279 }
280 }
281 }
134b2b64 282 }
ea8011f6 283
39b959db
SL
284 /*
285 * Scenario Builders
286 */
ea8011f6 287
288 /**
134b2b64 289 * create plain test scenario, no relationships/ACLs
ea8011f6 290 */
134b2b64 291 protected function createScenarioPlain() {
ea8011f6 292 // get logged in user
293 $user_id = $this->createLoggedInUser();
294 $this->assertNotEmpty($user_id);
295
296 // create test contacts
9099cab3
CW
297 $bush_sr_id = $this->individualCreate(['first_name' => 'George', 'middle_name' => 'H. W.', 'last_name' => 'Bush']);
298 $bush_jr_id = $this->individualCreate(['first_name' => 'George', 'middle_name' => 'W.', 'last_name' => 'Bush']);
299 $bush_laura_id = $this->individualCreate(['first_name' => 'Laura Lane', 'last_name' => 'Bush']);
300 $bush_brbra_id = $this->individualCreate(['first_name' => 'Barbara', 'last_name' => 'Bush']);
301 $bush_brother_id = $this->individualCreate(['first_name' => 'Brother', 'last_name' => 'Bush']);
302 $bush_nephew_id = $this->individualCreate(['first_name' => 'Nephew', 'last_name' => 'Bush']);
303 $bush_nephew2_id = $this->individualCreate(['first_name' => 'Nephew2', 'last_name' => 'Bush']);
304 $bush_otherbro_id = $this->individualCreate(['first_name' => 'Other Brother', 'last_name' => 'Bush']);
305 $bush_otherneph_id = $this->individualCreate(['first_name' => 'Other Nephew', 'last_name' => 'Bush']);
306
307 $contacts = [$user_id, $bush_sr_id, $bush_jr_id, $bush_laura_id, $bush_brbra_id, $bush_brother_id, $bush_nephew_id, $bush_nephew2_id, $bush_otherbro_id, $bush_otherneph_id];
134b2b64 308 sort($contacts);
309 return $contacts;
310 }
311
312 /**
313 * create plain test scenario, no relationships/ACLs
314 */
c1ebd31f 315 protected function createScenarioRelations() {
134b2b64 316 $contacts = $this->createScenarioPlain();
317
ea8011f6 318 // create some relationships
9099cab3 319 $this->callAPISuccess('Relationship', 'create', [
39b959db
SL
320 // CHILD OF
321 'relationship_type_id' => 1,
134b2b64 322 'contact_id_a' => $contacts[1],
323 'contact_id_b' => $contacts[0],
324 'is_permission_b_a' => 1,
325 'is_active' => 1,
9099cab3 326 ]);
ea8011f6 327
9099cab3 328 $this->callAPISuccess('Relationship', 'create', [
39b959db
SL
329 // CHILD OF
330 'relationship_type_id' => 1,
134b2b64 331 'contact_id_a' => $contacts[2],
332 'contact_id_b' => $contacts[1],
333 'is_permission_b_a' => 1,
334 'is_active' => 1,
9099cab3 335 ]);
ea8011f6 336
9099cab3 337 $this->callAPISuccess('Relationship', 'create', [
39b959db
SL
338 // CHILD OF
339 'relationship_type_id' => 1,
134b2b64 340 'contact_id_a' => $contacts[4],
341 'contact_id_b' => $contacts[2],
342 'is_permission_b_a' => 1,
343 'is_active' => 1,
9099cab3 344 ]);
ea8011f6 345
9099cab3 346 $this->callAPISuccess('Relationship', 'create', [
39b959db
SL
347 // SIBLING OF
348 'relationship_type_id' => 4,
aade61cd
AS
349 'contact_id_a' => $contacts[5],
350 'contact_id_b' => $contacts[0],
39b959db
SL
351 // View
352 'is_permission_b_a' => 2,
aade61cd 353 'is_active' => 1,
9099cab3 354 ]);
aade61cd 355
9099cab3 356 $this->callAPISuccess('Relationship', 'create', [
39b959db
SL
357 // CHILD OF
358 'relationship_type_id' => 1,
aade61cd
AS
359 'contact_id_a' => $contacts[6],
360 'contact_id_b' => $contacts[5],
39b959db
SL
361 // Edit
362 'is_permission_b_a' => 1,
aade61cd 363 'is_active' => 1,
9099cab3 364 ]);
aade61cd 365
9099cab3 366 $this->callAPISuccess('Relationship', 'create', [
39b959db
SL
367 // CHILD OF
368 'relationship_type_id' => 1,
aade61cd
AS
369 'contact_id_a' => $contacts[7],
370 'contact_id_b' => $contacts[5],
39b959db
SL
371 // View
372 'is_permission_b_a' => 2,
aade61cd 373 'is_active' => 1,
9099cab3 374 ]);
aade61cd 375
9099cab3 376 $this->callAPISuccess('Relationship', 'create', [
39b959db
SL
377 // SIBLING OF
378 'relationship_type_id' => 4,
aade61cd
AS
379 'contact_id_a' => $contacts[0],
380 'contact_id_b' => $contacts[8],
39b959db
SL
381 // edit (as a_b)
382 'is_permission_a_b' => 1,
aade61cd 383 'is_active' => 1,
9099cab3 384 ]);
aade61cd 385
9099cab3 386 $this->callAPISuccess('Relationship', 'create', [
39b959db
SL
387 // CHILD OF
388 'relationship_type_id' => 1,
aade61cd
AS
389 'contact_id_a' => $contacts[9],
390 'contact_id_b' => $contacts[8],
39b959db
SL
391 // view
392 'is_permission_b_a' => 2,
aade61cd 393 'is_active' => 1,
9099cab3 394 ]);
aade61cd 395
134b2b64 396 return $contacts;
ea8011f6 397 }
c1ebd31f 398
e4541c56 399 /**
400 * ACL HOOK implementation for various tests
c1ebd31f 401 */
e4541c56 402 public function hook_civicrm_aclWhereClause($type, &$tables, &$whereTables, &$contactID, &$where) {
c1ebd31f 403 if (!empty($this->allowedContactsACL)) {
404 $contact_id_list = implode(',', $this->allowedContactsACL);
405 $where = " contact_a.id IN ($contact_id_list)";
406 }
407 }
e4541c56 408
ea8011f6 409}