Skip to content

Implement cache clearance for original token scopes during revocation#3071

Open
KD23243 wants to merge 7 commits intowso2-extensions:masterfrom
KD23243:cacheClearRevoke
Open

Implement cache clearance for original token scopes during revocation#3071
KD23243 wants to merge 7 commits intowso2-extensions:masterfrom
KD23243:cacheClearRevoke

Conversation

@KD23243
Copy link
Copy Markdown
Contributor

@KD23243 KD23243 commented Feb 27, 2026

Implementation

This pull request introduces an enhancement to the OAuth token revocation process by ensuring that OAuth cache entries are cleared using the original, persisted scopes from the database rather than potentially mutated scopes from the in-memory token object. This helps prevent cache inconsistencies during token revocation. The key changes are as follows:

Enhancement to cache clearing logic:

  • Added a new method clearOAuthCacheUsingPersistedScopes to OAuthUtil.java, which retrieves the original scopes from the database and uses them to clear OAuth cache entries, addressing cases where scopes may have been mutated during request processing.
  • Updated OAuth2Service.java to call the new clearOAuthCacheUsingPersistedScopes method during token revocation, ensuring cache consistency.

Dependency and import updates:

  • Added imports for OAuthServerConfiguration and OAuthRevocationRequestDTO in OAuthUtil.java to support the new cache clearing logic. [1] [2]

Testing

Screen.Recording.2026-02-24.at.6.40.10.PM.mov

Summary by CodeRabbit

  • Bug Fixes

    • Revocation now reliably clears caches using the originally persisted token scopes (including token-bound tokens), preventing stale cache mismatches caused by in-memory scope changes.
  • Tests

    • Expanded unit tests covering cache-clearing in revocation (empty/blank/missing scopes, successful clearance, DAO errors) and broader token/utility behaviors to improve stability.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new OAuthUtil method that reads persisted token scopes and clears cache entries using those original scopes; OAuth2Service invokes this during token revocation for token-bound access tokens. Unit tests for many edge cases and related utilities were added.

Changes

Cohort / File(s) Summary
Cache Clearing Logic
components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/OAuthUtil.java
Added clearOAuthCacheUsingPersistedScopes(String tokenBindingReference, AccessTokenDO accessTokenDO, OAuthRevocationRequestDTO revokeRequestDTO) which fetches the persisted AccessTokenDO via DAO, derives the original scopes, and clears OAuth cache entries for persisted scopes (with and without tokenBindingReference). Includes early exits and IdentityOAuth2Exception handling.
Revocation Integration
components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/OAuth2Service.java
Calls the new OAuthUtil method during access-token revocation when a token-binding reference exists, adding an extra cache invalidation step before revocation proceeds.
Unit Tests & Coverage
components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java
Added extensive tests (many new public test methods) covering persisted-scope cache clearing edge cases, DAO exceptions, random number behavior, error handling utilities, cache utilities, and system listener invocation scenarios.

Sequence Diagram

sequenceDiagram
    actor Revocation as OAuth2Service
    participant Util as OAuthUtil
    participant Config as OAuthServerConfiguration
    participant DAO as AccessTokenDAO
    participant Cache as OAuthCache

    Revocation->>Util: clearOAuthCacheUsingPersistedScopes(bindingRef, accessTokenDO, revokeRequestDTO)
    Util->>Util: validate allowed scopes & token string
    Util->>Config: get token persistence factory / DAO
    Util->>DAO: getAccessToken(accessTokenString)
    DAO-->>Util: persisted AccessTokenDO (original scopes)
    Util->>Util: build scope string from persisted scopes
    Util->>Cache: clear cache (scopes + bindingRef, consumerKey, authzUser)
    Cache-->>Util: ack
    Util->>Cache: clear cache (scopes, consumerKey, authzUser)
    Cache-->>Util: ack
    Util-->>Revocation: return
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • piraveena
  • shashimalcse
  • wso2-engineering

Poem

🐇 I hop through persisted scopes with care,

