3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2016 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
29 * This class is intended to test ACL permission using the multisite module
31 * @package CiviCRM_APIv3
32 * @subpackage API_Contact
35 class api_v3_ACLPermissionTest
extends CiviUnitTestCase
{
36 protected $_apiversion = 3;
37 public $DBResetRequired = FALSE;
39 protected $allowedContactId = 0;
41 public function setUp() {
43 $baoObj = new CRM_Core_DAO();
44 $baoObj->createTestObject('CRM_Pledge_BAO_Pledge', array(), 1, 0);
45 $baoObj->createTestObject('CRM_Core_BAO_Phone', array(), 1, 0);
46 $config = CRM_Core_Config
::singleton();
47 $config->userPermissionClass
->permissions
= array();
52 * @see CiviUnitTestCase::tearDown()
54 public function tearDown() {
55 CRM_Utils_Hook
::singleton()->reset();
56 $tablesToTruncate = array(
58 'civicrm_group_contact',
62 'civicrm_acl_entity_role',
63 'civicrm_acl_contact_cache',
64 'civicrm_contribution',
65 'civicrm_participant',
68 'civicrm_activity_contact',
70 $this->quickCleanup($tablesToTruncate);
71 $config = CRM_Core_Config
::singleton();
72 unset($config->userPermissionClass
->permissions
);
76 * Function tests that an empty where hook returns no results.
78 public function testContactGetNoResultsHook() {
79 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookNoResults'));
80 $result = $this->callAPISuccess('contact', 'get', array(
81 'check_permissions' => 1,
82 'return' => 'display_name',
84 $this->assertEquals(0, $result['count']);
88 * Function tests that an empty where hook returns exactly 1 result with "view my contact".
90 * CRM-16512 caused contacts with Edit my contact to be able to view all records.
92 public function testContactGetOneResultHookWithViewMyContact() {
93 $this->createLoggedInUser();
94 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookNoResults'));
95 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= array('access CiviCRM', 'view my contact');
96 $result = $this->callAPISuccess('contact', 'get', array(
97 'check_permissions' => 1,
98 'return' => 'display_name',
100 $this->assertEquals(1, $result['count']);
104 * Function tests that a user with "edit my contact" can edit themselves.
106 public function testContactEditHookWithEditMyContact() {
107 $cid = $this->createLoggedInUser();
108 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookNoResults'));
109 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= array('access CiviCRM', 'edit my contact');
110 $this->callAPISuccess('contact', 'create', array(
111 'check_permissions' => 1,
117 * Ensure contact permissions extend to related entities like email
119 public function testRelatedEntityPermissions() {
120 $this->createLoggedInUser();
121 $disallowedContact = $this->individualCreate(array(), 0);
122 $this->allowedContactId
= $this->individualCreate(array(), 1);
123 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereOnlyOne'));
124 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= array('access CiviCRM');
125 $testEntities = array(
126 'Email' => array('email' => 'null@nothing', 'location_type_id' => 1),
127 'Phone' => array('phone' => '123456', 'location_type_id' => 1),
128 'IM' => array('name' => 'hello', 'location_type_id' => 1),
129 'Website' => array('url' => 'http://test'),
130 'Address' => array('street_address' => '123 Sesame St.', 'location_type_id' => 1),
132 foreach ($testEntities as $entity => $params) {
134 'contact_id' => $disallowedContact,
135 'check_permissions' => 1,
137 // We should be prevented from getting or creating entities for a contact we don't have permission for
138 $this->callAPIFailure($entity, 'create', $params);
139 $results = $this->callAPISuccess($entity, 'get', array('contact_id' => $disallowedContact, 'check_permissions' => 1));
140 $this->assertEquals(0, $results['count']);
142 // We should be allowed to create and get for contacts we do have permission on
143 $params['contact_id'] = $this->allowedContactId
;
144 $this->callAPISuccess($entity, 'create', $params);
145 $results = $this->callAPISuccess($entity, 'get', array('contact_id' => $this->allowedContactId
, 'check_permissions' => 1));
146 $this->assertGreaterThan(0, $results['count']);
151 * Function tests all results are returned.
153 public function testContactGetAllResultsHook() {
154 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
155 $result = $this->callAPISuccess('contact', 'get', array(
156 'check_permissions' => 1,
157 'return' => 'display_name',
160 $this->assertEquals(2, $result['count']);
164 * Function tests that deleted contacts are not returned.
166 public function testContactGetPermissionHookNoDeleted() {
167 $this->callAPISuccess('contact', 'create', array('id' => 2, 'is_deleted' => 1));
168 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
169 $result = $this->callAPISuccess('contact', 'get', array(
170 'check_permissions' => 1,
171 'return' => 'display_name',
173 $this->assertEquals(1, $result['count']);
177 * Test permissions limited by hook.
179 public function testContactGetHookLimitingHook() {
180 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereOnlySecond'));
182 $result = $this->callAPISuccess('contact', 'get', array(
183 'check_permissions' => 1,
184 'return' => 'display_name',
186 $this->assertEquals(1, $result['count']);
190 * Confirm that without check permissions we still get 2 contacts returned.
192 public function testContactGetHookLimitingHookDontCheck() {
193 $result = $this->callAPISuccess('contact', 'get', array(
194 'check_permissions' => 0,
195 'return' => 'display_name',
197 $this->assertEquals(2, $result['count']);
201 * Check that id works as a filter.
203 public function testContactGetIDFilter() {
204 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
205 $result = $this->callAPISuccess('contact', 'get', array(
208 'check_permissions' => 1,
211 $this->assertEquals(1, $result['count']);
212 $this->assertEquals(2, $result['id']);
216 * Check that address IS returned.
218 public function testContactGetAddressReturned() {
219 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereOnlySecond'));
220 $fullresult = $this->callAPISuccess('contact', 'get', array(
223 //return doesn't work for all keys - can't fix that here so let's skip ...
224 //prefix & suffix are inconsistent due to CRM-7929
225 // unsure about others but return doesn't work on them
226 $elementsReturnDoesntSupport = array(
237 $expectedReturnElements = array_diff(array_keys($fullresult['values'][0]), $elementsReturnDoesntSupport);
238 $result = $this->callAPISuccess('contact', 'get', array(
239 'check_permissions' => 1,
240 'return' => $expectedReturnElements,
243 $this->assertEquals(1, $result['count']);
244 foreach ($expectedReturnElements as $element) {
245 $this->assertArrayHasKey($element, $result['values'][0]);
250 * Check that pledge IS not returned.
252 public function testContactGetPledgeIDNotReturned() {
253 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
254 $this->callAPISuccess('contact', 'get', array(
257 $result = $this->callAPISuccess('contact', 'get', array(
258 'check_permissions' => 1,
259 'return' => 'pledge_id',
262 $this->assertArrayNotHasKey('pledge_id', $result['values'][0]);
266 * Check that pledge IS not an allowable filter.
268 public function testContactGetPledgeIDNotFiltered() {
269 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
270 $this->callAPISuccess('contact', 'get', array(
273 $result = $this->callAPISuccess('contact', 'get', array(
274 'check_permissions' => 1,
278 $this->assertEquals(2, $result['count']);
282 * Check that chaining doesn't bypass permissions
284 public function testContactGetPledgeNotChainable() {
285 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereOnlySecond'));
286 $this->callAPISuccess('contact', 'get', array(
289 $this->callAPIFailure('contact', 'get', array(
290 'check_permissions' => 1,
291 'api.pledge.get' => 1,
294 'Error in call to pledge_get : API permission check failed for pledge/get call; missing permission: access CiviCRM.'
298 public function setupCoreACL() {
299 $this->createLoggedInUser();
300 $this->_permissionedDisabledGroup
= $this->groupCreate(array(
301 'title' => 'pick-me-disabled',
303 'name' => 'pick-me-disabled',
305 $this->_permissionedGroup
= $this->groupCreate(array(
306 'title' => 'pick-me-active',
308 'name' => 'pick-me-active',
314 * @dataProvider entities
315 * confirm that without check permissions we still get 2 contacts returned
318 public function testEntitiesGetHookLimitingHookNoCheck($entity) {
319 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= array();
320 $this->setUpEntities($entity);
321 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookNoResults'));
322 $result = $this->callAPISuccess($entity, 'get', array(
323 'check_permissions' => 0,
324 'return' => 'contact_id',
326 $this->assertEquals(2, $result['count']);
330 * @dataProvider entities
331 * confirm that without check permissions we still get 2 entities returned
334 public function testEntitiesGetCoreACLLimitingHookNoCheck($entity) {
335 $this->setupCoreACL();
336 //CRM_Core_Config::singleton()->userPermissionClass->permissions = array();
337 $this->setUpEntities($entity);
338 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookNoResults'));
339 $result = $this->callAPISuccess($entity, 'get', array(
340 'check_permissions' => 0,
341 'return' => 'contact_id',
343 $this->assertEquals(2, $result['count']);
347 * @dataProvider entities
348 * confirm that with check permissions we don't get entities
350 * @throws \PHPUnit_Framework_IncompleteTestError
352 public function testEntitiesGetCoreACLLimitingCheck($entity) {
353 $this->setupCoreACL();
354 $this->setUpEntities($entity);
355 $result = $this->callAPISuccess($entity, 'get', array(
356 'check_permissions' => 1,
357 'return' => 'contact_id',
359 $this->assertEquals(0, $result['count']);
363 * @dataProvider entities
364 * Function tests that an empty where hook returns no results
365 * @param string $entity
366 * @throws \PHPUnit_Framework_IncompleteTestError
368 public function testEntityGetNoResultsHook($entity) {
369 $this->markTestIncomplete('hook acls only work with contacts so far');
370 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= array();
371 $this->setUpEntities($entity);
372 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookNoResults'));
373 $result = $this->callAPISuccess($entity, 'get', array(
374 'check_permission' => 1,
376 $this->assertEquals(0, $result['count']);
382 public static function entities() {
383 return array(array('contribution'), array('participant'));// @todo array('pledge' => 'pledge')
390 public function setUpEntities($entity) {
391 $baoObj = new CRM_Core_DAO();
392 $baoObj->createTestObject(_civicrm_api3_get_BAO($entity), array(), 2, 0);
393 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= array(
395 'access CiviContribute',
397 'view event participants',
402 * No results returned.
404 * @implements CRM_Utils_Hook::aclWhereClause
406 * @param string $type
407 * @param array $tables
408 * @param array $whereTables
409 * @param int $contactID
410 * @param string $where
412 public function aclWhereHookNoResults($type, &$tables, &$whereTables, &$contactID, &$where) {
416 * All results returned.
418 * @implements CRM_Utils_Hook::aclWhereClause
420 * @param string $type
421 * @param array $tables
422 * @param array $whereTables
423 * @param int $contactID
424 * @param string $where
426 public function aclWhereHookAllResults($type, &$tables, &$whereTables, &$contactID, &$where) {
431 * All but first results returned.
432 * @implements CRM_Utils_Hook::aclWhereClause
435 * @param $whereTables
439 public function aclWhereOnlySecond($type, &$tables, &$whereTables, &$contactID, &$where) {
440 $where = " contact_a.id > 1";
444 * Only specified contact returned.
445 * @implements CRM_Utils_Hook::aclWhereClause
448 * @param $whereTables
452 public function aclWhereOnlyOne($type, &$tables, &$whereTables, &$contactID, &$where) {
453 $where = " contact_a.id = " . $this->allowedContactId
;
457 * Basic check that an unpermissioned call keeps working and permissioned call fails.
459 public function testGetActivityNoPermissions() {
460 $this->setPermissions(array());
461 $this->callAPISuccess('Activity', 'get', array());
462 $this->callAPIFailure('Activity', 'get', array('check_permissions' => 1));
466 * View all activities is enough regardless of contact ACLs.
468 public function testGetActivityViewAllActivitiesEnoughWithOrWithoutID() {
469 $activity = $this->activityCreate();
470 $this->setPermissions(array('view all activities', 'access CiviCRM'));
471 $this->callAPISuccess('Activity', 'getsingle', array('check_permissions' => 1, 'id' => $activity['id']));
472 $this->callAPISuccess('Activity', 'getsingle', array('check_permissions' => 1));
476 * View all activities is required unless id is passed in.
478 public function testGetActivityViewAllContactsNotEnoughWIthoutID() {
479 $this->setPermissions(array('view all contacts', 'access CiviCRM'));
480 $this->callAPIFailure('Activity', 'get', array('check_permissions' => 1));
484 * View all activities is required unless id is passed in, in which case ACLs are used.
486 public function testGetActivityViewAllContactsEnoughWIthID() {
487 $activity = $this->activityCreate();
488 $this->setPermissions(array('view all contacts', 'access CiviCRM'));
489 $this->callAPISuccess('Activity', 'getsingle', array('check_permissions' => 1, 'id' => $activity['id']));
493 * View all activities is required unless id is passed in, in which case ACLs are used.
495 public function testGetActivityAccessCiviCRMNotEnough() {
496 $activity = $this->activityCreate();
497 $this->setPermissions(array('access CiviCRM'));
498 $this->callAPIFailure('Activity', 'getsingle', array('check_permissions' => 1, 'id' => $activity['id']));
502 * Check that activities can be retrieved by ACL.
504 * The activities api applies ACLs in a very limited circumstance, if id is passed in.
505 * Otherwise it sticks with the blunt original permissions.
507 public function testGetActivityByACL() {
508 $this->setPermissions(array('access CiviCRM'));
509 $activity = $this->activityCreate();
511 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
512 $this->callAPISuccess('Activity', 'getsingle', array('check_permissions' => 1, 'id' => $activity['id']));
516 * To leverage ACL permission to view an activity you must be able to see all of the contacts.
518 public function testGetActivityByAclCannotViewAllContacts() {
519 $activity = $this->activityCreate();
520 $contacts = $this->getActivityContacts($activity);
521 $this->setPermissions(array('access CiviCRM'));
523 foreach ($contacts as $contact_id) {
524 $this->allowedContactId
= $contact_id;
525 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereOnlyOne'));
526 $this->callAPIFailure('Activity', 'getsingle', array('check_permissions' => 1, 'id' => $activity['id']));
531 * Check that if the source contact is deleted but we can view the others we can see the activity.
535 * @throws \CRM_Core_Exception
537 public function testGetActivityACLSourceContactDeleted() {
538 $this->setPermissions(array('access CiviCRM', 'delete contacts'));
539 $activity = $this->activityCreate();
540 $contacts = $this->getActivityContacts($activity);
542 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
543 $this->contactDelete($contacts['source_contact_id']);
544 $this->callAPISuccess('Activity', 'getsingle', array('check_permissions' => 1, 'id' => $activity['id']));
548 * Get the contacts for the activity.
553 * @throws \CRM_Core_Exception
555 protected function getActivityContacts($activity) {
558 $activityContacts = $this->callAPISuccess('ActivityContact', 'get', array(
559 'activity_id' => $activity['id'],
563 $activityRecordTypes = $this->callAPISuccess('ActivityContact', 'getoptions', array('field' => 'record_type_id'));
564 foreach ($activityContacts['values'] as $activityContact) {
565 $type = $activityRecordTypes['values'][$activityContact['record_type_id']];
567 case 'Activity Source':
568 $contacts['source_contact_id'] = $activityContact['contact_id'];
571 case 'Activity Targets':
572 $contacts['target_contact_id'] = $activityContact['contact_id'];
575 case 'Activity Assignees':
576 $contacts['assignee_contact_id'] = $activityContact['contact_id'];