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 2106b52a2..e9e530d64 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 @@ -1,8 +1,19 @@ package edu.internet2.tier.shibboleth.admin.ui.configuration +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.DynamicHttpMetadataResolver +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.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.repository.MetadataResolverRepository import edu.internet2.tier.shibboleth.admin.ui.security.model.Role import edu.internet2.tier.shibboleth.admin.ui.security.model.User 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 import org.springframework.transaction.annotation.Transactional @@ -14,8 +25,11 @@ import javax.annotation.PostConstruct class DevConfig { private final UserRepository adminUserRepository - DevConfig(UserRepository adminUserRepository) { + private final MetadataResolverRepository metadataResolverRepository + + DevConfig(UserRepository adminUserRepository, MetadataResolverRepository metadataResolverRepository) { this.adminUserRepository = adminUserRepository + this.metadataResolverRepository = metadataResolverRepository } @Transactional @@ -32,4 +46,57 @@ class DevConfig { adminUserRepository.save(user) } } + + @Transactional + @Profile('fbhmr') + @Bean + MetadataResolver fbhmr(ModelRepresentationConversions modelRepresentationConversions) { + return this.metadataResolverRepository.save(new FileBackedHttpMetadataResolver().with { + enabled = true + xmlId = 'test-fbhmr' + name = 'test-fbhmr' + metadataURL = 'http://md.incommon.org/InCommon/InCommon-metadata.xml' + backingFile = '%{idp.home}/test-fbhmr.xml' + reloadableMetadataResolverAttributes = new ReloadableMetadataResolverAttributes() + httpMetadataResolverAttributes = new HttpMetadataResolverAttributes() + it.metadataFilters.add(new EntityAttributesFilter().with { + it.name = 'test' + it.filterEnabled = true + it.entityAttributesFilterTarget = new EntityAttributesFilterTarget().with { + it.entityAttributesFilterTargetType = EntityAttributesFilterTarget.EntityAttributesFilterTargetType.ENTITY + it.value = ["https://carmenwiki.osu.edu/shibboleth"] + return it + } + it.attributeRelease = ['eduPersonPrincipalName', 'givenName', 'surname', 'mail'] + it.relyingPartyOverrides = null + return it + }) + return it + }) + } + + @Profile('dhmr') + @Transactional + @Bean + MetadataResolver dhmr(ModelRepresentationConversions modelRepresentationConversions) { + return this.metadataResolverRepository.save(new DynamicHttpMetadataResolver().with { + it.enabled = true + it.xmlId = 'test-dhmr' + it.name = 'test-dhmr' + it.metadataRequestURLConstructionScheme = new MetadataQueryProtocolScheme(content: 'http://mdq-beta.incommon.org/global') + it.metadataFilters.add(new EntityAttributesFilter().with { + it.name = 'test' + it.filterEnabled = true + it.entityAttributesFilterTarget = new EntityAttributesFilterTarget().with { + it.entityAttributesFilterTargetType = EntityAttributesFilterTarget.EntityAttributesFilterTargetType.ENTITY + it.value = ["https://issues.shibboleth.net/shibboleth"] + return it + } + it.attributeRelease = ['eduPersonPrincipalName', 'givenName', 'surname', 'mail'] + it.relyingPartyOverrides = null + return it + }) + return it + }) + } } diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverUiDefinitionController.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverUiDefinitionController.groovy index 8232c687d..e6c878b0b 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverUiDefinitionController.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverUiDefinitionController.groovy @@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RestController import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.filesystemMetadataProviderSchema import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.localDynamicMetadataProviderSchema -//import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.dynamicHttpMetadataProviderSchema +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.dynamicHttpMetadataProviderSchema import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType @@ -44,9 +44,9 @@ class MetadataResolverUiDefinitionController { case SchemaType.LOCAL_DYNAMIC_METADATA_RESOLVER: jsonSchemaLocation = localDynamicMetadataProviderSchema(this.jsonSchemaResourceLocationRegistry) break -/* case SchemaType.DYNAMIC_HTTP_METADATA_RESOLVER: + case SchemaType.DYNAMIC_HTTP_METADATA_RESOLVER: jsonSchemaLocation = dynamicHttpMetadataProviderSchema(this.jsonSchemaResourceLocationRegistry) - break*/ + break default: throw new UnsupportedOperationException("Json schema for an unsupported metadata resolver (" + resolverType + ") was requested") } 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 4a95484e8..c444bd214 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 @@ -10,7 +10,11 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetada 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.MetadataQueryProtocolScheme +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataRequestURLConstructionScheme +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.RegexScheme 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.domain.resolvers.opensaml.OpenSamlChainingMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.Refilterable import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects @@ -287,6 +291,36 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService { httpMaxCacheEntries: resolver.httpMetadataResolverAttributes?.httpMaxCacheEntries, httpMaxCacheEntrySize: resolver.httpMetadataResolverAttributes?.httpMaxCacheEntrySize) { + switch (MetadataRequestURLConstructionScheme.SchemeType.get(resolver.metadataRequestURLConstructionScheme.type)) { + case MetadataRequestURLConstructionScheme.SchemeType.METADATA_QUERY_PROTOCOL: + MetadataQueryProtocolScheme scheme = (MetadataQueryProtocolScheme) resolver.metadataRequestURLConstructionScheme + MetadataQueryProtocol(transformRef: scheme.transformRef) { + if (scheme.content != null) { + mkp.yield(scheme.content) + } + } + break + case MetadataRequestURLConstructionScheme.SchemeType.TEMPLATE: + TemplateScheme scheme = (TemplateScheme) resolver.metadataRequestURLConstructionScheme + Template(encodingStyle: scheme.encodingStyle, + transformRef: scheme.transformRef, + velocityEngine: scheme.velocityEngine) { + if (scheme.content != null) { + mkp.yield(scheme.content) + } + } + break + case MetadataRequestURLConstructionScheme.SchemeType.REGEX: + RegexScheme scheme = (RegexScheme) resolver.metadataRequestURLConstructionScheme + Regex(match: scheme.match) { + if (scheme.content != null) { + mkp.yield(scheme.content) + } + } + break + default: + break + } childNodes() } } 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 b548b98d4..2bd90bcc3 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 @@ -1,6 +1,9 @@ package edu.internet2.tier.shibboleth.admin.ui; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository; +import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -16,6 +19,7 @@ import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; @SpringBootApplication @ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "edu.internet2.tier.shibboleth.admin.ui.configuration.auto.*")) @@ -25,6 +29,8 @@ @EnableWebSecurity public class ShibbolethUiApplication extends SpringBootServletInitializer { + private static final Logger logger = LoggerFactory.getLogger(ShibbolethUiApplication.class); + @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(ShibbolethUiApplication.class); @@ -42,9 +48,29 @@ public static class MetadataResolversResourceIdEmitter { MetadataResolverRepository metadataResolverRepository; @EventListener - void showMetadataResolversResourceIds(ApplicationStartedEvent e) { + public void showMetadataResolversResourceIds(ApplicationStartedEvent e) { + metadataResolverRepository.findAll() + .forEach(it -> logger.info(String.format("MetadataResolver [%s: %s]", it.getName(), it.getResourceId()))); + } + } + + @Component + public static class MetadataResolverInitializingApplicationStartupListener { + + @Autowired + MetadataResolverService metadataResolverService; + + @Autowired + MetadataResolverRepository metadataResolverRepository; + + @Transactional + @EventListener + public void initializeResolvers(ApplicationStartedEvent e) { metadataResolverRepository.findAll() - .forEach(it -> System.out.println(String.format("MetadataResolver [%s: %s]", it.getName(), it.getResourceId()))); + .forEach(it -> { + logger.info(String.format("Reloading filters for resolver [%s: %s]", it.getName(), it.getResourceId())); + metadataResolverService.reloadFilters(it.getResourceId()); + }); } } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/JsonSchemaComponentsConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/JsonSchemaComponentsConfiguration.java index 1f58fb3cc..f179c7880 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/JsonSchemaComponentsConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/JsonSchemaComponentsConfiguration.java @@ -14,7 +14,7 @@ import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.METADATA_SOURCES; import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.FILESYSTEM_METADATA_RESOLVER; import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.LOCAL_DYNAMIC_METADATA_RESOLVER; -//import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.DYNAMIC_HTTP_METADATA_RESOLVER; +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.DYNAMIC_HTTP_METADATA_RESOLVER; /** * @author Dmitriy Kopylenko @@ -43,10 +43,10 @@ public class JsonSchemaComponentsConfiguration { @Setter private String localDynamicMetadataResolverUiSchemaLocation = "classpath:local-dynamic-metadata-provider.schema.json"; -/* TODO: Will be added as part of SHIBUI-704 + //Configured via @ConfigurationProperties (using setter method) with 'shibui.dynamic-http-metadata-provider-ui-schema-location' property and + // default value set here if that property is not explicitly set in application.properties @Setter private String dynamicHttpMetadataResolverUiSchemaLocation = "classpath:dynamic-http-metadata-provider.schema.json"; -*/ @Bean public JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry(ResourceLoader resourceLoader, ObjectMapper jacksonMapper) { @@ -74,13 +74,13 @@ public JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry(Res .resourceLoader(resourceLoader) .jacksonMapper(jacksonMapper) .detectMalformedJson(true) - .build()); - /*.register(DYNAMIC_HTTP_METADATA_RESOLVER, JsonSchemaLocationBuilder.with() + .build()) + .register(DYNAMIC_HTTP_METADATA_RESOLVER, JsonSchemaLocationBuilder.with() .jsonSchemaLocation(dynamicHttpMetadataResolverUiSchemaLocation) .resourceLoader(resourceLoader) .jacksonMapper(jacksonMapper) .detectMalformedJson(true) - .build())*/ + .build()); } @Bean diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/MetadataResolverConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/MetadataResolverConfiguration.java index ad407cfb5..6378990a5 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/MetadataResolverConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/MetadataResolverConfiguration.java @@ -5,7 +5,6 @@ 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.TokenPlaceholderValueResolvingService; import edu.internet2.tier.shibboleth.admin.util.TokenPlaceholderResolvers; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.resolver.ResolverException; @@ -16,10 +15,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.Transactional; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.Set; /** * this is a temporary class until a better way of doing this is found. @@ -41,9 +43,10 @@ public class MetadataResolverConfiguration { MetadataResolverConverterService metadataResolverConverterService; @Bean + @Transactional //This injected dependency makes sure that this bean has been created and the wrapped placeholder resolver service //is available via static facade accessor method to all the downstream non-Spring managed consumers - public MetadataResolver metadataResolver(TokenPlaceholderResolvers tokenPlaceholderResolvers) throws ResolverException, ComponentInitializationException { + public MetadataResolver metadataResolver(TokenPlaceholderResolvers tokenPlaceholderResolvers, Optional> metadataResolvers) throws ResolverException, ComponentInitializationException { ChainingMetadataResolver metadataResolver = new OpenSamlChainingMetadataResolver(); metadataResolver.setId("chain"); 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 2250a0b18..63c1ffa36 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 @@ -219,9 +219,11 @@ public void setNil(@Nullable XSBooleanValue xsBooleanValue) { } + private transient final LockableClassToInstanceMultiMap objectMetadata = new LockableClassToInstanceMultiMap<>(true); + @Nonnull public LockableClassToInstanceMultiMap getObjectMetadata() { - return null; + return objectMetadata; } public String getSchemaTypeNamespaceURI() { 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 d1b3692d2..b98d4188b 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 @@ -5,9 +5,11 @@ import lombok.Setter; import lombok.ToString; +import javax.persistence.CascadeType; import javax.persistence.ElementCollection; import javax.persistence.Embedded; import javax.persistence.Entity; +import javax.persistence.OneToOne; import javax.persistence.OrderColumn; import java.util.List; @@ -36,6 +38,9 @@ public class DynamicHttpMetadataResolver extends MetadataResolver { @OrderColumn private List supportedContentTypes; + @OneToOne(cascade = CascadeType.ALL) + private MetadataRequestURLConstructionScheme metadataRequestURLConstructionScheme; + public DynamicHttpMetadataResolver() { type = "DynamicHttpMetadataResolver"; this.httpMetadataResolverAttributes = new HttpMetadataResolverAttributes(); 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 new file mode 100644 index 000000000..cc6dffb98 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataQueryProtocolScheme.java @@ -0,0 +1,23 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +import javax.persistence.Entity; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@Entity +@Getter +@Setter +@EqualsAndHashCode(callSuper = true) +public class MetadataQueryProtocolScheme extends MetadataRequestURLConstructionScheme { + + public MetadataQueryProtocolScheme() { + type = "MetadataQueryProtocol"; + } + + private String transformRef; +} 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 new file mode 100644 index 000000000..adddcc5aa --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataRequestURLConstructionScheme.java @@ -0,0 +1,61 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import edu.internet2.tier.shibboleth.admin.ui.domain.AbstractAuditable; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +import javax.persistence.Entity; +import javax.persistence.Transient; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@Entity +@Getter +@Setter +@EqualsAndHashCode(callSuper = true) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "@type", visible = true) +@JsonSubTypes({@JsonSubTypes.Type(value=MetadataQueryProtocolScheme.class, name="MetadataQueryProtocol"), + @JsonSubTypes.Type(value=TemplateScheme.class, name="Template"), + @JsonSubTypes.Type(value=RegexScheme.class, name="Regex")}) +public abstract class MetadataRequestURLConstructionScheme extends AbstractAuditable { + public enum SchemeType { + METADATA_QUERY_PROTOCOL("MetadataQueryProtocol"), + TEMPLATE("Template"), + REGEX("Regex"); + + private String schemeType; + private static final Map lookup = new HashMap<>(); + + static { + for (SchemeType schemeType : SchemeType.values()) { + lookup.put(schemeType.toString(), schemeType); + } + } + + SchemeType(String schemeType) { + this.schemeType = schemeType; + } + + public static SchemeType get(String schemeType) { + return lookup.get(schemeType); + } + + @Override + public String toString() { + return schemeType; + } + } + + @JsonProperty("@type") + @Transient + String type; + + String content; +} 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 new file mode 100644 index 000000000..bb175432c --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/RegexScheme.java @@ -0,0 +1,25 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +import javax.persistence.Entity; +import javax.validation.constraints.NotNull; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@Entity +@Getter +@Setter +@EqualsAndHashCode(callSuper = true) +public class RegexScheme extends MetadataRequestURLConstructionScheme { + + public RegexScheme() { + type = "Regex"; + } + + @NotNull + private String match; +} 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 new file mode 100644 index 000000000..6cb0c8d90 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/TemplateScheme.java @@ -0,0 +1,31 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +import javax.persistence.Entity; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@Entity +@Getter +@Setter +@EqualsAndHashCode(callSuper = true) +public class TemplateScheme extends MetadataRequestURLConstructionScheme { + + public TemplateScheme () { + type = "Template"; + } + + public enum EncodingStyle { + NONE, FORM, PATH, FRAGMENT + } + + private EncodingStyle encodingStyle = EncodingStyle.FORM; + + private String transformRef; + + private String velocityEngine = "shibboleth.VelocityEngine"; +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlFunctionDrivenDynamicHTTPMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlFunctionDrivenDynamicHTTPMetadataResolver.java index 1235660d8..cd952a777 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlFunctionDrivenDynamicHTTPMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlFunctionDrivenDynamicHTTPMetadataResolver.java @@ -1,11 +1,19 @@ package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml; import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver; +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataRequestURLConstructionScheme; +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.RegexScheme; +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.TemplateScheme; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.xml.ParserPool; import org.apache.http.impl.client.HttpClients; import org.apache.lucene.index.IndexWriter; +import org.apache.velocity.app.VelocityEngine; +import org.opensaml.saml.metadata.resolver.filter.MetadataFilterChain; import org.opensaml.saml.metadata.resolver.impl.FunctionDrivenDynamicHTTPMetadataResolver; +import org.opensaml.saml.metadata.resolver.impl.MetadataQueryProtocolRequestURLBuilder; +import org.opensaml.saml.metadata.resolver.impl.RegexRequestURLBuilder; +import org.opensaml.saml.metadata.resolver.impl.TemplateRequestURLBuilder; /** * @author Bill Smith (wsmith@unicon.net) @@ -33,19 +41,44 @@ public OpenSamlFunctionDrivenDynamicHTTPMetadataResolver(ParserPool parserPool, this.setSupportedContentTypes(sourceResolver.getSupportedContentTypes()); + this.setMetadataFilter(new MetadataFilterChain()); + //TODO: These don't seem to be used anywhere. // In the parser, if not null, a warning is logged .. but nothing else happens with them. // sourceResolver.getMaxConnectionsPerRoute(); // sourceResolver.getMaxConnectionsTotal(); + + switch (MetadataRequestURLConstructionScheme.SchemeType.get(sourceResolver.getMetadataRequestURLConstructionScheme().getType())) { + case METADATA_QUERY_PROTOCOL: + this.setRequestURLBuilder(new MetadataQueryProtocolRequestURLBuilder(sourceResolver.getMetadataRequestURLConstructionScheme().getContent())); + break; + case TEMPLATE: + //TODO: address this later with a prebuilt configuration + TemplateScheme templateScheme = (TemplateScheme) sourceResolver.getMetadataRequestURLConstructionScheme(); + this.setRequestURLBuilder(new TemplateRequestURLBuilder( + new VelocityEngine(), //TODO: we may want to do something with this here: templateScheme.getVelocityEngine() + templateScheme.getContent(), + TemplateRequestURLBuilder.EncodingStyle.valueOf(templateScheme.getEncodingStyle().toString().toLowerCase()), + null)); //TODO: this may need to be an actual Function, but all we have is a ref + break; + case REGEX: + RegexScheme regexScheme = (RegexScheme) sourceResolver.getMetadataRequestURLConstructionScheme(); + this.setRequestURLBuilder(new RegexRequestURLBuilder(regexScheme.getMatch(), regexScheme.getContent())); + break; + default: + break; + } } @Override protected void initMetadataResolver() throws ComponentInitializationException { - super.initMetadataResolver(); + if (sourceResolver.getDoInitialization()) { + super.initMetadataResolver(); - delegate.addIndexedDescriptorsFromBackingStore(this.getBackingStore(), - this.sourceResolver.getResourceId(), - indexWriter); + delegate.addIndexedDescriptorsFromBackingStore(this.getBackingStore(), + this.sourceResolver.getResourceId(), + indexWriter); + } } public void refresh() throws ComponentInitializationException { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaLocationLookup.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaLocationLookup.java index 344a6efcf..12c5ae67d 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaLocationLookup.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaLocationLookup.java @@ -4,7 +4,7 @@ import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.METADATA_SOURCES; import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.FILESYSTEM_METADATA_RESOLVER; import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.LOCAL_DYNAMIC_METADATA_RESOLVER; -//import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.DYNAMIC_HTTP_METADATA_RESOLVER; +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.DYNAMIC_HTTP_METADATA_RESOLVER; /** * Utility methods for common JSON schema types lookups. @@ -65,9 +65,16 @@ public static JsonSchemaResourceLocation localDynamicMetadataProviderSchema(Json .orElseThrow(() -> new IllegalStateException("JSON schema resource location for local dynamic metadata resolver is not registered.")); } -/* public static JsonSchemaResourceLocation dynamicHttpMetadataProviderSchema(JsonSchemaResourceLocationRegistry resourceLocationRegistry) { + /** + * Searches dynamic http metadata resolver JSON schema resource location object in the given location registry. + * + * @param resourceLocationRegistry + * @return dynamic http metadata resolver JSON schema resource location object + * @throws IllegalStateException if schema is not found in the given registry + */ + public static JsonSchemaResourceLocation dynamicHttpMetadataProviderSchema(JsonSchemaResourceLocationRegistry resourceLocationRegistry) { return resourceLocationRegistry .lookup(DYNAMIC_HTTP_METADATA_RESOLVER) .orElseThrow(() -> new IllegalStateException("JSON schema resource location for dynamic http metadata resolver is not registered.")); - }*/ + } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaResourceLocation.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaResourceLocation.java index 11c03006a..ad0f4cd84 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaResourceLocation.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaResourceLocation.java @@ -103,8 +103,8 @@ public enum SchemaType { // resolver types FILE_BACKED_HTTP_METADATA_RESOLVER("FileBackedHttpMetadataResolver"), FILESYSTEM_METADATA_RESOLVER("FilesystemMetadataResolver"), - LOCAL_DYNAMIC_METADATA_RESOLVER("LocalDynamicMetadataResolver"); -// DYNAMIC_HTTP_METADATA_RESOLVER; + LOCAL_DYNAMIC_METADATA_RESOLVER("LocalDynamicMetadataResolver"), + DYNAMIC_HTTP_METADATA_RESOLVER("DynamicHttpMetadataResolver"); String jsonType; diff --git a/backend/src/main/resources/dynamic-http-metadata-provider.schema.json b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json new file mode 100644 index 000000000..7362b40d7 --- /dev/null +++ b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json @@ -0,0 +1,718 @@ +{ + "type": "object", + "required": [ + "xmlId", + "metadataURL" + ], + "properties": { + "enabled": { + "title": "label.enable-provider-upon-saving", + "description": "tooltip.enable-provider-upon-saving", + "type": "boolean", + "default": false + }, + "xmlId": { + "title": "label.xml-id", + "description": "tooltip.xml-id", + "type": "string", + "default": "", + "minLength": 1 + }, + "metadataRequestURLConstructionScheme": { + "type": "object", + "properties": { + "@type": { + "title": "label.md-request-type", + "description": "tooltip.md-request-type", + "type": "string", + "widget": { + "id": "select" + }, + "oneOf": [ + { + "enum": [ + "MetadataQueryProtocol" + ], + "description": "value.md-query-protocol" + }, + { + "enum": [ + "Template" + ], + "description": "value.template" + }, + { + "enum": [ + "Regex" + ], + "description": "value.regex" + } + ] + }, + "content": { + "title": "label.md-request-value", + "description": "tooltip.md-request-value", + "type": "string" + }, + "transformRef": { + "title": "label.transform-ref", + "description": "tooltip.transform-ref", + "type": "string", + "visibleIf": { + "content": [ + "" + ] + } + }, + "encodingStyle": { + "title": "label.encoding-style", + "description": "tooltip.encoding-style", + "type": "string", + "widget": { + "id": "select" + }, + "default": "FORM", + "oneOf": [ + { + "enum": [ + "NONE" + ], + "description": "None" + }, + { + "enum": [ + "FORM" + ], + "description": "Form" + }, + { + "enum": [ + "PATH" + ], + "description": "Path" + }, + { + "enum": [ + "FRAGMENT" + ], + "description": "Fragment" + } + ], + "visibleIf": { + "@type": [ + "Template" + ] + } + }, + "velocityEngine": { + "title": "label.velocity-engine", + "description": "tooltip.velocity-engine", + "type": "string", + "default": "shibboleth.VelocityEngine", + "visibleIf": { + "@type": [ + "Template" + ] + } + }, + "match": { + "title": "label.match", + "description": "tooltip.match", + "type": "string", + "visibleIf": { + "@type": [ + "Regex" + ] + } + } + } + }, + "requireValidMetadata": { + "title": "label.require-valid-metadata", + "description": "tooltip.require-valid-metadata", + "type": "boolean", + "widget": { + "id": "boolean-radio" + }, + "oneOf": [ + { + "enum": [ + true + ], + "description": "value.true" + }, + { + "enum": [ + false + ], + "description": "value.false" + } + ], + "default": true + }, + "failFastInitialization": { + "title": "label.fail-fast-init", + "description": "tooltip.fail-fast-init", + "type": "boolean", + "widget": { + "id": "boolean-radio" + }, + "oneOf": [ + { + "enum": [ + true + ], + "description": "value.true" + }, + { + "enum": [ + false + ], + "description": "value.false" + } + ], + "default": true + }, + "dynamicMetadataResolverAttributes": { + "type": "object", + "properties": { + "refreshDelayFactor": { + "title": "label.refresh-delay-factor", + "description": "tooltip.refresh-delay-factor", + "type": "number", + "widget": { + "id": "number", + "step": 0.01 + }, + "placeholder": "label.real-number", + "minimum": 0, + "maximum": 1, + "default": null + }, + "minCacheDuration": { + "title": "label.min-cache-duration", + "description": "tooltip.min-cache-duration", + "type": "string", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "maxCacheDuration": { + "title": "label.max-cache-duration", + "description": "tooltip.max-cache-duration", + "type": "string", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "maxIdleEntityData": { + "title": "label.max-idle-entity-data", + "description": "tooltip.max-idle-entity-data", + "type": "string", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "cleanupTaskInterval": { + "title": "label.cleanup-task-interval", + "description": "tooltip.cleanup-task-interval", + "type": "string", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "persistentCacheManagerDirectory": { + "title": "label.persistent-cache-manager-directory", + "description": "tooltip.persistent-cache-manager-directory", + "type": "string", + "default": "", + "minLength": 1 + }, + "initializeFromPersistentCacheInBackground": { + "title": "label.initialize-from-persistent-cache-in-background", + "description": "tooltip.initialize-from-persistent-cache-in-background", + "type": "boolean", + "widget": { + "id": "boolean-radio" + }, + "oneOf": [ + { + "enum": [ + true + ], + "description": "value.true" + }, + { + "enum": [ + false + ], + "description": "value.false" + } + ], + "default": true + }, + "backgroundInitializationFromCacheDelay": { + "title": "label.background-init-from-cache-delay", + "description": "tooltip.background-init-from-cache-delay", + "type": "string", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "visibleIf": { + "initializeFromPersistentCacheInBackground": [ + true + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + } + } + }, + "httpMetadataResolverAttributes": { + "order": [], + "type": "object", + "fieldsets": [ + { + "title": "label.http-security-attributes", + "type": "section", + "class": "col-12", + "fields": [ + "disregardTLSCertificate" + ] + }, + { + "title": "label.http-connection-attributes", + "type": "section", + "fields": [ + "connectionRequestTimeout", + "connectionTimeout", + "socketTimeout" + ] + }, + { + "title": "label.http-proxy-attributes", + "type": "section", + "class": "col-12", + "fields": [ + "proxyHost", + "proxyPort", + "proxyUser", + "proxyPassword" + ] + }, + { + "title": "label.http-caching-attributes", + "type": "section", + "class": "col-12", + "fields": [ + "httpCaching", + "httpCacheDirectory", + "httpMaxCacheEntries", + "httpMaxCacheEntrySize" + ] + }, + { + "title": "", + "type": "hidden", + "class": "col-12", + "fields": [ + "tlsTrustEngineRef", + "httpClientSecurityParametersRef", + "httpClientRef" + ] + } + ], + "properties": { + "disregardTLSCertificate": { + "type": "boolean", + "title": "label.disregard-tls-cert", + "description": "tooltip.disregard-tls-cert", + "widget": { + "id": "boolean-radio" + }, + "oneOf": [ + { + "enum": [ + true + ], + "description": "True" + }, + { + "enum": [ + false + ], + "description": "False" + } + ], + "default": false + }, + "httpClientRef": { + "type": "string", + "title": "", + "description": "", + "placeholder": "", + "widget": "hidden", + "default": "" + }, + "connectionRequestTimeout": { + "type": "string", + "title": "label.connection-request-timeout", + "description": "tooltip.connection-request-timeout", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "connectionTimeout": { + "type": "string", + "title": "label.connection-timeout", + "description": "tooltip.connection-timeout", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "socketTimeout": { + "type": "string", + "title": "label.socket-timeout", + "description": "tooltip.socket-timeout", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "tlsTrustEngineRef": { + "type": "string", + "title": "", + "description": "", + "placeholder": "", + "widget": "hidden", + "default": "" + }, + "httpClientSecurityParametersRef": { + "type": "string", + "title": "", + "description": "", + "placeholder": "", + "widget": "hidden", + "default": "" + }, + "proxyHost": { + "type": "string", + "title": "label.proxy-host", + "description": "tooltip.proxy-host", + "placeholder": "", + "default": "" + }, + "proxyPort": { + "type": "string", + "title": "label.proxy-port", + "description": "tooltip.proxy-port", + "placeholder": "", + "default": "" + }, + "proxyUser": { + "type": "string", + "title": "label.proxy-user", + "description": "tooltip.proxy-user", + "placeholder": "", + "default": "" + }, + "proxyPassword": { + "type": "string", + "title": "label.proxy-password", + "description": "tooltip.proxy-password", + "placeholder": "", + "default": "" + }, + "httpCaching": { + "type": "string", + "title": "label.http-caching", + "description": "tooltip.http-caching", + "placeholder": "label.select-caching-type", + "widget": { + "id": "select" + }, + "oneOf": [ + { + "enum": [ + "none" + ], + "description": "value.none" + }, + { + "enum": [ + "file" + ], + "description": "value.file" + }, + { + "enum": [ + "memory" + ], + "description": "value.memory" + } + ] + }, + "httpCacheDirectory": { + "type": "string", + "title": "label.http-caching-directory", + "description": "tooltip.http-caching-directory", + "placeholder": "" + }, + "httpMaxCacheEntries": { + "type": "integer", + "title": "label.http-max-cache-entries", + "description": "tooltip.http-max-cache-entries", + "placeholder": "", + "default": 0, + "minimum": 0 + }, + "httpMaxCacheEntrySize": { + "type": "integer", + "title": "label.max-cache-entry-size", + "description": "tooltip.max-cache-entry-size", + "placeholder": "", + "default": 0, + "minimum": 0 + } + } + }, + "metadataFilters": { + "title": "", + "description": "", + "type": "object", + "properties": { + "RequiredValidUntil": { + "title": "label.required-valid-until", + "type": "object", + "widget": { + "id": "fieldset" + }, + "properties": { + "maxValidityInterval": { + "title": "label.max-validity-interval", + "description": "tooltip.max-validity-interval", + "type": "string", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + } + } + }, + "SignatureValidation": { + "title": "label.signature-validation-filter", + "type": "object", + "widget": { + "id": "fieldset" + }, + "properties": { + "requireSignedRoot": { + "title": "label.require-signed-root", + "description": "tooltip.require-signed-root", + "type": "boolean", + "default": true + }, + "certificateFile": { + "title": "label.certificate-file", + "description": "tooltip.certificate-file", + "type": "string", + "widget": "textarea", + "default": "" + } + }, + "anyOf": [ + { + "properties": { + "requireSignedRoot": { + "enum": [ + true + ] + } + }, + "required": [ + "certificateFile" + ] + }, + { + "properties": { + "requireSignedRoot": { + "enum": [ + false + ] + } + } + } + ] + }, + "EntityRoleWhiteList": { + "title": "label.entity-role-whitelist", + "type": "object", + "widget": { + "id": "fieldset" + }, + "properties": { + "retainedRoles": { + "title": "label.retained-roles", + "description": "tooltip.retained-roles", + "type": "array", + "items": { + "widget": { + "id": "select" + }, + "type": "string", + "oneOf": [ + { + "enum": [ + "SPSSODescriptor" + ], + "description": "value.spdescriptor" + }, + { + "enum": [ + "AttributeAuthorityDescriptor" + ], + "description": "value.attr-auth-descriptor" + } + ] + } + }, + "removeRolelessEntityDescriptors": { + "title": "label.remove-roleless-entity-descriptors", + "description": "tooltip.remove-roleless-entity-descriptors", + "type": "boolean", + "default": true + }, + "removeEmptyEntitiesDescriptors": { + "title": "label.remove-empty-entities-descriptors", + "description": "tooltip.remove-empty-entities-descriptors", + "type": "boolean", + "default": true + } + } + } + } + } + } +} \ No newline at end of file diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index 87aaf7299..31c4de9b8 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -76,6 +76,11 @@ 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 +value.template=Template brand.header.title=Source Management brand.logo-link-label=Shibboleth @@ -218,6 +223,7 @@ label.entity-id=Entity ID label.service-provider-name=Service Provider Name label.organization=Organization label.contacts=Contacts +label.contact=Contact label.mdui=MDUI Information label.service-provider-sso-descriptor=Service Provider Sso Descriptor label.service-enabled=Service Enabled @@ -316,6 +322,7 @@ label.reloading-attributes=Reloading Attributes label.metadata-filter-plugins=Metadata Filter Plugins label.advanced-settings=Advanced Settings label.edit-metadata-provider=Edit Metadata Provider +label.http-settings-advanced=Http Settings (Advanced) label.metadata-ui=User Interface / MDUI Information label.descriptor-info=SP SSO Descriptor Information @@ -337,9 +344,27 @@ label.attribute-eduPersonUniqueId=eduPersonUniqueId label.attribute-employeeNumber=employeeNumber label.force-authn=Force AuthN +label.dynamic-attributes=Dynamic Attributes +label.min-cache-duration=Min Cache Duration +label.max-cache-duration=Max Cache Duration +label.max-idle-entity-data=Max Idle Entity Data +label.cleanup-task-interval=Cleanup Task Interval +label.persistent-cache-manager-directory=Persistent Cache Manager Directory +label.initialize-from-persistent-cache-in-background=Initialize from Persistent Cache in Background? +label.background-init-from-cache-delay=Background Initialization from Cache Delay +label.source-directory=Source Directory +label.remove-idle-entity-data=Remove Idle Entity Data? label.do-resolver-initialization=Initialize label.file-doesnt-exist=The file specified in the resolver does not exist on the file system. Therefore, the resolver cannot be initialized. +label.md-request-type=Metadata Request URL Construction Type +label.md-request-value=Metadata Request URL Construction Value +label.transform-ref=Transform Ref +label.encoding-style=Encoding Style +label.velocity-engine=Velocity Engine +label.match=Match + + message.must-be-unique=Must be unique. message.name-must-be-unique=Name must be unique. message.uri-valid-format=URI must be valid format. @@ -436,6 +461,7 @@ tooltip.metadata-provider-name=Metadata Provider Name tooltip.metadata-provider-type=Metadata Provider Type tooltip.xml-id=Identifier for logging, identification for command line reload, etc. tooltip.metadata-url=Identifier for logging, identification for command line reload, etc. +tooltip.metadata-file=The absolute path to the local metadata file to be loaded. tooltip.init-from-backup=Flag indicating whether initialization should first attempt to load metadata from the backup file. If true, foreground initialization will be performed by loading the backing file, and then a refresh from the remote HTTP server will be scheduled to execute in a background thread, after a configured delay. This can improve IdP startup times when the remote HTTP file is large in size. tooltip.backing-file=Specifies where the backing file is located. If the remote server is unavailable at startup, the backing file is loaded instead. tooltip.backup-file-init-refresh-delay=Delay duration after which to schedule next HTTP refresh when initialized from the backing file. @@ -462,4 +488,21 @@ tooltip.filter-name=Filter Name tooltip.enable-filter=Enable Filter? tooltip.enable-service=Enable Service? +tooltip.min-cache-duration=The minimum duration for which metadata will be cached before it is refreshed. +tooltip.max-cache-duration=The maximum duration for which metadata will be cached before it is refreshed. +tooltip.max-idle-entity-data=The maximum duration for which metadata will be allowed to be idle (no requests for it) before it is removed from the cache. +tooltip.cleanup-task-interval=The interval at which the internal cleanup task should run. This task performs background maintenance tasks, such as the removal of expired and idle metadata. +tooltip.persistent-cache-manager-directory=The optional manager for the persistent cache store for resolved metadata. On metadata provider initialization, data present in the persistent cache will be loaded to memory, effectively restoring the state of the provider as closely as possible to that which existed before the previous shutdown. Each individual cache entry will only be loaded if 1) the entry is still valid as determined by the internal provider logic, and 2) the entry passes the (optional) predicate supplied via initializationFromCachePredicateRef. +tooltip.initialize-from-persistent-cache-in-background=Flag indicating whether should initialize from the persistent cache in the background. Initializing from the cache in the background will improve IdP startup times. +tooltip.background-init-from-cache-delay=The delay after which to schedule the background initialization from the persistent cache when initializeFromPersistentCacheInBackground=true. + +tooltip.source-directory=Convenience mechanism for wiring a FilesystemLoadSaveManager, loading from the specified source directory in the local filesystem. This attribute will be ignored if sourceManagerRef is also specified. Either this attribute or sourceManagerRef is required. +tooltip.remove-idle-entity-data=Flag indicating whether idle metadata should be removed. + tooltip.do-resolver-initialization=Initialize this resolver? In the case of Filesystem resolvers, this will cause the system to read the file and index the resolver. +tooltip.md-request-type=Options are 1) Metadata Query Protocol, 2) Template, 3) Regex. +tooltip.md-request-value=Content of the element. +tooltip.transform-ref=A reference to a transform function for the entityID. If used, the child element must be empty. +tooltip.encoding-style=Determines whether and how the entityID value will be URL encoded prior to replacement. Allowed values are: 1) "none" - no encoding is performed, 2) "form" - encoded using URL form parameter encoding (for query parameters), 3) "path" - encoded using URL path encoding, or 4) "fragment" - encoded using URL fragment encoding. The precise definition of these terms is defined in the documentation for the methods of the Guava library\u0027s UrlEscapers class. +tooltip.velocity-engine=This attribute may be used to specify the name of the Velocity engine defined within the application. +tooltip.match=A regular expression against which the entityID is evaluated. \ No newline at end of file diff --git a/backend/src/main/resources/i18n/messages_en.properties b/backend/src/main/resources/i18n/messages_en.properties index 984f3c21c..3d44ca8eb 100644 --- a/backend/src/main/resources/i18n/messages_en.properties +++ b/backend/src/main/resources/i18n/messages_en.properties @@ -76,9 +76,12 @@ 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 +value.template=Template + brand.header.title=Source Management brand.logo-link-label=Shibboleth brand.logo-link-description=Link to Shibboleth Website @@ -319,6 +322,7 @@ label.reloading-attributes=Reloading Attributes label.metadata-filter-plugins=Metadata Filter Plugins label.advanced-settings=Advanced Settings label.edit-metadata-provider=Edit Metadata Provider +label.http-settings-advanced=Http Settings (Advanced) label.metadata-ui=User Interface / MDUI Information label.descriptor-info=SP SSO Descriptor Information @@ -340,15 +344,27 @@ label.attribute-eduPersonUniqueId=eduPersonUniqueId label.attribute-employeeNumber=employeeNumber label.force-authn=Force AuthN -label.source-directory=Source Directory +label.dynamic-attributes=Dynamic Attributes label.min-cache-duration=Min Cache Duration label.max-cache-duration=Max Cache Duration label.max-idle-entity-data=Max Idle Entity Data -label.remove-idle-entity-data=Remove Idle Entity Data? label.cleanup-task-interval=Cleanup Task Interval +label.persistent-cache-manager-directory=Persistent Cache Manager Directory +label.initialize-from-persistent-cache-in-background=Initialize from Persistent Cache in Background? +label.background-init-from-cache-delay=Background Initialization from Cache Delay +label.source-directory=Source Directory +label.remove-idle-entity-data=Remove Idle Entity Data? label.do-resolver-initialization=Initialize label.file-doesnt-exist=The file specified in the resolver does not exist on the file system. Therefore, the resolver cannot be initialized. +label.md-request-type=Metadata Request URL Construction Type +label.md-request-value=Metadata Request URL Construction Value +label.transform-ref=Transform Ref +label.encoding-style=Encoding Style +label.velocity-engine=Velocity Engine +label.match=Match + + message.must-be-unique=Must be unique. message.name-must-be-unique=Name must be unique. message.uri-valid-format=URI must be valid format. @@ -473,10 +489,21 @@ tooltip.filter-name=Filter Name tooltip.enable-filter=Enable Filter? tooltip.enable-service=Enable Service? -tooltip.source-directory=Convenience mechanism for wiring a FilesystemLoadSaveManager, loading from the specified source directory in the local filesystem. This attribute will be ignored if sourceManagerRef is also specified. Either this attribute or sourceManagerRef is required. tooltip.min-cache-duration=The minimum duration for which metadata will be cached before it is refreshed. tooltip.max-cache-duration=The maximum duration for which metadata will be cached before it is refreshed. tooltip.max-idle-entity-data=The maximum duration for which metadata will be allowed to be idle (no requests for it) before it is removed from the cache. -tooltip.remove-idle-entity-data=Flag indicating whether idle metadata should be removed. tooltip.cleanup-task-interval=The interval at which the internal cleanup task should run. This task performs background maintenance tasks, such as the removal of expired and idle metadata. +tooltip.persistent-cache-manager-directory=The optional manager for the persistent cache store for resolved metadata. On metadata provider initialization, data present in the persistent cache will be loaded to memory, effectively restoring the state of the provider as closely as possible to that which existed before the previous shutdown. Each individual cache entry will only be loaded if 1) the entry is still valid as determined by the internal provider logic, and 2) the entry passes the (optional) predicate supplied via initializationFromCachePredicateRef. +tooltip.initialize-from-persistent-cache-in-background=Flag indicating whether should initialize from the persistent cache in the background. Initializing from the cache in the background will improve IdP startup times. +tooltip.background-init-from-cache-delay=The delay after which to schedule the background initialization from the persistent cache when initializeFromPersistentCacheInBackground=true. + +tooltip.source-directory=Convenience mechanism for wiring a FilesystemLoadSaveManager, loading from the specified source directory in the local filesystem. This attribute will be ignored if sourceManagerRef is also specified. Either this attribute or sourceManagerRef is required. +tooltip.remove-idle-entity-data=Flag indicating whether idle metadata should be removed. + tooltip.do-resolver-initialization=Initialize this resolver? In the case of Filesystem resolvers, this will cause the system to read the file and index the resolver. +tooltip.md-request-type=Options are 1) Metadata Query Protocol, 2) Template, 3) Regex. +tooltip.md-request-value=Content of the element. +tooltip.transform-ref=A reference to a transform function for the entityID. If used, the child element must be empty. +tooltip.encoding-style=Determines whether and how the entityID value will be URL encoded prior to replacement. Allowed values are: 1) "none" - no encoding is performed, 2) "form" - encoded using URL form parameter encoding (for query parameters), 3) "path" - encoded using URL path encoding, or 4) "fragment" - encoded using URL fragment encoding. The precise definition of these terms is defined in the documentation for the methods of the Guava library\u0027s UrlEscapers class. +tooltip.velocity-engine=This attribute may be used to specify the name of the Velocity engine defined within the application. +tooltip.match=A regular expression against which the entityID is evaluated. \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy index 3624f6ce3..671961e8b 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy @@ -12,6 +12,7 @@ import org.springframework.test.context.ActiveProfiles import spock.lang.Specification import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.* +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.DYNAMIC_HTTP_METADATA_RESOLVER import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.ENTITY_ATTRIBUTES_FILTERS import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.FILESYSTEM_METADATA_RESOLVER import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.LOCAL_DYNAMIC_METADATA_RESOLVER @@ -71,6 +72,12 @@ class BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests extends Speci .jacksonMapper(jacksonMapper) .detectMalformedJson(false) .build()) + .register(DYNAMIC_HTTP_METADATA_RESOLVER, JsonSchemaLocationBuilder.with() + .jsonSchemaLocation('classpath:dynamic-http-metadata-provider.schema.json') + .resourceLoader(resourceLoader) + .jacksonMapper(jacksonMapper) + .detectMalformedJson(false) + .build()) } } } \ No newline at end of file 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 58c7c1a0c..08a14feb5 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 @@ -57,8 +57,6 @@ class IncommonJPAMetadataResolverServiceImplTests extends Specification { metadataResolverRepository.save(mr) def output = metadataResolverService.generateConfiguration() - println(output.documentElement) - then: generatedXmlIsTheSameAsExpectedXml('/conf/278.xml', output) } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy index 8bb9bc7d4..1e2e61b3c 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy @@ -9,7 +9,11 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFil import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget import edu.internet2.tier.shibboleth.admin.ui.domain.filters.RequiredValidUntilFilter 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.MetadataQueryProtocolScheme +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.RegexScheme import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.SvnMetadataResource +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.TemplateScheme 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 @@ -280,6 +284,71 @@ class JPAMetadataResolverServiceImplTests extends Specification { generatedXmlIsTheSameAsExpectedXml('/conf/670.xml', generatedXmlDocument) } + def 'test generating DynamicHttpMetadataResolver xml snippet with MetadataQueryProtocolScheme'() { + given: + def resolver = new DynamicHttpMetadataResolver().with { + it.xmlId = 'DynamicHttpMetadataResolver' + it.metadataRequestURLConstructionScheme = new MetadataQueryProtocolScheme().with { + it.transformRef = 'This is a transformRef' + it.content = 'some content' + it + } + it + } + + when: + genXmlSnippet(markupBuilder) { + JPAMetadataResolverServiceImpl.cast(metadataResolverService).constructXmlNodeForResolver(resolver, it) {} + } + + then: + generatedXmlIsTheSameAsExpectedXml('/conf/704.1.xml', domBuilder.parseText(writer.toString())) + } + + def 'test generating DynamicHttpMetadataResolver xml snippet with TemplateScheme'() { + given: + def resolver = new DynamicHttpMetadataResolver().with { + it.xmlId = 'DynamicHttpMetadataResolver' + it.metadataRequestURLConstructionScheme = new TemplateScheme().with { + it.encodingStyle = TemplateScheme.EncodingStyle.FORM + it.transformRef = 'This is a transformRef' + it.velocityEngine = 'This is a velocityEngine' + it.content = 'some content' + it + } + it + } + + when: + genXmlSnippet(markupBuilder) { + JPAMetadataResolverServiceImpl.cast(metadataResolverService).constructXmlNodeForResolver(resolver, it) {} + } + + then: + generatedXmlIsTheSameAsExpectedXml('/conf/704.2.xml', domBuilder.parseText(writer.toString())) + } + + def 'test generating DynamicHttpMetadataResolver xml snippet with RegexScheme'() { + given: + def resolver = new DynamicHttpMetadataResolver().with { + it.xmlId = 'DynamicHttpMetadataResolver' + it.metadataRequestURLConstructionScheme = new RegexScheme().with { + it.match = 'This is the match field' + it.content = 'some content' + it + } + it + } + + when: + genXmlSnippet(markupBuilder) { + JPAMetadataResolverServiceImpl.cast(metadataResolverService).constructXmlNodeForResolver(resolver, it) {} + } + + then: + generatedXmlIsTheSameAsExpectedXml('/conf/704.3.xml', domBuilder.parseText(writer.toString())) + } + static genXmlSnippet(MarkupBuilder xml, Closure xmlNodeGenerator) { xml.MetadataProvider('id': 'ShibbolethMetadata', 'xmlns': 'urn:mace:shibboleth:2.0:metadata', 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 eaf031c3e..071516cc2 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 @@ -512,6 +512,11 @@ class TestObjectGenerator { it.dynamicMetadataResolverAttributes = new DynamicMetadataResolverAttributes().with { it } + it.metadataRequestURLConstructionScheme = new MetadataQueryProtocolScheme().with { + it.transformRef = 'transformRef' + it.content = 'content' + it + } it } } diff --git a/backend/src/test/resources/conf/278.2.xml b/backend/src/test/resources/conf/278.2.xml index 3b5e9c731..82a25f618 100644 --- a/backend/src/test/resources/conf/278.2.xml +++ b/backend/src/test/resources/conf/278.2.xml @@ -23,6 +23,7 @@ socketTimeout="PT5S" supportedContentTypes="[]" xsi:type="DynamicHttpMetadataProvider"> + content diff --git a/backend/src/test/resources/conf/278.xml b/backend/src/test/resources/conf/278.xml index 3bebc347b..6db6e5cfa 100644 --- a/backend/src/test/resources/conf/278.xml +++ b/backend/src/test/resources/conf/278.xml @@ -23,6 +23,7 @@ socketTimeout="PT5S" supportedContentTypes="[]" xsi:type="DynamicHttpMetadataProvider"> + content diff --git a/backend/src/test/resources/conf/704.1.xml b/backend/src/test/resources/conf/704.1.xml new file mode 100644 index 000000000..a7e5d63a0 --- /dev/null +++ b/backend/src/test/resources/conf/704.1.xml @@ -0,0 +1,5 @@ + + + some content + + \ No newline at end of file diff --git a/backend/src/test/resources/conf/704.2.xml b/backend/src/test/resources/conf/704.2.xml new file mode 100644 index 000000000..823b89de4 --- /dev/null +++ b/backend/src/test/resources/conf/704.2.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/backend/src/test/resources/conf/704.3.xml b/backend/src/test/resources/conf/704.3.xml new file mode 100644 index 000000000..d7ce637d3 --- /dev/null +++ b/backend/src/test/resources/conf/704.3.xml @@ -0,0 +1,5 @@ + + + some content + + \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json index f2aba6f0c..bb8441756 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -183,7 +183,6 @@ "anymatch": "2.0.0", "async-each": "1.0.1", "braces": "2.3.2", - "fsevents": "1.2.4", "glob-parent": "3.1.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -526,7 +525,6 @@ "anymatch": "2.0.0", "async-each": "1.0.1", "braces": "2.3.2", - "fsevents": "1.2.4", "glob-parent": "3.1.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -924,7 +922,6 @@ "anymatch": "2.0.0", "async-each": "1.0.1", "braces": "2.3.2", - "fsevents": "1.2.4", "glob-parent": "3.1.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -1475,7 +1472,6 @@ "anymatch": "2.0.0", "async-each": "1.0.1", "braces": "2.3.2", - "fsevents": "1.2.4", "glob-parent": "3.1.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -2811,7 +2807,6 @@ "requires": { "anymatch": "1.3.2", "async-each": "1.0.1", - "fsevents": "1.2.4", "glob-parent": "2.0.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -4641,535 +4636,6 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "fsevents": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", - "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", - "dev": true, - "optional": true, - "requires": { - "nan": "2.11.1", - "node-pre-gyp": "0.10.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "2.6.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "2.2.4" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.21", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": "2.1.2" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, - "minipass": { - "version": "2.2.4", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.1.1", - "yallist": "3.0.2" - } - }, - "minizlib": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "2.2.4" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.2.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "2.6.9", - "iconv-lite": "0.4.21", - "sax": "1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.10.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "1.0.3", - "mkdirp": "0.5.1", - "needle": "2.2.0", - "nopt": "4.0.1", - "npm-packlist": "1.1.10", - "npmlog": "4.1.2", - "rc": "1.2.7", - "rimraf": "2.6.2", - "semver": "5.5.0", - "tar": "4.4.1" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" - } - }, - "npm-bundled": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.1.10", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "3.0.1", - "npm-bundled": "1.0.3" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "0.5.1", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.1", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "rimraf": { - "version": "2.6.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "7.1.2" - } - }, - "safe-buffer": { - "version": "5.1.1", - "bundled": true, - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.5.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "1.0.1", - "fs-minipass": "1.2.5", - "minipass": "2.2.4", - "minizlib": "1.1.0", - "mkdirp": "0.5.1", - "safe-buffer": "5.1.1", - "yallist": "3.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "yallist": { - "version": "3.0.2", - "bundled": true, - "dev": true - } - } - }, "fstream": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", @@ -7895,13 +7361,6 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, - "nan": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", - "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", - "dev": true, - "optional": true - }, "nanomatch": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", @@ -11612,7 +11071,6 @@ "anymatch": "2.0.0", "async-each": "1.0.1", "braces": "2.3.2", - "fsevents": "1.2.4", "glob-parent": "3.1.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -12460,7 +11918,6 @@ "anymatch": "2.0.0", "async-each": "1.0.1", "braces": "2.3.2", - "fsevents": "1.2.4", "glob-parent": "3.1.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", diff --git a/ui/src/app/metadata/domain/model/providers/dynamic-http-metadata-provider.ts b/ui/src/app/metadata/domain/model/providers/dynamic-http-metadata-provider.ts new file mode 100644 index 000000000..5d3201836 --- /dev/null +++ b/ui/src/app/metadata/domain/model/providers/dynamic-http-metadata-provider.ts @@ -0,0 +1,51 @@ +import { BaseMetadataProvider } from './base-metadata-provider'; + +export interface DynamicHttpMetadataProvider extends BaseMetadataProvider { + id: string; + metadataURL: string; + dynamicMetadataResolverAttributes: DynamicMetadataResolverAttributes; + httpMetadataResolverAttributes: HttpMetadataResolverAttributes; + maxConnectionsTotal: number; + maxConnectionsPerRoute: number; + supportedContentTypes: string[]; +} + +export interface DynamicMetadataResolverAttributes { + refreshDelayFactor: number; + minCacheDuration: string; + maxCacheDuration: string; + maxIdleEntityData: string; + removeIdleEntityData: boolean; + cleanupTaskInterval: string; + + persistentCacheManagerRef: string; + persistentCacheManagerDirectory: string; + persistentCacheKeyGeneratorRef: string; + initializeFromPersistentCacheInBackground: boolean; + backgroundInitializationFromCacheDelay: string; + initializationFromCachePredicateRef: string; +} + +export interface HttpMetadataResolverAttributes { + httpClientRef; + connectionRequestTimeout: string; + connectionTimeout: string; + socketTimeout: string; + disregardTLSCertificate: boolean; + tlsTrustEngineRef: string; + httpClientSecurityParametersRef: string; + proxyHost: string; + proxyPort: string; + proxyUser: string; + proxyPassword: string; + httpCaching: HttpCachingType; + httpCacheDirectory: string; + httpMaxCacheEntries: number; + httpMaxCacheEntrySize: number; +} + +export enum HttpCachingType { + NONE = 'none', + FILE = 'file', + MEMORY = 'memory' +} diff --git a/ui/src/app/metadata/provider/container/provider-edit-step.component.html b/ui/src/app/metadata/provider/container/provider-edit-step.component.html index bab3e5b90..ad969fd9e 100644 --- a/ui/src/app/metadata/provider/container/provider-edit-step.component.html +++ b/ui/src/app/metadata/provider/container/provider-edit-step.component.html @@ -10,6 +10,7 @@ [schema]="schema$ | async" [model]="model$ | async" [validators]="validators$ | async" + [bindings]="bindings$ | async" (onChange)="valueChangeSubject.next($event)" (onErrorChange)="statusChangeSubject.next($event)"> \ No newline at end of file diff --git a/ui/src/app/metadata/provider/container/provider-edit-step.component.ts b/ui/src/app/metadata/provider/container/provider-edit-step.component.ts index 25e6085c4..be79bb1fa 100644 --- a/ui/src/app/metadata/provider/container/provider-edit-step.component.ts +++ b/ui/src/app/metadata/provider/container/provider-edit-step.component.ts @@ -37,6 +37,7 @@ export class ProviderEditStepComponent implements OnDestroy { definition$: Observable>; changes$: Observable; step$: Observable; + bindings$: Observable; validators$: Observable<{ [key: string]: any }>; @@ -50,6 +51,7 @@ export class ProviderEditStepComponent implements OnDestroy { this.provider$ = this.store.select(fromProvider.getSelectedProvider); this.step$ = this.store.select(fromWizard.getCurrent); this.schema$ = this.store.select(fromWizard.getSchema); + this.bindings$ = this.definition$.pipe(map(d => d.bindings)); this.step$.subscribe(s => { if (s && s.locked) { diff --git a/ui/src/app/metadata/provider/container/provider-wizard-step.component.html b/ui/src/app/metadata/provider/container/provider-wizard-step.component.html index 5d07730fd..a9a3be2e4 100644 --- a/ui/src/app/metadata/provider/container/provider-wizard-step.component.html +++ b/ui/src/app/metadata/provider/container/provider-wizard-step.component.html @@ -3,6 +3,7 @@ [schema]="schema$ | async" [model]="model$ | async" [validators]="validators$ | async" + [bindings]="bindings$ | async" (onChange)="valueChangeSubject.next($event)" (onErrorChange)="statusChangeSubject.next($event)"> \ No newline at end of file diff --git a/ui/src/app/metadata/provider/container/provider-wizard-step.component.ts b/ui/src/app/metadata/provider/container/provider-wizard-step.component.ts index 9ff4ded23..a0b145078 100644 --- a/ui/src/app/metadata/provider/container/provider-wizard-step.component.ts +++ b/ui/src/app/metadata/provider/container/provider-wizard-step.component.ts @@ -28,6 +28,7 @@ export class ProviderWizardStepComponent implements OnDestroy { private statusChangeEmitted$ = this.statusChangeSubject.asObservable(); schema$: Observable; + bindings$: Observable; schema: any; definition$: Observable>; changes$: Observable; @@ -46,6 +47,7 @@ export class ProviderWizardStepComponent implements OnDestroy { filter(s => s && Object.keys(s.properties).length > 0) ); this.definition$ = this.store.select(fromWizard.getWizardDefinition); + this.bindings$ = this.definition$.pipe(map(d => d.bindings)); this.changes$ = this.store.select(fromProvider.getEntityChanges); this.validators$ = this.definition$.pipe( diff --git a/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts b/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts new file mode 100644 index 000000000..1692d8f36 --- /dev/null +++ b/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts @@ -0,0 +1,154 @@ +import { FormProperty } from 'ngx-schema-form/lib/model/formproperty'; +import { ArrayProperty } from 'ngx-schema-form/lib/model/arrayproperty'; +import { ObjectProperty } from 'ngx-schema-form/lib/model/objectproperty'; + +import { Wizard } from '../../../wizard/model'; +import { DynamicHttpMetadataProvider } from '../../domain/model/providers/dynamic-http-metadata-provider'; +import { BaseMetadataProviderEditor } from './base.provider.form'; +import UriValidator from '../../../shared/validation/uri.validator'; + +export const DynamicHttpMetadataProviderWizard: Wizard = { + ...BaseMetadataProviderEditor, + label: 'DynamicHttpMetadataProvider', + type: 'DynamicHttpMetadataResolver', + bindings: { + '/metadataRequestURLConstructionScheme/@type': [ + { + 'input': (event, property: FormProperty) => { + let transform = property.parent.getProperty('transformRef'); + let content = property.parent.getProperty('content'); + if (!content.value && property.value !== 'Regex') { + transform.setVisible(true); + } else { + transform.setVisible(false); + } + } + } + ] + }, + getValidators(namesList: string[] = [], xmlIdList: string[] = []): any { + const validators = BaseMetadataProviderEditor.getValidators(namesList); + validators['/xmlId'] = (value, property, form) => { + const err = xmlIdList.indexOf(value) > -1 ? { + code: 'INVALID_ID', + path: `#${property.path}`, + message: 'message.id-unique', + params: [value] + } : null; + return err; + }; + validators['/metadataURL'] = (value, property, form) => { + return !UriValidator.isUri(value) ? { + code: 'INVALID_URI', + path: `#${property.path}`, + message: 'message.uri-valid-format', + params: [value] + } : null; + }; + + return validators; + }, + steps: [ + { + id: 'common', + label: 'label.common-attributes', + index: 2, + initialValues: [], + schema: '/api/ui/MetadataResolver/DynamicHttpMetadataResolver', + fields: [ + 'xmlId', + 'metadataURL', + 'requireValidMetadata', + 'failFastInitialization', + 'metadataRequestURLConstructionScheme' + ] + }, + { + id: 'dynamic', + label: 'label.dynamic-attributes', + index: 3, + initialValues: [], + schema: '/api/ui/MetadataResolver/DynamicHttpMetadataResolver', + fields: [ + 'dynamicMetadataResolverAttributes' + ] + }, + { + id: 'plugins', + label: 'label.metadata-filter-plugins', + index: 4, + initialValues: [ + { key: 'metadataFilters', value: [] } + ], + schema: '/api/ui/MetadataResolver/DynamicHttpMetadataResolver', + fields: [ + 'metadataFilters' + ] + }, + { + id: 'summary', + label: 'label.finished', + index: 5, + initialValues: [], + schema: '/api/ui/MetadataResolver/DynamicHttpMetadataResolver', + fields: [ + 'enabled' + ] + } + ] +}; + + +export const DynamicHttpMetadataProviderEditor: Wizard = { + ...DynamicHttpMetadataProviderWizard, + steps: [ + { + id: 'common', + label: 'label.common-attributes', + index: 1, + initialValues: [], + schema: '/api/ui/MetadataResolver/DynamicHttpMetadataResolver', + fields: [ + 'xmlId', + 'metadataRequestURLConstructionScheme', + 'enabled', + 'requireValidMetadata', + 'failFastInitialization' + ], + order: ['xmlId', 'metadataRequestURLConstructionScheme', 'enabled', 'requireValidMetadata', 'failFastInitialization'] + }, + { + id: 'dynamic', + label: 'label.dynamic-attributes', + index: 3, + initialValues: [], + schema: '/api/ui/MetadataResolver/DynamicHttpMetadataResolver', + fields: [ + 'dynamicMetadataResolverAttributes' + ] + }, + { + id: 'plugins', + label: 'label.metadata-filter-plugins', + index: 4, + initialValues: [ + { key: 'metadataFilters', value: [] } + ], + schema: '/api/ui/MetadataResolver/DynamicHttpMetadataResolver', + fields: [ + 'metadataFilters' + ] + }, + { + id: 'advanced', + label: 'label.http-settings-advanced', + index: 4, + initialValues: [], + locked: true, + schema: '/api/ui/MetadataResolver/DynamicHttpMetadataResolver', + fields: [ + 'httpMetadataResolverAttributes' + ] + } + ] +}; diff --git a/ui/src/app/metadata/provider/model/index.ts b/ui/src/app/metadata/provider/model/index.ts index 2ccf25aea..123f99812 100644 --- a/ui/src/app/metadata/provider/model/index.ts +++ b/ui/src/app/metadata/provider/model/index.ts @@ -1,22 +1,26 @@ import { FileBackedHttpMetadataProviderWizard } from './file-backed-http.provider.form'; import { FileBackedHttpMetadataProviderEditor } from './file-backed-http.provider.form'; +import { DynamicHttpMetadataProviderWizard, DynamicHttpMetadataProviderEditor } from './dynamic-http.provider.form'; import { LocalDynamicMetadataProviderWizard, LocalDynamicMetadataProviderEditor } from './local-dynamic.provider.form'; import { FileSystemMetadataProviderWizard, FileSystemMetadataProviderEditor } from './file-system.provider.form'; export const MetadataProviderWizardTypes = [ FileBackedHttpMetadataProviderWizard, + DynamicHttpMetadataProviderWizard, FileSystemMetadataProviderWizard, LocalDynamicMetadataProviderWizard ]; export const MetadataProviderEditorTypes = [ FileBackedHttpMetadataProviderEditor, + DynamicHttpMetadataProviderEditor, LocalDynamicMetadataProviderEditor, FileSystemMetadataProviderEditor ]; export const FilterableProviders = [ FileBackedHttpMetadataProviderEditor.type, + DynamicHttpMetadataProviderEditor.type, LocalDynamicMetadataProviderEditor.type ]; diff --git a/ui/src/app/wizard/model/wizard.ts b/ui/src/app/wizard/model/wizard.ts index 48c73aef4..3115ed041 100644 --- a/ui/src/app/wizard/model/wizard.ts +++ b/ui/src/app/wizard/model/wizard.ts @@ -15,15 +15,21 @@ export interface WizardStep { fieldsets?: WizardFieldset[]; summary?: boolean; override?: any; + order?: string[]; } export interface WizardFieldset { type: string; class?: string[]; - fields: string[]; + fields: (string | WizardFieldsubset)[]; } export interface WizardValue { key: string; value: any; } + +export interface WizardFieldsubset { + parent: string; + children: string[]; +} diff --git a/ui/src/app/wizard/reducer/index.ts b/ui/src/app/wizard/reducer/index.ts index b07ed3cd4..3446f3713 100644 --- a/ui/src/app/wizard/reducer/index.ts +++ b/ui/src/app/wizard/reducer/index.ts @@ -3,6 +3,7 @@ import * as fromWizard from './wizard.reducer'; import { createFeatureSelector, createSelector } from '@ngrx/store'; import { Wizard, WizardStep } from '../model'; import { diff } from 'deep-object-diff'; +import { SchemaService } from '../../schema-form/service/schema.service'; export interface WizardState { wizard: fromWizard.State; @@ -75,6 +76,10 @@ export const getSplitSchema = (schema: any, step: WizardStep) => { }); } + if (step.order) { + s.order = step.order; + } + if (schema.definitions) { s.definitions = schema.definitions; } diff --git a/ui/src/assets/schema/provider/dynamic-http.schema.json b/ui/src/assets/schema/provider/dynamic-http.schema.json new file mode 100644 index 000000000..9de6996de --- /dev/null +++ b/ui/src/assets/schema/provider/dynamic-http.schema.json @@ -0,0 +1,724 @@ +{ + "type": "object", + "order": [ + "xmlId", + "metadataURL" + ], + "required": [ + "xmlId", + "metadataURL" + ], + "properties": { + "enabled": { + "title": "label.enable-provider-upon-saving", + "description": "tooltip.enable-provider-upon-saving", + "type": "boolean", + "default": false + }, + "xmlId": { + "title": "label.xml-id", + "description": "tooltip.xml-id", + "type": "string", + "default": "", + "minLength": 1 + }, + "metadataRequestURLConstructionScheme": { + "title": "label.md-request-url-construction-schema", + "description": "tooltip.md-request-url-construction-schema", + "type": "object", + "properties": { + "@type": { + "title": "label.md-request-type", + "description": "tooltip.md-request-type", + "type": "string", + "widget": { + "id": "select" + }, + "oneOf": [ + { + "enum": [ + "MetadataQueryProtocol" + ], + "description": "value.md-query-protocol" + }, + { + "enum": [ + "Template" + ], + "description": "value.template" + }, + { + "enum": [ + "Regex" + ], + "description": "value.regex" + } + ] + }, + "content": { + "title": "label.value", + "description": "tooltip.md-request-url-construction-scheme-value", + "type": "string" + }, + "transformRef": { + "title": "label.transform-ref", + "description": "tooltip.transform-ref", + "type": "string", + "visibleIf": { + "content": [ + "" + ] + } + }, + "encodingStyle": { + "title": "label.encoding-style", + "description": "tooltip.encoding-style", + "type": "string", + "widget": { + "id": "select" + }, + "default": "FORM", + "oneOf": [ + { + "enum": [ + "NONE" + ], + "description": "None" + }, + { + "enum": [ + "FORM" + ], + "description": "Form" + }, + { + "enum": [ + "PATH" + ], + "description": "Path" + }, + { + "enum": [ + "FRAGMENT" + ], + "description": "Fragment" + } + ], + "visibleIf": { + "@type": [ + "Template" + ] + } + }, + "velocityEngine": { + "title": "label.velocity-engine", + "description": "tooltip.velocity-engine", + "type": "string", + "default": "shibboleth.VelocityEngine", + "visibleIf": { + "@type": [ + "Template" + ] + } + }, + "match": { + "title": "label.match", + "description": "tooltip.match", + "type": "string", + "visibleIf": { + "@type": [ + "Regex" + ] + } + } + } + }, + "requireValidMetadata": { + "title": "label.require-valid-metadata", + "description": "tooltip.require-valid-metadata", + "type": "boolean", + "widget": { + "id": "boolean-radio" + }, + "oneOf": [ + { + "enum": [ + true + ], + "description": "value.true" + }, + { + "enum": [ + false + ], + "description": "value.false" + } + ], + "default": true + }, + "failFastInitialization": { + "title": "label.fail-fast-init", + "description": "tooltip.fail-fast-init", + "type": "boolean", + "widget": { + "id": "boolean-radio" + }, + "oneOf": [ + { + "enum": [ + true + ], + "description": "value.true" + }, + { + "enum": [ + false + ], + "description": "value.false" + } + ], + "default": true + }, + "dynamicMetadataResolverAttributes": { + "type": "object", + "properties": { + "refreshDelayFactor": { + "title": "label.refresh-delay-factor", + "description": "tooltip.refresh-delay-factor", + "type": "number", + "widget": { + "id": "number", + "step": 0.01 + }, + "placeholder": "label.real-number", + "minimum": 0, + "maximum": 1, + "default": null + }, + "minCacheDuration": { + "title": "label.min-cache-duration", + "description": "tooltip.min-cache-duration", + "type": "string", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "maxCacheDuration": { + "title": "label.max-cache-duration", + "description": "tooltip.max-cache-duration", + "type": "string", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "maxIdleEntityData": { + "title": "label.max-idle-entity-data", + "description": "tooltip.max-idle-entity-data", + "type": "string", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "cleanupTaskInterval": { + "title": "label.cleanup-task-interval", + "description": "tooltip.cleanup-task-interval", + "type": "string", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "persistentCacheManagerDirectory": { + "title": "label.persistent-cache-manager-directory", + "description": "tooltip.persistent-cache-manager-directory", + "type": "string", + "default": "", + "minLength": 1 + }, + "initializeFromPersistentCacheInBackground": { + "title": "label.initialize-from-persistent-cache-in-background", + "description": "tooltip.initialize-from-persistent-cache-in-background", + "type": "boolean", + "widget": { + "id": "boolean-radio" + }, + "oneOf": [ + { + "enum": [ + true + ], + "description": "value.true" + }, + { + "enum": [ + false + ], + "description": "value.false" + } + ], + "default": true + }, + "backgroundInitializationFromCacheDelay": { + "title": "label.background-init-from-cache-delay", + "description": "tooltip.background-init-from-cache-delay", + "type": "string", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "visibleIf": { + "initializeFromPersistentCacheInBackground": [ + true + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + } + } + }, + "httpMetadataResolverAttributes": { + "order": [], + "type": "object", + "fieldsets": [ + { + "title": "label.http-security-attributes", + "type": "section", + "class": "col-12", + "fields": [ + "disregardTLSCertificate" + ] + }, + { + "title": "label.http-connection-attributes", + "type": "section", + "fields": [ + "connectionRequestTimeout", + "connectionTimeout", + "socketTimeout" + ] + }, + { + "title": "label.http-proxy-attributes", + "type": "section", + "class": "col-12", + "fields": [ + "proxyHost", + "proxyPort", + "proxyUser", + "proxyPassword" + ] + }, + { + "title": "label.http-caching-attributes", + "type": "section", + "class": "col-12", + "fields": [ + "httpCaching", + "httpCacheDirectory", + "httpMaxCacheEntries", + "httpMaxCacheEntrySize" + ] + }, + { + "title": "", + "type": "hidden", + "class": "col-12", + "fields": [ + "tlsTrustEngineRef", + "httpClientSecurityParametersRef", + "httpClientRef" + ] + } + ], + "properties": { + "disregardTLSCertificate": { + "type": "boolean", + "title": "label.disregard-tls-cert", + "description": "tooltip.disregard-tls-cert", + "widget": { + "id": "boolean-radio" + }, + "oneOf": [ + { + "enum": [ + true + ], + "description": "True" + }, + { + "enum": [ + false + ], + "description": "False" + } + ], + "default": false + }, + "httpClientRef": { + "type": "string", + "title": "", + "description": "", + "placeholder": "", + "widget": "hidden", + "default": "" + }, + "connectionRequestTimeout": { + "type": "string", + "title": "label.connection-request-timeout", + "description": "tooltip.connection-request-timeout", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "connectionTimeout": { + "type": "string", + "title": "label.connection-timeout", + "description": "tooltip.connection-timeout", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "socketTimeout": { + "type": "string", + "title": "label.socket-timeout", + "description": "tooltip.socket-timeout", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "tlsTrustEngineRef": { + "type": "string", + "title": "", + "description": "", + "placeholder": "", + "widget": "hidden", + "default": "" + }, + "httpClientSecurityParametersRef": { + "type": "string", + "title": "", + "description": "", + "placeholder": "", + "widget": "hidden", + "default": "" + }, + "proxyHost": { + "type": "string", + "title": "label.proxy-host", + "description": "tooltip.proxy-host", + "placeholder": "", + "default": "" + }, + "proxyPort": { + "type": "string", + "title": "label.proxy-port", + "description": "tooltip.proxy-port", + "placeholder": "", + "default": "" + }, + "proxyUser": { + "type": "string", + "title": "label.proxy-user", + "description": "tooltip.proxy-user", + "placeholder": "", + "default": "" + }, + "proxyPassword": { + "type": "string", + "title": "label.proxy-password", + "description": "tooltip.proxy-password", + "placeholder": "", + "default": "" + }, + "httpCaching": { + "type": "string", + "title": "label.http-caching", + "description": "tooltip.http-caching", + "placeholder": "label.select-caching-type", + "widget": { + "id": "select" + }, + "oneOf": [ + { + "enum": [ + "none" + ], + "description": "value.none" + }, + { + "enum": [ + "file" + ], + "description": "value.file" + }, + { + "enum": [ + "memory" + ], + "description": "value.memory" + } + ] + }, + "httpCacheDirectory": { + "type": "string", + "title": "label.http-caching-directory", + "description": "tooltip.http-caching-directory", + "placeholder": "" + }, + "httpMaxCacheEntries": { + "type": "integer", + "title": "label.http-max-cache-entries", + "description": "tooltip.http-max-cache-entries", + "placeholder": "", + "default": 0, + "minimum": 0 + }, + "httpMaxCacheEntrySize": { + "type": "integer", + "title": "label.max-cache-entry-size", + "description": "tooltip.max-cache-entry-size", + "placeholder": "", + "default": 0, + "minimum": 0 + } + } + }, + "metadataFilters": { + "title": "", + "description": "", + "type": "object", + "properties": { + "RequiredValidUntil": { + "title": "label.required-valid-until", + "type": "object", + "widget": { + "id": "fieldset" + }, + "properties": { + "maxValidityInterval": { + "title": "label.max-validity-interval", + "description": "tooltip.max-validity-interval", + "type": "string", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + } + } + }, + "SignatureValidation": { + "title": "label.signature-validation-filter", + "type": "object", + "widget": { + "id": "fieldset" + }, + "properties": { + "requireSignedRoot": { + "title": "label.require-signed-root", + "description": "tooltip.require-signed-root", + "type": "boolean", + "default": true + }, + "certificateFile": { + "title": "label.certificate-file", + "description": "tooltip.certificate-file", + "type": "string", + "widget": "textarea", + "default": "" + } + }, + "anyOf": [ + { + "properties": { + "requireSignedRoot": { + "enum": [ + true + ] + } + }, + "required": [ + "certificateFile" + ] + }, + { + "properties": { + "requireSignedRoot": { + "enum": [ + false + ] + } + } + } + ] + }, + "EntityRoleWhiteList": { + "title": "label.entity-role-whitelist", + "type": "object", + "widget": { + "id": "fieldset" + }, + "properties": { + "retainedRoles": { + "title": "label.retained-roles", + "description": "tooltip.retained-roles", + "type": "array", + "items": { + "widget": { + "id": "select" + }, + "type": "string", + "oneOf": [ + { + "enum": [ + "SPSSODescriptor" + ], + "description": "value.spdescriptor" + }, + { + "enum": [ + "AttributeAuthorityDescriptor" + ], + "description": "value.attr-auth-descriptor" + } + ] + } + }, + "removeRolelessEntityDescriptors": { + "title": "label.remove-roleless-entity-descriptors", + "description": "tooltip.remove-roleless-entity-descriptors", + "type": "boolean", + "default": true + }, + "removeEmptyEntitiesDescriptors": { + "title": "label.remove-empty-entities-descriptors", + "description": "tooltip.remove-empty-entities-descriptors", + "type": "boolean", + "default": true + } + } + } + } + } + } +} \ No newline at end of file