[java-opensaml] 09/16: WIP on artifact decoder and SAML SOAP client context builder.
Brent Putman
putmanb at georgetown.edu
Sun Dec 17 00:08:19 EST 2017
This is an automated email from the git hooks/post-receive script.
putmanb pushed a commit to branch master
in repository java-opensaml.
View the commit online:
http://git.shibboleth.net/view/?p=java-opensaml.git;a=commit;h=5ac542e138f61f9a4af61020ed3c9e84dbc663da
commit 5ac542e138f61f9a4af61020ed3c9e84dbc663da
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Sun Dec 10 17:07:53 2017 -0500
WIP on artifact decoder and SAML SOAP client context builder.
---
.../messaging/SAMLSOAPClientContextBuilder.java | 269 +++++++++++++++++++++
.../binding/decoding/impl/HTTPArtifactDecoder.java | 85 ++++---
2 files changed, 324 insertions(+), 30 deletions(-)
diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/common/messaging/SAMLSOAPClientContextBuilder.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/common/messaging/SAMLSOAPClientContextBuilder.java
new file mode 100644
index 0000000..d35b6a1
--- /dev/null
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/common/messaging/SAMLSOAPClientContextBuilder.java
@@ -0,0 +1,269 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.opensaml.saml.common.messaging;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.xml.namespace.QName;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.messaging.MessageException;
+import org.opensaml.messaging.context.InOutOperationContext;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.saml.common.SAMLObject;
+import org.opensaml.saml.common.messaging.context.SAMLMetadataContext;
+import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
+import org.opensaml.saml.common.messaging.context.SAMLSelfEntityContext;
+import org.opensaml.saml.saml2.metadata.EntityDescriptor;
+import org.opensaml.saml.saml2.metadata.RoleDescriptor;
+
+//TODO when impl finished, document required vs optional data and derivation rules
+
+/**
+ * Builder {@link InOutOperationContext} instances for SAML SOAP client use cases.
+ *
+ * @param <InboundMessageType> the inbound message type
+ * @param <OutboundMessageType> the outbound message type
+ */
+public class SAMLSOAPClientContextBuilder<InboundMessageType extends SAMLObject,
+ OutboundMessageType extends SAMLObject> {
+
+ /** The outbound message. **/
+ private OutboundMessageType outboundMessage;
+
+ /** The SAML self entityID. **/
+ private String selfEntityID;
+
+ /** The SAML peer entityID. **/
+ private String peerEntityID;
+
+ /** The SAML peer entity roles. **/
+ private QName peerEntityRole;
+
+ /** The SAML peer EntityDescriptor. **/
+ private EntityDescriptor peerEntityDescriptor;
+
+ /** The SAML peer RoleDescriptor. **/
+ private RoleDescriptor peerRoleDescriptor;
+
+ /**
+ * Get the outbound message.
+ *
+ * @return the outbound message
+ */
+ @Nullable public OutboundMessageType getOutboundMessage() {
+ return outboundMessage;
+ }
+
+ /**
+ * Set the outbound message.
+ *
+ * @param message the outbound message
+ * @return this builder instance
+ */
+ @Nonnull public SAMLSOAPClientContextBuilder<InboundMessageType, OutboundMessageType> setOutboundMessage(
+ final OutboundMessageType message) {
+ outboundMessage = message;
+ return this;
+ }
+
+ /**
+ * Get the SAML self entityID.
+ *
+ * @return the SAML self entityID
+ */
+ @Nullable public String getSelfEntityID() {
+ return selfEntityID;
+ }
+
+ /**
+ * Set the SAML self entityID.
+ *
+ * @param entityID the SAML self entityID.
+ * @return this builder instance
+ */
+ @Nonnull public SAMLSOAPClientContextBuilder<InboundMessageType, OutboundMessageType> setSelfEntityID(
+ final String entityID) {
+ selfEntityID = entityID;
+ return this;
+ }
+
+ /**
+ * Get the SAML peer entityID.
+ *
+ * @return the SAML peer entityID
+ */
+ @Nullable public String getPeerEntityID() {
+ if (peerEntityID != null) {
+ return peerEntityID;
+ } else if (getPeerEntityDescriptor() != null) {
+ return getPeerEntityDescriptor().getEntityID();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Set the SAML peer entityID.
+ *
+ * @param entityID the SAML peer entityID
+ * @return this builder instance
+ */
+ @Nonnull public SAMLSOAPClientContextBuilder<InboundMessageType, OutboundMessageType> setPeerEntityID(
+ final String entityID) {
+ peerEntityID = entityID;
+ return this;
+ }
+
+ /**
+ * Get the SAML peer role.
+ *
+ * @return the SAML peer role
+ */
+ @Nullable public QName getPeerEntityRole() {
+ if (peerEntityRole != null) {
+ return peerEntityRole;
+ } else if (getPeerRoleDescriptor() != null) {
+ if (getPeerRoleDescriptor().getSchemaType() != null) {
+ return getPeerRoleDescriptor().getSchemaType();
+ } else {
+ return getPeerRoleDescriptor().getElementQName();
+ }
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Set the SAML peer role.
+ *
+ * @param role the SAML peer role
+ * @return this builder instance
+ */
+ @Nonnull public SAMLSOAPClientContextBuilder<InboundMessageType, OutboundMessageType> setPeerEntityRole(
+ final QName role) {
+ peerEntityRole = role;
+ return this;
+ }
+
+ /**
+ * Get the SAML peer EntityDscriptor.
+ *
+ * @return the SAML peer EntityDescriptor
+ */
+ @Nullable public EntityDescriptor getPeerEntityDescriptor() {
+ if (peerEntityDescriptor != null) {
+ return peerEntityDescriptor;
+ } else if (getPeerRoleDescriptor() != null) {
+ final XMLObject roleParent = getPeerRoleDescriptor().getParent();
+ if (roleParent instanceof EntityDescriptor) {
+ return (EntityDescriptor) roleParent;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Set the SAML peer EntityDescriptor.
+ *
+ * @param entityDescriptor the SAML peer EntityDescriptor
+ * @return this builder instance
+ */
+ @Nonnull public SAMLSOAPClientContextBuilder<InboundMessageType, OutboundMessageType> setPeerEntityDescriptor(
+ final EntityDescriptor entityDescriptor) {
+ peerEntityDescriptor = entityDescriptor;
+ return this;
+ }
+
+ /**
+ * Get the SAML peer RoleDescriptor.
+ *
+ * @return the SAML peer RoleDescriptor
+ */
+ @Nullable public RoleDescriptor getPeerRoleDescriptor() {
+ return peerRoleDescriptor;
+ }
+
+ /**
+ * Set the SAML peer RoleDescriptor.
+ *
+ * @param roleDescriptor the SAML peer RoleDescriptor.
+ * @return this builder instance
+ */
+ @Nonnull public SAMLSOAPClientContextBuilder<InboundMessageType, OutboundMessageType> setPeerRoleDescriptor(
+ final RoleDescriptor roleDescriptor) {
+ peerRoleDescriptor = roleDescriptor;
+ return this;
+ }
+
+ /**
+ * Build the new operation context.
+ *
+ * @return the operation context
+ *
+ * @throws MessageException if any required data is not supplied and can not be derived from other supplied data
+ */
+ public InOutOperationContext<InboundMessageType, OutboundMessageType> build() throws MessageException {
+ if (getOutboundMessage() == null) {
+ errorMissingData("Outbound message");
+ }
+ final MessageContext<OutboundMessageType> outboundContext = new MessageContext<OutboundMessageType>();
+ outboundContext.setMessage(getOutboundMessage());
+
+ final InOutOperationContext<InboundMessageType, OutboundMessageType> opContext =
+ new InOutOperationContext<>(null, outboundContext);
+
+ //TODO is this required always?
+ final String selfID = getSelfEntityID();
+ if (selfID != null) {
+ final SAMLSelfEntityContext selfContext = opContext.getSubcontext(SAMLSelfEntityContext.class, true);
+ selfContext.setEntityId(selfID);
+ }
+
+ // Both of these required, either supplied or derived
+ final String peerID = getPeerEntityID();
+ if (peerID == null) {
+ errorMissingData("Peer entityID");
+ }
+ final QName peerRoleName = getPeerEntityRole();
+ if (peerRoleName == null) {
+ errorMissingData("Peer role");
+ }
+ final SAMLPeerEntityContext peerContext = opContext.getSubcontext(SAMLPeerEntityContext.class, true);
+ peerContext.setEntityId(peerID);
+ peerContext.setRole(peerRoleName);
+
+ // Both optional, could be resolved in SOAP handling pipeline by handler(s)
+ final SAMLMetadataContext metadataContext = peerContext.getSubcontext(SAMLMetadataContext.class, true);
+ metadataContext.setEntityDescriptor(getPeerEntityDescriptor());
+ metadataContext.setRoleDescriptor(getPeerRoleDescriptor());
+
+ return opContext;
+ }
+
+ /**
+ * Convenience method to report out an error due to missing required data.
+ *
+ * @param details the error details
+ * @throws MessageException the error to be reported out
+ */
+ private void errorMissingData(@Nonnull final String details) throws MessageException {
+ throw new MessageException("Required context data was not supplied or derivable: " + details);
+ }
+
+}
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/binding/decoding/impl/HTTPArtifactDecoder.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/binding/decoding/impl/HTTPArtifactDecoder.java
index 50a451f..edc73fc 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/binding/decoding/impl/HTTPArtifactDecoder.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/binding/decoding/impl/HTTPArtifactDecoder.java
@@ -26,6 +26,7 @@ import javax.xml.namespace.QName;
import org.joda.time.DateTime;
import org.joda.time.chrono.ISOChronology;
import org.opensaml.core.xml.util.XMLObjectSupport;
+import org.opensaml.messaging.MessageException;
import org.opensaml.messaging.context.InOutOperationContext;
import org.opensaml.messaging.context.MessageContext;
import org.opensaml.messaging.decoder.MessageDecodingException;
@@ -37,6 +38,7 @@ import org.opensaml.saml.common.binding.SAMLBindingSupport;
import org.opensaml.saml.common.binding.artifact.SAMLSourceLocationArtifact;
import org.opensaml.saml.common.binding.decoding.SAMLMessageDecoder;
import org.opensaml.saml.common.binding.impl.DefaultEndpointResolver;
+import org.opensaml.saml.common.messaging.SAMLSOAPClientContextBuilder;
import org.opensaml.saml.common.messaging.context.SAMLBindingContext;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.config.SAMLConfigurationSupport;
@@ -343,7 +345,7 @@ public class HTTPArtifactDecoder extends BaseHttpServletRequestXMLMessageDecoder
if (peerRoleDescriptor == null) {
throw new MessageDecodingException("Failed to resolve peer RoleDescriptor based on inbound artifact");
}
-
+
final ArtifactResolutionService ars = resolveArtifactEndpoint(artifact, peerRoleDescriptor);
final SAMLObject inboundMessage = dereferenceArtifact(artifact, peerRoleDescriptor, ars);
@@ -362,36 +364,41 @@ public class HTTPArtifactDecoder extends BaseHttpServletRequestXMLMessageDecoder
* @param ars
* @return
*/
- private SAMLObject dereferenceArtifact(final SAML2Artifact artifact, final RoleDescriptor peerRoleDescriptor,
- final ArtifactResolutionService ars)
- throws MessageDecodingException {
+ private SAMLObject dereferenceArtifact(final SAML2Artifact artifact, final RoleDescriptor peerRoleDescriptor,
+ final ArtifactResolutionService ars) throws MessageDecodingException {
- final MessageContext<SAMLObject> outbound = new MessageContext<>();
- outbound.setMessage(buildArtifactResolveRequestMessage(artifact, ars.getLocation(), peerRoleDescriptor));
- //TODO more population of context
- // - signing params
- // - client TLS params
- // - setting up stuff for handling response
- //TODO what components needed to support signing and client TLS, and how do we get them?
- //TODO probably support optional static injected creds and params, as well as injected resolution strategies
+ try {
+ final String selfEntityID = resolveSelfEntityID(peerRoleDescriptor);
- final InOutOperationContext<SAMLObject, SAMLObject> opContext = new InOutOperationContext<>(null, outbound);
+ // TODO can assume/enforce response as ArtifactResponse here?
+ final InOutOperationContext<SAMLObject, ArtifactResolve> opContext = new SAMLSOAPClientContextBuilder()
+ .setOutboundMessage(buildArtifactResolveRequestMessage(
+ artifact, ars.getLocation(), peerRoleDescriptor, selfEntityID))
+ .setPeerRoleDescriptor(peerRoleDescriptor)
+ .setSelfEntityID(selfEntityID)
+ .build();
- try {
log.trace("Executing ArtifactResolve over SOAP 1.1 binding to endpoint: {}", ars.getLocation());
soapClient.send(ars.getLocation(), opContext);
- SAMLObject response = opContext.getInboundMessageContext().getMessage();
+ final SAMLObject response = opContext.getInboundMessageContext().getMessage();
if (response instanceof ArtifactResponse) {
return validateAndExtractResponseMessage((ArtifactResponse) response);
} else {
- throw new MessageDecodingException("SOAP message payload was not an instance of ArtifactResponse: " + response.getClass().getName());
+ throw new MessageDecodingException("SOAP message payload was not an instance of ArtifactResponse: "
+ + response.getClass().getName());
}
- } catch (final SOAPException | SecurityException e) {
+ } catch (final MessageException | SOAPException | SecurityException e) {
throw new MessageDecodingException("Error dereferencing artifact", e);
}
}
- private SAMLObject validateAndExtractResponseMessage(ArtifactResponse artifactResponse) throws MessageDecodingException {
+ /**
+ * @param artifactResponse
+ * @return
+ * @throws MessageDecodingException
+ */
+ private SAMLObject validateAndExtractResponseMessage(@Nonnull final ArtifactResponse artifactResponse)
+ throws MessageDecodingException {
if (artifactResponse.getStatus() == null
|| artifactResponse.getStatus().getStatusCode() == null
|| artifactResponse.getStatus().getStatusCode().getValue() == null) {
@@ -414,10 +421,11 @@ public class HTTPArtifactDecoder extends BaseHttpServletRequestXMLMessageDecoder
* @param artifact
* @param endpoint
* @param peerRoleDescriptor
+ * @param selfEntityID
* @return
*/
private ArtifactResolve buildArtifactResolveRequestMessage(final SAML2Artifact artifact, final String endpoint,
- final RoleDescriptor peerRoleDescriptor) {
+ final RoleDescriptor peerRoleDescriptor, final String selfEntityID) {
final ArtifactResolve request =
(ArtifactResolve) XMLObjectSupport.buildXMLObject(ArtifactResolve.DEFAULT_ELEMENT_NAME);
@@ -428,20 +436,27 @@ public class HTTPArtifactDecoder extends BaseHttpServletRequestXMLMessageDecoder
request.setID(idStrategy.generateIdentifier(true));
request.setDestination(endpoint);
request.setIssueInstant(new DateTime(ISOChronology.getInstanceUTC()));
- request.setIssuer(buildIssuer(peerRoleDescriptor));
+ request.setIssuer(buildIssuer(selfEntityID));
return request;
}
/**
+ * @param peerRoleDescriptor
+ * @return
+ */
+ private String resolveSelfEntityID(RoleDescriptor peerRoleDescriptor) throws MessageDecodingException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ /**
* @param peerRoleDescriptor
* @return
*/
- private Issuer buildIssuer(final RoleDescriptor peerRoleDescriptor) {
+ private Issuer buildIssuer(final String selfEntityID) {
final Issuer issuer = (Issuer) XMLObjectSupport.buildXMLObject(Issuer.DEFAULT_ELEMENT_NAME);
- //TODO how do we get our own entityID?
- // probably support optional static injected self entityID as well as injected resolution strategy
- //issuer.setValue("TODO");
+ issuer.setValue(selfEntityID);
return issuer;
}
@@ -450,8 +465,8 @@ public class HTTPArtifactDecoder extends BaseHttpServletRequestXMLMessageDecoder
* @param peerRoleDescriptor
* @return
*/
- private ArtifactResolutionService resolveArtifactEndpoint(final SAML2Artifact artifact,
- final RoleDescriptor peerRoleDescriptor) throws MessageDecodingException {
+ private ArtifactResolutionService resolveArtifactEndpoint(@Nonnull final SAML2Artifact artifact,
+ @Nonnull final RoleDescriptor peerRoleDescriptor) throws MessageDecodingException {
final RoleDescriptorCriterion roleDescriptorCriterion = new RoleDescriptorCriterion(peerRoleDescriptor);
final ArtifactResolutionService arsTemplate =
@@ -488,14 +503,19 @@ public class HTTPArtifactDecoder extends BaseHttpServletRequestXMLMessageDecoder
* @param artifact
* @return
*/
- private RoleDescriptor resolvePeerRoleDescriptor(final SAML2Artifact artifact) throws MessageDecodingException {
+ @Nonnull private RoleDescriptor resolvePeerRoleDescriptor(@Nonnull final SAML2Artifact artifact)
+ throws MessageDecodingException {
final CriteriaSet criteriaSet = new CriteriaSet(
new ArtifactCriterion(artifact),
new ProtocolCriterion(SAMLConstants.SAML20P_NS),
new EntityRoleCriterion(getPeerEntityRole()));
try {
- return roleDescriptorResolver.resolveSingle(criteriaSet);
+ RoleDescriptor rd = roleDescriptorResolver.resolveSingle(criteriaSet);
+ if (rd == null) {
+ throw new MessageDecodingException("Unable to resolve peer RoleDescriptor from supplied artifact");
+ }
+ return rd;
} catch (final ResolverException e) {
throw new MessageDecodingException("Error resolving peer entity RoleDescriptor", e);
}
@@ -505,8 +525,13 @@ public class HTTPArtifactDecoder extends BaseHttpServletRequestXMLMessageDecoder
* @param encodedArtifact
* @return
*/
- private SAML2Artifact parseArtifact(final String encodedArtifact) throws MessageDecodingException {
- return artifactBuilderFactory.buildArtifact(encodedArtifact);
+ @Nonnull private SAML2Artifact parseArtifact(@Nonnull final String encodedArtifact) throws MessageDecodingException {
+ //TODO not sure if this handles well bad input. Determine if can throw an unchecked and handle here.
+ SAML2Artifact artifact = artifactBuilderFactory.buildArtifact(encodedArtifact);
+ if (artifact == null) {
+ throw new MessageDecodingException("Could not build SAML2Artifact instance from encoded artifact");
+ }
+ return artifact;
}
/**
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list