From f96a45d21ac49f7ea99e50168e788cafdaaf6544 Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Fri, 8 Jun 2018 13:51:05 -0400 Subject: [PATCH] SHIBUI-517[532] work in progress --- .../JPAMetadataResolverServiceImpl.groovy | 88 +++++++++++-------- .../FileBackedHttpMetadataResolver.java | 9 ++ ...HttpMetadataResolverRepositoryTests.groovy | 6 +- ...JPAMetadataResolverServiceImplTests.groovy | 53 +++++++++++ .../admin/ui/util/TestHelpers.groovy | 12 +++ .../admin/ui/util/TestObjectGenerator.groovy | 22 +++++ backend/src/test/resources/conf/532.xml | 15 ++++ 7 files changed, 165 insertions(+), 40 deletions(-) create mode 100644 backend/src/test/resources/conf/532.xml diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImpl.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImpl.groovy index f28480e73..fdf84927d 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImpl.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImpl.groovy @@ -1,28 +1,29 @@ package edu.internet2.tier.shibboleth.admin.ui.service; -import com.google.common.base.Predicate; -import edu.internet2.tier.shibboleth.admin.ui.domain.EntityAttributesFilter; +import com.google.common.base.Predicate +import edu.internet2.tier.shibboleth.admin.ui.domain.EntityAttributesFilter import edu.internet2.tier.shibboleth.admin.ui.domain.EntityAttributesFilterTarget -import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects; +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository +import groovy.util.logging.Slf4j import groovy.xml.DOMBuilder -import groovy.xml.MarkupBuilder; -import net.shibboleth.utilities.java.support.resolver.ResolverException; -import org.opensaml.saml.common.profile.logic.EntityIdPredicate; -import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver; -import org.opensaml.saml.metadata.resolver.MetadataResolver; -import org.opensaml.saml.metadata.resolver.RefreshableMetadataResolver; -import org.opensaml.saml.metadata.resolver.filter.MetadataFilter; -import org.opensaml.saml.metadata.resolver.filter.MetadataFilterChain; -import org.opensaml.saml.saml2.core.Attribute; -import org.opensaml.saml.saml2.metadata.EntityDescriptor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.w3c.dom.Document; - -public class JPAMetadataResolverServiceImpl implements MetadataResolverService { - private static final Logger logger = LoggerFactory.getLogger(JPAMetadataResolverServiceImpl.class); +import groovy.xml.MarkupBuilder +import net.shibboleth.utilities.java.support.resolver.ResolverException +import org.opensaml.saml.common.profile.logic.EntityIdPredicate +import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver +import org.opensaml.saml.metadata.resolver.MetadataResolver +import org.opensaml.saml.metadata.resolver.RefreshableMetadataResolver +import org.opensaml.saml.metadata.resolver.filter.MetadataFilter +import org.opensaml.saml.metadata.resolver.filter.MetadataFilterChain +import org.opensaml.saml.saml2.core.Attribute +import org.opensaml.saml.saml2.metadata.EntityDescriptor + +import org.springframework.beans.factory.annotation.Autowired +import org.w3c.dom.Document + +@Slf4j +class JPAMetadataResolverServiceImpl implements MetadataResolverService { @Autowired private MetadataResolver metadataResolver @@ -35,52 +36,52 @@ public class JPAMetadataResolverServiceImpl implements MetadataResolverService { // TODO: enhance @Override - public void reloadFilters(String metadataResolverName) { - ChainingMetadataResolver chainingMetadataResolver = (ChainingMetadataResolver)metadataResolver; - - // MetadataResolver targetMetadataResolver = chainingMetadataResolver.getResolvers().stream().filter(r -> r.getId().equals(metadataResolverName)).findFirst().get(); + void reloadFilters(String metadataResolverName) { + ChainingMetadataResolver chainingMetadataResolver = (ChainingMetadataResolver)metadataResolver MetadataResolver targetMetadataResolver = chainingMetadataResolver.getResolvers().find { it.id == metadataResolverName } - edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver jpaMetadataResolver = metadataResolverRepository.findByName(metadataResolverName); + edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver jpaMetadataResolver = metadataResolverRepository.findByName(metadataResolverName) if (targetMetadataResolver && targetMetadataResolver.getMetadataFilter() instanceof MetadataFilterChain) { - MetadataFilterChain metadataFilterChain = (MetadataFilterChain)targetMetadataResolver.getMetadataFilter(); + MetadataFilterChain metadataFilterChain = (MetadataFilterChain)targetMetadataResolver.getMetadataFilter() - List metadataFilters = new ArrayList<>(); + List metadataFilters = new ArrayList<>() for (edu.internet2.tier.shibboleth.admin.ui.domain.MetadataFilter metadataFilter : jpaMetadataResolver.getMetadataFilters()) { if (metadataFilter instanceof EntityAttributesFilter) { - EntityAttributesFilter entityAttributesFilter = (EntityAttributesFilter) metadataFilter; + EntityAttributesFilter entityAttributesFilter = (EntityAttributesFilter) metadataFilter - org.opensaml.saml.metadata.resolver.filter.impl.EntityAttributesFilter target = new org.opensaml.saml.metadata.resolver.filter.impl.EntityAttributesFilter(); - Map, Collection> rules = new HashMap<>(); + org.opensaml.saml.metadata.resolver.filter.impl.EntityAttributesFilter target = new org.opensaml.saml.metadata.resolver.filter.impl.EntityAttributesFilter() + Map, Collection> rules = new HashMap<>() if (entityAttributesFilter.getEntityAttributesFilterTarget().getEntityAttributesFilterTargetType() == EntityAttributesFilterTarget.EntityAttributesFilterTargetType.ENTITY) { rules.put( new EntityIdPredicate(entityAttributesFilter.getEntityAttributesFilterTarget().getValue()), (List)(List)entityAttributesFilter.getAttributes() - ); + ) } - target.setRules(rules); - metadataFilters.add(target); + target.setRules(rules) + metadataFilters.add(target) } } - metadataFilterChain.setFilters(metadataFilters); + metadataFilterChain.setFilters(metadataFilters) } if (metadataResolver instanceof RefreshableMetadataResolver) { try { - ((RefreshableMetadataResolver)metadataResolver).refresh(); + ((RefreshableMetadataResolver)metadataResolver).refresh() } catch (ResolverException e) { - logger.warn("error refreshing metadataResolver " + metadataResolverName, e); + log.warn("error refreshing metadataResolver " + metadataResolverName, e) } } } // TODO: enhance @Override - public Document generateConfiguration() { + Document generateConfiguration() { // TODO: this can probably be a better writer new StringWriter().withCloseable { writer -> def xml = new MarkupBuilder(writer) + xml.omitEmptyAttributes = true + xml.omitNullAttributes = true xml.MetadataProvider(id: 'ShibbolethMetadata', xmlns: 'urn:mace:shibboleth:2.0:metadata', @@ -135,4 +136,17 @@ public class JPAMetadataResolverServiceImpl implements MetadataResolverService { return DOMBuilder.newInstance().parseText(writer.toString()) } } + + void constructXmlNodeFor(FileBackedHttpMetadataResolver resolver, def markupBuilderDelegate) { + def attributes = [id: "${resolver.name}", + 'xsi:type': 'FileBackedHTTPMetadataProvider', + backingFile: resolver.backingFile, + metadataURL: resolver.metadataURL, + minRefreshDelay: resolver.reloadableMetadataResolverAttributes?.minRefreshDelay, + maxRefreshDelay: resolver.reloadableMetadataResolverAttributes?.maxRefreshDelay, + refreshDelayFactor: resolver.reloadableMetadataResolverAttributes?.refreshDelayFactor] + + markupBuilderDelegate.MetadataProvider(attributes) + } + } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FileBackedHttpMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FileBackedHttpMetadataResolver.java index c9850ecc6..4678dca1d 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FileBackedHttpMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FileBackedHttpMetadataResolver.java @@ -17,6 +17,15 @@ @ToString public class FileBackedHttpMetadataResolver extends MetadataResolver { + private String metadataURL; + + private String backingFile; + + private Boolean initializeFromBackupFile; + + private String backupFileInitNextRefreshDelay; + + @Embedded private ReloadableMetadataResolverAttributes reloadableMetadataResolverAttributes; diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/FileBackedHttpMetadataResolverRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/FileBackedHttpMetadataResolverRepositoryTests.groovy index d0beb24d2..5074750ea 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/FileBackedHttpMetadataResolverRepositoryTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/FileBackedHttpMetadataResolverRepositoryTests.groovy @@ -30,7 +30,7 @@ import static edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.HttpMetada class FileBackedHttpMetadataResolverRepositoryTests extends Specification { @Autowired - FileBackedHttpMetadataResolverRepository repositoryUnderTest + MetadataResolverRepository repositoryUnderTest @Autowired EntityManager entityManager @@ -119,9 +119,9 @@ class FileBackedHttpMetadataResolverRepositoryTests extends Specification { entityManager.flush() then: - def item1 = repositoryUnderTest.findByResourceId(persistedResolver.resourceId) + def item1 = repositoryUnderTest.findByName(persistedResolver.name) entityManager.clear() - def item2 = repositoryUnderTest.findByResourceId(persistedResolver.resourceId) + def item2 = repositoryUnderTest.findByName(persistedResolver.name) item1.hashCode() == item2.hashCode() } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy index afc93df62..a20ce8eb9 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy @@ -6,6 +6,11 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.EntityAttributesFilter import edu.internet2.tier.shibboleth.admin.ui.domain.EntityAttributesFilterTarget import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository + +import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator +import edu.internet2.tier.shibboleth.admin.util.AttributeUtility +import groovy.xml.DOMBuilder +import groovy.xml.MarkupBuilder import net.shibboleth.ext.spring.resource.ResourceHelper import net.shibboleth.utilities.java.support.resolver.CriteriaSet import org.joda.time.DateTime @@ -28,6 +33,8 @@ import org.xmlunit.builder.DiffBuilder import org.xmlunit.builder.Input import spock.lang.Specification +import static edu.internet2.tier.shibboleth.admin.ui.util.TestHelpers.generatedXmlIsTheSameAsExpectedXml + @SpringBootTest @DataJpaTest @ContextConfiguration(classes=[CoreShibUiConfiguration, SearchConfiguration]) @@ -50,6 +57,30 @@ class JPAMetadataResolverServiceImplTests extends Specification { @Autowired OpenSamlObjects openSamlObjects + @Autowired + AttributeUtility attributeUtility + + TestObjectGenerator testObjectGenerator + + DOMBuilder domBuilder + + StringWriter writer + + MarkupBuilder markupBuilder + + def setup() { + testObjectGenerator = new TestObjectGenerator(attributeUtility) + domBuilder = DOMBuilder.newInstance() + writer = new StringWriter() + markupBuilder = new MarkupBuilder(writer) + markupBuilder.omitNullAttributes = true + markupBuilder.omitEmptyAttributes = true + } + + def cleanup() { + writer.close() + } + def 'test adding a filter'() { given: def expectedXML = ''' @@ -98,6 +129,28 @@ class JPAMetadataResolverServiceImplTests extends Specification { !diff.hasDifferences() } + def 'test generating FileBackedHttMetadataResolver xml snippet'() { + given: + def resolver = testObjectGenerator.fileBackedHttpMetadataResolver() + + when: + genXmlSnippet(markupBuilder) { JPAMetadataResolverServiceImpl.cast(metadataResolverService).constructXmlNodeFor(resolver, it) } + + then: + assert generatedXmlIsTheSameAsExpectedXml('/conf/532.xml', domBuilder.parseText(writer.toString())) + } + + static genXmlSnippet(MarkupBuilder xml, Closure xmlNodeGenerator) { + xml.MetadataProvider('id': 'ShibbolethMetadata', + 'xmlns': 'urn:mace:shibboleth:2.0:metadata', + 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:type': 'ChainingMetadataProvider', + 'xsi:schemaLocation': 'urn:mace:shibboleth:2.0:metadata http://shibboleth.net/schema/idp/shibboleth-metadata.xsd urn:mace:shibboleth:2.0:resource http://shibboleth.net/schema/idp/shibboleth-resource.xsd urn:mace:shibboleth:2.0:security http://shibboleth.net/schema/idp/shibboleth-security.xsd urn:oasis:names:tc:SAML:2.0:metadata http://docs.oasis-open.org/security/saml/v2.0/saml-schema-metadata-2.0.xsd urn:oasis:names:tc:SAML:2.0:assertion http://docs.oasis-open.org/security/saml/v2.0/saml-schema-assertion-2.0.xsd' + ) { + xmlNodeGenerator(delegate) + } + } + @TestConfiguration static class Config { @Autowired diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestHelpers.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestHelpers.groovy index 1a6a085e2..3d8b834e8 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestHelpers.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestHelpers.groovy @@ -2,6 +2,9 @@ package edu.internet2.tier.shibboleth.admin.ui.util import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.RelyingPartyOverridesRepresentation import org.apache.commons.lang.StringUtils +import org.w3c.dom.Document +import org.xmlunit.builder.DiffBuilder +import org.xmlunit.builder.Input /** * @author Bill Smith (wsmith@unicon.net) @@ -22,4 +25,13 @@ class TestHelpers { return count } + + static generatedXmlIsTheSameAsExpectedXml(String expectedXmlResource, Document generatedXml) { + def diffs = DiffBuilder.compare(Input.fromStream(this.getResourceAsStream(expectedXmlResource))).withTest(Input.fromDocument + (generatedXml)) + .ignoreComments().ignoreWhitespace().build().differences + + !DiffBuilder.compare(Input.fromStream(this.getResourceAsStream(expectedXmlResource))).withTest(Input.fromDocument(generatedXml)) + .ignoreComments().ignoreWhitespace().build().hasDifferences() + } } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy index 302bdac95..b8adc23d0 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy @@ -12,10 +12,16 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.OrganizationURL import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.FilterRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.FilterTargetRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.RelyingPartyOverridesRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.HttpMetadataResolverAttributes +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ReloadableMetadataResolverAttributes import edu.internet2.tier.shibboleth.admin.util.AttributeUtility import edu.internet2.tier.shibboleth.admin.util.MDDCConstants import org.opensaml.saml.saml2.metadata.Organization +import static edu.internet2.tier.shibboleth.admin.ui.domain.EntityAttributesFilterTarget.EntityAttributesFilterTargetType.ENTITY +import static edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.HttpMetadataResolverAttributes.HttpCachingType.memory + /** * @author Bill Smith (wsmith@unicon.net) */ @@ -190,6 +196,22 @@ class TestObjectGenerator { return contactPerson } + FileBackedHttpMetadataResolver fileBackedHttpMetadataResolver() { + new FileBackedHttpMetadataResolver().with { + it.name = 'HTTPMetadata' + it.backingFile = '%{idp.home}/metadata/incommonmd.xml' + it.metadataURL = 'http://md.incommon.org/InCommon/InCommon-metadata.xml' + + it.reloadableMetadataResolverAttributes = new ReloadableMetadataResolverAttributes().with { + it.minRefreshDelay = 'PT5M' + it.maxRefreshDelay = 'PT1H' + it.refreshDelayFactor = 0.75 + it + } + it + } + } + /** * This method takes a type and a size and builds a List of that size containing objects of that type. This is * intended to be used with things that extend LocalizedName such as {@link OrganizationName}, {@link OrganizationDisplayName}, diff --git a/backend/src/test/resources/conf/532.xml b/backend/src/test/resources/conf/532.xml new file mode 100644 index 000000000..cdfd93301 --- /dev/null +++ b/backend/src/test/resources/conf/532.xml @@ -0,0 +1,15 @@ + + + + +