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()
11 class CRM_ACL_ListTest
extends CiviUnitTestCase
{
16 public function setUp() {
18 // $this->quickCleanup(array('civicrm_acl_contact_cache'), TRUE);
19 $this->useTransaction(TRUE);
20 $this->allowedContactsACL
= [];
24 * general test for the 'view all contacts' permission
26 public function testViewAllPermission() {
27 // create test contacts
28 $contacts = $this->createScenarioPlain();
30 // test WITH all permissions
31 // NULL means 'all permissions' in UnitTests environment
32 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= NULL;
33 $result = CRM_Contact_BAO_Contact_Permission
::allowList($contacts);
35 $this->assertEquals($result, $contacts, "Contacts should be viewable when 'view all contacts'");
37 // test WITH explicit permission
38 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['view all contacts'];
39 $result = CRM_Contact_BAO_Contact_Permission
::allowList($contacts, CRM_Core_Permission
::VIEW
);
41 $this->assertEquals($result, $contacts, "Contacts should be viewable when 'view all contacts'");
43 // test WITH EDIT permissions (should imply VIEW)
44 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['edit all contacts'];
45 $result = CRM_Contact_BAO_Contact_Permission
::allowList($contacts, CRM_Core_Permission
::VIEW
);
47 $this->assertEquals($result, $contacts, "Contacts should be viewable when 'edit all contacts'");
49 // test WITHOUT permission
50 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= [];
51 $result = CRM_Contact_BAO_Contact_Permission
::allowList($contacts);
53 $this->assertEmpty($result, "Contacts should NOT be viewable when 'view all contacts' is not set");
57 * general test for the 'view all contacts' permission
59 public function testEditAllPermission() {
60 // create test contacts
61 $contacts = $this->createScenarioPlain();
63 // test WITH explicit permission
64 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['edit all contacts'];
65 $result = CRM_Contact_BAO_Contact_Permission
::allowList($contacts, CRM_Core_Permission
::EDIT
);
67 $this->assertEquals($result, $contacts, "Contacts should be viewable when 'edit all contacts'");
69 // test WITHOUT permission
70 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= [];
71 $result = CRM_Contact_BAO_Contact_Permission
::allowList($contacts);
73 $this->assertEmpty($result, "Contacts should NOT be viewable when 'edit all contacts' is not set");
77 * Test access related to the 'access deleted contact' permission
79 public function testViewEditDeleted() {
80 // create test contacts
81 $contacts = $this->createScenarioPlain();
84 $deleted_contact_id = $contacts[2];
85 $this->callAPISuccess('Contact', 'create', ['id' => $deleted_contact_id, 'contact_is_deleted' => 1]);
86 $deleted_contact = $this->callAPISuccess('Contact', 'getsingle', ['id' => $deleted_contact_id]);
87 $this->assertEquals($deleted_contact['contact_is_deleted'], 1, "Contact should've been deleted");
89 // test WITH explicit permission
90 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['edit all contacts', 'view all contacts'];
91 $result = CRM_Contact_BAO_Contact_Permission
::allowList($contacts, CRM_Core_Permission
::EDIT
);
93 $this->assertNotContains($deleted_contact_id, $result, "Deleted contacts should be excluded");
94 $this->assertEquals(count($result), count($contacts) - 1, "Only deleted contacts should be excluded");
98 * Test access based on relations
100 * There should be the following permission-relationship
101 * contact[0] -> contact[1] -> contact[2]
103 public function testPermissionByRelation() {
104 // create test scenario
105 $contacts = $this->createScenarioRelations();
107 // remove all permissions
108 $config = CRM_Core_Config
::singleton();
109 $config->userPermissionClass
->permissions
= [];
110 $permissions_to_check = [CRM_Core_Permission
::VIEW
=> 'View', CRM_Core_Permission
::EDIT
=> 'Edit'];
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);
119 $this->assertNotContains($contacts[0], $result, "User[0] should NOT have $permission_label permission on contact[0].");
120 $this->assertContains($contacts[1], $result, "User[0] should have $permission_label permission on contact[1].");
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].");
125 if ($permission == CRM_Core_Permission
::VIEW
) {
126 $this->assertContains($contacts[5], $result, "User[0] should have $permission_label permission on contact[5].");
129 $this->assertNotContains($contacts[5], $result, "User[0] should NOT have $permission_label permission on contact[5].");
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].");
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].");
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);
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
148 $this->assertContains($contacts[2], $result, "User[0] should have second degree $permission_label permission on contact[2].");
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].");
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].");
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].");
165 $this->assertNotContains($contacts[7], $result, "User[0] should NOT have second degree $permission_label permission on contact[7].");
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].");
173 $this->assertNotContains($contacts[9], $result, "User[0] should NOT have second degree $permission_label permission on contact[9].");
179 * Test access based on ACL
181 public function testPermissionByACL() {
182 $contacts = $this->createScenarioPlain();
185 $this->hookClass
->setHook('civicrm_aclWhereClause', [$this, 'hook_civicrm_aclWhereClause']);
188 $permissions_to_check = [CRM_Core_Permission
::VIEW
=> 'View', CRM_Core_Permission
::EDIT
=> 'Edit'];
190 $this->allowedContactsACL
= [$contacts[0], $contacts[1], $contacts[4]];
191 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= [];
192 $result = CRM_Contact_BAO_Contact_Permission
::allowList($contacts);
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].");
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].");
199 $this->assertContains($contacts[4], $result, "User[0] should NOT have an ACL permission on contact[4].");
203 * Test access with a mix of ACL and relationship
205 public function testPermissionACLvsRelationship() {
206 $contacts = $this->createScenarioRelations();
209 $this->hookClass
->setHook('civicrm_aclWhereClause', [$this, 'hook_civicrm_aclWhereClause']);
211 $config = CRM_Core_Config
::singleton();
212 $config->userPermissionClass
->permissions
= [];
213 $config->secondDegRelPermissions
= TRUE;
215 $this->allowedContactsACL
= [$contacts[0], $contacts[1], $contacts[4]];
216 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= [];
217 $result = CRM_Contact_BAO_Contact_Permission
::allowList($contacts);
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].");
223 $this->assertNotContains($contacts[3], $result, "User[0] should NOT have an ACL permission on contact[3].");
224 $this->assertContains($contacts[4], $result, "User[0] should have an ACL permission on contact[4].");
228 * Test access related to the 'access deleted contact' permission
230 public function testPermissionCompare() {
231 $contacts = $this->createScenarioRelations();
232 $contact_index = array_flip($contacts);
235 $this->hookClass
->setHook('civicrm_aclWhereClause', [$this, 'hook_civicrm_aclWhereClause']);
237 $config = CRM_Core_Config
::singleton();
238 $this->allowedContactsACL
= [$contacts[0], $contacts[1], $contacts[4]];
239 $config->secondDegRelPermissions
= TRUE;
241 // test configurations
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']];
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;
250 if (is_array($user_permissions) && count($user_permissions) == 0) {
251 // slight (explainable) deviation on the own contact
252 unset($contact_range[0]);
255 $config->userPermissionClass
->permissions
= $user_permissions;
256 $user_permissions_label = json_encode($user_permissions);
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.");
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);
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}'");
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}'");
289 * create plain test scenario, no relationships/ACLs
291 protected function createScenarioPlain() {
292 // get logged in user
293 $user_id = $this->createLoggedInUser();
294 $this->assertNotEmpty($user_id);
296 // create test contacts
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']);
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];
313 * create plain test scenario, no relationships/ACLs
315 protected function createScenarioRelations() {
316 $contacts = $this->createScenarioPlain();
318 // create some relationships
319 $this->callAPISuccess('Relationship', 'create', [
321 'relationship_type_id' => 1,
322 'contact_id_a' => $contacts[1],
323 'contact_id_b' => $contacts[0],
324 'is_permission_b_a' => 1,
328 $this->callAPISuccess('Relationship', 'create', [
330 'relationship_type_id' => 1,
331 'contact_id_a' => $contacts[2],
332 'contact_id_b' => $contacts[1],
333 'is_permission_b_a' => 1,
337 $this->callAPISuccess('Relationship', 'create', [
339 'relationship_type_id' => 1,
340 'contact_id_a' => $contacts[4],
341 'contact_id_b' => $contacts[2],
342 'is_permission_b_a' => 1,
346 $this->callAPISuccess('Relationship', 'create', [
348 'relationship_type_id' => 4,
349 'contact_id_a' => $contacts[5],
350 'contact_id_b' => $contacts[0],
352 'is_permission_b_a' => 2,
356 $this->callAPISuccess('Relationship', 'create', [
358 'relationship_type_id' => 1,
359 'contact_id_a' => $contacts[6],
360 'contact_id_b' => $contacts[5],
362 'is_permission_b_a' => 1,
366 $this->callAPISuccess('Relationship', 'create', [
368 'relationship_type_id' => 1,
369 'contact_id_a' => $contacts[7],
370 'contact_id_b' => $contacts[5],
372 'is_permission_b_a' => 2,
376 $this->callAPISuccess('Relationship', 'create', [
378 'relationship_type_id' => 4,
379 'contact_id_a' => $contacts[0],
380 'contact_id_b' => $contacts[8],
382 'is_permission_a_b' => 1,
386 $this->callAPISuccess('Relationship', 'create', [
388 'relationship_type_id' => 1,
389 'contact_id_a' => $contacts[9],
390 'contact_id_b' => $contacts[8],
392 'is_permission_b_a' => 2,
400 * ACL HOOK implementation for various tests
402 public function hook_civicrm_aclWhereClause($type, &$tables, &$whereTables, &$contactID, &$where) {
403 if (!empty($this->allowedContactsACL
)) {
404 $contact_id_list = implode(',', $this->allowedContactsACL
);
405 $where = " contact_a.id IN ($contact_id_list)";