[java-shib-attribute] 02/02: Add a Thymeleaf templated search executor (for LDAP)and wire up. Plus tests
Rod Widdowson
rdw at steadingsoftware.com
Thu Jan 25 15:00:07 UTC 2024
This is an automated email from the git hooks/post-receive script.
rdw pushed a commit to branch dev/IDP-226
in repository java-shib-attribute.
View the commit online:
http://git.shibboleth.net/view/?p=java-shib-attribute.git;a=commit;h=115e1f3d75cd6b18d9772bdebf84900682aa3c68
commit 115e1f3d75cd6b18d9772bdebf84900682aa3c68
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Thu Jan 25 14:59:34 2024 +0000
Add a Thymeleaf templated search executor (for LDAP)and wire up. Plus tests
---
shib-attribute-resolver-api/pom.xml | 5 +
.../ThymeleafExecutableSearchFilterBuilder.java | 254 +++++++++++++++++++++
.../resolver/dc/impl/ThymeLeafSandbox.java | 6 -
.../dc/ldap/impl/LDAPDataConnectorTest.java | 68 +++++-
.../dc/ldap/impl/LDAPDataConnectorParser.java | 23 +-
.../schema/shibboleth-attribute-resolver.xsd | 5 +
.../dc/ldap/impl/LDAPDataConnectorParserTest.java | 23 ++
.../ldap/resolver/ldap-attribute-resolver-tl.xml | 95 ++++++++
8 files changed, 464 insertions(+), 15 deletions(-)
diff --git a/shib-attribute-resolver-api/pom.xml b/shib-attribute-resolver-api/pom.xml
index c82f8dbf7..fd02ab9a2 100644
--- a/shib-attribute-resolver-api/pom.xml
+++ b/shib-attribute-resolver-api/pom.xml
@@ -50,6 +50,11 @@
<artifactId>shib-service</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.thymeleaf</groupId>
+ <artifactId>thymeleaf</artifactId>
+ </dependency>
+
<dependency>
<groupId>${shib-shared.groupId}</groupId>
<artifactId>shib-velocity</artifactId>
diff --git a/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ThymeleafExecutableSearchFilterBuilder.java b/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ThymeleafExecutableSearchFilterBuilder.java
new file mode 100644
index 000000000..7d8a154bb
--- /dev/null
+++ b/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ThymeleafExecutableSearchFilterBuilder.java
@@ -0,0 +1,254 @@
+/*
+ * Licensed 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.dc.ldap;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.ldaptive.FilterTemplate;
+import org.slf4j.Logger;
+import org.thymeleaf.IEngineConfiguration;
+import org.thymeleaf.TemplateEngine;
+import org.thymeleaf.context.Context;
+import org.thymeleaf.context.ITemplateContext;
+import org.thymeleaf.dialect.AbstractProcessorDialect;
+import org.thymeleaf.exceptions.TemplateProcessingException;
+import org.thymeleaf.model.ITemplateEnd;
+import org.thymeleaf.model.ITemplateStart;
+import org.thymeleaf.processor.IProcessor;
+import org.thymeleaf.processor.templateboundaries.AbstractTemplateBoundariesProcessor;
+import org.thymeleaf.processor.templateboundaries.ITemplateBoundariesStructureHandler;
+import org.thymeleaf.standard.StandardDialect;
+import org.thymeleaf.standard.inline.AbstractStandardInliner;
+import org.thymeleaf.standard.processor.StandardInliningTextProcessor;
+import org.thymeleaf.templatemode.TemplateMode;
+import org.thymeleaf.templateresolver.StringTemplateResolver;
+
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.dc.ExecutableSearchBuilder;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.velocity.ThymeleafTemplate;
+
+/**
+ * An {@link ExecutableSearchBuilder} that generates the search filter to
+ * be executed by evaluating a {@link Template} against the currently resolved attributes within a
+ * {@link AttributeResolutionContext}.
+ */
+public class ThymeleafExecutableSearchFilterBuilder extends AbstractExecutableSearchFilterBuilder {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ThymeleafExecutableSearchFilterBuilder.class);
+
+ /** Template evaluated to generate a search filter. */
+ @NonnullAfterInit private ThymeleafTemplate template;
+
+ /** Template (as Text) to be evaluated. */
+ @NonnullAfterInit private String templateText;
+
+ /** ThymeLeaf Engine. */
+ @Nonnull static private TemplateEngine engine = createEngine();
+
+ /**
+ * Gets the template to be evaluated.
+ *
+ * @return the template
+ */
+ @NonnullAfterInit public ThymeleafTemplate getTemplate() {
+ return template;
+ }
+
+ /**
+ * Gets the template text to be evaluated.
+ *
+ * @return the template text
+ */
+ @NonnullAfterInit public String getTemplateText() {
+ return templateText;
+ }
+
+ /**
+ * Sets the template to be evaluated.
+ *
+ * @param velocityTemplate template to be evaluated
+ */
+ public void setTemplateText(@Nullable final String velocityTemplate) {
+ checkSetterPreconditions();
+
+ templateText = StringSupport.trimOrNull(velocityTemplate);
+ }
+
+ /** Method to allow private additions to the velocity context.
+ * @param velocityContext where to add the information
+ * @param resolutionContext current resolution context
+ *
+ protected void addExtraVelocityContext(@Nonnull final IContext velocityContext,
+ @Nonnull final AttributeResolutionContext resolutionContext) {
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ final String localTemplateText = templateText;
+ if (null == localTemplateText) {
+ throw new ComponentInitializationException(
+ "TemplatedExecutableStatementBuilder: template text must be non null");
+ }
+
+ template = ThymeleafTemplate.fromTemplate(engine, localTemplateText);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull public ExecutableSearchFilter build(@Nonnull final AttributeResolutionContext resolutionContext,
+ @Nonnull final Map<String, List<IdPAttributeValue>> dependencyAttributes) throws ResolutionException {
+
+ final Context context = new Context();
+ log.trace("Creating search filter using attribute resolution context {}", resolutionContext);
+ context.setVariable("resolutionContext", resolutionContext);
+
+ if (dependencyAttributes != null && !dependencyAttributes.isEmpty()) {
+ for (final Map.Entry<String, List<IdPAttributeValue>> entry : dependencyAttributes.entrySet()) {
+ final List<Object> values = new ArrayList<>(entry.getValue().size());
+ for (final IdPAttributeValue value : entry.getValue()) {
+ values.add(value.getNativeValue());
+ }
+ log.trace("Adding dependency {} to context with {} value(s)", entry.getKey(), values.size());
+ context.setVariable(entry.getKey(), values);
+ }
+ }
+
+ try {
+ final FilterTemplate searchFilter = new FilterTemplate(merge(context));
+ return super.build(searchFilter);
+ } catch (final Exception e) {
+ log.error("Error running template: {}", e.getMessage());
+ throw new ResolutionException("Error running template", e);
+ }
+ }
+
+ /**
+ * Invokes {@link Template#merge(org.apache.velocity.context.Context)} on the supplied context.
+ *
+ * @param context to merge
+ *
+ * @return result of the merge operation
+ */
+ @Nonnull protected String merge(@Nonnull final Context context) {
+ final String result = template.merge(context).trim();
+ log.debug("Template text {} yields {}", templateText, result);
+ assert result != null;
+ return result;
+ }
+
+ /** Create the one-off template engine `used for
+ * @return
+ */
+ private static TemplateEngine createEngine() {
+ final TemplateEngine engine = new TemplateEngine();
+ engine.setAdditionalDialects(CollectionSupport.singleton(new LdaptiveDialect()));
+ engine.setTemplateResolver(new StringTemplateResolver());
+ return engine;
+ }
+
+ /*
+ * Classes used to create the engine. This is an API module so we keep the classes inline and private (need exbedded into shib-shared)
+ */
+
+ public static final class LdaptiveTemplateBoundariesProcessor extends AbstractTemplateBoundariesProcessor {
+
+ public static final int PRECEDENCE = 11;
+
+ public LdaptiveTemplateBoundariesProcessor(final TemplateMode templateMode) {
+ super(templateMode, PRECEDENCE);
+ }
+
+
+ @Override
+ public void doProcessTemplateStart(
+ final ITemplateContext context,
+ final ITemplateStart templateStart, final ITemplateBoundariesStructureHandler structureHandler) {
+
+ switch (getTemplateMode()) {
+
+ case TEXT:
+ structureHandler.setInliner(new LdaptiveInliner(context.getConfiguration()));
+ break;
+
+ default:
+ throw new TemplateProcessingException(
+ "Unrecognized template mode: " + getTemplateMode() + ", cannot initialize inlining!");
+
+ }
+
+ }
+
+
+ @Override
+ public void doProcessTemplateEnd(
+ final ITemplateContext context,
+ final ITemplateEnd templateEnd, final ITemplateBoundariesStructureHandler structureHandler) {
+
+ // Empty - nothing to be done on template end
+
+ }
+ }
+
+ private static class LdaptiveInliner extends AbstractStandardInliner {
+
+ protected LdaptiveInliner(IEngineConfiguration configuration) {
+ super(configuration, TemplateMode.TEXT);
+ }
+
+ @Override
+ protected String produceEscapedOutput(Object value) {
+ if (value instanceof String str){
+ return FilterTemplate.encodeValue(str);
+ } else if (value instanceof byte[] array) {
+ return FilterTemplate.encodeValue((byte[]) array);
+ }
+ return value.toString();
+ }
+
+ }
+
+ private static class LdaptiveDialect extends AbstractProcessorDialect {
+
+ private static final String DIALECT_NAME = "SANDBOX Dialect";
+
+
+ public LdaptiveDialect() {
+ super(DIALECT_NAME, "sandbox", StandardDialect.PROCESSOR_PRECEDENCE);
+ }
+
+ public Set<IProcessor> getProcessors(final String dialectPrefix) {
+ return CollectionSupport.setOf(
+ new StandardInliningTextProcessor(TemplateMode.TEXT),
+ new LdaptiveTemplateBoundariesProcessor(TemplateMode.TEXT));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/impl/ThymeLeafSandbox.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/impl/ThymeLeafSandbox.java
index c62a8fda5..b494598b3 100644
--- a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/impl/ThymeLeafSandbox.java
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/impl/ThymeLeafSandbox.java
@@ -25,18 +25,12 @@ import org.thymeleaf.TemplateSpec;
import org.thymeleaf.context.Context;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.dialect.AbstractProcessorDialect;
-import org.thymeleaf.engine.EngineEventUtils;
import org.thymeleaf.exceptions.TemplateProcessingException;
-import org.thymeleaf.inline.IInliner;
-import org.thymeleaf.inline.NoOpInliner;
import org.thymeleaf.model.ITemplateEnd;
import org.thymeleaf.model.ITemplateStart;
-import org.thymeleaf.model.IText;
import org.thymeleaf.processor.IProcessor;
import org.thymeleaf.processor.templateboundaries.AbstractTemplateBoundariesProcessor;
import org.thymeleaf.processor.templateboundaries.ITemplateBoundariesStructureHandler;
-import org.thymeleaf.processor.text.AbstractTextProcessor;
-import org.thymeleaf.processor.text.ITextStructureHandler;
import org.thymeleaf.standard.StandardDialect;
import org.thymeleaf.standard.inline.AbstractStandardInliner;
import org.thymeleaf.standard.processor.StandardInliningTextProcessor;
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnectorTest.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnectorTest.java
index 827a0ba50..ea7ae5c27 100644
--- a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnectorTest.java
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnectorTest.java
@@ -49,6 +49,7 @@ import net.shibboleth.idp.attribute.resolver.dc.ldap.ParameterizedExecutableSear
import net.shibboleth.idp.attribute.resolver.dc.ldap.SearchResultMappingStrategy;
import net.shibboleth.idp.attribute.resolver.dc.ldap.StringAttributeValueMappingStrategy;
import net.shibboleth.idp.attribute.resolver.dc.ldap.TemplatedExecutableSearchFilterBuilder;
+import net.shibboleth.idp.attribute.resolver.dc.ldap.ThymeleafExecutableSearchFilterBuilder;
import net.shibboleth.idp.attribute.resolver.testing.TestSources;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.component.ComponentInitializationException;
@@ -268,6 +269,13 @@ public class LDAPDataConnectorTest {
assertEquals(filter.getResultCacheKey(), "(cn=domain\\5Cuser\\2A)");
}
+ @Test public void resolveTemplateThymeleaf() throws ComponentInitializationException, ResolutionException {
+ final ThymeleafExecutableSearchFilterBuilder builder = new ThymeleafExecutableSearchFilterBuilder();
+ builder.setTemplateText("(uid=[[${resolutionContext.principal}]])");
+ builder.initialize();
+ resolve(builder);
+ }
+
@Test public void resolveTemplate() throws ComponentInitializationException, ResolutionException {
final TemplatedExecutableSearchFilterBuilder builder = new TemplatedExecutableSearchFilterBuilder();
builder.setTemplateText("(uid=${resolutionContext.principal})");
@@ -275,10 +283,11 @@ public class LDAPDataConnectorTest {
builder.initialize();
resolve(builder);
}
+
@Test(expectedExceptions={ResolutionException.class}) public void resolveTemplateExcept() throws ComponentInitializationException, ResolutionException {
final TemplatedExecutableSearchFilterBuilder builder = new TemplatedExecutableSearchFilterBuilder();
- builder.setTemplateText("(uid=${resolutionContext.AttributeRecipientID.toString().substring(99, 106)})");
+ builder.setTemplateText("(uid=[[${resolutionContext.AttributeRecipientID.toString().substring(99, 106)}]])");
final Properties props = new Properties();
// TODO, should expose a way to set the strict prop underneath
props.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT, "true");
@@ -289,6 +298,29 @@ public class LDAPDataConnectorTest {
resolve(builder);
}
+ @Test(enabled=false, expectedExceptions={ResolutionException.class}) public void resolveTemplateExceptThymeleaf() throws ComponentInitializationException, ResolutionException {
+ final ThymeleafExecutableSearchFilterBuilder builder = new ThymeleafExecutableSearchFilterBuilder();
+ builder.setTemplateText("(uid=[[${resolutionContext.AttributeRecipientID.toString().substring(99, 106)}]])");
+ fail("What do the proerties do for Velocity in the above");
+ builder.initialize();
+ resolve(builder);
+ }
+
+ @Test public void resolveTemplateWithDependsThymeleaf() throws ComponentInitializationException, ResolutionException {
+ final ThymeleafExecutableSearchFilterBuilder builder = new ThymeleafExecutableSearchFilterBuilder();
+ builder.setTemplateText("(&(cn=[[${resolutionContext.principal}]])(eduPersonAffiliation=[[${affiliation[0]}]]))");
+ builder.initialize();
+ final AttributeResolutionContext context =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+ final Map<String, List<IdPAttributeValue>> dependsAttributes = new HashMap<>();
+ final List<IdPAttributeValue> attributeValues = new ArrayList<>();
+ attributeValues.add(new StringAttributeValue("student"));
+ dependsAttributes.put("affiliation", attributeValues);
+ final ExecutableSearchFilter filter = builder.build(context, dependsAttributes);
+ assertEquals(filter.getSearchFilter().format(), "(&(cn=PETER_THE_PRINCIPAL)(eduPersonAffiliation=student))");
+ assertEquals(filter.getResultCacheKey(), "(&(cn=PETER_THE_PRINCIPAL)(eduPersonAffiliation=student))");
+ }
@Test public void resolveTemplateWithDepends() throws ComponentInitializationException, ResolutionException {
final TemplatedExecutableSearchFilterBuilder builder = new TemplatedExecutableSearchFilterBuilder();
@@ -307,6 +339,28 @@ public class LDAPDataConnectorTest {
assertEquals(filter.getResultCacheKey(), "(&(cn=PETER_THE_PRINCIPAL)(eduPersonAffiliation=student))");
}
+ @Test public void resolveTemplateWithMultiValueDependsThymeleaf() throws ComponentInitializationException, ResolutionException {
+ final ThymeleafExecutableSearchFilterBuilder builder = new ThymeleafExecutableSearchFilterBuilder();
+ builder.setTemplateText(
+ "(&(cn=[[${resolutionContext.principal}]])(eduPersonEntitlement=[[${entitlement[0]}]])(eduPersonEntitlement=[[${entitlement[1]}]]))");
+ builder.initialize();
+ final AttributeResolutionContext context =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+ final Map<String, List<IdPAttributeValue>> dependsAttributes = new HashMap<>();
+ final List<IdPAttributeValue> attributeValues = new ArrayList<>();
+ attributeValues.add(new StringAttributeValue("entitlement1"));
+ attributeValues.add(new StringAttributeValue("entitlement*"));
+ dependsAttributes.put("entitlement", attributeValues);
+ final ExecutableSearchFilter filter = builder.build(context, dependsAttributes);
+ assertEquals(
+ filter.getSearchFilter().format(),
+ "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2A))");
+ assertEquals(
+ filter.getResultCacheKey(),
+ "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2A))");
+ }
+
@Test public void resolveTemplateWithMultiValueDepends() throws ComponentInitializationException, ResolutionException {
final TemplatedExecutableSearchFilterBuilder builder = new TemplatedExecutableSearchFilterBuilder();
builder.setTemplateText(
@@ -329,7 +383,17 @@ public class LDAPDataConnectorTest {
filter.getResultCacheKey(),
"(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2A))");
}
-
+ @Test public void escapeTemplateThymeleaf() throws ComponentInitializationException, ResolutionException {
+ final ThymeleafExecutableSearchFilterBuilder builder = new ThymeleafExecutableSearchFilterBuilder();
+ builder.setTemplateText("(cn=[[${resolutionContext.principal}]])");
+ builder.initialize();
+ final AttributeResolutionContext context =
+ TestSources.createResolutionContext("domain\\user*", TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+ final ExecutableSearchFilter filter = builder.build(context, CollectionSupport.emptyMap());
+ assertEquals(filter.getSearchFilter().format(), "(cn=domain\\5Cuser\\2A)");
+ assertEquals(filter.getResultCacheKey(), "(cn=domain\\5Cuser\\2A)");
+ }
@Test public void escapeTemplate() throws ComponentInitializationException, ResolutionException {
final TemplatedExecutableSearchFilterBuilder builder = new TemplatedExecutableSearchFilterBuilder();
builder.setTemplateText("(cn=${resolutionContext.principal})");
diff --git a/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java b/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java
index b644371a1..67da92dee 100644
--- a/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java
+++ b/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java
@@ -58,6 +58,7 @@ import org.w3c.dom.Element;
import net.shibboleth.idp.attribute.resolver.dc.ldap.StringAttributeValueMappingStrategy;
import net.shibboleth.idp.attribute.resolver.dc.ldap.TemplatedExecutableSearchFilterBuilder;
+import net.shibboleth.idp.attribute.resolver.dc.ldap.ThymeleafExecutableSearchFilterBuilder;
import net.shibboleth.idp.attribute.resolver.dc.ldap.impl.ConnectionFactoryValidator;
import net.shibboleth.idp.attribute.resolver.dc.ldap.impl.LDAPDataConnector;
import net.shibboleth.idp.attribute.resolver.spring.dc.AbstractDataConnectorParser;
@@ -451,15 +452,23 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
* @return the bean definition for the template search builder.
*/
@Nonnull public BeanDefinition createTemplateBuilder() {
- final BeanDefinitionBuilder templateBuilder = BeanDefinitionBuilder.genericBeanDefinition(
- TemplatedExecutableSearchFilterBuilder.class);
- templateBuilder.setInitMethodName("initialize");
-
+ final String useThymeleafAttr = StringSupport.trimOrNull(configElement.getAttribute("useThymeleaf"));
+ boolean useTymeleaf = useThymeleafAttr == null ? false : Boolean.parseBoolean(useThymeleafAttr);
String velocityEngineRef = StringSupport.trimOrNull(configElement.getAttribute("templateEngine"));
- if (null == velocityEngineRef) {
- velocityEngineRef = "shibboleth.VelocityEngine";
+ final BeanDefinitionBuilder templateBuilder;
+ if (useTymeleaf) {
+ templateBuilder = BeanDefinitionBuilder.genericBeanDefinition(ThymeleafExecutableSearchFilterBuilder.class);
+ if (velocityEngineRef != null) {
+ LOG.error("templateEngine is not valif for Thymeleaf scripting");
+ }
+ } else {
+ templateBuilder = BeanDefinitionBuilder.genericBeanDefinition(TemplatedExecutableSearchFilterBuilder.class);
+ if (null == velocityEngineRef) {
+ velocityEngineRef = "shibboleth.VelocityEngine";
+ }
+ templateBuilder.addPropertyReference("velocityEngine", velocityEngineRef);
}
- templateBuilder.addPropertyReference("velocityEngine", velocityEngineRef);
+ templateBuilder.setInitMethodName("initialize");
templateBuilder.addPropertyValue("templateText", getFilterText());
diff --git a/shib-attribute-resolver-spring/src/main/resources/schema/shibboleth-attribute-resolver.xsd b/shib-attribute-resolver-spring/src/main/resources/schema/shibboleth-attribute-resolver.xsd
index cca79e74f..594a42ca0 100644
--- a/shib-attribute-resolver-spring/src/main/resources/schema/shibboleth-attribute-resolver.xsd
+++ b/shib-attribute-resolver-spring/src/main/resources/schema/shibboleth-attribute-resolver.xsd
@@ -917,6 +917,11 @@
</documentation>
</annotation>
</attribute>
+ <attribute name="useThymeleaf" type="string">
+ <annotation>
+ <documentation>Do we use Velocity or Thymeleaf</documentation>
+ </annotation>
+ </attribute>
<attribute name="useStartTLS" type="resolver:string">
<annotation>
<documentation>
diff --git a/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParserTest.java b/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParserTest.java
index 37c39852a..a58fcdbf1 100644
--- a/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParserTest.java
+++ b/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParserTest.java
@@ -166,6 +166,29 @@ public class LDAPDataConnectorParserTest {
assertEquals(attrs.get("employeeNumber").getValues().get(0).getNativeValue(), "C2J20hMNp7NlUwQ+");
assertNotNull(attrs.get("entryDN"));
}
+
+ @Test public void tlConfig() throws Exception {
+ final LDAPDataConnector dataConnector =
+ getLdapDataConnector(new String[] {"net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-tl.xml"});
+ assertNotNull(dataConnector);
+ assertFalse(dataConnector.isFailFastInitialize());
+ doTest(dataConnector);
+ final StringAttributeValueMappingStrategy mappingStrategy =
+ (StringAttributeValueMappingStrategy) dataConnector.getMappingStrategy();
+ assertEquals(mappingStrategy.getResultRenamingMap().size(), 1);
+ assertEquals(mappingStrategy.getResultRenamingMap().get("homephone"), "phonenumber");
+
+ dataConnector.initialize();
+ final AttributeResolutionContext context =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+ final Map<String, IdPAttribute> attrs = dataConnector.resolve(context);
+ assert attrs != null;
+ assertEquals(2, attrs.size());
+ assertEquals(attrs.get("employeeNumber").getValues().get(0).getNativeValue(), "C2J20hMNp7NlUwQ+");
+ assertNotNull(attrs.get("entryDN"));
+ }
+
@Test public void v2NoSec() throws Exception {
final LDAPDataConnector dataConnector =
diff --git a/shib-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-tl.xml b/shib-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-tl.xml
new file mode 100644
index 000000000..398378d1f
--- /dev/null
+++ b/shib-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-tl.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<AttributeResolver xmlns="urn:mace:shibboleth:2.0:resolver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sec="urn:mace:shibboleth:2.0:security"
+ xsi:schemaLocation="urn:mace:shibboleth:2.0:resolver http://shibboleth.net/schema/idp/shibboleth-attribute-resolver.xsd
+ urn:mace:shibboleth:2.0:security http://shibboleth.net/schema/idp/shibboleth-security.xsd">
+
+ <DataConnector id="myLDAP" xsi:type="LDAPDirectory"
+ ldapURL="ldap://localhost:10389"
+ baseDN="ou=people,dc=shibboleth,dc=net"
+ principal="cn=Directory Manager"
+ principalCredential="password"
+ failFastInitialize="false"
+ useStartTLS="true"
+ startTLSTimeout="PT2S"
+ useThymeleaf="true"
+ searchTimeLimit="PT7S"
+ noResultIsError="true"
+ multipleResultsIsError="true"
+ noRetryDelay="PT5M"
+ trustFile="net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap.crt"
+ connectTimeout="PT2S"
+ responseTimeout="PT4S">
+
+ <FailoverDataConnector ref="2123"/>
+
+ <ResultCache expireAfterAccess="PT10S" maximumCachedElements="25"/>
+
+ <Column columnName="homephone" attributeID="phonenumber" />
+
+ <ConnectionPool
+ minPoolSize="5"
+ maxPoolSize="10"
+ blockWaitTime="PT5S"
+ expirationTime="PT10M"
+ validatePeriodically="true"
+ validateTimerPeriod="PT15M"
+ validateDN="dc=shibboleth,dc=net"
+ validateFilter="(ou=people)" />
+
+
+ <StartTLSAuthenticationCredential xsi:type="sec:X509ResourceBacked" id="IdPtoLDAPCredential">
+ <sec:PrivateKey>src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.key</sec:PrivateKey>
+ <sec:Certificate>src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.crt</sec:Certificate>
+ </StartTLSAuthenticationCredential>
+
+ <StartTLSAuthenticationCredential xsi:type="sec:X509ResourceBacked" id="IdPtoLDAPCredential">
+ <sec:PrivateKey>src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.key</sec:PrivateKey>
+ <sec:Certificate>src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.crt</sec:Certificate>
+ </StartTLSAuthenticationCredential>
+
+ <StartTLSTrustCredential xsi:type="sec:X509ResourceBacked" id="IdPtoLDAPCredential">
+ <sec:PrivateKey>src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.key</sec:PrivateKey>
+ <sec:Certificate>src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.crt</sec:Certificate>
+ </StartTLSTrustCredential>
+
+ <StartTLSTrustCredential xsi:type="sec:X509ResourceBacked" id="IdPtoLDAPCredential">
+ <sec:PrivateKey>src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.key</sec:PrivateKey>
+ <sec:Certificate>src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.crt</sec:Certificate>
+ </StartTLSTrustCredential>
+
+ <InputAttributeDefinition ref="321"/>
+
+ <ReturnAttributes>a b g t employeeNumber</ReturnAttributes>
+
+ <ReturnAttributes>a b g t employeeNumber</ReturnAttributes>
+
+ <BinaryAttributes>g employeeNumber</BinaryAttributes>
+
+ <FilterTemplate>
+ <![CDATA[
+ (uid=[[${resolutionContext.principal}]])
+ ]]>
+ </FilterTemplate>
+
+ <ResultCache maximumCachedElements="20"/>
+
+ <FilterTemplate>
+ <![CDATA[
+ (uid=[[${resolutionContext.principal}]]
+ )
+ ]]>
+ </FilterTemplate>
+
+ <ConnectionPool
+ minPoolSize="5"
+ maxPoolSize="10"
+ blockWaitTime="PT5S"
+ expirationTime="PT10M"
+ validatePeriodically="true"
+ validateTimerPeriod="PT15M"
+ validateDN="dc=shibboleth,dc=net"
+ validateFilter="(ou=people)"/>
+
+ </DataConnector>
+
+</AttributeResolver>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list