diff --git a/Jenkinsfile b/Jenkinsfile index bc032671c..8c6befc98 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -48,5 +48,8 @@ pipeline { success { emailext body: '''${SCRIPT, template="groovy-text.template"}''', recipientProviders: [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']], subject: '[SHIBUI] Build Success' } + always { + cleanWs() + } } -} \ No newline at end of file +} diff --git a/backend/build.gradle b/backend/build.gradle index 4e5822e2e..3af60585e 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -46,6 +46,36 @@ configurations { } } +def generatedSrcDir = new File(buildDir, 'generated/src/main/java') + +sourceSets { + main { + groovy { + srcDirs = ['src/main/groovy', 'src/main/java', generatedSrcDir] + } + java { + srcDirs = [] + } + } + integrationTest { + groovy { + srcDirs = ['src/integration/groovy'] + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + } + resources { + srcDir 'src/integration/resources' + } + } + + enversTest { + groovy { + srcDir 'src/enversTest/groovy' + } + resources.srcDir 'src/enversTest/resources' + } +} + processResources.dependsOn(':ui:npm_run_buildProd') jar { @@ -174,29 +204,14 @@ dependencies { // Envers for persistent entities versioning compile 'org.hibernate:hibernate-envers' -} -def generatedSrcDir = new File(buildDir, 'generated/src/main/java') + enversTestCompile sourceSets.main.output + enversTestCompile sourceSets.test.output + enversTestCompile configurations.compile + enversTestCompile configurations.testCompile -sourceSets { - main { - groovy { - srcDirs = ['src/main/groovy', 'src/main/java', generatedSrcDir] - } - java { - srcDirs = [] - } - } - integrationTest { - groovy { - srcDirs = ['src/integration/groovy'] - compileClasspath += main.output + test.output - runtimeClasspath += main.output + test.output - } - resources { - srcDir 'src/integration/resources' - } - } + enversTestRuntime configurations.runtime + enversTestRuntime configurations.testRuntime } task integrationTest(type: Test) { @@ -210,6 +225,19 @@ 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 +} + +check { + dependsOn 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..05e3ad139 --- /dev/null +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/EntityDescriptorEnversVersioningTests.groovy @@ -0,0 +1,647 @@ +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.AssertionConsumerService +import edu.internet2.tier.shibboleth.admin.ui.domain.Attribute +import edu.internet2.tier.shibboleth.admin.ui.domain.ContactPerson +import edu.internet2.tier.shibboleth.admin.ui.domain.Description +import edu.internet2.tier.shibboleth.admin.ui.domain.DisplayName +import edu.internet2.tier.shibboleth.admin.ui.domain.EmailAddress +import edu.internet2.tier.shibboleth.admin.ui.domain.EntityAttributes +import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor +import edu.internet2.tier.shibboleth.admin.ui.domain.Extensions +import edu.internet2.tier.shibboleth.admin.ui.domain.GivenName +import edu.internet2.tier.shibboleth.admin.ui.domain.InformationURL +import edu.internet2.tier.shibboleth.admin.ui.domain.KeyDescriptor +import edu.internet2.tier.shibboleth.admin.ui.domain.KeyInfo +import edu.internet2.tier.shibboleth.admin.ui.domain.Logo +import edu.internet2.tier.shibboleth.admin.ui.domain.NameIDFormat +import edu.internet2.tier.shibboleth.admin.ui.domain.Organization +import edu.internet2.tier.shibboleth.admin.ui.domain.OrganizationDisplayName +import edu.internet2.tier.shibboleth.admin.ui.domain.OrganizationName +import edu.internet2.tier.shibboleth.admin.ui.domain.OrganizationURL +import edu.internet2.tier.shibboleth.admin.ui.domain.PrivacyStatementURL +import edu.internet2.tier.shibboleth.admin.ui.domain.SPSSODescriptor +import edu.internet2.tier.shibboleth.admin.ui.domain.SingleLogoutService +import edu.internet2.tier.shibboleth.admin.ui.domain.UIInfo +import edu.internet2.tier.shibboleth.admin.ui.domain.X509Certificate +import edu.internet2.tier.shibboleth.admin.ui.domain.X509Data +import edu.internet2.tier.shibboleth.admin.ui.domain.XSBoolean +import edu.internet2.tier.shibboleth.admin.ui.domain.XSString +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.AssertionConsumerServiceRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.ContactRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.LogoutEndpointRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.MduiRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.OrganizationRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.SecurityInfoRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.ServiceProviderSsoDescriptorRepresentation +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.getModifiedEntityNames +import static edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex +import static edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex +import static edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfEntityDescriptor + +/** + * 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 = updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + then: + entityDescriptorHistory.size() == 1 + getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).contactPersons[0].givenName.name == 'name' + getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).contactPersons[0].type == org.opensaml.saml.saml2.metadata.ContactPersonTypeEnumeration.ADMINISTRATIVE + getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).contactPersons[0].emailAddresses[0].address == 'test@test' + getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).timestamp > 0L + getModifiedEntityNames(entityDescriptorHistory, 0).sort() == expectedModifiedPersistentEntities.sort() + + when: + representation = new EntityDescriptorRepresentation().with { + it.contacts = [new ContactRepresentation(type: 'administrative', name: 'nameUPDATED', emailAddress: 'test@test')] + it + } + entityDescriptorHistory = updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + then: + entityDescriptorHistory.size() == 2 + getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).contactPersons[0].givenName.name == 'nameUPDATED' + getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).contactPersons[0].type == org.opensaml.saml.saml2.metadata.ContactPersonTypeEnumeration.ADMINISTRATIVE + getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).contactPersons[0].emailAddresses[0].address == 'test@test' + getRevisionEntityForRevisionIndex(entityDescriptorHistory, 1).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(entityDescriptorHistory, 1).timestamp > 0L + 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 = updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, + entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + then: + entityDescriptorHistory.size() == 3 + getTargetEntityForRevisionIndex(entityDescriptorHistory, 2).contactPersons[0].givenName.name == 'nameUPDATED2' + getTargetEntityForRevisionIndex(entityDescriptorHistory, 2).contactPersons[0].type == org.opensaml.saml.saml2.metadata.ContactPersonTypeEnumeration.OTHER + getTargetEntityForRevisionIndex(entityDescriptorHistory, 2).contactPersons[0].emailAddresses[0].address == 'test@test.com' + getRevisionEntityForRevisionIndex(entityDescriptorHistory, 2).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(entityDescriptorHistory, 2).timestamp > 0L + getModifiedEntityNames(entityDescriptorHistory, 2).sort() == expectedModifiedPersistentEntities.sort() + + //Also make sure we have our original revision + getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).contactPersons[0].givenName.name == 'nameUPDATED' + getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).contactPersons[0].type == org.opensaml.saml.saml2.metadata.ContactPersonTypeEnumeration.ADMINISTRATIVE + getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).contactPersons[0].emailAddresses[0].address == 'test@test' + getRevisionEntityForRevisionIndex(entityDescriptorHistory, 1).principalUserName == 'anonymousUser' + 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 = updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + then: + entityDescriptorHistory.size() == 1 + getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).organization.organizationNames[0].value == 'org' + getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).organization.displayNames[0].value == 'display org' + getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).organization.URLs[0].value == 'http://org.edu' + getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).timestamp > 0L + 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 = updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + then: + entityDescriptorHistory.size() == 2 + getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).organization.organizationNames[0].value == 'orgUpdated' + getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).organization.displayNames[0].value == 'display org Updated' + getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).organization.URLs[0].value == 'http://org2.edu' + getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).timestamp > 0L + getModifiedEntityNames(entityDescriptorHistory, 1).sort() == expectedModifiedPersistentEntities.sort() + + //Check the original revision is intact + getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).organization.organizationNames[0].value == 'org' + getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).organization.displayNames[0].value == 'display org' + getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).organization.URLs[0].value == 'http://org.edu' + getRevisionEntityForRevisionIndex(entityDescriptorHistory, 1).principalUserName == 'anonymousUser' + 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 = updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + then: + entityDescriptorHistory.size() == 1 + getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).roleDescriptors[0].nameIDFormats[0].format == 'format' + getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).roleDescriptors[0].supportedProtocols[0] == 'urn:oasis:names:tc:SAML:1.1:protocol' + getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).roleDescriptors[0].supportedProtocols[1] == null + getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).timestamp > 0L + 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 = updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + then: + entityDescriptorHistory.size() == 2 + getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).roleDescriptors[0].nameIDFormats[0].format == 'formatUPDATED' + getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).roleDescriptors[0].supportedProtocols[0] == 'urn:oasis:names:tc:SAML:1.1:protocol' + getTargetEntityForRevisionIndex(entityDescriptorHistory, 1).roleDescriptors[0].supportedProtocols[1] == 'urn:oasis:names:tc:SAML:2.0:protocol' + getRevisionEntityForRevisionIndex(entityDescriptorHistory, 1).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(entityDescriptorHistory, 1).timestamp > 0L + getModifiedEntityNames(entityDescriptorHistory, 1).sort() == expectedModifiedPersistentEntities.sort() + + //Check the original revision is intact + getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).roleDescriptors[0].nameIDFormats[0].format == 'format' + getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).roleDescriptors[0].supportedProtocols[0] == 'urn:oasis:names:tc:SAML:1.1:protocol' + getTargetEntityForRevisionIndex(entityDescriptorHistory, 0).roleDescriptors[0].supportedProtocols[1] == null + getRevisionEntityForRevisionIndex(entityDescriptorHistory, 0).principalUserName == 'anonymousUser' + 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 = 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(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 + 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 = updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + //Get second revision + uiinfo = entityDescriptorService.getUIInfo(getTargetEntityForRevisionIndex(entityDescriptorHistory, 1)) + //And initial revision + def uiinfoInitialRevision = entityDescriptorService.getUIInfo(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 + 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 = updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + //Get initial revision + SPSSODescriptor spssoDescriptor = + entityDescriptorService.getSPSSODescriptorFromEntityDescriptor(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' + 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 = updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + + //Get second revision + SPSSODescriptor spssoDescriptor_second = entityDescriptorService.getSPSSODescriptorFromEntityDescriptor(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(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' + 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 = updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + SPSSODescriptor spssoDescriptor = + entityDescriptorService.getSPSSODescriptorFromEntityDescriptor(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' + 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 = updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + SPSSODescriptor spssoDescriptor2 = + entityDescriptorService.getSPSSODescriptorFromEntityDescriptor(getTargetEntityForRevisionIndex(entityDescriptorHistory,1)) + def (acs1, acs2) = [spssoDescriptor2.assertionConsumerServices[0], spssoDescriptor2.assertionConsumerServices[1]] + + //Initial revision + spssoDescriptor = + entityDescriptorService.getSPSSODescriptorFromEntityDescriptor(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' + 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 = updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + SPSSODescriptor spssoDescriptor = + entityDescriptorService.getSPSSODescriptorFromEntityDescriptor(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' + 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 = updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + SPSSODescriptor spssoDescriptor2 = + entityDescriptorService.getSPSSODescriptorFromEntityDescriptor(getTargetEntityForRevisionIndex(entityDescriptorHistory, 1)) + def (slo1, slo2) = [spssoDescriptor2.singleLogoutServices[0], spssoDescriptor2.singleLogoutServices[1]] + + //Initial revision + spssoDescriptor = + entityDescriptorService.getSPSSODescriptorFromEntityDescriptor(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' + 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 = updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + EntityAttributes attrs = entityDescriptorService.getEntityAttributes(getTargetEntityForRevisionIndex(entityDescriptorHistory, 0)) + + then: + entityDescriptorHistory.size() == 1 + attrs.attributes[0].attributeValues[0].storedValue == 'true' + attrs.attributes[1].attributeValues[0].xsStringvalue == 'attr1' + getModifiedEntityNames(entityDescriptorHistory, 0).sort() == expectedModifiedPersistentEntities.sort() + + when: + representation = new EntityDescriptorRepresentation().with { + it.relyingPartyOverrides = [signAssertion: false] + it.attributeRelease = ['attr1', 'attr2'] + it + } + + entityDescriptorHistory = updateAndGetRevisionHistoryOfEntityDescriptor(ed, representation, entityDescriptorService, + entityDescriptorRepository, + txMgr, + entityManager) + + EntityAttributes attrs2 = entityDescriptorService.getEntityAttributes(getTargetEntityForRevisionIndex(entityDescriptorHistory, 1)) + + //Initial revision + attrs = entityDescriptorService.getEntityAttributes(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' + 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/test/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 similarity index 95% rename from backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEntityBasicEnversVersioningTests.groovy rename to backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEntityBasicEnversVersioningTests.groovy index c4f70dbcd..f8ac3b431 100644 --- a/backend/src/test/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 @@ -21,8 +21,6 @@ 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. */ @@ -55,7 +53,7 @@ class MetadataResolverEntityBasicEnversVersioningTests extends Specification { def rev = metadataResolverHistory[0] then: - rev[1].principalUserName == 'anonymous' + rev[1].principalUserName == 'anonymousUser' when: mdr.name = 'Updated' @@ -98,7 +96,7 @@ class MetadataResolverEntityBasicEnversVersioningTests extends Specification { //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)) + 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..d88a604f3 --- /dev/null +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversEntityDescriptorVersionServiceTests.groovy @@ -0,0 +1,118 @@ +package edu.internet2.tier.shibboleth.admin.ui.service.envers + +import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration +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 +import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration +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/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy index 8db64fd67..e6ef57980 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy @@ -9,6 +9,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.HttpMetadataResol import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataQueryProtocolScheme import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ReloadableMetadataResolverAttributes +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.repository.MetadataResolverRepository import edu.internet2.tier.shibboleth.admin.ui.security.model.Role @@ -16,6 +17,7 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.User import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository import edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions + import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Profile import org.springframework.stereotype.Component @@ -32,11 +34,19 @@ class DevConfig { private final MetadataResolverRepository metadataResolverRepository private final EntityDescriptorRepository entityDescriptorRepository - DevConfig(UserRepository adminUserRepository, MetadataResolverRepository metadataResolverRepository, RoleRepository roleRepository, EntityDescriptorRepository entityDescriptorRepository) { + private final OpenSamlObjects openSamlObjects + + DevConfig(UserRepository adminUserRepository, + MetadataResolverRepository metadataResolverRepository, + RoleRepository roleRepository, + EntityDescriptorRepository entityDescriptorRepository, + OpenSamlObjects openSamlObjects) { + this.adminUserRepository = adminUserRepository this.metadataResolverRepository = metadataResolverRepository this.roleRepository = roleRepository this.entityDescriptorRepository = entityDescriptorRepository + this.openSamlObjects = openSamlObjects } @Transactional diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy index 9151e4ef4..620d2252c 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy @@ -1,16 +1,13 @@ package edu.internet2.tier.shibboleth.admin.ui.jsonschema -import edu.internet2.tier.shibboleth.admin.ui.controller.ErrorResponse + import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation import mjson.Json import org.springframework.beans.factory.annotation.Autowired import org.springframework.core.MethodParameter import org.springframework.http.HttpInputMessage -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity import org.springframework.http.converter.HttpMessageConverter import org.springframework.web.bind.annotation.ControllerAdvice -import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter import javax.annotation.PostConstruct diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/ShibbolethUiApplication.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/ShibbolethUiApplication.java index 2bd90bcc3..42e7901e7 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/ShibbolethUiApplication.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/ShibbolethUiApplication.java @@ -23,7 +23,7 @@ @SpringBootApplication @ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "edu.internet2.tier.shibboleth.admin.ui.configuration.auto.*")) -@EntityScan(basePackages = {"edu.internet2.tier.shibboleth.admin.ui.domain", "edu.internet2.tier.shibboleth.admin.ui.security.model"}) +@EntityScan(basePackages = {"edu.internet2.tier.shibboleth.admin.ui.domain", "edu.internet2.tier.shibboleth.admin.ui.envers", "edu.internet2.tier.shibboleth.admin.ui.security.model"}) @EnableJpaAuditing @EnableScheduling @EnableWebSecurity diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/EntitiesVersioningConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/EntitiesVersioningConfiguration.java new file mode 100644 index 000000000..b190f2f5d --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/EntitiesVersioningConfiguration.java @@ -0,0 +1,22 @@ +package edu.internet2.tier.shibboleth.admin.ui.configuration; + +import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService; +import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorVersionService; +import edu.internet2.tier.shibboleth.admin.ui.service.EnversEntityDescriptorVersionService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +@Configuration +public class EntitiesVersioningConfiguration { + + @PersistenceContext + private EntityManager entityManager; + + @Bean + EntityDescriptorVersionService entityDescriptorVersionService(EntityDescriptorService entityDescriptorService) { + return new EnversEntityDescriptorVersionService(entityManager, entityDescriptorService); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java index 80213f5cd..8bb0da84f 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java @@ -2,13 +2,13 @@ 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.domain.versioning.Version; 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.security.model.User; -import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository; -import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService; +import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorVersionService; import org.opensaml.core.xml.io.MarshallingException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,8 +32,8 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import javax.annotation.PostConstruct; -import javax.xml.ws.Response; import java.net.URI; +import java.util.List; import java.util.stream.Collectors; @RestController @@ -52,20 +52,17 @@ public class EntityDescriptorController { @Autowired RestTemplateBuilder restTemplateBuilder; - private UserRepository userRepository; - - private RoleRepository roleRepository; - private UserService userService; private RestTemplate restTemplate; + private EntityDescriptorVersionService versionService; + private static Logger LOGGER = LoggerFactory.getLogger(EntityDescriptorController.class); - public EntityDescriptorController(UserRepository userRepository, RoleRepository roleRepository, UserService userService) { - this.userRepository = userRepository; - this.roleRepository = roleRepository; + public EntityDescriptorController(UserService userService, EntityDescriptorVersionService versionService) { this.userService = userService; + this.versionService = versionService; } @PostConstruct @@ -220,6 +217,40 @@ public ResponseEntity deleteOne(@PathVariable String resourceId) { } } + //Versioning endpoints + + @GetMapping("/EntityDescriptor/{resourceId}/Versions") + public ResponseEntity getAllVersions(@PathVariable String resourceId) { + EntityDescriptor ed = entityDescriptorRepository.findByResourceId(resourceId); + if (ed == null) { + return ResponseEntity.notFound().build(); + } + List versions = versionService.findVersionsForEntityDescriptor(resourceId); + if (versions.isEmpty()) { + return ResponseEntity.notFound().build(); + } + if(isAuthorizedFor(ed.getCreatedBy())) { + return ResponseEntity.ok(versions); + } + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + + @GetMapping("/EntityDescriptor/{resourceId}/Versions/{versionId}") + public ResponseEntity getSpecificVersion(@PathVariable String resourceId, @PathVariable String versionId) { + EntityDescriptorRepresentation edRepresentation = + versionService.findSpecificVersionOfEntityDescriptor(resourceId, versionId); + + if (edRepresentation == null) { + return ResponseEntity.notFound().build(); + } + if(isAuthorizedFor(edRepresentation.getCreatedBy())) { + return ResponseEntity.ok(edRepresentation); + } + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + + //Private methods + private static URI getResourceUriFor(EntityDescriptor ed) { return ServletUriComponentsBuilder .fromCurrentServletMapping().path("/api/EntityDescriptor") @@ -267,4 +298,11 @@ private ResponseEntity handleUploadingEntityDescriptorXml(byte[] rawXmlBytes, .body(entityDescriptorService.createRepresentationFromDescriptor(persistedEd)); } + private boolean isAuthorizedFor(String username) { + User u = userService.getCurrentUser(); + return (u != null) && + (u.getRole().equals("ROLE_ADMIN") + || (u.getUsername().equals(username))); + } + } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractAttributeExtensibleXMLObject.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractAttributeExtensibleXMLObject.java index 075280330..2b4208865 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractAttributeExtensibleXMLObject.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractAttributeExtensibleXMLObject.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.AttributeExtensibleXMLObject; import org.opensaml.core.xml.util.AttributeMap; @@ -10,6 +11,7 @@ @MappedSuperclass @EqualsAndHashCode(callSuper = true, exclude={"unknownAttributes"}) +@Audited public abstract class AbstractAttributeExtensibleXMLObject extends AbstractXMLObject implements AttributeExtensibleXMLObject { private transient final AttributeMap unknownAttributes; @@ -24,4 +26,4 @@ public abstract class AbstractAttributeExtensibleXMLObject extends AbstractXMLOb public AttributeMap getUnknownAttributes() { return this.unknownAttributes; } -} \ No newline at end of file +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractAuditable.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractAuditable.java index 1d23d5113..567a6637e 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractAuditable.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractAuditable.java @@ -3,6 +3,7 @@ import lombok.EqualsAndHashCode; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; +import org.hibernate.envers.Audited; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedBy; @@ -22,6 +23,7 @@ @MappedSuperclass @EntityListeners(AuditingEntityListener.class) @EqualsAndHashCode +@Audited public abstract class AbstractAuditable implements Auditable { @Id diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractDescriptor.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractDescriptor.java index 1f6075f24..449cda701 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractDescriptor.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractDescriptor.java @@ -2,6 +2,7 @@ import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; +import org.hibernate.envers.Audited; import org.joda.time.DateTime; import org.opensaml.core.xml.XMLObject; import org.opensaml.saml.saml2.common.CacheableSAMLObject; @@ -18,6 +19,7 @@ @MappedSuperclass @EqualsAndHashCode(callSuper = true) +@Audited public abstract class AbstractDescriptor extends AbstractAttributeExtensibleXMLObject implements CacheableSAMLObject, TimeBoundSAMLObject, SignableXMLObject { private Long cacheDuration; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractElementExtensibleXMLObject.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractElementExtensibleXMLObject.java index d406e8256..03a91e284 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractElementExtensibleXMLObject.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractElementExtensibleXMLObject.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.ElementExtensibleXMLObject; import org.opensaml.core.xml.XMLObject; @@ -20,6 +21,7 @@ @Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) @EqualsAndHashCode(callSuper = true) +@Audited public abstract class AbstractElementExtensibleXMLObject extends AbstractXMLObject implements ElementExtensibleXMLObject { @OneToMany(cascade = CascadeType.ALL) @OrderColumn diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractLangBearingURL.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractLangBearingURL.java index 7f66a06c6..96fb81a69 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractLangBearingURL.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractLangBearingURL.java @@ -1,5 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; +import org.hibernate.envers.Audited; import org.opensaml.saml.saml2.metadata.LocalizedURI; import javax.annotation.Nullable; @@ -10,6 +11,7 @@ @Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@Audited abstract class AbstractLangBearingURL extends XSURI implements LocalizedURI { @Column(name = "informationUrlXmlLang") private String xmlLang; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractXMLObject.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractXMLObject.java index 1e8f4c2a0..3b4ac0d92 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractXMLObject.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AbstractXMLObject.java @@ -3,6 +3,8 @@ import lombok.EqualsAndHashCode; import net.shibboleth.utilities.java.support.collection.LockableClassToInstanceMultiMap; import net.shibboleth.utilities.java.support.xml.QNameSupport; +import org.hibernate.envers.AuditOverride; +import org.hibernate.envers.Audited; import org.opensaml.core.config.ConfigurationService; import org.opensaml.core.xml.Namespace; import org.opensaml.core.xml.NamespaceManager; @@ -30,6 +32,8 @@ @Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) @EqualsAndHashCode(callSuper = true) +@Audited +@AuditOverride(forClass = AbstractAuditable.class) public abstract class AbstractXMLObject extends AbstractAuditable implements XMLObject { private String namespaceURI; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ArtifactResolutionService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ArtifactResolutionService.java index 0d82f8a0d..1231299a7 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ArtifactResolutionService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ArtifactResolutionService.java @@ -1,11 +1,13 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class ArtifactResolutionService extends IndexedEndpoint implements org.opensaml.saml.saml2.metadata.ArtifactResolutionService { } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AssertionConsumerService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AssertionConsumerService.java index a89045075..f63a76fcd 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AssertionConsumerService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AssertionConsumerService.java @@ -1,11 +1,13 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class AssertionConsumerService extends IndexedEndpoint implements org.opensaml.saml.saml2.metadata.AssertionConsumerService { } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Attribute.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Attribute.java index 45e1500e9..1a9a28323 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Attribute.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Attribute.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.XMLObject; import javax.annotation.Nullable; @@ -15,6 +16,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class Attribute extends AbstractAttributeExtensibleXMLObject implements org.opensaml.saml.saml2.core.Attribute { private String name; @@ -25,6 +27,7 @@ public class Attribute extends AbstractAttributeExtensibleXMLObject implements o @OneToMany(cascade = CascadeType.ALL) @OrderColumn + @Audited private List attributeValues = new ArrayList<>(); @Override diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AttributeConsumingService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AttributeConsumingService.java index 5080c1fc0..a1dba47fa 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AttributeConsumingService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AttributeConsumingService.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.schema.XSBooleanValue; import javax.persistence.CascadeType; @@ -14,6 +15,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class AttributeConsumingService extends AbstractXMLObject implements org.opensaml.saml.saml2.metadata.AttributeConsumingService { private int acsIndex; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ContactPerson.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ContactPerson.java index 104dc076f..ca0400f32 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ContactPerson.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ContactPerson.java @@ -1,6 +1,8 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; +import org.hibernate.envers.NotAudited; import org.opensaml.core.xml.XMLObject; import org.opensaml.saml.saml2.metadata.ContactPersonTypeEnumeration; @@ -12,20 +14,24 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class ContactPerson extends AbstractAttributeExtensibleXMLObject implements org.opensaml.saml.saml2.metadata.ContactPerson { private String contactPersonType; @OneToOne(cascade = CascadeType.ALL) + @NotAudited private Extensions extensions; @OneToOne(cascade = CascadeType.ALL) + @NotAudited private Company company; @OneToOne(cascade = CascadeType.ALL) private GivenName givenName; @OneToOne(cascade = CascadeType.ALL) + @NotAudited private SurName surName; @OneToMany(cascade = CascadeType.ALL) @@ -36,6 +42,7 @@ public class ContactPerson extends AbstractAttributeExtensibleXMLObject implemen @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "contactpersn_telenmbr_id") @OrderColumn + @NotAudited private List telephoneNumbers = new ArrayList<>(); @Override diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Description.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Description.java index 81b66542d..db994740a 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Description.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Description.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.annotation.Nullable; import javax.persistence.Column; @@ -8,6 +9,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class Description extends AbstractXMLObject implements org.opensaml.saml.ext.saml2mdui.Description { @Column(name = "descriptionXMLLang") diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/DisplayName.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/DisplayName.java index 564b0b0f6..5ee4815d9 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/DisplayName.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/DisplayName.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.annotation.Nullable; import javax.persistence.Column; @@ -8,6 +9,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class DisplayName extends AbstractXMLObject implements org.opensaml.saml.ext.saml2mdui.DisplayName { @Column(name = "displayNameXMLLan") diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EmailAddress.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EmailAddress.java index 313ebd65e..2cf346579 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EmailAddress.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EmailAddress.java @@ -1,11 +1,14 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.AuditOverride; +import org.hibernate.envers.Audited; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class EmailAddress extends AbstractXMLObject implements org.opensaml.saml.saml2.metadata.EmailAddress { private String address; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EncryptionMethod.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EncryptionMethod.java index 688dea31d..06a47da63 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EncryptionMethod.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EncryptionMethod.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.xmlsec.encryption.KeySize; import org.opensaml.xmlsec.encryption.OAEPparams; @@ -10,6 +11,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class EncryptionMethod extends AbstractElementExtensibleXMLObject implements org.opensaml.saml.saml2.metadata.EncryptionMethod { private String algorithm; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Endpoint.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Endpoint.java index 97094187a..2699895a0 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Endpoint.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Endpoint.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.XMLObject; import javax.annotation.Nonnull; @@ -14,6 +15,7 @@ */ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class Endpoint extends AbstractAttributeExtensibleXMLObject implements org.opensaml.saml.saml2.metadata.Endpoint { private String binding; @@ -72,4 +74,4 @@ public List getUnknownXMLObjects() { public List getUnknownXMLObjects(@Nonnull QName qName) { return null; //TODO } -} \ No newline at end of file +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityAttributes.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityAttributes.java index ed97305fc..d48a3ea6f 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityAttributes.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityAttributes.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.XMLObject; import org.opensaml.saml.saml2.core.Assertion; @@ -16,6 +17,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class EntityAttributes extends AbstractElementExtensibleXMLObject implements org.opensaml.saml.ext.saml2mdattr.EntityAttributes { @OneToMany(cascade = CascadeType.ALL) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java index 2f53e5cc8..dc036fa7f 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java @@ -2,27 +2,21 @@ import com.google.common.base.MoreObjects; import com.google.common.collect.Lists; - import lombok.EqualsAndHashCode; -import org.opensaml.core.config.ConfigurationService; +import org.hibernate.envers.Audited; +import org.hibernate.envers.NotAudited; import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.config.XMLObjectProviderRegistry; -import org.opensaml.core.xml.io.MarshallingException; import org.springframework.util.StringUtils; -import org.w3c.dom.Element; import javax.annotation.Nullable; - -import javax.persistence.JoinColumn; import javax.persistence.CascadeType; import javax.persistence.Entity; +import javax.persistence.JoinColumn; import javax.persistence.OneToMany; import javax.persistence.OneToOne; import javax.persistence.OrderColumn; import javax.persistence.Transient; - import javax.xml.namespace.QName; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -32,7 +26,8 @@ @Entity -@EqualsAndHashCode(callSuper = true) +@EqualsAndHashCode(callSuper = true, exclude={"versionModifiedTimestamp"}) +@Audited public class EntityDescriptor extends AbstractDescriptor implements org.opensaml.saml.saml2.metadata.EntityDescriptor { private String localId; @@ -44,6 +39,8 @@ public class EntityDescriptor extends AbstractDescriptor implements org.opensaml private String resourceId; + private Long versionModifiedTimestamp; + @OneToOne(cascade = CascadeType.ALL) private Organization organization; @@ -58,18 +55,23 @@ public class EntityDescriptor extends AbstractDescriptor implements org.opensaml @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "entitydesc_addlmetdatlocations_id") @OrderColumn + @NotAudited private List additionalMetadataLocations = new ArrayList<>(); @OneToOne(cascade = CascadeType.ALL) + @NotAudited private AuthnAuthorityDescriptor authnAuthorityDescriptor; @OneToOne(cascade = CascadeType.ALL) + @NotAudited private AttributeAuthorityDescriptor attributeAuthorityDescriptor; @OneToOne(cascade = CascadeType.ALL) + @NotAudited private PDPDescriptor pdpDescriptor; @OneToOne(cascade = CascadeType.ALL) + @NotAudited private AffiliationDescriptor affiliationDescriptor; public EntityDescriptor() { @@ -77,6 +79,10 @@ public EntityDescriptor() { this.resourceId = UUID.randomUUID().toString(); } + public void setVersionModifiedTimestamp(Long versionModifiedTimestamp) { + this.versionModifiedTimestamp = versionModifiedTimestamp; + } + //getters and setters @Override public String getID() { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Extensions.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Extensions.java index c1538f463..01dcf4aa8 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Extensions.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Extensions.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.XMLObject; import javax.annotation.Nullable; @@ -13,6 +14,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class Extensions extends AbstractElementExtensibleXMLObject implements org.opensaml.saml.saml2.metadata.Extensions { @Nullable @Override diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/GivenName.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/GivenName.java index 785b60804..612733fc4 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/GivenName.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/GivenName.java @@ -1,11 +1,14 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.AuditOverride; +import org.hibernate.envers.Audited; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class GivenName extends AbstractXMLObject implements org.opensaml.saml.saml2.metadata.GivenName { private String name; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IndexedEndpoint.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IndexedEndpoint.java index e8d8f1551..cc0fdd6d5 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IndexedEndpoint.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IndexedEndpoint.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.schema.XSBooleanValue; import javax.persistence.Entity; @@ -8,6 +9,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class IndexedEndpoint extends Endpoint implements org.opensaml.saml.saml2.metadata.IndexedEndpoint { private Integer endpointIndex; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/InformationURL.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/InformationURL.java index 45d5477d1..e960e8463 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/InformationURL.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/InformationURL.java @@ -1,10 +1,12 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class InformationURL extends AbstractLangBearingURL implements org.opensaml.saml.ext.saml2mdui.InformationURL { } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/KeyDescriptor.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/KeyDescriptor.java index 74ddbaa2c..2b07eea4c 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/KeyDescriptor.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/KeyDescriptor.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.XMLObject; import org.opensaml.security.credential.UsageType; import org.opensaml.xmlsec.signature.KeyInfo; @@ -12,6 +13,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class KeyDescriptor extends AbstractXMLObject implements org.opensaml.saml.saml2.metadata.KeyDescriptor { @Column(name = "keyDescriptorName") diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/KeyInfo.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/KeyInfo.java index 7ea604d17..7497e5837 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/KeyInfo.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/KeyInfo.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.XMLObject; import org.opensaml.xmlsec.encryption.AgreementMethod; import org.opensaml.xmlsec.encryption.EncryptedKey; @@ -29,6 +30,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class KeyInfo extends AbstractXMLObject implements org.opensaml.xmlsec.signature.KeyInfo { @OneToMany(cascade = CascadeType.ALL) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/LocalizedName.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/LocalizedName.java index 2aa493293..e0a54d494 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/LocalizedName.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/LocalizedName.java @@ -1,9 +1,12 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; +import org.hibernate.envers.Audited; + import javax.annotation.Nullable; import javax.persistence.MappedSuperclass; @MappedSuperclass +@Audited public class LocalizedName extends AbstractXMLObject implements org.opensaml.saml.saml2.metadata.LocalizedName { private String xMLLang; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Logo.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Logo.java index b9173bb22..13453438a 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Logo.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Logo.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.annotation.Nullable; import javax.persistence.Column; @@ -8,6 +9,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class Logo extends AbstractXMLObject implements org.opensaml.saml.ext.saml2mdui.Logo { @Column(name = "logUrl") private String url; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ManageNameIDService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ManageNameIDService.java index ec43780d3..70924db96 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ManageNameIDService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ManageNameIDService.java @@ -1,11 +1,13 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class ManageNameIDService extends Endpoint implements org.opensaml.saml.saml2.metadata.ManageNameIDService { } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/NameIDFormat.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/NameIDFormat.java index 4e63ff301..148ec91e7 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/NameIDFormat.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/NameIDFormat.java @@ -1,11 +1,13 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class NameIDFormat extends AbstractXMLObject implements org.opensaml.saml.saml2.metadata.NameIDFormat { private String format; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Organization.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Organization.java index 1e11b499e..7ea76b1f0 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Organization.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Organization.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.XMLObject; import javax.annotation.Nullable; @@ -11,6 +12,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class Organization extends AbstractAttributeExtensibleXMLObject implements org.opensaml.saml.saml2.metadata.Organization { @OneToOne(cascade = CascadeType.ALL) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/OrganizationDisplayName.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/OrganizationDisplayName.java index 8d473aa82..971217fd8 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/OrganizationDisplayName.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/OrganizationDisplayName.java @@ -1,11 +1,13 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class OrganizationDisplayName extends LocalizedName implements org.opensaml.saml.saml2.metadata.OrganizationDisplayName { } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/OrganizationName.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/OrganizationName.java index edc9317c1..5af2408b9 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/OrganizationName.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/OrganizationName.java @@ -1,11 +1,13 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class OrganizationName extends LocalizedName implements org.opensaml.saml.saml2.metadata.OrganizationName { } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/OrganizationURL.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/OrganizationURL.java index 6af0d7318..1cf739ece 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/OrganizationURL.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/OrganizationURL.java @@ -1,12 +1,14 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.annotation.Nullable; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class OrganizationURL extends AbstractXMLObject implements org.opensaml.saml.saml2.metadata.OrganizationURL { private String xMLLang; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/PrivacyStatementURL.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/PrivacyStatementURL.java index a6d792d80..a98f5a7ad 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/PrivacyStatementURL.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/PrivacyStatementURL.java @@ -1,10 +1,12 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class PrivacyStatementURL extends AbstractLangBearingURL implements org.opensaml.saml.ext.saml2mdui.PrivacyStatementURL { } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RequestedAttribute.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RequestedAttribute.java index a7860e931..17c932ed2 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RequestedAttribute.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RequestedAttribute.java @@ -1,12 +1,14 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.schema.XSBooleanValue; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class RequestedAttribute extends Attribute implements org.opensaml.saml.saml2.metadata.RequestedAttribute { private boolean isRequired; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RoleDescriptor.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RoleDescriptor.java index fc235c110..b5bc78bc6 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RoleDescriptor.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RoleDescriptor.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.util.AttributeMap; @@ -25,6 +26,7 @@ @Entity @EqualsAndHashCode(callSuper = true, exclude={"unknownAttributes"}) +@Audited public class RoleDescriptor extends AbstractDescriptor implements org.opensaml.saml.saml2.metadata.RoleDescriptor { @ElementCollection diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SPSSODescriptor.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SPSSODescriptor.java index 379c2c928..e90542c5a 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SPSSODescriptor.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SPSSODescriptor.java @@ -2,6 +2,7 @@ import com.google.common.collect.Lists; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.schema.XSBooleanValue; @@ -14,6 +15,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class SPSSODescriptor extends SSODescriptor implements org.opensaml.saml.saml2.metadata.SPSSODescriptor { private Boolean isAuthnRequestsSigned; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SSODescriptor.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SSODescriptor.java index 1f34fc931..4e885581d 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SSODescriptor.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SSODescriptor.java @@ -2,6 +2,7 @@ import com.google.common.collect.Lists; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.XMLObject; import javax.annotation.Nullable; @@ -17,6 +18,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class SSODescriptor extends RoleDescriptor implements org.opensaml.saml.saml2.metadata.SSODescriptor { @OneToMany(cascade = CascadeType.ALL) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ServiceDescription.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ServiceDescription.java index 4c3a54511..9fda942ae 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ServiceDescription.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ServiceDescription.java @@ -1,11 +1,13 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class ServiceDescription extends LocalizedName implements org.opensaml.saml.saml2.metadata.ServiceDescription { } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ServiceName.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ServiceName.java index b4047f0c8..7e23c2b67 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ServiceName.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ServiceName.java @@ -1,11 +1,13 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class ServiceName extends LocalizedName implements org.opensaml.saml.saml2.metadata.ServiceName { } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SingleLogoutService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SingleLogoutService.java index 67967676e..ca9aa095c 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SingleLogoutService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SingleLogoutService.java @@ -1,11 +1,13 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class SingleLogoutService extends Endpoint implements org.opensaml.saml.saml2.metadata.SingleLogoutService { } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/UIInfo.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/UIInfo.java index f7912aa65..241eebfac 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/UIInfo.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/UIInfo.java @@ -3,6 +3,7 @@ import lombok.EqualsAndHashCode; import org.hibernate.annotations.Cascade; import org.hibernate.annotations.CascadeType; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.XMLObject; import org.opensaml.saml.ext.saml2mdui.Description; import org.opensaml.saml.ext.saml2mdui.DisplayName; @@ -22,6 +23,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class UIInfo extends AbstractXMLObject implements org.opensaml.saml.ext.saml2mdui.UIInfo { @OneToMany @Cascade(CascadeType.ALL) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/X509Certificate.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/X509Certificate.java index 2fe5ea571..b1db21b72 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/X509Certificate.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/X509Certificate.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.annotation.Nullable; import javax.persistence.Column; @@ -9,6 +10,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class X509Certificate extends AbstractXMLObject implements org.opensaml.xmlsec.signature.X509Certificate { @Column(name = "x509CertificateValue") @Lob diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/X509Data.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/X509Data.java index 46c58324a..e875932cd 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/X509Data.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/X509Data.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.XMLObject; import org.opensaml.xmlsec.signature.X509CRL; import org.opensaml.xmlsec.signature.X509Certificate; @@ -23,6 +24,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class X509Data extends AbstractXMLObject implements org.opensaml.xmlsec.signature.X509Data { @OneToMany(cascade = CascadeType.ALL) @OrderColumn diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSAny.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSAny.java index 1c1d32f4b..de81fcdf6 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSAny.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSAny.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.util.AttributeMap; import javax.annotation.Nonnull; @@ -10,6 +11,7 @@ @Entity @EqualsAndHashCode(callSuper = true, exclude = {"unknownAttributes"}) +@Audited public class XSAny extends AbstractElementExtensibleXMLObject implements org.opensaml.core.xml.schema.XSAny { private String textContext; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSBase64Binary.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSBase64Binary.java index 0d886d52a..edfd8eec8 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSBase64Binary.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSBase64Binary.java @@ -1,12 +1,14 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.annotation.Nullable; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class XSBase64Binary extends AbstractXMLObject implements org.opensaml.core.xml.schema.XSBase64Binary { private String b64value; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSBoolean.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSBoolean.java index 72399bf06..8b0f258d1 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSBoolean.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSBoolean.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.opensaml.core.xml.schema.XSBooleanValue; import javax.annotation.Nullable; @@ -9,6 +10,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class XSBoolean extends AbstractXMLObject implements org.opensaml.core.xml.schema.XSBoolean { private String storedValue; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSDateTime.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSDateTime.java index 0ea3116f8..1b13a3cd4 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSDateTime.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSDateTime.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import org.joda.time.DateTime; import org.joda.time.chrono.ISOChronology; import org.joda.time.format.DateTimeFormatter; @@ -13,6 +14,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class XSDateTime extends AbstractXMLObject implements org.opensaml.core.xml.schema.XSDateTime { private DateTime dateTime; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSInteger.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSInteger.java index 294d0a575..cd6c205da 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSInteger.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSInteger.java @@ -1,12 +1,14 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.annotation.Nullable; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class XSInteger extends AbstractXMLObject implements org.opensaml.core.xml.schema.XSInteger { private int intValue; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSQName.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSQName.java index 259e3df36..b210ea7a4 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSQName.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSQName.java @@ -2,6 +2,7 @@ import lombok.EqualsAndHashCode; import net.shibboleth.utilities.java.support.xml.QNameSupport; +import org.hibernate.envers.Audited; import javax.annotation.Nullable; import javax.persistence.Entity; @@ -10,6 +11,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class XSQName extends AbstractXMLObject implements org.opensaml.core.xml.schema.XSQName { @Nullable @Override diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSString.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSString.java index f35e8e287..dbc0d4c38 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSString.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSString.java @@ -1,12 +1,14 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.annotation.Nullable; import javax.persistence.Entity; @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class XSString extends AbstractXMLObject implements org.opensaml.core.xml.schema.XSString { private String xsStringvalue; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSURI.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSURI.java index 2780c5472..8663bf818 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSURI.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/XSURI.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.EqualsAndHashCode; +import org.hibernate.envers.Audited; import javax.annotation.Nullable; import javax.persistence.Column; @@ -8,6 +9,7 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited public class XSURI extends AbstractXMLObject implements org.opensaml.core.xml.schema.XSURI { @Column(name = "xsuriValue") private String value; 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 4ffadae52..b80b8190f 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 @@ -16,8 +16,6 @@ @Getter @Setter @ToString -@Audited -@AuditOverride(forClass = AbstractAuditable.class) public class FileBackedHttpMetadataResolver extends MetadataResolver { public FileBackedHttpMetadataResolver() { type = "FileBackedHttpMetadataResolver"; 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 ec639ba38..11300c3b7 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.AuditOverride; import org.hibernate.envers.Audited; import javax.persistence.CascadeType; @@ -39,6 +40,7 @@ @JsonSubTypes.Type(value = FilesystemMetadataResolver.class, name = "FilesystemMetadataResolver"), @JsonSubTypes.Type(value = ResourceBackedMetadataResolver.class, name = "ResourceBackedMetadataResolver")}) @Audited +@AuditOverride(forClass = AbstractAuditable.class) public class MetadataResolver extends AbstractAuditable { @JsonProperty("@type") diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/versioning/Version.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/versioning/Version.java new file mode 100644 index 000000000..ce3687507 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/versioning/Version.java @@ -0,0 +1,32 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.versioning; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * Represents version information of any versioned entity in the system. + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@ToString +@EqualsAndHashCode +public class Version implements Serializable { + + private String id; + + private String creator; + + private LocalDateTime date; + + private static final long serialVersionUID = 3429591830989243421L; + +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/envers/PrincipalAwareRevisionEntity.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/envers/PrincipalAwareRevisionEntity.java index 8ee27218f..6a2847b38 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/envers/PrincipalAwareRevisionEntity.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/envers/PrincipalAwareRevisionEntity.java @@ -3,6 +3,7 @@ import lombok.Getter; import lombok.Setter; import org.hibernate.envers.DefaultRevisionEntity; +import org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity; import org.hibernate.envers.RevisionEntity; import javax.persistence.Entity; @@ -14,7 +15,11 @@ @RevisionEntity(PrincipalEnhancingRevisionListener.class) @Getter @Setter -public class PrincipalAwareRevisionEntity extends DefaultRevisionEntity { +public class PrincipalAwareRevisionEntity extends DefaultTrackingModifiedEntitiesRevisionEntity { private String principalUserName; + + public String idAsString() { + return String.valueOf(getId()); + } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/envers/PrincipalEnhancingRevisionListener.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/envers/PrincipalEnhancingRevisionListener.java index 12af196ed..c1895b052 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/envers/PrincipalEnhancingRevisionListener.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/envers/PrincipalEnhancingRevisionListener.java @@ -9,7 +9,7 @@ */ public class PrincipalEnhancingRevisionListener implements RevisionListener { - private static final String ANONYMOUS = "anonymous"; + private static final String ANONYMOUS = "anonymousUser"; @Override public void newRevision(Object revisionEntity) { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityDescriptorVersionService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityDescriptorVersionService.java new file mode 100644 index 000000000..6803796fa --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityDescriptorVersionService.java @@ -0,0 +1,20 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +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.domain.versioning.Version; +import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; + +/** + * API containing operations pertaining to {@link EntityDescriptor} versioning. + */ +public interface EntityDescriptorVersionService { + + List findVersionsForEntityDescriptor(String resourceId); + + EntityDescriptorRepresentation findSpecificVersionOfEntityDescriptor(String resourceId, String versionId); +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EnversEntityDescriptorVersionService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EnversEntityDescriptorVersionService.java new file mode 100644 index 000000000..91dccd5af --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EnversEntityDescriptorVersionService.java @@ -0,0 +1,77 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +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.domain.versioning.Version; +import edu.internet2.tier.shibboleth.admin.ui.envers.PrincipalAwareRevisionEntity; +import org.hibernate.envers.AuditReader; +import org.hibernate.envers.AuditReaderFactory; +import org.hibernate.envers.RevisionEntity; +import org.hibernate.envers.query.AuditEntity; +import org.springframework.data.jpa.repository.JpaContext; + +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.PersistenceContext; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import static java.util.Comparator.comparing; +import static java.util.stream.Collectors.toList; + +/** + * Hibernate Envers based implementation of {@link EntityDescriptorVersionService}. + */ +public class EnversEntityDescriptorVersionService implements EntityDescriptorVersionService { + + private EntityManager entityManager; + + private EntityDescriptorService entityDescriptorService; + + public EnversEntityDescriptorVersionService(EntityManager entityManager, EntityDescriptorService entityDescriptorService) { + this.entityManager = entityManager; + this.entityDescriptorService = entityDescriptorService; + } + + @Override + public List findVersionsForEntityDescriptor(String resourceId) { + List revs = AuditReaderFactory.get(entityManager).createQuery() + .forRevisionsOfEntity(EntityDescriptor.class, false, false) + .add(AuditEntity.property("resourceId").eq(resourceId)) + .getResultList(); + + Object listOfVersions = revs.stream() + .map(it -> ((Object[])it)[1]) + .map(it -> { + return new Version(((PrincipalAwareRevisionEntity) it).idAsString(), + ((PrincipalAwareRevisionEntity) it).getPrincipalUserName(), + ((PrincipalAwareRevisionEntity) it).getRevisionDate() + .toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDateTime()); + }) + .sorted(comparing(Version::getDate)) + .collect(toList()); + + return (List)listOfVersions; + } + + @Override + public EntityDescriptorRepresentation findSpecificVersionOfEntityDescriptor(String resourceId, String versionId) { + try { + Object revision = AuditReaderFactory.get(entityManager).createQuery() + .forEntitiesAtRevision(EntityDescriptor.class, Integer.valueOf(versionId)) + .add(AuditEntity.property("resourceId").eq(resourceId)) + .add(AuditEntity.revisionNumber().eq(Integer.valueOf(versionId))) + .getSingleResult(); + return entityDescriptorService.createRepresentationFromDescriptor((EntityDescriptor) revision); + } + catch (NoResultException e) { + return null; + } + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java index 9bbf5ec2a..ca7a8659e 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java @@ -56,6 +56,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -120,6 +121,10 @@ private EntityDescriptor buildDescriptorFromRepresentation(final EntityDescripto setupLogout(ed, representation); setupRelyingPartyOverrides(ed, representation); + //Let envers recognize update revision type for EntityDescriptor type + //when modifying Attributes and SPSSODescriptor inside RoleDescriptors collection + ed.setVersionModifiedTimestamp(System.currentTimeMillis()); + return ed; } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy index 928cd18ec..89cb89d54 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy @@ -11,6 +11,7 @@ import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorReposit import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService +import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorVersionService import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityDescriptorServiceImpl import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityServiceImpl import edu.internet2.tier.shibboleth.admin.ui.util.RandomGenerator @@ -69,6 +70,7 @@ class EntityDescriptorControllerTests extends Specification { RoleRepository roleRepository = Mock() UserService userService + EntityDescriptorVersionService versionService = Mock() def setup() { generator = new TestObjectGenerator() @@ -78,7 +80,7 @@ class EntityDescriptorControllerTests extends Specification { userService = new UserService(roleRepository, userRepository) service = new JPAEntityDescriptorServiceImpl(openSamlObjects, new JPAEntityServiceImpl(openSamlObjects), userService) - controller = new EntityDescriptorController(userRepository, roleRepository, userService) + controller = new EntityDescriptorController(userService, versionService) controller.entityDescriptorRepository = entityDescriptorRepository controller.openSamlObjects = openSamlObjects controller.entityDescriptorService = service diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/versioning/VersionJsonSerializationBasicTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/versioning/VersionJsonSerializationBasicTests.groovy new file mode 100644 index 000000000..585369249 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/versioning/VersionJsonSerializationBasicTests.groovy @@ -0,0 +1,37 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.versioning + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import spock.lang.Specification + +import java.time.LocalDateTime +import java.time.Month + +class VersionJsonSerializationBasicTests extends Specification { + + ObjectMapper mapper + + def setup() { + mapper = new ObjectMapper() + mapper.registerModule(new JavaTimeModule()) + } + + def "Verify basic Version JSON serialization"() { + given: + def staticDate = LocalDateTime.of(2019, Month.MAY,20,15,0,0) + def version = new Version('2', 'kramer', staticDate) + def expectedJson = """ + { + "id": "2", + "creator": "kramer", + "date": "2019-05-20T15:00" + } + """ + + when: + def deSerialized = mapper.readValue(expectedJson, Version) + + then: + deSerialized == version + } +} diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/EnversTestsSupport.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/EnversTestsSupport.groovy new file mode 100644 index 000000000..ca447e7f0 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/EnversTestsSupport.groovy @@ -0,0 +1,63 @@ +package edu.internet2.tier.shibboleth.admin.ui.repository.envers + +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 edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService +import org.hibernate.envers.AuditReaderFactory +import org.hibernate.envers.query.AuditEntity +import org.hibernate.envers.query.AuditQuery +import org.springframework.transaction.PlatformTransactionManager +import org.springframework.transaction.support.DefaultTransactionDefinition + +import javax.persistence.EntityManager + +import static org.springframework.transaction.TransactionDefinition.PROPAGATION_REQUIRES_NEW + +class EnversTestsSupport { + + //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 + static doInExplicitTransaction(PlatformTransactionManager txMgr, Closure uow) { + def txStatus = txMgr.getTransaction(new DefaultTransactionDefinition(PROPAGATION_REQUIRES_NEW)) + def entity = uow() + txMgr.commit(txStatus) + entity + } + + static updateAndGetRevisionHistoryOfEntityDescriptor(EntityDescriptor ed, EntityDescriptorRepresentation representation, + EntityDescriptorService eds, + EntityDescriptorRepository edr, + PlatformTransactionManager txMgr, + EntityManager em) { + eds.updateDescriptorFromRepresentation(ed, representation) + doInExplicitTransaction(txMgr) { + edr.save(ed) + } + + getRevisionHistoryForEntityType(em, EntityDescriptor, ed.resourceId) + } + + static getRevisionHistoryForEntityType(EntityManager em, Class entityType, String resourceId) { + def auditReader = AuditReaderFactory.get(em) + AuditQuery auditQuery = auditReader + .createQuery() + .forRevisionsOfEntity(entityType, false, false) + .add(AuditEntity.property("resourceId").eq(resourceId)) + auditQuery.resultList + } + + static getTargetEntityForRevisionIndex(List revHistory, int revIndex) { + revHistory[revIndex][0] + } + + static getRevisionEntityForRevisionIndex(List revHistory, int revIndex) { + revHistory[revIndex][1] + } + + static getModifiedEntityNames(List revHistory, int revIndex) { + getRevisionEntityForRevisionIndex(revHistory, revIndex).modifiedEntityNames + } +} diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy index b0626d431..1c1778c60 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy @@ -5,7 +5,6 @@ import edu.internet2.tier.shibboleth.admin.ui.ShibbolethUiApplication 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.domain.EntityDescriptor -import edu.internet2.tier.shibboleth.admin.ui.domain.SPSSODescriptor import edu.internet2.tier.shibboleth.admin.ui.domain.XSAny import edu.internet2.tier.shibboleth.admin.ui.domain.XSAnyBuilder import edu.internet2.tier.shibboleth.admin.ui.domain.XSBoolean @@ -26,7 +25,6 @@ import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService import edu.internet2.tier.shibboleth.admin.ui.util.RandomGenerator import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator import edu.internet2.tier.shibboleth.admin.util.AttributeUtility -import groovy.json.JsonOutput import org.skyscreamer.jsonassert.JSONAssert import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest @@ -37,7 +35,6 @@ import org.xmlunit.builder.DiffBuilder import org.xmlunit.builder.Input import org.xmlunit.diff.DefaultNodeMatcher import org.xmlunit.diff.ElementSelectors -import spock.lang.Ignore import spock.lang.Specification @ContextConfiguration(classes=[CoreShibUiConfiguration, CustomPropertiesConfiguration]) diff --git a/settings.gradle b/settings.gradle index 8fae26617..b3ab3e757 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include 'backend', 'ui', 'pac4j-module' \ No newline at end of file +include 'backend', 'ui', 'pac4j-module'