Ian province abbreviation patch - issue 724
[civicrm-core.git] / Civi / API / Subscriber / DynamicFKAuthorization.php
index 4aeef4c314cd40b88525b394b59dd900e5be4520..1d7a0dd3835826bd1afcc70b3966fa9760d8fc2b 100644 (file)
@@ -1,9 +1,9 @@
 <?php
 /*
  +--------------------------------------------------------------------+
- | CiviCRM version 4.6                                                |
+ | CiviCRM version 4.7                                                |
  +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC (c) 2004-2014                                |
+ | Copyright CiviCRM LLC (c) 2004-2015                                |
  +--------------------------------------------------------------------+
  | This file is a part of CiviCRM.                                    |
  |                                                                    |
@@ -39,7 +39,8 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  * imitating the permissions of the Mailing.
  *
  * Note: This enforces a constraint: all matching API calls must define
- * either "id" (e.g. for the file) or "entity_table".
+ * "id" (e.g. for the file) or "entity_table+entity_id" or
+ * "field_name+entity_id".
  *
  * Note: The permission guard does not exactly authorize the request, but it
  * may veto authorization.
@@ -59,8 +60,10 @@ class DynamicFKAuthorization implements EventSubscriberInterface {
 
   /**
    * @var \Civi\API\Kernel
+   *
+   * Treat as private. Marked public due to PHP 5.3-compatibility issues.
    */
-  protected $kernel;
+  public $kernel;
 
   /**
    * @var string, the entity for which we want to manage permissions
@@ -73,7 +76,7 @@ class DynamicFKAuthorization implements EventSubscriberInterface {
   protected $actions;
 
   /**
-   * @var string, SQL; a query which looks up the related entity
+   * @var string, SQL. Given a file ID, determine the entity+table it's attached to.
    *
    * ex: "SELECT if(cf.id,1,0) as is_valid, cef.entity_table, cef.entity_id
    * FROM civicrm_file cf
@@ -88,6 +91,21 @@ class DynamicFKAuthorization implements EventSubscriberInterface {
    */
   protected $lookupDelegateSql;
 
+  /**
+   * @var string, SQL. Get a list of (field_name, table_name, extends) tuples.
+   *
+   * For example, one tuple might be ("custom_123", "civicrm_value_mygroup_4",
+   * "Activity").
+   */
+  protected $lookupCustomFieldSql;
+
+  /**
+   * @var array
+   *
+   * Each item is an array(field_name => $, table_name => $, extends => $)
+   */
+  protected $lookupCustomFieldCache;
+
   /**
    * @var array list of related tables for which FKs are allowed
    */
@@ -104,14 +122,17 @@ class DynamicFKAuthorization implements EventSubscriberInterface {
    *   "get", "delete").
    * @param string $lookupDelegateSql
    *   See docblock in DynamicFKAuthorization::$lookupDelegateSql.
+   * @param string $lookupCustomFieldSql
+   *   See docblock in DynamicFKAuthorization::$lookupCustomFieldSql.
    * @param array|NULL $allowedDelegates
    *   e.g. "civicrm_mailing","civicrm_activity"; NULL to allow any.
    */
