[java-identity-provider] branch main updated: Relocate subject-sourced IdPAttribute functions and unit tests.
Scott Cantor
cantor.2 at osu.edu
Mon Jul 11 15:21:34 UTC 2022
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=7ceb0b18f68a2a4e3f46e8dd72d623a431b990fc
The following commit(s) were added to refs/heads/main by this push:
new 7ceb0b18f Relocate subject-sourced IdPAttribute functions and unit tests.
7ceb0b18f is described below
commit 7ceb0b18f68a2a4e3f46e8dd72d623a431b990fc
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon Jul 11 11:21:31 2022 -0400
Relocate subject-sourced IdPAttribute functions and unit tests.
---
idp-authn-impl/pom.xml | 26 +++
.../impl/IdPAttributePrincipalValuesFunction.java | 76 +++++++
.../SubjectDerivedAttributeValuesFunction.java | 193 ++++++++++++++++++
...ontextDerivedAttributeDefinitionParserTest.java | 147 ++++++++++++++
.../SubjectDerivedAttributeValuesFunctionTest.java | 226 +++++++++++++++++++++
.../resolver/spring/ad/contextDerived.xml | 5 +
.../resolver/spring/ad/subjectDerived.xml | 9 +
.../resolver/spring/ad/subjectDerivedComplex.xml | 5 +
.../spring/ad/subjectDerivedDependency.xml | 10 +
.../resolver/spring/ad/subjectDerivedFail.xml | 9 +
.../resolver/spring/ad/subjectDerivedViaC14N.xml | 9 +
.../resolver/spring/ad/subjectDerivedWarn.xml | 8 +
.../resolver/spring/contextDerivedBeans.xml | 20 ++
.../idp/attribute/resolver/spring/customBean.xml | 50 +++++
idp-spring/.classpath | 2 +
15 files changed, 795 insertions(+)
diff --git a/idp-authn-impl/pom.xml b/idp-authn-impl/pom.xml
index 286f9bd7f..a5fa79154 100644
--- a/idp-authn-impl/pom.xml
+++ b/idp-authn-impl/pom.xml
@@ -241,6 +241,32 @@
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>net.shibboleth</groupId>
+ <artifactId>shib-attribute-resolver-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>net.shibboleth</groupId>
+ <artifactId>shib-attribute-resolver-api</artifactId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+
+ <dependency>
+ <groupId>net.shibboleth</groupId>
+ <artifactId>shib-attribute-resolver-spring</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>net.shibboleth</groupId>
+ <artifactId>shib-attribute-resolver-spring</artifactId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+
<dependency>
<groupId>${opensaml.groupId}</groupId>
<artifactId>opensaml-core</artifactId>
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/context/impl/IdPAttributePrincipalValuesFunction.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/context/impl/IdPAttributePrincipalValuesFunction.java
new file mode 100644
index 000000000..d6c10b7ef
--- /dev/null
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/context/impl/IdPAttributePrincipalValuesFunction.java
@@ -0,0 +1,76 @@
+/*
+ * 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.authn.context.impl;
+
+import java.security.Principal;
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.authn.principal.IdPAttributePrincipal;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/** Engine to mine values from {@link IdPAttributePrincipal}s. */
+public class IdPAttributePrincipalValuesFunction extends AbstractInitializableComponent implements
+ Function<Principal, List<IdPAttributeValue>> {
+
+ /** The Attribute Name to look for. */
+ @NonnullAfterInit @NotEmpty private String attributeName;
+
+ /**
+ * Set the attribute name.
+ *
+ * @param attrName the attribute name to read values from
+ */
+ public void setAttributeName(@Nonnull @NotEmpty final String attrName) {
+ attributeName = Constraint.isNotNull(StringSupport.trimOrNull(attrName),
+ "Attribute Name cannot be null or empty");
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (attributeName == null) {
+ throw new ComponentInitializationException("Attribute name cannot be null or empty");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nullable public List<IdPAttributeValue> apply(@Nullable final Principal principal) {
+
+ if (null != principal && principal instanceof IdPAttributePrincipal) {
+ final IdPAttributePrincipal attributePrincipal = (IdPAttributePrincipal) principal;
+ final IdPAttribute attribute = attributePrincipal.getAttribute();
+ if (attributeName.equals(attribute.getId())) {
+ return attribute.getValues();
+ }
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/context/impl/SubjectDerivedAttributeValuesFunction.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/context/impl/SubjectDerivedAttributeValuesFunction.java
new file mode 100644
index 000000000..c43b25218
--- /dev/null
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/context/impl/SubjectDerivedAttributeValuesFunction.java
@@ -0,0 +1,193 @@
+/*
+ * 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.authn.context.impl;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.security.auth.Subject;
+
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
+import net.shibboleth.idp.authn.context.SubjectContext;
+import net.shibboleth.idp.authn.context.navigate.SubjectCanonicalizationContextSubjectLookupFunction;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A Function which returns {@link IdPAttributeValue}s derived from the {@link java.security.Principal}s
+ * associated with the request. The precise values are determined by an injected {@link Function}.
+ */
+public class SubjectDerivedAttributeValuesFunction extends AbstractIdentifiableInitializableComponent implements
+ Function<ProfileRequestContext,List<IdPAttributeValue>> {
+
+ /** Logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(SubjectDerivedAttributeValuesFunction.class);
+
+ /** Flag denoting whether plugin is being used for subject c14n or standard usage. */
+ private boolean forCanonicalization;
+
+ /** Strategy used to locate the {@link SubjectContext} to use. */
+ @Nonnull private Function<ProfileRequestContext,SubjectContext> scLookupStrategy;
+
+ /**
+ * {@link Function} used to generate the values associated with a {@link Principal}
+ *
+ * The {@link Function} returns null or an empty list if the {@link Principal} isn't relevant.
+ */
+ @NonnullAfterInit private Function<Principal,List<IdPAttributeValue>> attributeValuesFunction;
+
+ /** Strategy used to locate the {@link Subject} to use. */
+ @Nullable private Function<ProfileRequestContext,Subject> subjectLookupStrategy;
+
+ /** Constructor. */
+ public SubjectDerivedAttributeValuesFunction() {
+ scLookupStrategy = new ChildContextLookup<>(SubjectContext.class);
+ }
+
+ /**
+ * Sets whether the definition is being used during Subject Canonicalization, causing
+ * auto-installation of an alternate Subject lookup strategy.
+ *
+ * @param flag flag to set
+ */
+ public void setForCanonicalization(final boolean flag) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ forCanonicalization = flag;
+ }
+
+ /**
+ * Sets the strategy used to locate the {@link SubjectContext} associated with a given
+ * {@link net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext}.
+ *
+ * @param strategy strategy used to locate the {@link SubjectContext} associated with a given
+ * {@link net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext}
+ */
+ public void setSubjectContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,SubjectContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ scLookupStrategy = Constraint.isNotNull(strategy, "SubjectContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Sets the function to extract attribute values from a {@link Principal}.
+ *
+ * @param strategy strategy function
+ */
+ public void setAttributeValuesFunction(@Nonnull final Function<Principal,List<IdPAttributeValue>> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ attributeValuesFunction = Constraint.isNotNull(strategy, "Attribute value lookup strategy cannot be null");
+ }
+
+ /**
+ * Sets the strategy used to locate a {@link Subject} associated with a given
+ * {@link net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext}.
+ *
+ * @param strategy strategy used to locate a {@link Subject} associated with a given
+ * {@link net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext}
+ */
+ public void setSubjectLookupStrategy(
+ @Nullable final Function<ProfileRequestContext,Subject> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ subjectLookupStrategy = strategy;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (attributeValuesFunction == null) {
+ throw new ComponentInitializationException("Attribute value lookup strategy cannot be null");
+ }
+
+ if (forCanonicalization && subjectLookupStrategy == null) {
+ log.debug("{} Marked for use during canonicalication, auto-installing Subject lookup strategy",
+ getLogPrefix());
+ subjectLookupStrategy = new SubjectCanonicalizationContextSubjectLookupFunction().compose(
+ new ChildContextLookup<>(SubjectCanonicalizationContext.class));
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nullable public List<IdPAttributeValue> apply(@Nullable final ProfileRequestContext prc) {
+
+ Collection<Subject> subjects = Collections.emptyList();
+
+ if (subjectLookupStrategy != null) {
+ final Subject subject = subjectLookupStrategy.apply(prc);
+ if (subject == null) {
+ log.debug("{} No Subject returned from lookup, no attribute resolved", getLogPrefix());
+ return null;
+ }
+ subjects = Collections.singletonList(subject);
+ } else {
+ final SubjectContext cs = scLookupStrategy.apply(prc);
+ if (cs == null || cs.getSubjects().isEmpty()) {
+ log.debug("{} No Subjects returned from SubjectContext lookup, no attribute resolved", getLogPrefix());
+ return null;
+ }
+ subjects = cs.getSubjects();
+ }
+
+ final List<IdPAttributeValue> results = new ArrayList<>();
+
+ for (final Subject subject : subjects) {
+ for (final Principal principal : subject.getPrincipals()) {
+ final List<IdPAttributeValue> values = attributeValuesFunction.apply(principal);
+ if (null != values && !values.isEmpty()) {
+ results.addAll(values);
+ }
+ }
+ }
+ if (results.isEmpty()) {
+ log.info("{} Generated no values, no attribute resolved", getLogPrefix());
+ return null;
+ }
+ log.debug("{} Generated {} values", getLogPrefix(), results.size());
+ log.trace("{} Values:", getLogPrefix(), results);
+ return results;
+ }
+
+ /**
+ * Produce a consistent log prefix.
+ *
+ * @return a consistent log prefix
+ */
+ private String getLogPrefix() {
+ return "SubjectDerivedAttributeDefinition " + getId();
+ }
+
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/context/impl/ContextDerivedAttributeDefinitionParserTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/context/impl/ContextDerivedAttributeDefinitionParserTest.java
new file mode 100644
index 000000000..dd3a4a118
--- /dev/null
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/context/impl/ContextDerivedAttributeDefinitionParserTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.authn.context.impl;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+
+import org.springframework.beans.factory.BeanDefinitionStoreException;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.resolver.AttributeDefinition;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.ad.impl.ContextDerivedAttributeDefinition;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.spring.ad.impl.ContextDerivedAttributeDefinitionParser;
+import net.shibboleth.idp.attribute.resolver.spring.ad.impl.SubjectDerivedAttributeDefinitionParser;
+import net.shibboleth.idp.attribute.resolver.spring.testing.BaseAttributeDefinitionParserTest;
+import net.shibboleth.idp.attribute.resolver.testing.TestSources;
+import net.shibboleth.idp.authn.AuthenticationResult;
+import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
+import net.shibboleth.idp.authn.context.SubjectContext;
+import net.shibboleth.idp.authn.principal.IdPAttributePrincipal;
+import net.shibboleth.idp.saml.authn.principal.AuthenticationMethodPrincipal;
+
+/**
+ * Test for {@link SubjectDerivedAttributeDefinitionParser} and {@link ContextDerivedAttributeDefinitionParser}.
+ */
+ at SuppressWarnings("javadoc")
+public class ContextDerivedAttributeDefinitionParserTest extends BaseAttributeDefinitionParserTest {
+
+ /** Simple result. */
+ private static final String SIMPLE_VALUE = "simple";
+
+ private AttributeResolutionContext getCtx(final String attributeName, boolean c14n) {
+ final List<IdPAttributeValue> list = new ArrayList<>(2);
+ list.add(new StringAttributeValue(SIMPLE_VALUE));
+ list.add(new StringAttributeValue(SIMPLE_VALUE + "2"));
+
+ final IdPAttribute attr = new IdPAttribute(attributeName);
+ attr.setValues(list);
+
+ final AttributeResolutionContext ctx = TestSources.createResolutionContext(TestSources.PRINCIPAL_ID,
+ TestSources.IDP_ENTITY_ID, TestSources.SP_ENTITY_ID);
+ final Subject subject = new Subject();
+ subject.getPrincipals().add(new IdPAttributePrincipal(attr));
+ subject.getPrincipals().add(new AuthenticationMethodPrincipal(SIMPLE_VALUE + "2"));
+
+ if (c14n) {
+ ctx.getParent().getSubcontext(SubjectCanonicalizationContext.class, true).setSubject(subject);
+ } else {
+ final SubjectContext sc = ctx.getParent().getSubcontext(SubjectContext.class, true);
+ final Map<String, AuthenticationResult> authnResults = sc.getAuthenticationResults();
+ authnResults.put("one", new AuthenticationResult("1", subject));
+ }
+
+ return ctx;
+ }
+
+ @Test public void resolverSubject() throws ResolutionException {
+ final AttributeDefinition attrDef =
+ getAttributeDefn("subjectDerived.xml", ContextDerivedAttributeDefinition.class);
+
+ final List<IdPAttributeValue> foo = attrDef.resolve(getCtx("Whatever", false)).getValues();
+
+ assertEquals(2, foo.size());
+ assertTrue(foo.contains(new StringAttributeValue(SIMPLE_VALUE)));
+ assertTrue(foo.contains(new StringAttributeValue(SIMPLE_VALUE + "2")));
+
+ }
+
+ @Test public void resolverSubjectViaC14N() throws ResolutionException {
+ final AttributeDefinition attrDef =
+ getAttributeDefn("subjectDerivedViaC14N.xml", ContextDerivedAttributeDefinition.class);
+
+ final List<IdPAttributeValue> foo = attrDef.resolve(getCtx("Whatever", true)).getValues();
+
+ assertEquals(2, foo.size());
+ assertTrue(foo.contains(new StringAttributeValue(SIMPLE_VALUE)));
+ assertTrue(foo.contains(new StringAttributeValue(SIMPLE_VALUE + "2")));
+
+ }
+
+ @Test public void complex() throws ResolutionException {
+ final AttributeDefinition attrDef = getAttributeDefn("subjectDerivedComplex.xml", "contextDerivedBeans.xml",
+ ContextDerivedAttributeDefinition.class);
+
+ final List<IdPAttributeValue> foo = attrDef.resolve(getCtx("BeanWhatever", false)).getValues();
+
+ assertEquals(2, foo.size());
+ assertTrue(foo.contains(new StringAttributeValue(SIMPLE_VALUE)));
+ assertTrue(foo.contains(new StringAttributeValue(SIMPLE_VALUE + "2")));
+ }
+
+ @Test public void context() throws ResolutionException {
+ final AttributeDefinition attrDef = getAttributeDefn("contextDerived.xml", "contextDerivedBeans.xml",
+ ContextDerivedAttributeDefinition.class);
+
+ final List<IdPAttributeValue> foo = attrDef.resolve(getCtx("BeanWhatever", false)).getValues();
+
+ assertEquals(2, foo.size());
+ assertTrue(foo.contains(new StringAttributeValue(SIMPLE_VALUE)));
+ assertTrue(foo.contains(new StringAttributeValue(SIMPLE_VALUE + "2")));
+ }
+
+ @Test public void warn() throws ResolutionException {
+ final AttributeDefinition attrDef =
+ getAttributeDefn("subjectDerivedWarn.xml", ContextDerivedAttributeDefinition.class);
+ final List<IdPAttributeValue> foo = attrDef.resolve(getCtx("Whatever", false)).getValues();
+
+ assertEquals(2, foo.size());
+ assertTrue(foo.contains(new StringAttributeValue(SIMPLE_VALUE)));
+ assertTrue(foo.contains(new StringAttributeValue(SIMPLE_VALUE + "2")));
+ }
+
+ @Test(expectedExceptions = {BeanDefinitionStoreException.class}) public void fail() throws ResolutionException {
+ getAttributeDefn("subjectDerivedFail.xml", ContextDerivedAttributeDefinition.class);
+ }
+
+ @Test(expectedExceptions = {BeanDefinitionStoreException.class}) public void dependency()
+ throws ResolutionException {
+ getAttributeDefn("subjectDerivedDependency.xml", ContextDerivedAttributeDefinition.class);
+ }
+}
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/context/impl/SubjectDerivedAttributeValuesFunctionTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/context/impl/SubjectDerivedAttributeValuesFunctionTest.java
new file mode 100644
index 000000000..b97d851d2
--- /dev/null
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/context/impl/SubjectDerivedAttributeValuesFunctionTest.java
@@ -0,0 +1,226 @@
+/*
+ * 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.authn.context.impl;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.ad.impl.ContextDerivedAttributeDefinition;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.testing.TestSources;
+import net.shibboleth.idp.authn.AuthenticationResult;
+import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
+import net.shibboleth.idp.authn.context.SubjectContext;
+import net.shibboleth.idp.authn.principal.IdPAttributePrincipal;
+import net.shibboleth.idp.saml.authn.principal.AuthenticationMethodPrincipal;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.logic.FunctionSupport;
+
+/** Test for {@link SubjectDerivedAttributeValuesFunction}. */
+ at SuppressWarnings("javadoc")
+public class SubjectDerivedAttributeValuesFunctionTest {
+
+ /** Simple result. */
+ private static final String SIMPLE_VALUE = "simple";
+
+
+ @Test public void noSubjectContext() throws ComponentInitializationException, ResolutionException {
+
+ final SubjectDerivedAttributeValuesFunction ctxValueFunction = new SubjectDerivedAttributeValuesFunction();
+ ctxValueFunction.setId("pDaD");
+ final IdPAttributePrincipalValuesFunction fn = new IdPAttributePrincipalValuesFunction();
+ fn.setAttributeName("wibble");
+ fn.doInitialize();
+ ctxValueFunction.setAttributeValuesFunction(fn);
+ ctxValueFunction.initialize();
+
+ final ContextDerivedAttributeDefinition defn = new ContextDerivedAttributeDefinition();
+ defn.setAttributeValuesFunction(ctxValueFunction);
+ defn.setId("pDAD");
+ defn.initialize();
+
+ final AttributeResolutionContext ctx =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+
+ final IdPAttribute result = defn.resolve(ctx);
+ assertNull(result);
+ }
+
+ @Test public void simpleValue() throws ComponentInitializationException, ResolutionException {
+ final List<IdPAttributeValue> list = new ArrayList<>(2);
+ list.add(new StringAttributeValue(SIMPLE_VALUE));
+ list.add(new StringAttributeValue(SIMPLE_VALUE + "2"));
+
+ final IdPAttribute attr = new IdPAttribute("wibble");
+ attr.setValues(list);
+
+ final SubjectDerivedAttributeValuesFunction ctxValueFunction = new SubjectDerivedAttributeValuesFunction();
+ ctxValueFunction.setId("pDaD");
+ final IdPAttributePrincipalValuesFunction fn = new IdPAttributePrincipalValuesFunction();
+ fn.setAttributeName("wibble");
+ fn.doInitialize();
+ ctxValueFunction.setAttributeValuesFunction(fn);
+ ctxValueFunction.initialize();
+
+ final ContextDerivedAttributeDefinition defn = new ContextDerivedAttributeDefinition();
+ defn.setAttributeValuesFunction(ctxValueFunction);
+ defn.setId("pDAD");
+ defn.initialize();
+
+ final AttributeResolutionContext ctx =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+ final SubjectContext sc = ctx.getParent().getSubcontext(SubjectContext.class, true);
+ final Map<String, AuthenticationResult> authnResults = sc.getAuthenticationResults();
+ final Subject subject = new Subject();
+ subject.getPrincipals().add(new IdPAttributePrincipal(attr));
+ subject.getPrincipals().add(new AuthenticationMethodPrincipal(SIMPLE_VALUE + "2"));
+ authnResults.put("one", new AuthenticationResult("1", subject));
+
+
+ final List<IdPAttributeValue> foo = defn.resolve(ctx).getValues();
+
+ assertEquals(2, foo.size());
+ assertTrue(foo.contains(new StringAttributeValue(SIMPLE_VALUE)));
+ assertTrue(foo.contains(new StringAttributeValue(SIMPLE_VALUE + "2")));
+ }
+
+ @Test public void simpleValueViaSubject() throws ComponentInitializationException, ResolutionException {
+ final List<IdPAttributeValue> list = new ArrayList<>(2);
+ list.add(new StringAttributeValue(SIMPLE_VALUE));
+ list.add(new StringAttributeValue(SIMPLE_VALUE + "2"));
+
+ final IdPAttribute attr = new IdPAttribute("wibble");
+ attr.setValues(list);
+
+ final Subject subject = new Subject();
+ subject.getPrincipals().add(new IdPAttributePrincipal(attr));
+ subject.getPrincipals().add(new AuthenticationMethodPrincipal(SIMPLE_VALUE + "2"));
+
+ final SubjectDerivedAttributeValuesFunction ctxValueFunction = new SubjectDerivedAttributeValuesFunction();
+ ctxValueFunction.setId("pDaD");
+ ctxValueFunction.setSubjectLookupStrategy(FunctionSupport.constant(subject));
+ final IdPAttributePrincipalValuesFunction fn = new IdPAttributePrincipalValuesFunction();
+ fn.setAttributeName("wibble");
+ fn.doInitialize();
+ ctxValueFunction.setAttributeValuesFunction(fn);
+ ctxValueFunction.initialize();
+
+ final ContextDerivedAttributeDefinition defn = new ContextDerivedAttributeDefinition();
+ defn.setAttributeValuesFunction(ctxValueFunction);
+ defn.setId("pDAD");
+ defn.initialize();
+
+ final AttributeResolutionContext ctx =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+
+ final List<IdPAttributeValue> foo = defn.resolve(ctx).getValues();
+
+ assertEquals(2, foo.size());
+ assertTrue(foo.contains(new StringAttributeValue(SIMPLE_VALUE)));
+ assertTrue(foo.contains(new StringAttributeValue(SIMPLE_VALUE + "2")));
+ }
+
+ @Test public void simpleValueViaSubjectC14N() throws ComponentInitializationException, ResolutionException {
+ final List<IdPAttributeValue> list = new ArrayList<>(2);
+ list.add(new StringAttributeValue(SIMPLE_VALUE));
+ list.add(new StringAttributeValue(SIMPLE_VALUE + "2"));
+
+ final IdPAttribute attr = new IdPAttribute("wibble");
+ attr.setValues(list);
+
+ final Subject subject = new Subject();
+ subject.getPrincipals().add(new IdPAttributePrincipal(attr));
+ subject.getPrincipals().add(new AuthenticationMethodPrincipal(SIMPLE_VALUE + "2"));
+
+ final SubjectDerivedAttributeValuesFunction ctxValueFunction = new SubjectDerivedAttributeValuesFunction();
+ ctxValueFunction.setId("pDaD");
+ ctxValueFunction.setForCanonicalization(true);
+ final IdPAttributePrincipalValuesFunction fn = new IdPAttributePrincipalValuesFunction();
+ fn.setAttributeName("wibble");
+ fn.doInitialize();
+ ctxValueFunction.setAttributeValuesFunction(fn);
+ ctxValueFunction.initialize();
+
+ final ContextDerivedAttributeDefinition defn = new ContextDerivedAttributeDefinition();
+ defn.setAttributeValuesFunction(ctxValueFunction);
+ defn.setId("pDAD");
+ defn.initialize();
+
+ final AttributeResolutionContext ctx =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+ ctx.getParent().getSubcontext(SubjectCanonicalizationContext.class, true).setSubject(subject);
+
+ final List<IdPAttributeValue> foo = defn.resolve(ctx).getValues();
+
+ assertEquals(2, foo.size());
+ assertTrue(foo.contains(new StringAttributeValue(SIMPLE_VALUE)));
+ assertTrue(foo.contains(new StringAttributeValue(SIMPLE_VALUE + "2")));
+ }
+
+ @Test public void empty() throws ComponentInitializationException, ResolutionException {
+ final List<IdPAttributeValue> list = Collections.emptyList();
+
+ final IdPAttribute attr = new IdPAttribute("wibble");
+ attr.setValues(list);
+
+ final SubjectDerivedAttributeValuesFunction ctxValueFunction = new SubjectDerivedAttributeValuesFunction();
+ ctxValueFunction.setId("pDaD");
+ final IdPAttributePrincipalValuesFunction fn = new IdPAttributePrincipalValuesFunction();
+ fn.setAttributeName("wibble");
+ fn.doInitialize();
+ ctxValueFunction.setAttributeValuesFunction(fn);
+ ctxValueFunction.initialize();
+
+ final ContextDerivedAttributeDefinition defn = new ContextDerivedAttributeDefinition();
+ defn.setAttributeValuesFunction(ctxValueFunction);
+ defn.setId("pDAD");
+ defn.initialize();
+
+ final AttributeResolutionContext ctx =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+ final SubjectContext sc = ctx.getParent().getSubcontext(SubjectContext.class, true);
+ final Map<String, AuthenticationResult> authnResults = sc.getAuthenticationResults();
+ final Subject subject = new Subject();
+ subject.getPrincipals().add(new IdPAttributePrincipal(attr));
+ subject.getPrincipals().add(new AuthenticationMethodPrincipal(SIMPLE_VALUE + "2"));
+ authnResults.put("one", new AuthenticationResult("1", subject));
+
+ final IdPAttribute result = defn.resolve(ctx);
+ assertNull(result);
+ }
+
+}
diff --git a/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/contextDerived.xml b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/contextDerived.xml
new file mode 100644
index 000000000..5ee934a3e
--- /dev/null
+++ b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/contextDerived.xml
@@ -0,0 +1,5 @@
+ <AttributeDefinition xmlns="urn:mace:shibboleth:2.0:resolver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ dependencyOnly="1"
+ xsi:schemaLocation="urn:mace:shibboleth:2.0:resolver http://shibboleth.net/schema/idp/shibboleth-attribute-resolver.xsd"
+
+ xsi:type="ContextDerivedAttribute" id="PD1" attributeValuesFunctionRef="ctxAttributeValuesFunction"/>
diff --git a/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerived.xml b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerived.xml
new file mode 100644
index 000000000..6697b3e38
--- /dev/null
+++ b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerived.xml
@@ -0,0 +1,9 @@
+ <AttributeDefinition xmlns="urn:mace:shibboleth:2.0:resolver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ dependencyOnly="1"
+ xsi:schemaLocation="urn:mace:shibboleth:2.0:resolver http://shibboleth.net/schema/idp/shibboleth-attribute-resolver.xsd"
+
+ xsi:type="SubjectDerivedAttribute" id="PD1" principalAttributeName="Whatever">
+
+ <AttributeEncoder xsi:type="SAML2String" name="urn:oid:0.9.2342.19200300.100.1.1" friendlyName="uid" />
+
+</AttributeDefinition>
diff --git a/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerivedComplex.xml b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerivedComplex.xml
new file mode 100644
index 000000000..b9d0b9590
--- /dev/null
+++ b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerivedComplex.xml
@@ -0,0 +1,5 @@
+ <AttributeDefinition xmlns="urn:mace:shibboleth:2.0:resolver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ dependencyOnly="1"
+ xsi:schemaLocation="urn:mace:shibboleth:2.0:resolver http://shibboleth.net/schema/idp/shibboleth-attribute-resolver.xsd"
+
+ xsi:type="SubjectDerivedAttribute" id="PD1" attributeValuesFunctionRef="princialAttributeValuesFunction"/>
diff --git a/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerivedDependency.xml b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerivedDependency.xml
new file mode 100644
index 000000000..deb66cda2
--- /dev/null
+++ b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerivedDependency.xml
@@ -0,0 +1,10 @@
+ <AttributeDefinition 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"
+ dependencyOnly="1"
+ xsi:type="SubjectDerivedAttribute" id="PD1" principalAttributeName="Whatever">
+
+ <AttributeEncoder xsi:type="SAML2String" name="urn:oid:0.9.2342.19200300.100.1.1" friendlyName="uid" />
+ <!-- The following line is here to throw a parsing error -->
+ <InputAttributeDefinition ref="32" />
+
+</AttributeDefinition>
diff --git a/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerivedFail.xml b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerivedFail.xml
new file mode 100644
index 000000000..714481200
--- /dev/null
+++ b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerivedFail.xml
@@ -0,0 +1,9 @@
+ <AttributeDefinition 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"
+
+ dependencyOnly="1"
+ xsi:type="SubjectDerivedAttribute" id="PD1" >
+
+ <AttributeEncoder xsi:type="SAML2String" name="urn:oid:0.9.2342.19200300.100.1.1" friendlyName="uid" />
+
+</AttributeDefinition>
diff --git a/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerivedViaC14N.xml b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerivedViaC14N.xml
new file mode 100644
index 000000000..38db1e6b6
--- /dev/null
+++ b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerivedViaC14N.xml
@@ -0,0 +1,9 @@
+ <AttributeDefinition xmlns="urn:mace:shibboleth:2.0:resolver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ dependencyOnly="1"
+ xsi:schemaLocation="urn:mace:shibboleth:2.0:resolver http://shibboleth.net/schema/idp/shibboleth-attribute-resolver.xsd"
+
+ xsi:type="SubjectDerivedAttribute" id="PD1" principalAttributeName="Whatever" forCanonicalization="1">
+
+ <AttributeEncoder xsi:type="SAML2String" name="urn:oid:0.9.2342.19200300.100.1.1" friendlyName="uid" />
+
+</AttributeDefinition>
diff --git a/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerivedWarn.xml b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerivedWarn.xml
new file mode 100644
index 000000000..f3153693d
--- /dev/null
+++ b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/ad/subjectDerivedWarn.xml
@@ -0,0 +1,8 @@
+ <AttributeDefinition 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"
+ dependencyOnly="1"
+ xsi:type="SubjectDerivedAttribute" id="PD1" principalAttributeName="Whatever" attributeValuesFunctionRef="engineBean">
+
+ <AttributeEncoder xsi:type="SAML2String" name="urn:oid:0.9.2342.19200300.100.1.1" friendlyName="uid" />
+
+</AttributeDefinition>
diff --git a/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/contextDerivedBeans.xml b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/contextDerivedBeans.xml
new file mode 100644
index 000000000..08f94d5af
--- /dev/null
+++ b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/contextDerivedBeans.xml
@@ -0,0 +1,20 @@
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:c="http://www.springframework.org/schema/c"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+ default-init-method="initialize"
+ default-destroy-method="destroy">
+
+ <bean id="princialAttributeValuesFunction" class="net.shibboleth.idp.authn.context.impl.IdPAttributePrincipalValuesFunction"
+ p:attributeName="BeanWhatever"/>
+
+ <bean id="ctxAttributeValuesFunction" class="net.shibboleth.idp.authn.context.impl.SubjectDerivedAttributeValuesFunction"
+ p:attributeValuesFunction-ref="princialAttributeValuesFunction" p:id="ctxAttributeFunction"/>
+</beans>
+
\ No newline at end of file
diff --git a/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/customBean.xml b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/customBean.xml
new file mode 100644
index 000000000..1032a48a6
--- /dev/null
+++ b/idp-authn-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/customBean.xml
@@ -0,0 +1,50 @@
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:c="http://www.springframework.org/schema/c"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+ default-init-method="initialize"
+ default-destroy-method="destroy">
+
+ <util:map id="shibboleth.CustomScriptObject">
+ <entry key="foo" value="bar"/>
+ </util:map>
+
+ <util:map id="other.CustomScriptObject">
+ <entry key="bar" value="foo"/>
+ </util:map>
+
+ <bean id="shibboleth.Predicate" destroy-method="" class="com.google.common.base.Predicates" factory-method="alwaysFalse"/>
+
+ <bean id="shibboleth.PropertySourcesPlaceholderConfigurer" destroy-method=""
+ class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"
+ p:placeholderPrefix="%{" p:placeholderSuffix="}" />
+
+ <!-- Necessary for encoder parsing to function, normally part of registry wiring. -->
+
+ <bean id="SAML2StringTranscoder"
+ class="net.shibboleth.idp.saml.attribute.transcoding.impl.SAML2StringAttributeTranscoder" />
+ <bean id="SAML2ScopedStringTranscoder"
+ class="net.shibboleth.idp.saml.attribute.transcoding.impl.SAML2ScopedStringAttributeTranscoder" />
+ <bean id="SAML2ByteTranscoder"
+ class="net.shibboleth.idp.saml.attribute.transcoding.impl.SAML2ByteAttributeTranscoder" />
+ <bean id="SAML2XMLObjectTranscoder"
+ class="net.shibboleth.idp.saml.attribute.transcoding.impl.SAML2XMLObjectAttributeTranscoder" />
+
+ <bean id="SAML1StringTranscoder"
+ class="net.shibboleth.idp.saml.attribute.transcoding.impl.SAML1StringAttributeTranscoder" />
+ <bean id="SAML1ScopedStringTranscoder"
+ class="net.shibboleth.idp.saml.attribute.transcoding.impl.SAML1ScopedStringAttributeTranscoder" />
+ <bean id="SAML1ByteTranscoder"
+ class="net.shibboleth.idp.saml.attribute.transcoding.impl.SAML1ByteAttributeTranscoder" />
+ <bean id="SAML1XMLObjectTranscoder"
+ class="net.shibboleth.idp.saml.attribute.transcoding.impl.SAML1XMLObjectAttributeTranscoder" />
+
+</beans>
+
+
\ No newline at end of file
diff --git a/idp-spring/.classpath b/idp-spring/.classpath
index dc3178b5a..be3679b89 100644
--- a/idp-spring/.classpath
+++ b/idp-spring/.classpath
@@ -6,6 +6,7 @@
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
+ <classpathentry excluding="**" kind="src" path="src/main/resources"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="test" value="true"/>
@@ -21,6 +22,7 @@
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
<attributes>
+ <attribute name="module" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list