Expand CustomValue::_checkAccess()
authorTim Otten <totten@civicrm.org>
Wed, 9 Jun 2021 10:24:21 +0000 (03:24 -0700)
committerColeman Watts <coleman@civicrm.org>
Wed, 9 Jun 2021 18:23:44 +0000 (14:23 -0400)
Before: Reports that access is available based one delegated-check for `Contact.update`

After: Reports that access is available based on multiple checks:

  1. The user must have access to the relevant CustomGroup (by way of ACL or perms)
  2. The user must have acces to the underlying entity (by way of checkAccessDelgated)

Comments: I did a bit of testing with `Custom_*.get`, and it does seem to
give access to single-value CustomGroups. So I removed a comment about
multi-value CustomGroups and expanded to a larger list of entities.

CRM/Core/BAO/CustomValue.php

index 3f1760d799c0ce9fd2f2ddd1df64ea401498fdc6..ca4d0ea388d6a33ccf5986c3f80a6dbe2450fab3 100644 (file)
@@ -234,24 +234,46 @@ class CRM_Core_BAO_CustomValue extends CRM_Core_DAO {
    *   TRUE if granted. FALSE if prohibited. NULL if indeterminate.
    */
   public static function _checkAccess(string $entityName, string $action, array $record, int $userID): ?bool {
+    // This check implements two rules: you must have access to the specific custom-data-group - and to the underlying record (e.g. Contact).
+
     $groupName = substr($entityName, 0, 7) === 'Custom_' ? substr($entityName, 7) : NULL;
+    $extends = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $groupName, 'extends', 'name');
+    $id = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $groupName, 'id', 'name');
     if (!$groupName) {
       // $groupName is required but the function signature has to match the parent.
-      throw new CRM_Core_Exception('Missing required $groupName in CustomValue::checkAccess');
+      throw new CRM_Core_Exception('Missing required group-name in CustomValue::checkAccess');
     }
-    // Currently, multi-record custom data always extends Contacts
-    $extends = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $groupName, 'extends', 'name');
-    if (!in_array($extends, ['Contact', 'Individual', 'Organization', 'Household'])) {
-      throw new CRM_Core_Exception("Cannot assess delegated permissions for group {$groupName}.");
+
+    if (empty($extends) || empty($id)) {
+      throw new CRM_Core_Exception('Received invalid group-name in CustomValue::checkAccess');
     }
 
-    $cid = $record['entity_id'] ?? NULL;
-    if (!$cid) {
+    $customGroups = [$id => $id];
+    $defaultGroups = CRM_Core_Permission::customGroupAdmin() ? [$id] : [];
+    // FIXME: Per current onscreen help (Admin=>ACLs=>Add ACLs), CustomGroup ACLs treat VIEW and EDIT as the same. Skimming code, it appears that existing checks use VIEW.
+    $accessList = CRM_ACL_API::group(CRM_Core_Permission::VIEW, $userID, 'civicrm_custom_group', $customGroups, $defaultGroups);
+    if (empty($accessList)) {
+      return FALSE;
+    }
+
+    $eid = $record['entity_id'] ?? NULL;
+    if (!$eid) {
       $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $groupName, 'table_name', 'name');
-      $cid = CRM_Core_DAO::singleValueQuery("SELECT entity_id FROM `$tableName` WHERE id = " . (int) $record['id']);
+      $eid = CRM_Core_DAO::singleValueQuery("SELECT entity_id FROM `$tableName` WHERE id = " . (int) $record['id']);
     }
 
-    return \Civi\Api4\Utils\CoreUtil::checkAccessDelegated('Contact', 'update', ['id' => $cid], $userID);
+    // Do we have access to the target record?
+    if (in_array($extends, ['Contact', 'Individual', 'Organization', 'Household'])) {
+      return \Civi\Api4\Utils\CoreUtil::checkAccessDelegated('Contact', 'update', ['id' => $eid], $userID);
+    }
+    elseif (\Civi\Api4\Utils\CoreUtil::getApiClass($extends)) {
+      // For most entities (Activity, Relationship, Contribution, ad nauseum), we acn just use an eponymous API.
+      return \Civi\Api4\Utils\CoreUtil::checkAccessDelegated($extends, 'update', ['id' => $eid], $userID);
+    }
+    else {
+      // Do you need to add a special case for some oddball custom-group type?
+      throw new CRM_Core_Exception("Cannot assess delegated permissions for group {$groupName}.");
+    }
   }
 
 }