[java-opensaml] 02/04: Update SAMLMetadataLookupHandler to support copy from existing context.

Brent Putman putmanb at georgetown.edu
Sat Feb 1 00:32:10 EST 2020


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=3ad50ca8b0477fb569ff764539fc4563dc50b736

commit 3ad50ca8b0477fb569ff764539fc4563dc50b736
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Fri Jan 31 18:54:05 2020 -0500

    Update SAMLMetadataLookupHandler to support copy from existing context.
---
 .../binding/impl/SAMLMetadataLookupHandler.java    | 114 ++++++++++++++--
 .../impl/SAMLMetadataLookupHandlerTest.java        | 152 +++++++++++++++++++++
 2 files changed, 251 insertions(+), 15 deletions(-)

diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/impl/SAMLMetadataLookupHandler.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/impl/SAMLMetadataLookupHandler.java
index 87d295f..f2288be 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/impl/SAMLMetadataLookupHandler.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/common/binding/impl/SAMLMetadataLookupHandler.java
@@ -17,7 +17,12 @@
 
 package org.opensaml.saml.common.binding.impl;
 
+import java.util.Objects;
+import java.util.function.Function;
+
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.xml.namespace.QName;
 
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
@@ -44,13 +49,25 @@ import org.slf4j.LoggerFactory;
 
 /**
  * Handler for inbound SAML protocol messages that attempts to locate SAML metadata for
- * a SAML entity, and attaches it with a {@link SAMLMetadataContext} as a child of a 
- * pre-existing concrete instance of {@link AbstractSAMLEntityContext}. The entity context class is configurable
- * and defaults to {@link SAMLPeerEntityContext}.
+ * a SAML entity, and attaches it as a {@link SAMLMetadataContext} child of a
+ * pre-existing concrete instance of {@link AbstractSAMLEntityContext}.
+ *
+ * <p>
+ * The entity context class is configurable and defaults to {@link SAMLPeerEntityContext}.
+ * The handler will no-op in the absence of an existing {@link AbstractSAMLEntityContext}
+ * child of the message context with non-null values for both entityID and role.
+ * </p>
  * 
- * <p>The handler will no-op in the absence of a populated {@link AbstractSAMLEntityContext} instance 
- * with an entityID and role to look up. A protocol from a {@link SAMLProtocolContext}
- * will be added to the lookup, if available.</p>
+ * <p>
+ * If the optional copy strategy is configured via {@link #setCopyContextStrategy(Function)},
+ * and if that lookup finds an existing metadata context with compatible data (matching entityID and role),
+ * then its data will be re-used.
+ * </p>
+ *
+ * <p>
+ * Otherwise an attempt to resolve metadata will be performed with the configured {@link RoleDescriptorResolver}.
+ * A protocol from a {@link SAMLProtocolContext} will be added to the lookup, if available.
+ * </p>
  */
 public class SAMLMetadataLookupHandler extends AbstractMessageHandler {
     
@@ -63,7 +80,21 @@ public class SAMLMetadataLookupHandler extends AbstractMessageHandler {
     /** The context class representing the SAML entity whose data is to be resolved. 
      * Defaults to: {@link SAMLPeerEntityContext}. */
     @Nonnull private Class<? extends AbstractSAMLEntityContext> entityContextClass = SAMLPeerEntityContext.class;
-    
+
+    /** Optional strategy for resolving an existing metadata context from which to copy data. */
+    @Nullable private Function<MessageContext, SAMLMetadataContext> copyContextStrategy;
+
+    /**
+     * Set the optional strategy for resolving an existing metadata context from which to copy data.
+     *
+     * @param strategy the strategy function
+     */
+    public void setCopyContextStrategy(@Nullable final Function<MessageContext, SAMLMetadataContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        copyContextStrategy = strategy;
+    }
+
     /**
      * Set the class type holding the SAML entity data.
      * 
@@ -98,25 +129,32 @@ public class SAMLMetadataLookupHandler extends AbstractMessageHandler {
         }
     }
 
-// Checkstyle: ReturnCount OFF
     /** {@inheritDoc} */
     @Override
     protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
 
         final AbstractSAMLEntityContext entityCtx = messageContext.getSubcontext(entityContextClass);
-        final SAMLProtocolContext protocolCtx = messageContext.getSubcontext(SAMLProtocolContext.class);
-     
+
         if (entityCtx == null || entityCtx.getEntityId() == null || entityCtx.getRole() == null) {
             log.info("{} SAML entity context class '{}' missing or did not contain an entityID or role", getLogPrefix(),
                     entityContextClass.getName());
             return;
         }
-        
+
+        final SAMLMetadataContext existingMetadataCtx = resolveExisting(messageContext,
+                entityCtx.getEntityId(), entityCtx.getRole());
+        if (existingMetadataCtx != null) {
+            log.info("{} Resolved existing metadata context, re-using it", getLogPrefix());
+            entityCtx.addSubcontext(existingMetadataCtx);
+            return;
+        }
+
         final EntityIdCriterion entityIdCriterion = new EntityIdCriterion(entityCtx.getEntityId());
         final EntityRoleCriterion roleCriterion = new EntityRoleCriterion(entityCtx.getRole());
         
         ProtocolCriterion protocolCriterion = null;
+        final SAMLProtocolContext protocolCtx = messageContext.getSubcontext(SAMLProtocolContext.class);
         if (protocolCtx != null && protocolCtx.getProtocol() != null) {
             protocolCriterion = new ProtocolCriterion(protocolCtx.getProtocol());
         }
@@ -127,11 +165,11 @@ public class SAMLMetadataLookupHandler extends AbstractMessageHandler {
             if (roleMetadata == null) {
                 if (protocolCriterion != null) {
                     log.info("{} No metadata returned for {} in role {} with protocol {}",
-                            new Object[]{getLogPrefix(), entityCtx.getEntityId(), entityCtx.getRole(),
-                                protocolCriterion.getProtocol(),});
+                            getLogPrefix(), entityCtx.getEntityId(), entityCtx.getRole(),
+                            protocolCriterion.getProtocol());
                 } else {
                     log.info("{} No metadata returned for {} in role {}",
-                            new Object[]{getLogPrefix(), entityCtx.getEntityId(), entityCtx.getRole(),});
+                            getLogPrefix(), entityCtx.getEntityId(), entityCtx.getRole());
                 }
                 return;
             }
@@ -148,6 +186,52 @@ public class SAMLMetadataLookupHandler extends AbstractMessageHandler {
             log.error("{} ResolverException thrown during metadata lookup", getLogPrefix(), e);
         }
     }
