Add weight column to ACLs and support weighted ACL rules
authorSeamus Lee <seamuslee001@gmail.com>
Wed, 21 Jun 2023 22:57:20 +0000 (08:57 +1000)
committerSeamus Lee <seamuslee001@gmail.com>
Thu, 22 Jun 2023 16:46:23 +0000 (02:46 +1000)
Add in upgrade step

Rename field to be priority and add in upgrade guards as per Coleman

Updates as per discussion with coleman

CRM/ACL/BAO/ACL.php
CRM/ACL/DAO/ACL.php
CRM/ACL/Form/ACL.php
CRM/ACL/Page/ACL.php
CRM/Upgrade/Incremental/php/FiveSixtyFour.php
templates/CRM/ACL/Form/ACL.tpl
templates/CRM/ACL/Page/ACL.tpl
tests/phpunit/api/v3/ACLPermissionTest.php
xml/schema/ACL/ACL.xml

index 977f3d3997b86db198ee32d9e1fb8ae32701fbe2..ac8c86c78def0758f28f4716b05ade9af9e8821b 100644 (file)
@@ -529,23 +529,49 @@ ORDER BY a.object_id
     $acls = CRM_ACL_BAO_Cache::build($contactID);
     $aclKeys = array_keys($acls);
     $aclKeys = implode(',', $aclKeys);
+    $select = "a.operation, a.object_id";
+    $hasPriorty = FALSE;
+    if (array_key_exists('priority', CRM_ACL_BAO_ACL::getSupportedFields())) {
+      $select .= ",a.priority";
+      $hasPriority = TRUE;
+    }
     $query = "
-SELECT   a.operation, a.object_id
+SELECT   {$select}
   FROM   civicrm_acl_cache c, civicrm_acl a
  WHERE   c.acl_id       =  a.id
    AND   a.is_active    =  1
    AND   a.object_table = %1
    AND   a.id        IN ( $aclKeys )
    AND   a.deny         = 1
-GROUP BY a.operation,a.object_id
-ORDER BY a.object_id
 ";
     $params = [1 => [$tableName, 'String']];
     $dao = CRM_Core_DAO::executeQuery($query, $params);
     while ($dao->fetch()) {
       if ($dao->object_id) {
         if (self::matchType($type, $dao->operation)) {
-          $ids[] = $dao->object_id;
+          if ($hasPriority) {
+            $permittedRules = CRM_Core_DAO::singleValueQuery("SELECT count(a.id)
+              FROM civicrm_acl a
+              INNER JOIN civicrm_acl_cache c ON c.acl_id = a.id
+              WHERE a.id IN ( $aclKeys )
+              AND a.is_active = 1
+              AND a.object_table = %1
+              AND a.deny = 0
+              AND a.priority > %2
+              AND a.object_id = %3", [
+                1 => [$tableName, 'String'],
+                2 => [(int) $dao->priority, 'Integer'],
+                3 => [$dao->object_id, 'Integer'],
+              ]);
+            if (empty($permittedRules) && !in_array($dao->object_id, $ids, TRUE)) {
+              $ids[] = $dao->object_id;
+            }
+          }
+          else {
+            if (!in_array($dao->object_id, $ids, TRUE)) {
+              $ids[] = $dao->object_id;
+            }
+          }
         }
       }
     }
index 16e184652ae700cde9b01f0679fb7687b031d2c2..4abc03ab4ca37aa6839bda265223683cbdc25814 100644 (file)
@@ -6,7 +6,7 @@
  *
  * Generated from xml/schema/CRM/ACL/ACL.xml
  * DO NOT EDIT.  Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:f21ef3073d6247d130341cd182793ea6)
+ * (GenCodeChecksum:9d50ed80344474830f87df285dc6cbf2)
  */
 
 /**
@@ -129,6 +129,13 @@ class CRM_ACL_DAO_ACL extends CRM_Core_DAO {
    */
   public $is_active;
 
+  /**
+   * @var int|string
+   *   (SQL type: int)
+   *   Note that values will be retrieved from the database as a string.
+   */
+  public $priority;
+
   /**
    * Class constructor.
    */
@@ -403,6 +410,28 @@ class CRM_ACL_DAO_ACL extends CRM_Core_DAO {
           ],
           'add' => '1.6',
         ],
