/******************************************************************************* * Copyright 2014 Federal Chancellery Austria * MOA-ID has been developed in a cooperation between BRZ, the Federal * Chancellery Austria - ICT staff unit, and Graz University of Technology. * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * http://www.osor.eu/eupl/ * * Unless required by applicable law or agreed to in writing, software * distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and * limitations under the Licence. * * This product combines work with different licenses. See the "NOTICE" text * file for details on the various modules and licenses. * The "NOTICE" text file is part of the distribution. Any derivative works * that you distribute must include a readable copy of the "NOTICE" text file. ******************************************************************************/ /* * Copyright 2003 Federal Chancellery Austria * MOA-ID has been developed in a cooperation between BRZ, the Federal * Chancellery Austria - ICT staff unit, and Graz University of Technology. * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * http://www.osor.eu/eupl/ * * Unless required by applicable law or agreed to in writing, software * distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and * limitations under the Licence. * * This product combines work with different licenses. See the "NOTICE" text * file for details on the various modules and licenses. * The "NOTICE" text file is part of the distribution. Any derivative works * that you distribute must include a readable copy of the "NOTICE" text file. */ package at.gv.egovernment.moa.id.protocols.saml1; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringWriter; import java.util.Calendar; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import com.google.common.net.MediaType; import at.gv.egiz.eaaf.core.impl.gui.velocity.VelocityProvider; import at.gv.egiz.eaaf.core.impl.idp.controller.AbstractController; import at.gv.egiz.eaaf.core.impl.utils.DOMUtils; import at.gv.egiz.eaaf.core.impl.utils.HTTPUtils; import at.gv.egiz.eaaf.core.impl.utils.Random; import at.gv.egiz.eaaf.core.impl.utils.XPathUtils; import at.gv.egovernment.moa.id.auth.builder.SAMLResponseBuilder; import at.gv.egovernment.moa.id.auth.exception.AuthenticationException; import at.gv.egovernment.moa.id.commons.api.exceptions.MOAIDException; import at.gv.egovernment.moa.id.commons.utils.MOAIDMessageProvider; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.Constants; import at.gv.egovernment.moa.util.DateTimeUtils; /** * Web service for picking up authentication data created in the MOA-ID Auth component. * * This getAssertion WebService implementations a hacked solution to integrate SAML1 into * the new Spring based MOA-ID implementation. * * @deprecated * It is too bad about the time to implement a better solution, * since SAML1 is deprecated MOA-ID >= 2.0.0 * * @author tlenz */ @Controller public class GetAuthenticationDataService extends AbstractController implements Constants { @Autowired private SAML1AuthenticationServer saml1AuthServer; private static final String PARAM_WSDL="wsdl"; private static final String PARAM_XSD="xsd"; private static final String TEMPLATE_PLAIN_INFO="plain_info.vm"; private static final String TEMPLATE_WSDL="wsdl/MOA-ID-1.x.vm"; private static final String TEMPLATE_XSD="wsdl/MOA-SPSS-1.2.vm"; private static final String TEMPLATE_SOAP_ERROR="soap_error.vm"; private static final String TEMPLATE_SOAP_SUCCESS="soap_success.vm"; private static final String SERVICE_ENDPOINT = "/services/GetAuthenticationData"; private static final String CONTEXT_ENDPOINT = "endpoint"; private static final String CONTEXT_ERROR = "error"; private static final String CONTEXT_SOAP_RESPONSEID = "responseID"; private static final String CONTEXT_SOAP_REQUESTEID = "requestID"; private static final String CONTEXT_SOAP_ISSUEINSTANT = "issueInstant"; private static final String CONTEXT_SOAP_ERRORMESSAGE = "errorMsg"; private static final String CONTEXT_SOAP_STATUSCODE = "statusCode"; private static final String CONTEXT_SOAP_ASSERTION = "assertion"; @RequestMapping(value = {"/services/GetAuthenticationData", "/services"}, method = {RequestMethod.POST}) public void getAuthenticationData(HttpServletRequest req, HttpServletResponse resp) throws IOException { InputStream is = null; VelocityContext context = new VelocityContext(); try { is = req.getInputStream(); Element soapReq = DOMUtils.parseXmlNonValidating(is); //process request Element soapResp = processRequest(soapReq); String respString = DOMUtils.serializeNode(soapResp, true); resp.setContentType(MediaType.XML_UTF_8.toString()); context.put(CONTEXT_SOAP_ASSERTION, respString); evaluateTemplate(context, resp, TEMPLATE_SOAP_SUCCESS); } catch (ParserConfigurationException | SAXException | IOException | TransformerException e) { Logger.error("SAML1 GetAuthenticationData receive a non-valid request.", e); resp.setContentType(MediaType.XML_UTF_8.toString()); context.put(CONTEXT_SOAP_ISSUEINSTANT, DateTimeUtils.buildDateTimeUTC(Calendar.getInstance())); context.put(CONTEXT_SOAP_RESPONSEID, Random.nextRandom()); context.put(CONTEXT_SOAP_STATUSCODE, "samlp:Requester"); context.put(CONTEXT_SOAP_ERRORMESSAGE, e.getMessage()); evaluateTemplate(context, resp, TEMPLATE_SOAP_ERROR); } catch (SAML1AssertionResponseBuildException e) { Logger.error("SAML1 GetAuthenticationData response build failed..", e); resp.setContentType(MediaType.XML_UTF_8.toString()); context.put(CONTEXT_SOAP_ISSUEINSTANT, e.getIssueInstant()); context.put(CONTEXT_SOAP_REQUESTEID, e.getRequestID()); context.put(CONTEXT_SOAP_RESPONSEID, e.getResponseID()); context.put(CONTEXT_SOAP_STATUSCODE, "samlp:Responder"); context.put(CONTEXT_SOAP_ERRORMESSAGE, e.getMessage()); evaluateTemplate(context, resp, TEMPLATE_SOAP_ERROR); } finally { try { if (is != null) is.close(); } catch (Exception e) { } } } @RequestMapping(value = "/services/GetAuthenticationData", method = {RequestMethod.GET}) public void getAuthenticationDataWSDL(HttpServletRequest req, HttpServletResponse resp) throws Exception { String wsdl_param = req.getParameter(PARAM_WSDL); String xsd_param = req.getParameter(PARAM_XSD); String fullServiceEndPoint = HTTPUtils.extractAuthURLFromRequest(req) + SERVICE_ENDPOINT; VelocityContext context = new VelocityContext(); context.put(CONTEXT_ENDPOINT, fullServiceEndPoint); if (wsdl_param != null) { //print wsdl resp.setContentType(MediaType.XML_UTF_8.toString()); evaluateTemplate(context, resp, TEMPLATE_WSDL); } else if (xsd_param != null){ //print xsd resp.setContentType(MediaType.XML_UTF_8.toString()); evaluateTemplate(context, resp, TEMPLATE_XSD); } else { //print plain info resp.setContentType(MediaType.XML_UTF_8.toString()); evaluateTemplate(context, resp, TEMPLATE_PLAIN_INFO); } } private Element processRequest(Element soapReq) throws ParserConfigurationException, IOException, SAXException, TransformerException, SAML1AssertionResponseBuildException { String requestID = ""; String statusCode = ""; String subStatusCode = null; String statusMessageCode = null; String statusMessage = null; String samlAssertion = ""; Element responses; //select soap-body element NodeList saml1ReqList = soapReq.getElementsByTagNameNS(soapReq.getNamespaceURI(), "Body");; if (saml1ReqList.getLength() != 1) { saml1ReqList = soapReq.getElementsByTagNameNS(soapReq.getNamespaceURI(), "body");; if (saml1ReqList.getLength() != 1) { throw new SAXException("No unique 'soap-env:Body' element."); } } //get the first child from body which is of type Element (SAML1 Request element) Element saml1Req = null; Node reqObj = saml1ReqList.item(0).getFirstChild(); while (reqObj != null) { if (reqObj instanceof Element) { saml1Req = (Element) reqObj; break; } else { reqObj = reqObj.getNextSibling(); } } if (saml1Req == null) { throw new SAXException("Every child of 'soap-env:Body' element has a wrong type."); } //validate the SAML1 request element, which we selected above DOMUtils.validateElement(saml1Req, ALL_SCHEMA_LOCATIONS, null); //parse inforamtion from SAML1 request try { NodeList samlArtifactList = XPathUtils.selectNodeList(saml1Req, "samlp:AssertionArtifact"); if (samlArtifactList.getLength() == 0) { // no SAML artifact given in request statusCode = "samlp:Requester"; statusMessageCode = "1202"; } else if (samlArtifactList.getLength() > 1) { // too many SAML artifacts given in request statusCode = "samlp:Requester"; subStatusCode = "samlp:TooManyResponses"; statusMessageCode = "1203"; } else { Element samlArtifactElem = (Element)samlArtifactList.item(0); requestID = saml1Req.getAttribute("RequestID"); String samlArtifact = DOMUtils.getText(samlArtifactElem); try { samlAssertion = saml1AuthServer.getSaml1AuthenticationData(samlArtifact); // success statusCode = "samlp:Success"; statusMessageCode = "1200"; } catch (ClassCastException ex) { try { Throwable error = saml1AuthServer.getErrorResponse(samlArtifact); statusCode = "samlp:Responder"; if (error instanceof MOAIDException) { statusMessageCode = ((MOAIDException)error).getMessageId(); statusMessage = StringEscapeUtils.escapeXml(((MOAIDException)error).getMessage()); } else { statusMessage = StringEscapeUtils.escapeXml(error.getMessage()); } subStatusCode = statusMessager.getResponseErrorCode(error); } catch (Exception e) { //no authentication data for given SAML artifact statusCode = "samlp:Requester"; subStatusCode = "samlp:ResourceNotRecognized"; statusMessage = ex.toString(); } } catch (AuthenticationException ex) { //no authentication data for given SAML artifact statusCode = "samlp:Requester"; subStatusCode = "samlp:ResourceNotRecognized"; statusMessage = ex.toString(); } } } catch (Throwable t) { // invalid request format statusCode = "samlp:Requester"; statusMessageCode = "1204"; } String responseID = Random.nextRandom(); String issueInstant = DateTimeUtils.buildDateTimeUTC(Calendar.getInstance()); try { if (statusMessage == null) statusMessage = MOAIDMessageProvider.getInstance().getMessage(statusMessageCode, null); responses = new SAMLResponseBuilder().build( responseID, requestID, issueInstant, statusCode, subStatusCode, statusMessage, samlAssertion); return responses; } catch (Throwable e) { throw new SAML1AssertionResponseBuildException(responseID, issueInstant, requestID, "1299", e.getMessage(), e); } } private void evaluateTemplate(VelocityContext context, HttpServletResponse httpResp, String templateURL) throws IOException { InputStream is = null; try { is = Thread.currentThread() .getContextClassLoader() .getResourceAsStream(templateURL); VelocityEngine engine = VelocityProvider.getClassPathVelocityEngine(); BufferedReader reader = new BufferedReader(new InputStreamReader(is )); StringWriter writer = new StringWriter(); engine.evaluate(context, writer, "SAML1 GetAuthenticationData", reader); byte[] content = writer.toString().getBytes("UTF-8"); httpResp.setContentLength(content.length); httpResp.getOutputStream().write(content); } catch (Exception e) { Logger.error("SAML1 GetAuthenticationData has an error:", e); throw new IOException(e); } finally { if (is != null) is.close(); } } }