Skip to content

Commit

Permalink
Upon a password change we will expire all HTTP sessions for that user
Browse files Browse the repository at this point in the history
  • Loading branch information
fhanik committed Aug 5, 2015
1 parent cad9bdd commit 9730cd6
Show file tree
Hide file tree
Showing 13 changed files with 425 additions and 21 deletions.
Expand Up @@ -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());
}
Expand Down
@@ -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());
}
}
Expand Up @@ -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;
Expand All @@ -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.
Expand All @@ -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");
}
Expand All @@ -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.)
Expand Down
Expand Up @@ -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;
Expand Down
Expand Up @@ -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);
Expand Down
@@ -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);
}

}
@@ -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());

}
}

0 comments on commit 9730cd6

Please sign in to comment.