Critical severity9.8NVD Advisory· Published Oct 24, 2017· Updated May 13, 2026
CVE-2015-5171
CVE-2015-5171
Description
The password change functionality in Cloud Foundry Runtime cf-release before 216, UAA before 2.5.2, and Pivotal Cloud Foundry (PCF) Elastic Runtime before 1.7.0 allow attackers to have unspecified impact by leveraging failure to expire existing sessions.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven | < 2.5.2 | 2.5.2 |
Affected products
3- cpe:2.3:a:pivotal_software:cloud_foundry_elastic_runtime:*:*:*:*:*:*:*:*Range: <1.7.0
- cpe:2.3:a:pivotal_software:cloud_foundry_uaa:*:*:*:*:*:*:*:*Range: <2.5.2
Patches
27c70a85f4c7bMerge branch 'feature/reset_session_during_password_change' into develop
13 files changed · +425 −21
common/src/main/java/org/cloudfoundry/identity/uaa/authentication/AuthzAuthenticationFilter.java+1 −1 modified@@ -82,7 +82,7 @@ public class AuthzAuthenticationFilter implements Filter { * @param methods the methods to set (defaults to POST) */ public void setMethods(Set<String> methods) { - this.methods = new HashSet<String>(); + this.methods = new HashSet<>(); for (String method : methods) { this.methods.add(method.toUpperCase()); }
common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java+5 −2 modified@@ -134,8 +134,11 @@ public Authentication authenticate(Authentication req) throws AuthenticationExce } } - Authentication success = new UaaAuthentication(new UaaPrincipal(user), - user.getAuthorities(), (UaaAuthenticationDetails) req.getDetails()); + Authentication success = new UaaAuthentication( + new UaaPrincipal(user), + user.getAuthorities(), + (UaaAuthenticationDetails) req.getDetails()); + publish(new UserAuthenticationSuccessEvent(user, success)); return success;
common/src/main/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilter.java+98 −0 added@@ -0,0 +1,98 @@ +/* + * ****************************************************************************** + * * Cloud Foundry + * * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * * + * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * * You may not use this product except in compliance with the License. + * * + * * This product includes a number of subcomponents with + * * separate copyright notices and license terms. Your use of these + * * subcomponents is subject to the terms and conditions of the + * * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.authentication; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.RedirectStrategy; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; + +public class SessionResetFilter extends OncePerRequestFilter { + + private static Log logger = LogFactory.getLog(SessionResetFilter.class); + + private final RedirectStrategy strategy; + private final String redirectUrl; + private final UaaUserDatabase userDatabase; + + public SessionResetFilter(RedirectStrategy strategy, String redirectUrl, UaaUserDatabase userDatabase) { + this.strategy = strategy; + this.redirectUrl = redirectUrl; + this.userDatabase = userDatabase; + } + + public String getRedirectUrl() { + return redirectUrl; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + SecurityContext context = SecurityContextHolder.getContext(); + if (context!=null && context.getAuthentication()!=null && context.getAuthentication() instanceof UaaAuthentication) { + UaaAuthentication authentication = (UaaAuthentication)context.getAuthentication(); + if (authentication.isAuthenticated() && + Origin.UAA.equals(authentication.getPrincipal().getOrigin()) && + null != request.getSession(false)) { + + boolean redirect = false; + String userId = authentication.getPrincipal().getId(); + try { + logger.debug("Evaluating user-id for session reset:"+userId); + UaaUser user = userDatabase.retrieveUserById(userId); + long lastAuthTime = authentication.getAuthenticatedTime(); + long passwordModTime = user.getPasswordLastModified().getTime() ; + //if the password has changed after authentication time + if (hasPasswordChangedAfterAuthentication(lastAuthTime, passwordModTime)) { + logger.debug(String.format("Resetting user session for user ID: %s Auth Time: %s Password Change Time: %s",userId, lastAuthTime, passwordModTime)); + redirect = true; + } + } catch (UsernameNotFoundException x) { + logger.info("Authenticated user ["+userId+"] was not found in DB."); + redirect = true; + } + if (redirect) { + handleRedirect(request, response); + return; + } + } + } + filterChain.doFilter(request,response); + } + + protected boolean hasPasswordChangedAfterAuthentication(long lastAuthTime, long passwordModTime) { + return passwordModTime > lastAuthTime; + } + + protected void handleRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException { + HttpSession session = request.getSession(false); + if (session!=null) { + session.invalidate(); + } + strategy.sendRedirect(request, response, getRedirectUrl()); + } +}
common/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java+11 −2 modified@@ -13,6 +13,7 @@ package org.cloudfoundry.identity.uaa.authentication; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -30,6 +31,7 @@ public class UaaAuthentication implements Authentication, Serializable { private UaaPrincipal principal; private UaaAuthenticationDetails details; private boolean authenticated; + private long authenticatedTime = -1l; /** * Creates a token with the supplied array of authorities. @@ -40,15 +42,16 @@ public class UaaAuthentication implements Authentication, Serializable { public UaaAuthentication(UaaPrincipal principal, List<? extends GrantedAuthority> authorities, UaaAuthenticationDetails details) { - this(principal, null, authorities, details, true); + this(principal, null, authorities, details, true, System.currentTimeMillis()); } @JsonCreator public UaaAuthentication(@JsonProperty("principal") UaaPrincipal principal, @JsonProperty("credentials") Object credentials, @JsonProperty("authorities") List<? extends GrantedAuthority> authorities, @JsonProperty("details") UaaAuthenticationDetails details, - @JsonProperty("authenticated") boolean authenticated) { + @JsonProperty("authenticated") boolean authenticated, + @JsonProperty(value = "authenticatedTime", defaultValue = "-1") long authenticatedTime) { if (principal == null || authorities == null) { throw new IllegalArgumentException("principal and authorities must not be null"); } @@ -57,9 +60,15 @@ public UaaAuthentication(@JsonProperty("principal") UaaPrincipal principal, this.details = details; this.credentials = credentials; this.authenticated = authenticated; + this.authenticatedTime = authenticatedTime == 0 ? -1 : authenticatedTime; + } + + public long getAuthenticatedTime() { + return authenticatedTime; } @Override + @JsonIgnore public String getName() { // Should we return the ID for the principal name? (No, because the // UaaUserDatabase retrieves users by name.)
common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java+1 −1 modified@@ -109,7 +109,7 @@ protected void stripScopesFromAuthentication(String identityZoneId, HttpServletR null, UaaStringUtils.getAuthoritiesFromStrings(clientScopes), new UaaAuthenticationDetails(servletRequest), - true); + true, userAuthentication.getAuthenticatedTime()); } oa = new OAuth2Authentication(request, userAuthentication); oa.setDetails(oaDetails);
common/src/test/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilterTests.java+172 −0 added@@ -0,0 +1,172 @@ +/* + * ****************************************************************************** + * * Cloud Foundry + * * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * * + * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * * You may not use this product except in compliance with the License. + * * + * * This product includes a number of subcomponents with + * * separate copyright notices and license terms. Your use of these + * * subcomponents is subject to the terms and conditions of the + * * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.authentication; + +import org.cloudfoundry.identity.uaa.user.InMemoryUaaUserDatabase; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.DefaultRedirectStrategy; +import org.springframework.util.ReflectionUtils; + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.lang.reflect.Field; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +public class SessionResetFilterTests { + + SessionResetFilter filter; + HttpServletResponse response; + HttpServletRequest request; + HttpSession session; + FilterChain chain; + UaaUserDatabase userDatabase; + UaaAuthentication authentication; + Date yesterday; + UaaUser user; + Map<String,UaaUser> users; + + @Before + public void setUpFilter() throws Exception { + + yesterday = new Date(System.currentTimeMillis()-(1000*60*60*24)); + + user = new UaaUser( + "user-id", + "username", + "password", + "email", + Collections.EMPTY_LIST, + "given name", + "family name", + yesterday, + yesterday, + Origin.UAA, + null, + true, + IdentityZone.getUaa().getId(), + "salt", + yesterday + ); + + UaaPrincipal principal = new UaaPrincipal(user); + + authentication = new UaaAuthentication(principal, null, Collections.EMPTY_LIST, null, true, System.currentTimeMillis()); + + users = new HashMap<>(); + users.put(user.getId(), user); + userDatabase = new InMemoryUaaUserDatabase(users); + + chain = mock(FilterChain.class); + request = mock(HttpServletRequest.class); + response = mock(HttpServletResponse.class); + session = mock(HttpSession.class); + when(request.getSession(anyBoolean())).thenReturn(session); + filter = new SessionResetFilter(new DefaultRedirectStrategy(),"/login", userDatabase); + } + + @After + public void clearThingsUp() { + SecurityContextHolder.clearContext(); + IdentityZoneHolder.clear(); + } + + + @Test + public void test_No_Authentication_Present() throws Exception { + filter.doFilterInternal(request, response, chain); + verify(chain, times(1)).doFilter(request, response); + } + + @Test + public void test_No_UAA_Authentication_Present() throws Exception { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken("test","test"); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + filter.doFilterInternal(request, response, chain); + verify(chain, times(1)).doFilter(request, response); + verifyZeroInteractions(request); + verifyZeroInteractions(response); + } + + @Test + public void test_User_Modified_After_Authentication() throws Exception { + setFieldValue("authenticatedTime", (yesterday.getTime() - (1000 * 60 * 60 * 24)), authentication); + SecurityContextHolder.getContext().setAuthentication(authentication); + filter.doFilterInternal(request, response, chain); + + //user is not forwarded, and error response is generated right away + Mockito.verifyZeroInteractions(chain); + //user redirect + verify(response, times(1)).sendRedirect(anyString()); + //session was requested + verify(request, times(2)).getSession(false); + //session was invalidated + verify(session, times(1)).invalidate(); + } + + protected long dropMilliSeconds(long time) { + return ( time / 1000l ) * 1000l; + } + + @Test + public void test_User_Not_Modified() throws Exception { + SecurityContextHolder.getContext().setAuthentication(authentication); + filter.doFilterInternal(request, response, chain); + verify(chain, times(1)).doFilter(request, response); + verifyZeroInteractions(response); + } + + @Test + public void test_User_Not_Originated_In_Uaa() throws Exception { + SecurityContextHolder.getContext().setAuthentication(authentication); + setFieldValue("origin", Origin.LDAP, authentication.getPrincipal()); + filter.doFilterInternal(request, response, chain); + verify(chain, times(1)).doFilter(request, response); + verifyZeroInteractions(request); + verifyZeroInteractions(response); + } + + protected void setFieldValue(String fieldname, Object value, Object object) { + Field f = ReflectionUtils.findField(object.getClass(), fieldname); + ReflectionUtils.makeAccessible(f); + ReflectionUtils.setField(f, object, value); + } + +} \ No newline at end of file
common/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializationTests.java+35 −0 added@@ -0,0 +1,35 @@ +/* + * ****************************************************************************** + * * Cloud Foundry + * * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * * + * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * * You may not use this product except in compliance with the License. + * * + * * This product includes a number of subcomponents with + * * separate copyright notices and license terms. Your use of these + * * subcomponents is subject to the terms and conditions of the + * * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.authentication; + +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.junit.Assert; +import org.junit.Test; + +public class UaaAuthenticationSerializationTests { + + @Test + public void testDeserializationWithoutAuthenticatedTime() throws Exception { + String data ="{\"principal\":{\"id\":\"user-id\",\"name\":\"username\",\"email\":\"email\",\"origin\":\"uaa\",\"externalId\":null,\"zoneId\":\"uaa\"},\"credentials\":null,\"authorities\":[],\"details\":null,\"authenticated\":true,\"authenticatedTime\":1438649464353,\"name\":\"username\"}"; + UaaAuthentication authentication1 = JsonUtils.readValue(data, UaaAuthentication.class); + Assert.assertEquals(1438649464353l, authentication1.getAuthenticatedTime()); + + String dataWithoutTime ="{\"principal\":{\"id\":\"user-id\",\"name\":\"username\",\"email\":\"email\",\"origin\":\"uaa\",\"externalId\":null,\"zoneId\":\"uaa\"},\"credentials\":null,\"authorities\":[],\"details\":null,\"authenticated\":true,\"name\":\"username\"}"; + UaaAuthentication authentication2 = JsonUtils.readValue(dataWithoutTime, UaaAuthentication.class); + Assert.assertEquals(-1, authentication2.getAuthenticatedTime()); + + } +} \ No newline at end of file
login/src/main/java/org/cloudfoundry/identity/uaa/login/ChangePasswordController.java+13 −0 modified@@ -12,6 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.login; +import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; +import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.BadCredentialsException; @@ -26,6 +28,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.util.Arrays; +import java.util.LinkedList; + import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; @@ -67,6 +72,14 @@ public String changePassword( changePasswordService.changePassword(username, currentPassword, newPassword); request.getSession().invalidate(); request.getSession(true); + if (authentication instanceof UaaAuthentication) { + UaaAuthentication uaaAuthentication = (UaaAuthentication)authentication; + authentication = new UaaAuthentication( + uaaAuthentication.getPrincipal(), + new LinkedList<>(uaaAuthentication.getAuthorities()), + new UaaAuthenticationDetails(request) + ); + } securityContext.setAuthentication(authentication); return "redirect:profile"; } catch (BadCredentialsException e) {
scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java+11 −4 modified@@ -46,7 +46,9 @@ import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; +import java.util.Calendar; import java.util.Date; +import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -66,7 +68,7 @@ public class JdbcScimUserProvisioning extends AbstractQueryable<ScimUser> implem public static final String CREATE_USER_SQL = "insert into users (" + USER_FIELDS + ",password) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; - public static final String UPDATE_USER_SQL = "update users set version=?, lastModified=?, userName=?, email=?, givenName=?, familyName=?, active=?, phoneNumber=?, verified=?, origin=?, external_id=?, salt=?, passwd_lastmodified=? where id=? and version=?"; + public static final String UPDATE_USER_SQL = "update users set version=?, lastModified=?, userName=?, email=?, givenName=?, familyName=?, active=?, phoneNumber=?, verified=?, origin=?, external_id=?, salt=? where id=? and version=?"; public static final String DEACTIVATE_USER_SQL = "update users set active=? where id=?"; @@ -169,7 +171,7 @@ public void setValues(PreparedStatement ps) throws SQLException { ps.setString(13, StringUtils.hasText(user.getExternalId())?user.getExternalId():null); ps.setString(14, identityZoneId); ps.setString(15, user.getSalt()); - ps.setTimestamp(16, t); + ps.setTimestamp(16, getPasswordLastModifiedTimestamp(t)); ps.setString(17, user.getPassword()); } @@ -185,6 +187,12 @@ public void setValues(PreparedStatement ps) throws SQLException { return retrieve(id); } + protected Timestamp getPasswordLastModifiedTimestamp(Timestamp t) { + Calendar cal = new GregorianCalendar(); + cal.set(Calendar.MILLISECOND, 0); + return new Timestamp(cal.getTimeInMillis()); + } + @Override public ScimUser createUser(ScimUser user, final String password) throws InvalidPasswordException, InvalidScimResourceException { @@ -232,7 +240,6 @@ public void setValues(PreparedStatement ps) throws SQLException { ps.setString(pos++, origin); ps.setString(pos++, StringUtils.hasText(user.getExternalId())?user.getExternalId():null); ps.setString(pos++, user.getSalt()); - ps.setTimestamp(pos++, t); ps.setString(pos++, id); ps.setInt(pos++, user.getVersion()); } @@ -265,7 +272,7 @@ public void setValues(PreparedStatement ps) throws SQLException { Timestamp t = new Timestamp(new Date().getTime()); ps.setTimestamp(1, t); ps.setString(2, encNewPassword); - ps.setTimestamp(3, t); + ps.setTimestamp(3, getPasswordLastModifiedTimestamp(t)); ps.setString(4, id); } });
scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java+3 −3 modified@@ -164,7 +164,7 @@ public void canCreateUserInDefaultIdentityZone() { assertEquals("uaa", map.get("identity_zone_id")); assertNull(user.getPasswordLastModified()); assertNotNull(created.getPasswordLastModified()); - assertEquals(created.getMeta().getCreated(), created.getPasswordLastModified()); + assertEquals((created.getMeta().getCreated().getTime() / 1000l) * 1000l, created.getPasswordLastModified().getTime()); } @Test @@ -174,13 +174,13 @@ public void canModifyPassword() throws Exception { ScimUser created = db.createUser(user, "j7hyqpassX"); assertNull(user.getPasswordLastModified()); assertNotNull(created.getPasswordLastModified()); - assertEquals(created.getMeta().getCreated(), created.getPasswordLastModified()); + assertEquals((created.getMeta().getCreated().getTime() / 1000l) * 1000l, created.getPasswordLastModified().getTime()); Thread.sleep(10); db.changePassword(created.getId(), "j7hyqpassX", "j7hyqpassXXX"); user = db.retrieve(created.getId()); assertNotNull(user.getPasswordLastModified()); - assertEquals(user.getMeta().getLastModified(), user.getPasswordLastModified()); + assertEquals((user.getMeta().getLastModified().getTime() / 1000l) * 1000l, user.getPasswordLastModified().getTime()); } @Test
uaa/src/main/webapp/WEB-INF/spring-servlet.xml+9 −0 modified@@ -91,6 +91,7 @@ <entry value-ref="identityZoneSwitchingFilter" key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).after(@oauth2TokenParseFilter)}"/> <entry value-ref="xFrameOptionsFilter" key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).position(101)}"/> + <entry value-ref="sessionResetFilter" key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).position(102)}"/> </map> </property> </bean> @@ -107,6 +108,14 @@ <property name="additionalInternalHostnames" value="#{@config['zones']==null ? null : @config['zones']['internal']==null ? null : @config['zones']['internal']['hostnames']}"/> </bean> + <bean id="sessionResetFilter" class="org.cloudfoundry.identity.uaa.authentication.SessionResetFilter"> + <constructor-arg> + <bean class="org.springframework.security.web.DefaultRedirectStrategy"/> + </constructor-arg> + <constructor-arg value="/login"/> + <constructor-arg ref="userDatabase"/> + </bean> + <bean id="identityZoneSwitchingFilter" class="org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter"/> <bean id="uaaUrl" class="java.lang.String">
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/ChangePasswordIT.java+7 −8 modified@@ -12,15 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.integration.feature; -import java.security.SecureRandom; - -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - import com.dumbster.smtp.SimpleSmtpServer; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -35,6 +27,13 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.web.client.RestTemplate; +import java.security.SecureRandom; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = DefaultIntegrationTestConfig.class) public class ChangePasswordIT {
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/password/PasswordChangeEndpointMockMvcTests.java+59 −0 modified@@ -32,8 +32,10 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.TEXT_HTML; import static org.springframework.http.MediaType.TEXT_HTML_VALUE; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -144,6 +146,63 @@ public void changePassword_Resets_Session() throws Exception { } + @Test + public void changePassword_Resets_All_Sessions() throws Exception { + ScimUser user = createUser(); + + MockHttpSession session = new MockHttpSession(); + MockHttpSession afterLoginSessionA = (MockHttpSession) getMockMvc().perform(post("/login.do") + .session(session) + .accept(TEXT_HTML_VALUE) + .param("username", user.getUserName()) + .param("password", "secr3T")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/")) + .andReturn().getRequest().getSession(false); + + session = new MockHttpSession(); + MockHttpSession afterLoginSessionB = (MockHttpSession) getMockMvc().perform(post("/login.do") + .session(session) + .accept(TEXT_HTML_VALUE) + .param("username", user.getUserName()) + .param("password", "secr3T")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/")) + .andReturn().getRequest().getSession(false); + + + assertNotNull(afterLoginSessionA.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)); + assertNotNull(afterLoginSessionB.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)); + + getMockMvc().perform(get("/profile").session(afterLoginSessionB)) + .andExpect(status().isOk()); + + Thread.sleep(1000 - (System.currentTimeMillis() % 1000) + 1); + + MockHttpSession afterPasswordChange = (MockHttpSession) getMockMvc().perform(post("/change_password.do") + .session(afterLoginSessionA) + .with(csrf()) + .accept(TEXT_HTML_VALUE) + .param("current_password", "secr3T") + .param("new_password", "secr3T1") + .param("confirm_password", "secr3T1")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("profile")) + .andReturn().getRequest().getSession(false); + + assertTrue(afterLoginSessionA.isInvalid()); + assertNotNull(afterPasswordChange); + assertNotNull(afterPasswordChange.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)); + assertNotSame(afterLoginSessionA, afterPasswordChange); + getMockMvc().perform( + get("/profile") + .session(afterLoginSessionB) + .accept(TEXT_HTML)) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login")); + + } + private ScimUser createUser() throws Exception { String id = generator.generate(); ScimUser user = new ScimUser(id, id + "user@example.com", "name", "familyname");
9730cd6a3bbbUpon a password change we will expire all HTTP sessions for that user
13 files changed · +425 −21
common/src/main/java/org/cloudfoundry/identity/uaa/authentication/AuthzAuthenticationFilter.java+1 −1 modified@@ -82,7 +82,7 @@ public class AuthzAuthenticationFilter implements Filter { * @param methods the methods to set (defaults to POST) */ public void setMethods(Set<String> methods) { - this.methods = new HashSet<String>(); + this.methods = new HashSet<>(); for (String method : methods) { this.methods.add(method.toUpperCase()); }
common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java+5 −2 modified@@ -134,8 +134,11 @@ public Authentication authenticate(Authentication req) throws AuthenticationExce } } - Authentication success = new UaaAuthentication(new UaaPrincipal(user), - user.getAuthorities(), (UaaAuthenticationDetails) req.getDetails()); + Authentication success = new UaaAuthentication( + new UaaPrincipal(user), + user.getAuthorities(), + (UaaAuthenticationDetails) req.getDetails()); + publish(new UserAuthenticationSuccessEvent(user, success)); return success;
common/src/main/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilter.java+98 −0 added@@ -0,0 +1,98 @@ +/* + * ****************************************************************************** + * * Cloud Foundry + * * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * * + * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * * You may not use this product except in compliance with the License. + * * + * * This product includes a number of subcomponents with + * * separate copyright notices and license terms. Your use of these + * * subcomponents is subject to the terms and conditions of the + * * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.authentication; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.RedirectStrategy; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; + +public class SessionResetFilter extends OncePerRequestFilter { + + private static Log logger = LogFactory.getLog(SessionResetFilter.class); + + private final RedirectStrategy strategy; + private final String redirectUrl; + private final UaaUserDatabase userDatabase; + + public SessionResetFilter(RedirectStrategy strategy, String redirectUrl, UaaUserDatabase userDatabase) { + this.strategy = strategy; + this.redirectUrl = redirectUrl; + this.userDatabase = userDatabase; + } + + public String getRedirectUrl() { + return redirectUrl; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + SecurityContext context = SecurityContextHolder.getContext(); + if (context!=null && context.getAuthentication()!=null && context.getAuthentication() instanceof UaaAuthentication) { + UaaAuthentication authentication = (UaaAuthentication)context.getAuthentication(); + if (authentication.isAuthenticated() && + Origin.UAA.equals(authentication.getPrincipal().getOrigin()) && + null != request.getSession(false)) { + + boolean redirect = false; + String userId = authentication.getPrincipal().getId(); + try { + logger.debug("Evaluating user-id for session reset:"+userId); + UaaUser user = userDatabase.retrieveUserById(userId); + long lastAuthTime = authentication.getAuthenticatedTime(); + long passwordModTime = user.getPasswordLastModified().getTime() ; + //if the password has changed after authentication time + if (hasPasswordChangedAfterAuthentication(lastAuthTime, passwordModTime)) { + logger.debug(String.format("Resetting user session for user ID: %s Auth Time: %s Password Change Time: %s",userId, lastAuthTime, passwordModTime)); + redirect = true; + } + } catch (UsernameNotFoundException x) { + logger.info("Authenticated user ["+userId+"] was not found in DB."); + redirect = true; + } + if (redirect) { + handleRedirect(request, response); + return; + } + } + } + filterChain.doFilter(request,response); + } + + protected boolean hasPasswordChangedAfterAuthentication(long lastAuthTime, long passwordModTime) { + return passwordModTime > lastAuthTime; + } + + protected void handleRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException { + HttpSession session = request.getSession(false); + if (session!=null) { + session.invalidate(); + } + strategy.sendRedirect(request, response, getRedirectUrl()); + } +}
common/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java+11 −2 modified@@ -13,6 +13,7 @@ package org.cloudfoundry.identity.uaa.authentication; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -30,6 +31,7 @@ public class UaaAuthentication implements Authentication, Serializable { private UaaPrincipal principal; private UaaAuthenticationDetails details; private boolean authenticated; + private long authenticatedTime = -1l; /** * Creates a token with the supplied array of authorities. @@ -40,15 +42,16 @@ public class UaaAuthentication implements Authentication, Serializable { public UaaAuthentication(UaaPrincipal principal, List<? extends GrantedAuthority> authorities, UaaAuthenticationDetails details) { - this(principal, null, authorities, details, true); + this(principal, null, authorities, details, true, System.currentTimeMillis()); } @JsonCreator public UaaAuthentication(@JsonProperty("principal") UaaPrincipal principal, @JsonProperty("credentials") Object credentials, @JsonProperty("authorities") List<? extends GrantedAuthority> authorities, @JsonProperty("details") UaaAuthenticationDetails details, - @JsonProperty("authenticated") boolean authenticated) { + @JsonProperty("authenticated") boolean authenticated, + @JsonProperty(value = "authenticatedTime", defaultValue = "-1") long authenticatedTime) { if (principal == null || authorities == null) { throw new IllegalArgumentException("principal and authorities must not be null"); } @@ -57,9 +60,15 @@ public UaaAuthentication(@JsonProperty("principal") UaaPrincipal principal, this.details = details; this.credentials = credentials; this.authenticated = authenticated; + this.authenticatedTime = authenticatedTime == 0 ? -1 : authenticatedTime; + } + + public long getAuthenticatedTime() { + return authenticatedTime; } @Override + @JsonIgnore public String getName() { // Should we return the ID for the principal name? (No, because the // UaaUserDatabase retrieves users by name.)
common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java+1 −1 modified@@ -109,7 +109,7 @@ protected void stripScopesFromAuthentication(String identityZoneId, HttpServletR null, UaaStringUtils.getAuthoritiesFromStrings(clientScopes), new UaaAuthenticationDetails(servletRequest), - true); + true, userAuthentication.getAuthenticatedTime()); } oa = new OAuth2Authentication(request, userAuthentication); oa.setDetails(oaDetails);
common/src/test/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilterTests.java+172 −0 added@@ -0,0 +1,172 @@ +/* + * ****************************************************************************** + * * Cloud Foundry + * * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * * + * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * * You may not use this product except in compliance with the License. + * * + * * This product includes a number of subcomponents with + * * separate copyright notices and license terms. Your use of these + * * subcomponents is subject to the terms and conditions of the + * * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.authentication; + +import org.cloudfoundry.identity.uaa.user.InMemoryUaaUserDatabase; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.DefaultRedirectStrategy; +import org.springframework.util.ReflectionUtils; + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.lang.reflect.Field; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +public class SessionResetFilterTests { + + SessionResetFilter filter; + HttpServletResponse response; + HttpServletRequest request; + HttpSession session; + FilterChain chain; + UaaUserDatabase userDatabase; + UaaAuthentication authentication; + Date yesterday; + UaaUser user; + Map<String,UaaUser> users; + + @Before + public void setUpFilter() throws Exception { + + yesterday = new Date(System.currentTimeMillis()-(1000*60*60*24)); + + user = new UaaUser( + "user-id", + "username", + "password", + "email", + Collections.EMPTY_LIST, + "given name", + "family name", + yesterday, + yesterday, + Origin.UAA, + null, + true, + IdentityZone.getUaa().getId(), + "salt", + yesterday + ); + + UaaPrincipal principal = new UaaPrincipal(user); + + authentication = new UaaAuthentication(principal, null, Collections.EMPTY_LIST, null, true, System.currentTimeMillis()); + + users = new HashMap<>(); + users.put(user.getId(), user); + userDatabase = new InMemoryUaaUserDatabase(users); + + chain = mock(FilterChain.class); + request = mock(HttpServletRequest.class); + response = mock(HttpServletResponse.class); + session = mock(HttpSession.class); + when(request.getSession(anyBoolean())).thenReturn(session); + filter = new SessionResetFilter(new DefaultRedirectStrategy(),"/login", userDatabase); + } + + @After + public void clearThingsUp() { + SecurityContextHolder.clearContext(); + IdentityZoneHolder.clear(); + } + + + @Test + public void test_No_Authentication_Present() throws Exception { + filter.doFilterInternal(request, response, chain); + verify(chain, times(1)).doFilter(request, response); + } + + @Test + public void test_No_UAA_Authentication_Present() throws Exception { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken("test","test"); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + filter.doFilterInternal(request, response, chain); + verify(chain, times(1)).doFilter(request, response); + verifyZeroInteractions(request); + verifyZeroInteractions(response); + } + + @Test + public void test_User_Modified_After_Authentication() throws Exception { + setFieldValue("authenticatedTime", (yesterday.getTime() - (1000 * 60 * 60 * 24)), authentication); + SecurityContextHolder.getContext().setAuthentication(authentication); + filter.doFilterInternal(request, response, chain); + + //user is not forwarded, and error response is generated right away + Mockito.verifyZeroInteractions(chain); + //user redirect + verify(response, times(1)).sendRedirect(anyString()); + //session was requested + verify(request, times(2)).getSession(false); + //session was invalidated + verify(session, times(1)).invalidate(); + } + + protected long dropMilliSeconds(long time) { + return ( time / 1000l ) * 1000l; + } + + @Test + public void test_User_Not_Modified() throws Exception { + SecurityContextHolder.getContext().setAuthentication(authentication); + filter.doFilterInternal(request, response, chain); + verify(chain, times(1)).doFilter(request, response); + verifyZeroInteractions(response); + } + + @Test + public void test_User_Not_Originated_In_Uaa() throws Exception { + SecurityContextHolder.getContext().setAuthentication(authentication); + setFieldValue("origin", Origin.LDAP, authentication.getPrincipal()); + filter.doFilterInternal(request, response, chain); + verify(chain, times(1)).doFilter(request, response); + verifyZeroInteractions(request); + verifyZeroInteractions(response); + } + + protected void setFieldValue(String fieldname, Object value, Object object) { + Field f = ReflectionUtils.findField(object.getClass(), fieldname); + ReflectionUtils.makeAccessible(f); + ReflectionUtils.setField(f, object, value); + } + +} \ No newline at end of file
common/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializationTests.java+35 −0 added@@ -0,0 +1,35 @@ +/* + * ****************************************************************************** + * * Cloud Foundry + * * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * * + * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * * You may not use this product except in compliance with the License. + * * + * * This product includes a number of subcomponents with + * * separate copyright notices and license terms. Your use of these + * * subcomponents is subject to the terms and conditions of the + * * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.authentication; + +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.junit.Assert; +import org.junit.Test; + +public class UaaAuthenticationSerializationTests { + + @Test + public void testDeserializationWithoutAuthenticatedTime() throws Exception { + String data ="{\"principal\":{\"id\":\"user-id\",\"name\":\"username\",\"email\":\"email\",\"origin\":\"uaa\",\"externalId\":null,\"zoneId\":\"uaa\"},\"credentials\":null,\"authorities\":[],\"details\":null,\"authenticated\":true,\"authenticatedTime\":1438649464353,\"name\":\"username\"}"; + UaaAuthentication authentication1 = JsonUtils.readValue(data, UaaAuthentication.class); + Assert.assertEquals(1438649464353l, authentication1.getAuthenticatedTime()); + + String dataWithoutTime ="{\"principal\":{\"id\":\"user-id\",\"name\":\"username\",\"email\":\"email\",\"origin\":\"uaa\",\"externalId\":null,\"zoneId\":\"uaa\"},\"credentials\":null,\"authorities\":[],\"details\":null,\"authenticated\":true,\"name\":\"username\"}"; + UaaAuthentication authentication2 = JsonUtils.readValue(dataWithoutTime, UaaAuthentication.class); + Assert.assertEquals(-1, authentication2.getAuthenticatedTime()); + + } +} \ No newline at end of file
login/src/main/java/org/cloudfoundry/identity/uaa/login/ChangePasswordController.java+13 −0 modified@@ -12,6 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.login; +import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; +import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.BadCredentialsException; @@ -26,6 +28,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.util.Arrays; +import java.util.LinkedList; + import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; @@ -67,6 +72,14 @@ public String changePassword( changePasswordService.changePassword(username, currentPassword, newPassword); request.getSession().invalidate(); request.getSession(true); + if (authentication instanceof UaaAuthentication) { + UaaAuthentication uaaAuthentication = (UaaAuthentication)authentication; + authentication = new UaaAuthentication( + uaaAuthentication.getPrincipal(), + new LinkedList<>(uaaAuthentication.getAuthorities()), + new UaaAuthenticationDetails(request) + ); + } securityContext.setAuthentication(authentication); return "redirect:profile"; } catch (BadCredentialsException e) {
scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java+11 −4 modified@@ -46,7 +46,9 @@ import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; +import java.util.Calendar; import java.util.Date; +import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -66,7 +68,7 @@ public class JdbcScimUserProvisioning extends AbstractQueryable<ScimUser> implem public static final String CREATE_USER_SQL = "insert into users (" + USER_FIELDS + ",password) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; - public static final String UPDATE_USER_SQL = "update users set version=?, lastModified=?, userName=?, email=?, givenName=?, familyName=?, active=?, phoneNumber=?, verified=?, origin=?, external_id=?, salt=?, passwd_lastmodified=? where id=? and version=?"; + public static final String UPDATE_USER_SQL = "update users set version=?, lastModified=?, userName=?, email=?, givenName=?, familyName=?, active=?, phoneNumber=?, verified=?, origin=?, external_id=?, salt=? where id=? and version=?"; public static final String DEACTIVATE_USER_SQL = "update users set active=? where id=?"; @@ -169,7 +171,7 @@ public void setValues(PreparedStatement ps) throws SQLException { ps.setString(13, StringUtils.hasText(user.getExternalId())?user.getExternalId():null); ps.setString(14, identityZoneId); ps.setString(15, user.getSalt()); - ps.setTimestamp(16, t); + ps.setTimestamp(16, getPasswordLastModifiedTimestamp(t)); ps.setString(17, user.getPassword()); } @@ -185,6 +187,12 @@ public void setValues(PreparedStatement ps) throws SQLException { return retrieve(id); } + protected Timestamp getPasswordLastModifiedTimestamp(Timestamp t) { + Calendar cal = new GregorianCalendar(); + cal.set(Calendar.MILLISECOND, 0); + return new Timestamp(cal.getTimeInMillis()); + } + @Override public ScimUser createUser(ScimUser user, final String password) throws InvalidPasswordException, InvalidScimResourceException { @@ -232,7 +240,6 @@ public void setValues(PreparedStatement ps) throws SQLException { ps.setString(pos++, origin); ps.setString(pos++, StringUtils.hasText(user.getExternalId())?user.getExternalId():null); ps.setString(pos++, user.getSalt()); - ps.setTimestamp(pos++, t); ps.setString(pos++, id); ps.setInt(pos++, user.getVersion()); } @@ -265,7 +272,7 @@ public void setValues(PreparedStatement ps) throws SQLException { Timestamp t = new Timestamp(new Date().getTime()); ps.setTimestamp(1, t); ps.setString(2, encNewPassword); - ps.setTimestamp(3, t); + ps.setTimestamp(3, getPasswordLastModifiedTimestamp(t)); ps.setString(4, id); } });
scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java+3 −3 modified@@ -164,7 +164,7 @@ public void canCreateUserInDefaultIdentityZone() { assertEquals("uaa", map.get("identity_zone_id")); assertNull(user.getPasswordLastModified()); assertNotNull(created.getPasswordLastModified()); - assertEquals(created.getMeta().getCreated(), created.getPasswordLastModified()); + assertEquals((created.getMeta().getCreated().getTime() / 1000l) * 1000l, created.getPasswordLastModified().getTime()); } @Test @@ -174,13 +174,13 @@ public void canModifyPassword() throws Exception { ScimUser created = db.createUser(user, "j7hyqpassX"); assertNull(user.getPasswordLastModified()); assertNotNull(created.getPasswordLastModified()); - assertEquals(created.getMeta().getCreated(), created.getPasswordLastModified()); + assertEquals((created.getMeta().getCreated().getTime() / 1000l) * 1000l, created.getPasswordLastModified().getTime()); Thread.sleep(10); db.changePassword(created.getId(), "j7hyqpassX", "j7hyqpassXXX"); user = db.retrieve(created.getId()); assertNotNull(user.getPasswordLastModified()); - assertEquals(user.getMeta().getLastModified(), user.getPasswordLastModified()); + assertEquals((user.getMeta().getLastModified().getTime() / 1000l) * 1000l, user.getPasswordLastModified().getTime()); } @Test
uaa/src/main/webapp/WEB-INF/spring-servlet.xml+9 −0 modified@@ -91,6 +91,7 @@ <entry value-ref="identityZoneSwitchingFilter" key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).after(@oauth2TokenParseFilter)}"/> <entry value-ref="xFrameOptionsFilter" key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).position(101)}"/> + <entry value-ref="sessionResetFilter" key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).position(102)}"/> </map> </property> </bean> @@ -107,6 +108,14 @@ <property name="additionalInternalHostnames" value="#{@config['zones']==null ? null : @config['zones']['internal']==null ? null : @config['zones']['internal']['hostnames']}"/> </bean> + <bean id="sessionResetFilter" class="org.cloudfoundry.identity.uaa.authentication.SessionResetFilter"> + <constructor-arg> + <bean class="org.springframework.security.web.DefaultRedirectStrategy"/> + </constructor-arg> + <constructor-arg value="/login"/> + <constructor-arg ref="userDatabase"/> + </bean> + <bean id="identityZoneSwitchingFilter" class="org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter"/> <bean id="uaaUrl" class="java.lang.String">
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/ChangePasswordIT.java+7 −8 modified@@ -12,15 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.integration.feature; -import java.security.SecureRandom; - -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - import com.dumbster.smtp.SimpleSmtpServer; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -35,6 +27,13 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.web.client.RestTemplate; +import java.security.SecureRandom; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = DefaultIntegrationTestConfig.class) public class ChangePasswordIT {
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/password/PasswordChangeEndpointMockMvcTests.java+59 −0 modified@@ -32,8 +32,10 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.TEXT_HTML; import static org.springframework.http.MediaType.TEXT_HTML_VALUE; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -144,6 +146,63 @@ public void changePassword_Resets_Session() throws Exception { } + @Test + public void changePassword_Resets_All_Sessions() throws Exception { + ScimUser user = createUser(); + + MockHttpSession session = new MockHttpSession(); + MockHttpSession afterLoginSessionA = (MockHttpSession) getMockMvc().perform(post("/login.do") + .session(session) + .accept(TEXT_HTML_VALUE) + .param("username", user.getUserName()) + .param("password", "secr3T")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/")) + .andReturn().getRequest().getSession(false); + + session = new MockHttpSession(); + MockHttpSession afterLoginSessionB = (MockHttpSession) getMockMvc().perform(post("/login.do") + .session(session) + .accept(TEXT_HTML_VALUE) + .param("username", user.getUserName()) + .param("password", "secr3T")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/")) + .andReturn().getRequest().getSession(false); + + + assertNotNull(afterLoginSessionA.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)); + assertNotNull(afterLoginSessionB.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)); + + getMockMvc().perform(get("/profile").session(afterLoginSessionB)) + .andExpect(status().isOk()); + + Thread.sleep(1000 - (System.currentTimeMillis() % 1000) + 1); + + MockHttpSession afterPasswordChange = (MockHttpSession) getMockMvc().perform(post("/change_password.do") + .session(afterLoginSessionA) + .with(csrf()) + .accept(TEXT_HTML_VALUE) + .param("current_password", "secr3T") + .param("new_password", "secr3T1") + .param("confirm_password", "secr3T1")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("profile")) + .andReturn().getRequest().getSession(false); + + assertTrue(afterLoginSessionA.isInvalid()); + assertNotNull(afterPasswordChange); + assertNotNull(afterPasswordChange.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)); + assertNotSame(afterLoginSessionA, afterPasswordChange); + getMockMvc().perform( + get("/profile") + .session(afterLoginSessionB) + .accept(TEXT_HTML)) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login")); + + } + private ScimUser createUser() throws Exception { String id = generator.generate(); ScimUser user = new ScimUser(id, id + "user@example.com", "name", "familyname");
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-h533-q6jc-qx28ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2015-5171ghsaADVISORY
- pivotal.io/security/cve-2015-5170-5173nvdVendor AdvisoryWEB
- github.com/cloudfoundry/uaa/commit/7c70a85f4c7b5a9b1603bc57cab14a7ffef2168bghsaWEB
- github.com/cloudfoundry/uaa/commit/9730cd6a3bbb481ee4e400b51952b537589c469dghsaWEB
News mentions
0No linked articles in our index yet.