Merge pull request #7797 from JKingsnorth/CRM-17977
[civicrm-core.git] / tests / phpunit / api / v3 / ACLPermissionTest.php
index f74de1077bd744a33c53dd5d7b1fa08ad14578a6..d38085ca80166b8a37c29f9751eee9562692349f 100644 (file)
@@ -3,7 +3,7 @@
  +--------------------------------------------------------------------+
  | 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();
@@ -64,6 +64,8 @@ class api_v3_ACLPermissionTest extends CiviUnitTestCase {
       'civicrm_contribution',
       'civicrm_participant',
       'civicrm_uf_match',
+      'civicrm_activity',
+      'civicrm_activity_contact',
     );
     $this->quickCleanup($tablesToTruncate);
     $config = CRM_Core_Config::singleton();
@@ -102,7 +104,6 @@ class api_v3_ACLPermissionTest extends CiviUnitTestCase {
    * 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');
@@ -112,6 +113,40 @@ class api_v3_ACLPermissionTest extends CiviUnitTestCase {
     ));
   }
 
+  /**
+   * 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.
    */
@@ -315,7 +350,6 @@ class api_v3_ACLPermissionTest extends CiviUnitTestCase {
    * @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(
@@ -325,7 +359,6 @@ class api_v3_ACLPermissionTest extends CiviUnitTestCase {
     $this->assertEquals(0, $result['count']);
   }
 
-
   /**
    * @dataProvider entities
    * Function tests that an empty where hook returns no results
@@ -367,17 +400,35 @@ class api_v3_ACLPermissionTest extends CiviUnitTestCase {
 
   /**
    * 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
@@ -385,12 +436,12 @@ class api_v3_ACLPermissionTest extends CiviUnitTestCase {
    * @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
@@ -398,8 +449,136 @@ class api_v3_ACLPermissionTest extends CiviUnitTestCase {
    * @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;
   }
 
 }