From c850648b74ad0e5121c68f3fc5049bcd66ddd839 Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Wed, 21 Aug 2019 14:07:19 -0400 Subject: [PATCH 01/19] Schema validation verification WIP --- ...lerSchemaValidationIntegrationTests.groovy | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerSchemaValidationIntegrationTests.groovy diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerSchemaValidationIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerSchemaValidationIntegrationTests.groovy new file mode 100644 index 000000000..39e630223 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerSchemaValidationIntegrationTests.groovy @@ -0,0 +1,54 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.web.client.TestRestTemplate +import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.springframework.test.context.ActiveProfiles +import spock.lang.Specification + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles(["no-auth", "dev"]) +class EntityDescriptorControllerSchemaValidationIntegrationTests extends Specification { + + @Autowired + private TestRestTemplate restTemplate + + static RESOURCE_URI = '/api/EntityDescriptor' + + def 'POST /EntityDescriptor with invalid payload according to schema validation'() { + given: + def postedJsonBody = """ + { + "serviceProviderName": "SP", + "entityId": "ED", + "organization": null, + "serviceEnabled": true, + "createdDate": null, + "modifiedDate": null, + "organization": null, + "contacts": null, + "mdui": null, + "serviceProviderSsoDescriptor": null, + "logoutEndpoints": null, + "securityInfo": null, + "assertionConsumerServices": null, + "relyingPartyOverrides": null, + "attributeRelease": null, + "current": false + } + """ + + when: + def result = this.restTemplate.postForEntity(RESOURCE_URI, createRequestHttpEntityFor { postedJsonBody }, Map) + + then: + result.statusCodeValue == 400 + result.body.errorMessage.count('Type mistmatch for null') > 0 + } + + private static HttpEntity createRequestHttpEntityFor(Closure jsonBodySupplier) { + new HttpEntity(jsonBodySupplier(), ['Content-Type': 'application/json'] as HttpHeaders) + } +} From bdf8e93330f4341f5420035dda6e3f0f95ae31ee Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Thu, 22 Aug 2019 09:26:24 -0400 Subject: [PATCH 02/19] 1414: MetadataResolvers JSON schema validation WIP --- ...orSchemaValidatingControllerAdvice.groovy} | 20 +--- .../LowLevelJsonSchemaValidator.groovy | 58 ++++++++++ ...verSchemaValidatingControllerAdvice.groovy | 41 +++++++ ...lerSchemaValidationIntegrationTests.groovy | 100 ++++++++++++++++++ 4 files changed, 204 insertions(+), 15 deletions(-) rename backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/{RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy => EntityDescriptorSchemaValidatingControllerAdvice.groovy} (70%) create mode 100644 backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy create mode 100644 backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataResolverSchemaValidatingControllerAdvice.groovy create mode 100644 backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerSchemaValidationIntegrationTests.groovy 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/EntityDescriptorSchemaValidatingControllerAdvice.groovy similarity index 70% rename from backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy rename to backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/EntityDescriptorSchemaValidatingControllerAdvice.groovy index 620d2252c..f25950256 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/EntityDescriptorSchemaValidatingControllerAdvice.groovy @@ -2,7 +2,6 @@ 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 @@ -14,6 +13,7 @@ import javax.annotation.PostConstruct import java.lang.reflect.Type import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.metadataSourcesSchema +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.LowLevelJsonSchemaValidator.validatePayloadAgainstSchema /** * Controller advice implementation for validating relying party overrides payload coming from UI layer @@ -22,7 +22,7 @@ import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocati * @author Dmitriy Kopylenko */ @ControllerAdvice -class RelyingPartyOverridesJsonSchemaValidatingControllerAdvice extends RequestBodyAdviceAdapter { +class EntityDescriptorSchemaValidatingControllerAdvice extends RequestBodyAdviceAdapter { @Autowired JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry @@ -38,22 +38,12 @@ class RelyingPartyOverridesJsonSchemaValidatingControllerAdvice extends RequestB HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) throws IOException { - def bytes = inputMessage.body.bytes - def schema = Json.schema(this.jsonSchemaLocation.uri) - - def stream = new ByteArrayInputStream(bytes) - def validationResult = schema.validate(Json.read(stream.getText())) - if (!validationResult.at('ok')) { - throw new JsonSchemaValidationFailedException(validationResult.at('errors').asList()) - } - return [ - getBody: { new ByteArrayInputStream(bytes) }, - getHeaders: { inputMessage.headers } - ] as HttpInputMessage + + return validatePayloadAgainstSchema(inputMessage, this.jsonSchemaLocation.uri) } @PostConstruct void init() { - this.jsonSchemaLocation = metadataSourcesSchema(this.jsonSchemaResourceLocationRegistry); + this.jsonSchemaLocation = metadataSourcesSchema(this.jsonSchemaResourceLocationRegistry) } } diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy new file mode 100644 index 000000000..b80c1f5d6 --- /dev/null +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy @@ -0,0 +1,58 @@ +package edu.internet2.tier.shibboleth.admin.ui.jsonschema + +import mjson.Json +import org.springframework.http.HttpInputMessage + +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.dynamicHttpMetadataProviderSchema +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.filesystemMetadataProviderSchema +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.localDynamicMetadataProviderSchema + +/** + * Currently uses mjson library. + */ +class LowLevelJsonSchemaValidator { + + static HttpInputMessage validatePayloadAgainstSchema(HttpInputMessage inputMessage, URI schemaUri) { + def json = extractJsonPayload(inputMessage) + def schema = Json.schema(schemaUri) + doValidate(schema, json) + } + + static HttpInputMessage validateMetadataResolverTypePayloadAgainstSchema(HttpInputMessage inputMessage, + JsonSchemaResourceLocationRegistry schemaRegistry) { + def json = extractJsonPayload(inputMessage) + def schemaUri = null + switch (json.asMap()['@type']) { + case 'LocalDynamicMetadataResolver': + schemaUri = localDynamicMetadataProviderSchema(schemaRegistry).uri + break + case 'DynamicHttpMetadataResolver': + schemaUri = dynamicHttpMetadataProviderSchema(schemaRegistry).uri + break + case 'FilesystemMetadataResolver': + schemaUri = filesystemMetadataProviderSchema(schemaRegistry).uri + break + default: + break + } + if(!schemaUri) { + return inputMessage + } + doValidate(Json.schema(schemaUri), json) + } + + private static Json extractJsonPayload(HttpInputMessage inputMessage) { + Json.read(new ByteArrayInputStream(inputMessage.body.bytes).getText()) + } + + private static HttpInputMessage doValidate(Json.Schema schema, Json json) { + def validationResult = schema.validate(json) + if (!validationResult.at('ok')) { + throw new JsonSchemaValidationFailedException(validationResult.at('errors').asList()) + } + return [ + getBody : { new ByteArrayInputStream(bytes) }, + getHeaders: { inputMessage.headers } + ] as HttpInputMessage + } +} diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataResolverSchemaValidatingControllerAdvice.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataResolverSchemaValidatingControllerAdvice.groovy new file mode 100644 index 000000000..6ecbef95a --- /dev/null +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataResolverSchemaValidatingControllerAdvice.groovy @@ -0,0 +1,41 @@ +package edu.internet2.tier.shibboleth.admin.ui.jsonschema + + +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.core.MethodParameter +import org.springframework.http.HttpInputMessage +import org.springframework.http.converter.HttpMessageConverter +import org.springframework.web.bind.annotation.ControllerAdvice +import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter + +import java.lang.reflect.Type + +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.LowLevelJsonSchemaValidator.validateMetadataResolverTypePayloadAgainstSchema + +/** + * Controller advice implementation for validating metadata resolvers payload coming from UI layer + * against pre-defined JSON schema for their respected types. Choosing of the appropriate schema based on incoming + * resolver types is delegated to @{LowLevelJsonSchemaValidator}. + * + * @author Dmitriy Kopylenko + */ +@ControllerAdvice +class MetadataResolverSchemaValidatingControllerAdvice extends RequestBodyAdviceAdapter { + + @Autowired + JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry + + @Override + boolean supports(MethodParameter methodParameter, Type targetType, Class> converterType) { + targetType.typeName == MetadataResolver.typeName + } + + @Override + HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, + Type targetType, Class> converterType) + throws IOException { + + validateMetadataResolverTypePayloadAgainstSchema(inputMessage, jsonSchemaResourceLocationRegistry) + } +} diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerSchemaValidationIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerSchemaValidationIntegrationTests.groovy new file mode 100644 index 000000000..1f96de134 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerSchemaValidationIntegrationTests.groovy @@ -0,0 +1,100 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.web.client.TestRestTemplate +import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.springframework.test.context.ActiveProfiles +import spock.lang.Specification + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles(["no-auth", "dev"]) +class MetadataResolverControllerSchemaValidationIntegrationTests extends Specification { + + @Autowired + private TestRestTemplate restTemplate + + static RESOURCE_URI = '/api/MetadataResolvers' + + def 'POST for LocalDynamicMetadataResolver with invalid payload according to schema validation'() { + given: + def postedJsonBody = """ + { + "createdDate" : null, + "modifiedDate" : null, + "createdBy" : null, + "modifiedBy" : null, + "name" : null, + "requireValidMetadata" : true, + "failFastInitialization" : true, + "sortKey" : null, + "criterionPredicateRegistryRef" : null, + "useDefaultPredicateRegistry" : true, + "satisfyAnyPredicates" : false, + "metadataFilters" : [ { + "createdDate" : null, + "modifiedDate" : null, + "createdBy" : null, + "modifiedBy" : null, + "name" : "EntityAttributes", + "filterEnabled" : false, + "version" : 463855403, + "entityAttributesFilterTarget" : { + "createdDate" : null, + "modifiedDate" : null, + "createdBy" : null, + "modifiedBy" : null, + "entityAttributesFilterTargetType" : "ENTITY", + "value" : [ "CedewbJJET" ], + "audId" : null + }, + "attributeRelease" : [ "9ktPyjjiCn" ], + "relyingPartyOverrides" : { + "signAssertion" : false, + "dontSignResponse" : true, + "turnOffEncryption" : true, + "useSha" : false, + "ignoreAuthenticationMethod" : false, + "omitNotBefore" : true, + "responderId" : null, + "nameIdFormats" : [ ], + "authenticationMethods" : [ ] + }, + "audId" : null, + "@type" : "EntityAttributes" + }, { + "createdDate" : null, + "modifiedDate" : null, + "createdBy" : null, + "modifiedBy" : null, + "name" : "EntityRoleWhiteList", + "filterEnabled" : false, + "version" : 0, + "removeRolelessEntityDescriptors" : true, + "removeEmptyEntitiesDescriptors" : true, + "retainedRoles" : [ "role1", "role2" ], + "audId" : null, + "@type" : "EntityRoleWhiteList" + } ], + "version" : 0, + "sourceDirectory" : "dir", + "sourceManagerRef" : null, + "sourceKeyGeneratorRef" : null, + "audId" : null, + "@type" : "LocalDynamicMetadataResolver" + } + """ + + when: + def result = this.restTemplate.postForEntity(RESOURCE_URI, createRequestHttpEntityFor { postedJsonBody }, Map) + + then: + result.statusCodeValue == 400 + result.body.errorMessage.count('Type mistmatch for null') > 0 + } + + private static HttpEntity createRequestHttpEntityFor(Closure jsonBodySupplier) { + new HttpEntity(jsonBodySupplier(), ['Content-Type': 'application/json'] as HttpHeaders) + } +} From ffffc0b70822bee27f71804f6e9d52d28c204a33 Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Fri, 23 Aug 2019 10:26:42 -0400 Subject: [PATCH 03/19] Metadata Resolvers schema validation --- .../LowLevelJsonSchemaValidator.groovy | 28 ++++++++----- .../DynamicHttpMetadataResolver.java | 2 + .../DynamicMetadataResolverAttributes.java | 2 +- ...SamlMetadataResolverConstructorHelper.java | 2 +- ...dynamic-http-metadata-provider.schema.json | 6 +-- ...ocal-dynamic-metadata-provider.schema.json | 4 +- ...lerSchemaValidationIntegrationTests.groovy | 42 ++++++++++++++++--- ...ResolversControllerIntegrationTests.groovy | 12 +++++- .../admin/ui/util/TestObjectGenerator.groovy | 1 + 9 files changed, 75 insertions(+), 24 deletions(-) diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy index b80c1f5d6..a793f0fbd 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy @@ -13,14 +13,16 @@ import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocati class LowLevelJsonSchemaValidator { static HttpInputMessage validatePayloadAgainstSchema(HttpInputMessage inputMessage, URI schemaUri) { - def json = extractJsonPayload(inputMessage) + def origInput = [inputMessage.body.bytes, inputMessage.headers] + def json = extractJsonPayload(origInput) def schema = Json.schema(schemaUri) - doValidate(schema, json) + doValidate(origInput, schema, json) } static HttpInputMessage validateMetadataResolverTypePayloadAgainstSchema(HttpInputMessage inputMessage, JsonSchemaResourceLocationRegistry schemaRegistry) { - def json = extractJsonPayload(inputMessage) + def origInput = [inputMessage.body.bytes, inputMessage.headers] + def json = extractJsonPayload(origInput) def schemaUri = null switch (json.asMap()['@type']) { case 'LocalDynamicMetadataResolver': @@ -36,23 +38,27 @@ class LowLevelJsonSchemaValidator { break } if(!schemaUri) { - return inputMessage + return newInputMessage(origInput) } - doValidate(Json.schema(schemaUri), json) + doValidate(origInput, Json.schema(schemaUri), json) } - private static Json extractJsonPayload(HttpInputMessage inputMessage) { - Json.read(new ByteArrayInputStream(inputMessage.body.bytes).getText()) + private static Json extractJsonPayload(List origInput) { + Json.read(new ByteArrayInputStream(origInput[0]).getText()) } - private static HttpInputMessage doValidate(Json.Schema schema, Json json) { + private static HttpInputMessage doValidate(List origInput, Json.Schema schema, Json json) { def validationResult = schema.validate(json) if (!validationResult.at('ok')) { throw new JsonSchemaValidationFailedException(validationResult.at('errors').asList()) } - return [ - getBody : { new ByteArrayInputStream(bytes) }, - getHeaders: { inputMessage.headers } + newInputMessage(origInput) + } + + private static newInputMessage(origInput) { + [ + getBody : { new ByteArrayInputStream(origInput[0]) }, + getHeaders: { origInput[1] } ] as HttpInputMessage } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java index 732078747..d22d9c6df 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java @@ -36,6 +36,8 @@ public class DynamicHttpMetadataResolver extends MetadataResolver { private Integer maxConnectionsPerRoute = 100; + private String metadataURL; + @ElementCollection @OrderColumn private List supportedContentTypes; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicMetadataResolverAttributes.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicMetadataResolverAttributes.java index 564f2871e..d817284ba 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicMetadataResolverAttributes.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicMetadataResolverAttributes.java @@ -20,7 +20,7 @@ public class DynamicMetadataResolverAttributes { private String taskTimerRef; - private Double refreshDelayFactor = 0.75; + private Float refreshDelayFactor = 0.75F; private String minCacheDuration = "PT10M"; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlMetadataResolverConstructorHelper.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlMetadataResolverConstructorHelper.java index d52e47448..862624b05 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlMetadataResolverConstructorHelper.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlMetadataResolverConstructorHelper.java @@ -57,7 +57,7 @@ public static void updateOpenSamlMetadataResolverFromDynamicMetadataResolverAttr } if (attributes.getRefreshDelayFactor() != null) { - dynamicMetadataResolver.setRefreshDelayFactor(attributes.getRefreshDelayFactor().floatValue()); + dynamicMetadataResolver.setRefreshDelayFactor(attributes.getRefreshDelayFactor()); } if (attributes.getRemoveIdleEntityData() != null) { diff --git a/backend/src/main/resources/dynamic-http-metadata-provider.schema.json b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json index f7106c6e5..cc0696e87 100644 --- a/backend/src/main/resources/dynamic-http-metadata-provider.schema.json +++ b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json @@ -147,7 +147,7 @@ "refreshDelayFactor": { "title": "label.refresh-delay-factor", "description": "tooltip.refresh-delay-factor", - "type": "string", + "type": "number", "widget": { "id": "string", "help": "message.real-number" @@ -578,7 +578,7 @@ "metadataFilters": { "title": "", "description": "", - "type": "object", + "type": "array", "properties": { "RequiredValidUntil": { "title": "label.required-valid-until", @@ -703,4 +703,4 @@ } } } -} \ No newline at end of file +} diff --git a/backend/src/main/resources/local-dynamic-metadata-provider.schema.json b/backend/src/main/resources/local-dynamic-metadata-provider.schema.json index f39904f36..72d846023 100644 --- a/backend/src/main/resources/local-dynamic-metadata-provider.schema.json +++ b/backend/src/main/resources/local-dynamic-metadata-provider.schema.json @@ -61,7 +61,7 @@ "refreshDelayFactor": { "title": "label.refresh-delay-factor", "description": "tooltip.refresh-delay-factor", - "type": "string", + "type": "number", "widget": { "id": "string", "help": "message.real-number" @@ -188,4 +188,4 @@ } } } -} \ No newline at end of file +} diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerSchemaValidationIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerSchemaValidationIntegrationTests.groovy index 1f96de134..016b63cb7 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerSchemaValidationIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerSchemaValidationIntegrationTests.groovy @@ -17,6 +17,16 @@ class MetadataResolverControllerSchemaValidationIntegrationTests extends Specifi static RESOURCE_URI = '/api/MetadataResolvers' + private HTTP_POST = { body -> + this.restTemplate.postForEntity(RESOURCE_URI, createRequestHttpEntityFor(body), Map) + } + + private static checkJsonValidationIsPerformed = { + assert it.statusCodeValue == 400 + assert it.body.errorMessage.count('Type mistmatch for null') > 0 + true + } + def 'POST for LocalDynamicMetadataResolver with invalid payload according to schema validation'() { given: def postedJsonBody = """ @@ -87,14 +97,36 @@ class MetadataResolverControllerSchemaValidationIntegrationTests extends Specifi """ when: - def result = this.restTemplate.postForEntity(RESOURCE_URI, createRequestHttpEntityFor { postedJsonBody }, Map) + def result = HTTP_POST(postedJsonBody) + + then: + checkJsonValidationIsPerformed(result) + + } + + def 'POST for DynamicHttpMetadataResolver with invalid payload according to schema validation'() { + given: + def postedJsonBody = """ + { + "name" : null, + "xmlId": "123", + "metadataURL": "http://metadata", + "metadataRequestURLConstructionScheme": {"@type": "MetadataQueryProtocol", "content": "scheme"}, + "@type" : "DynamicHttpMetadataResolver" + } + """ + + when: + def result = HTTP_POST(postedJsonBody) then: - result.statusCodeValue == 400 - result.body.errorMessage.count('Type mistmatch for null') > 0 + checkJsonValidationIsPerformed(result) + } - private static HttpEntity createRequestHttpEntityFor(Closure jsonBodySupplier) { - new HttpEntity(jsonBodySupplier(), ['Content-Type': 'application/json'] as HttpHeaders) + private static HttpEntity createRequestHttpEntityFor(String jsonBody) { + new HttpEntity(jsonBody, ['Content-Type': 'application/json'] as HttpHeaders) } + + } 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 d22902e30..84c2c72ed 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 @@ -8,6 +8,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFil import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.LocalDynamicMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataQueryProtocolScheme 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.util.TestObjectGenerator @@ -27,6 +28,7 @@ import org.springframework.test.context.ActiveProfiles import spock.lang.Specification import spock.lang.Unroll +import static com.fasterxml.jackson.annotation.JsonInclude.Include.* import static org.springframework.http.HttpMethod.PUT /** @@ -59,6 +61,7 @@ class MetadataResolversControllerIntegrationTests extends Specification { generator = new TestObjectGenerator(attributeUtility, customPropertiesConfiguration) mapper = new ObjectMapper() mapper.enable(SerializationFeature.INDENT_OUTPUT) + mapper.setSerializationInclusion(NON_NULL) mapper.registerModule(new JavaTimeModule()) } @@ -254,7 +257,14 @@ class MetadataResolversControllerIntegrationTests extends Specification { def "PUT concrete MetadataResolver with version conflict -> /api/MetadataResolvers/{resourceId}"() { given: 'One resolver is available in data store' def resolver = new DynamicHttpMetadataResolver().with { - it.name = 'Test DynamicHttpMetadataResolver' + it.name = 'DynamicHTTP' + it.xmlId = 'DynamicHTTP' + it.metadataURL = 'http://metadata' + it.metadataRequestURLConstructionScheme = new MetadataQueryProtocolScheme().with { + it.transformRef = 'transformRef' + it.content = 'content' + it + } it } def resolverResourceId = resolver.resourceId 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 982722ebd..cc1403bee 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 @@ -535,6 +535,7 @@ class TestObjectGenerator { new DynamicHttpMetadataResolver().with { it.name = 'DynamicHTTP' it.xmlId = 'DynamicHTTP' + it.metadataURL = 'http://metadata' it.dynamicMetadataResolverAttributes = new DynamicMetadataResolverAttributes().with { it } From 0d48d4c0c4289cf713806747a6a66580372a66d3 Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Fri, 23 Aug 2019 11:07:48 -0400 Subject: [PATCH 04/19] Test for filesystem resolver validation --- ...lerSchemaValidationIntegrationTests.groovy | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerSchemaValidationIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerSchemaValidationIntegrationTests.groovy index 016b63cb7..d26b90cb5 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerSchemaValidationIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerSchemaValidationIntegrationTests.groovy @@ -124,6 +124,25 @@ class MetadataResolverControllerSchemaValidationIntegrationTests extends Specifi } + def 'POST for FilesystemMetadataResolver with invalid payload according to schema validation'() { + given: + def postedJsonBody = """ + { + "name" : null, + "xmlId": "123", + "metadataFile": "%{shib.home}/metadata.xml", + "@type" : "FilesystemMetadataResolver" + } + """ + + when: + def result = HTTP_POST(postedJsonBody) + + then: + checkJsonValidationIsPerformed(result) + + } + private static HttpEntity createRequestHttpEntityFor(String jsonBody) { new HttpEntity(jsonBody, ['Content-Type': 'application/json'] as HttpHeaders) } From 27518515dc108431b46839a119d0cb80370ff76b Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Fri, 23 Aug 2019 11:28:36 -0400 Subject: [PATCH 05/19] WIP --- .../ui/jsonschema/LowLevelJsonSchemaValidator.groovy | 8 +++++++- ...adataResolversSchemaValidatingControllerAdvice.groovy} | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) rename backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/{MetadataResolverSchemaValidatingControllerAdvice.groovy => MetadataResolversSchemaValidatingControllerAdvice.groovy} (94%) diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy index a793f0fbd..bea82ba26 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy @@ -21,6 +21,7 @@ class LowLevelJsonSchemaValidator { static HttpInputMessage validateMetadataResolverTypePayloadAgainstSchema(HttpInputMessage inputMessage, JsonSchemaResourceLocationRegistry schemaRegistry) { + def origInput = [inputMessage.body.bytes, inputMessage.headers] def json = extractJsonPayload(origInput) def schemaUri = null @@ -37,12 +38,17 @@ class LowLevelJsonSchemaValidator { default: break } - if(!schemaUri) { + if (!schemaUri) { return newInputMessage(origInput) } doValidate(origInput, Json.schema(schemaUri), json) } + static HttpInputMessage validateMetadataFilterTypePayloadAgainstSchema(HttpInputMessage inputMessage, + JsonSchemaResourceLocationRegistry schemaRegistry) { + null + } + private static Json extractJsonPayload(List origInput) { Json.read(new ByteArrayInputStream(origInput[0]).getText()) } diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataResolverSchemaValidatingControllerAdvice.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataResolversSchemaValidatingControllerAdvice.groovy similarity index 94% rename from backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataResolverSchemaValidatingControllerAdvice.groovy rename to backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataResolversSchemaValidatingControllerAdvice.groovy index 6ecbef95a..1c051a9c0 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataResolverSchemaValidatingControllerAdvice.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataResolversSchemaValidatingControllerAdvice.groovy @@ -21,7 +21,7 @@ import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.LowLevelJsonSche * @author Dmitriy Kopylenko */ @ControllerAdvice -class MetadataResolverSchemaValidatingControllerAdvice extends RequestBodyAdviceAdapter { +class MetadataResolversSchemaValidatingControllerAdvice extends RequestBodyAdviceAdapter { @Autowired JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry From bcced870a6e64976ec9d4207427f3da413424636 Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Fri, 23 Aug 2019 11:29:01 -0400 Subject: [PATCH 06/19] WIP --- .../admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy | 5 ----- 1 file changed, 5 deletions(-) diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy index bea82ba26..dd46760a5 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy @@ -44,11 +44,6 @@ class LowLevelJsonSchemaValidator { doValidate(origInput, Json.schema(schemaUri), json) } - static HttpInputMessage validateMetadataFilterTypePayloadAgainstSchema(HttpInputMessage inputMessage, - JsonSchemaResourceLocationRegistry schemaRegistry) { - null - } - private static Json extractJsonPayload(List origInput) { Json.read(new ByteArrayInputStream(origInput[0]).getText()) } From e4fdcc36b57869d01a6f9a7b5ca642e29f11b8dd Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Wed, 11 Sep 2019 15:39:04 -0400 Subject: [PATCH 07/19] SHIBUI-1478 --- .../LowLevelJsonSchemaValidator.groovy | 21 +++++ ...ersSchemaValidatingControllerAdvice.groovy | 42 ++++++++++ ...lerSchemaValidationIntegrationTests.groovy | 78 +++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataFiltersSchemaValidatingControllerAdvice.groovy create mode 100644 backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerSchemaValidationIntegrationTests.groovy diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy index dd46760a5..ada854ac9 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy @@ -4,11 +4,14 @@ import mjson.Json import org.springframework.http.HttpInputMessage import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.dynamicHttpMetadataProviderSchema +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.entityAttributesFiltersSchema import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.filesystemMetadataProviderSchema import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.localDynamicMetadataProviderSchema /** * Currently uses mjson library. + * + * @author Dmitriy Kopylenko */ class LowLevelJsonSchemaValidator { @@ -44,6 +47,24 @@ class LowLevelJsonSchemaValidator { doValidate(origInput, Json.schema(schemaUri), json) } + static HttpInputMessage validateMetadataFilterTypePayloadAgainstSchema(HttpInputMessage inputMessage, + JsonSchemaResourceLocationRegistry schemaRegistry) { + def origInput = [inputMessage.body.bytes, inputMessage.headers] + def json = extractJsonPayload(origInput) + def schemaUri = null + switch (json.asMap()['@type']) { + case 'EntityAttributes': + schemaUri = entityAttributesFiltersSchema(schemaRegistry).uri + break + default: + break + } + if (!schemaUri) { + return newInputMessage(origInput) + } + doValidate(origInput, Json.schema(schemaUri), json) + } + private static Json extractJsonPayload(List origInput) { Json.read(new ByteArrayInputStream(origInput[0]).getText()) } diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataFiltersSchemaValidatingControllerAdvice.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataFiltersSchemaValidatingControllerAdvice.groovy new file mode 100644 index 000000000..08f32f17d --- /dev/null +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataFiltersSchemaValidatingControllerAdvice.groovy @@ -0,0 +1,42 @@ +package edu.internet2.tier.shibboleth.admin.ui.jsonschema + +import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.core.MethodParameter +import org.springframework.http.HttpInputMessage +import org.springframework.http.converter.HttpMessageConverter +import org.springframework.web.bind.annotation.ControllerAdvice +import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter + +import java.lang.reflect.Type + +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.LowLevelJsonSchemaValidator.validateMetadataFilterTypePayloadAgainstSchema +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.LowLevelJsonSchemaValidator.validateMetadataResolverTypePayloadAgainstSchema + +/** + * Controller advice implementation for validating metadata filters payload coming from UI layer + * against pre-defined JSON schema for their respected types. Choosing of the appropriate schema based on incoming + * resolver types is delegated to @{LowLevelJsonSchemaValidator}. + * + * @author Dmitriy Kopylenko + */ +@ControllerAdvice +class MetadataFiltersSchemaValidatingControllerAdvice extends RequestBodyAdviceAdapter { + + @Autowired + JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry + + @Override + boolean supports(MethodParameter methodParameter, Type targetType, Class> converterType) { + targetType.typeName == MetadataFilter.typeName + } + + @Override + HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, + Type targetType, Class> converterType) + throws IOException { + + validateMetadataFilterTypePayloadAgainstSchema(inputMessage, jsonSchemaResourceLocationRegistry) + } +} diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerSchemaValidationIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerSchemaValidationIntegrationTests.groovy new file mode 100644 index 000000000..47c09cd93 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerSchemaValidationIntegrationTests.groovy @@ -0,0 +1,78 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver +import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.web.client.TestRestTemplate +import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.springframework.test.context.ActiveProfiles +import spock.lang.Specification + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles(["no-auth", "dev"]) +class MetadataFiltersControllerSchemaValidationIntegrationTests extends Specification { + + @Autowired + private TestRestTemplate restTemplate + + @Autowired + MetadataResolverRepository metadataResolverRepository + + static RESOURCE_URI = '/api/MetadataResolvers/%s/Filters' + + private HTTP_POST = { body, resourceId -> + this.restTemplate.postForEntity(resourceUriFor(RESOURCE_URI, resourceId), createRequestHttpEntityFor(body), Map) + } + + private static checkJsonValidationIsPerformed = { + assert it.statusCodeValue == 400 + assert it.body.errorMessage.count('Type mistmatch for null') > 0 + assert it.body.errorMessage.count('Type mistmatch for "not-a-boolean"') > 0 + true + } + + def 'POST for EntityAttributesFilter with invalid payload according to schema validation'() { + given: + def resolver = metadataResolverRepository.save(new FileBackedHttpMetadataResolver(name: 'fbmr', backingFile: '/tmp/metadata.xml')) + def postedJsonBody = """ + { + "name" : "EntityAttributes", + "filterEnabled" : "not-a-boolean", + "entityAttributesFilterTarget" : { + "entityAttributesFilterTargetType" : "ENTITY", + "value" : [ "CedewbJJET" ] + }, + "attributeRelease" : [ "9ktPyjjiCn" ], + "relyingPartyOverrides" : { + "signAssertion" : false, + "dontSignResponse" : true, + "turnOffEncryption" : true, + "useSha" : false, + "ignoreAuthenticationMethod" : false, + "omitNotBefore" : true, + "responderId" : null, + "nameIdFormats" : [ ], + "authenticationMethods" : [ ] + }, + "@type" : "EntityAttributes" + } + """ + + when: + def result = HTTP_POST(postedJsonBody, resolver.resourceId) + + then: + checkJsonValidationIsPerformed(result) + + } + + private static HttpEntity createRequestHttpEntityFor(String jsonBody) { + new HttpEntity(jsonBody, ['Content-Type': 'application/json'] as HttpHeaders) + } + + private static resourceUriFor(String uriTemplate, String resourceId) { + String.format(uriTemplate, resourceId) + } +} From 91d6abb731f3bf3c8894aa383da9fe1980aceb30 Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Wed, 11 Sep 2019 16:30:30 -0400 Subject: [PATCH 08/19] SHIBUI-1478 --- .../LowLevelJsonSchemaValidator.groovy | 4 +++ ...lerSchemaValidationIntegrationTests.groovy | 26 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy index ada854ac9..bcf25b4d9 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/LowLevelJsonSchemaValidator.groovy @@ -7,6 +7,7 @@ import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocati import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.entityAttributesFiltersSchema import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.filesystemMetadataProviderSchema import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.localDynamicMetadataProviderSchema +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.nameIdFormatFilterSchema /** * Currently uses mjson library. @@ -56,6 +57,9 @@ class LowLevelJsonSchemaValidator { case 'EntityAttributes': schemaUri = entityAttributesFiltersSchema(schemaRegistry).uri break + case 'NameIDFormat': + schemaUri = nameIdFormatFilterSchema(schemaRegistry).uri + break default: break } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerSchemaValidationIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerSchemaValidationIntegrationTests.groovy index 47c09cd93..32169b063 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerSchemaValidationIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerSchemaValidationIntegrationTests.groovy @@ -1,5 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.controller +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository import org.springframework.beans.factory.annotation.Autowired @@ -35,7 +36,7 @@ class MetadataFiltersControllerSchemaValidationIntegrationTests extends Specific def 'POST for EntityAttributesFilter with invalid payload according to schema validation'() { given: - def resolver = metadataResolverRepository.save(new FileBackedHttpMetadataResolver(name: 'fbmr', backingFile: '/tmp/metadata.xml')) + def resolver = metadataResolverRepository.save(new DynamicHttpMetadataResolver(name: 'dmr')) def postedJsonBody = """ { "name" : "EntityAttributes", @@ -68,6 +69,29 @@ class MetadataFiltersControllerSchemaValidationIntegrationTests extends Specific } + def 'POST for NameIdFormatFilter with invalid payload according to schema validation'() { + given: + def resolver = metadataResolverRepository.save(new FileBackedHttpMetadataResolver(name: 'fbmr', backingFile: '/tmp/metadata.xml')) + def postedJsonBody = """ + { + "name" : null, + "filterEnabled" : "not-a-boolean", + "removeExistingFormats" : false, + "formats" : [ "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" ], + "nameIdFormatFilterTarget" : { + "nameIdFormatFilterTargetType" : "ENTITY", + "value" : [ "https://sp1.example.org" ] + }, + "@type" : "NameIDFormat" + }""" + + when: + def result = HTTP_POST(postedJsonBody, resolver.resourceId) + + then: + checkJsonValidationIsPerformed(result) + } + private static HttpEntity createRequestHttpEntityFor(String jsonBody) { new HttpEntity(jsonBody, ['Content-Type': 'application/json'] as HttpHeaders) } From 5f2dbeb4900d4d4dbdae74ba28d3c379a5ccf4ff Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Wed, 11 Sep 2019 16:39:48 -0400 Subject: [PATCH 09/19] Cleanup --- .../MetadataFiltersSchemaValidatingControllerAdvice.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataFiltersSchemaValidatingControllerAdvice.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataFiltersSchemaValidatingControllerAdvice.groovy index 08f32f17d..160a63360 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataFiltersSchemaValidatingControllerAdvice.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataFiltersSchemaValidatingControllerAdvice.groovy @@ -12,7 +12,6 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAd import java.lang.reflect.Type import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.LowLevelJsonSchemaValidator.validateMetadataFilterTypePayloadAgainstSchema -import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.LowLevelJsonSchemaValidator.validateMetadataResolverTypePayloadAgainstSchema /** * Controller advice implementation for validating metadata filters payload coming from UI layer From 1f1ec429ccb84bd46592edb6903f536abda99298 Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Wed, 11 Sep 2019 16:40:19 -0400 Subject: [PATCH 10/19] Cleanup --- .../MetadataFiltersSchemaValidatingControllerAdvice.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataFiltersSchemaValidatingControllerAdvice.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataFiltersSchemaValidatingControllerAdvice.groovy index 160a63360..3608da194 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataFiltersSchemaValidatingControllerAdvice.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/MetadataFiltersSchemaValidatingControllerAdvice.groovy @@ -1,7 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.jsonschema import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter -import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver import org.springframework.beans.factory.annotation.Autowired import org.springframework.core.MethodParameter import org.springframework.http.HttpInputMessage From 1f3cf5394b0077539e62fccf8e9c98d9bfdddc15 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 24 Sep 2019 08:57:15 -0700 Subject: [PATCH 11/19] SHIBUI-1478 Updating schema --- ...dynamic-http-metadata-provider.schema.json | 35 +++++++--- .../file-system-metadata-provider.schema.json | 11 ++-- ...ocal-dynamic-metadata-provider.schema.json | 9 +-- .../model/dynamic-http.provider.form.ts | 8 +-- ui/src/app/schema-form/registry.ts | 2 + ui/src/app/schema-form/schema-form.module.ts | 2 + .../widget/number/float.component.html | 25 ++++++++ .../widget/number/float.component.ts | 21 ++++++ .../provider/filebacked-http.schema.json | 64 +++++++++++-------- 9 files changed, 128 insertions(+), 49 deletions(-) create mode 100644 ui/src/app/schema-form/widget/number/float.component.html create mode 100644 ui/src/app/schema-form/widget/number/float.component.ts diff --git a/backend/src/main/resources/dynamic-http-metadata-provider.schema.json b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json index cc0696e87..a5898dbdb 100644 --- a/backend/src/main/resources/dynamic-http-metadata-provider.schema.json +++ b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json @@ -149,12 +149,13 @@ "description": "tooltip.refresh-delay-factor", "type": "number", "widget": { - "id": "string", - "help": "message.real-number" + "id": "float", + "help": "message.real-number", + "step": 0.01 }, "placeholder": "label.real-number", - "default": "", - "pattern": "^(?:([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)|)$" + "minimum": 0.01, + "maximum": 1 }, "minCacheDuration": { "title": "label.min-cache-duration", @@ -579,14 +580,20 @@ "title": "", "description": "", "type": "array", - "properties": { - "RequiredValidUntil": { + "additionalItems": true, + "items": [ + { "title": "label.required-valid-until", "type": "object", "widget": { "id": "fieldset" }, "properties": { + "@type": { + "type": "string", + "default": "RequiredValidUntil", + "widget": "hidden" + }, "maxValidityInterval": { "title": "label.max-validity-interval", "description": "tooltip.max-validity-interval", @@ -611,13 +618,18 @@ } } }, - "SignatureValidation": { + { "title": "label.signature-validation-filter", "type": "object", "widget": { "id": "fieldset" }, "properties": { + "@type": { + "type": "string", + "default": "SignatureValidation", + "widget": "hidden" + }, "requireSignedRoot": { "title": "label.require-signed-root", "description": "tooltip.require-signed-root", @@ -654,13 +666,18 @@ } ] }, - "EntityRoleWhiteList": { + { "title": "label.entity-role-whitelist", "type": "object", "widget": { "id": "fieldset" }, "properties": { + "@type": { + "type": "string", + "default": "EntityRoleWhiteList", + "widget": "hidden" + }, "retainedRoles": { "title": "label.retained-roles", "description": "tooltip.retained-roles", @@ -700,7 +717,7 @@ } } } - } + ] } } } diff --git a/backend/src/main/resources/file-system-metadata-provider.schema.json b/backend/src/main/resources/file-system-metadata-provider.schema.json index af2f8af0a..834e602f2 100644 --- a/backend/src/main/resources/file-system-metadata-provider.schema.json +++ b/backend/src/main/resources/file-system-metadata-provider.schema.json @@ -128,14 +128,15 @@ "refreshDelayFactor": { "title": "label.refresh-delay-factor", "description": "tooltip.refresh-delay-factor", - "type": "string", + "type": "number", "widget": { - "id": "string", - "help": "message.real-number" + "id": "float", + "help": "message.real-number", + "step": 0.01 }, "placeholder": "label.real-number", - "default": "", - "pattern": "^(?:([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)|)$" + "minimum": 0.01, + "maximum": 1 } } } diff --git a/backend/src/main/resources/local-dynamic-metadata-provider.schema.json b/backend/src/main/resources/local-dynamic-metadata-provider.schema.json index 72d846023..d2b8a89da 100644 --- a/backend/src/main/resources/local-dynamic-metadata-provider.schema.json +++ b/backend/src/main/resources/local-dynamic-metadata-provider.schema.json @@ -63,12 +63,13 @@ "description": "tooltip.refresh-delay-factor", "type": "number", "widget": { - "id": "string", - "help": "message.real-number" + "id": "float", + "help": "message.real-number", + "step": 0.01 }, "placeholder": "label.real-number", - "default": "", - "pattern": "^(?:([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)|)$" + "minimum": 0.01, + "maximum": 1 }, "minCacheDuration": { "title": "label.min-cache-duration", diff --git a/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts b/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts index 9ef3bf2bb..80de5ab17 100644 --- a/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts +++ b/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts @@ -97,9 +97,7 @@ export const DynamicHttpMetadataProviderWizard: Wizard + + {{schema.description}} + + diff --git a/ui/src/app/schema-form/widget/number/float.component.ts b/ui/src/app/schema-form/widget/number/float.component.ts new file mode 100644 index 000000000..3278829ba --- /dev/null +++ b/ui/src/app/schema-form/widget/number/float.component.ts @@ -0,0 +1,21 @@ +import { + Component, +} from '@angular/core'; +import { IntegerWidget } from 'ngx-schema-form'; +import { SchemaService } from '../../service/schema.service'; + +@Component({ + selector: 'float-component', + templateUrl: `./float.component.html` +}) +export class CustomFloatComponent extends IntegerWidget { + constructor( + private widgetService: SchemaService + ) { + super(); + } + + get required(): boolean { + return this.widgetService.isRequired(this.formProperty); + } +} diff --git a/ui/src/assets/schema/provider/filebacked-http.schema.json b/ui/src/assets/schema/provider/filebacked-http.schema.json index 0422ffbd2..b093095a9 100644 --- a/ui/src/assets/schema/provider/filebacked-http.schema.json +++ b/ui/src/assets/schema/provider/filebacked-http.schema.json @@ -498,31 +498,36 @@ "refreshDelayFactor": { "title": "label.refresh-delay-factor", "description": "tooltip.refresh-delay-factor", - "type": "string", + "type": "number", "widget": { - "id": "string", - "help": "message.real-number" + "id": "float", + "help": "message.real-number", + "step": 0.01 }, "placeholder": "label.real-number", - "minimum": 0, - "maximum": 1, - "default": "", - "pattern": "^(?:([0]*(\\.[0-9]+)?|[0]*\\.[0-9]*[1-9][0-9]*)|)$" + "minimum": 0.01, + "maximum": 1 } } }, "metadataFilters": { "title": "", "description": "", - "type": "object", - "properties": { - "RequiredValidUntil": { + "type": "array", + "additionalItems": true, + "items": [ + { "title": "label.required-valid-until", "type": "object", "widget": { "id": "fieldset" }, "properties": { + "@type": { + "type": "string", + "default": "RequiredValidUntil", + "widget": "hidden" + }, "maxValidityInterval": { "title": "label.max-validity-interval", "description": "tooltip.max-validity-interval", @@ -532,10 +537,14 @@ "id": "datalist", "data": [ "PT0S", - "P14D", - "P7D", - "P1D", - "PT12H" + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" ] }, "default": null, @@ -543,13 +552,18 @@ } } }, - "SignatureValidation": { + { "title": "label.signature-validation-filter", "type": "object", "widget": { "id": "fieldset" }, "properties": { + "@type": { + "type": "string", + "default": "SignatureValidation", + "widget": "hidden" + }, "requireSignedRoot": { "title": "label.require-signed-root", "description": "tooltip.require-signed-root", @@ -559,8 +573,7 @@ "certificateFile": { "title": "label.certificate-file", "description": "tooltip.certificate-file", - "type": "string", - "widget": "textline" + "type": "string" } }, "anyOf": [ @@ -570,10 +583,6 @@ "enum": [ true ] - }, - "certificateFile": { - "minLength": 1, - "type": "string" } }, "required": [ @@ -591,13 +600,18 @@ } ] }, - "EntityRoleWhiteList": { + { "title": "label.entity-role-whitelist", "type": "object", "widget": { "id": "fieldset" }, "properties": { + "@type": { + "type": "string", + "default": "EntityRoleWhiteList", + "widget": "hidden" + }, "retainedRoles": { "title": "label.retained-roles", "description": "tooltip.retained-roles", @@ -610,13 +624,13 @@ "oneOf": [ { "enum": [ - "md:SPSSODescriptor" + "SPSSODescriptor" ], "description": "value.spdescriptor" }, { "enum": [ - "md:AttributeAuthorityDescriptor" + "AttributeAuthorityDescriptor" ], "description": "value.attr-auth-descriptor" } @@ -637,7 +651,7 @@ } } } - } + ] } } } \ No newline at end of file From 2649e8dfed851d6903ce8f5d8bd6cf601eddf8df Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 24 Sep 2019 09:32:33 -0700 Subject: [PATCH 12/19] SHIBUI-1502 Removed empty changes --- .../metadata-configuration.component.html | 7 +----- .../metadata-comparison.component.ts | 2 -- .../metadata/configuration/reducer/index.ts | 24 +++++++++++++------ 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/ui/src/app/metadata/configuration/component/metadata-configuration.component.html b/ui/src/app/metadata/configuration/component/metadata-configuration.component.html index e0685e743..daa0951a5 100644 --- a/ui/src/app/metadata/configuration/component/metadata-configuration.component.html +++ b/ui/src/app/metadata/configuration/component/metadata-configuration.component.html @@ -1,6 +1,6 @@
-
+

(preview)="onPreview($event)"> - -
- No Changes -
-

diff --git a/ui/src/app/metadata/configuration/container/metadata-comparison.component.ts b/ui/src/app/metadata/configuration/container/metadata-comparison.component.ts index 06f821633..e080558e1 100644 --- a/ui/src/app/metadata/configuration/container/metadata-comparison.component.ts +++ b/ui/src/app/metadata/configuration/container/metadata-comparison.component.ts @@ -39,8 +39,6 @@ export class MetadataComparisonComponent implements OnDestroy { this.numVersions$ = this.store.select(getComparisonConfigurationCount); this.type$ = this.store.select(fromReducer.getConfigurationModelType); - this.versions$.subscribe(console.log); - this.sub = this.limiter.pipe( withLatestFrom(this.limited$), map(([compare, limit]) => new ViewChanged(!limit)) diff --git a/ui/src/app/metadata/configuration/reducer/index.ts b/ui/src/app/metadata/configuration/reducer/index.ts index 38087a034..2b67b7a7a 100644 --- a/ui/src/app/metadata/configuration/reducer/index.ts +++ b/ui/src/app/metadata/configuration/reducer/index.ts @@ -145,6 +145,14 @@ export const getConfigurationModelNameFn = export const getConfigurationModelTypeFn = (config: Metadata) => config ? ('@type' in config) ? config['@type'] : 'resolver' : null; +export const filterPluginTypes = ['RequiredValidUntil', 'SignatureValidation', 'EntityRoleWhiteList']; +export const isAdditionalFilter = (type) => filterPluginTypes.indexOf(type) === -1; + +export const getVersionModelFiltersFn = + (model, kind) => kind === 'provider' ? + model.metadataFilters.filter(filter => isAdditionalFilter(filter['@type'])) : + null; + // Version History export const getHistoryState = createSelector(getState, getHistoryStateFn); @@ -178,8 +186,16 @@ export const getCompareState = createSelector(getState, getCompareStateFn); export const getComparisonLoading = createSelector(getCompareState, fromCompare.getComparisonLoading); export const getComparisonModels = createSelector(getCompareState, fromCompare.getVersionModels); export const getComparisonModelsLoaded = createSelector(getCompareState, fromCompare.getVersionModelsLoaded); + +export const getComparisonModelsFilteredFn = (models) => models.map((model) => ({ + ...model, + metadataFilters: getVersionModelFiltersFn(model, model.type) +})); + +export const getComparisonModelsFiltered = createSelector(getComparisonModels, getComparisonModelsFilteredFn); + export const getComparisonConfigurations = createSelector( - getComparisonModels, + getComparisonModelsFiltered, getConfigurationDefinition, getConfigurationSchema, getConfigurationSectionsFn @@ -228,13 +244,7 @@ export const getLimitedComparisonConfigurations = createSelector( export const getRestoreState = createSelector(getState, getRestoreStateFn); -export const filterPluginTypes = ['RequiredValidUntil', 'SignatureValidation', 'EntityRoleWhiteList']; -export const isAdditionalFilter = (type) => filterPluginTypes.indexOf(type) === -1; -export const getVersionModelFiltersFn = - (model, kind) => kind === 'provider' ? - model.metadataFilters.filter(filter => isAdditionalFilter(filter['@type'])) : - null; export const getVersionState = createSelector(getState, getVersionStateFn); From 4cabaf7667e15ddff1b4bc3aa2a81b394b328465 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 24 Sep 2019 09:53:02 -0700 Subject: [PATCH 13/19] SHIBUI-1502 added zero state --- .../metadata-configuration.component.html | 77 ++++++++++--------- .../metadata-configuration.component.ts | 11 ++- 2 files changed, 50 insertions(+), 38 deletions(-) diff --git a/ui/src/app/metadata/configuration/component/metadata-configuration.component.html b/ui/src/app/metadata/configuration/component/metadata-configuration.component.html index daa0951a5..4c57ca75a 100644 --- a/ui/src/app/metadata/configuration/component/metadata-configuration.component.html +++ b/ui/src/app/metadata/configuration/component/metadata-configuration.component.html @@ -1,39 +1,46 @@ -
-
-
-
-

- - 0{{ i + 1 }} - - {{ section.label | translate }} -

-
- -
-
-
- -
- Option - - Value - {{ date | date:DATE_FORMAT }} - +
+ +
+
+
+

+ + 0{{ i + 1 }} + + {{ section.label | translate }} +

+
+
- - - +
+
+ +
+ Option + + Value + {{ date | date:DATE_FORMAT }} + +
+ + +
+
+
+
+ + -
+
\ No newline at end of file diff --git a/ui/src/app/metadata/configuration/component/metadata-configuration.component.ts b/ui/src/app/metadata/configuration/component/metadata-configuration.component.ts index 1b04ba636..e2d75c4da 100644 --- a/ui/src/app/metadata/configuration/component/metadata-configuration.component.ts +++ b/ui/src/app/metadata/configuration/component/metadata-configuration.component.ts @@ -1,4 +1,4 @@ -import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter } from '@angular/core'; +import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, OnChanges } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; import { MetadataConfiguration } from '../model/metadata-configuration'; import { Metadata } from '../../domain/domain.type'; @@ -6,11 +6,10 @@ import { CONFIG_DATE_FORMAT } from '../configuration.values'; @Component({ selector: 'metadata-configuration', - changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: './metadata-configuration.component.html', styleUrls: ['./metadata-configuration.component.scss'] }) -export class MetadataConfigurationComponent { +export class MetadataConfigurationComponent implements OnChanges { @Input() configuration: MetadataConfiguration; @Input() definition: any; @Input() entity: Metadata; @@ -19,6 +18,8 @@ export class MetadataConfigurationComponent { @Output() preview: EventEmitter = new EventEmitter(); + zero = false; + DATE_FORMAT = CONFIG_DATE_FORMAT; constructor( @@ -26,6 +27,10 @@ export class MetadataConfigurationComponent { private activatedRoute: ActivatedRoute ) {} + ngOnChanges(): void { + this.zero = this.configuration.sections.some(s => !s.properties.length); + } + edit(id: string): void { this.router.navigate(['../', 'edit', id], { relativeTo: this.activatedRoute.parent }); } From 81bfaeccb2b18157a9d9245b51dc8d53446c2846 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Wed, 25 Sep 2019 10:52:02 -0700 Subject: [PATCH 14/19] SHIBUI-1478 Updated schemas for UI --- ...dynamic-http-metadata-provider.schema.json | 24 +- .../file-system-metadata-provider.schema.json | 4 +- ...ebacked-http-metadata-provider.schema.json | 653 ++++++++++++++++++ ...ocal-dynamic-metadata-provider.schema.json | 4 +- .../provider/filebacked-http.schema.json | 32 +- 5 files changed, 677 insertions(+), 40 deletions(-) create mode 100644 backend/src/main/resources/filebacked-http-metadata-provider.schema.json diff --git a/backend/src/main/resources/dynamic-http-metadata-provider.schema.json b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json index a5898dbdb..28758fee6 100644 --- a/backend/src/main/resources/dynamic-http-metadata-provider.schema.json +++ b/backend/src/main/resources/dynamic-http-metadata-provider.schema.json @@ -4,7 +4,6 @@ "name", "@type", "xmlId", - "metadataURL", "metadataRequestURLConstructionScheme" ], "properties": { @@ -154,8 +153,8 @@ "step": 0.01 }, "placeholder": "label.real-number", - "minimum": 0.01, - "maximum": 1 + "minimum": 0.001, + "maximum": 0.999 }, "minCacheDuration": { "title": "label.min-cache-duration", @@ -577,23 +576,20 @@ } }, "metadataFilters": { + "$id": "metadataFilters", "title": "", "description": "", "type": "array", "additionalItems": true, "items": [ { + "$id": "RequiredValidUntil", "title": "label.required-valid-until", "type": "object", "widget": { "id": "fieldset" }, "properties": { - "@type": { - "type": "string", - "default": "RequiredValidUntil", - "widget": "hidden" - }, "maxValidityInterval": { "title": "label.max-validity-interval", "description": "tooltip.max-validity-interval", @@ -619,17 +615,13 @@ } }, { + "$id": "SignatureValidation", "title": "label.signature-validation-filter", "type": "object", "widget": { "id": "fieldset" }, "properties": { - "@type": { - "type": "string", - "default": "SignatureValidation", - "widget": "hidden" - }, "requireSignedRoot": { "title": "label.require-signed-root", "description": "tooltip.require-signed-root", @@ -667,17 +659,13 @@ ] }, { + "$id": "EntityRoleWhiteList", "title": "label.entity-role-whitelist", "type": "object", "widget": { "id": "fieldset" }, "properties": { - "@type": { - "type": "string", - "default": "EntityRoleWhiteList", - "widget": "hidden" - }, "retainedRoles": { "title": "label.retained-roles", "description": "tooltip.retained-roles", diff --git a/backend/src/main/resources/file-system-metadata-provider.schema.json b/backend/src/main/resources/file-system-metadata-provider.schema.json index 834e602f2..f6037c79b 100644 --- a/backend/src/main/resources/file-system-metadata-provider.schema.json +++ b/backend/src/main/resources/file-system-metadata-provider.schema.json @@ -135,8 +135,8 @@ "step": 0.01 }, "placeholder": "label.real-number", - "minimum": 0.01, - "maximum": 1 + "minimum": 0.001, + "maximum": 0.999 } } } diff --git a/backend/src/main/resources/filebacked-http-metadata-provider.schema.json b/backend/src/main/resources/filebacked-http-metadata-provider.schema.json new file mode 100644 index 000000000..505fe6ebd --- /dev/null +++ b/backend/src/main/resources/filebacked-http-metadata-provider.schema.json @@ -0,0 +1,653 @@ +{ + "type": "object", + "order": [ + "name", + "@type", + "enabled", + "xmlId", + "metadataURL", + "initializeFromBackupFile", + "backingFile", + "backupFileInitNextRefreshDelay", + "requireValidMetadata", + "failFastInitialization", + "useDefaultPredicateRegistry", + "satisfyAnyPredicates", + "httpMetadataResolverAttributes", + "reloadableMetadataResolverAttributes", + "metadataFilters" + ], + "required": [ + "name", + "@type", + "xmlId", + "metadataURL", + "backingFile", + "backupFileInitNextRefreshDelay" + ], + "properties": { + "name": { + "title": "label.metadata-provider-name-dashboard-display-only", + "description": "tooltip.metadata-provider-name-dashboard-display-only", + "type": "string", + "widget": { + "id": "string", + "help": "message.must-be-unique" + } + }, + "@type": { + "title": "label.metadata-provider-type", + "description": "tooltip.metadata-provider-type", + "placeholder": "label.select-metadata-type", + "type": "string", + "readOnly": true, + "widget": { + "id": "select", + "disabled": true + }, + "oneOf": [ + { + "enum": [ + "FileBackedHttpMetadataResolver" + ], + "description": "value.file-backed-http-metadata-provider" + } + ] + }, + "enabled": { + "title": "label.enable-service", + "description": "tooltip.enable-service", + "type": "boolean", + "default": false + }, + "xmlId": { + "title": "label.xml-id", + "description": "tooltip.xml-id", + "type": "string", + "default": "", + "minLength": 1 + }, + "metadataURL": { + "title": "label.metadata-url", + "description": "tooltip.metadata-url", + "type": "string", + "default": "", + "minLength": 1 + }, + "initializeFromBackupFile": { + "title": "label.init-from-backup", + "description": "tooltip.init-from-backup", + "type": "boolean", + "widget": { + "id": "boolean-radio" + }, + "oneOf": [ + { + "enum": [ + true + ], + "description": "value.true" + }, + { + "enum": [ + false + ], + "description": "value.false" + } + ], + "default": true + }, + "backingFile": { + "title": "label.backing-file", + "description": "tooltip.backing-file", + "type": "string", + "default": "" + }, + "backupFileInitNextRefreshDelay": { + "title": "label.backup-file-init-refresh-delay", + "description": "tooltip.backup-file-init-refresh-delay", + "type": "string", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "requireValidMetadata": { + "title": "label.require-valid-metadata", + "description": "tooltip.require-valid-metadata", + "type": "boolean", + "widget": { + "id": "boolean-radio" + }, + "oneOf": [ + { + "enum": [ + true + ], + "description": "value.true" + }, + { + "enum": [ + false + ], + "description": "value.false" + } + ], + "default": true + }, + "failFastInitialization": { + "title": "label.fail-fast-init", + "description": "tooltip.fail-fast-init", + "type": "boolean", + "widget": { + "id": "boolean-radio" + }, + "oneOf": [ + { + "enum": [ + true + ], + "description": "value.true" + }, + { + "enum": [ + false + ], + "description": "value.false" + } + ], + "default": true + }, + "useDefaultPredicateRegistry": { + "title": "label.use-default-predicate-reg", + "description": "tooltip.use-default-predicate-reg", + "type": "boolean", + "widget": { + "id": "boolean-radio" + }, + "oneOf": [ + { + "enum": [ + true + ], + "description": "value.true" + }, + { + "enum": [ + false + ], + "description": "value.false" + } + ], + "default": true + }, + "satisfyAnyPredicates": { + "$id": "satisfyAnyPredicates", + "title": "label.satisfy-any-predicates", + "description": "tooltip.satisfy-any-predicates", + "type": "boolean", + "widget": { + "id": "boolean-radio" + }, + "oneOf": [ + { + "enum": [ + true + ], + "description": "value.true" + }, + { + "enum": [ + false + ], + "description": "value.false" + } + ], + "default": false + }, + "httpMetadataResolverAttributes": { + "$id": "httpMetadataResolverAttributes", + "order": [], + "type": "object", + "fieldsets": [ + { + "title": "label.http-connection-attributes", + "type": "section", + "fields": [ + "connectionRequestTimeout", + "connectionTimeout", + "socketTimeout" + ] + }, + { + "title": "label.http-security-attributes", + "type": "section", + "class": "col-12", + "fields": [ + "disregardTLSCertificate" + ] + }, + { + "title": "label.http-proxy-attributes", + "type": "section", + "class": "col-12", + "fields": [ + "proxyHost", + "proxyPort", + "proxyUser", + "proxyPassword" + ] + }, + { + "title": "label.http-caching-attributes", + "type": "section", + "class": "col-12", + "fields": [ + "httpCaching", + "httpCacheDirectory", + "httpMaxCacheEntries", + "httpMaxCacheEntrySize" + ] + }, + { + "title": "", + "type": "hidden", + "class": "col-12", + "fields": [ + "tlsTrustEngineRef", + "httpClientSecurityParametersRef", + "httpClientRef" + ] + } + ], + "properties": { + "httpClientRef": { + "type": "string", + "title": "", + "description": "", + "placeholder": "", + "widget": "hidden" + }, + "connectionRequestTimeout": { + "type": "string", + "title": "label.connection-request-timeout", + "description": "tooltip.connection-request-timeout", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "connectionTimeout": { + "type": "string", + "title": "label.connection-timeout", + "description": "tooltip.connection-timeout", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "socketTimeout": { + "type": "string", + "title": "label.socket-timeout", + "description": "tooltip.socket-timeout", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "disregardTLSCertificate": { + "type": "boolean", + "title": "label.disregard-tls-cert", + "description": "tooltip.disregard-tls-cert", + "widget": { + "id": "boolean-radio" + }, + "oneOf": [ + { + "enum": [ + true + ], + "description": "True" + }, + { + "enum": [ + false + ], + "description": "False" + } + ] + }, + "tlsTrustEngineRef": { + "type": "string", + "title": "", + "description": "", + "placeholder": "", + "widget": "hidden" + }, + "httpClientSecurityParametersRef": { + "type": "string", + "title": "", + "description": "", + "placeholder": "", + "widget": "hidden" + }, + "proxyHost": { + "type": "string", + "title": "label.proxy-host", + "description": "tooltip.proxy-host", + "placeholder": "" + }, + "proxyPort": { + "type": "string", + "title": "label.proxy-port", + "description": "tooltip.proxy-port", + "placeholder": "" + }, + "proxyUser": { + "type": "string", + "title": "label.proxy-user", + "description": "tooltip.proxy-user", + "placeholder": "" + }, + "proxyPassword": { + "type": "string", + "title": "label.proxy-password", + "description": "tooltip.proxy-password", + "placeholder": "" + }, + "httpCaching": { + "type": "string", + "title": "label.http-caching", + "description": "tooltip.http-caching", + "placeholder": "label.select-caching-type", + "widget": { + "id": "select" + }, + "oneOf": [ + { + "enum": [ + "none" + ], + "description": "value.none" + }, + { + "enum": [ + "file" + ], + "description": "value.file" + }, + { + "enum": [ + "memory" + ], + "description": "value.memory" + } + ] + }, + "httpCacheDirectory": { + "type": "string", + "title": "label.http-caching-directory", + "description": "tooltip.http-caching-directory", + "placeholder": "" + }, + "httpMaxCacheEntries": { + "type": "integer", + "title": "label.http-max-cache-entries", + "description": "tooltip.http-max-cache-entries", + "placeholder": "", + "minimum": 0 + }, + "httpMaxCacheEntrySize": { + "type": "integer", + "title": "label.max-cache-entry-size", + "description": "tooltip.max-cache-entry-size", + "placeholder": "", + "minimum": 0 + } + } + }, + "reloadableMetadataResolverAttributes": { + "$id": "reloadableMetadataResolverAttributes", + "type": "object", + "properties": { + "minRefreshDelay": { + "title": "label.min-refresh-delay", + "description": "tooltip.min-refresh-delay", + "type": "string", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "maxRefreshDelay": { + "title": "label.max-refresh-delay", + "description": "tooltip.max-refresh-delay", + "type": "string", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "refreshDelayFactor": { + "title": "label.refresh-delay-factor", + "description": "tooltip.refresh-delay-factor", + "type": "number", + "widget": { + "id": "float", + "help": "message.real-number", + "step": 0.01 + }, + "placeholder": "label.real-number", + "minimum": 0.001, + "maximum": 0.999 + } + } + }, + "metadataFilters": { + "$id": "metadataFilters", + "title": "", + "description": "", + "type": "array", + "additionalItems": true, + "items": [ + { + "$id": "RequiredValidUntil", + "title": "label.required-valid-until", + "type": "object", + "widget": { + "id": "fieldset" + }, + "properties": { + "maxValidityInterval": { + "title": "label.max-validity-interval", + "description": "tooltip.max-validity-interval", + "type": "string", + "placeholder": "label.duration", + "widget": { + "id": "datalist", + "data": [ + "PT0S", + "PT30S", + "PT1M", + "PT10M", + "PT30M", + "PT1H", + "PT4H", + "PT12H", + "PT24H" + ] + }, + "default": null, + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + } + } + }, + { + "$id": "SignatureValidation", + "title": "label.signature-validation-filter", + "type": "object", + "widget": { + "id": "fieldset" + }, + "properties": { + "requireSignedRoot": { + "title": "label.require-signed-root", + "description": "tooltip.require-signed-root", + "type": "boolean", + "default": true + }, + "certificateFile": { + "title": "label.certificate-file", + "description": "tooltip.certificate-file", + "type": "string" + } + }, + "anyOf": [ + { + "properties": { + "requireSignedRoot": { + "enum": [ + true + ] + } + }, + "required": [ + "certificateFile" + ] + }, + { + "properties": { + "requireSignedRoot": { + "enum": [ + false + ] + } + } + } + ] + }, + { + "$id": "EntityRoleWhiteList", + "title": "label.entity-role-whitelist", + "type": "object", + "widget": { + "id": "fieldset" + }, + "properties": { + "retainedRoles": { + "title": "label.retained-roles", + "description": "tooltip.retained-roles", + "type": "array", + "items": { + "widget": { + "id": "select" + }, + "type": "string", + "oneOf": [ + { + "enum": [ + "SPSSODescriptor" + ], + "description": "value.spdescriptor" + }, + { + "enum": [ + "AttributeAuthorityDescriptor" + ], + "description": "value.attr-auth-descriptor" + } + ] + } + }, + "removeRolelessEntityDescriptors": { + "title": "label.remove-roleless-entity-descriptors", + "description": "tooltip.remove-roleless-entity-descriptors", + "type": "boolean", + "default": true + }, + "removeEmptyEntitiesDescriptors": { + "title": "label.remove-empty-entities-descriptors", + "description": "tooltip.remove-empty-entities-descriptors", + "type": "boolean", + "default": true + } + } + } + ] + } + } +} \ No newline at end of file diff --git a/backend/src/main/resources/local-dynamic-metadata-provider.schema.json b/backend/src/main/resources/local-dynamic-metadata-provider.schema.json index d2b8a89da..c7cfeb360 100644 --- a/backend/src/main/resources/local-dynamic-metadata-provider.schema.json +++ b/backend/src/main/resources/local-dynamic-metadata-provider.schema.json @@ -68,8 +68,8 @@ "step": 0.01 }, "placeholder": "label.real-number", - "minimum": 0.01, - "maximum": 1 + "minimum": 0.001, + "maximum": 0.999 }, "minCacheDuration": { "title": "label.min-cache-duration", diff --git a/ui/src/assets/schema/provider/filebacked-http.schema.json b/ui/src/assets/schema/provider/filebacked-http.schema.json index b093095a9..505fe6ebd 100644 --- a/ui/src/assets/schema/provider/filebacked-http.schema.json +++ b/ui/src/assets/schema/provider/filebacked-http.schema.json @@ -3,6 +3,7 @@ "order": [ "name", "@type", + "enabled", "xmlId", "metadataURL", "initializeFromBackupFile", @@ -11,7 +12,10 @@ "requireValidMetadata", "failFastInitialization", "useDefaultPredicateRegistry", - "satisfyAnyPredicates" + "satisfyAnyPredicates", + "httpMetadataResolverAttributes", + "reloadableMetadataResolverAttributes", + "metadataFilters" ], "required": [ "name", @@ -190,6 +194,7 @@ "default": true }, "satisfyAnyPredicates": { + "$id": "satisfyAnyPredicates", "title": "label.satisfy-any-predicates", "description": "tooltip.satisfy-any-predicates", "type": "boolean", @@ -213,6 +218,7 @@ "default": false }, "httpMetadataResolverAttributes": { + "$id": "httpMetadataResolverAttributes", "order": [], "type": "object", "fieldsets": [ @@ -449,6 +455,7 @@ } }, "reloadableMetadataResolverAttributes": { + "$id": "reloadableMetadataResolverAttributes", "type": "object", "properties": { "minRefreshDelay": { @@ -505,29 +512,26 @@ "step": 0.01 }, "placeholder": "label.real-number", - "minimum": 0.01, - "maximum": 1 + "minimum": 0.001, + "maximum": 0.999 } } }, "metadataFilters": { + "$id": "metadataFilters", "title": "", "description": "", "type": "array", "additionalItems": true, "items": [ { + "$id": "RequiredValidUntil", "title": "label.required-valid-until", "type": "object", "widget": { "id": "fieldset" }, "properties": { - "@type": { - "type": "string", - "default": "RequiredValidUntil", - "widget": "hidden" - }, "maxValidityInterval": { "title": "label.max-validity-interval", "description": "tooltip.max-validity-interval", @@ -553,17 +557,13 @@ } }, { + "$id": "SignatureValidation", "title": "label.signature-validation-filter", "type": "object", "widget": { "id": "fieldset" }, "properties": { - "@type": { - "type": "string", - "default": "SignatureValidation", - "widget": "hidden" - }, "requireSignedRoot": { "title": "label.require-signed-root", "description": "tooltip.require-signed-root", @@ -601,17 +601,13 @@ ] }, { + "$id": "EntityRoleWhiteList", "title": "label.entity-role-whitelist", "type": "object", "widget": { "id": "fieldset" }, "properties": { - "@type": { - "type": "string", - "default": "EntityRoleWhiteList", - "widget": "hidden" - }, "retainedRoles": { "title": "label.retained-roles", "description": "tooltip.retained-roles", From 85f6d2dc3c8e921e2b333090230477ea440c5679 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Wed, 25 Sep 2019 10:53:36 -0700 Subject: [PATCH 15/19] SHIBUI-1478 Updated UI to support new schemas --- .../metadata/configuration/reducer/index.ts | 11 +++- .../model/dynamic-http.provider.form.ts | 12 +---- .../model/file-backed-http.provider.form.ts | 13 ++--- .../model/file-system.provider.form.ts | 10 ---- .../model/local-dynamic.provider.form.ts | 10 ---- .../app/metadata/provider/model/utilities.ts | 25 +++++++++ .../widget/number/float.component.html | 8 +-- .../widget/number/float.component.ts | 51 ++++++++++++++++++- ui/src/app/wizard/model/form-definition.ts | 1 + ui/src/app/wizard/reducer/index.ts | 6 ++- 10 files changed, 99 insertions(+), 48 deletions(-) create mode 100644 ui/src/app/metadata/provider/model/utilities.ts diff --git a/ui/src/app/metadata/configuration/reducer/index.ts b/ui/src/app/metadata/configuration/reducer/index.ts index 38087a034..2a46f79e5 100644 --- a/ui/src/app/metadata/configuration/reducer/index.ts +++ b/ui/src/app/metadata/configuration/reducer/index.ts @@ -51,9 +51,18 @@ export const getConfigurationModelKind = createSelector(getConfigurationState, f export const getConfigurationModelId = createSelector(getConfigurationState, fromConfiguration.getModelId); export const getConfigurationDefinition = createSelector(getConfigurationState, fromConfiguration.getDefinition); -export const getConfigurationSchema = createSelector(getConfigurationState, fromConfiguration.getSchema); +export const getSchema = createSelector(getConfigurationState, fromConfiguration.getSchema); export const getConfigurationXml = createSelector(getConfigurationState, fromConfiguration.getXml); +export const processSchemaFn = (definition, schema) => { + return definition && schema ? + definition.schemaPreprocessor ? + definition.schemaPreprocessor(schema) : schema + : schema; +}; + +export const getConfigurationSchema = createSelector(getConfigurationDefinition, getSchema, processSchemaFn); + export const assignValueToProperties = (models, properties, definition: any): any[] => { return properties.map(prop => { const differences = models.some((model, index, array) => { diff --git a/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts b/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts index 80de5ab17..2096ac38b 100644 --- a/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts +++ b/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts @@ -1,21 +1,13 @@ import { Wizard } from '../../../wizard/model'; import { DynamicHttpMetadataProvider } from '../../domain/model/providers/dynamic-http-metadata-provider'; import { BaseMetadataProviderEditor } from './base.provider.form'; +import { metadataFilterProcessor } from './utilities'; export const DynamicHttpMetadataProviderWizard: Wizard = { ...BaseMetadataProviderEditor, label: 'DynamicHttpMetadataProvider', type: 'DynamicHttpMetadataResolver', - formatter: (changes: DynamicHttpMetadataProvider) => { - let base = BaseMetadataProviderEditor.formatter(changes); - if (base.dynamicMetadataResolverAttributes) { - if (base.dynamicMetadataResolverAttributes.refreshDelayFactor) { - base.dynamicMetadataResolverAttributes.refreshDelayFactor = - base.dynamicMetadataResolverAttributes.refreshDelayFactor.toString(); - } - } - return base; - }, + schemaPreprocessor: metadataFilterProcessor, getValidators(namesList: string[] = [], xmlIdList: string[] = []): any { const validators = BaseMetadataProviderEditor.getValidators(namesList, xmlIdList); diff --git a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts index d3e4d9995..15064bd64 100644 --- a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts +++ b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts @@ -3,20 +3,13 @@ import { FileBackedHttpMetadataProvider } from '../../domain/model/providers/fil import { BaseMetadataProviderEditor } from './base.provider.form'; import { UriValidator } from '../../../shared/validation/uri.validator'; +import { metadataFilterProcessor } from './utilities'; + export const FileBackedHttpMetadataProviderWizard: Wizard = { ...BaseMetadataProviderEditor, label: 'FileBackedHttpMetadataProvider', type: 'FileBackedHttpMetadataResolver', - formatter: (changes: FileBackedHttpMetadataProvider) => { - let base = BaseMetadataProviderEditor.formatter(changes); - if (base.reloadableMetadataResolverAttributes) { - if (base.reloadableMetadataResolverAttributes.refreshDelayFactor) { - base.reloadableMetadataResolverAttributes.refreshDelayFactor = - base.reloadableMetadataResolverAttributes.refreshDelayFactor.toString(); - } - } - return base; - }, + schemaPreprocessor: metadataFilterProcessor, getValidators(namesList: string[] = [], xmlIdList: string[] = []): any { const validators = BaseMetadataProviderEditor.getValidators(namesList, xmlIdList); validators['/metadataURL'] = (value, property, form) => { diff --git a/ui/src/app/metadata/provider/model/file-system.provider.form.ts b/ui/src/app/metadata/provider/model/file-system.provider.form.ts index fcf780ac5..9fcf7fb6d 100644 --- a/ui/src/app/metadata/provider/model/file-system.provider.form.ts +++ b/ui/src/app/metadata/provider/model/file-system.provider.form.ts @@ -6,16 +6,6 @@ export const FileSystemMetadataProviderWizard: Wizard { - let base = BaseMetadataProviderEditor.formatter(changes); - if (base.reloadableMetadataResolverAttributes) { - if (base.reloadableMetadataResolverAttributes.refreshDelayFactor) { - base.reloadableMetadataResolverAttributes.refreshDelayFactor = - base.reloadableMetadataResolverAttributes.refreshDelayFactor.toString(); - } - } - return base; - }, schema: '/api/ui/MetadataResolver/FilesystemMetadataResolver', steps: [ { diff --git a/ui/src/app/metadata/provider/model/local-dynamic.provider.form.ts b/ui/src/app/metadata/provider/model/local-dynamic.provider.form.ts index c53562873..c79bc487d 100644 --- a/ui/src/app/metadata/provider/model/local-dynamic.provider.form.ts +++ b/ui/src/app/metadata/provider/model/local-dynamic.provider.form.ts @@ -7,16 +7,6 @@ export const LocalDynamicMetadataProviderWizard: Wizard { - let base = BaseMetadataProviderEditor.formatter(changes); - if (base.dynamicMetadataResolverAttributes) { - if (base.dynamicMetadataResolverAttributes.refreshDelayFactor) { - base.dynamicMetadataResolverAttributes.refreshDelayFactor = - base.dynamicMetadataResolverAttributes.refreshDelayFactor.toString(); - } - } - return base; - }, schema: '/api/ui/MetadataResolver/LocalDynamicMetadataResolver', steps: [ { diff --git a/ui/src/app/metadata/provider/model/utilities.ts b/ui/src/app/metadata/provider/model/utilities.ts new file mode 100644 index 000000000..aea90bc21 --- /dev/null +++ b/ui/src/app/metadata/provider/model/utilities.ts @@ -0,0 +1,25 @@ +export const metadataFilterProcessor = (schema) => { + console.log(schema); + if (!schema) { + return null; + } + if (!schema.properties || !schema.properties.metadataFilters) { + return schema; + } + const filters = schema.properties.metadataFilters; + const processed = ({ + ...schema, + properties: { + ...schema.properties, + metadataFilters: { + type: 'object', + properties: filters.items.reduce((collection, filterType) => ({ + ...collection, + [filterType.$id]: filterType + }), {}) + } + } + }); + console.log(processed); + return processed; +}; diff --git a/ui/src/app/schema-form/widget/number/float.component.html b/ui/src/app/schema-form/widget/number/float.component.html index c1c78d34c..56e15bc69 100644 --- a/ui/src/app/schema-form/widget/number/float.component.html +++ b/ui/src/app/schema-form/widget/number/float.component.html @@ -9,17 +9,17 @@ {{schema.description}} -
diff --git a/ui/src/app/schema-form/widget/number/float.component.ts b/ui/src/app/schema-form/widget/number/float.component.ts index 3278829ba..4a465a6ef 100644 --- a/ui/src/app/schema-form/widget/number/float.component.ts +++ b/ui/src/app/schema-form/widget/number/float.component.ts @@ -1,5 +1,5 @@ import { - Component, + Component, AfterViewInit, ViewChild, ElementRef, } from '@angular/core'; import { IntegerWidget } from 'ngx-schema-form'; import { SchemaService } from '../../service/schema.service'; @@ -8,14 +8,61 @@ import { SchemaService } from '../../service/schema.service'; selector: 'float-component', templateUrl: `./float.component.html` }) -export class CustomFloatComponent extends IntegerWidget { +export class CustomFloatComponent extends IntegerWidget implements AfterViewInit { + private _displayValue: string; + @ViewChild('input') element: ElementRef; + constructor( private widgetService: SchemaService ) { super(); } + ngAfterViewInit() { + super.ngAfterViewInit(); + const control = this.control; + this.formProperty.valueChanges.subscribe((newValue) => { + if (typeof this._displayValue !== 'undefined') { + // Ignore the model value, use the display value instead + if (control.value !== this._displayValue) { + control.setValue(this._displayValue, { emitEvent: false }); + } + } else { + if (control.value !== newValue) { + control.setValue(newValue, { emitEvent: false }); + } + } + }); + this.formProperty.errorsChanges.subscribe((errors) => { + control.setErrors(errors, { emitEvent: true }); + const messages = (errors || []) + .filter(e => { + return e.path && e.path.slice(1) === this.formProperty.path; + }) + .map(e => e.message); + this.errorMessages = messages.filter((m, i) => messages.indexOf(m) === i); + }); + control.valueChanges.subscribe((newValue) => { + const native = (this.element.nativeElement); + this._displayValue = newValue; + this.formProperty.setValue(newValue, false); + if (newValue === '' && native.validity.badInput) { + this.formProperty.extendErrors([{ + code: 'INVALID_NUMBER', + path: `#${this.formProperty.path}`, + message: 'Invalid number', + }]); + } + }); + } + get required(): boolean { return this.widgetService.isRequired(this.formProperty); } + + get minimum(): number { + return this.required ? + this.schema.minimum : + this.formProperty.value === null ? null : this.schema.minimum; + } } diff --git a/ui/src/app/wizard/model/form-definition.ts b/ui/src/app/wizard/model/form-definition.ts index bde8e10ad..5e3cbb6ea 100644 --- a/ui/src/app/wizard/model/form-definition.ts +++ b/ui/src/app/wizard/model/form-definition.ts @@ -10,4 +10,5 @@ export interface FormDefinition { parser(changes: Partial, schema?: any); formatter(changes: Partial, schema?: any); getValidators?(...args: any[]): { [key: string]: any }; + schemaPreprocessor?(schema: any): any; } diff --git a/ui/src/app/wizard/reducer/index.ts b/ui/src/app/wizard/reducer/index.ts index d85d930be..027ac1673 100644 --- a/ui/src/app/wizard/reducer/index.ts +++ b/ui/src/app/wizard/reducer/index.ts @@ -128,8 +128,12 @@ export const getLockedStatus = createSelector(getState, fromWizard.getLocked); export const getSchemaLockedFn = (step, locked) => step ? step.locked ? locked : false : false; export const getLocked = createSelector(getCurrent, getLockedStatus, getSchemaLockedFn); +export const getSchemaProcessedFn = (schema, definition) => + definition.schemaPreprocessor ? definition.schemaPreprocessor(schema) : schema; + export const getSchemaObject = createSelector(getState, fromWizard.getSchema); -export const getParsedSchema = createSelector(getSchemaObject, getLocked, getSchemaParseFn); +export const getProcessedSchema = createSelector(getSchemaObject, getWizardDefinition, getSchemaProcessedFn); +export const getParsedSchema = createSelector(getProcessedSchema, getLocked, getSchemaParseFn); export const getSchema = createSelector(getParsedSchema, getCurrent, getSplitSchema); From 471e88dc735e0a8e3d2355be66c39bd93f8a12ef Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Wed, 25 Sep 2019 14:03:39 -0400 Subject: [PATCH 16/19] Got rid of metadataUrl property --- .../admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java | 2 -- .../ui/domain/resolvers/FileBackedHttpMetadataResolver.java | 2 -- 2 files changed, 4 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java index d22d9c6df..732078747 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/DynamicHttpMetadataResolver.java @@ -36,8 +36,6 @@ public class DynamicHttpMetadataResolver extends MetadataResolver { private Integer maxConnectionsPerRoute = 100; - private String metadataURL; - @ElementCollection @OrderColumn private List supportedContentTypes; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FileBackedHttpMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FileBackedHttpMetadataResolver.java index 17fe28ab9..27c4217e5 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FileBackedHttpMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FileBackedHttpMetadataResolver.java @@ -21,8 +21,6 @@ public FileBackedHttpMetadataResolver() { type = "FileBackedHttpMetadataResolver"; } - private String metadataURL; - private String backingFile; private Boolean initializeFromBackupFile = true; From a68d3c6c5ac696b9670d4dd1303fee81825423ba Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Wed, 25 Sep 2019 15:14:10 -0400 Subject: [PATCH 17/19] Compile fix --- .../ui/domain/resolvers/FileBackedHttpMetadataResolver.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FileBackedHttpMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FileBackedHttpMetadataResolver.java index 27c4217e5..17fe28ab9 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FileBackedHttpMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/FileBackedHttpMetadataResolver.java @@ -21,6 +21,8 @@ public FileBackedHttpMetadataResolver() { type = "FileBackedHttpMetadataResolver"; } + private String metadataURL; + private String backingFile; private Boolean initializeFromBackupFile = true; From f53821964b6abd4cbd2b94788cef8a7cbdb14e92 Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Wed, 25 Sep 2019 16:12:53 -0400 Subject: [PATCH 18/19] Fix tests --- .../MetadataResolversControllerIntegrationTests.groovy | 1 - .../tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy | 1 - 2 files changed, 2 deletions(-) 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 84c2c72ed..e6b0c2197 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 @@ -259,7 +259,6 @@ class MetadataResolversControllerIntegrationTests extends Specification { def resolver = new DynamicHttpMetadataResolver().with { it.name = 'DynamicHTTP' it.xmlId = 'DynamicHTTP' - it.metadataURL = 'http://metadata' it.metadataRequestURLConstructionScheme = new MetadataQueryProtocolScheme().with { it.transformRef = 'transformRef' it.content = 'content' 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 cc1403bee..982722ebd 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 @@ -535,7 +535,6 @@ class TestObjectGenerator { new DynamicHttpMetadataResolver().with { it.name = 'DynamicHTTP' it.xmlId = 'DynamicHTTP' - it.metadataURL = 'http://metadata' it.dynamicMetadataResolverAttributes = new DynamicMetadataResolverAttributes().with { it } From 574254be7d71d34580244b89cb90b8d8b907a921 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Wed, 25 Sep 2019 16:04:31 -0700 Subject: [PATCH 19/19] SHIBUI-1478 A couple small Selenium test fixes. Hopefully. --- backend/src/integration/resources/SHIBUI-1392.side | 9 +-------- backend/src/integration/resources/SHIBUI-1407-1.side | 7 +++++++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/src/integration/resources/SHIBUI-1392.side b/backend/src/integration/resources/SHIBUI-1392.side index 20cb9c8ee..ec421765b 100644 --- a/backend/src/integration/resources/SHIBUI-1392.side +++ b/backend/src/integration/resources/SHIBUI-1392.side @@ -389,20 +389,13 @@ ["xpath=//p", "xpath:position"] ], "value": "" - }, { - "id": "84be6a98-5739-42e8-b7ca-06a6c86e9f40", - "comment": "", - "command": "editContent", - "target": "id=/nameIdFormatFilterTarget.target", - "targets": [], - "value": "(true);" }, { "id": "05870356-d3db-4540-bb3f-db34b1cf65f1", "comment": "", "command": "sendKeys", "target": "id=/nameIdFormatFilterTarget.target", "targets": [], - "value": "eval" + "value": "eval(true);" }, { "id": "d7721254-68c9-4140-af2a-1757cce99da7", "comment": "", diff --git a/backend/src/integration/resources/SHIBUI-1407-1.side b/backend/src/integration/resources/SHIBUI-1407-1.side index 374834ff5..5960f7fc1 100644 --- a/backend/src/integration/resources/SHIBUI-1407-1.side +++ b/backend/src/integration/resources/SHIBUI-1407-1.side @@ -2187,6 +2187,13 @@ "target": "5000", "targets": [], "value": "" + }, { + "id": "c2fcb197-7e0d-4b64-82a5-ad24cf24126b", + "comment": "", + "command": "waitForElementEditable", + "target": "id=/serviceProviderName", + "targets": [], + "value": "30000" }, { "id": "99731068-2016-4a7f-8a38-febfb711d027", "comment": "",