/** * @param Update $update * @return mixed * @throws Exception\RuntimeException * @throws \Directus\Acl\Exception\UnauthorizedFieldWriteException * @throws \Directus\Acl\Exception\UnauthorizedTableBigEditException * @throws \Directus\Acl\Exception\UnauthorizedTableEditException */ protected function executeUpdate(Update $update) { $currentUserId = null; if (Auth::loggedIn()) { $currentUser = Auth::getUserInfo(); $currentUserId = intval($currentUser['id']); } $updateState = $update->getRawState(); $updateTable = $this->getRawTableNameFromQueryStateTable($updateState['table']); $cmsOwnerColumn = $this->acl->getCmsOwnerColumnByTable($updateTable); /** * ACL Enforcement */ // check if it's NOT soft delete $updateFields = $updateState['set']; if (!(count($updateFields) == 2 && array_key_exists(STATUS_COLUMN_NAME, $updateFields) && $updateFields[STATUS_COLUMN_NAME] == STATUS_DELETED_NUM)) { if (!$this->acl->hasTablePrivilege($updateTable, 'bigedit')) { // Parsing for the column name is unnecessary. Zend enforces raw column names. /** * Enforce Privilege: "Big" Edit */ if (false === $cmsOwnerColumn) { // All edits are "big" edits if there is no magic owner column. $aclErrorPrefix = $this->acl->getErrorMessagePrefix(); throw new UnauthorizedTableBigEditException($aclErrorPrefix . "The table `{$updateTable}` is missing the `user_create_column` within `directus_tables` (BigEdit Permission Forbidden)"); } else { // Who are the owners of these rows? list($resultQty, $ownerIds) = $this->acl->getCmsOwnerIdsByTableGatewayAndPredicate($this, $updateState['where']); // Enforce if (is_null($currentUserId) || count(array_diff($ownerIds, array($currentUserId)))) { $aclErrorPrefix = $this->acl->getErrorMessagePrefix(); throw new UnauthorizedTableBigEditException($aclErrorPrefix . "Table bigedit access forbidden on {$resultQty} `{$updateTable}` table record(s) and " . count($ownerIds) . " CMS owner(s) (with ids " . implode(", ", $ownerIds) . ")."); } } /** * Enforce write field blacklist (if user lacks bigedit privileges on this table) */ $attemptOffsets = array_keys($updateState['set']); $this->acl->enforceBlacklist($updateTable, $attemptOffsets, Acl::FIELD_WRITE_BLACKLIST); } if (!$this->acl->hasTablePrivilege($updateTable, 'edit')) { /** * Enforce Privilege: "Little" Edit (I am the record CMS owner) */ if (false !== $cmsOwnerColumn) { if (!isset($predicateResultQty)) { // Who are the owners of these rows? list($predicateResultQty, $predicateOwnerIds) = $this->acl->getCmsOwnerIdsByTableGatewayAndPredicate($this, $updateState['where']); } if (in_array($currentUserId, $predicateOwnerIds)) { $aclErrorPrefix = $this->acl->getErrorMessagePrefix(); throw new UnauthorizedTableEditException($aclErrorPrefix . "Table edit access forbidden on {$predicateResultQty} `{$updateTable}` table records owned by the authenticated CMS user (#{$currentUserId})."); } } } } try { return parent::executeUpdate($update); } catch (\Zend\Db\Adapter\Exception\InvalidQueryException $e) { if ('production' !== DIRECTUS_ENV) { // @TODO: these lines are the same as the executeInsert, // let's put it together if (strpos(strtolower($e->getMessage()), 'duplicate entry') !== FALSE) { throw new DuplicateEntryException($e->getMessage()); } throw new \RuntimeException("This query failed: " . $this->dumpSql($update), 0, $e); } // @todo send developer warning throw $e; } }
/** * @param Update $update * @return mixed * @throws Exception\RuntimeException * @throws \Directus\Acl\Exception\UnauthorizedFieldWriteException * @throws \Directus\Acl\Exception\UnauthorizedTableBigEditException * @throws \Directus\Acl\Exception\UnauthorizedTableEditException */ protected function executeUpdate(Update $update) { $currentUserId = null; if (Auth::loggedIn()) { $currentUser = Auth::getUserInfo(); $currentUserId = intval($currentUser['id']); } $updateState = $update->getRawState(); $updateTable = $this->getRawTableNameFromQueryStateTable($updateState['table']); $cmsOwnerColumn = $this->acl->getCmsOwnerColumnByTable($updateTable); $updateData = $updateState['set']; /** * ACL Enforcement */ // check if it's NOT soft delete $updateFields = $updateState['set']; $permissionName = 'edit'; $hasStatusColumn = array_key_exists(STATUS_COLUMN_NAME, $updateFields) ? true : false; if ($hasStatusColumn && $updateFields[STATUS_COLUMN_NAME] == STATUS_DELETED_NUM) { $permissionName = 'delete'; } if (!$this->acl->hasTablePrivilege($updateTable, 'big' . $permissionName)) { // Parsing for the column name is unnecessary. Zend enforces raw column names. /** * Enforce Privilege: "Big" Edit */ if (false === $cmsOwnerColumn) { // All edits are "big" edits if there is no magic owner column. $aclErrorPrefix = $this->acl->getErrorMessagePrefix(); throw new UnauthorizedTableBigEditException($aclErrorPrefix . 'The table `' . $updateTable . '` is missing the `user_create_column` within `directus_tables` (BigEdit Permission Forbidden)'); } else { // Who are the owners of these rows? list($resultQty, $ownerIds) = $this->acl->getCmsOwnerIdsByTableGatewayAndPredicate($this, $updateState['where']); // Enforce if (is_null($currentUserId) || count(array_diff($ownerIds, [$currentUserId]))) { // $aclErrorPrefix = $this->acl->getErrorMessagePrefix(); // throw new UnauthorizedTableBigEditException($aclErrorPrefix . "Table bigedit access forbidden on $resultQty `$updateTable` table record(s) and " . count($ownerIds) . " CMS owner(s) (with ids " . implode(", ", $ownerIds) . ")."); $groupsTableGateway = self::makeTableGatewayFromTableName($this->acl, 'directus_groups', $this->adapter); $group = $groupsTableGateway->find($this->acl->getGroupId()); throw new UnauthorizedTableBigEditException('[' . $group['name'] . '] permissions only allow you to [' . $permissionName . '] your own items.'); } } } if (!$this->acl->hasTablePrivilege($updateTable, $permissionName)) { /** * Enforce Privilege: "Little" Edit (I am the record CMS owner) */ if (false !== $cmsOwnerColumn) { if (!isset($predicateResultQty)) { // Who are the owners of these rows? list($predicateResultQty, $predicateOwnerIds) = $this->acl->getCmsOwnerIdsByTableGatewayAndPredicate($this, $updateState['where']); } if (in_array($currentUserId, $predicateOwnerIds)) { $aclErrorPrefix = $this->acl->getErrorMessagePrefix(); throw new UnauthorizedTableEditException($aclErrorPrefix . 'Table edit access forbidden on ' . $predicateResultQty . '`' . $updateTable . '` table records owned by the authenticated CMS user (#' . $currentUserId . '.'); } } } // Enforce write field blacklist $attemptOffsets = array_keys($updateState['set']); $this->acl->enforceBlacklist($updateTable, $attemptOffsets, Acl::FIELD_WRITE_BLACKLIST); try { $this->emitter->run('table.update:before', [$updateTable, $updateData]); $this->emitter->run('table.update.' . $updateTable . ':before', [$updateData]); $result = parent::executeUpdate($update); $this->emitter->run('table.update', [$updateTable, $updateData]); $this->emitter->run('table.update:after', [$updateTable, $updateData]); $this->emitter->run('table.update.' . $updateTable, [$updateData]); $this->emitter->run('table.update.' . $updateTable . ':after', [$updateData]); return $result; } catch (\Zend\Db\Adapter\Exception\InvalidQueryException $e) { // @TODO: these lines are the same as the executeInsert, // let's put it together if (strpos(strtolower($e->getMessage()), 'duplicate entry') !== FALSE) { throw new DuplicateEntryException($e->getMessage()); } if ('production' !== DIRECTUS_ENV) { throw new \RuntimeException('This query failed: ' . $this->dumpSql($update), 0, $e); } // @todo send developer warning throw $e; } }