diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerVersionEndpointsIntegrationTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerVersionEndpointsIntegrationTests.groovy index 8e23362e7..d1bf151d3 100644 --- a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerVersionEndpointsIntegrationTests.groovy +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerVersionEndpointsIntegrationTests.groovy @@ -1,5 +1,9 @@ package edu.internet2.tier.shibboleth.admin.ui.controller +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration 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 @@ -11,10 +15,14 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FilesystemMetadat import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver 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.MetadataResolverVersionService +import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator +import edu.internet2.tier.shibboleth.admin.util.AttributeUtility import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.web.client.TestRestTemplate import org.springframework.test.context.ActiveProfiles +import org.springframework.transaction.PlatformTransactionManager import spock.lang.Specification import static edu.internet2.tier.shibboleth.admin.ui.domain.filters.NameIdFormatFilterTarget.NameIdFormatFilterTargetType.ENTITY @@ -32,12 +40,34 @@ class MetadataResolverControllerVersionEndpointsIntegrationTests extends Specifi @Autowired MetadataResolverRepository repository + @Autowired + AttributeUtility attributeUtility + + @Autowired + CustomPropertiesConfiguration customPropertiesConfiguration + + ObjectMapper mapper + TestObjectGenerator generator + + @Autowired + PlatformTransactionManager txMgr + + @Autowired + MetadataResolverVersionService metadataResolverVersionService + static BASE_URI = '/api/MetadataResolvers' static ALL_VERSIONS_URI = "$BASE_URI/%s/Versions" static SPECIFIC_VERSION_URI = "$BASE_URI/%s/Versions/%s" + def setup() { + generator = new TestObjectGenerator(attributeUtility, customPropertiesConfiguration) + mapper = new ObjectMapper() + mapper.enable(SerializationFeature.INDENT_OUTPUT) + mapper.registerModule(new JavaTimeModule()) + } + def "GET /api/MetadataResolvers/{resourceId}/Versions with non-existent resolver"() { when: def result = getAllMetadataResolverVersions('non-existent-resolver-id', String) @@ -111,6 +141,7 @@ class MetadataResolverControllerVersionEndpointsIntegrationTests extends Specifi } def "SHIBUI-1386"() { + given: MetadataResolver mr = new FileBackedHttpMetadataResolver(name: 'testme') mr = repository.save(mr) @@ -142,6 +173,7 @@ class MetadataResolverControllerVersionEndpointsIntegrationTests extends Specifi } def "SHIBUI-1500"() { + given: MetadataResolver mr = new FileBackedHttpMetadataResolver(name: 'shibui-1500') mr = repository.save(mr) @@ -159,6 +191,7 @@ class MetadataResolverControllerVersionEndpointsIntegrationTests extends Specifi } def "SHIBUI-1499"() { + given: MetadataResolver mr = new FileBackedHttpMetadataResolver(name: 'shibui-1499') mr = repository.save(mr) @@ -182,6 +215,29 @@ class MetadataResolverControllerVersionEndpointsIntegrationTests extends Specifi noExceptionThrown() } + def "SHIBUI-1501"() { + given: + def mr = new FileBackedHttpMetadataResolver(name: 'shibui-1501') + mr = repository.save(mr) + + when: 'add a filter' + EntityAttributesFilter filter = this.generator.entityAttributesFilter() + mr.addFilter(filter) + def resolver = (repository.save(mr) as MetadataResolver).withTraits AttributeReleaseAndOverrides + resolver.entityAttributesFilterIntoTransientRepresentation() + + def allVersions = getAllMetadataResolverVersions(mr.resourceId, List) + def mrv2 = getMetadataResolverForVersion(mr.resourceId, allVersions.body[1].id, MetadataResolver) + .body.withTraits AttributeReleaseAndOverrides + + then: + mrv2.metadataFilters.size() == 1 + mrv2.attributesRelease(0).size() == resolver.attributesRelease(0).size() + mrv2.overrides(0).size() == resolver.overrides(0).size() + mrv2.attributesRelease(0) == resolver.attributesRelease(0) + mrv2.overrides(0) == resolver.overrides(0) + } + private getAllMetadataResolverVersions(String resourceId, responseType) { this.restTemplate.getForEntity(resourceUriFor(ALL_VERSIONS_URI, resourceId), responseType) } @@ -198,3 +254,13 @@ class MetadataResolverControllerVersionEndpointsIntegrationTests extends Specifi String.format(uriTemplate, resourceId) } } + +trait AttributeReleaseAndOverrides { + List attributesRelease(int filterIndex) { + (this.metadataFilters[filterIndex] as EntityAttributesFilter).attributeRelease + } + + Map overrides(int filterIndex) { + (this.metadataFilters[filterIndex] as EntityAttributesFilter).relyingPartyOverrides + } +} diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataFilterEnversVersioningTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataFilterEnversVersioningTests.groovy index fe1e5eee4..6df7da461 100644 --- a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataFilterEnversVersioningTests.groovy +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataFilterEnversVersioningTests.groovy @@ -1,6 +1,10 @@ package edu.internet2.tier.shibboleth.admin.ui.repository.envers +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.EntitiesVersioningConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration @@ -19,6 +23,8 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ResourceBackedMet import edu.internet2.tier.shibboleth.admin.ui.repository.FilterRepository import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverVersionService +import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator +import edu.internet2.tier.shibboleth.admin.util.AttributeUtility import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.domain.EntityScan import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest @@ -49,6 +55,22 @@ class MetadataFilterEnversVersioningTests extends Specification { @Autowired PlatformTransactionManager txMgr + @Autowired + AttributeUtility attributeUtility + + @Autowired + CustomPropertiesConfiguration customPropertiesConfiguration + + ObjectMapper mapper + TestObjectGenerator generator + + def setup() { + generator = new TestObjectGenerator(attributeUtility, customPropertiesConfiguration) + mapper = new ObjectMapper() + mapper.enable(SerializationFeature.INDENT_OUTPUT) + mapper.registerModule(new JavaTimeModule()) + } + def "test versioning of MetadataResolver with EntityRoleWhiteListFilter"() { when: 'Add initial filter' diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java index dbdc0ddd1..e07272629 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java @@ -170,6 +170,7 @@ public ResponseEntity getAllVersions(@PathVariable String resourceId) { } @GetMapping("/MetadataResolvers/{resourceId}/Versions/{versionId}") + @Transactional(readOnly = true) public ResponseEntity getSpecificVersion(@PathVariable String resourceId, @PathVariable String versionId) { MetadataResolver resolver = versionService.findSpecificVersionOfMetadataResolver(resourceId, versionId); if (resolver == null) { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilter.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilter.java index 5d6d3132a..a3e0a6a93 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilter.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilter.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import static edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions.getAttributeListFromAttributeReleaseList; import static edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions.getAttributeListFromRelyingPartyOverridesRepresentation; @@ -69,6 +70,8 @@ private void rebuildAttributes() { @PostLoad public void intoTransientRepresentation() { + //For some update operations, list of attributes could contain null values. Filter them out + this.attributes.removeIf(Objects::isNull); this.attributeRelease = getAttributeReleaseListFromAttributeList(this.attributes); this.relyingPartyOverrides = getRelyingPartyOverridesRepresentationFromAttributeList(this.attributes); } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataResolver.java index 9e67a5549..d24cd3638 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataResolver.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import edu.internet2.tier.shibboleth.admin.ui.domain.AbstractAuditable; +import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter; import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -100,4 +101,14 @@ public void addFilter(MetadataFilter metadataFilter) { public void markAsModified() { this.versionModifiedTimestamp = System.currentTimeMillis(); } + + public void entityAttributesFilterIntoTransientRepresentation() { + //expose explicit API to call to convert into transient representation + //used in unit/integration tests where JPA's @PostLoad callback execution engine is not available + this.metadataFilters + .stream() + .filter(EntityAttributesFilter.class::isInstance) + .map(EntityAttributesFilter.class::cast) + .forEach(EntityAttributesFilter::intoTransientRepresentation); + } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EnversMetadataResolverVersionService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EnversMetadataResolverVersionService.java index c98203eca..b0d90195f 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EnversMetadataResolverVersionService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EnversMetadataResolverVersionService.java @@ -1,5 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.service; +import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter; import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver; import edu.internet2.tier.shibboleth.admin.ui.domain.versioning.Version; import edu.internet2.tier.shibboleth.admin.ui.envers.EnversVersionServiceSupport; @@ -27,6 +28,16 @@ public List findVersionsForMetadataResolver(String resourceId) { @Override public MetadataResolver findSpecificVersionOfMetadataResolver(String resourceId, String versionId) { Object mrObject = enversVersionServiceSupport.findSpecificVersionOfPersistentEntity(resourceId, versionId, MetadataResolver.class); - return mrObject == null ? null : (MetadataResolver) mrObject; + if(mrObject == null) { + return null; + } + MetadataResolver resolver = (MetadataResolver) mrObject; + + //The @PostLoad is not honored by Envers. So need to do this manually for EntityAttributesFilters + //So the correct representation is built and returned to upstream clients expecting JSON + resolver.entityAttributesFilterIntoTransientRepresentation(); + + return resolver; + } }