[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