VYPR
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.

PackageAffected versionsPatched versions
org.cloudfoundry.identity:cloudfoundry-identity-serverMaven
< 2.5.22.5.2

Affected products

3

Patches

2
7c70a85f4c7b

Merge branch 'feature/reset_session_during_password_change' into develop

https://github.com/cloudfoundry/uaaFilip HanikAug 5, 2015via ghsa
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");
    
9730cd6a3bbb

Upon a password change we will expire all HTTP sessions for that user

https://github.com/cloudfoundry/uaaFilip HanikAug 4, 2015via ghsa
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

News mentions

0

No linked articles in our index yet.