[java-identity-provider] 01/02: IDP-1743 Support use of dynamic scope in AttributeDefinition

Rod Widdowson rdw at steadingsoftware.com
Wed Feb 3 16:09:06 UTC 2021


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

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

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

commit fbc9f09393776779c09fba33458ea2102d3ab20d
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Wed Feb 3 15:38:42 2021 +0000

    IDP-1743 Support use of dynamic scope in AttributeDefinition
    
    https://issues.shibboleth.net/jira/browse/IDP-1743
    
    Resolver changes.
---
 .../ad/impl/ScopedAttributeDefinition.java         | 115 ++++++++++++++++++---
 .../resolver/ad/impl/ScopedAttributeTest.java      |  54 ++++++++--
 .../idp/saml/impl/testing/TestSources.java         |   1 +
 3 files changed, 149 insertions(+), 21 deletions(-)

diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/ad/impl/ScopedAttributeDefinition.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/ad/impl/ScopedAttributeDefinition.java
index 03d8e237b..4f6f45a3e 100644
--- a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/ad/impl/ScopedAttributeDefinition.java
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/ad/impl/ScopedAttributeDefinition.java
@@ -18,11 +18,17 @@
 package net.shibboleth.idp.attribute.resolver.ad.impl;
 
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import javax.annotation.concurrent.ThreadSafe;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import net.shibboleth.idp.attribute.EmptyAttributeValue;
 import net.shibboleth.idp.attribute.IdPAttribute;
 import net.shibboleth.idp.attribute.IdPAttributeValue;
@@ -32,6 +38,8 @@ import net.shibboleth.idp.attribute.UnsupportedAttributeTypeException;
 import net.shibboleth.idp.attribute.resolver.AbstractAttributeDefinition;
 import net.shibboleth.idp.attribute.resolver.PluginDependencySupport;
 import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.ResolvedAttributeDefinition;
+import net.shibboleth.idp.attribute.resolver.ResolverAttributeDefinitionDependency;
 import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
 import net.shibboleth.idp.attribute.resolver.context.AttributeResolverWorkContext;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
@@ -41,9 +49,6 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 /**
  * An attribute definition that creates {@link ScopedStringAttributeValue}s by taking a source attribute value and
  * applying a static scope to each.
@@ -54,21 +59,27 @@ public class ScopedAttributeDefinition extends AbstractAttributeDefinition {
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(ScopedAttributeDefinition.class);
 
-    /** Scope value. */
-    @NonnullAfterInit private String scope;
+    /** Scope value. Mutually exclusive with {@link #scopeSource} */
+    @Nullable private String scope;
+
+    /** Scope source. Mutually exclusive with {@link #scope} */
+    @Nullable private String scopeSource;
+
+    /** The attribute dependencies mine the scopeSource attribute (if there is one). */
+    @NonnullAfterInit private Collection<ResolverAttributeDefinitionDependency> nonScopeAttributeDependencies;
 
     /**
      * Get scope value.
-     * 
+     *
      * @return Returns the scope.
      */
