/*
* Copyright 2015 herd contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.finra.herd.app.security;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.finra.herd.app.AbstractAppTest;
import org.finra.herd.model.dto.ApplicationUser;
import org.finra.herd.model.dto.ConfigurationValue;
import org.finra.herd.model.dto.SecurityUserWrapper;
/**
* Tests cases where the {@link org.finra.herd.app.security.TrustedUserAuthenticationFilter} and {@link org.finra.herd.app.security.HttpHeaderAuthenticationFilter}
* are used in sequence.
*/
public class SecurityFilterChainTest extends AbstractAppTest
{
@Before
public void before()
{
SecurityContextHolder.clearContext();
}
/**
* When the filters are executed with security disabled, and the filters are run again with security enabled, the trusted user should no longer be in the
* context and instead the user should be created based on the headers given in the request.
*
* @throws Exception
*/
@Test
public void testFilterAuthenticatedUserOverridesTrustedUser() throws Exception
{
String expectedUserId = "testUser";
HashMap<String, Object> requestHeaders = new HashMap<>();
// Execute filters with security disabled
Authentication authentication1 = executeAuthenticationFilters(false, requestHeaders);
assertAuthenticatedUserId(TrustedApplicationUserBuilder.TRUSTED_USER_ID, TrustedApplicationUserBuilder.TRUSTED_USER_FIRST_NAME, null, authentication1);
// Execute filters with security disabled
requestHeaders.put("userId", expectedUserId);
Authentication authentication2 = executeAuthenticationFilters(true, requestHeaders);
assertAuthenticatedUserId(expectedUserId, null, null, authentication2);
}
/**
* When the filters are executed twice with security enabled, and the user ID header changes between the requests, the session's user should be recreated
* with the new headers.
*
* @throws Exception
*/
@Test
public void testFilterUserIsReauthenticatedWhenUserIdChanges() throws Exception
{
String expectedUserId1 = "testUser1";
String expectedUserId2 = "testUser2";
HashMap<String, Object> requestHeaders = new HashMap<>();
requestHeaders.put("userId", expectedUserId1);
Authentication authentication1 = executeAuthenticationFilters(true, requestHeaders);
assertAuthenticatedUserId(expectedUserId1, null, null, authentication1);
requestHeaders.put("userId", expectedUserId2);
Authentication authentication2 = executeAuthenticationFilters(true, requestHeaders);
assertAuthenticatedUserId(expectedUserId2, null, null, authentication2);
}
/**
* When the filters are executed twice with security enabled, and the session init time header changes between the requests, the session should be recreated
* with the new headers.
*
* @throws Exception
*/
@Test
public void testFilterUserIsReauthenticatedWhenSessionInitTimeChanges() throws Exception
{
String expectedUserId = "testUser1";
Date expectedSessionInitTime1 = new Date(1000l);
Date expectedSessionInitTime2 = new Date(2000l);
SimpleDateFormat dateFormat = new SimpleDateFormat(HttpHeaderApplicationUserBuilder.CALENDAR_PATTERN_PWD);
HashMap<String, Object> requestHeaders = new HashMap<>();
requestHeaders.put("userId", expectedUserId);
requestHeaders.put("sessionInitTime", dateFormat.format(expectedSessionInitTime1));
Authentication authentication1 = executeAuthenticationFilters(true, requestHeaders);
assertAuthenticatedUserId(expectedUserId, null, expectedSessionInitTime1, authentication1);
requestHeaders.put("sessionInitTime", dateFormat.format(expectedSessionInitTime2));
Authentication authentication2 = executeAuthenticationFilters(true, requestHeaders);
assertAuthenticatedUserId(expectedUserId, null, expectedSessionInitTime2, authentication2);
}
/**
* When the filters are executed twice with security enabled, and only the first name header changes between the requests, the session should be
* recreated. This behavior should also apply for any headers other than userId and sessionInitTime.
*
* @throws Exception
*/
@Test
public void testFilterUserIsReauthenticatedWhenFirstNameChanges() throws Exception
{
String expectedUserId = "testUser1";
String firstName1 = "firstName1";
HashMap<String, Object> requestHeaders = new HashMap<>();
requestHeaders.put("userId", expectedUserId);
requestHeaders.put("firstName", firstName1);
Authentication authentication1 = executeAuthenticationFilters(true, requestHeaders);
assertAuthenticatedUserId(expectedUserId, firstName1, null, authentication1);
requestHeaders.put("firstName", "differentFirstName");
Authentication authentication2 = executeAuthenticationFilters(true, requestHeaders);
assertAuthenticatedUserId(expectedUserId, "differentFirstName", null, authentication2);
}
/**
* Makes the following assertions about the given {@link Authentication}: <ol> <li>is not null</li> <li>principal is not null</li> <li>principal type is
* {@link org.finra.herd.model.dto.SecurityUserWrapper}</li> <li>principal applicationUser is not null</li> <li>principal applicationUser userId equals
* given userId</li> <li>principal applicationUser firstName equals given firstName</li> <li>principal applicationUser uesrId equals given userId</li>
* <li>principal applicationUser sessionInitTime equals given sessionInitTime</li> </ol>
*
* @param expectedUserId
* @param expectedFirstName
* @param expectedSessionInitTime
* @param authentication {@link Authentication} to assert
*/
private void assertAuthenticatedUserId(String expectedUserId, String expectedFirstName, Date expectedSessionInitTime, Authentication authentication)
{
Assert.assertNotNull("authentication is null", authentication);
Assert.assertNotNull("authentication principal is null", authentication.getPrincipal());
Assert.assertEquals("authentication principal type", SecurityUserWrapper.class, authentication.getPrincipal().getClass());
SecurityUserWrapper securityUserWrapper = (SecurityUserWrapper) authentication.getPrincipal();
ApplicationUser applicationUser = securityUserWrapper.getApplicationUser();
Assert.assertNotNull("securityUserWrapper applicationUser is null", applicationUser);
Assert.assertEquals("securityUserWrapper applicationUser userId", expectedUserId, applicationUser.getUserId());
Assert.assertEquals("securityUserWrapper applicationUser firstName", expectedFirstName, applicationUser.getFirstName());
Assert.assertEquals("securityUserWrapper applicationUser sessionInitTime", expectedSessionInitTime, applicationUser.getSessionInitTime());
}
/**
* Executes {@link org.finra.herd.app.security.TrustedUserAuthenticationFilter} and {@link org.finra.herd.app.security.HttpHeaderAuthenticationFilter} in
* sequence with security enabled or disabled, with the given request headers. Returns the final {@link Authentication} as the result of the filter
* executions.
*
* @param isSecurityEnabled true to enable security, false otherwise
* @param requestHeaders request headers
*
* @return {@link Authentication}, may be null if filters did not put any authentication in the context.
* @throws Exception
*/
private Authentication executeAuthenticationFilters(Boolean isSecurityEnabled, Map<String, Object> requestHeaders) throws Exception
{
// Build a mock http request with the headers
MockHttpServletRequest request = new MockHttpServletRequest();
for (Map.Entry<String, Object> header : requestHeaders.entrySet())
{
request.addHeader(header.getKey(), header.getValue());
}
MockHttpServletResponse response = new MockHttpServletResponse();
// Override environment with test security settings. Enable or disable security based on parameter.
Map<String, Object> defaultSecurityEnvironmentVariables = getDefaultSecurityEnvironmentVariables();
defaultSecurityEnvironmentVariables.put(ConfigurationValue.SECURITY_ENABLED_SPEL_EXPRESSION.getKey(), isSecurityEnabled.toString());
modifyPropertySourceInEnvironment(defaultSecurityEnvironmentVariables);
try
{
// Execute the filters
trustedUserAuthenticationFilter.doFilter(request, response, new MockFilterChain());
httpHeaderAuthenticationFilter.doFilter(request, response, new MockFilterChain());
// Return the authentication in the context
return SecurityContextHolder.getContext().getAuthentication();
}
finally
{
// Restore the environment to default in case of errors
restorePropertySourceInEnvironment();
}
}
}