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 c7792db77..84f6666bf 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -3,7 +3,6 @@ plugins { id 'war' id 'org.springframework.boot' version '2.0.0.RELEASE' id 'com.gorylenko.gradle-git-properties' version '1.4.21' - id 'net.researchgate.release' version '2.6.0' id 'io.franzbecker.gradle-lombok' version '1.13' id 'com.palantir.docker' version '0.20.1' id 'com.palantir.docker-run' version '0.20.1' @@ -47,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 { @@ -175,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) { @@ -211,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') { @@ -267,6 +294,12 @@ task generateSources { xmlSecBuilders.ObjectProviders.ObjectProvider.BuilderClass.each { processLine(it['@className'].toString(), 'src/main/templates/XMLSecBuilderTemplate.java') } + + new XmlSlurper().parse(file('src/main/resources/jpa-saml2-metadata-algorithm-config.xml')).with { builders -> + builders.ObjectProviders.ObjectProvider.BuilderClass.each { + processLine(it['@className'].toString(), 'src/main/templates/AlgorithmBuilderTemplate.java') + } + } } } 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..99b5810ce --- /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/controller/MetadataResolverControllerVersionEndpointsIntegrationTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerVersionEndpointsIntegrationTests.groovy new file mode 100644 index 000000000..f23ea333d --- /dev/null +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerVersionEndpointsIntegrationTests.groovy @@ -0,0 +1,121 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FilesystemMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository +import 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 MetadataResolverControllerVersionEndpointsIntegrationTests extends Specification { + + @Autowired + private TestRestTemplate restTemplate + + @Autowired + MetadataResolverRepository repository + + static BASE_URI = '/api/MetadataResolvers' + + static ALL_VERSIONS_URI = "$BASE_URI/%s/Versions" + + static SPECIFIC_VERSION_URI = "$BASE_URI/%s/Versions/%s" + + def "GET /api/MetadataResolvers/{resourceId}/Versions with non-existent resolver"() { + when: + def result = getAllMetadataResolverVersions('non-existent-resolver-id', String) + + then: + result.statusCodeValue == 404 + } + + def "GET /api/MetadataResolvers/{resourceId}/Versions with 1 resolver version"() { + given: + MetadataResolver mr = new LocalDynamicMetadataResolver(name: 'resolver') + repository.save(mr) + + when: + def result = getAllMetadataResolverVersions(mr.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/MetadataResolvers/{resourceId}/Versions with 2 resolver versions"() { + given: + MetadataResolver mr = new FileBackedHttpMetadataResolver(name: 'resolver') + mr = repository.save(mr) + //Will create a second version for UPDATE revision + mr.name = 'resolverUPDATED' + repository.save(mr) + + when: + def result = getAllMetadataResolverVersions(mr.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/MetadataResolvers/{resourceId}/Versions/{version} for non existent version"() { + given: + MetadataResolver mr = new DynamicHttpMetadataResolver(name: 'resolver') + mr = repository.save(mr) + + when: + def result = getMetadataResolverForVersion(mr.resourceId, '1000', MetadataResolver) + + then: + result.statusCodeValue == 404 + } + + def "GET /api/MetadataResolvers/{resourceId}/Versions/{version} with 2 resolver versions returns correct resolver for specific version"() { + given: + MetadataResolver mr = new FilesystemMetadataResolver(name: 'resolver') + mr = repository.save(mr) + //Will create a second version for UPDATE revision + mr.name = 'resolverUPDATED' + repository.save(mr) + + when: + def allVersions = getAllMetadataResolverVersions(mr.resourceId, List) + def mrv1 = getMetadataResolverForVersion(mr.resourceId, allVersions.body[0].id, MetadataResolver) + def mrv2 = getMetadataResolverForVersion(mr.resourceId, allVersions.body[1].id, MetadataResolver) + + then: + mrv1.statusCodeValue == 200 + mrv1.body.name == 'resolver' + mrv2.statusCodeValue == 200 + mrv2.body.name == 'resolverUPDATED' + } + + private getAllMetadataResolverVersions(String resourceId, responseType) { + this.restTemplate.getForEntity(resourceUriFor(ALL_VERSIONS_URI, resourceId), responseType) + } + + private getMetadataResolverForVersion(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..b7f31270a --- /dev/null +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/EntityDescriptorEnversVersioningTests.groovy @@ -0,0 +1,651 @@ +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/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataFilterEnversVersioningTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataFilterEnversVersioningTests.groovy new file mode 100644 index 000000000..fe1e5eee4 --- /dev/null +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataFilterEnversVersioningTests.groovy @@ -0,0 +1,315 @@ +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.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.filters.EntityAttributesFilter +import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityRoleWhiteListFilter +import edu.internet2.tier.shibboleth.admin.ui.domain.filters.NameIdFormatFilter +import edu.internet2.tier.shibboleth.admin.ui.domain.filters.RequiredValidUntilFilter +import edu.internet2.tier.shibboleth.admin.ui.domain.filters.SignatureValidationFilter +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FilesystemMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ResourceBackedMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.repository.FilterRepository +import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository +import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverVersionService +import 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 + + +/** + * Testing metadata resolver envers versioning with metadata filters + */ +@DataJpaTest +@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, SearchConfiguration, TestConfiguration, EntitiesVersioningConfiguration]) +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") +class MetadataFilterEnversVersioningTests extends Specification { + + @Autowired + MetadataResolverRepository metadataResolverRepository + + @Autowired + FilterRepository filterRepository + + @Autowired + MetadataResolverVersionService metadataResolverVersionService + + @Autowired + PlatformTransactionManager txMgr + + + def "test versioning of MetadataResolver with EntityRoleWhiteListFilter"() { + when: 'Add initial filter' + def mr = new LocalDynamicMetadataResolver(name: 'resolver') + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + EntityRoleWhiteListFilter filter = new EntityRoleWhiteListFilter().with { + it.retainedRoles = ['role1'] + it + } + mr.metadataFilters.add(filter) + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + def versions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + def mrv1 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[0].id) + def mrv2 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[1].id) + + then: + versions.size() == 2 + mrv1.metadataFilters.size() == 0 + mrv2.metadataFilters.size() == 1 + + when: 'Update filter' + filter = mr.metadataFilters[0] + filter.retainedRoles = ['role1', 'role2'] + filter.removeEmptyEntitiesDescriptors = false + filter = EnversTestsSupport.doInExplicitTransaction(txMgr) { + filterRepository.save(filter) + } + mr.markAsModified() + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + versions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + mrv1 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[0].id) + mrv2 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[1].id) + def mrv3 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[2].id) + + then: + versions.size() == 3 + mrv1.metadataFilters.size() == 0 + mrv2.metadataFilters[0].retainedRoles.size() == 1 + mrv2.metadataFilters[0].retainedRoles == ['role1'] + mrv3.metadataFilters[0].retainedRoles.size() == 2 + mrv3.metadataFilters[0].retainedRoles == ['role1','role2'] + mrv3.metadataFilters[0].removeEmptyEntitiesDescriptors == false + } + + def "test versioning of MetadataResolver with EntityAttributesFilter"() { + when: 'Add initial filter' + def mr = new FileBackedHttpMetadataResolver(name: 'resolver') + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + EntityAttributesFilter filter = new EntityAttributesFilter().with { + it.attributeRelease = ['attr1'] + it + } + mr.metadataFilters.add(filter) + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + def versions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + def mrv1 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[0].id) + def mrv2 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[1].id) + + then: + versions.size() == 2 + mrv1.metadataFilters.size() == 0 + mrv2.metadataFilters.size() == 1 + + when: 'Update filter' + filter = mr.metadataFilters[0] + filter.attributeRelease = ['attr1, attr2'] + filter = EnversTestsSupport.doInExplicitTransaction(txMgr) { + filterRepository.save(filter) + } + mr.markAsModified() + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + versions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + mrv1 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[0].id) + mrv2 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[1].id) + def mrv3 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[2].id) + + then: + versions.size() == 3 + mrv1.metadataFilters.size() == 0 + mrv2.metadataFilters[0].attributes[0].attributeValues[0].xsStringvalue == 'attr1' + mrv3.metadataFilters[0].attributes[0].attributeValues[0].xsStringvalue == 'attr1, attr2' + } + + def "test versioning of MetadataResolver with SignatureValidationFilter"() { + when: 'Add initial filter' + def mr = new DynamicHttpMetadataResolver(name: 'resolver') + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + SignatureValidationFilter filter = new SignatureValidationFilter().with { + it.certificateFile = 'cert1.file' + it + } + mr.metadataFilters.add(filter) + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + def versions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + def mrv1 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[0].id) + def mrv2 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[1].id) + + then: + versions.size() == 2 + mrv1.metadataFilters.size() == 0 + mrv2.metadataFilters.size() == 1 + mrv2.metadataFilters[0].certificateFile == 'cert1.file' + + when: 'Update filter' + filter = mr.metadataFilters[0] + filter.certificateFile = 'cert2.file' + filter = EnversTestsSupport.doInExplicitTransaction(txMgr) { + filterRepository.save(filter) + } + mr.markAsModified() + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + versions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + mrv1 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[0].id) + mrv2 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[1].id) + def mrv3 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[2].id) + + then: + versions.size() == 3 + mrv1.metadataFilters.size() == 0 + mrv2.metadataFilters[0].certificateFile == 'cert1.file' + mrv3.metadataFilters[0].certificateFile == 'cert2.file' + } + + def "test versioning of MetadataResolver with RequiredValidUntilFilter"() { + when: 'Add initial filter' + def mr = new FilesystemMetadataResolver(name: 'resolver') + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + RequiredValidUntilFilter filter = new RequiredValidUntilFilter().with { + it.maxValidityInterval = "PT1S" + it + } + mr.metadataFilters.add(filter) + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + def versions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + def mrv1 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[0].id) + def mrv2 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[1].id) + + then: + versions.size() == 2 + mrv1.metadataFilters.size() == 0 + mrv2.metadataFilters.size() == 1 + mrv2.metadataFilters[0].maxValidityInterval == 'PT1S' + + when: 'Update filter' + filter = mr.metadataFilters[0] + filter.maxValidityInterval = 'PT30S' + filter = EnversTestsSupport.doInExplicitTransaction(txMgr) { + filterRepository.save(filter) + } + mr.markAsModified() + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + versions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + mrv1 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[0].id) + mrv2 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[1].id) + def mrv3 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[2].id) + + then: + versions.size() == 3 + mrv1.metadataFilters.size() == 0 + mrv2.metadataFilters[0].maxValidityInterval == 'PT1S' + mrv3.metadataFilters[0].maxValidityInterval == 'PT30S' + } + + def "test versioning of MetadataResolver with NameIdFormatFilter"() { + when: 'Add initial filter' + def mr = new ResourceBackedMetadataResolver(name: 'resolver') + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + NameIdFormatFilter filter = new NameIdFormatFilter().with { + it.formats = ['format1'] + it + } + mr.metadataFilters.add(filter) + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + def versions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + def mrv1 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[0].id) + def mrv2 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[1].id) + + then: + versions.size() == 2 + mrv1.metadataFilters.size() == 0 + mrv2.metadataFilters.size() == 1 + mrv2.metadataFilters[0].formats == ['format1'] + + when: 'Update filter' + filter = mr.metadataFilters[0] + filter.formats = ['format1', 'format2'] + filter.removeExistingFormats = true + filter = EnversTestsSupport.doInExplicitTransaction(txMgr) { + filterRepository.save(filter) + } + mr.markAsModified() + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + versions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + mrv1 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[0].id) + mrv2 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[1].id) + def mrv3 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[2].id) + + then: + versions.size() == 3 + mrv1.metadataFilters.size() == 0 + mrv2.metadataFilters[0].formats == ['format1'] + mrv3.metadataFilters[0].formats == ['format1', 'format2'] + mrv3.metadataFilters[0].removeExistingFormats == true + } + + def "test versioning of deleting a filter"() { + when: 'Add initial filter' + def mr = new MetadataResolver(name: 'resolver') + def filter = new EntityRoleWhiteListFilter() + + mr.metadataFilters.add(filter) + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + //And now remove filter + filter = filterRepository.findByResourceId(filter.resourceId) + mr.metadataFilters = [] + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + EnversTestsSupport.doInExplicitTransaction(txMgr) { + filterRepository.delete(filter) + } + + def versions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + def mrv1 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[0].id) + def mrv2 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[1].id) + + then: + versions.size() == 2 + mrv1.metadataFilters.size() == 1 + mrv2.metadataFilters.size() == 0 + } +} 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/repository/envers/MetadataResolverEnversVersioningTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEnversVersioningTests.groovy new file mode 100644 index 000000000..2ba81bcdf --- /dev/null +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEnversVersioningTests.groovy @@ -0,0 +1,318 @@ +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.resolvers.ClasspathMetadataResource +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicMetadataResolverAttributes +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FilesystemMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.HttpMetadataResolverAttributes +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ReloadableMetadataResolverAttributes +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ResourceBackedMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.TemplateScheme +import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects +import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository +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.domain.resolvers.HttpMetadataResolverAttributes.HttpCachingType.file +import static edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.HttpMetadataResolverAttributes.HttpCachingType.none +import static edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.* + +/** + * Testing metadata resolver 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 MetadataResolverEnversVersioningTests extends Specification { + + @Autowired + MetadataResolverRepository metadataResolverRepository + + @Autowired + EntityManager entityManager + + @Autowired + PlatformTransactionManager txMgr + + def "test versioning of LocalDynamicMetadataResolver"() { + setup: + def expectedModifiedPersistentEntities = [LocalDynamicMetadataResolver.name] + + when: + LocalDynamicMetadataResolver resolver = new LocalDynamicMetadataResolver(name: 'ldmr').with { + it.dynamicMetadataResolverAttributes = new DynamicMetadataResolverAttributes() + it + } + def resolverHistory = updateAndGetRevisionHistoryOfMetadataResolver(resolver, + metadataResolverRepository, + LocalDynamicMetadataResolver, + txMgr, + entityManager) + + then: + resolverHistory.size() == 1 + getTargetEntityForRevisionIndex(resolverHistory, 0).name == 'ldmr' + getTargetEntityForRevisionIndex(resolverHistory, 0).dynamicMetadataResolverAttributes.refreshDelayFactor == 0.75 + getRevisionEntityForRevisionIndex(resolverHistory, 0).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(resolverHistory, 0).timestamp > 0L + getModifiedEntityNames(resolverHistory, 0).sort() == expectedModifiedPersistentEntities.sort() + + when: + resolver.name = 'ldmr_UPDATED' + resolver.dynamicMetadataResolverAttributes.refreshDelayFactor = 1.00 + resolverHistory = updateAndGetRevisionHistoryOfMetadataResolver(resolver, + metadataResolverRepository, + LocalDynamicMetadataResolver, + txMgr, + entityManager) + + then: + resolverHistory.size() == 2 + getTargetEntityForRevisionIndex(resolverHistory, 1).name == 'ldmr_UPDATED' + getTargetEntityForRevisionIndex(resolverHistory, 1).dynamicMetadataResolverAttributes.refreshDelayFactor == 1.00 + getRevisionEntityForRevisionIndex(resolverHistory, 1).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(resolverHistory, 1).timestamp > 0L + + //Check the original revision is intact + getTargetEntityForRevisionIndex(resolverHistory, 0).name == 'ldmr' + getTargetEntityForRevisionIndex(resolverHistory, 0).dynamicMetadataResolverAttributes.refreshDelayFactor == 0.75 + getRevisionEntityForRevisionIndex(resolverHistory, 0).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(resolverHistory, 0).timestamp > 0L + } + + def "test versioning of FileBackedHttpMetadataResolver"() { + setup: + def expectedModifiedPersistentEntities = [FileBackedHttpMetadataResolver.name] + + when: + FileBackedHttpMetadataResolver resolver = new FileBackedHttpMetadataResolver(name: 'fbmr').with { + it.httpMetadataResolverAttributes = new HttpMetadataResolverAttributes(proxyUser: 'proxyUser', + proxyPassword: 'proxyPass', + httpCaching: none) + it.reloadableMetadataResolverAttributes = new ReloadableMetadataResolverAttributes(indexesRef: 'indexRef') + it + } + def resolverHistory = updateAndGetRevisionHistoryOfMetadataResolver(resolver, + metadataResolverRepository, + FileBackedHttpMetadataResolver, + txMgr, + entityManager) + + then: + resolverHistory.size() == 1 + getTargetEntityForRevisionIndex(resolverHistory, 0).name == 'fbmr' + getTargetEntityForRevisionIndex(resolverHistory, 0).httpMetadataResolverAttributes.proxyUser == 'proxyUser' + getTargetEntityForRevisionIndex(resolverHistory, 0).httpMetadataResolverAttributes.proxyPassword == 'proxyPass' + getTargetEntityForRevisionIndex(resolverHistory, 0).httpMetadataResolverAttributes.httpCaching == none + getTargetEntityForRevisionIndex(resolverHistory, 0).reloadableMetadataResolverAttributes.indexesRef == 'indexRef' + getRevisionEntityForRevisionIndex(resolverHistory, 0).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(resolverHistory, 0).timestamp > 0L + getModifiedEntityNames(resolverHistory, 0).sort() == expectedModifiedPersistentEntities.sort() + + when: + resolver.name = 'fbmrUPDATED' + resolver.httpMetadataResolverAttributes.proxyUser = 'proxyUserUPDATED' + resolver.httpMetadataResolverAttributes.proxyPassword = 'proxyPassUPDATED' + resolver.httpMetadataResolverAttributes.httpCaching = file + resolver.reloadableMetadataResolverAttributes.indexesRef = 'indexRefUPDATED' + + resolverHistory = updateAndGetRevisionHistoryOfMetadataResolver(resolver, + metadataResolverRepository, + FileBackedHttpMetadataResolver, + txMgr, + entityManager) + + then: + resolverHistory.size() == 2 + getTargetEntityForRevisionIndex(resolverHistory, 1).name == 'fbmrUPDATED' + getTargetEntityForRevisionIndex(resolverHistory, 1).httpMetadataResolverAttributes.proxyUser == 'proxyUserUPDATED' + getTargetEntityForRevisionIndex(resolverHistory, 1).httpMetadataResolverAttributes.proxyPassword == 'proxyPassUPDATED' + getTargetEntityForRevisionIndex(resolverHistory, 1).httpMetadataResolverAttributes.httpCaching == file + getTargetEntityForRevisionIndex(resolverHistory, 1).reloadableMetadataResolverAttributes.indexesRef == 'indexRefUPDATED' + getRevisionEntityForRevisionIndex(resolverHistory, 1).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(resolverHistory, 1).timestamp > 0L + getModifiedEntityNames(resolverHistory, 1).sort() == expectedModifiedPersistentEntities.sort() + + //Check the original revision is intact + getTargetEntityForRevisionIndex(resolverHistory, 0).name == 'fbmr' + getTargetEntityForRevisionIndex(resolverHistory, 0).httpMetadataResolverAttributes.proxyUser == 'proxyUser' + getTargetEntityForRevisionIndex(resolverHistory, 0).httpMetadataResolverAttributes.proxyPassword == 'proxyPass' + getTargetEntityForRevisionIndex(resolverHistory, 0).httpMetadataResolverAttributes.httpCaching == none + getTargetEntityForRevisionIndex(resolverHistory, 0).reloadableMetadataResolverAttributes.indexesRef == 'indexRef' + getRevisionEntityForRevisionIndex(resolverHistory, 0).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(resolverHistory, 0).timestamp > 0L + } + + def "test versioning of DynamicHttpMetadataResolver"() { + setup: + def expectedModifiedPersistentEntities = [DynamicHttpMetadataResolver.name, + TemplateScheme.name] + + when: + DynamicHttpMetadataResolver resolver = new DynamicHttpMetadataResolver(name: 'dhmr').with { + it.metadataRequestURLConstructionScheme = new TemplateScheme().with { + it.content = 'content' + it + } + it + } + + def resolverHistory = updateAndGetRevisionHistoryOfMetadataResolver(resolver, + metadataResolverRepository, + DynamicHttpMetadataResolver, + txMgr, + entityManager) + + then: + resolverHistory.size() == 1 + getTargetEntityForRevisionIndex(resolverHistory, 0).name == 'dhmr' + getRevisionEntityForRevisionIndex(resolverHistory, 0).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(resolverHistory, 0).timestamp > 0L + getModifiedEntityNames(resolverHistory, 0).sort() == expectedModifiedPersistentEntities.sort() + + when: + resolver.name = 'dhmrUPDATED' + resolver.metadataRequestURLConstructionScheme.content = 'Updated content' + + resolverHistory = updateAndGetRevisionHistoryOfMetadataResolver(resolver, + metadataResolverRepository, + DynamicHttpMetadataResolver, + txMgr, + entityManager) + + then: + resolverHistory.size() == 2 + getTargetEntityForRevisionIndex(resolverHistory, 1).name == 'dhmrUPDATED' + getTargetEntityForRevisionIndex(resolverHistory, 1).metadataRequestURLConstructionScheme.content == 'Updated content' + getRevisionEntityForRevisionIndex(resolverHistory, 1).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(resolverHistory, 1).timestamp > 0L + getModifiedEntityNames(resolverHistory, 1).sort() == expectedModifiedPersistentEntities.sort() + + //Check the original revision is intact + getTargetEntityForRevisionIndex(resolverHistory, 0).name == 'dhmr' + getRevisionEntityForRevisionIndex(resolverHistory, 0).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(resolverHistory, 0).timestamp > 0L + getModifiedEntityNames(resolverHistory, 0).sort() == expectedModifiedPersistentEntities.sort() + } + + def "test versioning of FilesystemMetadataResolver"() { + setup: + def expectedModifiedPersistentEntities = [FilesystemMetadataResolver.name] + + when: + FilesystemMetadataResolver resolver = new FilesystemMetadataResolver(name: 'fmr', metadataFile: 'metadata.xml').with { + it.reloadableMetadataResolverAttributes = new ReloadableMetadataResolverAttributes(indexesRef: 'indexesRef') + it + } + + def resolverHistory = updateAndGetRevisionHistoryOfMetadataResolver(resolver, + metadataResolverRepository, + FilesystemMetadataResolver, + txMgr, + entityManager) + + then: + resolverHistory.size() == 1 + getTargetEntityForRevisionIndex(resolverHistory, 0).name == 'fmr' + getTargetEntityForRevisionIndex(resolverHistory, 0).metadataFile == 'metadata.xml' + getTargetEntityForRevisionIndex(resolverHistory, 0).reloadableMetadataResolverAttributes.indexesRef == 'indexesRef' + getRevisionEntityForRevisionIndex(resolverHistory, 0).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(resolverHistory, 0).timestamp > 0L + getModifiedEntityNames(resolverHistory, 0).sort() == expectedModifiedPersistentEntities.sort() + + when: + resolver.name = 'fmrUPDATED' + resolver.metadataFile = 'metadataUPDATED.xml' + resolver.reloadableMetadataResolverAttributes.indexesRef = 'indexesRefUPDATED' + + resolverHistory = updateAndGetRevisionHistoryOfMetadataResolver(resolver, + metadataResolverRepository, + FilesystemMetadataResolver, + txMgr, + entityManager) + + then: + resolverHistory.size() == 2 + getTargetEntityForRevisionIndex(resolverHistory, 1).name == 'fmrUPDATED' + getTargetEntityForRevisionIndex(resolverHistory, 1).metadataFile == 'metadataUPDATED.xml' + getTargetEntityForRevisionIndex(resolverHistory, 1).reloadableMetadataResolverAttributes.indexesRef == 'indexesRefUPDATED' + getRevisionEntityForRevisionIndex(resolverHistory, 1).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(resolverHistory, 1).timestamp > 0L + getModifiedEntityNames(resolverHistory, 1).sort() == expectedModifiedPersistentEntities.sort() + + //Check the original revision is intact + getTargetEntityForRevisionIndex(resolverHistory, 0).name == 'fmr' + getTargetEntityForRevisionIndex(resolverHistory, 0).metadataFile == 'metadata.xml' + getTargetEntityForRevisionIndex(resolverHistory, 0).reloadableMetadataResolverAttributes.indexesRef == 'indexesRef' + getRevisionEntityForRevisionIndex(resolverHistory, 0).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(resolverHistory, 0).timestamp > 0L + } + + def "test versioning of ResourceBackedMetadataResolver"() { + setup: + def expectedModifiedPersistentEntities = [ResourceBackedMetadataResolver.name] + + when: + ResourceBackedMetadataResolver resolver = new ResourceBackedMetadataResolver(name: 'rbmr').with { + it.reloadableMetadataResolverAttributes = new ReloadableMetadataResolverAttributes(taskTimerRef: 'taskTimerRef') + it.classpathMetadataResource = new ClasspathMetadataResource(file: 'metadata.xml') + it + } + + def resolverHistory = updateAndGetRevisionHistoryOfMetadataResolver(resolver, + metadataResolverRepository, + ResourceBackedMetadataResolver, + txMgr, + entityManager) + + then: + resolverHistory.size() == 1 + getTargetEntityForRevisionIndex(resolverHistory, 0).name == 'rbmr' + getTargetEntityForRevisionIndex(resolverHistory, 0).reloadableMetadataResolverAttributes.taskTimerRef == 'taskTimerRef' + getTargetEntityForRevisionIndex(resolverHistory, 0).classpathMetadataResource.file == 'metadata.xml' + getRevisionEntityForRevisionIndex(resolverHistory, 0).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(resolverHistory, 0).timestamp > 0L + getModifiedEntityNames(resolverHistory, 0).sort() == expectedModifiedPersistentEntities.sort() + + when: + resolver.name = 'rbmrUPDATED' + resolver.reloadableMetadataResolverAttributes.taskTimerRef = 'taskTimerRefUPDATED' + resolver.classpathMetadataResource.file = 'metadataUPDATED.xml' + + resolverHistory = updateAndGetRevisionHistoryOfMetadataResolver(resolver, + metadataResolverRepository, + ResourceBackedMetadataResolver, + txMgr, + entityManager) + + then: + resolverHistory.size() == 2 + getTargetEntityForRevisionIndex(resolverHistory, 1).name == 'rbmrUPDATED' + getTargetEntityForRevisionIndex(resolverHistory, 1).reloadableMetadataResolverAttributes.taskTimerRef == 'taskTimerRefUPDATED' + getTargetEntityForRevisionIndex(resolverHistory, 1).classpathMetadataResource.file == 'metadataUPDATED.xml' + getRevisionEntityForRevisionIndex(resolverHistory, 1).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(resolverHistory, 1).timestamp > 0L + getModifiedEntityNames(resolverHistory, 1).sort() == expectedModifiedPersistentEntities.sort() + + //Check the original revision is intact + getTargetEntityForRevisionIndex(resolverHistory, 0).name == 'rbmr' + getTargetEntityForRevisionIndex(resolverHistory, 0).reloadableMetadataResolverAttributes.taskTimerRef == 'taskTimerRef' + getTargetEntityForRevisionIndex(resolverHistory, 0).classpathMetadataResource.file == 'metadata.xml' + getRevisionEntityForRevisionIndex(resolverHistory, 0).principalUserName == 'anonymousUser' + getRevisionEntityForRevisionIndex(resolverHistory, 0).timestamp > 0L + } +} 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..0289be502 --- /dev/null +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversEntityDescriptorVersionServiceTests.groovy @@ -0,0 +1,122 @@ +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.repository.envers.EnversTestsSupport +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.EnableJpaAuditing +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.ZonedDateTime + + +@DataJpaTest +@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, TestConfiguration, SearchConfiguration, EntitiesVersioningConfiguration]) +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") +@EnableJpaAuditing +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 = 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 < ZonedDateTime.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/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversMetadataResolverVersionServiceTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversMetadataResolverVersionServiceTests.groovy new file mode 100644 index 000000000..97b37f10e --- /dev/null +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversMetadataResolverVersionServiceTests.groovy @@ -0,0 +1,137 @@ +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.resolvers.DynamicHttpMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FilesystemMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ResourceBackedMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository +import edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport +import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverVersionService +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.EnableJpaAuditing +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.ZonedDateTime + + +@DataJpaTest +@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, TestConfiguration, SearchConfiguration, EntitiesVersioningConfiguration]) +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") +@EnableJpaAuditing +class EnversMetadataResolverVersionServiceTests extends Specification { + + @Autowired + MetadataResolverVersionService metadataResolverVersionService + + @Autowired + MetadataResolverRepository metadataResolverRepository + + @Autowired + PlatformTransactionManager txMgr + + def "versioning service returns correct number of versions sorted by modified date in natural order"() { + when: 'Initial version' + MetadataResolver mr = new LocalDynamicMetadataResolver(name: 'ldmr') + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + def versions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + + then: + versions.size() == 1 + versions[0].id + versions[0].creator + versions[0].date < ZonedDateTime.now() + + when: 'Second version' + mr.name = 'ldmr2' + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + versions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.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' + mr.name = 'ldmr3' + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + versions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.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 metadata resolver for version number"() { + when: 'Initial version' + MetadataResolver mr = new FilesystemMetadataResolver(name: 'fsmr') + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + def versions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + def v1Mr = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[0].id) + + then: + v1Mr.name == 'fsmr' + v1Mr.resourceId == mr.resourceId + + when: 'Update the original' + mr.name = 'fsmr2' + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + versions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + def v2Mr = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, versions[1].id) + + then: + v2Mr.name == 'fsmr2' + v2Mr.resourceId == mr.resourceId + } + + def "versioning service returns null for non existent version number"() { + when: 'Initial version' + MetadataResolver mr = new ResourceBackedMetadataResolver(name: 'rbmr') + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + def nonexitentMrVersion = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, '1000') + + then: + !nonexitentMrVersion + } + + def "versioning service returns null for non existent metadata resolver number"() { + when: 'Initial version' + MetadataResolver mr = new DynamicHttpMetadataResolver(name: 'dhmr') + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + def versions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + def nonexitentMr = metadataResolverVersionService.findSpecificVersionOfMetadataResolver('non-existent', versions[0].id) + + then: + !nonexitentMr + } +} diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversVersioningMetadataTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversVersioningMetadataTests.groovy new file mode 100644 index 000000000..9eed8e2bc --- /dev/null +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversVersioningMetadataTests.groovy @@ -0,0 +1,127 @@ +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.domain.resolvers.DynamicHttpMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository +import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository +import edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport +import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorVersionService +import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverVersionService +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.EnableJpaAuditing +import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.test.context.ContextConfiguration +import org.springframework.transaction.PlatformTransactionManager +import spock.lang.Specification + +@DataJpaTest +@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, TestConfiguration, SearchConfiguration, EntitiesVersioningConfiguration]) +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") +@EnableJpaAuditing +class EnversVersioningMetadataTests extends Specification { + + @Autowired + MetadataResolverVersionService metadataResolverVersionService + + @Autowired + MetadataResolverRepository metadataResolverRepository + + @Autowired + EntityDescriptorVersionService entityDescriptorVersionService + + @Autowired + EntityDescriptorRepository entityDescriptorRepository + + @Autowired + PlatformTransactionManager txMgr + + def "versioning service uses versioning metadata from target entities enhanced by boot auditing facility"() { + when: 'Initial versions' + MetadataResolver mr = new LocalDynamicMetadataResolver(name: 'resolver') + EntityDescriptor ed = new EntityDescriptor(serviceProviderName: 'descriptor') + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + ed = EnversTestsSupport.doInExplicitTransaction(txMgr) { + entityDescriptorRepository.save(ed) + } + def mrVersions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + def edVersions = entityDescriptorVersionService.findVersionsForEntityDescriptor(ed.resourceId) + + then: + mrVersions[0].creator == mr.createdBy + mrVersions[0].date == mr.createdDateAsZonedDateTime() + edVersions[0].creator == ed.createdBy + edVersions[0].date == ed.createdDateAsZonedDateTime() + + when: 'new version due to update' + mr.name = 'UPDATED' + ed.serviceProviderName = 'UPDATED' + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + ed = EnversTestsSupport.doInExplicitTransaction(txMgr) { + entityDescriptorRepository.save(ed) + } + mrVersions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + edVersions = entityDescriptorVersionService.findVersionsForEntityDescriptor(ed.resourceId) + + then: + mrVersions[1].creator == mr.modifiedBy + mrVersions[1].date == mr.modifiedDateAsZonedDateTime() + edVersions[1].creator == ed.modifiedBy + edVersions[1].date == ed.modifiedDateAsZonedDateTime() + } + + def "test current version correct logic"() { + when: 'Initial versions' + MetadataResolver mr = new DynamicHttpMetadataResolver(name: 'resolver') + EntityDescriptor ed = new EntityDescriptor(serviceProviderName: 'descriptor') + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + ed = EnversTestsSupport.doInExplicitTransaction(txMgr) { + entityDescriptorRepository.save(ed) + } + def mrVersions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + def edVersions = entityDescriptorVersionService.findVersionsForEntityDescriptor(ed.resourceId) + def mrV1 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, mrVersions[0].id) + def edV1 = entityDescriptorVersionService.findSpecificVersionOfEntityDescriptor(ed.resourceId, edVersions[0].id) + + then: + mrV1.isCurrent() + edV1.isCurrent() + + when: 'new version due to update' + mr.name = 'UPDATED' + ed.serviceProviderName = 'UPDATED' + mr = EnversTestsSupport.doInExplicitTransaction(txMgr) { + metadataResolverRepository.save(mr) + } + ed = EnversTestsSupport.doInExplicitTransaction(txMgr) { + entityDescriptorRepository.save(ed) + } + mrVersions = metadataResolverVersionService.findVersionsForMetadataResolver(mr.resourceId) + edVersions = entityDescriptorVersionService.findVersionsForEntityDescriptor(ed.resourceId) + mrV1 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, mrVersions[0].id) + edV1 = entityDescriptorVersionService.findSpecificVersionOfEntityDescriptor(ed.resourceId, edVersions[0].id) + def mrV2 = metadataResolverVersionService.findSpecificVersionOfMetadataResolver(mr.resourceId, mrVersions[1].id) + def edV2 = entityDescriptorVersionService.findSpecificVersionOfEntityDescriptor(ed.resourceId, edVersions[1].id) + + then: + !mrV1.isCurrent() + !edV1.isCurrent() + mrV2.isCurrent() + edV2.isCurrent() + } +} 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/CoreShibUiConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java index 8f964b96a..da33c53f8 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java @@ -17,6 +17,8 @@ import edu.internet2.tier.shibboleth.admin.ui.service.EntityIdsSearchService; import edu.internet2.tier.shibboleth.admin.ui.service.EntityIdsSearchServiceImpl; import edu.internet2.tier.shibboleth.admin.ui.service.EntityService; +import edu.internet2.tier.shibboleth.admin.ui.service.FileCheckingFileWritingService; +import edu.internet2.tier.shibboleth.admin.ui.service.FileWritingService; import edu.internet2.tier.shibboleth.admin.ui.service.FilterService; import edu.internet2.tier.shibboleth.admin.ui.service.FilterTargetService; import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityDescriptorServiceImpl; @@ -98,13 +100,13 @@ public AttributeUtility attributeUtility() { @Bean @ConditionalOnProperty(name = "shibui.metadata-dir") public EntityDescriptorFilesScheduledTasks entityDescriptorFilesScheduledTasks(EntityDescriptorRepository entityDescriptorRepository, @Value("${shibui.metadata-dir}") final String metadataDir) { - return new EntityDescriptorFilesScheduledTasks(metadataDir, entityDescriptorRepository, openSamlObjects()); + return new EntityDescriptorFilesScheduledTasks(metadataDir, entityDescriptorRepository, openSamlObjects(), fileWritingService()); } @Bean @ConditionalOnProperty(name = "shibui.metadataProviders.target") public MetadataProvidersScheduledTasks metadataProvidersScheduledTasks(@Value("${shibui.metadataProviders.target}") final Resource resource, final MetadataResolverService metadataResolverService) { - return new MetadataProvidersScheduledTasks(resource, metadataResolverService); + return new MetadataProvidersScheduledTasks(resource, metadataResolverService, fileWritingService()); } @Bean @@ -202,4 +204,9 @@ public ModelRepresentationConversions modelRepresentationConversions() { public UserService userService(RoleRepository roleRepository, UserRepository userRepository) { return new UserService(roleRepository, userRepository); } + + @Bean + public FileWritingService fileWritingService() { + return new FileCheckingFileWritingService(); + } } 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..9341a45c2 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/EntitiesVersioningConfiguration.java @@ -0,0 +1,35 @@ +package edu.internet2.tier.shibboleth.admin.ui.configuration; + +import edu.internet2.tier.shibboleth.admin.ui.envers.EnversVersionServiceSupport; +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 edu.internet2.tier.shibboleth.admin.ui.service.EnversMetadataResolverVersionService; +import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverVersionService; +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 + public EntityDescriptorVersionService entityDescriptorVersionService(EntityDescriptorService entityDescriptorService) { + return new EnversEntityDescriptorVersionService(enversVersionServiceSupport(), entityDescriptorService); + } + + @Bean + public MetadataResolverVersionService metadataResolverVersionService() { + return new EnversMetadataResolverVersionService(enversVersionServiceSupport()); + } + + @Bean + public EnversVersionServiceSupport enversVersionServiceSupport() { + return new EnversVersionServiceSupport(entityManager); + } +} 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/controller/MetadataFiltersController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java index 57d734f77..cbf5051ba 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersController.java @@ -7,16 +7,10 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.filters.RequiredValidUntilFilter; import edu.internet2.tier.shibboleth.admin.ui.domain.filters.SignatureValidationFilter; import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver; -import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSamlFunctionDrivenDynamicHTTPMetadataResolver; -import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSamlLocalDynamicMetadataResolver; import edu.internet2.tier.shibboleth.admin.ui.repository.FilterRepository; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository; import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverService; -import net.shibboleth.utilities.java.support.component.ComponentInitializationException; -import net.shibboleth.utilities.java.support.resolver.ResolverException; -import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver; -import org.opensaml.saml.metadata.resolver.RefreshableMetadataResolver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -36,6 +30,7 @@ import java.net.URI; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.function.Supplier; import java.util.stream.Stream; @@ -102,19 +97,19 @@ public ResponseEntity create(@PathVariable String metadataResolverId, @Reques public ResponseEntity update(@PathVariable String metadataResolverId, @PathVariable String resourceId, @RequestBody MetadataFilter updatedFilter) { - MetadataFilter filterTobeUpdated = filterRepository.findByResourceId(resourceId); - if (filterTobeUpdated == null) { - return ResponseEntity.notFound().build(); - } MetadataResolver metadataResolver = findResolverOrThrowHttp404(metadataResolverId); - // check to make sure that the relationship exists - if (!metadataResolver.getMetadataFilters().contains(filterTobeUpdated)) { - // TODO: find a better response - return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + //Now we operate directly on the filter attached to MetadataResolver, + //Instead of fetching filter separately, to accommodate correct envers versioning with uni-directional one-to-many + Optional filterTobeUpdatedOptional = metadataResolver.getMetadataFilters() + .stream() + .filter(it -> it.getResourceId().equals(resourceId)) + .findFirst(); + if(!filterTobeUpdatedOptional.isPresent()) { + return ResponseEntity.notFound().build(); } - + MetadataFilter filterTobeUpdated = filterTobeUpdatedOptional.get(); if (!resourceId.equals(updatedFilter.getResourceId())) { return new ResponseEntity(HttpStatus.CONFLICT); } @@ -130,6 +125,10 @@ public ResponseEntity update(@PathVariable String metadataResolverId, MetadataFilter persistedFilter = filterRepository.save(filterTobeUpdated); + //To support envers versioning from MetadataResolver side + metadataResolver.markAsModified(); + repository.save(metadataResolver); + // TODO: this is wrong metadataResolverService.reloadFilters(metadataResolver.getResourceId()); @@ -244,4 +243,4 @@ private static URI getResourceUriFor(MetadataResolver mr, String filterResourceI .build() .toUri(); } -} \ No newline at end of file +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java index 952c118c2..dbdc0ddd1 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java @@ -5,10 +5,12 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver; import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolverValidationService; import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSamlChainingMetadataResolver; +import edu.internet2.tier.shibboleth.admin.ui.domain.versioning.Version; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository; import edu.internet2.tier.shibboleth.admin.ui.service.IndexWriterService; import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverConverterService; import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverService; +import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverVersionService; import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolversPositionOrderContainerService; import edu.internet2.tier.shibboleth.admin.util.OpenSamlChainingMetadataResolverUtil; import lombok.extern.slf4j.Slf4j; @@ -69,6 +71,9 @@ public class MetadataResolversController { @Autowired MetadataResolverConverterService metadataResolverConverterService; + @Autowired + MetadataResolverVersionService versionService; + @ExceptionHandler({InvalidTypeIdException.class, IOException.class, HttpMessageNotReadableException.class}) public ResponseEntity unableToParseJson(Exception ex) { return ResponseEntity.badRequest().body(new ErrorResponse(HttpStatus.BAD_REQUEST.toString(), ex.getMessage(), ex.getCause().getMessage())); @@ -149,6 +154,30 @@ public ResponseEntity update(@PathVariable String resourceId, @RequestBody Me return ResponseEntity.ok(persistedResolver); } + //Versioning endpoints + + @GetMapping("/MetadataResolvers/{resourceId}/Versions") + public ResponseEntity getAllVersions(@PathVariable String resourceId) { + MetadataResolver resolver = resolverRepository.findByResourceId(resourceId); + if (resolver == null) { + return ResponseEntity.notFound().build(); + } + List versions = versionService.findVersionsForMetadataResolver(resourceId); + if (versions.isEmpty()) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(versions); + } + + @GetMapping("/MetadataResolvers/{resourceId}/Versions/{versionId}") + public ResponseEntity getSpecificVersion(@PathVariable String resourceId, @PathVariable String versionId) { + MetadataResolver resolver = versionService.findSpecificVersionOfMetadataResolver(resourceId, versionId); + if (resolver == null) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(resolver); + } + @SuppressWarnings("Unchecked") private ResponseEntity validate(MetadataResolver metadataResolver) { ValidationResult validationResult = metadataResolverValidationService.validateIfNecessary(metadataResolver); @@ -159,6 +188,8 @@ private ResponseEntity validate(MetadataResolver metadataResolver) { return null; } + //Private methods + private static URI getResourceUriFor(MetadataResolver resolver) { return ServletUriComponentsBuilder .fromCurrentServletMapping().path("/api/MetadataResolvers/") 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..62bb46df8 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,21 +1,27 @@ 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; import javax.annotation.Nonnull; +import javax.persistence.ElementCollection; import javax.persistence.MappedSuperclass; +import javax.persistence.PostLoad; +import javax.persistence.PrePersist; import javax.persistence.Transient; +import javax.xml.namespace.QName; +import java.util.HashMap; +import java.util.Map; @MappedSuperclass -@EqualsAndHashCode(callSuper = true, exclude={"unknownAttributes"}) +@EqualsAndHashCode(callSuper = true, exclude={"storageAttributeMap"}) +@Audited public abstract class AbstractAttributeExtensibleXMLObject extends AbstractXMLObject implements AttributeExtensibleXMLObject { - - private transient final AttributeMap unknownAttributes; + private transient final AttributeMap unknownAttributes = new AttributeMap(this); AbstractAttributeExtensibleXMLObject() { - unknownAttributes = new AttributeMap(this); } @Nonnull @@ -24,4 +30,17 @@ public abstract class AbstractAttributeExtensibleXMLObject extends AbstractXMLOb public AttributeMap getUnknownAttributes() { return this.unknownAttributes; } + + @ElementCollection + private Map storageAttributeMap = new HashMap<>(); + + @PrePersist + void prePersist() { + this.storageAttributeMap = this.unknownAttributes; + } + + @PostLoad + void postLoad() { + this.unknownAttributes.putAll(this.storageAttributeMap); + } } \ 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..3d2ed1391 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 @@ -1,8 +1,11 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; 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; @@ -15,13 +18,20 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.MappedSuperclass; +import javax.persistence.Transient; import javax.validation.constraints.NotNull; +import java.time.Instant; import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; @MappedSuperclass @EntityListeners(AuditingEntityListener.class) -@EqualsAndHashCode +@EqualsAndHashCode(exclude = {"current"}) +@Audited public abstract class AbstractAuditable implements Auditable { @Id @@ -46,6 +56,9 @@ public abstract class AbstractAuditable implements Auditable { @LastModifiedBy private String modifiedBy; + @Transient + @JsonProperty + private boolean current; @Override public Long getAudId() { @@ -92,4 +105,28 @@ public String getModifiedBy() { public void setModifiedBy(String modifiedBy) { this.modifiedBy = modifiedBy; } + + public ZonedDateTime createdDateAsZonedDateTime() { + return toZonedDateTime(this.createdDate); + } + + public ZonedDateTime modifiedDateAsZonedDateTime() { + return toZonedDateTime(this.modifiedDate); + } + + public boolean isCurrent() { + return this.current; + } + + public void markAsCurrent() { + this.current = true; + } + + private static ZonedDateTime toZonedDateTime(LocalDateTime localDateTime) { + return localDateTime + .atZone(ZoneId.systemDefault()) + .toInstant() + .atOffset(ZoneOffset.UTC) + .toZonedDateTime(); + } } 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/DigestMethod.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/DigestMethod.java new file mode 100644 index 000000000..bb0fe5359 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/DigestMethod.java @@ -0,0 +1,29 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain; + +import lombok.EqualsAndHashCode; + +import javax.annotation.Nullable; +import javax.persistence.Entity; + +@Entity +@EqualsAndHashCode(callSuper = true) +public class DigestMethod extends AbstractElementExtensibleXMLObject implements org.opensaml.saml.ext.saml2alg.DigestMethod { + private String algorithm; + + public DigestMethod() {} + + public DigestMethod(String algorithm) { + this.algorithm = algorithm; + } + + @Nullable + @Override + public String getAlgorithm() { + return null; + } + + @Override + public void setAlgorithm(@Nullable String value) { + + } +} 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/RequestInitiator.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RequestInitiator.java index 78fd0028e..dbd667ff9 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RequestInitiator.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RequestInitiator.java @@ -1,9 +1,20 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; +import lombok.EqualsAndHashCode; import org.opensaml.core.xml.util.AttributeMap; import javax.annotation.Nonnull; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.PostLoad; +import javax.persistence.PrePersist; +import javax.persistence.Transient; +import javax.xml.namespace.QName; +import java.util.HashMap; +import java.util.Map; +@Entity +@EqualsAndHashCode(callSuper = true, exclude = {"storageAttributeMap"}) public class RequestInitiator extends AbstractElementExtensibleXMLObject implements org.opensaml.saml.ext.saml2mdreqinit.RequestInitiator { private String binding; @Override @@ -40,10 +51,25 @@ public void setResponseLocation(String location) { this.responseLocation = location; } + @ElementCollection + private Map storageAttributeMap = new HashMap<>(); + + @Transient private AttributeMap attributeMap = new AttributeMap(this); + @PrePersist + void prePersist() { + this.storageAttributeMap = this.attributeMap; + } + + @PostLoad + void postLoad() { + this.attributeMap.putAll(this.storageAttributeMap); + } + @Nonnull @Override + @Transient public AttributeMap getUnknownAttributes() { return this.attributeMap; } 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/SigningMethod.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SigningMethod.java new file mode 100644 index 000000000..c66996c9b --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/SigningMethod.java @@ -0,0 +1,55 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain; + +import lombok.EqualsAndHashCode; + +import javax.annotation.Nullable; +import javax.persistence.Entity; + +@Entity +@EqualsAndHashCode(callSuper = true) +public class SigningMethod extends AbstractElementExtensibleXMLObject implements org.opensaml.saml.ext.saml2alg.SigningMethod { + private String algorithm; + private Integer minKeySize; + private Integer maxKeySize; + + public SigningMethod() {} + + public SigningMethod(String algorithm, Integer minKeySize, Integer maxKeySize) { + this.algorithm = algorithm; + this.minKeySize = minKeySize; + this.maxKeySize = maxKeySize; + } + + @Nullable + @Override + public String getAlgorithm() { + return this.algorithm; + } + + @Override + public void setAlgorithm(@Nullable String value) { + this.algorithm = value; + } + + @Nullable + @Override + public Integer getMinKeySize() { + return this.minKeySize; + } + + @Override + public void setMinKeySize(@Nullable Integer value) { + this.minKeySize = value; + } + + @Nullable + @Override + public Integer getMaxKeySize() { + return this.maxKeySize; + } + + @Override + public void setMaxKeySize(@Nullable Integer value) { + this.maxKeySize = value; + } +} 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/filters/EntityAttributesFilter.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilter.java index 9d68c39b3..5d6d3132a 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilter.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilter.java @@ -6,6 +6,7 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.hibernate.envers.Audited; import javax.persistence.CascadeType; import javax.persistence.Entity; @@ -28,6 +29,7 @@ @Getter @Setter @ToString +@Audited public class EntityAttributesFilter extends MetadataFilter { public EntityAttributesFilter() { @@ -70,4 +72,4 @@ public void intoTransientRepresentation() { this.attributeRelease = getAttributeReleaseListFromAttributeList(this.attributes); this.relyingPartyOverrides = getRelyingPartyOverridesRepresentationFromAttributeList(this.attributes); } -} \ No newline at end of file +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilterTarget.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilterTarget.java index 5effdae3d..0abbbd11c 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilterTarget.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilterTarget.java @@ -1,8 +1,9 @@ package edu.internet2.tier.shibboleth.admin.ui.domain.filters; -import com.fasterxml.jackson.annotation.JsonIgnore; import edu.internet2.tier.shibboleth.admin.ui.domain.AbstractAuditable; import lombok.EqualsAndHashCode; +import org.hibernate.envers.AuditOverride; +import org.hibernate.envers.Audited; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,6 +15,8 @@ @Entity @EqualsAndHashCode(callSuper = true) +@Audited +@AuditOverride(forClass = AbstractAuditable.class) public class EntityAttributesFilterTarget extends AbstractAuditable { public enum EntityAttributesFilterTargetType { ENTITY, CONDITION_SCRIPT, CONDITION_REF, REGEX diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityRoleWhiteListFilter.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityRoleWhiteListFilter.java index 111b75c2a..26c5e9976 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityRoleWhiteListFilter.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityRoleWhiteListFilter.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.hibernate.envers.Audited; import javax.persistence.CollectionTable; import javax.persistence.Column; @@ -19,6 +20,7 @@ @Getter @Setter @ToString +@Audited public class EntityRoleWhiteListFilter extends MetadataFilter { public EntityRoleWhiteListFilter() { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/MetadataFilter.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/MetadataFilter.java index 5ee500437..b15058cd0 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/MetadataFilter.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/MetadataFilter.java @@ -10,6 +10,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +import org.hibernate.envers.AuditOverride; import org.hibernate.envers.Audited; import javax.persistence.Column; @@ -36,6 +37,7 @@ @JsonSubTypes.Type(value=RequiredValidUntilFilter.class, name="RequiredValidUntil"), @JsonSubTypes.Type(value=NameIdFormatFilter.class, name="NameIDFormat")}) @Audited +@AuditOverride(forClass = AbstractAuditable.class) public class MetadataFilter extends AbstractAuditable { @JsonProperty("@type") @@ -59,4 +61,4 @@ public int getVersion() { } return this.hashCode(); } -} \ No newline at end of file +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/NameIdFormatFilter.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/NameIdFormatFilter.java index 608561407..157e5f766 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/NameIdFormatFilter.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/NameIdFormatFilter.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.hibernate.envers.Audited; import javax.persistence.CascadeType; import javax.persistence.ElementCollection; @@ -17,6 +18,7 @@ @Getter @Setter @ToString +@Audited public class NameIdFormatFilter extends MetadataFilter { public NameIdFormatFilter() { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/NameIdFormatFilterTarget.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/NameIdFormatFilterTarget.java index a346d983f..c8fd10c90 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/NameIdFormatFilterTarget.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/NameIdFormatFilterTarget.java @@ -3,6 +3,8 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.AbstractAuditable; import lombok.EqualsAndHashCode; import lombok.ToString; +import org.hibernate.envers.AuditOverride; +import org.hibernate.envers.Audited; import javax.persistence.ElementCollection; import javax.persistence.Entity; @@ -13,6 +15,8 @@ @Entity @EqualsAndHashCode(callSuper = true) @ToString +@Audited +@AuditOverride(forClass = AbstractAuditable.class) public class NameIdFormatFilterTarget extends AbstractAuditable { public enum NameIdFormatFilterTargetType { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/RequiredValidUntilFilter.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/RequiredValidUntilFilter.java index 1aca84d4b..cda431408 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/RequiredValidUntilFilter.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/RequiredValidUntilFilter.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.hibernate.envers.Audited; import javax.persistence.Entity; @@ -12,6 +13,7 @@ @Getter @Setter @ToString +@Audited public class RequiredValidUntilFilter extends MetadataFilter { public RequiredValidUntilFilter() { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/SignatureValidationFilter.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/SignatureValidationFilter.java index 7e83ae6d4..5cf71225e 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/SignatureValidationFilter.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/SignatureValidationFilter.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.hibernate.envers.Audited; import javax.persistence.Entity; @@ -12,6 +13,7 @@ @Getter @Setter @ToString +@Audited public class SignatureValidationFilter extends MetadataFilter { public SignatureValidationFilter() { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java index e378f5fbb..44f1463c5 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java @@ -1,5 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain.frontend; +import com.fasterxml.jackson.annotation.JsonProperty; + import javax.validation.constraints.NotNull; import java.io.Serializable; import java.time.LocalDateTime; @@ -64,6 +66,9 @@ public EntityDescriptorRepresentation(String id, private String createdBy; + @JsonProperty + private boolean current; + public String getId() { return id; } @@ -112,6 +117,7 @@ public void setMdui(MduiRepresentation mdui) { this.mdui = mdui; } + public ServiceProviderSsoDescriptorRepresentation getServiceProviderSsoDescriptor() { return this.getServiceProviderSsoDescriptor(false); } @@ -213,4 +219,12 @@ public String getCreatedBy() { public void setCreatedBy(String createdBy) { this.createdBy = createdBy; } + + public boolean isCurrent() { + return current; + } + + public void setCurrent(boolean current) { + this.current = current; + } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java index e8deb0e3e..732078747 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java @@ -22,6 +22,7 @@ @Getter @Setter @ToString +@Audited public class DynamicHttpMetadataResolver extends MetadataResolver { public static final String DEFAULT_TIMEOUT = "PT5S"; 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..17fe28ab9 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 @@ -1,11 +1,10 @@ package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers; -import edu.internet2.tier.shibboleth.admin.ui.domain.AbstractAuditable; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import lombok.ToString; -import org.hibernate.envers.AuditOverride; + import org.hibernate.envers.Audited; import javax.persistence.Embedded; @@ -17,7 +16,6 @@ @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/FilesystemMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FilesystemMetadataResolver.java index b96a74c14..ce3319d02 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FilesystemMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FilesystemMetadataResolver.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.hibernate.envers.Audited; import javax.persistence.Embedded; import javax.persistence.Entity; @@ -16,6 +17,7 @@ @Getter @Setter @ToString +@Audited public class FilesystemMetadataResolver extends MetadataResolver { public FilesystemMetadataResolver() { type = "FilesystemMetadataResolver"; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/LocalDynamicMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/LocalDynamicMetadataResolver.java index 6b722803a..49cc09642 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/LocalDynamicMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/LocalDynamicMetadataResolver.java @@ -8,11 +8,14 @@ import javax.persistence.Embedded; import javax.persistence.Entity; +import org.hibernate.envers.Audited; + @Entity @EqualsAndHashCode(callSuper = true) @Getter @Setter @ToString +@Audited public class LocalDynamicMetadataResolver extends MetadataResolver { public LocalDynamicMetadataResolver() { type = "LocalDynamicMetadataResolver"; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataQueryProtocolScheme.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataQueryProtocolScheme.java index cc6dffb98..77b7cc733 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataQueryProtocolScheme.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataQueryProtocolScheme.java @@ -3,6 +3,7 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; +import org.hibernate.envers.Audited; import javax.persistence.Entity; @@ -13,6 +14,7 @@ @Getter @Setter @EqualsAndHashCode(callSuper = true) +@Audited public class MetadataQueryProtocolScheme extends MetadataRequestURLConstructionScheme { public MetadataQueryProtocolScheme() { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataRequestURLConstructionScheme.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataRequestURLConstructionScheme.java index adddcc5aa..af3b0eab5 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataRequestURLConstructionScheme.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataRequestURLConstructionScheme.java @@ -7,6 +7,8 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; +import org.hibernate.envers.AuditOverride; +import org.hibernate.envers.Audited; import javax.persistence.Entity; import javax.persistence.Transient; @@ -24,6 +26,8 @@ @JsonSubTypes({@JsonSubTypes.Type(value=MetadataQueryProtocolScheme.class, name="MetadataQueryProtocol"), @JsonSubTypes.Type(value=TemplateScheme.class, name="Template"), @JsonSubTypes.Type(value=RegexScheme.class, name="Regex")}) +@Audited +@AuditOverride(forClass = AbstractAuditable.class) public abstract class MetadataRequestURLConstructionScheme extends AbstractAuditable { public enum SchemeType { METADATA_QUERY_PROTOCOL("MetadataQueryProtocol"), 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..dc30d40d8 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 @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers; import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; @@ -11,6 +12,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +import org.hibernate.envers.AuditOverride; import org.hibernate.envers.Audited; import javax.persistence.CascadeType; @@ -27,7 +29,7 @@ @Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) -@EqualsAndHashCode(callSuper = true, exclude = {"version"}) +@EqualsAndHashCode(callSuper = true, exclude = {"version", "versionModifiedTimestamp"}) @NoArgsConstructor @Getter @Setter @@ -39,6 +41,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") @@ -70,6 +73,9 @@ public class MetadataResolver extends AbstractAuditable { private Boolean doInitialization = true; + @JsonIgnore + private Long versionModifiedTimestamp; + @OneToMany(cascade = CascadeType.ALL) @OrderColumn private List metadataFilters = new ArrayList<>(); @@ -84,4 +90,8 @@ public int getVersion() { } return this.hashCode(); } + + public void markAsModified() { + this.versionModifiedTimestamp = System.currentTimeMillis(); + } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/RegexScheme.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/RegexScheme.java index a8d4fffaa..ce0202b6c 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/RegexScheme.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/RegexScheme.java @@ -3,6 +3,7 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; +import org.hibernate.envers.Audited; import javax.persistence.Column; import javax.persistence.Entity; @@ -15,6 +16,7 @@ @Getter @Setter @EqualsAndHashCode(callSuper = true) +@Audited public class RegexScheme extends MetadataRequestURLConstructionScheme { public RegexScheme() { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/ResourceBackedMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/ResourceBackedMetadataResolver.java index 55c833f2d..2b480641f 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/ResourceBackedMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/ResourceBackedMetadataResolver.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.hibernate.envers.Audited; import javax.persistence.Embedded; import javax.persistence.Entity; @@ -16,6 +17,7 @@ @Getter @Setter @ToString +@Audited public class ResourceBackedMetadataResolver extends MetadataResolver { public ResourceBackedMetadataResolver() { type = "ResourceBackedMetadataResolver"; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/TemplateScheme.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/TemplateScheme.java index 6cb0c8d90..a176bacd7 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/TemplateScheme.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/TemplateScheme.java @@ -3,6 +3,7 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; +import org.hibernate.envers.Audited; import javax.persistence.Entity; @@ -13,6 +14,7 @@ @Getter @Setter @EqualsAndHashCode(callSuper = true) +@Audited public class TemplateScheme extends MetadataRequestURLConstructionScheme { public TemplateScheme () { 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..100b0fdbd --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/versioning/Version.java @@ -0,0 +1,34 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.versioning; + +import com.fasterxml.jackson.annotation.JsonFormat; +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.ZonedDateTime; + +/** + * 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; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + private ZonedDateTime date; + + private static final long serialVersionUID = 3429591830989243421L; + +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/envers/EnversVersionServiceSupport.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/envers/EnversVersionServiceSupport.java new file mode 100644 index 000000000..bbf399227 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/envers/EnversVersionServiceSupport.java @@ -0,0 +1,74 @@ +package edu.internet2.tier.shibboleth.admin.ui.envers; + +import edu.internet2.tier.shibboleth.admin.ui.domain.AbstractAuditable; +import edu.internet2.tier.shibboleth.admin.ui.domain.versioning.Version; +import org.hibernate.envers.AuditReaderFactory; +import org.hibernate.envers.query.AuditEntity; + +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import java.time.ZoneOffset; +import java.util.List; + +import static java.util.Comparator.comparing; +import static java.util.stream.Collectors.toList; + +/** + * Encapsulates common functionality interfacing with Envers AuditReader low level API + * to query for revisions of various persistent entities. + */ +public class EnversVersionServiceSupport { + + private EntityManager entityManager; + + public EnversVersionServiceSupport(EntityManager entityManager) { + this.entityManager = entityManager; + } + + public List findVersionsForPersistentEntity(String resourceId, Class entityClass) { + List revs = AuditReaderFactory.get(entityManager).createQuery() + .forRevisionsOfEntity(entityClass, false, false) + .add(AuditEntity.property("resourceId").eq(resourceId)) + .getResultList(); + + Object listOfVersions = revs.stream() + .map(it -> { + return new Version(((PrincipalAwareRevisionEntity) ((Object[]) it)[1]).idAsString(), + ((AbstractAuditable) ((Object[]) it)[0]).getModifiedBy(), + ((AbstractAuditable) ((Object[]) it)[0]).modifiedDateAsZonedDateTime()); + }) + .sorted(comparing(Version::getDate)) + .collect(toList()); + + return (List) listOfVersions; + } + + public Object findSpecificVersionOfPersistentEntity(String resourceId, String versionId, Class entityClass) { + try { + AbstractAuditable abstractAuditable = + (AbstractAuditable) AuditReaderFactory.get(entityManager).createQuery() + .forEntitiesAtRevision(entityClass, Integer.valueOf(versionId)) + .add(AuditEntity.property("resourceId").eq(resourceId)) + .add(AuditEntity.revisionNumber().eq(Integer.valueOf(versionId))) + .getSingleResult(); + if(isCurrentRevision(resourceId, versionId, entityClass)) { + abstractAuditable.markAsCurrent(); + } + return abstractAuditable; + } catch (NoResultException e) { + return null; + } + } + + private boolean isCurrentRevision(String resourceId, String versionId, Class entityClass) { + Number revision = (Number) AuditReaderFactory + .get(entityManager) + .createQuery() + .forRevisionsOfEntity(entityClass, false, false) + .addProjection(AuditEntity.revisionNumber().max()) + .add(AuditEntity.property("resourceId").eq(resourceId)) + .getSingleResult(); + + return Integer.valueOf(versionId) == revision.intValue(); + } +} 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..e0aef8807 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 @@ -2,7 +2,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 +14,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/opensaml/config/JPAXMLObjectProviderInitializer.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/opensaml/config/JPAXMLObjectProviderInitializer.java index 2b6718dd1..25000ae67 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/opensaml/config/JPAXMLObjectProviderInitializer.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/opensaml/config/JPAXMLObjectProviderInitializer.java @@ -14,8 +14,8 @@ protected String[] getConfigResources() { "/jpa-schema-config.xml", "/jpa-saml2-metadata-ui-config.xml", "/jpa-signature-config.xml", + "/jpa-saml2-metadata-algorithm-config.xml", "/encryption-config.xml", - "/saml2-metadata-algorithm-config.xml", "/jpa-saml2-metadata-reqinit-config.xml", "/saml2-protocol-config.xml", "/modified-saml2-assertion-config.xml" diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/scheduled/EntityDescriptorFilesScheduledTasks.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/scheduled/EntityDescriptorFilesScheduledTasks.java index d7bb02282..35f819165 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/scheduled/EntityDescriptorFilesScheduledTasks.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/scheduled/EntityDescriptorFilesScheduledTasks.java @@ -4,6 +4,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor; 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.FileWritingService; import org.bouncycastle.util.encoders.Hex; import org.opensaml.core.xml.io.MarshallingException; import org.slf4j.Logger; @@ -34,7 +35,7 @@ * @since 1.0 */ @Configuration -@ConditionalOnProperty(name = "shibui-metadata-dir") +@ConditionalOnProperty(name = "shibui.metadata-dir") public class EntityDescriptorFilesScheduledTasks { private static final Logger LOGGER = LoggerFactory.getLogger(EntityDescriptorFilesScheduledTasks.class); @@ -49,12 +50,16 @@ public class EntityDescriptorFilesScheduledTasks { private static final String TARGET_FILE_TEMPLATE = "%s/%s"; + private final FileWritingService fileWritingService; + public EntityDescriptorFilesScheduledTasks(String metadataDirName, EntityDescriptorRepository entityDescriptorRepository, - OpenSamlObjects openSamlObjects) { + OpenSamlObjects openSamlObjects, + FileWritingService fileWritingService) { this.metadataDirName = metadataDirName; this.entityDescriptorRepository = entityDescriptorRepository; this.openSamlObjects = openSamlObjects; + this.fileWritingService = fileWritingService; } @Scheduled(fixedRateString = "${shibui.taskRunRate:30000}") @@ -71,7 +76,7 @@ public void generateEntityDescriptorFiles() throws MarshallingException { try { String xmlContent = this.openSamlObjects.marshalToXmlString(ed); - Files.write(targetFilePath, xmlContent.getBytes()); + fileWritingService.write(targetFilePath, xmlContent); } catch (MarshallingException | IOException e) { //TODO: any other better way to handle it? LOGGER.error("Error marshalling entity descriptor into a file {} - {}", ed.getEntityID(), e.getMessage()); diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/scheduled/MetadataProvidersScheduledTasks.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/scheduled/MetadataProvidersScheduledTasks.java index 49048f39b..a58b0df24 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/scheduled/MetadataProvidersScheduledTasks.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/scheduled/MetadataProvidersScheduledTasks.java @@ -1,5 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.scheduled; +import edu.internet2.tier.shibboleth.admin.ui.service.FileWritingService; import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,6 +19,7 @@ import javax.xml.transform.stream.StreamResult; import java.io.IOException; import java.io.OutputStream; +import java.io.StringWriter; @Configuration @ConditionalOnProperty("shibui.metadataProviders.target") @@ -26,22 +28,25 @@ public class MetadataProvidersScheduledTasks { private final Resource target; private final MetadataResolverService metadataResolverService; + private final FileWritingService fileWritingService; - public MetadataProvidersScheduledTasks(Resource target, MetadataResolverService metadataResolverService) { + public MetadataProvidersScheduledTasks(Resource target, MetadataResolverService metadataResolverService, FileWritingService fileWritingService) { this.target = target; this.metadataResolverService = metadataResolverService; + this.fileWritingService = fileWritingService; } @Scheduled(fixedRateString = "${shibui.metadataProviders.taskRunRate:30000}") @Transactional(readOnly = true) public void generateMetadataProvidersFile() { - try (OutputStream os = ((WritableResource)target).getOutputStream()) { + try (StringWriter os = new StringWriter()) { Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); transformer.transform(new DOMSource(metadataResolverService.generateConfiguration()), new StreamResult(os)); + this.fileWritingService.write((WritableResource)this.target, os.toString()); } catch (IOException | TransformerException e) { logger.error(e.getLocalizedMessage(), e); } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/DefaultAuditorAware.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/DefaultAuditorAware.java index 080b4312d..9ec1a7eb7 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/DefaultAuditorAware.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/DefaultAuditorAware.java @@ -3,7 +3,6 @@ import org.springframework.data.domain.AuditorAware; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.User; import java.util.Optional; @@ -16,12 +15,14 @@ */ public class DefaultAuditorAware implements AuditorAware { + private static final String ANONYMOUS = "anonymousUser"; + @Override public Optional getCurrentAuditor() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || !authentication.isAuthenticated()) { - return Optional.empty(); + return Optional.of(ANONYMOUS); } - return Optional.of(User.class.cast(authentication.getPrincipal()).getUsername()); + return Optional.of(authentication.getName()); } } 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..5e1542ea2 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityDescriptorVersionService.java @@ -0,0 +1,17 @@ +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 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..2bfced24d --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EnversEntityDescriptorVersionService.java @@ -0,0 +1,34 @@ +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.EnversVersionServiceSupport; + +import java.util.List; + +/** + * Hibernate Envers based implementation of {@link EntityDescriptorVersionService}.* + */ +public class EnversEntityDescriptorVersionService implements EntityDescriptorVersionService { + + private EnversVersionServiceSupport enversVersionServiceSupport; + + private EntityDescriptorService entityDescriptorService; + + public EnversEntityDescriptorVersionService(EnversVersionServiceSupport enversVersionServiceSupport, EntityDescriptorService entityDescriptorService) { + this.enversVersionServiceSupport = enversVersionServiceSupport; + this.entityDescriptorService = entityDescriptorService; + } + + @Override + public List findVersionsForEntityDescriptor(String resourceId) { + return enversVersionServiceSupport.findVersionsForPersistentEntity(resourceId, EntityDescriptor.class); + } + + @Override + public EntityDescriptorRepresentation findSpecificVersionOfEntityDescriptor(String resourceId, String versionId) { + Object edObject = enversVersionServiceSupport.findSpecificVersionOfPersistentEntity(resourceId, versionId, EntityDescriptor.class); + return edObject == null ? null : entityDescriptorService.createRepresentationFromDescriptor((EntityDescriptor) edObject); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EnversMetadataResolverVersionService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EnversMetadataResolverVersionService.java new file mode 100644 index 000000000..c98203eca --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EnversMetadataResolverVersionService.java @@ -0,0 +1,32 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver; +import edu.internet2.tier.shibboleth.admin.ui.domain.versioning.Version; +import edu.internet2.tier.shibboleth.admin.ui.envers.EnversVersionServiceSupport; + +import java.util.List; + + +/** + * Hibernate Envers based implementation of {@link MetadataResolverVersionService}. + */ +public class EnversMetadataResolverVersionService implements MetadataResolverVersionService { + + + private EnversVersionServiceSupport enversVersionServiceSupport; + + public EnversMetadataResolverVersionService(EnversVersionServiceSupport enversVersionServiceSupport) { + this.enversVersionServiceSupport = enversVersionServiceSupport; + } + + @Override + public List findVersionsForMetadataResolver(String resourceId) { + return enversVersionServiceSupport.findVersionsForPersistentEntity(resourceId, MetadataResolver.class); + } + + @Override + public MetadataResolver findSpecificVersionOfMetadataResolver(String resourceId, String versionId) { + Object mrObject = enversVersionServiceSupport.findSpecificVersionOfPersistentEntity(resourceId, versionId, MetadataResolver.class); + return mrObject == null ? null : (MetadataResolver) mrObject; + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/FileCheckingFileWritingService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/FileCheckingFileWritingService.java new file mode 100644 index 000000000..6c05fa987 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/FileCheckingFileWritingService.java @@ -0,0 +1,75 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import org.springframework.core.io.WritableResource; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +public class FileCheckingFileWritingService implements FileWritingService { + private static final String DEFAULT_ALGORITHM = "MD5"; + private final String algorithm; + + public FileCheckingFileWritingService() { + this(DEFAULT_ALGORITHM); + } + + public FileCheckingFileWritingService(String algorithm) { + this.algorithm = algorithm; + } + + @Override + public void write(Path path, String content) throws IOException { + if (Files.exists(path)) { + try (InputStream is = Files.newInputStream(path)) { + if (checkContentMatches(is, content)) { + return; + } + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + writeContent(path, content); + } + + @Override + public void write(WritableResource resource, String content) throws IOException { + if (resource.exists()) { + try (InputStream is = resource.getInputStream()) { + if (checkContentMatches(is, content)) { + return; + } + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + writeContent(resource, content); + } + + private boolean checkContentMatches(InputStream inputStream, String content) throws NoSuchAlgorithmException, IOException { + MessageDigest md = MessageDigest.getInstance(this.algorithm); + try (DigestInputStream dis = new DigestInputStream(inputStream, md)) { + byte[] buf = new byte[4096]; + while (dis.read(buf) > -1) {} + } + byte[] fileDigest = md.digest(); + byte[] contentDigest = md.digest(content.getBytes()); + return Arrays.equals(fileDigest, contentDigest); + } + + void writeContent(Path path, String content) throws IOException { + Files.write(path, content.getBytes()); + } + + void writeContent(WritableResource resource, String content) throws IOException { + try (OutputStream os = resource.getOutputStream()) { + os.write(content.getBytes()); + } + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/FileWritingService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/FileWritingService.java new file mode 100644 index 000000000..80493c4c0 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/FileWritingService.java @@ -0,0 +1,30 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import org.springframework.core.io.WritableResource; + +import java.io.IOException; +import java.nio.file.Path; + +/** + * Service interface for writing files. Implementations may perform various tasks + * before or after writing the file. + */ +public interface FileWritingService { + /** + * Write content to a file + * + * @param path target file Path + * @param content content to write + * @throws IOException + */ + void write(Path path, String content) throws IOException; + + /** + * Write content to a writeable resource + * + * @param resource + * @param content + * @throws IOException + */ + void write(WritableResource resource, String content) throws IOException; +} 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..7be97f308 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; } @@ -492,6 +497,7 @@ public EntityDescriptorRepresentation createRepresentationFromDescriptor(org.ope representation.setModifiedDate(ed.getModifiedDate()); representation.setVersion(ed.hashCode()); representation.setCreatedBy(ed.getCreatedBy()); + representation.setCurrent(ed.isCurrent()); if (ed.getSPSSODescriptor("") != null && ed.getSPSSODescriptor("").getSupportedProtocols().size() > 0) { ServiceProviderSsoDescriptorRepresentation serviceProviderSsoDescriptorRepresentation = representation.getServiceProviderSsoDescriptor(true); diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/MetadataResolverVersionService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/MetadataResolverVersionService.java new file mode 100644 index 000000000..16f3dd1a3 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/MetadataResolverVersionService.java @@ -0,0 +1,16 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver; +import edu.internet2.tier.shibboleth.admin.ui.domain.versioning.Version; + +import java.util.List; + +/** + * API containing operations pertaining to {@link MetadataResolver} versioning. + */ +public interface MetadataResolverVersionService { + + List findVersionsForMetadataResolver(String resourceId); + + MetadataResolver findSpecificVersionOfMetadataResolver(String resourceId, String versionId); +} diff --git a/backend/src/main/resources/dynamic-http-metadata-provider.schema.json b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json index 68fdf6fd7..f7106c6e5 100644 --- a/backend/src/main/resources/dynamic-http-metadata-provider.schema.json +++ b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json @@ -1,11 +1,41 @@ { "type": "object", "required": [ + "name", + "@type", "xmlId", "metadataURL", "metadataRequestURLConstructionScheme" ], "properties": { + "name": { + "title": "label.metadata-provider-name-dashboard-display-only", + "description": "tooltip.metadata-provider-name-dashboard-display-only", + "type": "string", + "widget": { + "id": "string", + "help": "message.must-be-unique" + } + }, + "@type": { + "title": "label.metadata-provider-type", + "description": "tooltip.metadata-provider-type", + "placeholder": "label.select-metadata-type", + "type": "string", + "readOnly": true, + "widget": { + "id": "select", + "disabled": true + }, + "oneOf": [ + { + "enum": [ + "DynamicHttpMetadataResolver" + ], + "description": "value.dynamic-http-metadata-provider" + } + ] + }, "enabled": { "title": "label.enable-provider-upon-saving", "description": "tooltip.enable-provider-upon-saving", diff --git a/backend/src/main/resources/file-system-metadata-provider.schema.json b/backend/src/main/resources/file-system-metadata-provider.schema.json index cbfec6b8c..af2f8af0a 100644 --- a/backend/src/main/resources/file-system-metadata-provider.schema.json +++ b/backend/src/main/resources/file-system-metadata-provider.schema.json @@ -8,8 +8,8 @@ ], "properties": { "name": { - "title": "label.service-provider-name-dashboard-display-only", - "description": "tooltip.metadata-provider-name", + "title": "label.metadata-provider-name-dashboard-display-only", + "description": "tooltip.metadata-provider-name-dashboard-display-only", "type": "string", "widget": { "id": "string", @@ -29,7 +29,7 @@ "oneOf": [ { "enum": [ - "FileSystemMetadataResolver" + "FilesystemMetadataResolver" ], "description": "value.file-system-metadata-provider" } diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index 7cadee855..f3054d636 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -48,9 +48,13 @@ action.move-down=Move Down action.edit=Edit action.add-filter=Add Filter action.manage-filters=Manage Filters +action.version-history=Version History +action.options=Options +action.xml=XML value.enabled=Enabled value.disabled=Disabled +value.current=Current value.none=None value.file=File value.memory=Memory @@ -80,7 +84,6 @@ value.dynamic-http-metadata-provider=DynamicHttpMetadataProvider value.entity-attributes-filter=EntityAttributes Filter value.spdescriptor=SPSSODescriptor value.attr-auth-descriptor=AttributeAuthorityDescriptor -value.dynamic-http-metadata-provider=DynamicHttpMetadataProvider value.local-dynamic-metadata-provider=LocalDynamicMetadataProvider value.md-query-protocol=MetadataQueryProtocol @@ -236,6 +239,7 @@ label.filter-name=Filter Name label.filter-enabled=Filter Enabled label.filter-target=FilterTarget label.filter-type=Filter Type +label.option=Option label.value=Value label.binding-type=Binding Type label.sign-assertion=Sign Assertions @@ -384,6 +388,13 @@ label.email=Email label.role=Role label.delete=Delete? +label.title=Title +label.enabled=Enabled +label.author=Author +label.creation-date=Creation Date +label.order=Order +label.provider-type=Provider Type + label.metadata-resolver-history=Metadata resolver history label.metadata-version-history=Metadata Version History label.select-version=Select Version @@ -396,6 +407,9 @@ label.current=Current label.restore=Restore label.compare-selected=Compare Selected +label.saved=Saved +label.by=By + message.delete-user-title=Delete User? message.delete-user-body=You are requesting to delete a user. If you complete this process the user will be removed. This cannot be undone. Do you wish to continue? diff --git a/backend/src/main/resources/jpa-saml2-metadata-algorithm-config.xml b/backend/src/main/resources/jpa-saml2-metadata-algorithm-config.xml new file mode 100644 index 000000000..f6432a71a --- /dev/null +++ b/backend/src/main/resources/jpa-saml2-metadata-algorithm-config.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/local-dynamic-metadata-provider.schema.json b/backend/src/main/resources/local-dynamic-metadata-provider.schema.json index d683db316..f39904f36 100644 --- a/backend/src/main/resources/local-dynamic-metadata-provider.schema.json +++ b/backend/src/main/resources/local-dynamic-metadata-provider.schema.json @@ -8,8 +8,8 @@ ], "properties": { "name": { - "title": "label.metadata-provider-name-display-only", - "description": "tooltip.metadata-provider-name-display-only", + "title": "label.metadata-provider-name-dashboard-display-only", + "description": "tooltip.metadata-provider-name-dashboard-display-only", "type": "string", "widget": { "id": "string", @@ -29,9 +29,9 @@ "oneOf": [ { "enum": [ - "FileSystemMetadataResolver" + "LocalDynamicMetadataResolver" ], - "description": "value.file-system-metadata-provider" + "description": "value.local-dynamic-metadata-provider" } ] }, diff --git a/backend/src/main/resources/metadata-sources-ui-schema.json b/backend/src/main/resources/metadata-sources-ui-schema.json index 3f002253a..9d64e75b4 100644 --- a/backend/src/main/resources/metadata-sources-ui-schema.json +++ b/backend/src/main/resources/metadata-sources-ui-schema.json @@ -310,6 +310,7 @@ }, "attributeRelease": { "type": "array", + "title": "label.attribute-release", "description": "Attribute release table - select the attributes you want to release (default unchecked)", "widget": { "id": "checklist", diff --git a/backend/src/main/templates/AlgorithmBuilderTemplate.java b/backend/src/main/templates/AlgorithmBuilderTemplate.java new file mode 100644 index 000000000..26d1e3077 --- /dev/null +++ b/backend/src/main/templates/AlgorithmBuilderTemplate.java @@ -0,0 +1,22 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain; + +import edu.internet2.tier.shibboleth.admin.ui.opensaml.xml.AbstractSAMLObjectBuilder; +import org.opensaml.saml.common.xml.SAMLConstants; + +public class {{TOKEN}}Builder extends AbstractSAMLObjectBuilder<{{TOKEN}}> { + public {{TOKEN}}Builder() { + } + + public {{TOKEN}} buildObject() { + return buildObject(SAMLConstants.SAML20ALG_NS, {{TOKEN}}.DEFAULT_ELEMENT_LOCAL_NAME, + SAMLConstants.SAML20ALG_PREFIX); + } + + public {{TOKEN}} buildObject(final String namespaceURI, final String localName, final String namespacePrefix) { + {{TOKEN}} o = new {{TOKEN}}(); + o.setNamespaceURI(namespaceURI); + o.setElementLocalName(localName); + o.setNamespacePrefix(namespacePrefix); + return o; + } +} diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy index a01645a09..676547ffd 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy @@ -3,6 +3,7 @@ package edu.internet2.tier.shibboleth.admin.ui.configuration import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSamlChainingMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository +import edu.internet2.tier.shibboleth.admin.ui.security.DefaultAuditorAware import edu.internet2.tier.shibboleth.admin.ui.service.IndexWriterService import net.shibboleth.ext.spring.resource.ResourceHelper import net.shibboleth.utilities.java.support.component.ComponentInitializationException @@ -20,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.io.ClassPathResource +import org.springframework.data.domain.AuditorAware import org.springframework.mail.javamail.JavaMailSender import org.springframework.mail.javamail.JavaMailSenderImpl @@ -87,4 +89,9 @@ class TestConfiguration { metadataResolver.initialize() return metadataResolver } + + @Bean + AuditorAware defaultAuditorAware() { + return new DefaultAuditorAware() + } } 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..bf3778fbb 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 @@ -145,7 +147,8 @@ class EntityDescriptorControllerTests extends Specification { "relyingPartyOverrides": null, "attributeRelease": null, "version": $version, - "createdBy": null + "createdBy": null, + "current": false } ] """ @@ -200,7 +203,8 @@ class EntityDescriptorControllerTests extends Specification { "relyingPartyOverrides": null, "attributeRelease": null, "version": $versionOne, - "createdBy": null + "createdBy": null, + "current": false }, { "id": "uuid-2", @@ -219,7 +223,8 @@ class EntityDescriptorControllerTests extends Specification { "relyingPartyOverrides": null, "attributeRelease": null, "version": $versionTwo, - "createdBy": null + "createdBy": null, + "current": false } ] """ @@ -271,7 +276,8 @@ class EntityDescriptorControllerTests extends Specification { "relyingPartyOverrides": null, "attributeRelease": null, "version": $versionOne, - "createdBy": "someUser" + "createdBy": "someUser", + "current": false } ] """ @@ -323,7 +329,8 @@ class EntityDescriptorControllerTests extends Specification { "securityInfo": null, "assertionConsumerServices": null, "relyingPartyOverrides": null, - "attributeRelease": null + "attributeRelease": null, + "current": false } """ @@ -346,7 +353,8 @@ class EntityDescriptorControllerTests extends Specification { "relyingPartyOverrides": null, "attributeRelease": null, "version": $version, - "createdBy": null + "createdBy": null, + "current": false } """ @@ -502,7 +510,8 @@ class EntityDescriptorControllerTests extends Specification { "relyingPartyOverrides": null, "attributeRelease": null, "version": $version, - "createdBy": null + "createdBy": null, + "current": false } """ @@ -554,7 +563,8 @@ class EntityDescriptorControllerTests extends Specification { "relyingPartyOverrides": null, "attributeRelease": null, "version": $version, - "createdBy": "someUser" + "createdBy": "someUser", + "current": false } """ diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy index 802afdb81..16ab5f6e0 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy @@ -201,7 +201,6 @@ class MetadataFiltersControllerTests extends Specification { updatedMetadataResolver.getMetadataFilters().add(updatedFilter) 1 * metadataResolverRepository.findByResourceId(_) >> originalMetadataResolver - 1 * metadataFilterRepository.findByResourceId(_) >> originalFilter 1 * metadataFilterRepository.save(_) >> updatedFilter def filterUUID = updatedFilter.getResourceId() @@ -240,7 +239,6 @@ class MetadataFiltersControllerTests extends Specification { originalMetadataResolver.getMetadataFilters().add(randomFilter) 1 * metadataResolverRepository.findByResourceId(_) >> originalMetadataResolver - 1 * metadataFilterRepository.findByResourceId(_) >> randomFilter def filterUUID = randomFilter.getResourceId() @@ -253,4 +251,4 @@ class MetadataFiltersControllerTests extends Specification { then: result.andExpect(status().is(409)) } -} \ No newline at end of file +} 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..b7fecdf4d --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/versioning/VersionJsonSerializationBasicTests.groovy @@ -0,0 +1,43 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.versioning + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import groovy.json.JsonSlurper +import spock.lang.Specification + +import java.time.ZonedDateTime + +class VersionJsonSerializationBasicTests extends Specification { + + ObjectMapper mapper + + JsonSlurper jsonSlurper + + def setup() { + mapper = new ObjectMapper() + mapper.registerModule(new JavaTimeModule()) + jsonSlurper = new JsonSlurper() + } + + def "Verify basic Version JSON serialization"() { + given: + def staticDate = ZonedDateTime.parse("2019-05-20T15:00:00.574Z") + def version = new Version('2', 'kramer', staticDate) + def expectedJson = """ + { + "id": "2", + "creator": "kramer", + "date": "2019-05-20T15:00:00.574Z" + } + """ + def expectedJsonMap = jsonSlurper.parseText(expectedJson) + + when: + def deSerializedJsonMap = jsonSlurper.parseText(mapper.writeValueAsString(version)) + + then: + deSerializedJsonMap.date == expectedJsonMap.date + deSerializedJsonMap.id == expectedJsonMap.id + deSerializedJsonMap.creator == expectedJsonMap.creator + } +} diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepositoryTest.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepositoryTest.groovy index 4ecb6e758..741df5ac5 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepositoryTest.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepositoryTest.groovy @@ -1,21 +1,25 @@ package edu.internet2.tier.shibboleth.admin.ui.repository -import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration -import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration -import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSamlChainingMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects 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.JPAEntityDescriptorServiceImpl import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityServiceImpl +import org.apache.lucene.analysis.Analyzer +import org.apache.lucene.analysis.en.EnglishAnalyzer +import org.opensaml.saml.metadata.resolver.MetadataResolver 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.boot.test.context.TestConfiguration +import org.springframework.context.annotation.Bean import org.springframework.data.jpa.repository.config.EnableJpaRepositories - +import org.springframework.test.annotation.DirtiesContext import org.springframework.test.context.ContextConfiguration import spock.lang.Specification @@ -25,9 +29,10 @@ import javax.persistence.EntityManager * A highly unnecessary test so that I can check to make sure that persistence is correct for the model */ @DataJpaTest -@ContextConfiguration(classes=[CoreShibUiConfiguration, SearchConfiguration, TestConfiguration, InternationalizationConfiguration]) +@ContextConfiguration(classes=[CoreShibUiConfiguration, InternationalizationConfiguration]) @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") +@DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD) class EntityDescriptorRepositoryTest extends Specification { @Autowired EntityDescriptorRepository entityDescriptorRepository @@ -42,7 +47,7 @@ class EntityDescriptorRepositoryTest extends Specification { UserRepository userRepository OpenSamlObjects openSamlObjects = new OpenSamlObjects().with { - init() + it.init() it } @@ -61,4 +66,30 @@ class EntityDescriptorRepositoryTest extends Specification { then: item1.hashCode() == item2.hashCode() } + + def "SHIBUI-950"() { + when: + def input = openSamlObjects.unmarshalFromXml(this.class.getResource('/metadata/SHIBUI-950.xml').bytes) as EntityDescriptor + entityDescriptorRepository.save(input) + + then: + noExceptionThrown() + } + + @TestConfiguration + static class Config { + @Bean + MetadataResolver metadataResolver() { + new OpenSamlChainingMetadataResolver().with { + it.id = 'tester' + it.initialize() + return it + } + } + + @Bean + Analyzer analyzer() { + return new EnglishAnalyzer() + } + } } 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..2052cf9d1 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/EnversTestsSupport.groovy @@ -0,0 +1,76 @@ +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.domain.resolvers.MetadataResolver +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.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 updateAndGetRevisionHistoryOfMetadataResolver(MetadataResolver mr, + MetadataResolverRepository mrr, + Class < ? > type, + PlatformTransactionManager + txMgr, EntityManager em) { + + doInExplicitTransaction(txMgr) { + mrr.save(mr) + } + getRevisionHistoryForEntityType(em, type, mr.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/scheduled/EntityDescriptorFilesScheduledTasksTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/scheduled/EntityDescriptorFilesScheduledTasksTests.groovy index 117c0fbd4..f47928082 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/scheduled/EntityDescriptorFilesScheduledTasksTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/scheduled/EntityDescriptorFilesScheduledTasksTests.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.FileCheckingFileWritingService 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 @@ -57,7 +58,7 @@ class EntityDescriptorFilesScheduledTasksTests extends Specification { randomGenerator = new RandomGenerator() tempPath = tempPath + randomGenerator.randomRangeInt(10000, 20000) service = new JPAEntityDescriptorServiceImpl(openSamlObjects, new JPAEntityServiceImpl(openSamlObjects), new UserService(roleRepository, userRepository)) - entityDescriptorFilesScheduledTasks = new EntityDescriptorFilesScheduledTasks(tempPath, entityDescriptorRepository, openSamlObjects) + entityDescriptorFilesScheduledTasks = new EntityDescriptorFilesScheduledTasks(tempPath, entityDescriptorRepository, openSamlObjects, new FileCheckingFileWritingService()) directory = new File(tempPath) directory.mkdir() } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersControllerIntegrationTests.groovy index 0e96fc1b7..ab60461a0 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersControllerIntegrationTests.groovy @@ -39,30 +39,30 @@ class UsersControllerIntegrationTests extends Specification { def expectedJson = """ [ { - "modifiedBy" : null, + "modifiedBy" : anonymousUser, "firstName" : "Joe", "emailAddress" : "joe@institution.edu", "role" : "ROLE_ADMIN", "username" : "admin", - "createdBy" : null, + "createdBy" : anonymousUser, "lastName" : "Doe" }, { - "modifiedBy" : null, + "modifiedBy" : anonymousUser, "firstName" : "Peter", "emailAddress" : "peter@institution.edu", "role" : "ROLE_USER", "username" : "nonadmin", - "createdBy" : null, + "createdBy" : anonymousUser, "lastName" : "Vandelay" }, { - "modifiedBy" : null, + "modifiedBy" : anonymousUser, "firstName" : "Anon", "emailAddress" : "anon@institution.edu", "role" : "ROLE_ADMIN", "username" : "anonymousUser", - "createdBy" : null, + "createdBy" : anonymousUser, "lastName" : "Ymous" } ]""" @@ -83,12 +83,12 @@ class UsersControllerIntegrationTests extends Specification { given: def expectedJson = """ { - "modifiedBy" : null, + "modifiedBy" : anonymousUser, "firstName" : "Joe", "emailAddress" : "joe@institution.edu", "role" : "ROLE_ADMIN", "username" : "admin", - "createdBy" : null, + "createdBy" : anonymousUser, "lastName" : "Doe" }""" when: 'GET request is made for one existing user' @@ -229,4 +229,4 @@ class UsersControllerIntegrationTests extends Specification { then: result.andExpect(status().isNotFound()) } -} \ No newline at end of file +} diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/FileCheckingFileWritingServiceTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/FileCheckingFileWritingServiceTests.groovy new file mode 100644 index 000000000..45465475a --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/FileCheckingFileWritingServiceTests.groovy @@ -0,0 +1,96 @@ +package edu.internet2.tier.shibboleth.admin.ui.service + +import org.springframework.core.io.PathResource +import org.springframework.core.io.WritableResource +import spock.lang.Specification + +import java.nio.file.Files +import java.nio.file.Path +import java.security.NoSuchAlgorithmException + +class FileCheckingFileWritingServiceTests extends Specification { + def writer = Spy(FileCheckingFileWritingService) + + Path file + + WritableResource resource + + def setup() { + file = Files.createTempFile('test1', '.txt') + resource = new PathResource(file) + } + + def 'test bad algorithm'() { + setup: + def badWriter = new FileCheckingFileWritingService('badAlGoreRhythm') + + when: + badWriter.write(Files.createTempFile('testbadalgorithm', '.txt'), 'bad') + + then: + RuntimeException ex = thrown() + assert ex.cause instanceof NoSuchAlgorithmException + } + + def 'test a single write to a Path'() { + when: + writer.write(file, 'testme') + + then: + 1 * writer.writeContent(file, 'testme') + assert file.text == 'testme' + } + + def 'test writes with changed content to a Path'() { + when: + writer.write(file, 'testme') + writer.write(file, 'anothertest') + + then: + 1 * writer.writeContent(file, 'testme') + 1 * writer.writeContent(file, 'anothertest') + assert file.text == 'anothertest' + } + + def 'test writes with unchanged content, should only write once to a Path'() { + when: + (1..5).each { + writer.write(file, 'testme2') + } + + then: + 1 * writer.writeContent(file, 'testme2') + assert file.text == 'testme2' + } + + def 'test a single write to a WriteableResource'() { + when: + writer.write(resource, 'testme') + + then: + 1 * writer.writeContent(resource, 'testme') + assert resource.getFile().text == 'testme' + } + + def 'test write with changed content to a WritableResource'() { + when: + writer.write(resource, 'testme') + writer.write(resource, 'anothertest') + + then: + 1 * writer.writeContent(resource, 'testme') + 1 * writer.writeContent(resource, 'anothertest') + assert resource.getFile().text == 'anothertest' + } + + def 'test writes with unchanged content, should only write once to a WriteableResource'() { + when: + (1..5).each { + writer.write(resource, 'testme2') + } + + then: + 1 * writer.writeContent(resource, 'testme2') + assert resource.getFile().text == 'testme2' + } +} 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/backend/src/test/resources/metadata/SHIBUI-950.xml b/backend/src/test/resources/metadata/SHIBUI-950.xml new file mode 100644 index 000000000..1d534cf44 --- /dev/null +++ b/backend/src/test/resources/metadata/SHIBUI-950.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + +0uoDhgpIppfgBW09Xl/CzKh+FtwMpQnyrKUFp4Z4xzM= + + +mR7mTtB6hQYGchYSvOp9xt5emHVT3/vq7ikVkARf3OF3mLkILMX60m5h73T3ZrBK +siVglL2USnsaecbqxZ/zn0Kd+PHcrUyMBBUnZdsmX+C8DfSKvV6fdDRWjcsjG4pt +DvnX+6UwqESev7wIm0rBfYJMMMEimxsWssQQg7v/yAuoeMCoz8sUPIpMm6sB6qXl +6ldXLt2dmDSXtrtd2Er+e8lp4QvpohI9DyzDqtHKAKD0y3TMqniZ8LA59cnjTcQg +MCD9DszcBuexniOMTqrgDG4VFAexSn6k2le/eXJIOsRouVizHOZSei1VxZoFl3IR +PPs+eFOTsligBCEVBPWEUg== +MIIDQjCCAiqgAwIBAgIJAMI1r/DZzTEJMA0GCSqGSIb3DQEBBQUAME8xEzARBgoJ +kiaJk/IsZAEZFgNuZXQxGjAYBgoJkiaJk/IsZAEZFgpzaGliYm9sZXRoMRwwGgYD +VQQDExN3aWtpLnNoaWJib2xldGgubmV0MB4XDTEzMTEyNTE0NTcyOFoXDTM3MDMw +NDE0NTcyOFowTzETMBEGCgmSJomT8ixkARkWA25ldDEaMBgGCgmSJomT8ixkARkW +CnNoaWJib2xldGgxHDAaBgNVBAMTE3dpa2kuc2hpYmJvbGV0aC5uZXQwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1viMiWhYa8cmxJ6rQ8yORYMD6Gx5n +r/r9wQko+Tbjl/qGS0LaTfPQCokvwrD06506MPHainaMqbjlO4gDjq2LpU9/iy0s +iLuY7UHgDqNNZOELBTQOMwLAFcuEA10FCWjJRglT+6w3xEFeU+dZkBXV1VvKBvsZ +SiuQw437CcV3ueEF4+ZB0l9uyq8o3wzKRZ9DnpyFL7SUJiHJPuqqXZuyQnjLrbVZ +KjjumGnY3LJTUo1xoUEuhqj5RMfspn2oc5YnIYka5YrCBmYKJV3QtCFbLA/cz8nF +m+lOvYGz8nl3wHNkZIVRoetVw/Mhf7lzex0rh3XBdS6vVcT75uH0X1OPAgMBAAGj +ITAfMB0GA1UdDgQWBBQe1XwZavrgAhRXrfhv1gGUwSkc7DANBgkqhkiG9w0BAQUF +AAOCAQEADCGhWJ+oZ8ltcjJ7D66rMg1HOZT6GFCVeZ7MfhY/KFrvsnITNbTA+SgZ +tCJt/BLlZXxpzmix19bD9bNwqEMo7WSqBy77X7SS97ZXti6y6vwAz8h78vzQopOd +rnn8XXyWxtrtRRCK4RMpZGrVm3sfBPW68j9hiPHZqewE4nLavjCki/I9rCMe5dJE +3+ZRf4Ip/9hYqM+a5Chcvbo2zJEOtw+EUQqNTZ51j33H/2qF9UoSpt74UFh+Jd5y +L2GoFSt/gCld78j/7cU3ObGQEme+hVVZ8/uGa/cCYvFt75vNBdnlj4icZ6fgFe9R +9h5hlBTGD3PULSFmCdkgxtwIyd855Q== + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MIIDQjCCAiqgAwIBAgIJAMI1r/DZzTEJMA0GCSqGSIb3DQEBBQUAME8xEzARBgoJ +kiaJk/IsZAEZFgNuZXQxGjAYBgoJkiaJk/IsZAEZFgpzaGliYm9sZXRoMRwwGgYD +VQQDExN3aWtpLnNoaWJib2xldGgubmV0MB4XDTEzMTEyNTE0NTcyOFoXDTM3MDMw +NDE0NTcyOFowTzETMBEGCgmSJomT8ixkARkWA25ldDEaMBgGCgmSJomT8ixkARkW +CnNoaWJib2xldGgxHDAaBgNVBAMTE3dpa2kuc2hpYmJvbGV0aC5uZXQwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1viMiWhYa8cmxJ6rQ8yORYMD6Gx5n +r/r9wQko+Tbjl/qGS0LaTfPQCokvwrD06506MPHainaMqbjlO4gDjq2LpU9/iy0s +iLuY7UHgDqNNZOELBTQOMwLAFcuEA10FCWjJRglT+6w3xEFeU+dZkBXV1VvKBvsZ +SiuQw437CcV3ueEF4+ZB0l9uyq8o3wzKRZ9DnpyFL7SUJiHJPuqqXZuyQnjLrbVZ +KjjumGnY3LJTUo1xoUEuhqj5RMfspn2oc5YnIYka5YrCBmYKJV3QtCFbLA/cz8nF +m+lOvYGz8nl3wHNkZIVRoetVw/Mhf7lzex0rh3XBdS6vVcT75uH0X1OPAgMBAAGj +ITAfMB0GA1UdDgQWBBQe1XwZavrgAhRXrfhv1gGUwSkc7DANBgkqhkiG9w0BAQUF +AAOCAQEADCGhWJ+oZ8ltcjJ7D66rMg1HOZT6GFCVeZ7MfhY/KFrvsnITNbTA+SgZ +tCJt/BLlZXxpzmix19bD9bNwqEMo7WSqBy77X7SS97ZXti6y6vwAz8h78vzQopOd +rnn8XXyWxtrtRRCK4RMpZGrVm3sfBPW68j9hiPHZqewE4nLavjCki/I9rCMe5dJE +3+ZRf4Ip/9hYqM+a5Chcvbo2zJEOtw+EUQqNTZ51j33H/2qF9UoSpt74UFh+Jd5y +L2GoFSt/gCld78j/7cU3ObGQEme+hVVZ8/uGa/cCYvFt75vNBdnlj4icZ6fgFe9R +9h5hlBTGD3PULSFmCdkgxtwIyd855Q== + + + + + + + + + + + + + + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + Shibboleth Federated Wiki + + A shared Wiki service with automatic registration for users who can supply a supported + identifier, such as eduPersonPrincipalName or eduPersonTargetedID. + + + + + + + + + + + + + Shibboleth Consortium + Shibboleth Consortium + http://www.shibboleth.net/ + + + Shibboleth.Net Technical Support + contact@shibboleth.net + \ No newline at end of file diff --git a/build.gradle b/build.gradle index b1dad6f5c..5b78682dc 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,28 @@ +plugins { + id 'base' + id 'net.researchgate.release' version '2.6.0' + id 'com.github.breadmoirai.github-release' version '2.2.9' +} + +subprojects { + tasks.withType(Test) { + maxHeapSize = "3g" + } +} + +tasks.findByName('release').dependsOn project.getTasksByName('test', true) + +githubRelease { + token project.'i2.github.token' + owner project.'i2.github.owner' + repo project.'i2.github.repo' + releaseAssets project('backend').getTasksByName('bootWar', false).outputs, project('backend').getTasksByName('bootJar', false).outputs + overwrite true + apiEndpoint project.'i2.github.apiEndpoint' +} + +afterReleaseBuild.dependsOn project.getTasksByName('githubRelease', false) + task wrapper(type: Wrapper) { gradleVersion = '4.8.1' } diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 0022b801c..8b3566fc6 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -390,71 +390,6 @@ paths: '409': description: Already Exists deprecated: false - '/MetadataResolvers/{metadataResolverId}/Filters/{resourceId}/Versions': - get: - tags: - - metadata-filters-controller - description: Get list of Versions for an entity descriptor - produces: - - application/json - parameters: - - name: metadataResolverId - in: path - description: metadataResolverId - required: true - type: string - - name: resourceId - in: path - description: resourceId - required: true - type: string - responses: - '200': - description: OK. Items returned should be ordered by `date`, ascending (oldest version first). - schema: - type: array - items: - $ref: '#/definitions/Version' - '401': - description: Unauthorized - '403': - description: Forbidden - '404': - description: Not Found - '/MetadataResolvers/{metadataResolverId}/Filters/{resourceId}/Versions/{versionId}': - get: - tags: - - metadata-filters-controller - description: Get a Version of an entity descriptor - produces: - - application/json - parameters: - - name: metadataResolverId - in: path - description: metadataResolverId - required: true - type: string - - name: resourceId - in: path - description: resourceId - required: true - type: string - - name: versionId - in: path - description: versionId of a version of the EntityDescriptor - required: true - type: string - responses: - '200': - description: OK - schema: - $ref: '#/definitions/MetadataFilter' - '401': - description: Unauthorized - '403': - description: Forbidden - '404': - description: Not Found '/MetadataResolvers/{resourceId}': get: tags: diff --git a/gradle.properties b/gradle.properties index 01ee65808..4fdf282d7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,4 +11,10 @@ hibernate.version=5.2.11.Final lucene.version=7.2.1 -org.gradle.jvmargs=-Xmx4g -XX:-UseGCOverheadLimit +org.gradle.jvmargs=-Xmx1g -XX:-UseGCOverheadLimit + +# set token in personal global +i2.github.token= +i2.github.owner=TIER +i2.github.repo=shib-idp-ui +i2.github.apiEndpoint=https://github.internet2.edu/api/v3 diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jAuditorAware.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jAuditorAware.java index 0fe287c7e..47332d43b 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jAuditorAware.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jAuditorAware.java @@ -7,11 +7,14 @@ import java.util.Optional; public class Pac4jAuditorAware implements AuditorAware { + + private static final String ANONYMOUS = "anonymousUser"; + @Override public Optional getCurrentAuditor() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null) { - return Optional.empty(); + return Optional.of(ANONYMOUS); } return Optional.of(authentication.getName()); } 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' diff --git a/ui/build.gradle b/ui/build.gradle index 7735a3116..64fe2e561 100644 --- a/ui/build.gradle +++ b/ui/build.gradle @@ -9,6 +9,8 @@ node { download = true } +npmInstall.setNpmCommand('ci') + npm_run_build { inputs.dir 'src' outputs.dir 'dist' diff --git a/ui/package-lock.json b/ui/package-lock.json index fc76f65cc..25fad932c 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -3511,6 +3511,7 @@ "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -3582,9 +3583,9 @@ "dev": true }, "are-we-there-yet": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", - "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, "requires": { "delegates": "^1.0.0", @@ -3717,9 +3718,9 @@ } }, "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, "assign-symbols": { @@ -3796,16 +3797,17 @@ } }, "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", "dev": true }, "aws4": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", - "dev": true + "dev": true, + "optional": true }, "axobject-query": { "version": "2.0.2", @@ -4164,15 +4166,6 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "dev": true, - "requires": { - "hoek": "2.x.x" - } - }, "bootstrap": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz", @@ -4759,6 +4752,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", "dev": true, + "optional": true, "requires": { "delayed-stream": "~1.0.0" } @@ -5025,15 +5019,6 @@ "which": "^1.2.9" } }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "dev": true, - "requires": { - "boom": "2.x.x" - } - }, "crypto-browserify": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", @@ -6364,14 +6349,25 @@ "dev": true }, "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, "requires": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.5", + "combined-stream": "^1.0.6", "mime-types": "^2.1.12" + }, + "dependencies": { + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + } } }, "forwarded": { @@ -6472,7 +6468,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -6493,12 +6490,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6513,17 +6512,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -6640,7 +6642,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -6652,6 +6655,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6666,6 +6670,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6673,12 +6678,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -6697,6 +6704,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -6777,7 +6785,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -6789,6 +6798,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -6874,7 +6884,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -6910,6 +6921,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6929,6 +6941,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6972,19 +6985,21 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, "fstream": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -7016,29 +7031,14 @@ } }, "gaze": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz", - "integrity": "sha1-hHIkZ3rbiHDWeSV+0ziP22HkAQU=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", "dev": true, "requires": { "globule": "^1.0.0" } }, - "generate-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", - "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", - "dev": true - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "dev": true, - "requires": { - "is-property": "^1.0.0" - } - }, "genfun": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz", @@ -7150,14 +7150,22 @@ } }, "globule": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", - "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", "dev": true, "requires": { "glob": "~7.1.1", - "lodash": "~4.17.4", + "lodash": "~4.17.10", "minimatch": "~3.0.2" + }, + "dependencies": { + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + } } }, "graceful-fs": { @@ -7273,29 +7281,58 @@ } }, "har-schema": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", - "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", "dev": true }, "har-validator": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "dev": true, "requires": { - "ajv": "^4.9.1", - "har-schema": "^1.0.5" + "ajv": "^6.5.5", + "har-schema": "^2.0.0" }, "dependencies": { "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "dev": true, "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" } } } @@ -7431,18 +7468,6 @@ "minimalistic-assert": "^1.0.0" } }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "dev": true, - "requires": { - "boom": "2.x.x", - "cryptiles": "2.x.x", - "hoek": "2.x.x", - "sntp": "1.x.x" - } - }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", @@ -7460,12 +7485,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", - "dev": true - }, "homedir-polyfill": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", @@ -7963,12 +7982,12 @@ } }, "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "requires": { - "assert-plus": "^0.2.0", + "assert-plus": "^1.0.0", "jsprim": "^1.2.2", "sshpk": "^1.7.0" } @@ -8332,25 +8351,6 @@ "is-extglob": "^2.1.1" } }, - "is-my-ip-valid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", - "dev": true - }, - "is-my-json-valid": { - "version": "2.17.2", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz", - "integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==", - "dev": true, - "requires": { - "generate-function": "^2.0.0", - "generate-object-property": "^1.1.0", - "is-my-ip-valid": "^1.0.0", - "jsonpointer": "^4.0.0", - "xtend": "^4.0.0" - } - }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -8416,12 +8416,6 @@ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true - }, "is-regex": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", @@ -8928,9 +8922,9 @@ } }, "js-base64": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.3.tgz", - "integrity": "sha512-H7ErYLM34CvDMto3GbD6xD0JLUGYXR3QTcH6B/tr4Hi/QpSThnCsIp+Sy5FRTw3B0d6py4HcNkW7nO/wdtGWEw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", + "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", "dev": true }, "js-tokens": { @@ -8980,15 +8974,6 @@ "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", "dev": true }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "requires": { - "jsonify": "~0.0.0" - } - }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -9016,24 +9001,12 @@ "graceful-fs": "^4.1.6" } }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true - }, "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", - "dev": true - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -9329,7 +9302,8 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", - "dev": true + "dev": true, + "optional": true }, "http-signature": { "version": "1.2.0", @@ -9484,12 +9458,6 @@ "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", "dev": true }, - "lodash.assign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", - "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", - "dev": true - }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -9512,12 +9480,6 @@ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, - "lodash.mergewith": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", - "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", - "dev": true - }, "lodash.tail": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", @@ -9583,7 +9545,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true + "dev": true, + "optional": true }, "loose-envify": { "version": "1.3.1", @@ -10232,6 +10195,14 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, + "ngx-infinite-scroll": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-7.2.0.tgz", + "integrity": "sha512-EcqjKpU1ukRV3YXOW8cTVtbzPpa9UPaRtYBCg0ZQH3ceCDm+xzLbd4pXy6oKAIN4zN1r/pyGuf5XOJkA8vr6yg==", + "requires": { + "opencollective-postinstall": "^2.0.2" + } + }, "ngx-schema-form": { "version": "2.2.0-beta.1", "resolved": "https://registry.npmjs.org/ngx-schema-form/-/ngx-schema-form-2.2.0-beta.1.tgz", @@ -10274,20 +10245,19 @@ "dev": true }, "node-gyp": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.6.2.tgz", - "integrity": "sha1-m/vlRWIoYoSDjnUOrAUpWFP6HGA=", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", "dev": true, "requires": { "fstream": "^1.0.0", "glob": "^7.0.3", "graceful-fs": "^4.1.2", - "minimatch": "^3.0.2", "mkdirp": "^0.5.0", "nopt": "2 || 3", "npmlog": "0 || 1 || 2 || 3 || 4", "osenv": "0", - "request": "2", + "request": "^2.87.0", "rimraf": "2", "semver": "~5.3.0", "tar": "^2.0.0", @@ -10334,9 +10304,9 @@ } }, "node-sass": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.0.tgz", - "integrity": "sha512-QFHfrZl6lqRU3csypwviz2XLgGNOoWQbo2GOvtsfQqOfL4cy1BtWnhx/XUeAO9LT3ahBzSRXcEO6DdvAH9DzSg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz", + "integrity": "sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==", "dev": true, "requires": { "async-foreach": "^0.1.3", @@ -10346,15 +10316,13 @@ "get-stdin": "^4.0.1", "glob": "^7.0.3", "in-publish": "^2.0.0", - "lodash.assign": "^4.2.0", - "lodash.clonedeep": "^4.3.2", - "lodash.mergewith": "^4.6.0", + "lodash": "^4.17.11", "meow": "^3.7.0", "mkdirp": "^0.5.1", - "nan": "^2.10.0", - "node-gyp": "^3.3.1", + "nan": "^2.13.2", + "node-gyp": "^3.8.0", "npmlog": "^4.0.0", - "request": "~2.79.0", + "request": "^2.88.0", "sass-graph": "^2.2.4", "stdout-stream": "^1.4.0", "true-case-path": "^1.0.2" @@ -10366,12 +10334,6 @@ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, - "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", - "dev": true - }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", @@ -10385,69 +10347,23 @@ "supports-color": "^2.0.0" } }, - "har-validator": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", - "dev": true, - "requires": { - "chalk": "^1.1.1", - "commander": "^2.9.0", - "is-my-json-valid": "^2.12.4", - "pinkie-promise": "^2.0.0" - } - }, - "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, - "qs": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", - "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=", + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", "dev": true }, - "request": { - "version": "2.79.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", - "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", - "dev": true, - "requires": { - "aws-sign2": "~0.6.0", - "aws4": "^1.2.1", - "caseless": "~0.11.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.0", - "forever-agent": "~0.6.1", - "form-data": "~2.1.1", - "har-validator": "~2.0.6", - "hawk": "~3.1.3", - "http-signature": "~1.1.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.7", - "oauth-sign": "~0.8.1", - "qs": "~6.3.0", - "stringstream": "~0.0.4", - "tough-cookie": "~2.3.0", - "tunnel-agent": "~0.4.1", - "uuid": "^3.0.0" - } - }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true - }, - "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", - "dev": true } } }, @@ -10618,7 +10534,8 @@ "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -10748,6 +10665,11 @@ "is-wsl": "^1.1.0" } }, + "opencollective-postinstall": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz", + "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==" + }, "opener": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", @@ -11273,9 +11195,9 @@ } }, "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, "pify": { @@ -11547,6 +11469,12 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, + "psl": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.33.tgz", + "integrity": "sha512-LTDP2uSrsc7XCb5lO7A8BI1qYxRe/8EqlRvMeEl6rsnYAqDOl8xHR+8lSAIVfrNaSAlTPTNOCgNjWcoUL3AZsw==", + "dev": true + }, "public-encrypt": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz", @@ -11594,9 +11522,9 @@ "dev": true }, "qs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true }, "querystring": { @@ -11879,33 +11807,97 @@ } }, "request": { - "version": "2.81.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "dev": true, "requires": { - "aws-sign2": "~0.6.0", - "aws4": "^1.2.1", + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.1.1", - "har-validator": "~4.2.1", - "hawk": "~3.1.3", - "http-signature": "~1.1.0", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.7", - "oauth-sign": "~0.8.1", - "performance-now": "^0.2.0", - "qs": "~6.4.0", - "safe-buffer": "^5.0.1", - "stringstream": "~0.0.4", - "tough-cookie": "~2.3.0", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", - "uuid": "^3.0.0" + "uuid": "^3.3.2" + }, + "dependencies": { + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, + "requires": { + "mime-db": "1.40.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + } } }, "require-directory": { @@ -12474,15 +12466,6 @@ "kind-of": "^3.2.0" } }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", - "dev": true, - "requires": { - "hoek": "2.x.x" - } - }, "socket.io": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", @@ -12833,9 +12816,9 @@ "dev": true }, "stdout-stream": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.0.tgz", - "integrity": "sha1-osfIWH5U2UJ+qe2zrD8s1SLfN4s=", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", "dev": true, "requires": { "readable-stream": "^2.0.1" @@ -12940,7 +12923,8 @@ "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", - "dev": true + "dev": true, + "optional": true }, "strip-ansi": { "version": "3.0.1", @@ -13069,13 +13053,13 @@ "dev": true }, "tar": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", "dev": true, "requires": { "block-stream": "*", - "fstream": "^1.0.2", + "fstream": "^1.0.12", "inherits": "2" } }, @@ -13190,6 +13174,7 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", "dev": true, + "optional": true, "requires": { "punycode": "^1.4.1" } @@ -13213,27 +13198,12 @@ "dev": true }, "true-case-path": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.2.tgz", - "integrity": "sha1-fskRMJJHZsf1c74wIMNPj9/QDWI=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", "dev": true, "requires": { - "glob": "^6.0.4" - }, - "dependencies": { - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } + "glob": "^7.1.2" } }, "tryer": { @@ -15416,12 +15386,12 @@ "dev": true }, "wide-align": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", - "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "requires": { - "string-width": "^1.0.2" + "string-width": "^1.0.2 || 2" } }, "window-size": { diff --git a/ui/package.json b/ui/package.json index 6cfe807cd..b293be085 100644 --- a/ui/package.json +++ b/ui/package.json @@ -6,7 +6,7 @@ "ng": "ng", "start": "ng serve --proxy-config proxy.conf.json", "build": "ng build", - "test": "ng test --code-coverage --source-map=false", + "test": "ng test --code-coverage --source-map=true", "lint": "ng lint", "e2e": "ng e2e", "build:static": "node-sass src/static.scss ./dist/unsecured/static.css", @@ -43,6 +43,7 @@ "deepmerge": "^2.2.1", "file-saver": "^1.3.3", "font-awesome": "^4.7.0", + "ngx-infinite-scroll": "^7.2.0", "ngx-schema-form": "^2.2.0-beta.1", "rxjs": "^6.5.1", "rxjs-compat": "^6.5.1", @@ -72,6 +73,7 @@ "karma-jasmine-html-reporter": "^1.4.2", "karma-spec-reporter": "0.0.32", "ncp": "^2.0.0", + "node-sass": "^4.12.0", "path": "^0.12.7", "ts-node": "~3.2.0", "tslint": "~5.3.2", diff --git a/ui/src/app/admin/component/enable-metadata.component.html b/ui/src/app/admin/component/enable-metadata.component.html index cba54f177..01465184f 100644 --- a/ui/src/app/admin/component/enable-metadata.component.html +++ b/ui/src/app/admin/component/enable-metadata.component.html @@ -4,9 +4,8 @@ aria-label="Provider Item Accordion. Press Spacebar to open"> resolvers.map(r => new FileBackedHttpMetadataResolver(r))) ); this.loading$ = this.store.select(fromDashboard.getSearchLoading); - this.entitiesOpen$ = this.store.select(fromDashboard.getOpenProviders); this.total$ = this.resolvers$.pipe(map(list => list.length)); } @@ -51,10 +49,6 @@ export class EnableMetadataComponent implements OnInit { this.router.navigate(['metadata', 'resolver', entity.getId(), 'edit']); } - toggleEntity(entity: MetadataEntity): void { - this.store.dispatch(new ToggleEntityDisplay(entity.getId())); - } - openPreviewDialog(entity: MetadataEntity): void { this.store.dispatch(new PreviewEntity({ id: entity.getId(), entity })); } diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index fb19ae16a..52b5a0f87 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -21,26 +21,19 @@ Add New