/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.protocol.saml.profile.ecp.util;
import org.keycloak.saml.processing.core.saml.v2.util.DocumentUtil;
import org.keycloak.saml.processing.web.util.PostBindingUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.Name;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFault;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public final class Soap {
public static SoapFaultBuilder createFault() {
return new SoapFaultBuilder();
}
public static SoapMessageBuilder createMessage() {
return new SoapMessageBuilder();
}
/**
* <p>Returns a string encoded accordingly with the SAML HTTP POST Binding specification based on the
* given <code>inputStream</code> which must contain a valid SOAP message.
*
* <p>The resulting string is based on the Body of the SOAP message, which should map to a valid SAML message.
*
* @param inputStream the input stream containing a valid SOAP message with a Body that contains a SAML message
*
* @return a string encoded accordingly with the SAML HTTP POST Binding specification
*/
public static String toSamlHttpPostMessage(InputStream inputStream) {
try {
MessageFactory messageFactory = MessageFactory.newInstance();
SOAPMessage soapMessage = messageFactory.createMessage(null, inputStream);
SOAPBody soapBody = soapMessage.getSOAPBody();
Node authnRequestNode = soapBody.getFirstChild();
Document document = DocumentUtil.createDocument();
document.appendChild(document.importNode(authnRequestNode, true));
return PostBindingUtil.base64Encode(DocumentUtil.asString(document));
} catch (Exception e) {
throw new RuntimeException("Error creating fault message.", e);
}
}
public static class SoapMessageBuilder {
private final SOAPMessage message;
private final SOAPBody body;
private final SOAPEnvelope envelope;
private SoapMessageBuilder() {
try {
this.message = MessageFactory.newInstance().createMessage();
this.envelope = message.getSOAPPart().getEnvelope();
this.body = message.getSOAPBody();
} catch (Exception e) {
throw new RuntimeException("Error creating fault message.", e);
}
}
public SoapMessageBuilder addToBody(Document document) {
try {
this.body.addDocument(document);
} catch (SOAPException e) {
throw new RuntimeException("Could not add document to SOAP body.", e);
}
return this;
}
public SoapMessageBuilder addNamespace(String prefix, String ns) {
try {
envelope.addNamespaceDeclaration(prefix, ns);
} catch (SOAPException e) {
throw new RuntimeException("Could not add namespace to SOAP Envelope.", e);
}
return this;
}
public SOAPHeaderElement addHeader(String name, String prefix) {
try {
return this.envelope.getHeader().addHeaderElement(envelope.createQName(name, prefix));
} catch (SOAPException e) {
throw new RuntimeException("Could not add SOAP Header.", e);
}
}
public Name createName(String name) {
try {
return this.envelope.createName(name);
} catch (SOAPException e) {
throw new RuntimeException("Could not create Name.", e);
}
}
public Response build() {
return build(Status.OK);
}
Response build(Status status) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
this.message.writeTo(outputStream);
} catch (Exception e) {
throw new RuntimeException("Error while building SOAP Fault.", e);
}
return Response.status(status).entity(outputStream.toByteArray()).build();
}
SOAPMessage getMessage() {
return this.message;
}
}
public static class SoapFaultBuilder {
private final SOAPFault fault;
private final SoapMessageBuilder messageBuilder;
private SoapFaultBuilder() {
this.messageBuilder = createMessage();
try {
this.fault = messageBuilder.getMessage().getSOAPBody().addFault();
} catch (SOAPException e) {
throw new RuntimeException("Could not create SOAP Fault.", e);
}
}
public SoapFaultBuilder detail(String detail) {
try {
this.fault.addDetail().setValue(detail);
} catch (SOAPException e) {
throw new RuntimeException("Error creating fault message.", e);
}
return this;
}
public SoapFaultBuilder reason(String reason) {
try {
this.fault.setFaultString(reason);
} catch (SOAPException e) {
throw new RuntimeException("Error creating fault message.", e);
}
return this;
}
public SoapFaultBuilder code(String code) {
try {
this.fault.setFaultCode(code);
} catch (SOAPException e) {
throw new RuntimeException("Error creating fault message.", e);
}
return this;
}
public Response build() {
return this.messageBuilder.build(Status.INTERNAL_SERVER_ERROR);
}
}
}