[REF] Fix Event location to create it's locations directly rather than via shared...
[civicrm-core.git] / tests / phpunit / api / v3 / ACLPermissionTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 use Civi\Api4\Contact;
13 use Civi\Api4\CustomField;
14 use Civi\Api4\CustomGroup;
15 use Civi\Api4\CustomValue;
16
17 /**
18 * This class is intended to test ACL permission using the multisite module
19 *
20 * @package CiviCRM_APIv3
21 * @subpackage API_Contact
22 * @group headless
23 */
24 class api_v3_ACLPermissionTest extends CiviUnitTestCase {
25
26 use CRMTraits_ACL_PermissionTrait;
27
28 public $DBResetRequired = FALSE;
29 protected $_entity;
30
31 public function setUp() {
32 parent::setUp();
33 CRM_Core_DAO::createTestObject('CRM_Pledge_BAO_Pledge', [], 1, 0);
34 $this->callAPISuccess('Phone', 'create', ['id' => $this->individualCreate(['email' => '']), 'phone' => '911', 'location_type_id' => 'Home']);
35 $this->prepareForACLs();
36 }
37
38 /**
39 * (non-PHPdoc)
40 * @see CiviUnitTestCase::tearDown()
41 */
42 public function tearDown() {
43 $this->cleanUpAfterACLs();
44 $tablesToTruncate = [
45 'civicrm_contact',
46 'civicrm_address',
47 'civicrm_group_contact',
48 'civicrm_group',
49 'civicrm_acl',
50 'civicrm_acl_cache',
51 'civicrm_acl_entity_role',
52 'civicrm_acl_contact_cache',
53 'civicrm_contribution',
54 'civicrm_line_item',
55 'civicrm_participant',
56 'civicrm_uf_match',
57 'civicrm_activity',
58 'civicrm_activity_contact',
59 'civicrm_note',
60 'civicrm_entity_tag',
61 'civicrm_tag',
62 ];
63 $this->quickCleanup($tablesToTruncate);
64 }
65
66 /**
67 * Function tests that an empty where hook returns no results.
68 *
69 * @param int $version
70 *
71 * @dataProvider versionThreeAndFour
72 * @throws \CRM_Core_Exception
73 */
74 public function testContactGetNoResultsHook($version) {
75 $this->_apiversion = $version;
76 $this->hookClass->setHook('civicrm_aclWhereClause', [
77 $this,
78 'aclWhereHookNoResults',
79 ]);
80 $result = $this->callAPISuccess('contact', 'get', [
81 'check_permissions' => 1,
82 'return' => 'display_name',
83 ]);
84 $this->assertEquals(0, $result['count']);
85 }
86
87 /**
88 * Function tests that an empty where hook returns exactly 1 result with "view my contact".
89 *
90 * CRM-16512 caused contacts with Edit my contact to be able to view all records.
91 *
92 * @param int $version
93 *
94 * @dataProvider versionThreeAndFour
95 * @throws \CRM_Core_Exception
96 */
97 public function testContactGetOneResultHookWithViewMyContact($version) {
98 $this->_apiversion = $version;
99 $this->createLoggedInUser();
100 $this->hookClass->setHook('civicrm_aclWhereClause', [
101 $this,
102 'aclWhereHookNoResults',
103 ]);
104 CRM_Core_Config::singleton()->userPermissionClass->permissions = [
105 'access CiviCRM',
106 'view my contact',
107 ];
108 $result = $this->callAPISuccess('contact', 'get', [
109 'check_permissions' => 1,
110 'return' => 'display_name',
111 ]);
112 $this->assertEquals(1, $result['count']);
113 }
114
115 /**
116 * Function tests that a user with "edit my contact" can edit themselves.
117 *
118 * @param int $version
119 *
120 * @dataProvider versionThreeAndFour
121 * @throws \CRM_Core_Exception
122 */
123 public function testContactEditHookWithEditMyContact($version) {
124 $this->_apiversion = $version;
125 $cid = $this->createLoggedInUser();
126 $this->hookClass->setHook('civicrm_aclWhereClause', [
127 $this,
128 'aclWhereHookNoResults',
129 ]);
130 CRM_Core_Config::singleton()->userPermissionClass->permissions = [
131 'access CiviCRM',
132 'edit my contact',
133 ];
134 $this->callAPISuccess('contact', 'create', [
135 'check_permissions' => 1,
136 'id' => $cid,
137 'first_name' => 'NewName',
138 ]);
139 }
140
141 /**
142 * Ensure contact permissions do not block contact-less location entities.
143 *
144 * @param int $version
145 *
146 * @dataProvider versionThreeAndFour
147 * @throws \CRM_Core_Exception
148 */
149 public function testAddressWithoutContactIDAccess($version) {
150 $this->_apiversion = $version;
151 $ownID = $this->createLoggedInUser();
152 CRM_Core_Config::singleton()->userPermissionClass->permissions = [
153 'access CiviCRM',
154 'view all contacts',
155 ];
156 $this->callAPISuccess('Address', 'create', [
157 'city' => 'Mouseville',
158 'location_type_id' => 'Main',
159 'api.LocBlock.create' => 1,
160 'contact_id' => $ownID,
161 ]);
162 $this->callAPISuccessGetSingle('Address', [
163 'city' => 'Mouseville',
164 'check_permissions' => 1,
165 ]);
166 CRM_Core_DAO::executeQuery('UPDATE civicrm_address SET contact_id = NULL WHERE contact_id = %1', [
167 1 => [
168 $ownID,
169 'Integer',
170 ],
171 ]);
172 $this->callAPISuccessGetSingle('Address', [
173 'city' => 'Mouseville',
174 'check_permissions' => 1,
175 ]);
176 }
177
178 /**
179 * Ensure contact permissions extend to related entities like email
180 *
181 * @param int $version
182 *
183 * @throws \CRM_Core_Exception
184 * @throws \CiviCRM_API3_Exception
185 * @dataProvider versionThreeAndFour
186 * FIXME: Finish api4 part
187 */
188 public function testRelatedEntityPermissions($version) {
189 $this->_apiversion = $version;
190 $this->createLoggedInUser();
191 $disallowedContact = $this->individualCreate([], 0);
192 $this->allowedContactId = $this->individualCreate([], 1);
193 $this->hookClass->setHook('civicrm_aclWhereClause', [
194 $this,
195 'aclWhereOnlyOne',
196 ]);
197 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM'];
198 $testEntities = [
199 'Email' => ['email' => 'null@nothing', 'location_type_id' => 1],
200 'Phone' => ['phone' => '123456', 'location_type_id' => 1],
201 'IM' => ['name' => 'hello', 'location_type_id' => 1],
202 'Website' => ['url' => 'http://test'],
203 'Address' => [
204 'street_address' => '123 Sesame St.',
205 'location_type_id' => 1,
206 ],
207 ];
208 foreach ($testEntities as $entity => $params) {
209 $params += [
210 'contact_id' => $disallowedContact,
211 'check_permissions' => 1,
212 ];
213 // We should be prevented from getting or creating entities for a contact we don't have permission for
214 $this->callAPIFailure($entity, 'create', $params);
215 $this->callAPISuccess($entity, 'create', ['check_permissions' => 0] + $params);
216 $results = $this->callAPISuccess($entity, 'get', [
217 'contact_id' => $disallowedContact,
218 'check_permissions' => 1,
219 ]);
220 $this->assertEquals(0, $results['count']);
221
222 // We should be allowed to create and get for contacts we do have permission on
223 $params['contact_id'] = $this->allowedContactId;
224 $this->callAPISuccess($entity, 'create', $params);
225 $results = $this->callAPISuccess($entity, 'get', [
226 'contact_id' => $this->allowedContactId,
227 'check_permissions' => 1,
228 ]);
229 $this->assertGreaterThan(0, $results['count']);
230 }
231 if ($version == 4) {
232 $this->markTestIncomplete('Skipping entity_id related perms in api4 for now.');
233 }
234 $newTag = civicrm_api3('Tag', 'create', [
235 'name' => 'Foo123',
236 ]);
237 $relatedEntities = [
238 'Note' => ['note' => 'abc'],
239 'EntityTag' => ['tag_id' => $newTag['id']],
240 ];
241 foreach ($relatedEntities as $entity => $params) {
242 $params += [
243 'entity_id' => $disallowedContact,
244 'entity_table' => 'civicrm_contact',
245 'check_permissions' => 1,
246 ];
247 // We should be prevented from getting or creating entities for a contact we don't have permission for
248 $this->callAPIFailure($entity, 'create', $params);
249 $this->callAPISuccess($entity, 'create', ['check_permissions' => 0] + $params);
250 $results = $this->callAPISuccess($entity, 'get', [
251 'entity_id' => $disallowedContact,
252 'entity_table' => 'civicrm_contact',
253 'check_permissions' => 1,
254 ]);
255 $this->assertEquals(0, $results['count']);
256
257 // We should be allowed to create and get for entities we do have permission on
258 $params['entity_id'] = $this->allowedContactId;
259 $this->callAPISuccess($entity, 'create', $params);
260 $results = $this->callAPISuccess($entity, 'get', [
261 'entity_id' => $this->allowedContactId,
262 'entity_table' => 'civicrm_contact',
263 'check_permissions' => 1,
264 ]);
265 $this->assertGreaterThan(0, $results['count']);
266 }
267 }
268
269 /**
270 * Function tests all results are returned.
271 *
272 * @param int $version
273 *
274 * @dataProvider versionThreeAndFour
275 * @throws \CRM_Core_Exception
276 */
277 public function testContactGetAllResultsHook($version) {
278 $this->_apiversion = $version;
279 $this->hookClass->setHook('civicrm_aclWhereClause', [
280 $this,
281 'aclWhereHookAllResults',
282 ]);
283 $result = $this->callAPISuccess('contact', 'get', [
284 'check_permissions' => 1,
285 'return' => 'display_name',
286 ]);
287
288 $this->assertEquals(2, $result['count']);
289 }
290
291 /**
292 * Function tests that deleted contacts are not returned.
293 *
294 * @param int $version
295 *
296 * @dataProvider versionThreeAndFour
297 * @throws \CRM_Core_Exception
298 */
299 public function testContactGetPermissionHookNoDeleted($version) {
300 $this->_apiversion = $version;
301 $this->callAPISuccess('contact', 'create', ['id' => 2, 'is_deleted' => 1]);
302 $this->hookClass->setHook('civicrm_aclWhereClause', [
303 $this,
304 'aclWhereHookAllResults',
305 ]);
306 $result = $this->callAPISuccess('contact', 'get', [
307 'check_permissions' => 1,
308 'return' => 'display_name',
309 ]);
310 $this->assertEquals(1, $result['count']);
311 }
312
313 /**
314 * Test permissions limited by hook.
315 *
316 * @param int $version
317 *
318 * @dataProvider versionThreeAndFour
319 * @throws \CRM_Core_Exception
320 */
321 public function testContactGetHookLimitingHook($version) {
322 $this->_apiversion = $version;
323 $this->hookClass->setHook('civicrm_aclWhereClause', [
324 $this,
325 'aclWhereOnlySecond',
326 ]);
327
328 $result = $this->callAPISuccess('contact', 'get', [
329 'check_permissions' => 1,
330 'return' => 'display_name',
331 ]);
332 $this->assertEquals(1, $result['count']);
333 }
334
335 /**
336 * Confirm that without check permissions we still get 2 contacts returned.
337 *
338 * @param int $version
339 *
340 * @dataProvider versionThreeAndFour
341 * @throws \CRM_Core_Exception
342 */
343 public function testContactGetHookLimitingHookDontCheck($version) {
344 $this->_apiversion = $version;
345 $result = $this->callAPISuccess('contact', 'get', [
346 'check_permissions' => 0,
347 'return' => 'display_name',
348 ]);
349 $this->assertEquals(2, $result['count']);
350 }
351
352 /**
353 * Check that id works as a filter.
354 * @param int $version
355 * @dataProvider versionThreeAndFour
356 */
357 public function testContactGetIDFilter($version) {
358 $this->_apiversion = $version;
359 $this->hookClass->setHook('civicrm_aclWhereClause', [
360 $this,
361 'aclWhereHookAllResults',
362 ]);
363 $result = $this->callAPISuccess('contact', 'get', [
364 'sequential' => 1,
365 'id' => 2,
366 'check_permissions' => 1,
367 ]);
368
369 $this->assertEquals(1, $result['count']);
370 $this->assertEquals(2, $result['id']);
371 }
372
373 /**
374 * Check that address IS returned.
375 */
376 public function testContactGetAddressReturned() {
377 $this->hookClass->setHook('civicrm_aclWhereClause', [
378 $this,
379 'aclWhereOnlySecond',
380 ]);
381 $fullresult = $this->callAPISuccess('contact', 'get', [
382 'sequential' => 1,
383 ]);
384 //return doesn't work for all keys - can't fix that here so let's skip ...
385 //prefix & suffix are inconsistent due to CRM-7929
386 // unsure about others but return doesn't work on them
387 $elementsReturnDoesntSupport = [
388 'prefix',
389 'suffix',
390 'gender',
391 'current_employer',
392 'phone_id',
393 'phone_type_id',
394 'phone',
395 'worldregion_id',
396 'world_region',
397 ];
398 $expectedReturnElements = array_diff(array_keys($fullresult['values'][0]), $elementsReturnDoesntSupport);
399 $result = $this->callAPISuccess('contact', 'get', [
400 'check_permissions' => 1,
401 'return' => $expectedReturnElements,
402 'sequential' => 1,
403 ]);
404 $this->assertEquals(1, $result['count']);
405 foreach ($expectedReturnElements as $element) {
406 $this->assertArrayHasKey($element, $result['values'][0]);
407 }
408 }
409
410 /**
411 * Check that pledge IS not returned.
412 * @param int $version
413 * @dataProvider versionThreeAndFour
414 */
415 public function testContactGetPledgeIDNotReturned($version) {
416 $this->_apiversion = $version;
417 $this->hookClass->setHook('civicrm_aclWhereClause', [
418 $this,
419 'aclWhereHookAllResults',
420 ]);
421 $this->callAPISuccess('contact', 'get', [
422 'sequential' => 1,
423 ]);
424 $result = $this->callAPISuccess('contact', 'get', [
425 'check_permissions' => 1,
426 'return' => 'pledge_id',
427 'sequential' => 1,
428 ]);
429 $this->assertArrayNotHasKey('pledge_id', $result['values'][0]);
430 }
431
432 /**
433 * Check that pledge IS not an allowable filter.
434 */
435 public function testContactGetPledgeIDNotFiltered() {
436 $this->hookClass->setHook('civicrm_aclWhereClause', [
437 $this,
438 'aclWhereHookAllResults',
439 ]);
440 $this->callAPISuccess('contact', 'get', [
441 'sequential' => 1,
442 ]);
443 $result = $this->callAPISuccess('contact', 'get', [
444 'check_permissions' => 1,
445 'pledge_id' => 1,
446 'sequential' => 1,
447 ]);
448 $this->assertEquals(2, $result['count']);
449 }
450
451 /**
452 * Check that chaining doesn't bypass permissions
453 *
454 * @param int $version
455 *
456 * @dataProvider versionThreeAndFour
457 * @throws \CRM_Core_Exception
458 */
459 public function testContactGetPledgeNotChainable($version) {
460 $this->_apiversion = $version;
461 $this->hookClass->setHook('civicrm_aclWhereClause', [
462 $this,
463 'aclWhereOnlySecond',
464 ]);
465 $this->callAPISuccess('contact', 'get', [
466 'sequential' => 1,
467 ]);
468 $this->callAPIFailure('contact', 'get', [
469 'check_permissions' => 1,
470 'api.pledge.get' => 1,
471 'sequential' => 1,
472 ],
473 'Error in call to Pledge_get : API permission check failed for Pledge/get call; insufficient permission: require access CiviCRM and access CiviPledge'
474 );
475 }
476
477 public function setupCoreACL() {
478 $this->createLoggedInUser();
479 $this->_permissionedDisabledGroup = $this->groupCreate([
480 'title' => 'pick-me-disabled',
481 'is_active' => 0,
482 'name' => 'pick-me-disabled',
483 ]);
484 $this->_permissionedGroup = $this->groupCreate([
485 'title' => 'pick-me-active',
486 'is_active' => 1,
487 'name' => 'pick-me-active',
488 ]);
489 $this->setupACL();
490 }
491
492 /**
493 * @dataProvider entities
494 * confirm that without check permissions we still get 2 contacts returned
495 *
496 * @param string $entity
497 *
498 * @throws \CRM_Core_Exception
499 */
500 public function testEntitiesGetHookLimitingHookNoCheck($entity) {
501 CRM_Core_Config::singleton()->userPermissionClass->permissions = [];
502 $this->setUpEntities($entity);
503 $this->hookClass->setHook('civicrm_aclWhereClause', [
504 $this,
505 'aclWhereHookNoResults',
506 ]);
507 $result = $this->callAPISuccess($entity, 'get', [
508 'check_permissions' => 0,
509 'return' => 'contact_id',
510 ]);
511 $this->assertEquals(2, $result['count']);
512 }
513
514 /**
515 * @dataProvider entities
516 * confirm that without check permissions we still get 2 entities returned
517 * @param $entity
518 */
519 public function testEntitiesGetCoreACLLimitingHookNoCheck($entity) {
520 $this->setupCoreACL();
521 //CRM_Core_Config::singleton()->userPermissionClass->permissions = array();
522 $this->setUpEntities($entity);
523 $this->hookClass->setHook('civicrm_aclWhereClause', [
524 $this,
525 'aclWhereHookNoResults',
526 ]);
527 $result = $this->callAPISuccess($entity, 'get', [
528 'check_permissions' => 0,
529 'return' => 'contact_id',
530 ]);
531 $this->assertEquals(2, $result['count']);
532 }
533
534 /**
535 * @dataProvider entities
536 * confirm that with check permissions we don't get entities
537 *
538 * @param $entity
539 *
540 * @throws \PHPUnit\Framework\IncompleteTestError
541 * @throws \CRM_Core_Exception
542 */
543 public function testEntitiesGetCoreACLLimitingCheck($entity) {
544 $this->setupCoreACL();
545 $this->setUpEntities($entity);
546 $result = $this->callAPISuccess($entity, 'get', [
547 'check_permissions' => 1,
548 'return' => 'contact_id',
549 ]);
550 $this->assertEquals(0, $result['count']);
551 }
552
553 /**
554 * @dataProvider entities
555 * Function tests that an empty where hook returns no results
556 *
557 * @param string $entity
558 *
559 * @throws \PHPUnit\Framework\IncompleteTestError
560 * @throws \CRM_Core_Exception
561 */
562 public function testEntityGetNoResultsHook($entity) {
563 $this->markTestIncomplete('hook acls only work with contacts so far');
564 CRM_Core_Config::singleton()->userPermissionClass->permissions = [];
565 $this->setUpEntities($entity);
566 $this->hookClass->setHook('civicrm_aclWhereClause', [
567 $this,
568 'aclWhereHookNoResults',
569 ]);
570 $result = $this->callAPISuccess($entity, 'get', [
571 'check_permission' => 1,
572 ]);
573 $this->assertEquals(0, $result['count']);
574 }
575
576 /**
577 * @return array
578 */
579 public static function entities() {
580 return [
581 ['contribution'],
582 ['participant'],
583 // @todo array('pledge' => 'pledge')
584 ];
585 }
586
587 /**
588 * Create 2 entities.
589 *
590 * @param string $entity
591 */
592 public function setUpEntities($entity) {
593 CRM_Core_DAO::createTestObject(_civicrm_api3_get_BAO($entity), [], 2, 0);
594 CRM_Core_Config::singleton()->userPermissionClass->permissions = [
595 'access CiviCRM',
596 'access CiviContribute',
597 'access CiviEvent',
598 'view event participants',
599 ];
600 }
601
602 /**
603 * Basic check that an un-permissioned call keeps working and permissioned call fails.
604 *
605 * @param int $version
606 *
607 * @dataProvider versionThreeAndFour
608 * @throws \CRM_Core_Exception
609 */
610 public function testGetActivityNoPermissions($version) {
611 $this->_apiversion = $version;
612 $this->setPermissions([]);
613 $this->callAPISuccess('Activity', 'get');
614 $this->callAPIFailure('Activity', 'get', ['check_permissions' => 1]);
615 }
616
617 /**
618 * View all activities is enough regardless of contact ACLs.
619 *
620 * @param int $version
621 *
622 * @throws \CRM_Core_Exception
623 * @throws \CiviCRM_API3_Exception
624 * @dataProvider versionThreeAndFour
625 */
626 public function testGetActivityViewAllActivitiesDoesntCutItAnymore($version) {
627 $this->_apiversion = $version;
628 $activity = $this->activityCreate();
629 $this->setPermissions(['view all activities', 'access CiviCRM']);
630 $this->callAPISuccessGetCount('Activity', [
631 'check_permissions' => 1,
632 'id' => $activity['id'],
633 ], 0);
634 }
635
636 /**
637 * View all activities is required unless id is passed in.
638 *
639 * @param int $version
640 *
641 * @dataProvider versionThreeAndFour
642 * @throws \CRM_Core_Exception
643 */
644 public function testGetActivityViewAllContactsEnoughWithoutID($version) {
645 $this->_apiversion = $version;
646 $this->setPermissions(['view all contacts', 'access CiviCRM']);
647 $this->callAPISuccess('Activity', 'get', ['check_permissions' => 1]);
648 }
649
650 /**
651 * Without view all activities contact level acls are used.
652 *
653 * @param int $version
654 *
655 * @throws \CRM_Core_Exception
656 * @throws \CiviCRM_API3_Exception
657 * @dataProvider versionThreeAndFour
658 */
659 public function testGetActivityViewAllContactsEnoughWIthID($version) {
660 $this->_apiversion = $version;
661 $activity = $this->activityCreate();
662 $this->setPermissions(['view all contacts', 'access CiviCRM']);
663 $this->callAPISuccess('Activity', 'getsingle', [
664 'check_permissions' => 1,
665 'id' => $activity['id'],
666 ]);
667 }
668
669 /**
670 * Check the error message is not a permission error.
671 *
672 * @param int $version
673 *
674 * @throws \CRM_Core_Exception
675 * @throws \CiviCRM_API3_Exception
676 * @dataProvider versionThreeAndFour
677 */
678 public function testGetActivityAccessCiviCRMEnough($version) {
679 $this->_apiversion = $version;
680 $activity = $this->activityCreate();
681 $this->setPermissions(['access CiviCRM']);
682 $this->callAPIFailure('Activity', 'getsingle', [
683 'check_permissions' => 1,
684 'id' => $activity['id'],
685 ], 'Expected one Activity but found 0');
686 $this->callAPISuccessGetCount('Activity', [
687 'check_permissions' => 1,
688 'id' => $activity['id'],
689 ], 0);
690 }
691
692 /**
693 * Check that component related activity filtering.
694 *
695 * If the contact does NOT have permission to 'view all contacts' but they DO have permission
696 * to view the contact in question they will only see the activities of components they have access too.
697 *
698 * (logically the same component limit should apply when they have access to view all too but....
699 * adding test for 'how it is at the moment.)
700 *
701 * @param int $version
702 *
703 * @throws \CRM_Core_Exception
704 * @throws \CiviCRM_API3_Exception
705 * @dataProvider versionThreeAndFour
706 */
707 public function testGetActivityCheckPermissionsByComponent($version) {
708 $this->_apiversion = $version;
709 $activity = $this->activityCreate(['activity_type_id' => 'Contribution']);
710 $activity2 = $this->activityCreate(['activity_type_id' => 'Pledge Reminder']);
711 $this->hookClass->setHook('civicrm_aclWhereClause', [
712 $this,
713 'aclWhereHookAllResults',
714 ]);
715 $this->setPermissions(['access CiviCRM', 'access CiviContribute']);
716 $this->callAPISuccessGetSingle('Activity', [
717 'check_permissions' => 1,
718 'id' => ['IN' => [$activity['id'], $activity2['id']]],
719 ]);
720 $this->callAPISuccessGetCount('Activity', [
721 'check_permissions' => 1,
722 'id' => ['IN' => [$activity['id'], $activity2['id']]],
723 ], 1);
724
725 }
726
727 /**
728 * Check that component related activity filtering works for CiviCase.
729 *
730 * @param int $version
731 *
732 * @throws \CRM_Core_Exception
733 * @throws \CiviCRM_API3_Exception
734 * @dataProvider versionThreeAndFour
735 */
736 public function testGetActivityCheckPermissionsByCaseComponent($version) {
737 $this->_apiversion = $version;
738 CRM_Core_BAO_ConfigSetting::enableComponent('CiviCase');
739 $activity = $this->activityCreate(['activity_type_id' => 'Open Case']);
740 $activity2 = $this->activityCreate(['activity_type_id' => 'Pledge Reminder']);
741 $this->hookClass->setHook('civicrm_aclWhereClause', [
742 $this,
743 'aclWhereHookAllResults',
744 ]);
745 $this->setPermissions([
746 'access CiviCRM',
747 'access CiviContribute',
748 'access all cases and activities',
749 ]);
750 $this->callAPISuccessGetSingle('Activity', [
751 'check_permissions' => 1,
752 'id' => ['IN' => [$activity['id'], $activity2['id']]],
753 ]);
754 $this->callAPISuccessGetCount('Activity', [
755 'check_permissions' => 1,
756 'id' => ['IN' => [$activity['id'], $activity2['id']]],
757 ], 1);
758 }
759
760 /**
761 * Check that activities can be retrieved by ACL.
762 *
763 * The activities api applies ACLs in a very limited circumstance, if id is passed in.
764 * Otherwise it sticks with the blunt original permissions.
765 *
766 * @param int $version
767 *
768 * @throws \CRM_Core_Exception
769 * @throws \CiviCRM_API3_Exception
770 * @dataProvider versionThreeAndFour
771 */
772 public function testGetActivityByACL($version) {
773 $this->_apiversion = $version;
774 $this->setPermissions(['access CiviCRM']);
775 $activity = $this->activityCreate();
776
777 $this->hookClass->setHook('civicrm_aclWhereClause', [
778 $this,
779 'aclWhereHookAllResults',
780 ]);
781 $this->callAPISuccessGetSingle('Activity', [
782 'check_permissions' => 1,
783 'id' => $activity['id'],
784 ]);
785 $this->callAPISuccessGetCount('Activity', [
786 'check_permissions' => 1,
787 'id' => $activity['id'],
788 ]);
789 }
790
791 /**
792 * To leverage ACL permission to view an activity you must be able to see any of the contacts.
793 * FIXME: Api4
794 */
795 public function testGetActivityByAclCannotViewAllContacts() {
796 $activity = $this->activityCreate(['assignee_contact_id' => $this->individualCreate()]);
797 $contacts = $this->getActivityContacts($activity);
798 $this->setPermissions(['access CiviCRM']);
799
800 foreach ($contacts as $role => $contact_id) {
801 $this->allowedContactId = $contact_id;
802 $this->hookClass->setHook('civicrm_aclWhereClause', [
803 $this,
804 'aclWhereOnlyOne',
805 ]);
806 $this->cleanupCachedPermissions();
807 $result = $this->callAPISuccessGetSingle('Activity', [
808 'check_permissions' => 1,
809 'id' => $activity['id'],
810 'return' => [
811 'source_contact_id',
812 'target_contact_id',
813 'assignee_contact_id',
814 ],
815 ]);
816 foreach ([
817 'source_contact',
818 'target_contact',
819 'assignee_contact',
820 ] as $roleName) {
821 $roleKey = $roleName . '_id';
822 if ($role !== $roleKey) {
823 $this->assertTrue(empty($result[$roleKey]), "Only contact in $role is permissioned to be returned, not $roleKey");
824 }
825 else {
826 $this->assertEquals([$contact_id], (array) $result[$roleKey]);
827 $this->assertNotEmpty($result[$roleName . '_name']);
828 }
829 }
830 }
831 }
832
833 /**
834 * To leverage ACL permission to view an activity you must be able to see any of the contacts.
835 *
836 * @param int $version
837 *
838 * @throws \CRM_Core_Exception
839 * @throws \CiviCRM_API3_Exception
840 * @dataProvider versionThreeAndFour
841 */
842 public function testGetActivityByAclCannotViewAnyContacts($version) {
843 $this->_apiversion = $version;
844 $activity = $this->activityCreate();
845 $contacts = $this->getActivityContacts($activity);
846 $this->setPermissions(['access CiviCRM']);
847
848 foreach ($contacts as $contact_id) {
849 $this->callAPIFailure('Activity', 'getsingle', [
850 'check_permissions' => 1,
851 'id' => $activity['id'],
852 ]);
853 }
854 }
855
856 /**
857 * Check that if the source contact is deleted but we can view the others we can see the activity.
858 *
859 * CRM-18409.
860 *
861 * @param int $version
862 *
863 * @dataProvider versionThreeAndFour
864 * @throws \CiviCRM_API3_Exception
865 * @throws \CRM_Core_Exception
866 */
867 public function testGetActivityACLSourceContactDeleted($version) {
868 $this->_apiversion = $version;
869 $this->setPermissions(['access CiviCRM', 'delete contacts']);
870 $activity = $this->activityCreate();
871 $contacts = $this->getActivityContacts($activity);
872
873 $this->hookClass->setHook('civicrm_aclWhereClause', [
874 $this,
875 'aclWhereHookAllResults',
876 ]);
877 $this->contactDelete($contacts['source_contact_id']);
878 $this->callAPISuccess('Activity', 'getsingle', [
879 'check_permissions' => 1,
880 'id' => $activity['id'],
881 ]);
882 }
883
884 /**
885 * Test get activities multiple ids with check permissions
886 *
887 * @see https://issues.civicrm.org/jira/browse/CRM-20441
888 *
889 * @param int $version
890 *
891 * @throws \CRM_Core_Exception
892 * @throws \CiviCRM_API3_Exception
893 * @dataProvider versionThreeAndFour
894 */
895 public function testActivitiesGetMultipleIdsCheckPermissions($version) {
896 $this->_apiversion = $version;
897 $this->createLoggedInUser();
898 $activity = $this->activityCreate();
899 $activity2 = $this->activityCreate();
900 $this->setPermissions(['access CiviCRM']);
901 $this->hookClass->setHook('civicrm_aclWhereClause', [
902 $this,
903 'aclWhereHookAllResults',
904 ]);
905 // Get activities associated with contact $this->_contactID.
906 $params = [
907 'id' => ['IN' => [$activity['id'], $activity2['id']]],
908 'check_permissions' => TRUE,
909 ];
910 $result = $this->callAPISuccess('activity', 'get', $params);
911 $this->assertEquals(2, $result['count']);
912 }
913
914 /**
915 * Test get activities multiple ids with check permissions
916 * Limit access to One contact
917 *
918 * @see https://issues.civicrm.org/jira/browse/CRM-20441
919 *
920 * @param int $version
921 *
922 * @throws \CRM_Core_Exception
923 * @throws \CiviCRM_API3_Exception
924 * @dataProvider versionThreeAndFour
925 */
926 public function testActivitiesGetMultipleIdsCheckPermissionsLimitedACL($version) {
927 $this->_apiversion = $version;
928 $this->createLoggedInUser();
929 $activity = $this->activityCreate();
930 $contacts = $this->getActivityContacts($activity);
931 $this->setPermissions(['access CiviCRM']);
932 foreach ($contacts as $contact_id) {
933 $this->allowedContacts[] = $contact_id;
934 }
935 $this->hookClass->setHook('civicrm_aclWhereClause', [
936 $this,
937 'aclWhereMultipleContacts',
938 ]);
939 $contact2 = $this->individualCreate();
940 $activity2 = $this->activityCreate(['source_contact_id' => $contact2]);
941 // Get activities associated with contact $this->_contactID.
942 $params = [
943 'id' => ['IN' => [$activity['id']]],
944 'check_permissions' => TRUE,
945 ];
946 $result = $this->callAPISuccess('activity', 'get', $params);
947 $this->assertEquals(1, $result['count']);
948 $this->callAPIFailure('activity', 'getsingle', array_merge($params, [
949 'id' => [
950 'IN',
951 [$activity2['id']],
952 ],
953 ]));
954 }
955
956 /**
957 * Test get activities multiple ids with check permissions
958 *
959 * @see https://issues.civicrm.org/jira/browse/CRM-20441
960 *
961 * @param int $version
962 *
963 * @throws \CRM_Core_Exception
964 * @throws \CiviCRM_API3_Exception
965 * @dataProvider versionThreeAndFour
966 */
967 public function testActivitiesGetMultipleIdsCheckPermissionsNotIN($version) {
968 $this->_apiversion = $version;
969 $this->createLoggedInUser();
970 $activity = $this->activityCreate();
971 $activity2 = $this->activityCreate();
972 $this->setPermissions(['access CiviCRM']);
973 $this->hookClass->setHook('civicrm_aclWhereClause', [
974 $this,
975 'aclWhereHookAllResults',
976 ]);
977 // Get activities associated with contact $this->_contactID.
978 $params = [
979 'id' => ['NOT IN' => [$activity['id'], $activity2['id']]],
980 'check_permissions' => TRUE,
981 ];
982 $result = $this->callAPISuccess('activity', 'get', $params);
983 $this->assertEquals(0, $result['count']);
984 }
985
986 /**
987 * Get the contacts for the activity.
988 *
989 * @param $activity
990 *
991 * @return array
992 * @throws \CRM_Core_Exception
993 */
994 protected function getActivityContacts($activity) {
995 $contacts = [];
996
997 $activityContacts = $this->callAPISuccess('ActivityContact', 'get', [
998 'activity_id' => $activity['id'],
999 ]);
1000
1001 $activityRecordTypes = $this->callAPISuccess('ActivityContact', 'getoptions', ['field' => 'record_type_id']);
1002 foreach ($activityContacts['values'] as $activityContact) {
1003 $type = $activityRecordTypes['values'][$activityContact['record_type_id']];
1004 switch ($type) {
1005 case 'Activity Source':
1006 $contacts['source_contact_id'] = $activityContact['contact_id'];
1007 break;
1008
1009 case 'Activity Targets':
1010 $contacts['target_contact_id'] = $activityContact['contact_id'];
1011 break;
1012
1013 case 'Activity Assignees':
1014 $contacts['assignee_contact_id'] = $activityContact['contact_id'];
1015 break;
1016
1017 }
1018 }
1019 return $contacts;
1020 }
1021
1022 /**
1023 * Test that the 'everyone' group can be given access to a contact.
1024 * FIXME: Api4
1025 */
1026 public function testGetACLEveryonePermittedEntity() {
1027 $this->setupScenarioCoreACLEveryonePermittedToGroup();
1028 $this->callAPISuccessGetCount('Contact', [
1029 'id' => $this->scenarioIDs['Contact']['permitted_contact'],
1030 'check_permissions' => 1,
1031 ], 1);
1032
1033 $this->callAPISuccessGetCount('Contact', [
1034 'id' => $this->scenarioIDs['Contact']['non_permitted_contact'],
1035 'check_permissions' => 1,
1036 ], 0);
1037
1038 // Also check that we can access ACLs through a path that uses the acl_contact_cache table.
1039 // historically this has caused errors due to the key_constraint on that table.
1040 // This is a bit of an artificial check as we have to amp up permissions to access this api.
1041 // However, the lower level function is more directly accessed through the Contribution & Event & Profile
1042 $dupes = $this->callAPISuccess('Contact', 'duplicatecheck', [
1043 'match' => [
1044 'first_name' => 'Anthony',
1045 'last_name' => 'Anderson',
1046 'contact_type' => 'Individual',
1047 'email' => 'anthony_anderson@civicrm.org',
1048 ],
1049 'check_permissions' => 0,
1050 ]);
1051 $this->assertEquals(2, $dupes['count']);
1052 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM'];
1053
1054 $dupes = $this->callAPISuccess('Contact', 'duplicatecheck', [
1055 'match' => [
1056 'first_name' => 'Anthony',
1057 'last_name' => 'Anderson',
1058 'contact_type' => 'Individual',
1059 'email' => 'anthony_anderson@civicrm.org',
1060 ],
1061 'check_permissions' => 1,
1062 ]);
1063 $this->assertEquals(1, $dupes['count']);
1064
1065 }
1066
1067 /**
1068 * @param int $version
1069 *
1070 * @dataProvider versionThreeAndFour
1071 * @throws \CRM_Core_Exception
1072 */
1073 public function testContactGetViaJoin($version) {
1074 $this->_apiversion = $version;
1075 $this->createLoggedInUser();
1076 $main = $this->individualCreate(['first_name' => 'Main']);
1077 $other = $this->individualCreate(['first_name' => 'Other'], 1);
1078 $tag1 = $this->tagCreate(['name' => uniqid('created'), 'created_id' => $main])['id'];
1079 $tag2 = $this->tagCreate(['name' => uniqid('other'), 'created_id' => $other])['id'];
1080 $this->setPermissions(['access CiviCRM']);
1081 $this->hookClass->setHook('civicrm_aclWhereClause', [$this, 'aclWhereHookAllResults']);
1082 $createdFirstName = $version === 4 ? 'created.first_name' : 'created_id.first_name';
1083 $result = $this->callAPISuccess('Tag', 'get', [
1084 'check_permissions' => 1,
1085 'return' => ['id', $createdFirstName],
1086 'id' => ['IN' => [$tag1, $tag2]],
1087 ]);
1088 $this->assertEquals('Main', $result['values'][$tag1][$createdFirstName]);
1089 $this->assertEquals('Other', $result['values'][$tag2][$createdFirstName]);
1090 $this->allowedContactId = $main;
1091 $this->hookClass->setHook('civicrm_aclWhereClause', [$this, 'aclWhereOnlyOne']);
1092 $this->cleanupCachedPermissions();
1093 $result = $this->callAPISuccess('Tag', 'get', [
1094 'check_permissions' => 1,
1095 'return' => ['id', $createdFirstName],
1096 'id' => ['IN' => [$tag1, $tag2]],
1097 ]);
1098 $this->assertEquals('Main', $result['values'][$tag1][$createdFirstName]);
1099 $this->assertEquals($tag2, $result['values'][$tag2]['id']);
1100 $this->assertFalse(isset($result['values'][$tag2][$createdFirstName]));
1101 }
1102
1103 public function testApi4CustomEntityACL() {
1104 $group = uniqid('mg');
1105 $textField = uniqid('tx');
1106
1107 CustomGroup::create(FALSE)
1108 ->addValue('name', $group)
1109 ->addValue('extends', 'Contact')
1110 ->addValue('is_multiple', TRUE)
1111 ->addChain('field', CustomField::create()
1112 ->addValue('label', $textField)
1113 ->addValue('custom_group_id', '$id')
1114 ->addValue('html_type', 'Text')
1115 ->addValue('data_type', 'String')
1116 )
1117 ->execute();
1118
1119 $this->createLoggedInUser();
1120 $c1 = $this->individualCreate(['first_name' => 'C1']);
1121 $c2 = $this->individualCreate(['first_name' => 'C2', 'is_deleted' => 1], 1);
1122
1123 CustomValue::save($group)->setCheckPermissions(FALSE)
1124 ->addRecord(['entity_id' => $c1, $textField => '1'])
1125 ->addRecord(['entity_id' => $c2, $textField => '2'])
1126 ->execute();
1127
1128 $this->setPermissions(['access CiviCRM', 'view debug output']);
1129 $this->hookClass->setHook('civicrm_aclWhereClause', [$this, 'aclWhereHookAllResults']);
1130
1131 // Without "access deleted contacts" we won't see C2
1132 $vals = CustomValue::get($group)->setDebug(TRUE)->execute();
1133 $this->assertCount(1, $vals);
1134 $this->assertEquals($c1, $vals[0]['entity_id']);
1135
1136 $this->setPermissions(['access CiviCRM', 'access deleted contacts', 'view debug output']);
1137 $this->hookClass->setHook('civicrm_aclWhereClause', [$this, 'aclWhereHookAllResults']);
1138 $this->cleanupCachedPermissions();
1139
1140 $vals = CustomValue::get($group)->execute();
1141 $this->assertCount(2, $vals);
1142
1143 $this->allowedContactId = $c2;
1144 $this->hookClass->setHook('civicrm_aclWhereClause', [$this, 'aclWhereOnlyOne']);
1145 $this->cleanupCachedPermissions();
1146
1147 $vals = CustomValue::get($group)->addSelect('*', 'contact.first_name')->execute();
1148 $this->assertCount(1, $vals);
1149 $this->assertEquals($c2, $vals[0]['entity_id']);
1150 $this->assertEquals('C2', $vals[0]['contact.first_name']);
1151
1152 $vals = Contact::get()
1153 ->addJoin('Custom_' . $group . ' AS cf')
1154 ->addSelect('first_name', 'cf.' . $textField)
1155 ->addWhere('is_deleted', '=', TRUE)
1156 ->execute();
1157 $this->assertCount(1, $vals);
1158 $this->assertEquals('C2', $vals[0]['first_name']);
1159 $this->assertEquals('2', $vals[0]['cf.' . $textField]);
1160 }
1161
1162 }