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.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.cookie.BasicCookieStore; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; 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 CloseableHttpResponse 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()); CloseableHttpResponse 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.getReasonPhrase()); } catch (TransformerException | IOException e) { logger.debug("Could not post assertion back to SP"); throw new ECPAuthenticationException(e); } try { httpPost.reset(); } catch (Exception 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; } }