-// Checkstyle: ReturnCount OFF
+
+    /**
+     * Attempt to resolve an existing {@link SAMLMetadataContext} from which to copy.
+     *
+     * <p>
+     * The returned context will always be a fresh parent-less instance, suitable for the caller to
+     * directly store in the current message context.
+     * </p>
+     *
+     * @param messageContext the current message context
+     * @param entityID the entityID against which to match
+     * @param role the entity role against which to match
+     *
+     * @return a new instance of {@link SAMLMetadataContext}, or null if one can not be resolved
+     */
+    @Nullable protected SAMLMetadataContext resolveExisting(@Nonnull final MessageContext messageContext,
+            @Nonnull final String entityID, @Nonnull final QName role) {
+
+        if (copyContextStrategy == null) {
+            return null;
+        }
+
+        final SAMLMetadataContext existing = copyContextStrategy.apply(messageContext);
+        if (existing != null) {
+            if (existing.getEntityDescriptor() != null && existing.getRoleDescriptor() != null) {
+                // Validate that existing data has the same entityID and role
+                if (Objects.equals(existing.getEntityDescriptor().getEntityID(), entityID)
+                        && (Objects.equals(existing.getRoleDescriptor().getElementQName(), role)
+                                || Objects.equals(existing.getRoleDescriptor().getSchemaType(), role))
+                        ) {
+                    log.debug("{} Found an existing and suitable SAMLMetadataContext from which to copy ",
+                            getLogPrefix());
+                    final SAMLMetadataContext copy = new SAMLMetadataContext();
+                    copy.setEntityDescriptor(existing.getEntityDescriptor());
+                    copy.setRoleDescriptor(existing.getRoleDescriptor());
+                    return copy;
+                }
+                log.debug("{} Existing SAMLMetadataContext was resolved, but was either the entityID "
+                        + "or role did not match the entity context data", getLogPrefix());
+            }
+            log.debug("{} Existing SAMLMetadataContext was resolved, but was missing EntityDescriptor "
+                    + "or RoleDescriptor data", getLogPrefix());
+        } else {
+            log.debug("{} No existing SAMLMetadataContext was resolved", getLogPrefix());
+        }
+        return null;
+    }
 
 }
