We discussed in the context of search kit that there are 2 competing concepts of
'administer CiviCRM'
1) like drupal user 1, can do anything
2) role that has various administrative access but acls etc still apply
In search kit we have an interest in allowing users who won't do dumb things
the ability to expose data to people who otherwise would not have access to that data
- e.g to create a listing of event participants to expose to anonymous users.
This effectively means we are giving people the power to create displays
that set check_permissions to FALSE. This would potentially enable people
to not just bypass ACLs applied to others but also acls applied to them.
In order words it could be a privellege escallation.
To prevent any unexpected escallation we decided that this ability
should only be given to contacts who explicitly have access to everything
anyway. There is no existing permission that does this (although
there is a perception that there is)
$permissions = self::getCoreAndComponentPermissions($all);
// Add any permissions defined in hook_civicrm_permission implementations.
- $module_permissions = CRM_Core_Config::singleton()->userPermissionClass->getAllModulePermissions(TRUE);
+ $module_permissions = CRM_Core_Config::singleton()->userPermissionClass->getAllModulePermissions(TRUE, $permissions);
$permissions = array_merge($permissions, $module_permissions);
if (!$descriptions) {
foreach ($permissions as $name => $attr) {
'label' => $prefix . ts('administer CiviCRM Data'),
'description' => ts('Permit altering all restricted data options'),
],
+ 'all CiviCRM permissions and ACLs' => [
+ 'label' => $prefix . ts('all CiviCRM permissions and ACLs'),
+ 'description' => ts('Administer and use CiviCRM bypassing any other permission or ACL checks and enabling the creation of displays and forms that allow others to bypass checks. This permission should be given out with care'),
+ ],
];
if (self::isMultisiteEnabled()) {
// This could arguably be moved to the multisite extension but
'description' => ts('Administer multiple organizations. In practice this allows editing the group organization link'),
];
}
- foreach (self::getImpliedPermissions() as $name => $includes) {
- foreach ($includes as $permission) {
- $permissions[$name][] = $permissions[$permission];
- }
- }
return $permissions;
}
*
* @return array
*/
- public static function getImpliedPermissions() {
+ public static function getImpliedAdminPermissions(): array {
return [
- 'administer CiviCRM' => ['administer CiviCRM system', 'administer CiviCRM data'],
- 'administer CiviCRM data' => ['edit message templates', 'administer dedupe rules'],
- 'administer CiviCRM system' => ['edit system workflow message templates'],
+ 'administer CiviCRM' => ['implied_permissions' => ['administer CiviCRM system', 'administer CiviCRM data']],
+ 'administer CiviCRM data' => ['implied_permissions' => ['edit message templates', 'administer dedupe rules']],
+ 'administer CiviCRM system' => ['implied_permissions' => ['edit system workflow message templates']],
];
}
*
* @return array
*/
- public static function getImpliedPermissionsFor(string $permission) {
- $return = [];
- foreach (self::getImpliedPermissions() as $superPermission => $components) {
- if (in_array($permission, $components, TRUE)) {
- $return[$superPermission] = $superPermission;
+ public static function getImpliedPermissionsFor(string $permission): array {
+ $implied = Civi::cache('metadata')->get('implied_permissions', []);
+ if (isset($implied[$permission])) {
+ return $implied[$permission];
+ }
+ $implied[$permission] = [];
+ foreach (self::basicPermissions(FALSE, TRUE) as $key => $details) {
+ if (in_array($permission, $details['implied_permissions'] ?? [], TRUE)) {
+ $implied[$permission][] = $key;
}
}
- return $return;
+ Civi::cache('metadata')->set('implied_permissions', $implied);
+ return $implied[$permission];
}
/**
protected static function getCoreAndComponentPermissions(bool $all): array {
$permissions = self::getCorePermissions();
$permissions = array_merge($permissions, self::getComponentPermissions($all));
+ $permissions['all CiviCRM permissions and ACLs']['implied_permissions'] = array_keys($permissions);
return $permissions;
}
* a permission name
*/
public function translatePermission($perm, $nativePrefix, $map) {
- list ($civiPrefix, $name) = CRM_Utils_String::parsePrefix(':', $perm, NULL);
+ [$civiPrefix, $name] = CRM_Utils_String::parsePrefix(':', $perm, NULL);
switch ($civiPrefix) {
case $nativePrefix:
return $name;
* The permission to check.
* @param int $userId
*
+ * @return bool;
+ *
*/
public function check($str, $userId = NULL) {
//no default behaviour
+ return FALSE;
}
/**
* Array of permissions, in the same format as CRM_Core_Permission::getCorePermissions().
* @see CRM_Core_Permission::getCorePermissions
*/
- public static function getModulePermissions($module) {
+ public function getModulePermissions($module): array {
$return_permissions = [];
$fn_name = "{$module}_civicrm_permission";
if (function_exists($fn_name)) {
* in all enabled CiviCRM module extensions.
*
* @param bool $descriptions
+ * @param array $permissions
*
* @return array
* Array of permissions, in the same format as CRM_Core_Permission::getCorePermissions().
*/
- public function getAllModulePermissions($descriptions = FALSE) {
- // Passing in false here is to be deprecated.
- $permissions = [];
- CRM_Utils_Hook::permission($permissions);
+ public function getAllModulePermissions($descriptions = FALSE, &$permissions): array {
+ $newPermissions = [];
+ CRM_Utils_Hook::permission($newPermissions, $permissions);
+ $permissions = array_merge($permissions, $newPermissions);
if ($descriptions) {
foreach ($permissions as $permission => $label) {
}
}
else {
+ // Passing in false here is to be deprecated.
foreach ($permissions as $permission => $label) {
$permissions[$permission] = (is_array($label)) ? array_shift($label) : $label;
}
* @return array
* Array of permissions, in the same format as CRM_Core_Permission::getCorePermissions().
*/
- public static function getModulePermissions($module) {
+ public function getModulePermissions($module):array {
$return_permissions = [];
$fn_name = "{$module}_civicrm_permission";
if (function_exists($fn_name)) {
* 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 $newPermissions
+ * Array to be filled with permissions.
* @param array $permissions
- * Array of permissions. See CRM_Core_Permission::getCorePermissions() for
- * the format of this array.
+ * Already calculated permissions. These can be altered. Notably an
+ * extension might want to add it's permissions to 'implied' or to
+ * remove some permissions.
*
* @return null
* The return value is ignored
*/
- public static function permission(&$permissions) {
- return self::singleton()->invoke(['permissions'], $permissions,
- self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject,
+ public static function permission(&$newPermissions, &$permissions) {
+ return self::singleton()->invoke(['permissions', 'all_permissions'], $newPermissions, $permissions,
+ self::$_nullObject, self::$_nullObject, self::$_nullObject, self::$_nullObject,
'civicrm_permission'
);
}
* @return array
* (0 => input to translatePermission, 1 => expected output from translatePermission)
*/
- public function translateData() {
+ public function translateData(): array {
$cases = [];
$cases[] = ['administer CiviCRM', 'administer CiviCRM'];
* @param string $expected
* The name of an actual permission (based on translation matrix for "runtime").
*/
- public function testTranslate($input, $expected) {
+ public function testTranslate(string $input, string $expected): void {
$perm = new CRM_Core_Permission_Base();
$actual = $perm->translatePermission($input, 'myruntime', [
'universal name' => 'local name',
/**
* Test that the user has the implied permission of administer CiviCRM data by virtue of having administer CiviCRM.
*/
- public function testImpliedPermission() {
+ public function testImpliedPermission(): void {
$this->createLoggedInUser();
CRM_Core_Config::singleton()->userPermissionClass->permissions = [
'administer CiviCRM',
$this->assertTrue(CRM_Core_Permission::check('administer CiviCRM data'));
}
+ /**
+ * Test that the super permission gives the implied permission of the whole shebang.
+ */
+ public function testImpliedPermissionSuperDuper(): void {
+ $this->createLoggedInUser();
+ CRM_Core_Config::singleton()->userPermissionClass->permissions = [
+ 'all CiviCRM permissions and ACLs',
+ ];
+ $this->assertTrue(CRM_Core_Permission::check('view all contacts'));
+ }
+
}