Problem/Motivation

We want to be more specific when serializing access denied exception. In particular we want the resource identifier object to be present in the error. That resource identifier object should point to the entity that caused the access denied exception.

Proposed resolution

Introduce an EntityAccessDeniedHttpException in the same fashion that we did for UnprocessableHttpEntityException.

Remaining tasks

User interface changes

API changes

Data model changes

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

e0ipso created an issue. See original summary.

dawehner’s picture

Here is a rough start. Do you believe we should support potential multiple sources of errors on the longrun? I guess for now, as we throw exceptions, we can't really.

e0ipso’s picture

Status: Active » Needs work

Thanks @dawehner! This is definitely the path we want to move this forward.

Some comments.

  1. +++ b/src/Controller/EntityResource.php
    @@ -117,7 +119,15 @@ class EntityResource {
    +      $exception = new EntityAccessDeniedHttpException(403, 'The current user is not allowed to GET the selected resource.');
    

    The new statements do not match the class constructor arguments.

  2. +++ b/src/Controller/EntityResource.php
    @@ -117,7 +119,15 @@ class EntityResource {
    +      if ($entity_access instanceof AccessResultReasonInterface) {
    +        $exception->addError($entity, '/data', $entity_access->getReason());
    +      }
    +      else {
    +        $exception->addError($entity, '/data');
    +      }
    

    There seems to be quite some duplication around this code.

    What if we had a static method in EntityAccessDeniedHttpException that did all this?

    Something like:

    public static function create($message, $entity, $pointer, $access = NULL) {
      …
    }
    

    In that scenario we could turn addError into a protected method to reduce API surface.

  3. +++ b/src/Error/EntityAccessDeniedHttpException.php
    @@ -0,0 +1,63 @@
    +   * UnprocessableHttpEntityException constructor.
    

    Copy & Pasta

  4. +++ b/src/Error/EntityAccessDeniedHttpException.php
    @@ -0,0 +1,63 @@
    +    $this->entities[] = $entity;
    +    $this->pointers[] = $pointer;
    +    $this->reasons[] = $reason;
    

    This approach seems a bit brittle. Why not an array of errors keyed by entity ID.

    $this->errors[$entity->id()] = [
      'entity' => $entity,
      'pointer' => $pointer,
      'entity' => $reason,
    ];
    

    That way the arrays cannot get out of sync with each other.

  5. +++ b/src/Error/EntityAccessDeniedHttpException.php
    @@ -0,0 +1,63 @@
    +    return $this;
    

    What's the gain on returning self instead of not returning?

dawehner’s picture

public static function create($message, $entity, $pointer, $access = NULL) {
  …

I thought about that ... there is one small problem, the backtrace would be a bit more confusing than on a normal exception. The backtrace will be created where the new call is done. Do we care much about that?

What's the gain on returning self instead of not returning?

Well, theoretically we could chain call, but I couldn't care less :)

dawehner’s picture

Status: Needs work » Needs review

The last submitted patch, 2: 2844130-2.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 4: 2844130-4.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
12.5 KB
4.77 KB

One remaining question is: Do we really want to pretend we can support multiple exception. To be honest I'm not convinced by that.

Status: Needs review » Needs work

The last submitted patch, 8: 2844130-8.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
16.63 KB
14.1 KB

I removed the "support" for multiple exceptions now. The test coverage got also improved.

e0ipso’s picture

Assigned: Unassigned » e0ipso
Status: Needs review » Needs work
FileSize
16.74 KB
+++ b/src/Normalizer/EntityAccessDeniedHttpExceptionNormalizer.php
@@ -0,0 +1,46 @@
+    if ($exception instanceof EntityAccessDeniedHttpException) {

This should be enforced by protected $supportedInterfaceOrClass = EntityAccessDeniedHttpException::class;. However I'm good keeping this in case anyone changes the applies logic when extending this.


Assigning to myself to add a test for a collection that includes 403. I also did a needed reroll.

e0ipso’s picture

Status: Needs work » Needs review
e0ipso’s picture

Changes:

  • Added the resource name to the error ID to identify the entity that failed more easily.
  • Included the 403 inside of collections and patching relationships.
  • Fixed some JSON pointers.
  • Added an extra assertion for the collections 403s.
e0ipso’s picture

Ugh! I sent the interdiff with the wrong extension. Now the testbot is going to choke. Is there any way to cancel a queued test?

e0ipso’s picture

e0ipso’s picture

Status: Needs review » Fixed

  • e0ipso committed 088c9cd on 8.x-1.x authored by dawehner
    feat(Errors) Create a custom EntityAccessDeniedHttpException (#2844130...
dawehner’s picture

Thank you @e0ipso!

Status: Fixed » Closed (fixed)

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