package net.unicon.idp.externalauth;
import net.shibboleth.idp.authn.ExternalAuthentication;
import net.shibboleth.idp.authn.ExternalAuthenticationException;
import org.apache.commons.lang.StringUtils;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.jasig.cas.client.validation.Cas30ServiceTicketValidator;
import org.jasig.cas.client.validation.TicketValidationException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.BDDMockito;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.api.support.membermodification.MemberModifier;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.web.context.WebApplicationContext;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
@RunWith(PowerMockRunner.class)
@PrepareForTest({ExternalAuthentication.class, Cas20ServiceTicketValidator.class})
public class ShibcasAuthServletTest {
private String CONVERSATION = "conversation=e1s1";
private String CONVERSATION_TICKET = "conversation=e1s1&ticket=ST-1234-123456789-a";
private String CONVERSATION_TICKET_GATEWAY_ATTEMPTED = "conversation=e1s1&ticket=ST-1234-123456789-a&gatewayAttempted=true";
private String E1S1 = "E1S1";
private String JDOE = "jdoe";
private String TICKET = "ST-1234-123456789-a";
private String URL_WITH_CONVERSATION = "https://shibserver.example.edu/idp/Authn/ExtCas?conversation=e1s1";
private String URL_WITH_CONVERSATION_GATEWAY_ATTEMPTED = "https://shibserver.example.edu/idp/Authn/ExtCas?conversation=e1s1&gatewayAttempted=true";
@Test
public void testDoGetStandard() throws Exception {
//Mock some objects.
HttpServletRequest request = createDoGetHttpServletRequest(CONVERSATION_TICKET, TICKET, null);
HttpServletResponse response = createMockHttpServletResponse();
Assertion assertion = createMockAssertion();
Cas20ServiceTicketValidator ticketValidator = PowerMockito.mock(Cas20ServiceTicketValidator.class);
PowerMockito.when(ticketValidator.validate(TICKET, URL_WITH_CONVERSATION)).thenReturn(assertion);
PowerMockito.mockStatic(ExternalAuthentication.class);
BDDMockito.given(ExternalAuthentication.startExternalAuthentication(request)).willReturn(E1S1);
//Prep our object
ShibcasAuthServlet shibcasAuthServlet = createShibcasAuthServlet();
//Override the internal Cas20TicketValidator because we don't want it to call a real server
MemberModifier.field(ShibcasAuthServlet.class, "ticketValidator").set(shibcasAuthServlet, ticketValidator);
//Standard request/response
BDDMockito.given(request.getAttribute(ExternalAuthentication.FORCE_AUTHN_PARAM)).willReturn("false");
BDDMockito.given(request.getAttribute(ExternalAuthentication.PASSIVE_AUTHN_PARAM)).willReturn("false");
shibcasAuthServlet.doGet(request, response);
//Verify
verify(request).setAttribute(ExternalAuthentication.PRINCIPAL_NAME_KEY, JDOE);
}
@Test
public void testDoGetBadTicket() throws Exception {
//Mock some objects.
HttpServletRequest request = createDoGetHttpServletRequest(CONVERSATION_TICKET, TICKET, "false");
HttpServletResponse response = createMockHttpServletResponse();
Cas20ServiceTicketValidator ticketValidator = PowerMockito.mock(Cas20ServiceTicketValidator.class);
PowerMockito.when(ticketValidator.validate(TICKET, URL_WITH_CONVERSATION)).thenThrow(new TicketValidationException("Invalid Ticket"));
PowerMockito.mockStatic(ExternalAuthentication.class);
BDDMockito.given(ExternalAuthentication.startExternalAuthentication(request)).willThrow(new ExternalAuthenticationException());
//Prep our object
ShibcasAuthServlet shibcasAuthServlet = createShibcasAuthServlet();
//Override the internal Cas20TicketValidator because we don't want it to call a real server
MemberModifier.field(ShibcasAuthServlet.class, "ticketValidator").set(shibcasAuthServlet, ticketValidator);
//Standard request/response - bad ticket
BDDMockito.given(request.getAttribute(ExternalAuthentication.FORCE_AUTHN_PARAM)).willReturn("false");
BDDMockito.given(request.getAttribute(ExternalAuthentication.PASSIVE_AUTHN_PARAM)).willReturn("false");
shibcasAuthServlet.doGet(request, response);
//Verify
verify(request).getRequestDispatcher("/no-conversation-state.jsp");
verify(response).setStatus(404);
}
@Test
public void testDoGetPassiveAuthenticated() throws Exception {
//Mock some objects.
HttpServletRequest request = createDoGetHttpServletRequest(CONVERSATION_TICKET + "&gatewayAttempted=true", TICKET, "true");
HttpServletResponse response = createMockHttpServletResponse();
Assertion assertion = createMockAssertion();
Cas20ServiceTicketValidator ticketValidator = PowerMockito.mock(Cas20ServiceTicketValidator.class);
PowerMockito.when(ticketValidator.validate(TICKET, URL_WITH_CONVERSATION_GATEWAY_ATTEMPTED)).thenReturn(assertion);
PowerMockito.mockStatic(ExternalAuthentication.class);
BDDMockito.given(ExternalAuthentication.startExternalAuthentication(request)).willReturn(E1S1);
//Prep our object
ShibcasAuthServlet shibcasAuthServlet = createShibcasAuthServlet();
//Override the internal Cas20TicketValidator because we don't want it to call a real server
MemberModifier.field(ShibcasAuthServlet.class, "ticketValidator").set(shibcasAuthServlet, ticketValidator);
//Passive request/response with authenticated user
BDDMockito.given(request.getAttribute(ExternalAuthentication.FORCE_AUTHN_PARAM)).willReturn("false");
BDDMockito.given(request.getAttribute(ExternalAuthentication.PASSIVE_AUTHN_PARAM)).willReturn("true");
shibcasAuthServlet.doGet(request, response);
//Verify
verify(request).setAttribute(ExternalAuthentication.PRINCIPAL_NAME_KEY, JDOE);
}
@Test
public void testDoGetPassiveNotAuthenticated() throws Exception {
//Mock some objects.
HttpServletRequest request = createDoGetHttpServletRequest("conversation=e1s1&gatewayAttempted=true", null, "true");
HttpServletResponse response = createMockHttpServletResponse();
Cas20ServiceTicketValidator ticketValidator = PowerMockito.mock(Cas20ServiceTicketValidator.class);
PowerMockito.mockStatic(ExternalAuthentication.class);
BDDMockito.given(ExternalAuthentication.startExternalAuthentication(request)).willReturn(E1S1);
//Prep our object
ShibcasAuthServlet shibcasAuthServlet = createShibcasAuthServlet();
//Override the internal Cas20TicketValidator because we don't want it to call a real server
MemberModifier.field(ShibcasAuthServlet.class, "ticketValidator").set(shibcasAuthServlet, ticketValidator);
//Passive request/response with no user
BDDMockito.given(request.getAttribute(ExternalAuthentication.FORCE_AUTHN_PARAM)).willReturn("false");
BDDMockito.given(request.getAttribute(ExternalAuthentication.PASSIVE_AUTHN_PARAM)).willReturn("true");
shibcasAuthServlet.doGet(request, response);
//Verify
verify(request, never()).setAttribute(eq(ExternalAuthentication.PRINCIPAL_NAME_KEY), any());
verify(request).setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, "NoPassive");
verify(ticketValidator, never()).validate(anyString(), anyString());
}
@Test
public void testDoGetForced() throws Exception {
//Mock some objects.
HttpServletRequest request = createDoGetHttpServletRequest(CONVERSATION_TICKET, TICKET, null);
HttpServletResponse response = createMockHttpServletResponse();
Assertion assertion = createMockAssertion();
Cas20ServiceTicketValidator ticketValidator = PowerMockito.mock(Cas20ServiceTicketValidator.class);
PowerMockito.when(ticketValidator.validate(TICKET, URL_WITH_CONVERSATION)).thenReturn(assertion);
PowerMockito.mockStatic(ExternalAuthentication.class);
BDDMockito.given(ExternalAuthentication.startExternalAuthentication(request)).willReturn(E1S1);
//Prep our object
ShibcasAuthServlet shibcasAuthServlet = createShibcasAuthServlet();
//Override the internal Cas20TicketValidator because we don't want it to call a real server
MemberModifier.field(ShibcasAuthServlet.class, "ticketValidator").set(shibcasAuthServlet, ticketValidator);
//Forced request/response
BDDMockito.given(request.getAttribute(ExternalAuthentication.FORCE_AUTHN_PARAM)).willReturn("true");
BDDMockito.given(request.getAttribute(ExternalAuthentication.PASSIVE_AUTHN_PARAM)).willReturn("false");
shibcasAuthServlet.doGet(request, response);
//Verify
verify(request).setAttribute(ExternalAuthentication.PRINCIPAL_NAME_KEY, JDOE);
}
@Test
public void testDoGetPassiveAndForced() throws Exception {
//Mock some objects.
HttpServletRequest request = createDoGetHttpServletRequest(CONVERSATION_TICKET_GATEWAY_ATTEMPTED, TICKET, "true");
HttpServletResponse response = createMockHttpServletResponse();
Assertion assertion = createMockAssertion();
Cas20ServiceTicketValidator ticketValidator = PowerMockito.mock(Cas30ServiceTicketValidator.class);
PowerMockito.when(ticketValidator.validate(TICKET, URL_WITH_CONVERSATION_GATEWAY_ATTEMPTED)).thenReturn(assertion);
PowerMockito.mockStatic(ExternalAuthentication.class);
BDDMockito.given(ExternalAuthentication.startExternalAuthentication(request)).willReturn(E1S1);
//Prep our object
ShibcasAuthServlet shibcasAuthServlet = createShibcasAuthServlet();
//Override the internal Cas30TicketValidator because we don't want it to call a real server
MemberModifier.field(ShibcasAuthServlet.class, "ticketValidator").set(shibcasAuthServlet, ticketValidator);
//Passive and forced request/response
BDDMockito.given(request.getAttribute(ExternalAuthentication.FORCE_AUTHN_PARAM)).willReturn("true");
BDDMockito.given(request.getAttribute(ExternalAuthentication.PASSIVE_AUTHN_PARAM)).willReturn("true");
shibcasAuthServlet.doGet(request, response);
//Verify
verify(request).setAttribute(ExternalAuthentication.PRINCIPAL_NAME_KEY, JDOE);
}
@Test
public void testStartLoginRequestStandard() throws Exception {
HttpServletRequest request = createMockHttpServletRequest();
BDDMockito.given(request.getQueryString()).willReturn(CONVERSATION);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
BDDMockito.given(response.encodeURL(URL_WITH_CONVERSATION)).willReturn(URL_WITH_CONVERSATION);
ShibcasAuthServlet shibcasAuthServlet = new ShibcasAuthServlet();
shibcasAuthServlet.init(createMockServletConfig());
shibcasAuthServlet.startLoginRequest(request, response, false, false);
verify(response).sendRedirect("https://cassserver.example.edu/cas/login?service=https%3A%2F%2Fshibserver.example.edu%2Fidp%2FAuthn%2FExtCas%3Fconversation%3De1s1&entityId=http%3A%2F%2Ftest.edu%2Fsp");
}
@Test
public void testStartLoginRequestEmbeddedEntityId() throws Exception {
HttpServletRequest request = createMockHttpServletRequest();
BDDMockito.given(request.getQueryString()).willReturn(CONVERSATION);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
BDDMockito.given(response.encodeURL(URL_WITH_CONVERSATION)).willReturn(URL_WITH_CONVERSATION);
ShibcasAuthServlet shibcasAuthServlet = new ShibcasAuthServlet();
shibcasAuthServlet.init(createMockServletConfig("embed"));
shibcasAuthServlet.startLoginRequest(request, response, false, false);
verify(response).sendRedirect("https://cassserver.example.edu/cas/login?service=https%3A%2F%2Fshibserver.example.edu%2Fidp%2FAuthn%2FExtCas%3Fconversation%3De1s1%26entityId%3Dhttp%3A%2F%2Ftest.edu%2Fsp");
}
@Test
public void testStartLoginRequestAppendedEntityId() throws Exception {
HttpServletRequest request = createMockHttpServletRequest();
BDDMockito.given(request.getQueryString()).willReturn(CONVERSATION);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
BDDMockito.given(response.encodeURL(URL_WITH_CONVERSATION)).willReturn(URL_WITH_CONVERSATION);
ShibcasAuthServlet shibcasAuthServlet = new ShibcasAuthServlet();
shibcasAuthServlet.init(createMockServletConfig("append"));
shibcasAuthServlet.startLoginRequest(request, response, false, false);
verify(response).sendRedirect("https://cassserver.example.edu/cas/login?service=https%3A%2F%2Fshibserver.example.edu%2Fidp%2FAuthn%2FExtCas%3Fconversation%3De1s1&entityId=http%3A%2F%2Ftest.edu%2Fsp");
}
@Test
public void testStartLoginRequestPassive() throws Exception {
HttpServletRequest request = createMockHttpServletRequest();
BDDMockito.given(request.getQueryString()).willReturn(CONVERSATION);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
BDDMockito.given(response.encodeURL(URL_WITH_CONVERSATION)).willReturn(URL_WITH_CONVERSATION);
ShibcasAuthServlet shibcasAuthServlet = new ShibcasAuthServlet();
shibcasAuthServlet.init(createMockServletConfig());
//Passive
shibcasAuthServlet.startLoginRequest(request, response, false, true);
verify(response).sendRedirect("https://cassserver.example.edu/cas/login?service=https%3A%2F%2Fshibserver.example.edu%2Fidp%2FAuthn%2FExtCas%3Fconversation%3De1s1%26gatewayAttempted%3Dtrue&gateway=true&entityId=http%3A%2F%2Ftest.edu%2Fsp");
}
@Test
public void testStartLoginRequestForced() throws Exception {
HttpServletRequest request = createMockHttpServletRequest();
BDDMockito.given(request.getQueryString()).willReturn(CONVERSATION);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
BDDMockito.given(response.encodeURL(URL_WITH_CONVERSATION)).willReturn(URL_WITH_CONVERSATION);
ShibcasAuthServlet shibcasAuthServlet = new ShibcasAuthServlet();
shibcasAuthServlet.init(createMockServletConfig());
//Forced
shibcasAuthServlet.startLoginRequest(request, response, true, false);
verify(response).sendRedirect("https://cassserver.example.edu/cas/login?service=https%3A%2F%2Fshibserver.example.edu%2Fidp%2FAuthn%2FExtCas%3Fconversation%3De1s1&renew=true&entityId=http%3A%2F%2Ftest.edu%2Fsp");
}
@Test
public void testStartLoginRequestPassiveAndForced() throws Exception {
HttpServletRequest request = createMockHttpServletRequest();
BDDMockito.given(request.getQueryString()).willReturn(CONVERSATION);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
BDDMockito.given(response.encodeURL(URL_WITH_CONVERSATION)).willReturn(URL_WITH_CONVERSATION);
ShibcasAuthServlet shibcasAuthServlet = new ShibcasAuthServlet();
shibcasAuthServlet.init(createMockServletConfig());
//Passive and Forced
shibcasAuthServlet.startLoginRequest(request, response, true, true);
verify(response).sendRedirect("https://cassserver.example.edu/cas/login?service=https%3A%2F%2Fshibserver.example.edu%2Fidp%2FAuthn%2FExtCas%3Fconversation%3De1s1%26gatewayAttempted%3Dtrue&renew=true&gateway=true&entityId=http%3A%2F%2Ftest.edu%2Fsp");
}
private HttpServletRequest createDoGetHttpServletRequest(String queryString, String ticket, String gatewayAttempted) {
HttpServletRequest request = createMockHttpServletRequest();
BDDMockito.given(request.getQueryString()).willReturn(queryString);
BDDMockito.given(request.getParameter("ticket")).willReturn(ticket);
BDDMockito.given(request.getParameter("gatewayAttempted")).willReturn(gatewayAttempted);
return request;
}
private Assertion createMockAssertion() {
Assertion assertion = Mockito.mock(Assertion.class);
AttributePrincipal attributePrincipal = Mockito.mock(AttributePrincipal.class);
BDDMockito.given(attributePrincipal.getName()).willReturn(JDOE);
BDDMockito.given(assertion.getPrincipal()).willReturn(attributePrincipal);
return assertion;
}
private ServletConfig createMockServletConfig() {
return createMockServletConfig("");
}
private ServletConfig createMockServletConfig(String entityIdLocation) {
ServletConfig config = Mockito.mock(ServletConfig.class);
ServletContext servletContext = Mockito.mock(ServletContext.class);
ApplicationContext applicationContext = Mockito.mock(ApplicationContext.class);
Environment environment = Mockito.mock(Environment.class);
BDDMockito.given(config.getServletContext()).willReturn(servletContext);
BDDMockito.given(servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)).willReturn(applicationContext);
BDDMockito.given(applicationContext.getEnvironment()).willReturn(environment);
BDDMockito.given(environment.getRequiredProperty("shibcas.casServerUrlPrefix")).willReturn("https://cassserver.example.edu/cas");
BDDMockito.given(environment.getRequiredProperty("shibcas.casServerLoginUrl")).willReturn("https://cassserver.example.edu/cas/login");
BDDMockito.given(environment.getRequiredProperty("shibcas.serverName")).willReturn("https://shibserver.example.edu");
BDDMockito.given(environment.getRequiredProperty("shibcas.casToShibTranslators")).willReturn(null);
BDDMockito.given(environment.getProperty("shibcas.ticketValidatorName", "cas30")).willReturn("cas30");
if (StringUtils.isNotEmpty(entityIdLocation)) {
BDDMockito.given(environment.getProperty("shibcas.entityIdLocation", "append")).willReturn(entityIdLocation);
} else {
BDDMockito.given(environment.getProperty("shibcas.entityIdLocation", "append")).willReturn("append");
}
return config;
}
private HttpServletRequest createMockHttpServletRequest() {
try {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
BDDMockito.given(request.getScheme()).willReturn("http");
BDDMockito.given(request.getMethod()).willReturn("GET");
BDDMockito.given(request.isSecure()).willReturn(true);
BDDMockito.given(request.getHeader("Host")).willReturn("shibserver.example.edu");
//BDDMockito.given(request.getHeader("X-Forwarded-Host")).willReturn();
BDDMockito.given(request.getServerPort()).willReturn(443);
BDDMockito.given(request.getRequestURI()).willReturn("/idp/Authn/ExtCas");
BDDMockito.given(request.getRequestURL()).willReturn(new StringBuffer("/idp/Authn/ExtCas"));
BDDMockito.given(request.getAttribute(ExternalAuthentication.RELYING_PARTY_PARAM)).willReturn("http://test.edu/sp");
return request;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private HttpServletResponse createMockHttpServletResponse() {
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
BDDMockito.given(response.encodeURL(URL_WITH_CONVERSATION)).willReturn(URL_WITH_CONVERSATION);
BDDMockito.given(response.encodeURL(URL_WITH_CONVERSATION_GATEWAY_ATTEMPTED)).willReturn(URL_WITH_CONVERSATION_GATEWAY_ATTEMPTED);
return response;
}
private ShibcasAuthServlet createShibcasAuthServlet() throws ServletException {
ShibcasAuthServlet shibcasAuthServlet;
shibcasAuthServlet = new ShibcasAuthServlet();
shibcasAuthServlet.init(createMockServletConfig());
return shibcasAuthServlet;
}
}