diff --git a/backend/build.gradle b/backend/build.gradle index 1ecb7fea8..f03f9f9ed 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -136,6 +136,9 @@ dependencies { //JSON schema generator testCompile 'com.kjetland:mbknor-jackson-jsonschema_2.12:1.0.29' testCompile 'javax.validation:validation-api:2.0.1.Final' + + //JSON schema validator + compile 'org.sharegov:mjson:1.4.1' } def generatedSrcDir = new File(buildDir, 'generated/src/main/java') diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityAttributesFiltersUiDefinitionController.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityAttributesFiltersUiDefinitionController.groovy new file mode 100644 index 000000000..ffd3ee3ab --- /dev/null +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityAttributesFiltersUiDefinitionController.groovy @@ -0,0 +1,61 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import com.fasterxml.jackson.databind.ObjectMapper +import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation +import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocationRegistry +import edu.internet2.tier.shibboleth.admin.ui.service.JsonSchemaBuilderService +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +import javax.annotation.PostConstruct + +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.entityAttributesFiltersSchema +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR + +/** + * Controller implementing REST resource responsible for exposing structure definition for metadata sources user + * interface in terms of JSON schema. + * + * @author Dmitriy Kopylenko + * @author Bill Smith (wsmith@unicon.net) + */ +@RestController +@RequestMapping('/api/ui/EntityAttributesFilters') +class EntityAttributesFiltersUiDefinitionController { + + @Autowired + JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry + + JsonSchemaResourceLocation jsonSchemaLocation + + @Autowired + ObjectMapper jacksonObjectMapper + + @Autowired + JsonSchemaBuilderService jsonSchemaBuilderService + + @GetMapping + ResponseEntity getUiDefinitionJsonSchema() { + try { + def parsedJson = jacksonObjectMapper.readValue(this.jsonSchemaLocation.url, Map) + jsonSchemaBuilderService.addReleaseAttributesToJson(parsedJson['properties']['attributeRelease']['widget']) + jsonSchemaBuilderService.addRelyingPartyOverridesToJson(parsedJson['properties']['relyingPartyOverrides']) + jsonSchemaBuilderService.addRelyingPartyOverridesCollectionDefinitionsToJson(parsedJson["definitions"]) + return ResponseEntity.ok(parsedJson) + } + catch (Exception e) { + e.printStackTrace() + return ResponseEntity.status(INTERNAL_SERVER_ERROR) + .body([jsonParseError : e.getMessage(), + sourceUiSchemaDefinitionFile: this.jsonSchemaLocation.url]) + } + } + + @PostConstruct + void init() { + this.jsonSchemaLocation = entityAttributesFiltersSchema(this.jsonSchemaResourceLocationRegistry); + } +} diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataSourcesUiDefinitionController.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataSourcesUiDefinitionController.groovy index 2423c48f5..d138f3a57 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataSourcesUiDefinitionController.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataSourcesUiDefinitionController.groovy @@ -1,14 +1,20 @@ package edu.internet2.tier.shibboleth.admin.ui.controller import com.fasterxml.jackson.databind.ObjectMapper -import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomAttributesConfiguration -import edu.internet2.tier.shibboleth.admin.ui.jsonschema.MetadataSourcesJsonSchemaResourceLocation +import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation +import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocationRegistry +import edu.internet2.tier.shibboleth.admin.ui.service.JsonSchemaBuilderService +import org.slf4j.Logger +import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import javax.annotation.PostConstruct + +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.metadataSourcesSchema import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR /** @@ -22,29 +28,38 @@ import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR @RequestMapping('/api/ui/MetadataSources') class MetadataSourcesUiDefinitionController { + private static final Logger logger = LoggerFactory.getLogger(MetadataSourcesUiDefinitionController.class); + @Autowired - MetadataSourcesJsonSchemaResourceLocation jsonSchemaLocation + JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry + + JsonSchemaResourceLocation jsonSchemaLocation @Autowired ObjectMapper jacksonObjectMapper @Autowired - CustomAttributesConfiguration customAttributesConfiguration + JsonSchemaBuilderService jsonSchemaBuilderService @GetMapping ResponseEntity getUiDefinitionJsonSchema() { try { def parsedJson = jacksonObjectMapper.readValue(this.jsonSchemaLocation.url, Map) - parsedJson['properties']['attributeRelease']['widget']['data'] = - customAttributesConfiguration.getAttributes().collect { - [key: it['name'], label: it['displayName']] - } + jsonSchemaBuilderService.addReleaseAttributesToJson(parsedJson['properties']['attributeRelease']['widget']) + jsonSchemaBuilderService.addRelyingPartyOverridesToJson(parsedJson['properties']['relyingPartyOverrides']) + jsonSchemaBuilderService.addRelyingPartyOverridesCollectionDefinitionsToJson(parsedJson["definitions"]) return ResponseEntity.ok(parsedJson) } - catch (Exception e) { + catch (IOException e) { + logger.error("An error occurred while attempting to get json schema for metadata sources!", e) return ResponseEntity.status(INTERNAL_SERVER_ERROR) .body([jsonParseError : e.getMessage(), sourceUiSchemaDefinitionFile: this.jsonSchemaLocation.url]) } } + + @PostConstruct + void init() { + this.jsonSchemaLocation = metadataSourcesSchema(this.jsonSchemaResourceLocationRegistry); + } } diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy new file mode 100644 index 000000000..712142172 --- /dev/null +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy @@ -0,0 +1,61 @@ +package edu.internet2.tier.shibboleth.admin.ui.jsonschema + +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation +import mjson.Json +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.core.MethodParameter +import org.springframework.http.HttpInputMessage +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.http.converter.HttpMessageConverter +import org.springframework.web.bind.annotation.ControllerAdvice +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.context.request.WebRequest +import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter + +import javax.annotation.PostConstruct +import java.lang.reflect.Type + +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.metadataSourcesSchema + +/** + * Controller advice implementation for validating relying party overrides payload coming from UI layer + * against pre-defined JSON schema. + * + * @author Dmitriy Kopylenko + */ +@ControllerAdvice +class RelyingPartyOverridesJsonSchemaValidatingControllerAdvice extends RequestBodyAdviceAdapter { + + @Autowired + JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry + + JsonSchemaResourceLocation jsonSchemaLocation + + @Override + boolean supports(MethodParameter methodParameter, Type targetType, Class> converterType) { + targetType.typeName == EntityDescriptorRepresentation.typeName + } + + @Override + Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { + def relyingPartyOverrides = EntityDescriptorRepresentation.cast(body).relyingPartyOverrides + def relyingPartyOverridesJson = Json.make([relyingPartyOverrides: relyingPartyOverrides]) + def schema = Json.schema(this.jsonSchemaLocation.uri) + def validationResult = schema.validate(relyingPartyOverridesJson) + if (!validationResult.at('ok')) { + throw new JsonSchemaValidationFailedException(validationResult.at('errors').asList()) + } + body + } + + @ExceptionHandler(JsonSchemaValidationFailedException) + final ResponseEntity handleUserNotFoundException(JsonSchemaValidationFailedException ex, WebRequest request) { + new ResponseEntity<>([errors: ex.errors], HttpStatus.BAD_REQUEST) + } + + @PostConstruct + void init() { + this.jsonSchemaLocation = metadataSourcesSchema(this.jsonSchemaResourceLocationRegistry); + } +} 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 4e6879d0f..4a95484e8 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 @@ -125,15 +125,15 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService { resolversPositionOrderContainerService.allMetadataResolversInDefinedOrderOrUnordered.each { edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver mr -> - //TODO: We do not currently marshall the internal incommon chaining resolver (with BaseMetadataResolver type) - if ((mr.type != 'BaseMetadataResolver') && (mr.enabled)) { - constructXmlNodeForResolver(mr, delegate) { - //TODO: enhance - mr.metadataFilters.each { edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter filter -> - constructXmlNodeForFilter(filter, delegate) + //TODO: We do not currently marshall the internal incommon chaining resolver (with BaseMetadataResolver type) + if ((mr.type != 'BaseMetadataResolver') && (mr.enabled)) { + constructXmlNodeForResolver(mr, delegate) { + //TODO: enhance + mr.metadataFilters.each { edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter filter -> + constructXmlNodeForFilter(filter, delegate) + } } } - } } } return DOMBuilder.newInstance().parseText(writer.toString()) @@ -407,4 +407,4 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService { } } -} +} \ No newline at end of file diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JsonSchemaBuilderService.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JsonSchemaBuilderService.groovy new file mode 100644 index 000000000..b98bcab26 --- /dev/null +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JsonSchemaBuilderService.groovy @@ -0,0 +1,65 @@ +package edu.internet2.tier.shibboleth.admin.ui.service + +import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration +import org.springframework.beans.factory.annotation.Autowired + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +class JsonSchemaBuilderService { + + @Autowired + CustomPropertiesConfiguration customPropertiesConfiguration + + void addReleaseAttributesToJson(Object json) { + json['data'] = customPropertiesConfiguration.getAttributes().collect { + [key: it['name'], label: it['displayName']] + } + } + + void addRelyingPartyOverridesToJson(Object json) { + def properties = [:] + customPropertiesConfiguration.getOverrides().each { + def property + if (it['displayType'] == 'list' + || it['displayType'] == 'set') { + property = [$ref: '#/definitions/' + it['name']] + } else { + property = + [title : it['displayName'], + description: it['helpText'], + type : it['displayType']] + if (it['displayType'] == 'boolean') { + property['default'] = (Boolean)(it['defaultValue']) + } else { + property['default'] = it['defaultValue'] + } + } + properties[(String) it['name']] = property + } + json['properties'] = properties + } + + void addRelyingPartyOverridesCollectionDefinitionsToJson(Object json) { + customPropertiesConfiguration.getOverrides().stream().filter { + it -> it['displayType'] && (it['displayType'] == 'list' || it['displayType'] == 'set') + }.each { + def definition = [title : it['displayName'], + description: it['helpText'], + type : 'array', + default : null] + if (it['displayType'] == 'set') { + definition['uniqueItems'] = true + } else if (it['displayType'] == 'list') { + definition['uniqueItems'] = false + } + def items = [type : 'string', + minLength: 1, // TODO: should this be configurable? + maxLength: 255] //TODO: or this? + items.widget = [id: 'datalist', data: it['defaultValues']] + + definition['items'] = items + json[(String) it['name']] = definition + } + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java index a63c3da6e..fc1e0e8b4 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java @@ -25,13 +25,14 @@ import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolversPositionOrderContainerService; import edu.internet2.tier.shibboleth.admin.util.AttributeUtility; import edu.internet2.tier.shibboleth.admin.util.LuceneUtility; +import edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions; import org.apache.lucene.analysis.Analyzer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.ResourceBundleMessageSource; @@ -46,7 +47,7 @@ import javax.servlet.http.HttpServletRequest; @Configuration -@EnableConfigurationProperties(CustomAttributesConfiguration.class) +@EnableConfigurationProperties(CustomPropertiesConfiguration.class) public class CoreShibUiConfiguration { private static final Logger logger = LoggerFactory.getLogger(CoreShibUiConfiguration.class); @@ -180,12 +181,17 @@ public LuceneUtility luceneUtility(DirectoryService directoryService) { } @Bean - public CustomAttributesConfiguration customAttributesConfiguration() { - return new CustomAttributesConfiguration(); + public CustomPropertiesConfiguration customPropertiesConfiguration() { + return new CustomPropertiesConfiguration(); } @Bean public Module stringTrimModule() { return new StringTrimModule(); } + + @Bean + public ModelRepresentationConversions modelRepresentationConversions() { + return new ModelRepresentationConversions(customPropertiesConfiguration()); + } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomAttributesConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomPropertiesConfiguration.java similarity index 62% rename from backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomAttributesConfiguration.java rename to backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomPropertiesConfiguration.java index aa12be6b2..a6a1db63d 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomAttributesConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomPropertiesConfiguration.java @@ -1,5 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.configuration; +import edu.internet2.tier.shibboleth.admin.ui.domain.RelyingPartyOverrideProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @@ -12,9 +13,10 @@ */ @Configuration @ConfigurationProperties(prefix="custom") -public class CustomAttributesConfiguration { +public class CustomPropertiesConfiguration { private List> attributes = new ArrayList<>(); + private List overrides = new ArrayList<>(); public List> getAttributes() { return attributes; @@ -23,4 +25,12 @@ public List> getAttributes() { public void setAttributes(List> attributes) { this.attributes = attributes; } + + public List getOverrides() { + return overrides; + } + + public void setOverrides(List overrides) { + this.overrides = overrides; + } } 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 new file mode 100644 index 000000000..1bf2745c6 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/JsonSchemaComponentsConfiguration.java @@ -0,0 +1,55 @@ +package edu.internet2.tier.shibboleth.admin.ui.configuration; + +import com.fasterxml.jackson.databind.ObjectMapper; +import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocationRegistry; +import edu.internet2.tier.shibboleth.admin.ui.service.JsonSchemaBuilderService; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ResourceLoader; + +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.*; +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.METADATA_SOURCES; + +/** + * @author Dmitriy Kopylenko + */ +@Configuration +@ConfigurationProperties("shibui") +public class JsonSchemaComponentsConfiguration { + + //Configured via @ConfigurationProperties (using setter method) with 'shibui.metadata-sources-ui-schema-location' property and default + //value set here if that property is not explicitly set in application.properties + @Setter + private String metadataSourcesUiSchemaLocation = "classpath:metadata-sources-ui-schema.json"; + + //Configured via @ConfigurationProperties (using setter method) with 'shibui.entity-attributes-filters-ui-schema-location' property and + // default value set here if that property is not explicitly set in application.properties + @Setter + private String entityAttributesFiltersUiSchemaLocation = "classpath:entity-attributes-filters-ui-schema.json"; + + @Bean + public JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry(ResourceLoader resourceLoader, ObjectMapper jacksonMapper) { + return JsonSchemaResourceLocationRegistry.inMemory() + .register(METADATA_SOURCES, JsonSchemaLocationBuilder.with() + .jsonSchemaLocation(metadataSourcesUiSchemaLocation) + .resourceLoader(resourceLoader) + .jacksonMapper(jacksonMapper) + .detectMalformedJson(true) + .build()) + .register(ENTITY_ATTRIBUTES_FILTERS, JsonSchemaLocationBuilder.with() + .jsonSchemaLocation(entityAttributesFiltersUiSchemaLocation) + .resourceLoader(resourceLoader) + .jacksonMapper(jacksonMapper) + .detectMalformedJson(true) + .build()); + + } + + @Bean + public JsonSchemaBuilderService jsonSchemaBuilderService() { + return new JsonSchemaBuilderService(); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/JsonSchemaValidationComponentsConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/JsonSchemaValidationComponentsConfiguration.java deleted file mode 100644 index 48fb33ede..000000000 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/JsonSchemaValidationComponentsConfiguration.java +++ /dev/null @@ -1,30 +0,0 @@ -package edu.internet2.tier.shibboleth.admin.ui.configuration; - -import com.fasterxml.jackson.databind.ObjectMapper; -import edu.internet2.tier.shibboleth.admin.ui.jsonschema.MetadataSourcesJsonSchemaResourceLocation; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ResourceLoader; - -/** - * @author Dmitriy Kopylenko - */ -@Configuration -@ConfigurationProperties("shibui") -public class JsonSchemaValidationComponentsConfiguration { - - //Configured via @ConfigurationProperties (using setter method) with 'shibui.metadata-sources-ui-schema-location' property and default - //value set here if that property is not explicitly set in application.properties - private String metadataSourcesUiSchemaLocation ="classpath:metadata-sources-ui-schema.json"; - - //This setter is used by Boot's @ConfiguratonProperties binding machinery - public void setMetadataSourcesUiSchemaLocation(String metadataSourcesUiSchemaLocation) { - this.metadataSourcesUiSchemaLocation = metadataSourcesUiSchemaLocation; - } - - @Bean - public MetadataSourcesJsonSchemaResourceLocation metadataSourcesJsonSchemaResourceLocation(ResourceLoader resourceLoader, ObjectMapper jacksonMapper) { - return new MetadataSourcesJsonSchemaResourceLocation(metadataSourcesUiSchemaLocation, resourceLoader, jacksonMapper); - } -} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ConfigurationController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ConfigurationController.java index 4a0388428..f71e76cb5 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ConfigurationController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ConfigurationController.java @@ -1,6 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.controller; -import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomAttributesConfiguration; +import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; @@ -15,10 +15,10 @@ public class ConfigurationController { @Autowired - CustomAttributesConfiguration customAttributesConfiguration; + CustomPropertiesConfiguration customPropertiesConfiguration; @GetMapping(value = "/customAttributes") public ResponseEntity getCustomAttributes() { - return ResponseEntity.ok(customAttributesConfiguration.getAttributes()); + return ResponseEntity.ok(customPropertiesConfiguration.getAttributes()); } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RelyingPartyOverrideProperty.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RelyingPartyOverrideProperty.java new file mode 100644 index 000000000..24432d1be --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RelyingPartyOverrideProperty.java @@ -0,0 +1,40 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@Setter +@Getter +public class RelyingPartyOverrideProperty { + private String name; + private String displayName; + private String displayType; + private String defaultValue; + private String helpText; + private List defaultValues; + private String persistType; + private String persistValue; + private String attributeName; + private String attributeFriendlyName; + + @Override + public String toString() { + return "RelyingPartyOverrideProperty{" + + "\nname='" + name + '\'' + + ", \ndisplayName='" + displayName + '\'' + + ", \ndisplayType='" + displayType + '\'' + + ", \ndefaultValue='" + defaultValue + '\'' + + ", \nhelpText='" + helpText + '\'' + + ", \npersistType='" + persistType + '\'' + + ", \npersistValue='" + persistValue + '\'' + + ", \ndefaultValues=" + defaultValues + + ", \nattributeName='" + attributeName + '\'' + + ", \nattributeFriendlyName='" + attributeFriendlyName + '\'' + + "\n}"; + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilter.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilter.java index 8492e745d..86a2181c7 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilter.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilter.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import edu.internet2.tier.shibboleth.admin.ui.domain.Attribute; -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.RelyingPartyOverridesRepresentation; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; @@ -16,8 +15,8 @@ import javax.persistence.PostLoad; import javax.persistence.Transient; import java.util.ArrayList; - import java.util.List; +import java.util.Map; import static edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions.getAttributeListFromAttributeReleaseList; import static edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions.getAttributeListFromRelyingPartyOverridesRepresentation; @@ -52,9 +51,9 @@ public void setAttributeRelease(List attributeRelease) { } @Transient - private RelyingPartyOverridesRepresentation relyingPartyOverrides; + private Map relyingPartyOverrides; - public void setRelyingPartyOverrides(RelyingPartyOverridesRepresentation relyingPartyOverridesRepresentation) { + public void setRelyingPartyOverrides(Map relyingPartyOverridesRepresentation) { this.relyingPartyOverrides = relyingPartyOverridesRepresentation; this.rebuildAttributes(); } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java index c684d458a..b879e76d5 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java @@ -5,6 +5,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Map; public class EntityDescriptorRepresentation implements Serializable { @@ -58,7 +59,7 @@ public EntityDescriptorRepresentation(String id, private LocalDateTime modifiedDate; - private RelyingPartyOverridesRepresentation relyingPartyOverrides; + private Map relyingPartyOverrides; private List attributeRelease; @@ -180,11 +181,11 @@ public void setModifiedDate(LocalDateTime modifiedDate) { this.modifiedDate = modifiedDate; } - public RelyingPartyOverridesRepresentation getRelyingPartyOverrides() { + public Map getRelyingPartyOverrides() { return relyingPartyOverrides; } - public void setRelyingPartyOverrides(RelyingPartyOverridesRepresentation relyingPartyOverrides) { + public void setRelyingPartyOverrides(Map relyingPartyOverrides) { this.relyingPartyOverrides = relyingPartyOverrides; } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/FilterRepresentation.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/FilterRepresentation.java index b9e2f1213..0276a2b9c 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/FilterRepresentation.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/FilterRepresentation.java @@ -3,13 +3,14 @@ import java.io.Serializable; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; public class FilterRepresentation implements Serializable { private String id; private String filterName; private boolean filterEnabled; private FilterTargetRepresentation filterTarget; - private RelyingPartyOverridesRepresentation relyingPartyOverrides; + private Map relyingPartyOverrides; private List attributeRelease; private LocalDateTime createdDate; private LocalDateTime modifiedDate; @@ -57,11 +58,11 @@ public void setFilterTarget(FilterTargetRepresentation filterTarget) { this.filterTarget = filterTarget; } - public RelyingPartyOverridesRepresentation getRelyingPartyOverrides() { + public Map getRelyingPartyOverrides() { return relyingPartyOverrides; } - public void setRelyingPartyOverrides(RelyingPartyOverridesRepresentation relyingPartyOverrides) { + public void setRelyingPartyOverrides(Map relyingPartyOverrides) { this.relyingPartyOverrides = relyingPartyOverrides; } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/RelyingPartyOverridesRepresentation.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/RelyingPartyOverridesRepresentation.java deleted file mode 100644 index cac2a1d6a..000000000 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/RelyingPartyOverridesRepresentation.java +++ /dev/null @@ -1,110 +0,0 @@ -package edu.internet2.tier.shibboleth.admin.ui.domain.frontend; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -public class RelyingPartyOverridesRepresentation implements Serializable { - - private static final long serialVersionUID = 2439457246884861580L; - - private boolean signAssertion; - - private boolean dontSignResponse; - - private boolean turnOffEncryption; - - private boolean useSha; - - private boolean ignoreAuthenticationMethod; - - private boolean omitNotBefore; - - private String responderId; - - private List nameIdFormats = new ArrayList<>(); - - private List authenticationMethods = new ArrayList<>(); - - private boolean forceAuthn; - - public boolean isSignAssertion() { - return signAssertion; - } - - public void setSignAssertion(boolean signAssertion) { - this.signAssertion = signAssertion; - } - - public boolean isDontSignResponse() { - return dontSignResponse; - } - - public void setDontSignResponse(boolean dontSignResponse) { - this.dontSignResponse = dontSignResponse; - } - - public boolean isTurnOffEncryption() { - return turnOffEncryption; - } - - public void setTurnOffEncryption(boolean turnOffEncryption) { - this.turnOffEncryption = turnOffEncryption; - } - - public boolean isUseSha() { - return useSha; - } - - public void setUseSha(boolean useSha) { - this.useSha = useSha; - } - - public boolean isIgnoreAuthenticationMethod() { - return ignoreAuthenticationMethod; - } - - public void setIgnoreAuthenticationMethod(boolean ignoreAuthenticationMethod) { - this.ignoreAuthenticationMethod = ignoreAuthenticationMethod; - } - - public boolean isOmitNotBefore() { - return omitNotBefore; - } - - public void setOmitNotBefore(boolean omitNotBefore) { - this.omitNotBefore = omitNotBefore; - } - - public String getResponderId() { - return responderId; - } - - public void setResponderId(String responderId) { - this.responderId = responderId; - } - - public List getNameIdFormats() { - return nameIdFormats; - } - - public void setNameIdFormats(List nameIdFormats) { - this.nameIdFormats = nameIdFormats; - } - - public List getAuthenticationMethods() { - return authenticationMethods; - } - - public void setAuthenticationMethods(List authenticationMethods) { - this.authenticationMethods = authenticationMethods; - } - - public boolean isForceAuthn() { - return forceAuthn; - } - - public void setForceAuthn(boolean forceAuthn) { - this.forceAuthn = forceAuthn; - } -} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/InMemoryJsonSchemaResourceLocationRegistry.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/InMemoryJsonSchemaResourceLocationRegistry.java new file mode 100644 index 000000000..2840619b2 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/InMemoryJsonSchemaResourceLocationRegistry.java @@ -0,0 +1,31 @@ +package edu.internet2.tier.shibboleth.admin.ui.jsonschema; + +import java.util.EnumMap; +import java.util.Map; +import java.util.Optional; + +/** + * Default implementation of {@link JsonSchemaResourceLocationRegistry}. + *

+ * This class has package private visibility as creation of it is delegated to public static factory method + * on the registry interface itself. + * + * @author Dmitriy Kopylenko + */ +class InMemoryJsonSchemaResourceLocationRegistry implements JsonSchemaResourceLocationRegistry { + + private Map schemaLocations = + new EnumMap<>(JsonSchemaResourceLocation.SchemaType.class); + + + @Override + public JsonSchemaResourceLocationRegistry register(JsonSchemaResourceLocation.SchemaType type, JsonSchemaResourceLocation location) { + this.schemaLocations.put(type, location); + return this; + } + + @Override + public Optional lookup(JsonSchemaResourceLocation.SchemaType type) { + return Optional.ofNullable(this.schemaLocations.get(type)); + } +} 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 new file mode 100644 index 000000000..e04dd72de --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaLocationLookup.java @@ -0,0 +1,38 @@ +package edu.internet2.tier.shibboleth.admin.ui.jsonschema; + +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.METADATA_SOURCES; + +/** + * Utility methods for common JSON schema types lookups. + * + * @author Dmitriy Kopylenko + */ +public abstract class JsonSchemaLocationLookup { + + /** + * Searches metadata sources JSON schema resource location object in the given location registry. + * + * @param resourceLocationRegistry + * @return metadata sources JSON schema resource location object + * @throws IllegalStateException if schema is not found in the given registry + */ + public static JsonSchemaResourceLocation metadataSourcesSchema(JsonSchemaResourceLocationRegistry resourceLocationRegistry) { + return resourceLocationRegistry + .lookup(METADATA_SOURCES) + .orElseThrow(() -> new IllegalStateException("JSON schema resource location for metadata sources is not registered.")); + } + + /** + * Searches entity attributes filters JSON schema resource location object in the given location registry. + * + * @param resourceLocationRegistry + * @returnentity attributes filters JSON schema resource location object + * @throws IllegalStateException if schema is not found in the given registry + */ + public static JsonSchemaResourceLocation entityAttributesFiltersSchema(JsonSchemaResourceLocationRegistry resourceLocationRegistry) { + return resourceLocationRegistry + .lookup(ENTITY_ATTRIBUTES_FILTERS) + .orElseThrow(() -> new IllegalStateException("JSON schema resource location for entity attributes filters is not registered.")); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataSourcesJsonSchemaResourceLocation.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaResourceLocation.java similarity index 55% rename from backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataSourcesJsonSchemaResourceLocation.java rename to backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaResourceLocation.java index 98d53cd39..f08ac6069 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataSourcesJsonSchemaResourceLocation.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaResourceLocation.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.jsonschema; import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Builder; import org.springframework.beans.factory.BeanInitializationException; import org.springframework.core.io.ResourceLoader; @@ -11,13 +12,13 @@ import java.util.Map; /** - * Encapsulates metadata sources JSON schema location. + * Encapsulates arbitrary JSON schema location. * * @author Dmitriy Kopylenko */ -public class MetadataSourcesJsonSchemaResourceLocation { +public class JsonSchemaResourceLocation { - private final String metadataSourcesUiSchemaLocation; + private final String jsonSchemaLocation; private URL jsonSchemaUrl; @@ -27,18 +28,19 @@ public class MetadataSourcesJsonSchemaResourceLocation { private boolean detectMalformedJsonDuringInit = true; - public MetadataSourcesJsonSchemaResourceLocation(String metadataSourcesUiSchemaLocation, ResourceLoader resourceLoader, ObjectMapper jacksonMapper) { - this.metadataSourcesUiSchemaLocation = metadataSourcesUiSchemaLocation; + public JsonSchemaResourceLocation(String jsonSchemaLocation, ResourceLoader resourceLoader, ObjectMapper jacksonMapper) { + this.jsonSchemaLocation = jsonSchemaLocation; this.resourceLoader = resourceLoader; this.jacksonMapper = jacksonMapper; } //This constructor is used in tests - public MetadataSourcesJsonSchemaResourceLocation(String metadataSourcesUiSchemaLocation, - ResourceLoader resourceLoader, - ObjectMapper jacksonMapper, - boolean detectMalformedJsonDuringInit) { - this.metadataSourcesUiSchemaLocation = metadataSourcesUiSchemaLocation; + public JsonSchemaResourceLocation(String jsonSchemaLocation, + ResourceLoader resourceLoader, + ObjectMapper jacksonMapper, + boolean detectMalformedJsonDuringInit) { + + this.jsonSchemaLocation = jsonSchemaLocation; this.resourceLoader = resourceLoader; this.jacksonMapper = jacksonMapper; this.detectMalformedJsonDuringInit = detectMalformedJsonDuringInit; @@ -60,7 +62,7 @@ public URI getUri() { @PostConstruct public void init() { try { - this.jsonSchemaUrl = this.resourceLoader.getResource(this.metadataSourcesUiSchemaLocation).getURL(); + this.jsonSchemaUrl = this.resourceLoader.getResource(this.jsonSchemaLocation).getURL(); if(this.detectMalformedJsonDuringInit) { //Detect malformed JSON schema early, during application start up and fail fast with useful exception message this.jacksonMapper.readValue(this.jsonSchemaUrl, Map.class); @@ -69,9 +71,26 @@ public void init() { catch (Exception ex) { StringBuilder msg = new StringBuilder(String.format("An error is detected during JSON parsing => [%s]", ex.getMessage())); - msg.append(String.format("Offending resource => [%s]", this.metadataSourcesUiSchemaLocation)); + msg.append(String.format("Offending resource => [%s]", this.jsonSchemaLocation)); throw new BeanInitializationException(msg.toString(), ex); } } -} + + public static class JsonSchemaLocationBuilder { + + @Builder(builderMethodName = "with") + public static JsonSchemaResourceLocation newSchemaLocation(String jsonSchemaLocation, + ResourceLoader resourceLoader, + ObjectMapper jacksonMapper, + boolean detectMalformedJson) { + JsonSchemaResourceLocation location = new JsonSchemaResourceLocation(jsonSchemaLocation, resourceLoader, jacksonMapper, detectMalformedJson); + location.init(); + return location; + } + } + + public enum SchemaType { + METADATA_SOURCES, ENTITY_ATTRIBUTES_FILTERS + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaResourceLocationRegistry.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaResourceLocationRegistry.java new file mode 100644 index 000000000..e5a6e88c5 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaResourceLocationRegistry.java @@ -0,0 +1,37 @@ +package edu.internet2.tier.shibboleth.admin.ui.jsonschema; + +import java.util.Optional; + +/** + * An API to store and expose JSON schema resource locations for various JSON schema types. Typically configured as a Spring + * bean and injected into Spring-managed components interested in looking up JSON schema locations by particular type. + * + * @author Dmitriy Kopylenko + */ +public interface JsonSchemaResourceLocationRegistry { + + /** + * Register json schema resource location for given schema type. + * + * @param type of JSON schema + * @param location of JSON schema resource + */ + JsonSchemaResourceLocationRegistry register(JsonSchemaResourceLocation.SchemaType type, JsonSchemaResourceLocation location); + + /** + * Look up json schema resource location by given schema type. + * + * @param type type of JSON schema + * @return optional location of JSON schema resource + */ + Optional lookup(JsonSchemaResourceLocation.SchemaType type); + + /** + * Factory method. + * + * @return in-memory implementation + */ + static JsonSchemaResourceLocationRegistry inMemory() { + return new InMemoryJsonSchemaResourceLocationRegistry(); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaValidationFailedException.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaValidationFailedException.java new file mode 100644 index 000000000..eac2a6d2f --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaValidationFailedException.java @@ -0,0 +1,18 @@ +package edu.internet2.tier.shibboleth.admin.ui.jsonschema; + +import java.util.List; + +/** + * Indicates JSON schema validation failure. Encapsulates a list of error messages produced by JSON schema validator + * component. + * + * @author Dmitriy Kopylenko + */ +class JsonSchemaValidationFailedException extends RuntimeException { + + List errors; + + JsonSchemaValidationFailedException(List errors) { + this.errors = errors; + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityDescriptorService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityDescriptorService.java index 086c65f64..ea57c4d06 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityDescriptorService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityDescriptorService.java @@ -2,10 +2,10 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.Attribute; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation; -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.RelyingPartyOverridesRepresentation; import org.opensaml.saml.saml2.metadata.EntityDescriptor; import java.util.List; +import java.util.Map; /** * Main backend facade API that defines operations pertaining to manipulating {@link EntityDescriptor} state. @@ -47,11 +47,11 @@ public interface EntityDescriptorService { List getAttributeReleaseListFromAttributeList(List attributeList); /** - * Given a list of attributes, generate a RelyingPartyOverridesRepresentation + * Given a list of attributes, generate a map of relying party overrides * * @param attributeList the list of attributes to generate from - * @return a RelyingPartyOverridesRepresentation based on the given list of attributes + * @return a map of String->Object (property name -> property value) based on the given list of attributes */ - RelyingPartyOverridesRepresentation getRelyingPartyOverridesRepresentationFromAttributeList(List attributeList); + Map getRelyingPartyOverridesRepresentationFromAttributeList(List attributeList); } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityService.java index 5a4a65a25..6a676844a 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityService.java @@ -1,10 +1,10 @@ package edu.internet2.tier.shibboleth.admin.ui.service; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation; -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.RelyingPartyOverridesRepresentation; import org.opensaml.saml.saml2.core.Attribute; import java.util.List; +import java.util.Map; /** * facade API that defines operations for creating various entities from JSON representations @@ -13,5 +13,5 @@ public interface EntityService { List getAttributeListFromEntityRepresentation(EntityDescriptorRepresentation entityDescriptorRepresentation); edu.internet2.tier.shibboleth.admin.ui.domain.Attribute getAttributeFromAttributeReleaseList(List attributeReleaseList); List getAttributeListFromAttributeReleaseList(List attributeReleaseList); - List getAttributeListFromRelyingPartyOverridesRepresentation(RelyingPartyOverridesRepresentation relyingPartyOverridesRepresentation); + List getAttributeListFromRelyingPartyOverridesRepresentation(Map relyingPartyOverridesRepresentation); } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java index a0ec7e1b6..adb8e4cb8 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java @@ -25,25 +25,28 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.OrganizationName; import edu.internet2.tier.shibboleth.admin.ui.domain.OrganizationURL; import edu.internet2.tier.shibboleth.admin.ui.domain.PrivacyStatementURL; +import edu.internet2.tier.shibboleth.admin.ui.domain.RelyingPartyOverrideProperty; import edu.internet2.tier.shibboleth.admin.ui.domain.SPSSODescriptor; import edu.internet2.tier.shibboleth.admin.ui.domain.SingleLogoutService; import edu.internet2.tier.shibboleth.admin.ui.domain.UIInfo; import edu.internet2.tier.shibboleth.admin.ui.domain.XSAny; import edu.internet2.tier.shibboleth.admin.ui.domain.XSBoolean; +import edu.internet2.tier.shibboleth.admin.ui.domain.XSInteger; +import edu.internet2.tier.shibboleth.admin.ui.domain.XSString; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.AssertionConsumerServiceRepresentation; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.ContactRepresentation; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.LogoutEndpointRepresentation; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.MduiRepresentation; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.OrganizationRepresentation; -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.RelyingPartyOverridesRepresentation; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.SecurityInfoRepresentation; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.ServiceProviderSsoDescriptorRepresentation; import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects; import edu.internet2.tier.shibboleth.admin.util.MDDCConstants; import edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions; +import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.schema.XSBooleanValue; import org.opensaml.xmlsec.signature.KeyInfo; import org.opensaml.xmlsec.signature.X509Certificate; @@ -54,12 +57,13 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; -import static edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions.getBooleanValueOfAttribute; import static edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions.getStringListOfAttributeValues; -import static edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions.getStringListValueOfAttribute; /** * Default implementation of {@link EntityDescriptorService} @@ -489,66 +493,75 @@ public EntityDescriptorRepresentation createRepresentationFromDescriptor(org.ope // set up extensions if (ed.getExtensions() != null && ed.getExtensions().getUnknownXMLObjects(EntityAttributes.DEFAULT_ELEMENT_NAME) != null && ed.getExtensions().getUnknownXMLObjects(EntityAttributes.DEFAULT_ELEMENT_NAME).size() == 1) { // we have entity attributes (hopefully), so should have overrides - RelyingPartyOverridesRepresentation relyingPartyOverridesRepresentation = new RelyingPartyOverridesRepresentation(); - representation.setRelyingPartyOverrides(relyingPartyOverridesRepresentation); + Map relyingPartyOverrides = new HashMap<>(); for (org.opensaml.saml.saml2.core.Attribute attribute : ((EntityAttributes) ed.getExtensions().getUnknownXMLObjects(EntityAttributes.DEFAULT_ELEMENT_NAME).get(0)).getAttributes()) { Attribute jpaAttribute = (Attribute) attribute; - // TODO: this is going to get real ugly real quick. clean it up, future Jj! - switch (jpaAttribute.getName()) { - case MDDCConstants.SIGN_ASSERTIONS: - relyingPartyOverridesRepresentation.setSignAssertion(getBooleanValueOfAttribute(jpaAttribute)); - break; - case MDDCConstants.SIGN_RESPONSES: - relyingPartyOverridesRepresentation.setDontSignResponse(!getBooleanValueOfAttribute(jpaAttribute)); - break; - case MDDCConstants.ENCRYPT_ASSERTIONS: - relyingPartyOverridesRepresentation.setTurnOffEncryption(!getBooleanValueOfAttribute(jpaAttribute)); - break; - case MDDCConstants.SECURITY_CONFIGURATION: - if (getStringListValueOfAttribute(jpaAttribute).contains("shibboleth.SecurityConfiguration.SHA1")) { - relyingPartyOverridesRepresentation.setUseSha(true); - } - break; - case MDDCConstants.DISALLOWED_FEATURES: - if ((Integer.decode(getStringListValueOfAttribute(jpaAttribute).get(0)) & 0x1) == 0x1) { - relyingPartyOverridesRepresentation.setIgnoreAuthenticationMethod(true); + + if (jpaAttribute.getName().equals(MDDCConstants.RELEASE_ATTRIBUTES)) { + representation.setAttributeRelease(getStringListOfAttributeValues(attribute.getAttributeValues())); + } else { + Optional override = ModelRepresentationConversions.getOverrideByAttributeName(jpaAttribute.getName()); + if (override.isPresent()) { + RelyingPartyOverrideProperty overrideProperty = (RelyingPartyOverrideProperty)override.get(); + Object attributeValues = null; + switch (ModelRepresentationConversions.AttributeTypes.valueOf(overrideProperty.getDisplayType().toUpperCase())) { + case STRING: + if (jpaAttribute.getAttributeValues().size() != 1) { + throw new RuntimeException("Multiple/No values detected where one is expected!"); + } + attributeValues = getValueFromXSStringOrXSAny(jpaAttribute.getAttributeValues().get(0)); + break; + case INTEGER: + if (jpaAttribute.getAttributeValues().size() != 1) { + throw new RuntimeException("Multiple/No values detected where one is expected!"); + } + attributeValues = ((XSInteger)jpaAttribute.getAttributeValues().get(0)).getValue(); + break; + case BOOLEAN: + if (jpaAttribute.getAttributeValues().size() != 1) { + throw new RuntimeException("Multiple/No values detected where one is expected!"); + } + if (overrideProperty.getPersistType() != null && + !overrideProperty.getPersistType().equals(overrideProperty.getDisplayType())) { + attributeValues = getValueFromXSStringOrXSAny(jpaAttribute.getAttributeValues().get(0)); + } else { + attributeValues = Boolean.valueOf(((XSBoolean) jpaAttribute.getAttributeValues() + .get(0)).getStoredValue()); + } + break; + case SET: + case LIST: + attributeValues = jpaAttribute.getAttributeValues().stream() + .map(attributeValue -> getValueFromXSStringOrXSAny(attributeValue)) + .collect(Collectors.toList()); } - break; - case MDDCConstants.INCLUDE_CONDITIONS_NOT_BEFORE: - relyingPartyOverridesRepresentation.setOmitNotBefore(!getBooleanValueOfAttribute(jpaAttribute)); - break; - case MDDCConstants.RESPONDER_ID: - relyingPartyOverridesRepresentation.setResponderId(getStringListValueOfAttribute(jpaAttribute).get(0)); - break; - case MDDCConstants.NAME_ID_FORMAT_PRECEDENCE: - relyingPartyOverridesRepresentation.setNameIdFormats(getStringListValueOfAttribute(jpaAttribute)); - break; - case MDDCConstants.DEFAULT_AUTHENTICATION_METHODS: - relyingPartyOverridesRepresentation.setAuthenticationMethods(getStringListValueOfAttribute(jpaAttribute)); - break; - case MDDCConstants.RELEASE_ATTRIBUTES: - representation.setAttributeRelease(getStringListOfAttributeValues(attribute.getAttributeValues())); - break; - case MDDCConstants.FORCE_AUTHN: - relyingPartyOverridesRepresentation.setForceAuthn(getBooleanValueOfAttribute(jpaAttribute)); - break; - default: - break; + relyingPartyOverrides.put(((RelyingPartyOverrideProperty) override.get()).getName(), attributeValues); + } } } + + representation.setRelyingPartyOverrides(relyingPartyOverrides); } return representation; } + private String getValueFromXSStringOrXSAny(XMLObject xmlObject) { + if (xmlObject instanceof XSAny) { + return ((XSAny)xmlObject).getTextContent(); + } else { + return ((XSString)xmlObject).getValue(); + } + } + @Override public List getAttributeReleaseListFromAttributeList(List attributeList) { return ModelRepresentationConversions.getAttributeReleaseListFromAttributeList(attributeList); } @Override - public RelyingPartyOverridesRepresentation getRelyingPartyOverridesRepresentationFromAttributeList(List attributeList) { + public Map getRelyingPartyOverridesRepresentationFromAttributeList(List attributeList) { return ModelRepresentationConversions.getRelyingPartyOverridesRepresentationFromAttributeList(attributeList); } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityServiceImpl.java index e8b4ce590..31a8b28ae 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityServiceImpl.java @@ -1,18 +1,21 @@ package edu.internet2.tier.shibboleth.admin.ui.service; +import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration; import edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBuilder; import edu.internet2.tier.shibboleth.admin.ui.domain.AttributeValue; +import edu.internet2.tier.shibboleth.admin.ui.domain.RelyingPartyOverrideProperty; import edu.internet2.tier.shibboleth.admin.ui.domain.XSString; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation; -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.RelyingPartyOverridesRepresentation; import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects; import edu.internet2.tier.shibboleth.admin.util.AttributeUtility; import edu.internet2.tier.shibboleth.admin.util.MDDCConstants; +import edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions; import org.opensaml.saml.saml2.core.Attribute; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.List; +import java.util.Map; public class JPAEntityServiceImpl implements EntityService { @@ -22,6 +25,9 @@ public class JPAEntityServiceImpl implements EntityService { @Autowired private AttributeUtility attributeUtility; + @Autowired + private CustomPropertiesConfiguration customPropertiesConfiguration; + public JPAEntityServiceImpl(OpenSamlObjects openSamlObjects) { this.openSamlObjects = openSamlObjects; } @@ -31,6 +37,14 @@ public JPAEntityServiceImpl(OpenSamlObjects openSamlObjects, AttributeUtility at this.attributeUtility = attributeUtility; } + public JPAEntityServiceImpl(OpenSamlObjects openSamlObjects, + AttributeUtility attributeUtility, + CustomPropertiesConfiguration customPropertiesConfiguration) { + this.openSamlObjects = openSamlObjects; + this.attributeUtility = attributeUtility; + this.customPropertiesConfiguration = customPropertiesConfiguration; + } + @Override public List getAttributeListFromEntityRepresentation(EntityDescriptorRepresentation entityDescriptorRepresentation) { List list = new ArrayList<>(); @@ -81,44 +95,51 @@ public List getAttributeListFromAttributeReleaseList(List att } @Override - public List getAttributeListFromRelyingPartyOverridesRepresentation(RelyingPartyOverridesRepresentation relyingPartyOverridesRepresentation) { + public List getAttributeListFromRelyingPartyOverridesRepresentation(Map relyingPartyOverridesRepresentation) { + List overridePropertyList = customPropertiesConfiguration.getOverrides(); List list = new ArrayList<>(); - if (relyingPartyOverridesRepresentation != null) { - if (relyingPartyOverridesRepresentation.isSignAssertion()) { - list.add(attributeUtility.createAttributeWithBooleanValue(MDDCConstants.SIGN_ASSERTIONS, MDDCConstants.SIGN_ASSERTIONS_FN, true)); - } - if (relyingPartyOverridesRepresentation.isDontSignResponse()) { - list.add(attributeUtility.createAttributeWithBooleanValue(MDDCConstants.SIGN_RESPONSES, MDDCConstants.SIGN_RESPONSES_FN, false)); - } - if (relyingPartyOverridesRepresentation.isTurnOffEncryption()) { - list.add(attributeUtility.createAttributeWithBooleanValue(MDDCConstants.ENCRYPT_ASSERTIONS, MDDCConstants.ENCRYPT_ASSERTIONS_FN, false)); - } - if (relyingPartyOverridesRepresentation.isUseSha()) { - list.add(attributeUtility.createAttributeWithArbitraryValues(MDDCConstants.SECURITY_CONFIGURATION, MDDCConstants.SECURITY_CONFIGURATION_FN, "shibboleth.SecurityConfiguration.SHA1")); - } - if (relyingPartyOverridesRepresentation.isIgnoreAuthenticationMethod()) { - // this is actually going to be wrong, but it will work for the time being. this should be a bitmask value that we calculate - // TODO: fix - list.add(attributeUtility.createAttributeWithArbitraryValues(MDDCConstants.DISALLOWED_FEATURES, MDDCConstants.DISALLOWED_FEATURES_FN, "0x1")); - } - if (relyingPartyOverridesRepresentation.isOmitNotBefore()) { - list.add(attributeUtility.createAttributeWithBooleanValue(MDDCConstants.INCLUDE_CONDITIONS_NOT_BEFORE, MDDCConstants.INCLUDE_CONDITIONS_NOT_BEFORE_FN, false)); - } - if (relyingPartyOverridesRepresentation.getResponderId() != null && !"".equals(relyingPartyOverridesRepresentation.getResponderId())) { - list.add(attributeUtility.createAttributeWithArbitraryValues(MDDCConstants.RESPONDER_ID, MDDCConstants.RESPONDER_ID_FN, relyingPartyOverridesRepresentation.getResponderId())); - } - if (relyingPartyOverridesRepresentation.getNameIdFormats() != null && relyingPartyOverridesRepresentation.getNameIdFormats().size() > 0) { - list.add(attributeUtility.createAttributeWithArbitraryValues(MDDCConstants.NAME_ID_FORMAT_PRECEDENCE, MDDCConstants.NAME_ID_FORMAT_PRECEDENCE_FN, relyingPartyOverridesRepresentation.getNameIdFormats())); - } - if (relyingPartyOverridesRepresentation.getAuthenticationMethods() != null && relyingPartyOverridesRepresentation.getAuthenticationMethods().size() > 0) { - list.add(attributeUtility.createAttributeWithArbitraryValues(MDDCConstants.DEFAULT_AUTHENTICATION_METHODS, MDDCConstants.DEFAULT_AUTHENTICATION_METHODS_FN, relyingPartyOverridesRepresentation.getAuthenticationMethods())); - } - if (relyingPartyOverridesRepresentation.isForceAuthn()) { - list.add(attributeUtility.createAttributeWithBooleanValue(MDDCConstants.FORCE_AUTHN, MDDCConstants.FORCE_AUTHN_FN, true)); + for (Map.Entry entry : relyingPartyOverridesRepresentation.entrySet()) { + String key = (String) entry.getKey(); + RelyingPartyOverrideProperty overrideProperty = overridePropertyList.stream().filter(op -> op.getName().equals(key)).findFirst().get(); + switch (ModelRepresentationConversions.AttributeTypes.valueOf(overrideProperty.getDisplayType().toUpperCase())) { + case BOOLEAN: + if (overrideProperty.getPersistType() != null && + !overrideProperty.getPersistType().equalsIgnoreCase("boolean")) { + list.add(attributeUtility.createAttributeWithStringValues(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + (String) entry.getValue())); + } else { + list.add(attributeUtility.createAttributeWithBooleanValue(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + (Boolean) entry.getValue())); + } + break; + case INTEGER: + list.add(attributeUtility.createAttributeWithIntegerValue(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + Integer.valueOf((String) entry.getValue()))); + break; + case STRING: + list.add(attributeUtility.createAttributeWithStringValues(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + (String) entry.getValue())); + break; + case SET: + list.add(attributeUtility.createAttributeWithStringValues(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + (List) entry.getValue())); + break; + case LIST: + list.add(attributeUtility.createAttributeWithStringValues(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + (List) entry.getValue())); + break; + default: + throw new UnsupportedOperationException("getAttributeListFromRelyingPartyOverridesRepresentation was called with an unsupported type (" + overrideProperty.getDisplayType() + ")!"); } } - return (List)(List)list; + return (List) (List) list; } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/AttributeUtility.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/AttributeUtility.java index 2282b04e2..20fca363c 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/AttributeUtility.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/AttributeUtility.java @@ -3,12 +3,13 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.AttributeValue; import edu.internet2.tier.shibboleth.admin.ui.domain.XSAny; import edu.internet2.tier.shibboleth.admin.ui.domain.XSBoolean; +import edu.internet2.tier.shibboleth.admin.ui.domain.XSInteger; import edu.internet2.tier.shibboleth.admin.ui.domain.XSString; import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects; import org.opensaml.core.xml.schema.XSBooleanValue; -import org.springframework.beans.factory.annotation.Autowired; import java.util.List; +import java.util.Set; /** * @author Bill Smith (wsmith@unicon.net) @@ -17,54 +18,66 @@ public class AttributeUtility { private OpenSamlObjects openSamlObjects; + private static final String URI = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"; + public AttributeUtility(OpenSamlObjects openSamlObjects) { this.openSamlObjects = openSamlObjects; } public edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createAttributeWithBooleanValue(String name, String friendlyName, Boolean value) { - edu.internet2.tier.shibboleth.admin.ui.domain.Attribute attribute = ((edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBuilder) openSamlObjects.getBuilderFactory().getBuilder(edu.internet2.tier.shibboleth.admin.ui.domain.Attribute.DEFAULT_ELEMENT_NAME)).buildObject(); - attribute.setName(name); - attribute.setFriendlyName(friendlyName); - attribute.setNameFormat("urn:oasis:names:tc:SAML:2.0:attrname-format:uri"); + edu.internet2.tier.shibboleth.admin.ui.domain.Attribute attribute = createNewAttribute(name, friendlyName); XSBoolean xsBoolean = (XSBoolean) openSamlObjects.getBuilderFactory().getBuilder(XSBoolean.TYPE_NAME).buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSBoolean.TYPE_NAME); xsBoolean.setValue(XSBooleanValue.valueOf(value.toString())); - attribute.getAttributeValues().add(xsBoolean); + return attribute; } - public edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createAttributeWithStringValues(String name, List values) { - edu.internet2.tier.shibboleth.admin.ui.domain.Attribute attribute = ((edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBuilder) openSamlObjects.getBuilderFactory() - .getBuilder(edu.internet2.tier.shibboleth.admin.ui.domain.Attribute.DEFAULT_ELEMENT_NAME)).buildObject(); - attribute.setName(name); - //TODO: Do we need a friendlyName? - //TODO: Do we need a NameFormat? + public edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createAttributeWithIntegerValue(String name, String friendlyName, Integer value) { + edu.internet2.tier.shibboleth.admin.ui.domain.Attribute attribute = createNewAttribute(name, friendlyName); + + XSInteger xsInteger = (XSInteger) openSamlObjects.getBuilderFactory().getBuilder(XSInteger.TYPE_NAME).buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSInteger.TYPE_NAME); + xsInteger.setValue(value); + attribute.getAttributeValues().add(xsInteger); - values.forEach(attributeString -> { - XSString xsString = (XSString) openSamlObjects.getBuilderFactory().getBuilder(XSString.TYPE_NAME).buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); - xsString.setValue(attributeString); - attribute.getAttributeValues().add(xsString); - }); return attribute; } - public edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createAttributeWithArbitraryValues(String name, String friendlyName, String... values) { - edu.internet2.tier.shibboleth.admin.ui.domain.Attribute attribute = ((edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBuilder) openSamlObjects.getBuilderFactory().getBuilder(edu.internet2.tier.shibboleth.admin.ui.domain.Attribute.DEFAULT_ELEMENT_NAME)).buildObject(); - attribute.setName(name); - attribute.setFriendlyName(friendlyName); - attribute.setNameFormat("urn:oasis:names:tc:SAML:2.0:attrname-format:uri"); + public edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createAttributeWithStringValues(String name, String friendlyName, String... values) { + edu.internet2.tier.shibboleth.admin.ui.domain.Attribute attribute = createNewAttribute(name, friendlyName); for (String value : values) { - XSAny xsAny = (XSAny) openSamlObjects.getBuilderFactory().getBuilder(XSAny.TYPE_NAME).buildObject(AttributeValue.DEFAULT_ELEMENT_NAME); - xsAny.setTextContent(value); - attribute.getAttributeValues().add(xsAny); + XSString xsString = (XSString) openSamlObjects.getBuilderFactory().getBuilder(XSString.TYPE_NAME).buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); + xsString.setValue(value); + attribute.getAttributeValues().add(xsString); } return attribute; } - public edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createAttributeWithArbitraryValues(String name, String friendlyName, List values) { - return createAttributeWithArbitraryValues(name, friendlyName, values.toArray(new String[]{})); + /* + * Provided for calling with name = MDDCConstants.RELEASE_ATTRIBUTES. + */ + public edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createAttributeWithStringValues(String name, List values) { + return createAttributeWithStringValues(name, null, values); + } + + public edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createAttributeWithStringValues(String name, String friendlyName, List values) { + return createAttributeWithStringValues(name, friendlyName, values.toArray(new String[]{})); + } + + /* Calling this method with name = MDDCConstants.RELEASE_ATTRIBUTES seems to be a special case. In this case, + * we haven't been setting the friendlyName or nameFormat. Hence the null check. + */ + private edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createNewAttribute(String name, String friendlyName) { + edu.internet2.tier.shibboleth.admin.ui.domain.Attribute attribute = ((edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBuilder) openSamlObjects.getBuilderFactory() + .getBuilder(edu.internet2.tier.shibboleth.admin.ui.domain.Attribute.DEFAULT_ELEMENT_NAME)).buildObject(); + attribute.setName(name); + if (friendlyName != null) { + attribute.setFriendlyName(friendlyName); + attribute.setNameFormat(URI); + } + return attribute; } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/ModelRepresentationConversions.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/ModelRepresentationConversions.java index d2fbda436..f7279179c 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/ModelRepresentationConversions.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/ModelRepresentationConversions.java @@ -1,23 +1,40 @@ package edu.internet2.tier.shibboleth.admin.util; +import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration; import edu.internet2.tier.shibboleth.admin.ui.domain.Attribute; +import edu.internet2.tier.shibboleth.admin.ui.domain.RelyingPartyOverrideProperty; +import edu.internet2.tier.shibboleth.admin.ui.domain.XSAny; import edu.internet2.tier.shibboleth.admin.ui.domain.XSBoolean; -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.RelyingPartyOverridesRepresentation; +import edu.internet2.tier.shibboleth.admin.ui.domain.XSInteger; +import edu.internet2.tier.shibboleth.admin.ui.domain.XSString; import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import org.opensaml.core.xml.XMLObject; +import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; /** * Utility class to deal with model conversions related functionality */ -public abstract class ModelRepresentationConversions { +public class ModelRepresentationConversions { private static final AttributeUtility ATTRIBUTE_UTILITY; + private static CustomPropertiesConfiguration customPropertiesConfiguration; + + @Autowired + public ModelRepresentationConversions(CustomPropertiesConfiguration customPropertiesConfiguration) { + ModelRepresentationConversions.customPropertiesConfiguration = customPropertiesConfiguration; + } + static { OpenSamlObjects openSamlObjects = new OpenSamlObjects(); try { @@ -62,53 +79,53 @@ public static List getStringListValueOfAttribute(Attribute attribute) { return getStringListOfAttributeValues(attribute.getAttributeValues()); } - public static RelyingPartyOverridesRepresentation getRelyingPartyOverridesRepresentationFromAttributeList(List attributeList) { - RelyingPartyOverridesRepresentation relyingPartyOverridesRepresentation = new RelyingPartyOverridesRepresentation(); + public static Optional getOverrideByAttributeName(String attributeName) { + return customPropertiesConfiguration.getOverrides().stream().filter(it -> it.getAttributeName().equals(attributeName)).findFirst(); + } + + public static Map getRelyingPartyOverridesRepresentationFromAttributeList(List attributeList) { + Map relyingPartyOverrides = new HashMap<>(); for (org.opensaml.saml.saml2.core.Attribute attribute : attributeList) { Attribute jpaAttribute = (Attribute) attribute; - // TODO: this is going to get real ugly real quick. clean it up, future Jj! - switch (jpaAttribute.getName()) { - case MDDCConstants.SIGN_ASSERTIONS: - relyingPartyOverridesRepresentation.setSignAssertion(getBooleanValueOfAttribute(jpaAttribute)); - break; - case MDDCConstants.SIGN_RESPONSES: - relyingPartyOverridesRepresentation.setDontSignResponse(!getBooleanValueOfAttribute(jpaAttribute)); - break; - case MDDCConstants.ENCRYPT_ASSERTIONS: - relyingPartyOverridesRepresentation.setTurnOffEncryption(!getBooleanValueOfAttribute(jpaAttribute)); - break; - case MDDCConstants.SECURITY_CONFIGURATION: - if (getStringListValueOfAttribute(jpaAttribute).contains("shibboleth.SecurityConfiguration.SHA1")) { - relyingPartyOverridesRepresentation.setUseSha(true); - } - break; - case MDDCConstants.DISALLOWED_FEATURES: - if ((Integer.decode(getStringListValueOfAttribute(jpaAttribute).get(0)) & 0x1) == 0x1) { - relyingPartyOverridesRepresentation.setIgnoreAuthenticationMethod(true); - } - break; - case MDDCConstants.INCLUDE_CONDITIONS_NOT_BEFORE: - relyingPartyOverridesRepresentation.setOmitNotBefore(!getBooleanValueOfAttribute(jpaAttribute)); - break; - case MDDCConstants.RESPONDER_ID: - relyingPartyOverridesRepresentation.setResponderId(getStringListValueOfAttribute(jpaAttribute).get(0)); - break; - case MDDCConstants.NAME_ID_FORMAT_PRECEDENCE: - relyingPartyOverridesRepresentation.setNameIdFormats(getStringListValueOfAttribute(jpaAttribute)); - break; - case MDDCConstants.DEFAULT_AUTHENTICATION_METHODS: - relyingPartyOverridesRepresentation.setAuthenticationMethods(getStringListValueOfAttribute(jpaAttribute)); - break; - case MDDCConstants.FORCE_AUTHN: - relyingPartyOverridesRepresentation.setForceAuthn(getBooleanValueOfAttribute(jpaAttribute)); - break; - default: - break; + + Optional override = getOverrideByAttributeName(jpaAttribute.getName()); + if (override.isPresent()) { + relyingPartyOverrides.put(((RelyingPartyOverrideProperty)override.get()).getName(), + getOverrideFromAttribute(jpaAttribute)); } } - return relyingPartyOverridesRepresentation; + return relyingPartyOverrides; + } + + public static Object getOverrideFromAttribute(Attribute attribute) { + RelyingPartyOverrideProperty relyingPartyOverrideProperty = customPropertiesConfiguration.getOverrides().stream() + .filter(it -> it.getAttributeFriendlyName().equals(attribute.getFriendlyName())).findFirst().get(); + + List attributeValues = attribute.getAttributeValues(); + switch(AttributeTypes.valueOf(relyingPartyOverrideProperty.getDisplayType().toUpperCase())) { + case BOOLEAN: + if (relyingPartyOverrideProperty.getPersistType() != null + && (!relyingPartyOverrideProperty.getPersistType().equalsIgnoreCase("boolean"))) { + return true; + } else { + return Boolean.valueOf(((XSBoolean) attributeValues.get(0)).getStoredValue()); + } + case INTEGER: + return ((XSInteger) attributeValues.get(0)).getValue(); + case STRING: + if (attributeValues.get(0) instanceof XSAny) { + return ((XSAny) attributeValues.get(0)).getTextContent(); + } else { + return ((XSString) attributeValues.get(0)).getValue(); + } + case LIST: + case SET: + return attributeValues.stream().map(it -> ((XSString) it).getValue()).collect(Collectors.toList()); + default: + throw new UnsupportedOperationException("An unsupported persist type was specified (" + relyingPartyOverrideProperty.getPersistType() + ")!"); + } } public static List getAttributeListFromAttributeReleaseList(List attributeReleaseList) { @@ -122,50 +139,76 @@ public static List getAttributeListFromA } public static List getAttributeListFromRelyingPartyOverridesRepresentation - (RelyingPartyOverridesRepresentation relyingPartyOverridesRepresentation) { + (Map relyingPartyOverridesRepresentation) { + List overridePropertyList = customPropertiesConfiguration.getOverrides(); List list = new ArrayList<>(); if (relyingPartyOverridesRepresentation != null) { - if (relyingPartyOverridesRepresentation.isSignAssertion()) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithBooleanValue(MDDCConstants.SIGN_ASSERTIONS, MDDCConstants.SIGN_ASSERTIONS_FN, true)); - } - if (relyingPartyOverridesRepresentation.isDontSignResponse()) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithBooleanValue(MDDCConstants.SIGN_RESPONSES, MDDCConstants.SIGN_RESPONSES_FN, false)); - } - if (relyingPartyOverridesRepresentation.isTurnOffEncryption()) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithBooleanValue(MDDCConstants.ENCRYPT_ASSERTIONS, MDDCConstants.ENCRYPT_ASSERTIONS_FN, false)); - } - if (relyingPartyOverridesRepresentation.isUseSha()) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithArbitraryValues(MDDCConstants.SECURITY_CONFIGURATION, MDDCConstants - .SECURITY_CONFIGURATION_FN, "shibboleth.SecurityConfiguration.SHA1")); - } - if (relyingPartyOverridesRepresentation.isIgnoreAuthenticationMethod()) { - // this is actually going to be wrong, but it will work for the time being. this should be a bitmask value that we calculate - // TODO: fix - list.add(ATTRIBUTE_UTILITY.createAttributeWithArbitraryValues(MDDCConstants.DISALLOWED_FEATURES, MDDCConstants.DISALLOWED_FEATURES_FN, - "0x1")); - } - if (relyingPartyOverridesRepresentation.isOmitNotBefore()) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithBooleanValue(MDDCConstants.INCLUDE_CONDITIONS_NOT_BEFORE, MDDCConstants - .INCLUDE_CONDITIONS_NOT_BEFORE_FN, false)); - } - if (relyingPartyOverridesRepresentation.getResponderId() != null && !"".equals(relyingPartyOverridesRepresentation.getResponderId())) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithArbitraryValues(MDDCConstants.RESPONDER_ID, MDDCConstants.RESPONDER_ID_FN, - relyingPartyOverridesRepresentation.getResponderId())); - } - if (relyingPartyOverridesRepresentation.getNameIdFormats() != null && relyingPartyOverridesRepresentation.getNameIdFormats().size() > 0) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithArbitraryValues(MDDCConstants.NAME_ID_FORMAT_PRECEDENCE, MDDCConstants - .NAME_ID_FORMAT_PRECEDENCE_FN, relyingPartyOverridesRepresentation.getNameIdFormats())); - } - if (relyingPartyOverridesRepresentation.getAuthenticationMethods() != null && relyingPartyOverridesRepresentation.getAuthenticationMethods().size() > 0) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithArbitraryValues(MDDCConstants.DEFAULT_AUTHENTICATION_METHODS, MDDCConstants - .DEFAULT_AUTHENTICATION_METHODS_FN, relyingPartyOverridesRepresentation.getAuthenticationMethods())); - } - if (relyingPartyOverridesRepresentation.isForceAuthn()) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithBooleanValue(MDDCConstants.FORCE_AUTHN, MDDCConstants.FORCE_AUTHN_FN, true)); + for (Map.Entry entry : relyingPartyOverridesRepresentation.entrySet()) { + String key = (String) entry.getKey(); + RelyingPartyOverrideProperty overrideProperty = overridePropertyList.stream().filter(op -> op.getName().equals(key)).findFirst().get(); + switch (AttributeTypes.valueOf(overrideProperty.getDisplayType().toUpperCase())) { + case BOOLEAN: + if (overrideProperty.getPersistType() != null && + !overrideProperty.getPersistType().equals(overrideProperty.getDisplayType())) { + list.add(ATTRIBUTE_UTILITY.createAttributeWithStringValues(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + overrideProperty.getPersistValue())); + } else { + list.add(ATTRIBUTE_UTILITY.createAttributeWithBooleanValue(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + (Boolean) entry.getValue())); + } + break; + case INTEGER: + list.add(ATTRIBUTE_UTILITY.createAttributeWithIntegerValue(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + (Integer) entry.getValue())); + break; + case STRING: + list.add(ATTRIBUTE_UTILITY.createAttributeWithStringValues(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + (String) entry.getValue())); + break; + case SET: + Set setValues; + if (entry.getValue() instanceof Set) { + setValues = (Set) entry.getValue(); + } else if (entry.getValue() instanceof List) { + setValues = new HashSet<>(); + setValues.addAll((List) entry.getValue()); + } else { + throw new UnsupportedOperationException("The collection passed from the UI is neither a Set or List. This shouldn't happen. Fix this!"); + } + if (setValues.size() > 0) { + list.add(ATTRIBUTE_UTILITY.createAttributeWithStringValues(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + new ArrayList<>(setValues))); + } + break; + case LIST: + List listValues = (List) entry.getValue(); + if (listValues.size() > 0) { + list.add(ATTRIBUTE_UTILITY.createAttributeWithStringValues(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + listValues)); + } + break; + default: + throw new UnsupportedOperationException("getAttributeListFromRelyingPartyOverridesRepresentation was called with an unsupported type (" + overrideProperty.getDisplayType() + ")!"); + } } } return (List) (List) list; } + + + public enum AttributeTypes { + BOOLEAN, + INTEGER, + STRING, + SET, + LIST + } } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index c4423a063..9907c0ff9 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -51,6 +51,7 @@ shibui.logout-url=/dashboard #shibui.default-password= shibui.metadata-sources-ui-schema-location=classpath:metadata-sources-ui-schema.json +shibui.entity-attributes-filters-ui-schema-location=classpath:entity-attributes-filters-ui-schema.json #Actuator endpoints (info) # Un-comment to get full git details exposed like author, abbreviated SHA-1, commit message diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 563d01073..12bfd55cc 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -26,3 +26,105 @@ custom: - name: employeeNumber displayName: label.attribute-employeeNumber # Custom attributes + +# The following contains a map of "relying party overrides". +# The structure of an entry is as follows: +# - name: The name of the entry. used to uniquely identify this entry. +# displayName: This will normally be the label used when displaying this override in the UI +# displayType: The type to use when displaying this option +# helpText: This is the help-icon hover-over text +# defaultValues: One or more values to be displayed as default options in the UI +# persistType: Optional. If it is necessary to persist something different than the override's display type, +# set that type here. For example, display a boolean, but persist a string. +# persistValue: Required only when persistType is used. Defines the value to be persisted. +# attributeName: This is the name of the attribute to be used in the xml. This is assumed to be a URI. +# attributeFriendlyName: This is the friendly name associated with the above attributeName. +# +# It is imperative when defining these that the "displayType" and "persistType" are known types. +# Typos or unsupported values here will result in that override being skipped! +# Supported types are as follows: boolean, integer, string, set, list +# Note that "persistType" doesn't have to match "displayType". However, the only unmatching combination currently +# supported is a "displayType" of "boolean" and "persistType" of "string". + overrides: + # Default overrides + - name: signAssertion + displayName: label.sign-the-assertion + displayType: boolean + defaultValue: false + helpText: tooltip.sign-assertion + attributeName: http://shibboleth.net/ns/profiles/saml2/sso/browser/signAssertions + attributeFriendlyName: signAssertions + - name: dontSignResponse + displayName: label.dont-sign-the-response + displayType: boolean + defaultValue: false + helpText: tooltip.dont-sign-response + attributeName: http://shibboleth.net/ns/profiles/saml2/sso/browser/signResponses + attributeFriendlyName: signResponses + - name: turnOffEncryption + displayName: label.turn-off-encryption-of-response + displayType: boolean + defaultValue: false + helpText: tooltip.turn-off-encryption + attributeName: http://shibboleth.net/ns/profiles/encryptAssertions + attributeFriendlyName: encryptAssertions + - name: useSha + displayName: label.use-sha1-signing-algorithm + displayType: boolean + defaultValue: false + helpText: tooltip.usa-sha-algorithm + persistType: string + persistValue: shibboleth.SecurityConfiguration.SHA1 + attributeName: http://shibboleth.net/ns/profiles/securityConfiguration + attributeFriendlyName: securityConfiguration + - name: ignoreAuthenticationMethod + displayName: label.ignore-any-sp-requested-authentication-method + displayType: boolean + defaultValue: false + helpText: tooltip.ignore-auth-method + persistType: string + persistValue: 0x1 + attributeName: http://shibboleth.net/ns/profiles/disallowedFeatures + attributeFriendlyName: disallowedFeatures + - name: omitNotBefore + displayName: label.omit-not-before-condition + displayType: boolean + defaultValue: false + helpText: tooltip.omit-not-before-condition + attributeName: http://shibboleth.net/ns/profiles/includeConditionsNotBefore + attributeFriendlyName: includeConditionsNotBefore + - name: responderId + displayName: label.responder-id + displayType: string + defaultValue: null + helpText: tooltip.responder-id + attributeName: http://shibboleth.net/ns/profiles/responderId + attributeFriendlyName: responderId + - name: nameIdFormats + displayName: label.nameid-format-to-send + displayType: set + helpText: tooltip.nameid-format + defaultValues: + - urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + - urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + - urn:oasis:names:tc:SAML:2.0:nameid-format:transient + attributeName: http://shibboleth.net/ns/profiles/nameIDFormatPrecedence + attributeFriendlyName: nameIDFormatPrecedence + - name: authenticationMethods + displayName: label.authentication-methods-to-use + displayType: set + helpText: tooltip.authentication-methods-to-use + defaultValues: + - https://refeds.org/profile/mfa + - urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken + - urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + attributeName: http://shibboleth.net/ns/profiles/defaultAuthenticationMethods + attributeFriendlyName: defaultAuthenticationMethods + - name: forceAuthn + displayName: label.force-authn + displayType: boolean + defaultValue: false + helpText: tooltip.force-authn + attributeName: http://shibboleth.net/ns/profiles/forceAuthn + attributeFriendlyName: forceAuthn \ No newline at end of file diff --git a/backend/src/main/resources/entity-attributes-filters-ui-schema.json b/backend/src/main/resources/entity-attributes-filters-ui-schema.json new file mode 100644 index 000000000..5d71f0ba8 --- /dev/null +++ b/backend/src/main/resources/entity-attributes-filters-ui-schema.json @@ -0,0 +1,228 @@ +{ + "title": "EntityAttributes Filter", + "type": "object", + "widget": { + "id": "fieldset" + }, + "properties": { + "name": { + "title": "label.filter-name", + "description": "tooltip.filter-name", + "type": "string", + "widget": { + "id": "string", + "help": "message.must-be-unique" + } + }, + "@type": { + "type": "string", + "widget": { + "id": "hidden" + }, + "default": "EntityAttributes" + }, + "resourceId": { + "type": "string", + "widget": { + "id": "hidden" + } + }, + "version": { + "type": "integer", + "widget": { + "id": "hidden" + } + }, + "filterEnabled": { + "title": "label.enable-filter", + "description": "tooltip.enable-filter", + "type": "boolean", + "default": false + }, + "entityAttributesFilterTarget": { + "title": "label.search-criteria", + "description": "tooltip.search-criteria", + "type": "object", + "widget": { + "id": "filter-target" + }, + "properties": { + "entityAttributesFilterTargetType": { + "title": "", + "type": "string", + "default": "ENTITY", + "oneOf": [ + { + "enum": [ + "ENTITY" + ], + "description": "value.entity-id" + }, + { + "enum": [ + "REGEX" + ], + "description": "value.regex" + }, + { + "enum": [ + "CONDITION_SCRIPT" + ], + "description": "value.script" + } + ] + }, + "value": { + "type": "array", + "buttons": [ + { + "id": "preview", + "label": "action.preview", + "widget": "icon-button" + } + ], + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string" + } + } + }, + "required": ["value", "entityAttributesFilterTargetType"] + }, + "relyingPartyOverrides": { + "type": "object", + "properties": { + "signAssertion": { + "title": "label.sign-the-assertion", + "description": "tooltip.sign-assertion", + "type": "boolean", + "default": false + }, + "dontSignResponse": { + "title": "label.dont-sign-the-response", + "description": "tooltip.dont-sign-response", + "type": "boolean", + "default": false + }, + "turnOffEncryption": { + "title": "label.turn-off-encryption-of-response", + "description": "tooltip.turn-off-encryption", + "type": "boolean", + "default": false + }, + "useSha": { + "title": "label.use-sha1-signing-algorithm", + "description": "tooltip.usa-sha-algorithm", + "type": "boolean", + "default": false + }, + "ignoreAuthenticationMethod": { + "title": "label.ignore-any-sp-requested-authentication-method", + "description": "tooltip.ignore-auth-method", + "type": "boolean", + "default": false + }, + "forceAuthn": { + "title": "label.force-authn", + "description": "tooltip.force-authn", + "type": "boolean", + "default": false + }, + "omitNotBefore": { + "title": "label.omit-not-before-condition", + "type": "boolean", + "description": "tooltip.omit-not-before-condition", + "default": false + }, + "responderId": { + "title": "label.responder-id", + "description": "tooltip.responder-id", + "type": "string" + }, + "nameIdFormats": { + "title": "label.nameid-format-to-send", + "placeholder": "label.nameid-format", + "description": "tooltip.nameid-format", + "type": "array", + "uniqueItems": true, + "items": { + "title": "label.nameid-format", + "type": "string", + "widget": { + "id": "datalist", + "data": [ + "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", + "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", + "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", + "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" + ] + } + }, + "default": null + }, + "authenticationMethods": { + "title": "label.authentication-methods-to-use", + "description": "tooltip.authentication-methods-to-use", + "type": "array", + "placeholder": "label.authentication-method", + "uniqueItems": true, + "items": { + "type": "string", + "title": "label.authentication-method", + "widget": { + "id": "datalist", + "data": [ + "https://refeds.org/profile/mfa", + "urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken", + "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" + ] + } + }, + "default": null + } + } + }, + "attributeRelease": { + "type": "array", + "description": "Attribute release table - select the attributes you want to release (default unchecked)", + "widget": { + "id": "checklist", + "dataUrl": "/customAttributes" + }, + "items": { + "type": "string" + } + } + }, + "required": [ + "name" + ], + "fieldsets": [ + { + "type": "group-lg", + "fields": [ + "name", + "@type", + "resourceId", + "version", + "entityAttributesFilterTarget" + ] + }, + { + "type": "group", + "fields": [ + "filterEnabled", + "relyingPartyOverrides" + ] + }, + { + "type": "group", + "fields": [ + "attributeRelease" + ] + } + ], + "definitions": { + } +} \ No newline at end of file diff --git a/backend/src/main/resources/metadata-sources-ui-schema.json b/backend/src/main/resources/metadata-sources-ui-schema.json index e2a2298dd..4d6ef830c 100644 --- a/backend/src/main/resources/metadata-sources-ui-schema.json +++ b/backend/src/main/resources/metadata-sources-ui-schema.json @@ -300,61 +300,7 @@ }, "relyingPartyOverrides": { "type": "object", - "properties": { - "signAssertion": { - "title": "label.sign-the-assertion", - "description": "tooltip.sign-assertion", - "type": "boolean", - "default": false - }, - "dontSignResponse": { - "title": "label.dont-sign-the-response", - "description": "tooltip.dont-sign-response", - "type": "boolean", - "default": false - }, - "turnOffEncryption": { - "title": "label.turn-off-encryption-of-response", - "description": "tooltip.turn-off-encryption", - "type": "boolean", - "default": false - }, - "useSha": { - "title": "label.use-sha1-signing-algorithm", - "description": "tooltip.usa-sha-algorithm", - "type": "boolean", - "default": false - }, - "ignoreAuthenticationMethod": { - "title": "label.ignore-any-sp-requested-authentication-method", - "description": "tooltip.ignore-auth-method", - "type": "boolean", - "default": false - }, - "forceAuthn": { - "title": "label.force-authn", - "description": "tooltip.force-authn", - "type": "boolean", - "default": false - }, - "omitNotBefore": { - "title": "label.omit-not-before-condition", - "type": "boolean", - "description": "tooltip.omit-not-before-condition", - "default": false - }, - "nameIdFormats": { - "$ref": "#/definitions/NameIdFormatList" - }, - "authenticationMethods": { - "$ref": "#/definitions/AuthenticationMethodList" - }, - "responderId": { - "title": "label.responder-id", - "description": "tooltip.responder-id", - "type": "string" - } - } + "properties": {} }, "attributeRelease": { "type": "array", @@ -526,50 +472,6 @@ } } }, - "NameIdFormatList": { - "title": "label.nameid-format-to-send", - "placeholder": "label.nameid-format", - "description": "tooltip.nameid-format", - "type": "array", - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 1, - "maxLength": 255, - "widget": { - "id": "datalist", - "data": [ - "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", - "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", - "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", - "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" - ] - } - }, - "default": null - }, - "AuthenticationMethodList": { - "title": "label.authentication-methods-to-use", - "description": "tooltip.authentication-methods-to-use", - "type": "array", - "placeholder": "label.authentication-method", - "uniqueItems": true, - "items": { - "type": "string", - "title": "label.authentication-method", - "minLength": 1, - "maxLength": 255, - "widget": { - "id": "datalist", - "data": [ - "https://refeds.org/profile/mfa", - "urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken", - "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" - ] - } - }, - "default": null - }, "LogoutEndpoint": { "title": "label.new-endpoint", "description": "tooltip.new-endpoint", 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 9b6a5df54..c881d81c7 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 @@ -1,7 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.controller import com.fasterxml.jackson.databind.ObjectMapper -import edu.internet2.tier.shibboleth.admin.ui.jsonschema.MetadataSourcesJsonSchemaResourceLocation +import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocationRegistry import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.TestConfiguration @@ -11,6 +11,10 @@ import org.springframework.core.io.ResourceLoader 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.ENTITY_ATTRIBUTES_FILTERS +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.METADATA_SOURCES + /** * @author Dmitriy Kopylenko */ @@ -36,11 +40,22 @@ class BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests extends Speci @TestConfiguration static class Config { @Bean - MetadataSourcesJsonSchemaResourceLocation metadataSourcesJsonSchemaResourceLocation(ResourceLoader resourceLoader, - ObjectMapper jacksonMapper) { - - new MetadataSourcesJsonSchemaResourceLocation('classpath:metadata-sources-ui-schema_MALFORMED.json', - resourceLoader, jacksonMapper, false) + JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry(ResourceLoader resourceLoader, + ObjectMapper jacksonMapper) { + + JsonSchemaResourceLocationRegistry.inMemory() + .register(METADATA_SOURCES, JsonSchemaLocationBuilder.with() + .jsonSchemaLocation('classpath:metadata-sources-ui-schema_MALFORMED.json') + .resourceLoader(resourceLoader) + .jacksonMapper(jacksonMapper) + .detectMalformedJson(false) + .build()) + .register(ENTITY_ATTRIBUTES_FILTERS, JsonSchemaLocationBuilder.with() + .jsonSchemaLocation('classpath:entity-attributes-filters-ui-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/controller/EntitiesControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy index 33a407f1a..8bf12484a 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.controller import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects +import groovy.json.JsonOutput import net.shibboleth.ext.spring.resource.ResourceHelper import org.joda.time.DateTime import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver @@ -15,6 +16,7 @@ import org.springframework.context.annotation.Bean import org.springframework.core.io.ClassPathResource import org.springframework.test.context.ActiveProfiles import org.springframework.test.web.reactive.server.WebTestClient +import org.springframework.test.web.servlet.result.MockMvcResultHandlers import org.springframework.web.util.DefaultUriBuilderFactory import org.xmlunit.builder.DiffBuilder import org.xmlunit.builder.Input @@ -59,17 +61,7 @@ class EntitiesControllerIntegrationTests extends Specification { "serviceEnabled":false, "createdDate":null, "modifiedDate":null, - "relyingPartyOverrides":{ - "signAssertion":false, - "dontSignResponse":false, - "turnOffEncryption":false, - "useSha":false, - "ignoreAuthenticationMethod":false, - "omitNotBefore":false, - "responderId":null, - "nameIdFormats":[], - "authenticationMethods":[] - }, + "relyingPartyOverrides":{}, "attributeRelease":["givenName","employeeNumber"] } ''' diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerTests.groovy index 89a43e4a8..99a8a0fd4 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerTests.groovy @@ -77,17 +77,7 @@ class EntitiesControllerTests extends Specification { "serviceEnabled":false, "createdDate":null, "modifiedDate":null, - "relyingPartyOverrides":{ - "signAssertion":false, - "dontSignResponse":false, - "turnOffEncryption":false, - "useSha":false, - "ignoreAuthenticationMethod":false, - "omitNotBefore":false, - "responderId":null, - "nameIdFormats":[], - "authenticationMethods":[] - }, + "relyingPartyOverrides":{}, "attributeRelease":["givenName","employeeNumber"] } ''' diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy index bc3de95e5..122a349ae 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy @@ -448,18 +448,7 @@ class EntityDescriptorControllerTests extends Specification { "serviceEnabled": false, "createdDate": null, "modifiedDate": null, - "relyingPartyOverrides": { - "signAssertion": false, - "dontSignResponse": false, - "turnOffEncryption": false, - "useSha": false, - "ignoreAuthenticationMethod": false, - "omitNotBefore": false, - "responderId": null, - "nameIdFormats": [], - "authenticationMethods": [], - "forceAuthn": false - }, + "relyingPartyOverrides": {}, "attributeRelease": [ "givenName", "employeeNumber" @@ -577,18 +566,7 @@ class EntityDescriptorControllerTests extends Specification { "serviceEnabled": false, "createdDate": null, "modifiedDate": null, - "relyingPartyOverrides": { - "signAssertion": false, - "dontSignResponse": false, - "turnOffEncryption": false, - "useSha": false, - "ignoreAuthenticationMethod": false, - "omitNotBefore": false, - "responderId": null, - "nameIdFormats": [], - "authenticationMethods": [], - "forceAuthn": false - }, + "relyingPartyOverrides": {}, "attributeRelease": [ "givenName", "employeeNumber" diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerIntegrationTests.groovy index 7f7beefe7..206421f49 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerIntegrationTests.groovy @@ -3,6 +3,7 @@ package edu.internet2.tier.shibboleth.admin.ui.controller import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSamlChainingMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverConverterService @@ -40,6 +41,9 @@ class MetadataFiltersControllerIntegrationTests extends Specification { @Autowired AttributeUtility attributeUtility + @Autowired + CustomPropertiesConfiguration customPropertiesConfiguration + @Autowired MetadataResolverConverterService metadataResolverConverterService @@ -54,7 +58,7 @@ class MetadataFiltersControllerIntegrationTests extends Specification { static BASE_URI = '/api/MetadataResolvers' def setup() { - generator = new TestObjectGenerator(attributeUtility) + generator = new TestObjectGenerator(attributeUtility, customPropertiesConfiguration) mapper = new ObjectMapper() mapper.enable(SerializationFeature.INDENT_OUTPUT) mapper.registerModule(new JavaTimeModule()) diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy index 13b8f188a..db1f15ab9 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy @@ -2,6 +2,7 @@ package edu.internet2.tier.shibboleth.admin.ui.controller import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializationFeature +import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration @@ -46,6 +47,9 @@ class MetadataFiltersControllerTests extends Specification { @Autowired AttributeUtility attributeUtility + @Autowired + CustomPropertiesConfiguration customPropertiesConfiguration + @Autowired FilterService filterService @@ -65,7 +69,7 @@ class MetadataFiltersControllerTests extends Specification { def setup() { randomGenerator = new RandomGenerator() - testObjectGenerator = new TestObjectGenerator(attributeUtility) + testObjectGenerator = new TestObjectGenerator(attributeUtility, customPropertiesConfiguration) mapper = new ObjectMapper() mapper.enable(SerializationFeature.INDENT_OUTPUT) diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversControllerIntegrationTests.groovy index c4453305f..d22902e30 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversControllerIntegrationTests.groovy @@ -3,6 +3,7 @@ package edu.internet2.tier.shibboleth.admin.ui.controller import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver @@ -44,6 +45,9 @@ class MetadataResolversControllerIntegrationTests extends Specification { @Autowired AttributeUtility attributeUtility + @Autowired + CustomPropertiesConfiguration customPropertiesConfiguration + ObjectMapper mapper TestObjectGenerator generator @@ -52,7 +56,7 @@ class MetadataResolversControllerIntegrationTests extends Specification { static BASE_URI = '/api/MetadataResolvers' def setup() { - generator = new TestObjectGenerator(attributeUtility) + generator = new TestObjectGenerator(attributeUtility, customPropertiesConfiguration) mapper = new ObjectMapper() mapper.enable(SerializationFeature.INDENT_OUTPUT) mapper.registerModule(new JavaTimeModule()) diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/filters/PolymorphicFiltersJacksonHandlingTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/filters/PolymorphicFiltersJacksonHandlingTests.groovy index 7841e045b..b3afd2adf 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/filters/PolymorphicFiltersJacksonHandlingTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/filters/PolymorphicFiltersJacksonHandlingTests.groovy @@ -2,21 +2,24 @@ package edu.internet2.tier.shibboleth.admin.ui.domain.filters import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializationFeature -import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter -import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityRoleWhiteListFilter -import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter -import edu.internet2.tier.shibboleth.admin.ui.domain.filters.RequiredValidUntilFilter +import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator import edu.internet2.tier.shibboleth.admin.util.AttributeUtility +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest import spock.lang.Specification +@SpringBootTest class PolymorphicFiltersJacksonHandlingTests extends Specification { ObjectMapper mapper AttributeUtility attributeUtility + @Autowired + CustomPropertiesConfiguration customPropertiesConfiguration + TestObjectGenerator testObjectGenerator def setup() { @@ -27,7 +30,7 @@ class PolymorphicFiltersJacksonHandlingTests extends Specification { it.init() it }) - testObjectGenerator = new TestObjectGenerator(attributeUtility) + testObjectGenerator = new TestObjectGenerator(attributeUtility, customPropertiesConfiguration) } def "Correct polymorphic serialization of EntityRoleWhiteListFilter"() { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/PolymorphicResolversJacksonHandlingTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/PolymorphicResolversJacksonHandlingTests.groovy index 79962f546..f22c875aa 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/PolymorphicResolversJacksonHandlingTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/PolymorphicResolversJacksonHandlingTests.groovy @@ -2,19 +2,26 @@ package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializationFeature +import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityRoleWhiteListFilter import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator import edu.internet2.tier.shibboleth.admin.util.AttributeUtility +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest import spock.lang.Specification +@SpringBootTest class PolymorphicResolversJacksonHandlingTests extends Specification { ObjectMapper mapper AttributeUtility attributeUtility + @Autowired + CustomPropertiesConfiguration customPropertiesConfiguration + TestObjectGenerator testObjectGenerator def setup() { @@ -25,7 +32,7 @@ class PolymorphicResolversJacksonHandlingTests extends Specification { it.init() it }) - testObjectGenerator = new TestObjectGenerator(attributeUtility) + testObjectGenerator = new TestObjectGenerator(attributeUtility, customPropertiesConfiguration) } def "Correct polymorphic serialization of LocalDynamicMetadataResolver"() { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/MetadataResolverRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/MetadataResolverRepositoryTests.groovy index 872182ce8..506c2b488 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/MetadataResolverRepositoryTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/MetadataResolverRepositoryTests.groovy @@ -6,7 +6,6 @@ import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfigurat import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.RelyingPartyOverridesRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver @@ -80,10 +79,7 @@ class MetadataResolverRepositoryTests extends Specification { it.name = 'original' it.resourceId = 'new-filter-UUID' it.attributeRelease = ['attr-for-release'] - it.relyingPartyOverrides = new RelyingPartyOverridesRepresentation().with { - it.signAssertion = true - it - } + it.setRelyingPartyOverrides(['signAssertion': true]) // to make sure it.rebuildAttributes() is called it } MetadataResolver metadataResolver = metadataResolverRepository.findAll().iterator().next() @@ -113,7 +109,7 @@ class MetadataResolverRepositoryTests extends Specification { it.value == 'attr-for-release' } } - persistedFilter.relyingPartyOverrides.signAssertion + persistedFilter.relyingPartyOverrides["signAssertion"] when: entityManager.flush() @@ -123,10 +119,7 @@ class MetadataResolverRepositoryTests extends Specification { it.name = 'updated' it.resourceId = 'new-filter-UUID' it.attributeRelease = ['attr-for-release', 'attr-for-release2'] - it.relyingPartyOverrides = new RelyingPartyOverridesRepresentation().with { - it.signAssertion = false - it - } + it.relyingPartyOverrides = ['signAssertion': false] it } metadataResolver = metadataResolverRepository.findAll().iterator().next() @@ -166,7 +159,7 @@ class MetadataResolverRepositoryTests extends Specification { it.value == 'attr-for-release2' } } - !persistedFilter.relyingPartyOverrides.signAssertion + !persistedFilter.relyingPartyOverrides["signAssertion"] } def "test persisting DynamicHttpMetadataResolver "() { 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 003e8c167..cae0c8afc 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 @@ -3,6 +3,7 @@ package edu.internet2.tier.shibboleth.admin.ui.service import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.domain.XSString import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityRoleWhiteListFilter @@ -12,6 +13,7 @@ import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator import edu.internet2.tier.shibboleth.admin.util.AttributeUtility +import groovy.xml.XmlUtil import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver import org.opensaml.saml.metadata.resolver.MetadataResolver import org.springframework.beans.factory.annotation.Autowired @@ -73,11 +75,11 @@ class IncommonJPAMetadataResolverServiceImplTests extends Specification { it.value = ['https://sp1.example.org'] it } - def attribute = attributeUtility.createAttributeWithArbitraryValues('here', null, 'there') + def attribute = attributeUtility.createAttributeWithStringValues('here', null, 'there') attribute.nameFormat = null attribute.namespacePrefix = 'saml' attribute.attributeValues.each { val -> - val.namespacePrefix = 'saml' + ((XSString)val).namespacePrefix = 'saml' } it.attributes = [attribute] it diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy index 679260897..e69ff9ad5 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy @@ -1,6 +1,9 @@ package edu.internet2.tier.shibboleth.admin.ui.service import com.fasterxml.jackson.databind.ObjectMapper +import edu.internet2.tier.shibboleth.admin.ui.ShibbolethUiApplication +import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor import edu.internet2.tier.shibboleth.admin.ui.domain.XSAny import edu.internet2.tier.shibboleth.admin.ui.domain.XSBoolean @@ -10,22 +13,31 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRe import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.LogoutEndpointRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.MduiRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.OrganizationRepresentation -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.RelyingPartyOverridesRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.SecurityInfoRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.ServiceProviderSsoDescriptorRepresentation import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.util.RandomGenerator import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator import edu.internet2.tier.shibboleth.admin.util.AttributeUtility +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.json.JacksonTester +import org.springframework.context.annotation.PropertySource +import org.springframework.test.context.ContextConfiguration import org.xmlunit.builder.DiffBuilder import org.xmlunit.builder.Input import org.xmlunit.diff.DefaultNodeMatcher import org.xmlunit.diff.ElementSelectors import spock.lang.Specification +@ContextConfiguration(classes=[CoreShibUiConfiguration, CustomPropertiesConfiguration]) +@SpringBootTest(classes = ShibbolethUiApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) +@PropertySource("classpath:application.yml") class JPAEntityDescriptorServiceImplTests extends Specification { + @Autowired + CustomPropertiesConfiguration customPropertiesConfiguration + def testObjectGenerator OpenSamlObjects openSamlObjects = new OpenSamlObjects().with { @@ -33,14 +45,15 @@ class JPAEntityDescriptorServiceImplTests extends Specification { it } - def service = new JPAEntityDescriptorServiceImpl(openSamlObjects, - new JPAEntityServiceImpl(openSamlObjects, new AttributeUtility(openSamlObjects))) + def service JacksonTester jacksonTester RandomGenerator generator def setup() { + service = new JPAEntityDescriptorServiceImpl(openSamlObjects, + new JPAEntityServiceImpl(openSamlObjects, new AttributeUtility(openSamlObjects), customPropertiesConfiguration)) JacksonTester.initFields(this, new ObjectMapper()) generator = new RandomGenerator() testObjectGenerator = new TestObjectGenerator() @@ -467,10 +480,7 @@ class JPAEntityDescriptorServiceImplTests extends Specification { def test = openSamlObjects.marshalToXmlString(service.createDescriptorFromRepresentation(new EntityDescriptorRepresentation().with { it.entityId = 'http://test.example.org/test1' - it.relyingPartyOverrides = new RelyingPartyOverridesRepresentation().with { - it.forceAuthn = true; - it - } + it.relyingPartyOverrides = ['forceAuthn': true] it })) @@ -487,7 +497,7 @@ class JPAEntityDescriptorServiceImplTests extends Specification { def output = service.createRepresentationFromDescriptor(service.createDescriptorFromRepresentation(representation)) then: - assert output.relyingPartyOverrides?.forceAuthn == true + assert output.relyingPartyOverrides?.forceAuthn == representation.relyingPartyOverrides.get("forceAuthn") } def "test ACS configuration"() { @@ -633,7 +643,8 @@ class JPAEntityDescriptorServiceImplTests extends Specification { def actualOutputJson = jacksonTester.write(actualOutputRepresentation) then: - // TODO: finish + // TODO: finish - This won't ever be identical due to transformations & null value representations + // How about reading in an actual output json and comparing with that instead? // Assertions.assertThat(actualOutputJson).isEqualToJson('/json/SHIBUI-219-3.json') assert true } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityServiceImplTests.groovy index 83a5c76f4..df888a972 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityServiceImplTests.groovy @@ -1,5 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.service +import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration @@ -33,17 +34,19 @@ class JPAEntityServiceImplTests extends Specification { @Autowired AttributeUtility attributeUtility + @Autowired + CustomPropertiesConfiguration customPropertiesConfiguration + def randomGenerator def testObjectGenerator def service def setup() { - service = new JPAEntityServiceImpl(openSamlObjects) - service.attributeUtility = attributeUtility + service = new JPAEntityServiceImpl(openSamlObjects, attributeUtility, customPropertiesConfiguration) randomGenerator = new RandomGenerator() - testObjectGenerator = new TestObjectGenerator(attributeUtility) + testObjectGenerator = new TestObjectGenerator(attributeUtility, customPropertiesConfiguration) } def "getAttributeListFromEntityRepresentation builds an appropriate attribute list"() { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAFilterServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAFilterServiceImplTests.groovy index 51a6f89bc..25dd61fa5 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAFilterServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAFilterServiceImplTests.groovy @@ -1,5 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.service +import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration @@ -33,9 +34,12 @@ class JPAFilterServiceImplTests extends Specification { @Autowired AttributeUtility attributeUtility + @Autowired + CustomPropertiesConfiguration customPropertiesConfiguration + def setup() { randomGenerator = new RandomGenerator() - testObjectGenerator = new TestObjectGenerator(attributeUtility) + testObjectGenerator = new TestObjectGenerator(attributeUtility, customPropertiesConfiguration) } def "createFilterFromRepresentation properly creates a filter from a representation"() { 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 316bfebbd..8bb9bc7d4 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 @@ -3,6 +3,7 @@ package edu.internet2.tier.shibboleth.admin.ui.service import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.MetadataResolverConverterConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.PlaceholderResolverComponentsConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget @@ -14,6 +15,7 @@ import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator import edu.internet2.tier.shibboleth.admin.util.AttributeUtility +import edu.internet2.tier.shibboleth.admin.util.TokenPlaceholderResolvers import groovy.xml.DOMBuilder import groovy.xml.MarkupBuilder import net.shibboleth.ext.spring.resource.ResourceHelper @@ -42,7 +44,7 @@ import static edu.internet2.tier.shibboleth.admin.ui.util.TestHelpers.generatedX @SpringBootTest @DataJpaTest -@ContextConfiguration(classes=[CoreShibUiConfiguration, MetadataResolverConverterConfiguration, SearchConfiguration, InternationalizationConfiguration]) +@ContextConfiguration(classes=[CoreShibUiConfiguration, MetadataResolverConverterConfiguration, SearchConfiguration, InternationalizationConfiguration, PlaceholderResolverComponentsConfiguration]) @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) @@ -295,7 +297,7 @@ class JPAMetadataResolverServiceImplTests extends Specification { OpenSamlObjects openSamlObjects @Bean - MetadataResolver metadataResolver() { + MetadataResolver metadataResolver(TokenPlaceholderResolvers tokenPlaceholderResolvers) { def resource = ResourceHelper.of(new ClassPathResource("/metadata/aggregate.xml")) def aggregate = new ResourceBackedMetadataResolver(resource){ @Override diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestHelpers.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestHelpers.groovy index 29db1fb1d..9311fde2d 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestHelpers.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestHelpers.groovy @@ -1,6 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.util -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.RelyingPartyOverridesRepresentation +import groovy.xml.XmlUtil import org.apache.commons.lang.StringUtils import org.w3c.dom.Document import org.xmlunit.builder.DiffBuilder @@ -10,18 +10,18 @@ import org.xmlunit.builder.Input * @author Bill Smith (wsmith@unicon.net) */ class TestHelpers { - static int determineCountOfAttributesFromRelyingPartyOverrides(RelyingPartyOverridesRepresentation relyingPartyOverridesRepresentation) { + static int determineCountOfAttributesFromRelyingPartyOverrides(Map relyingPartyOverridesRepresentation) { int count = 0 - count += relyingPartyOverridesRepresentation.authenticationMethods.size() != 0 ? 1 : 0 - count += relyingPartyOverridesRepresentation.dontSignResponse ? 1 : 0 - count += relyingPartyOverridesRepresentation.ignoreAuthenticationMethod ? 1 : 0 - count += relyingPartyOverridesRepresentation.nameIdFormats.size() != 0 ? 1 : 0 - count += relyingPartyOverridesRepresentation.omitNotBefore ? 1 : 0 - count += relyingPartyOverridesRepresentation.signAssertion ? 1 : 0 - count += relyingPartyOverridesRepresentation.turnOffEncryption ? 1 : 0 - count += relyingPartyOverridesRepresentation.useSha ? 1 : 0 - count += StringUtils.isNotBlank(relyingPartyOverridesRepresentation.responderId) ? 1 : 0 + relyingPartyOverridesRepresentation.entrySet().each {entry -> + if (entry.value instanceof Collection) { + count += ((Collection)entry.value).size() != 0 ? 1 : 0 + } else if (entry.value instanceof String) { + count += StringUtils.isNotBlank((String)entry.value) ? 1 : 0 + } else { + count++ + } + } return count } @@ -35,4 +35,8 @@ class TestHelpers { .build() .hasDifferences() } + + static String XmlDocumentToString(Document document) { + return XmlUtil.serialize(document.documentElement) + } } 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 cecaa25e6..eaf031c3e 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy @@ -1,14 +1,15 @@ package edu.internet2.tier.shibboleth.admin.ui.util +import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.* import edu.internet2.tier.shibboleth.admin.ui.domain.filters.* import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget.EntityAttributesFilterTargetType import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.FilterRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.FilterTargetRepresentation -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.RelyingPartyOverridesRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.* import edu.internet2.tier.shibboleth.admin.util.AttributeUtility import edu.internet2.tier.shibboleth.admin.util.MDDCConstants +import edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions import org.opensaml.saml.saml2.metadata.Organization import java.nio.file.Files @@ -21,12 +22,21 @@ class TestObjectGenerator { AttributeUtility attributeUtility + CustomPropertiesConfiguration customPropertiesConfiguration + RandomGenerator generator = new RandomGenerator() + TestObjectGenerator() {} + TestObjectGenerator(AttributeUtility attributeUtility) { this.attributeUtility = attributeUtility } + TestObjectGenerator(AttributeUtility attributeUtility, CustomPropertiesConfiguration customPropertiesConfiguration) { + this.attributeUtility = attributeUtility + this.customPropertiesConfiguration = customPropertiesConfiguration + } + DynamicHttpMetadataResolver buildDynamicHttpMetadataResolver() { def resolver = new DynamicHttpMetadataResolver().with { it.dynamicMetadataResolverAttributes = buildDynamicMetadataResolverAttributes() @@ -258,34 +268,29 @@ class TestObjectGenerator { List buildAttributesList() { List attributes = new ArrayList<>() - if (generator.randomBoolean()) { - attributes.add(attributeUtility.createAttributeWithBooleanValue(MDDCConstants.SIGN_ASSERTIONS, MDDCConstants.SIGN_ASSERTIONS_FN, true)) - } - if (generator.randomBoolean()) { - attributes.add(attributeUtility.createAttributeWithBooleanValue(MDDCConstants.SIGN_RESPONSES, MDDCConstants.SIGN_RESPONSES_FN, false)) - } - if (generator.randomBoolean()) { - attributes.add(attributeUtility.createAttributeWithBooleanValue(MDDCConstants.ENCRYPT_ASSERTIONS, MDDCConstants.ENCRYPT_ASSERTIONS_FN, false)) - } - if (generator.randomBoolean()) { - attributes.add(attributeUtility.createAttributeWithArbitraryValues(MDDCConstants.SECURITY_CONFIGURATION, MDDCConstants.SECURITY_CONFIGURATION_FN, "shibboleth.SecurityConfiguration.SHA1")) - } - if (generator.randomBoolean()) { - // this is actually going to be wrong, but it will work for the time being. this should be a bitmask value that we calculate - // TODO: fix - attributes.add(attributeUtility.createAttributeWithArbitraryValues(MDDCConstants.DISALLOWED_FEATURES, MDDCConstants.DISALLOWED_FEATURES_FN, "0x1")) - } - if (generator.randomBoolean()) { - attributes.add(attributeUtility.createAttributeWithBooleanValue(MDDCConstants.INCLUDE_CONDITIONS_NOT_BEFORE, MDDCConstants.INCLUDE_CONDITIONS_NOT_BEFORE_FN, false)) - } - if (generator.randomBoolean()) { - attributes.add(attributeUtility.createAttributeWithArbitraryValues(MDDCConstants.RESPONDER_ID, MDDCConstants.RESPONDER_ID_FN, generator.randomId())) - } - if (generator.randomBoolean()) { - attributes.add(attributeUtility.createAttributeWithArbitraryValues(MDDCConstants.NAME_ID_FORMAT_PRECEDENCE, MDDCConstants.NAME_ID_FORMAT_PRECEDENCE_FN, generator.randomStringList())) - } - if (generator.randomBoolean()) { - attributes.add(attributeUtility.createAttributeWithArbitraryValues(MDDCConstants.DEFAULT_AUTHENTICATION_METHODS, MDDCConstants.DEFAULT_AUTHENTICATION_METHODS_FN, generator.randomStringList())) + customPropertiesConfiguration.getOverrides().each {override -> + if (generator.randomBoolean()) { + switch (ModelRepresentationConversions.AttributeTypes.valueOf(override.getDisplayType().toUpperCase())) { + case ModelRepresentationConversions.AttributeTypes.BOOLEAN: + if (override.getPersistType() != null && + override.getPersistType() != override.getDisplayType()) { + attributes.add(attributeUtility.createAttributeWithStringValues(override.getAttributeName(), override.getAttributeFriendlyName(), generator.randomString(30))) + } else { + attributes.add(attributeUtility.createAttributeWithBooleanValue(override.getAttributeName(), override.getAttributeFriendlyName(), generator.randomBoolean())) + } + break + case ModelRepresentationConversions.AttributeTypes.INTEGER: + attributes.add(attributeUtility.createAttributeWithIntegerValue(override.getAttributeName(), override.getAttributeFriendlyName(), generator.randomInt(0, 999999))) + break + case ModelRepresentationConversions.AttributeTypes.STRING: + attributes.add(attributeUtility.createAttributeWithStringValues(override.getAttributeName(), override.getAttributeFriendlyName(), generator.randomString(30))) + break + case ModelRepresentationConversions.AttributeTypes.SET: + case ModelRepresentationConversions.AttributeTypes.LIST: + attributes.add(attributeUtility.createAttributeWithStringValues(override.getAttributeName(), override.getAttributeFriendlyName(), generator.randomStringList())) + break + } + } } if (generator.randomBoolean()) { attributes.add(attributeUtility.createAttributeWithStringValues(MDDCConstants.RELEASE_ATTRIBUTES, generator.randomStringList())) @@ -306,20 +311,37 @@ class TestObjectGenerator { return representation } - RelyingPartyOverridesRepresentation buildRelyingPartyOverridesRepresentation() { - RelyingPartyOverridesRepresentation representation = new RelyingPartyOverridesRepresentation() - - representation.setAuthenticationMethods(generator.randomStringList()) - representation.setDontSignResponse(generator.randomBoolean()) - representation.setIgnoreAuthenticationMethod(generator.randomBoolean()) - representation.setNameIdFormats(generator.randomStringList()) - representation.setOmitNotBefore(generator.randomBoolean()) - representation.setSignAssertion(generator.randomBoolean()) - representation.setTurnOffEncryption(generator.randomBoolean()) - representation.setUseSha(generator.randomBoolean()) - representation.setResponderId(generator.randomId()) + Map buildRelyingPartyOverridesRepresentation() { + def representation = [:] + + customPropertiesConfiguration.getOverrides().each { override -> + switch (ModelRepresentationConversions.AttributeTypes.valueOf(override.getDisplayType().toUpperCase())) { + case ModelRepresentationConversions.AttributeTypes.BOOLEAN: + if (override.getPersistType() != null && + override.getPersistType() != override.getDisplayType()) { + representation.put(override.getName(), generator.randomString(30)) + } else { + representation.put(override.getName(), generator.randomBoolean()) + } + break + case ModelRepresentationConversions.AttributeTypes.INTEGER: + representation.put(override.getName(), generator.randomInt(0, 999999)) + break + case ModelRepresentationConversions.AttributeTypes.STRING: + representation.put(override.getName(), generator.randomString(30)) + break + case ModelRepresentationConversions.AttributeTypes.SET: + case ModelRepresentationConversions.AttributeTypes.LIST: + def someStrings = new ArrayList() + (0..generator.randomInt(1, 5)).each { + someStrings << generator.randomString(20) + } + representation.put(override.getName(), someStrings) + break + } + } - return representation + representation } String buildEntityAttributesFilterTargetValueByType(EntityAttributesFilterTargetType type) { diff --git a/backend/src/test/resources/conf/278.2.xml b/backend/src/test/resources/conf/278.2.xml index 20b513e1f..269a2f3ec 100644 --- a/backend/src/test/resources/conf/278.2.xml +++ b/backend/src/test/resources/conf/278.2.xml @@ -28,7 +28,7 @@ - there + there https://sp1.example.org diff --git a/ui/src/app/metadata/filter/container/edit-filter.component.ts b/ui/src/app/metadata/filter/container/edit-filter.component.ts index 6e058a464..a98cbc62d 100644 --- a/ui/src/app/metadata/filter/container/edit-filter.component.ts +++ b/ui/src/app/metadata/filter/container/edit-filter.component.ts @@ -11,6 +11,7 @@ import { UpdateFilterRequest } from '../action/collection.action'; import { CancelCreateFilter, UpdateFilterChanges } from '../action/filter.action'; import { PreviewEntity } from '../../domain/action/entity.action'; import { EntityAttributesFilterEntity } from '../../domain/entity'; +import { shareReplay } from 'rxjs/operators'; @Component({ selector: 'edit-filter-page', @@ -40,7 +41,7 @@ export class EditFilterComponent { ) { this.definition = MetadataFilterTypes.EntityAttributesFilter; - this.schema$ = this.schemaService.get(this.definition.schema); + this.schema$ = this.schemaService.get(this.definition.schema).pipe(shareReplay()); this.isSaving$ = this.store.select(fromFilter.getCollectionSaving); this.model$ = this.store.select(fromFilter.getSelectedFilter); diff --git a/ui/src/app/metadata/filter/container/new-filter.component.ts b/ui/src/app/metadata/filter/container/new-filter.component.ts index 542126491..7215b55c4 100644 --- a/ui/src/app/metadata/filter/container/new-filter.component.ts +++ b/ui/src/app/metadata/filter/container/new-filter.component.ts @@ -1,7 +1,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { Subject, Observable, of } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { takeUntil, shareReplay } from 'rxjs/operators'; import * as fromFilter from '../reducer'; import { MetadataFilterTypes } from '../model'; @@ -39,7 +39,7 @@ export class NewFilterComponent implements OnDestroy, OnInit { ) { this.definition = MetadataFilterTypes.EntityAttributesFilter; - this.schema$ = this.schemaService.get(this.definition.schema); + this.schema$ = this.schemaService.get(this.definition.schema).pipe(shareReplay()); this.isSaving$ = this.store.select(fromFilter.getCollectionSaving); this.model$ = of({}); } @@ -51,6 +51,7 @@ export class NewFilterComponent implements OnDestroy, OnInit { this.statusChangeEmitted$ .pipe(takeUntil(this.ngUnsubscribe)) .subscribe(valid => { + console.log(valid); this.isValid = valid.value ? valid.value.length === 0 : true; }); diff --git a/ui/src/app/metadata/filter/model/entity-attributes.filter.ts b/ui/src/app/metadata/filter/model/entity-attributes.filter.ts index fc358dece..0d559c094 100644 --- a/ui/src/app/metadata/filter/model/entity-attributes.filter.ts +++ b/ui/src/app/metadata/filter/model/entity-attributes.filter.ts @@ -4,7 +4,7 @@ import { MetadataFilter } from '../../domain/model'; export const EntityAttributesFilter: FormDefinition = { label: 'EntityAttributes', type: 'EntityAttributes', - schema: 'assets/schema/filter/entity-attributes.schema.json', + schema: '/api/ui/EntityAttributesFilters', getValidators(): any { const validators = {}; return validators; diff --git a/ui/src/assets/schema/filter/metadata-filter.schema.json b/ui/src/assets/schema/filter/metadata-filter.schema.json deleted file mode 100644 index d33b7c1af..000000000 --- a/ui/src/assets/schema/filter/metadata-filter.schema.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "title": "label.metadata-filter", - "type": "object", - "widget": { - "id": "fieldset" - }, - "properties": { - "name": { - "title": "label.metadata-filter-name", - "description": "tooltip.metadata-filter-name", - "type": "string", - "widget": { - "id": "string", - "help": "message.must-be-unique" - } - }, - "@type": { - "title": "label.metadata-filter-type", - "description": "tooltip.metadata-filter-type", - "placeholder": "action.select-metadata-filter-type", - "type": "string", - "widget": { - "id": "select" - }, - "oneOf": [ - { - "enum": [ - "EntityAttributes" - ], - "description": "value.entity-attributes-filter" - } - ] - } - }, - "required": [ - "name", - "@type" - ], - "fieldsets": [ - { - "type": "section", - "fields": [ - "name", - "@type" - ] - } - ] -} \ No newline at end of file