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