+        'priority' => [
+          'name' => 'priority',
+          'type' => CRM_Utils_Type::T_INT,
+          'title' => ts('Priority'),
+          'required' => TRUE,
+          'usage' => [
+            'import' => FALSE,
+            'export' => FALSE,
+            'duplicate_matching' => FALSE,
+            'token' => FALSE,
+          ],
+          'where' => 'civicrm_acl.priority',
+          'default' => '0',
+          'table_name' => 'civicrm_acl',
+          'entity' => 'ACL',
+          'bao' => 'CRM_ACL_BAO_ACL',
+          'localizable' => 0,
+          'html' => [
+            'type' => 'Number',
+          ],
+          'add' => '5.64',
+        ],
       ];
       CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']);
     }
@@ -481,6 +510,14 @@ class CRM_ACL_DAO_ACL extends CRM_Core_DAO {
         'localizable' => FALSE,
         'sig' => 'civicrm_acl::0::acl_id',
       ],
+      'index_priority' => [
+        'name' => 'index_priority',
+        'field' => [
+          0 => 'priority',
+        ],
+        'localizable' => FALSE,
+        'sig' => 'civicrm_acl::0::priority',
+      ],
     ];
     return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices;
   }
index 10e6369f50501f925c3c6b04b9ca0658ea8dcf72..4ac78946d102014bf62df045811ad1ea9ccbf28b 100644 (file)
@@ -29,6 +29,7 @@ class CRM_ACL_Form_ACL extends CRM_Admin_Form {
 
     if ($this->_action & CRM_Core_Action::ADD) {
       $defaults['object_type'] = 1;
+      $defaults['priority'] = 0;
     }
 
     $showHide = new CRM_Core_ShowHideBlocks();
@@ -164,6 +165,7 @@ class CRM_ACL_Form_ACL extends CRM_Admin_Form {
       0 => ts('Allow'),
       1 => ts('Deny'),
     ]);
+    $this->add('number', 'priority', ts('Priority'));
 
     $this->addFormRule(['CRM_ACL_Form_ACL', 'formRule']);
   }
index 8c3b3b79a1e5b4f62f6b57c9d4925e7cf0859f56..b5d3cc89552dc9fb83a830f67e795d2cc09f07d9 100644 (file)
@@ -137,6 +137,7 @@ ORDER BY entity_id
       $acl[$dao->id]['object_id'] = $dao->object_id;
       $acl[$dao->id]['is_active'] = $dao->is_active;
       $acl[$dao->id]['deny'] = $dao->deny;
+      $acl[$dao->id]['priority'] = $dao->priority;
 
       if ($acl[$dao->id]['entity_id']) {
         $acl[$dao->id]['entity'] = $roles[$acl[$dao->id]['entity_id']] ?? NULL;
index b6a5ead8b457ac794bfde60a03b2b33d212362e6..fdd22f175764b5b060bce996392afff8d54a94c9 100644 (file)
@@ -28,6 +28,7 @@ class CRM_Upgrade_Incremental_php_FiveSixtyFour extends CRM_Upgrade_Incremental_
    *   The version number matching this function name
    */
   public function upgrade_5_64_alpha1($rev): void {
+    $this->addTask('Add priority column onto ACL table', 'addColumn', 'civicrm_acl', 'priority', 'int NOT NULL DEFAULT 0');
     $this->addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev);
     $this->addTask('Update post_URL/cancel_URL in logging tables', 'updateLogging');
   }
index 583be52ac03a8b164d8c15512e7bb47dc54e6ae5..d51c24a97f1c67e779b1ceb986e4616d310f917c 100644 (file)
        <td class="label">{$form.deny.label}</td>
        <td>{$form.deny.html}</td>
      </tr>
+     <tr class="crm-acl-form-block-priority">
+       <td class="label">{$form.priority.label}</td>
+       <td>{$form.priority.label}<br />
+         <span class="description">{ts}Higher priority ACL rules will override lower priority rules{/ts}</span>
+       </td>
+     </tr>
   </table>
   <div id="id-group-acl">
    <table  class="form-layout-compressed">
index f5b54b9c260eff5f54c66d743202e959824e94b6..3f3850fe2986d861d12462a496d75f3d6c0640b1 100644 (file)
@@ -31,6 +31,7 @@
               <th>{ts}Description{/ts}</th>
               <th>{ts}Enabled?{/ts}</th>
               <th>{ts}Mode{/ts}</th>
+              <th>{ts}Priority{/ts}</th>
               <th></th>
             </tr>
             </thead>
