package org.jboss.seam.security.external.saml;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.jboss.solder.logging.Logger;
import org.jboss.seam.security.external.Base64;
import org.jboss.seam.security.external.InvalidRequestException;
import org.jboss.seam.security.external.JaxbContext;
import org.jboss.seam.security.external.dialogues.api.DialogueManager;
import org.jboss.seam.security.external.jaxb.samlv2.protocol.RequestAbstractType;
import org.jboss.seam.security.external.jaxb.samlv2.protocol.ResponseType;
import org.jboss.seam.security.external.jaxb.samlv2.protocol.StatusResponseType;
import org.jboss.seam.security.external.saml.idp.SamlIdpBeanApi;
import org.jboss.seam.security.external.saml.idp.SamlIdpSingleLogoutService;
import org.jboss.seam.security.external.saml.idp.SamlIdpSingleSignOnService;
import org.jboss.seam.security.external.saml.sp.SamlSpBeanApi;
import org.jboss.seam.security.external.saml.sp.SamlSpSingleLogoutService;
import org.jboss.seam.security.external.saml.sp.SamlSpSingleSignOnService;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
/**
* @author Marcel Kolsteren
*/
@ApplicationScoped
public class SamlMessageReceiver {
@Inject
private Logger log;
@Inject
private DialogueManager dialogueManager;
@Inject
private Instance<SamlDialogue> samlDialogue;
@Inject
private SamlSpSingleLogoutService samlSpSingleLogoutService;
@Inject
private SamlIdpSingleLogoutService samlIdpSingleLogoutService;
@Inject
private SamlSpSingleSignOnService samlSpSingleSignOnService;
@Inject
private SamlIdpSingleSignOnService samlIdpSingleSignOnService;
@Inject
private Instance<SamlEntityBean> samlEntityBean;
@Inject
private Instance<SamlSpBeanApi> samlSpBean;
@Inject
private Instance<SamlIdpBeanApi> samlIdpBean;
@Inject
private SamlSignatureUtilForPostBinding signatureUtilForPostBinding;
@Inject
private SamlSignatureUtilForRedirectBinding signatureUtilForRedirectBinding;
@Inject
@JaxbContext({RequestAbstractType.class, StatusResponseType.class})
private JAXBContext jaxbContext;
public void handleIncomingSamlMessage(SamlServiceType service, HttpServletRequest httpRequest, HttpServletResponse httpResponse, SamlIdpOrSp idpOrSp) throws InvalidRequestException {
String samlRequestParam = httpRequest.getParameter(SamlRedirectMessage.QSP_SAML_REQUEST);
String samlResponseParam = httpRequest.getParameter(SamlRedirectMessage.QSP_SAML_RESPONSE);
SamlRequestOrResponse samlRequestOrResponse;
String samlMessage;
if (samlRequestParam != null && samlResponseParam == null) {
samlMessage = samlRequestParam;
samlRequestOrResponse = SamlRequestOrResponse.REQUEST;
} else if (samlRequestParam == null && samlResponseParam != null) {
samlMessage = samlResponseParam;
samlRequestOrResponse = SamlRequestOrResponse.RESPONSE;
} else {
throw new InvalidRequestException("SAML message should either have a SAMLRequest parameter or a SAMLResponse parameter");
}
InputStream is;
if (httpRequest.getMethod().equals("POST")) {
byte[] decodedMessage = Base64.decode(samlMessage);
is = new ByteArrayInputStream(decodedMessage);
} else {
byte[] base64Decoded = Base64.decode(samlMessage);
ByteArrayInputStream bais = new ByteArrayInputStream(base64Decoded);
is = new InflaterInputStream(bais, new Inflater(true));
}
Document document = getDocument(is);
String issuerEntityId;
RequestAbstractType samlRequestMessage = null;
StatusResponseType samlResponseMessage = null;
if (samlRequestOrResponse.isRequest()) {
samlRequestMessage = getSamlRequest(document);
issuerEntityId = samlRequestMessage.getIssuer().getValue();
} else {
samlResponseMessage = getSamlResponse(document);
issuerEntityId = samlResponseMessage.getIssuer().getValue();
}
log.debug("Received: " + SamlUtils.getDocumentAsString(document));
try {
if (samlRequestOrResponse.isRequest() || samlResponseMessage.getInResponseTo() == null) {
// Request or unsolicited response
String destination = samlRequestOrResponse.isRequest() ? samlRequestMessage.getDestination() : samlResponseMessage.getDestination();
if (!samlEntityBean.get().getServiceURL(service).equals(destination)) {
throw new InvalidRequestException("Destination (" + destination + ") is not valid.");
}
dialogueManager.beginDialogue();
samlDialogue.get().setExternalProviderMessageId(samlRequestOrResponse.isRequest() ? samlRequestMessage.getID() : samlResponseMessage.getID());
SamlExternalEntity externalProvider = samlEntityBean.get().getExternalSamlEntityByEntityId(issuerEntityId);
if (externalProvider == null) {
throw new InvalidRequestException("Received message from unknown entity id " + issuerEntityId);
}
samlDialogue.get().setExternalProvider(externalProvider);
} else {
String dialogueId = samlResponseMessage.getInResponseTo();
if (!dialogueManager.isExistingDialogue(dialogueId)) {
throw new InvalidRequestException("No request that corresponds with the received response");
}
dialogueManager.attachDialogue(dialogueId);
if (!(samlDialogue.get().getExternalProvider().getEntityId().equals(issuerEntityId))) {
throw new InvalidRequestException("Identity samlEntityBean of request and response do not match");
}
}
SamlExternalEntity externalProvider = samlEntityBean.get().getExternalSamlEntityByEntityId(issuerEntityId);
boolean validate;
if (samlRequestOrResponse.isRequest()) {
if (service.getProfile() == SamlProfile.SINGLE_SIGN_ON) {
if (idpOrSp == SamlIdpOrSp.IDP) {
validate = samlIdpBean.get().isWantAuthnRequestsSigned();
} else {
validate = samlSpBean.get().isWantAssertionsSigned();
}
} else {
if (idpOrSp == SamlIdpOrSp.IDP) {
validate = samlIdpBean.get().isWantSingleLogoutMessagesSigned();
} else {
validate = samlSpBean.get().isWantSingleLogoutMessagesSigned();
}
}
} else {
validate = samlResponseMessage instanceof ResponseType;
}
if (validate) {
if (httpRequest.getMethod().equals("POST")) {
signatureUtilForPostBinding.validateSignature(externalProvider.getPublicKey(), document);
} else {
SamlRedirectMessage redirectMessage = new SamlRedirectMessage(samlRequestOrResponse, httpRequest);
signatureUtilForRedirectBinding.validateSignature(redirectMessage, externalProvider.getPublicKey());
}
}
if (service.getProfile() == SamlProfile.SINGLE_SIGN_ON) {
if (samlRequestOrResponse.isRequest()) {
samlIdpSingleSignOnService.processSPRequest(httpRequest, httpResponse, samlRequestMessage);
} else {
samlSpSingleSignOnService.processIDPResponse(httpRequest, httpResponse, samlResponseMessage);
}
} else {
if (samlRequestOrResponse.isRequest()) {
if (idpOrSp == SamlIdpOrSp.IDP) {
samlIdpSingleLogoutService.processSPRequest(httpRequest, httpResponse, samlRequestMessage);
} else {
samlSpSingleLogoutService.processIDPRequest(httpRequest, httpResponse, samlRequestMessage);
}
} else {
if (idpOrSp == SamlIdpOrSp.IDP) {
samlIdpSingleLogoutService.processSPResponse(httpRequest, httpResponse, samlResponseMessage);
} else {
samlSpSingleLogoutService.processIDPResponse(httpRequest, httpResponse, samlResponseMessage);
}
}
}
} catch (Exception e) {
if (dialogueManager.isAttached()) {
dialogueManager.endDialogue();
}
throw new RuntimeException(e);
}
dialogueManager.detachDialogue();
}
private RequestAbstractType getSamlRequest(Document document) throws InvalidRequestException {
try {
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
@SuppressWarnings("unchecked")
JAXBElement<RequestAbstractType> jaxbRequest = (JAXBElement<RequestAbstractType>) unmarshaller.unmarshal(document);
RequestAbstractType request = jaxbRequest.getValue();
return request;
} catch (JAXBException e) {
throw new InvalidRequestException("SAML message could not be parsed", e);
}
}
private StatusResponseType getSamlResponse(Document document) throws InvalidRequestException {
try {
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
@SuppressWarnings("unchecked")
JAXBElement<StatusResponseType> jaxbResponseType = (JAXBElement<StatusResponseType>) unmarshaller.unmarshal(document);
StatusResponseType statusResponse = jaxbResponseType.getValue();
return statusResponse;
} catch (JAXBException e) {
throw new InvalidRequestException("SAML message could not be parsed", e);
}
}
private Document getDocument(InputStream is) throws InvalidRequestException {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
factory.setXIncludeAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(is);
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
} catch (SAXException e) {
throw new InvalidRequestException("SAML request could not be parsed", e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}