I fetch what once lived hidden there,
With tiny paws I clear each key,
So stale tokens vanish, tidy and free,
A burrow neat — no stash to spare.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete against the repository template. It lacks several required sections including Purpose/Goals/Approach, User stories, Release notes, Documentation, Training, Certification, Marketing, Release notes, Automation tests details, Security checks, Samples, Related PRs, Migrations, Test environment, and Learning. Complete the PR description by filling in all required template sections, particularly Purpose/Goals/Approach, Automation tests (code coverage %), Security checks (yes/no responses), Test environment details, and Documentation links.
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly summarizes the main change: implementing cache clearance for original token scopes during revocation, which is the primary objective of the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@wso2-engineering wso2-engineering bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Agent Log Improvement Checklist

⚠️ Warning: AI-Generated Review Comments

  • The log-related comments and suggestions in this review were generated by an AI tool to assist with identifying potential improvements. Purpose of reviewing the code for log improvements is to improve the troubleshooting capabilities of our products.
  • Please make sure to manually review and validate all suggestions before applying any changes. Not every code suggestion would make sense or add value to our purpose. Therefore, you have the freedom to decide which of the suggestions are helpful.

✅ Before merging this pull request:

  • Review all AI-generated comments for accuracy and relevance.
  • Complete and verify the table below. We need your feedback to measure the accuracy of these suggestions and the value they add. If you are rejecting a certain code suggestion, please mention the reason briefly in the suggestion for us to capture it.
Comment Accepted (Y/N) Reason
#### Log Improvement Suggestion No: 1
#### Log Improvement Suggestion No: 2
#### Log Improvement Suggestion No: 3
#### Log Improvement Suggestion No: 4
#### Log Improvement Suggestion No: 5
#### Log Improvement Suggestion No: 6

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java (1)

813-835: Test structure observation.

In this test, mockAccessTokenDAO is created but never wired to mockFactory. The verification at line 833 passes because the method exits early when allowed scopes are empty, so no DAO call occurs. While functionally correct, the mock setup could be misleading to future readers.

Consider adding a brief comment explaining the mock is intentionally unconnected since we're verifying early exit behavior, or wire up the mock properly to make the test more self-documenting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java`
around lines 813 - 835, Wire the unused mockAccessTokenDAO to the mocked
OAuthTokenPersistenceFactory to make the test intent explicit: after creating
mockFactory and mockAccessTokenDAO, stub mockFactory.getAccessTokenDAO() to
return mockAccessTokenDAO (i.e.,
when(mockFactory.getAccessTokenDAO()).thenReturn(mockAccessTokenDAO)) before
calling OAuthUtil.clearOAuthCacheUsingPersistedScopes; alternatively, if you
intentionally want it unconnected to assert early exit, add a brief comment
above the mock creation explaining that the DAO is deliberately not wired
because allowed scopes are empty and the method should return early.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java`:
- Around line 813-835: Wire the unused mockAccessTokenDAO to the mocked
OAuthTokenPersistenceFactory to make the test intent explicit: after creating
mockFactory and mockAccessTokenDAO, stub mockFactory.getAccessTokenDAO() to
return mockAccessTokenDAO (i.e.,
when(mockFactory.getAccessTokenDAO()).thenReturn(mockAccessTokenDAO)) before
calling OAuthUtil.clearOAuthCacheUsingPersistedScopes; alternatively, if you
intentionally want it unconnected to assert early exit, add a brief comment
above the mock creation explaining that the DAO is deliberately not wired
because allowed scopes are empty and the method should return early.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f1dc08 and cf8b7a8.

📒 Files selected for processing (3)
  • components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/OAuthUtil.java
  • components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/OAuth2Service.java
  • components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java

@codecov
Copy link
Copy Markdown

codecov bot commented Feb 27, 2026

Codecov Report

❌ Patch coverage is 79.16667% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.35%. Comparing base (e2e2314) to head (1d31749).
⚠️ Report is 19 commits behind head on master.

Files with missing lines Patch % Lines
...java/org/wso2/carbon/identity/oauth/OAuthUtil.java 78.26% 3 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3071      +/-   ##
============================================
- Coverage     57.76%   57.35%   -0.42%     
+ Complexity    10229    10194      -35     
============================================
  Files           707      707              
  Lines         56817    58635    +1818     
  Branches      13861    13299     -562     
============================================
+ Hits          32820    33628     +808     
- Misses        19599    20527     +928     
- Partials       4398     4480      +82     
Flag Coverage Δ
unit 42.37% <79.16%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jenkins-is-staging
Copy link
Copy Markdown

PR builder started
Link: https://github.com/wso2/product-is/actions/runs/22480094039

@jenkins-is-staging
Copy link
Copy Markdown

PR builder completed
Link: https://github.com/wso2/product-is/actions/runs/22480094039
Status: success

Copy link
Copy Markdown

@jenkins-is-staging jenkins-is-staging left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving the pull request based on the successful pr build https://github.com/wso2/product-is/actions/runs/22480094039

// The in-memory scopes may be mutated during validation. To avoid cache-key
// mismatches, retrieve the original scopes from the database before clearing
// the OAuth cache.
AccessTokenDO dbTokenDO = OAuthTokenPersistenceFactory.getInstance()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessing dao layer from util method may not be the correct way. Don't we have service methods to fetch the access token.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed with 5584aaf

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java (2)

846-848: Same mock wiring suggestion applies here.

Same pattern as the previous test — consider wiring mockAccessTokenDAO to mockFactory for a stronger assertion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java`
around lines 846 - 848, Test creates mockFactory and mockAccessTokenDAO but
never wires the DAO to the factory; update the test to stub
OAuthTokenPersistenceFactory.getInstance() to return mockFactory and also stub
mockFactory.getAccessTokenDAO() (or the factory method that returns an
AccessTokenDAO) to return mockAccessTokenDAO so callers in OAuthUtilTest use the
mocked DAO; reference OAuthTokenPersistenceFactory, getInstance, mockFactory,
AccessTokenDAO, and mockAccessTokenDAO when adding the when(...).thenReturn(...)
wiring.

