[java-identity-provider] branch main updated: IDP-1697 - StorageService-backed DataConnector
Scott Cantor
cantor.2 at osu.edu
Wed Oct 21 22:19:57 UTC 2020
This is an automated email from the git hooks/post-receive script.
scantor 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=38b1037d8de4e49f0a80db12b4116d405c8cfbaa
The following commit(s) were added to refs/heads/main by this push:
new 38b1037d8 IDP-1697 - StorageService-backed DataConnector
38b1037d8 is described below
commit 38b1037d8de4e49f0a80db12b4116d405c8cfbaa
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Oct 21 18:19:51 2020 -0400
IDP-1697 - StorageService-backed DataConnector
https://issues.shibboleth.net/jira/browse/IDP-1697
Spring parser, unit tests.
---
.../storage/impl/StorageServiceDataConnector.java | 16 +-
.../impl/StorageServiceDataConnectorTest.java | 8 +-
.../impl/StorageServiceDataConnectorParser.java | 256 +++++++++++++++++++++
.../spring/dc/storage/impl/package-info.java | 22 ++
.../impl/AttributeResolverNamespaceHandler.java | 3 +
.../StorageServiceDataConnectorParserTest.java | 225 ++++++++++++++++++
.../resolver/spring/dc/storage/spring-beans.xml | 31 +++
.../storage-attribute-resolver-spring-context.xml | 24 ++
.../storage-attribute-resolver-v2-hybrid.xml | 17 ++
.../storage-attribute-resolver-v2-simple.xml | 16 ++
.../dc/storage/storage-attribute-resolver-v2.xml | 22 ++
.../schema/shibboleth-attribute-resolver.xsd | 95 ++++++++
12 files changed, 723 insertions(+), 12 deletions(-)
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/storage/impl/StorageServiceDataConnector.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/storage/impl/StorageServiceDataConnector.java
index d12d2509c..0853b16b4 100644
--- a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/storage/impl/StorageServiceDataConnector.java
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/storage/impl/StorageServiceDataConnector.java
@@ -62,7 +62,7 @@ public class StorageServiceDataConnector
@NonnullAfterInit private StorageService storageService;
/** ID of the attribute generated by this data connector if simple result mapping used. */
- @NonnullAfterInit private String generatedAttribute;
+ @NonnullAfterInit private String generatedAttributeID;
/** Whether no record is an error. */
private boolean noResultAnError;
@@ -99,8 +99,8 @@ public class StorageServiceDataConnector
*
* @return ID of the attribute generated by this connector
*/
- @NonnullAfterInit public String getGeneratedAttributeId() {
- return generatedAttribute;
+ @NonnullAfterInit public String getGeneratedAttributeID() {
+ return generatedAttributeID;
}
/**
@@ -118,13 +118,13 @@ public class StorageServiceDataConnector
/**
* Set the ID of the attribute generated by this connector if simple result mapping used.
*
- * @param newAttributeId what to set.
+ * @param id what to set.
*/
- public void setGeneratedAttributeId(@Nullable final String newAttributeId) {
+ public void setGeneratedAttributeID(@Nullable final String id) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
- generatedAttribute = StringSupport.trimOrNull(newAttributeId);
+ generatedAttributeID = StringSupport.trimOrNull(id);
}
/** {@inheritDoc} */
@@ -135,11 +135,11 @@ public class StorageServiceDataConnector
}
if (getMappingStrategy() == null) {
- if (generatedAttribute == null) {
+ if (generatedAttributeID == null) {
throw new ComponentInitializationException(
getLogPrefix() + " No mapping strategy or generated attribute ID set");
}
- setMappingStrategy(new SimpleStorageMappingStrategy(generatedAttribute));
+ setMappingStrategy(new SimpleStorageMappingStrategy(generatedAttributeID));
}
super.doInitialize();
diff --git a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/storage/impl/StorageServiceDataConnectorTest.java b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/storage/impl/StorageServiceDataConnectorTest.java
index 066fed7a6..86f66efb8 100644
--- a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/storage/impl/StorageServiceDataConnectorTest.java
+++ b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/storage/impl/StorageServiceDataConnectorTest.java
@@ -82,7 +82,7 @@ public class StorageServiceDataConnectorTest {
builder.initialize();
connector.setExecutableSearchBuilder(builder);
- connector.setGeneratedAttributeId("foobar");
+ connector.setGeneratedAttributeID("foobar");
connector.initialize();
final AttributeResolutionContext context =
@@ -104,7 +104,7 @@ public class StorageServiceDataConnectorTest {
builder.initialize();
connector.setExecutableSearchBuilder(builder);
- connector.setGeneratedAttributeId("foobar");
+ connector.setGeneratedAttributeID("foobar");
connector.setNoResultAnError(true);
connector.initialize();
@@ -123,7 +123,7 @@ public class StorageServiceDataConnectorTest {
builder.initialize();
connector.setExecutableSearchBuilder(builder);
- connector.setGeneratedAttributeId("foobar");
+ connector.setGeneratedAttributeID("foobar");
connector.initialize();
storage.create("foo", "bar", "test", null);
@@ -148,7 +148,7 @@ public class StorageServiceDataConnectorTest {
builder.initialize();
connector.setExecutableSearchBuilder(builder);
- connector.setGeneratedAttributeId("foobar");
+ connector.setGeneratedAttributeID("foobar");
final TestCache cache = new TestCache();
connector.setResultsCache(cache);
diff --git a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/storage/impl/StorageServiceDataConnectorParser.java b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/storage/impl/StorageServiceDataConnectorParser.java
new file mode 100644
index 000000000..0910c776d
--- /dev/null
+++ b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/storage/impl/StorageServiceDataConnectorParser.java
@@ -0,0 +1,256 @@
+/*
+ * 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.attribute.resolver.spring.dc.storage.impl;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.xml.namespace.QName;
+
+import net.shibboleth.ext.spring.util.SpringSupport;
+import net.shibboleth.idp.attribute.resolver.dc.storage.impl.ScriptedStorageMappingStrategy;
+import net.shibboleth.idp.attribute.resolver.dc.storage.impl.StorageServiceDataConnector;
+import net.shibboleth.idp.attribute.resolver.dc.storage.impl.TemplatedSearchBuilder;
+import net.shibboleth.idp.attribute.resolver.spring.dc.AbstractDataConnectorParser;
+import net.shibboleth.idp.attribute.resolver.spring.dc.impl.CacheConfigParser;
+import net.shibboleth.idp.attribute.resolver.spring.impl.AttributeResolverNamespaceHandler;
+import net.shibboleth.idp.profile.spring.relyingparty.metadata.ScriptTypeBeanParser;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+import net.shibboleth.utilities.java.support.xml.AttributeSupport;
+import net.shibboleth.utilities.java.support.xml.ElementSupport;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.w3c.dom.Element;
+
+/** Bean definition Parser for a {@link StorageServiceDataConnector}. */
+public class StorageServiceDataConnectorParser extends AbstractDataConnectorParser {
+
+ /** Schema type name. */
+ @Nonnull public static final QName TYPE_NAME =
+ new QName(AttributeResolverNamespaceHandler.NAMESPACE, "StorageService");
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(StorageServiceDataConnectorParser.class);
+
+ /** {@inheritDoc} */
+ @Override protected Class<StorageServiceDataConnector> getNativeBeanClass() {
+ return StorageServiceDataConnector.class;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doV2Parse(@Nonnull final Element config, @Nonnull final ParserContext parserContext,
+ @Nonnull final BeanDefinitionBuilder builder) {
+
+ log.debug("{} Parsing custom configuration {}", getLogPrefix(), config);
+
+ final V2Parser v2Parser = new V2Parser(config, getLogPrefix());
+
+ final String searchBuilderID = v2Parser.getBeanSearchBuilderID();
+ if (searchBuilderID != null) {
+ builder.addPropertyReference("executableSearchBuilder", searchBuilderID);
+ } else {
+ builder.addPropertyValue("executableSearchBuilder", v2Parser.createTemplateBuilder());
+ }
+
+ final String mappingStrategyID = v2Parser.getBeanMappingStrategyID();
+ if (mappingStrategyID != null) {
+ builder.addPropertyReference("mappingStrategy", mappingStrategyID);
+ if (config.hasAttributeNS(null, "generatedAttributeID")) {
+ log.warn("{} Ignoring generatedAttributeID in favor of explicit mapping strategy", getLogPrefix());
+ }
+ } else {
+ final BeanDefinition def = v2Parser.createMappingStrategy(config.getAttributeNS(null, "id"));
+ if (def != null) {
+ builder.addPropertyValue("mappingStrategy", def);
+ if (config.hasAttributeNS(null, "generatedAttributeID")) {
+ log.warn("{} Ignoring generatedAttributeID in favor of <RecordMapping> element", getLogPrefix());
+ }
+ } else {
+ builder.addPropertyValue("generatedAttributeID", config.getAttributeNS(null, "generatedAttributeID"));
+ }
+ }
+
+ final String resultCacheBeanID = CacheConfigParser.getBeanResultCacheID(config);
+ if (null != resultCacheBeanID) {
+ builder.addPropertyReference("resultsCache", resultCacheBeanID);
+ } else {
+ builder.addPropertyValue("resultsCache", v2Parser.createCache(parserContext));
+ }
+
+ builder.addPropertyReference("storageService", config.getAttributeNS(null, "storageServiceRef"));
+
+ final String noResultIsError =
+ AttributeSupport.getAttributeValue(config, new QName("noResultIsError"));
+ if (noResultIsError != null) {
+ builder.addPropertyValue("noResultAnError", SpringSupport.getStringValueAsBoolean(noResultIsError));
+ }
+
+ builder.setInitMethodName("initialize");
+ builder.setDestroyMethodName("destroy");
+ }
+
+ /**
+ * Utility class for parsing v2 schema configuration.
+ *
+ */
+ protected static class V2Parser {
+
+ /** Base XML element. */
+ @Nonnull private final Element configElement;
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(V2Parser.class);
+
+ /** Parent parser's log prefix.*/
+ @Nonnull @NotEmpty private final String logPrefix;
+
+ /**
+ * Creates a new V2Parser with the supplied element.
+ *
+ * @param config StorageService DataConnector element
+ * @param prefix the parent parser's log prefix.
+ */
+ public V2Parser(@Nonnull final Element config, @Nonnull @NotEmpty final String prefix) {
+ Constraint.isNotNull(config, "StorageService DataConnector element cannot be null");
+ configElement = config;
+ logPrefix = prefix;
+ }
+
+ /**
+ * Get the bean ID of an externally defined search builder.
+ *
+ * @return search builder bean ID
+ */
+ @Nullable public String getBeanSearchBuilderID() {
+ return AttributeSupport.getAttributeValue(configElement, null, "executableSearchBuilderRef");
+ }
+
+ /**
+ * Create the definition of the search builder.
+ *
+ * @return the bean definition for the search builder, or null
+ */
+ @Nullable public BeanDefinition createTemplateBuilder() {
+
+ final List<Element> contextTemplates = ElementSupport.getChildElements(configElement,
+ new QName(AttributeResolverNamespaceHandler.NAMESPACE, "ContextTemplate"));
+ final List<Element> keyTemplates = ElementSupport.getChildElements(configElement,
+ new QName(AttributeResolverNamespaceHandler.NAMESPACE, "KeyTemplate"));
+ if (contextTemplates.size() == 0 || keyTemplates.size() == 0) {
+ return null;
+ }
+
+ final BeanDefinitionBuilder templateBuilder =
+ BeanDefinitionBuilder.genericBeanDefinition(TemplatedSearchBuilder.class);
+ templateBuilder.setInitMethodName("initialize");
+ templateBuilder.setDestroyMethodName("destroy");
+
+ String velocityEngineRef = StringSupport.trimOrNull(configElement.getAttributeNS(null, "templateEngine"));
+ if (null == velocityEngineRef) {
+ velocityEngineRef = "shibboleth.VelocityEngine";
+ }
+ templateBuilder.addPropertyReference("velocityEngine", velocityEngineRef);
+
+ if (contextTemplates.size() > 1) {
+ log.warn("{} A maximum of 1 <ContextTemplate> should be specified; the first one has been used",
+ getLogPrefix());
+ } else if (keyTemplates.size() > 1) {
+ log.warn("{} A maximum of 1 <KeyTemplate> should be specified; the first one has been used",
+ getLogPrefix());
+ }
+
+ final Element contextTemplate = contextTemplates.get(0);
+ final Element keyTemplate = keyTemplates.get(0);
+
+ if (configElement.hasAttributeNS(null, "customObjectRef")) {
+ templateBuilder.addPropertyReference("customObject",
+ configElement.getAttributeNS(null, "customObjectRef"));
+ }
+
+ templateBuilder.addPropertyValue("contextTemplateText", contextTemplate.getTextContent());
+ templateBuilder.addPropertyValue("keyTemplateText", keyTemplate.getTextContent());
+
+ return templateBuilder.getBeanDefinition();
+ }
+
+ /**
+ * Get the bean ID of an externally defined mapping strategy.
+ *
+ * @return mapping strategy bean ID
+ */
+ @Nullable public String getBeanMappingStrategyID() {
+ return AttributeSupport.getAttributeValue(configElement, null, "mappingStrategyRef");
+ }
+
+ /**
+ * Create the scripted result mapping strategy.
+ *
+ * @param id the ID of the
+ *
+ * @return mapping strategy
+ */
+ @Nullable public BeanDefinition createMappingStrategy(@Nullable final String id) {
+
+ final List<Element> mappings = ElementSupport.getChildElements(configElement,
+ new QName(AttributeResolverNamespaceHandler.NAMESPACE, "RecordMapping"));
+
+ if (mappings.size() > 1) {
+ log.warn("{} A maximum of 1 <RecordMapping> should be specified; the first one has been used",
+ getLogPrefix());
+ } else if (mappings.isEmpty()) {
+ // No element means to fall back to simple record mapping.
+ return null;
+ }
+
+ final BeanDefinitionBuilder mapper =
+ ScriptTypeBeanParser.parseScriptType(ScriptedStorageMappingStrategy.class, mappings.get(0));
+ if (id != null) {
+ mapper.addPropertyValue("logPrefix", id + ':');
+ }
+
+ return mapper.getBeanDefinition();
+ }
+
+ /**
+ * Create the results cache. See {@link CacheConfigParser}.
+ *
+ * @param parserContext bean parser context
+ *
+ * @return results cache
+ */
+ @Nullable public BeanDefinition createCache(@Nonnull final ParserContext parserContext) {
+ final CacheConfigParser parser = new CacheConfigParser(configElement);
+ return parser.createCache(parserContext);
+ }
+
+ /** The parent parser's log prefix.
+ * @return the log prefix.
+ */
+ @Nonnull @NotEmpty private String getLogPrefix() {
+ return logPrefix;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/storage/impl/package-info.java b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/storage/impl/package-info.java
new file mode 100644
index 000000000..505e066d7
--- /dev/null
+++ b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/storage/impl/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * Parser for StorageService data connector.
+ */
+
+package net.shibboleth.idp.attribute.resolver.spring.dc.storage.impl;
\ No newline at end of file
diff --git a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/AttributeResolverNamespaceHandler.java b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/AttributeResolverNamespaceHandler.java
index 4991dbc1e..be674ee5c 100644
--- a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/AttributeResolverNamespaceHandler.java
+++ b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/AttributeResolverNamespaceHandler.java
@@ -46,6 +46,7 @@ import net.shibboleth.idp.attribute.resolver.spring.dc.impl.StoredIdDataConnecto
import net.shibboleth.idp.attribute.resolver.spring.dc.impl.SubjectDataConnectorParser;
import net.shibboleth.idp.attribute.resolver.spring.dc.ldap.impl.LDAPDataConnectorParser;
import net.shibboleth.idp.attribute.resolver.spring.dc.rdbms.impl.RDBMSDataConnectorParser;
+import net.shibboleth.idp.attribute.resolver.spring.dc.storage.impl.StorageServiceDataConnectorParser;
import net.shibboleth.idp.attribute.resolver.spring.enc.impl.SAML1Base64AttributeEncoderParser;
import net.shibboleth.idp.attribute.resolver.spring.enc.impl.SAML1ScopedStringAttributeEncoderParser;
import net.shibboleth.idp.attribute.resolver.spring.enc.impl.SAML1StringAttributeEncoderParser;
@@ -115,6 +116,8 @@ public class AttributeResolverNamespaceHandler extends BaseSpringNamespaceHandle
registerBeanDefinitionParser(StaticDataConnectorParser.TYPE_NAME_RESOLVER, new StaticDataConnectorParser());
registerBeanDefinitionParser(StoredIdDataConnectorParser.TYPE_NAME_RESOLVER, new StoredIdDataConnectorParser());
registerBeanDefinitionParser(SubjectDataConnectorParser.TYPE_NAME_RESOLVER, new SubjectDataConnectorParser());
+ registerBeanDefinitionParser(StorageServiceDataConnectorParser.TYPE_NAME,
+ new StorageServiceDataConnectorParser());
// Encoders
diff --git a/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/storage/StorageServiceDataConnectorParserTest.java b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/storage/StorageServiceDataConnectorParserTest.java
new file mode 100644
index 000000000..ca59117ce
--- /dev/null
+++ b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/storage/StorageServiceDataConnectorParserTest.java
@@ -0,0 +1,225 @@
+/*
+ * 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.attribute.resolver.spring.dc.storage;
+
+import static org.testng.Assert.*;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.opensaml.storage.StorageService;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.mock.env.MockPropertySource;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+import org.testng.reporters.Files;
+
+import net.shibboleth.ext.spring.resource.PreferFileSystemResourceLoader;
+import net.shibboleth.ext.spring.util.ApplicationContextBuilder;
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.resolver.NoResultAnErrorResolutionException;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.dc.storage.impl.StorageServiceDataConnector;
+import net.shibboleth.idp.saml.impl.testing.TestSources;
+
+/** Test for {@link StorageServiceDataConnectorParser}. */
+ at SuppressWarnings("javadoc")
+public class StorageServiceDataConnectorParserTest {
+
+ private static final String SCRIPT_PATH = "/net/shibboleth/idp/attribute/resolver/impl/dc/storage/";
+
+ private GenericApplicationContext pendingTeardownContext = null;
+
+ @AfterMethod public void tearDownTestContext() {
+ if (null == pendingTeardownContext ) {
+ return;
+ }
+ pendingTeardownContext.close();
+ pendingTeardownContext = null;
+ }
+
+ private void setTestContext(final GenericApplicationContext context) {
+ tearDownTestContext();
+ pendingTeardownContext = context;
+ }
+
+ @Test public void v2Simple() throws Exception {
+
+ final StorageServiceDataConnector connector =
+ getDataConnector(null,
+ "net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-v2-simple.xml");
+ assertNotNull(connector);
+
+ pendingTeardownContext.getBean(StorageService.class).create("foo", "bar", "testdata", null);
+
+ final AttributeResolutionContext context =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+
+ final Map<String,IdPAttribute> attrs = connector.resolve(context);
+
+ assertEquals(attrs.size(), 1);
+
+ assertEquals(attrs.get("test").getValues().size(), 1);
+ assertEquals(((StringAttributeValue)attrs.get("test").getValues().get(0)).getValue(), "testdata");
+
+ assertTrue(connector.getResultsCache().size() == 1);
+ }
+
+ @Test public void v2Config() throws Exception {
+
+ final MockPropertySource propSource = singletonPropertySource("context", "foo");
+ propSource.setProperty("key", "bar");
+ propSource.setProperty("scriptPath", (SCRIPT_PATH) + "test.js");
+
+ final StorageServiceDataConnector connector =
+ getDataConnector(propSource,
+ "net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-v2.xml");
+ assertNotNull(connector);
+
+ pendingTeardownContext.getBean(StorageService.class).create("foo", "bar",
+ Files.streamToString(getClass().getResourceAsStream(SCRIPT_PATH + "test.json")), null);
+
+ final AttributeResolutionContext context =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+
+ final Map<String,IdPAttribute> attrs = connector.resolve(context);
+
+ assertEquals(attrs.size(), 2);
+
+ assertEquals(attrs.get("foo").getValues().size(), 1);
+ assertEquals(((StringAttributeValue)attrs.get("foo").getValues().get(0)).getValue(), "foo1");
+
+ assertEquals(attrs.get("bar").getValues().size(), 2);
+ assertEquals(((StringAttributeValue)attrs.get("bar").getValues().get(0)).getValue(), "bar1");
+ assertEquals(((StringAttributeValue)attrs.get("bar").getValues().get(1)).getValue(), "bar2");
+
+ assertTrue(connector.getResultsCache().size() == 1);
+ }
+
+ @Test(expectedExceptions=NoResultAnErrorResolutionException.class) public void v2Missing() throws Exception {
+
+ final MockPropertySource propSource = singletonPropertySource("context", "foo");
+ propSource.setProperty("key", "baz");
+ propSource.setProperty("scriptPath", (SCRIPT_PATH) + "test.js");
+ propSource.setProperty("missingerror", "true");
+
+ final StorageServiceDataConnector connector =
+ getDataConnector(propSource,
+ "net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-v2.xml");
+ assertNotNull(connector);
+
+ final AttributeResolutionContext context =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+
+ connector.resolve(context);
+ }
+
+ @Test public void v2MissingOk() throws Exception {
+
+ final MockPropertySource propSource = singletonPropertySource("context", "foo");
+ propSource.setProperty("key", "baz");
+ propSource.setProperty("scriptPath", (SCRIPT_PATH) + "test.js");
+
+ final StorageServiceDataConnector connector =
+ getDataConnector(propSource,
+ "net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-v2.xml");
+ assertNotNull(connector);
+
+ final AttributeResolutionContext context =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+
+ final Map<String,IdPAttribute> attrs = connector.resolve(context);
+
+ assertTrue(attrs.isEmpty());
+ }
+
+ @Test public void hybridConfig() throws Exception {
+ final MockPropertySource propSource = singletonPropertySource("context", "foo");
+ propSource.setProperty("key", "bar");
+ propSource.setProperty("scriptPath", (SCRIPT_PATH) + "test.js");
+
+ final StorageServiceDataConnector connector =
+ getDataConnector(propSource,
+ "net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-v2-hybrid.xml",
+ "net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-spring-context.xml");
+ assertNotNull(connector);
+
+ pendingTeardownContext.getBean(StorageService.class).create("foo", "bar",
+ Files.streamToString(getClass().getResourceAsStream(SCRIPT_PATH + "test.json")), null);
+
+ final AttributeResolutionContext context =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+
+ final Map<String,IdPAttribute> attrs = connector.resolve(context);
+
+ assertEquals(attrs.size(), 2);
+
+ assertEquals(attrs.get("foo").getValues().size(), 1);
+ assertEquals(((StringAttributeValue)attrs.get("foo").getValues().get(0)).getValue(), "foo1");
+
+ assertEquals(attrs.get("bar").getValues().size(), 2);
+ assertEquals(((StringAttributeValue)attrs.get("bar").getValues().get(0)).getValue(), "bar1");
+ assertEquals(((StringAttributeValue)attrs.get("bar").getValues().get(1)).getValue(), "bar2");
+
+ assertTrue(connector.getResultsCache().size() == 1);
+ }
+
+ private StorageServiceDataConnector getDataConnector(final PropertySource<?> propSource, final String... beanDefinitions)
+ throws IOException {
+
+ final ResourceLoader loader = new PreferFileSystemResourceLoader();
+
+ final ApplicationContextBuilder builder = new ApplicationContextBuilder();
+ builder.setName("ApplicationContext: " + StorageServiceDataConnectorParserTest.class);
+
+ final Collection<String> defs = new ArrayList<>(Arrays.asList(beanDefinitions));
+ defs.add("net/shibboleth/idp/attribute/resolver/spring/dc/storage/spring-beans.xml");
+
+ builder.setServiceConfigurations(defs.stream().map(s -> loader.getResource(s)).collect(Collectors.toList()));
+
+ if (propSource != null) {
+ builder.setPropertySources(Collections.singletonList(propSource));
+ }
+
+ final GenericApplicationContext context = builder.build();
+
+ setTestContext(context);
+
+ return (StorageServiceDataConnector) context.getBean("myStorage");
+ }
+
+ private MockPropertySource singletonPropertySource(final String name, final String value) {
+ final MockPropertySource propSource = new MockPropertySource("localProperties");
+ propSource.setProperty(name, value);
+ return propSource;
+ }
+
+}
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/storage/spring-beans.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/storage/spring-beans.xml
new file mode 100644
index 000000000..6034ae178
--- /dev/null
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/storage/spring-beans.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:c="http://www.springframework.org/schema/c"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd"
+ default-init-method="initialize"
+ default-destroy-method="destroy">
+
+ <bean id="CustomObject" class="java.lang.String" c:_0="foo" />
+
+ <bean id="shibboleth.VelocityEngine" class="net.shibboleth.ext.spring.velocity.VelocityEngineFactoryBean">
+ <property name="velocityProperties">
+ <props>
+ <prop key="resource.loader">classpath, string</prop>
+ <prop key="classpath.resource.loader.class">
+ org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
+ </prop>
+ <prop key="string.resource.loader.class">
+ org.apache.velocity.runtime.resource.loader.StringResourceLoader
+ </prop>
+ </props>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.PropertySourcesPlaceholderConfigurer"
+ class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"
+ p:placeholderPrefix="%{" p:placeholderSuffix="}" />
+
+ <bean id="myStorageService" p:id="myStorageService" class="org.opensaml.storage.impl.MemoryStorageService" />
+
+</beans>
\ No newline at end of file
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-spring-context.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-spring-context.xml
new file mode 100644
index 000000000..c2022342a
--- /dev/null
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-spring-context.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:c="http://www.springframework.org/schema/c"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd"
+ default-init-method="initialize"
+ default-destroy-method="destroy">
+
+ <bean id="cacheBuilder" class="com.google.common.cache.CacheBuilder" factory-method="from">
+ <constructor-arg value="expireAfterAccess=10s,maximumSize=25" />
+ </bean>
+
+ <bean id="cache" factory-bean="cacheBuilder" factory-method="build" />
+
+ <bean id="template" class="net.shibboleth.idp.attribute.resolver.dc.storage.impl.TemplatedSearchBuilder"
+ p:velocityEngine-ref="shibboleth.VelocityEngine"
+ p:contextTemplateText="%{context}"
+ p:keyTemplateText="%{key}" />
+
+ <bean id="mapping" class="net.shibboleth.idp.attribute.resolver.dc.storage.impl.ScriptedStorageMappingStrategy"
+ factory-method="resourceScript"
+ c:_0="%{scriptPath}" />
+
+</beans>
\ No newline at end of file
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-v2-hybrid.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-v2-hybrid.xml
new file mode 100644
index 000000000..b581d64c5
--- /dev/null
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-v2-hybrid.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<AttributeResolver
+ xmlns="urn:mace:shibboleth:2.0:resolver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="urn:mace:shibboleth:2.0:resolver http://shibboleth.net/schema/idp/shibboleth-attribute-resolver.xsd">
+
+ <DataConnector id="myStorage" xsi:type="StorageService"
+ storageServiceRef="myStorageService"
+ customObjectRef="CustomObject"
+ noResultIsError="%{missingerror:false}"
+ executableSearchBuilderRef="template"
+ mappingStrategyRef="mapping">
+
+ <ResultCacheBean>cache</ResultCacheBean>
+
+ </DataConnector>
+
+</AttributeResolver>
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-v2-simple.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-v2-simple.xml
new file mode 100644
index 000000000..20b5a604f
--- /dev/null
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-v2-simple.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<AttributeResolver
+ xmlns="urn:mace:shibboleth:2.0:resolver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="urn:mace:shibboleth:2.0:resolver http://shibboleth.net/schema/idp/shibboleth-attribute-resolver.xsd">
+
+ <DataConnector id="myStorage" xsi:type="StorageService"
+ storageServiceRef="myStorageService" generatedAttributeID="test">
+
+ <ContextTemplate>foo</ContextTemplate>
+ <KeyTemplate>bar</KeyTemplate>
+
+ <ResultCache expireAfterWrite="PT10S" />
+
+ </DataConnector>
+
+</AttributeResolver>
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-v2.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-v2.xml
new file mode 100644
index 000000000..4f66f564d
--- /dev/null
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/storage/storage-attribute-resolver-v2.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<AttributeResolver
+ xmlns="urn:mace:shibboleth:2.0:resolver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="urn:mace:shibboleth:2.0:resolver http://shibboleth.net/schema/idp/shibboleth-attribute-resolver.xsd">
+
+ <DataConnector id="myStorage" xsi:type="StorageService"
+ storageServiceRef="myStorageService"
+ customObjectRef="CustomObject"
+ noResultIsError="%{missingerror:false}">
+
+ <ContextTemplate >%{context}</ContextTemplate>
+ <KeyTemplate>%{key}</KeyTemplate>
+
+ <RecordMapping>
+ <ScriptFile>%{scriptPath}</ScriptFile>
+ </RecordMapping>
+
+ <ResultCache expireAfterWrite="PT10S" />
+
+ </DataConnector>
+
+</AttributeResolver>
diff --git a/idp-schema/src/main/resources/schema/shibboleth-attribute-resolver.xsd b/idp-schema/src/main/resources/schema/shibboleth-attribute-resolver.xsd
index ae84ee70b..c6922b34b 100644
--- a/idp-schema/src/main/resources/schema/shibboleth-attribute-resolver.xsd
+++ b/idp-schema/src/main/resources/schema/shibboleth-attribute-resolver.xsd
@@ -1483,6 +1483,101 @@
</complexContent>
</complexType>
+ <complexType name="StorageService">
+ <annotation>
+ <documentation>
+ A data connector definition that queries for a record via the IdP's StorageService API.
+ Simple and scripted handling of records, typically in JSON, is the primary use case.
+ </documentation>
+ </annotation>
+ <complexContent>
+ <extension base="resolver:BaseDataConnectorType">
+ <choice maxOccurs="unbounded">
+ <element ref="resolver:InputAttributeDefinition"/>
+ <element ref="resolver:InputDataConnector"/>
+ <element ref="resolver:FailoverDataConnector"/>
+ <element name="ContextTemplate" type="resolver:string">
+ <annotation>
+ <documentation>
+ A template that will be used to create the context to query.
+ </documentation>
+ </annotation>
+ </element>
+ <element name="KeyTemplate" type="resolver:string">
+ <annotation>
+ <documentation>
+ A template that will be used to create the key to query.
+ </documentation>
+ </annotation>
+ </element>
+ <element name="RecordMapping" type="resolver:ScriptType">
+ <annotation>
+ <documentation>
+ Maps the record into attributes by means of scripting.
+ Mutually exclusive with generatedAttributeID attribute.
+ </documentation>
+ </annotation>
+ </element>
+ <element name="ResultCache" type="resolver:CacheConfigType"/>
+ <element name="ResultCacheBean" type="resolver:string"/>
+ </choice>
+ <attribute name="storageServiceRef" type="resolver:string" use="required">
+ <annotation>
+ <documentation>
+ Reference to Spring bean of the StorageService to use.
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="customObjectRef" type="resolver:string">
+ <annotation>
+ <documentation>
+ Injected object into Context/Key template building process.
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="generatedAttributeID" type="resolver:string">
+ <annotation>
+ <documentation>
+ The name of the attribute produced by this data connector if simple
+ record mapping is used. Mutually exclusive with ResponseMapping element.
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="noResultIsError" type="resolver:string">
+ <annotation>
+ <documentation>
+ A boolean flag indicating whether a search returning no record should be considered an error. If
+ an error is raised and a failover dependency is defined for this connector the failover will
+ be invoked.
+ Default value is false.
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="templateEngine" type="resolver:string">
+ <annotation>
+ <documentation>
+ Name of the template engine defined within the application.
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="executableSearchBuilderRef" type="resolver:string">
+ <annotation>
+ <documentation>
+ Reference to a Spring bean providing the ExecutableSearchBuilder implementation to use.
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="mappingStrategyRef" type="resolver:string">
+ <annotation>
+ <documentation>
+ Reference to a Spring bean providing the MappingStrategy implementation to use.
+ </documentation>
+ </annotation>
+ </attribute>
+ </extension>
+ </complexContent>
+ </complexType>
+
<complexType name="StoredId">
<annotation>
<documentation>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list