Skip to content

Commit

Permalink
Merged in SHIBUI-906 (pull request #224)
Browse files Browse the repository at this point in the history
SHIBUI-906 (custom relying party overrides)

Approved-by: Ryan Mathis <rmathis@unicon.net>
  • Loading branch information
Bill Smith authored and Jonathan Johnson committed Nov 8, 2018
2 parents e8ffd19 + 304ad8f commit a7665b8
Show file tree
Hide file tree
Showing 53 changed files with 1,312 additions and 677 deletions.
3 changes: 3 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ dependencies {
//JSON schema generator
testCompile 'com.kjetland:mbknor-jackson-jsonschema_2.12:1.0.29'
testCompile 'javax.validation:validation-api:2.0.1.Final'

//JSON schema validator
compile 'org.sharegov:mjson:1.4.1'
}

def generatedSrcDir = new File(buildDir, 'generated/src/main/java')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package edu.internet2.tier.shibboleth.admin.ui.controller

import com.fasterxml.jackson.databind.ObjectMapper
import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation
import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocationRegistry
import edu.internet2.tier.shibboleth.admin.ui.service.JsonSchemaBuilderService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

import javax.annotation.PostConstruct

import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.entityAttributesFiltersSchema
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR

/**
* Controller implementing REST resource responsible for exposing structure definition for metadata sources user
* interface in terms of JSON schema.
*
* @author Dmitriy Kopylenko
* @author Bill Smith (wsmith@unicon.net)
*/
@RestController
@RequestMapping('/api/ui/EntityAttributesFilters')
class EntityAttributesFiltersUiDefinitionController {

@Autowired
JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry

JsonSchemaResourceLocation jsonSchemaLocation

@Autowired
ObjectMapper jacksonObjectMapper

@Autowired
JsonSchemaBuilderService jsonSchemaBuilderService

@GetMapping
ResponseEntity<?> getUiDefinitionJsonSchema() {
try {
def parsedJson = jacksonObjectMapper.readValue(this.jsonSchemaLocation.url, Map)
jsonSchemaBuilderService.addReleaseAttributesToJson(parsedJson['properties']['attributeRelease']['widget'])
jsonSchemaBuilderService.addRelyingPartyOverridesToJson(parsedJson['properties']['relyingPartyOverrides'])
jsonSchemaBuilderService.addRelyingPartyOverridesCollectionDefinitionsToJson(parsedJson["definitions"])
return ResponseEntity.ok(parsedJson)
}
catch (Exception e) {
e.printStackTrace()
return ResponseEntity.status(INTERNAL_SERVER_ERROR)
.body([jsonParseError : e.getMessage(),
sourceUiSchemaDefinitionFile: this.jsonSchemaLocation.url])
}
}

@PostConstruct
void init() {
this.jsonSchemaLocation = entityAttributesFiltersSchema(this.jsonSchemaResourceLocationRegistry);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package edu.internet2.tier.shibboleth.admin.ui.controller

import com.fasterxml.jackson.databind.ObjectMapper
import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomAttributesConfiguration
import edu.internet2.tier.shibboleth.admin.ui.jsonschema.MetadataSourcesJsonSchemaResourceLocation
import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation
import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocationRegistry
import edu.internet2.tier.shibboleth.admin.ui.service.JsonSchemaBuilderService
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

import javax.annotation.PostConstruct

import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.metadataSourcesSchema
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR

/**
Expand All @@ -22,29 +28,38 @@ import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR
@RequestMapping('/api/ui/MetadataSources')
class MetadataSourcesUiDefinitionController {

private static final Logger logger = LoggerFactory.getLogger(MetadataSourcesUiDefinitionController.class);

@Autowired
MetadataSourcesJsonSchemaResourceLocation jsonSchemaLocation
JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry

JsonSchemaResourceLocation jsonSchemaLocation

@Autowired
ObjectMapper jacksonObjectMapper

@Autowired
CustomAttributesConfiguration customAttributesConfiguration
JsonSchemaBuilderService jsonSchemaBuilderService

@GetMapping
ResponseEntity<?> getUiDefinitionJsonSchema() {
try {
def parsedJson = jacksonObjectMapper.readValue(this.jsonSchemaLocation.url, Map)
parsedJson['properties']['attributeRelease']['widget']['data'] =
customAttributesConfiguration.getAttributes().collect {
[key: it['name'], label: it['displayName']]
}
jsonSchemaBuilderService.addReleaseAttributesToJson(parsedJson['properties']['attributeRelease']['widget'])
jsonSchemaBuilderService.addRelyingPartyOverridesToJson(parsedJson['properties']['relyingPartyOverrides'])
jsonSchemaBuilderService.addRelyingPartyOverridesCollectionDefinitionsToJson(parsedJson["definitions"])
return ResponseEntity.ok(parsedJson)
}
catch (Exception e) {
catch (IOException e) {
logger.error("An error occurred while attempting to get json schema for metadata sources!", e)
return ResponseEntity.status(INTERNAL_SERVER_ERROR)
.body([jsonParseError : e.getMessage(),
sourceUiSchemaDefinitionFile: this.jsonSchemaLocation.url])
}
}

@PostConstruct
void init() {
this.jsonSchemaLocation = metadataSourcesSchema(this.jsonSchemaResourceLocationRegistry);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package edu.internet2.tier.shibboleth.admin.ui.jsonschema

import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation
import mjson.Json
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.core.MethodParameter
import org.springframework.http.HttpInputMessage
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.http.converter.HttpMessageConverter
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.context.request.WebRequest
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter

import javax.annotation.PostConstruct
import java.lang.reflect.Type

import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.metadataSourcesSchema

/**
* Controller advice implementation for validating relying party overrides payload coming from UI layer
* against pre-defined JSON schema.
*
* @author Dmitriy Kopylenko
*/
@ControllerAdvice
class RelyingPartyOverridesJsonSchemaValidatingControllerAdvice extends RequestBodyAdviceAdapter {

@Autowired
JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry

JsonSchemaResourceLocation jsonSchemaLocation

@Override
boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
targetType.typeName == EntityDescriptorRepresentation.typeName
}

@Override
Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
def relyingPartyOverrides = EntityDescriptorRepresentation.cast(body).relyingPartyOverrides
def relyingPartyOverridesJson = Json.make([relyingPartyOverrides: relyingPartyOverrides])
def schema = Json.schema(this.jsonSchemaLocation.uri)
def validationResult = schema.validate(relyingPartyOverridesJson)
if (!validationResult.at('ok')) {
throw new JsonSchemaValidationFailedException(validationResult.at('errors').asList())
}
body
}

@ExceptionHandler(JsonSchemaValidationFailedException)
final ResponseEntity<?> handleUserNotFoundException(JsonSchemaValidationFailedException ex, WebRequest request) {
new ResponseEntity<>([errors: ex.errors], HttpStatus.BAD_REQUEST)
}

@PostConstruct
void init() {
this.jsonSchemaLocation = metadataSourcesSchema(this.jsonSchemaResourceLocationRegistry);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,15 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService {

resolversPositionOrderContainerService.allMetadataResolversInDefinedOrderOrUnordered.each {
edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver mr ->
//TODO: We do not currently marshall the internal incommon chaining resolver (with BaseMetadataResolver type)
if ((mr.type != 'BaseMetadataResolver') && (mr.enabled)) {
constructXmlNodeForResolver(mr, delegate) {
//TODO: enhance
mr.metadataFilters.each { edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter filter ->
constructXmlNodeForFilter(filter, delegate)
//TODO: We do not currently marshall the internal incommon chaining resolver (with BaseMetadataResolver type)
if ((mr.type != 'BaseMetadataResolver') && (mr.enabled)) {
constructXmlNodeForResolver(mr, delegate) {
//TODO: enhance
mr.metadataFilters.each { edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter filter ->
constructXmlNodeForFilter(filter, delegate)
}
}
}
}
}
}
return DOMBuilder.newInstance().parseText(writer.toString())
Expand Down Expand Up @@ -407,4 +407,4 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService {
}

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package edu.internet2.tier.shibboleth.admin.ui.service

import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration
import org.springframework.beans.factory.annotation.Autowired

/**
* @author Bill Smith (wsmith@unicon.net)
*/
class JsonSchemaBuilderService {

@Autowired
CustomPropertiesConfiguration customPropertiesConfiguration

void addReleaseAttributesToJson(Object json) {
json['data'] = customPropertiesConfiguration.getAttributes().collect {
[key: it['name'], label: it['displayName']]
}
}

void addRelyingPartyOverridesToJson(Object json) {
def properties = [:]
customPropertiesConfiguration.getOverrides().each {
def property
if (it['displayType'] == 'list'
|| it['displayType'] == 'set') {
property = [$ref: '#/definitions/' + it['name']]
} else {
property =
[title : it['displayName'],
description: it['helpText'],
type : it['displayType']]
if (it['displayType'] == 'boolean') {
property['default'] = (Boolean)(it['defaultValue'])
} else {
property['default'] = it['defaultValue']
}
}
properties[(String) it['name']] = property
}
json['properties'] = properties
}

void addRelyingPartyOverridesCollectionDefinitionsToJson(Object json) {
customPropertiesConfiguration.getOverrides().stream().filter {
it -> it['displayType'] && (it['displayType'] == 'list' || it['displayType'] == 'set')
}.each {
def definition = [title : it['displayName'],
description: it['helpText'],
type : 'array',
default : null]
if (it['displayType'] == 'set') {
definition['uniqueItems'] = true
} else if (it['displayType'] == 'list') {
definition['uniqueItems'] = false
}
def items = [type : 'string',
minLength: 1, // TODO: should this be configurable?
maxLength: 255] //TODO: or this?
items.widget = [id: 'datalist', data: it['defaultValues']]

definition['items'] = items
json[(String) it['name']] = definition
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@
import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolversPositionOrderContainerService;
import edu.internet2.tier.shibboleth.admin.util.AttributeUtility;
import edu.internet2.tier.shibboleth.admin.util.LuceneUtility;
import edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions;
import org.apache.lucene.analysis.Analyzer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
Expand All @@ -46,7 +47,7 @@
import javax.servlet.http.HttpServletRequest;

@Configuration
@EnableConfigurationProperties(CustomAttributesConfiguration.class)
@EnableConfigurationProperties(CustomPropertiesConfiguration.class)
public class CoreShibUiConfiguration {
private static final Logger logger = LoggerFactory.getLogger(CoreShibUiConfiguration.class);

Expand Down Expand Up @@ -180,12 +181,17 @@ public LuceneUtility luceneUtility(DirectoryService directoryService) {
}

@Bean
public CustomAttributesConfiguration customAttributesConfiguration() {
return new CustomAttributesConfiguration();
public CustomPropertiesConfiguration customPropertiesConfiguration() {
return new CustomPropertiesConfiguration();
}

@Bean
public Module stringTrimModule() {
return new StringTrimModule();
}

@Bean
public ModelRepresentationConversions modelRepresentationConversions() {
return new ModelRepresentationConversions(customPropertiesConfiguration());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.internet2.tier.shibboleth.admin.ui.configuration;

import edu.internet2.tier.shibboleth.admin.ui.domain.RelyingPartyOverrideProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

Expand All @@ -12,9 +13,10 @@
*/
@Configuration
@ConfigurationProperties(prefix="custom")
public class CustomAttributesConfiguration {
public class CustomPropertiesConfiguration {

private List<? extends Map<String, String>> attributes = new ArrayList<>();
private List<RelyingPartyOverrideProperty> overrides = new ArrayList<>();

public List<? extends Map<String, String>> getAttributes() {
return attributes;
Expand All @@ -23,4 +25,12 @@ public List<? extends Map<String, String>> getAttributes() {
public void setAttributes(List<? extends Map<String, String>> attributes) {
this.attributes = attributes;
}

public List<RelyingPartyOverrideProperty> getOverrides() {
return overrides;
}

public void setOverrides(List<RelyingPartyOverrideProperty> overrides) {
this.overrides = overrides;
}
}
Loading

0 comments on commit a7665b8

Please sign in to comment.