Skip to content

Set the error code on user store exceptions thrown for non existing users - #4641

Merged
sadilchamishka merged 1 commit into
wso2:4.12.xfrom
sadilchamishka:fix/propagate-non-existing-user-error-code
Oct 5, 2026
Merged

sadilchamishka merged 1 commit into
wso2:4.12.xfrom
sadilchamishka:fix/propagate-non-existing-user-error-code

Conversation

@sadilchamishka

@sadilchamishka sadilchamishka commented Oct 4, 2026 •

Copy link
Copy Markdown

Purpose

Describe the problems, issues, or needs driving this feature/fix and include links to related issues in the following format: Resolves issue1, issue2, etc.

AbstractUserStoreManager builds the non-existing-user error code and then drops it on the floor:

String errorCode = ErrorMessages.ERROR_CODE_NON_EXISTING_USER.getCode();   // "30007"
String errorMessage = String.format(ErrorMessages.ERROR_CODE_NON_EXISTING_USER.getMessage(), userID, ...);
handleGetUserClaimValuesFailureWithID(errorCode, errorMessage, userID, claims, profileName);
throw new UserStoreException(errorCode + " - " + errorMessage);

UserStoreException(String) leaves the errorCode field null, so the code survives only as a prefix of the message. A caller that branches on getErrorCode() to tell a missing user (a client side condition, usually worth a debug line) from a broken user store (a server side failure worth an error line) cannot do so, and every such guard silently takes the failure branch. callSecure propagates the null faithfully, so the loss is invisible all the way up the stack.

No GitHub issue; found while tracing why a downstream guard on getErrorCode() never matched.

Related Issues

Related PRs

Goals

Describe the solutions that this feature/fix will introduce to resolve the problems described above

Make the non-existing-user error code available on the exception, so callers can classify the failure without parsing the message.

Approach

Describe how you are implementing the solutions. Include an animated GIF or screenshot if the change affects the UI (email documentation@wso2.com to review all UI text). Include a link to a Markdown file or Google doc if the feature write-up is too long to paste here.

Pass errorCode to the two-argument UserStoreException constructor at the 19 remaining ERROR_CODE_NON_EXISTING_USER throw sites in AbstractUserStoreManager. One site already did this, so the change makes the class consistent rather than introducing a new convention.

Message text is unchanged, so anything matching on the message keeps working. No UI impact.

User stories

Summary of user stories addressed by this change>

As a component that calls the user store, I can tell "this user does not exist" from "the user store failed" by reading UserStoreException.getErrorCode(), so I can log and handle the two differently.

Developer Checklist (Mandatory)

  • Complete the Developer Checklist in the related product-is issue to track any behavioral change or migration impact.

No product-is issue was raised. This fills a field that was already supposed to carry the code and does not change any message, control flow, API signature, or persisted data, so there is no behavioural change or migration impact to track. Happy to raise one if a reviewer disagrees.

Release note

Brief description of the new feature or bug fix as it will appear in the release notes

Non-existing-user failures from AbstractUserStoreManager now carry the error code on the exception instead of only inside the message.

Documentation

Link(s) to product documentation that addresses the changes of this PR. If no doc impact, enter “N/A” plus brief explanation of why there’s no doc impact

N/A. Internal exception detail, not a documented or configurable behaviour.

Training

Link to the PR for changes to the training content in https://github.com/wso2/WSO2-Training, if applicable

N/A.

Certification

Type “Sent” when you have provided new/updated certification questions, plus four answers for each question (correct answer highlighted in bold), based on this change. Certification questions/answers should be sent to certification@wso2.com and NOT pasted in this PR. If there is no impact on certification exams, type “N/A” and explain why.

N/A. The change is not observable in any product configuration or administration task an exam would cover.

Marketing

Link to drafts of marketing content that will describe and promote this feature, including product page changes, technical articles, blog posts, videos, etc., if applicable

N/A.

Automation tests

  • Unit tests

    Code coverage information

    AbstractUserStoreManagerErrorCodeTest covers both claim-reading entry points on the non-existing-user path: getUserClaimValuesWithID and getUserClaimValueWithID. Each asserts getErrorCode() equals 30007.

    Verified both ways on org.wso2.carbon.user.core:

    • with the change: Tests run: 19, Failures: 0, Errors: 0
    • with AbstractUserStoreManager reverted and the tests kept: Failures: 2 — expected [30007] but found [null]

    The remaining 17 throw sites take the identical one-token edit and are not individually covered.

  • Integration tests

    Details about the test cases and coverage

    None added. There is no externally observable behaviour to assert: no message, status code, or API response changes.

Security checks

  • Followed secure coding standards in http://wso2.com/technical-reports/wso2-secure-engineering-guidelines? yes
  • Ran FindSecurityBugs plugin and verified report? no — not run. The change passes an existing in-process constant to a different constructor of the same exception; it introduces no new data flow, input handling, or external interaction for the plugin to analyse.
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets? yes

Note the error message already contains the user identifier and is unchanged by this PR; the error code added to the exception is the constant "30007" and carries no user data.

Samples

Provide high-level details about the samples related to this feature

N/A.

Related PRs

List any other related PRs

Migrations (if applicable)

Describe migration steps and platforms on which migration has been tested

N/A. No schema, configuration, or persisted data is touched.

Test environment

List all JDK versions, operating systems, databases, and browser/versions on which this feature/fix was tested

  • JDK: Temurin 21.0.10 (JDK 11 cannot load the carbon-p2-plugin Maven extension and JDK 17 fails with invalid target release: 21, so 21 is the only version this branch builds on)
  • OS: macOS 15 (arm64)
  • Database: none. The tests are mock based and do not touch a database.
  • Browser: N/A

