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 09f54ab75..64f33a01d 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,19 +1,11 @@ -package edu.internet2.tier.shibboleth.admin.ui.service; - -import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver -import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver +package edu.internet2.tier.shibboleth.admin.ui.service import com.google.common.base.Predicate -import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter -import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget -import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityRoleWhiteListFilter - -import edu.internet2.tier.shibboleth.admin.ui.domain.filters.RequiredValidUntilFilter - +import edu.internet2.tier.shibboleth.admin.ui.domain.filters.* +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver - +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver 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 @@ -27,9 +19,7 @@ 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 @@ -117,6 +107,52 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService { } } + void constructXmlNodeForFilter(SignatureValidationFilter filter, def markupBuilderDelegate) { + markupBuilderDelegate.MetadataFilter(id: filter.name, + 'xsi:type': 'SignatureValidation', + 'xmlns:md': 'urn:oasis:names:tc:SAML:2.0:metadata', + 'requireSignedRoot': !filter.requireSignedRoot ?: null, + 'certificateFile': filter.certificateFile, + 'defaultCriteriaRef': filter.defaultCriteriaRef, + 'signaturePrevalidatorRef': filter.signaturePrevalidatorRef, + 'dynamicTrustedNamesStrategyRef': filter.dynamicTrustedNamesStrategyRef, + 'trustEngineRef': filter.trustEngineRef, + 'publicKey': filter.publicKey) + } + + void constructXmlNodeForFilter(EntityAttributesFilter filter, def markupBuilderDelegate) { + markupBuilderDelegate.MetadataFilter('xsi:type': 'EntityAttributes') { + // TODO: enhance. currently this does weird things with namespaces + filter.attributes.each { attribute -> + mkp.yieldUnescaped(openSamlObjects.marshalToXmlString(attribute, false)) + } + if (filter.entityAttributesFilterTarget.entityAttributesFilterTargetType == EntityAttributesFilterTarget + .EntityAttributesFilterTargetType.ENTITY) { + filter.entityAttributesFilterTarget.value.each { + Entity(it) + } + } + } + } + + void constructXmlNodeForFilter(EntityRoleWhiteListFilter filter, def markupBuilderDelegate) { + markupBuilderDelegate.MetadataFilter( + 'xsi:type': 'EntityRoleWhiteList', + 'xmlns:md': 'urn:oasis:names:tc:SAML:2.0:metadata' + ) { + filter.retainedRoles.each { + markupBuilderDelegate.RetainedRole(it) + } + } + } + + void constructXmlNodeForFilter(RequiredValidUntilFilter filter, def markupBuilderDelegate) { + markupBuilderDelegate.MetadataFilter( + 'xsi:type': 'RequiredValidUntil', + maxValidityInterval: filter.maxValidityInterval + ) + } + void constructXmlNodeForResolver(DynamicHttpMetadataResolver resolver, def markupBuilderDelegate, Closure childNodes) { markupBuilderDelegate.MetadataProvider(id: resolver.name, 'xsi:type': 'DynamicHttpMetadataProvider', @@ -164,39 +200,6 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService { } } - void constructXmlNodeForFilter(EntityAttributesFilter filter, def markupBuilderDelegate) { - markupBuilderDelegate.MetadataFilter('xsi:type': 'EntityAttributes') { - // TODO: enhance. currently this does weird things with namespaces - filter.attributes.each { attribute -> - mkp.yieldUnescaped(openSamlObjects.marshalToXmlString(attribute, false)) - } - if (filter.entityAttributesFilterTarget.entityAttributesFilterTargetType == EntityAttributesFilterTarget - .EntityAttributesFilterTargetType.ENTITY) { - filter.entityAttributesFilterTarget.value.each { - Entity(it) - } - } - } - } - - void constructXmlNodeForFilter(EntityRoleWhiteListFilter filter, def markupBuilderDelegate) { - markupBuilderDelegate.MetadataFilter( - 'xsi:type': 'EntityRoleWhiteList', - 'xmlns:md': 'urn:oasis:names:tc:SAML:2.0:metadata' - ) { - filter.retainedRoles.each { - markupBuilderDelegate.RetainedRole(it) - } - } - } - - void constructXmlNodeForFilter(RequiredValidUntilFilter filter, def markupBuilderDelegate) { - markupBuilderDelegate.MetadataFilter( - 'xsi:type': 'RequiredValidUntil', - maxValidityInterval: filter.maxValidityInterval - ) - } - void constructXmlNodeForResolver(FileBackedHttpMetadataResolver resolver, def markupBuilderDelegate, Closure childNodes) { markupBuilderDelegate.MetadataProvider(id: resolver.name, 'xsi:type': 'FileBackedHTTPMetadataProvider', diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java index 75eea0984..7d873afa6 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java @@ -4,10 +4,9 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityRoleWhiteListFilter; import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter; import edu.internet2.tier.shibboleth.admin.ui.domain.filters.RequiredValidUntilFilter; -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.FilterRepresentation; +import edu.internet2.tier.shibboleth.admin.ui.domain.filters.SignatureValidationFilter; import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository; -import edu.internet2.tier.shibboleth.admin.ui.service.FilterService; import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -135,6 +134,8 @@ public ResponseEntity update(@PathVariable String metadataResolverId, MetadataFilter persistedFilter = convertIntoTransientRepresentationIfNecessary(persistedMr.getMetadataFilters().stream(), updatedFilter.getResourceId()); + persistedFilter.setVersion(persistedFilter.hashCode()); + return ResponseEntity.ok().body(persistedFilter); } @@ -155,6 +156,9 @@ private MetadataFilter convertIntoTransientRepresentationIfNecessary(Stream { + + SignatureValidationFilter findByName(String name); + + SignatureValidationFilter findByResourceId(String resourceId); + + boolean deleteByResourceId(String resourceId); +} diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy index 3cd5f00c0..d160411db 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy @@ -21,10 +21,10 @@ import org.springframework.boot.autoconfigure.domain.EntityScan import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.data.jpa.repository.config.EnableJpaRepositories import org.springframework.test.context.ContextConfiguration -import org.springframework.test.web.servlet.result.MockMvcResultHandlers import org.springframework.test.web.servlet.setup.MockMvcBuilders import org.w3c.dom.Document import spock.lang.Specification +import spock.lang.Unroll import static org.hamcrest.CoreMatchers.containsString import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8 @@ -94,7 +94,6 @@ class MetadataFiltersControllerTests extends Specification { when: def result = mockMvc.perform(get("$BASE_URI/foo/Filters")) - println(mapper.writeValueAsString(expectedContent)) then: result.andExpect(expectedHttpResponseStatus) @@ -122,9 +121,10 @@ class MetadataFiltersControllerTests extends Specification { .andExpect(content().json(mapper.writeValueAsString(expectedFilter))) } - def "FilterController.create creates the desired filter"() { + @Unroll + def "FilterController.create creates the desired filter (filterType: #filterType)"(String filterType) { given: - def randomFilter = testObjectGenerator.entityAttributesFilter() + def randomFilter = testObjectGenerator.buildRandomFilterOfType(filterType) def metadataResolver = new MetadataResolver() metadataResolver.setResourceId(randomGenerator.randomId()) metadataResolver.setMetadataFilters(testObjectGenerator.buildAllTypesOfFilterList()) @@ -142,9 +142,6 @@ class MetadataFiltersControllerTests extends Specification { def expectedResponseHeaderValue = "$BASE_URI/$expectedMetadataResolverUUID/Filters/$expectedFilterUUID" def expectedJsonBody = mapper.writeValueAsString(randomFilter) def postedJsonBody = expectedJsonBody - ~/"id":.*?,/ // remove the "id:," - println postedJsonBody - def filter = mapper.readValue(postedJsonBody, MetadataFilter) - println filter when: def result = mockMvc.perform( @@ -156,13 +153,22 @@ class MetadataFiltersControllerTests extends Specification { result.andExpect(status().isCreated()) .andExpect(content().json(expectedJsonBody, true)) .andExpect(header().string(expectedResponseHeader, containsString(expectedResponseHeaderValue))) + + where: + filterType | _ + 'entityAttributes' | _ + 'entityRoleWhiteList' | _ + 'signatureValidation' | _ + 'requiredValidUntil' | _ } - def "FilterController.update updates the target EntityAttributes filter as desired"() { + @Unroll + def "FilterController.update updates the target #filterType filter as desired"(String filterType) { given: - def originalFilter = testObjectGenerator.entityAttributesFilter() + def originalFilter = testObjectGenerator.buildRandomFilterOfType(filterType) def updatedFilter = testObjectGenerator.copyOf(originalFilter) updatedFilter.name = 'Updated Filter' + updatedFilter.version = originalFilter.hashCode() def postedJsonBody = mapper.writeValueAsString(updatedFilter) def originalMetadataResolver = new MetadataResolver() @@ -188,10 +194,19 @@ class MetadataFiltersControllerTests extends Specification { then: def expectedJson = new JsonSlurper().parseText(postedJsonBody) - updatedFilter.fromTransientRepresentation() + if (filterType == 'entityAttributes') { + EntityAttributesFilter.cast(updatedFilter).fromTransientRepresentation() + } expectedJson << [version: updatedFilter.hashCode()] result.andExpect(status().isOk()) .andExpect(content().json(JsonOutput.toJson(expectedJson), true)) + + where: + filterType | _ + 'entityAttributes' | _ + 'entityRoleWhiteList' | _ + 'signatureValidation' | _ + 'requiredValidUntil' | _ } def "FilterController.update filter 409's if the version numbers don't match"() { 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 b2dd89727..fe5be3277 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 @@ -1,18 +1,13 @@ package edu.internet2.tier.shibboleth.admin.ui.util import edu.internet2.tier.shibboleth.admin.ui.domain.* -import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter -import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget -import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityRoleWhiteListFilter -import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter -import edu.internet2.tier.shibboleth.admin.ui.domain.filters.RequiredValidUntilFilter +import edu.internet2.tier.shibboleth.admin.ui.domain.filters.* 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.* import edu.internet2.tier.shibboleth.admin.util.AttributeUtility import edu.internet2.tier.shibboleth.admin.util.MDDCConstants - import org.opensaml.saml.saml2.metadata.Organization import java.util.function.Supplier @@ -121,11 +116,46 @@ class TestObjectGenerator { (1..generator.randomInt(4, 10)).each { filterList.add(buildFilter { entityAttributesFilter() }) filterList.add(buildFilter { entityRoleWhitelistFilter() }) + filterList.add(buildFilter { signatureValidationFilter() }) filterList.add(buildFilter { requiredValidUntilFilter() }) } return filterList } + MetadataFilter buildRandomFilterOfType(String filterType) { + def randomFilter + switch (filterType) { + case 'entityAttributes': + randomFilter = entityAttributesFilter() + break + case 'entityRoleWhiteList': + randomFilter = entityRoleWhitelistFilter() + break + case 'signatureValidation': + randomFilter = signatureValidationFilter() + break + case 'requiredValidUntil': + randomFilter = requiredValidUntilFilter() + break + default: + throw new RuntimeException("Did you forget to create a TestObjectGenerator.copyOf method for filtertype: ${filterType} ?"); + } + randomFilter + } + + SignatureValidationFilter signatureValidationFilter() { + new SignatureValidationFilter().with { + it.name = 'SignatureValidation' + it.requireSignedRoot = generator.randomBoolean() + it.certificateFile = generator.randomString(50) + it.defaultCriteriaRef = generator.randomString(10) + it.signaturePrevalidatorRef = generator.randomString(10) + it.dynamicTrustedNamesStrategyRef = generator.randomString(10) + it.trustEngineRef = generator.randomString(10) + it.publicKey = generator.randomString(50) + it + } + } EntityRoleWhiteListFilter entityRoleWhitelistFilter() { new EntityRoleWhiteListFilter().with { it.name = 'EntityRoleWhiteList' @@ -152,6 +182,41 @@ class TestObjectGenerator { } } + RequiredValidUntilFilter copyOf(RequiredValidUntilFilter requiredValidUntilFilter) { + new RequiredValidUntilFilter().with { + it.name = requiredValidUntilFilter.name + it.resourceId = requiredValidUntilFilter.resourceId + it.maxValidityInterval = requiredValidUntilFilter.maxValidityInterval + it + } + } + + SignatureValidationFilter copyOf(SignatureValidationFilter signatureValidationFilter) { + new SignatureValidationFilter().with { + it.name = signatureValidationFilter.name + it.resourceId = signatureValidationFilter.resourceId + it.trustEngineRef = signatureValidationFilter.trustEngineRef + it.signaturePrevalidatorRef = signatureValidationFilter.signaturePrevalidatorRef + it.publicKey = signatureValidationFilter.publicKey + it.dynamicTrustedNamesStrategyRef = signatureValidationFilter.dynamicTrustedNamesStrategyRef + it.requireSignedRoot = signatureValidationFilter.requireSignedRoot + it.certificateFile = signatureValidationFilter.certificateFile + it.defaultCriteriaRef = signatureValidationFilter.defaultCriteriaRef + it + } + } + + EntityRoleWhiteListFilter copyOf(EntityRoleWhiteListFilter entityRoleWhiteListFilter) { + new EntityRoleWhiteListFilter().with { + it.name = entityRoleWhiteListFilter.name + it.resourceId = entityRoleWhiteListFilter.resourceId + it.removeEmptyEntitiesDescriptors = entityRoleWhiteListFilter.removeEmptyEntitiesDescriptors + it.removeRolelessEntityDescriptors = entityRoleWhiteListFilter.removeRolelessEntityDescriptors + it.retainedRoles = entityRoleWhiteListFilter.retainedRoles + it + } + } + EntityAttributesFilter copyOf(EntityAttributesFilter entityAttributesFilter) { new EntityAttributesFilter().with { it.name = entityAttributesFilter.name