keycloak-aplcache
Changes
saml-core/pom.xml 12(+12 -0)
saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java 2(+2 -0)
saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java 3(+3 -0)
saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java 2(+2 -0)
saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java 2(+2 -0)
saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLExtensionsParser.java 82(+82 -0)
saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java 3(+3 -0)
saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java 2(+2 -0)
saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java 3(+3 -0)
saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java 26(+26 -0)
saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java 20(+20 -0)
saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java 18(+18 -0)
saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java 107(+107 -0)
saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response.xml 29(+29 -0)
Details
saml-core/pom.xml 12(+12 -0)
diff --git a/saml-core/pom.xml b/saml-core/pom.xml
index 8483240..8c08b69 100755
--- a/saml-core/pom.xml
+++ b/saml-core/pom.xml
@@ -53,6 +53,18 @@
<groupId>org.apache.santuario</groupId>
<artifactId>xmlsec</artifactId>
</dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.12</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-core</artifactId>
+ <version>1.3</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
<resources>
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java b/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java
index 76a72ae..2f58911 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java
@@ -450,6 +450,11 @@ public class DefaultPicketLinkLogger implements PicketLinkLogger {
return new RuntimeException(ErrorCodes.EXPECTED_TAG + tag + ">. Found <" + foundElementTag + ">");
}
+ @Override
+ public RuntimeException parserExpectedNamespace(String ns, String foundElementNs) {
+ return new RuntimeException(ErrorCodes.EXPECTED_NAMESPACE + ns + ">. Found <" + foundElementNs + ">");
+ }
+
/*
*(non-Javadoc)
*
@@ -2378,4 +2383,10 @@ public class DefaultPicketLinkLogger implements PicketLinkLogger {
return new ProcessingException("Wrong audience [" + serviceURL + "].");
}
+ @Override
+ public ProcessingException samlExtensionUnknownChild(Class<?> clazz) {
+ return new ProcessingException("Unknown child type specified for extension: "
+ + (clazz == null ? "<null>" : clazz.getSimpleName())
+ + ".");
+ }
}
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java b/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java
index 09f4301..37a7755 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java
@@ -48,6 +48,8 @@ public interface ErrorCodes {
String EXPECTED_TAG = "PL00066: Parser : Expected start tag:";
+ String EXPECTED_NAMESPACE = "PL00107: Parser : Expected start element namespace:";
+
String EXPECTED_TEXT_VALUE = "PL00071: Parser: Expected text value:";
String EXPECTED_END_TAG = "PL00066: Parser : Expected end tag:";
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java b/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java
index 7ac6a09..91f2f54 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java
@@ -297,6 +297,14 @@ public interface PicketLinkLogger {
RuntimeException parserExpectedTag(String tag, String foundElementTag);
/**
+ * @param ns
+ * @param foundElementNs
+ *
+ * @return
+ */
+ RuntimeException parserExpectedNamespace(String ns, String foundElementNs);
+
+ /**
* @param elementName
*
* @return
@@ -1219,4 +1227,6 @@ public interface PicketLinkLogger {
RuntimeException parserFeatureNotSupported(String feature);
ProcessingException samlAssertionWrongAudience(String serviceURL);
+
+ ProcessingException samlExtensionUnknownChild(Class<?> clazz);
}
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java
index ca0316b..06d6042 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java
@@ -58,6 +58,8 @@ public class SAMLArtifactResolveParser extends SAMLRequestAbstractParser impleme
continue;
} else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
continue;
+ } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+ continue;
} else
throw new RuntimeException(ErrorCodes.UNKNOWN_START_ELEMENT + elementName + "::location="
+ startElement.getLocation());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java
index 5f98403..9d3686b 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java
@@ -68,6 +68,9 @@ public class SAMLArtifactResponseParser extends SAMLStatusResponseTypeParser imp
} else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
Element sig = StaxParserUtil.getDOMElement(xmlEventReader);
response.setSignature(sig);
+ } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+ SAMLExtensionsParser extensionsParser = new SAMLExtensionsParser();
+ response.setExtensions(extensionsParser.parse(xmlEventReader));
} else if (JBossSAMLConstants.AUTHN_REQUEST.get().equals(elementName)) {
SAMLAuthNRequestParser authnParser = new SAMLAuthNRequestParser();
AuthnRequestType authn = (AuthnRequestType) authnParser.parse(xmlEventReader);
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java
index 139e4e0..6102e9e 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java
@@ -60,6 +60,8 @@ public class SAMLAttributeQueryParser extends SAMLRequestAbstractParser implemen
continue;
} else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
continue;
+ } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+ continue;
} else
throw new RuntimeException(ErrorCodes.UNKNOWN_START_ELEMENT + elementName + "::location="
+ startElement.getLocation());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java
index 5a15b2c..f1d3349 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java
@@ -76,6 +76,8 @@ public class SAMLAuthNRequestParser extends SAMLRequestAbstractParser implements
continue;
} else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
continue;
+ } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+ continue;
} else
throw new RuntimeException(ErrorCodes.UNKNOWN_START_ELEMENT + elementName + "::location="
+ startElement.getLocation());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLExtensionsParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLExtensionsParser.java
new file mode 100644
index 0000000..5f7ebbb
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLExtensionsParser.java
@@ -0,0 +1,82 @@
+/*
+ * 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.saml.processing.core.parsers.saml;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
+import org.keycloak.saml.common.PicketLinkLogger;
+import org.keycloak.saml.common.PicketLinkLoggerFactory;
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.parsers.ParserNamespaceSupport;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+/**
+ * Parses <samlp:Extensions> SAML2 element into series of DOM nodes.
+ *
+ * @author hmlnarik
+ */
+public class SAMLExtensionsParser implements ParserNamespaceSupport {
+
+ private static final String EXTENSIONS = JBossSAMLConstants.EXTENSIONS.get();
+
+ private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
+
+ @Override
+ public ExtensionsType parse(XMLEventReader xmlEventReader) throws ParsingException {
+ // Get the startelement
+ StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+ StaxParserUtil.validate(startElement, EXTENSIONS);
+
+ ExtensionsType extensions = new ExtensionsType();
+
+ while (xmlEventReader.hasNext()) {
+ XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+ if (xmlEvent instanceof EndElement) {
+ EndElement endElement = (EndElement) xmlEvent;
+ if (StaxParserUtil.matches(endElement, EXTENSIONS)) {
+ endElement = StaxParserUtil.getNextEndElement(xmlEventReader);
+ break;
+ } else
+ throw logger.parserUnknownEndElement(StaxParserUtil.getEndElementName(endElement));
+ }
+
+ startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+ if (startElement == null)
+ break;
+
+ extensions.addExtension(StaxParserUtil.getDOMElement(xmlEventReader));
+ }
+
+ return extensions;
+ }
+
+ @Override
+ public boolean supports(QName qname) {
+ String nsURI = qname.getNamespaceURI();
+ String localPart = qname.getLocalPart();
+
+ return nsURI.equals(JBossSAMLURIConstants.PROTOCOL_NSURI.get())
+ && localPart.equals(JBossSAMLConstants.EXTENSIONS.get());
+ }
+
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java
index a2691c2..92eaf90 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java
@@ -71,6 +71,9 @@ public class SAMLResponseParser extends SAMLStatusResponseTypeParser implements
} else if (JBossSAMLConstants.ASSERTION.get().equals(elementName)) {
SAMLAssertionParser assertionParser = new SAMLAssertionParser();
response.addAssertion(new RTChoiceType((AssertionType) assertionParser.parse(xmlEventReader)));
+ } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+ SAMLExtensionsParser extensionsParser = new SAMLExtensionsParser();
+ response.setExtensions(extensionsParser.parse(xmlEventReader));
} else if (JBossSAMLConstants.STATUS.get().equals(elementName)) {
response.setStatus(parseStatus(xmlEventReader));
} else if (JBossSAMLConstants.ENCRYPTED_ASSERTION.get().equals(elementName)) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java
index f604cf5..22ed383 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java
@@ -74,6 +74,8 @@ public class SAMLSloRequestParser extends SAMLRequestAbstractParser implements P
continue;
} else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
continue;
+ } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+ continue;
} else
throw logger.parserUnknownTag(elementName, startElement.getLocation());
}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java
index 167a3c5..c0f473b 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java
@@ -60,6 +60,9 @@ public class SAMLSloResponseParser extends SAMLStatusResponseTypeParser implemen
} else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
StaxParserUtil.bypassElementBlock(xmlEventReader, JBossSAMLConstants.SIGNATURE.get());
+ } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+ SAMLExtensionsParser extensionsParser = new SAMLExtensionsParser();
+ response.setExtensions(extensionsParser.parse(xmlEventReader));
} else if (JBossSAMLConstants.STATUS.get().equals(elementName)) {
response.setStatus(parseStatus(xmlEventReader));
}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java
index 4c041d1..068c91a 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java
@@ -43,8 +43,12 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
+import org.keycloak.saml.SamlProtocolExtensionsAwareBuilder;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ASSERTION_NSURI;
+import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_NSURI;
+import org.w3c.dom.Node;
/**
* Base Class for the Stax writers for SAML
@@ -244,6 +248,28 @@ public class BaseWriter {
StaxUtil.flush(writer);
}
+ public void write(ExtensionsType extensions) throws ProcessingException {
+ if (extensions.getAny().isEmpty()) {
+ return;
+ }
+
+ StaxUtil.writeStartElement(writer, PROTOCOL_PREFIX, JBossSAMLConstants.EXTENSIONS.get(), PROTOCOL_NSURI.get());
+
+ for (Object o : extensions.getAny()) {
+ if (o instanceof Node) {
+ StaxUtil.writeDOMNode(writer, (Node) o);
+ } else if (o instanceof SamlProtocolExtensionsAwareBuilder.NodeGenerator) {
+ SamlProtocolExtensionsAwareBuilder.NodeGenerator ng = (SamlProtocolExtensionsAwareBuilder.NodeGenerator) o;
+ ng.write(writer);
+ } else {
+ throw logger.samlExtensionUnknownChild(o == null ? null : o.getClass());
+ }
+ }
+
+ StaxUtil.writeEndElement(writer);
+ StaxUtil.flush(writer);
+ }
+
private void write(SubjectConfirmationType subjectConfirmationType) throws ProcessingException {
StaxUtil.writeStartElement(writer, ASSERTION_PREFIX, JBossSAMLConstants.SUBJECT_CONFIRMATION.get(),
ASSERTION_NSURI.get());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java
index 9f99780..8c115f5 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java
@@ -36,6 +36,7 @@ import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamWriter;
import java.net.URI;
import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ASSERTION_NSURI;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_NSURI;
@@ -122,6 +123,11 @@ public class SAMLRequestWriter extends BaseWriter {
StaxUtil.writeDOMElement(writer, sig);
}
+ ExtensionsType extensions = request.getExtensions();
+ if (extensions != null && ! extensions.getAny().isEmpty()) {
+ write(extensions);
+ }
+
NameIDPolicyType nameIDPolicy = request.getNameIDPolicy();
if (nameIDPolicy != null) {
write(nameIDPolicy);
@@ -171,6 +177,11 @@ public class SAMLRequestWriter extends BaseWriter {
StaxUtil.writeDOMElement(writer, signature);
}
+ ExtensionsType extensions = logOutRequest.getExtensions();
+ if (extensions != null && ! extensions.getAny().isEmpty()) {
+ write(extensions);
+ }
+
NameIDType nameID = logOutRequest.getNameID();
if (nameID != null) {
write(nameID, new QName(ASSERTION_NSURI.get(), JBossSAMLConstants.NAMEID.get(), ASSERTION_PREFIX));
@@ -278,6 +289,11 @@ public class SAMLRequestWriter extends BaseWriter {
if (sig != null) {
StaxUtil.writeDOMElement(writer, sig);
}
+ ExtensionsType extensions = request.getExtensions();
+ if (extensions != null && ! extensions.getAny().isEmpty()) {
+ write(extensions);
+ }
+
String artifact = request.getArtifact();
if (StringUtil.isNotNull(artifact)) {
StaxUtil.writeStartElement(writer, PROTOCOL_PREFIX, JBossSAMLConstants.ARTIFACT.get(), PROTOCOL_NSURI.get());
@@ -315,6 +331,10 @@ public class SAMLRequestWriter extends BaseWriter {
if (sig != null) {
StaxUtil.writeDOMElement(writer, sig);
}
+ ExtensionsType extensions = request.getExtensions();
+ if (extensions != null && ! extensions.getAny().isEmpty()) {
+ write(extensions);
+ }
SubjectType subject = request.getSubject();
if (subject != null) {
write(subject);
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java
index 07fae2a..9327a73 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java
@@ -37,6 +37,7 @@ import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamWriter;
import java.net.URI;
import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
/**
* Write a SAML Response to stream
@@ -78,6 +79,10 @@ public class SAMLResponseWriter extends BaseWriter {
if (sig != null) {
StaxUtil.writeDOMElement(writer, sig);
}
+ ExtensionsType extensions = response.getExtensions();
+ if (extensions != null && extensions.getAny() != null && ! extensions.getAny().isEmpty()) {
+ write(extensions);
+ }
StatusType status = response.getStatus();
write(status);
@@ -119,6 +124,10 @@ public class SAMLResponseWriter extends BaseWriter {
if (sig != null) {
StaxUtil.writeDOMElement(writer, sig);
}
+ ExtensionsType extensions = response.getExtensions();
+ if (extensions != null && extensions.getAny() != null && ! extensions.getAny().isEmpty()) {
+ write(extensions);
+ }
StatusType status = response.getStatus();
if (status != null) {
@@ -163,6 +172,15 @@ public class SAMLResponseWriter extends BaseWriter {
NameIDType issuer = response.getIssuer();
write(issuer, new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(), JBossSAMLConstants.ISSUER.get(), ASSERTION_PREFIX));
+ Element sig = response.getSignature();
+ if (sig != null) {
+ StaxUtil.writeDOMElement(writer, sig);
+ }
+ ExtensionsType extensions = response.getExtensions();
+ if (extensions != null && extensions.getAny() != null && ! extensions.getAny().isEmpty()) {
+ write(extensions);
+ }
+
StatusType status = response.getStatus();
write(status);
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
index 302237e..ec4fc28 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
@@ -25,15 +25,19 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
import org.w3c.dom.Document;
import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
/**
* @author pedroigor
*/
-public class SAML2AuthnRequestBuilder {
+public class SAML2AuthnRequestBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2AuthnRequestBuilder> {
private final AuthnRequestType authnRequestType;
protected String destination;
protected String issuer;
+ protected final List<NodeGenerator> extensions = new LinkedList<>();
public SAML2AuthnRequestBuilder destination(String destination) {
this.destination = destination;
@@ -45,6 +49,12 @@ public class SAML2AuthnRequestBuilder {
return this;
}
+ @Override
+ public SAML2AuthnRequestBuilder addExtension(NodeGenerator extension) {
+ this.extensions.add(extension);
+ return this;
+ }
+
public SAML2AuthnRequestBuilder() {
try {
this.authnRequestType = new AuthnRequestType(IDGenerator.create("ID_"), XMLTimeUtil.getIssueInstant());
@@ -90,6 +100,14 @@ public class SAML2AuthnRequestBuilder {
authnRequestType.setDestination(URI.create(this.destination));
+ if (! this.extensions.isEmpty()) {
+ ExtensionsType extensionsType = new ExtensionsType();
+ for (NodeGenerator extension : this.extensions) {
+ extensionsType.addExtension(extension);
+ }
+ authnRequestType.setExtensions(extensionsType);
+ }
+
return new SAML2Request().convert(authnRequestType);
} catch (Exception e) {
throw new RuntimeException("Could not convert " + authnRequestType + " to a document.", e);
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
index 99d1c1f..6da6799 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
@@ -17,7 +17,10 @@
package org.keycloak.saml;
+import java.util.LinkedList;
+import java.util.List;
import org.keycloak.dom.saml.v2.assertion.NameIDType;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
import org.keycloak.saml.common.exceptions.ConfigurationException;
import org.keycloak.saml.common.exceptions.ParsingException;
@@ -32,11 +35,12 @@ import org.w3c.dom.Document;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class SAML2ErrorResponseBuilder {
+public class SAML2ErrorResponseBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2ErrorResponseBuilder> {
protected String status;
protected String destination;
protected String issuer;
+ protected final List<NodeGenerator> extensions = new LinkedList<>();
public SAML2ErrorResponseBuilder status(String status) {
this.status = status;
@@ -53,6 +57,11 @@ public class SAML2ErrorResponseBuilder {
return this;
}
+ @Override
+ public SAML2ErrorResponseBuilder addExtension(NodeGenerator extension) {
+ this.extensions.add(extension);
+ return this;
+ }
public Document buildDocument() throws ProcessingException {
@@ -66,6 +75,14 @@ public class SAML2ErrorResponseBuilder {
statusResponse.setIssuer(issuer);
statusResponse.setDestination(destination);
+ if (! this.extensions.isEmpty()) {
+ ExtensionsType extensionsType = new ExtensionsType();
+ for (NodeGenerator extension : this.extensions) {
+ extensionsType.addExtension(extension);
+ }
+ statusResponse.setExtensions(extensionsType);
+ }
+
SAML2Response saml2Response = new SAML2Response();
return saml2Response.convert(statusResponse);
} catch (ConfigurationException e) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java
index 2386edb..17dafc7 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java
@@ -39,6 +39,9 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
import org.w3c.dom.Document;
import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
import static org.keycloak.saml.common.util.StringUtil.isNotNull;
@@ -49,7 +52,7 @@ import static org.keycloak.saml.common.util.StringUtil.isNotNull;
*
* @author bburke@redhat.com
*/
-public class SAML2LoginResponseBuilder {
+public class SAML2LoginResponseBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2LoginResponseBuilder> {
protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
protected String destination;
@@ -64,6 +67,7 @@ public class SAML2LoginResponseBuilder {
protected String authMethod;
protected String requestIssuer;
protected String sessionIndex;
+ protected final List<NodeGenerator> extensions = new LinkedList<>();
public SAML2LoginResponseBuilder sessionIndex(String sessionIndex) {
@@ -136,6 +140,12 @@ public class SAML2LoginResponseBuilder {
return this;
}
+ @Override
+ public SAML2LoginResponseBuilder addExtension(NodeGenerator extension) {
+ this.extensions.add(extension);
+ return this;
+ }
+
public Document buildDocument(ResponseType responseType) throws ConfigurationException, ProcessingException {
Document samlResponseDocument = null;
@@ -207,6 +217,14 @@ public class SAML2LoginResponseBuilder {
assertion.addStatement(authnStatement);
}
+ if (! this.extensions.isEmpty()) {
+ ExtensionsType extensionsType = new ExtensionsType();
+ for (NodeGenerator extension : this.extensions) {
+ extensionsType.addExtension(extension);
+ }
+ responseType.setExtensions(extensionsType);
+ }
+
return responseType;
}
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java
index 99b1cf8..d0e81ba 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java
@@ -27,18 +27,22 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
import org.w3c.dom.Document;
import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class SAML2LogoutRequestBuilder {
+public class SAML2LogoutRequestBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2LogoutRequestBuilder> {
protected String userPrincipal;
protected String userPrincipalFormat;
protected String sessionIndex;
protected long assertionExpiration;
protected String destination;
protected String issuer;
+ protected final List<NodeGenerator> extensions = new LinkedList<>();
public SAML2LogoutRequestBuilder destination(String destination) {
this.destination = destination;
@@ -50,6 +54,12 @@ public class SAML2LogoutRequestBuilder {
return this;
}
+ @Override
+ public SAML2LogoutRequestBuilder addExtension(NodeGenerator extension) {
+ this.extensions.add(extension);
+ return this;
+ }
+
/**
* Length of time in seconds the assertion is valid for
* See SAML core specification 2.5.1.2 NotOnOrAfter
@@ -99,6 +109,15 @@ public class SAML2LogoutRequestBuilder {
if (assertionExpiration > 0) lort.setNotOnOrAfter(XMLTimeUtil.add(lort.getIssueInstant(), assertionExpiration * 1000));
lort.setDestination(URI.create(destination));
+
+ if (! this.extensions.isEmpty()) {
+ ExtensionsType extensionsType = new ExtensionsType();
+ for (NodeGenerator extension : this.extensions) {
+ extensionsType.addExtension(extension);
+ }
+ lort.setExtensions(extensionsType);
+ }
+
return lort;
}
}
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java
index c00a4d4..8050e81 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java
@@ -31,16 +31,20 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
import org.w3c.dom.Document;
import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class SAML2LogoutResponseBuilder {
+public class SAML2LogoutResponseBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2LogoutResponseBuilder> {
protected String logoutRequestID;
protected String destination;
protected String issuer;
+ protected final List<NodeGenerator> extensions = new LinkedList<>();
public SAML2LogoutResponseBuilder logoutRequestID(String logoutRequestID) {
this.logoutRequestID = logoutRequestID;
@@ -57,6 +61,11 @@ public class SAML2LogoutResponseBuilder {
return this;
}
+ @Override
+ public SAML2LogoutResponseBuilder addExtension(NodeGenerator extension) {
+ this.extensions.add(extension);
+ return this;
+ }
public Document buildDocument() throws ProcessingException {
Document samlResponse = null;
@@ -77,6 +86,14 @@ public class SAML2LogoutResponseBuilder {
statusResponse.setIssuer(issuer);
statusResponse.setDestination(destination);
+ if (! this.extensions.isEmpty()) {
+ ExtensionsType extensionsType = new ExtensionsType();
+ for (NodeGenerator extension : this.extensions) {
+ extensionsType.addExtension(extension);
+ }
+ statusResponse.setExtensions(extensionsType);
+ }
+
SAML2Response saml2Response = new SAML2Response();
samlResponse = saml2Response.convert(statusResponse);
} catch (ConfigurationException e) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/SamlProtocolExtensionsAwareBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SamlProtocolExtensionsAwareBuilder.java
new file mode 100644
index 0000000..2192df6
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/SamlProtocolExtensionsAwareBuilder.java
@@ -0,0 +1,49 @@
+/*
+ * 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.saml;
+
+import javax.xml.stream.XMLStreamWriter;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+
+/**
+ * Implementations of this interface are builders that can register <samlp:Extensions>
+ * content providers.
+ *
+ * @author hmlnarik
+ */
+public interface SamlProtocolExtensionsAwareBuilder<T> {
+
+ public interface NodeGenerator {
+ /**
+ * Generate contents of the <samlp:Extensions> tag. When this method is invoked,
+ * the writer has already emitted the <samlp:Extensions> start tag.
+ *
+ * @param writer Writer to use for producing XML output
+ * @throws ProcessingException If any exception fails
+ */
+ void write(XMLStreamWriter writer) throws ProcessingException;
+ }
+
+ /**
+ * Adds a given node subtree as a SAML protocol extension into the SAML protocol message.
+ *
+ * @param extension
+ * @return
+ */
+ T addExtension(NodeGenerator extension);
+}
diff --git a/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java
new file mode 100644
index 0000000..ad150e9
--- /dev/null
+++ b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.saml.processing.core.parsers.saml;
+
+import java.io.InputStream;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.*;
+import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
+import org.w3c.dom.Element;
+
+/**
+ * Test class for SAML parser.
+ *
+ * TODO: Add further tests.
+ *
+ * @author hmlnarik
+ */
+public class SAMLParserTest {
+
+ @Test
+ public void testSaml20EncryptedAssertionsSignedReceivedWithRedirectBinding() throws Exception {
+ InputStream st = SAMLParserTest.class.getResourceAsStream("saml20-encrypted-signed-redirect-response.xml");
+ SAMLParser parser = new SAMLParser();
+
+ Object parsedObject = parser.parse(st);
+ assertThat(parsedObject, instanceOf(ResponseType.class));
+
+ ResponseType resp = (ResponseType) parsedObject;
+ assertThat(resp.getSignature(), nullValue());
+ assertThat(resp.getConsent(), nullValue());
+ assertThat(resp.getIssuer(), not(nullValue()));
+ assertThat(resp.getIssuer().getValue(), is("http://localhost:8081/auth/realms/saml-demo"));
+
+ assertThat(resp.getExtensions(), not(nullValue()));
+ assertThat(resp.getExtensions().getAny().size(), is(1));
+ assertThat(resp.getExtensions().getAny().get(0), instanceOf(Element.class));
+ Element el = (Element) resp.getExtensions().getAny().get(0);
+ assertThat(el.getLocalName(), is("KeyInfo"));
+ assertThat(el.getNamespaceURI(), is("urn:keycloak:ext:key:1.0"));
+ assertThat(el.hasAttribute("MessageSigningKeyId"), is(true));
+ assertThat(el.getAttribute("MessageSigningKeyId"), is("FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"));
+
+ assertThat(resp.getAssertions(), not(nullValue()));
+ assertThat(resp.getAssertions().size(), is(1));
+ }
+
+ @Test
+ public void testSaml20EncryptedAssertionsSignedTwoExtensionsReceivedWithRedirectBinding() throws Exception {
+ Element el;
+
+ InputStream st = SAMLParserTest.class.getResourceAsStream("saml20-encrypted-signed-redirect-response-two-extensions.xml");
+ SAMLParser parser = new SAMLParser();
+
+ Object parsedObject = parser.parse(st);
+ assertThat(parsedObject, instanceOf(ResponseType.class));
+
+ ResponseType resp = (ResponseType) parsedObject;
+ assertThat(resp.getSignature(), nullValue());
+ assertThat(resp.getConsent(), nullValue());
+ assertThat(resp.getIssuer(), not(nullValue()));
+ assertThat(resp.getIssuer().getValue(), is("http://localhost:8081/auth/realms/saml-demo"));
+
+ assertThat(resp.getExtensions(), not(nullValue()));
+ assertThat(resp.getExtensions().getAny().size(), is(2));
+ assertThat(resp.getExtensions().getAny().get(0), instanceOf(Element.class));
+ el = (Element) resp.getExtensions().getAny().get(0);
+ assertThat(el.getLocalName(), is("KeyInfo"));
+ assertThat(el.getNamespaceURI(), is("urn:keycloak:ext:key:1.0"));
+ assertThat(el.hasAttribute("MessageSigningKeyId"), is(true));
+ assertThat(el.getAttribute("MessageSigningKeyId"), is("FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"));
+ assertThat(resp.getExtensions().getAny().get(1), instanceOf(Element.class));
+ el = (Element) resp.getExtensions().getAny().get(1);
+ assertThat(el.getLocalName(), is("ever"));
+ assertThat(el.getNamespaceURI(), is("urn:keycloak:ext:what:1.0"));
+ assertThat(el.hasAttribute("what"), is(true));
+ assertThat(el.getAttribute("what"), is("ever"));
+
+ assertThat(resp.getAssertions(), not(nullValue()));
+ assertThat(resp.getAssertions().size(), is(1));
+ }
+
+ @Test
+ public void testSaml20PostLogoutRequest() throws Exception {
+ InputStream st = SAMLParserTest.class.getResourceAsStream("saml20-signed-logout-request.xml");
+ SAMLParser parser = new SAMLParser();
+
+ Object parsedObject = parser.parse(st);
+ assertThat(parsedObject, instanceOf(LogoutRequestType.class));
+
+ }
+}
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response.xml
new file mode 100644
index 0000000..d8d4c15
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response.xml
@@ -0,0 +1,29 @@
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Destination="http://localhost:8080/sales-post-enc/saml" ID="ID_0b43d444-d1a8-44a5-8caf-38e176489e1f" InResponseTo="ID_223d3591-22fb-4b3c-9e38-4719293b2d94" IssueInstant="2016-11-01T13:52:43.054Z" Version="2.0">
+ <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://localhost:8081/auth/realms/saml-demo</saml:Issuer>
+ <samlp:Extensions>
+ <kckey:KeyInfo xmlns:kckey="urn:keycloak:ext:key:1.0" MessageSigningKeyId="FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"/>
+ </samlp:Extensions>
+ <samlp:Status>
+ <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+ </samlp:Status>
+ <saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+ <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
+ <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+ <xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
+ <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
+ <xenc:CipherData>
+ <xenc:CipherValue>
+ OkvZTx/ifYLef74rY0F9I8lbJaatgSEguo+zwh5JrYWcO09Ib2gtz5+z+67Is2+wk/OzKp154r8qAI5vY9AYvuXCslKL/wbcZ1UILL78F0T/iiUW3VpWy8Wvz5nezBFPRqot8WiFQykByjlBg1Z8XOts+uIdyqBBi/WjYeJGMaQ=
+ </xenc:CipherValue>
+ </xenc:CipherData>
+ </xenc:EncryptedKey>
+ </ds:KeyInfo>
+ <xenc:CipherData>
+ <xenc:CipherValue>
+ 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
+ </xenc:CipherValue>
+ </xenc:CipherData>
+ </xenc:EncryptedData>
+ </saml:EncryptedAssertion>
+</samlp:Response>
\ No newline at end of file
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response-two-extensions.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response-two-extensions.xml
new file mode 100644
index 0000000..94a6fdb
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response-two-extensions.xml
@@ -0,0 +1,30 @@
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Destination="http://localhost:8080/sales-post-enc/saml" ID="ID_0b43d444-d1a8-44a5-8caf-38e176489e1f" InResponseTo="ID_223d3591-22fb-4b3c-9e38-4719293b2d94" IssueInstant="2016-11-01T13:52:43.054Z" Version="2.0">
+ <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://localhost:8081/auth/realms/saml-demo</saml:Issuer>
+ <samlp:Extensions>
+ <kckey:KeyInfo xmlns:kckey="urn:keycloak:ext:key:1.0" MessageSigningKeyId="FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"/>
+ <what:ever xmlns:what="urn:keycloak:ext:what:1.0" what="ever"/>
+ </samlp:Extensions>
+ <samlp:Status>
+ <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+ </samlp:Status>
+ <saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+ <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
+ <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+ <xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
+ <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
+ <xenc:CipherData>
+ <xenc:CipherValue>
+ OkvZTx/ifYLef74rY0F9I8lbJaatgSEguo+zwh5JrYWcO09Ib2gtz5+z+67Is2+wk/OzKp154r8qAI5vY9AYvuXCslKL/wbcZ1UILL78F0T/iiUW3VpWy8Wvz5nezBFPRqot8WiFQykByjlBg1Z8XOts+uIdyqBBi/WjYeJGMaQ=
+ </xenc:CipherValue>
+ </xenc:CipherData>
+ </xenc:EncryptedKey>
+ </ds:KeyInfo>
+ <xenc:CipherData>
+ <xenc:CipherValue>
+ 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
+ </xenc:CipherValue>
+ </xenc:CipherData>
+ </xenc:EncryptedData>
+ </saml:EncryptedAssertion>
+</samlp:Response>
\ No newline at end of file
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-signed-logout-request.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-signed-logout-request.xml
new file mode 100644
index 0000000..8c4ab20
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-signed-logout-request.xml
@@ -0,0 +1,32 @@
+<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Destination="http://localhost:8081/auth/realms/saml-demo/protocol/saml" ID="ID_4790c6a3-4b9f-4c0a-a368-5c0e498544e4" IssueInstant="2016-11-01T14:36:43.194Z" Version="2.0">
+ <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://localhost:8080/sales-post-enc/</saml:Issuer>
+ <dsig:Signature xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
+ <dsig:SignedInfo>
+ <dsig:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+ <dsig:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
+ <dsig:Reference URI="#ID_4790c6a3-4b9f-4c0a-a368-5c0e498544e4">
+ <dsig:Transforms>
+ <dsig:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
+ <dsig:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+ </dsig:Transforms>
+ <dsig:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
+ <dsig:DigestValue>zeWNo5eav5tFOOCEJ1YU9eINkPnBSfixzAr8AOC4R4c=</dsig:DigestValue>
+ </dsig:Reference>
+ </dsig:SignedInfo>
+ <dsig:SignatureValue>
+ pyOiS1LsV/XR08zhcN6IqSYuKTDln4otmCvZxCc07ORP1C9jragu8V8rEE09qt/zBcdw7Arb8eLNNC6oCnrnMxuvzRInVTwt7T5K3t0UlzRWOb3HMElhcWFEgDzh6uKw5Cr45A01XNpojtJWCML/qU2Enyyy80FBlCJNcbzyLxE=
+ </dsig:SignatureValue>
+ <dsig:KeyInfo>
+ <dsig:KeyValue>
+ <dsig:RSAKeyValue>
+ <dsig:Modulus>
+ 2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEik=
+ </dsig:Modulus>
+ <dsig:Exponent>AQAB</dsig:Exponent>
+ </dsig:RSAKeyValue>
+ </dsig:KeyValue>
+ </dsig:KeyInfo>
+ </dsig:Signature>
+ <saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">bburke</saml:NameID>
+ <samlp:SessionIndex>a3b2df1c-1095-487b-8b56-f62818c449e3</samlp:SessionIndex>
+</samlp:LogoutRequest>