Fix a bug in second degree permssions where the a_b, b_a combination was mishandled
[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);
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].");
134b2b64 126 }
e4541c56 127
134b2b64 128 // run this for SECOND DEGREE relations
129 $config->secondDegRelPermissions = TRUE;
130 $this->assertTrue($config->secondDegRelPermissions);
131 foreach ($permissions_to_check as $permission => $permission_label) {
132 $result = CRM_Contact_BAO_Contact_Permission::allowList($contacts, $permission);
133 sort($result);
134
c1ebd31f 135 $this->assertNotContains($contacts[0], $result, "User[0] should NOT have $permission_label permission on contact[0].");
e4541c56 136 $this->assertContains($contacts[1], $result, "User[0] should have $permission_label permission on contact[1].");
137 $this->assertContains($contacts[2], $result, "User[0] should have second degree $permission_label permission on contact[2].");
c1ebd31f 138 $this->assertNotContains($contacts[3], $result, "User[0] should NOT have $permission_label permission on contact[3].");
139 $this->assertNotContains($contacts[4], $result, "User[0] should NOT have $permission_label permission on contact[4].");
134b2b64 140 }
ea8011f6 141 }
142
143
134b2b64 144 /**
c1ebd31f 145 * Test access based on ACL
134b2b64 146 */
c1ebd31f 147 public function testPermissionByACL() {
148 $contacts = $this->createScenarioPlain();
149
150 // set custom hook
151 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'hook_civicrm_aclWhereClause'));
152
153 // run simple test
154 $permissions_to_check = array(CRM_Core_Permission::VIEW => 'View', CRM_Core_Permission::EDIT => 'Edit');
155
156 $this->allowedContactsACL = array($contacts[0], $contacts[1], $contacts[4]);
157 CRM_Core_Config::singleton()->userPermissionClass->permissions = array();
158 $result = CRM_Contact_BAO_Contact_Permission::allowList($contacts);
159 sort($result);
160
e4541c56 161 $this->assertContains($contacts[0], $result, "User[0] should NOT have an ACL permission on contact[0].");
162 $this->assertContains($contacts[1], $result, "User[0] should have an ACL permission on contact[1].");
c1ebd31f 163 $this->assertNotContains($contacts[2], $result, "User[0] should NOT have an ACL permission on contact[2].");
164 $this->assertNotContains($contacts[3], $result, "User[0] should NOT have an RELATION permission on contact[3].");
e4541c56 165 $this->assertContains($contacts[4], $result, "User[0] should NOT have an ACL permission on contact[4].");
134b2b64 166 }
ea8011f6 167
c1ebd31f 168
134b2b64 169 /**
c1ebd31f 170 * Test access with a mix of ACL and relationship
134b2b64 171 */
c1ebd31f 172 public function testPermissionACLvsRelationship() {
173 $contacts = $this->createScenarioRelations();
174
175 // set custom hook
176 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'hook_civicrm_aclWhereClause'));
177
178 $config = CRM_Core_Config::singleton();
179 $config->userPermissionClass->permissions = array();
180 $config->secondDegRelPermissions = TRUE;
181
182 $this->allowedContactsACL = array($contacts[0], $contacts[1], $contacts[4]);
183 CRM_Core_Config::singleton()->userPermissionClass->permissions = array();
184 $result = CRM_Contact_BAO_Contact_Permission::allowList($contacts);
185 sort($result);
186
e4541c56 187 $this->assertContains($contacts[0], $result, "User[0] should have an ACL permission on contact[0].");
188 $this->assertContains($contacts[1], $result, "User[0] should have an ACL permission on contact[1].");
189 $this->assertContains($contacts[2], $result, "User[0] should have second degree an relation permission on contact[2].");
c1ebd31f 190 $this->assertNotContains($contacts[3], $result, "User[0] should NOT have an ACL permission on contact[3].");
e4541c56 191 $this->assertContains($contacts[4], $result, "User[0] should have an ACL permission on contact[4].");
134b2b64 192 }
ea8011f6 193
134b2b64 194 /**
195 * Test access related to the 'access deleted contact' permission
196 */
3c645834 197 public function testPermissionCompare() {
198 $contacts = $this->createScenarioRelations();
199 $contact_index = array_flip($contacts);
200
201 // set custom hook
202 $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'hook_civicrm_aclWhereClause'));
203
204 $config = CRM_Core_Config::singleton();
205 $this->allowedContactsACL = array($contacts[0], $contacts[1], $contacts[4]);
206 $config->secondDegRelPermissions = TRUE;
207
208 // test configurations
209 $permissions_to_check = array(CRM_Core_Permission::VIEW => 'View', CRM_Core_Permission::EDIT => 'Edit');
210 $user_permission_options = array(/*ALL*/ NULL, /*NONE*/ array(), array('view all contacts'), array('edit all contacts'), array('view all contacts', 'edit all contacts'));
211
212 // run all combinations of those
213 foreach ($permissions_to_check as $permission_to_check => $permission_label) {
214 foreach ($user_permission_options as $user_permissions) {
215 // select the contact range
216 $contact_range = $contacts;
e4541c56 217 if (is_array($user_permissions) && count($user_permissions) == 0) {
3c645834 218 // slight (explainable) deviation on the own contact
219 unset($contact_range[0]);
220 }
221
222 $config->userPermissionClass->permissions = $user_permissions;
223 $user_permissions_label = json_encode($user_permissions);
224
225 // get the list result
226 $list_result = CRM_Contact_BAO_Contact_Permission::allowList($contact_range, $permission_to_check);
227 $this->assertTrue(count($list_result) <= count($contact_range), "Permission::allowList should return a subset of the contats.");
228 foreach ($list_result as $contact_id) {
229 $this->assertContains($contact_id, $contact_range, "Permission::allowList should return a subset of the contats.");
230 }
231
232 // now compare the results
233 foreach ($contact_range as $contact_id) {
234 $individual_result = CRM_Contact_BAO_Contact_Permission::allow($contact_id, $permission_to_check);
235
236 if (in_array($contact_id, $list_result)) {
237 // listPermission reports PERMISSION GRANTED
238 $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}'");
239
e4541c56 240 }
241 else {
3c645834 242 // listPermission reports PERMISSION DENIED
243 $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}'");
244
245 }
246 }
247 }
248 }
134b2b64 249 }
ea8011f6 250
251
134b2b64 252 /****************************************************
253 * Scenario Builders *
254 ***************************************************/
ea8011f6 255
256 /**
134b2b64 257 * create plain test scenario, no relationships/ACLs
ea8011f6 258 */
134b2b64 259 protected function createScenarioPlain() {
ea8011f6 260 // get logged in user
261 $user_id = $this->createLoggedInUser();
262 $this->assertNotEmpty($user_id);
263
264 // create test contacts
265 $bush_sr_id = $this->individualCreate(array('first_name' => 'George', 'middle_name' => 'W.', 'last_name' => 'Bush'));
266 $bush_jr_id = $this->individualCreate(array('first_name' => 'George', 'middle_name' => 'H. W.', 'last_name' => 'Bush'));
267 $bush_laura_id = $this->individualCreate(array('first_name' => 'Laura Lane', 'last_name' => 'Bush'));
268 $bush_brbra_id = $this->individualCreate(array('first_name' => 'Barbara', 'last_name' => 'Bush'));
269
134b2b64 270 $contacts = array($user_id, $bush_sr_id, $bush_jr_id, $bush_laura_id, $bush_brbra_id);
271 sort($contacts);
272 return $contacts;
273 }
274
275 /**
276 * create plain test scenario, no relationships/ACLs
277 */
c1ebd31f 278 protected function createScenarioRelations() {
134b2b64 279 $contacts = $this->createScenarioPlain();
280
ea8011f6 281 // create some relationships
282 $this->callAPISuccess('Relationship', 'create', array(
e4541c56 283 'relationship_type_id' => 1, // CHILD OF
134b2b64 284 'contact_id_a' => $contacts[1],
285 'contact_id_b' => $contacts[0],
286 'is_permission_b_a' => 1,
287 'is_active' => 1,
ea8011f6 288 ));
289
290 $this->callAPISuccess('Relationship', 'create', array(
e4541c56 291 'relationship_type_id' => 1, // CHILD OF
134b2b64 292 'contact_id_a' => $contacts[2],
293 'contact_id_b' => $contacts[1],
294 'is_permission_b_a' => 1,
295 'is_active' => 1,
ea8011f6 296 ));
297
298 // create some relationships
299 $this->callAPISuccess('Relationship', 'create', array(
e4541c56 300 'relationship_type_id' => 1, // CHILD OF
134b2b64 301 'contact_id_a' => $contacts[4],
302 'contact_id_b' => $contacts[2],
303 'is_permission_b_a' => 1,
304 'is_active' => 1,
ea8011f6 305 ));
306
134b2b64 307 return $contacts;
ea8011f6 308 }
c1ebd31f 309
e4541c56 310 /**
311 * ACL HOOK implementation for various tests
c1ebd31f 312 */
e4541c56 313 public function hook_civicrm_aclWhereClause($type, &$tables, &$whereTables, &$contactID, &$where) {
c1ebd31f 314 if (!empty($this->allowedContactsACL)) {
315 $contact_id_list = implode(',', $this->allowedContactsACL);
316 $where = " contact_a.id IN ($contact_id_list)";
317 }
318 }
e4541c56 319
ea8011f6 320}