Skip to content

Commit

Permalink
Merged in feature/shibui-1788 (pull request #484)
Browse files Browse the repository at this point in the history
Feature/shibui 1788

Approved-by: Ryan Mathis
  • Loading branch information
chasegawa authored and rmathis committed Jul 1, 2021
2 parents bae0760 + 27f2633 commit 42792ca
Show file tree
Hide file tree
Showing 44 changed files with 773 additions and 491 deletions.
2 changes: 1 addition & 1 deletion backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ dependencies {
runtimeOnly 'org.glassfish.jaxb:jaxb-runtime:2.3.0'

compile "com.h2database:h2"
runtimeOnly "org.postgresql:postgresql"
runtimeOnly "org.postgresql:postgresql:42.2.20"
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client:2.2.0'
runtimeOnly 'mysql:mysql-connector-java:5.1.48'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package edu.internet2.tier.shibboleth.admin.ui.service

import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration
import edu.internet2.tier.shibboleth.admin.ui.domain.IRelyingPartyOverrideProperty
import edu.internet2.tier.shibboleth.admin.ui.security.model.User
import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService
import org.springframework.beans.factory.annotation.Autowired
Expand Down Expand Up @@ -29,15 +30,15 @@ class JsonSchemaBuilderService {
def properties = [:]
customPropertiesConfiguration.getOverrides().each {
def property
if (it['displayType'] == 'list'
|| it['displayType'] == 'set') {
if (it['displayType'] == 'list' || it['displayType'] == 'set' || it['displayType'] == 'selection_list') {
property = [$ref: '#/definitions/' + it['name']]
} else {
property =
[title : it['displayName'],
description: it['helpText'],
type : it['displayType'],
examples : it['examples']]
[title : it['displayName'],
description : it['helpText'],
type : ((IRelyingPartyOverrideProperty)it).getTypeForUI(),
default : it['displayType'] == 'boolean' ? Boolean.getBoolean(it['defaultValue']) : it['defaultValue'],
examples : it['examples']]
}
properties[(String) it['name']] = property
}
Expand All @@ -46,13 +47,12 @@ class JsonSchemaBuilderService {

void addRelyingPartyOverridesCollectionDefinitionsToJson(Object json) {
customPropertiesConfiguration.getOverrides().stream().filter {
it -> it['displayType'] && (it['displayType'] == 'list' || it['displayType'] == 'set')
it -> it['displayType'] && (it['displayType'] == 'list' || it['displayType'] == 'set' || it['displayType'] == 'selection_list')
}.each {
def definition = [title : it['displayName'],
description: it['helpText'],
type : 'array',
default : null]
if (it['displayType'] == 'set') {
type : 'array']
if (it['displayType'] == 'set' || it['displayType'] == 'selection_list') {
definition['uniqueItems'] = true
} else if (it['displayType'] == 'list') {
definition['uniqueItems'] = false
Expand All @@ -61,6 +61,8 @@ class JsonSchemaBuilderService {
minLength: 1, // TODO: should this be configurable?
maxLength: 255] //TODO: or this?
items.examples = it['examples']
items['default'] = it['defaultValue']


definition['items'] = items
json[(String) it['name']] = definition
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,88 @@
package edu.internet2.tier.shibboleth.admin.ui.configuration;

import edu.internet2.tier.shibboleth.admin.ui.domain.IRelyingPartyOverrideProperty;
import edu.internet2.tier.shibboleth.admin.ui.domain.RelyingPartyOverrideProperty;
import edu.internet2.tier.shibboleth.admin.ui.service.CustomEntityAttributesDefinitionService;
import edu.internet2.tier.shibboleth.admin.ui.service.events.CustomEntityAttributeDefinitionChangeEvent;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* @author Bill Smith (wsmith@unicon.net)
*/
@Configuration
@ConfigurationProperties(prefix="custom")
public class CustomPropertiesConfiguration {
import javax.annotation.PostConstruct;

@Configuration
@ConfigurationProperties(prefix = "custom")
public class CustomPropertiesConfiguration implements ApplicationListener<CustomEntityAttributeDefinitionChangeEvent> {
private List<? extends Map<String, String>> attributes = new ArrayList<>();
private List<RelyingPartyOverrideProperty> overrides = new ArrayList<>();

private CustomEntityAttributesDefinitionService ceadService;

private HashMap<String, IRelyingPartyOverrideProperty> overrides = new HashMap<>();

private List<RelyingPartyOverrideProperty> overridesFromConfigFile = new ArrayList<>();

private void buildRelyingPartyOverrides() {
// Start over with a clean map and get the CustomEntityAttributesDefinitions from the DB
HashMap<String, IRelyingPartyOverrideProperty> reloaded = new HashMap<>();
ceadService.getAllDefinitions().forEach(def -> {
def.updateExamplesList(); // totally non-ooo, but @PostLoad wasn't working and JPA/Hibernate is doing some reflection crap
reloaded.put(def.getName(), def);
});

// We only want to add to an override from the config file if the incoming override (by name) isn't already in
// the list of overrides (ie DB > file config)
for (RelyingPartyOverrideProperty rpop : this.overridesFromConfigFile) {
if (!reloaded.containsKey(rpop.getName())) {
reloaded.put(rpop.getName(), rpop);
}
}

this.overrides = reloaded;
}

public List<? extends Map<String, String>> getAttributes() {
return attributes;
}

public List<IRelyingPartyOverrideProperty> getOverrides() {
return new ArrayList<>(overrides.values());
}

/**
* We don't know what change occurred, so the easiest thing to do is just rebuild our map of overrides.
* (especially since the small occurrence of this and number of items makes doing this ok perf-wise).
*/
@Override
public void onApplicationEvent(CustomEntityAttributeDefinitionChangeEvent arg0) {
buildRelyingPartyOverrides();
}

@PostConstruct
public void postConstruct() {
// Make sure we have the right data
buildRelyingPartyOverrides();
}

public void setAttributes(List<? extends Map<String, String>> attributes) {
this.attributes = attributes;
}

public List<RelyingPartyOverrideProperty> getOverrides() {
return overrides;
@Autowired
public void setCeadService(CustomEntityAttributesDefinitionService ceadService) {
this.ceadService = ceadService;
}

public void setOverrides(List<RelyingPartyOverrideProperty> overrides) {
this.overrides = overrides;
/**
* This setter will get used by Spring's property system to create objects from a config file (should the properties exist)
*/
public void setOverrides(List<RelyingPartyOverrideProperty> overridesFromConfigFile) {
this.overridesFromConfigFile = overridesFromConfigFile;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package edu.internet2.tier.shibboleth.admin.ui.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
Expand All @@ -18,7 +16,6 @@
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import edu.internet2.tier.shibboleth.admin.ui.domain.CustomEntityAttributeDefinition;
import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor;
import edu.internet2.tier.shibboleth.admin.ui.service.CustomEntityAttributesDefinitionService;

@Controller
Expand All @@ -31,15 +28,15 @@ public class CustomEntityAttributesDefinitionsController {
@Transactional
public ResponseEntity<?> create(@RequestBody CustomEntityAttributeDefinition definition) {
// If already defined, we can't create a new one, nor will this call update the definition
CustomEntityAttributeDefinition cad = caService.find(definition.getName());
CustomEntityAttributeDefinition cad = caService.find(definition.getResourceId());

if (cad != null) {
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ServletUriComponentsBuilder.fromCurrentServletMapping().path("/api/custom/entity/attribute").build().toUri());

return ResponseEntity.status(HttpStatus.CONFLICT).headers(headers)
.body(new ErrorResponse(String.valueOf(HttpStatus.CONFLICT.value()),
String.format("The custom attribute definition with name: [%s] already exists.", definition.getName())));
String.format("The custom attribute definition already exists - unable to create a new definition")));
}

CustomEntityAttributeDefinition result = caService.createOrUpdateDefinition(definition);
Expand All @@ -49,7 +46,7 @@ public ResponseEntity<?> create(@RequestBody CustomEntityAttributeDefinition def
@PutMapping("/attribute")
@Transactional
public ResponseEntity<?> update(@RequestBody CustomEntityAttributeDefinition definition) {
CustomEntityAttributeDefinition cad = caService.find(definition.getName());
CustomEntityAttributeDefinition cad = caService.find(definition.getResourceId());
if (cad == null) {
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ServletUriComponentsBuilder.fromCurrentServletMapping().path("/api/custom/entity/attribute").build().toUri());
Expand All @@ -63,40 +60,44 @@ public ResponseEntity<?> update(@RequestBody CustomEntityAttributeDefinition def
return ResponseEntity.ok(result);
}

/**
* @return List of IRelyingPartyOverrideProperty objects. This will include all of the CustomEntityAttributeDefinition
* and the RelyingPartyOverrideProperties from any configuration file that was read in at startup.
*/
@GetMapping("/attributes")
@Transactional(readOnly = true)
public ResponseEntity<?> getAll() {
return ResponseEntity.ok(caService.getAllDefinitions());
}

@GetMapping("/attribute/{name}")
@GetMapping("/attribute/{resourceId}")
@Transactional(readOnly = true)
public ResponseEntity<?> getOne(@PathVariable String name) {
CustomEntityAttributeDefinition cad = caService.find(name);
public ResponseEntity<?> getOne(@PathVariable String resourceId) {
CustomEntityAttributeDefinition cad = caService.find(resourceId);
if (cad == null) {
HttpHeaders headers = new HttpHeaders();
headers.setLocation(
ServletUriComponentsBuilder.fromCurrentServletMapping().path("/api/custom/entity/attribute/" + name).build().toUri());
ServletUriComponentsBuilder.fromCurrentServletMapping().path("/api/custom/entity/attribute/" + resourceId).build().toUri());

return ResponseEntity.status(HttpStatus.NOT_FOUND).headers(headers)
.body(new ErrorResponse(String.valueOf(HttpStatus.NOT_FOUND.value()),
String.format("The custom attribute definition with name: [%s] does not already exist.", name)));
String.format("The custom attribute definition with resource id: [%s] does not already exist.", resourceId)));
}
return ResponseEntity.ok(cad);
}

@DeleteMapping("/attribute/{name}")
@DeleteMapping("/attribute/{resourceId}")
@Transactional
public ResponseEntity<?> delete(@PathVariable String name) {
CustomEntityAttributeDefinition cad = caService.find(name);
public ResponseEntity<?> delete(@PathVariable String resourceId) {
CustomEntityAttributeDefinition cad = caService.find(resourceId);
if (cad == null) {
HttpHeaders headers = new HttpHeaders();
headers.setLocation(
ServletUriComponentsBuilder.fromCurrentServletMapping().path("/api/custom/entity/attribute/" + name).build().toUri());
ServletUriComponentsBuilder.fromCurrentServletMapping().path("/api/custom/entity/attribute/" + resourceId).build().toUri());

return ResponseEntity.status(HttpStatus.NOT_FOUND).headers(headers)
.body(new ErrorResponse(String.valueOf(HttpStatus.NOT_FOUND.value()),
String.format("The custom attribute definition with name: [%s] does not already exist.", name)));
String.format("The custom attribute definition with resource id: [%s] does not already exist.", resourceId)));
}
caService.deleteDefinition(cad);
return ResponseEntity.noContent().build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,116 @@

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Transient;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.envers.Audited;

import lombok.Data;

@Entity(name = "custom_entity_attribute_definition")
@Audited
@Data
public class CustomEntityAttributeDefinition {
@Id
@Column(nullable = false)
String name;

@Column(name = "help_text", nullable = true)
String helpText;
public class CustomEntityAttributeDefinition implements IRelyingPartyOverrideProperty {
@Column(name = "attribute_friendly_name", nullable = true)
String attributeFriendlyName;

@Column(name = "attribute_name", nullable = true)
String attributeName;

@Column(name = "attribute_type", nullable = false)
CustomAttributeType attributeType;

@Column(name = "default_value", nullable = true)
String defaultValue;

@ElementCollection
@CollectionTable(name = "custom_entity_attr_list_items", joinColumns = @JoinColumn(name = "name"))
@Fetch(FetchMode.JOIN)
@Column(name = "value", nullable = false)
Set<String> customAttrListDefinitions = new HashSet<>();

@Column(name = "default_value", nullable = true)
String defaultValue;

// @TODO: logic to ensure defaultValue matches an item from the list of values when SELECTION_LIST is the type ??
}
@Column(name = "display_name", nullable = true)
String displayName;

enum CustomAttributeType {
STRING, BOOLEAN, INTEGER, LONG, DOUBLE, DURATION, SELECTION_LIST, SPRING_BEAN_ID
@Transient
Set<String> examples;

@Column(name = "help_text", nullable = true)
String helpText;

@Column(name = "invert", nullable = true)
String invert;

@Column(nullable = false)
String name;

@Column(name = "persist_type", nullable = true)
String persistType;

@Column(name = "persist_value", nullable = true)
String persistValue;

@Id
@Column(name = "resource_id", nullable = false)
String resourceId = UUID.randomUUID().toString();

@Override
public Set<String> getDefaultValues() {
return customAttrListDefinitions;
}

@Override
public String getDisplayType() {
return attributeType.name().toLowerCase();
}

@Override
public Boolean getFromConfigFile() {
return Boolean.FALSE;
}

public String getTypeForUI() {
switch (attributeType) {
case BOOLEAN:
case INTEGER:
return getDisplayType();
case SELECTION_LIST:
return "list";
default: // DOUBLE, DURATION, LONG, SPRING_BEAN_ID, STRING
return "string";
}
}

@Override
public void setDefaultValues(Set<String> defaultValues) {
// This is here to comply with the interface only and should not be used to change the set of values in this implementation
}

@Override
public void setDisplayType(String displayType) {
// This is here to comply with the interface only and should not be used to change the value in this implementation
}

/**
* Ensure there are no whitespace characters in the name
*/
@Override
public void setName(String name) {
this.name = name.replaceAll("\\s","");
}

public void updateExamplesList() {
examples = customAttrListDefinitions;
}
}
Loading

0 comments on commit 42792ca

Please sign in to comment.