3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 +--------------------------------------------------------------------+
12 use Civi\Api4\Contact
;
13 use Civi\Api4\CustomField
;
14 use Civi\Api4\CustomGroup
;
15 use Civi\Api4\CustomValue
;
18 * This class is intended to test ACL permission using the multisite module
20 * @package CiviCRM_APIv3
21 * @subpackage API_Contact
24 class api_v3_ACLPermissionTest
extends CiviUnitTestCase
{
26 use Civi\Test\ACLPermissionTrait
;
29 * Should financials be checked after the test but before tear down.
31 * The setup methodology in this class bypasses valid financial creation
36 protected $isValidateFinancialsOnPostAssert = FALSE;
38 public $DBResetRequired = FALSE;
41 public function setUp(): void
{
43 CRM_Core_DAO
::createTestObject('CRM_Pledge_BAO_Pledge', [], 1, 0);
44 $this->callAPISuccess('Phone', 'create', ['id' => $this->individualCreate(['email' => '']), 'phone' => '911', 'location_type_id' => 'Home']);
45 $this->prepareForACLs();
50 * @see CiviUnitTestCase::tearDown()
52 public function tearDown(): void
{
53 $this->cleanUpAfterACLs();
57 'civicrm_group_contact',
61 'civicrm_acl_entity_role',
62 'civicrm_acl_contact_cache',
63 'civicrm_contribution',
65 'civicrm_participant',
68 'civicrm_activity_contact',
74 $this->quickCleanup($tablesToTruncate);
78 * Function tests that an empty where hook returns no results.
82 * @dataProvider versionThreeAndFour
84 public function testContactGetNoResultsHook(int $version): void
{
85 $this->_apiversion
= $version;
86 $this->hookClass
->setHook('civicrm_aclWhereClause', [
88 'aclWhereHookNoResults',
90 $result = $this->callAPISuccess('contact', 'get', [
91 'check_permissions' => 1,
92 'return' => 'display_name',
94 $this->assertEquals(0, $result['count']);
98 * Function tests that an empty where hook returns exactly 1 result with "view my contact".
100 * CRM-16512 caused contacts with Edit my contact to be able to view all records.
102 * @param int $version
104 * @dataProvider versionThreeAndFour
106 public function testContactGetOneResultHookWithViewMyContact(int $version): void
{
107 $this->_apiversion
= $version;
108 $this->createLoggedInUser();
109 $this->hookClass
->setHook('civicrm_aclWhereClause', [
111 'aclWhereHookNoResults',
113 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= [
117 $result = $this->callAPISuccess('contact', 'get', [
118 'check_permissions' => 1,
119 'return' => 'display_name',
121 $this->assertEquals(1, $result['count']);
125 * Function tests that a user with "edit my contact" can edit themselves.
127 * @param int $version
129 * @dataProvider versionThreeAndFour
131 public function testContactEditHookWithEditMyContact(int $version): void
{
132 $this->_apiversion
= $version;
133 $cid = $this->createLoggedInUser();
134 $this->hookClass
->setHook('civicrm_aclWhereClause', [
136 'aclWhereHookNoResults',
138 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= [
142 $this->callAPISuccess('contact', 'create', [
143 'check_permissions' => 1,
145 'first_name' => 'NewName',
150 * Ensure contact permissions do not block contact-less location entities.
152 * @param int $version
154 * @dataProvider versionThreeAndFour
156 public function testAddressWithoutContactIDAccess(int $version): void
{
157 $this->_apiversion
= $version;
158 $ownID = $this->createLoggedInUser();
159 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= [
163 $this->callAPISuccess('Address', 'create', [
164 'city' => 'Mouseville',
165 'location_type_id' => 'Main',
166 'api.LocBlock.create' => 1,
167 'contact_id' => $ownID,
169 $this->callAPISuccessGetSingle('Address', [
170 'city' => 'Mouseville',
171 'check_permissions' => 1,
173 CRM_Core_DAO
::executeQuery('UPDATE civicrm_address SET contact_id = NULL WHERE contact_id = %1', [
179 $this->callAPISuccessGetSingle('Address', [
180 'city' => 'Mouseville',
181 'check_permissions' => 1,
186 * Ensure contact permissions extend to related entities like email
188 * @param int $version
190 * @throws \CiviCRM_API3_Exception
191 * @dataProvider versionThreeAndFour
193 public function testRelatedEntityPermissions(int $version): void
{
194 $this->_apiversion
= $version;
195 $this->createLoggedInUser();
196 $disallowedContact = $this->individualCreate([]);
197 $this->allowedContactId
= $this->individualCreate([], 1);
198 $this->hookClass
->setHook('civicrm_aclWhereClause', [
202 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['access CiviCRM'];
204 'Email' => ['email' => 'null@nothing', 'location_type_id' => 1],
205 'Phone' => ['phone' => '123456', 'location_type_id' => 1],
206 'IM' => ['name' => 'hello', 'location_type_id' => 1],
207 'Website' => ['url' => 'http://test'],
209 'street_address' => '123 Sesame St.',
210 'location_type_id' => 1,
213 foreach ($testEntities as $entity => $params) {
215 'contact_id' => $disallowedContact,
216 'check_permissions' => 1,
218 // We should be prevented from getting or creating entities for a contact we don't have permission for
219 $this->callAPIFailure($entity, 'create', $params);
220 $this->callAPISuccess($entity, 'create', ['check_permissions' => 0] +
$params);
221 $results = $this->callAPISuccess($entity, 'get', [
222 'contact_id' => $disallowedContact,
223 'check_permissions' => 1,
225 $this->assertEquals(0, $results['count']);
227 // We should be allowed to create and get for contacts we do have permission on
228 $params['contact_id'] = $this->allowedContactId
;
229 $this->callAPISuccess($entity, 'create', $params);
230 $results = $this->callAPISuccess($entity, 'get', [
231 'contact_id' => $this->allowedContactId
,
232 'check_permissions' => 1,
234 $this->assertGreaterThan(0, $results['count']);
236 $newTag = civicrm_api3('Tag', 'create', [
240 'Note' => ['note' => 'abc'],
241 'EntityTag' => ['tag_id' => $newTag['id']],
243 foreach ($relatedEntities as $entity => $params) {
245 'entity_id' => $disallowedContact,
246 'entity_table' => 'civicrm_contact',
247 'check_permissions' => 1,
249 // We should be prevented from getting or creating entities for a contact we don't have permission for
250 $this->callAPIFailure($entity, 'create', $params);
251 $this->callAPISuccess($entity, 'create', ['check_permissions' => 0] +
$params);
252 $results = $this->callAPISuccess($entity, 'get', [
253 'entity_id' => $disallowedContact,
254 'entity_table' => 'civicrm_contact',
255 'check_permissions' => 1,
257 $this->assertEquals(0, $results['count']);
259 // We should be allowed to create and get for entities we do have permission on
260 $params['entity_id'] = $this->allowedContactId
;
261 $this->callAPISuccess($entity, 'create', $params);
262 $results = $this->callAPISuccess($entity, 'get', [
263 'entity_id' => $this->allowedContactId
,
264 'entity_table' => 'civicrm_contact',
265 'check_permissions' => 1,
267 $this->assertGreaterThan(0, $results['count']);
272 * Function tests all results are returned.
274 * @param int $version
276 * @dataProvider versionThreeAndFour
278 public function testContactGetAllResultsHook(int $version): void
{
279 $this->_apiversion
= $version;
280 $this->hookClass
->setHook('civicrm_aclWhereClause', [
282 'aclWhereHookAllResults',
284 $result = $this->callAPISuccess('contact', 'get', [
285 'check_permissions' => 1,
286 'return' => 'display_name',
289 $this->assertEquals(2, $result['count']);
293 * Function tests that deleted contacts are not returned.
295 * @param int $version
297 * @dataProvider versionThreeAndFour
299 public function testContactGetPermissionHookNoDeleted(int $version): void
{
300 $this->_apiversion
= $version;
301 $this->callAPISuccess('contact', 'create', ['id' => 2, 'is_deleted' => 1]);
302 $this->hookClass
->setHook('civicrm_aclWhereClause', [
304 'aclWhereHookAllResults',
306 $result = $this->callAPISuccess('contact', 'get', [
307 'check_permissions' => 1,
308 'return' => 'display_name',
310 $this->assertEquals(1, $result['count']);
314 * Test permissions limited by hook.
316 * @param int $version
318 * @dataProvider versionThreeAndFour
320 public function testContactGetHookLimitingHook(int $version): void
{
321 $this->_apiversion
= $version;
322 $this->hookClass
->setHook('civicrm_aclWhereClause', [
324 'aclWhereOnlySecond',
327 $result = $this->callAPISuccess('contact', 'get', [
328 'check_permissions' => 1,
329 'return' => 'display_name',
331 $this->assertEquals(1, $result['count']);
335 * Confirm that without check permissions we still get 2 contacts returned.
337 * @param int $version
339 * @dataProvider versionThreeAndFour
341 public function testContactGetHookLimitingHookDontCheck(int $version): void
{
342 $this->_apiversion
= $version;
343 $result = $this->callAPISuccess('contact', 'get', [
344 'check_permissions' => 0,
345 'return' => 'display_name',
347 $this->assertEquals(2, $result['count']);
351 * Check that id works as a filter.
353 * @param int $version
355 * @dataProvider versionThreeAndFour
357 public function testContactGetIDFilter(int $version): void
{
358 $this->_apiversion
= $version;
359 $this->hookClass
->setHook('civicrm_aclWhereClause', [
361 'aclWhereHookAllResults',
363 $result = $this->callAPISuccess('contact', 'get', [
366 'check_permissions' => 1,
369 $this->assertEquals(1, $result['count']);
370 $this->assertEquals(2, $result['id']);
374 * Check that address IS returned.
376 public function testContactGetAddressReturned(): void
{
377 $this->hookClass
->setHook('civicrm_aclWhereClause', [
379 'aclWhereOnlySecond',
381 $fullresult = $this->callAPISuccess('contact', 'get', [
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 = [
398 $expectedReturnElements = array_diff(array_keys($fullresult['values'][0]), $elementsReturnDoesntSupport);
399 $result = $this->callAPISuccess('contact', 'get', [
400 'check_permissions' => 1,
401 'return' => $expectedReturnElements,
404 $this->assertEquals(1, $result['count']);
405 foreach ($expectedReturnElements as $element) {
406 $this->assertArrayHasKey($element, $result['values'][0]);
411 * Check that pledge IS not returned.
413 * @param int $version
415 * @dataProvider versionThreeAndFour
417 public function testContactGetPledgeIDNotReturned(int $version): void
{
418 $this->_apiversion
= $version;
419 $this->hookClass
->setHook('civicrm_aclWhereClause', [
421 'aclWhereHookAllResults',
423 $this->callAPISuccess('contact', 'get', [
426 $result = $this->callAPISuccess('contact', 'get', [
427 'check_permissions' => 1,
428 'return' => 'pledge_id',
431 $this->assertArrayNotHasKey('pledge_id', $result['values'][0]);
435 * Check that pledge IS not an allowable filter.
437 public function testContactGetPledgeIDNotFiltered(): void
{
438 $this->hookClass
->setHook('civicrm_aclWhereClause', [
440 'aclWhereHookAllResults',
442 $this->callAPISuccess('contact', 'get', [
445 $result = $this->callAPISuccess('contact', 'get', [
446 'check_permissions' => 1,
450 $this->assertEquals(2, $result['count']);
454 * Check that chaining doesn't bypass permissions
456 * @param int $version
458 * @dataProvider versionThreeAndFour
460 public function testContactGetPledgeNotChainable(int $version): void
{
461 $this->_apiversion
= $version;
462 $this->hookClass
->setHook('civicrm_aclWhereClause', [
464 'aclWhereOnlySecond',
466 $this->callAPISuccess('contact', 'get', [
469 $this->callAPIFailure('contact', 'get', [
470 'check_permissions' => 1,
471 'api.pledge.get' => 1,
474 'Error in call to Pledge_get : API permission check failed for Pledge/get call; insufficient permission: require access CiviCRM and access CiviPledge'
478 public function setupCoreACL(): void
{
479 $this->createLoggedInUser();
480 $this->_permissionedDisabledGroup
= $this->groupCreate([
481 'title' => 'pick-me-disabled',
483 'name' => 'pick-me-disabled',
485 $this->_permissionedGroup
= $this->groupCreate([
486 'title' => 'pick-me-active',
488 'name' => 'pick-me-active',
494 * @dataProvider entities
495 * confirm that without check permissions we still get 2 contacts returned
497 * @param string $entity
498 * @param int $apiVersion
500 public function testEntitiesGetHookLimitingHookNoCheck(string $entity, int $apiVersion): void
{
501 $this->_apiversion
= $apiVersion;
502 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= [];
503 $this->setUpEntities($entity);
504 $this->hookClass
->setHook('civicrm_aclWhereClause', [
506 'aclWhereHookNoResults',
508 $result = $this->callAPISuccess($entity, 'get', [
509 'check_permissions' => 0,
510 'return' => 'contact_id',
512 $this->assertEquals(2, $result['count'], "failed with entity : $entity and api version $apiVersion");
516 * @dataProvider entities
517 * confirm that without check permissions we still get 2 entities returned
519 * @param string $entity
520 * @param int $apiVersion
522 public function testEntitiesGetCoreACLLimitingHookNoCheck(string $entity, int $apiVersion): void
{
523 $this->_apiversion
= $apiVersion;
524 $this->setupCoreACL();
525 //CRM_Core_Config::singleton()->userPermissionClass->permissions = array();
526 $this->setUpEntities($entity);
527 $this->hookClass
->setHook('civicrm_aclWhereClause', [
529 'aclWhereHookNoResults',
531 $result = $this->callAPISuccess($entity, 'get', [
532 'check_permissions' => 0,
533 'return' => 'contact_id',
535 $this->assertEquals(2, $result['count'], "failed with entity : $entity and api version $apiVersion");
539 * @dataProvider entities
540 * confirm that with check permissions we don't get entities
545 public function testEntitiesGetCoreACLLimitingCheck($entity, $apiVersion): void
{
546 $this->_apiversion
= $apiVersion;
547 $this->setupCoreACL();
548 $this->setUpEntities($entity);
549 $result = $this->callAPISuccess($entity, 'get', [
550 'check_permissions' => 1,
551 'return' => 'contact_id',
553 $this->assertEquals(0, $result['count'], "failed with entity : $entity and api version $apiVersion");
557 * @dataProvider entities
558 * Function tests that an empty where hook returns no results
560 * @param string $entity
561 * @param int $apiVersion
563 public function testEntityGetNoResultsHook(string $entity, int $apiVersion): void
{
564 $this->_apiversion
= $apiVersion;
565 $this->markTestIncomplete('hook acls only work with contacts so far');
566 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= [];
567 $this->setUpEntities($entity);
568 $this->hookClass
->setHook('civicrm_aclWhereClause', [
570 'aclWhereHookNoResults',
572 $result = $this->callAPISuccess($entity, 'get', [
573 'check_permission' => 1,
575 $this->assertEquals(0, $result['count']);
581 public static function entities(): array {
591 * @param string $entity
593 public function setUpEntities(string $entity): void
{
594 CRM_Core_DAO
::createTestObject(_civicrm_api3_get_BAO($entity), [], 2, 0);
595 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= [
597 'access CiviContribute',
599 'view event participants',
605 * Basic check that an un-permissioned call keeps working and permissioned call fails.
607 * @param int $version
609 * @dataProvider versionThreeAndFour
611 public function testGetActivityNoPermissions(int $version): void
{
612 $this->_apiversion
= $version;
613 $this->setPermissions([]);
614 $this->callAPISuccess('Activity', 'get');
615 $this->callAPIFailure('Activity', 'get', ['check_permissions' => 1]);
619 * View all activities is enough regardless of contact ACLs.
621 * @param int $version
623 * @throws \CRM_Core_Exception
624 * @throws \CiviCRM_API3_Exception
625 * @dataProvider versionThreeAndFour
627 public function testGetActivityViewAllActivitiesDoesntCutItAnymore(int $version): void
{
628 $this->_apiversion
= $version;
629 $activity = $this->activityCreate();
630 $this->setPermissions(['view all activities', 'access CiviCRM']);
631 $this->callAPISuccessGetCount('Activity', [
632 'check_permissions' => 1,
633 'id' => $activity['id'],
638 * View all activities is required unless id is passed in.
640 * @param int $version
642 * @dataProvider versionThreeAndFour
644 public function testGetActivityViewAllContactsEnoughWithoutID(int $version): void
{
645 $this->_apiversion
= $version;
646 $this->setPermissions(['view all contacts', 'access CiviCRM']);
647 $this->callAPISuccess('Activity', 'get', ['check_permissions' => 1]);
651 * Without view all activities contact level acls are used.
653 * @param int $version
655 * @throws \CRM_Core_Exception
656 * @throws \CiviCRM_API3_Exception
657 * @dataProvider versionThreeAndFour
659 public function testGetActivityViewAllContactsEnoughWIthID(int $version): void
{
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'],
670 * Check the error message is not a permission error.
672 * @param int $version
674 * @throws \CRM_Core_Exception
675 * @throws \CiviCRM_API3_Exception
676 * @dataProvider versionThreeAndFour
678 public function testGetActivityAccessCiviCRMEnough(int $version): void
{
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'],
686 ], 'Expected one Activity but found 0');
687 $this->callAPISuccessGetCount('Activity', [
688 'check_permissions' => 1,
689 'id' => $activity['id'],
694 * Check that component related activity filtering.
696 * If the contact does NOT have permission to 'view all contacts' but they DO have permission
697 * to view the contact in question they will only see the activities of components they have access too.
699 * (logically the same component limit should apply when they have access to view all too but....
700 * adding test for 'how it is at the moment.)
702 * @param int $version
704 * @throws \CRM_Core_Exception
705 * @throws \CiviCRM_API3_Exception
706 * @dataProvider versionThreeAndFour
708 public function testGetActivityCheckPermissionsByComponent(int $version): void
{
709 $this->_apiversion
= $version;
710 $activity = $this->activityCreate(['activity_type_id' => 'Contribution']);
711 $activity2 = $this->activityCreate(['activity_type_id' => 'Pledge Reminder']);
712 $this->hookClass
->setHook('civicrm_aclWhereClause', [
714 'aclWhereHookAllResults',
716 $this->setPermissions(['access CiviCRM', 'access CiviContribute']);
717 $this->callAPISuccessGetCount('Activity', [
718 'check_permissions' => 1,
719 'id' => ['IN' => [$activity['id'], $activity2['id']]],
721 $this->callAPISuccessGetCount('Activity', [
722 'check_permissions' => 1,
723 'id' => ['IN' => [$activity['id'], $activity2['id']]],
728 * Check that component related activity filtering works for CiviCase.
730 * @param int $version
732 * @throws \CRM_Core_Exception
733 * @throws \CiviCRM_API3_Exception
734 * @dataProvider versionThreeAndFour
736 public function testGetActivityCheckPermissionsByCaseComponent(int $version): void
{
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', [
743 'aclWhereHookAllResults',
745 $this->setPermissions([
747 'access CiviContribute',
748 'access all cases and activities',
750 $this->callAPISuccessGetCount('Activity', [
751 'check_permissions' => 1,
752 'id' => ['IN' => [$activity['id'], $activity2['id']]],
754 $this->callAPISuccessGetCount('Activity', [
755 'check_permissions' => 1,
756 'id' => ['IN' => [$activity['id'], $activity2['id']]],
761 * Check that activities can be retrieved by ACL.
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.
766 * @param int $version
768 * @throws \CRM_Core_Exception
769 * @throws \CiviCRM_API3_Exception
770 * @dataProvider versionThreeAndFour
772 public function testGetActivityByACL(int $version): void
{
773 $this->_apiversion
= $version;
774 $this->setPermissions(['access CiviCRM']);
775 $activity = $this->activityCreate();
777 $this->hookClass
->setHook('civicrm_aclWhereClause', [
779 'aclWhereHookAllResults',
781 $this->callAPISuccessGetCount('Activity', [
782 'check_permissions' => 1,
783 'id' => $activity['id'],
785 $this->callAPISuccessGetCount('Activity', [
786 'check_permissions' => 1,
787 'id' => $activity['id'],
792 * To leverage ACL permission to view an activity you must be able to see any
793 * of the contacts. FIXME: Api4
795 * @throws \CRM_Core_Exception|\CiviCRM_API3_Exception
797 public function testGetActivityByAclCannotViewAllContacts(): void
{
798 $activity = $this->activityCreate(['assignee_contact_id' => $this->individualCreate()]);
799 $contacts = $this->getActivityContacts($activity);
800 $this->setPermissions(['access CiviCRM']);
802 foreach ($contacts as $role => $contact_id) {
803 $this->allowedContactId
= $contact_id;
804 $this->hookClass
->setHook('civicrm_aclWhereClause', [
808 $this->cleanupCachedPermissions();
809 $result = $this->callAPISuccessGetSingle('Activity', [
810 'check_permissions' => 1,
811 'id' => $activity['id'],
815 'assignee_contact_id',
823 $roleKey = $roleName . '_id';
824 if ($role !== $roleKey) {
825 $this->assertTrue(empty($result[$roleKey]), "Only contact in $role is permissioned to be returned, not $roleKey");
828 $this->assertEquals([$contact_id], (array) $result[$roleKey]);
829 $this->assertNotEmpty($result[$roleName . '_name']);
836 * To leverage ACL permission to view an activity you must be able to see any of the contacts.
838 * @param int $version
840 * @throws \CRM_Core_Exception
841 * @throws \CiviCRM_API3_Exception
842 * @dataProvider versionThreeAndFour
844 public function testGetActivityByAclCannotViewAnyContacts(int $version): void
{
845 $this->_apiversion
= $version;
846 $activity = $this->activityCreate();
847 $contacts = $this->getActivityContacts($activity);
848 $this->setPermissions(['access CiviCRM']);
850 foreach ($contacts as $contact_id) {
851 $this->callAPIFailure('Activity', 'getsingle', [
852 'check_permissions' => 1,
853 'id' => $activity['id'],
859 * Check that if the source contact is deleted but we can view the others we can see the activity.
863 * @param int $version
865 * @dataProvider versionThreeAndFour
866 * @throws \CiviCRM_API3_Exception
867 * @throws \CRM_Core_Exception
869 public function testGetActivityACLSourceContactDeleted($version): void
{
870 $this->_apiversion
= $version;
871 $this->setPermissions(['access CiviCRM', 'delete contacts']);
872 $activity = $this->activityCreate();
873 $contacts = $this->getActivityContacts($activity);
875 $this->hookClass
->setHook('civicrm_aclWhereClause', [
877 'aclWhereHookAllResults',
879 $this->contactDelete($contacts['source_contact_id']);
880 $this->callAPISuccessGetCount('Activity', [
881 'check_permissions' => 1,
882 'id' => $activity['id'],
887 * Test get activities multiple ids with check permissions
889 * @see https://issues.civicrm.org/jira/browse/CRM-20441
891 * @param int $version
893 * @throws \CRM_Core_Exception
894 * @throws \CiviCRM_API3_Exception
895 * @dataProvider versionThreeAndFour
897 public function testActivitiesGetMultipleIdsCheckPermissions(int $version): void
{
898 $this->_apiversion
= $version;
899 $this->createLoggedInUser();
900 $activity = $this->activityCreate();
901 $activity2 = $this->activityCreate();
902 $this->setPermissions(['access CiviCRM']);
903 $this->hookClass
->setHook('civicrm_aclWhereClause', [
905 'aclWhereHookAllResults',
907 // Get activities associated with contact $this->_contactID.
909 'id' => ['IN' => [$activity['id'], $activity2['id']]],
910 'check_permissions' => TRUE,
912 $this->callAPISuccessGetCount('Activity', $params, 2);
916 * Test get activities multiple ids with check permissions
917 * Limit access to One contact
919 * @see https://issues.civicrm.org/jira/browse/CRM-20441
921 * @param int $version
923 * @throws \CRM_Core_Exception
924 * @throws \CiviCRM_API3_Exception
925 * @dataProvider versionThreeAndFour
927 public function testActivitiesGetMultipleIdsCheckPermissionsLimitedACL(int $version): void
{
928 $this->_apiversion
= $version;
929 $this->createLoggedInUser();
930 $activity = $this->activityCreate();
931 $contacts = $this->getActivityContacts($activity);
932 $this->setPermissions(['access CiviCRM']);
933 foreach ($contacts as $contact_id) {
934 $this->allowedContacts
[] = $contact_id;
936 $this->hookClass
->setHook('civicrm_aclWhereClause', [
938 'aclWhereMultipleContacts',
940 $contact2 = $this->individualCreate();
941 $activity2 = $this->activityCreate(['source_contact_id' => $contact2]);
942 // Get activities associated with contact $this->_contactID.
944 'id' => ['IN' => [$activity['id']]],
945 'check_permissions' => TRUE,
947 $result = $this->callAPISuccess('activity', 'get', $params);
948 $this->assertEquals(1, $result['count']);
949 $this->callAPIFailure('activity', 'getsingle', array_merge($params, [
951 'IN' => [$activity2['id']],
957 * Test get activities multiple ids with check permissions
959 * @see https://issues.civicrm.org/jira/browse/CRM-20441
961 * @param int $version
963 * @throws \CRM_Core_Exception
964 * @throws \CiviCRM_API3_Exception
965 * @dataProvider versionThreeAndFour
967 public function testActivitiesGetMultipleIdsCheckPermissionsNotIN(int $version): void
{
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', [
975 'aclWhereHookAllResults',
977 // Get activities associated with contact $this->_contactID.
979 'id' => ['NOT IN' => [$activity['id'], $activity2['id']]],
980 'check_permissions' => TRUE,
982 $result = $this->callAPISuccess('activity', 'get', $params);
983 $this->assertEquals(0, $result['count']);
987 * Get the contacts for the activity.
993 protected function getActivityContacts($activity): array {
996 $activityContacts = $this->callAPISuccess('ActivityContact', 'get', [
997 'activity_id' => $activity['id'],
998 'return' => ['record_type_id', 'contact_id'],
1001 $activityRecordTypes = $this->callAPISuccess('ActivityContact', 'getoptions', ['field' => 'record_type_id']);
1002 foreach ($activityContacts as $activityContact) {
1003 $type = $activityRecordTypes['values'][$activityContact['record_type_id']];
1005 case 'Activity Source':
1006 $contacts['source_contact_id'] = $activityContact['contact_id'];
1009 case 'Activity Targets':
1010 $contacts['target_contact_id'] = $activityContact['contact_id'];
1013 case 'Activity Assignees':
1014 $contacts['assignee_contact_id'] = $activityContact['contact_id'];
1023 * Test that the 'everyone' group can be given access to a contact.
1026 public function testGetACLEveryonePermittedEntity(): void
{
1027 $this->setupScenarioCoreACLEveryonePermittedToGroup();
1028 $this->callAPISuccessGetCount('Contact', [
1029 'id' => $this->scenarioIDs
['Contact']['permitted_contact'],
1030 'check_permissions' => 1,
1033 $this->callAPISuccessGetCount('Contact', [
1034 'id' => $this->scenarioIDs
['Contact']['non_permitted_contact'],
1035 'check_permissions' => 1,
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', [
1044 'first_name' => 'Anthony',
1045 'last_name' => 'Anderson',
1046 'contact_type' => 'Individual',
1047 'email' => 'anthony_anderson@civicrm.org',
1049 'check_permissions' => 0,
1051 $this->assertEquals(2, $dupes['count']);
1052 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['access CiviCRM'];
1054 $dupes = $this->callAPISuccess('Contact', 'duplicatecheck', [
1056 'first_name' => 'Anthony',
1057 'last_name' => 'Anderson',
1058 'contact_type' => 'Individual',
1059 'email' => 'anthony_anderson@civicrm.org',
1061 'check_permissions' => 1,
1063 $this->assertEquals(1, $dupes['count']);
1068 * @param int $version
1070 * @dataProvider versionThreeAndFour
1072 public function testContactGetViaJoin(int $version): void
{
1073 $this->_apiversion
= $version;
1074 $this->createLoggedInUser();
1075 $main = $this->individualCreate(['first_name' => 'Main']);
1076 $other = $this->individualCreate(['first_name' => 'Other'], 1);
1077 $tag1 = $this->tagCreate(['name' => 'tag_1', 'created_id' => $main])['id'];
1078 $tag2 = $this->tagCreate(['name' => 'tag_2', 'created_id' => $other])['id'];
1079 $this->setPermissions(['access CiviCRM']);
1080 $this->hookClass
->setHook('civicrm_aclWhereClause', [$this, 'aclWhereHookAllResults']);
1081 $createdFirstName = 'created_id.first_name';
1082 $result = $this->callAPISuccess('Tag', 'get', [
1083 'check_permissions' => 1,
1084 'return' => ['id', $createdFirstName],
1085 'id' => ['IN' => [$tag1, $tag2]],
1087 $this->assertEquals('Main', $result['values'][$tag1][$createdFirstName]);
1088 $this->assertEquals('Other', $result['values'][$tag2][$createdFirstName]);
1089 $this->allowedContactId
= $main;
1090 $this->hookClass
->setHook('civicrm_aclWhereClause', [$this, 'aclWhereOnlyOne']);
1091 $this->cleanupCachedPermissions();
1092 $result = $this->callAPISuccess('Tag', 'get', [
1093 'check_permissions' => 1,
1094 'return' => ['id', $createdFirstName],
1095 'id' => ['IN' => [$tag1, $tag2]],
1097 $this->assertEquals('Main', $result['values'][$tag1][$createdFirstName]);
1098 $this->assertEquals($tag2, $result['values'][$tag2]['id']);
1099 $this->assertFalse(isset($result['values'][$tag2][$createdFirstName]));
1103 * @throws \API_Exception
1105 public function testApi4CustomEntityACL(): void
{
1106 $group = 'test_group';
1107 $textField = 'text_field';
1109 CustomGroup
::create(FALSE)
1110 ->addValue('title', $group)
1111 ->addValue('extends', 'Contact')
1112 ->addValue('is_multiple', TRUE)
1113 ->addChain('field', CustomField
::create()
1114 ->addValue('label', $textField)
1115 ->addValue('custom_group_id', '$id')
1116 ->addValue('html_type', 'Text')
1117 ->addValue('data_type', 'String')
1121 $this->createLoggedInUser();
1122 $c1 = $this->individualCreate(['first_name' => 'C1']);
1123 $c2 = $this->individualCreate(['first_name' => 'C2', 'is_deleted' => 1], 1);
1125 CustomValue
::save($group)->setCheckPermissions(FALSE)
1126 ->addRecord(['entity_id' => $c1, $textField => '1'])
1127 ->addRecord(['entity_id' => $c2, $textField => '2'])
1130 $this->setPermissions(['access CiviCRM', 'view debug output', 'access all custom data']);
1131 $this->hookClass
->setHook('civicrm_aclWhereClause', [$this, 'aclWhereHookAllResults']);
1133 // Without "access deleted contacts" we won't see C2
1134 $vals = CustomValue
::get($group)->setDebug(TRUE)->execute();
1135 $this->assertCount(1, $vals);
1136 $this->assertEquals($c1, $vals[0]['entity_id']);
1138 $this->setPermissions(['access CiviCRM', 'access deleted contacts', 'view debug output', 'access all custom data']);
1139 $this->hookClass
->setHook('civicrm_aclWhereClause', [$this, 'aclWhereHookAllResults']);
1140 $this->cleanupCachedPermissions();
1142 $vals = CustomValue
::get($group)->execute();
1143 $this->assertCount(2, $vals);
1145 $this->allowedContactId
= $c2;
1146 $this->hookClass
->setHook('civicrm_aclWhereClause', [$this, 'aclWhereOnlyOne']);
1147 $this->cleanupCachedPermissions();
1149 $vals = CustomValue
::get($group)->addSelect('*', 'contact.first_name')->execute();
1150 $this->assertCount(1, $vals);
1151 $this->assertEquals($c2, $vals[0]['entity_id']);
1152 $this->assertEquals('C2', $vals[0]['contact.first_name']);
1154 $vals = Contact
::get()
1155 ->addJoin('Custom_' . $group . ' AS cf')
1156 ->addSelect('first_name', 'cf.' . $textField)
1157 ->addWhere('is_deleted', '=', TRUE)
1159 $this->assertCount(1, $vals);
1160 $this->assertEquals('C2', $vals[0]['first_name']);
1161 $this->assertEquals('2', $vals[0]['cf.' . $textField]);