fix: #3630881 Store the client on refresh tokens

Closes https://www.drupal.org/project/simple_oauth/issues/3630881

What changed

RefreshTokenEntityNormalizer::normalize() now sets client from the client of the access token the refresh token belongs to. Before, it saved only value and expire, so every refresh_token row had an empty client field and ExpiredCollector::collectForClient($client, TRUE) never returned refresh tokens.

Existing refresh tokens keep an empty client. The row has no link to its access token, so there's nothing to backfill from. They expire on their own.

Behavior

  • collectForClient() without $include_refresh, collectForAccount(), and consumer and user update invalidation all filter out the refresh_token bundle. Their behavior doesn't change.
  • collectForClient($client, TRUE) now returns the client's refresh tokens. With #3630880 (!240 (merged)), deleting a consumer also deletes its refresh tokens.
  • Related: simple_oauth_server_metadata's TokenRevocationService::validateOwnership() returns FALSE when a token has no client. I haven't verified whether its lookup finds refresh tokens, but this removes one reason RFC 7009 revocation of a refresh token would fail.

Testing

RefreshTokenTest::testRefreshTokenReferencesClient() asserts that the refresh token issued in the authorization code flow references the consumer and that collectForClient($client, TRUE) returns it. Without the fix, it fails with null for the client.

AI-Generated: Yes. I wrote this fix and description with Claude Code and reviewed both.

Merge request reports

Loading
Loading