@@ -44,6 +45,7 @@
                 <td class="crm-acl-name crm-editable" data-field="name">{$row.name}</td>
                 <td class="crm-acl-is_active" id="row_{$aclID}_status">{if $row.is_active eq 1} {ts}Yes{/ts} {else} {ts}No{/ts} {/if}</td>
                 <td class="crm-acl-deny" id="row_{$aclID}_deny">{if $row.deny}{ts}Deny{/ts}{else}{ts}Allow{/ts}{/if}</td>
+                <td class="crm-acl-priority" id="row_{$aclID}_priority">{$row.priority}</td>
                 <td>{$row.action|replace:'xx':$aclID}</td>
               </tr>
             {/foreach}
index 5be31665708fb18ecc105cdaafd303bca8fa3137..de46201180ca7bf0b66f3c16614a84448118d9d9 100644 (file)
@@ -1316,6 +1316,78 @@ class api_v3_ACLPermissionTest extends CiviUnitTestCase {
     Civi::$statics['CRM_ACL_BAO_ACL'] = [];
   }
 
+  /**
+   * @throws \CRM_Core_Exception
+   */
+  public function testPriorityCustomGroupACL(): void {
+    // Create 2 multi-record custom entities and 2 regular custom fields
+    $customGroups = [];
+    foreach ([1, 2] as $i) {
+      $customGroups[$i] = CustomGroup::create(FALSE)
+        ->addValue('title', "priority_extra_group_$i")
+        ->addValue('extends', 'Contact')
+        ->addValue('is_multiple', FALSE)
+        ->addChain('field', CustomField::create()
+          ->addValue('label', "priority_extra_field_$i")
+          ->addValue('custom_group_id', '$id')
+          ->addValue('html_type', 'Text')
+          ->addValue('data_type', 'String')
+        )
+        ->execute()->single()['id'];
+      $this->callAPISuccess('Acl', 'create', [
+        'name' => 'Deny everyone to access custom group ' . $customGroups[$i],
+        'entity_table' => 'civicrm_acl_role',
+        'entity_id' => 0,
+        'operation' => 'Edit',
+        'object_table' => 'civicrm_custom_group',
+        'object_id' => $customGroups[$i],
+        'deny' => 1,
+      ]);
+    }
+
+    $this->callAPISuccess('OptionValue', 'create', [
+      'option_group_id' => 'acl_role',
+      'label' => 'Test Priority ACL Role',
+      'value' => 5,
+      'is_active' => 1,
+    ]);
+    $aclGroup = $this->groupCreate();
+    ACLEntityRole::create(FALSE)->setValues([
+      'acl_role_id' => 5,
+      'entity_table' => 'civicrm_group',
+      'entity_id' => $aclGroup,
+      'is_active' => 1,
+    ])->execute();
+    $this->callAPISuccess('Acl', 'create', [
+      'name' => 'Test Postive Priority ACL',
+      'priority' => 1,
+      'entity_table' => 'civicrm_acl_role',
+      'entity_id' => 5,
+      'operation' => 'Edit',
+      'object_table' => 'civicrm_custom_group',
+      'object_id' => $customGroups[2],
+    ]);
+    $userID = $this->createLoggedInUser();
+    CRM_Core_Config::singleton()->userPermissionClass->permissions = [
+      'access CiviCRM',
+      'view my contact',
+    ];
+    $this->callAPISuccess('GroupContact', 'create', [
+      'contact_id' => $userID,
+      'group_id' => $aclGroup,
+      'status' => 'Added',
+    ]);
+    Civi::cache('metadata')->clear();
+    Civi::$statics['CRM_ACL_BAO_ACL'] = [];
+    $getFields = Contact::getFields()
+      ->addWhere('name', 'LIKE', 'priority_extra_group_%.priority_extra_field_%')
+      ->execute();
+    $this->assertCount(1, $getFields);
+
+    Civi::cache('metadata')->clear();
+    Civi::$statics['CRM_ACL_BAO_ACL'] = [];
+  }
+
   public function aclGroupHookAllResults($action, $contactID, $tableName, &$allGroups, &$currentGroups) {
     if ($tableName === $this->aclGroupHookType) {
       $currentGroups = array_keys($allGroups);
index 326b69372b39e87a8f9a7fb786556d3a24115729..d7a71d63be5ee0a4c6d5a2c3365483318a63737e 100644 (file)
       <label>Enabled</label>
     </html>
   </field>
+  <field>
+    <name>priority</name>
+    <type>int</type>
+    <default>0</default>
+    <required>true</required>
+    <add>5.64</add>
+    <html>
+      <type>Number</type>
+    </html>
+  </field>
+  <index>
+    <name>index_priority</name>
+    <fieldName>priority</fieldName>
+  </index>
 </table>