Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
7 changed files
with
421 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
97 changes: 97 additions & 0 deletions
97
src/main/java/uk/org/iay/incommon/mda/dom/saml/shib/ScopeValidationStage.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package uk.org.iay.incommon.mda.dom.saml.shib; | ||
|
||
import java.util.List; | ||
|
||
import javax.annotation.Nonnull; | ||
|
||
import org.w3c.dom.Element; | ||
|
||
import net.shibboleth.metadata.dom.AbstractDOMValidationStage; | ||
import net.shibboleth.metadata.pipeline.StageProcessingException; | ||
import net.shibboleth.metadata.validate.Validator; | ||
import net.shibboleth.utilities.java.support.component.ComponentInitializationException; | ||
import net.shibboleth.utilities.java.support.xml.AttributeSupport; | ||
import net.shibboleth.utilities.java.support.xml.ElementSupport; | ||
import uk.org.iay.incommon.mda.validate.ValidatorSequence; | ||
|
||
/** | ||
* Stage to apply a collection of validators to Shibboleth <code>shibmd:Scope</code> | ||
* values. | ||
* | ||
* A separate collection of validators is used for the case of the <code>regexp</code> | ||
* attribute being <code>true</code> and <code>false</code>. | ||
*/ | ||
public class ScopeValidationStage extends AbstractDOMValidationStage<String> { | ||
|
||
/** The sequence of validators to apply to <code>regexp</code> scopes. */ | ||
@Nonnull | ||
private 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 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 List<Validator<String>> getRegexpValidators() { | ||
return regexpValidators.getValidators(); | ||
} | ||
|
||
@Override | ||
protected boolean applicable(@Nonnull final Element element) { | ||
return ElementSupport.isElementNamed(element, ShibbolethMetadataSupport.SCOPE_NAME); | ||
} | ||
|
||
@Override | ||
protected void visit(@Nonnull final Element element, @Nonnull final TraversalContext context) | ||
throws StageProcessingException { | ||
final String text = element.getTextContent(); | ||
final Boolean isRegexp = AttributeSupport.getAttributeValueAsBoolean( | ||
AttributeSupport.getAttribute(element, ShibbolethMetadataSupport.REGEXP_ATTRIB_NAME)); | ||
if (isRegexp == null || !isRegexp.booleanValue()) { | ||
// non-regexp Scope, apply normal validators | ||
applyValidators(text, context); | ||
} else { | ||
// regexp Scope, apply secondary validators | ||
regexpValidators.validate(text, context.getItem(), getId()); | ||
} | ||
} | ||
|
||
@Override | ||
protected void doDestroy() { | ||
regexpValidators.destroy(); | ||
regexpValidators = null; | ||
super.doDestroy(); | ||
} | ||
|
||
@Override | ||
protected void doInitialize() throws ComponentInitializationException { | ||
super.doInitialize(); | ||
regexpValidators.setId(getId()); | ||
regexpValidators.initialize(); | ||
} | ||
|
||
} |
40 changes: 40 additions & 0 deletions
40
src/main/java/uk/org/iay/incommon/mda/dom/saml/shib/ShibbolethMetadataSupport.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package uk.org.iay.incommon.mda.dom.saml.shib; | ||
|
||
import javax.annotation.concurrent.ThreadSafe; | ||
import javax.xml.namespace.QName; | ||
|
||
/** Helper class for dealing with Shibboleth metadata. */ | ||
@ThreadSafe | ||
public final class ShibbolethMetadataSupport { | ||
|
||
/** Shibboleth metadata namespace URI. */ | ||
public static final String SHIBMD_NS = "urn:mace:shibboleth:metadata:1.0"; | ||
|
||
/** Default Shibboleth metadata namespace prefix. */ | ||
public static final String SHIBMD_PREFIX = "shibmd"; | ||
|
||
/** Scope element name. */ | ||
public static final QName SCOPE_NAME = new QName(SHIBMD_NS, "Scope", SHIBMD_PREFIX); | ||
|
||
/** regexp attribute name. */ | ||
public static final QName REGEXP_ATTRIB_NAME = new QName("regexp"); | ||
|
||
/** Constructor. */ | ||
private ShibbolethMetadataSupport() { | ||
|
||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
src/main/java/uk/org/iay/incommon/mda/dom/saml/shib/package-info.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
/** | ||
* Aggregator beans dealing with Shibboleth SAML metadata. | ||
*/ | ||
package uk.org.iay.incommon.mda.dom.saml.shib; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
172 changes: 172 additions & 0 deletions
172
src/test/java/uk/org/iay/incommon/mda/dom/saml/shib/ScopeValidationStageLitmusTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
|
||
package uk.org.iay.incommon.mda.dom.saml.shib; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.List; | ||
|
||
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.utilities.java.support.xml.AttributeSupport; | ||
import net.shibboleth.utilities.java.support.xml.ElementSupport; | ||
|
||
/** | ||
* A litmus test for {@link ScopeValidationStage} involving a set of valid and invalid | ||
* scope values, both for regular expression and plain cases. | ||
* | ||
* The configuration for the stage is taken from a Spring XML configuration file. | ||
*/ | ||
@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 { | ||
@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 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 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 Collection<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); | ||
Assert.assertEquals(errors.size(), 0, "expected no errors for '" + value + "'[" + isRegex + "]"); | ||
} | ||
|
||
/** 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"); | ||
bad("", "empty"); | ||
bad(" "); | ||
bad(" "); | ||
bad(" example.org", "white space"); | ||
bad("example.org ", "white space"); | ||
bad("EXAMPLE.ORG", "upper-case"); | ||
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"); | ||
|
||
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"); | ||
// bad literal tail: upper case | ||
badRegexp("^([a-zA-Z0-9-]{1,63}\\.){0,2}vho\\.aaf\\.edu\\.AU$", "includes upper-case characters"); | ||
|
||
// 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$"); | ||
|
||
// eduGAIN examples | ||
|
||
} | ||
} |
75 changes: 75 additions & 0 deletions
75
...resources/uk/org/iay/incommon/mda/dom/saml/shib/ScopeValidationStageLitmusTest-config.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
<?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:uk/org/iay/incommon/mda/beans.xml"/> | ||
|
||
<bean class="net.shibboleth.ext.spring.config.IdentifiableBeanPostProcessor"/> | ||
|
||
<bean id="litmusTest" parent="inc.stage_parent" | ||
class="uk.org.iay.incommon.mda.dom.saml.shib.ScopeValidationStage"> | ||
<property name="validators"> | ||
<list> | ||
<bean parent="inc.RejectStringRegexValidator" | ||
p:regex="" p:message="scope element must not be empty"/> | ||
<bean parent="inc.RejectStringRegexValidator" | ||
p:regex=".*\s.*" p:message="scope '%s' includes white space"/> | ||
<bean parent="inc.RejectStringRegexValidator" | ||
p:regex=".*\p{Upper}.*" p:message="scope '%s' includes upper-case characters"/> | ||
<bean parent="inc.AsDomainNameStringValidator" | ||
p:message="scope is not a valid domain name: %s"> | ||
<property name="validators"> | ||
<list> | ||
<!-- DNS name validators --> | ||
<bean parent="inc.RejectDomainNamePublicSuffixValidator" | ||
p:message="scope is a public suffix: '%s'"/> | ||
<bean parent="inc.RejectDomainNameNotUnderPublicSuffixValidator" | ||
p:message="scope is not under a public suffix: '%s'"/> | ||
</list> | ||
</property> | ||
</bean> | ||
</list> | ||
</property> | ||
<property name="regexpValidators"> | ||
<list> | ||
<bean parent="inc.RejectStringRegexValidator" | ||
p:regex="" p:message="scope element must not be empty"/> | ||
<bean parent="inc.RejectStringRegexValidator" | ||
p:regex=".*\s.*" p:message="scope '%s' includes white space"/> | ||
<bean parent="inc.RejectStringRegexValidator" | ||
p:regex="[^^].*" p:message="regex scope '%s' does not start with an anchor ('^')"/> | ||
<bean parent="inc.RejectStringRegexValidator" | ||
p:regex=".*[^$]" p:message="regex scope '%s' does not end with an anchor ('$')"/> | ||
<bean parent="inc.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="inc.RejectStringRegexValidator" | ||
p:regex=".*\p{Upper}.*" p:message="literal tail '%s' includes upper-case characters"/> | ||
<bean parent="inc.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="inc.RejectDomainNamePublicSuffixValidator" | ||
p:message="literal tail is a public suffix: '%s'"/> | ||
<bean parent="inc.RejectDomainNameNotUnderPublicSuffixValidator" | ||
p:message="literal tail is not under a public suffix: '%s'"/> | ||
</list> | ||
</property> | ||
</bean> | ||
</list> | ||
</property> | ||
</bean> | ||
</list> | ||
</property> | ||
</bean> | ||
|
||
</beans> |