[java-identity-provider] branch master updated: IDP-1217 Support the RequestedAttributes Extension

Rod Widdowson rdw at steadingsoftware.com
Sun Jun 17 09:36:11 EDT 2018


This is an automated email from the git hooks/post-receive script.

rdw pushed a commit to branch master
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=7f920f5012b5e980f0f446ccfce3775c5ba0c5e4

The following commit(s) were added to refs/heads/master by this push:
       new  7f920f5   IDP-1217 Support the RequestedAttributes Extension
7f920f5 is described below

commit 7f920f5012b5e980f0f446ccfce3775c5ba0c5e4
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Sun Jun 17 14:25:18 2018 +0100

    IDP-1217 Support the RequestedAttributes Extension
    
    https://issues.shibboleth.net/jira/browse/IDP-1217
    
    Featuring:
    - Web flow action to do the work
    - webflow wiring for same
    - Metadata generator changes to announce support.
---
 .../system/flows/saml/saml-abstract-beans.xml      |   6 +-
 .../system/flows/saml/saml-abstract-flow.xml       |   2 +
 .../installer/metadata/impl/MetadataGenerator.java |  17 +-
 ...estedAttributesToAttributeConsumingService.java | 251 +++++++++++++++++++++
 4 files changed, 272 insertions(+), 4 deletions(-)

diff --git a/idp-conf/src/main/resources/system/flows/saml/saml-abstract-beans.xml b/idp-conf/src/main/resources/system/flows/saml/saml-abstract-beans.xml
index 6a49b59..ee82526 100644
--- a/idp-conf/src/main/resources/system/flows/saml/saml-abstract-beans.xml
+++ b/idp-conf/src/main/resources/system/flows/saml/saml-abstract-beans.xml
@@ -73,7 +73,7 @@
             </bean>
         </constructor-arg>
     </bean>
-    
+
     <bean id="SAMLPopulateAttributeConsumingService"
             class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor" scope="prototype"
             c:executionDirection="INBOUND">
@@ -82,6 +82,10 @@
         </constructor-arg>
     </bean>
 
+    <bean id="AddRequestedAttributesToAttributeConsumingService" scope="prototype"
+            class="net.shibboleth.idp.saml.saml2.profile.impl.AddRequestedAttributesToAttributeConsumingService"
+            p:resolverService-ref="shibboleth.AttributeResolverService" />
+
     <bean id="InitializeRelyingPartyContexFromSAMLPeer"
         class="net.shibboleth.idp.saml.profile.impl.InitializeRelyingPartyContextFromSAMLPeer" scope="prototype" />
 
diff --git a/idp-conf/src/main/resources/system/flows/saml/saml-abstract-flow.xml b/idp-conf/src/main/resources/system/flows/saml/saml-abstract-flow.xml
index 5018f27..bade68c 100644
--- a/idp-conf/src/main/resources/system/flows/saml/saml-abstract-flow.xml
+++ b/idp-conf/src/main/resources/system/flows/saml/saml-abstract-flow.xml
@@ -1,3 +1,4 @@
+
 <flow xmlns="http://www.springframework.org/schema/webflow"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
@@ -17,6 +18,7 @@
         <evaluate expression="SAMLProtocolAndRole" />
         <evaluate expression="SAMLMetadataLookup" />
         <evaluate expression="SAMLPopulateAttributeConsumingService" />
+        <evaluate expression="AddRequestedAttributesToAttributeConsumingService" />
 
         <evaluate expression="InitializeRelyingPartyContexFromSAMLPeer" />
         <evaluate expression="SelectRelyingPartyConfiguration" />
diff --git a/idp-installer/src/main/java/net/shibboleth/idp/installer/metadata/impl/MetadataGenerator.java b/idp-installer/src/main/java/net/shibboleth/idp/installer/metadata/impl/MetadataGenerator.java
index 6c83119..3940299 100644
--- a/idp-installer/src/main/java/net/shibboleth/idp/installer/metadata/impl/MetadataGenerator.java
+++ b/idp-installer/src/main/java/net/shibboleth/idp/installer/metadata/impl/MetadataGenerator.java
@@ -33,6 +33,7 @@ import javax.annotation.Nullable;
 
 import org.opensaml.core.xml.LangBearing;
 import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.ext.reqattr.RequestedAttributes;
 import org.opensaml.saml.ext.saml2mdui.Description;
 import org.opensaml.saml.ext.saml2mdui.DisplayName;
 import org.opensaml.saml.ext.saml2mdui.Logo;
