SearchKit - Pass-thu permission checks from SearchDisplay::run to underlying API
authorColeman Watts <coleman@civicrm.org>
Thu, 29 Apr 2021 00:49:06 +0000 (20:49 -0400)
committerColeman Watts <coleman@civicrm.org>
Mon, 3 May 2021 00:14:29 +0000 (20:14 -0400)
The SearchDisplay::run api action essentially wraps api.get for the search entity,
so it makes sense to allow open access to the run api but require permission checks
on the delegated api call.

ext/search/Civi/Api4/Action/SearchDisplay/Run.php
ext/search/Civi/Api4/SearchDisplay.php
ext/search/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php
tests/phpunit/CRMTraits/ACL/PermissionTrait.php

index 22725aee4d6c325c91daaf804080575a65fec95e..57567b34e618ea38eaf242ffa4ea152f12ac0003 100644 (file)
@@ -90,6 +90,7 @@ class Run extends \Civi\Api4\Generic\AbstractAction {
     }
     $entityName = $this->savedSearch['api_entity'];
     $apiParams =& $this->savedSearch['api_params'];
+    $apiParams['checkPermissions'] = $this->checkPermissions;
     $apiParams += ['where' => []];
     $settings = $this->display['settings'];
     $page = NULL;
index 85e65602f1167756ee825d7447b6203603df6ef5..939e74664e739c58d8d3f89e66c8d351d9fd75be 100644 (file)
@@ -29,4 +29,10 @@ class SearchDisplay extends Generic\DAOEntity {
       ->setCheckPermissions($checkPermissions);
   }
 
+  public static function permissions() {
+    $permissions = parent::permissions();
+    $permissions['run'] = [];
+    return $permissions;
+  }
+
 }
index 6ffb5790a56f0ee362b63d5ec5226c961759acd5..02506e6a8f129912dad90cc79cc9f3134f28db17 100644 (file)
@@ -2,13 +2,20 @@
 namespace api\v4\SearchDisplay;
 
 use Civi\Api4\Contact;
+use Civi\Api4\SavedSearch;
+use Civi\Api4\SearchDisplay;
+use Civi\Api4\UFMatch;
 use Civi\Test\HeadlessInterface;
 use Civi\Test\TransactionalInterface;
 
+// FIXME: This shouldn't be needed but the core classLoader doesn't seem present when this file loads
+require_once 'tests/phpunit/CRMTraits/ACL/PermissionTrait.php';
+
 /**
  * @group headless
  */
 class SearchRunTest extends \PHPUnit\Framework\TestCase implements HeadlessInterface, TransactionalInterface {
+  use \CRMTraits_ACL_PermissionTrait;
 
   public function setUpHeadless() {
     // Civi\Test has many helpers, like install(), uninstall(), sql(), and sqlFile().
@@ -102,4 +109,109 @@ class SearchRunTest extends \PHPUnit\Framework\TestCase implements HeadlessInter
     $this->assertEquals('Two', $result[1]['first_name']);
   }
 
+  /**
+   * Test running a searchDisplay as a restricted user.
+   */
+  public function testDisplayACLCheck() {
+    $lastName = uniqid(__FUNCTION__);
+    $sampleData = [
+      ['first_name' => 'User', 'last_name' => uniqid('user')],
+      ['first_name' => 'One', 'last_name' => $lastName],
+      ['first_name' => 'Two', 'last_name' => $lastName],
+      ['first_name' => 'Three', 'last_name' => $lastName],
+      ['first_name' => 'Four', 'last_name' => $lastName],
+    ];
+    $sampleData = Contact::save(FALSE)
+      ->setRecords($sampleData)->execute()
+      ->indexBy('first_name')->column('id');
+
+    // Create logged-in user
+    UFMatch::delete(FALSE)
+      ->addWhere('uf_id', '=', 6)
+      ->execute();
+    UFMatch::create(FALSE)->setValues([
+      'contact_id' => $sampleData['User'],
+      'uf_name' => 'superman',
+      'uf_id' => 6,
+    ])->execute();
+
+    $session = \CRM_Core_Session::singleton();
+    $session->set('userID', $sampleData['User']);
+    $hooks = \CRM_Utils_Hook::singleton();
+    \CRM_Core_Config::singleton()->userPermissionClass->permissions = [
+      'access CiviCRM',
+    ];
+
+    $search = SavedSearch::create(FALSE)
+      ->setValues([
+        'name' => uniqid(__FUNCTION__),
+        'api_entity' => 'Contact',
+        'api_params' => [
+          'version' => 4,
+          'select' => ['id', 'first_name', 'last_name'],
+          'where' => [['last_name', '=', $lastName]],
+        ],
+      ])
+      ->addChain('display', SearchDisplay::create()
+        ->setValues([
+          'type' => 'table',
+          'label' => uniqid(__FUNCTION__),
+          'saved_search_id' => '$id',
+          'settings' => [
+            'limit' => 20,
+            'pager' => TRUE,
+            'columns' => [
+              [
+                'key' => 'id',
+                'label' => 'Contact ID',
+                'dataType' => 'Integer',
+                'type' => 'field',
+              ],
+              [
+                'key' => 'first_name',
+                'label' => 'First Name',
+                'dataType' => 'String',
+                'type' => 'field',
+              ],
+              [
+                'key' => 'last_name',
+                'label' => 'Last Name',
+                'dataType' => 'String',
+                'type' => 'field',
+              ],
+            ],
+            'sort' => [
+              ['id', 'ASC'],
+            ],
+          ],
+        ]), 0)
+      ->execute()->first();
+
+    $params = [
+      'return' => 'page:1',
+      'savedSearch' => $search['name'],
+      'display' => $search['display']['name'],
+      'afform' => NULL,
+    ];
+
+    $hooks->setHook('civicrm_aclWhereClause', [$this, 'aclWhereHookNoResults']);
+    $result = civicrm_api4('SearchDisplay', 'run', $params);
+    $this->assertCount(0, $result);
+
+    $this->allowedContactId = $sampleData['Two'];
+    $hooks->setHook('civicrm_aclWhereClause', [$this, 'aclWhereOnlyOne']);
+    $this->cleanupCachedPermissions();
+    $result = civicrm_api4('SearchDisplay', 'run', $params);
+    $this->assertCount(1, $result);
+    $this->assertEquals($sampleData['Two'], $result[0]['id']);
+
+    $hooks->setHook('civicrm_aclWhereClause', [$this, 'aclWhereGreaterThan']);
+    $this->cleanupCachedPermissions();
+    $result = civicrm_api4('SearchDisplay', 'run', $params);
+    $this->assertCount(2, $result);
+    $this->assertEquals($sampleData['Three'], $result[0]['id']);
+    $this->assertEquals($sampleData['Four'], $result[1]['id']);
+
+  }
+
 }
index dc0c2882e005ba44d413785c445d924fc07f329c..7622d4c8446dd783a44b12ab897cf4712a893633 100644 (file)
@@ -94,6 +94,21 @@ trait CRMTraits_ACL_PermissionTrait {
     $where = " contact_a.id = " . $this->allowedContactId;
   }
 
+  /**
+   * Results after the allowedContact are returned.
+   *
+   * @implements CRM_Utils_Hook::aclWhereClause
+   *
+   * @param string $type
+   * @param array $tables
+   * @param array $whereTables
+   * @param int $contactID
+   * @param string $where
+   */
+  public function aclWhereGreaterThan($type, &$tables, &$whereTables, &$contactID, &$where) {
+    $where = " contact_a.id > " . $this->allowedContactId;
+  }
+
   /**
    * Set up a core ACL.
    *