[java-metadata-aggregator] 02/02: MDA-287 - Add a stage to validate Shibboleth scopes
Ian Young
ian at iay.org.uk
Wed Apr 19 09:57:15 UTC 2023
This is an automated email from the git hooks/post-receive script.
iay pushed a commit to branch main
in repository java-metadata-aggregator.
View the commit online:
http://git.shibboleth.net/view/?p=java-metadata-aggregator.git;a=commit;h=15e16abec304c52df6c1ccd612a35ebd50ef96e6
commit 15e16abec304c52df6c1ccd612a35ebd50ef96e6
Author: Ian Young <ian at iay.org.uk>
AuthorDate: Wed Apr 19 10:57:05 2023 +0100
MDA-287 - Add a stage to validate Shibboleth scopes
https://shibboleth.atlassian.net/browse/MDA-287
---
.../dom/saml/shibboleth/ScopeValidationStage.java | 138 ++++++++++++++++
.../saml/shibboleth/ShibbolethMetadataSupport.java | 48 ++++++
.../metadata/dom/saml/shibboleth/package-info.java | 23 +++
.../metadata/validate/BaseAsValidator.java | 157 ++++++++++++++++++
...ectDomainNameNotUnderPublicSuffixValidator.java | 54 +++++++
.../net/RejectDomainNamePublicSuffixValidator.java | 49 ++++++
.../metadata/validate/net/package-info.java | 23 +++
.../string/AsDomainNameStringValidator.java | 53 ++++++
.../string/AsLiteralTailStringValidator.java | 107 ++++++++++++
.../resources/net/shibboleth/metadata/beans.xml | 30 ++++
.../shibboleth/ScopeValidationStageLitmusTest.java | 179 +++++++++++++++++++++
...omainNameNotUnderPublicSuffixValidatorTest.java | 91 +++++++++++
.../RejectDomainNamePublicSuffixValidatorTest.java | 75 +++++++++
.../string/AsDomainNameStringValidatorTest.java | 111 +++++++++++++
.../string/AsLiteralTailStringValidatorTest.java | 93 +++++++++++
.../ScopeValidationStageLitmusTest-config.xml | 70 ++++++++
16 files changed, 1301 insertions(+)
diff --git a/mda-framework/src/main/java/net/shibboleth/metadata/dom/saml/shibboleth/ScopeValidationStage.java b/mda-framework/src/main/java/net/shibboleth/metadata/dom/saml/shibboleth/ScopeValidationStage.java
new file mode 100644
index 0000000..897e0fd
--- /dev/null
+++ b/mda-framework/src/main/java/net/shibboleth/metadata/dom/saml/shibboleth/ScopeValidationStage.java
@@ -0,0 +1,138 @@
+/*
+ * 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.metadata.dom.saml.shibboleth;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.w3c.dom.Element;
+
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.dom.AbstractDOMValidationStage;
+import net.shibboleth.metadata.dom.DOMTraversalContext;
+import net.shibboleth.metadata.dom.SimpleDOMTraversalContext;
+import net.shibboleth.metadata.pipeline.StageProcessingException;
+import net.shibboleth.metadata.validate.Validator;
+import net.shibboleth.metadata.validate.ValidatorSequence;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.xml.AttributeSupport;
+import net.shibboleth.shared.xml.ElementSupport;
+
+/**
+ * Stage to apply a collection of validators to Shibboleth <code>shibmd:Scope</code>
+ * values.
+ *
+ * <p>
+ * A separate collection of validators is used for the case of the <code>regexp</code>
+ * attribute being <code>true</code> and <code>false</code>.
+ * </p>
+ *
+ * @since 0.10.0
+ */
+ at ThreadSafe
+public class ScopeValidationStage extends AbstractDOMValidationStage<String, DOMTraversalContext> {
+
+ /** The sequence of validators to apply to <code>regexp</code> scopes. */
+ @GuardedBy("this")
+ private @Nonnull ValidatorSequence<String> regexpValidators = new ValidatorSequence<>();
+
+ /**
+ * Set the sequence of validators to apply to each <code>regexp</code> scope.
+ *
+ * @param newValidators the list of validators to set
+ */
+ public synchronized void setRegexpValidators(@Nonnull final List<Validator<String>> newValidators) {
+ regexpValidators.setValidators(newValidators);
+ }
+
+ /**
+ * Gets the sequence of validators being applied to each <code>regexp</code> scope.
+ *
+ * @return list of validators
+ */
+ @Nonnull
+ public synchronized List<Validator<String>> getRegexpValidators() {
+ return regexpValidators.getValidators();
+ }
+
+ @Override
+ protected @Nonnull DOMTraversalContext buildContext(final @Nonnull Item<Element> item) {
+ return new SimpleDOMTraversalContext(item);
+ }
+
+ @Override
+ protected boolean applicable(@Nonnull final Element element, @Nonnull final DOMTraversalContext context) {
+ return ElementSupport.isElementNamed(element, ShibbolethMetadataSupport.SCOPE_NAME);
+ }
+
+ /**
+ * Evaluate the <code>regexp</code> attribute on an {@link Element}.
+ *
+ * <p>Treats a missing attribute or non-boolean string values as <code>false</code></p>8
+ *
+ * @param element {@link Element} hosting the <code>regexp</code> attribute.
+ * @return <code>true</code> if the attribute has a true value (<code>1</code> or
+ * <code>true</code> or variations), otherwise <code>false</code>
+ */
+ private boolean evaluateRegexpAttribute(final @Nonnull Element element) {
+ final var attr = AttributeSupport.getAttribute(element, ShibbolethMetadataSupport.REGEXP_ATTRIB_NAME);
+ if (attr == null) {
+ return false;
+ }
+
+ final Boolean isRegexp = AttributeSupport.getAttributeValueAsBoolean(attr);
+ if (isRegexp == null) {
+ return false;
+ }
+
+ return isRegexp.booleanValue();
+ }
+
+ @Override
+ protected void visit(final @Nonnull Element element, final @Nonnull DOMTraversalContext context)
+ throws StageProcessingException {
+
+ final String text = element.getTextContent();
+ assert text != null;
+
+ if (!evaluateRegexpAttribute(element)) {
+ // non-regexp Scope, apply normal validators
+ applyValidators(text, context);
+ } else {
+ // regexp Scope, apply secondary validators
+ regexpValidators.validate(text, context.getItem(), ensureId());
+ }
+ }
+
+ @Override
+ protected void doDestroy() {
+ regexpValidators.destroy();
+ super.doDestroy();
+ }
+
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ regexpValidators.setId(ensureId());
+ regexpValidators.initialize();
+ }
+
+}
diff --git a/mda-framework/src/main/java/net/shibboleth/metadata/dom/saml/shibboleth/ShibbolethMetadataSupport.java b/mda-framework/src/main/java/net/shibboleth/metadata/dom/saml/shibboleth/ShibbolethMetadataSupport.java
new file mode 100644
index 0000000..39650e4
--- /dev/null
+++ b/mda-framework/src/main/java/net/shibboleth/metadata/dom/saml/shibboleth/ShibbolethMetadataSupport.java
@@ -0,0 +1,48 @@
+/*
+ * 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.metadata.dom.saml.shibboleth;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
+import javax.xml.namespace.QName;
+
+/**
+ * Helper class for dealing with Shibboleth metadata.
+ *
+ * @since 0.10.0
+ */
+ at ThreadSafe
+public final class ShibbolethMetadataSupport {
+
+ /** Shibboleth metadata namespace URI. */
+ public static final @Nonnull String SHIBMD_NS = "urn:mace:shibboleth:metadata:1.0";
+
+ /** Default Shibboleth metadata namespace prefix. */
+ public static final @Nonnull String SHIBMD_PREFIX = "shibmd";
+
+ /** Scope element name. */
+ public static final @Nonnull QName SCOPE_NAME = new QName(SHIBMD_NS, "Scope", SHIBMD_PREFIX);
+
+ /** regexp attribute name. */
+ public static final @Nonnull QName REGEXP_ATTRIB_NAME = new QName("regexp");
+
+ /** Constructor. */
+ private ShibbolethMetadataSupport() {
+
+ }
+}
diff --git a/mda-framework/src/main/java/net/shibboleth/metadata/dom/saml/shibboleth/package-info.java b/mda-framework/src/main/java/net/shibboleth/metadata/dom/saml/shibboleth/package-info.java
new file mode 100644
index 0000000..7b834c7
--- /dev/null
+++ b/mda-framework/src/main/java/net/shibboleth/metadata/dom/saml/shibboleth/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * Aggregator classes for Shibboleth SAML metadata.
+ *
+ * @since 0.10.0
+ */
+package net.shibboleth.metadata.dom.saml.shibboleth;
diff --git a/mda-framework/src/main/java/net/shibboleth/metadata/validate/BaseAsValidator.java b/mda-framework/src/main/java/net/shibboleth/metadata/validate/BaseAsValidator.java
new file mode 100644
index 0000000..096e067
--- /dev/null
+++ b/mda-framework/src/main/java/net/shibboleth/metadata/validate/BaseAsValidator.java
@@ -0,0 +1,157 @@
+/*
+ * 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.metadata.validate;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
+
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.pipeline.StageProcessingException;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * An abstract base class for {@link Validator} implementations which validate a value of
+ * one type "as" another type: for example, to validate a <code>String</code> "as" an
+ * <code>InternetDomainName</code>.
+ *
+ * <p>The implementation calls a template method in the implementation subclass to perform the
+ * conversion. If the conversion succeeds, a sequence of {@link Validator}s are applied to
+ * that new value.</p>
+ *
+ * <p>If the value cannot be converted to the new type, the template method is expected to
+ * throw {@link IllegalArgumentException}. In this case, behaviour depends on the
+ * {@link #conversionRequired} property.</p>
+ *
+ * <p>If {@link #conversionRequired} is <code>true</code> (the default) then an error
+ * status will be applied to the {@link Item}, and the validator will return
+ * {@link net.shibboleth.metadata.validate.Validator.Action#DONE}.</p>
+ *
+ * <p>If {@link #conversionRequired} is <code>false</code> then the validator
+ * will simply return {@link net.shibboleth.metadata.validate.Validator.Action#CONTINUE}
+ * so that subsequent validators may still be applied. This allows several "as" validators
+ * to be applied in sequence, each taking a different approach.</p>
+ *
+ * @param <V> type of the original value
+ * @param <A> type of the new value to which validators should be applied
+ *
+ * @since 0.10.0
+ */
+ at ThreadSafe
+public abstract class BaseAsValidator<V, A> extends BaseValidator implements Validator<V> {
+
+ /** The validator sequence to apply. */
+ @GuardedBy("this")
+ private @Nonnull ValidatorSequence<A> validators = new ValidatorSequence<>();
+
+ /** Whether conversion to the new type must succeed. Default: <code>true</code> */
+ private boolean conversionRequired = true;
+
+ /**
+ * Set the list of validators to apply to each item.
+ *
+ * @param newValidators the list of validators to set
+ */
+ public synchronized void setValidators(@Nonnull final List<Validator<A>> newValidators) {
+ validators.setValidators(newValidators);
+ }
+
+ /**
+ * Gets the list of validators being applied to each item.
+ *
+ * @return list of validators
+ */
+ @Nonnull
+ public synchronized List<Validator<A>> getValidators() {
+ return validators.getValidators();
+ }
+
+ /**
+ * Set whether conversion to the new type is required to succeed.
+ *
+ * @param required <code>true</code> if the conversion is required to succeed
+ */
+ public void setConversionRequired(final boolean required) {
+ conversionRequired = required;
+ }
+
+ /**
+ * Returns whether conversion to the new type is required to succeed.
+ *
+ * @return <code>true</code> if the conversion is required to succeed
+ */
+ public boolean isConversionRequired() {
+ return conversionRequired;
+ }
+
+ /**
+ * Apply each of the configured validators in turn to the provided object.
+ *
+ * @param value object to be validated
+ * @param item the {@link Item} context for the validation
+ *
+ * @return the result of applying the validators to the value
+ *
+ * @throws StageProcessingException if errors occur during processing
+ */
+ protected @Nonnull Action applyValidators(@Nonnull final A value, @Nonnull final Item<?> item)
+ throws StageProcessingException {
+ return validators.validate(value, item, ensureId());
+ }
+
+ /**
+ * Convert from the old value type to the new.
+ *
+ * @param from a value of the old type
+ * @return a value of the new type
+ * @throws IllegalArgumentException if a conversion can not be performed
+ */
+ protected abstract @Nonnull A convert(@Nonnull final V from) throws IllegalArgumentException;
+
+ @Override
+ public @Nonnull Action validate(@Nonnull final V t, @Nonnull final Item<?> item, @Nonnull final String stageId)
+ throws StageProcessingException {
+ try {
+ final A v = convert(t);
+ return applyValidators(v, item);
+ } catch (final IllegalArgumentException e) {
+ if (isConversionRequired()) {
+ addErrorMessage(t, item, stageId);
+ return Action.DONE;
+ } else {
+ return Action.CONTINUE;
+ }
+ }
+ }
+
+ @Override
+ protected void doDestroy() {
+ validators.destroy();
+ super.doDestroy();
+ }
+
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ validators.setId(ensureId());
+ validators.initialize();
+ }
+
+}
diff --git a/mda-framework/src/main/java/net/shibboleth/metadata/validate/net/RejectDomainNameNotUnderPublicSuffixValidator.java b/mda-framework/src/main/java/net/shibboleth/metadata/validate/net/RejectDomainNameNotUnderPublicSuffixValidator.java
new file mode 100644
index 0000000..2faffea
--- /dev/null
+++ b/mda-framework/src/main/java/net/shibboleth/metadata/validate/net/RejectDomainNameNotUnderPublicSuffixValidator.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.metadata.validate.net;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
+
+import com.google.common.net.InternetDomainName;
+
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.validate.BaseValidator;
+import net.shibboleth.metadata.validate.Validator;
+
+/**
+ * A validator that checks whether an {@link InternetDomainName} is under a public suffix.
+ *
+ * <p>
+ * A domain name which is <em>not</em> under a public suffix might be a public suffix itself,
+ * or might terminate in something which is not a public suffix.
+ * </p>
+ *
+ * @since 0.10.0
+ */
+ at ThreadSafe
+public class RejectDomainNameNotUnderPublicSuffixValidator extends BaseValidator
+ implements Validator<InternetDomainName> {
+
+ @Override
+ public @Nonnull Action validate(@Nonnull final InternetDomainName domain, @Nonnull final Item<?> item,
+ @Nonnull final String stageId) {
+ if (domain.isUnderPublicSuffix()) {
+ return Action.CONTINUE;
+ } else {
+ addErrorMessage(domain, item, stageId);
+ return Action.DONE;
+ }
+ }
+
+}
diff --git a/mda-framework/src/main/java/net/shibboleth/metadata/validate/net/RejectDomainNamePublicSuffixValidator.java b/mda-framework/src/main/java/net/shibboleth/metadata/validate/net/RejectDomainNamePublicSuffixValidator.java
new file mode 100644
index 0000000..7b5e849
--- /dev/null
+++ b/mda-framework/src/main/java/net/shibboleth/metadata/validate/net/RejectDomainNamePublicSuffixValidator.java
@@ -0,0 +1,49 @@
+/*
+ * 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.metadata.validate.net;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
+
+import com.google.common.net.InternetDomainName;
+
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.validate.BaseValidator;
+import net.shibboleth.metadata.validate.Validator;
+
+/**
+ * A validator that rejects an {@link InternetDomainName} if it is a public suffix.
+ *
+ * @since 0.10.0
+ */
+ at ThreadSafe
+public class RejectDomainNamePublicSuffixValidator extends BaseValidator
+ implements Validator<InternetDomainName> {
+
+ @Override
+ public @Nonnull Action validate(@Nonnull final InternetDomainName domain, @Nonnull final Item<?> item,
+ @Nonnull final String stageId) {
+ if (domain.isPublicSuffix()) {
+ addErrorMessage(domain, item, stageId);
+ return Action.DONE;
+ } else {
+ return Action.CONTINUE;
+ }
+ }
+
+}
diff --git a/mda-framework/src/main/java/net/shibboleth/metadata/validate/net/package-info.java b/mda-framework/src/main/java/net/shibboleth/metadata/validate/net/package-info.java
new file mode 100644
index 0000000..0202753
--- /dev/null
+++ b/mda-framework/src/main/java/net/shibboleth/metadata/validate/net/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes for validation of network-related object types.
+ *
+ * @since 0.10.0
+ */
+package net.shibboleth.metadata.validate.net;
diff --git a/mda-framework/src/main/java/net/shibboleth/metadata/validate/string/AsDomainNameStringValidator.java b/mda-framework/src/main/java/net/shibboleth/metadata/validate/string/AsDomainNameStringValidator.java
new file mode 100644
index 0000000..f307883
--- /dev/null
+++ b/mda-framework/src/main/java/net/shibboleth/metadata/validate/string/AsDomainNameStringValidator.java
@@ -0,0 +1,53 @@
+/*
+ * 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.metadata.validate.string;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.net.InternetDomainName;
+
+import net.shibboleth.metadata.validate.BaseAsValidator;
+import net.shibboleth.metadata.validate.Validator;
+
+/**
+ * A <code>Validator</code> that checks {@link String} values as domain names by converting the
+ * value to an {@link InternetDomainName} and applying a sequence of validators to that value.
+ *
+ * <p>
+ * This validator fails (and returns {@link net.shibboleth.metadata.validate.Validator.Action#DONE}) if the
+ * value can not be converted to an {@link InternetDomainName}.
+ * </p>
+ *
+ * <p>
+ * Otherwise, the validator applies the sequence of validators to the {@link InternetDomainName} and returns
+ * the value of that sequence.
+ * </p>
+ *
+ * @since 0.10.0
+ */
+public class AsDomainNameStringValidator extends BaseAsValidator<String, InternetDomainName>
+ implements Validator<String> {
+
+ @Override
+ protected @Nonnull InternetDomainName convert(@Nonnull final String domain) throws IllegalArgumentException {
+ final var result = InternetDomainName.from(domain);
+ assert result != null;
+ return result;
+ }
+
+}
diff --git a/mda-framework/src/main/java/net/shibboleth/metadata/validate/string/AsLiteralTailStringValidator.java b/mda-framework/src/main/java/net/shibboleth/metadata/validate/string/AsLiteralTailStringValidator.java
new file mode 100644
index 0000000..f71eb9e
--- /dev/null
+++ b/mda-framework/src/main/java/net/shibboleth/metadata/validate/string/AsLiteralTailStringValidator.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.metadata.validate.string;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.metadata.validate.BaseAsValidator;
+import net.shibboleth.metadata.validate.Validator;
+
+/**
+ * A <code>Validator</code> that assists in the validation of regular-expression <shibmd:Scope>
+ * values that include a literal tail.
+ *
+ * <p>A literal tail is:</p>
+ *
+ * <ul>
+ * <li>a sequence of at least two domain labels</li>
+ * <li>separated by literal <code>'.'</code> characters (encoded
+ * in the regular expression as <code>'\.'</code></li>
+ * <li>explicitly anchored at the end of the regular expression</li>
+ * <li>preceded by anything terminating with an encoded literal <code>'.'</code>
+ * </ul>
+ *
+ * <p>For example, the literal tail in the regular expression
+ * <code>'^([a-zA-Z0-9-]{1,63}\.){0,2}ddd\.ccc\.bbb\.aa$'</code> is <code>'ccc.bbb.aa'</code>.</p>
+ *
+ * <p>The literal tail is extracted from the regular expression, has its encoded <code>'.'</code>
+ * characters converted to normal ones, and then validated as a {@link String} by a new sequence of
+ * validators.</p>
+ *
+ * <p>This validator fails (and returns {@link net.shibboleth.metadata.validate.Validator.Action#DONE}) if the
+ * value does not possess a literal tail.</p>
+ *
+ * <p>Otherwise, the validator applies the sequence of validators to the new value and returns
+ * the value of that sequence.</p>
+ *
+ * @since 0.10.0
+ */
+public class AsLiteralTailStringValidator extends BaseAsValidator<String, String>
+ implements Validator<String> {
+
+ /**
+ * Regular expression to match and extract the literal tail.
+ *
+ * <p>The component parts of this expression are:</p>
+ *
+ * <ul>
+ * <li>implicitly, anchored at the start of the string being matched</li>
+ * <li><code>.*?</code> matches non-greedily any text at the start of the regular expression,
+ * maximising the size of the later parts of the match</li>
+ * <li><code>\\\\.</code> matches <code>\.</code> in the regular expression, which in turn
+ * matches a literal <code>.</code> in the scope</li>
+ * <li>A group containing:
+ * <ul>
+ * <li>At least one DNS label terminated by a literal '.'</li>
+ * <li>A final DNS label</li>
+ * </ul>
+ * </li>
+ * <li><code>\\$</code> matches an end-string marker in the regular expression being matched</li>
+ * <li>implicitly, anchored at the end of the string being matched</li>
+ * </ul>
+ *
+ * <p>The matching of DNS labels is not exact. For example, labels starting or ending with hyphens
+ * are accepted as part of a literal tail. This will normally be detected by the nested validator
+ * sequence applied to the result.</p>
+ *
+ * <p>Similarly, upper-case characters are permitted in the literal tail although these would
+ * not normally be permitted in scopes. Again, these characters are permitted so that a more
+ * specific error can be reported, rather than just a generic failure to convert.</p>
+ */
+ private final Pattern pattern = Pattern.compile(".*?\\\\.(([a-zA-Z0-9-]+\\\\.)+[a-zA-Z0-9-]+)\\$");
+
+ @Override
+ protected @Nonnull String convert(@Nonnull final String regex) throws IllegalArgumentException {
+ // Match against the regular expression
+ final Matcher matcher = pattern.matcher(regex);
+
+ // If the pattern does not match, signal that the string does not have a literal tail
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException();
+ }
+
+ // Remove all '\' characters from the result.
+ final var result = matcher.group(1).replaceAll("\\\\", "");
+ assert result != null;
+ return result;
+ }
+
+}
diff --git a/mda-framework/src/main/resources/net/shibboleth/metadata/beans.xml b/mda-framework/src/main/resources/net/shibboleth/metadata/beans.xml
index d71f356..e03c545 100644
--- a/mda-framework/src/main/resources/net/shibboleth/metadata/beans.xml
+++ b/mda-framework/src/main/resources/net/shibboleth/metadata/beans.xml
@@ -213,6 +213,13 @@
<bean id="mda.IPHintValidationStage" abstract="true" parent="mda.stage_parent"
class="net.shibboleth.metadata.dom.saml.mdui.IPHintValidationStage"/>
+ <!--
+ net.shibboleth.metadata.dom.saml.shibboleth
+ -->
+
+ <bean id="mda.ScopeValidationStage" abstract="true" parent="mda.stage_parent"
+ class="net.shibboleth.metadata.dom.saml.shibboleth.ScopeValidationStage"/>
+
<!--
net.shibboleth.metadata.pipeline
-->
@@ -299,6 +306,16 @@
<bean id="mda.RejectAllValidator" abstract="true" parent="mda.validator_parent"
class="net.shibboleth.metadata.validate.RejectAllValidator"/>
+ <!--
+ net.shibboleth.metadata.validate.net
+ -->
+
+ <bean id="mda.RejectDomainNameNotUnderPublicSuffixValidator" abstract="true" parent="mda.component_parent"
+ class="net.shibboleth.metadata.validate.net.RejectDomainNameNotUnderPublicSuffixValidator"/>
+
+ <bean id="mda.RejectDomainNamePublicSuffixValidator" abstract="true" parent="mda.component_parent"
+ class="net.shibboleth.metadata.validate.net.RejectDomainNamePublicSuffixValidator"/>
+
<!--
net.shibboleth.metadata.validate.string
-->
@@ -309,6 +326,12 @@
<bean id="mda.AcceptStringValueValidator" abstract="true" parent="mda.validator_parent"
class="net.shibboleth.metadata.validate.string.AcceptStringValueValidator"/>
+ <bean id="mda.AsDomainNameStringValidator" abstract="true" parent="mda.component_parent"
+ class="net.shibboleth.metadata.validate.string.AsDomainNameStringValidator"/>
+
+ <bean id="mda.AsLiteralTailStringValidator" abstract="true" parent="mda.component_parent"
+ class="net.shibboleth.metadata.validate.string.AsLiteralTailStringValidator"/>
+
<bean id="mda.RejectStringRegexValidator" abstract="true" parent="mda.validator_parent"
class="net.shibboleth.metadata.validate.string.RejectStringRegexValidator"/>
@@ -405,11 +428,18 @@
<!-- Beans adopted from inc-mda project in MDA 0.10.0. -->
<entry key="inc.AcceptAllValidator" value="mda.AcceptAllValidator"/>
+ <entry key="inc.AsDomainNameStringValidator" value="mda.AsDomainNameStringValidator"/>
+ <entry key="inc.AsLiteralTailStringValidator" value="mda.AsLiteralTailStringValidator"/>
<entry key="inc.RejectAllValidator" value="mda.RejectAllValidator"/>
<entry key="inc.AcceptStringRegexValidator" value="mda.AcceptStringRegexValidator"/>
<entry key="inc.AcceptStringValueValidator" value="mda.AcceptStringValueValidator"/>
+ <entry key="inc.RejectDomainNameNotUnderPublicSuffixValidator"
+ value="mda.RejectDomainNameNotUnderPublicSuffixValidator"/>
+ <entry key="inc.RejectDomainNamePublicSuffixValidator"
+ value="mda.RejectDomainNamePublicSuffixValidator"/>
<entry key="inc.RejectStringRegexValidator" value="mda.RejectStringRegexValidator"/>
<entry key="inc.RejectStringValueValidator" value="mda.RejectStringValueValidator"/>
+ <entry key="inc.ScopeValidationStage" value="mda.ScopeValidationStage"/>
</util:map>
diff --git a/mda-framework/src/test/java/net/shibboleth/metadata/dom/saml/shibboleth/ScopeValidationStageLitmusTest.java b/mda-framework/src/test/java/net/shibboleth/metadata/dom/saml/shibboleth/ScopeValidationStageLitmusTest.java
new file mode 100644
index 0000000..7a9fd9d
--- /dev/null
+++ b/mda-framework/src/test/java/net/shibboleth/metadata/dom/saml/shibboleth/ScopeValidationStageLitmusTest.java
@@ -0,0 +1,179 @@
+
+package net.shibboleth.metadata.dom.saml.shibboleth;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import net.shibboleth.metadata.ErrorStatus;
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.dom.DOMElementItem;
+import net.shibboleth.metadata.pipeline.Stage;
+import net.shibboleth.shared.xml.AttributeSupport;
+import net.shibboleth.shared.xml.ElementSupport;
+
+/**
+ * A litmus test for {@link ScopeValidationStage} involving a set of valid and invalid
+ * scope values, both for regular expression and plain cases.
+ *
+ * <p>
+ * The configuration for the stage is taken from a Spring XML configuration file.
+ * </p>
+ */
+ at ContextConfiguration("ScopeValidationStageLitmusTest-config.xml")
+public class ScopeValidationStageLitmusTest extends AbstractTestNGSpringContextTests {
+
+ /** Build documents using this. */
+ private DocumentBuilder dBuilder;
+
+ /** {@link Stage} to run for each test. */
+ private Stage<Element> stage;
+
+ @BeforeClass
+ private void setUp() throws Exception {
+ final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+ dBuilder = dbFactory.newDocumentBuilder();
+ stage = makeStage();
+ }
+
+ /** Acquire the configured stage from the Spring context. */
+ private Stage<Element> makeStage() throws Exception {
+ assert applicationContext != null;
+ @SuppressWarnings("unchecked")
+ final Stage<Element> stage = applicationContext.getBean("litmusTest", Stage.class);
+ stage.initialize();
+ return stage;
+ }
+
+ /** Build a <code>shibmd:Scope</code> {@link Element}. */
+ private Element buildScope(final @Nonnull Document document, final String value, final boolean isRegex) {
+ final Element element = ElementSupport.constructElement(document, ShibbolethMetadataSupport.SCOPE_NAME);
+ AttributeSupport.appendAttribute(element, ShibbolethMetadataSupport.REGEXP_ATTRIB_NAME,
+ isRegex ? "true" : "false");
+ element.setTextContent(value);
+ return element;
+ }
+
+ /** Build a {@link Document} containing an appropriate <code>shibmd:Scope</code> {@link Element}. */
+ private @Nonnull Document buildDocument(final String value, final boolean isRegex) {
+ final Document document = dBuilder.newDocument();
+ document.appendChild(buildScope(document, value, isRegex));
+ return document;
+ }
+
+ /** Run the test stage on a single {@link Item}. */
+ private List<ErrorStatus> runTest(final Item<Element> item) throws Exception {
+ final List<Item<Element>> coll = new ArrayList<>();
+ coll.add(item);
+ stage.execute(coll);
+ final List<ErrorStatus> errors = item.getItemMetadata().get(ErrorStatus.class);
+ return errors;
+ }
+
+ /** Test a value-regexp combination we expect to be accepted. */
+ private void good(final String value, final boolean isRegex) throws Exception {
+ final Item<Element> item = new DOMElementItem(buildDocument(value, isRegex));
+ final List<ErrorStatus> errors = runTest(item);
+ if (errors.size() != 0) {
+ Assert.fail("expected no errors for '" + value + "'[" + isRegex + "] " +
+ "but saw \"" + errors.get(0).getStatusMessage() + "\"");
+ }
+ }
+
+ /** Test a non-regexp value we expect to be accepted. */
+ private void good(final String value) throws Exception {
+ good(value, false);
+ }
+
+ /** Test a regexp value we expect to be accepted. */
+ private void goodRegexp(final String value) throws Exception {
+ good(value, true);
+ }
+
+ /** Test a value-regexp combination we expect to be rejected. */
+ private void bad(final String value, final boolean isRegex, final String why) throws Exception {
+ final Item<Element> item = new DOMElementItem(buildDocument(value, isRegex));
+ final List<ErrorStatus> errors = runTest(item);
+ Assert.assertEquals(errors.size(), 1, "expected an error for '" + value + "'[" + isRegex + "]");
+ final ErrorStatus error = errors.get(0);
+ final String message = error.getStatusMessage();
+ Assert.assertTrue(message.contains(why), "error '" + message + "' didn't contain '" + why + "'");
+ }
+
+ /** Test a non-regexp value we expect to be rejected. */
+ private void bad(final String value) throws Exception {
+ bad(value, false, "");
+ }
+
+ /** Test a non-regexp value we expect to be rejected. */
+ private void bad(final String value, final String why) throws Exception {
+ bad(value, false, why);
+ }
+
+ /** Test a regexp value we expect to be rejected. */
+ private void badRegexp(final String value, final String why) throws Exception {
+ bad(value, true, why);
+ }
+
+ /** Test a regexp value we expect to be rejected. */
+ private void badRegexp(final String value) throws Exception {
+ bad(value, true, "");
+ }
+
+ @Test
+ public void litmusTests() throws Exception {
+ good("example.org");
+ good("UGent.be");
+ bad("", "empty");
+ bad(" ");
+ bad(" ");
+ bad(" example.org", "white space");
+ bad("example.org ", "white space");
+ bad("example**.org", "scope is not a valid domain name: example**.org");
+ bad("uk", "scope is a public suffix");
+ bad("ac.uk", "scope is a public suffix");
+ bad("random.nonsense", "scope is not under a public suffix");
+ good("example.ac.uk");
+ bad("adm.aau.dk at aau.dk"); // incommon/inc-meta#58
+ bad("example .org", "white space");
+ bad("\nexample.org", "white space");
+
+ badRegexp("", "empty");
+ badRegexp(" ");
+ badRegexp(" ");
+ badRegexp("aaaa$", "does not start with an anchor ('^')");
+ badRegexp("^aaaa", "does not end with an anchor ('$')");
+ goodRegexp("^([a-zA-Z0-9-]{1,63}\\.){0,2}vho\\.aaf\\.edu\\.au$");
+ // don't use literal .s
+ badRegexp("^([a-zA-Z0-9-]{1,63}.){0,2}vho.aaf.edu.au$", "does not end with a literal tail");
+ // bad literal tail: no public suffix
+ badRegexp("^([a-zA-Z0-9-]{1,63}\\.){0,2}vho\\.aaf\\.edu\\.nopublic$", "literal tail is not under a public suffix");
+ // bad literal tail: is a public suffix
+ badRegexp("^.*\\.ac\\.uk$", "literal tail is a public suffix");
+
+ // UK federation examples
+ goodRegexp("^.+\\.atomwide\\.com$");
+ goodRegexp("^.+\\.856\\.eng\\.ukfederation\\.org\\.uk$");
+ goodRegexp("^.+\\.scot\\.nhs\\.uk$");
+ goodRegexp("^.+\\.login\\.groupcall\\.com$");
+ goodRegexp("^.+\\.logintestingthirks\\.groupcall\\.com$");
+ goodRegexp("^.+\\.logintest\\.me\\.e2bn\\.org$");
+ goodRegexp("^.+\\.loginstaging\\.groupcall\\.com$");
+ goodRegexp("^.+\\.identityfor\\.co\\.uk$");
+ goodRegexp("^.+\\.rmunify\\.com$");
+
+ // Combination regexp plus case significance
+ goodRegexp("^.+\\.UGent\\.be$");
+ }
+}
diff --git a/mda-framework/src/test/java/net/shibboleth/metadata/validate/net/RejectDomainNameNotUnderPublicSuffixValidatorTest.java b/mda-framework/src/test/java/net/shibboleth/metadata/validate/net/RejectDomainNameNotUnderPublicSuffixValidatorTest.java
new file mode 100644
index 0000000..d06de16
--- /dev/null
+++ b/mda-framework/src/test/java/net/shibboleth/metadata/validate/net/RejectDomainNameNotUnderPublicSuffixValidatorTest.java
@@ -0,0 +1,91 @@
+package net.shibboleth.metadata.validate.net;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.net.InternetDomainName;
+
+import net.shibboleth.metadata.ErrorStatus;
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.testing.MockItem;
+import net.shibboleth.metadata.validate.Validator.Action;
+
+public class RejectDomainNameNotUnderPublicSuffixValidatorTest {
+
+ @Test
+ public void normal() throws Exception {
+ final Item<String> item = new MockItem("content");
+ final RejectDomainNameNotUnderPublicSuffixValidator val =
+ new RejectDomainNameNotUnderPublicSuffixValidator();
+ val.setId("validate");
+ val.initialize();
+
+ final InternetDomainName domain = InternetDomainName.from("example.org");
+ assert domain != null;
+ final Action res = val.validate(domain, item, "stage");
+ Assert.assertNotNull(res);
+ Assert.assertEquals(res, Action.CONTINUE);
+ Assert.assertEquals(item.getItemMetadata().get(ErrorStatus.class).size(), 0);
+
+ final var domain2 = InternetDomainName.from("ed.ac.uk");
+ assert domain2 != null;
+ Assert.assertEquals(val.validate(domain2, item, "stage"), Action.CONTINUE);
+ Assert.assertEquals(item.getItemMetadata().get(ErrorStatus.class).size(), 0);
+ }
+
+ @Test
+ public void uk() throws Exception {
+ final Item<String> item = new MockItem("content");
+ final RejectDomainNameNotUnderPublicSuffixValidator val =
+ new RejectDomainNameNotUnderPublicSuffixValidator();
+ val.setId("validate");
+ val.initialize();
+
+ final InternetDomainName domain = InternetDomainName.from("uk");
+ assert domain != null;
+ final Action res = val.validate(domain, item, "stage");
+ Assert.assertNotNull(res);
+ Assert.assertEquals(res, Action.DONE);
+ Assert.assertEquals(item.getItemMetadata().get(ErrorStatus.class).size(), 1);
+ Assert.assertTrue(item.getItemMetadata().get(ErrorStatus.class).get(0).getStatusMessage().contains("rejected"));
+ }
+
+ @Test
+ public void ac_uk() throws Exception {
+ final Item<String> item = new MockItem("content");
+ final RejectDomainNameNotUnderPublicSuffixValidator val =
+ new RejectDomainNameNotUnderPublicSuffixValidator();
+ val.setId("validate");
+ val.initialize();
+
+ final InternetDomainName domain = InternetDomainName.from("ac.uk");
+ assert domain != null;
+ final Action res = val.validate(domain, item, "stage");
+ Assert.assertNotNull(res);
+ Assert.assertEquals(res, Action.DONE);
+ Assert.assertEquals(item.getItemMetadata().get(ErrorStatus.class).size(), 1);
+ Assert.assertTrue(item.getItemMetadata().get(ErrorStatus.class).get(0).getStatusMessage().contains("rejected"));
+ }
+
+ @Test
+ public void wibble_wobble() throws Exception {
+ final Item<String> item = new MockItem("content");
+ final RejectDomainNameNotUnderPublicSuffixValidator val =
+ new RejectDomainNameNotUnderPublicSuffixValidator();
+ val.setId("validate");
+ val.setMessage("scope is not under a public suffix: '%s'");
+ val.initialize();
+
+ // This is (currently) just a nonsense value, so it doesn't have a public suffix
+ // and isn't under one either.
+ final InternetDomainName domain = InternetDomainName.from("wibble.wobble");
+ assert domain != null;
+ final Action res = val.validate(domain, item, "stage");
+ Assert.assertNotNull(res);
+ Assert.assertEquals(res, Action.DONE);
+ Assert.assertEquals(item.getItemMetadata().get(ErrorStatus.class).size(), 1);
+ Assert.assertEquals(item.getItemMetadata().get(ErrorStatus.class).get(0).getStatusMessage(),
+ "scope is not under a public suffix: 'wibble.wobble'");
+ }
+
+}
diff --git a/mda-framework/src/test/java/net/shibboleth/metadata/validate/net/RejectDomainNamePublicSuffixValidatorTest.java b/mda-framework/src/test/java/net/shibboleth/metadata/validate/net/RejectDomainNamePublicSuffixValidatorTest.java
new file mode 100644
index 0000000..a35d2f5
--- /dev/null
+++ b/mda-framework/src/test/java/net/shibboleth/metadata/validate/net/RejectDomainNamePublicSuffixValidatorTest.java
@@ -0,0 +1,75 @@
+package net.shibboleth.metadata.validate.net;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.net.InternetDomainName;
+
+import net.shibboleth.metadata.ErrorStatus;
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.testing.MockItem;
+import net.shibboleth.metadata.validate.Validator.Action;
+
+public class RejectDomainNamePublicSuffixValidatorTest {
+
+ @Test
+ public void normal() throws Exception {
+ final Item<String> item = new MockItem("content");
+ final RejectDomainNamePublicSuffixValidator val =
+ new RejectDomainNamePublicSuffixValidator();
+ val.setId("validate");
+ val.initialize();
+
+ final InternetDomainName domain = InternetDomainName.from("example.org");
+ assert domain != null;
+ final Action res = val.validate(domain, item, "stage");
+ Assert.assertNotNull(res);
+ Assert.assertEquals(res, Action.CONTINUE);
+ Assert.assertEquals(item.getItemMetadata().get(ErrorStatus.class).size(), 0);
+
+ final var domain2 = InternetDomainName.from("ed.ac.uk");
+ assert domain2 != null;
+ Assert.assertEquals(val.validate(domain2, item, "stage"), Action.CONTINUE);
+ Assert.assertEquals(item.getItemMetadata().get(ErrorStatus.class).size(), 0);
+
+ final var domain3 = InternetDomainName.from("complete.nonsense");
+ assert domain3 != null;
+ Assert.assertEquals(val.validate(domain3, item, "stage"), Action.CONTINUE);
+ Assert.assertEquals(item.getItemMetadata().get(ErrorStatus.class).size(), 0);
+ }
+
+ @Test
+ public void uk() throws Exception {
+ final Item<String> item = new MockItem("content");
+ final RejectDomainNamePublicSuffixValidator val =
+ new RejectDomainNamePublicSuffixValidator();
+ val.setId("validate");
+ val.initialize();
+
+ final InternetDomainName domain = InternetDomainName.from("uk");
+ assert domain != null;
+ final Action res = val.validate(domain, item, "stage");
+ Assert.assertNotNull(res);
+ Assert.assertEquals(res, Action.DONE);
+ Assert.assertEquals(item.getItemMetadata().get(ErrorStatus.class).size(), 1);
+ Assert.assertTrue(item.getItemMetadata().get(ErrorStatus.class).get(0).getStatusMessage().contains("rejected"));
+ }
+
+ @Test
+ public void ac_uk() throws Exception {
+ final Item<String> item = new MockItem("content");
+ final RejectDomainNamePublicSuffixValidator val =
+ new RejectDomainNamePublicSuffixValidator();
+ val.setId("validate");
+ val.initialize();
+
+ final InternetDomainName domain = InternetDomainName.from("ac.uk");
+ assert domain != null;
+ final Action res = val.validate(domain, item, "stage");
+ Assert.assertNotNull(res);
+ Assert.assertEquals(res, Action.DONE);
+ Assert.assertEquals(item.getItemMetadata().get(ErrorStatus.class).size(), 1);
+ Assert.assertTrue(item.getItemMetadata().get(ErrorStatus.class).get(0).getStatusMessage().contains("rejected"));
+ }
+
+}
diff --git a/mda-framework/src/test/java/net/shibboleth/metadata/validate/string/AsDomainNameStringValidatorTest.java b/mda-framework/src/test/java/net/shibboleth/metadata/validate/string/AsDomainNameStringValidatorTest.java
new file mode 100644
index 0000000..3025a28
--- /dev/null
+++ b/mda-framework/src/test/java/net/shibboleth/metadata/validate/string/AsDomainNameStringValidatorTest.java
@@ -0,0 +1,111 @@
+
+package net.shibboleth.metadata.validate.string;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.net.InternetDomainName;
+
+import net.shibboleth.metadata.ErrorStatus;
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.pipeline.StageProcessingException;
+import net.shibboleth.metadata.testing.MockItem;
+import net.shibboleth.metadata.validate.BaseValidator;
+import net.shibboleth.metadata.validate.Validator;
+import net.shibboleth.metadata.validate.Validator.Action;
+
+public class AsDomainNameStringValidatorTest {
+
+ private static class CountingValidator extends BaseValidator implements Validator<InternetDomainName> {
+ public int count;
+ private final @Nonnull Action action;
+
+ @Override
+ public @Nonnull Action validate(@Nonnull InternetDomainName e, @Nonnull Item<?> item, @Nonnull String stageId)
+ throws StageProcessingException {
+ count++;
+ return action;
+ }
+
+ /** Constructor. */
+ public CountingValidator(final @Nonnull Action a) {
+ action = a;
+ }
+ }
+
+ @Test
+ public void testOK() throws Exception {
+ final CountingValidator counter = new CountingValidator(Action.CONTINUE);
+ counter.setId("counter");
+ counter.initialize();
+
+ final List<Validator<InternetDomainName>> nestedSequence = new ArrayList<>();
+ nestedSequence.add(counter);
+
+ final Item<String> item = new MockItem("content");
+
+ final AsDomainNameStringValidator val = new AsDomainNameStringValidator();
+ val.setId("id");
+ val.setValidators(nestedSequence);
+ val.initialize();
+
+ final Action res = val.validate("example.org", item, "stage");
+ Assert.assertEquals(res, Action.CONTINUE);
+ Assert.assertEquals(item.getItemMetadata().get(ErrorStatus.class).size(), 0);
+ Assert.assertEquals(counter.count, 1);
+ }
+
+ @Test
+ public void testNoConvertDefault() throws Exception {
+ final CountingValidator counter = new CountingValidator(Action.CONTINUE);
+ counter.setId("counter");
+ counter.initialize();
+
+ final List<Validator<InternetDomainName>> nestedSequence = new ArrayList<>();
+ nestedSequence.add(counter);
+
+ final Item<String> item = new MockItem("content");
+
+ final AsDomainNameStringValidator val = new AsDomainNameStringValidator();
+ val.setId("id");
+ val.setValidators(nestedSequence);
+ val.setMessage("quick brown %s");
+ val.initialize();
+
+ final Action res = val.validate("example**.org", item, "stage");
+ Assert.assertEquals(res, Action.DONE);
+ Assert.assertEquals(item.getItemMetadata().get(ErrorStatus.class).size(), 1);
+ final ErrorStatus err = item.getItemMetadata().get(ErrorStatus.class).get(0);
+ Assert.assertTrue(err.getStatusMessage().contains("quick brown example**.org"));
+ Assert.assertEquals(counter.count, 0);
+ }
+
+ @Test
+ public void testNoConvertFalse() throws Exception {
+ final CountingValidator counter = new CountingValidator(Action.CONTINUE);
+ counter.setId("counter");
+ counter.initialize();
+
+ final List<Validator<InternetDomainName>> nestedSequence = new ArrayList<>();
+ nestedSequence.add(counter);
+
+ final Item<String> item = new MockItem("content");
+
+ final AsDomainNameStringValidator val = new AsDomainNameStringValidator();
+ val.setId("id");
+ val.setConversionRequired(false);
+ val.setValidators(nestedSequence);
+ val.initialize();
+
+ final Action res = val.validate("example**.org", item, "stage");
+ Assert.assertEquals(res, Action.CONTINUE);
+ Assert.assertEquals(item.getItemMetadata().get(ErrorStatus.class).size(), 0);
+ Assert.assertEquals(counter.count, 0);
+ }
+
+}
diff --git a/mda-framework/src/test/java/net/shibboleth/metadata/validate/string/AsLiteralTailStringValidatorTest.java b/mda-framework/src/test/java/net/shibboleth/metadata/validate/string/AsLiteralTailStringValidatorTest.java
new file mode 100644
index 0000000..bd7cbc2
--- /dev/null
+++ b/mda-framework/src/test/java/net/shibboleth/metadata/validate/string/AsLiteralTailStringValidatorTest.java
@@ -0,0 +1,93 @@
+
+package net.shibboleth.metadata.validate.string;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nonnull;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.pipeline.StageProcessingException;
+import net.shibboleth.metadata.testing.MockItem;
+import net.shibboleth.metadata.validate.BaseValidator;
+import net.shibboleth.metadata.validate.Validator;
+import net.shibboleth.metadata.validate.Validator.Action;
+
+public class AsLiteralTailStringValidatorTest {
+
+ private static class CountingCapturingValidator extends BaseValidator implements Validator<String> {
+ public int count;
+ public String value;
+ private final @Nonnull Action action;
+
+ @Override
+ public @Nonnull Action validate(@Nonnull String e, @Nonnull Item<?> item, @Nonnull String stageId) throws StageProcessingException {
+ count++;
+ value = e;
+ return action;
+ }
+
+ /** Constructor. */
+ public CountingCapturingValidator(final @Nonnull Action a) {
+ action = a;
+ }
+ }
+
+ @Test
+ public void testAssumptions() throws Exception {
+ final Pattern pattern = Pattern.compile(".*?\\\\.(([a-zA-Z0-9-]+\\\\.)+[a-zA-Z0-9-]+)\\$");
+ final String value = "^([a-zA-Z0-9-]{1,63}\\.){0,2}vho\\.aaf\\.edu\\.au$";
+ final Matcher matcher = pattern.matcher(value);
+ Assert.assertTrue(matcher.matches());
+ }
+
+ @Test
+ public void testExample() throws Exception {
+ final CountingCapturingValidator ccv = new CountingCapturingValidator(Action.CONTINUE);
+ ccv.setId("ccv");
+ ccv.initialize();
+
+ final List<Validator<String>> nvs = new ArrayList<>();
+ nvs.add(ccv);
+
+ final AsLiteralTailStringValidator val = new AsLiteralTailStringValidator();
+ val.setId("val");
+ val.setValidators(nvs);
+ val.initialize();
+
+ final Item<String> item = new MockItem("content");
+ Assert.assertEquals(val.validate("^([a-zA-Z0-9-]{1,63}\\.){0,2}vho\\.aaf\\.edu\\.au$", item, "stage"), Action.CONTINUE);
+ Assert.assertEquals(ccv.count, 1);
+ Assert.assertEquals(ccv.value, "aaf.edu.au");
+ }
+
+ /*
+ * Example from the REFEDS MRPS template document.
+ *
+ * See https://github.com/REFEDS/MRPS/blob/master/MRPS-templatev1.1.pdf
+ */
+ @Test
+ public void testREFEDSExample() throws Exception {
+ final CountingCapturingValidator ccv = new CountingCapturingValidator(Action.CONTINUE);
+ ccv.setId("ccv");
+ ccv.initialize();
+
+ final List<Validator<String>> nvs = new ArrayList<>();
+ nvs.add(ccv);
+
+ final AsLiteralTailStringValidator val = new AsLiteralTailStringValidator();
+ val.setId("val");
+ val.setValidators(nvs);
+ val.initialize();
+
+ final Item<String> item = new MockItem("content");
+ Assert.assertEquals(val.validate("^(foo|bar)\\.example\\.com$", item, "stage"), Action.CONTINUE);
+ Assert.assertEquals(ccv.count, 1);
+ Assert.assertEquals(ccv.value, "example.com");
+ }
+}
diff --git a/mda-framework/src/test/resources/net/shibboleth/metadata/dom/saml/shibboleth/ScopeValidationStageLitmusTest-config.xml b/mda-framework/src/test/resources/net/shibboleth/metadata/dom/saml/shibboleth/ScopeValidationStageLitmusTest-config.xml
new file mode 100644
index 0000000..8ab948f
--- /dev/null
+++ b/mda-framework/src/test/resources/net/shibboleth/metadata/dom/saml/shibboleth/ScopeValidationStageLitmusTest-config.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:c="http://www.springframework.org/schema/c"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:util="http://www.springframework.org/schema/util"
+ 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/util http://www.springframework.org/schema/util/spring-util.xsd">
+
+ <import resource="classpath:net/shibboleth/metadata/beans.xml"/>
+
+ <bean class="net.shibboleth.shared.spring.config.IdentifiableBeanPostProcessor"/>
+
+ <bean id="litmusTest" parent="mda.ScopeValidationStage">
+ <property name="validators">
+ <list>
+ <bean parent="mda.RejectStringRegexValidator"
+ p:regex="" p:message="scope element must not be empty"/>
+ <bean parent="mda.RejectStringRegexValidator"
+ p:regex=".*\s.*" p:message="scope '%s' includes white space"/>
+ <bean parent="mda.AsDomainNameStringValidator"
+ p:message="scope is not a valid domain name: %s">
+ <property name="validators">
+ <list>
+ <!-- DNS name validators -->
+ <bean parent="mda.RejectDomainNamePublicSuffixValidator"
+ p:message="scope is a public suffix: '%s'"/>
+ <bean parent="mda.RejectDomainNameNotUnderPublicSuffixValidator"
+ p:message="scope is not under a public suffix: '%s'"/>
+ </list>
+ </property>
+ </bean>
+ </list>
+ </property>
+ <property name="regexpValidators">
+ <list>
+ <bean parent="mda.RejectStringRegexValidator"
+ p:regex="" p:message="scope element must not be empty"/>
+ <bean parent="mda.RejectStringRegexValidator"
+ p:regex=".*\s.*" p:message="scope '%s' includes white space"/>
+ <bean parent="mda.RejectStringRegexValidator"
+ p:regex="[^^].*" p:message="regex scope '%s' does not start with an anchor ('^')"/>
+ <bean parent="mda.RejectStringRegexValidator"
+ p:regex=".*[^$]" p:message="regex scope '%s' does not end with an anchor ('$')"/>
+ <bean parent="mda.AsLiteralTailStringValidator"
+ p:message="regular expression '%s' does not end with a literal tail">
+ <property name="validators">
+ <!-- validators to apply to the literal tail -->
+ <list>
+ <bean parent="mda.AsDomainNameStringValidator"
+ p:message="literal tail is not a valid domain name: %s">
+ <property name="validators">
+ <list>
+ <!-- DNS name validators for the literal tail -->
+ <bean parent="mda.RejectDomainNamePublicSuffixValidator"
+ p:message="literal tail is a public suffix: '%s'"/>
+ <bean parent="mda.RejectDomainNameNotUnderPublicSuffixValidator"
+ p:message="literal tail is not under a public suffix: '%s'"/>
+ </list>
+ </property>
+ </bean>
+ </list>
+ </property>
+ </bean>
+ </list>
+ </property>
+ </bean>
+
+</beans>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list