From e4fdcc36b57869d01a6f9a7b5ca642e29f11b8dd Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Wed, 11 Sep 2019 15:39:04 -0400 Subject: [PATCH] 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) + } +}