Learning

Describe the research phase and any blog posts, patterns, libraries, or add-ons you used to solve the problem.

The investigation started from a downstream guard on getErrorCode() that never matched, and the question of whether the fix belonged in the caller or here. Reading callSecure (AbstractUserStoreManager:253-268) showed it preserves whatever code the throw site set, which narrowed it to the throw sites themselves. Grepping errorCode + " - " + errorMessage in this class showed 33 single-argument throws against 2 that pass the code, so the convention already existed and was simply not applied consistently. Scope was limited to the ERROR_CODE_NON_EXISTING_USER sites, since those are the ones callers are known to branch on.

The JUnit 3 tests under src/test/java/.../jdbc/ are not run by Surefire in this module — the TestNG provider is selected, so those classes report Tests run: 0 while the build still passes. The new test is TestNG, and AbstractUserStoreManager turned out to be testable without a database: mock(UniqueIDJDBCUserStoreManager.class, CALLS_REAL_METHODS), set realmConfig by reflection, then stub the protected getUserStoreWithID and doCheckExistingUserWithID.

@coderabbitai

coderabbitai Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: wso2/carbon-kernel/.coderabbit.yml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b86ed300-f77b-49c8-ae52-ad33fecbdad5
📥 Commits

Reviewing files that changed from the base of the PR and between 20d11cc and f7f4441.

📒 Files selected for processing (2)
  • core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/common/AbstractUserStoreManager.java
  • core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/common/AbstractUserStoreManagerErrorCodeTest.java

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.


📝 Summary

Summary by CodeRabbit

  • Bug Fixes
    • Missing-user errors from claim operations, user deletion, and credential updates now include the appropriate error code.

Walkthrough

Missing-user exception paths in AbstractUserStoreManager now pass the existing error code to UserStoreException. New tests check the error code from singular and plural claim-value lookups with a non-existing user ID.

Changes

Missing-user error handling

Layer / File(s) Summary
Propagate missing-user error codes
core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/common/AbstractUserStoreManager.java, core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/common/AbstractUserStoreManagerErrorCodeTest.java
Nineteen missing-user exception paths now pass the existing error code to UserStoreException. Tests verify the code for singular and plural user-ID claim-value lookups.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to f7f44

The missing-user exceptions now retain their error codes without changing their messages, and the added tests check both singular and plural user-ID claim lookups. No issue identified here warrants delaying merge.

Security Architecture Review

Security architecture risk: 🔵 Low · up to f7f44

The change preserves existing failure messages and security checks while making missing-user error codes available to callers. No new access path or permission expansion was identified. Production callers may classify these failures differently, and those integrations were not fully verified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The evidenced effect spans existing user-store claim access, claim mutation, user deletion, and credential-update failure paths. Callers can now distinguish missing users by code. The examined paths do not gain new tenant reach, user-store routing, or privileged operations; exposure through external service adapters remains unverified.

Trust Boundaries and Controls

  • observed — The examined username and user-ID operations retain secure dispatch, domain-aware delegation, existence checks, and applicable self-deletion, protected-user, and read-only restrictions. The modified throws remain on existing failure branches; setting the code does not authorize an operation or redirect it to another user store.
🚥 Pre-merge checks | ✅ 4 | ❓ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ❓ Inconclusive Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 1 files. (1 skipped: 1 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly describes the main change: setting the error code on exceptions for non-existing users.
Description check ✅ Passed The description covers the template sections with specific purpose, approach, tests, security checks, and impact details. It also explains why some items do not apply.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 1 files. (1 skipped: 1 too large.)

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟢 Approval recommended

The change is a verified, mechanical, message-preserving fix that correctly uses the (message, errorCode) constructor at every non-existing-user throw site and is covered by a new test.

Review effort: Balanced
Findings: None

What changed in this PR

This PR fixes an error-code loss in AbstractUserStoreManager. When a user-store operation fails because the user does not exist, the code built a 30007 (ERROR_CODE_NON_EXISTING_USER) error code but threw new UserStoreException(errorCode + " - " + errorMessage) — the single-argument constructor leaves the exception's errorCode field null, so the code survived only as a message prefix. Callers that branch on getErrorCode() to distinguish a client-side "missing user" from a server-side store failure could not do so. The change passes errorCode to the two-argument UserStoreException(message, errorCode) constructor at the remaining throw sites, making the whole class consistent with the one site that already did this, and adds a focused test. This is a core class in WSO2 identity management, but the change is purely additive to exception construction and leaves message text unchanged.

Changes:

  • Pass errorCode to the two-argument UserStoreException constructor at 19 ERROR_CODE_NON_EXISTING_USER throw sites across the name-based and ID-based user APIs.
  • Add AbstractUserStoreManagerErrorCodeTest verifying the error code is carried on the exception for getUserClaimValuesWithID and getUserClaimValueWithID.
File Description
core/​org.wso2.carbon.user.core/​src/​main/​java/​org/​wso2/​carbon/​user/​core/​common/​AbstractUserStoreManager.java Adds errorCode as the second constructor argument at 19 non-existing-user throw sites so getErrorCode() returns 30007 instead of null; message text unchanged.
core/​org.wso2.carbon.user.core/​src/​test/​java/​org/​wso2/​carbon/​user/​core/​common/​AbstractUserStoreManagerErrorCodeTest.java New TestNG test asserting the error code is propagated on the exception for two representative claim-read methods.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@jenkins-is-staging

Copy link
Copy Markdown

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

@jenkins-is-staging

Copy link
Copy Markdown

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

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

Copy link
Copy Markdown

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/37252397041

@sadilchamishka
sadilchamishka merged commit c04d72d into wso2:4.12.x Oct 5, 2026
2 checks passed
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