822-824: Consider wiring the mock DAO to strengthen the test.

The mockAccessTokenDAO is created but not connected to mockFactory. The verification passes trivially because the mock is never used. For a stronger test, wire the mock:

 OAuthTokenPersistenceFactory mockFactory = mock(OAuthTokenPersistenceFactory.class);
 oAuthTokenPersistenceFactory.when(OAuthTokenPersistenceFactory::getInstance).thenReturn(mockFactory);
 AccessTokenDAO mockAccessTokenDAO = mock(AccessTokenDAO.class);
+when(mockFactory.getAccessTokenDAO()).thenReturn(mockAccessTokenDAO);

This ensures the verification confirms the early return actually prevents DAO access, rather than passing because the mock isn't wired.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java`
around lines 822 - 824, The test creates mockFactory
(OAuthTokenPersistenceFactory) and mockAccessTokenDAO (AccessTokenDAO) but never
wires them together, so the DAO is never used; update the test to stub
mockFactory to return mockAccessTokenDAO from the factory method (e.g.,
when(mockFactory.getAccessTokenDAO()).thenReturn(mockAccessTokenDAO) or
equivalent) and ensure OAuthTokenPersistenceFactory.getInstance() still returns
mockFactory so verifications against mockAccessTokenDAO actually exercise the
mock rather than passing vacuously.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java`:
- Around line 846-848: Test creates mockFactory and mockAccessTokenDAO but never
wires the DAO to the factory; update the test to stub
OAuthTokenPersistenceFactory.getInstance() to return mockFactory and also stub
mockFactory.getAccessTokenDAO() (or the factory method that returns an
AccessTokenDAO) to return mockAccessTokenDAO so callers in OAuthUtilTest use the
mocked DAO; reference OAuthTokenPersistenceFactory, getInstance, mockFactory,
AccessTokenDAO, and mockAccessTokenDAO when adding the when(...).thenReturn(...)
wiring.
- Around line 822-824: The test creates mockFactory
(OAuthTokenPersistenceFactory) and mockAccessTokenDAO (AccessTokenDAO) but never
wires them together, so the DAO is never used; update the test to stub
mockFactory to return mockAccessTokenDAO from the factory method (e.g.,
when(mockFactory.getAccessTokenDAO()).thenReturn(mockAccessTokenDAO) or
equivalent) and ensure OAuthTokenPersistenceFactory.getInstance() still returns
mockFactory so verifications against mockAccessTokenDAO actually exercise the
mock rather than passing vacuously.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 62906e8a-19a1-4c27-a4b6-f6b704d00d1b

📥 Commits

Reviewing files that changed from the base of the PR and between cf8b7a8 and dfb6e42.

📒 Files selected for processing (3)
  • components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/OAuthUtil.java
  • components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/OAuth2Service.java
  • components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth/OAuthUtil.java
  • components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/OAuth2Service.java

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java (1)

1222-1266: Consider using try-finally for interceptor cleanup to improve test isolation.

The interceptor cleanup (addOauthEventInterceptorProxy(null)) is placed at the end of each test method. If a test fails before reaching this line (e.g., due to an assertion failure or unexpected exception), the interceptor remains set, potentially affecting subsequent tests.

Consider wrapping the test body in try-finally:

`@Test`
public void testInvokePreRevocationBySystemListeners_AccessTokenDO_WhenInterceptorEnabled() throws Exception {
    OAuthEventInterceptor interceptor = mock(OAuthEventInterceptor.class);
    when(interceptor.isEnabled()).thenReturn(true);
    OAuthComponentServiceHolder.getInstance().addOauthEventInterceptorProxy(interceptor);
    
    try {
        AccessTokenDO accessTokenDO = new AccessTokenDO();
        Map<String, Object> params = new HashMap<>();
        
        OAuthUtil.invokePreRevocationBySystemListeners(accessTokenDO, params);
        
        verify(interceptor, times(1)).onPreTokenRevocationBySystem(accessTokenDO, params);
    } finally {
        OAuthComponentServiceHolder.getInstance().addOauthEventInterceptorProxy(null);
    }
}

Alternatively, centralize interceptor cleanup in @AfterMethod if multiple tests use this pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java`
around lines 1222 - 1266, Wrap each test that sets a global interceptor (e.g.,
in
testInvokePreRevocationBySystemListeners_AccessTokenDO_WhenInterceptorEnabled,
...WhenInterceptorThrowsException) in a try-finally or move cleanup to an
`@AfterMethod` so
OAuthComponentServiceHolder.getInstance().addOauthEventInterceptorProxy(null)
always runs; specifically ensure after adding the interceptor (via
addOauthEventInterceptorProxy(interceptor)) the test body runs in try { ... }
and the finally block calls addOauthEventInterceptorProxy(null) to guarantee
cleanup and avoid cross-test interference.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java`:
- Around line 1222-1266: Wrap each test that sets a global interceptor (e.g., in
testInvokePreRevocationBySystemListeners_AccessTokenDO_WhenInterceptorEnabled,
...WhenInterceptorThrowsException) in a try-finally or move cleanup to an
`@AfterMethod` so
OAuthComponentServiceHolder.getInstance().addOauthEventInterceptorProxy(null)
always runs; specifically ensure after adding the interceptor (via
addOauthEventInterceptorProxy(interceptor)) the test body runs in try { ... }
and the finally block calls addOauthEventInterceptorProxy(null) to guarantee
cleanup and avoid cross-test interference.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8e37b2c3-f6a0-4bc3-97fc-389f7bcba389

📥 Commits

Reviewing files that changed from the base of the PR and between ebdf741 and 1d31749.

📒 Files selected for processing (1)
  • components/org.wso2.carbon.identity.oauth/src/test/java/org/wso2/carbon/identity/oauth/OAuthUtilTest.java

sadilchamishka
sadilchamishka previously approved these changes Mar 12, 2026
// The in-memory scopes may be mutated during validation. To avoid cache-key
// mismatches, retrieve the original scopes from the database before clearing
// the OAuth cache.
AccessTokenDO dbTokenDO = OAuth2Util.findAccessToken(accessToken, true);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have AccessTokenDO object in the method, why we need to get the string and get the DO again?

and don't use OAuth2Util.findAccessToken, use the default Token Provider

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants