--- /dev/null
+<?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
+ */
+
+/**
+ * Trait shared with entities attached to the contact record.
+ */
+trait CRM_Contact_AccessTrait {
+
+ /**
+ * @param string $action
+ * @param array $record
+ * @param int|NULL $userID
+ * @return bool
+ * @see CRM_Core_DAO::checkAccess
+ */
+ public static function _checkAccess(string $action, array $record, $userID) {
+ $cid = $record['contact_id'] ?? NULL;
+ if (!$cid && !empty($record['id'])) {
+ $cid = CRM_Core_DAO::getFieldValue(__CLASS__, $record['id'], 'contact_id');
+ }
+ if (!$cid) {
+ // With no contact id this must be part of an event locblock
+ return in_array(__CLASS__, ['CRM_Core_BAO_Phone', 'CRM_Core_BAO_Email', 'CRM_Core_BAO_Address']) &&
+ CRM_Core_Permission::check('edit all events', $userID);
+ }
+ return CRM_Contact_BAO_Contact::checkAccess($action, ['id' => $cid], $userID);
+ }
+
+}
];
}
+ /**
+ * @param string $action
+ * @param array $record
+ * @param $userID
+ * @return bool
+ * @see CRM_Core_DAO::checkAccess
+ */
+ public static function _checkAccess(string $action, array $record, $userID): bool {
+ switch ($action) {
+ case 'create':
+ return CRM_Core_Permission::check('add contacts', $userID);
+
+ case 'get':
+ $actionType = CRM_Core_Permission::VIEW;
+ break;
+
+ case 'delete':
+ $actionType = CRM_Core_Permission::DELETE;
+ break;
+
+ default:
+ $actionType = CRM_Core_Permission::EDIT;
+ break;
+ }
+
+ return CRM_Contact_BAO_Contact_Permission::allow($record['id'], $actionType, $userID);
+ }
+
}
* @param int $id
* Contact id.
* @param int|string $type the type of operation (view|edit)
+ * @param int $userID
+ * Contact id of user to check (defaults to current logged-in user)
*
* @return bool
* true if the user has permission, false otherwise
*/
- public static function allow($id, $type = CRM_Core_Permission::VIEW) {
- // get logged in user
- $contactID = CRM_Core_Session::getLoggedInContactID();
+ public static function allow($id, $type = CRM_Core_Permission::VIEW, $userID = NULL) {
+ // Default to logged in user if not supplied
+ $userID = $userID ?? CRM_Core_Session::getLoggedInContactID();
// first: check if contact is trying to view own contact
- if ($contactID == $id && ($type == CRM_Core_Permission::VIEW && CRM_Core_Permission::check('view my contact')
- || $type == CRM_Core_Permission::EDIT && CRM_Core_Permission::check('edit my contact'))
+ if ($userID == $id && ($type == CRM_Core_Permission::VIEW && CRM_Core_Permission::check('view my contact')
+ || $type == CRM_Core_Permission::EDIT && CRM_Core_Permission::check('edit my contact', $userID))
) {
return TRUE;
}
// FIXME: push this somewhere below, to not give this permission so many rights
$isDeleted = (bool) CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $id, 'is_deleted');
- if (CRM_Core_Permission::check('access deleted contacts') && $isDeleted) {
+ if (CRM_Core_Permission::check('access deleted contacts', $userID) && $isDeleted) {
return TRUE;
}
// short circuit for admin rights here so we avoid unneeeded queries
// some duplication of code, but we skip 3-5 queries
- if (CRM_Core_Permission::check('edit all contacts') ||
- ($type == CRM_ACL_API::VIEW && CRM_Core_Permission::check('view all contacts'))
+ if (CRM_Core_Permission::check('edit all contacts', $userID) ||
+ ($type == CRM_Core_Permission::VIEW && CRM_Core_Permission::check('view all contacts', $userID))
) {
return TRUE;
}
// check permission based on relationship, CRM-2963
- if (self::relationshipList([$id], $type)) {
+ if (self::relationshipList([$id], $type, $userID)) {
return TRUE;
}
$tables = [];
$whereTables = [];
- $permission = CRM_ACL_API::whereClause($type, $tables, $whereTables, NULL, FALSE, FALSE, TRUE);
+ $permission = CRM_ACL_API::whereClause($type, $tables, $whereTables, $userID, FALSE, FALSE, TRUE);
$from = CRM_Contact_BAO_Query::fromClause($whereTables);
$query = "
LIMIT 1
";
- if (CRM_Core_DAO::singleValueQuery($query, [1 => [$id, 'Integer']])) {
- return TRUE;
- }
- return FALSE;
+ return (bool) CRM_Core_DAO::singleValueQuery($query, [1 => [$id, 'Integer']]);
}
/**
/**
* Filter a list of contact_ids by the ones that the
- * currently active user as a permissioned relationship with
+ * user as a permissioned relationship with
*
* @param array $contact_ids
* List of contact IDs to be filtered
- *
* @param int $type
* access type CRM_Core_Permission::VIEW or CRM_Core_Permission::EDIT
+ * @param int $userID
*
* @return array
* List of contact IDs that the user has permissions for
*/
- public static function relationshipList($contact_ids, $type) {
+ public static function relationshipList($contact_ids, $type, $userID = NULL) {
$result_set = [];
// no processing empty lists (avoid SQL errors as well)
return [];
}
- // get the currently logged in user
- $contactID = CRM_Core_Session::getLoggedInContactID();
- if (empty($contactID)) {
+ // Default to currently logged in user
+ $userID = $userID ?? CRM_Core_Session::getLoggedInContactID();
+ if (empty($userID)) {
return [];
}
SELECT civicrm_relationship.{$contact_id_column} AS contact_id
FROM civicrm_relationship
{$LEFT_JOIN_DELETED}
- WHERE civicrm_relationship.{$user_id_column} = {$contactID}
+ WHERE civicrm_relationship.{$user_id_column} = {$userID}
AND civicrm_relationship.{$contact_id_column} IN ({$contact_id_list})
AND civicrm_relationship.is_active = 1
AND civicrm_relationship.is_permission_{$direction['from']}_{$direction['to']} {$is_perm_condition}
FROM civicrm_relationship first_degree_relationship
LEFT JOIN civicrm_relationship second_degree_relationship ON first_degree_relationship.contact_id_{$first_direction['to']} = second_degree_relationship.contact_id_{$second_direction['from']}
{$LEFT_JOIN_DELETED}
- WHERE first_degree_relationship.contact_id_{$first_direction['from']} = {$contactID}
+ WHERE first_degree_relationship.contact_id_{$first_direction['from']} = {$userID}
AND second_degree_relationship.contact_id_{$second_direction['to']} IN ({$contact_id_list})
AND first_degree_relationship.is_active = 1
AND first_degree_relationship.is_permission_{$first_direction['from']}_{$first_direction['to']} {$is_perm_condition}
* This is class to handle address related functions.
*/
class CRM_Core_BAO_Address extends CRM_Core_DAO_Address {
+ use CRM_Contact_AccessTrait;
/**
* Takes an associative array and creates a address.
* This class contains functions for email handling.
*/
class CRM_Core_BAO_Email extends CRM_Core_DAO_Email {
+ use CRM_Contact_AccessTrait;
/**
* Create email address.
* This class contain function for IM handling
*/
class CRM_Core_BAO_IM extends CRM_Core_DAO_IM {
+ use CRM_Contact_AccessTrait;
/**
* Create or update IM record.
* This class contains function for Open Id
*/
class CRM_Core_BAO_OpenID extends CRM_Core_DAO_OpenID {
+ use CRM_Contact_AccessTrait;
/**
* Create or update OpenID record.
* Class contains functions for phone.
*/
class CRM_Core_BAO_Phone extends CRM_Core_DAO_Phone {
+ use CRM_Contact_AccessTrait;
/**
* Create phone object - note that the create function calls 'add' but
* This class contain function for Website handling.
*/
class CRM_Core_BAO_Website extends CRM_Core_DAO_Website {
+ use CRM_Contact_AccessTrait;
/**
* Create or update Website record.
}
$items = $this->getBatchRecords();
+
+ if ($this->getCheckPermissions()) {
+ foreach ($items as $key => $item) {
+ if (!CoreUtil::checkAccess($this->getEntityName(), $this->getActionName(), $item)) {
+ throw new UnauthorizedException("ACL check failed");
+ }
+ $items[$key]['check_permissions'] = TRUE;
+ }
+ }
if ($items) {
$result->exchangeArray($this->deleteObjects($items));
}
$ids = [];
$baoName = $this->getBaoName();
- if ($this->getCheckPermissions()) {
- foreach (array_keys($items) as $key) {
- if (!CoreUtil::checkAccess($this->getEntityName(), $this->getActionName(), $items[$key])) {
- throw new UnauthorizedException("ACL check failed");
- }
- $items[$key]['check_permissions'] = TRUE;
- $this->checkContactPermissions($baoName, $items[$key]);
- }
- }
-
if ($this->getEntityName() !== 'EntityTag' && method_exists($baoName, 'del')) {
foreach ($items as $item) {
$args = [$item['id']];
$item['contact_id'] = $entityId;
}
- if ($this->getCheckPermissions()) {
- $this->checkContactPermissions($baoName, $item);
- }
-
if ($this->getEntityName() === 'Address') {
$createResult = $baoName::$method($item, $this->fixAddress);
}
return isset($info[$fieldName]) ? ['suffix' => $suffix] + $info[$fieldName] : NULL;
}
- /**
- * Check edit/delete permissions for contacts and related entities.
- *
- * @param string $baoName
- * @param array $item
- *
- * @throws \Civi\API\Exception\UnauthorizedException
- */
- protected function checkContactPermissions($baoName, $item) {
- if ($baoName === 'CRM_Contact_BAO_Contact' && !empty($item['id'])) {
- $permission = $this->getActionName() === 'delete' ? \CRM_Core_Permission::DELETE : \CRM_Core_Permission::EDIT;
- if (!\CRM_Contact_BAO_Contact_Permission::allow($item['id'], $permission)) {
- throw new \Civi\API\Exception\UnauthorizedException('Permission denied to modify contact record');
- }
- }
- else {
- // Fixme: decouple from v3
- require_once 'api/v3/utils.php';
- _civicrm_api3_check_edit_permissions($baoName, $item);
- }
- }
-
}
$baoName = self::getBAOFromApiName($entityName);
// If entity has a BAO, run the BAO::checkAccess function, which will call the hook
if ($baoName && strpos($baoName, '_BAO_')) {
- $baoName::checkAccess($actionName, $record, NULL, $granted);
+ $granted = $baoName::checkAccess($actionName, $record, NULL, $granted);
}
// Otherwise, call the hook directly
else {
$config->userPermissionClass->permissions = ['access CiviCRM'];
$result = $this->callAPIFailure('contact', 'update', $params);
- $this->assertEquals('Permission denied to modify contact record', $result['error_message']);
+ if ($version == 3) {
+ $this->assertEquals('Permission denied to modify contact record', $result['error_message']);
+ }
+ else {
+ $this->assertEquals('ACL check failed', $result['error_message']);
+ }
$config->userPermissionClass->permissions = [
'access CiviCRM',