\ No newline at end of file
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/impl/SAMLMetadataLookupHandlerTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/impl/SAMLMetadataLookupHandlerTest.java
index a04b996..0a0cbb3 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/impl/SAMLMetadataLookupHandlerTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/common/binding/impl/SAMLMetadataLookupHandlerTest.java
@@ -20,6 +20,7 @@ package org.opensaml.saml.common.binding.impl;
 import java.io.File;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.util.function.Function;
 
 import org.opensaml.core.xml.XMLObjectBaseTestCase;
 import org.opensaml.messaging.context.MessageContext;
@@ -35,7 +36,9 @@ import org.opensaml.saml.saml1.core.AttributeQuery;
 import org.opensaml.saml.saml1.core.Request;
 import org.opensaml.saml.saml1.profile.SAML1ActionTestingSupport;
 import org.opensaml.saml.saml2.core.AuthnRequest;
+import org.opensaml.saml.saml2.metadata.EntityDescriptor;
 import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
+import org.opensaml.saml.saml2.metadata.RoleDescriptor;
 import org.opensaml.saml.saml2.metadata.SPSSODescriptor;
 import org.opensaml.saml.saml2.profile.SAML2ActionTestingSupport;
 import org.testng.Assert;
@@ -56,6 +59,11 @@ public class SAMLMetadataLookupHandlerTest extends XMLObjectBaseTestCase {
     private SAMLMetadataLookupHandler handler;
     private MessageContext messageContext;
     
+    private SAMLMetadataContext existingMetadataContext;
+    private EntityDescriptor existingEntityDescriptor;
+    private RoleDescriptor existingRoleDescriptor;
+    private Function<MessageContext, SAMLMetadataContext> copyContextStrategy;
+
     @BeforeClass
     public void classSetUp() throws ResolverException, URISyntaxException, ComponentInitializationException {
         final URL mdURL = SAMLMetadataLookupHandlerTest.class
@@ -75,6 +83,16 @@ public class SAMLMetadataLookupHandlerTest extends XMLObjectBaseTestCase {
     public void setUp() {
         handler = new SAMLMetadataLookupHandler();
         messageContext = new MessageContext();
+
+        existingEntityDescriptor = buildXMLObject(EntityDescriptor.DEFAULT_ELEMENT_NAME);
+        existingEntityDescriptor.setEntityID("urn:mace:incommon:osu.edu");
+        existingRoleDescriptor = buildXMLObject(IDPSSODescriptor.DEFAULT_ELEMENT_NAME);
+
+        existingMetadataContext = new SAMLMetadataContext();
+        existingMetadataContext.setEntityDescriptor(existingEntityDescriptor);
+        existingMetadataContext.setRoleDescriptor(existingRoleDescriptor);
+
+        copyContextStrategy = mc -> { return existingMetadataContext; };
     }
     
     @Test
@@ -202,4 +220,138 @@ public class SAMLMetadataLookupHandlerTest extends XMLObjectBaseTestCase {
         Assert.assertNotNull(mdCtx.getEntityDescriptor());
     }
     
+    @Test
+    public void testCopySuccess() throws ComponentInitializationException, MessageHandlerException {
+        handler.setRoleDescriptorResolver(roleResolver);
+        handler.setCopyContextStrategy(copyContextStrategy);
+        handler.initialize();
+
+        SAMLPeerEntityContext peerContext = messageContext.getSubcontext(SAMLPeerEntityContext.class, true);
+        peerContext.setRole(IDPSSODescriptor.DEFAULT_ELEMENT_NAME);
+        messageContext.getSubcontext(SAMLProtocolContext.class, true).setProtocol(SAMLConstants.SAML11P_NS);
+
+        Request request = SAML1ActionTestingSupport.buildAttributeQueryRequest(null);
+        ((AttributeQuery) request.getQuery()).setResource("urn:mace:incommon:osu.edu");
+        messageContext.setMessage(request);
+
+        handler.invoke(messageContext);
+
+        SAMLMetadataContext mdCtx = peerContext.getSubcontext(SAMLMetadataContext.class, false);
+        Assert.assertNotNull(mdCtx);
+        Assert.assertNotSame(mdCtx, existingMetadataContext);
+        Assert.assertNotNull(mdCtx.getRoleDescriptor());
+        Assert.assertSame(mdCtx.getRoleDescriptor(), existingMetadataContext.getRoleDescriptor());
+        Assert.assertNotNull(mdCtx.getEntityDescriptor());
+        Assert.assertSame(mdCtx.getEntityDescriptor(), existingMetadataContext.getEntityDescriptor());
+    }
+
+    @Test
+    public void testCopyFailMissingExistingEntityDescriptor() throws ComponentInitializationException, MessageHandlerException {
+        existingMetadataContext.setEntityDescriptor(null);
+
+        handler.setRoleDescriptorResolver(roleResolver);
+        handler.setCopyContextStrategy(copyContextStrategy);
+        handler.initialize();
+
+        SAMLPeerEntityContext peerContext = messageContext.getSubcontext(SAMLPeerEntityContext.class, true);
+        peerContext.setRole(IDPSSODescriptor.DEFAULT_ELEMENT_NAME);
+        messageContext.getSubcontext(SAMLProtocolContext.class, true).setProtocol(SAMLConstants.SAML11P_NS);
+
+        Request request = SAML1ActionTestingSupport.buildAttributeQueryRequest(null);
+        ((AttributeQuery) request.getQuery()).setResource("urn:mace:incommon:osu.edu");
+        messageContext.setMessage(request);
+
+        handler.invoke(messageContext);
+
+        SAMLMetadataContext mdCtx = peerContext.getSubcontext(SAMLMetadataContext.class, false);
+        Assert.assertNotNull(mdCtx);
+        Assert.assertNotSame(mdCtx, existingMetadataContext);
+        Assert.assertNotNull(mdCtx.getRoleDescriptor());
+        Assert.assertNotSame(mdCtx.getRoleDescriptor(), existingMetadataContext.getRoleDescriptor());
+        Assert.assertNotNull(mdCtx.getEntityDescriptor());
+        Assert.assertNotSame(mdCtx.getEntityDescriptor(), existingMetadataContext.getEntityDescriptor());
+    }
+
+    @Test
+    public void testCopyFailMissingExistingRoleDescriptor() throws ComponentInitializationException, MessageHandlerException {
+        existingMetadataContext.setRoleDescriptor(null);
+
+        handler.setRoleDescriptorResolver(roleResolver);
+        handler.setCopyContextStrategy(copyContextStrategy);
+        handler.initialize();
+
+        SAMLPeerEntityContext peerContext = messageContext.getSubcontext(SAMLPeerEntityContext.class, true);
+        peerContext.setRole(IDPSSODescriptor.DEFAULT_ELEMENT_NAME);
+        messageContext.getSubcontext(SAMLProtocolContext.class, true).setProtocol(SAMLConstants.SAML11P_NS);
+
+        Request request = SAML1ActionTestingSupport.buildAttributeQueryRequest(null);
+        ((AttributeQuery) request.getQuery()).setResource("urn:mace:incommon:osu.edu");
+        messageContext.setMessage(request);
+
+        handler.invoke(messageContext);
+
+        SAMLMetadataContext mdCtx = peerContext.getSubcontext(SAMLMetadataContext.class, false);
+        Assert.assertNotNull(mdCtx);
+        Assert.assertNotSame(mdCtx, existingMetadataContext);
+        Assert.assertNotNull(mdCtx.getRoleDescriptor());
+        Assert.assertNotSame(mdCtx.getRoleDescriptor(), existingMetadataContext.getRoleDescriptor());
+        Assert.assertNotNull(mdCtx.getEntityDescriptor());
+        Assert.assertNotSame(mdCtx.getEntityDescriptor(), existingMetadataContext.getEntityDescriptor());
+    }
+
+    @Test
+    public void testCopyFailEntityIDMismatch() throws ComponentInitializationException, MessageHandlerException {
+        existingEntityDescriptor.setEntityID("https://bogus.example.org");
+
+        handler.setRoleDescriptorResolver(roleResolver);
+        handler.setCopyContextStrategy(copyContextStrategy);
+        handler.initialize();
+
+        SAMLPeerEntityContext peerContext = messageContext.getSubcontext(SAMLPeerEntityContext.class, true);
+        peerContext.setRole(IDPSSODescriptor.DEFAULT_ELEMENT_NAME);
+        messageContext.getSubcontext(SAMLProtocolContext.class, true).setProtocol(SAMLConstants.SAML11P_NS);
+
+        Request request = SAML1ActionTestingSupport.buildAttributeQueryRequest(null);
+        ((AttributeQuery) request.getQuery()).setResource("urn:mace:incommon:osu.edu");
+        messageContext.setMessage(request);
+
+        handler.invoke(messageContext);
+
+        SAMLMetadataContext mdCtx = peerContext.getSubcontext(SAMLMetadataContext.class, false);
+        Assert.assertNotNull(mdCtx);
+        Assert.assertNotSame(mdCtx, existingMetadataContext);
+        Assert.assertNotNull(mdCtx.getRoleDescriptor());
+        Assert.assertNotSame(mdCtx.getRoleDescriptor(), existingMetadataContext.getRoleDescriptor());
+        Assert.assertNotNull(mdCtx.getEntityDescriptor());
+        Assert.assertNotSame(mdCtx.getEntityDescriptor(), existingMetadataContext.getEntityDescriptor());
+    }
+
+    @Test
+    public void testCopyFailRoleMismatch() throws ComponentInitializationException, MessageHandlerException {
+        existingMetadataContext.setRoleDescriptor(buildXMLObject(SPSSODescriptor.DEFAULT_ELEMENT_NAME));
+
+        handler.setRoleDescriptorResolver(roleResolver);
+        handler.setCopyContextStrategy(copyContextStrategy);
+        handler.initialize();
+
+        SAMLPeerEntityContext peerContext = messageContext.getSubcontext(SAMLPeerEntityContext.class, true);
+        peerContext.setRole(IDPSSODescriptor.DEFAULT_ELEMENT_NAME);
+        messageContext.getSubcontext(SAMLProtocolContext.class, true).setProtocol(SAMLConstants.SAML11P_NS);
+
+        Request request = SAML1ActionTestingSupport.buildAttributeQueryRequest(null);
+        ((AttributeQuery) request.getQuery()).setResource("urn:mace:incommon:osu.edu");
+        messageContext.setMessage(request);
+
+        handler.invoke(messageContext);
+
+        SAMLMetadataContext mdCtx = peerContext.getSubcontext(SAMLMetadataContext.class, false);
+        Assert.assertNotNull(mdCtx);
+        Assert.assertNotSame(mdCtx, existingMetadataContext);
+        Assert.assertNotNull(mdCtx.getRoleDescriptor());
+        Assert.assertNotSame(mdCtx.getRoleDescriptor(), existingMetadataContext.getRoleDescriptor());
+        Assert.assertNotNull(mdCtx.getEntityDescriptor());
+        Assert.assertNotSame(mdCtx.getEntityDescriptor(), existingMetadataContext.getEntityDescriptor());
+    }
+
+
 }
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list