Skip to content

Commit

Permalink
1414: MetadataResolvers JSON schema validation WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
dima767 committed Aug 22, 2019
1 parent c850648 commit bdf8e93
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -38,22 +38,12 @@ class RelyingPartyOverridesJsonSchemaValidatingControllerAdvice extends RequestB
HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> 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)
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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<? extends HttpMessageConverter<?>> converterType) {
targetType.typeName == MetadataResolver.typeName
}

@Override
HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType)
throws IOException {

validateMetadataResolverTypePayloadAgainstSchema(inputMessage, jsonSchemaResourceLocationRegistry)
}
}
Original file line number Diff line number Diff line change
@@ -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<String> createRequestHttpEntityFor(Closure jsonBodySupplier) {
new HttpEntity<String>(jsonBodySupplier(), ['Content-Type': 'application/json'] as HttpHeaders)
}
}

0 comments on commit bdf8e93

Please sign in to comment.