Skip to content
Snippets Groups Projects

#3529426: Add field and entity access check on `ApiAutoSaveController::post()`

Files
3
@@ -15,6 +15,7 @@ use Drupal\Core\Entity\EntityPublishedInterface;
@@ -15,6 +15,7 @@ use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\File\FileUrlGeneratorInterface;
 
use Drupal\Core\Http\Exception\CacheableAccessDeniedHttpException;
use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Utility\Error;
use Drupal\Core\Utility\Error;
@@ -150,7 +151,7 @@ final class ApiAutoSaveController extends ApiControllerBase {
@@ -150,7 +151,7 @@ final class ApiAutoSaveController extends ApiControllerBase {
}
}
// We keep these in an array instead of making use of a collection like
// We keep these in an array instead of making use of a collection like
// ConstraintViolationList so we can keep violations grouped by each entity.
// ConstraintViolationList, so we can keep violations grouped by each entity.
$violationSets = [];
$violationSets = [];
$entities = [];
$entities = [];
// The client auto-saves do not contain the 'data' key, so we need to use
// The client auto-saves do not contain the 'data' key, so we need to use
@@ -159,6 +160,15 @@ final class ApiAutoSaveController extends ApiControllerBase {
@@ -159,6 +160,15 @@ final class ApiAutoSaveController extends ApiControllerBase {
foreach ($publish_auto_saves as $auto_save) {
foreach ($publish_auto_saves as $auto_save) {
$entity = $this->entityTypeManager->getStorage($auto_save['entity_type'])
$entity = $this->entityTypeManager->getStorage($auto_save['entity_type'])
->load($auto_save['entity_id']);
->load($auto_save['entity_id']);
 
assert($entity instanceof EntityInterface);
 
$access = $entity->access(operation: 'update', return_as_object: TRUE);
 
if (!$access->isAllowed()) {
 
$cache = new CacheableMetadata();
 
$cache->addCacheableDependency($entity);
 
$cache->addCacheableDependency($access);
 
$cache->addCacheTags([AutoSaveManager::CACHE_TAG]);
 
throw new CacheableAccessDeniedHttpException($cache, sprintf('Unable to update entity \'%s\'', $entity->label()));
 
}
try {
try {
if ($entity instanceof PageRegion) {
if ($entity instanceof PageRegion) {
@@ -176,19 +186,32 @@ final class ApiAutoSaveController extends ApiControllerBase {
@@ -176,19 +186,32 @@ final class ApiAutoSaveController extends ApiControllerBase {
}
}
else {
else {
assert($entity instanceof ContentEntityInterface);
assert($entity instanceof ContentEntityInterface);
 
// For checking field access, we cannot use the ClientDataToEntityConverter
 
// as it internally uses the form, and the form will already filter out
 
// and ignore the fields we don't have access to.
 
$updated_entity_form_field_names = [];
 
// Expand form values from their respective element name, e.g.
 
// ['title[0][value]' => 'Node title'] becomes
 
// ['title' => ['value' => 'Node title']].
 
// @see \Drupal\experience_builder\Controller\ApiLayoutController::getEntityData
 
\parse_str(\http_build_query($auto_save['data']['entity_form_fields']), $updated_entity_form_field_names);
 
// Filter out form fields that are not entity fields including form_* keys.
 
$updated_entity_form_field_names = array_keys(array_filter($updated_entity_form_field_names, static fn(string|int $key): bool => (is_string($key) && $entity->hasField($key)), ARRAY_FILTER_USE_KEY));
$is_new = AutoSaveManager::contentEntityIsConsideredNew($entity);
foreach ($updated_entity_form_field_names as $updated_entity_form_field_name) {
$access = $entity->get($updated_entity_form_field_name)->access(operation: 'edit', return_as_object: TRUE);
if ($entity instanceof EntityPublishedInterface) {
// We need to ignore the changed field, see \Drupal\Core\Field\ChangedFieldItemList::defaultAccess.
$entity->setPublished();
if ($updated_entity_form_field_name !== 'changed' && !$access->isAllowed()) {
}
$cache = new CacheableMetadata();
if ($entity instanceof RevisionableInterface) {
$cache->addCacheableDependency($entity);
// If the entity is new, the autosaved data is considered to be part
$cache->addCacheableDependency($access);
// of the first revision. Therefore, do not create a new revision
$cache->addCacheTags([AutoSaveManager::CACHE_TAG]);
// for new entities.
throw new CacheableAccessDeniedHttpException($cache, sprintf('Unable to update entity \'%s\', no access to field \'%s\'.', $entity->label(), $updated_entity_form_field_name));
$entity->setNewRevision(!$is_new);
}
}
}
 
$is_new = AutoSaveManager::contentEntityIsConsideredNew($entity);
 
// Pluck out only the content region.
// Pluck out only the content region.
$content_region = \array_values(\array_filter($auto_save['data']['layout'], static fn(array $region) => $region['id'] === XbPageVariant::MAIN_CONTENT_REGION));
$content_region = \array_values(\array_filter($auto_save['data']['layout'], static fn(array $region) => $region['id'] === XbPageVariant::MAIN_CONTENT_REGION));
$this->clientDataToEntityConverter->convert([
$this->clientDataToEntityConverter->convert([
@@ -196,6 +219,16 @@ final class ApiAutoSaveController extends ApiControllerBase {
@@ -196,6 +219,16 @@ final class ApiAutoSaveController extends ApiControllerBase {
'model' => $auto_save['data']['model'],
'model' => $auto_save['data']['model'],
'entity_form_fields' => $auto_save['data']['entity_form_fields'],
'entity_form_fields' => $auto_save['data']['entity_form_fields'],
], $entity);
], $entity);
 
 
if ($entity instanceof RevisionableInterface) {
 
// If the entity is new, the autosaved data is considered to be part
 
// of the first revision. Therefore, do not create a new revision
 
// for new entities.
 
$entity->setNewRevision(!$is_new);
 
}
 
if ($entity instanceof EntityPublishedInterface) {
 
$entity->setPublished();
 
}
}
}
$entities[] = $entity;
$entities[] = $entity;
Loading