Cannot remove Acquia DAM authorization if token expires, causing WSOD for all DAM functionality

Created on 2 August 2022, over 2 years ago
Updated 27 August 2024, 7 months ago

Problem/Motivation

All DAM operations around the site started failing with a WSOD for a specific user but no other accounts. Found the root cause was an (see note below) unauthorized access token via the following error when attempting to click the "Remove Acquia DAM authorization" checkbox and save user profile:

The website encountered an unexpected error. Please try again later.

GuzzleHttp\Exception\ClientException: Client error: `POST redacted domain/api/rest/oauth/logout` resulted in a `404 Not Found` response: {"error":"Not Found","description":"Access token not found."} in GuzzleHttp\Exception\RequestException::create() (line 113 of /var/www/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php).
GuzzleHttp\Middleware::GuzzleHttp\{closure}(Object) (Line: 204)
GuzzleHttp\Promise\Promise::callHandler(1, Object, NULL) (Line: 153)
GuzzleHttp\Promise\Promise::GuzzleHttp\Promise\{closure}() (Line: 48)
GuzzleHttp\Promise\TaskQueue->run(1) (Line: 248)
GuzzleHttp\Promise\Promise->invokeWaitFn() (Line: 224)
GuzzleHttp\Promise\Promise->waitIfPending() (Line: 269)
GuzzleHttp\Promise\Promise->invokeWaitList() (Line: 226)
GuzzleHttp\Promise\Promise->waitIfPending() (Line: 62)
GuzzleHttp\Promise\Promise->wait() (Line: 182)
GuzzleHttp\Client->request('post', 'redacted domain/api/rest/oauth/logout', Array) (Line: 95)
GuzzleHttp\Client->__call('post', Array) (Line: 105)
Drupal\media_acquiadam\AcquiadamAuthService::cancel('redacted token') (Line: 85)
media_acquiadam_unauthorize(Array, Object)
call_user_func_array('media_acquiadam_unauthorize', Array) (Line: 114)
Drupal\Core\Form\FormSubmitter->executeSubmitHandlers(Array, Object) (Line: 52)
Drupal\Core\Form\FormSubmitter->doSubmitForm(Array, Object) (Line: 601)
Drupal\Core\Form\FormBuilder->processForm('user_form', Array, Object) (Line: 320)
Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 73)
Drupal\Core\Controller\FormController->getContentResult(Object, Object) (Line: 39)
Drupal\layout_builder\Controller\LayoutBuilderHtmlEntityFormController->getContentResult(Object, Object)
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 564)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 158)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 80)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 265)
Drupal\shield\ShieldMiddleware->bypass(Object, 1, 1) (Line: 132)
Drupal\shield\ShieldMiddleware->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 708)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

Steps to reproduce

  1. Authorize Acquia DAM for an account to obtain a token.
  2. Wait for token to expire* (see note below)
  3. Attempt to use "Remove Acquia DAM authorization" functionality

I'll note here that, since the expiration time for tokens could be a while, you could approximate this behavior for testing the handling of the response error by simply passing a garbage token instead of an expired one. The response from the API does not note the token is expired, just that it is "not found". So passing a bad token would get this same response and you could then test the error handling needed, as noted below.

Proposed resolution

So, tracing this out, this issue comes from the API returning a 401 if the given token is found to be invalid. Specifically, in media_acquiadam/media_acquiadam.module in the media_acquiadam_unauthorize() function.

Relevant code:

...
 // Cancel the user token on Acquia DAM.
    $cancelled = AcquiadamAuthService::cancel($acquiadam_account['acquiadam_token']);

    // Remove the Acquia DAM data (mainly the token) from the user account.
    if ($cancelled) {
      \Drupal::service('user.data')
        ->set('media_acquiadam', $user->id(), 'account', []);
    }
...

The issue is that, with an expired* token, the cancel() call receives a 401 Unauthorized and throws an error, causing a WSOD noting that the provided access token was not found.

My temporary workaround to unblock the user was to comment out the call to the cancel() function and also the if statement around the user.data service line. Leaving only the line that clears out the related Acquia account information.

