+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
- | Copyright CiviCRM LLC (c) 2004-2015 |
+ | Copyright CiviCRM LLC (c) 2004-2016 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
+--------------------------------------------------------------------+
*/
-require_once 'CiviTest/CiviUnitTestCase.php';
-
/**
* This class is intended to test ACL permission using the multisite module
*
* @package CiviCRM_APIv3
* @subpackage API_Contact
+ * @group headless
*/
class api_v3_ACLPermissionTest extends CiviUnitTestCase {
protected $_apiversion = 3;
public $DBResetRequired = FALSE;
protected $_entity;
+ protected $allowedContactId = 0;
public function setUp() {
parent::setUp();
'civicrm_contribution',
'civicrm_participant',
'civicrm_uf_match',
+ 'civicrm_activity',
+ 'civicrm_activity_contact',
);
$this->quickCleanup($tablesToTruncate);
$config = CRM_Core_Config::singleton();
* Function tests that a user with "edit my contact" can edit themselves.
*/
public function testContactEditHookWithEditMyContact() {
- $this->markTestIncomplete('api acls only work with contact get so far');
$cid = $this->createLoggedInUser();
$this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookNoResults'));
CRM_Core_Config::singleton()->userPermissionClass->permissions = array('access CiviCRM', 'edit my contact');
));
}
+ /**
+ * Ensure contact permissions extend to related entities like email
+ */
+ public function testRelatedEntityPermissions() {
+ $this->createLoggedInUser();
+ $disallowedContact = $this->individualCreate(array(), 0);
+ $this->allowedContactId = $this->individualCreate(array(), 1);
+ $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereOnlyOne'));
+ CRM_Core_Config::singleton()->userPermissionClass->permissions = array('access CiviCRM');
+ $testEntities = array(
+ 'Email' => array('email' => 'null@nothing', 'location_type_id' => 1),
+ 'Phone' => array('phone' => '123456', 'location_type_id' => 1),
+ 'IM' => array('name' => 'hello', 'location_type_id' => 1),
+ 'Website' => array('url' => 'http://test'),
+ 'Address' => array('street_address' => '123 Sesame St.', 'location_type_id' => 1),
+ );
+ foreach ($testEntities as $entity => $params) {
+ $params += array(
+ 'contact_id' => $disallowedContact,
+ 'check_permissions' => 1,
+ );
+ // We should be prevented from getting or creating entities for a contact we don't have permission for
+ $this->callAPIFailure($entity, 'create', $params);
+ $results = $this->callAPISuccess($entity, 'get', array('contact_id' => $disallowedContact, 'check_permissions' => 1));
+ $this->assertEquals(0, $results['count']);
+
+ // We should be allowed to create and get for contacts we do have permission on
+ $params['contact_id'] = $this->allowedContactId;
+ $this->callAPISuccess($entity, 'create', $params);
+ $results = $this->callAPISuccess($entity, 'get', array('contact_id' => $this->allowedContactId, 'check_permissions' => 1));
+ $this->assertGreaterThan(0, $results['count']);
+ }
+ }
+
/**
* Function tests all results are returned.
*/
* @throws \PHPUnit_Framework_IncompleteTestError
*/
public function testEntitiesGetCoreACLLimitingCheck($entity) {
- $this->markTestIncomplete('this does not work in 4.4 but can be enabled in 4.5 or a security release of 4.4 including the important security fix CRM-14877');
$this->setupCoreACL();
$this->setUpEntities($entity);
$result = $this->callAPISuccess($entity, 'get', array(
$this->assertEquals(0, $result['count']);
}
-
/**
* @dataProvider entities
* Function tests that an empty where hook returns no results
/**
* No results returned.
- * @param $type
- * @param $tables
- * @param $whereTables
- * @param $contactID
- * @param $where
+ *
+ * @implements CRM_Utils_Hook::aclWhereClause
+ *
+ * @param string $type
+ * @param array $tables
+ * @param array $whereTables
+ * @param int $contactID
+ * @param string $where
*/
public function aclWhereHookNoResults($type, &$tables, &$whereTables, &$contactID, &$where) {
}
/**
* All results returned.
+ *
+ * @implements CRM_Utils_Hook::aclWhereClause
+ *
+ * @param string $type
+ * @param array $tables
+ * @param array $whereTables
+ * @param int $contactID
+ * @param string $where
+ */
+ public function aclWhereHookAllResults($type, &$tables, &$whereTables, &$contactID, &$where) {
+ $where = " (1) ";
+ }
+
+ /**
+ * All but first results returned.
* @implements CRM_Utils_Hook::aclWhereClause
* @param $type
* @param $tables
* @param $contactID
* @param $where
*/
- public function aclWhereHookAllResults($type, &$tables, &$whereTables, &$contactID, &$where) {
- $where = " (1) ";
+ public function aclWhereOnlySecond($type, &$tables, &$whereTables, &$contactID, &$where) {
+ $where = " contact_a.id > 1";
}
/**
- * Full results returned.
+ * Only specified contact returned.
* @implements CRM_Utils_Hook::aclWhereClause
* @param $type
* @param $tables
* @param $contactID
* @param $where
*/
- public function aclWhereOnlySecond($type, &$tables, &$whereTables, &$contactID, &$where) {
- $where = " contact_a.id > 1";
+ public function aclWhereOnlyOne($type, &$tables, &$whereTables, &$contactID, &$where) {
+ $where = " contact_a.id = " . $this->allowedContactId;
+ }
+
+ /**
+ * Basic check that an unpermissioned call keeps working and permissioned call fails.
+ */
+ public function testGetActivityNoPermissions() {
+ $this->setPermissions(array());
+ $this->callAPISuccess('Activity', 'get', array());
+ $this->callAPIFailure('Activity', 'get', array('check_permissions' => 1));
+ }
+
+ /**
+ * View all activities is enough regardless of contact ACLs.
+ */
+ public function testGetActivityViewAllActivitiesEnoughWithOrWithoutID() {
+ $activity = $this->activityCreate();
+ $this->setPermissions(array('view all activities', 'access CiviCRM'));
+ $this->callAPISuccess('Activity', 'getsingle', array('check_permissions' => 1, 'id' => $activity['id']));
+ $this->callAPISuccess('Activity', 'getsingle', array('check_permissions' => 1));
+ }
+
+ /**
+ * View all activities is required unless id is passed in.
+ */
+ public function testGetActivityViewAllContactsNotEnoughWIthoutID() {
+ $this->setPermissions(array('view all contacts', 'access CiviCRM'));
+ $this->callAPIFailure('Activity', 'get', array('check_permissions' => 1));
+ }
+
+ /**
+ * View all activities is required unless id is passed in, in which case ACLs are used.
+ */
+ public function testGetActivityViewAllContactsEnoughWIthID() {
+ $activity = $this->activityCreate();
+ $this->setPermissions(array('view all contacts', 'access CiviCRM'));
+ $this->callAPISuccess('Activity', 'getsingle', array('check_permissions' => 1, 'id' => $activity['id']));
+ }
+
+ /**
+ * View all activities is required unless id is passed in, in which case ACLs are used.
+ */
+ public function testGetActivityAccessCiviCRMNotEnough() {
+ $activity = $this->activityCreate();
+ $this->setPermissions(array('access CiviCRM'));
+ $this->callAPIFailure('Activity', 'getsingle', array('check_permissions' => 1, 'id' => $activity['id']));
+ }
+
+ /**
+ * Check that activities can be retrieved by ACL.
+ *
+ * The activities api applies ACLs in a very limited circumstance, if id is passed in.
+ * Otherwise it sticks with the blunt original permissions.
+ */
+ public function testGetActivityByACL() {
+ $this->setPermissions(array('access CiviCRM'));
+ $activity = $this->activityCreate();
+
+ $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
+ $this->callAPISuccess('Activity', 'getsingle', array('check_permissions' => 1, 'id' => $activity['id']));
+ }
+
+ /**
+ * To leverage ACL permission to view an activity you must be able to see all of the contacts.
+ */
+ public function testGetActivityByAclCannotViewAllContacts() {
+ $activity = $this->activityCreate();
+ $contacts = $this->getActivityContacts($activity);
+ $this->setPermissions(array('access CiviCRM'));
+
+ foreach ($contacts as $contact_id) {
+ $this->allowedContactId = $contact_id;
+ $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereOnlyOne'));
+ $this->callAPIFailure('Activity', 'getsingle', array('check_permissions' => 1, 'id' => $activity['id']));
+ }
+ }
+
+ /**
+ * Check that if the source contact is deleted but we can view the others we can see the activity.
+ *
+ * CRM-18409.
+ *
+ * @throws \CRM_Core_Exception
+ */
+ public function testGetActivityACLSourceContactDeleted() {
+ $this->setPermissions(array('access CiviCRM', 'delete contacts'));
+ $activity = $this->activityCreate();
+ $contacts = $this->getActivityContacts($activity);
+
+ $this->hookClass->setHook('civicrm_aclWhereClause', array($this, 'aclWhereHookAllResults'));
+ $this->contactDelete($contacts['source_contact_id']);
+ $this->callAPISuccess('Activity', 'getsingle', array('check_permissions' => 1, 'id' => $activity['id']));
+ }
+
+ /**
+ * Get the contacts for the activity.
+ *
+ * @param $activity
+ *
+ * @return array
+ * @throws \CRM_Core_Exception
+ */
+ protected function getActivityContacts($activity) {
+ $contacts = array();
+
+ $activityContacts = $this->callAPISuccess('ActivityContact', 'get', array(
+ 'activity_id' => $activity['id'],
+ )
+ );
+
+ $activityRecordTypes = $this->callAPISuccess('ActivityContact', 'getoptions', array('field' => 'record_type_id'));
+ foreach ($activityContacts['values'] as $activityContact) {
+ $type = $activityRecordTypes['values'][$activityContact['record_type_id']];
+ switch ($type) {
+ case 'Activity Source':
+ $contacts['source_contact_id'] = $activityContact['contact_id'];
+ break;
+
+ case 'Activity Targets':
+ $contacts['target_contact_id'] = $activityContact['contact_id'];
+ break;
+
+ case 'Activity Assignees':
+ $contacts['assignee_contact_id'] = $activityContact['contact_id'];
+ break;
+
+ }
+ }
+ return $contacts;
}
}