CRM-17795 - Add aclWhere clause to api joins
authorColeman Watts <coleman@civicrm.org>
Fri, 8 Jan 2016 19:46:23 +0000 (14:46 -0500)
committerColeman Watts <coleman@civicrm.org>
Sat, 9 Jan 2016 01:51:03 +0000 (20:51 -0500)
CRM/Contact/BAO/Contact.php
CRM/Contact/BAO/Contact/Permission.php
CRM/Core/BAO/UFMatch.php
CRM/Core/DAO.php
Civi/API/SelectQuery.php

index c41b27b08d5d5367ce427a440a389ccecdd0575c..3bceb0df779bd7045036603f8b7922b56b9d865b 100644 (file)
@@ -3425,4 +3425,11 @@ LEFT JOIN civicrm_address add2 ON ( add1.master_id = add2.id )
     return TRUE;
   }
 
+  /**
+   * @inheritDoc
+   */
+  public function apiWhereClause($tableAlias) {
+    return CRM_Contact_BAO_Contact_Permission::cacheSubquery("`$tableAlias`.id");
+  }
+
 }
index 761b6572523c3c55a1ee1f81cac82bb649744179..5d63c51a56d5ec2441aa20c01494e33398979500 100644 (file)
@@ -229,7 +229,7 @@ AND    $operationClause LIMIT 1";
    *
    * @param string $contactIdField
    *   Full "table_name.field_name" for the field containing a contact id
-   * @return string
+   * @return string|NULL
    */
   public static function cacheSubquery($contactIdField) {
     $clauses = array();
@@ -241,7 +241,7 @@ AND    $operationClause LIMIT 1";
     if (!CRM_Core_Permission::check('access deleted contacts')) {
       $clauses[] = "$contactIdField NOT IN (SELECT id FROM civicrm_contact WHERE is_deleted = 1)";
     }
-    return $clauses ? implode(' AND ', $clauses) : '1';
+    return $clauses ? implode(' AND ', $clauses) : NULL;
   }
 
   /**
index 84bacc1c4c4af2aac5548402fa8a00943d6c1fde..03b018838d720c89545321328802f8738c95e54a 100644 (file)
@@ -633,4 +633,12 @@ AND    domain_id    = %4
     return $ufValues[$ufID];
   }
 
+  /**
+   * @inheritDoc
+   */
+  public function apiWhereClause($tableAlias) {
+    // Prevent default behavior of joining ACLs onto the contact_id field
+    return NULL;
+  }
+
 }
index a130e4c8f28f476e79b32faccb42e472000186c3..0759ba95f7e2d9b7adab2820557d90cc1b87638c 100644 (file)
@@ -2446,9 +2446,25 @@ SELECT contact_id
   }
 
   /**
+   * @deprecated
    * @param array $params
    */
   public function setApiFilter(&$params) {
   }
 
+  /**
+   * Generates a clause suitable for adding to WHERE or ON when doing an api.get for this entity
+   *
+   * @param string $tableAlias
+   * @return null|string
+   */
+  public function apiWhereClause($tableAlias) {
+    $fields = $this->fields();
+    $cidField = CRM_Utils_Array::value('contact_id', $fields);
+    if (CRM_Utils_Array::value('FKClassName', $cidField) == 'CRM_Contact_DAO_Contact') {
+      return CRM_Contact_BAO_Contact_Permission::cacheSubquery("`$tableAlias`.contact_id");
+    }
+    return NULL;
+  }
+
 }
