From 70655affaa6247da1baa79817e1d5aab135a7e9b Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Tue, 13 Apr 2021 08:17:01 -0400 Subject: [PATCH] APIv4 - Fix gatekeeper checks across joins --- Civi/Api4/Query/Api4SelectQuery.php | 30 +++++++++++++++++++++++++++++ Civi/Api4/Service/Schema/Joiner.php | 11 +++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/Civi/Api4/Query/Api4SelectQuery.php b/Civi/Api4/Query/Api4SelectQuery.php index 8e42ed2b25..83033154f8 100644 --- a/Civi/Api4/Query/Api4SelectQuery.php +++ b/Civi/Api4/Query/Api4SelectQuery.php @@ -81,6 +81,11 @@ class Api4SelectQuery { */ private $explicitJoins = []; + /** + * @var array + */ + private $entityAccess = []; + /** * @param \Civi\Api4\Generic\DAOGetAction $apiGet */ @@ -100,6 +105,8 @@ class Api4SelectQuery { $tableName = CoreUtil::getTableName($this->getEntity()); $this->query = \CRM_Utils_SQL_Select::from($tableName . ' ' . self::MAIN_TABLE_ALIAS); + $this->entityAccess[$this->getEntity()] = TRUE; + // Add ACLs first to avoid redundant subclauses $baoName = CoreUtil::getBAOFromApiName($this->getEntity()); $this->query->where($this->getAclClause(self::MAIN_TABLE_ALIAS, $baoName)); @@ -579,6 +586,25 @@ class Api4SelectQuery { return $field; } + /** + * Check the "gatekeeper" permissions for performing "get" on a given entity. + * + * @param $entity + * @return bool + */ + public function checkEntityAccess($entity) { + if (!$this->getCheckPermissions()) { + return TRUE; + } + if (!isset($this->entityAccess[$entity])) { + $this->entityAccess[$entity] = (bool) civicrm_api4($entity, 'getActions', [ + 'where' => [['name', '=', 'get']], + 'select' => ['name'], + ])->first(); + } + return $this->entityAccess[$entity]; + } + /** * Join onto other entities as specified by the api call. * @@ -591,6 +617,10 @@ class Api4SelectQuery { $entity = array_shift($join); // Which might contain an alias. Split on the keyword "AS" list($entity, $alias) = array_pad(explode(' AS ', $entity), 2, NULL); + // Ensure permissions + if (!$this->checkEntityAccess($entity)) { + continue; + } // Ensure alias is a safe string, and supply default if not given $alias = $alias ? \CRM_Utils_String::munge($alias, '_', 256) : strtolower($entity); // First item in the array is a boolean indicating if the join is required (aka INNER or LEFT). diff --git a/Civi/Api4/Service/Schema/Joiner.php b/Civi/Api4/Service/Schema/Joiner.php index a412d6571c..ce92405683 100644 --- a/Civi/Api4/Service/Schema/Joiner.php +++ b/Civi/Api4/Service/Schema/Joiner.php @@ -19,7 +19,9 @@ namespace Civi\Api4\Service\Schema; +use Civi\API\Exception\UnauthorizedException; use Civi\Api4\Query\Api4SelectQuery; +use Civi\Api4\Utils\CoreUtil; class Joiner { /** @@ -70,9 +72,14 @@ class Joiner { foreach ($fullPath as $link) { $target = $link->getTargetTable(); $alias = $link->getAlias(); - $bao = \CRM_Core_DAO_AllCoreTables::getBAOClassName(\CRM_Core_DAO_AllCoreTables::getClassForTable($target)); + $joinEntity = CoreUtil::getApiNameFromTableName($target); + + if ($joinEntity && !$query->checkEntityAccess($joinEntity)) { + throw new UnauthorizedException('Cannot join to ' . $joinEntity); + } + + $bao = $joinEntity ? CoreUtil::getBAOFromApiName($joinEntity) : NULL; $conditions = $link->getConditionsForJoin($baseTableAlias); - // Custom fields do not have a bao, and currently do not have field-specific ACLs if ($bao) { $conditions = array_merge($conditions, $query->getAclClause($alias, $bao, $joinPath)); } -- 2.25.1