diff --git a/backend/build.gradle b/backend/build.gradle index 4e5822e2e..6dd21a097 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -44,6 +44,13 @@ configurations { integrationTestRuntime { extendsFrom testRuntime } + enversTestCompile { + extendsFrom testCompile + + } + enversTestRuntime { + extendsFrom testRuntime + } } processResources.dependsOn(':ui:npm_run_buildProd') @@ -197,6 +204,13 @@ sourceSets { srcDir 'src/integration/resources' } } + enversTest { + groovy { + srcDirs = ['src/enversTest/groovy'] + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + } + } } task integrationTest(type: Test) { @@ -210,6 +224,18 @@ task integrationTest(type: Test) { systemProperties['user.dir'] = workingDir } +task enversTest(type: Test) { + group = 'verification' + description = 'Run tests pertaing to envers versioning engine' + testClassesDirs = sourceSets.enversTest.output.classesDirs + classpath = sourceSets.enversTest.runtimeClasspath + systemProperties = System.properties + systemProperties['user.dir'] = workingDir +} +test.finalizedBy enversTest + + + task generateSources { inputs.dir('src/main/templates') inputs.files fileTree('src/main/resources') { diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerVersionEndpointsIntegrationTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerVersionEndpointsIntegrationTests.groovy new file mode 100644 index 000000000..705f50021 --- /dev/null +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerVersionEndpointsIntegrationTests.groovy @@ -0,0 +1,118 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation +import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository +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 spock.lang.Specification + +/** + * @author Dmitriy Kopylenko + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles(['no-auth', 'dev']) +class EntityDescriptorControllerVersionEndpointsIntegrationTests extends Specification { + + @Autowired + private TestRestTemplate restTemplate + + @Autowired + EntityDescriptorRepository entityDescriptorRepository + + static BASE_URI = '/api/EntityDescriptor' + + static ALL_VERSIONS_URI = "$BASE_URI/%s/Versions" + + static SPECIFIC_VERSION_URI = "$BASE_URI/%s/Versions/%s" + + def "GET /api/EntityDescriptor/{resourceId}/Versions with non-existent entity descriptor"() { + when: + def result = getAllEntityDescriptorVersions('non-existent-ed-id', String) + + then: + result.statusCodeValue == 404 + } + + def "GET /api/EntityDescriptor{resourceId}/Versions with 1 entity descriptor version"() { + given: + EntityDescriptor ed = new EntityDescriptor(entityID: 'http://test/controller', createdBy: 'anonymousUser') + entityDescriptorRepository.save(ed) + + when: + def result = getAllEntityDescriptorVersions(ed.resourceId, List) + + then: + result.statusCodeValue == 200 + result.body.size == 1 + result.body[0].id && result.body[0].creator && result.body[0].date + } + + def "GET /api/EntityDescriptor{resourceId}/Versions with 2 entity descriptor versions"() { + given: + EntityDescriptor ed = new EntityDescriptor(entityID: 'http://test/controller', createdBy: 'anonymousUser') + ed = entityDescriptorRepository.save(ed) + //Will created a second version for UPDATE revision + ed.serviceEnabled = true + entityDescriptorRepository.save(ed) + + when: + def result = getAllEntityDescriptorVersions(ed.resourceId, List) + + then: + result.statusCodeValue == 200 + result.body.size == 2 + result.body[0].id < result.body[1].id + result.body[0].date < result.body[1].date + } + + def "GET /api/EntityDescriptor{resourceId}/Versions/{version} for non existent version"() { + given: + EntityDescriptor ed = new EntityDescriptor(entityID: 'http://test/controller', createdBy: 'anonymousUser') + ed = entityDescriptorRepository.save(ed) + + when: + def result = getEntityDescriptorForVersion(ed.resourceId, '1000', EntityDescriptorRepresentation) + + then: + result.statusCodeValue == 404 + } + + def "GET /api/EntityDescriptor{resourceId}/Versions/{version} with 2 entity descriptor versions returns correct ED for specific versions"() { + given: + EntityDescriptor ed = new EntityDescriptor(entityID: 'http://test/controller', createdBy: 'anonymousUser', serviceProviderName: 'SP1') + ed = entityDescriptorRepository.save(ed) + //Will created a second version for UPDATE revision + ed.serviceProviderName = 'SP2' + entityDescriptorRepository.save(ed) + + when: + def allVersions = getAllEntityDescriptorVersions(ed.resourceId, List) + def edv1 = getEntityDescriptorForVersion(ed.resourceId, allVersions.body[0].id, EntityDescriptorRepresentation) + def edv2 = getEntityDescriptorForVersion(ed.resourceId, allVersions.body[1].id, EntityDescriptorRepresentation) + + then: + edv1.statusCodeValue == 200 + edv1.body.serviceProviderName == 'SP1' + edv2.statusCodeValue == 200 + edv2.body.serviceProviderName == 'SP2' + } + + private getAllEntityDescriptorVersions(String resourceId, responseType) { + this.restTemplate.getForEntity(resourceUriFor(ALL_VERSIONS_URI, resourceId), responseType) + } + + private getEntityDescriptorForVersion(String resourceId, String version, responseType) { + this.restTemplate.getForEntity(resourceUriFor(SPECIFIC_VERSION_URI, resourceId, version), responseType) + } + + private static resourceUriFor(String uriTemplate, String resourceId, String version) { + String.format(uriTemplate, resourceId, version) + } + + private static resourceUriFor(String uriTemplate, String resourceId) { + String.format(uriTemplate, resourceId) + } +} diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/EntityDescriptorEnversVersioningTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/EntityDescriptorEnversVersioningTests.groovy new file mode 100644 index 000000000..f8f97b0fc --- /dev/null +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/EntityDescriptorEnversVersioningTests.groovy @@ -0,0 +1,609 @@ +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.opensaml.OpenSamlObjects +import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository +import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService +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 spock.lang.Specification + +import javax.persistence.EntityManager + +import static edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.* + +/** + * Testing entity descriptor envers versioning + */ +@DataJpaTest +@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, SearchConfiguration, TestConfiguration]) +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") +class EntityDescriptorEnversVersioningTests extends Specification { + + @Autowired + EntityDescriptorRepository entityDescriptorRepository + + @Autowired + EntityDescriptorService entityDescriptorService + + @Autowired + EntityManager entityManager + + @Autowired + PlatformTransactionManager txMgr + + @Autowired + OpenSamlObjects openSamlObjects + + def "test versioning with contact persons"() { + setup: + def expectedModifiedPersistentEntities = [EntityDescriptor.name, ContactPerson.name, GivenName.name, EmailAddress.name] + + when: + def ed = new EntityDescriptor() + def representation = new EntityDescriptorRepresentation().with { + it.contacts = [new ContactRepresentation(type: 'administrative', name: 'name', emailAddress: 'test@test')] + it + } + def entityDescriptorHistory = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + then: + entityDescriptorHistory.size() == 1 + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).contactPersons[0].givenName.name == 'name' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).contactPersons[0].type == org.opensaml.saml.saml2.metadata.ContactPersonTypeEnumeration.ADMINISTRATIVE + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).contactPersons[0].emailAddresses[0].address == 'test@test' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).principalUserName == 'anonymousUser' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).timestamp > 0L + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames(entityDescriptorHistory, 0).sort() == expectedModifiedPersistentEntities.sort() + + when: + representation = new EntityDescriptorRepresentation().with { + it.contacts = [new ContactRepresentation(type: 'administrative', name: 'nameUPDATED', emailAddress: 'test@test')] + it + } + entityDescriptorHistory = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + then: + entityDescriptorHistory.size() == 2 + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).contactPersons[0].givenName.name == 'nameUPDATED' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).contactPersons[0].type == org.opensaml.saml.saml2.metadata.ContactPersonTypeEnumeration.ADMINISTRATIVE + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).contactPersons[0].emailAddresses[0].address == 'test@test' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 1).principalUserName == 'anonymousUser' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 1).timestamp > 0L + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames(entityDescriptorHistory, 1).sort() == expectedModifiedPersistentEntities.sort() + + when: + representation = new EntityDescriptorRepresentation().with { + it.contacts = [new ContactRepresentation(type: 'other', name: 'nameUPDATED2', emailAddress: 'test@test.com')] + it + } + entityDescriptorHistory = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, + entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + then: + entityDescriptorHistory.size() == 3 + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 2).contactPersons[0].givenName.name == 'nameUPDATED2' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 2).contactPersons[0].type == org.opensaml.saml.saml2.metadata.ContactPersonTypeEnumeration.OTHER + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 2).contactPersons[0].emailAddresses[0].address == 'test@test.com' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 2).principalUserName == 'anonymousUser' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 2).timestamp > 0L + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames(entityDescriptorHistory, 2).sort() == expectedModifiedPersistentEntities.sort() + + //Also make sure we have our original revision + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).contactPersons[0].givenName.name == 'nameUPDATED' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).contactPersons[0].type == org.opensaml.saml.saml2.metadata.ContactPersonTypeEnumeration.ADMINISTRATIVE + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).contactPersons[0].emailAddresses[0].address == 'test@test' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 1).principalUserName == 'anonymousUser' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 1).timestamp > 0L + + } + + def "test versioning with organization"() { + setup: + def expectedModifiedPersistentEntities = [EntityDescriptor.name, + Organization.name, + OrganizationDisplayName.name, + OrganizationName.name, + OrganizationURL.name] + + when: + EntityDescriptor ed = new EntityDescriptor() + def representation = new EntityDescriptorRepresentation().with { + it.organization = new OrganizationRepresentation(name: 'org', displayName: 'display org', url: 'http://org.edu') + it + } + def entityDescriptorHistory = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + then: + entityDescriptorHistory.size() == 1 + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).organization.organizationNames[0].value == 'org' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).organization.displayNames[0].value == 'display org' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).organization.URLs[0].value == 'http://org.edu' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).principalUserName == 'anonymousUser' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).timestamp > 0L + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames(entityDescriptorHistory, 0).sort() == expectedModifiedPersistentEntities.sort() + + when: + representation = new EntityDescriptorRepresentation().with { + it.organization = new OrganizationRepresentation(name: 'orgUpdated', displayName: 'display org Updated', url: 'http://org2.edu') + it + } + entityDescriptorHistory = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + then: + entityDescriptorHistory.size() == 2 + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).organization.organizationNames[0].value == 'orgUpdated' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).organization.displayNames[0].value == 'display org Updated' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).organization.URLs[0].value == 'http://org2.edu' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).principalUserName == 'anonymousUser' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).timestamp > 0L + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames(entityDescriptorHistory, 1).sort() == expectedModifiedPersistentEntities.sort() + + //Check the original revision is intact + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).organization.organizationNames[0].value == 'org' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).organization.displayNames[0].value == 'display org' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).organization.URLs[0].value == 'http://org.edu' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 1).principalUserName == 'anonymousUser' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 1).timestamp > 0L + } + + def "test versioning with sp sso descriptor"() { + setup: + def expectedModifiedPersistentEntities = [EntityDescriptor.name, + NameIDFormat.name, + SPSSODescriptor.name] + when: + EntityDescriptor ed = new EntityDescriptor() + def representation = new EntityDescriptorRepresentation().with { + it.serviceProviderSsoDescriptor = new ServiceProviderSsoDescriptorRepresentation().with { + it.protocolSupportEnum = 'SAML 1.1' + it.nameIdFormats = ['format'] + it + } + it + } + def entityDescriptorHistory = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + then: + entityDescriptorHistory.size() == 1 + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).roleDescriptors[0].nameIDFormats[0].format == 'format' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).roleDescriptors[0].supportedProtocols[0] == 'urn:oasis:names:tc:SAML:1.1:protocol' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).roleDescriptors[0].supportedProtocols[1] == null + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).principalUserName == 'anonymousUser' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).timestamp > 0L + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames(entityDescriptorHistory, 0).sort() == expectedModifiedPersistentEntities.sort() + + when: + representation = new EntityDescriptorRepresentation().with { + it.serviceProviderSsoDescriptor = new ServiceProviderSsoDescriptorRepresentation().with { + it.protocolSupportEnum = 'SAML 1.1, SAML 2' + it.nameIdFormats = ['formatUPDATED'] + it + } + it + } + + entityDescriptorHistory = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + then: + entityDescriptorHistory.size() == 2 + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).roleDescriptors[0].nameIDFormats[0].format == 'formatUPDATED' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).roleDescriptors[0].supportedProtocols[0] == 'urn:oasis:names:tc:SAML:1.1:protocol' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).roleDescriptors[0].supportedProtocols[1] == 'urn:oasis:names:tc:SAML:2.0:protocol' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 1).principalUserName == 'anonymousUser' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 1).timestamp > 0L + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames(entityDescriptorHistory, 1).sort() == expectedModifiedPersistentEntities.sort() + + //Check the original revision is intact + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).roleDescriptors[0].nameIDFormats[0].format == 'format' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).roleDescriptors[0].supportedProtocols[0] == 'urn:oasis:names:tc:SAML:1.1:protocol' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).roleDescriptors[0].supportedProtocols[1] == null + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).principalUserName == 'anonymousUser' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).timestamp > 0L + } + + def "test versioning with uiInfo"() { + setup: + def expectedModifiedPersistentEntities = [EntityDescriptor.name, + Description.name, + DisplayName.name, + SPSSODescriptor.name, + Extensions.name, + InformationURL.name, + Logo.name, + PrivacyStatementURL.name, + UIInfo.name] + + when: + EntityDescriptor ed = new EntityDescriptor() + def representation = new EntityDescriptorRepresentation().with { + it.mdui = new MduiRepresentation().with { + it.displayName = 'Initial display name' + it.informationUrl = 'http://info' + it.privacyStatementUrl = 'http://privacy' + it.description = 'Initial desc' + it.logoUrl = 'http://logo' + it.logoHeight = 20 + it.logoWidth = 30 + it + } + it + } + def entityDescriptorHistory = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + //Groovy FTW - able to call any private methods on ANY object. Get first revision + UIInfo uiinfo = entityDescriptorService.getUIInfo(edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0)) + + then: + entityDescriptorHistory.size() == 1 + uiinfo.displayNames[0].value == 'Initial display name' + uiinfo.informationURLs[0].value == 'http://info' + uiinfo.privacyStatementURLs[0].value == 'http://privacy' + uiinfo.descriptions[0].value == 'Initial desc' + uiinfo.logos[0].URL == 'http://logo' + uiinfo.logos[0].height == 20 + uiinfo.logos[0].width == 30 + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames(entityDescriptorHistory, 0).sort() == expectedModifiedPersistentEntities.sort() + + when: + representation = new EntityDescriptorRepresentation().with { + it.mdui = new MduiRepresentation().with { + it.displayName = 'Display name UPDATED' + it.informationUrl = 'http://info.updated' + it.privacyStatementUrl = 'http://privacy.updated' + it.description = 'Desc UPDATED' + it.logoUrl = 'http://logo.updated' + it.logoHeight = 30 + it.logoWidth = 40 + it + } + it + } + entityDescriptorHistory = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + //Get second revision + uiinfo = entityDescriptorService.getUIInfo(edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 1)) + //And initial revision + def uiinfoInitialRevision = entityDescriptorService.getUIInfo(edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0)) + + then: + entityDescriptorHistory.size() == 2 + uiinfo.displayNames[0].value == 'Display name UPDATED' + uiinfo.informationURLs[0].value == 'http://info.updated' + uiinfo.privacyStatementURLs[0].value == 'http://privacy.updated' + uiinfo.descriptions[0].value == 'Desc UPDATED' + uiinfo.logos[0].URL == 'http://logo.updated' + uiinfo.logos[0].height == 30 + uiinfo.logos[0].width == 40 + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames(entityDescriptorHistory, 1).sort() == expectedModifiedPersistentEntities.sort() + + //Check the initial revision is still intact + uiinfoInitialRevision.displayNames[0].value == 'Initial display name' + uiinfoInitialRevision.informationURLs[0].value == 'http://info' + uiinfoInitialRevision.privacyStatementURLs[0].value == 'http://privacy' + uiinfoInitialRevision.descriptions[0].value == 'Initial desc' + uiinfoInitialRevision.logos[0].URL == 'http://logo' + uiinfoInitialRevision.logos[0].height == 20 + uiinfoInitialRevision.logos[0].width == 30 + } + + def "test versioning with security"() { + setup: + def expectedModifiedPersistentEntities = [EntityDescriptor.name, + KeyDescriptor.name, + KeyInfo.name, + SPSSODescriptor.name, + X509Certificate.name, + X509Data.name] + + when: + EntityDescriptor ed = new EntityDescriptor() + def representation = new EntityDescriptorRepresentation().with { + it.securityInfo = new SecurityInfoRepresentation().with { + it.authenticationRequestsSigned = true + it.x509CertificateAvailable = true + it.x509Certificates = [new SecurityInfoRepresentation.X509CertificateRepresentation(name: 'sign', type: 'signing', value: 'signingValue')] + it + } + it + } + + def entityDescriptorHistory = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + //Get initial revision + SPSSODescriptor spssoDescriptor = + entityDescriptorService.getSPSSODescriptorFromEntityDescriptor(edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory,0)) + + KeyDescriptor keyDescriptor = spssoDescriptor.keyDescriptors[0] + X509Certificate x509cert = keyDescriptor.keyInfo.x509Datas[0].x509Certificates[0] + + then: + entityDescriptorHistory.size() == 1 + spssoDescriptor.isAuthnRequestsSigned() + keyDescriptor.name == 'sign' + keyDescriptor.usageType == 'signing' + x509cert.value == 'signingValue' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames(entityDescriptorHistory, 0).sort() == expectedModifiedPersistentEntities.sort() + + when: + representation = new EntityDescriptorRepresentation().with { + it.securityInfo = new SecurityInfoRepresentation().with { + it.authenticationRequestsSigned = false + it.x509CertificateAvailable = true + it.x509Certificates = [new SecurityInfoRepresentation.X509CertificateRepresentation(name: 'sign', type: 'signing', value: 'signingValue'), + new SecurityInfoRepresentation.X509CertificateRepresentation(name: 'encrypt', type: 'encryption', value: 'encryptionValue')] + it + } + it + } + + entityDescriptorHistory = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + + //Get second revision + SPSSODescriptor spssoDescriptor_second = entityDescriptorService.getSPSSODescriptorFromEntityDescriptor(edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory,1)) + + KeyDescriptor keyDescriptor_second1 = spssoDescriptor_second.keyDescriptors[0] + X509Certificate x509cert_second1 = keyDescriptor_second1.keyInfo.x509Datas[0].x509Certificates[0] + KeyDescriptor keyDescriptor_second2 = spssoDescriptor_second.keyDescriptors[1] + X509Certificate x509cert_second2 = keyDescriptor_second2.keyInfo.x509Datas[0].x509Certificates[0] + + + //Get initial revision + spssoDescriptor = + entityDescriptorService.getSPSSODescriptorFromEntityDescriptor(edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory,0)) + + keyDescriptor = spssoDescriptor.keyDescriptors[0] + x509cert = keyDescriptor.keyInfo.x509Datas[0].x509Certificates[0] + + then: + entityDescriptorHistory.size() == 2 + !spssoDescriptor_second.isAuthnRequestsSigned() + keyDescriptor_second1.name == 'sign' + keyDescriptor_second1.usageType == 'signing' + keyDescriptor_second2.name == 'encrypt' + keyDescriptor_second2.usageType == 'encryption' + x509cert_second1.value == 'signingValue' + x509cert_second2.value == 'encryptionValue' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames(entityDescriptorHistory, 1).sort() == expectedModifiedPersistentEntities.sort() + + //Check the initial version is intact + spssoDescriptor.keyDescriptors.size() == 1 + spssoDescriptor.isAuthnRequestsSigned() + keyDescriptor.name == 'sign' + keyDescriptor.usageType == 'signing' + x509cert.value == 'signingValue' + } + + def "test versioning ACS"() { + setup: + def expectedModifiedPersistentEntities = [EntityDescriptor.name, + SPSSODescriptor.name, + AssertionConsumerService.name] + + when: + EntityDescriptor ed = new EntityDescriptor() + def representation = new EntityDescriptorRepresentation().with { + it.assertionConsumerServices = [ + new AssertionConsumerServiceRepresentation(locationUrl: 'http://acs', binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST')] + it + } + + def entityDescriptorHistory = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + SPSSODescriptor spssoDescriptor = + entityDescriptorService.getSPSSODescriptorFromEntityDescriptor(edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory,0)) + AssertionConsumerService acs = spssoDescriptor.assertionConsumerServices[0] + + then: + entityDescriptorHistory.size() == 1 + !acs.isDefault() + acs.location == 'http://acs' + acs.binding == 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames(entityDescriptorHistory, 0).sort() == expectedModifiedPersistentEntities.sort() + + + when: + representation = new EntityDescriptorRepresentation().with { + it.assertionConsumerServices = [ + new AssertionConsumerServiceRepresentation(locationUrl: 'http://acs.updated', binding: 'urn:oasis:names:tc:SAML:2.0:bindings:PAOS', makeDefault: true), + new AssertionConsumerServiceRepresentation(locationUrl: 'http://acs2', binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact')] + it + } + + entityDescriptorHistory = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + SPSSODescriptor spssoDescriptor2 = + entityDescriptorService.getSPSSODescriptorFromEntityDescriptor(edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory,1)) + def (acs1, acs2) = [spssoDescriptor2.assertionConsumerServices[0], spssoDescriptor2.assertionConsumerServices[1]] + + //Initial revision + spssoDescriptor = + entityDescriptorService.getSPSSODescriptorFromEntityDescriptor(edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory,0)) + acs = spssoDescriptor.assertionConsumerServices[0] + + then: + entityDescriptorHistory.size() == 2 + acs1.isDefault() + !acs2.isDefault() + acs1.location == 'http://acs.updated' + acs1.binding == 'urn:oasis:names:tc:SAML:2.0:bindings:PAOS' + acs2.location == 'http://acs2' + acs2.binding == 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames(entityDescriptorHistory, 1).sort() == expectedModifiedPersistentEntities.sort() + + //Check the initial revision is intact + !acs.isDefault() + acs.location == 'http://acs' + acs.binding == 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' + } + + def "test versioning logout"() { + setup: + def expectedModifiedPersistentEntities = [EntityDescriptor.name, + SPSSODescriptor.name, + SingleLogoutService.name] + + when: + EntityDescriptor ed = new EntityDescriptor() + def representation = new EntityDescriptorRepresentation().with { + it.logoutEndpoints = [new LogoutEndpointRepresentation(url: 'http://logout', bindingType: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST')] + it + } + + def entityDescriptorHistory = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + SPSSODescriptor spssoDescriptor = + entityDescriptorService.getSPSSODescriptorFromEntityDescriptor(edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0)) + SingleLogoutService slo = spssoDescriptor.singleLogoutServices[0] + + then: + entityDescriptorHistory.size() == 1 + slo.location == 'http://logout' + slo.binding == 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames(entityDescriptorHistory, 0).sort() == expectedModifiedPersistentEntities.sort() + + when: + representation = new EntityDescriptorRepresentation().with { + it.logoutEndpoints = [new LogoutEndpointRepresentation(url: 'http://logout.updated', bindingType: 'urn:oasis:names:tc:SAML:2.0:bindings:PAOS'), + new LogoutEndpointRepresentation(url: 'http://logout2', bindingType: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact')] + it + } + + entityDescriptorHistory = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + SPSSODescriptor spssoDescriptor2 = + entityDescriptorService.getSPSSODescriptorFromEntityDescriptor(edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 1)) + def (slo1, slo2) = [spssoDescriptor2.singleLogoutServices[0], spssoDescriptor2.singleLogoutServices[1]] + + //Initial revision + spssoDescriptor = + entityDescriptorService.getSPSSODescriptorFromEntityDescriptor(edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0)) + slo = spssoDescriptor.singleLogoutServices[0] + + then: + entityDescriptorHistory.size() == 2 + slo1.location == 'http://logout.updated' + slo1.binding == 'urn:oasis:names:tc:SAML:2.0:bindings:PAOS' + slo2.location == 'http://logout2' + slo2.binding == 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames(entityDescriptorHistory, 1).sort() == expectedModifiedPersistentEntities.sort() + + //Check the initial version is intact + slo.location == 'http://logout' + slo.binding == 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' + } + + def "test versioning relying party overrides"() { + setup: + def expectedModifiedPersistentEntities = [EntityDescriptor.name, + EntityAttributes.name, + Extensions.name, + Attribute.name, + XSBoolean.name, + XSString.name] + + when: + EntityDescriptor ed = new EntityDescriptor() + def representation = new EntityDescriptorRepresentation().with { + it.relyingPartyOverrides = [signAssertion: true] + it.attributeRelease = ['attr1'] + it + } + + def entityDescriptorHistory = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + EntityAttributes attrs = entityDescriptorService.getEntityAttributes(edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0)) + + then: + entityDescriptorHistory.size() == 1 + attrs.attributes[0].attributeValues[0].storedValue == 'true' + attrs.attributes[1].attributeValues[0].xsStringvalue == 'attr1' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames(entityDescriptorHistory, 0).sort() == expectedModifiedPersistentEntities.sort() + + when: + representation = new EntityDescriptorRepresentation().with { + it.relyingPartyOverrides = [signAssertion: false] + it.attributeRelease = ['attr1', 'attr2'] + it + } + + entityDescriptorHistory = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + EntityAttributes attrs2 = entityDescriptorService.getEntityAttributes(edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 1)) + + //Initial revision + attrs = entityDescriptorService.getEntityAttributes(edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex(entityDescriptorHistory, 0)) + + expectedModifiedPersistentEntities = [EntityDescriptor.name, + EntityAttributes.name, + Attribute.name, + XSString.name] + then: + entityDescriptorHistory.size() == 2 + attrs2.attributes[0].attributeValues[0].xsStringvalue == 'attr1' + attrs2.attributes[0].attributeValues[1].xsStringvalue == 'attr2' + edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames(entityDescriptorHistory, 1).sort() == expectedModifiedPersistentEntities.sort() + + //Check the initial revision is intact + attrs.attributes[0].attributeValues[0].storedValue == 'true' + attrs.attributes[1].attributeValues[0].xsStringvalue == 'attr1' + attrs.attributes[1].attributeValues[1] == null + } +} diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEntityBasicEnversVersioningTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEntityBasicEnversVersioningTests.groovy new file mode 100644 index 000000000..f8ac3b431 --- /dev/null +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEntityBasicEnversVersioningTests.groovy @@ -0,0 +1,104 @@ +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 + +/** + * 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: + MetadataResolver mdr = doInExplicitTransaction { + metadataResolverRepository.save(create {new MetadataResolver()}) + } + def metadataResolverHistory = resolverHistory() + + then: + metadataResolverHistory.size() == 1 + + when: + def rev = metadataResolverHistory[0] + + then: + rev[1].principalUserName == 'anonymousUser' + + when: + mdr.name = 'Updated' + doInExplicitTransaction { + metadataResolverRepository.save(mdr) + } + metadataResolverHistory = resolverHistory() + + then: + metadataResolverHistory.size == 2 + } + + private resolverHistory() { + 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(org.springframework.transaction.TransactionDefinition.PROPAGATION_REQUIRES_NEW)) + def entity = uow() + txMgr.commit(txStatus) + entity + } +} diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversEntityDescriptorVersionServiceTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversEntityDescriptorVersionServiceTests.groovy new file mode 100644 index 000000000..58cd83704 --- /dev/null +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversEntityDescriptorVersionServiceTests.groovy @@ -0,0 +1,114 @@ +package edu.internet2.tier.shibboleth.admin.ui.service.envers + + +import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor +import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository +import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService +import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorVersionService +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 spock.lang.Specification + +import java.time.LocalDateTime + +@DataJpaTest +@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, TestConfiguration, SearchConfiguration, EntitiesVersioningConfiguration]) +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") +class EnversEntityDescriptorVersionServiceTests extends Specification { + + @Autowired + EntityDescriptorVersionService entityDescriptorVersionService + + @Autowired + EntityDescriptorRepository entityDescriptorRepository + + @Autowired + EntityDescriptorService entityDescriptorService + + @Autowired + PlatformTransactionManager txMgr + + def "versioning service returns correct number of versions sorted by modified date in natural order"() { + when: 'Initial version' + EntityDescriptor ed = new EntityDescriptor(entityID: 'ed', serviceProviderName: 'SP1') + ed = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.doInExplicitTransaction(txMgr) { + entityDescriptorRepository.save(ed) + } + def versions = entityDescriptorVersionService.findVersionsForEntityDescriptor(ed.resourceId) + + then: + versions.size() == 1 + versions[0].id + versions[0].creator + versions[0].date < LocalDateTime.now() + + when: 'Second version' + ed.serviceProviderName = 'SP2' + ed = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.doInExplicitTransaction(txMgr) { + entityDescriptorRepository.save(ed) + } + versions = entityDescriptorVersionService.findVersionsForEntityDescriptor(ed.resourceId) + + then: + versions.size() == 2 + versions[0].id && versions[1].id + versions[0].creator && versions[1].creator + versions[0].date < versions[1].date + + when: 'Third version' + ed.serviceProviderName = 'SP3' + ed = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.doInExplicitTransaction(txMgr) { + entityDescriptorRepository.save(ed) + } + versions = entityDescriptorVersionService.findVersionsForEntityDescriptor(ed.resourceId) + + then: + versions.size() == 3 + versions[0].id && versions[1].id && versions[2].id + versions[0].creator && versions[1].creator && versions[2].creator + (versions[0].date < versions[1].date) && (versions[1].date < versions[2].date) + } + + def "versioning service returns correct entity descriptor for version number"() { + when: 'Initial version' + EntityDescriptor ed = new EntityDescriptor(entityID: 'ed', serviceProviderName: 'SP1', createdBy: 'anonymousUser') + ed = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.doInExplicitTransaction(txMgr) { + entityDescriptorRepository.save(ed) + } + def versions = entityDescriptorVersionService.findVersionsForEntityDescriptor(ed.resourceId) + def v1EdRepresentation = entityDescriptorVersionService.findSpecificVersionOfEntityDescriptor(ed.resourceId, versions[0].id) + + then: + v1EdRepresentation.serviceProviderName == 'SP1' + v1EdRepresentation.id == ed.resourceId + + when: 'Update the original' + ed.serviceProviderName = 'SP2' + ed = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.doInExplicitTransaction(txMgr) { + entityDescriptorRepository.save(ed) + } + versions = entityDescriptorVersionService.findVersionsForEntityDescriptor(ed.resourceId) + def v2EdRepresentation = entityDescriptorVersionService.findSpecificVersionOfEntityDescriptor(ed.resourceId, versions[1].id) + + then: + v2EdRepresentation.serviceProviderName == 'SP2' + v2EdRepresentation.id == ed.resourceId + } + + def "versioning service returns null for non existent version number"() { + when: 'Initial version' + EntityDescriptor ed = new EntityDescriptor(entityID: 'ed', serviceProviderName: 'SP1', createdBy: 'anonymousUser') + ed = edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.doInExplicitTransaction(txMgr) { + entityDescriptorRepository.save(ed) + } + def edRepresentation = entityDescriptorVersionService.findSpecificVersionOfEntityDescriptor(ed.resourceId, '1000') + + then: + !edRepresentation + } +} diff --git a/settings.gradle b/settings.gradle index ba298e3de..b3ab3e757 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include 'backend', 'ui', 'pac4j-module', 'envers-tests-module' +include 'backend', 'ui', 'pac4j-module'