index cbc1cb0bb339a12cea2cfff371932c6732413295..d6ad183b7fee07e7eaa7a7283dd94ffad25dbab1 100644 (file)
@@ -41,6 +41,10 @@ namespace Civi\API;
  */
 class SelectQuery {
 
+  /**
+   * @var \CRM_Core_DAO
+   */
+  protected $bao;
   /**
    * @var string
    */
@@ -69,33 +73,44 @@ class SelectQuery {
    * @var array
    */
   protected $entityFieldNames;
+  /**
+   * @var string|bool
+   */
+  protected $checkPermissions;
 
   /**
-   * @param string $dao_name
-   *   Name of DAO
+   * @param string $bao_name
+   *   Name of BAO
    * @param array $params
    *   As passed into api get function.
    * @param bool $isFillUniqueFields
    *   Do we need to ensure unique fields continue to be populated for this api? (backward compatibility).
    */
-  public function __construct($dao_name, $params, $isFillUniqueFields) {
-    /* @var \CRM_Core_DAO $dao */
-    $dao = new $dao_name();
-    $this->entity = _civicrm_api_get_entity_name_from_dao($dao);
+  public function __construct($bao_name, $params, $isFillUniqueFields) {
+    $this->bao = new $bao_name();
+    $this->entity = _civicrm_api_get_entity_name_from_dao($this->bao);
     $this->params = $params;
     $this->isFillUniqueFields = $isFillUniqueFields;
+    $this->checkPermissions = \CRM_Utils_Array::value('check_permissions', $this->params, FALSE);
     $this->options = _civicrm_api3_get_options_from_params($this->params);
 
-    $this->entityFieldNames = _civicrm_api3_field_names(_civicrm_api3_build_fields_array($dao));
+    $this->entityFieldNames = _civicrm_api3_field_names(_civicrm_api3_build_fields_array($this->bao));
     // Call this function directly instead of using the api wrapper to force unique field names off
     require_once 'api/v3/Generic.php';
     $apiSpec = \civicrm_api3_generic_getfields(array('entity' => $this->entity, 'version' => 3, 'params' => array('action' => 'get')), FALSE);
     $this->apiFieldSpec = $apiSpec['values'];
 
-    $this->query = \CRM_Utils_SQL_Select::from($dao->tableName() . " a");
-    $dao->free();
+    $this->query = \CRM_Utils_SQL_Select::from($this->bao->tableName() . " a");
   }
 
+  /**
+   * Build & execute the query and return results array
+   *
+   * @return array
+   * @throws \API_Exception
+   * @throws \CRM_Core_Exception
+   * @throws \Exception
+   */
   public function run() {
     // $select_fields maps column names to the field names of the result values.
     $select_fields = $custom_fields = array();
@@ -273,7 +288,8 @@ class SelectQuery {
     }
 
     // ACLs
-    $this->addAclClause();
+    $this->query->where($this->getAclClause('a'));
+    $this->bao->free();
 
     $result_entities = array();
     $result_dao = \CRM_Core_DAO::executeQuery($this->query->toSQL());
@@ -319,9 +335,12 @@ class SelectQuery {
    *
    * Adds one or more joins to the query to make this field available for use in a clause.
    *
+   * Enforces permissions at the api level and by appending the acl clause for that entity to the join.
+   *
    * @param $fkFieldName
    * @return array|null
    *   Returns the table and field name for adding this field to a SELECT or WHERE clause
+   * @throws \API_Exception
    */
   private function addFkField($fkFieldName) {
     $stack = explode('.', $fkFieldName);
@@ -358,9 +377,16 @@ class SelectQuery {
         return NULL;
       }
       $fkTable = \CRM_Core_DAO_AllCoreTables::getTableForClass($fkField['FKClassName']);
-      $tableAlias = "{$fk}_to_$fkTable";
+      $tableAlias = implode('_to_', array_slice($stack, 0, $depth)) . "_to_$fkTable";
+      $joinClause = "LEFT JOIN $fkTable $tableAlias ON $prev.$fk = $tableAlias.id";
 
-      $this->query->join($tableAlias, "LEFT JOIN $fkTable $tableAlias ON $prev.$fk = $tableAlias.id");
+      // Add acl condition
+      $joinCondition = $this->getAclClause($tableAlias, $fkField['FKClassName']);
+      if ($joinCondition !== NULL) {
+        $joinClause .= " AND $joinCondition";
+      }
+
+      $this->query->join($tableAlias, $joinClause);
 
       if (strpos($fieldName, 'custom_') === 0) {
         list($tableAlias, $fieldName) = $this->addCustomField($fieldInfo, $tableAlias);
@@ -427,14 +453,14 @@ class SelectQuery {
    * @return bool
    */
   private function checkPermissionToJoin($entity, $fieldStack) {
-    if (empty($this->params['check_permissions'])) {
+    if (!$this->checkPermissions) {
       return TRUE;
     }
     // Build an array of params that relate to the joined entity
     $params = array(
       'version' => 3,
       'return' => array(),
-      'check_permissions' => \CRM_Utils_Array::value('check_permissions', $this->params, FALSE),
+      'check_permissions' => $this->checkPermissions,
     );
     $prefix = implode('.', $fieldStack) . '.';
     $len = strlen($prefix);
@@ -453,15 +479,24 @@ class SelectQuery {
   }
 
   /**
-   * If this entity has a `contact_id` field, add appropriate acl clause
+   * Get acl clause for an entity
+   *
+   * @param string $tableAlias
+   * @param \CRM_Core_DAO $bao
+   * @return null|string
    */
-  private function addAclClause() {
-    if (in_array('contact_id', $this->entityFieldNames)) {
-      $clause = \CRM_Contact_BAO_Contact_Permission::cacheSubquery('a.contact_id');
-      if ($clause !== '1') {
-        $this->query->where($clause);
-      }
+  private function getAclClause($tableAlias, $bao = NULL) {
+    if (!$this->checkPermissions) {
+      return NULL;
+    }
+    if (!$bao) {
+      $bao = $this->bao;
+    }
+    else {
+      $baoName = str_replace('_DAO_', '_BAO_', $bao);
+      $bao = new $baoName();
     }
+    return $bao->apiWhereClause($tableAlias);
   }
 
 }