Skip to content

Provision the user credential from the flow user in password recovery and ask password flows - #1152

Open
KD23243 wants to merge 3 commits into
wso2-extensions:masterfrom
KD23243:provisionCredentialFromFlowUser
Open

Provision the user credential from the flow user in password recovery and ask password flows#1152
KD23243 wants to merge 3 commits into
wso2-extensions:masterfrom
KD23243:provisionCredentialFromFlowUser

Conversation

@KD23243

@KD23243 KD23243 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Purpose

In the password recovery and ask password (invited user registration) flows, the provisioned credential should reflect the value present on the flow user at the time of provisioning, so that a flow extension configured after the password provisioning step can override it. Previously the provisioning step re-read the retained raw user input, discarding any credential set on the flow user by a later extension.

Related Issue

Manual Testing

ID Flow Scenario / Setup Expected Result Verified
1 PASSWORD_RECOVERY Reset via Email OTP, primary store Password updated; login works with new password Yes
2 PASSWORD_RECOVERY Reset via SMS OTP, primary store Password updated; login works with new password Yes
3 PASSWORD_RECOVERY Reset via Magic Link, primary store Password updated; login works with new password Yes
4 INVITED_USER_REGISTRATION Ask-password via SMS OTP, primary store Password set; login works Yes
5 REGISTRATION Self-signup with password, primary store User created; login works Yes
6 INVITED_USER_REGISTRATION Set password + fill profile claims Password set AND claims persisted Yes
7 PASSWORD_RECOVERY Flow that also updates a claim Password reset AND claim written Yes
8 INVITED_USER_REGISTRATION Passkey registration Passkey Yes
9 INVITED_USER_REGISTRATION Consent V2 enabled, purpose declared Exactly one consent receipt created Yes
10 PASSWORD_RECOVERY Pre-update password action rejects the password Raw API code = FE-60012 Yes
11 INVITED_USER_REGISTRATION Pre-update password action rejects the password Raw API code = FE-60012 Yes
12 REGISTRATION Pre-update password action rejects the password Raw API code = FEE-60003 Yes
14 INVITED_USER_REGISTRATION Missing or invalid confirmation code Error: Required properties are missing in the context Yes

Implementation

  • PasswordProvisioningExecutor now only captures the submitted password onto the flow user (FlowUser.setUserCredentials) and returns; it no longer updates the credential itself.
  • The credential update, together with the pre-update handling for the ask password flow, is moved into the terminal UserProvisioningExecutor. It reads the credential from the flow user (getUserCredentials), so a value overridden by a flow extension between the two steps is the value that gets persisted.
  • Claim update and consent processing are consolidated in UserProvisioningExecutor.
  • The existing error code returned when the pre-update password action rejects the credential is retained for these flows.

This covers the server-side credential provisioning behavior; the Console flow builder changes for exposing the flow extension step are handled separately.

Summary by CodeRabbit

Summary by CodeRabbit

  • Bug Fixes

    • Streamlined password provisioning to reliably capture provided passwords and complete the flow using existing credentials when no input is provided.
    • Improved non-registration user updates by handling password and claim persistence in a more consistent flow.
    • Enhanced password pre-update validation error reporting to better reflect flow-engine error codes/messages.
    • Improved invited-user password event handling and ensured password buffers are cleared after processing.
  • Tests

    • Updated and expanded executor test coverage for password recovery and credential preservation behavior.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Provisioning flow changes

Layer / File(s) Summary
Simplify password capture
components/.../PasswordProvisioningExecutor.java, components/.../PasswordProvisioningExecutorTest.java
PasswordProvisioningExecutor now captures supplied credentials and completes when credentials are present; tests focus on this behavior.
Delegate password and claim updates
components/.../UserProvisioningExecutor.java, components/.../UserProvisioningExecutorTest.java
UserProvisioningExecutor delegates password and claim handling to dedicated helpers for invited registration and password recovery, including FlowUser credential propagation.
Pre-password events and error codes
components/.../UserProvisioningExecutor.java, components/.../UserProvisioningExecutorTest.java
Invited registration enters a flow, dispatches a pre-password event, clears the password buffer, and uses caller-provided action-failure error codes validated by recovery tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant UserProvisioningExecutor
  participant ThreadLocalFlow
  participant IdentityEventService
  participant UserStoreManager
  UserProvisioningExecutor->>ThreadLocalFlow: enter invited-registration flow
  UserProvisioningExecutor->>IdentityEventService: dispatch PRE_ADD_NEW_PASSWORD
  UserProvisioningExecutor->>UserStoreManager: update user password
  UserProvisioningExecutor->>ThreadLocalFlow: exit flow
Loading

Possibly related PRs

