package org.cloudfoundry.identity.uaa.login;
import org.apache.commons.codec.binary.Base64;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails;
import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest;
import org.cloudfoundry.identity.uaa.oauth.RemoteUserAuthentication;
import org.cloudfoundry.identity.uaa.provider.saml.LoginSamlAuthenticationToken;
import org.cloudfoundry.identity.uaa.security.web.UaaRequestMatcher;
import org.cloudfoundry.identity.uaa.user.UaaUserDatabase;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.providers.ExpiringUsernameAuthenticationToken;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;
import static org.springframework.http.MediaType.APPLICATION_JSON;
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.result.MockMvcResultMatchers.status;
public class PasscodeMockMvcTests extends InjectedMockContextTest {
private CaptureSecurityContextFilter captureSecurityContextFilter;
private static String USERNAME = "marissa";
private UaaPrincipal marissa;
@After
public void clearSecContext() {
SecurityContextHolder.clearContext();
}
@Before
public void setUp() throws Exception {
FilterChainProxy springSecurityFilterChain = (FilterChainProxy) getWebApplicationContext().getBean("org.springframework.security.filterChainProxy");
if (captureSecurityContextFilter==null) {
captureSecurityContextFilter = new CaptureSecurityContextFilter();
List<SecurityFilterChain> chains = springSecurityFilterChain.getFilterChains();
for (SecurityFilterChain chain : chains) {
if (chain instanceof DefaultSecurityFilterChain) {
DefaultSecurityFilterChain dfc = (DefaultSecurityFilterChain) chain;
if (dfc.getRequestMatcher() instanceof UaaRequestMatcher) {
UaaRequestMatcher matcher = (UaaRequestMatcher) dfc.getRequestMatcher();
if (matcher.toString().contains("passcodeTokenMatcher")) {
dfc.getFilters().add(captureSecurityContextFilter);
break;
}
}
}
}
UaaUserDatabase db = getWebApplicationContext().getBean(UaaUserDatabase.class);
marissa = new UaaPrincipal(db.retrieveUserByName(USERNAME, OriginKeys.UAA));
}
}
@Test
public void testLoginUsingPasscodeWithSamlToken() throws Exception {
ExpiringUsernameAuthenticationToken et = new ExpiringUsernameAuthenticationToken(USERNAME, null);
LoginSamlAuthenticationToken auth = new LoginSamlAuthenticationToken(marissa, et);
final MockSecurityContext mockSecurityContext = new MockSecurityContext(auth);
SecurityContextHolder.setContext(mockSecurityContext);
MockHttpSession session = new MockHttpSession();
session.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
mockSecurityContext
);
MockHttpServletRequestBuilder get = get("/passcode")
.accept(APPLICATION_JSON)
.session(session);
String passcode = JsonUtils.readValue(
getMockMvc().perform(get)
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
String.class);
mockSecurityContext.setAuthentication(null);
session = new MockHttpSession();
session.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
mockSecurityContext
);
String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64(("cf:").getBytes()));
MockHttpServletRequestBuilder post = post("/oauth/token")
.accept(APPLICATION_JSON)
.contentType(APPLICATION_FORM_URLENCODED)
.header("Authorization", basicDigestHeaderValue)
.param("grant_type", "password")
.param("passcode", passcode)
.param("response_type", "token");
Map accessToken =
JsonUtils.readValue(
getMockMvc().perform(post)
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
Map.class);
assertEquals("bearer", accessToken.get("token_type"));
assertNotNull(accessToken.get("access_token"));
assertNotNull(accessToken.get("refresh_token"));
String[] scopes = ((String) accessToken.get("scope")).split(" ");
assertThat(Arrays.asList(scopes), containsInAnyOrder("uaa.user", "scim.userids", "password.write", "cloud_controller.write", "openid", "cloud_controller.read"));
Authentication authentication = captureSecurityContextFilter.getAuthentication();
assertNotNull(authentication);
assertTrue(authentication instanceof OAuth2Authentication);
assertTrue(((OAuth2Authentication)authentication).getUserAuthentication() instanceof UsernamePasswordAuthenticationToken);
assertTrue(authentication.getPrincipal() instanceof UaaPrincipal);
assertEquals(marissa.getOrigin(), ((UaaPrincipal)authentication.getPrincipal()).getOrigin());
}
@Test
public void testLoginUsingPasscodeWithUaaToken() throws Exception {
UaaAuthenticationDetails details = new UaaAuthenticationDetails(new MockHttpServletRequest());
UaaAuthentication uaaAuthentication = new UaaAuthentication(marissa, new ArrayList<GrantedAuthority>(),details);
final MockSecurityContext mockSecurityContext = new MockSecurityContext(uaaAuthentication);
SecurityContextHolder.setContext(mockSecurityContext);
MockHttpSession session = new MockHttpSession();
session.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
mockSecurityContext
);
MockHttpServletRequestBuilder get = get("/passcode")
.accept(APPLICATION_JSON)
.session(session);
String passcode = JsonUtils.readValue(
getMockMvc().perform(get)
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
String.class);
mockSecurityContext.setAuthentication(null);
session = new MockHttpSession();
session.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
mockSecurityContext
);
String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64(("cf:").getBytes()));
MockHttpServletRequestBuilder post = post("/oauth/token")
.accept(APPLICATION_JSON)
.contentType(APPLICATION_FORM_URLENCODED)
.header("Authorization", basicDigestHeaderValue)
.param("grant_type", "password")
.param("passcode", passcode)
.param("response_type", "token");
Map accessToken =
JsonUtils.readValue(
getMockMvc().perform(post)
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
Map.class);
assertEquals("bearer", accessToken.get("token_type"));
assertNotNull(accessToken.get("access_token"));
assertNotNull(accessToken.get("refresh_token"));
String[] scopes = ((String) accessToken.get("scope")).split(" ");
assertThat(Arrays.asList(scopes), containsInAnyOrder("uaa.user", "scim.userids", "password.write", "cloud_controller.write", "openid", "cloud_controller.read"));
Authentication authentication = captureSecurityContextFilter.getAuthentication();
assertNotNull(authentication);
assertTrue(authentication instanceof OAuth2Authentication);
assertTrue(((OAuth2Authentication)authentication).getUserAuthentication() instanceof UsernamePasswordAuthenticationToken);
assertTrue(authentication.getPrincipal() instanceof UaaPrincipal);
assertEquals(marissa.getOrigin(), ((UaaPrincipal)authentication.getPrincipal()).getOrigin());
}
@Test
public void testLoginUsingPasscodeWithUnknownToken() throws Exception {
RemoteUserAuthentication userAuthentication = new RemoteUserAuthentication(
marissa.getId(),
marissa.getName(),
marissa.getEmail(),
new ArrayList<GrantedAuthority>()
);
final MockSecurityContext mockSecurityContext = new MockSecurityContext(userAuthentication);
SecurityContextHolder.setContext(mockSecurityContext);
MockHttpSession session = new MockHttpSession();
session.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
mockSecurityContext
);
MockHttpServletRequestBuilder get = get("/passcode")
.accept(APPLICATION_JSON)
.session(session);
getMockMvc().perform(get)
.andExpect(status().isForbidden());
}
@Test
public void testLoginUsingOldPasscode() throws Exception {
UaaAuthenticationDetails details = new UaaAuthenticationDetails(new MockHttpServletRequest());
UaaAuthentication uaaAuthentication = new UaaAuthentication(marissa, new ArrayList<GrantedAuthority>(),details);
final MockSecurityContext mockSecurityContext = new MockSecurityContext(uaaAuthentication);
SecurityContextHolder.setContext(mockSecurityContext);
MockHttpSession session = new MockHttpSession();
session.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
mockSecurityContext
);
MockHttpServletRequestBuilder get = get("/passcode")
.accept(APPLICATION_JSON)
.session(session);
String passcode = JsonUtils.readValue(
getMockMvc().perform(get)
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
String.class);
// Get another code, which should expire the old.
getMockMvc().perform(get("/passcode")
.accept(APPLICATION_JSON)
.session(session));
mockSecurityContext.setAuthentication(null);
session = new MockHttpSession();
session.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
mockSecurityContext
);
String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64(("cf:").getBytes()));
MockHttpServletRequestBuilder post = post("/oauth/token")
.accept(APPLICATION_JSON)
.contentType(APPLICATION_FORM_URLENCODED)
.header("Authorization", basicDigestHeaderValue)
.param("grant_type", "password")
.param("passcode", passcode)
.param("response_type", "token");
getMockMvc().perform(post)
.andExpect(status().isUnauthorized());
}
@Test
public void loginUsingInvalidPasscode() throws Exception {
String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64(("cf:").getBytes()));
MockHttpServletRequestBuilder post = post("/oauth/token")
.accept(APPLICATION_JSON)
.contentType(APPLICATION_FORM_URLENCODED)
.header("Authorization", basicDigestHeaderValue)
.param("grant_type", "password")
.param("response_type", "token")
.param("passcode", "no_such_passcode");
String content = getMockMvc().perform(post)
.andExpect(status().isUnauthorized())
.andReturn().getResponse().getContentAsString();
assertThat(content, Matchers.containsString("Invalid passcode"));
}
@Test
public void loginUsingNoPasscode() throws Exception {
String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64(("cf:").getBytes()));
MockHttpServletRequestBuilder post = post("/oauth/token")
.accept(APPLICATION_JSON)
.contentType(APPLICATION_FORM_URLENCODED)
.header("Authorization", basicDigestHeaderValue)
.param("grant_type", "password")
.param("response_type", "token")
.param("passcode", "");
String content = getMockMvc().perform(post)
.andExpect(status().isUnauthorized())
.andReturn().getResponse().getContentAsString();
assertThat(content, Matchers.containsString("Passcode information is missing."));
}
public static class MockSecurityContext implements SecurityContext {
private static final long serialVersionUID = -1386535243513362694L;
private Authentication authentication;
public MockSecurityContext(Authentication authentication) {
this.authentication = authentication;
}
@Override
public Authentication getAuthentication() {
return this.authentication;
}
@Override
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
}
public static class CaptureSecurityContextFilter extends GenericFilterBean {
private Authentication authentication;
public Authentication getAuthentication() {
return authentication;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
authentication = SecurityContextHolder.getContext().getAuthentication();
chain.doFilter(request, response);
}
}
}