From ffffc0b70822bee27f71804f6e9d52d28c204a33 Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Fri, 23 Aug 2019 10:26:42 -0400 Subject: [PATCH] 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 }