-    @NonnullAfterInit public String getScope() {
+    @Nullable public String getScope() {
         return scope;
     }
 
     /**
      * Set the scope for this definition.
-     * 
+     *
      * @param newScope what to set.
      */
     public void setScope(@Nonnull @NotEmpty final String newScope) {
@@ -78,6 +89,60 @@ public class ScopedAttributeDefinition extends AbstractAttributeDefinition {
         scope = Constraint.isNotNull(StringSupport.trimOrNull(newScope), "Scope can not be null or empty");
     }
 
+    /**
+     * Get scope source (attribute id).
+     *
+     * @return Returns the scope.
+     */
+    @Nullable public String getScopeSource() {
+        return scopeSource;
+    }
+
+    /**
+     * Set the source of the scope for this definition.
+     *
+     * @param attributeId what to set.
+     */
+    public void setScopeSource(@Nonnull @NotEmpty final String attributeId) {
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        scopeSource = Constraint.isNotNull(
+                StringSupport.trimOrNull(attributeId), "ScopeSource can not be null or empty");
+    }
+
+    /** Lookup the source attribute in the resolved list.
+     * @param workContext where to look
+     * @return the single string value contained in the attribute
+     * @throws ResolutionException if the attribute was not there or if it didn't have only one
+     * string value
+     */
+    private String getScopeFromSource(@Nonnull final AttributeResolverWorkContext workContext)
+            throws ResolutionException{
+        final ResolvedAttributeDefinition resolved =
+                workContext.getResolvedIdPAttributeDefinitions().get(getScopeSource());
+        if (resolved == null) {
+            log.error("{} Scope source '{}' not found in resolved dependencies", getLogPrefix(), getScopeSource());
+            log.debug("{} Attributes available {}", getLogPrefix(),
+                    workContext.getResolvedIdPAttributeDefinitions().entrySet());
+            throw new ResolutionException("Scope source not found in resolved dependencies");
+        }
+        final List<IdPAttributeValue> values = resolved.getResolvedAttribute().getValues();
+        if (values.size() != 1) {
+            log.error("{} Exactly one value required for {}, {} found", getLogPrefix(),
+                    getScopeSource(), values.size());
+            log.debug("{} Values returned {}",  getLogPrefix(), values);
+            throw new ResolutionException("Exactly one value for scope source required");
+        }
+        final IdPAttributeValue value = values.get(0);
+        if ((value instanceof StringAttributeValue) && !(value instanceof ScopedStringAttributeValue)) {
+            return ((StringAttributeValue) value).getValue();
+        }
+        log.error("{} Attribute {} must return a StringAttributeValue returned a {}", getLogPrefix(),
+                getScopeSource(), value.getClass());
+        throw new ResolutionException("SourceAttribute must only return a StringAttributeValue");
+    }
+
     /** {@inheritDoc} */
     @Override @Nonnull protected IdPAttribute doAttributeDefinitionResolve(
             @Nonnull final AttributeResolutionContext resolutionContext,
@@ -87,10 +152,16 @@ public class ScopedAttributeDefinition extends AbstractAttributeDefinition {
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
 
         final IdPAttribute resultantAttribute = new IdPAttribute(getId());
+        final String scopeValue;
+        if (scopeSource == null) {
+            scopeValue = scope;
+        } else {
+            scopeValue = getScopeFromSource(workContext);
+        }
 
         final List<IdPAttributeValue> dependencyValues =
                 PluginDependencySupport.getMergedAttributeValues(workContext,
-                        getAttributeDependencies(), 
+                        nonScopeAttributeDependencies,
                         getDataConnectorDependencies(), 
                         getId());
 
@@ -109,7 +180,8 @@ public class ScopedAttributeDefinition extends AbstractAttributeDefinition {
                         + dependencyValue.getClass().getName()));
             }
 
-            valueList.add(new ScopedStringAttributeValue(((StringAttributeValue) dependencyValue).getValue(), scope));
+            valueList.add(new ScopedStringAttributeValue(((StringAttributeValue) dependencyValue).getValue(),
+                    scopeValue));
         }
         resultantAttribute.setValues(valueList);
         return resultantAttribute;
@@ -119,12 +191,27 @@ public class ScopedAttributeDefinition extends AbstractAttributeDefinition {
     @Override protected void doInitialize() throws ComponentInitializationException {
         super.doInitialize();
 
-        if (null == scope) {
-            throw new ComponentInitializationException(getLogPrefix() + "': no scope was configured");
+        if (scope != null) {
+            if (scopeSource != null) {
+                throw new ComponentInitializationException(getLogPrefix() +
+                        "': cannot specify scope and scopeSource");
+            }
+            nonScopeAttributeDependencies = getAttributeDependencies();
+        } else if (scopeSource != null) {
+            final ResolverAttributeDefinitionDependency source = new ResolverAttributeDefinitionDependency(scopeSource);
+            final HashSet<ResolverAttributeDefinitionDependency> nonScope = new HashSet<>(getAttributeDependencies());
+            if (!nonScope.remove(source)) {
+                throw new ComponentInitializationException(getLogPrefix() +
+                        "': AttributeDependencies did not contain scope source '" +
+                        scopeSource + "'");
+            }
+            nonScopeAttributeDependencies = nonScope;
+        } else {
+            throw new ComponentInitializationException(getLogPrefix() + "': neither scope now scopeSource configured");
         }
 
-        if (getDataConnectorDependencies().isEmpty() && getAttributeDependencies().isEmpty()) {
-            throw new ComponentInitializationException(getLogPrefix() + "': no dependencies were configured");
+        if (getDataConnectorDependencies().isEmpty() && nonScopeAttributeDependencies.isEmpty()) {
+            throw new ComponentInitializationException(getLogPrefix() + "': no actual dependencies were configured");
         }
     }
     
diff --git a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/ad/impl/ScopedAttributeTest.java b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/ad/impl/ScopedAttributeTest.java
index 438916940..5af0203cc 100644
--- a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/ad/impl/ScopedAttributeTest.java
+++ b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/ad/impl/ScopedAttributeTest.java
@@ -67,17 +67,17 @@ public class ScopedAttributeTest {
     private static final String TEST_SCOPE = "scope";
 
     /**
-     * Test resolution of the scoped attribute resolver.
-     * 
+     * Test resolution of the scoped attribute resolver (static scope).
+     *
      * @throws ResolutionException if resolution failed.
      * @throws ComponentInitializationException if any of our initializations failed (which it shouldn't)
      */
     @Test public void scopes() throws ResolutionException, ComponentInitializationException {
 
         // Set the dependency on the data connector
-        final Set<ResolverDataConnectorDependency> dependencySet = new LazySet<>();
-        dependencySet.add(TestSources.makeDataConnectorDependency(TestSources.STATIC_CONNECTOR_NAME,
-                TestSources.DEPENDS_ON_ATTRIBUTE_NAME_CONNECTOR));
+        final Set<ResolverDataConnectorDependency> dependencySet = Set.of(
+                TestSources.makeDataConnectorDependency(TestSources.STATIC_CONNECTOR_NAME,
+                                                        TestSources.DEPENDS_ON_ATTRIBUTE_NAME_CONNECTOR));
 
         final ScopedAttributeDefinition scoped = new ScopedAttributeDefinition();
         scoped.setScope(TEST_SCOPE);
@@ -86,8 +86,7 @@ public class ScopedAttributeTest {
         scoped.initialize();
 
         // And resolve
-        final Set<DataConnector> connectorSet = new LazySet<>();
-        connectorSet.add(TestSources.populatedStaticConnector());
+        final Set<DataConnector> connectorSet = Set.of(TestSources.populatedStaticConnector());
 
         final Set<AttributeDefinition> attributeSet = new LazySet<>();
         attributeSet.add(scoped);
@@ -108,7 +107,48 @@ public class ScopedAttributeTest {
         assertTrue(
                 f.contains(new ScopedStringAttributeValue(TestSources.COMMON_ATTRIBUTE_VALUE_STRING, TEST_SCOPE)),
                 "looking for CONNECTOR_ATTRIBUTE_VALUE");
+    }
+
+    /**
+     * Test resolution of the scoped attribute resolver (dynamic scope)
+     *
+     * @throws ResolutionException if resolution failed.
+     * @throws ComponentInitializationException if any of our initializations failed (which it shouldn't)
+     */
+    @Test public void scopeSource() throws ResolutionException, ComponentInitializationException {
+
+        // Set the dependency on the data connector
+        final Set<ResolverDataConnectorDependency> dependencySet =Set.of(
+                TestSources.makeDataConnectorDependency(TestSources.STATIC_CONNECTOR_NAME,
+                                                        TestSources.DEPENDS_ON_ATTRIBUTE_NAME_CONNECTOR));
+
+        final ScopedAttributeDefinition scoped = new ScopedAttributeDefinition();
+        scoped.setScopeSource("ScopeSource");
+        scoped.setId(TEST_ATTRIBUTE_NAME);
+        scoped.setDataConnectorDependencies(dependencySet);
+        scoped.setAttributeDependencies(Set.of(TestSources.makeAttributeDefinitionDependency("ScopeSource")));
+        scoped.initialize();
+
+        // And resolve
+        final Set<DataConnector> connectorSet = Set.of(TestSources.populatedStaticConnector());
+        final Set<AttributeDefinition> attributeSet = Set.of(scoped, TestSources.populatedStaticAttribute("ScopeSource", 1));
 
+        final AttributeResolverImpl resolver = AttributeResolverImplTest.newAttributeResolverImpl("foo", attributeSet, connectorSet);
+        resolver.initialize();
+
+        final AttributeResolutionContext context = new AttributeResolutionContext();
+        resolver.resolveAttributes(context);
+
+        // Now test that we got exactly what we expected - two scoped attributes
+        final Collection<?> f = context.getResolvedIdPAttributes().get(TEST_ATTRIBUTE_NAME).getValues();
+
+        assertEquals(f.size(), 2);
+        assertTrue(
+                f.contains(new ScopedStringAttributeValue(TestSources.COMMON_ATTRIBUTE_VALUE_STRING, TestSources.COMMON_ATTRIBUTE_VALUE_STRING)),
+                "looking for COMMON_ATTRIBUTE_VALUE");
+        assertTrue(
+                f.contains(new ScopedStringAttributeValue(TestSources.COMMON_ATTRIBUTE_VALUE_STRING, TestSources.COMMON_ATTRIBUTE_VALUE_STRING)),
+                "looking for CONNECTOR_ATTRIBUTE_VALUE");
     }
 
     @Test public void invalidValueType() throws ComponentInitializationException {
diff --git a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/impl/testing/TestSources.java b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/impl/testing/TestSources.java
index cdd64e173..fc3304081 100644
--- a/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/impl/testing/TestSources.java
+++ b/idp-saml-impl/src/test/java/net/shibboleth/idp/saml/impl/testing/TestSources.java
@@ -51,6 +51,7 @@ import net.shibboleth.utilities.java.support.component.ComponentInitializationEx
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 
 /** Basic data sources for testing the attribute generators. */
+ at SuppressWarnings("javadoc")
 public final class TestSources {
     /** The name we use in this test for the static connector. */
     public static final String STATIC_CONNECTOR_NAME = "staticCon";

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


More information about the commits mailing list