diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/ShibPropertiesBootstrap.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/ShibPropertiesBootstrap.groovy index daf75b61e..d39485ca7 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/ShibPropertiesBootstrap.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/ShibPropertiesBootstrap.groovy @@ -1,7 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.service import com.opencsv.CSVReader -import edu.internet2.tier.shibboleth.admin.ui.domain.ShibConfigurationProperty +import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibConfigurationProperty import groovy.util.logging.Slf4j import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.context.event.ApplicationStartedEvent @@ -62,7 +62,7 @@ class ShibPropertiesBootstrap { // Save anything that's left if (propertiesMap.size() > 0) { log.info("Saving/loading [" + propertiesMap.size() + "] properties to the database") - service.addAll(propertiesMap.values()) + service.addAllConfigurationProperties(propertiesMap.values()) } log.info("COMPLETED: ensuring base Shibboleth properties configuration has loaded") diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomPropertiesConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomPropertiesConfiguration.java index c2a032f36..ee18f0e65 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomPropertiesConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CustomPropertiesConfiguration.java @@ -2,7 +2,7 @@ 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.domain.ShibConfigurationProperty; +import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibConfigurationProperty; import edu.internet2.tier.shibboleth.admin.ui.service.CustomEntityAttributesDefinitionService; import edu.internet2.tier.shibboleth.admin.ui.service.ShibConfigurationService; import edu.internet2.tier.shibboleth.admin.ui.service.events.CustomEntityAttributeDefinitionChangeEvent; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesController.java index a96e2db5d..1721228d5 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesController.java @@ -1,14 +1,28 @@ package edu.internet2.tier.shibboleth.admin.ui.controller; +import edu.internet2.tier.shibboleth.admin.ui.domain.CustomEntityAttributeDefinition; +import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibPropertySet; +import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; +import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException; import edu.internet2.tier.shibboleth.admin.ui.service.ShibConfigurationService; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tags; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import java.util.List; @RestController @RequestMapping(value = "/api/shib") @@ -19,7 +33,47 @@ public class ShibPropertiesController { @GetMapping("/properties") @Transactional(readOnly = true) - public ResponseEntity getAll() { - return ResponseEntity.ok(service.getAll()); + public ResponseEntity getAllConfigurationProperties() { + return ResponseEntity.ok(service.getAllConfigurationProperties()); + } + + /** + * @return a List of the set names and their ids + */ + @GetMapping("/property/set") + @Transactional(readOnly = true) + public ResponseEntity getAllPropertySets() { + return ResponseEntity.ok(service.getAllPropertySets()); + } + + @GetMapping("/property/set/{resourceId}") + @Transactional(readOnly = true) + public ResponseEntity getPropertySet(@PathVariable Integer resourceId) throws EntityNotFoundException { + return ResponseEntity.ok(service.getSet(resourceId)); + } + + @DeleteMapping("/property/set/{resourceId}") + @Secured("ROLE_ADMIN") + @Transactional + public ResponseEntity deletePropertySet(@PathVariable Integer resourceId) throws EntityNotFoundException { + service.delete(resourceId); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/property/set") + @Secured("ROLE_ADMIN") + @Transactional + public ResponseEntity createPropertySet(@RequestBody ShibPropertySet newSet) throws ObjectIdExistsException { + // If already defined, we won't/can't create a new one, nor will this call update on the definition + try { + ShibPropertySet set = service.getSet(newSet.getResourceId()); + throw new ObjectIdExistsException(Integer.toString(newSet.getResourceId())); + } + catch (EntityNotFoundException e) { + // we hope not to find this - do nothing + } + + ShibPropertySet result = service.save(newSet); + return ResponseEntity.status(HttpStatus.CREATED).body(result); } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesControllerExceptionHandler.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesControllerExceptionHandler.java new file mode 100644 index 000000000..35adfcef0 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesControllerExceptionHandler.java @@ -0,0 +1,44 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller; + +import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; +import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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.ResponseEntityExceptionHandler; + +@ControllerAdvice(assignableTypes = {ShibPropertiesController.class}) +public class ShibPropertiesControllerExceptionHandler extends ResponseEntityExceptionHandler { + +// @ExceptionHandler({ ConcurrentModificationException.class }) +// public ResponseEntity handleConcurrentModificationException(ConcurrentModificationException e, WebRequest request) { +// return ResponseEntity.status(HttpStatus.CONFLICT).body(new ErrorResponse(HttpStatus.CONFLICT, e.getMessage())); +// } + + @ExceptionHandler({ EntityNotFoundException.class }) + public ResponseEntity handleEntityNotFoundException(EntityNotFoundException e, WebRequest request) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse(HttpStatus.NOT_FOUND, e.getMessage())); + } + +// @ExceptionHandler({ ForbiddenException.class }) +// public ResponseEntity handleForbiddenAccess(ForbiddenException e, WebRequest request) { +// return ResponseEntity.status(HttpStatus.FORBIDDEN).body(new ErrorResponse(HttpStatus.FORBIDDEN, e.getMessage())); +// } + +// @ExceptionHandler({ InvalidPatternMatchException.class }) +// public ResponseEntity handleInvalidUrlMatchException(InvalidPatternMatchException e, WebRequest request) { +// return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponse(HttpStatus.BAD_REQUEST, e.getMessage())); +// } + + @ExceptionHandler({ ObjectIdExistsException.class }) + public ResponseEntity handleObjectIdExistsException(ObjectIdExistsException e, WebRequest request) { + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(EntityDescriptorController.getResourceUriFor(e.getMessage())); + return ResponseEntity.status(HttpStatus.CONFLICT).headers(headers).body(new ErrorResponse( + String.valueOf(HttpStatus.CONFLICT.value()), + String.format("The property set with id [%s] already exists.", e.getMessage()))); + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ShibConfigurationProperty.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/shib/properties/ShibConfigurationProperty.java similarity index 96% rename from backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ShibConfigurationProperty.java rename to backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/shib/properties/ShibConfigurationProperty.java index eb0f4ea77..69e860302 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ShibConfigurationProperty.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/shib/properties/ShibConfigurationProperty.java @@ -1,4 +1,4 @@ -package edu.internet2.tier.shibboleth.admin.ui.domain; +package edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties; import com.fasterxml.jackson.annotation.JsonIgnore; import edu.internet2.tier.shibboleth.admin.util.EmptyStringToNullConverter; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/shib/properties/ShibPropertySet.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/shib/properties/ShibPropertySet.java new file mode 100644 index 000000000..309f7e1b6 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/shib/properties/ShibPropertySet.java @@ -0,0 +1,53 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties; + +import edu.internet2.tier.shibboleth.admin.util.EmptyStringToNullConverter; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.envers.Audited; + +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; + +@Entity(name = "shib_property_set") +@Audited +@Getter +@Setter +@ToString +@RequiredArgsConstructor +public class ShibPropertySet { + @Id + @GeneratedValue + private int resourceId; + + @Column(unique = true, nullable = false) + @Convert(converter = EmptyStringToNullConverter.class) + private String name; + + @OneToMany + private List properties = new ArrayList<>(); + + @Override + public boolean equals(Object o) { + if (o instanceof ShibPropertySet) { + ShibPropertySet that = (ShibPropertySet) o; + boolean result = this.name.equals(that.name) && this.resourceId == that.resourceId && this.properties.size() == that.properties.size(); + if (result == true) { + for (ShibPropertySetting thisSetting : this.properties) { + if ( !that.properties.contains(thisSetting) ) { + return false; + } + } + } + return result; + } + return false; + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/shib/properties/ShibPropertySetting.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/shib/properties/ShibPropertySetting.java new file mode 100644 index 000000000..2fa85ff2b --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/shib/properties/ShibPropertySetting.java @@ -0,0 +1,29 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties; + +import lombok.Data; +import org.hibernate.envers.Audited; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +@Entity(name = "shib_property_setting") +@Audited +@Data +public class ShibPropertySetting { + @Id + @GeneratedValue + private int resourceId; + + @Column + private String configFile; + + @Column + private String propertyName; + + @Column + private String propertyValue; + +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/EntityNotFoundException.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/EntityNotFoundException.java index 4d0009523..212c9f990 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/EntityNotFoundException.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/EntityNotFoundException.java @@ -1,5 +1,8 @@ package edu.internet2.tier.shibboleth.admin.ui.exception; +/** + * Generically meaning - hibernate entity, not SAML entity + */ public class EntityNotFoundException extends Exception { public EntityNotFoundException(String message) { super(message); diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/ProjectionIdAndName.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/ProjectionIdAndName.java new file mode 100644 index 000000000..6731aea86 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/ProjectionIdAndName.java @@ -0,0 +1,6 @@ +package edu.internet2.tier.shibboleth.admin.ui.repository; + +public interface ProjectionIdAndName{ + String getResourceId(); + String getName(); +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/ShibConfigurationRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/ShibConfigurationRepository.java index e5889b3cd..86ed4f90a 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/ShibConfigurationRepository.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/ShibConfigurationRepository.java @@ -1,6 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.repository; -import edu.internet2.tier.shibboleth.admin.ui.domain.ShibConfigurationProperty; +import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibConfigurationProperty; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/ShibPropertySetRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/ShibPropertySetRepository.java new file mode 100644 index 000000000..983758f32 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/ShibPropertySetRepository.java @@ -0,0 +1,17 @@ +package edu.internet2.tier.shibboleth.admin.ui.repository; + +import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibPropertySet; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +/** + * Repository to manage {@link ShibPropertySet} instances. + */ +public interface ShibPropertySetRepository extends JpaRepository { + ShibPropertySet findByName(String name); + + ShibPropertySet findByResourceId(Integer id); + + List findAllBy(); +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/ShibPropertySettingRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/ShibPropertySettingRepository.java new file mode 100644 index 000000000..6dda2047b --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/ShibPropertySettingRepository.java @@ -0,0 +1,10 @@ +package edu.internet2.tier.shibboleth.admin.ui.repository; + +import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibPropertySetting; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * Repository to manage {@link ShibPropertySetting} instances. + */ +public interface ShibPropertySettingRepository extends JpaRepository { +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/ShibConfigurationService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/ShibConfigurationService.java index e1eaf5897..d0c220962 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/ShibConfigurationService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/ShibConfigurationService.java @@ -1,16 +1,29 @@ package edu.internet2.tier.shibboleth.admin.ui.service; -import edu.internet2.tier.shibboleth.admin.ui.domain.ShibConfigurationProperty; +import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibConfigurationProperty; +import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibPropertySet; +import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; +import edu.internet2.tier.shibboleth.admin.ui.repository.ProjectionIdAndName; import java.util.Collection; import java.util.List; public interface ShibConfigurationService { - void addAll(Collection newProperties); + void addAllConfigurationProperties(Collection newProperties); + + void delete(int resourceId) throws EntityNotFoundException; + + List getAllConfigurationProperties(); + + List getAllPropertySets(); List getExistingPropertyNames(); - void save(ShibConfigurationProperty prop); + ShibPropertySet getSet(int resourceId) throws EntityNotFoundException; + + ShibPropertySet getSet(String name); + + ShibPropertySet save(ShibPropertySet set); - List getAll(); + ShibConfigurationProperty save(ShibConfigurationProperty prop); } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/ShibConfigurationServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/ShibConfigurationServiceImpl.java index 1fec3181d..b394caa1f 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/ShibConfigurationServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/ShibConfigurationServiceImpl.java @@ -1,35 +1,121 @@ package edu.internet2.tier.shibboleth.admin.ui.service; -import edu.internet2.tier.shibboleth.admin.ui.domain.ShibConfigurationProperty; +import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibConfigurationProperty; +import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibPropertySet; +import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibPropertySetting; +import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; +import edu.internet2.tier.shibboleth.admin.ui.repository.ProjectionIdAndName; import edu.internet2.tier.shibboleth.admin.ui.repository.ShibConfigurationRepository; +import edu.internet2.tier.shibboleth.admin.ui.repository.ShibPropertySetRepository; +import edu.internet2.tier.shibboleth.admin.ui.repository.ShibPropertySettingRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import javax.transaction.Transactional; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.ResourceBundle; @Service public class ShibConfigurationServiceImpl implements ShibConfigurationService { @Autowired - private ShibConfigurationRepository repository; + private ShibConfigurationRepository shibConfigurationRepository; + + @Autowired + private ShibPropertySetRepository shibPropertySetRepository; + + @Autowired + private ShibPropertySettingRepository shibPropertySettingRepository; + + @Override + public void addAllConfigurationProperties(Collection newProperties) { + shibConfigurationRepository.saveAll(newProperties); + } + + @Override + public void delete(int resourceId) throws EntityNotFoundException { + ShibPropertySet set = shibPropertySetRepository.findByResourceId(resourceId); + if (set == null) { + throw new EntityNotFoundException(String.format("The property set with id [%s] was not found for update.", resourceId)); + } + shibPropertySettingRepository.deleteAll(set.getProperties()); + shibPropertySetRepository.delete(set); + } + + @Override + public List getAllConfigurationProperties() { + return shibConfigurationRepository.findAll(); + } @Override - public void addAll(Collection newProperties) { - repository.saveAll(newProperties); + public List getAllPropertySets() { + return shibPropertySetRepository.findAllBy(); } @Override public List getExistingPropertyNames() { - return repository.getPropertyNames(); + return shibConfigurationRepository.getPropertyNames(); + } + + @Override + public ShibPropertySet getSet(int resourceId) throws EntityNotFoundException { + ShibPropertySet result = shibPropertySetRepository.findByResourceId(resourceId); + if (result == null) { + throw new EntityNotFoundException((String.format("The property set with id [%s] was not found.", resourceId))); + } + return result; } @Override - public void save(ShibConfigurationProperty prop) { - repository.save(prop); + public ShibPropertySet getSet(String name) { + return shibPropertySetRepository.findByName(name); } @Override - public List getAll() { - return repository.findAll(); + public ShibConfigurationProperty save(ShibConfigurationProperty prop) { + return shibConfigurationRepository.save(prop); } + + @Override + @Transactional + public ShibPropertySet save(ShibPropertySet incomingPropSet) { + ShibPropertySet result = new ShibPropertySet(); + List propertiesToUpdate = new ArrayList<>(); + + if (incomingPropSet.getResourceId() == 0) { + // The incoming set is new, so treat the properties as all new as well + propertiesToUpdate.addAll(shibPropertySettingRepository.saveAll(incomingPropSet.getProperties())); + result.setName(incomingPropSet.getName()); + } else { + // if the prop set exists, get the existing entity and update it + result = shibPropertySetRepository.findByResourceId(incomingPropSet.getResourceId()); + result.setName(incomingPropSet.getName()); + + HashMap existingPropMap = new HashMap<>(); + result.getProperties().forEach(prop -> existingPropMap.put(prop.getPropertyName(), prop)); + // find props that are no longer in the set and remove them + incomingPropSet.getProperties().forEach(prop -> existingPropMap.remove(prop.getPropertyName())); + shibPropertySettingRepository.deleteAll(existingPropMap.values()); + // reset our map of existing so we can find new entries + existingPropMap.clear(); + result.getProperties().forEach(prop -> existingPropMap.put(prop.getPropertyName(), prop)); + incomingPropSet.getProperties().forEach(prop -> { + if ( !existingPropMap.containsKey(prop.getPropertyName()) ) { + ShibPropertySetting updatedEntity = shibPropertySettingRepository.save(prop); + propertiesToUpdate.add(updatedEntity); + } else { + // get the entity from the map, update it, save to update list + ShibPropertySetting updatedEntity = existingPropMap.get(prop.getPropertyName()); + updatedEntity.setConfigFile(prop.getConfigFile()); + updatedEntity.setPropertyValue(prop.getPropertyValue()); + propertiesToUpdate.add(shibPropertySettingRepository.save(updatedEntity)); + } + }); + } + result.setProperties(propertiesToUpdate); + return shibPropertySetRepository.save(result); + } + } \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesControllerTests.groovy new file mode 100644 index 000000000..ae925f074 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesControllerTests.groovy @@ -0,0 +1,179 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import com.fasterxml.jackson.databind.ObjectMapper +import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest +import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibPropertySet +import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibPropertySetting +import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException +import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException +import edu.internet2.tier.shibboleth.admin.ui.repository.ShibPropertySetRepository +import edu.internet2.tier.shibboleth.admin.ui.repository.ShibPropertySettingRepository +import edu.internet2.tier.shibboleth.admin.ui.service.ShibConfigurationService +import edu.internet2.tier.shibboleth.admin.ui.util.WithMockAdmin +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.web.client.RestTemplate +import spock.lang.Subject + +import javax.persistence.EntityManager +import javax.transaction.Transactional + +import static org.hamcrest.CoreMatchers.containsString +import static org.springframework.http.MediaType.APPLICATION_JSON +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +class ShibPropertiesControllerTests extends AbstractBaseDataJpaTest { + @Subject + def controller + + @Autowired + ObjectMapper mapper + + @Autowired + EntityManager entityManager + + @Autowired + ShibPropertySetRepository propertySetRepo + + @Autowired + ShibPropertySettingRepository propertySettingRepo + + @Autowired + ShibConfigurationService shibConfigurationService + + def defaultSetResourceId + def mockRestTemplate = Mock(RestTemplate) + def mockMvc + + @Transactional + def setup() { + controller = new ShibPropertiesController() + controller.service = shibConfigurationService + mockMvc = MockMvcBuilders.standaloneSetup(controller).build() + + ShibPropertySetting prop1 = new ShibPropertySetting().with { it -> + it.propertyName = 'foo' + it.configFile = 'defaults.properties' + it.propertyValue = 'bar' + + it + } + ShibPropertySetting prop1Saved = propertySettingRepo.save(prop1) + ShibPropertySetting prop2 = new ShibPropertySetting().with { it -> + it.propertyName = 'foo2' + it.configFile = 'defaults.properties' + it.propertyValue = 'bar2' + + it + } + ShibPropertySetting prop2Saved = propertySettingRepo.save(prop2) + entityManager.flush() + entityManager.clear() + + ArrayList values = new ArrayList<>() + values.add(prop1Saved) + values.add(prop2Saved) + def set = new ShibPropertySet() + set.setName("set1") + set.setProperties(values) + def savedSet = propertySetRepo.save(set) + entityManager.flush() + entityManager.clear() + + defaultSetResourceId = savedSet.resourceId + } + + @WithMockAdmin + def "DELETE /api/shib/property/set"() { + given: + def long setCount = propertySetRepo.count() + def long propsCount = propertySettingRepo.count() + + expect: + setCount == 1 + propsCount == 2 + + try { + mockMvc.perform(delete("/api/shib/property/set/010")) + } + catch (Exception e) { + e instanceof EntityNotFoundException + } + + when: + def result = mockMvc.perform(delete("/api/shib/property/set/" + defaultSetResourceId)) + + then: + result.andExpect(status().isNoContent()) + propertySetRepo.count() == 0 + propertySettingRepo.count() == 0 + + + } + + @WithMockAdmin + def 'GET /api/shib/property/set/{resourceId} non-existent'() { + expect: + try { + mockMvc.perform(get("/api/shib/property/set/0101")) + } + catch (Exception e) { + e instanceof EntityNotFoundException + } + } + + @WithMockAdmin + def "POST /api/shib/property/set - existing set"() { + given: + def jsonBody = mapper.writeValueAsString(propertySetRepo.findByResourceId(defaultSetResourceId)) + + expect: + try { + mockMvc.perform(post('/api/shib/property/set').contentType(APPLICATION_JSON).content(jsonBody)) + } + catch (Exception e) { + e instanceof ObjectIdExistsException + } + } + + @WithMockAdmin + def "POST /api/shib/property/set - new set"() { + when: + ShibPropertySetting prop = new ShibPropertySetting().with { it -> + it.propertyName = 'food.for.thought' + it.configFile = 'defaults.properties' + it.propertyValue = 'true' + + it + } + ShibPropertySetting prop2 = new ShibPropertySetting().with { it -> + it.propertyName = 'food2.for2.thought' + it.configFile = 'defaults.properties' + it.propertyValue = 'true' + + it + } + ShibPropertySet set = new ShibPropertySet().with {it -> + it.properties.add(prop) + it.properties.add(prop2) + it.name = 'somerandom' + + it + } + + def jsonBody = mapper.writeValueAsString(set) + def result = mockMvc.perform(post('/api/shib/property/set').contentType(APPLICATION_JSON).content(jsonBody)) + + then: + result.andExpect(status().isCreated()).andExpect(jsonPath("\$.name").value("somerandom")) + def createdSet = propertySetRepo.findByName("somerandom") + createdSet.getProperties().size() == 2 + } +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/ShibPropertySetRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/ShibPropertySetRepositoryTests.groovy new file mode 100644 index 000000000..edcf106d9 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/ShibPropertySetRepositoryTests.groovy @@ -0,0 +1,64 @@ +package edu.internet2.tier.shibboleth.admin.ui.repository + +import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest +import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibPropertySet +import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibPropertySetting +import org.springframework.beans.factory.annotation.Autowired + +import javax.persistence.EntityManager + +/** + * Tests to validate the repo and model for ShibPropertySetRepository + * Because of how JPA works, these are pretty basic and we put "real use" tests/logic + * into the service that manages the sets + * + * @author chasegawa + */ +class ShibPropertySetRepositoryTests extends AbstractBaseDataJpaTest { + @Autowired + EntityManager entityManager + + @Autowired + ShibPropertySetRepository repo + + def "basic CRUD operations validated"() { + given: + // No properties, just a blank set + def set = new ShibPropertySet(); + set.setName("set1") + + // Confirm empty db state + when: + def allSets = repo.findAll() + + then: + allSets.size() == 0 + + // save check + when: + def savedSet = repo.save(set) + entityManager.flush() + entityManager.clear() + + then: + def allSets2 = repo.findAll() + allSets2.size() == 1 + + // fetch checks + def fetchedSet = repo.findByResourceId(savedSet.resourceId) + savedSet.equals(fetchedSet) + + def fetchedByName = repo.findByName(savedSet.name) + savedSet.equals(fetchedByName) + + // delete check + when: + repo.delete(set) + entityManager.flush() + entityManager.clear() + def noSets = repo.findAll() + + then: + noSets.size() == 0 + } +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/ShibConfigurationServiceTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/ShibConfigurationServiceTests.groovy new file mode 100644 index 000000000..f98f692a5 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/ShibConfigurationServiceTests.groovy @@ -0,0 +1,162 @@ +package edu.internet2.tier.shibboleth.admin.ui.service + +import com.fasterxml.jackson.databind.ObjectMapper +import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest +import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibPropertySet +import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibPropertySetting +import edu.internet2.tier.shibboleth.admin.ui.repository.ShibPropertySetRepository +import edu.internet2.tier.shibboleth.admin.ui.repository.ShibPropertySettingRepository +import org.springframework.beans.factory.annotation.Autowired + +import javax.persistence.EntityManager +import javax.transaction.Transactional + +class ShibConfigurationServiceTests extends AbstractBaseDataJpaTest { + @Autowired + EntityManager entityManager + + @Autowired + ShibPropertySetRepository propertySetRepo + + @Autowired + ShibPropertySettingRepository propertySettingRepo + + @Autowired + ShibConfigurationService service + + def defaultSetResourceId + + /** + * We use the object mapper to transform to json and then back to new objects so that what we send to the service is never + * the actual hibernate entity from the db, but an unattached copy (ie what the service would be getting as input in reality) + */ + def ObjectMapper objectMapper = new ObjectMapper(); + + @Transactional + def setup() { + ShibPropertySetting prop1 = new ShibPropertySetting().with { it -> + it.propertyName = 'foo' + it.configFile = 'defaults.properties' + it.propertyValue = 'bar' + + it + } + ShibPropertySetting prop1Saved = propertySettingRepo.save(prop1) + ShibPropertySetting prop2 = new ShibPropertySetting().with { it -> + it.propertyName = 'foo2' + it.configFile = 'defaults.properties' + it.propertyValue = 'bar2' + + it + } + ShibPropertySetting prop2Saved = propertySettingRepo.save(prop2) + entityManager.flush() + entityManager.clear() + + ArrayList values = new ArrayList<>() + values.add(prop1Saved) + values.add(prop2Saved) + def set = new ShibPropertySet() + set.setName("set1") + set.setProperties(values) + def savedSet = propertySetRepo.save(set) + entityManager.flush() + entityManager.clear() + + defaultSetResourceId = savedSet.resourceId + } + + def "check delete"() { + given: + def long setCount = propertySetRepo.count() + def long propsCount = propertySettingRepo.count() + + expect: + setCount == 1 + propsCount == 2 + + when: + service.delete(defaultSetResourceId) + + then: + propertySetRepo.count() == 0 + propertySettingRepo.count() == 0 + } + + def "create new using the service"() { + when: + ShibPropertySetting prop = new ShibPropertySetting().with { it -> + it.propertyName = 'food.for.thought' + it.configFile = 'defaults.properties' + it.propertyValue = 'true' + + it + } + ShibPropertySetting prop2 = new ShibPropertySetting().with { it -> + it.propertyName = 'food2.for2.thought' + it.configFile = 'defaults.properties' + it.propertyValue = 'true' + + it + } + ShibPropertySet set = new ShibPropertySet().with {it -> + it.properties.add(prop) + it.properties.add(prop2) + it.name = 'somerandom' + + it + } + service.save(set) + ShibPropertySet dbSet = propertySetRepo.findByName("somerandom") + + then: + dbSet.properties.size() == 2 + } + + def "update using the service (add and delete properties)"() { + when: + def defaultSet = propertySetRepo.findByResourceId(defaultSetResourceId) + ShibPropertySetting prop = new ShibPropertySetting().with { it -> + it.propertyName = 'food.for.thought' + it.configFile = 'defaults.properties' + it.propertyValue = 'true' + + it + } + + defaultSet.properties.add(prop) + // create a copy of the set so they can't possibly be real db entities + def copySet = objectMapper.readValue(objectMapper.writeValueAsString(defaultSet), ShibPropertySet.class) + service.save(copySet) + def updatedSet = propertySetRepo.findByResourceId(defaultSetResourceId) + + then: + updatedSet.properties.size() == 3 + + when: + updatedSet.properties.remove(0) + service.save(objectMapper.readValue(objectMapper.writeValueAsString(updatedSet), ShibPropertySet.class)) + def updatedSet2 = propertySetRepo.findByResourceId(defaultSetResourceId) + + then: + updatedSet2.properties.size() == 2 + } + + def "fetch with the service"() { + when: + def sets = service.getAllPropertySets() + + then: + sets.size() == 1 + def set = sets.get(0) + set.getName().equals("set1") + + when: + def theSet = service.getSet(Integer.parseInt(set.getResourceId())) + + then: + theSet.getName().equals("set1") + theSet.getProperties().size() == 2 + } + +} \ No newline at end of file