diff --git a/backend/build.gradle b/backend/build.gradle index 156829bf4..c7792db77 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -172,6 +172,9 @@ dependencies { compile 'com.opencsv:opencsv:4.4' testCompile 'org.skyscreamer:jsonassert:1.5.0' + + // Envers for persistent entities versioning + compile 'org.hibernate:hibernate-envers' } def generatedSrcDir = new File(buildDir, 'generated/src/main/java') diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/MetadataFilter.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/MetadataFilter.java index 8a42cd1f7..5ee500437 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/MetadataFilter.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/MetadataFilter.java @@ -10,6 +10,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +import org.hibernate.envers.Audited; import javax.persistence.Column; import javax.persistence.Entity; @@ -34,6 +35,7 @@ @JsonSubTypes.Type(value=SignatureValidationFilter.class, name="SignatureValidation"), @JsonSubTypes.Type(value=RequiredValidUntilFilter.class, name="RequiredValidUntil"), @JsonSubTypes.Type(value=NameIdFormatFilter.class, name="NameIDFormat")}) +@Audited public class MetadataFilter extends AbstractAuditable { @JsonProperty("@type") diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java index b98d4188b..e8deb0e3e 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.hibernate.envers.Audited; import javax.persistence.CascadeType; import javax.persistence.ElementCollection; 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 8f2fbfd60..ec639ba38 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 @@ -11,6 +11,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +import org.hibernate.envers.Audited; import javax.persistence.CascadeType; import javax.persistence.Column; @@ -37,6 +38,7 @@ @JsonSubTypes.Type(value = DynamicHttpMetadataResolver.class, name = "DynamicHttpMetadataResolver"), @JsonSubTypes.Type(value = FilesystemMetadataResolver.class, name = "FilesystemMetadataResolver"), @JsonSubTypes.Type(value = ResourceBackedMetadataResolver.class, name = "ResourceBackedMetadataResolver")}) +@Audited public class MetadataResolver extends AbstractAuditable { @JsonProperty("@type") diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index b0f63bd34..d35c7c099 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -44,6 +44,9 @@ spring.jpa.properties.hibernate.format_sql=false spring.jpa.hibernate.use-new-id-generator-mappings=true +#Envers versioning +spring.jpa.properties.org.hibernate.envers.store_data_at_delete=true + # Set the following property to periodically write out the generated metadata files. There is no default value; the following is just an example # shibui.metadata-dir=/opt/shibboleth-idp/metadata/generated shibui.logout-url=/dashboard diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEntityBasicEnversVersioningTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEntityBasicEnversVersioningTests.groovy new file mode 100644 index 000000000..dddf7c126 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEntityBasicEnversVersioningTests.groovy @@ -0,0 +1,89 @@ +package edu.internet2.tier.shibboleth.admin.ui.repository.envers + +import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration +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.resolvers.MetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository +import org.hibernate.envers.AuditReaderFactory +import org.hibernate.envers.query.AuditQuery +import org.springframework.beans.factory.annotation.Autowired +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.transaction.PlatformTransactionManager +import org.springframework.transaction.support.DefaultTransactionDefinition +import spock.lang.Specification + +import javax.persistence.EntityManager + +import static org.springframework.transaction.TransactionDefinition.PROPAGATION_REQUIRES_NEW + +/** + * Testing metadata resolvers basic versioning by envers is functioning. + */ +@DataJpaTest +@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, TestConfiguration, SearchConfiguration]) +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") +class MetadataResolverEntityBasicEnversVersioningTests extends Specification { + + @Autowired + MetadataResolverRepository metadataResolverRepository + + @Autowired + EntityManager entityManager + + @Autowired + PlatformTransactionManager txMgr + + def "test basic audit and version data is created when persisting base metadata resolver with envers enabled"() { + when: + doInExplicitTransaction { + metadataResolverRepository.save(create {new MetadataResolver()}) + } + def metadataResolverHistory = metadataResolverHistory() + + then: + metadataResolverHistory + } + + private metadataResolverHistory() { + def auditReader = AuditReaderFactory.get(entityManager) + AuditQuery auditQuery = auditReader + .createQuery() + .forRevisionsOfEntity(MetadataResolver, false, true) + auditQuery.resultList + + } + + private static create(Closure concreteResolverSupplier) { + MetadataResolver resolver = concreteResolverSupplier() + resolver.with { + it.name = "testme" + it.metadataFilters.add(new EntityAttributesFilter().with { + it.entityAttributesFilterTarget = new EntityAttributesFilterTarget().with { + it.entityAttributesFilterTargetType = EntityAttributesFilterTarget.EntityAttributesFilterTargetType.ENTITY + it.value = ["hola"] + return it + } + return it + }) + } + resolver + } + + //This explicit low level transaction dance is required in order to verify history/version data that envers + //writes out only after the explicit transaction is committed, therefore making it impossible to verify within the main tx + //boundary of the test method which commits tx only after an execution of the test method. This let's us explicitly + //start/commit transaction making envers data written out and verifiable + private doInExplicitTransaction(Closure uow) { + def txStatus = txMgr.getTransaction(new DefaultTransactionDefinition(PROPAGATION_REQUIRES_NEW)) + uow() + txMgr.commit(txStatus) + } +}