diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImpl.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImpl.groovy index d24174234..664599cfa 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImpl.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImpl.groovy @@ -1,12 +1,12 @@ -package edu.internet2.tier.shibboleth.admin.ui.service; +package edu.internet2.tier.shibboleth.admin.ui.service import com.google.common.base.Predicate import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityRoleWhiteListFilter - +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.LocalDynamicMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository import groovy.util.logging.Slf4j @@ -21,9 +21,7 @@ import org.opensaml.saml.metadata.resolver.filter.MetadataFilter import org.opensaml.saml.metadata.resolver.filter.MetadataFilterChain import org.opensaml.saml.saml2.core.Attribute import org.opensaml.saml.saml2.metadata.EntityDescriptor - import org.springframework.beans.factory.annotation.Autowired - import org.w3c.dom.Document @Slf4j @@ -115,7 +113,6 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService { } } - void constructXmlNodeForFilter(EntityAttributesFilter filter, def markupBuilderDelegate) { markupBuilderDelegate.MetadataFilter('xsi:type': 'EntityAttributes') { // TODO: enhance. currently this does weird things with namespaces @@ -142,6 +139,53 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService { } } + void constructXmlNodeForResolver(DynamicHttpMetadataResolver resolver, def markupBuilderDelegate, Closure childNodes) { + markupBuilderDelegate.MetadataProvider(id: resolver.name, + 'xsi:type': 'DynamicHttpMetadataProvider', + requireValidMetadata: !resolver.requireValidMetadata ?: null, + failFastInitialization: !resolver.failFastInitialization ?: null, + sortKey: resolver.sortKey, + criterionPredicateRegistryRef: resolver.criterionPredicateRegistryRef, + useDefaultPredicateRegistry: !resolver.useDefaultPredicateRegistry ?: null, + satisfyAnyPredicates: resolver.satisfyAnyPredicates ?: null, + parserPoolRef: resolver.dynamicMetadataResolverAttributes?.parserPoolRef, + taskTimerRef: resolver.dynamicMetadataResolverAttributes?.taskTimerRef, + refreshDelayFactor: resolver.dynamicMetadataResolverAttributes?.refreshDelayFactor, + minCacheDuration: resolver.dynamicMetadataResolverAttributes?.minCacheDuration, + maxCacheDuration: resolver.dynamicMetadataResolverAttributes?.maxCacheDuration, + maxIdleEntityData: resolver.dynamicMetadataResolverAttributes?.maxIdleEntityData, + removeIdleEntityData: !resolver.dynamicMetadataResolverAttributes?.removeIdleEntityData ?: null, + cleanupTaskInterval: resolver.dynamicMetadataResolverAttributes?.cleanupTaskInterval, + persistentCacheManagerRef: resolver.dynamicMetadataResolverAttributes?.persistentCacheManagerRef, + persistentCacheManagerDirectory: resolver.dynamicMetadataResolverAttributes?.persistentCacheManagerDirectory, + persistentCacheKeyGeneratorRef: resolver.dynamicMetadataResolverAttributes?.persistentCacheKeyGeneratorRef, + initializeFromPersistentCacheInBackground: !resolver.dynamicMetadataResolverAttributes?.initializeFromPersistentCacheInBackground ?: null, + backgroundInitializationFromCacheDelay: resolver.dynamicMetadataResolverAttributes?.backgroundInitializationFromCacheDelay, + initializationFromCachePredicateRef: resolver.dynamicMetadataResolverAttributes?.initializationFromCachePredicateRef, + + maxConnectionsTotal: resolver.maxConnectionsTotal, + maxConnectionsPerRoute: resolver.maxConnectionsPerRoute, + supportedContentTypes: resolver.supportedContentTypes?.value, //not sure this is right. maybe take off the ?.value + + httpClientRef: resolver.httpMetadataResolverAttributes?.httpClientRef, + connectionRequestTimeout: resolver.httpMetadataResolverAttributes?.connectionRequestTimeout, + connectionTimeout: resolver.httpMetadataResolverAttributes?.connectionTimeout, + socketTimeout: resolver.httpMetadataResolverAttributes?.socketTimeout, + disregardTLSCertificate: resolver.httpMetadataResolverAttributes?.disregardTLSCertificate ?: null, + httpClientSecurityParametersRef: resolver.httpMetadataResolverAttributes?.httpClientSecurityParametersRef, + proxyHost: resolver.httpMetadataResolverAttributes?.proxyHost, + proxyPort: resolver.httpMetadataResolverAttributes?.proxyHost, + proxyUser: resolver.httpMetadataResolverAttributes?.proxyUser, + proxyPassword: resolver.httpMetadataResolverAttributes?.proxyPassword, + httpCaching: resolver.httpMetadataResolverAttributes?.httpCaching, + httpCacheDirectory: resolver.httpMetadataResolverAttributes?.httpCacheDirectory, + httpMaxCacheEntries: resolver.httpMetadataResolverAttributes?.httpMaxCacheEntries, + httpMaxCacheEntrySize: resolver.httpMetadataResolverAttributes?.httpMaxCacheEntrySize) { + + childNodes() + } + } + void constructXmlNodeForResolver(FileBackedHttpMetadataResolver resolver, def markupBuilderDelegate, Closure childNodes) { markupBuilderDelegate.MetadataProvider(id: resolver.name, 'xsi:type': 'FileBackedHTTPMetadataProvider', @@ -182,4 +226,37 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService { childNodes() } } + + void constructXmlNodeForResolver(LocalDynamicMetadataResolver resolver, def markupBuilderDelegate, Closure childNodes) { + markupBuilderDelegate.MetadataProvider(sourceDirectory: resolver.sourceDirectory, + sourceManagerRef: resolver.sourceManagerRef, + sourceKeyGeneratorRef: resolver.sourceKeyGeneratorRef, + + id: resolver.name, + 'xsi:type': 'DynamicHttpMetadataProvider', + requireValidMetadata: !resolver.requireValidMetadata ?: null, + failFastInitialization: !resolver.failFastInitialization ?: null, + sortKey: resolver.sortKey, + criterionPredicateRegistryRef: resolver.criterionPredicateRegistryRef, + useDefaultPredicateRegistry: !resolver.useDefaultPredicateRegistry ?: null, + satisfyAnyPredicates: resolver.satisfyAnyPredicates ?: null, + parserPoolRef: resolver.dynamicMetadataResolverAttributes?.parserPoolRef, + taskTimerRef: resolver.dynamicMetadataResolverAttributes?.taskTimerRef, + refreshDelayFactor: resolver.dynamicMetadataResolverAttributes?.refreshDelayFactor, + minCacheDuration: resolver.dynamicMetadataResolverAttributes?.minCacheDuration, + maxCacheDuration: resolver.dynamicMetadataResolverAttributes?.maxCacheDuration, + maxIdleEntityData: resolver.dynamicMetadataResolverAttributes?.maxIdleEntityData, + removeIdleEntityData: !resolver.dynamicMetadataResolverAttributes?.removeIdleEntityData ?: null, + cleanupTaskInterval: resolver.dynamicMetadataResolverAttributes?.cleanupTaskInterval, + persistentCacheManagerRef: resolver.dynamicMetadataResolverAttributes?.persistentCacheManagerRef, + persistentCacheManagerDirectory: resolver.dynamicMetadataResolverAttributes?.persistentCacheManagerDirectory, + persistentCacheKeyGeneratorRef: resolver.dynamicMetadataResolverAttributes?.persistentCacheKeyGeneratorRef, + initializeFromPersistentCacheInBackground: !resolver.dynamicMetadataResolverAttributes?.initializeFromPersistentCacheInBackground ?: null, + backgroundInitializationFromCacheDelay: resolver.dynamicMetadataResolverAttributes?.backgroundInitializationFromCacheDelay, + initializationFromCachePredicateRef: resolver.dynamicMetadataResolverAttributes?.initializationFromCachePredicateRef) { + + childNodes() + } + } + } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicHttpMetadataProviderController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicHttpMetadataProviderController.java new file mode 100644 index 000000000..866f54231 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicHttpMetadataProviderController.java @@ -0,0 +1,111 @@ +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.repository.DynamicHttpMetadataResolverRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import java.net.URI; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@RestController +@RequestMapping("/api/MetadataProvider/DynamicHttp") +public class DynamicHttpMetadataProviderController { + private static final Logger logger = LoggerFactory.getLogger(DynamicHttpMetadataProviderController.class); + + @Autowired + DynamicHttpMetadataResolverRepository repository; + + @DeleteMapping("/{resourceId}") + public ResponseEntity deleteByResourceId(@PathVariable String resourceId) { + if (repository.deleteByResourceId(resourceId)) { + return ResponseEntity.accepted().build(); + } else { + return ResponseEntity.notFound().build(); + } + } + + @GetMapping("/name/{metadataProviderName}") + @Transactional(readOnly = true) + public ResponseEntity getOneByName(@PathVariable String metadataProviderName) { + DynamicHttpMetadataResolver resolver = repository.findByName(metadataProviderName); + if (resolver == null) { + return ResponseEntity.notFound().build(); + } else { + resolver.setVersion(resolver.hashCode()); + return ResponseEntity.ok(resolver); + } + } + + @GetMapping("/{resourceId}") + @Transactional(readOnly = true) + public ResponseEntity getOneByResourceId(@PathVariable String resourceId) { + DynamicHttpMetadataResolver resolver = repository.findByResourceId(resourceId); + if (resolver == null) { + return ResponseEntity.notFound().build(); + } else { + resolver.setVersion(resolver.hashCode()); + return ResponseEntity.ok(resolver); + } + } + + @PostMapping + public ResponseEntity create(@RequestBody DynamicHttpMetadataResolver resolver) { + if (repository.findByName(resolver.getName()) != null) { + return ResponseEntity.status(HttpStatus.CONFLICT).build(); + } + + DynamicHttpMetadataResolver persistedResolver = repository.save(resolver); + persistedResolver.setVersion(persistedResolver.hashCode()); + + return ResponseEntity + .created(getResourceUriFor(persistedResolver)) + .body(persistedResolver); + } + + @PutMapping + public ResponseEntity update(@RequestBody DynamicHttpMetadataResolver resolver) { + DynamicHttpMetadataResolver existingResolver = repository.findByResourceId(resolver.getResourceId()); + + if (existingResolver == null) { + return ResponseEntity.notFound().build(); + } + + if (existingResolver.hashCode() != resolver.getVersion()) { + logger.info("Comparing: " + existingResolver.hashCode() + " with " + resolver.getVersion()); + return ResponseEntity.status(HttpStatus.CONFLICT).build(); + } + + resolver.setAudId(existingResolver.getAudId()); + //TODO: Do we need to set anything else? dates? + + DynamicHttpMetadataResolver updatedResolver = repository.save(resolver); + updatedResolver.setVersion(updatedResolver.hashCode()); + + return ResponseEntity.ok(updatedResolver); + } + + private static URI getResourceUriFor(DynamicHttpMetadataResolver resolver) { + return ServletUriComponentsBuilder + .fromCurrentServletMapping().path("/api/MetadataProvider/DynamicHttp/") + .pathSegment(resolver.getResourceId()) + .build() + .toUri(); + } + +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/LocalDynamicMetadataProviderController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/LocalDynamicMetadataProviderController.java new file mode 100644 index 000000000..2d3a104ee --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/LocalDynamicMetadataProviderController.java @@ -0,0 +1,110 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller; + +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver; +import edu.internet2.tier.shibboleth.admin.ui.repository.LocalDynamicMetadataResolverRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import java.net.URI; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@RestController +@RequestMapping("/api/MetadataProvider/LocalDynamic") +public class LocalDynamicMetadataProviderController { + private static final Logger logger = LoggerFactory.getLogger(LocalDynamicMetadataProviderController.class); + + @Autowired + LocalDynamicMetadataResolverRepository repository; + + @DeleteMapping("/{resourceId}") + public ResponseEntity deleteByResourceId(@PathVariable String resourceId) { + if (repository.deleteByResourceId(resourceId)) { + return ResponseEntity.accepted().build(); + } else { + return ResponseEntity.notFound().build(); + } + } + + @GetMapping("/name/{metadataProviderName}") + @Transactional(readOnly = true) + public ResponseEntity getOneByName(@PathVariable String metadataProviderName) { + LocalDynamicMetadataResolver resolver = repository.findByName(metadataProviderName); + if (resolver == null) { + return ResponseEntity.notFound().build(); + } else { + resolver.setVersion(resolver.hashCode()); + return ResponseEntity.ok(resolver); + } + } + + @GetMapping("/{resourceId}") + @Transactional(readOnly = true) + public ResponseEntity getOneByResourceId(@PathVariable String resourceId) { + LocalDynamicMetadataResolver resolver = repository.findByResourceId(resourceId); + if (resolver == null) { + return ResponseEntity.notFound().build(); + } else { + resolver.setVersion(resolver.hashCode()); + return ResponseEntity.ok(resolver); + } + } + + @PostMapping + public ResponseEntity create(@RequestBody LocalDynamicMetadataResolver resolver) { + if (repository.findByName(resolver.getName()) != null) { + return ResponseEntity.status(HttpStatus.CONFLICT).build(); + } + + LocalDynamicMetadataResolver persistedResolver = repository.save(resolver); + persistedResolver.setVersion(persistedResolver.hashCode()); + + return ResponseEntity + .created(getResourceUriFor(persistedResolver)) + .body(persistedResolver); + } + + @PutMapping + public ResponseEntity update(@RequestBody LocalDynamicMetadataResolver resolver) { + LocalDynamicMetadataResolver existingResolver = repository.findByResourceId(resolver.getResourceId()); + + if (existingResolver == null) { + return ResponseEntity.notFound().build(); + } + + if (existingResolver.hashCode() != resolver.getVersion()) { + logger.info("Comparing: " + existingResolver.hashCode() + " with " + resolver.getVersion()); + return ResponseEntity.status(HttpStatus.CONFLICT).build(); + } + + resolver.setAudId(existingResolver.getAudId()); + //TODO: Do we need to set anything else? dates? + + LocalDynamicMetadataResolver updatedResolver = repository.save(resolver); + updatedResolver.setVersion(updatedResolver.hashCode()); + + return ResponseEntity.ok(updatedResolver); + } + + private static URI getResourceUriFor(LocalDynamicMetadataResolver resolver) { + return ServletUriComponentsBuilder + .fromCurrentServletMapping().path("/api/MetadataProvider/LocalDynamic/") + .pathSegment(resolver.getResourceId()) + .build() + .toUri(); + } +} 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 new file mode 100644 index 000000000..0910c9050 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java @@ -0,0 +1,47 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import javax.persistence.ElementCollection; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.OrderColumn; +import java.util.List; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@Entity +@EqualsAndHashCode(callSuper = true) +@Getter +@Setter +@ToString +public class DynamicHttpMetadataResolver extends MetadataResolver { + + public static final String DEFAULT_TIMEOUT = "PT5S"; + + @Embedded + private DynamicMetadataResolverAttributes dynamicMetadataResolverAttributes; + + @Embedded + private HttpMetadataResolverAttributes httpMetadataResolverAttributes; + + private Integer maxConnectionsTotal = 100; + + private Integer maxConnectionsPerRoute = 100; + + @ElementCollection + @OrderColumn + private List supportedContentTypes; + + public DynamicHttpMetadataResolver() { + this.httpMetadataResolverAttributes = new HttpMetadataResolverAttributes(); + this.httpMetadataResolverAttributes.setConnectionRequestTimeout(DEFAULT_TIMEOUT); + this.httpMetadataResolverAttributes.setConnectionTimeout(DEFAULT_TIMEOUT); + this.httpMetadataResolverAttributes.setSocketTimeout(DEFAULT_TIMEOUT); + this.dynamicMetadataResolverAttributes = new DynamicMetadataResolverAttributes(); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicMetadataResolverAttributes.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicMetadataResolverAttributes.java new file mode 100644 index 000000000..564f2871e --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicMetadataResolverAttributes.java @@ -0,0 +1,47 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.persistence.Embeddable; + +@Embeddable +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@EqualsAndHashCode +public class DynamicMetadataResolverAttributes { + + private String parserPoolRef; + + private String taskTimerRef; + + private Double refreshDelayFactor = 0.75; + + private String minCacheDuration = "PT10M"; + + private String maxCacheDuration = "PT8H"; + + private String maxIdleEntityData = "PT8H"; + + private Boolean removeIdleEntityData; + + private String cleanupTaskInterval = "PT30M"; + + private String persistentCacheManagerRef; + + private String persistentCacheManagerDirectory; + + private String persistentCacheKeyGeneratorRef; + + private Boolean initializeFromPersistentCacheInBackground = true; + + private String backgroundInitializationFromCacheDelay = "PT2S"; + + private String initializationFromCachePredicateRef; + +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/HttpMetadataResolverAttributes.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/HttpMetadataResolverAttributes.java index 706c34e08..8054ba852 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/HttpMetadataResolverAttributes.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/HttpMetadataResolverAttributes.java @@ -28,7 +28,7 @@ public class HttpMetadataResolverAttributes { private String socketTimeout; - private Boolean disregardTLSCertificate; + private Boolean disregardTLSCertificate = false; private String tlsTrustEngineRef; 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 new file mode 100644 index 000000000..63ec78eb5 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/LocalDynamicMetadataResolver.java @@ -0,0 +1,29 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import javax.persistence.Embedded; +import javax.persistence.Entity; + +@Entity +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +@Getter +@Setter +@ToString +public class LocalDynamicMetadataResolver extends MetadataResolver { + + private String sourceDirectory; + + private String sourceManagerRef; + + private String sourceKeyGeneratorRef; + + @Embedded + private DynamicMetadataResolverAttributes dynamicMetadataResolverAttributes; + +} 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 659380db6..1bbafefba 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 @@ -45,7 +45,7 @@ public class MetadataResolver extends AbstractAuditable { private Boolean useDefaultPredicateRegistry = true; - private Boolean satisfyAnyPredicates; + private Boolean satisfyAnyPredicates = false; @OneToMany(cascade = CascadeType.ALL) @OrderColumn diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/DynamicHttpMetadataResolverRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/DynamicHttpMetadataResolverRepository.java new file mode 100644 index 000000000..c4a08804f --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/DynamicHttpMetadataResolverRepository.java @@ -0,0 +1,18 @@ +package edu.internet2.tier.shibboleth.admin.ui.repository; + +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver; +import org.springframework.data.repository.CrudRepository; + +/** + * Spring Data CRUD repository for instances of {@link DynamicHttpMetadataResolver}s. + * + * @author Bill Smith (wsmith@unicon.net) + */ +public interface DynamicHttpMetadataResolverRepository extends CrudRepository { + + DynamicHttpMetadataResolver findByName(String name); + + boolean deleteByResourceId(String resourceId); + + DynamicHttpMetadataResolver findByResourceId(String resourceId); +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/LocalDynamicMetadataResolverRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/LocalDynamicMetadataResolverRepository.java new file mode 100644 index 000000000..323b470d5 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/LocalDynamicMetadataResolverRepository.java @@ -0,0 +1,16 @@ +package edu.internet2.tier.shibboleth.admin.ui.repository; + +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver; +import org.springframework.data.repository.CrudRepository; + +/** + * Spring Data CRUD repository for instances of {@link LocalDynamicMetadataResolver}s. + */ +public interface LocalDynamicMetadataResolverRepository extends CrudRepository { + + LocalDynamicMetadataResolver findByName(String name); + + boolean deleteByResourceId(String resourceId); + + LocalDynamicMetadataResolver findByResourceId(String resourceId); +} diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicHttpMetadataProviderControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicHttpMetadataProviderControllerTests.groovy new file mode 100644 index 000000000..bf4ce2ffe --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicHttpMetadataProviderControllerTests.groovy @@ -0,0 +1,271 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import com.fasterxml.jackson.databind.ObjectMapper +import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.MetadataResolverConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.repository.DynamicHttpMetadataResolverRepository +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 groovy.json.JsonSlurper +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.test.web.servlet.setup.MockMvcBuilders +import spock.lang.Specification + +import static org.hamcrest.CoreMatchers.containsString +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.* +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.* + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@DataJpaTest +@ContextConfiguration(classes=[CoreShibUiConfiguration, SearchConfiguration, MetadataResolverConfiguration]) +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") +class DynamicHttpMetadataProviderControllerTests extends Specification { + RandomGenerator randomGenerator + TestObjectGenerator testObjectGenerator + ObjectMapper mapper + + def repository = Mock(DynamicHttpMetadataResolverRepository) + def controller + def mockMvc + + @Autowired + AttributeUtility attributeUtility + + def setup() { + randomGenerator = new RandomGenerator() + testObjectGenerator = new TestObjectGenerator(attributeUtility) + mapper = new ObjectMapper() + + controller = new DynamicHttpMetadataProviderController ( + repository: repository + ) + + mockMvc = MockMvcBuilders.standaloneSetup(controller).build() + } + + def "DELETE deletes the desired resolver"() { + given: + def randomResourceId = randomGenerator.randomId() + + 1 * repository.deleteByResourceId(randomResourceId) >> true + + when: + def result = mockMvc.perform( + delete("/api/MetadataProvider/DynamicHttp/$randomResourceId")) + + then: + result.andExpect(status().isAccepted()) + } + + def "DELETE returns error when desired resolver is not found"() { + given: + def randomResourceId = randomGenerator.randomId() + + 1 * repository.deleteByResourceId(randomResourceId) >> false + + when: + def result = mockMvc.perform( + delete("/api/MetadataProvider/DynamicHttp/$randomResourceId")) + + then: + result.andExpect(status().isNotFound()) + } + + def "POST a new resolver properly persists and returns the new persisted resolver"() { + given: + def resolver = testObjectGenerator.buildDynamicHttpMetadataResolver() + resolver.setVersion(resolver.hashCode()) + def postedJsonBody = mapper.writeValueAsString(resolver) + + 1 * repository.findByName(resolver.getName()) >> null + 1 * repository.save(_) >> resolver + + def expectedResolverUUID = resolver.getResourceId() + def expectedResponseHeader = 'Location' + def expectedResponseHeaderValue = "/api/MetadataProvider/DynamicHttp/$expectedResolverUUID" + + when: + def result = mockMvc.perform( + post('/api/MetadataProvider/DynamicHttp') + .contentType(APPLICATION_JSON_UTF8) + .content(postedJsonBody)) + + then: + result.andExpect(status().isCreated()) + .andExpect(content().json(postedJsonBody, false)) + .andExpect(header().string(expectedResponseHeader, containsString(expectedResponseHeaderValue))) + + } + + def "POST a new resolver that has a name of a persisted resolver returns conflict"() { + given: + def resolver = testObjectGenerator.buildDynamicHttpMetadataResolver() + def resolverName = resolver.name + def postedJsonBody = mapper.writeValueAsString(resolver) + + 1 * repository.findByName(resolverName) >> resolver + 0 * repository.save(_) + + when: + def result = mockMvc.perform( + post('/api/MetadataProvider/DynamicHttp') + .contentType(APPLICATION_JSON_UTF8) + .content(postedJsonBody)) + + then: + result.andExpect(status().isConflict()) + } + + def "GET by resourceId returns the desired persisted resolver"() { + given: + def resolver = testObjectGenerator.buildDynamicHttpMetadataResolver() + resolver.version = resolver.hashCode() + def resourceId = resolver.resourceId + def resolverJson = mapper.writeValueAsString(resolver) + + 1 * repository.findByResourceId(resourceId) >> resolver + + def expectedResponseContentType = APPLICATION_JSON_UTF8 + + when: + def result = mockMvc.perform( + get("/api/MetadataProvider/DynamicHttp/$resourceId")) + + then: + result.andExpect(status().isOk()) + .andExpect(content().contentType(expectedResponseContentType)) + .andExpect(content().json(resolverJson, false)) + } + + def "GET by unknown resource id returns not found"() { + given: + def randomResourceId = randomGenerator.randomId() + + 1 * repository.findByResourceId(randomResourceId) >> null + + when: + def result = mockMvc.perform( + get("/api/MetadataProvider/DynamicHttp/$randomResourceId")) + + then: + result.andExpect(status().isNotFound()) + } + + def "GET by resolver name returns the desired persisted resolver"() { + given: + def resolver = testObjectGenerator.buildDynamicHttpMetadataResolver() + resolver.version = resolver.hashCode() + def resolverName = resolver.name + def resolverJson = mapper.writeValueAsString(resolver) + + 1 * repository.findByName(resolverName) >> resolver + + def expectedResponseContentType = APPLICATION_JSON_UTF8 + + when: + def result = mockMvc.perform( + get("/api/MetadataProvider/DynamicHttp/name/$resolverName")) + + then: + result.andExpect(status().isOk()) + .andExpect(content().contentType(expectedResponseContentType)) + .andExpect(content().json(resolverJson, false)) + } + + def "GET by unknown resolver name returns not found"() { + given: + def randomResolverName = randomGenerator.randomString(10) + + 1 * repository.findByName(randomResolverName) >> null + + when: + def result = mockMvc.perform( + get("/api/MetadataProvider/DynamicHttp/name/$randomResolverName")) + + then: + result.andExpect(status().isNotFound()) + } + + def "PUT allows for a successful update of an already-persisted resolver"() { + given: + def existingResolver = testObjectGenerator.buildDynamicHttpMetadataResolver() + existingResolver.version = existingResolver.hashCode() + def resourceId = existingResolver.resourceId + def existingResolverJson = mapper.writeValueAsString(existingResolver) + + def updatedResolver = testObjectGenerator.buildDynamicHttpMetadataResolver() + updatedResolver.resourceId = existingResolver.resourceId + updatedResolver.version = existingResolver.version + def postedJsonBody = mapper.writeValueAsString(updatedResolver) + + 1 * repository.findByResourceId(existingResolver.resourceId) >> existingResolver + 1 * repository.save(_) >> updatedResolver + + def expectedResponseContentType = APPLICATION_JSON_UTF8 + + when: + def result = mockMvc.perform( + put('/api/MetadataProvider/DynamicHttp') + .contentType(APPLICATION_JSON_UTF8) + .content(postedJsonBody)) + + then: + def expectedJson = new JsonSlurper().parseText(postedJsonBody) + expectedJson << [version: updatedResolver.hashCode()] + result.andExpect(status().isOk()) + .andExpect(content().contentType(expectedResponseContentType)) + .andExpect(content().json(JsonOutput.toJson(expectedJson), false)) + } + + def "PUT of an updated resolver with an incorrect version returns a conflict"() { + given: + def existingResolver = testObjectGenerator.buildDynamicHttpMetadataResolver() + existingResolver.version = existingResolver.hashCode() + + def updatedResolver = testObjectGenerator.buildDynamicHttpMetadataResolver() + updatedResolver.resourceId = existingResolver.resourceId + updatedResolver.version = updatedResolver.hashCode() + def postedJsonBody = mapper.writeValueAsString(updatedResolver) + + 1 * repository.findByResourceId(existingResolver.resourceId) >> existingResolver + 0 * repository.save(_) + + when: + def result = mockMvc.perform( + put('/api/MetadataProvider/DynamicHttp') + .contentType(APPLICATION_JSON_UTF8) + .content(postedJsonBody)) + + then: + result.andExpect(status().isConflict()) + } + + def "PUT of a resolver that is not persisted returns not found"() { + given: + def resolver = testObjectGenerator.buildDynamicHttpMetadataResolver() + def postedJsonBody = mapper.writeValueAsString(resolver) + + 1 * repository.findByResourceId(resolver.resourceId) >> null + 0 * repository.save(_) + + when: + def result = mockMvc.perform( + put('/api/MetadataProvider/DynamicHttp') + .contentType(APPLICATION_JSON_UTF8) + .content(postedJsonBody)) + + then: + result.andExpect(status().isNotFound()) + } +} diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/LocalDynamicMetadataProviderControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/LocalDynamicMetadataProviderControllerTests.groovy new file mode 100644 index 000000000..bc13df607 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/LocalDynamicMetadataProviderControllerTests.groovy @@ -0,0 +1,275 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import com.fasterxml.jackson.databind.ObjectMapper +import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.MetadataResolverConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.repository.LocalDynamicMetadataResolverRepository +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 groovy.json.JsonSlurper +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.test.web.servlet.setup.MockMvcBuilders +import spock.lang.Specification + +import static org.hamcrest.CoreMatchers.containsString +import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.* +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.* + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@DataJpaTest +@ContextConfiguration(classes=[CoreShibUiConfiguration, SearchConfiguration, MetadataResolverConfiguration]) +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") +class LocalDynamicMetadataProviderControllerTests extends Specification { + RandomGenerator randomGenerator + TestObjectGenerator testObjectGenerator + ObjectMapper mapper + + def repository = Mock(LocalDynamicMetadataResolverRepository) + def controller + def mockMvc + + @Autowired + AttributeUtility attributeUtility + + def setup() { + randomGenerator = new RandomGenerator() + testObjectGenerator = new TestObjectGenerator(attributeUtility) + mapper = new ObjectMapper() + + controller = new LocalDynamicMetadataProviderController ( + repository: repository + ) + + mockMvc = MockMvcBuilders.standaloneSetup(controller).build() + } + + def "DELETE deletes the desired resolver"() { + given: + def randomResourceId = randomGenerator.randomId() + + 1 * repository.deleteByResourceId(randomResourceId) >> true + + when: + def result = mockMvc.perform( + delete("/api/MetadataProvider/LocalDynamic/$randomResourceId")) + + then: + result.andExpect(status().isAccepted()) + } + + def "DELETE returns error when desired resolver is not found"() { + given: + def randomResourceId = randomGenerator.randomId() + + 1 * repository.deleteByResourceId(randomResourceId) >> false + + when: + def result = mockMvc.perform( + delete("/api/MetadataProvider/LocalDynamic/$randomResourceId")) + + then: + result.andExpect(status().isNotFound()) + } + + def "POST a new resolver properly persists and returns the new persisted resolver"() { + given: + def resolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + resolver.version = resolver.hashCode() + def postedJsonBody = mapper.writeValueAsString(resolver) + + 1 * repository.findByName(resolver.getName()) >> null + 1 * repository.save(_) >> resolver + + def expectedResolverUUID = resolver.getResourceId() + def expectedResponseHeader = 'Location' + def expectedResponseHeaderValue = "/api/MetadataProvider/LocalDynamic/$expectedResolverUUID" + + when: + def result = mockMvc.perform( + post('/api/MetadataProvider/LocalDynamic') + .contentType(APPLICATION_JSON_UTF8) + .content(postedJsonBody)) + + then: + result.andExpect(status().isCreated()) + .andExpect(content().json(postedJsonBody, false)) + .andExpect(header().string(expectedResponseHeader, containsString(expectedResponseHeaderValue))) + } + + def "POST a new resolver that has a name of a persisted resolver returns conflict"() { + given: + def resolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + resolver.version = resolver.hashCode() + def postedJsonBody = mapper.writeValueAsString(resolver) + + 1 * repository.findByName(resolver.name) >> resolver + 0 * repository.save(_) + + when: + def result = mockMvc.perform( + post('/api/MetadataProvider/LocalDynamic') + .contentType(APPLICATION_JSON_UTF8) + .content(postedJsonBody)) + + then: + result.andExpect(status().isConflict()) + } + + def "GET by resourceId returns the desired persisted resolver"() { + given: + def resolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + resolver.version = resolver.hashCode() + def resolverJson = mapper.writeValueAsString(resolver) + def resolverId = resolver.resourceId + + 1 * repository.findByResourceId(resolverId) >> resolver + + def expectedResponseContentType = APPLICATION_JSON_UTF8 + + when: + def result = mockMvc.perform( + get("/api/MetadataProvider/LocalDynamic/$resolverId")) + + then: + result.andExpect(status().isOk()) + .andExpect(content().contentType(expectedResponseContentType)) + .andExpect(content().json(resolverJson, false)) + } + + def "GET by unknown resource id returns not found"() { + given: + def randomResourceId = randomGenerator.randomId() + + 1 * repository.findByResourceId(randomResourceId) >> null + + when: + def result = mockMvc.perform( + get("/api/MetadataProvider/LocalDynamic/$randomResourceId")) + + then: + result.andExpect(status().isNotFound()) + } + + def "GET by resolver name returns the desired persisted resolver"() { + given: + def resolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + resolver.version = resolver.hashCode() + def resolverName = resolver.name + def resolverJson = mapper.writeValueAsString(resolver) + + 1 * repository.findByName(resolverName) >> resolver + + def expectedResponseContentType = APPLICATION_JSON_UTF8 + + when: + def result = mockMvc.perform( + get("/api/MetadataProvider/LocalDynamic/name/$resolverName")) + + then: + result.andExpect(status().isOk()) + .andExpect(content().contentType(expectedResponseContentType)) + .andExpect(content().json(resolverJson, false)) + } + + def "GET by unknown resolver name returns not found"() { + given: + def randomResolverName = randomGenerator.randomString(10) + + 1 * repository.findByName(randomResolverName) >> null + + when: + def result = mockMvc.perform( + get("/api/MetadataProvider/LocalDynamic/name/$randomResolverName")) + + then: + result.andExpect(status().isNotFound()) + } + + def "PUT allows for a successful update of an already-persisted resolver"() { + given: + def existingResolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + existingResolver.version = existingResolver.hashCode() + def resourceId = existingResolver.resourceId + def updatedResolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + updatedResolver.setResourceId(resourceId) + updatedResolver.setVersion(existingResolver.hashCode()) + def postedJsonBody = mapper.writeValueAsString(updatedResolver) + + 1 * repository.findByResourceId(resourceId) >> existingResolver + 1 * repository.save(_) >> updatedResolver + + def expectedResponseContentType = APPLICATION_JSON_UTF8 + + when: + def result = mockMvc.perform( + put('/api/MetadataProvider/LocalDynamic') + .contentType(APPLICATION_JSON_UTF8) + .content(postedJsonBody)) + + then: + def expectedJson = new JsonSlurper().parseText(postedJsonBody) + expectedJson << [version: updatedResolver.hashCode()] + result.andExpect(status().isOk()) + .andExpect(content().contentType(expectedResponseContentType)) + .andExpect(content().json(JsonOutput.toJson(expectedJson), false)) + } + + def "PUT of an updated resolver with an incorrect version returns a conflict"() { + given: + def existingResolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + existingResolver.version = existingResolver.hashCode() + def resourceId = existingResolver.resourceId + + def updatedResolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + updatedResolver.resourceId = resourceId + updatedResolver.version = updatedResolver.hashCode() + def postedJsonBody = mapper.writeValueAsString(updatedResolver) + + 1 * repository.findByResourceId(resourceId) >> existingResolver + 0 * repository.save(_) + + when: + def result = mockMvc.perform( + put('/api/MetadataProvider/LocalDynamic') + .contentType(APPLICATION_JSON_UTF8) + .content(postedJsonBody)) + + then: + result.andExpect(status().isConflict()) + } + + def "PUT of a resolver that is not persisted returns not found"() { + given: + def existingResolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + existingResolver.version = existingResolver.hashCode() + def resourceId = existingResolver.resourceId + + def updatedResolver = testObjectGenerator.buildLocalDynamicMetadataResolver() + updatedResolver.resourceId = resourceId + updatedResolver.version = updatedResolver.hashCode() + def postedJsonBody = mapper.writeValueAsString(updatedResolver) + + 1 * repository.findByResourceId(resourceId) >> null + 0 * repository.save(_) + + when: + def result = mockMvc.perform( + put('/api/MetadataProvider/LocalDynamic') + .contentType(APPLICATION_JSON_UTF8) + .content(postedJsonBody)) + + then: + result.andExpect(status().isNotFound()) + } +} diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/FileBackedHttpMetadataResolverRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/FileBackedHttpMetadataResolverRepositoryTests.groovy index 8f38882d5..5ba740fcf 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/FileBackedHttpMetadataResolverRepositoryTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/FileBackedHttpMetadataResolverRepositoryTests.groovy @@ -54,7 +54,7 @@ class FileBackedHttpMetadataResolverRepositoryTests extends Specification { it.metadataFilters.add(new EntityAttributesFilter().with { it.entityAttributesFilterTarget = new EntityAttributesFilterTarget().with { it.entityAttributesFilterTargetType = ENTITY - it.setSingleValue(["hola"]) + it.setValue(["hola"]) it } it diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/LocalDynamicMetadataResolverRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/LocalDynamicMetadataResolverRepositoryTests.groovy new file mode 100644 index 000000000..027b9c030 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/LocalDynamicMetadataResolverRepositoryTests.groovy @@ -0,0 +1,79 @@ +package edu.internet2.tier.shibboleth.admin.ui.repository + +import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.MetadataResolverConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter +import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicMetadataResolverAttributes +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver +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.annotation.DirtiesContext +import org.springframework.test.context.ContextConfiguration +import spock.lang.Specification + +import javax.persistence.EntityManager + +import static edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget.EntityAttributesFilterTargetType.ENTITY + +@DataJpaTest +@ContextConfiguration(classes=[CoreShibUiConfiguration, SearchConfiguration, MetadataResolverConfiguration]) +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") +@DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD) +class LocalDynamicMetadataResolverRepositoryTests extends Specification { + + @Autowired + LocalDynamicMetadataResolverRepository repositoryUnderTest + + @Autowired + EntityManager entityManager + + def "local dynamic metadata resolver instances persist OK"() { + when: + def mdr = new LocalDynamicMetadataResolver().with { + it.name = 'LocalDynamicMetadataResolver' + it.sourceDirectory = '/etc/shibui' + it.sourceKeyGeneratorRef = 'sourceKeyGeneratorBean' + it.sourceManagerRef = 'sourceManagerBean' + it.dynamicMetadataResolverAttributes = new DynamicMetadataResolverAttributes().with { + it.cleanupTaskInterval = 'PT5H' + it.maxCacheDuration = 'PT8H' + it.initializeFromPersistentCacheInBackground = true + it + } + + it.metadataFilters.add(new EntityAttributesFilter().with { + it.entityAttributesFilterTarget = new EntityAttributesFilterTarget().with { + it.entityAttributesFilterTargetType = ENTITY + it.setValue(['hola']) + it + } + it + }) + + it + } + repositoryUnderTest.save(mdr) + + then: + repositoryUnderTest.findAll().size() > 0 + def item = repositoryUnderTest.findByName("LocalDynamicMetadataResolver") + + item.name == "LocalDynamicMetadataResolver" + item.metadataFilters.size() == 1 + item.metadataFilters[0].entityAttributesFilterTarget.entityAttributesFilterTargetType == ENTITY + item.metadataFilters[0].entityAttributesFilterTarget.value.size() == 1 + item.metadataFilters[0].entityAttributesFilterTarget.value.get(0) == "hola" + item.sourceDirectory == '/etc/shibui' + item.sourceKeyGeneratorRef == 'sourceKeyGeneratorBean' + item.sourceManagerRef == 'sourceManagerBean' + item.dynamicMetadataResolverAttributes.cleanupTaskInterval == 'PT5H' + item.dynamicMetadataResolverAttributes.maxCacheDuration == 'PT8H' + item.dynamicMetadataResolverAttributes.initializeFromPersistentCacheInBackground + } + +} diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/MetadataResolverRepositoryTest.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/MetadataResolverRepositoryTest.groovy index 4ec3b06ed..e2815dd2a 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/MetadataResolverRepositoryTest.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/MetadataResolverRepositoryTest.groovy @@ -3,7 +3,6 @@ package edu.internet2.tier.shibboleth.admin.ui.repository import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.MetadataResolverConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration - import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.RelyingPartyOverridesRepresentation @@ -11,7 +10,6 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityDescriptorServiceImpl import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityServiceImpl - import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.domain.EntityScan import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/IncommonJPAMetadataResolverServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/IncommonJPAMetadataResolverServiceImplTests.groovy index 7bf8c2cba..f249a5324 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/IncommonJPAMetadataResolverServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/IncommonJPAMetadataResolverServiceImplTests.groovy @@ -106,6 +106,12 @@ class IncommonJPAMetadataResolverServiceImplTests extends Specification { def mr = new TestObjectGenerator(attributeUtility).fileBackedHttpMetadataResolver() mr.setName("HTTPMetadata") metadataResolverRepository.save(mr) + + // Generate and test edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver. + metadataResolverRepository.save(new TestObjectGenerator(attributeUtility).dynamicHttpMetadataResolver()) + + // Generate and test edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver. + metadataResolverRepository.save(new TestObjectGenerator(attributeUtility).localDynamicMetadataResolver()) } return resolver diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy index 1b020c499..4069ab849 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy @@ -1,22 +1,14 @@ package edu.internet2.tier.shibboleth.admin.ui.util -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.* import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget -import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor -import edu.internet2.tier.shibboleth.admin.ui.domain.LocalizedName -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.filters.EntityRoleWhiteListFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.FilterRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.FilterTargetRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.RelyingPartyOverridesRepresentation -import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver -import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.HttpMetadataResolverAttributes -import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ReloadableMetadataResolverAttributes +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.* import edu.internet2.tier.shibboleth.admin.util.AttributeUtility import edu.internet2.tier.shibboleth.admin.util.MDDCConstants import org.opensaml.saml.saml2.metadata.Organization @@ -36,6 +28,25 @@ class TestObjectGenerator { this.attributeUtility = attributeUtility } + DynamicHttpMetadataResolver buildDynamicHttpMetadataResolver() { + def resolver = new DynamicHttpMetadataResolver().with { + it.dynamicMetadataResolverAttributes = buildDynamicMetadataResolverAttributes() + it.httpMetadataResolverAttributes = buildHttpMetadataResolverAttributes() + it.maxConnectionsPerRoute = generator.randomInt(1, 100) + it.maxConnectionsTotal = generator.randomInt(1, 100) + it.supportedContentTypes = generator.randomStringList() + it.name = generator.randomString(10) + it.requireValidMetadata = generator.randomBoolean() + it.failFastInitialization = generator.randomBoolean() + it.sortKey = generator.randomInt(1, 10) + it.criterionPredicateRegistryRef = generator.randomString(10) + it.useDefaultPredicateRegistry = generator.randomBoolean() + it.satisfyAnyPredicates = generator.randomBoolean() + it.metadataFilters = buildAllTypesOfFilterList() + it + } + return resolver + } HttpMetadataResolverAttributes buildHttpMetadataResolverAttributes() { def attributes = new HttpMetadataResolverAttributes().with { @@ -59,6 +70,50 @@ class TestObjectGenerator { return attributes } + HttpMetadataResolverAttributes.HttpCachingType randomHttpCachingType() { + HttpMetadataResolverAttributes.HttpCachingType.values()[generator.randomInt(0, 2)] + } + + LocalDynamicMetadataResolver buildLocalDynamicMetadataResolver() { + def resolver = new LocalDynamicMetadataResolver().with { + it.dynamicMetadataResolverAttributes = buildDynamicMetadataResolverAttributes() + it.sourceDirectory = generator.randomString(10) + it.sourceKeyGeneratorRef = generator.randomString(10) + it.sourceManagerRef = generator.randomString(10) + it.failFastInitialization = generator.randomBoolean() + it.name = generator.randomString(10) + it.requireValidMetadata = generator.randomBoolean() + it.useDefaultPredicateRegistry = generator.randomBoolean() + it.criterionPredicateRegistryRef = generator.randomString(10) + it.satisfyAnyPredicates = generator.randomBoolean() + it.sortKey = generator.randomInt(1, 10) + it.metadataFilters = buildAllTypesOfFilterList() + it + } + return resolver + } + + DynamicMetadataResolverAttributes buildDynamicMetadataResolverAttributes() { + def attributes = new DynamicMetadataResolverAttributes().with { + it.backgroundInitializationFromCacheDelay = generator.randomString(10) + it.cleanupTaskInterval = generator.randomString(10) + it.initializationFromCachePredicateRef = generator.randomString(10) + it.initializeFromPersistentCacheInBackground = generator.randomBoolean() + it.maxCacheDuration = generator.randomString(5) + it.maxIdleEntityData = generator.randomString(5) + it.minCacheDuration = generator.randomString(5) + it.parserPoolRef = generator.randomString(10) + it.persistentCacheKeyGeneratorRef = generator.randomString(10) + it.persistentCacheManagerDirectory = generator.randomString(10) + it.persistentCacheManagerRef = generator.randomString(10) + it.refreshDelayFactor = generator.randomInt(1, 5) + it.removeIdleEntityData = generator.randomBoolean() + it.taskTimerRef = generator.randomString() + it + } + attributes + } + List buildAllTypesOfFilterList() { List filterList = new ArrayList<>() (1..generator.randomInt(4, 10)).each { @@ -262,6 +317,20 @@ class TestObjectGenerator { } } + DynamicHttpMetadataResolver dynamicHttpMetadataResolver() { + new DynamicHttpMetadataResolver().with { + it.name = 'DynamicHTTP' + it + } + } + + LocalDynamicMetadataResolver localDynamicMetadataResolver() { + new LocalDynamicMetadataResolver().with { + it.name = 'LocalDynamic' + it + } + } + FileBackedHttpMetadataResolver buildFileBackedHttpMetadataResolver() { def resolver = new FileBackedHttpMetadataResolver() resolver.name = generator.randomString(10) @@ -290,10 +359,6 @@ class TestObjectGenerator { return attributes } - HttpMetadataResolverAttributes.HttpCachingType randomHttpCachingType() { - HttpMetadataResolverAttributes.HttpCachingType.values()[generator.randomInt(0, 2)] - } - /** * This method takes a type and a size and builds a List of that size containing objects of that type. This is * intended to be used with things that extend LocalizedName such as {@link OrganizationName}, {@link OrganizationDisplayName}, diff --git a/backend/src/test/resources/conf/278.2.xml b/backend/src/test/resources/conf/278.2.xml index 7b8718caf..208501c45 100644 --- a/backend/src/test/resources/conf/278.2.xml +++ b/backend/src/test/resources/conf/278.2.xml @@ -8,6 +8,34 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xsi:schemaLocation="urn:mace:shibboleth:2.0:metadata http://shibboleth.net/schema/idp/shibboleth-metadata.xsd urn:mace:shibboleth:2.0:resource http://shibboleth.net/schema/idp/shibboleth-resource.xsd urn:mace:shibboleth:2.0:security http://shibboleth.net/schema/idp/shibboleth-security.xsd urn:oasis:names:tc:SAML:2.0:metadata http://docs.oasis-open.org/security/saml/v2.0/saml-schema-metadata-2.0.xsd urn:oasis:names:tc:SAML:2.0:assertion http://docs.oasis-open.org/security/saml/v2.0/saml-schema-assertion-2.0.xsd"> + + + + + md:SPSSODescriptor + + + + there + + https://sp1.example.org + + - - - there - - https://sp1.example.org + + md:SPSSODescriptor + + + + + + + md:SPSSODescriptor diff --git a/backend/src/test/resources/conf/278.xml b/backend/src/test/resources/conf/278.xml index 5d02714ff..7fb468c52 100644 --- a/backend/src/test/resources/conf/278.xml +++ b/backend/src/test/resources/conf/278.xml @@ -8,6 +8,27 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xsi:schemaLocation="urn:mace:shibboleth:2.0:metadata http://shibboleth.net/schema/idp/shibboleth-metadata.xsd urn:mace:shibboleth:2.0:resource http://shibboleth.net/schema/idp/shibboleth-resource.xsd urn:mace:shibboleth:2.0:security http://shibboleth.net/schema/idp/shibboleth-security.xsd urn:oasis:names:tc:SAML:2.0:metadata http://docs.oasis-open.org/security/saml/v2.0/saml-schema-metadata-2.0.xsd urn:oasis:names:tc:SAML:2.0:assertion http://docs.oasis-open.org/security/saml/v2.0/saml-schema-assertion-2.0.xsd"> + + + + + md:SPSSODescriptor + + - + + + + + md:SPSSODescriptor + +