package edu.kit.scc.dei.ecplean;
import java.io.IOException;
import java.net.URI;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathException;
import org.apache.http.HttpResponse;
import org.apache.http.ParseException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class ECPAuthenticator extends ECPAuthenticatorBase {
public ECPAuthenticator(CloseableHttpClient client, String username, String password,
URI idpEcpEndpoint, URI spUrl) {
super(client);
authInfo = new ECPAuthenticationInfo(username, password, idpEcpEndpoint, spUrl);
}
public ECPAuthenticator(String username, String password,
URI idpEcpEndpoint, URI spUrl) {
super();
authInfo = new ECPAuthenticationInfo(username, password, idpEcpEndpoint, spUrl);
}
public HttpResponse authenticate() throws ECPAuthenticationException {
logger.info("Starting authentication");
logger.info("Contacting SP " + authInfo.getSpUrl());
authInfo.setAuthState(ECPAuthState.INITIAL_PAOS_SP);
setChanged();
notifyObservers(authInfo);
logger.info("Sending initial SP Request");
HttpGet httpGet = new HttpGet(authInfo.getSpUrl().toString());
httpGet.setHeader("Accept", "text/html, application/vnd.paos+xml");
httpGet.setHeader("PAOS", "ver=\"urn:liberty:paos:2003-08\";\"urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp\"");
HttpClientContext ctx = HttpClientContext.create();
ctx.setCookieStore(new BasicCookieStore());
HttpResponse httpResponse;
String responseBody;
try {
httpResponse = client.execute(httpGet, ctx);
responseBody = EntityUtils.toString(httpResponse.getEntity());
httpGet.reset();
} catch (IOException | ParseException e) {
logger.debug("Initial SP Request failed");
throw new ECPAuthenticationException(e);
}
Document initResponse;
try {
initResponse = buildDocumentFromString(responseBody);
} catch (IOException | SAXException | ParserConfigurationException e) {
logger.debug("Parsing SP Response failed");
throw new ECPAuthenticationException(e);
}
String relayState;
try {
relayState = (String) queryDocument(initResponse, "//ecp:RelayState", XPathConstants.STRING);
} catch (XPathException e) {
logger.debug("Could not find relay state in PAOS answer from SP");
throw new ECPAuthenticationException(e);
}
logger.info("Got relayState: " + relayState);
String responseConsumerUrl;
try {
responseConsumerUrl = (String) queryDocument(initResponse, "/S:Envelope/S:Header/paos:Request/@responseConsumerURL", XPathConstants.STRING);
} catch (XPathException e) {
logger.debug("Could not find response consumer url in PAOS answer from SP");
throw new ECPAuthenticationException(e);
}
logger.info("Got responseConsumerUrl: " + responseConsumerUrl);
Node firstChild = initResponse.getDocumentElement().getFirstChild();
initResponse.getDocumentElement().removeChild(firstChild);
Document idpResponse;
try {
idpResponse = authenticateIdP(initResponse);
} catch (ECPAuthenticationException e) {
logger.debug("Original SP response:\n" + responseBody);
try {
logger.debug("Sent to IdP:\n" + documentToString(initResponse));
} catch (TransformerException e1) {
logger.debug("Barf", e1);
}
throw e;
}
String statusCode = getStatusCode(idpResponse);
if (statusCode == null)
throw new ECPAuthenticationException("IdP returned no status code!!!x");
if (!statusCode.endsWith(":Success") && !statusCode.endsWith(":success"))
throw new ECPAuthenticationException("IdP Returned StatusCode " + statusCode);
String assertionConsumerUrl;
try {
assertionConsumerUrl = (String) queryDocument(idpResponse, "/S:Envelope/S:Header/ecp:Response/@AssertionConsumerServiceURL", XPathConstants.STRING);
} catch (Exception e) {
logger.debug("Could not find assertion consumer url in answer from IdP");
throw new ECPAuthenticationException(e);
}
logger.info("Got assertionConsumerUrl: " + assertionConsumerUrl);
if (! assertionConsumerUrl.equals(responseConsumerUrl)) {
throw new ECPAuthenticationException("Assertion- and ResponseConsumerURL don't match");
}
idpResponse.getDocumentElement().getFirstChild().getFirstChild().setTextContent(relayState);
logger.info("Sending Assertion to SP");
HttpPost httpPost = new HttpPost(assertionConsumerUrl);
httpPost.setHeader("Content-Type", "application/vnd.paos+xml");
//httpPost.setHeader("PAOS", "ver=\"urn:liberty:paos:2003-08\";\"urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp\"");
try {
httpPost.setEntity(new StringEntity(documentToString(idpResponse)));
httpResponse = client.execute(httpPost, ctx);
logger.info("Asserting resulted in " + httpResponse.getStatusLine());
httpPost.reset();
} catch (TransformerException | IOException e) {
logger.debug("Could not post assertion back to SP");
throw new ECPAuthenticationException(e);
}
logger.info("Requesting original URL");
httpGet = new HttpGet(authInfo.getSpUrl().toString());
try {
httpResponse = client.execute(httpGet, ctx);
} catch (IOException e) {
logger.debug("Could not request original URL");
throw new ECPAuthenticationException(e);
}
return httpResponse;
}
private String getStatusCode(Document idpResponse) {
NodeList nl;
String result = null;
try {
nl = (NodeList) queryDocument(idpResponse, "//*", XPathConstants.NODESET);
} catch (XPathException e) {
return null;
}
if (nl == null)
return null;
for (int i = 0; i < nl.getLength(); ++i) {
Node ns = nl.item(i);
if (!ns.getLocalName().endsWith("StatusCode"))
continue;
if (!ns.hasAttributes())
continue;
Node val = ns.getAttributes().getNamedItem("Value");
if (val == null)
continue;
if (result == null || result.endsWith(":Responder")) {
result = val.getNodeValue();
}
}
return result;
}
}