Merge pull request #14320 from sushantpaste/reporthook
[civicrm-core.git] / tests / phpunit / CRM / ACL / ListTest.php
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();
18 // $this->quickCleanup(array('civicrm_acl_contact_cache'), TRUE);
19 $this->useTransaction(TRUE);
20 $this->allowedContactsACL = [];
21 }
22
23 /**
24 * general test for the 'view all contacts' permission
25 */
26 public function testViewAllPermission() {
27 // create test contacts
28 $contacts = $this->createScenarioPlain();
29
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);
34 sort($result);
35 $this->assertEquals($result, $contacts, "Contacts should be viewable when 'view all contacts'");
36
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);
40 sort($result);
41 $this->assertEquals($result, $contacts, "Contacts should be viewable when 'view all contacts'");
42
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);
46 sort($result);
47 $this->assertEquals($result, $contacts, "Contacts should be viewable when 'edit all contacts'");
48
49 // test WITHOUT permission
50 CRM_Core_Config::singleton()->userPermissionClass->permissions = [];
51 $result = CRM_Contact_BAO_Contact_Permission::allowList($contacts);
52 sort($result);
53 $this->assertEmpty($result, "Contacts should NOT be viewable when 'view all contacts' is not set");
54 }
55
56 /**
57 * general test for the 'view all contacts' permission
58 */
59 public function testEditAllPermission() {
60 // create test contacts
61 $contacts = $this->createScenarioPlain();
62
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);
66 sort($result);
67 $this->assertEquals($result, $contacts, "Contacts should be viewable when 'edit all contacts'");
68
69 // test WITHOUT permission
70 CRM_Core_Config::singleton()->userPermissionClass->permissions = [];
71 $result = CRM_Contact_BAO_Contact_Permission::allowList($contacts);
72 sort($result);
73 $this->assertEmpty($result, "Contacts should NOT be viewable when 'edit all contacts' is not set");
74 }
75
76 /**
77 * Test access related to the 'access deleted contact' permission
78 */
79 public function testViewEditDeleted() {
80 // create test contacts
81 $contacts = $this->createScenarioPlain();
82
83 // delete one contact
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");
88
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);
92 sort($result);
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");
95 }
96
97 /**
98 * Test access based on relations
99 *
100 * There should be the following permission-relationship
101 * contact[0] -> contact[1] -> contact[2]
102 */
103 public function testPermissionByRelation() {
104 // create test scenario
105 $contacts = $this->createScenarioRelations();
106
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'];
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
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].");
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].");
136 }
137
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
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].");
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 }
175 }
176 }
177
178 /**
179 * Test access based on ACL
180 */
181 public function testPermissionByACL() {
182 $contacts = $this->createScenarioPlain();
183
184 // set custom hook
185 $this->hookClass->setHook('civicrm_aclWhereClause', [$this, 'hook_civicrm_aclWhereClause']);
186
187 // run simple test
188 $permissions_to_check = [CRM_Core_Permission::VIEW => 'View', CRM_Core_Permission::EDIT => 'Edit'];
189
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);
193 sort($result);
194
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].");
200 }
201
202 /**
203 * Test access with a mix of ACL and relationship
204 */
205 public function testPermissionACLvsRelationship() {
206 $contacts = $this->createScenarioRelations();
207
208 // set custom hook
209 $this->hookClass->setHook('civicrm_aclWhereClause', [$this, 'hook_civicrm_aclWhereClause']);
210
211 $config = CRM_Core_Config::singleton();
212 $config->userPermissionClass->permissions = [];
213 $config->secondDegRelPermissions = TRUE;
214
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);
218 sort($result);
219
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].");
225 }
226
227 /**
228 * Test access related to the 'access deleted contact' permission
229 */
230 public function testPermissionCompare() {
231 $contacts = $this->createScenarioRelations();
232 $contact_index = array_flip($contacts);
233
234 // set custom hook
235 $this->hookClass->setHook('civicrm_aclWhereClause', [$this, 'hook_civicrm_aclWhereClause']);
236
237 $config = CRM_Core_Config::singleton();
238 $this->allowedContactsACL = [$contacts[0], $contacts[1], $contacts[4]];
239 $config->secondDegRelPermissions = TRUE;
240
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']];
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;
250 if (is_array($user_permissions) && count($user_permissions) == 0) {
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
273 }
274 else {
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 }
282 }
283
284 /*
285 * Scenario Builders
286 */
287
288 /**
289 * create plain test scenario, no relationships/ACLs
290 */
291 protected function createScenarioPlain() {
292 // get logged in user
293 $user_id = $this->createLoggedInUser();
294 $this->assertNotEmpty($user_id);
295
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']);
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];
308 sort($contacts);
309 return $contacts;
310 }
311
312 /**
313 * create plain test scenario, no relationships/ACLs
314 */
315 protected function createScenarioRelations() {
316 $contacts = $this->createScenarioPlain();
317
318 // create some relationships
319 $this->callAPISuccess('Relationship', 'create', [
320 // CHILD OF
321 'relationship_type_id' => 1,
322 'contact_id_a' => $contacts[1],
323 'contact_id_b' => $contacts[0],
324 'is_permission_b_a' => 1,
325 'is_active' => 1,
326 ]);
327
328 $this->callAPISuccess('Relationship', 'create', [
329 // CHILD OF
330 'relationship_type_id' => 1,
331 'contact_id_a' => $contacts[2],
332 'contact_id_b' => $contacts[1],
333 'is_permission_b_a' => 1,
334 'is_active' => 1,
335 ]);
336
337 $this->callAPISuccess('Relationship', 'create', [
338 // CHILD OF
339 'relationship_type_id' => 1,
340 'contact_id_a' => $contacts[4],
341 'contact_id_b' => $contacts[2],
342 'is_permission_b_a' => 1,
343 'is_active' => 1,
344 ]);
345
346 $this->callAPISuccess('Relationship', 'create', [
347 // SIBLING OF
348 'relationship_type_id' => 4,
349 'contact_id_a' => $contacts[5],
350 'contact_id_b' => $contacts[0],
351 // View
352 'is_permission_b_a' => 2,
353 'is_active' => 1,
354 ]);
355
356 $this->callAPISuccess('Relationship', 'create', [
357 // CHILD OF
358 'relationship_type_id' => 1,
359 'contact_id_a' => $contacts[6],
360 'contact_id_b' => $contacts[5],
361 // Edit
362 'is_permission_b_a' => 1,
363 'is_active' => 1,
364 ]);
365
366 $this->callAPISuccess('Relationship', 'create', [
367 // CHILD OF
368 'relationship_type_id' => 1,
369 'contact_id_a' => $contacts[7],
370 'contact_id_b' => $contacts[5],
371 // View
372 'is_permission_b_a' => 2,
373 'is_active' => 1,
374 ]);
375
376 $this->callAPISuccess('Relationship', 'create', [
377 // SIBLING OF
378 'relationship_type_id' => 4,
379 'contact_id_a' => $contacts[0],
380 'contact_id_b' => $contacts[8],
381 // edit (as a_b)
382 'is_permission_a_b' => 1,
383 'is_active' => 1,
384 ]);
385
386 $this->callAPISuccess('Relationship', 'create', [
387 // CHILD OF
388 'relationship_type_id' => 1,
389 'contact_id_a' => $contacts[9],
390 'contact_id_b' => $contacts[8],
391 // view
392 'is_permission_b_a' => 2,
393 'is_active' => 1,
394 ]);
395
396 return $contacts;
397 }
398
399 /**
400 * ACL HOOK implementation for various tests
401 */
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)";
406 }
407 }
408
409 }