-
Notifications
You must be signed in to change notification settings - Fork 827
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Upon a password change we will expire all HTTP sessions for that user
https://www.pivotaltracker.com/story/show/100043648 [#100043648]
- Loading branch information
Showing
13 changed files
with
425 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
common/src/main/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
172 changes: 172 additions & 0 deletions
172
...n/src/test/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilterTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
|
||
} |
35 changes: 35 additions & 0 deletions
35
...ava/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializationTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
|
||
} | ||
} |
Oops, something went wrong.