summaryrefslogblamecommitdiffstats
path: root/src/main/java/edu/kit/scc/dei/ecplean/ECPAuthenticatorBase.java
blob: 454886f463bd1caa56302bf802f521c386c58cba (plain) (tree)
1
2
3
4
5




                                 
















                                                              













                                                                      

                                            
                             
                                   




                                                                
                                                                                           
         
                                                  
                                              




                                                                 

                                          

                                                                                    
 












                                                                                                       
                            
          
 
                                                                                          
                                                            
                                                                                
 












                                                                                                                               

                                                                                            

                                                                              

                                                                  




                                                                                         
                      



                                                                                                                                        
                  

                                            
                      
                                                                      

                                          
                                                                              

                                                                 
          
 


                                                                                                    
                                                           





                                                                     
                                                                                         















                                                                                      


                                                                                       
                                                                              

                                                                          

                      


                                                                                                           
                                                                 

                  




                                                                                         





                                                                                                                              





















                                                                                         
                                                     


                               



                                                   
 
package edu.kit.scc.dei.ecplean;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Observable;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathException;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.apache.hc.client5.http.auth.AuthenticationException;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.auth.BasicScheme;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpStatus;
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.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

public abstract class ECPAuthenticatorBase extends Observable {

	protected static Logger logger = LogManager.getLogger(ECPAuthenticatorBase.class);
	
	protected ECPAuthenticationInfo authInfo;
	protected CloseableHttpClient client;
	protected DocumentBuilderFactory documentBuilderFactory;
	protected XPathFactory xpathFactory;
	protected NamespaceResolver namespaceResolver;
	protected TransformerFactory transformerFactory;

	protected boolean retryWithoutAt;

	public ECPAuthenticatorBase(CloseableHttpClient client) {
		this.client = client == null ? HttpClients.createSystem() : client;

		documentBuilderFactory = DocumentBuilderFactory.newInstance();
		documentBuilderFactory.setNamespaceAware(true);
		
		xpathFactory = XPathFactory.newInstance();
		namespaceResolver = new NamespaceResolver();
		namespaceResolver.addNamespace("ecp", "urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp");
		namespaceResolver.addNamespace("S", "http://schemas.xmlsoap.org/soap/envelope/");
		namespaceResolver.addNamespace("paos", "urn:liberty:paos:2003-08");
		
		transformerFactory = TransformerFactory.newInstance();
	}

	public ECPAuthenticatorBase() {
		this(null);
	}

	private CloseableHttpResponse exec(Document idpRequest, String user, String pass)
			throws ECPAuthenticationException {
		final HttpHost httpHost = HttpHost.create(authInfo.getSpUrl());

		// setup basic authentication
		final UsernamePasswordCredentials userCredentials = new UsernamePasswordCredentials(user, pass.toCharArray());
      final BasicScheme basicAuth = new BasicScheme();
      basicAuth.initPreemptive(userCredentials);

      // create local HTTP context for basic authentication
      final HttpClientContext httpContext = HttpClientContext.create();
      httpContext.resetAuthExchange(httpHost, basicAuth);

      // create POST request to IdP
      final HttpPost httpPost = new HttpPost(authInfo.getIdpEcpEndpoint().toString());

      // fill content of POST request
		try {
			httpPost.setEntity(new StringEntity(documentToString(idpRequest)));
		} catch (TransformerException e1) {
			logger.warn("Error setting XML payload of IdP POST");
			throw new ECPAuthenticationException(e1);
		}

		// set content type of POST request
		httpPost.setHeader(HttpHeaders.CONTENT_TYPE, "text/xml; charset=utf-8");

		// set basic authentication header for POST request
		try {
			httpPost.setHeader(HttpHeaders.AUTHORIZATION, basicAuth.generateAuthResponse(httpHost, httpPost, httpContext));
		} catch (AuthenticationException e) {
			logger.warn("Error setting Authentication header for IdP POST");
			throw new ECPAuthenticationException(e);
		}

		// send POST request to IdP
		try {
			return client.execute(httpPost, httpContext);
		} catch (Exception e) {
			httpPost.reset();
			logger.error("Could not submit PAOS request to IdP");
			throw new ECPAuthenticationException(e);
		}
	}

	protected Document authenticateIdP(Document idpRequest) throws ECPAuthenticationException {
		logger.info("Sending initial IdP Request");

		CloseableHttpResponse httpResponse = null;
		String user = authInfo.getUsername();
		String pass = authInfo.getPassword();
		int at = user.lastIndexOf('@');
		boolean failed = false;
		try {
			httpResponse = exec(idpRequest, user, pass);
			failed = (httpResponse.getCode() == HttpStatus.SC_UNAUTHORIZED);
		} catch (ECPAuthenticationException e) {
			logger.debug("Could not submit PAOS request to IdP");
			if (at == -1)
				throw new ECPAuthenticationException(e);
			failed = true;
		}
		if (at != -1 && failed && retryWithoutAt) {
			// Retrying without the @ in the username is desired
			user = user.substring(0, at);
			try {
				httpResponse = exec(idpRequest, user, pass);
			} catch (ECPAuthenticationException e) {
				logger.debug("Could not submit PAOS request to IdP");
				throw new ECPAuthenticationException(e);
			}
		}
		String responseBody;
		try {
			responseBody = EntityUtils.toString(httpResponse.getEntity());
		} catch (RuntimeException | IOException | ParseException e) {
			logger.debug("Could not read response from IdP");
			throw new ECPAuthenticationException(e);
		}
		try {
			return buildDocumentFromString(responseBody);
		} catch (IOException | SAXException | ParserConfigurationException | RuntimeException e) {
			logger.debug("Could not parse XML response from IdP:\n" + responseBody);
			throw new ECPAuthenticationException(e);
		}

	}
	
	protected Document buildDocumentFromString(String input)
			throws IOException, ParserConfigurationException, SAXException {
		DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
		builder.setEntityResolver(new EntityResolver() {
			@Override
			public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
				return new InputSource(new StringReader(""));
			}
		});
		return builder.parse(new InputSource(new StringReader(input)));
	}

	protected Object queryDocument(Document xmlDocument, String expression,
			QName returnType) throws XPathException {
		XPath xpath = xpathFactory.newXPath();
		xpath.setNamespaceContext(namespaceResolver);
		XPathExpression xPathExpression = xpath.compile(expression);
		return xPathExpression.evaluate(xmlDocument, returnType);
	}

	protected String documentToString(Document xmlDocument)
			throws TransformerConfigurationException, TransformerException {
		Transformer transformer = transformerFactory.newTransformer();

		StreamResult result = new StreamResult(new StringWriter());
		DOMSource source = new DOMSource(xmlDocument);
		transformer.transform(source, result);

		return result.getWriter().toString();
	}

	public CloseableHttpClient getHttpClient() {
		return client;
	}

	public void setRetryWithoutAt(boolean b) {
		this.retryWithoutAt = b;
	}

}