From e9543b57dc431055b7862d45c910259dc901b942 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Wed, 22 Jul 2026 19:12:16 +0300 Subject: [PATCH 1/6] Fix remote address extraction in audit event origin string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The audit event origin builder cast Authentication.getDetails() to String unconditionally to parse a remoteAddress out of a JSON blob. When details was not a String — a common case for UaaAuthenticationDetails and OAuth2AuthenticationDetails, which expose the remote address directly — the cast raised ClassCastException, which the broad catch swallowed by appending the raw Object.toString() to the audit log. This leaked internal state (map contents, request parameters, token fragments, session markers) into the log line and made the origin's shape depend on which details type happened to be attached to the Authentication. For example, an OAuth2AuthenticationDetails instance would have its full toString appended, producing a log line like: Audit: GroupModifiedEvent ('{"role_collection":"evenRC1771046947","op":"add_saml_attribute","saml_entity":"iot-idp-1771046947","attribute":"Groups","value":"even"}'): principal=evenRC1771046947, origin=[client=admin, details=(remoteAddress=172.18.0.1, sessionId=, tokenType=Bearer, tokenValue=, sub=admin, iss=http://localhost:8080/uaa/oauth/token)], identityZoneId=[uaa] where the remoteAddress/sessionId/tokenType/tokenValue fragment came from the details object's toString, then sub/iss were appended by the token inspector — a shape that mixed two unrelated sources into one line. Replace the cast-and-catch with a pattern-matching switch that reads the remote address from each known details type: UaaAuthenticationDetails, OAuth2AuthenticationDetails, a Map (used by ResourceOwnerPasswordTokenGranter — currently has no remoteAddress key, but supported for forward compatibility), or a String parsed as JSON. Non-JSON strings, maps without a remoteAddress key, and null-valued fields fall through safely. Details types without a known accessor produce no remoteAddress line rather than dumping their contents. Add unit tests in AbstractUaaEventTest for each supported details type, for the non-JSON String and null-remoteAddress regression cases, for the Map paths (with and without a remoteAddress key), and for the client+user origin shape. Update AuditCheckMockMvcTests to reflect that audit origins no longer carry the sessionId= marker: the marker was only ever emitted via the buggy toString-leak path, carried no information (its value was a hardcoded literal), and disappears now that the leak is fixed. Drop the sessionId= assertions and collapse the assertLogMessageWithSession / assertLogMessageWithoutSession helpers, which are now equivalent, into a single assertLogMessage helper. --- .../uaa/audit/event/AbstractUaaEvent.java | 59 ++++++--- .../uaa/audit/event/AbstractUaaEventTest.java | 99 +++++++++++++- .../mock/audit/AuditCheckMockMvcTests.java | 125 ++++++------------ 3 files changed, 176 insertions(+), 107 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java index a8b0d4fcbda..291595b19e0 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java @@ -13,10 +13,10 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.audit.event; -import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; import org.cloudfoundry.identity.uaa.audit.UaaAuditService; +import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.oauth.UaaOauth2Authentication; import org.cloudfoundry.identity.uaa.oauth.jwt.Jwt; import org.cloudfoundry.identity.uaa.oauth.jwt.JwtHelper; @@ -24,16 +24,20 @@ import org.cloudfoundry.identity.uaa.oauth.provider.authentication.OAuth2AuthenticationDetails; import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEvent; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; +import tools.jackson.core.type.TypeReference; import java.io.Serial; import java.security.Principal; import java.util.ArrayList; import java.util.Collection; import java.util.Map; +import java.util.Optional; import static org.cloudfoundry.identity.uaa.util.UaaTokenUtils.isJwtToken; import static org.springframework.util.StringUtils.hasText; @@ -49,6 +53,9 @@ public abstract class AbstractUaaEvent extends ApplicationEvent { @Serial private static final long serialVersionUID = -7639844193401892160L; + + private static final Logger logger = LoggerFactory.getLogger(AbstractUaaEvent.class); + private final transient String zoneId; private Authentication authentication; @@ -115,28 +122,48 @@ private String getAuthenticationString(Authentication caller) { builder.append("caller=").append(caller.getName()); } - if (caller.getDetails() != null) { + Object details = caller.getDetails(); + if (details != null) { builder.append(", details=("); - try { - @SuppressWarnings("unchecked") - Map map = - JsonUtils.readValue((String) caller.getDetails(), new TypeReference>(){ - }); - if (map.containsKey("remoteAddress")) { - builder.append("remoteAddress=").append(map.get("remoteAddress")).append(", "); - } - builder.append("type=").append(caller.getDetails().getClass().getSimpleName()); - } catch (Exception _) { - // ignore - builder.append(caller.getDetails()); - } + extractRemoteAddress(details).ifPresent(address -> builder.append("remoteAddress=").append(address).append(", ")); + builder.append("type=").append(details.getClass().getSimpleName()); appendTokenDetails(caller, builder); builder.append(")"); } + return builder.toString(); } - protected void appendTokenDetails(Authentication caller, StringBuilder builder) { + private Optional extractRemoteAddress(Object details) { + return switch (details) { + case UaaAuthenticationDetails d -> Optional.ofNullable(d.getOrigin()); + case OAuth2AuthenticationDetails d -> Optional.ofNullable(d.getRemoteAddress()); + case Map map -> Optional.ofNullable(map.get("remoteAddress")).map(Object::toString); + case String jsonBlob -> extractRemoteAddressFromJson(jsonBlob); + default -> { + logger.warn("Unhandled Authentication.details type in audit origin: {}", details.getClass().getName()); + yield Optional.empty(); + } + }; + } + + private Optional extractRemoteAddressFromJson(String jsonBlob) { + Map map; + try { + map = JsonUtils.readValue(jsonBlob, new TypeReference<>() { + }); + } catch (Exception _) { + return Optional.empty(); + } + + if (map == null) { + return Optional.empty(); + } + + return Optional.ofNullable(map.get("remoteAddress")).map(Object::toString); + } + + private void appendTokenDetails(Authentication caller, StringBuilder builder) { String tokenValue = null; if (caller instanceof UaaOauth2Authentication uaaOauth2Authentication) { tokenValue = uaaOauth2Authentication.getTokenValue(); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEventTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEventTest.java index c96444eee24..5c4b64dcf45 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEventTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEventTest.java @@ -4,6 +4,7 @@ import org.cloudfoundry.identity.uaa.audit.AuditEventType; import org.cloudfoundry.identity.uaa.audit.JdbcAuditService; import org.cloudfoundry.identity.uaa.audit.UaaAuditService; +import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.oauth.UaaOauth2Authentication; import org.cloudfoundry.identity.uaa.oauth.provider.OAuth2Authentication; import org.cloudfoundry.identity.uaa.oauth.provider.OAuth2Request; @@ -69,19 +70,34 @@ void getContextAuthentication() { } @Test - void getOrigin() { + void getOrigin_whenMapDetailsHasNoKnownKeys_doesNotLeakMapContents() { UaaOauth2Authentication authentication = mock(UaaOauth2Authentication.class); OAuth2Request oAuth2Request = mock(OAuth2Request.class); when(authentication.getOAuth2Request()).thenReturn(oAuth2Request); when(authentication.getName()).thenReturn("marissa"); - when(authentication.getDetails()).thenReturn(Map.of("misc", "somedetails", "remoteAddress", "external")); + when(authentication.getDetails()).thenReturn(Map.of("grant_type", "password", "username", "marissa", "client_id", "clientid")); SecurityContextHolder.getContext().setAuthentication(authentication); String originString = event.getOrigin(authentication); assertThat(originString).contains("marissa") .contains("client=null") - .contains("misc=somedetails") - .contains("remoteAddress=external") - .contains("details=({"); + .doesNotContain("remoteAddress=") + .doesNotContain("grant_type=") + .doesNotContain("username=") + .doesNotContain("client_id="); + } + + @Test + void getOrigin_whenMapDetailsContainsRemoteAddress_extractsRemoteAddress() { + UaaOauth2Authentication authentication = mock(UaaOauth2Authentication.class); + OAuth2Request oAuth2Request = mock(OAuth2Request.class); + when(authentication.getOAuth2Request()).thenReturn(oAuth2Request); + when(authentication.getName()).thenReturn("marissa"); + when(authentication.getDetails()).thenReturn(Map.of("grant_type", "password", "remoteAddress", "10.0.0.1")); + SecurityContextHolder.getContext().setAuthentication(authentication); + String originString = event.getOrigin(authentication); + assertThat(originString).contains("marissa") + .contains("remoteAddress=10.0.0.1") + .doesNotContain("grant_type="); } @Test @@ -102,9 +118,82 @@ void getOriginDetailsParsed() { .contains("client=null") .doesNotContain("misc=somedetails") .contains("remoteAddress=external") + .contains("type=String") .doesNotContain("{"); } + @Test + void getOrigin_whenOAuth2AuthenticationWithUser_emitsClientAndUser() { + OAuth2Authentication authentication = mock(OAuth2Authentication.class); + OAuth2Request oAuth2Request = mock(OAuth2Request.class); + when(authentication.getOAuth2Request()).thenReturn(oAuth2Request); + when(oAuth2Request.getClientId()).thenReturn("clientid"); + when(authentication.isClientOnly()).thenReturn(false); + when(authentication.getName()).thenReturn("marissa"); + SecurityContextHolder.getContext().setAuthentication(authentication); + String originString = event.getOrigin(authentication); + assertThat(originString).contains("client=clientid") + .contains("user=marissa"); + } + + @Test + void getOrigin_whenStringDetailsIsNotJson_omitsRemoteAddress() { + UaaOauth2Authentication authentication = mock(UaaOauth2Authentication.class); + OAuth2Request oAuth2Request = mock(OAuth2Request.class); + when(authentication.getOAuth2Request()).thenReturn(oAuth2Request); + when(authentication.getName()).thenReturn("marissa"); + when(authentication.getDetails()).thenReturn("session-id-abc"); + SecurityContextHolder.getContext().setAuthentication(authentication); + String originString = event.getOrigin(authentication); + assertThat(originString).contains("marissa") + .contains("type=String") + .doesNotContain("remoteAddress="); + } + + @Test + void getOrigin_whenJsonDetailsMissingRemoteAddress_omitsRemoteAddress() { + UaaOauth2Authentication authentication = mock(UaaOauth2Authentication.class); + OAuth2Request oAuth2Request = mock(OAuth2Request.class); + when(authentication.getOAuth2Request()).thenReturn(oAuth2Request); + when(authentication.getName()).thenReturn("marissa"); + when(authentication.getDetails()).thenReturn("{\"remoteAddress\":null,\"sessionId\":\"abc\"}"); + SecurityContextHolder.getContext().setAuthentication(authentication); + String originString = event.getOrigin(authentication); + assertThat(originString).contains("marissa") + .contains("type=String") + .doesNotContain("remoteAddress="); + } + + @Test + void getOrigin_whenUaaAuthenticationDetails_extractsRemoteAddressFromAccessor() { + UaaOauth2Authentication authentication = mock(UaaOauth2Authentication.class); + OAuth2Request oAuth2Request = mock(OAuth2Request.class); + when(authentication.getOAuth2Request()).thenReturn(oAuth2Request); + when(authentication.getName()).thenReturn("marissa"); + UaaAuthenticationDetails uaaDetails = mock(UaaAuthenticationDetails.class); + when(uaaDetails.getOrigin()).thenReturn("10.0.0.1"); + when(authentication.getDetails()).thenReturn(uaaDetails); + SecurityContextHolder.getContext().setAuthentication(authentication); + String originString = event.getOrigin(authentication); + assertThat(originString).contains("details=(remoteAddress=10.0.0.1, type=UaaAuthenticationDetails"); + } + + @Test + void getOrigin_whenOAuth2AuthenticationDetails_extractsRemoteAddressFromAccessor() { + OAuth2Authentication authentication = mock(OAuth2Authentication.class); + OAuth2Request oAuth2Request = mock(OAuth2Request.class); + when(authentication.getOAuth2Request()).thenReturn(oAuth2Request); + when(oAuth2Request.getClientId()).thenReturn("clientid"); + when(authentication.isClientOnly()).thenReturn(true); + OAuth2AuthenticationDetails oauthDetails = mock(OAuth2AuthenticationDetails.class); + when(oauthDetails.getRemoteAddress()).thenReturn("172.18.0.1"); + when(authentication.getDetails()).thenReturn(oauthDetails); + SecurityContextHolder.getContext().setAuthentication(authentication); + String originString = event.getOrigin(authentication); + assertThat(originString).contains("client=clientid") + .contains("details=(remoteAddress=172.18.0.1, type=OAuth2AuthenticationDetails"); + } + @Test void getAuthenticationJsonWebTokenValue() { String originTokenString = event.getOrigin(mockAuthenticationWithToken("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYXJpc3NhIiwiaXNzIjoidWFhIn0.omitted")); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java index 5b25e070970..a4642a05ada 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java @@ -252,19 +252,17 @@ void userLoginTest() throws Exception { IdentityProviderAuthenticationSuccessEvent passwordEvent = testListener.getLatestEventOfType(IdentityProviderAuthenticationSuccessEvent.class); assertThat(passwordEvent.getUser().getUsername()).isEqualTo(testUser.getUserName()); - assertThat(passwordEvent.getAuditEvent().getOrigin()).contains("sessionId="); UserAuthenticationSuccessEvent userEvent = testListener.getLatestEventOfType(UserAuthenticationSuccessEvent.class); assertThat(userEvent.getUser().getId()).isEqualTo(passwordEvent.getUser().getId()); assertThat(userEvent.getUser().getUsername()).isEqualTo(testUser.getUserName()); - assertThat(userEvent.getAuditEvent().getOrigin()).contains("sessionId="); assertThat(passwordEvent.getAuthenticationType()).isEqualTo(OriginKeys.UAA); String passwordLogMsg = testLogger.getFirstLogMessageOfType(IdentityProviderAuthenticationSuccess); - assertLogMessageWithSession(passwordLogMsg, IdentityProviderAuthenticationSuccess, testUser.getId(), testUser.getUserName()); + assertLogMessage(passwordLogMsg, IdentityProviderAuthenticationSuccess, testUser.getId(), testUser.getUserName()); String userEventLogMsg = testLogger.getFirstLogMessageOfType(UserAuthenticationSuccess); - assertLogMessageWithSession(userEventLogMsg, UserAuthenticationSuccess, testUser.getId(), testUser.getUserName()); + assertLogMessage(userEventLogMsg, UserAuthenticationSuccess, testUser.getId(), testUser.getUserName()); } @ParameterizedTest @@ -288,19 +286,17 @@ void userLoginAuthenticateEndpointTest(String url) throws Exception { IdentityProviderAuthenticationSuccessEvent passwordEvent = testListener.getLatestEventOfType(IdentityProviderAuthenticationSuccessEvent.class); assertThat(passwordEvent.getUser().getUsername()).isEqualTo(testUser.getUserName()); - assertThat(passwordEvent.getAuditEvent().getOrigin()).contains("sessionId="); UserAuthenticationSuccessEvent userEvent = testListener.getLatestEventOfType(UserAuthenticationSuccessEvent.class); assertThat(userEvent.getUser().getId()).isEqualTo(passwordEvent.getUser().getId()); assertThat(userEvent.getUser().getUsername()).isEqualTo(testUser.getUserName()); - assertThat(userEvent.getAuditEvent().getOrigin()).contains("sessionId="); assertThat(passwordEvent.getAuthenticationType()).isEqualTo(OriginKeys.UAA); String passwordLogMsg = testLogger.getFirstLogMessageOfType(IdentityProviderAuthenticationSuccess); - assertLogMessageWithSession(passwordLogMsg, IdentityProviderAuthenticationSuccess, testUser.getId(), testUser.getUserName()); + assertLogMessage(passwordLogMsg, IdentityProviderAuthenticationSuccess, testUser.getId(), testUser.getUserName()); String userEventLogMsg = testLogger.getFirstLogMessageOfType(UserAuthenticationSuccess); - assertLogMessageWithSession(userEventLogMsg, UserAuthenticationSuccess, testUser.getId(), testUser.getUserName()); + assertLogMessage(userEventLogMsg, UserAuthenticationSuccess, testUser.getId(), testUser.getUserName()); } @Test @@ -321,24 +317,21 @@ void invalidPasswordLoginUnsuccessfulTest() throws Exception { IdentityProviderAuthenticationFailureEvent idpAuthFailEvent = (IdentityProviderAuthenticationFailureEvent) testListener.getEvents().getFirst(); assertThat(idpAuthFailEvent.getUsername()).isEqualTo(testUser.getUserName()); - assertThat(idpAuthFailEvent.getAuditEvent().getOrigin()).contains("sessionId="); UserAuthenticationFailureEvent userAuthFailEvent = (UserAuthenticationFailureEvent) testListener.getEvents().get(1); assertThat(userAuthFailEvent.getUser().getUsername()).isEqualTo(testUser.getUserName()); - assertThat(userAuthFailEvent.getAuditEvent().getOrigin()).contains("sessionId="); PrincipalAuthenticationFailureEvent principalAuthFailEvent = (PrincipalAuthenticationFailureEvent) testListener.getEvents().get(2); assertThat(principalAuthFailEvent.getName()).isEqualTo(testUser.getUserName()); - assertThat(principalAuthFailEvent.getAuditEvent().getOrigin()).doesNotContain("sessionId"); // PrincipalAuthenticationFailureEvent should not contain sessionId at all String idpAuthFailMsg = testLogger.getMessageAtIndex(0); - assertLogMessageWithSession(idpAuthFailMsg, IdentityProviderAuthenticationFailure, "null", testUser.getUserName()); + assertLogMessage(idpAuthFailMsg, IdentityProviderAuthenticationFailure, "null", testUser.getUserName()); String userAuthFailMsg = testLogger.getMessageAtIndex(1); - assertLogMessageWithSession(userAuthFailMsg, UserAuthenticationFailure, testUser.getId(), testUser.getUserName()); + assertLogMessage(userAuthFailMsg, UserAuthenticationFailure, testUser.getId(), testUser.getUserName()); String principalAuthFailMsg = testLogger.getMessageAtIndex(2); - assertLogMessageWithoutSession(principalAuthFailMsg, PrincipalAuthenticationFailure, testUser.getUserName(), "null"); + assertLogMessage(principalAuthFailMsg, PrincipalAuthenticationFailure, testUser.getUserName(), "null"); } @Test @@ -371,10 +364,9 @@ void unverifiedLegacyUserAuthenticationWhenAllowedTest( verify(authSuccessListener, times(1)).onApplicationEvent(captor.capture()); UserAuthenticationSuccessEvent event = captor.getValue(); assertThat(event.getUser().getUsername()).isEqualTo(molly.getUserName()); - assertThat(event.getAuditEvent().getOrigin()).contains("sessionId="); String userAuthLogMsg = testLogger.getFirstLogMessageOfType(UserAuthenticationSuccess); - assertLogMessageWithSession(userAuthLogMsg, UserAuthenticationSuccess, molly.getId(), molly.getUserName()); + assertLogMessage(userAuthLogMsg, UserAuthenticationSuccess, molly.getId(), molly.getUserName()); } @Test @@ -400,10 +392,9 @@ void unverifiedPostLegacyUserAuthenticationWhenAllowedTest() throws Exception { UnverifiedUserAuthenticationEvent unverifiedUserAuthEvent = testListener.getLatestEventOfType(UnverifiedUserAuthenticationEvent.class); assertThat(unverifiedUserAuthEvent.getUser().getUsername()).isEqualTo(molly.getUserName()); - assertThat(unverifiedUserAuthEvent.getAuditEvent().getOrigin()).contains("sessionId="); String userAuthLogMsg = testLogger.getFirstLogMessageOfType(UnverifiedUserAuthentication); - assertLogMessageWithSession(userAuthLogMsg, UnverifiedUserAuthentication, molly.getId(), molly.getUserName()); + assertLogMessage(userAuthLogMsg, UnverifiedUserAuthentication, molly.getId(), molly.getUserName()); } @Test @@ -427,10 +418,9 @@ void unverifiedUserAuthenticationWhenNotAllowedTest() throws Exception { UnverifiedUserAuthenticationEvent event = (UnverifiedUserAuthenticationEvent) testListener.getLatestEvent(); assertThat(event.getUser().getUsername()).isEqualTo(molly.getUserName()); - assertThat(event.getAuditEvent().getOrigin()).contains("sessionId="); String userAuthLogMsg = testLogger.getFirstLogMessageOfType(UnverifiedUserAuthentication); - assertLogMessageWithSession(userAuthLogMsg, UnverifiedUserAuthentication, molly.getId(), molly.getUserName()); + assertLogMessage(userAuthLogMsg, UnverifiedUserAuthentication, molly.getId(), molly.getUserName()); } @Test @@ -453,19 +443,15 @@ void invalidPasswordLoginAuthenticateEndpointTest() throws Exception { assertThat(event1.getUsername()).isEqualTo(testUser.getUserName()); assertThat(event2.getUser().getUsername()).isEqualTo(testUser.getUserName()); assertThat(event3.getName()).isEqualTo(testUser.getUserName()); - assertThat(event1.getAuditEvent().getOrigin()).contains("sessionId="); - assertThat(event2.getAuditEvent().getOrigin()).contains("sessionId="); - // PrincipalAuthenticationFailureEvent does not contain sessionId at all - assertThat(event3.getAuditEvent().getOrigin()).doesNotContain("sessionId="); String idpAuthLogMsg = testLogger.getMessageAtIndex(0); - assertLogMessageWithSession(idpAuthLogMsg, IdentityProviderAuthenticationFailure, "null", testUser.getUserName()); + assertLogMessage(idpAuthLogMsg, IdentityProviderAuthenticationFailure, "null", testUser.getUserName()); String userAuthLogMsg = testLogger.getMessageAtIndex(1); - assertLogMessageWithSession(userAuthLogMsg, UserAuthenticationFailure, testUser.getId(), testUser.getUserName()); + assertLogMessage(userAuthLogMsg, UserAuthenticationFailure, testUser.getId(), testUser.getUserName()); String principalAuthLogMsg = testLogger.getMessageAtIndex(2); - assertLogMessageWithoutSession(principalAuthLogMsg, PrincipalAuthenticationFailure, testUser.getUserName(), "null"); + assertLogMessage(principalAuthLogMsg, PrincipalAuthenticationFailure, testUser.getUserName(), "null"); } @Test @@ -493,9 +479,6 @@ void findAuditHistory(@Autowired JdbcAuditService auditService) throws Exception //after we reach our max attempts, 5, the system stops logging them until the period is over List events = auditService.find(jacobId, System.currentTimeMillis() - 10000, identityZoneManager.getCurrentIdentityZoneId()); assertThat(events).hasSize(5); - for (AuditEvent event : events) { - assertThat(event.getOrigin()).contains("sessionId="); - } } @Test @@ -517,16 +500,13 @@ void userNotFoundLoginUnsuccessfulTest() throws Exception { assertThatNumberOfAuditEventsReceivedIsGreaterThanOrEqualTo(2); UserNotFoundEvent event1 = (UserNotFoundEvent) testListener.getEvents().getFirst(); - assertThat(event1.getAuditEvent().getOrigin()).contains("sessionId="); PrincipalAuthenticationFailureEvent event2 = (PrincipalAuthenticationFailureEvent) testListener.getEvents().get(1); assertThat(((Authentication) event1.getSource()).getName()).isEqualTo(username); assertThat(event2.getName()).isEqualTo(username); - // PrincipalAuthenticationFailureEvent does not contain sessionId at all - assertThat(event2.getAuditEvent().getOrigin()).doesNotContain("sessionId="); String encodedUsername = Utf8.decode(Base64.encodeBase64(MessageDigest.getInstance("SHA-1").digest(Utf8.encode(username)))); - assertLogMessageWithSession(testLogger.getMessageAtIndex(0), UserNotFound, encodedUsername, ""); - assertLogMessageWithoutSession(testLogger.getMessageAtIndex(1), PrincipalAuthenticationFailure, username, "null"); + assertLogMessage(testLogger.getMessageAtIndex(0), UserNotFound, encodedUsername, ""); + assertLogMessage(testLogger.getMessageAtIndex(1), PrincipalAuthenticationFailure, username, "null"); } @Test @@ -547,17 +527,15 @@ void userChangePasswordTest() throws Exception { IdentityProviderAuthenticationSuccessEvent passwordevent = testListener.getLatestEventOfType(IdentityProviderAuthenticationSuccessEvent.class); String userid = passwordevent.getUser().getId(); - assertThat(passwordevent.getAuditEvent().getOrigin()).contains("sessionId="); UserAuthenticationSuccessEvent userevent = testListener.getLatestEventOfType(UserAuthenticationSuccessEvent.class); assertThat(userevent.getUser().getId()).isEqualTo(passwordevent.getUser().getId()); - assertThat(userevent.getAuditEvent().getOrigin()).contains("sessionId="); assertThat(passwordevent.getAuthenticationType()).isEqualTo(OriginKeys.UAA); String passwordLogMsg = testLogger.getFirstLogMessageOfType(IdentityProviderAuthenticationSuccess); - assertLogMessageWithSession(passwordLogMsg, IdentityProviderAuthenticationSuccess, testUser.getId(), testUser.getUserName()); + assertLogMessage(passwordLogMsg, IdentityProviderAuthenticationSuccess, testUser.getId(), testUser.getUserName()); String userEventLogMsg = testLogger.getFirstLogMessageOfType(UserAuthenticationSuccess); - assertLogMessageWithSession(userEventLogMsg, UserAuthenticationSuccess, testUser.getId(), testUser.getUserName()); + assertLogMessage(userEventLogMsg, UserAuthenticationSuccess, testUser.getId(), testUser.getUserName()); resetAuditTestReceivers(); String marissaToken = testClient.getUserOAuthAccessToken("app", "appclientsecret", testUser.getUserName(), testPassword, "password.write"); @@ -583,11 +561,10 @@ void userChangePasswordTest() throws Exception { PasswordChangeEvent pw = (PasswordChangeEvent) testListener.getLatestEvent(); assertThat(pw.getUser().getUsername()).isEqualTo(testUser.getUserName()); assertThat(pw.getMessage()).isEqualTo("Password changed"); - assertThat(pw.getAuditEvent().getOrigin()).contains("sessionId="); assertThat(pw.getAuditEvent().getPrincipalName()).isEqualTo(testUser.getUserName()); String pwLogMsg = testLogger.getLatestMessage(); - assertLogMessageWithSession(pwLogMsg, PasswordChangeSuccess, testUser.getId(), "Password changed"); + assertLogMessage(pwLogMsg, PasswordChangeSuccess, testUser.getId(), "Password changed"); assertThat(pwLogMsg).contains("principalName=[%s]".formatted(testUser.getUserName())); } @@ -610,17 +587,15 @@ void userChangeInvalidPasswordTest() throws Exception { IdentityProviderAuthenticationSuccessEvent passwordevent = testListener.getLatestEventOfType(IdentityProviderAuthenticationSuccessEvent.class); String userid = passwordevent.getUser().getId(); - assertThat(passwordevent.getAuditEvent().getOrigin()).contains("sessionId="); UserAuthenticationSuccessEvent userevent = testListener.getLatestEventOfType(UserAuthenticationSuccessEvent.class); assertThat(userevent.getUser().getId()).isEqualTo(passwordevent.getUser().getId()); - assertThat(userevent.getAuditEvent().getOrigin()).contains("sessionId="); assertThat(passwordevent.getAuthenticationType()).isEqualTo(OriginKeys.UAA); String passwordLogMsg = testLogger.getFirstLogMessageOfType(IdentityProviderAuthenticationSuccess); - assertLogMessageWithSession(passwordLogMsg, IdentityProviderAuthenticationSuccess, testUser.getId(), testUser.getUserName()); + assertLogMessage(passwordLogMsg, IdentityProviderAuthenticationSuccess, testUser.getId(), testUser.getUserName()); String userEventLogMsg = testLogger.getFirstLogMessageOfType(UserAuthenticationSuccess); - assertLogMessageWithSession(userEventLogMsg, UserAuthenticationSuccess, testUser.getId(), testUser.getUserName()); + assertLogMessage(userEventLogMsg, UserAuthenticationSuccess, testUser.getId(), testUser.getUserName()); resetAuditTestReceivers(); String marissaToken = testClient.getUserOAuthAccessToken("app", "appclientsecret", testUser.getUserName(), testPassword, "password.write"); @@ -648,11 +623,10 @@ void userChangeInvalidPasswordTest() throws Exception { PasswordChangeFailureEvent pwfe = (PasswordChangeFailureEvent) testListener.getLatestEvent(); assertThat(pwfe.getUser().getUsername()).isEqualTo(testUser.getUserName()); assertThat(pwfe.getMessage()).isEqualTo("Old password is incorrect"); - assertThat(pwfe.getAuditEvent().getOrigin()).contains("sessionId="); assertThat(pwfe.getAuditEvent().getPrincipalName()).isEqualTo(testUser.getUserName()); String pwfeLogMsg = testLogger.getLatestMessage(); - assertLogMessageWithSession(pwfeLogMsg, PasswordChangeFailure, testUser.getId(), "Old password is incorrect"); + assertLogMessage(pwfeLogMsg, PasswordChangeFailure, testUser.getId(), "Old password is incorrect"); assertThat(pwfeLogMsg).contains("principalName=[%s]".formatted(testUser.getUserName())); } @@ -672,7 +646,7 @@ void password_change_recorded_at_dao(@Autowired ScimUserProvisioning provisionin assertThat(pw.getAuditEvent().getPrincipalName()).isEqualTo(user.getUserName()); String pwLogMsg = testLogger.getLatestMessage(); - assertLogMessageWithoutSession(pwLogMsg, PasswordChangeSuccess, user.getId(), "Password changed"); + assertLogMessage(pwLogMsg, PasswordChangeSuccess, user.getId(), "Password changed"); assertThat(pwLogMsg).contains("principalName=[%s]".formatted(user.getUserName())); } @@ -701,11 +675,10 @@ void changePassword_ReturnsSuccess_WithValidExpiringCode() throws Exception { assertThat(pce.getUser().getUsername()).isEqualTo(testUser.getUserName()); assertThat(pce.getMessage()).isEqualTo("Password changed"); //PasswordChangeEvent does not contain session in this case - assertThat(pce.getAuditEvent().getOrigin()).doesNotContain("sessionId="); assertThat(pce.getAuditEvent().getPrincipalName()).isEqualTo(testUser.getUserName()); String pceLogMsg = testLogger.getLatestMessage(); - assertLogMessageWithoutSession(pceLogMsg, PasswordChangeSuccess, testUser.getId(), "Password changed"); + assertLogMessage(pceLogMsg, PasswordChangeSuccess, testUser.getId(), "Password changed"); assertThat(pceLogMsg).contains("principalName=[%s]".formatted(testUser.getUserName())); } @@ -726,7 +699,7 @@ void clientAuthenticationSuccess() throws Exception { AuditEvent auditEvent = event.getAuditEvent(); assertThat(auditEvent.getPrincipalId()).isEqualTo("login"); - assertLogMessageWithoutSession(testLogger.getMessageAtIndex(0), ClientAuthenticationSuccess, "login", "Client authentication success"); + assertLogMessage(testLogger.getMessageAtIndex(0), ClientAuthenticationSuccess, "login", "Client authentication success"); } @Test @@ -746,7 +719,7 @@ void clientAuthenticationFailure() throws Exception { AuditEvent auditEvent = event.getAuditEvent(); assertThat(auditEvent.getPrincipalId()).isEqualTo("login"); - assertLogMessageWithoutSession(testLogger.getLatestMessage(), ClientAuthenticationFailure, "login", "Bad credentials"); + assertLogMessage(testLogger.getLatestMessage(), ClientAuthenticationFailure, "login", "Bad credentials"); } @Test @@ -767,8 +740,8 @@ void clientAuthenticationFailureClientNotFound() throws Exception { ClientAuthenticationFailureEvent event1 = (ClientAuthenticationFailureEvent) testListener.getEvents().get(1); assertThat(event1.getClientId()).isEqualTo("login"); - assertLogMessageWithoutSession(testLogger.getMessageAtIndex(0), PrincipalAuthenticationFailure, "login2", "null"); - assertLogMessageWithoutSession(testLogger.getMessageAtIndex(1), ClientAuthenticationFailure, "login", "Bad credentials"); + assertLogMessage(testLogger.getMessageAtIndex(0), PrincipalAuthenticationFailure, "login2", "null"); + assertLogMessage(testLogger.getMessageAtIndex(1), ClientAuthenticationFailure, "login", "Bad credentials"); } @Test @@ -799,11 +772,10 @@ void userApprovalAdded() throws Exception { ApprovalModifiedEvent approvalModifiedEvent = (ApprovalModifiedEvent) testListener.getLatestEvent(); assertThat(approvalModifiedEvent.getAuthentication().getName()).isEqualTo(testUser.getUserName()); - assertThat(approvalModifiedEvent.getAuditEvent().getOrigin()).contains("sessionId="); String latestMessage = testLogger.getLatestMessage(); assertThat(latestMessage).contains(" user=" + testUser.getUserName()); - assertLogMessageWithSession(latestMessage, ApprovalModifiedEvent, testUser.getId(), "{\"scope\":\"cloud_controller.read\",\"status\":\"APPROVED\"}"); + assertLogMessage(latestMessage, ApprovalModifiedEvent, testUser.getId(), "{\"scope\":\"cloud_controller.read\",\"status\":\"APPROVED\"}"); } @Test @@ -833,14 +805,13 @@ void generateUserModifiedEvent_whenUserCreatedByClient() throws Exception { assertThat(userModifiedEvent.getAuthentication().getName()).isEqualTo(testAccounts.getAdminClientId()); assertThat(userModifiedEvent.getUsername()).isEqualTo(scimUser.getUserName()); assertThat(userModifiedEvent.getAuditEvent().getType()).isEqualTo(UserCreatedEvent); - assertThat(userModifiedEvent.getAuditEvent().getOrigin()).contains("sessionId="); ScimUser createdUser = jdbcScimUserProvisioning.retrieveAll(identityZoneManager.getCurrentIdentityZoneId()) .stream().filter(dbUser -> dbUser.getUserName().equals(scimUser.getUserName())).findFirst().get(); String logMessage = "[\"user_id=%s\",\"username=%s\"]".formatted( createdUser.getId(), scimUser.getUserName()); - assertLogMessageWithSession(testLogger.getLatestMessage(), + assertLogMessage(testLogger.getLatestMessage(), UserCreatedEvent, createdUser.getId(), logMessage); } @@ -905,8 +876,7 @@ void generateUserModifiedEvent_whenCreatingUser( assertThat(actualLogMessage).startsWith(UserCreatedEvent.toString()) .contains("principal=%s,".formatted(createdUser.getId())) .contains(logMessage) - .contains(", identityZoneId=[%s]".formatted(zoneSeeder.getIdentityZoneId())) - .matches(".*origin=\\[.*sessionId=.*\\].*"); + .contains(", identityZoneId=[%s]".formatted(zoneSeeder.getIdentityZoneId())); } @Test @@ -947,8 +917,7 @@ void generateUserDeletedEvent_whenDeletingUser( assertThat(actualLogMessage).startsWith(UserDeletedEvent.toString()) .contains("principal=%s,".formatted(scimUser.getId())) .contains(" ('%s'): ".formatted(logMessage)) - .contains(", identityZoneId=[%s]".formatted(zoneSeeder.getIdentityZoneId())) - .matches(".*origin=\\[.*sessionId=.*\\].*"); + .contains(", identityZoneId=[%s]".formatted(zoneSeeder.getIdentityZoneId())); } } @@ -990,7 +959,6 @@ void generateUserCreatedEvent_DuringLoginServerAuthorize() throws Exception { assertThat(userModifiedEvent.getAuthentication().getName()).isEqualTo("login"); assertThat(userModifiedEvent.getUsername()).isEqualTo(username); assertThat(userModifiedEvent.getAuditEvent().getType()).isEqualTo(UserCreatedEvent); - assertThat(userModifiedEvent.getAuditEvent().getOrigin()).contains("sessionId="); ScimUser createdUser = jdbcScimUserProvisioning.retrieveAll(identityZoneManager.getCurrentIdentityZoneId()) .stream().filter(dbUser -> dbUser.getUserName().equals(username)).findFirst().get(); @@ -999,7 +967,7 @@ void generateUserCreatedEvent_DuringLoginServerAuthorize() throws Exception { createdUser.getId(), username); - assertLogMessageWithSession(testLogger.getMessageAtIndex(0), + assertLogMessage(testLogger.getMessageAtIndex(0), UserCreatedEvent, createdUser.getId(), logMessage); } @@ -1060,10 +1028,9 @@ void generateUserModifiedEvent_whenModifyingUser( assertThat(userModifiedEvent.getAuthentication().getName()).isEqualTo(testAccounts.getAdminClientId()); assertThat(userModifiedEvent.getUsername()).isEqualTo(scimUser.getUserName()); assertThat(userModifiedEvent.getAuditEvent().getType()).isEqualTo(UserModifiedEvent); - assertThat(userModifiedEvent.getAuditEvent().getOrigin()).contains("sessionId="); String logMessage = "[\"user_id=%s\",\"username=%s\"]".formatted(scimUser.getId(), scimUser.getUserName()); - assertLogMessageWithSession(testLogger.getLatestMessage(), + assertLogMessage(testLogger.getLatestMessage(), UserModifiedEvent, scimUser.getId(), logMessage); @@ -1089,12 +1056,11 @@ void generateUserDeletedEvent_whenDeletingUser( assertThat(userDeletedEvent.getAuthentication().getName()).isEqualTo(testAccounts.getAdminClientId()); assertThat(userDeletedEvent.getUsername()).isEqualTo(scimUser.getUserName()); assertThat(userDeletedEvent.getAuditEvent().getType()).isEqualTo(UserDeletedEvent); - assertThat(userDeletedEvent.getAuditEvent().getOrigin()).contains("sessionId="); String logMessage = "[\"user_id=%s\",\"username=%s\"]".formatted( scimUser.getId(), scimUser.getUserName()); - assertLogMessageWithSession(testLogger.getLatestMessage(), + assertLogMessage(testLogger.getLatestMessage(), UserDeletedEvent, scimUser.getId(), logMessage); } } @@ -1143,9 +1109,8 @@ void userVerifiedEvent() throws Exception { assertThat(userModifiedEvent.getAuthentication().getName()).isEqualTo(testAccounts.getAdminClientId()); assertThat(userModifiedEvent.getUsername()).isEqualTo(username); assertThat(userModifiedEvent.getAuditEvent().getType()).isEqualTo(UserVerifiedEvent); - assertThat(userModifiedEvent.getAuditEvent().getOrigin()).contains("sessionId="); - assertLogMessageWithSession(testLogger.getLatestMessage(), + assertLogMessage(testLogger.getLatestMessage(), UserVerifiedEvent, user.getId(), "[\"user_id=%s\",\"username=%s\"]".formatted(user.getId(), username)); } @@ -1170,9 +1135,8 @@ void passwordResetRequestEvent() throws Exception { ResetPasswordRequestEvent event = (ResetPasswordRequestEvent) testListener.getLatestEvent(); assertThat(event.getAuditEvent().getPrincipalId()).isEqualTo(testUser.getUserName()); assertThat(event.getAuditEvent().getData()).isEqualTo(testUser.getPrimaryEmail()); - assertThat(event.getAuditEvent().getOrigin()).contains("sessionId="); - assertLogMessageWithSession(testLogger.getLatestMessage(), + assertLogMessage(testLogger.getLatestMessage(), PasswordResetRequest, testUser.getUserName(), testUser.getPrimaryEmail()); } @@ -1341,7 +1305,6 @@ private ScimUser createUser(String adminToken, String username, String firstname assertThat(userModifiedEvent.getAuthentication().getName()).isEqualTo(testAccounts.getAdminClientId()); assertThat(userModifiedEvent.getUsername()).isEqualTo(username); assertThat(userModifiedEvent.getAuditEvent().getType()).isEqualTo(UserCreatedEvent); - assertThat(userModifiedEvent.getAuditEvent().getOrigin()).contains("sessionId="); return JsonUtils.readValue(result.andReturn().getResponse().getContentAsString(), ScimUser.class); } @@ -1425,26 +1388,16 @@ private void assertSingleAuditEventLogMessage(AuditEventType expectedEventType, assertThat(message).contains("\"authorities\":[%s]".formatted(commaSeparatedQuotedAuthorities)); } - private void assertLogMessageWithSession(String actualLogMessage, AuditEventType expectedAuditEventType, String expectedPrincipal, String expectedUserName) { - assertThat(actualLogMessage).startsWith(expectedAuditEventType.toString() + " ") - .contains("principal=%s,".formatted(expectedPrincipal)) - .contains(" ('%s'): ".formatted(expectedUserName)) - .contains(", identityZoneId=[uaa]") - .matches(".*origin=\\[.*sessionId=.*\\].*"); - } - - private static void assertLogMessageWithoutSession(String actualLogMessage, AuditEventType expectedAuditEventType, String expectedPrincipal, String expectedUserName) { + private void assertLogMessage(String actualLogMessage, AuditEventType expectedAuditEventType, String expectedPrincipal, String expectedUserName) { assertThat(actualLogMessage).startsWith(expectedAuditEventType.toString() + " ") .contains("principal=%s,".formatted(expectedPrincipal)) .contains(" ('%s'): ".formatted(expectedUserName)) - .contains(", identityZoneId=[uaa]") - .doesNotContain("sessionId"); + .contains(", identityZoneId=[uaa]"); } private static void assertGroupMembershipLogMessage(String actualLogMessage, AuditEventType expectedEventType, String expectedGroupDisplayName, String expectedGroupId, String... expectedUserIds) { assertThat(actualLogMessage).startsWith(expectedEventType.toString() + " ") - .contains("principal=%s,".formatted(expectedGroupId)) - .doesNotContain("sessionId"); + .contains("principal=%s,".formatted(expectedGroupId)); Pattern groupLogPattern = Pattern.compile(" \\('\\{\"group_name\":\"" + Pattern.quote(expectedGroupDisplayName) + "\",\"members\":\\[(.*?)]}'\\): "); Matcher patternMatcher = groupLogPattern.matcher(actualLogMessage); From f80d678f702d5bd76639917f0151e4e4db781bbf Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Thu, 23 Jul 2026 00:45:29 +0300 Subject: [PATCH 2/6] simplify --- .../identity/uaa/audit/event/AbstractUaaEvent.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java index 291595b19e0..71d87fcaf69 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java @@ -148,19 +148,13 @@ private Optional extractRemoteAddress(Object details) { } private Optional extractRemoteAddressFromJson(String jsonBlob) { - Map map; try { - map = JsonUtils.readValue(jsonBlob, new TypeReference<>() { + Map map = JsonUtils.readValue(jsonBlob, new TypeReference<>() { }); - } catch (Exception _) { + return map == null ? Optional.empty() : extractRemoteAddress(map); + } catch (JsonUtils.JsonUtilException _) { return Optional.empty(); } - - if (map == null) { - return Optional.empty(); - } - - return Optional.ofNullable(map.get("remoteAddress")).map(Object::toString); } private void appendTokenDetails(Authentication caller, StringBuilder builder) { From 3f3f395754ac271619b1ed28cf4247b1ab5c954f Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Thu, 23 Jul 2026 16:53:50 +0300 Subject: [PATCH 3/6] handle WebAuthenticationDetails --- .../uaa/audit/event/AbstractUaaEvent.java | 2 ++ .../uaa/audit/event/AbstractUaaEventTest.java | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java index 71d87fcaf69..36abe443ff7 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java @@ -30,6 +30,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetails; import tools.jackson.core.type.TypeReference; import java.io.Serial; @@ -138,6 +139,7 @@ private Optional extractRemoteAddress(Object details) { return switch (details) { case UaaAuthenticationDetails d -> Optional.ofNullable(d.getOrigin()); case OAuth2AuthenticationDetails d -> Optional.ofNullable(d.getRemoteAddress()); + case WebAuthenticationDetails d -> Optional.of(d.getRemoteAddress()); case Map map -> Optional.ofNullable(map.get("remoteAddress")).map(Object::toString); case String jsonBlob -> extractRemoteAddressFromJson(jsonBlob); default -> { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEventTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEventTest.java index 5c4b64dcf45..1af4eb57329 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEventTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEventTest.java @@ -17,6 +17,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetails; import java.util.Map; @@ -194,6 +195,21 @@ void getOrigin_whenOAuth2AuthenticationDetails_extractsRemoteAddressFromAccessor .contains("details=(remoteAddress=172.18.0.1, type=OAuth2AuthenticationDetails"); } + @Test + void getOrigin_whenWebAuthenticationDetails_extractsRemoteAddressFromAccessor() { + UaaOauth2Authentication authentication = mock(UaaOauth2Authentication.class); + OAuth2Request oAuth2Request = mock(OAuth2Request.class); + when(authentication.getOAuth2Request()).thenReturn(oAuth2Request); + when(authentication.getName()).thenReturn("marissa"); + WebAuthenticationDetails webDetails = mock(WebAuthenticationDetails.class); + when(webDetails.getRemoteAddress()).thenReturn("192.168.1.5"); + when(authentication.getDetails()).thenReturn(webDetails); + SecurityContextHolder.getContext().setAuthentication(authentication); + String originString = event.getOrigin(authentication); + assertThat(originString).contains("marissa") + .contains("details=(remoteAddress=192.168.1.5, type=WebAuthenticationDetails"); + } + @Test void getAuthenticationJsonWebTokenValue() { String originTokenString = event.getOrigin(mockAuthenticationWithToken("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYXJpc3NhIiwiaXNzIjoidWFhIn0.omitted")); From 1c1c69cf53ec63658870a2fecdc0e0b43aef2950 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Thu, 23 Jul 2026 18:58:27 +0300 Subject: [PATCH 4/6] Restore sessionId absence assertion in audit log helpers --- .../identity/uaa/mock/audit/AuditCheckMockMvcTests.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java index a4642a05ada..c494a73408b 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java @@ -1392,12 +1392,14 @@ private void assertLogMessage(String actualLogMessage, AuditEventType expectedAu assertThat(actualLogMessage).startsWith(expectedAuditEventType.toString() + " ") .contains("principal=%s,".formatted(expectedPrincipal)) .contains(" ('%s'): ".formatted(expectedUserName)) - .contains(", identityZoneId=[uaa]"); + .contains(", identityZoneId=[uaa]") + .doesNotContain("sessionId"); } private static void assertGroupMembershipLogMessage(String actualLogMessage, AuditEventType expectedEventType, String expectedGroupDisplayName, String expectedGroupId, String... expectedUserIds) { assertThat(actualLogMessage).startsWith(expectedEventType.toString() + " ") - .contains("principal=%s,".formatted(expectedGroupId)); + .contains("principal=%s,".formatted(expectedGroupId)) + .doesNotContain("sessionId"); Pattern groupLogPattern = Pattern.compile(" \\('\\{\"group_name\":\"" + Pattern.quote(expectedGroupDisplayName) + "\",\"members\":\\[(.*?)]}'\\): "); Matcher patternMatcher = groupLogPattern.matcher(actualLogMessage); From f5b6cd0a525cadb659b4844255bc94e9d01bffe1 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Thu, 23 Jul 2026 18:58:32 +0300 Subject: [PATCH 5/6] Stop including session identifier in audit log origin for authentication events Replace details.toString() in AbstractUaaAuthenticationEvent.getOrigin() with an explicit implementation that emits only remoteAddress and clientId, so the session identifier is no longer included even in masked form. --- .../event/AbstractUaaAuthenticationEvent.java | 20 ++++++++++++++++++- .../UserAuthenticationSuccessEventTests.java | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/AbstractUaaAuthenticationEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/AbstractUaaAuthenticationEvent.java index 66322930102..e8590120d10 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/AbstractUaaAuthenticationEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/AbstractUaaAuthenticationEvent.java @@ -17,6 +17,10 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.springframework.security.core.Authentication; +import java.util.StringJoiner; + +import static org.springframework.util.StringUtils.hasText; + /** * @author Luke Taylor */ @@ -27,7 +31,21 @@ public abstract class AbstractUaaAuthenticationEvent extends AbstractUaaEvent { } protected String getOrigin(UaaAuthenticationDetails details) { - return details == null ? "unknown" : details.toString(); + if (details == null) { + return "unknown"; + } + + StringJoiner joiner = new StringJoiner(", "); + + if (hasText(details.getOrigin())) { + joiner.add("remoteAddress=" + details.getOrigin()); + } + + if (hasText(details.getClientId())) { + joiner.add("clientId=" + details.getClientId()); + } + + return joiner.length() == 0 ? "unknown" : joiner.toString(); } UaaAuthenticationDetails getAuthenticationDetails() { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/event/UserAuthenticationSuccessEventTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/event/UserAuthenticationSuccessEventTests.java index 5dd171d6412..8373c412f98 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/event/UserAuthenticationSuccessEventTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/event/UserAuthenticationSuccessEventTests.java @@ -27,6 +27,6 @@ void getOriginFromRequest() { assertThat(origin).contains("remoteAddress=127.10.10.10") .contains("clientId=client-id") - .contains("sessionId="); + .doesNotContain("sessionId"); } } From 7d89e23cec5e402ac57077af0ce81426df910106 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Thu, 23 Jul 2026 21:14:46 +0300 Subject: [PATCH 6/6] Filter blank remoteAddress values in extractRemoteAddress --- .../identity/uaa/audit/event/AbstractUaaEvent.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java index 36abe443ff7..576112f2835 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java @@ -31,6 +31,7 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetails; +import org.springframework.util.StringUtils; import tools.jackson.core.type.TypeReference; import java.io.Serial; @@ -41,7 +42,6 @@ import java.util.Optional; import static org.cloudfoundry.identity.uaa.util.UaaTokenUtils.isJwtToken; -import static org.springframework.util.StringUtils.hasText; /** * Base class for UAA events that want to publish audit records. @@ -137,10 +137,10 @@ private String getAuthenticationString(Authentication caller) { private Optional extractRemoteAddress(Object details) { return switch (details) { - case UaaAuthenticationDetails d -> Optional.ofNullable(d.getOrigin()); - case OAuth2AuthenticationDetails d -> Optional.ofNullable(d.getRemoteAddress()); - case WebAuthenticationDetails d -> Optional.of(d.getRemoteAddress()); - case Map map -> Optional.ofNullable(map.get("remoteAddress")).map(Object::toString); + case UaaAuthenticationDetails d -> Optional.ofNullable(d.getOrigin()).filter(StringUtils::hasText); + case OAuth2AuthenticationDetails d -> Optional.ofNullable(d.getRemoteAddress()).filter(StringUtils::hasText); + case WebAuthenticationDetails d -> Optional.ofNullable(d.getRemoteAddress()).filter(StringUtils::hasText); + case Map map -> Optional.ofNullable(map.get("remoteAddress")).map(Object::toString).filter(StringUtils::hasText); case String jsonBlob -> extractRemoteAddressFromJson(jsonBlob); default -> { logger.warn("Unhandled Authentication.details type in audit origin: {}", details.getClass().getName()); @@ -166,7 +166,7 @@ private void appendTokenDetails(Authentication caller, StringBuilder builder) { } else if (caller.getDetails() instanceof OAuth2AuthenticationDetails oAuth2AuthenticationDetails) { tokenValue = oAuth2AuthenticationDetails.getTokenValue(); } - if (hasText(tokenValue)) { + if (StringUtils.hasText(tokenValue)) { if (isJwtToken(tokenValue)) { try { Jwt token = JwtHelper.decode(tokenValue);