JSON API does not yet have support for Entity API's view label access operation:

    if ($operation === 'view label' && $this->viewLabelOperation == FALSE) {
      $operation = 'view';
    }

from \Drupal\Core\Entity\EntityAccessControlHandler::access

It was added in #2471154: Anonymous user label can't be viewed and auth user labels are only accessible with 'access user profiles' permission (CR: https://www.drupal.org/node/2661092)

When an entity is not accessible, we should check if its entity type supports the 'view label' operation, and if so, retrieve the label and explicitly mention it in the JSON API error object (http://jsonapi.org/format/#errors), so that at least A) listings of labels are possible, B) sensible error messages are possible

Comments

Wim Leers created an issue. See original summary.

wim leers’s picture

hampercm’s picture

This is definitely a good thing to add, particularly so that the module can respond with Users' names if the rest of the object is inaccessible.

wim leers’s picture

#3: indeed :)

hampercm’s picture

Status: Postponed » Needs review
StatusFileSize
new3.39 KB

I've been running into issues related to the fact that GETing the Anonymous user always returns access denied, so I decided to see what a solution might look like for falling back to "view label" when accessing Users. This approach is not as elegant as I'd like, but it seems to work well.

Making it generic to all Entities might be possible, though there's one piece to that puzzle I haven't figured out yet: how to determine which field is the Entity's "label". It's easy to get the label value itself in a generic way, but then where do you put it in the response without knowing that?

Status: Needs review » Needs work

The last submitted patch, 5: view_label_entity-2843922-5.patch, failed testing.

hampercm’s picture

Status: Needs work » Needs review

Retesting...

Status: Needs review » Needs work

The last submitted patch, 5: view_label_entity-2843922-5.patch, failed testing.

e0ipso’s picture

Thanks @hampercm!

  1. +++ b/src/Controller/EntityResource.php
    @@ -118,8 +119,17 @@ class EntityResource {
    +    if (!$entity_access->isAllowed() && $entity instanceof User) {
    ...
    +      $entity = User::create([
    
    @@ -724,6 +734,16 @@ class EntityResource {
    +    if (!$access->isAllowed() && $entity instanceof User) {
    

    All the User special case wrangling is a bit awkward. We should discuss if this principle should apply to all entities.

    Also, how is the error handling affected when we can display the label but not the entity?

  2. +++ b/src/Normalizer/Value/EntityNormalizerValue.php
    @@ -68,6 +69,12 @@ class EntityNormalizerValue implements ValueExtractorInterface, RefinableCacheab
    +      $this->values = array_filter($this->values, function($value, $key) {
    +        return ($key == 'name' || $key == 'uuid');
    +      }, ARRAY_FILTER_USE_BOTH);
    

    Damn! I did not know about this ARRAY_FILTER_USE_BOTH flag.

  3. +++ b/src/Normalizer/Value/EntityNormalizerValue.php
    @@ -68,6 +69,12 @@ class EntityNormalizerValue implements ValueExtractorInterface, RefinableCacheab
    +    // Remove extra User fields when access was limited to 'view label'.
    +    if ($entity instanceof User && empty($entity->mail->value)) {
    

    Maybe we should have a LabelOnlyEntityNormalizer class that handles the normalization in that scenario?

hampercm’s picture

Thanks for the review! Responses to #9:

1) Yes, I'd prefer a general solution, I'm just missing one piece of the puzzle, as noted in #5. I guess in some cases the label doesn't even correspond to an actual field, as it can be generated by a callback. Should we just put the label in the attributes section as "label" in that case? If so, would it be a good idea to return the "label" in our responses for all entities, even those the user has full view access to?

2) :-)

3) That approach might be a bit cleaner. We just need to be careful there's no information disclosure along the way.

e0ipso’s picture

I'm still on the fence on this. I don't like the view label pattern, even if it's already used somewhere else.

Quoting @hampercm in #3

This is definitely a good thing to add, particularly so that the module can respond with Users' names if the rest of the object is inaccessible.

I disagree that we should take the liberty to disclose parts of an entity (the label) when the entity says Access Denied for the view operation.

If you want an entity to have a public label for everyone, you should make the entity accessible to everyone and the deny access based on permissions for all the fields that are not the label.

I don't feel too strongly about this. I may be convinced the other way, so I'm open for discussion.

hampercm’s picture

The label will only be shown for entities that have 'view label' access granted to the user; this is a completely separate access operation. Most entities do not have 'view label' allowed by default, User entities being the main exception, since often times full access to user information is not granted to non-admin users. Following this pattern will be completely consistent with how Drupal core functions.

More specifically, the entity representation of the "Anonymous" user is NEVER accessible by 'view' access, even to user/1; only 'view label' is ever permitted. This makes handling entities that refer to the Anonymous user difficult without the 'view label' functionality.

hampercm’s picture

Assigned: Unassigned » hampercm

Working on an improved implementation of this...

e0ipso’s picture

FYIY I'm still not sold on this idea. I feel a site owner can achieve this without requiring any extra code in the jsonapi module.

wim leers’s picture

wim leers’s picture

Title: 'view label' entity access operation » Show label of inaccessible entities ('view' access denied) when 'view label' access is allowed
Category: Task » Feature request
Issue tags: +DX (Developer Experience)
gabesullice’s picture

Damn! I did not know about this ARRAY_FILTER_USE_BOTH flag.

:O me neither!

If you want an entity to have a public label for everyone, you should make the entity accessible to everyone and the deny access based on permissions for all the fields that are not the label.

Philosophically, I have to say I'm with @e0ipso here. However, I think that ship has sailed. We likely do need to support this. I've personally worked around this issue for client work with custom controllers on several occasions.

Maybe we should have a LabelOnlyEntityNormalizer class that handles the normalization in that scenario?

A new normalizer for this seems excessive... but I don't have an elegant alternative. I'll try to think more about this.

A few open thoughts and questions:

  1. The label needs to be under the attributes key. If so, we need to give it its own key (I assume this will be "label"). Will this appear only when the 'view label' mode is invoked or always?
  2. If the answer to the above is "always", I think Drupal\Core\Entity\ContentEntityBase ought to have a computed label field. This would make our implementation much easier.
  3. If it is "always" and it is not made a computed field, then we need to worry about the "label" key conflicting with other field names...
  4. I'm not sure if we already support computed fields for sparse fieldsets, but if not, this would be a great use case for it. I imagine that it will often be the case that the label is the only thing needed.
e0ipso’s picture

However, I think that ship has sailed. We likely do need to support this.

Please elaborate.


If I provide custom module that removes view access for all entities of a type, then having a module (core or not) allowing access via a view $propertyName access check seems like as security hole for information disclosure. I don't like navigating these waters unless we absolutely need to. I don't think this is the case.

wim leers’s picture

If I provide custom module that removes view access for all entities of a type, then having a module (core or not) allowing access via a view $propertyName access check seems like as security hole for information disclosure. I don't like navigating these waters unless we absolutely need to. I don't think this is the case.

I understand your hesitation here, and I'm glad you're questioning this :)

However, the whole view label thing is a not very well-known Entity API capability. It was added in #2471154: Anonymous user label can't be viewed and auth user labels are only accessible with 'access user profiles' permission (CR: https://www.drupal.org/node/2661092), in February 2016. This operation is only supported on entity types whose access control handlers explicitly opt in, see \Drupal\Core\Entity\EntityAccessControlHandler::$viewLabelOperation:

  /**
   * Allows to grant access to just the labels.
   *
   * By default, the "view label" operation falls back to "view". Set this to
   * TRUE to allow returning different access when just listing entity labels.
   *
   * @var bool
   */
  protected $viewLabelOperation = FALSE;

Therefore there is absolutely no security risk here: if you check view label access, for most entities, it'll fall back to view access, which means that viewing just the label will still be denied. Only for entity types that explicitly have custom logic for this, it may result in the label being exposed.

gabesullice’s picture

If I provide custom module that removes view access for all entities of a type, then having a module (core or not) allowing access via a view $propertyName access check seems like as security hole for information disclosure.

The reason I say that I think the ship has sailed is because the permissions have already landed in core and they established the precedent for how the operations should be interpreted.

The existence of `view label` implies that `view` should be interpreted as `view all`. We should also remember that Drupal permissions have always been grants, not rules. That is, if a user does not have the `view` permission, we don't say that the viewing the entity is forbidden, we say that the result is neutral. Thus, if another system (node grants, hook_entity_access, etc) does grant permission, it's not an information disclosure because access was never "forbidden" in the first place.

e0ipso’s picture

You both bring solid arguments. Count me convinced.

What should we do next?

wim leers’s picture

The existence of `view label` implies that `view` should be interpreted as `view all`.

To clarify: not view all entities, but view all fields :)

wim leers’s picture

You both bring solid arguments. Count me convinced.

❤️ consensus building like this :)

What should we do next?

I'd say: nothing just yet. We have agreement now that we want this feature. But we have more important things to tackle first. This is a nice-to-have for now.

We can keep this issue Needs work now instead of marking it Closed (works as designed). Or, if you prefer, we could mark it Postponed.

gabesullice’s picture

For next steps, we will need to answer some of these when we come back around to this.

A few open thoughts and questions:

  1. The label needs to be under the attributes key. If so, we need to give it its own key (I assume this will be "label"). Will this appear only when the 'view label' mode is invoked or always?
  2. If the answer to the above is "always", I think Drupal\Core\Entity\ContentEntityBase ought to have a computed label field. This would make our implementation much easier.
  3. If it is "always" and it is not made a computed field, then we need to worry about the "label" key conflicting with other field names...
  4. I'm not sure if we already support computed fields for sparse fieldsets, but if not, this would be a great use case for it. I imagine that it will often be the case that the label is the only thing needed.
e0ipso’s picture

StatusFileSize
new84.21 KB
new66.01 KB

I'm not sure if we already support computed fields for sparse fieldsets, but if not, this would be a great use case for it. I imagine that it will often be the case that the label is the only thing needed.

We totally do.

The label needs to be under the attributes key. If so, we need to give it its own key (I assume this will be "label"). Will this appear only when the 'view label' mode is invoked or always?

All entities that have this access type should either have:

  1. A label key.
  2. A label callback.

If there is a label key, then we're already providing the label under a known field. That answers all your questions.

If there is a label callback, chances are that we're already providing it as well. In fact you can see that label_callback is being phased out #2450793: Properly deprecate support for entity type label callbacks. Which means that we can avoid the issue there and ship with special behavior for User, since it's the only core entity that uses it.

wim leers’s picture

Version: 8.x-1.x-dev » 8.x-2.x-dev

#25++

Since this is a new feature, moving to the 2.x branch.

gabesullice’s picture

Assigned: hampercm » Unassigned
wim leers’s picture

Assigned: Unassigned » wim leers
Issue tags: +API-First Initiative, +blocker, +JS Modernization Initiative

@drpal yesterday indicated in private chat that this is blocking the JS Modernization Initiative. Hence I started working on this.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new8.76 KB

This patch:

  1. Starts from scratch; #5 neither applied nor was it a usable starting point. It did give me some inspiration though :)
  2. A new LabelOnlyEntity value object was added, which contains only a single value: an entity.
  3. EntityResource::getIndividual() and ::getCollection() now also check view label access if view access is forbidden. If view label access is allowed, the entity is decorated in a LabelOnlyEntity value object.
  4. Consequently, EntityCollection now needs to allow LabelOnlyEntity objects, because some of the entities in the collection may be values of this type.
  5. A new LabelOnlyEntityNormalizer is added, to normalize LabelOnlyEntity value objects. This normalizer:
    1. determines the label field name
    2. calls EntityNormalizer, which still retuns a EntityNormalizerValue object
    3. then reconstructs a EntityNormalizerValue, omitting all values in the original EntityNormalizerValue object except the label field's
  6. Tadaa! It works! 🎉

Status: Needs review » Needs work

The last submitted patch, 29: 2843922-29.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new368 bytes
new8.76 KB

Well that's a bummer:

src/Normalizer/LabelonlyEntityNormalizer.php

should have been

src/Normalizer/LabelOnlyEntityNormalizer.php
wim leers’s picture

StatusFileSize
new1.82 KB
new10.53 KB
new1.18 KB

Test coverage!

The last submitted patch, 31: 2843922-31.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Status: Needs review » Needs work

The last submitted patch, 32: 2843922-32-test_only_FAIL.patch, failed testing. View results

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new1.13 KB
new10.31 KB

One small mistake with big consequences in #29! Thankfully we have #2953318: Comprehensive JSON API integration test coverage phase 4: collections, filtering and sorting to protect us against this, since about a week! (Which also means this feature could never have landed until a week ago!)

Status: Needs review » Needs work

The last submitted patch, 35: 2843922-35.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new7.94 KB
new17.98 KB

From 53 fails in #32 to 8 in #35.

Most of the remaining failures are for entity types that do support view label access checking: User, Menu and DateFormat.

Unfortunately, UserAccessControlHandler and MenuAccessControlHandler grant ability to view labels blindly, i.e. to all users, even the anonymous user. This is a privacy risk, and potentially information disclosure. That being said, the same data can easily be exposed via the HTML representation of the data, and commonly is. The difference is of course the queryability.

This should further reduce the number of failures.


Note: I first came up with something automatic but pretty complex to determine which the appropriate behavior is, by using introspection:

    $entity_access_control_handler_class = $this->entity->getEntityType()->getAccessControlClass();
    $instance = new $entity_access_control_handler_class($this->entity->getEntityType());
    $reflection_property = (new \ReflectionClass($entity_access_control_handler_class))
      ->getProperty('viewLabelOperation');
    $reflection_property->setAccessible(TRUE);
    if ($reflection_property->getValue($instance) !== TRUE) {

… but this breaks down for the case of MediaType, which uses view label, but does require a permission.

wim leers’s picture

StatusFileSize
new2.72 KB
new18.52 KB

Fix CS violations.

The last submitted patch, 37: 2843922-37.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Status: Needs review » Needs work

The last submitted patch, 38: 2843922-38.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new886 bytes
new19.95 KB

Whew, this was insanely hard to figure out.

wim leers’s picture

StatusFileSize
new4.2 KB

And now the correct interdiff for #41 … 😳

wim leers’s picture

And finally, the updated expectations for JsonApiDocumentTopLevelNormalizerTest. Now the patch should be green. 🤞

wim leers’s picture

StatusFileSize
new3.57 KB
new23.46 KB

Oops.

The last submitted patch, 41: 2843922-41.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

wim leers’s picture

StatusFileSize
new1.41 KB
new23.09 KB

Now that we finally have a green patch … let's retry the "add failing test" thing from #32. Although, actually, that's not necessary: #32's test-only patch did trigger a single failure, as predicted!

Fixing CS violations.

wim leers’s picture

StatusFileSize
new529 bytes
new23.17 KB

Alright, #46 is green and fixes the original report.

However, in the case of User entities, special treatment is necessary, due to incompleteness of the Entity/Field API and User module. Otherwise we'll get "name":"" for the anonymous user! Let's first add a failing test to prove this.

Otherwise, you still can't see the label!

wim leers’s picture

Assigned: wim leers » Unassigned
StatusFileSize
new1.33 KB
new24.46 KB

And fix.

Now this is done. The JS Modernization Initiative should be unblocked now; they can apply this patch until it lands.

The last submitted patch, 46: 2843922-46.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Status: Needs review » Needs work

The last submitted patch, 48: 2843922-48.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new1.12 KB
new24.61 KB

#46 didn't take that Entity/Field API incompleteness into account. Fixed.

gabesullice’s picture

Status: Needs review » Needs work

Monumental effort! Good job. This is going to make many people very happy :)

Overall, this looks really good too.

One meta question: why is this 2.x?


  1. +++ b/src/Controller/EntityResource.php
    @@ -974,9 +978,13 @@ class EntityResource {
           $output['entity'] = new EntityAccessDeniedHttpException($entity, $access, '/data', 'The current user is not allowed to GET the selected resource.');
    +      $label_access = $entity->access('view label', NULL, TRUE);
    +      if ($label_access->isAllowed()) {
    +        $output['entity'] = new LabelOnlyEntity($entity);
    +      }
    

    Ubernit: can we make this an if/else or ternary to avoid reassigning the value?

  2. +++ b/src/Normalizer/EntityNormalizer.php
    @@ -187,6 +187,21 @@ class EntityNormalizer extends NormalizerBase implements DenormalizerInterface {
    +      // @todo Fix Entity/Field API and User module in Drupal core so that despite
    +      // there being a label callback, we can also still figure out that "name" is
    +      // the 'label' entity key.
    

    Is there already an issue for this? If so, can we add a URL?

  3. +++ b/src/Normalizer/LabelOnlyEntityNormalizer.php
    @@ -0,0 +1,98 @@
    +    // If the fields to use were specified, only output those field values.
    +    $context['resource_type'] = $this->resourceTypeRepository->get(
    

    This comment no longer makes sense.

  4. +++ b/src/Normalizer/LabelOnlyEntityNormalizer.php
    @@ -0,0 +1,98 @@
    +    // Determine the (internal) label field name.
    +    $label_field_name = $entity->getEntityType()->getKey('label');
    ...
    +    if ($entity->getEntityTypeId() === 'user') {
    +      $label_field_name = 'name';
    +    }
    

    Let's move this logic to LabelOnlyEntity and expose it as a method.

  5. +++ b/src/Normalizer/LabelOnlyEntityNormalizer.php
    @@ -0,0 +1,98 @@
    +    // @todo Fix Entity/Field API and User module in Drupal core so that despite
    +    // there being a label callback, we can also still figure out that "name" is
    +    // the 'label' entity key.
    

    Same as above.

  6. +++ b/src/Normalizer/LabelOnlyEntityNormalizer.php
    @@ -0,0 +1,98 @@
    +    // Reconstruct an EntityNormalizerValue object, this time with only the
    +    // label field.
    +    $label_only_values = [$public_field_label_name => $all_values[$public_field_label_name]];
    

    This is dangerous territory. Looks good though.

  7. +++ b/src/Resource/EntityCollection.php
    @@ -44,6 +45,7 @@ class EntityCollection implements \IteratorAggregate, \Countable {
         assert(Inspector::assertAll(function ($entity) {
           return $entity === NULL
             || $entity instanceof EntityInterface
    +        || $entity instanceof LabelOnlyEntity
             || $entity instanceof EntityAccessDeniedHttpException;
         }, $entities));
    

    This is starting to smell a little, not worth fixing here, but worth putting on the backs of our minds.

  8. +++ b/tests/src/Functional/DateFormatTest.php
    @@ -29,6 +29,11 @@ class DateFormatTest extends ResourceTestBase {
    +  protected static $anonymousUsersCanViewLabels = TRUE;
    

    I like the explicitness here, over introspection 👍

  9. +++ b/tests/src/Functional/ResourceResponseTestTrait.php
    @@ -156,6 +156,9 @@ trait ResourceResponseTestTrait {
               $target_access = static::entityAccess($target_entity, 'view', $this->account);
               if (!$target_access->isAllowed()) {
    +            $target_access = static::entityAccess($target_entity, 'view label', $this->account)->addCacheableDependency($target_access);
    

    Why reassign? ... Probably to reuse the code below WRT $access->getReason()?

  10. +++ b/tests/src/Functional/UserTest.php
    @@ -414,7 +419,7 @@ class UserTest extends ResourceTestBase {
    -    $collection_url = Url::fromRoute('jsonapi.user--user.collection');
    +    $collection_url = Url::fromRoute('jsonapi.user--user.collection', [], ['query' => ['filter[status]' => 1]]);
    

    Why is the status necessary? Can we add a comment?

  11. +++ b/tests/src/Functional/UserTest.php
    @@ -461,4 +466,21 @@ class UserTest extends ResourceTestBase {
    +    $this->assertSame(User::load(0)->uuid(), $doc['data'][0]['id']);
    +    $this->assertSame('Anonymous', $doc['data'][0]['attributes']['name']);
    

    These two lines assert the whole feature request! :)

  12. +++ b/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php
    @@ -262,19 +262,20 @@ class JsonApiDocumentTopLevelNormalizerTest extends JsonapiKernelTestBase {
    +    $this->assertCount(1, $normalized['included'][0]['attributes']);
    

    Good assertion.

wim leers’s picture

Issue tags: +Needs change record

One meta question: why is this 2.x?

Because

Unfortunately, UserAccessControlHandler and MenuAccessControlHandler grant ability to view labels blindly, i.e. to all users, even the anonymous user. This is a privacy risk, and potentially information disclosure. That being said, the same data can easily be exposed via the HTML representation of the data, and commonly is. The difference is of course the queryability.

That's also why this needs a CR.

wim leers’s picture

(I'll address the feedback tomorrow btw — thanks for the super fast, very thorough review! 🙏)

GrandmaGlassesRopeMan’s picture

StatusFileSize
new156.65 KB

👏 We're now able to get the anonymous user.

wim leers’s picture

🎉🍻

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new24.76 KB

No longer applied. Rebased.

wim leers’s picture

Status: Needs review » Needs work

#52 still needs to be addressed.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new7.36 KB
new25.65 KB

Addressed everything in #52.

wim leers’s picture

All changes in #59 were trivial. The only non-trivial change was the one for #59.10.

+++ b/tests/src/Functional/UserTest.php
@@ -415,7 +415,7 @@ class UserTest extends ResourceTestBase {
-    $collection_url = Url::fromRoute('jsonapi.user--user.collection', [], ['query' => ['filter[status]' => 1]]);
+    $collection_url = Url::fromRoute('jsonapi.user--user.collection');
     // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463.
     $user_a_url = Url::fromRoute(sprintf('jsonapi.user--user.individual'), ['user' => $user_a->uuid()]);
     /* $user_a_url = $user_a->toUrl('jsonapi'); */
@@ -430,7 +430,9 @@ class UserTest extends ResourceTestBase {

@@ -430,7 +430,9 @@ class UserTest extends ResourceTestBase {
     // Also when looking at the collection.
     $response = $this->request('GET', $collection_url, $request_options);
     $doc = Json::decode((string) $response->getBody());
-    $this->assertArrayHasKey('mail', $doc['data'][1]['attributes']);
+    $this->assertSame($user_a->uuid(), $doc['data']['2']['id']);
+    $this->assertArrayHasKey('mail', $doc['data'][2]['attributes'], "Own user--user resource's 'mail' field is visible.");
+    $this->assertSame($user_b->uuid(), $doc['data'][count($doc['data']) - 1]['id']);
     $this->assertArrayNotHasKey('mail', $doc['data'][count($doc['data']) - 1]['attributes']);
 
     // Now request the same URLs, but as user B (same roles/permissions).
@@ -443,7 +445,9 @@ class UserTest extends ResourceTestBase {

@@ -443,7 +445,9 @@ class UserTest extends ResourceTestBase {
     // Also when looking at the collection.
     $response = $this->request('GET', $collection_url, $request_options);
     $doc = Json::decode((string) $response->getBody());
-    $this->assertArrayNotHasKey('mail', $doc['data'][1]['attributes']);
+    $this->assertSame($user_a->uuid(), $doc['data']['2']['id']);
+    $this->assertArrayNotHasKey('mail', $doc['data'][2]['attributes']);
+    $this->assertSame($user_b->uuid(), $doc['data'][count($doc['data']) - 1]['id']);
     $this->assertArrayHasKey('mail', $doc['data'][count($doc['data']) - 1]['attributes']);

As you can see, I removed the filter[status]=1 URL query parameter. I was only able to do this because I updated the other assertions below it. Those assertions were assuming that in data[1], they'd find the user--user resource for the currently logged in user, which is able to see their own mail field. That's still true after this patch, but what does change is that there's an extra user--user resource showing up in the response: the anonymous user. Hence the assertion needs to be updated to data[2]!

To remove all ambiguity there, I also added assertions for the ID of that resource, to ensure we're making the assertions on the intended User entity.

Status: Needs review » Needs work

The last submitted patch, 59: 2843922-59.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new1.2 KB
new25.71 KB

Clearly, one change was not trivial. Fortunately it was a test-only change:

+++ b/tests/src/Functional/ResourceResponseTestTrait.php
@@ -154,10 +154,8 @@ trait ResourceResponseTestTrait {
-          $target_access = static::entityAccess($target_entity, 'view', $this->account);
-          if (!$target_access->isAllowed()) {
-            $target_access = static::entityAccess($target_entity, 'view label', $this->account)->addCacheableDependency($target_access);
-          }
+          $target_access = static::entityAccess($target_entity, 'view', $this->account)
+            ->orIf(static::entityAccess($target_entity, 'view label', $this->account));

This was changed to address #52.9. The reassign is necessary to match the logic in \Drupal\jsonapi\Controller\EntityResource::getEntityAndAccess() and \Drupal\jsonapi\Controller\EntityResource::getIndividual(). The old logic resulted in "allowed", the new logic (which doesn't match the logic in those methods) results in "forbidden". My bad.
So, why the reassignment? Because even if 'view' results in AccessResult::forbidden() (which is the case for the anonymous User entity), we need to allow 'view label' to override that. But "forbidden" trumps anything else in regular "or" or "and" joint conditions. This is a special case. Hence the need for a reassignment: to still allow "view label" access.

wim leers’s picture

StatusFileSize
new1.93 KB
new25.93 KB

#62 also made me realize that cacheability of the view label access control logic was not yet being bubbled correctly. Fixed that now. Tests were already passing because none of the view label access control logic have cacheability other than "vary by permissions" (user.permissions).

Also fixed the only CS violation.

wim leers’s picture

Issue tags: -Needs change record

CR created: https://www.drupal.org/node/2983616

IMHO this is ready.

  • gabesullice committed 6ee65d4 on 8.x-2.x
    Issue #2843922 by drpal, Wim Leers, hampercm, e0ipso, gabesullice: Show...
gabesullice’s picture

Status: Needs review » Fixed

I agree!

wim leers’s picture

Published the CR.

Status: Fixed » Closed (fixed)

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