To account for this edge case, that code should be updated to handle the 401 error and determine if the "access token not found" error was the cause. If it was, the code should continue into the if($cancelled) block to remove the bad token from the user.

Update: Created a patch for this and added it below. After some more thought, it seems that within this cancel() function, a 401 error would only be caused by a bad token being used.

Update: Now that I have patched this, catching this error in the .module file is a more appropriate and cleaner place to address this.

A Note on the token being "Expired"

My initial assumption was that the token had expired and that was causing this issue but that may be incorrect. Where we are seeing this issue is in an active dev environment where the database is still being copied and replaced between environments when necessary. Given that, this may not be caused by a token expiration but may, instead, be a function of those databases being out of sync and having different tokens.

That point is a bit moot, however, as this bug where an invalid token creates a situation where the user cannot clear their authentication and reauthenticate to get a new token should be handled.

πŸ› Bug report
Status

Fixed

Version

2.0

Component

Code

Created by

πŸ‡ΊπŸ‡ΈUnited States amitchell-p2

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • πŸ‡­πŸ‡ΊHungary Balu Ertl Budapest πŸ‡ͺπŸ‡Ί

    Balu Ertl β†’ made their first commit to this issue’s fork.

  • Status changed to Needs review 9 months ago
  • πŸ‡­πŸ‡ΊHungary Balu Ertl Budapest πŸ‡ͺπŸ‡Ί

    I feel it worth to clarify that the issue is about the Drupal user's authentication token (marked with green) and not the site's one (marked with red):

    However, by following the described steps I could not reproduce the WSOD issue as expected on the 2.x branch. My testing steps were in order:

    1. Install the module by Composer, then enable the parent and its Example Config sub-module by Drush
    2. Configure its site-wide settings on /admin/config/media/acquiadam with valid credentials
    3. Authorize Drupal user account on /user/{UID}/edit
    4. Test the proper way of working by creating a media item based on an Image type of DAM asset
    5. The media item should have a thumbnail and its name should be the asset title (usually a file name-like string)
    6. In the users_data table of the DB find your actual user account's row. Modify its value field by replacing the alphanumeric string under the serialized acquiadam_token key to something non-sense (like 0123456789abcdef0123456789abcdef). Ensure that the complete JSON value still starts with the term β€œwat_…” (acronym of Widen Access Token).
    7. Flush all caches.
    8. Test the erroneous way of working by creating another media item (eg. by sticking with the Image type of DAM asset).
    9. The media item is being created in Drupal but has no thumbnail (only a general placeholder image appears instead) and its name does not resemble any filename. (This is because no metadata was available due to the disconnection from the remote DAM system)
    10. Visit Watchdog and filter for the module name. Several log messages were emitted, some of them similar to these:
    11. Switching back the valid β€œwat_…” string in your user account's record of the DB (and flushing caches again) everything should work normal.
  • Status changed to Postponed: needs info 9 months ago
  • πŸ‡­πŸ‡ΊHungary Balu Ertl Budapest πŸ‡ͺπŸ‡Ί
  • πŸ‡­πŸ‡ΊHungary Balu Ertl Budapest πŸ‡ͺπŸ‡Ί
  • πŸ‡­πŸ‡ΊHungary Balu Ertl Budapest πŸ‡ͺπŸ‡Ί
  • πŸ‡­πŸ‡ΊHungary Balu Ertl Budapest πŸ‡ͺπŸ‡Ί

    Update: I just realised that my testing procedure described in #9 above is unrelated to this original issue. The unauthentication link needs to be clicked on the /user/{UID}/edit page instead, and then WSOD happens indeed. Sorry for the confuse.

  • Status changed to Needs review 9 months ago
  • πŸ‡­πŸ‡ΊHungary Balu Ertl Budapest πŸ‡ͺπŸ‡Ί
  • Pipeline finished with Skipped
    8 months ago
    #252389
  • First commit to issue fork.
  • Status changed to Fixed 8 months ago
  • πŸ‡ΊπŸ‡ΈUnited States japerry KVUO
  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.71.5 2024