Skip to content

Revoke other stateful sessions after self-password changes - #1235

Merged
vharseko merged 2 commits into
OpenIdentityPlatform:masterfrom
vharseko:fix/password-change-revoke-sessions
Nov 14, 2026
Merged

vharseko merged 2 commits into
OpenIdentityPlatform:masterfrom
vharseko:fix/password-change-revoke-sessions

Conversation

@vharseko

@vharseko vharseko commented Nov 13, 2026 •

Copy link
Copy Markdown
Member

A successful self-password change through the server-side AMIdentity.changePassword path revokes the user's other stateful sessions while preserving the current master session. Revocation runs after the password update, and failures are logged without changing the result of the password change.

Sessions are loaded with getSession(id, false, false) so a cache miss refreshes their state without resetting idle time, including when cache-based polling is enabled. A restricted caller token is resolved to its master session ID before excluding the current session from revocation. Failure to resolve that ID stops revocation, preserving the caller's session and the password-change result.

The caller must match the target user. This change does not revoke sessions when an administrator changes another user's password, and it does not cover the server's Remote SDK changePassword_idrepo handler, which calls IdServices directly. Stateless session revocation is outside this change. These scope limits are also documented in the API Javadoc.

Validation:

  • mvn -q -pl openam-core -am clean test -DskipTests=false: 3869 tests, 0 failures, 0 errors, 1 skipped; includes all 2342 openam-core tests with no skips.
  • mvn -q -pl openam-core -am -Dtest=PasswordChangeSessionRevokerTest,AMIdentityPasswordChangeTest,SessionCacheTest,SessionChangeAuthorizerTest,LocalOperationsTest,AMIdentityTest -Dsurefire.failIfNoSpecifiedTests=false test -DskipTests=false: 48 tests passed.
  • Executed a wrong-lookup-ID mutation: the strengthened test failed with expected [other] but found [current]. Restored the source before the clean full-suite run.

The regression fixture exercises real password-change orchestration, session-cache loading, session operations and the session-state authorization check. Persistence, configuration, delegation and restricted-token provider boundaries are mocked; this is not a live deployment integration test.

@vharseko
vharseko requested a review from maximthomas November 13, 2026 11:12
@vharseko vharseko added bug java Pull requests that update java code tests Test suite: coverage, fixtures, or test infrastructure security Security fix or hardening (CVE, GHSA, XSS/CSRF/SSRF) labels Nov 13, 2026

@maximthomas maximthomas left a comment

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.

praise: Revocation runs after the password update and isolates failures per session.

  • AMIdentity.changePassword invokes revocation only after idServices.changePassword returns.
  • PasswordChangeSessionRevoker.revoke catches individual session failures and continues.

issue (blocking): Uncached sessions remain valid when cache-based polling is enabled.

openam-core/src/main/java/com/sun/identity/idm/PasswordChangeSessionRevoker.java:41-42

With com.iplanet.am.session.client.polling.cacheBased=true, getSession(id, true, false) constructs a cache-miss Session without refreshing its initial INVALID state. SessionChangeAuthorizer checks that requester's state before allowing self-destruction, and getState(false) cannot refresh it in this mode. The helper logs the failure and leaves the other valid stateful session usable after the password change. Load a valid session without resetting its idle time:

Session session = cache.getSession(new SessionID(sessionId), false, false);
session.destroySession(session);

Pin: enable cache-based polling, leave another persisted valid session out of SessionCache, change the password and assert that the session is destroyed. The proposed line matches HEAD signatures; the replacement has not been compiled or run.


issue (blocking): A restricted caller token does not exclude its current master session from revocation.

openam-core/src/main/java/com/sun/identity/idm/PasswordChangeSessionRevoker.java:37, openam-core/src/main/java/com/sun/identity/idm/AMIdentity.java:482

AMIdentity passes restricted token ID R, while the CTS query returns its master session ID M. For a permitted admin self-password change with a satisfied restriction, M != R, so the helper treats the caller's cached valid master session as another session and destroys it. The same input at BASE preserves that session. Resolve the caller's master session identity before comparing it with the query results.

Pin: perform a permitted self-change using distinct restricted and master IDs; assert that the master remains valid and a different session is destroyed. This path is confirmed by source tracing, without a live restricted-token integration run.