@@ -339,6 +340,7 @@ public class MetadataGenerator {
         writeNameSpace(ExtensionsConstants.SHIB_MDEXT10_PREFIX, ExtensionsConstants.SHIB_MDEXT10_NS);
         writeNameSpace(XMLConstants.XML_PREFIX, XMLConstants.XML_NS);
         writeNameSpace(SAMLConstants.SAML20MDUI_PREFIX, SAMLConstants.SAML20MDUI_NS);
+        writeNameSpace(SAMLConstants.SAML20PREQ_ATTRR_PREFIX, SAMLConstants.SAML20PREQ_ATTR_NS);
 
         writer.write(" validUntil=\"" + DOMTypeSupport.longToDateTime(System.currentTimeMillis()) + "\"");
         
@@ -809,7 +811,10 @@ public class MetadataGenerator {
                 writer.write(SingleSignOnService.DEFAULT_ELEMENT_LOCAL_NAME);
                 writer.write(" Binding=\"");
                 writer.write(SAMLConstants.SAML2_POST_BINDING_URI);
-                writer.write("\" Location=\"https://");
+                writer.write("\" ");
+                writeNameSpaceQualified(SAMLConstants.SAML20PREQ_ATTRR_PREFIX,
+                        RequestedAttributes.SUPPORTS_REQUESTED_ATTRIBUTES_LOCAL_NAME);
+                writer.write("=\"true\" Location=\"https://");
                 writer.write(getDNSName());
                 writer.write("/idp/profile/SAML2/POST/SSO\"/>");
                 writer.newLine();
@@ -821,7 +826,10 @@ public class MetadataGenerator {
                 writer.write(SingleSignOnService.DEFAULT_ELEMENT_LOCAL_NAME);
                 writer.write(" Binding=\"");
                 writer.write(SAMLConstants.SAML2_POST_SIMPLE_SIGN_BINDING_URI);
-                writer.write("\" Location=\"https://");
+                writer.write("\" ");
+                writeNameSpaceQualified(SAMLConstants.SAML20PREQ_ATTRR_PREFIX,
+                        RequestedAttributes.SUPPORTS_REQUESTED_ATTRIBUTES_LOCAL_NAME);
+                writer.write("=\"true\" Location=\"https://");
                 writer.write(getDNSName());
                 writer.write("/idp/profile/SAML2/POST-SimpleSign/SSO\"/>");
                 writer.newLine();
@@ -833,7 +841,10 @@ public class MetadataGenerator {
                 writer.write(SingleSignOnService.DEFAULT_ELEMENT_LOCAL_NAME);
                 writer.write(" Binding=\"");
                 writer.write(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
-                writer.write("\" Location=\"https://");
+                writer.write("\" ");
+                writeNameSpaceQualified(SAMLConstants.SAML20PREQ_ATTRR_PREFIX,
+                        RequestedAttributes.SUPPORTS_REQUESTED_ATTRIBUTES_LOCAL_NAME);
+                writer.write("=\"true\" Location=\"https://");
                 writer.write(getDNSName());
                 writer.write("/idp/profile/SAML2/Redirect/SSO\"/>");
                 writer.newLine();
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/AddRequestedAttributesToAttributeConsumingService.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/AddRequestedAttributesToAttributeConsumingService.java
new file mode 100644
index 0000000..5a34dca
--- /dev/null
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/AddRequestedAttributesToAttributeConsumingService.java
@@ -0,0 +1,251 @@
+/*
+ * 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 net.shibboleth.idp.saml.saml2.profile.impl;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.idp.attribute.resolver.AttributeResolver;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.idp.saml.metadata.impl.AttributeMappingNodeProcessor;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.service.ReloadableService;
+
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.core.xml.io.MarshallingException;
+import org.opensaml.core.xml.io.UnmarshallingException;
+import org.opensaml.core.xml.util.XMLObjectSupport;
+import org.opensaml.core.xml.util.XMLObjectSupport.CloneOutputOption;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.opensaml.saml.common.messaging.context.AttributeConsumingServiceContext;
+import org.opensaml.saml.common.messaging.context.SAMLMetadataContext;
+import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
+import org.opensaml.saml.ext.reqattr.RequestedAttributes;
+import org.opensaml.saml.metadata.resolver.filter.FilterException;
+import org.opensaml.saml.saml2.core.AuthnRequest;
+import org.opensaml.saml.saml2.core.Extensions;
+import org.opensaml.saml.saml2.metadata.AttributeConsumingService;
+import org.opensaml.saml.saml2.metadata.RequestedAttribute;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+/**
+ * Action that add any {@link RequestedAttribute}s to the previously populated {@link AttributeConsumingServiceContext}.
+ */
+public class AddRequestedAttributesToAttributeConsumingService extends AbstractProfileAction  {
+
+    /** Logger. */
+    @Nonnull private final Logger log =
+            LoggerFactory.getLogger(AddRequestedAttributesToAttributeConsumingService.class);
+
+    /** strategy to get the {@link AttributeConsumingServiceContext}. */
+    @Nonnull private Function<ProfileRequestContext, AttributeConsumingServiceContext>
+        attributeConsumingServiceContextLookupStrategy;
+
+    /** strategy to get the {@link AttributeConsumingServiceContext}. */
+    @Nonnull private Function<ProfileRequestContext, List<RequestedAttribute>>
+        requestedAttributesLookupStrategy;
+
+    /** The attribute resolver we use to map attributes. */
+    @Nullable private ReloadableService<AttributeResolver> attributeResolverService;
+
+    /** The context we use to get and put the {@link AttributeConsumingService}.*/
+    private AttributeConsumingServiceContext acsContext;
+
+    /** The {@link AttributeConsumingService}} we are going to clone.*/
+    private AttributeConsumingService oldACS;
+
+    /** Lookup strategy for an {@link AttributeConsumingService} index. */
+    @Nullable private Function<ProfileRequestContext,Integer> indexLookupStrategy;
+
+    /**
+     * Constructor.
+     */
+    public AddRequestedAttributesToAttributeConsumingService() {
+        super();
+        // At this point, by default  the SAMLMetadataContext hangs off the SAMLPeerContext
+        attributeConsumingServiceContextLookupStrategy =
+                Functions.compose(
+                         new ChildContextLookup(AttributeConsumingServiceContext.class),
+                         Functions.compose(
+                                 new ChildContextLookup<>(SAMLMetadataContext.class),
+                                 Functions.compose(
+                                        new ChildContextLookup<>(SAMLPeerEntityContext.class),
+                                        new InboundMessageContextLookup())));
+
+        requestedAttributesLookupStrategy = new RequestedAttributesLookup();
+        indexLookupStrategy = new AuthnRequestIndexLookup();
+    }
+
+    /**
+     * Set the strategy to locate the {@link AttributeConsumingServiceContext} from the {@link ProfileRequestContext}.
+     *
+     * @param strategy lookup strategy
+     */
+    public void setAttributeConsumingServiceContextLookupStrategy(@Nonnull final
+            Function<ProfileRequestContext,AttributeConsumingServiceContext> strategy) {
+        attributeConsumingServiceContextLookupStrategy = Constraint.isNotNull(strategy,
+                "AttributeConsumingServiceContext lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the strategy to locate the {@link AttributeConsumingServiceContext} from the {@link ProfileRequestContext}.
+     *
+     * @param strategy lookup strategy
+     */
+    public void setRequestedAttributesLookupStrategy(@Nonnull final
+            Function<ProfileRequestContext,List<RequestedAttribute>> strategy) {
+        requestedAttributesLookupStrategy = Constraint.isNotNull(strategy,
+                "RequestedAttributesLookupStrategy lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the strategy to locate the {@link AttributeConsumingService} index from the {@link MessageContext}.
+     *
+     * @param strategy lookup strategy
+     */
+    public void setIndexLookupStrategy(@Nullable final Function<ProfileRequestContext,Integer> strategy) {
+        indexLookupStrategy = Constraint.isNotNull(strategy,
+                "AttributeConsumingService index lookup strategy cannot be null");
+    }
+
+    /**
+     * Sets the service which does the attribute mapping.
+     *
+     * @param resolverService the service for the attribute resolver we are to derive unmapping info from
+     */
+    public void setResolverService(@Nonnull final ReloadableService<AttributeResolver> resolverService) {
+        attributeResolverService = Constraint.isNotNull(resolverService, "AttributeResolver cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+        acsContext = attributeConsumingServiceContextLookupStrategy.apply(profileRequestContext);
+        if (acsContext == null) {
+            log.error("{} Unable to find AttributeConsumingServiceContext", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        oldACS = acsContext.getAttributeConsumingService();
+        if (oldACS == null) {
+            log.error("{} Unable to find peer's AttributeConsumingService", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc}*/
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+        /** The Requested Attributes, if any. */
+        final List<RequestedAttribute> requestedAttributes =
+                requestedAttributesLookupStrategy.apply(profileRequestContext);
+        if (requestedAttributes == null || requestedAttributes.isEmpty()) {
+            // Nothing further to do
+            return;
+        }
+        if (indexLookupStrategy.apply(profileRequestContext) != null) {
+            // "In the event that both are present, an Identity Provider SHOULD"
+            // ignore the extension
+            return;
+        }
+
+        try {
+            // Clone the ACS
+            final AttributeConsumingService newACS =
+                    XMLObjectSupport.cloneXMLObject(oldACS, CloneOutputOption.DropDOM);
+            // Add in the RequestedAttributes
+            for (final RequestedAttribute attribute: requestedAttributes) {
+                newACS.getRequestAttributes().add(
+                        XMLObjectSupport.cloneXMLObject(attribute, CloneOutputOption.DropDOM));
+            }
+            // Canonical processing
+            final AttributeMappingNodeProcessor processor = new AttributeMappingNodeProcessor(attributeResolverService);
+            processor.process(newACS);
+            // And plug it in
+            acsContext.setAttributeConsumingService(newACS);
+        } catch (final FilterException e) {
+            log.error("{} Error mapping Attributesresponding to request", getLogPrefix(), e);
+            ActionSupport.buildEvent(profileRequestContext, EventIds.RUNTIME_EXCEPTION);
+        } catch (final MarshallingException e) {
+            log.error("{} Error Cloning ACS", getLogPrefix(), e);
+            ActionSupport.buildEvent(profileRequestContext, EventIds.RUNTIME_EXCEPTION);
+       } catch (final UnmarshallingException e) {
+           log.error("{} Error Cloning ACS", getLogPrefix(), e);
+           ActionSupport.buildEvent(profileRequestContext, EventIds.RUNTIME_EXCEPTION);
+        }
+    }
+
+    /** Default lookup function that reads from a SAML 2 {@link AuthnRequest}. */
+    private class RequestedAttributesLookup implements Function<ProfileRequestContext,List<RequestedAttribute>> {
+
+        /** {@inheritDoc} */
+        @Override
+        public List<RequestedAttribute> apply(@Nullable final ProfileRequestContext input) {
+            if (input == null) {
+                return null;
+            }
+            final MessageContext messageContext = input.getInboundMessageContext();
+            final Object message = messageContext.getMessage();
+            if (message == null || !(message instanceof AuthnRequest)) {
+                return null;
+            }
+            final Extensions extensions = ((AuthnRequest) message).getExtensions();
+            if (extensions == null) {
+                return null;
+            }
+            final List<XMLObject> bindings = extensions.getUnknownXMLObjects(RequestedAttributes.DEFAULT_ELEMENT_NAME);
+            if (bindings == null || bindings.isEmpty()) {
+                return null;
+            }
+            return ((RequestedAttributes)bindings.get(0)).getRequestedAttributes();
+        }
+    }
+
+    /** Default lookup function that reads from a SAML 2 {@link AuthnRequest}. */
+    private class AuthnRequestIndexLookup implements Function<ProfileRequestContext,Integer> {
+
+        /** {@inheritDoc} */
+        @Override
+        public Integer apply(@Nullable final ProfileRequestContext input) {
+            if (input == null) {
+                return null;
+            }
+            final MessageContext messageContext = input.getInboundMessageContext();
+            final Object message = messageContext.getMessage();
+            if (message != null && message instanceof AuthnRequest) {
+                return ((AuthnRequest) message).getAttributeConsumingServiceIndex();
+            }
+            return null;
+        }
+    }
+}

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


More information about the commits mailing list