-  public function __construct($kernel, $entityName, $actions, $lookupDelegateSql, $allowedDelegates = NULL) {
+  public function __construct($kernel, $entityName, $actions, $lookupDelegateSql, $lookupCustomFieldSql, $allowedDelegates = NULL) {
     $this->kernel = $kernel;
-    $this->entityName = $entityName;
+    $this->entityName = \CRM_Utils_String::convertStringToCamel($entityName);
     $this->actions = $actions;
     $this->lookupDelegateSql = $lookupDelegateSql;
+    $this->lookupCustomFieldSql = $lookupCustomFieldSql;
     $this->allowedDelegates = $allowedDelegates;
   }
 
@@ -123,7 +144,16 @@ class DynamicFKAuthorization implements EventSubscriberInterface {
    */
   public function onApiAuthorize(\Civi\API\Event\AuthorizeEvent $event) {
     $apiRequest = $event->getApiRequest();
-    if ($apiRequest['version'] == 3 && $apiRequest['entity'] == $this->entityName && in_array(strtolower($apiRequest['action']), $this->actions)) {
+    if ($apiRequest['version'] == 3 && \CRM_Utils_String::convertStringToCamel($apiRequest['entity']) == $this->entityName && in_array(strtolower($apiRequest['action']), $this->actions)) {
+      if (isset($apiRequest['params']['field_name'])) {
+        $fldIdx = \CRM_Utils_Array::index(array('field_name'), $this->getCustomFields());
+        if (empty($fldIdx[$apiRequest['params']['field_name']])) {
+          throw new \Exception("Failed to map custom field to entity table");
+        }
+        $apiRequest['params']['entity_table'] = $fldIdx[$apiRequest['params']['field_name']]['entity_table'];
+        unset($apiRequest['params']['field_name']);
+      }
+
       if (/*!$isTrusted */
         empty($apiRequest['params']['id']) && empty($apiRequest['params']['entity_table'])
       ) {
@@ -177,20 +207,40 @@ class DynamicFKAuthorization implements EventSubscriberInterface {
   public function authorizeDelegate($action, $entityTable, $entityId, $apiRequest) {
     $entity = $this->getDelegatedEntityName($entityTable);
     if (!$entity) {
-      throw new \API_Exception("Failed to run permission check: Unrecognized target entity ($entityTable)");
+      throw new \API_Exception("Failed to run permission check: Unrecognized target entity table ($entityTable)");
+    }
+    if (!$entityId) {
+      throw new \Civi\API\Exception\UnauthorizedException("Authorization failed on ($entity): Missing entity_id");
     }
 
     if ($this->isTrusted($apiRequest)) {
       return;
     }
 
-    $params = array('check_permissions' => 1);
-    if ($entityId) {
-      $params['id'] = $entityId;
-    }
+    /**
+     * @var \Exception $exception
+     */
+    $exception = NULL;
+    $self = $this;
+    \CRM_Core_Transaction::create(TRUE)->run(function($tx) use ($entity, $action, $entityId, &$exception, $self) {
+      $tx->rollback(); // Just to be safe.
+
+      $params = array(
+        'version' => 3,
+        'check_permissions' => 1,
+        'id' => $entityId,
+      );
+
+      $result = $self->kernel->run($entity, $self->getDelegatedAction($action), $params);
+      if ($result['is_error'] || empty($result['values'])) {
+        $exception = new \Civi\API\Exception\UnauthorizedException("Authorization failed on ($entity,$entityId)", array(
+          'cause' => $result,
+        ));
+      }
+    });
 
-    if (!$this->kernel->runAuthorize($entity, $this->getDelegatedAction($action), $params)) {
-      throw new \Civi\API\Exception\UnauthorizedException("Authorization failed on ($entity,$entityId)");
+    if ($exception) {
+      throw $exception;
     }
   }
 
@@ -210,6 +260,7 @@ class DynamicFKAuthorization implements EventSubscriberInterface {
    */
   public function preventReassignment($fileId, $entityTable, $entityId, $apiRequest) {
     if (strtolower($apiRequest['action']) == 'create' && $fileId && !$this->isTrusted($apiRequest)) {
+      // TODO: no change in field_name?
       if (isset($apiRequest['params']['entity_table']) && $entityTable != $apiRequest['params']['entity_table']) {
         throw new \API_Exception("Cannot modify entity_table");
       }
@@ -267,13 +318,25 @@ class DynamicFKAuthorization implements EventSubscriberInterface {
    *   e.g. file ID.
    * @return array
    *   (0 => bool $isValid, 1 => string $entityTable, 2 => int $entityId)
+   * @throws \Exception
    */
   public function getDelegate($id) {
     $query = \CRM_Core_DAO::executeQuery($this->lookupDelegateSql, array(
       1 => array($id, 'Positive'),
     ));
     if ($query->fetch()) {
-      return array($query->is_valid, $query->entity_table, $query->entity_id);
+      if (!preg_match('/^civicrm_value_/', $query->entity_table)) {
+        // A normal attachment directly on its entity.
+        return array($query->is_valid, $query->entity_table, $query->entity_id);
+      }
+
+      // Ex: Translate custom-field table ("civicrm_value_foo_4") to
+      // entity table ("civicrm_activity").
+      $tblIdx = \CRM_Utils_Array::index(array('table_name'), $this->getCustomFields());
+      if (isset($tblIdx[$query->entity_table])) {
+        return array($query->is_valid, $tblIdx[$query->entity_table]['entity_table'], $query->entity_id);
+      }
+      throw new \Exception('Failed to lookup entity table for custom field.');
     }
     else {
       return array(FALSE, NULL, NULL);
@@ -290,4 +353,22 @@ class DynamicFKAuthorization implements EventSubscriberInterface {
     return empty($apiRequest['params']['check_permissions']) or $apiRequest['params']['check_permissions'] == FALSE;
   }
 
+  /**
+   * @return array
+   *   Each item has keys 'field_name', 'table_name', 'extends', 'entity_table'
+   */
+  public function getCustomFields() {
+    $query = \CRM_Core_DAO::executeQuery($this->lookupCustomFieldSql);
+    $rows = array();
+    while ($query->fetch()) {
+      $rows[] = array(
+        'field_name' => $query->field_name,
+        'table_name' => $query->table_name,
+        'extends' => $query->extends,
+        'entity_table' => \CRM_Core_BAO_CustomGroup::getTableNameByEntityName($query->extends),
+      );
+    }
+    return $rows;
+  }
+
 }