suggestion (non-blocking): Pin the exact session ID requested from the cache in a test-strength follow-up.

openam-core/src/test/java/com/sun/identity/idm/PasswordChangeSessionRevokerTest.java:39-44, :57-61

Both tests return the same mock for any SessionID. Replacing new SessionID(sessionId) with new SessionID(currentSessionId) would therefore still satisfy both tests. Capture the lookup argument in revokesOtherSessionsAndKeepsCurrentSession:

org.mockito.ArgumentCaptor<com.iplanet.dpro.session.SessionID> requestedId =
        org.mockito.ArgumentCaptor.forClass(com.iplanet.dpro.session.SessionID.class);
verify(cache).getSession(requestedId.capture(), anyBoolean(), anyBoolean());
org.testng.Assert.assertEquals(requestedId.getValue().toString(), "other");

Pin: the wrong lookup-ID mutation must fail this assertion. This is static mutation reasoning, not an executed mutation result. Deferred to a PasswordChangeSessionRevokerTest follow-up; no additional change is required in this PR for this suggestion.


note (non-blocking): The effective scope is self-password changes through the server-side AMIdentity path.

openam-core/src/main/java/com/sun/identity/idm/AMIdentity.java:478-482, openam-core/src/main/java/com/sun/identity/idm/server/IdRepoJAXRPCObjectImpl.java:665-677

The caller must match the target user. Remote SDK password changes still call server IdServices directly and leave other sessions usable; that outcome is unchanged from BASE and is recorded as a scope limitation.

@vharseko
vharseko force-pushed the fix/password-change-revoke-sessions branch from d294eba to 222f26e Compare November 13, 2026 14:25
@vharseko vharseko changed the title Revoke other stateful sessions after password change Revoke other stateful sessions after self-password changes Nov 13, 2026
@vharseko

Copy link
Copy Markdown
Member Author

Addressed all four points in 222f26e630.

  • Cache misses: changed the lookup to getSession(id, false, false). The regression test enables cache-based polling, leaves the other valid session out of SessionCache, changes the password, and checks removal from the storage fixture without resetting idle time. It checks storage removal rather than relying on the cached object's state.
  • Restricted caller: resolve the restricted token to its master session ID before excluding the current session. The regression test uses different restricted/master IDs and verifies that the master remains while another session is revoked. Resolution failure stops revocation without changing the successful password-change result.
  • Lookup ID: added an ArgumentCaptor asserting that the cache receives other. Executed the proposed wrong-ID mutation; the test failed with expected [other] but found [current]. Restored the source before the clean full-suite run. Also covered an individual destroySession failure so later sessions are still processed.
  • Scope: clarified the API Javadoc and PR description: server-side self-password changes only. Administrator changes to another user's password, the Remote SDK handler that calls IdServices directly, and stateless-session revocation remain outside this change.

Validation: 48 targeted session tests passed. mvn -q -pl openam-core -am clean test -DskipTests=false completed with 3869 tests, 0 failures, 0 errors and 1 skip; all 2342 openam-core tests passed with no skips.

The regression fixture keeps the real password-change, cache, session-operation and state-authorization paths; persistence, configuration, delegation and token-provider boundaries are mocked. No live restricted-token integration run is claimed.

@vharseko
vharseko requested a review from maximthomas November 13, 2026 14:26

@maximthomas maximthomas left a comment

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.

praise: The revision addresses the cache-miss, restricted-token preservation and lookup-ID test findings with focused regression coverage.

  • PasswordChangeSessionRevoker.revoke uses getSession(id, false, false); AMIdentityPasswordChangeTest checks uncached session removal under cache-based polling without resetting idle time.
  • AMIdentity.changePassword resolves restricted caller IDs before revocation; tests check master-session preservation and no revocation when resolution throws. PasswordChangeSessionRevokerTest asserts the exact lookup ID and continuation after a destroy failure.

@vharseko
vharseko merged commit 0970050 into OpenIdentityPlatform:master Nov 14, 2026
16 checks passed
@vharseko
vharseko deleted the fix/password-change-revoke-sessions branch November 14, 2026 13:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug java Pull requests that update java code security Security fix or hardening (CVE, GHSA, XSS/CSRF/SSRF) tests Test suite: coverage, fixtures, or test infrastructure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants