Steps to reproduce

  • Create a content type with an entity reference field, able to reference the same content type.
  • Create an unpublished node.
  • Create another node referencing the unpublished node.
  • Give a test user access to edit all content of that type, but not view unpublished content.
  • As the test user, edit the second node. Notice the reference field says “- Restricted access - (1)”
  • Try saving the node, get an “This entity (node: 1) cannot be referenced.” error. The only way it can be saved is to remove the reference.

Possible solutions

  • Hide the inaccessible reference completely from the form. The user won’t really know about or be able to delete the reference.
  • Bypass access checking when validating unchanged references.

Comments

drumm created an issue. See original summary.

spotzero’s picture

There are good reasons why saving a form with an entity reference to an entity that the current user doesn't have access to is not allowed, so I'd recommend against making this the default behaviour.

I think a reasonable implementation would be to add a specific permission to grant access to doing this, since you are giving the user an elevated access. Also, if we explicitly add a permission for this, it means that we can leave the referenced entity visible for the user (with the current "restricted access" text) and just alter the permissions check to allow the form submission.

drumm’s picture

Issue summary: View changes

I clarified the possible solution in the issue summary, emphasized words added.

Bypass access checking when validating unchanged references.

By default, Drupal doesn’t protect against content enumeration. You can get the IDs of all the inaccessible content by crawling node/{nid} and looking for 404 vs. 403. So exposing this in the field isn’t really a security issue. Presumably the user has some trust since they are editing content. That said, we shouldn't open up more than needed, so letting only unchanged references through makes sense.

spotzero’s picture

Ah, that's completely reasonable. Disregard my previous comment.

amateescu’s picture

Version: 8.3.x-dev » 8.2.x-dev
Category: Task » Bug report
Status: Active » Needs review
StatusFileSize
new4.32 KB
new5.61 KB

We've hit this problem in Entityqueue 7.x as well and we had to create a workaround for it: #2383903: Cannot save queues with entities user doesn't have access to

I would argue that this is actually a bug in Entity reference, which shouldn't try to validate pre-existing items. Here's a patch with a test.

Status: Needs review » Needs work

The last submitted patch, 5: 2791269.patch, failed testing.

The last submitted patch, 5: 2791269-test-only.patch, failed testing.

amateescu’s picture

Status: Needs work » Needs review
StatusFileSize
new5.63 KB
new993 bytes

Some tests don't bother to create the parent entity object but that's easy to fix, we just need an additional check.

marcvangend’s picture

This is related to #2807437: Anonymous user cannot upload private file, although that issue is not about existing references, but a valid (IMHO) use case for selectively allowing new references to inaccessible entities.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

peacog’s picture

Needs a re-roll because of the change to short array syntax in core #2776975: March 3, 2017: Convert core to array syntax coding standards for Drupal 8.3.x RC phase. Here's a new patch and a diff.

Interdiff doesn't seem to be able to cope with comparing the old with the new array syntax, so I've had to make do with a simple diff.

drumm’s picture

Looks okay.

Maybe we could use a test to make sure the inaccessible referenced entity remains referenced after save?

amateescu’s picture

StatusFileSize
new6 KB
new1.21 KB

Sure thing, that's a good idea :)

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

berdir’s picture

+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php
@@ -117,6 +117,20 @@ public function validate($value, Constraint $constraint) {
+      if ($value->getParent() && ($entity = $value->getEntity()) && !$entity->isNew()) {
+        $existing_entity = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id());
+        $referenced_entities = $existing_entity->{$value->getFieldDefinition()->getName()}->referencedEntities();
+
+        foreach ($referenced_entities as $referenced_entity) {
+          if (($key = array_search($referenced_entity->id(), $target_ids)) !== FALSE) {
+            unset($target_ids[$key]);
+          }
+        }

Wondering if we should make this specific to entities that exist but you as a user don't have access to?

Not quite sure how, to be honest, but this logic I think would also pass validation for use cases such as:

* No longer allowed based on the configuration, e.g. wrong bundle
* entity that was deleted (not quite sure what the current behavior there is)

amateescu’s picture

StatusFileSize
new9.15 KB
new10.4 KB
new10.28 KB

@Berdir, you are totally right!

I wrote new test cases for both of those scenarios, reference no longer allowed based on the configuration and entity that was deleted, and the test-only patch is the code from #13 with that additional test coverage.

The last submitted patch, 16: 2791269-16-test-only.patch, failed testing. View results

berdir’s picture

+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php
@@ -119,32 +119,31 @@ public function validate($value, Constraint $constraint) {
+          // Check if any of the invalid existing references are simply not
+          // accessible by the user, in which case they need to be excluded from
+          // validation
+          if (isset($previously_referenced_ids[$target_id]) && isset($existing_entities[$target_id]) && !$existing_entities[$target_id]->access('view')) {
+            continue;
+          }

Not sure how view vs view label operations apply here exactly. We currently use query tag based access for those entities, where we so far never talked about how that works combined with view label. Just wondering if there's possibly a weird/special case here, what happens when you have a user reference field and you don't have permission to view user profiles? Is there a case where that could result in the wrong result?

amateescu’s picture

Title: Allow saving with references to inaccessible items » Allow saving pre-existing references to inaccessible items

Just wondering if there's possibly a weird/special case here, what happens when you have a user reference field and you don't have permission to view user profiles? Is there a case where that could result in the wrong result?

Well, we have to keep in mind that all this is about letting the user save *pre-existing* inaccessible references, so I chose the 'view' operation especially because its scope is broader than 'view label'.

The expectation is that you are allowed to save a pre-existing reference to a user when you don't have access to view user profiles.

Edit: fixed typo.

berdir’s picture

Status: Needs review » Reviewed & tested by the community

Fair enough, lets do this, I think this is well tested.

catch’s picture

+++ b/core/tests/Drupal/KernelTests/Core/Entity/ValidReferenceConstraintValidatorTest.php
@@ -66,4 +83,152 @@ public function testValidation() {
+    // of that bundle ca not be referenced anymore.

s/ca/can.

catch’s picture

Status: Reviewed & tested by the community » Fixed

Fixed that on commit. Committed/pushed to 8.5.x and cherry-picked to 8.4.x. Thanks!

  • catch committed d79a838 on 8.5.x
    Issue #2791269 by amateescu, Peacog, drumm, Berdir: Allow saving pre-...

  • catch committed 38c3865 on 8.4.x
    Issue #2791269 by amateescu, Peacog, drumm, Berdir: Allow saving pre-...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

nghai’s picture

Dear @Berdir, Dear @amateescu

caught some issues related to the current solution for the requirement "Allow saving pre-existing references to inaccessible items"

have opened and reported the whole scenario in this ticket https://www.drupal.org/project/drupal/issues/2973863?

Could you please review once ?

Thanks

tim.plunkett’s picture

dunebl’s picture

This patch doesn't solve the following case:
1-Create a user reference field ALLOWING ONLY users with a given role
2-Add a user (having the requested role) to this field and save the node
3-block the user
4-As a non admin, edit the node and try to save it. It will be impossible (error: this entity (user:xxx) can not be referenced)