[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