APIv4 - Add "Permission.get" for browsing available permissions.
authorTim Otten <totten@civicrm.org>
Fri, 4 Dec 2020 07:34:44 +0000 (23:34 -0800)
committerTim Otten <totten@civicrm.org>
Wed, 9 Dec 2020 22:24:59 +0000 (14:24 -0800)
CRM/Core/Permission/List.php [new file with mode: 0644]
CRM/Utils/Hook.php
Civi/Api4/Action/Permission/Get.php [new file with mode: 0644]
Civi/Api4/Permission.php [new file with mode: 0644]
Civi/Core/Container.php

diff --git a/CRM/Core/Permission/List.php b/CRM/Core/Permission/List.php
new file mode 100644 (file)
index 0000000..d23ad50
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved.                        |
+ |                                                                    |
+ | This work is published under the GNU AGPLv3 license with some      |
+ | permitted exceptions and without any warranty. For full license    |
+ | and copyright information, see https://civicrm.org/licensing       |
+ +--------------------------------------------------------------------+
+ */
+
+use Civi\Core\Event\GenericHookEvent;
+
+/**
+ * Class CRM_Core_Permission_List
+ *
+ * When presenting the administrator with a list of available permissions (`Permission.get`),
+ * the methods in provide the default implementations.
+ *
+ * These methods are not intended for public consumption or frequent execution.
+ *
+ * @see \Civi\Api4\Action\Permission\Get
+ */
+class CRM_Core_Permission_List {
+
+  /**
+   * Enumerate concrete permissions that originate in CiviCRM (core or extension).
+   *
+   * @param \Civi\Core\Event\GenericHookEvent $e
+   * @see \CRM_Utils_Hook::permissionList
+   */
+  public static function findCiviPermissions(GenericHookEvent $e) {
+    $activeCorePerms = \CRM_Core_Permission::basicPermissions(FALSE);
+    $allCorePerms = \CRM_Core_Permission::basicPermissions(TRUE, TRUE);
+    foreach ($allCorePerms as $permName => $corePerm) {
+      $e->permissions[$permName] = [
+        'group' => 'civicrm',
+        'title' => $corePerm['label'] ?? $corePerm[0] ?? $permName,
+        'description' => $corePerm['description'] ?? $corePerm[1] ?? NULL,
+        'is_active' => isset($activeCorePerms[$permName]),
+      ];
+    }
+  }
+
+  /**
+   * Enumerate permissions that originate in the CMS (core or module/plugin),
+   * excluding any Civi permissions.
+   *
+   * @param \Civi\Core\Event\GenericHookEvent $e
+   * @see \CRM_Utils_Hook::permissionList
+   */
+  public static function findCmsPermissions(GenericHookEvent $e) {
+    $config = \CRM_Core_Config::singleton();
+
+    $ufPerms = $config->userPermissionClass->getAvailablePermissions();
+    foreach ($ufPerms as $permName => $cmsPerm) {
+      $e->permissions[$permName] = [
+        'group' => 'cms',
+        'title' => $cmsPerm['title'] ?? $permName,
+        'description' => $cmsPerm['description'] ?? NULL,
+      ];
+    }
+
+    // There are a handful of special permissions defined in CRM/Core/Permission/*.php
+    // using the `translatePermission()` mechanism.
+    $e->permissions['cms:view user account'] = [
+      'group' => 'cms',
+      'title' => ts('CMS') . ': ' . ts('View user accounts'),
+      'description' => ts('View user accounts. (Synthetic permission - adapts to local CMS)'),
+      'is_synthetic' => TRUE,
+    ];
+    $e->permissions['cms:administer users'] = [
+      'group' => 'cms',
+      'title' => ts('CMS') . ': ' . ts('Administer user accounts'),
+      'description' => ts('Administer user accounts. (Synthetic permission - adapts to local CMS)'),
+      'is_synthetic' => TRUE,
+    ];
+  }
+
+  /**
+   * @param \Civi\Core\Event\GenericHookEvent $e
+   * @see \CRM_Utils_Hook::permissionList
+   */
+  public static function findConstPermissions(GenericHookEvent $e) {
+    // There are a handful of special permissions defined in CRM/Core/Permission.
+    $e->permissions[\CRM_Core_Permission::ALWAYS_DENY_PERMISSION] = [
+      'group' => 'const',
+      'title' => ts('Constant: Always deny'),
+      'is_synthetic' => TRUE,
+    ];
+    $e->permissions[\CRM_Core_Permission::ALWAYS_ALLOW_PERMISSION] = [
+      'group' => 'const',
+      'title' => ts('Constant: Always allow'),
+      'is_synthetic' => TRUE,
+    ];
+  }
+
+}
index f173abac3bf2955e94aa4e49f799b737de7ddd14..388145cc73cca642530074893a7c2025680e1316 100644 (file)
@@ -2000,7 +2000,7 @@ abstract class CRM_Utils_Hook {
   }
 
   /**
-   * This hook is called when loading CMS permissions; use this hook to modify
+   * This hook is called when exporting Civi's permission to the CMS. Use this hook to modify
    * the array of system permissions for CiviCRM.
    *
    * @param array $permissions
@@ -2017,6 +2017,31 @@ abstract class CRM_Utils_Hook {
     );
   }
 
+  /**
+   * This hook is used to enumerate the list of available permissions. It may
+   * include concrete permissions defined by Civi, concrete permissions defined
+   * by the CMS, and/or synthetic permissions.
+   *
+   * @param array $permissions
+   *   Array of permissions, keyed by symbolic name. Each is an array with fields:
+   *     - group: string (ex: "civicrm", "cms")
+   *     - title: string (ex: "CiviEvent: Register for events")
+   *     - description: string (ex: "Register for events online")
+   *     - is_synthetic: bool (TRUE for synthetic permissions with a bespoke evaluation. FALSE for concrete permissions that registered+granted in the UF user-management layer.
+   *        Default TRUE iff name begins with '@')
+   *     - is_active: bool (TRUE if this permission is defined by. Default: TRUE)
+   *
+   * @return null
+   *   The return value is ignored
+   * @see Civi\Api4\Permission::get()
+   */
+  public static function permissionList(&$permissions) {
+    return self::singleton()->invoke(['permissions'], $permissions,
+      self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject,
+      'civicrm_permissionList'
+    );
+  }
+
   /**
    * This hook is called when checking permissions; use this hook to dynamically
    * escalate user permissions in certain use cases (cf. CRM-19256).
diff --git a/Civi/Api4/Action/Permission/Get.php b/Civi/Api4/Action/Permission/Get.php
new file mode 100644 (file)
index 0000000..7bb0afb
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved.                        |
+ |                                                                    |
+ | This work is published under the GNU AGPLv3 license with some      |
+ | permitted exceptions and without any warranty. For full license    |
+ | and copyright information, see https://civicrm.org/licensing       |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Api4\Action\Permission;
+
+use Civi\Api4\Generic\BasicGetAction;
+
+/**
+ * Get a list of extant permissions.
+ *
+ * NOTE: This is a high-level API intended for introspective use by administrative tools.
+ * It may be poorly suited to recursive usage (e.g. permissions defined dynamically
+ * on top of permissions!) or during install/uninstall processes.
+ *
+ * The list of permissions is generated via hook, and there is a standard/default
+ * listener.
+ *
+ * @see CRM_Core_Permission_List
+ * @see \CRM_Utils_Hook::permissionList
+ */
+class Get extends BasicGetAction {
+
+  public function getRecords() {
+    $cacheKey = 'list_' . $GLOBALS['tsLocale'];
+    if (!isset(\Civi::$statics[__CLASS__][$cacheKey])) {
+      $perms = [];
+      \CRM_Utils_Hook::permissionList($perms);
+      foreach (array_keys($perms) as $permName) {
+        $defaults = [
+          'name' => $permName,
+          'group' => 'unknown',
+          'is_synthetic' => ($permName[0] === '@'),
+          'is_active' => TRUE,
+        ];
+        $perms[$permName] = array_merge($defaults, $perms[$permName]);
+      }
+      \Civi::$statics[__CLASS__][$cacheKey] = $perms;
+    }
+
+    return \Civi::$statics[__CLASS__][$cacheKey];
+  }
+
+}
diff --git a/Civi/Api4/Permission.php b/Civi/Api4/Permission.php
new file mode 100644 (file)
index 0000000..8f5f3e5
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved.                        |
+ |                                                                    |
+ | This work is published under the GNU AGPLv3 license with some      |
+ | permitted exceptions and without any warranty. For full license    |
+ | and copyright information, see https://civicrm.org/licensing       |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC https://civicrm.org/licensing
+ */
+
+namespace Civi\Api4;
+
+/**
+ * (Read-only) Available permissions
+ *
+ * NOTE: This is a high-level API intended for introspective use by administrative tools.
+ * It may be poorly suited to recursive usage (e.g. permissions defined dynamically
+ * on top of permissions!) or during install/uninstall processes.
+ *
+ * @searchable false
+ * @package Civi\Api4
+ */
+class Permission extends Generic\AbstractEntity {
+
+  /**
+   * @param bool $checkPermissions
+   * @return \Civi\Api4\Generic\BasicGetAction
+   */
+  public static function get($checkPermissions = TRUE) {
+    return (new \Civi\Api4\Action\Permission\Get(__CLASS__, __FUNCTION__))->setCheckPermissions($checkPermissions);
+  }
+
+  /**
+   * @param bool $checkPermissions
+   * @return Generic\BasicGetFieldsAction
+   */
+  public static function getFields($checkPermissions = TRUE) {
+    return (new Generic\BasicGetFieldsAction(__CLASS__, __FUNCTION__, function() {
+      return [
+        [
+          'name' => 'group',
+          'title' => 'Group',
+          'required' => TRUE,
+          'data_type' => 'String',
+        ],
+        [
+          'name' => 'name',
+          'title' => 'Name',
+          'required' => TRUE,
+          'data_type' => 'String',
+        ],
+        [
+          'name' => 'title',
+          'title' => 'Title',
+          'required' => TRUE,
+          'data_type' => 'String',
+        ],
+        [
+          'name' => 'description',
+          'title' => 'Description',
+          'required' => FALSE,
+          'data_type' => 'String',
+        ],
+        [
+          'name' => 'is_synthetic',
+          'title' => 'Is Synthetic',
+          'required' => FALSE,
+          'data_type' => 'Boolean',
+        ],
+        [
+          'name' => 'is_active',
+          'title' => 'Is Active',
+          'description' => '',
+          'default' => TRUE,
+          'required' => FALSE,
+          'data_type' => 'Boolean',
+        ],
+      ];
+    }))->setCheckPermissions($checkPermissions);
+  }
+
+  /**
+   * @return array
+   */
+  public static function permissions() {
+    return [
+      "meta" => ["access CiviCRM"],
+      "default" => ["access CiviCRM"],
+    ];
+  }
+
+}
index 28b210e2b07433c703873769782af907a181e898..4f38b4e3a2f0fd2d11fcac5567d6159273b3f873 100644 (file)
@@ -366,6 +366,10 @@ class Container {
     $dispatcher->addListener('hook_civicrm_coreResourceList', ['\CRM_Utils_System', 'appendCoreResources']);
     $dispatcher->addListener('hook_civicrm_getAssetUrl', ['\CRM_Utils_System', 'alterAssetUrl']);
     $dispatcher->addListener('hook_civicrm_alterExternUrl', ['\CRM_Utils_System', 'migrateExternUrl'], 1000);
+    $dispatcher->addListener('hook_civicrm_permissionList', ['CRM_Core_Permission_List', 'findConstPermissions'], 975);
+    $dispatcher->addListener('hook_civicrm_permissionList', ['CRM_Core_Permission_List', 'findCiviPermissions'], 950);
+    $dispatcher->addListener('hook_civicrm_permissionList', ['CRM_Core_Permission_List', 'findCmsPermissions'], 925);
+
     $dispatcher->addListener('hook_civicrm_triggerInfo', ['\CRM_Contact_BAO_RelationshipCache', 'onHookTriggerInfo']);
     $dispatcher->addListener('civi.dao.postInsert', ['\CRM_Core_BAO_RecurringEntity', 'triggerInsert']);
     $dispatcher->addListener('civi.dao.postUpdate', ['\CRM_Core_BAO_RecurringEntity', 'triggerUpdate']);