Suggested reviewers: thamindur, imalshad, jenkins-is-staging

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description explains the purpose, implementation, related issues, and testing, but it omits most mandatory template sections. Add the required Goals, User stories, Developer Checklist, Release note, Documentation, Security checks, and other applicable template sections.
✅ Passed checks (3 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 summarizes the primary change: provisioning credentials from the flow user in password recovery and ask-password flows.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/executor/PasswordProvisioningExecutor.java (1)

69-85: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Update the stale executor test

PasswordProvisioningExecutorTest.testExecuteWithValidData still verifies updateCredentialByAdmin(...), but PasswordProvisioningExecutor.execute(...) now only stores the password on FlowUser. Remove that verify here and place it in UserProvisioningExecutorTest, where the password is actually persisted.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/executor/PasswordProvisioningExecutor.java`
around lines 69 - 85, The
testPasswordProvisioningExecutorTest.testExecuteWithValidData assertion should
no longer verify updateCredentialByAdmin, since
PasswordProvisioningExecutor.execute only stores credentials on FlowUser. Remove
that verification from PasswordProvisioningExecutorTest and add the
corresponding verification to UserProvisioningExecutorTest, where password
persistence occurs.
🧹 Nitpick comments (2)
components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/executor/UserProvisioningExecutor.java (2)

220-220: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

getOrDefault eagerly generates a throwaway password.

DefaultPasswordGenerator().generatePassword() is evaluated on every call, even when PASSWORD_KEY is present, wasting work and leaving a generated password char[] in memory that is never zeroized (only the returned password reference is wiped). Consider computeIfAbsent/an explicit branch so the generator only runs when actually needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/executor/UserProvisioningExecutor.java`
at line 220, Update the password lookup in UserProvisioningExecutor to avoid
eagerly calling DefaultPasswordGenerator.generatePassword when PASSWORD_KEY
already exists. Use an explicit presence check or lazy computeIfAbsent approach
so generation occurs only when needed, while preserving the existing password
reference cleanup behavior.

200-210: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Move enterFlow() out of the try to keep enter/exit balanced.

enterFlow() is the first statement inside the try, but exitFlow() runs in finally. If enterFlow() throws after (or without) pushing the flow, finally will still call exitFlow(), popping a flow that was never entered and corrupting the thread-local flow stack.

♻️ Suggested restructuring
-            try {
-                enterFlow();
-                handlePrePasswordUpdate(context, user, recoveryScenario, confirmationCode);
+            enterFlow();
+            try {
+                handlePrePasswordUpdate(context, user, recoveryScenario, confirmationCode);
                 updateUserPassword(userCredentials, flowUser, userStoreManager);
                 String userId = ((AbstractUserStoreManager) userStoreManager)
                         .getUserIDFromUserName(flowUser.getUsername());
                 flowUser.setUserId(userId);
                 flowUser.setUserStoreDomain(user.getUserStoreDomain());
             } finally {
                 IdentityContext.getThreadLocalIdentityContext().exitFlow();
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/executor/UserProvisioningExecutor.java`
around lines 200 - 210, Move enterFlow() in the UserProvisioningExecutor flow so
it executes immediately before the try block, while leaving the existing
exitFlow() call in finally. Keep the password update and user identity-setting
operations inside the try, ensuring exitFlow() runs only after a successful
enterFlow().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/executor/PasswordProvisioningExecutor.java`:
- Around line 69-85: The
testPasswordProvisioningExecutorTest.testExecuteWithValidData assertion should
no longer verify updateCredentialByAdmin, since
PasswordProvisioningExecutor.execute only stores credentials on FlowUser. Remove
that verification from PasswordProvisioningExecutorTest and add the
corresponding verification to UserProvisioningExecutorTest, where password
persistence occurs.

---

Nitpick comments:
In
`@components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/executor/UserProvisioningExecutor.java`:
- Line 220: Update the password lookup in UserProvisioningExecutor to avoid
eagerly calling DefaultPasswordGenerator.generatePassword when PASSWORD_KEY
already exists. Use an explicit presence check or lazy computeIfAbsent approach
so generation occurs only when needed, while preserving the existing password
reference cleanup behavior.
- Around line 200-210: Move enterFlow() in the UserProvisioningExecutor flow so
it executes immediately before the try block, while leaving the existing
exitFlow() call in finally. Keep the password update and user identity-setting
operations inside the try, ensuring exitFlow() runs only after a successful
enterFlow().

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7280808d-6bad-436e-946b-7a0562f75f7c

📥 Commits

Reviewing files that changed from the base of the PR and between 7009b94 and 4928e35.

📒 Files selected for processing (2)
  • components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/executor/PasswordProvisioningExecutor.java
  • components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/executor/UserProvisioningExecutor.java

@jenkins-is-staging

Copy link
Copy Markdown

@jenkins-is-staging

Copy link
Copy Markdown

PR builder completed
Link: https://github.com/wso2/product-is/actions/runs/29738587561
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/29738587561

response.setResult(STATUS_COMPLETE);
return response;
} catch (UserStoreException e) {
response = buildClientErrorResponseForActionFailure(response, e,

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.

can we guarantee that only the action failures would occur?

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.

Yes, buildClientErrorResponseForActionFailure will check UserStoreClientException for an action related error code, and return the existing value if it isn't found.

? LoggerUtils.getMaskedContent(user.getUsername())
: user.getUsername();
LOG.error("Error while updating password for user: " + maskedUsername, e);
throw new FlowEngineException(null, e.getMessage(), null, e);

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.

dont we need error codes?

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.

This preserves the existing behavior. In the old PasswordProvisioningExecutor, the generic password update failure path used errorResponse(response, e.getMessage()), which only set the message + STATUS_ERROR and not a code, so the null here keeps the same response.

@jenkins-is-staging

Copy link
Copy Markdown

@jenkins-is-staging

Copy link
Copy Markdown

PR builder completed
Link: https://github.com/wso2/product-is/actions/runs/29896517209
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/29896517209

@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.53846% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 55.30%. Comparing base (9c7a6ab) to head (3d11a59).
⚠️ Report is 4 commits behind head on master.

Files with missing lines Patch % Lines
...ty/recovery/executor/UserProvisioningExecutor.java 81.25% 5 Missing and 7 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1152      +/-   ##
============================================
+ Coverage     55.19%   55.30%   +0.10%     
- Complexity     3337     3339       +2     
============================================
  Files           317      317              
  Lines         22200    22156      -44     
  Branches       4589     4581       -8     
============================================
- Hits          12254    12253       -1     
+ Misses         8365     8327      -38     
+ Partials       1581     1576       -5     
Flag Coverage Δ
unit 45.49% <81.53%> (+0.09%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 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.

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