[java-shib-profile] branch main updated: JSPROF-1 - Move RelyingParty "layer" into java-shib-profile
Scott Cantor
cantor.2 at osu.edu
Wed Feb 15 15:56:46 UTC 2023
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch main
in repository java-shib-profile.
View the commit online:
http://git.shibboleth.net/view/?p=java-shib-profile.git;a=commit;h=58140ad4e37baa16e29d5a2c64a228673dc01632
The following commit(s) were added to refs/heads/main by this push:
new 58140ad JSPROF-1 - Move RelyingParty "layer" into java-shib-profile
58140ad is described below
commit 58140ad4e37baa16e29d5a2c64a228673dc01632
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Feb 15 10:56:44 2023 -0500
JSPROF-1 - Move RelyingParty "layer" into java-shib-profile
https://shibboleth.atlassian.net/browse/JSPROF-1
Migrate in predicate classes.
---
shib-profile-api/pom.xml | 59 +++---
.../config/logic/ResolveAttributesPredicate.java | 51 ++++++
.../profile/config/logic}/package-info.java | 4 +-
.../context/logic/AbstractAttributePredicate.java | 139 ++++++++++++++
.../logic/AbstractRelyingPartyPredicate.java | 79 ++++++++
.../context/logic/DateAttributePredicate.java | 175 ++++++++++++++++++
.../context/logic/DynamicAttributePredicate.java | 199 +++++++++++++++++++++
.../context/logic/LoopDetectionPredicate.java | 136 ++++++++++++++
.../context/logic/RegexAttributePredicate.java | 127 +++++++++++++
.../logic/RelyingPartyIdPredicate.java | 2 +-
.../profile/context/logic/ScriptedPredicate.java | 132 ++++++++++++++
.../context/logic/SimpleAttributePredicate.java | 172 ++++++++++++++++++
.../context/logic/SpringExpressionPredicate.java | 54 ++++++
.../context/logic/VerifiedProfilePredicate.java | 26 ++-
.../messaging/AbstractRelyingPartyPredicate.java | 87 +++++++++
.../context/logic/messaging}/package-info.java | 4 +-
.../profile/{ => context}/logic/package-info.java | 4 +-
.../context/logic/DateAttributePredicateTest.java | 159 ++++++++++++++++
.../logic/DynamicAttributePredicateTest.java | 100 +++++++++++
.../context/logic/LoopDetectionPredicateTest.java | 82 +++++++++
.../logic/RelyingPartyIdPredicateTest.java | 2 +-
.../context/logic/ScriptedPredicateTest.java | 107 +++++++++++
.../logic/SimpleAttributePredicateTest.java | 117 ++++++++++++
.../profile/logic/relyingPartyIdPredicates.xml | 12 +-
.../RelyingPartiesActivationConditionFactory.java | 4 +-
.../{ => context}/logic/impl/package-info.java | 2 +-
26 files changed, 1995 insertions(+), 40 deletions(-)
diff --git a/shib-profile-api/pom.xml b/shib-profile-api/pom.xml
index dc55e2d..385867b 100644
--- a/shib-profile-api/pom.xml
+++ b/shib-profile-api/pom.xml
@@ -22,6 +22,15 @@
<dependencies>
<!-- Compile Dependencies -->
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>shib-attribute-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>${opensaml.groupId}</groupId>
+ <artifactId>opensaml-core-api</artifactId>
+ </dependency>
<dependency>
<groupId>${opensaml.groupId}</groupId>
<artifactId>opensaml-profile-api</artifactId>
@@ -39,6 +48,10 @@
<artifactId>opensaml-xmlsec-api</artifactId>
</dependency>
+ <dependency>
+ <groupId>${shib-shared.groupId}</groupId>
+ <artifactId>shib-security</artifactId>
+ </dependency>
<dependency>
<groupId>${shib-shared.groupId}</groupId>
<artifactId>shib-service</artifactId>
@@ -48,6 +61,25 @@
<artifactId>shib-spring</artifactId>
</dependency>
+ <dependency>
+ <groupId>${spring.groupId}</groupId>
+ <artifactId>spring-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${spring.groupId}</groupId>
+ <artifactId>spring-expression</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
<!-- Provided Dependencies -->
<dependency>
<groupId>jakarta.servlet</groupId>
@@ -58,6 +90,12 @@
<!-- Runtime Dependencies -->
<!-- Test Dependencies -->
+ <dependency>
+ <groupId>${opensaml.groupId}</groupId>
+ <artifactId>opensaml-testing</artifactId>
+ <scope>test</scope>
+ </dependency>
+
<dependency>
<groupId>${spring.groupId}</groupId>
<artifactId>spring-context</artifactId>
@@ -66,25 +104,4 @@
</dependencies>
- <profiles>
- <!--
- Pull in a Javascript engine for testing in Java
- versions where the JDK doesn't provide one.
- -->
- <profile>
- <id>get-nashorn</id>
- <activation>
- <jdk>[15,</jdk>
- </activation>
- <dependencies>
- <dependency>
- <groupId>org.openjdk.nashorn</groupId>
- <artifactId>nashorn-core</artifactId>
- <version>${nashorn.jdk.version}</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
- </profile>
- </profiles>
-
</project>
diff --git a/shib-profile-api/src/main/java/net/shibboleth/profile/config/logic/ResolveAttributesPredicate.java b/shib-profile-api/src/main/java/net/shibboleth/profile/config/logic/ResolveAttributesPredicate.java
new file mode 100644
index 0000000..d88ab29
--- /dev/null
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/config/logic/ResolveAttributesPredicate.java
@@ -0,0 +1,51 @@
+/*
+ * 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.profile.config.logic;
+
+import javax.annotation.Nullable;
+
+import net.shibboleth.profile.config.AttributeResolvingProfileConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.logic.AbstractRelyingPartyPredicate;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+/**
+ * Predicate to determine whether a profile request should resolve attributes.
+ *
+ * @since 4.2.0
+ */
+public class ResolveAttributesPredicate extends AbstractRelyingPartyPredicate {
+
+ /** {@inheritDoc} */
+ public boolean test(@Nullable final ProfileRequestContext input) {
+ if (input != null) {
+ final RelyingPartyContext rpc = getRelyingPartyContext(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof AttributeResolvingProfileConfiguration) {
+ return ((AttributeResolvingProfileConfiguration) pc).isResolveAttributes(input);
+ }
+ }
+ }
+
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/shib-profile-impl/src/main/java/net/shibboleth/profile/logic/impl/package-info.java b/shib-profile-api/src/main/java/net/shibboleth/profile/config/logic/package-info.java
similarity index 89%
copy from shib-profile-impl/src/main/java/net/shibboleth/profile/logic/impl/package-info.java
copy to shib-profile-api/src/main/java/net/shibboleth/profile/config/logic/package-info.java
index 85190d0..865da32 100644
--- a/shib-profile-impl/src/main/java/net/shibboleth/profile/logic/impl/package-info.java
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/config/logic/package-info.java
@@ -16,6 +16,6 @@
*/
/**
- * Implementation classes supporting profile predicates.
+ * Predicates based on profile configuration settings.
*/
-package net.shibboleth.profile.logic.impl;
\ No newline at end of file
+package net.shibboleth.profile.config.logic;
\ No newline at end of file
diff --git a/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/AbstractAttributePredicate.java b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/AbstractAttributePredicate.java
new file mode 100644
index 0000000..8c78a9a
--- /dev/null
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/AbstractAttributePredicate.java
@@ -0,0 +1,139 @@
+/*
+ * 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.profile.context.logic;
+
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.context.AttributeContext;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Abstract base class for predicates operating on an {@link AttributeContext}.
+ */
+public abstract class AbstractAttributePredicate implements Predicate<ProfileRequestContext> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractAttributePredicate.class);
+
+ /** Strategy function to lookup {@link AttributeContext}. */
+ @Nonnull private Function<ProfileRequestContext,AttributeContext> attributeContextLookupStrategy;
+
+ /** Whether to look at filtered or unfiltered attributes. */
+ private boolean useUnfilteredAttributes;
+
+ /** Constructor. */
+ public AbstractAttributePredicate() {
+ attributeContextLookupStrategy =
+ new ChildContextLookup<>(AttributeContext.class).compose(
+ new ChildContextLookup<>(RelyingPartyContext.class));
+ useUnfilteredAttributes = true;
+ }
+
+ /**
+ * Get the lookup strategy to use to locate the {@link AttributeContext}.
+ *
+ * @return lookup strategy to use
+ */
+ @Nonnull public Function<ProfileRequestContext,AttributeContext> getAttributeContextLookupStrategy() {
+ return attributeContextLookupStrategy;
+ }
+
+ /**
+ * Set the lookup strategy to use to locate the {@link AttributeContext}.
+ *
+ * @param strategy lookup function to use
+ */
+ public void setAttributeContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,AttributeContext> strategy) {
+
+ attributeContextLookupStrategy =
+ Constraint.isNotNull(strategy, "AttributeContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Get whether to source the input attributes from the unfiltered set.
+ *
+ * @return whether to source the input attributes from the unfiltered set
+ */
+ public boolean isUseUnfilteredAttributes() {
+ return useUnfilteredAttributes;
+ }
+
+ /**
+ * Set whether to source the input attributes from the unfiltered set.
+ *
+ * <p>Defaults to true.</p>
+ *
+ * @param flag flag to set
+ */
+ public void setUseUnfilteredAttributes(final boolean flag) {
+ useUnfilteredAttributes = flag;
+ }
+
+ /** {@inheritDoc} */
+ public boolean test(@Nullable final ProfileRequestContext input) {
+
+ final AttributeContext attributeCtx = attributeContextLookupStrategy.apply(input);
+ if (attributeCtx == null) {
+ log.warn("No AttributeContext located for evaluation");
+ return allowNullAttributeContext();
+ }
+
+ final Map<String,IdPAttribute> attributes = useUnfilteredAttributes
+ ? attributeCtx.getUnfilteredIdPAttributes()
+ : attributeCtx.getIdPAttributes();
+
+ if (hasMatch(attributes)) {
+ log.debug("Context satisfied requirements");
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the result of the predicate in the case the attribute context is null.
+ *
+ * @return null context result
+ */
+ protected boolean allowNullAttributeContext() {
+ return false;
+ }
+
+ /**
+ * Abstract implementation of the condition to evaluate.
+ *
+ * @param attributeMap the attributes to evaluate
+ *
+ * @return the condition result
+ */
+ protected abstract boolean hasMatch(@Nonnull @NonnullElements final Map<String,IdPAttribute> attributeMap);
+
+}
\ No newline at end of file
diff --git a/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/AbstractRelyingPartyPredicate.java b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/AbstractRelyingPartyPredicate.java
new file mode 100644
index 0000000..537216d
--- /dev/null
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/AbstractRelyingPartyPredicate.java
@@ -0,0 +1,79 @@
+/*
+ * 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.profile.context.logic;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.logic.Constraint;
+
+import java.util.function.Predicate;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+/**
+ * Base class for a predicate that evaluates a {@link ProfileRequestContext} and requires access to a
+ * {@link RelyingPartyContext}.
+ */
+public abstract class AbstractRelyingPartyPredicate implements Predicate<ProfileRequestContext> {
+
+ /** Strategy function to lookup RelyingPartyContext. */
+ @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
+
+ /** Constructor. */
+ public AbstractRelyingPartyPredicate() {
+ relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
+ }
+
+ /**
+ * Get the lookup strategy to use to locate the {@link RelyingPartyContext}.
+ *
+ * @return lookup function to use
+ */
+ @Nonnull public Function<ProfileRequestContext,RelyingPartyContext> getRelyingPartyContextLookupStrategy() {
+ return relyingPartyContextLookupStrategy;
+ }
+
+ /**
+ * Set the lookup strategy to use to locate the {@link RelyingPartyContext}.
+ *
+ * @param strategy lookup function to use
+ */
+ public void setRelyingPartyContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,RelyingPartyContext> strategy) {
+
+ relyingPartyContextLookupStrategy =
+ Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Returns the result of evaluating the injected lookup strategy to obtain the {@link RelyingPartyContext}.
+ *
+ * @param input current profile request context
+ *
+ * @return the {@link RelyingPartyContext} or null
+ */
+ @Nullable protected RelyingPartyContext getRelyingPartyContext(@Nullable final ProfileRequestContext input) {
+ return relyingPartyContextLookupStrategy.apply(input);
+ }
+
+}
\ No newline at end of file
diff --git a/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/DateAttributePredicate.java b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/DateAttributePredicate.java
new file mode 100644
index 0000000..315971d
--- /dev/null
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/DateAttributePredicate.java
@@ -0,0 +1,175 @@
+/*
+ * 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.profile.context.logic;
+
+import java.time.DateTimeException;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.attribute.DateTimeAttributeValue;
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Provides a date/time matching predicate that compares a date-based attribute value against
+ * current system time with optional offset. By convention the predicate returns true if and only if
+ * the date represented by the attribute value is after the current system time; false otherwise.
+ * Thus the semantics are well-suited for cases such as evaluation of expiration dates.
+ *
+ * @author Marvin S. Addison
+ */
+public class DateAttributePredicate extends AbstractAttributePredicate {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DateAttributePredicate.class);
+
+ /** Name of attribute to query for. */
+ @Nonnull @NotEmpty private final String attributeName;
+
+ /** Formatter used to parse string-based date attribute values. */
+ @Nullable private final DateTimeFormatter dateTimeFormatter;
+
+ /** Offset from system time used for date comparisons. */
+ @Nonnull private Duration systemTimeOffset;
+
+ /** Result of predicate if attribute is missing or has no values. */
+ private boolean resultIfMissing;
+
+ /**
+ * Create a new instance that performs date comparisons against the given attribute.
+ *
+ * @param attribute Attribute name that provides candidate date values to test.
+ */
+ public DateAttributePredicate(@Nonnull @NotEmpty @ParameterName(name="attribute") final String attribute) {
+ attributeName = Constraint.isNotNull(attribute, "Attribute cannot be null");
+ dateTimeFormatter = null;
+ systemTimeOffset = Duration.ZERO;
+ }
+
+ /**
+ * Create a new instance that performs date comparisons against the given attribute
+ * using the given date parser.
+ *
+ * @param attribute Attribute name that provides candidate date values to test.
+ * @param formatter Date/time parser.
+ */
+ public DateAttributePredicate(@Nonnull @NotEmpty @ParameterName(name="attribute") final String attribute,
+ @Nonnull @ParameterName(name="formatter") final DateTimeFormatter formatter) {
+
+ attributeName = Constraint.isNotNull(attribute, "Attribute cannot be null");
+ dateTimeFormatter = Constraint.isNotNull(formatter, "Formatter cannot be null");
+ systemTimeOffset = Duration.ZERO;
+ }
+
+ /**
+ * Create a new instance that performs date comparisons against the given attribute
+ * using the given date parser.
+ *
+ * @param attribute Attribute name that provides candidate date values to test.
+ * @param formatString date/time parsing string, currently based on {@link DateTimeFormatter}
+ */
+ public DateAttributePredicate(@Nonnull @NotEmpty @ParameterName(name="attribute") final String attribute,
+ @Nonnull @NotEmpty @ParameterName(name="formatString") final String formatString) {
+ attributeName = Constraint.isNotNull(attribute, "Attribute cannot be null");
+ dateTimeFormatter = DateTimeFormatter.ofPattern(
+ Constraint.isNotNull(formatString, "Format string cannot be null"));
+ systemTimeOffset = Duration.ZERO;
+ }
+
+ /**
+ * Set the system time offset, which affects the reference date for comparisons.
+ *
+ * <p>By default all comparisons are against system time, i.e. zero offset.</p>
+ *
+ * @param offset System time offset. A negative value decreases the target date (sooner);
+ * a positive value increases the target date (later).
+ */
+ public void setOffset(@Nonnull final java.time.Duration offset) {
+ systemTimeOffset = Constraint.isNotNull(offset, "Offset cannot be null");
+ }
+
+ /**
+ * Set the result to return if the attribute to check is missing or has no values.
+ *
+ * @param flag flag to set
+ */
+ public void setResultIfMissing(final boolean flag) {
+ resultIfMissing = flag;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean allowNullAttributeContext() {
+ return resultIfMissing;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean hasMatch(@Nonnull @NonnullElements final Map<String,IdPAttribute> attributeMap) {
+
+ final IdPAttribute attribute = attributeMap.get(attributeName);
+ if (attribute == null) {
+ log.debug("Attribute {} not found in context, returning {}", attributeName, resultIfMissing);
+ return resultIfMissing;
+ } else if (attribute.getValues().isEmpty()) {
+ log.debug("Attribute {} has no values, returning {}", attributeName, resultIfMissing);
+ return resultIfMissing;
+ }
+
+ final Instant now = Instant.now();
+
+ String dateString;
+ for (final IdPAttributeValue value : attribute.getValues()) {
+ if (value instanceof DateTimeAttributeValue &&
+ ((DateTimeAttributeValue) value).getValue().plus(systemTimeOffset).isAfter(now)) {
+ return true;
+ } else if (value instanceof StringAttributeValue) {
+ if (dateTimeFormatter == null) {
+ log.warn("No DateTimeFormatter configured, ignoring string value");
+ continue;
+ }
+ dateString = ((StringAttributeValue) value).getValue();
+ try {
+ assert dateTimeFormatter != null;
+ if (Instant.from(dateTimeFormatter.parse(dateString)).plus(systemTimeOffset).isAfter(now)) {
+ return true;
+ }
+ } catch (final DateTimeException e) {
+ log.warn("{} is not a valid date for the configured formatting string", dateString, e);
+ }
+ } else {
+ log.warn("Ignoring unsupported value type: {}", value.getClass().getName());
+ }
+ }
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/DynamicAttributePredicate.java b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/DynamicAttributePredicate.java
new file mode 100644
index 0000000..384e113
--- /dev/null
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/DynamicAttributePredicate.java
@@ -0,0 +1,199 @@
+/*
+ * 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.profile.context.logic;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.context.AttributeContext;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * Predicate over an {@link AttributeContext} that derives the value(s) to match based
+ * on one or more supplied Functions instead of static values.
+ *
+ * <p>Each function installed must return a value that matches a value of the attribute
+ * corresponding to the map key.</p>
+ *
+ * <p>Functions may return a {@link String} or a {@link Collection} containing them.</p>
+ *
+ * @since 3.4.0
+ */
+public class DynamicAttributePredicate extends AbstractAttributePredicate {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DynamicAttributePredicate.class);
+
+ /** Map of attribute IDs to functions. */
+ @Nonnull @NonnullElements private ListMultimap<String,Function<ProfileRequestContext,Object>> attributeFunctionMap;
+
+ /** Constructor. */
+ public DynamicAttributePredicate() {
+ attributeFunctionMap = ArrayListMultimap.create();
+ }
+
+ /**
+ * Set the map of attribute/function pairs (as a map of function collections) to check for.
+ *
+ * @param map map of attribute/function pairs
+ */
+ public void setAttributeFunctionMap(
+ @Nonnull @NonnullElements final Map<String,Collection<Function<ProfileRequestContext,Object>>> map) {
+ Constraint.isNotNull(map, "Attribute/value map cannot be null");
+
+ attributeFunctionMap.clear();
+ for (final Map.Entry<String,Collection<Function<ProfileRequestContext,Object>>> entry : map.entrySet()) {
+ final String attributeId = StringSupport.trimOrNull(entry.getKey());
+ attributeFunctionMap.putAll(attributeId, List.copyOf(entry.getValue()));
+ }
+ }
+
+ /** {@inheritDoc} */
+ public boolean test(@Nullable final ProfileRequestContext input) {
+
+ final AttributeContext attributeCtx = getAttributeContextLookupStrategy().apply(input);
+ if (attributeCtx == null) {
+ log.warn("No AttributeContext located for evaluation");
+ return allowNullAttributeContext();
+ }
+
+ final Map<String,IdPAttribute> attributes = isUseUnfilteredAttributes()
+ ? attributeCtx.getUnfilteredIdPAttributes()
+ : attributeCtx.getIdPAttributes();
+
+ if (hasMatch(input, attributes)) {
+ log.debug("Context satisfied requirements");
+ return true;
+ }
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean hasMatch(@Nonnull @NonnullElements final Map<String,IdPAttribute> attributeMap) {
+ log.error("Method should never be called");
+ return false;
+ }
+
+// Checkstyle: CyclomaticComplexity OFF
+ /**
+ * Implementation of the condition to evaluate.
+ *
+ * @param profileRequestContext current profile request context
+ * @param attributeMap the attributes to evaluate
+ *
+ * @return the condition result
+ */
+ protected boolean hasMatch(@Nullable final ProfileRequestContext profileRequestContext,
+ @Nonnull @NonnullElements final Map<String,IdPAttribute> attributeMap) {
+
+ for (final String id : attributeFunctionMap.keySet()) {
+ log.debug("Checking for attribute: {}", id);
+
+ final IdPAttribute attribute = attributeMap.get(id);
+ if (attribute == null) {
+ log.debug("Attribute {} not found in context", id);
+ return false;
+ }
+
+ boolean matched = false;
+
+ for (final Function<ProfileRequestContext,Object> fn : attributeFunctionMap.get(id)) {
+
+ final Object candidate = fn.apply(profileRequestContext);
+
+ if (candidate instanceof String) {
+ matched = findMatch((String) candidate, attribute);
+ } else if (candidate instanceof Collection<?>) {
+ for (final Object subcandidate : (Collection<?>) candidate) {
+ if (subcandidate instanceof String) {
+ if (findMatch((String) subcandidate, attribute)) {
+ matched = true;
+ break;
+ }
+ } else {
+ log.error(
+ "Collection returned by function for attribute {} contained an unsupported type: {}",
+ id, subcandidate.getClass().getName());
+ }
+ }
+ } else {
+ log.error("Function for attribute {} returned an unsupported type: {}", id,
+ candidate.getClass().getName());
+ }
+
+ if (matched) {
+ break;
+ }
+ }
+
+ if (!matched) {
+ log.debug("Attribute {} values not matched", id);
+ return false;
+ }
+ }
+
+ return true;
+ }
+// Checkstyle: CyclomaticComplexity ON
+
+ /**
+ * Look for a matching value in an attribute.
+ *
+ * @param toMatch value to look for
+ * @param attribute attribute to check
+ *
+ * @return true iff the value is one of the attribute's values
+ */
+ protected boolean findMatch(@Nonnull @NotEmpty final String toMatch, @Nonnull final IdPAttribute attribute) {
+
+ if ("*".equals(toMatch)) {
+ log.debug("Wildcard (*) value rule for attribute {}", attribute.getId());
+ return true;
+ }
+ for (final IdPAttributeValue value : attribute.getValues()) {
+ if (value instanceof StringAttributeValue) {
+ if (toMatch.equals(((StringAttributeValue)value).getValue())) {
+ log.debug("Found matching value ({}) in attribute {}", toMatch, attribute.getId());
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/LoopDetectionPredicate.java b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/LoopDetectionPredicate.java
new file mode 100644
index 0000000..f457338
--- /dev/null
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/LoopDetectionPredicate.java
@@ -0,0 +1,136 @@
+/*
+ * 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.profile.context.logic;
+
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.core.metrics.MetricsSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.MetricRegistry.MetricSupplier;
+import com.codahale.metrics.SlidingTimeWindowMovingAverages;
+
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.Positive;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A condition that relies on a {@link Meter} to detect looping SPs.
+ *
+ * @since 4.1.0
+ */
+public class LoopDetectionPredicate extends AbstractRelyingPartyPredicate {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(LoopDetectionPredicate.class);
+
+ /** Count to trigger warning. */
+ private long threshold;
+
+ /** Map of RP names to meter names. */
+ @Nonnull @NonnullElements private Map<String,String> relyingPartyMap;
+
+ /** Lookup strategy to obtain subject name. */
+ @Nonnull private Function<ProfileRequestContext,String> usernameLookupStrategy;
+
+ /** Constructor. */
+ public LoopDetectionPredicate() {
+ threshold = 20;
+ relyingPartyMap = CollectionSupport.emptyMap();
+ usernameLookupStrategy = FunctionSupport.constant(null);
+ }
+
+ /**
+ * Set the warning threshold for the 1 minute moving average to exceed.
+ *
+ * <p>Defaults to 20.</p>
+ *
+ * @param value threshold to use
+ */
+ public void setThreshold(@Positive final long value) {
+ threshold = Constraint.isGreaterThan(0, value, "Threshold must be positive");
+ }
+
+ /**
+ * Set the map of relying party names to meter names to track counts.
+ *
+ * @param map map of RP/meter mappings
+ */
+ public void setRelyingPartyMap(@Nullable @NonnullElements final Map<String,String> map) {
+ if (map != null) {
+ relyingPartyMap = CollectionSupport.copyToMap(map);
+ } else {
+ relyingPartyMap = CollectionSupport.emptyMap();
+ }
+ }
+
+ /**
+ * Set lookup strategy to obtain username.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setUsernameLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ usernameLookupStrategy = Constraint.isNotNull(strategy, "Username lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ public boolean test(@Nullable final ProfileRequestContext input) {
+
+ final String username = usernameLookupStrategy.apply(input);
+ final RelyingPartyContext rpCtx = getRelyingPartyContextLookupStrategy().apply(input);
+
+ if (username != null && rpCtx != null && rpCtx.getRelyingPartyId() != null) {
+ String meterName = relyingPartyMap.get(rpCtx.getRelyingPartyId());
+ if (meterName != null) {
+ meterName = MetricRegistry.name("net.shibboleth.idp.loopDetection", meterName,
+ username.replace(".",""));
+ final MetricRegistry registry = MetricsSupport.getMetricRegistry();
+ if (registry == null) {
+ log.error("MetricRegistry was unavailable");
+ return false;
+ }
+ final Meter meter = registry.meter(meterName,
+ new MetricSupplier<Meter>() {
+ public Meter newMetric() {
+ return new Meter(new SlidingTimeWindowMovingAverages());
+ }
+ });
+ meter.mark();
+ final double rate = meter.getOneMinuteRate();
+ if (rate > threshold) {
+ log.warn("Meter {} rate of {} exceeded threshold of {}", meterName, rate, threshold);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/RegexAttributePredicate.java b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/RegexAttributePredicate.java
new file mode 100644
index 0000000..2256eb3
--- /dev/null
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/RegexAttributePredicate.java
@@ -0,0 +1,127 @@
+/*
+ * 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.profile.context.logic;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+/**
+ * Predicate that evaluates an {@link net.shibboleth.idp.attribute.context.AttributeContext} and checks
+ * a specific attribute for value(s) that match a regular expression.
+ *
+ * <p>This handles only simple string-valued data.</p>
+ */
+public class RegexAttributePredicate extends AbstractAttributePredicate {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(RegexAttributePredicate.class);
+
+ /** The attribute to evaluate. */
+ @Nullable @NotEmpty private String attributeId;
+
+ /** Regular expression. */
+ @Nullable private Pattern pattern;
+
+ /**
+ * Get the attribute ID to check.
+ *
+ * @return the attribute ID to check
+ */
+ @Nullable @NotEmpty public String getAttributeId() {
+ return attributeId;
+ }
+
+ /**
+ * Set the attribute ID to check.
+ *
+ * @param id the attribute ID to check
+ */
+ public void setAttributeId(@Nonnull @NotEmpty final String id) {
+ attributeId = Constraint.isNotNull(StringSupport.trimOrNull(id), "Attribute ID cannot be null or empty");
+ }
+
+ /**
+ * Get the pattern to match the attribute values against.
+ *
+ * @return the pattern to match the attribute values against
+ */
+ @Nullable public Pattern getPattern() {
+ return pattern;
+ }
+
+ /**
+ * Set the pattern to match the attribute values against.
+ *
+ * @param p the pattern to match the attribute values against
+ */
+ public void setPattern(@Nonnull final Pattern p) {
+ pattern = p;
+ }
+
+ /**
+ * Set the pattern to match the attribute values against.
+ *
+ * @param s the pattern to match the attribute values against
+ */
+ public void setPattern(@Nonnull @NotEmpty final String s) {
+ pattern = Pattern.compile(s);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected boolean hasMatch(@Nonnull @NonnullElements final Map<String, IdPAttribute> attributeMap) {
+
+ if (attributeId == null || pattern == null) {
+ log.warn("Attribute ID or regular expression were not set");
+ return false;
+ }
+
+ final IdPAttribute attribute = attributeMap.get(attributeId);
+ if (attribute == null) {
+ log.debug("Attribute '{}' not found in context", attributeId);
+ return false;
+ }
+
+ for (final IdPAttributeValue value : attribute.getValues()) {
+ if (value instanceof StringAttributeValue) {
+ assert pattern != null;
+ final Matcher m = pattern.matcher(((StringAttributeValue)value).getValue());
+ if (m.matches()) {
+ log.debug("Found matching value '{}' in attribute '{}'", m.group(), attributeId);
+ return true;
+ }
+ }
+ }
+ log.debug("Attribute '{}' values not matched", attributeId);
+ return false;
+ }
+
+}
diff --git a/shib-profile-api/src/main/java/net/shibboleth/profile/logic/RelyingPartyIdPredicate.java b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/RelyingPartyIdPredicate.java
similarity index 98%
rename from shib-profile-api/src/main/java/net/shibboleth/profile/logic/RelyingPartyIdPredicate.java
rename to shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/RelyingPartyIdPredicate.java
index 491f699..a14bbd3 100644
--- a/shib-profile-api/src/main/java/net/shibboleth/profile/logic/RelyingPartyIdPredicate.java
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/RelyingPartyIdPredicate.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package net.shibboleth.profile.logic;
+package net.shibboleth.profile.context.logic;
import java.util.Collection;
import java.util.function.Predicate;
diff --git a/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/ScriptedPredicate.java b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/ScriptedPredicate.java
new file mode 100644
index 0000000..426c304
--- /dev/null
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/ScriptedPredicate.java
@@ -0,0 +1,132 @@
+/*
+ * 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.profile.context.logic;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.script.ScriptContext;
+import javax.script.ScriptException;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.core.io.Resource;
+
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.scripting.EvaluableScript;
+
+/**
+ * A {@link java.util.function.Predicate} which calls out to a supplied script.
+ */
+public class ScriptedPredicate
+ extends net.shibboleth.shared.logic.ScriptedPredicate<ProfileRequestContext> {
+
+ /**
+ * Constructor.
+ *
+ * @param theScript the script we will evaluate.
+ * @param extraInfo debugging information.
+ */
+ public ScriptedPredicate(@Nonnull @NotEmpty @ParameterName(name="theScript") final EvaluableScript theScript,
+ @Nullable @NotEmpty @ParameterName(name="extraInfo") final String extraInfo) {
+ super(theScript, extraInfo);
+ setInputType(ProfileRequestContext.class);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param theScript the script we will evaluate.
+ */
+ public ScriptedPredicate(@Nonnull @NotEmpty @ParameterName(name="theScript") final EvaluableScript theScript) {
+ super(theScript);
+ setInputType(ProfileRequestContext.class);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void prepareContext(@Nonnull final ScriptContext scriptContext, @Nullable final Object... input) {
+ super.prepareContext(scriptContext, input);
+ scriptContext.setAttribute("profileContext", input != null ? input[0] : null, ScriptContext.ENGINE_SCOPE);
+ }
+
+ /**
+ * Factory to create {@link ScriptedPredicate} from a {@link Resource}.
+ *
+ * @param resource the resource to look at
+ * @param engineName the language
+ * @return the predicate
+ * @throws ScriptException if the compile fails
+ * @throws IOException if the file doesn't exist.
+ */
+ public static ScriptedPredicate resourceScript(@Nonnull @NotEmpty final String engineName,
+ @Nonnull final Resource resource) throws ScriptException, IOException {
+ try (final InputStream is = resource.getInputStream()) {
+ final EvaluableScript script = new EvaluableScript();
+ script.setEngineName(engineName);
+ script.setScript(is);
+ script.initializeWithScriptException();
+ return new ScriptedPredicate(script, resource.getDescription());
+ }
+ }
+
+ /**
+ * Factory to create {@link ScriptedPredicate} from a {@link Resource}.
+ *
+ * @param resource the resource to look at
+ * @return the predicate
+ * @throws ScriptException if the compile fails
+ * @throws IOException if the file doesn't exist.
+ */
+ public static ScriptedPredicate resourceScript(@Nonnull final Resource resource)
+ throws ScriptException, IOException {
+ return resourceScript(DEFAULT_ENGINE, resource);
+ }
+
+ /**
+ * Factory to create {@link ScriptedPredicate} from inline data.
+ *
+ * @param scriptSource the script, as a string
+ * @param engineName the language
+ * @return the predicate
+ * @throws ScriptException if the compile fails
+ */
+ public static ScriptedPredicate inlineScript(@Nonnull @NotEmpty final String engineName,
+ @Nonnull @NotEmpty final String scriptSource) throws ScriptException {
+ final EvaluableScript script = new EvaluableScript();
+ script.setEngineName(engineName);
+ script.setScript(scriptSource);
+ script.initializeWithScriptException();
+ return new ScriptedPredicate(script, "Inline");
+ }
+
+ /**
+ * Factory to create {@link ScriptedPredicate} from inline data.
+ *
+ * @param scriptSource the script, as a string
+ * @return the predicate
+ * @throws ScriptException if the compile fails
+ */
+ public static ScriptedPredicate inlineScript(@Nonnull @NotEmpty final String scriptSource) throws ScriptException {
+ return inlineScript(DEFAULT_ENGINE, scriptSource);
+ }
+
+}
\ No newline at end of file
diff --git a/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/SimpleAttributePredicate.java b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/SimpleAttributePredicate.java
new file mode 100644
index 0000000..1c6b166
--- /dev/null
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/SimpleAttributePredicate.java
@@ -0,0 +1,172 @@
+/*
+ * 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.profile.context.logic;
+
+import java.util.Collection;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.ScopedStringAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * Predicate that evaluates an {@link net.shibboleth.idp.attribute.context.AttributeContext} and checks
+ * for particular attribute/value pairs.
+ *
+ * <p>A map to a collection of strings is used to represent the attribute(s) and value(s) to evaluate.
+ * The values are evaluated as a disjunction (OR) and the attributes are evaluated as a conjunction (AND).</p>
+ *
+ * <p>This handles only simple string-valued data, or if scope is supplied, requires scoped values.</p>
+ *
+ * <p>For the special case of checking for an attribute's presence, regardless of values, the '*' value is
+ * supported. Note that this does NOT exclude pathological cases such as empty or null values. A more
+ * advanced predicate should be used to deal with such cases.</p>
+ */
+public class SimpleAttributePredicate extends AbstractAttributePredicate {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(SimpleAttributePredicate.class);
+
+ /** Map of attribute IDs to values. */
+ @Nonnull @NonnullElements private ListMultimap<String,String> attributeValueMap;
+
+ /** Optional scope to check. */
+ @Nullable @NotEmpty private String scope;
+
+ /** Constructor. */
+ public SimpleAttributePredicate() {
+ attributeValueMap = ArrayListMultimap.create();
+ }
+
+ /**
+ * Set the map of attribute/value pairs (as a map of string collections) to check for.
+ *
+ * @param map map of attribute/value pairs
+ */
+ public void setAttributeValueMap(@Nonnull @NonnullElements final Map<String,Collection<String>> map) {
+ Constraint.isNotNull(map, "Attribute/value map cannot be null");
+
+ attributeValueMap.clear();
+ for (final Map.Entry<String,Collection<String>> entry : map.entrySet()) {
+ final String attributeId = StringSupport.trimOrNull(entry.getKey());
+ attributeValueMap.putAll(attributeId, StringSupport.normalizeStringCollection(entry.getValue()));
+ }
+ }
+
+ /**
+ * Set a scope to check for.
+ *
+ * <p>If set, values that "match" must be scoped with this value. A "*" will match any scope, but
+ * one must exist.</p>
+ *
+ * @param s scope to check for
+ *
+ * @since 4.2.0
+ */
+ public void setScope(@Nullable @NotEmpty final String s) {
+ scope = StringSupport.trimOrNull(s);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean allowNullAttributeContext() {
+ return attributeValueMap.isEmpty() && scope == null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean hasMatch(final Map<String, IdPAttribute> attributeMap) {
+ for (final String id : attributeValueMap.keySet()) {
+ log.debug("Checking for attribute: {}", id);
+
+ final IdPAttribute attribute = attributeMap.get(id);
+ if (attribute == null) {
+ log.debug("Attribute {} not found in context", id);
+ return false;
+ }
+
+ boolean matched = false;
+
+ for (final String value : attributeValueMap.get(id)) {
+ if (findMatch(value, attribute)) {
+ matched = true;
+ break;
+ }
+ }
+
+ if (!matched) {
+ log.debug("Attribute {} values not matched", id);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Look for a matching value in an attribute.
+ *
+ * @param toMatch value to look for
+ * @param attribute attribute to check
+ *
+ * @return true iff the value is one of the attribute's values
+ */
+// Checkstyle: CyclomaticComplexity OFF
+ protected boolean findMatch(@Nonnull @NotEmpty final String toMatch, @Nonnull final IdPAttribute attribute) {
+
+ if ("*".equals(toMatch) && scope == null) {
+ log.debug("Wildcard (*) value rule for attribute {}", attribute.getId());
+ return true;
+ }
+
+ for (final IdPAttributeValue value : attribute.getValues()) {
+ if (scope != null && value instanceof ScopedStringAttributeValue) {
+ final String scopeCopy = scope;
+ if ("*".equals(toMatch) || toMatch.equals(((ScopedStringAttributeValue) value).getValue())) {
+ if ("*".equals(scopeCopy) || scopeCopy.equals(((ScopedStringAttributeValue) value).getScope())) {
+ log.debug("Found matching value ({}) and scope ({}) in attribute {}", toMatch, scopeCopy,
+ attribute.getId());
+ return true;
+ }
+ }
+ } else if (scope == null && value instanceof StringAttributeValue) {
+ if (toMatch.equals(((StringAttributeValue) value).getValue())) {
+ log.debug("Found matching value ({}) in attribute {}", toMatch, attribute.getId());
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+// Checkstyle: CyclomaticComplexity ON
+
+}
\ No newline at end of file
diff --git a/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/SpringExpressionPredicate.java b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/SpringExpressionPredicate.java
new file mode 100644
index 0000000..3305a0d
--- /dev/null
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/SpringExpressionPredicate.java
@@ -0,0 +1,54 @@
+/*
+ * 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.profile.context.logic;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.expression.EvaluationContext;
+
+/**
+ * Predicate whose condition is defined by an Spring EL expression.
+ *
+ * @author Daniel Lutz
+ */
+public class SpringExpressionPredicate
+ extends net.shibboleth.shared.spring.expression.SpringExpressionPredicate<ProfileRequestContext> {
+
+ /**
+ * Constructor.
+ *
+ * @param expression the expression to evaluate
+ */
+ public SpringExpressionPredicate(@Nonnull @NotEmpty @ParameterName(name="expression") final String expression) {
+ super(expression);
+ setInputType(ProfileRequestContext.class);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void prepareContext(@Nonnull final EvaluationContext context, @Nullable final Object... input) {
+ super.prepareContext(context, input);
+ context.setVariable("profileContext", input != null ? input[0] : input);
+ }
+
+}
\ No newline at end of file
diff --git a/shib-profile-impl/src/main/java/net/shibboleth/profile/logic/impl/package-info.java b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/VerifiedProfilePredicate.java
similarity index 55%
copy from shib-profile-impl/src/main/java/net/shibboleth/profile/logic/impl/package-info.java
copy to shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/VerifiedProfilePredicate.java
index 85190d0..9121d5c 100644
--- a/shib-profile-impl/src/main/java/net/shibboleth/profile/logic/impl/package-info.java
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/VerifiedProfilePredicate.java
@@ -15,7 +15,29 @@
* limitations under the License.
*/
+package net.shibboleth.profile.context.logic;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.profile.context.RelyingPartyContext;
+
/**
- * Implementation classes supporting profile predicates.
+ * Predicate to determine whether a profile request is from a verified source.
*/
-package net.shibboleth.profile.logic.impl;
\ No newline at end of file
+public class VerifiedProfilePredicate extends AbstractRelyingPartyPredicate {
+
+ /** {@inheritDoc} */
+ public boolean test(@Nullable final ProfileRequestContext input) {
+ if (input != null) {
+ final RelyingPartyContext rpc = getRelyingPartyContext(input);
+ if (rpc != null) {
+ return rpc.isVerified();
+ }
+ }
+
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/messaging/AbstractRelyingPartyPredicate.java b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/messaging/AbstractRelyingPartyPredicate.java
new file mode 100644
index 0000000..23892c8
--- /dev/null
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/messaging/AbstractRelyingPartyPredicate.java
@@ -0,0 +1,87 @@
+/*
+ * 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.profile.context.logic.messaging;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.InOutOperationContext;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.context.navigate.RecursiveTypedParentContextLookup;
+
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Abstract base class for a predicate that evaluates a {@link MessageContext}
+ * and which requires a {@link RelyingPartyContext} obtained via a lookup function,
+ * by default a child of the {@link InOutOperationContext}
+ * the parent of the specified {@link MessageContext}.
+ */
+public abstract class AbstractRelyingPartyPredicate implements Predicate<MessageContext> {
+
+ /**
+ * Strategy used to locate the {@link RelyingPartyContext} associated with a given {@link MessageContext}.
+ */
+ @Nonnull private Function<MessageContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
+
+ /** Constructor. */
+ public AbstractRelyingPartyPredicate() {
+ relyingPartyContextLookupStrategy =
+ new ChildContextLookup<>(RelyingPartyContext.class).compose(
+ new RecursiveTypedParentContextLookup<>(InOutOperationContext.class));
+ }
+
+ /**
+ * Set the strategy used to locate the {@link RelyingPartyContext} associated with a given
+ * {@link MessageContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setRelyingPartyContextLookupStrategy(
+ @Nonnull final Function<MessageContext,RelyingPartyContext> strategy) {
+ relyingPartyContextLookupStrategy =
+ Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Get the strategy used to locate the {@link RelyingPartyContext} associated with a given
+ * {@link MessageContext}.
+ *
+ * @return lookup strategy
+ */
+ @Nonnull public Function<MessageContext,RelyingPartyContext> getRelyingPartyContextLookupStrategy() {
+ return relyingPartyContextLookupStrategy;
+ }
+
+ /**
+ * Return the result of applying the lookup function to obtain the {@link RelyingPartyContext}.
+ *
+ * @param input message context
+ *
+ * @return the {@link RelyingPartyContext} or null
+ */
+ @Nullable protected RelyingPartyContext getRelyingPartyContext(@Nullable final MessageContext input) {
+ return relyingPartyContextLookupStrategy.apply(input);
+ }
+
+}
\ No newline at end of file
diff --git a/shib-profile-impl/src/main/java/net/shibboleth/profile/logic/impl/package-info.java b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/messaging/package-info.java
similarity index 89%
copy from shib-profile-impl/src/main/java/net/shibboleth/profile/logic/impl/package-info.java
copy to shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/messaging/package-info.java
index 85190d0..27ac960 100644
--- a/shib-profile-impl/src/main/java/net/shibboleth/profile/logic/impl/package-info.java
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/messaging/package-info.java
@@ -16,6 +16,6 @@
*/
/**
- * Implementation classes supporting profile predicates.
+ * Predicates that evaluate message contexts.
*/
-package net.shibboleth.profile.logic.impl;
\ No newline at end of file
+package net.shibboleth.profile.context.logic.messaging;
\ No newline at end of file
diff --git a/shib-profile-api/src/main/java/net/shibboleth/profile/logic/package-info.java b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/package-info.java
similarity index 90%
rename from shib-profile-api/src/main/java/net/shibboleth/profile/logic/package-info.java
rename to shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/package-info.java
index 79cd221..f67fb88 100644
--- a/shib-profile-api/src/main/java/net/shibboleth/profile/logic/package-info.java
+++ b/shib-profile-api/src/main/java/net/shibboleth/profile/context/logic/package-info.java
@@ -16,6 +16,6 @@
*/
/**
- * Predicates that test against profile request state.
+ * Predicates that evaluate profile contexts.
*/
-package net.shibboleth.profile.logic;
\ No newline at end of file
+package net.shibboleth.profile.context.logic;
\ No newline at end of file
diff --git a/shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/DateAttributePredicateTest.java b/shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/DateAttributePredicateTest.java
new file mode 100644
index 0000000..1e73798
--- /dev/null
+++ b/shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/DateAttributePredicateTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.profile.context.logic;
+
+import net.shibboleth.idp.attribute.DateTimeAttributeValue;
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.context.AttributeContext;
+import net.shibboleth.profile.context.RelyingPartyContext;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.time.Instant;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.testng.Assert.*;
+
+/**
+ * Unit test for {@link DateAttributePredicate}.
+ */
+ at SuppressWarnings("javadoc")
+public class DateAttributePredicateTest {
+
+ private final java.time.format.DateTimeFormatter javaformatter =
+ java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME;
+
+ @DataProvider(name = "test-data-java")
+ public Object[][] provideTestDataJava() {
+ return new Object[][] {
+ // Future date matches
+ new Object[] {
+ new DateAttributePredicate("expirationDate", javaformatter),
+ "expirationDate",
+ javaDateStrings(java.time.Duration.ofDays(1)),
+ true,
+ },
+ // Current date does not match
+ new Object[] {
+ new DateAttributePredicate("expirationDate", javaformatter),
+ "expirationDate",
+ javaDateStrings(java.time.Duration.ZERO),
+ false,
+ },
+ // Past date does not match
+ new Object[] {
+ new DateAttributePredicate("expirationDate", javaformatter),
+ "expirationDate",
+ javaDateStrings(java.time.Duration.ofDays(-1)),
+ false,
+ },
+ // Increase target date by 90 days
+ new Object[] {
+ newJavaPredicate("expirationDate", java.time.Duration.ofDays(90)),
+ "expirationDate",
+ javaDateStrings(java.time.Duration.ofDays(91)),
+ true,
+ },
+ // Decrease target date by 30 days
+ // e.g. expiration warning case
+ new Object[] {
+ newJavaPredicate("expirationDate", java.time.Duration.ofDays(-30)),
+ "expirationDate",
+ javaDateStrings(java.time.Duration.ofDays(29)),
+ false,
+ },
+ };
+ }
+
+
+ @Test(dataProvider = "test-data-java")
+ public void testJavaTime(
+ final DateAttributePredicate predicate,
+ final String attribute,
+ final String[] values,
+ final boolean expected) throws Exception {
+ assertEquals(predicate.test(createProfileRequestContext(attribute, values, null)), expected);
+ }
+
+ @Test
+ public void testDateTimeValues() {
+ final DateAttributePredicate predicate = new DateAttributePredicate("test");
+
+ assertTrue(predicate.test(createProfileRequestContext("test", null,
+ new Instant[] {Instant.now().plus(java.time.Duration.ofMinutes(5))})));
+
+ predicate.setOffset(java.time.Duration.ofMinutes(-10));
+
+ assertFalse(predicate.test(createProfileRequestContext("test", null,
+ new Instant[] {Instant.now().plus(java.time.Duration.ofMinutes(5))})));
+ }
+
+
+ private ProfileRequestContext createProfileRequestContext(final String name, final String[] values, final Instant[] dtvalues) {
+ final ProfileRequestContext prc = new ProfileRequestContext();
+ final RelyingPartyContext rpc = new RelyingPartyContext();
+ final IdPAttribute attribute = new IdPAttribute(name);
+ final List<IdPAttributeValue> attributeValues = new ArrayList<>();
+ if (values != null) {
+ for (String value : values) {
+ attributeValues.add(new StringAttributeValue(value));
+ }
+ }
+ if (dtvalues != null) {
+ for (Instant value : dtvalues) {
+ attributeValues.add(new DateTimeAttributeValue(value));
+ }
+ }
+ attribute.setValues(attributeValues);
+ final AttributeContext ac = new AttributeContext();
+ ac.setIdPAttributes(Collections.singletonList(attribute));
+ ac.setUnfilteredIdPAttributes(Collections.singletonList(attribute));
+ rpc.addSubcontext(ac);
+ prc.addSubcontext(rpc);
+ return prc;
+ }
+
+ /**
+ * Produces an array of date strings that are offsets from current system time.
+ *
+ * @param offsets One or more durations that are added to the current system time.
+ *
+ * @return Array of date strings, one for each provided offset.
+ */
+ private String[] javaDateStrings(final java.time.Duration ... offsets) {
+ final String[] dates = new String[offsets.length];
+ for (int i = 0; i < offsets.length; i++) {
+ dates[i] = javaformatter.format(ZonedDateTime.now().plus(offsets[i]));
+ }
+ return dates;
+ }
+
+ private DateAttributePredicate newJavaPredicate(final String attribute, final java.time.Duration offset) {
+ final DateAttributePredicate p = new DateAttributePredicate(attribute, javaformatter);
+ p.setOffset(offset);
+ return p;
+ }
+
+}
diff --git a/shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/DynamicAttributePredicateTest.java b/shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/DynamicAttributePredicateTest.java
new file mode 100644
index 0000000..5aa8b54
--- /dev/null
+++ b/shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/DynamicAttributePredicateTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.profile.context.logic;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.ScopedStringAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.context.AttributeContext;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.FunctionSupport;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static org.testng.Assert.*;
+
+/**
+ * Unit test for {@link DynamicAttributePredicate}.
+ */
+ at SuppressWarnings("javadoc")
+public class DynamicAttributePredicateTest {
+
+ @Test
+ public void testInvalid() {
+ final ProfileRequestContext prc = createProfileRequestContext("foo", List.of("bar", "baz"));
+ DynamicAttributePredicate predicate = new DynamicAttributePredicate();
+
+ predicate.setAttributeFunctionMap(Map.of("foo", CollectionSupport.singleton(FunctionSupport.constant(List.of(10)))));
+ assertFalse(predicate.test(prc));
+
+ predicate.setAttributeFunctionMap(Map.of("foo", CollectionSupport.singleton(FunctionSupport.constant(10))));
+ assertFalse(predicate.test(prc));
+ }
+
+ @Test
+ public void testString() {
+ final ProfileRequestContext prc = createProfileRequestContext("foo", List.of("bar", "baz"));
+ DynamicAttributePredicate predicate = new DynamicAttributePredicate();
+
+ predicate.setAttributeFunctionMap(Map.of("foo2", CollectionSupport.singleton(FunctionSupport.constant(List.of("bar")))));
+ assertFalse(predicate.test(prc));
+
+ predicate.setAttributeFunctionMap(Map.of("foo2", CollectionSupport.singleton(FunctionSupport.constant("bar"))));
+ assertFalse(predicate.test(prc));
+
+ predicate.setAttributeFunctionMap(Map.of("foo2", CollectionSupport.singleton(FunctionSupport.constant("*"))));
+ assertFalse(predicate.test(prc));
+
+ predicate.setAttributeFunctionMap(Map.of("foo", CollectionSupport.singleton(FunctionSupport.constant("*"))));
+ assertTrue(predicate.test(prc));
+
+ predicate.setAttributeFunctionMap(Map.of("foo", CollectionSupport.singleton(FunctionSupport.constant(List.of("bar", "baz")))));
+ assertTrue(predicate.test(prc));
+ }
+
+ private ProfileRequestContext createProfileRequestContext(final String name, final Collection<String> values) {
+ final ProfileRequestContext prc = new ProfileRequestContext();
+ final RelyingPartyContext rpc = new RelyingPartyContext();
+ final IdPAttribute attribute = new IdPAttribute(name);
+ final List<IdPAttributeValue> attributeValues = new ArrayList<>();
+ for (final String value : values) {
+ final int i = value.indexOf('@');
+ if (i == -1) {
+ attributeValues.add(new StringAttributeValue(value));
+ } else {
+ attributeValues.add(new ScopedStringAttributeValue(value.substring(0,i), value.substring(i + 1)));
+ }
+ }
+ attribute.setValues(attributeValues);
+ final AttributeContext ac = new AttributeContext();
+ ac.setIdPAttributes(CollectionSupport.singletonList(attribute));
+ ac.setUnfilteredIdPAttributes(CollectionSupport.singletonList(attribute));
+ rpc.addSubcontext(ac);
+ prc.addSubcontext(rpc);
+ return prc;
+ }
+
+}
\ No newline at end of file
diff --git a/shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/LoopDetectionPredicateTest.java b/shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/LoopDetectionPredicateTest.java
new file mode 100644
index 0000000..6d54ece
--- /dev/null
+++ b/shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/LoopDetectionPredicateTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.profile.context.logic;
+
+import java.util.Map;
+
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.logic.FunctionSupport;
+
+import org.opensaml.core.testing.OpenSAMLInitBaseTestCase;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/** Unit test for {@link LoopDetectionPredicate}. */
+ at SuppressWarnings("javadoc")
+public class LoopDetectionPredicateTest extends OpenSAMLInitBaseTestCase {
+
+ private ProfileRequestContext prc;
+ private RelyingPartyContext rpCtx;
+ private LoopDetectionPredicate pred;
+
+ @BeforeMethod
+ public void setUp() {
+ prc = new ProfileRequestContext();
+ rpCtx = prc.getOrCreateSubcontext(RelyingPartyContext.class);
+ pred = new LoopDetectionPredicate();
+ pred.setUsernameLookupStrategy(FunctionSupport.constant("jdoe.1"));
+ }
+
+ @Test
+ public void testNoMap() {
+ Assert.assertFalse(pred.test(prc));
+
+ rpCtx.setRelyingPartyId("foo");
+ Assert.assertFalse(pred.test(prc));
+ }
+
+ @Test
+ public void testNoMatch() {
+ pred.setRelyingPartyMap(Map.of("bar", "bar"));
+
+ rpCtx.setRelyingPartyId("foo");
+ Assert.assertFalse(pred.test(prc));
+ }
+
+ @Test
+ public void testMatch() {
+ pred.setRelyingPartyMap(Map.of("foo", "foo"));
+
+ rpCtx.setRelyingPartyId("foo");
+ Assert.assertFalse(pred.test(prc));
+ }
+
+ @Test
+ public void testExceed() throws InterruptedException {
+ pred.setRelyingPartyMap(Map.of("bar", "bar"));
+
+ rpCtx.setRelyingPartyId("bar");
+ for (int i=0; i<20; ++i) {
+ Assert.assertFalse(pred.test(prc));
+ }
+ Assert.assertTrue(pred.test(prc));
+ }
+
+}
\ No newline at end of file
diff --git a/shib-profile-api/src/test/java/net/shibboleth/profile/logic/RelyingPartyIdPredicateTest.java b/shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/RelyingPartyIdPredicateTest.java
similarity index 99%
rename from shib-profile-api/src/test/java/net/shibboleth/profile/logic/RelyingPartyIdPredicateTest.java
rename to shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/RelyingPartyIdPredicateTest.java
index 607b0cf..2fd05d8 100644
--- a/shib-profile-api/src/test/java/net/shibboleth/profile/logic/RelyingPartyIdPredicateTest.java
+++ b/shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/RelyingPartyIdPredicateTest.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package net.shibboleth.profile.logic;
+package net.shibboleth.profile.context.logic;
import net.shibboleth.profile.context.RelyingPartyContext;
import net.shibboleth.shared.collection.CollectionSupport;
diff --git a/shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/ScriptedPredicateTest.java b/shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/ScriptedPredicateTest.java
new file mode 100644
index 0000000..1685116
--- /dev/null
+++ b/shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/ScriptedPredicateTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.profile.context.logic;
+
+import javax.script.ScriptException;
+
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.spring.custom.SchemaTypeAwareXMLBeanDefinitionReader;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.io.ClassPathResource;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ * Tests for {@link ScriptedPredicate}.
+ */
+ at SuppressWarnings("javadoc")
+public class ScriptedPredicateTest {
+
+ private ProfileRequestContext withChild;
+
+ private ProfileRequestContext noChild;
+
+ @BeforeClass public void setup() {
+ withChild = new ProfileRequestContext();
+ withChild.getOrCreateSubcontext(RelyingPartyContext.class);
+ noChild = new ProfileRequestContext();
+ }
+
+ @Test public void simple() throws ScriptException {
+ ScriptedPredicate test = ScriptedPredicate.inlineScript("new java.lang.Boolean(true);");
+ Assert.assertTrue(test.test(withChild));
+
+ test = ScriptedPredicate.inlineScript("true");
+ Assert.assertTrue(test.test(withChild));
+
+ test = ScriptedPredicate.inlineScript("false");
+ Assert.assertFalse(test.test(withChild));
+
+ test = ScriptedPredicate.inlineScript("\"thirty\"");
+ Assert.assertFalse(test.test(withChild));
+ }
+
+ @Test public void custom() throws ScriptException {
+ ScriptedPredicate test = ScriptedPredicate.inlineScript("custom;");
+ test.setCustomObject(Boolean.valueOf(true));
+ Assert.assertTrue(test.test(withChild));
+
+ test.setCustomObject(Boolean.valueOf(false));
+ Assert.assertFalse(test.test(withChild));
+
+ }
+ @Test public void inlineBean() throws ScriptException {
+
+ try (final GenericApplicationContext ctx = new GenericApplicationContext()) {
+ final SchemaTypeAwareXMLBeanDefinitionReader beanDefinitionReader =
+ new SchemaTypeAwareXMLBeanDefinitionReader(ctx);
+
+ beanDefinitionReader.loadBeanDefinitions(new ClassPathResource(
+ "/net/shibboleth/idp/profile/logic/inlineBean.xml"));
+
+ ctx.refresh();
+ final ScriptedPredicate rule = ctx.getBean(ScriptedPredicate.class);
+
+ Assert.assertTrue(rule.test(withChild));
+
+ Assert.assertFalse(rule.test(noChild));
+ }
+ }
+
+ @Test public void resourceBean() throws ScriptException {
+
+ try (final GenericApplicationContext ctx = new GenericApplicationContext()) {
+ final SchemaTypeAwareXMLBeanDefinitionReader beanDefinitionReader =
+ new SchemaTypeAwareXMLBeanDefinitionReader(ctx);
+
+ beanDefinitionReader.loadBeanDefinitions(new ClassPathResource(
+ "/net/shibboleth/idp/profile/logic/resourceBean.xml"));
+
+ ctx.refresh();
+ final ScriptedPredicate rule = ctx.getBean(ScriptedPredicate.class);
+
+ Assert.assertTrue(rule.test(withChild));
+
+ Assert.assertFalse(rule.test(noChild));
+ }
+ }
+
+}
diff --git a/shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/SimpleAttributePredicateTest.java b/shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/SimpleAttributePredicateTest.java
new file mode 100644
index 0000000..04e5038
--- /dev/null
+++ b/shib-profile-api/src/test/java/net/shibboleth/profile/context/logic/SimpleAttributePredicateTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.profile.context.logic;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.ScopedStringAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.context.AttributeContext;
+import net.shibboleth.profile.context.RelyingPartyContext;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static org.testng.Assert.*;
+
+/**
+ * Unit test for {@link SimpleAttributePredicate}.
+ */
+ at SuppressWarnings("javadoc")
+public class SimpleAttributePredicateTest {
+
+ @Test
+ public void testString() {
+ final ProfileRequestContext prc = createProfileRequestContext("foo", List.of("bar", "baz"));
+ SimpleAttributePredicate predicate = new SimpleAttributePredicate();
+
+ predicate.setAttributeValueMap(Map.of("foo2", List.of("bar")));
+ assertFalse(predicate.test(prc));
+
+ predicate.setAttributeValueMap(Map.of("foo2", List.of("*")));
+ assertFalse(predicate.test(prc));
+
+ predicate.setAttributeValueMap(Map.of("foo", List.of("*")));
+ assertTrue(predicate.test(prc));
+
+ predicate.setAttributeValueMap(Map.of("foo", List.of("bar", "baz")));
+ assertTrue(predicate.test(prc));
+
+ predicate.setScope("zorkmid");
+ assertFalse(predicate.test(prc));
+ }
+
+ @Test
+ public void testScoped() {
+ final ProfileRequestContext prc = createProfileRequestContext("foo", List.of("bar at scope1", "baz at scope2"));
+ SimpleAttributePredicate predicate = new SimpleAttributePredicate();
+
+ predicate.setAttributeValueMap(Map.of("foo", List.of("bar")));
+ assertTrue(predicate.test(prc));
+
+ predicate.setAttributeValueMap(Map.of("foo", List.of("*")));
+ assertTrue(predicate.test(prc));
+
+ predicate.setScope("zorkmid");
+ assertFalse(predicate.test(prc));
+
+ predicate.setScope("*");
+ assertTrue(predicate.test(prc));
+
+ predicate.setAttributeValueMap(Map.of("foo", List.of("bar", "baz")));
+ assertTrue(predicate.test(prc));
+
+ predicate.setScope("scope1");
+ assertTrue(predicate.test(prc));
+
+ predicate.setScope("scope2");
+ assertTrue(predicate.test(prc));
+
+ predicate.setAttributeValueMap(Map.of("foo", List.of("bar")));
+ assertFalse(predicate.test(prc));
+ }
+
+ private ProfileRequestContext createProfileRequestContext(final String name, final Collection<String> values) {
+ final ProfileRequestContext prc = new ProfileRequestContext();
+ final RelyingPartyContext rpc = new RelyingPartyContext();
+ final IdPAttribute attribute = new IdPAttribute(name);
+ final List<IdPAttributeValue> attributeValues = new ArrayList<>();
+ for (final String value : values) {
+ final int i = value.indexOf('@');
+ if (i == -1) {
+ attributeValues.add(new StringAttributeValue(value));
+ } else {
+ attributeValues.add(new ScopedStringAttributeValue(value.substring(0,i), value.substring(i + 1)));
+ }
+ }
+ attribute.setValues(attributeValues);
+ final AttributeContext ac = new AttributeContext();
+ ac.setIdPAttributes(Collections.singletonList(attribute));
+ ac.setUnfilteredIdPAttributes(Collections.singletonList(attribute));
+ rpc.addSubcontext(ac);
+ prc.addSubcontext(rpc);
+ return prc;
+ }
+
+}
\ No newline at end of file
diff --git a/shib-profile-api/src/test/resources/net/shibboleth/profile/logic/relyingPartyIdPredicates.xml b/shib-profile-api/src/test/resources/net/shibboleth/profile/logic/relyingPartyIdPredicates.xml
index 811b4cb..5a8ca9a 100644
--- a/shib-profile-api/src/test/resources/net/shibboleth/profile/logic/relyingPartyIdPredicates.xml
+++ b/shib-profile-api/src/test/resources/net/shibboleth/profile/logic/relyingPartyIdPredicates.xml
@@ -18,17 +18,17 @@
</property>
</bean>
- <bean class="net.shibboleth.profile.logic.RelyingPartyIdPredicate" id="candidate" c:candidate="Single"/>
+ <bean class="net.shibboleth.profile.context.logic.RelyingPartyIdPredicate" id="candidate" c:candidate="Single"/>
- <bean class="net.shibboleth.profile.logic.RelyingPartyIdPredicate" id="candidates" c:candidates="#{ {'Single', 'Double'} }"/>
+ <bean class="net.shibboleth.profile.context.logic.RelyingPartyIdPredicate" id="candidates" c:candidates="#{ {'Single', 'Double'} }"/>
- <bean class="net.shibboleth.profile.logic.RelyingPartyIdPredicate" id="pred" c:pred="true"/>
+ <bean class="net.shibboleth.profile.context.logic.RelyingPartyIdPredicate" id="pred" c:pred="true"/>
- <bean class="net.shibboleth.profile.logic.RelyingPartyIdPredicate" id="candidate_0" c:candidate="Single"/>
+ <bean class="net.shibboleth.profile.context.logic.RelyingPartyIdPredicate" id="candidate_0" c:candidate="Single"/>
- <bean class="net.shibboleth.profile.logic.RelyingPartyIdPredicate" id="candidates_0" c:candidates="#{ {'Single', 'Double'} }"/>
+ <bean class="net.shibboleth.profile.context.logic.RelyingPartyIdPredicate" id="candidates_0" c:candidates="#{ {'Single', 'Double'} }"/>
- <bean class="net.shibboleth.profile.logic.RelyingPartyIdPredicate" id="pred_0" c:pred="true"/>
+ <bean class="net.shibboleth.profile.context.logic.RelyingPartyIdPredicate" id="pred_0" c:pred="true"/>
</beans>
\ No newline at end of file
diff --git a/shib-profile-impl/src/main/java/net/shibboleth/profile/logic/impl/RelyingPartiesActivationConditionFactory.java b/shib-profile-impl/src/main/java/net/shibboleth/profile/context/logic/impl/RelyingPartiesActivationConditionFactory.java
similarity index 95%
rename from shib-profile-impl/src/main/java/net/shibboleth/profile/logic/impl/RelyingPartiesActivationConditionFactory.java
rename to shib-profile-impl/src/main/java/net/shibboleth/profile/context/logic/impl/RelyingPartiesActivationConditionFactory.java
index e6ca28c..6393757 100644
--- a/shib-profile-impl/src/main/java/net/shibboleth/profile/logic/impl/RelyingPartiesActivationConditionFactory.java
+++ b/shib-profile-impl/src/main/java/net/shibboleth/profile/context/logic/impl/RelyingPartiesActivationConditionFactory.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package net.shibboleth.profile.logic.impl;
+package net.shibboleth.profile.context.logic.impl;
import java.util.Collection;
import java.util.Map;
@@ -29,7 +29,7 @@ import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
-import net.shibboleth.profile.logic.RelyingPartyIdPredicate;
+import net.shibboleth.profile.context.logic.RelyingPartyIdPredicate;
import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.shared.primitive.StringSupport;
diff --git a/shib-profile-impl/src/main/java/net/shibboleth/profile/logic/impl/package-info.java b/shib-profile-impl/src/main/java/net/shibboleth/profile/context/logic/impl/package-info.java
similarity index 94%
rename from shib-profile-impl/src/main/java/net/shibboleth/profile/logic/impl/package-info.java
rename to shib-profile-impl/src/main/java/net/shibboleth/profile/context/logic/impl/package-info.java
index 85190d0..30d803f 100644
--- a/shib-profile-impl/src/main/java/net/shibboleth/profile/logic/impl/package-info.java
+++ b/shib-profile-impl/src/main/java/net/shibboleth/profile/context/logic/impl/package-info.java
@@ -18,4 +18,4 @@
/**
* Implementation classes supporting profile predicates.
*/
-package net.shibboleth.profile.logic.impl;
\ No newline at end of file
+package net.shibboleth.profile.context.logic.impl;
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list