From 67901004aa076e2003c5d6ecb7257ef083048123 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Fri, 10 Sep 2021 16:56:47 -0700 Subject: [PATCH 1/4] SHIBUI-2059 Initial add of functionality needed on the backend --- .../controller/AttributeBundleController.java | 29 +++++ .../admin/ui/domain/AttributeBundle.java | 31 ++++++ .../ui/domain/BundleableAttributeType.java | 38 +++++++ .../repository/AttributeBundleRepository.java | 15 +++ .../ui/service/AttributeBundleService.java | 18 +++ ...undleableAttributeTypeValueSerializer.java | 29 +++++ .../src/main/resources/application.properties | 1 + .../AttributeBundleControllerTests.groovy | 104 ++++++++++++++++++ .../AttributeBundleRepositoryTests.groovy | 45 ++++++++ 9 files changed, 310 insertions(+) create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleController.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AttributeBundle.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/BundleableAttributeType.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepository.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/AttributeBundleService.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/BundleableAttributeTypeValueSerializer.java create mode 100644 backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy create mode 100644 backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepositoryTests.groovy diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleController.java new file mode 100644 index 000000000..ebbd057a1 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleController.java @@ -0,0 +1,29 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller; + +import edu.internet2.tier.shibboleth.admin.ui.service.AttributeBundleService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/custom/entity/bundles") +@Slf4j +public class AttributeBundleController { + @Autowired AttributeBundleService attributeBundleService; + + @GetMapping + @Transactional(readOnly = true) + public ResponseEntity getAll() { + return ResponseEntity.ok(attributeBundleService.findAll()); + } + + //POST + + //DELETE + + //PUT +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AttributeBundle.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AttributeBundle.java new file mode 100644 index 000000000..2df1132ac --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/AttributeBundle.java @@ -0,0 +1,31 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +@Entity(name = "attribute_bundle_definition") +@Data +public class AttributeBundle { + @Column(nullable = false) + @ElementCollection + Set attributes = new HashSet<>(); + + @Column(name = "name", nullable = true) + String name; + + @Id + @Column(name = "resource_id", nullable = false) + String resourceId = UUID.randomUUID().toString(); +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/BundleableAttributeType.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/BundleableAttributeType.java new file mode 100644 index 000000000..9977e90b1 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/BundleableAttributeType.java @@ -0,0 +1,38 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import edu.internet2.tier.shibboleth.admin.util.BundleableAttributeTypeValueSerializer; + +@JsonSerialize(using = BundleableAttributeTypeValueSerializer.class) +public enum BundleableAttributeType { + EDUPERSONPRINCIPALNAME("eduPersonPrincipalName"), + UID("uid"), + MAIL("mail"), + SURNAME("surname"), + GIVENNAME("givenName"), + EDUPERSONAFFILIATE("eduPersonAffiliation"), + EDUPERSONSCOPEDAFFILIATION("eduPersonScopedAffiliation"), + EDUPERSONPRIMARYAFFILIATION("eduPersonPrimaryAffiliation"), + EDUPERSONENTITLEMENT("eduPersonEntitlement"), + EDUPERSONASSURANCE("eduPersonAssurance"), + EDUPERSONUNIQUEID("eduPersonUniqueId"), + EMPLOYEENUMBER("employeeNumber"); + + String label; + + BundleableAttributeType(String val) { + label = val; + } + + public String label() {return label;} + + public static BundleableAttributeType valueOfLabel(String label) { + for (BundleableAttributeType e : values()) { + if (e.name().equals(label)) { + return e; + } + } + return null; + } + +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepository.java new file mode 100644 index 000000000..c2d52bb23 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepository.java @@ -0,0 +1,15 @@ +package edu.internet2.tier.shibboleth.admin.ui.repository; + +import edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBundle; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +/** + * Repository to manage {@link edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBundle} instances. + */ +public interface AttributeBundleRepository extends JpaRepository { + List findAll(); + + AttributeBundle save(AttributeBundle target); +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/AttributeBundleService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/AttributeBundleService.java new file mode 100644 index 000000000..413a14386 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/AttributeBundleService.java @@ -0,0 +1,18 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBundle; +import edu.internet2.tier.shibboleth.admin.ui.repository.AttributeBundleRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class AttributeBundleService { + @Autowired + AttributeBundleRepository attributeBundleRepository; + + public List findAll() { + return attributeBundleRepository.findAll(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/BundleableAttributeTypeValueSerializer.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/BundleableAttributeTypeValueSerializer.java new file mode 100644 index 000000000..32a7b7cc8 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/BundleableAttributeTypeValueSerializer.java @@ -0,0 +1,29 @@ +package edu.internet2.tier.shibboleth.admin.util; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import edu.internet2.tier.shibboleth.admin.ui.domain.BundleableAttributeType; + +import java.io.IOException; + +/** + * This simplifies translation to the front end. We use the ENUM on the backend, but the BundleableAttributeType + * is tagged to serialize using this helper. + * Note: The deserialize is done naturally by setting spring.jackson.mapper.accept-case-insensitive-enums=true in + * the application.properties and by the setup of the ENUM itself + */ +public class BundleableAttributeTypeValueSerializer extends StdSerializer { + public BundleableAttributeTypeValueSerializer() { + this(null); + } + + public BundleableAttributeTypeValueSerializer(Class t) { + super(t); + } + + @Override + public void serialize(BundleableAttributeType value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeString(value.label()); + } +} \ No newline at end of file diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 5b7b801f1..0556e5b45 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -24,6 +24,7 @@ spring.h2.console.settings.web-allow-others=true # spring.jackson.default-property-inclusion=non_absent spring.jackson.default-property-inclusion=NON_NULL +spring.jackson.mapper.accept-case-insensitive-enums=true # Database Configuration PostgreSQL #spring.datasource.url=jdbc:postgresql://localhost:5432/shibui diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy new file mode 100644 index 000000000..231e83601 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy @@ -0,0 +1,104 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import com.fasterxml.jackson.databind.MapperFeature +import com.fasterxml.jackson.databind.ObjectMapper +import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration +import edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBundle +import edu.internet2.tier.shibboleth.admin.ui.repository.AttributeBundleRepository +import edu.internet2.tier.shibboleth.admin.ui.service.AttributeBundleService +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import org.springframework.boot.test.context.TestConfiguration +import org.springframework.context.annotation.Bean +import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional +import spock.lang.Specification + +import static org.hamcrest.Matchers.containsInAnyOrder +import static org.springframework.http.MediaType.APPLICATION_JSON +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +@DataJpaTest +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") +@ContextConfiguration(classes = [ShibUIConfiguration, ABCTConfig]) +class AttributeBundleControllerTests extends Specification { + @Autowired + AttributeBundleController controller + + @Autowired + AttributeBundleRepository attributeBundleRepository + + ObjectMapper objectMapper = new ObjectMapper().with { + it.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) + it + } + + def MockMvc + + @Transactional + def setup() { + mockMvc = MockMvcBuilders.standaloneSetup(controller).build() + attributeBundleRepository.deleteAll() + } + + def "GET checks" () { + expect: + attributeBundleRepository.findAll().isEmpty() + + when: "fetch for no bundles" + def result = mockMvc.perform(get('/api/custom/entity/bundles')) + + then: + result.andExpect(status().isOk()) + .andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(content().json('[]')) + + when: "add a bundle" + def json = """ + { + "name": "bundleName", + "resourceId": "randomIDVal", + "attributes": ["eduPersonPrincipalName", "surname", "givenName"] + } + """ + + AttributeBundle bundle = objectMapper.readValue(json, AttributeBundle.class) + attributeBundleRepository.saveAndFlush(bundle) + result = mockMvc.perform(get('/api/custom/entity/bundles')) + + then: + result.andExpect(status().isOk()) + .andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.[0].name").value("bundleName")) + .andExpect(jsonPath("\$.[0].resourceId").value("randomIDVal")) + .andExpect(jsonPath("\$.[0].attributes", containsInAnyOrder("eduPersonPrincipalName", "surname", "givenName"))) + } + + // can go away with merge to develop... + @TestConfiguration + private static class ABCTConfig { + @Bean + AttributeBundleController attributeBundleController(AttributeBundleService attributeBundleService) { + new AttributeBundleController().with { + it.attributeBundleService = attributeBundleService + it + } + } + + @Bean + AttributeBundleService attributeBundleService(AttributeBundleRepository repo) { + new AttributeBundleService().with { + it.attributeBundleRepository = repo + it + } + } + + } +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepositoryTests.groovy new file mode 100644 index 000000000..148cceda8 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepositoryTests.groovy @@ -0,0 +1,45 @@ +package edu.internet2.tier.shibboleth.admin.ui.repository + +import com.fasterxml.jackson.databind.MapperFeature +import com.fasterxml.jackson.databind.ObjectMapper +import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration +import edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBundle +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.test.context.ContextConfiguration +import spock.lang.Specification + +@DataJpaTest +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") +@ContextConfiguration(classes = [ShibUIConfiguration]) +class AttributeBundleRepositoryTests extends Specification { + @Autowired + AttributeBundleRepository abRepo + + ObjectMapper objectMapper = new ObjectMapper().with { + it.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) + it + } + + def "test create and fetch" () { + given: + def json = """ + { + "name": "bundleName", + "resourceId": "randomIDVal", + "attributes": ["eduPersonPrincipalName", "surname", "givenName"] + } + """ + + AttributeBundle bundle = objectMapper.readValue(json, AttributeBundle.class) + + when: + def result = abRepo.save(bundle) + + then: + result == bundle + } +} \ No newline at end of file From de71b30815a26b53e3145264ca1659b69fea5740 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Fri, 10 Sep 2021 22:07:29 -0700 Subject: [PATCH 2/4] SHIBUI-2059 added logic for create bundles --- .../controller/AttributeBundleController.java | 16 +++++- .../AttributeBundleExceptionHandler.java | 22 ++++++++ .../EntityDescriptorController.java | 16 ++---- ...yDescriptorControllerExceptionHandler.java | 26 +++++----- .../ui/domain/BundleableAttributeType.java | 4 +- .../ui/exception/EntityIdExistsException.java | 8 --- .../ui/exception/ObjectIdExistsException.java | 8 +++ .../repository/AttributeBundleRepository.java | 3 ++ .../ui/service/AttributeBundleService.java | 8 +++ .../ui/service/EntityDescriptorService.java | 11 ++-- .../JPAEntityDescriptorServiceImpl.java | 9 ++-- .../AttributeBundleControllerTests.groovy | 50 ++++++++++++++++++- .../EntityDescriptorControllerTests.groovy | 6 +-- 13 files changed, 138 insertions(+), 49 deletions(-) create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleExceptionHandler.java delete mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/EntityIdExistsException.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/ObjectIdExistsException.java diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleController.java index ebbd057a1..ca1c18af5 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleController.java @@ -1,11 +1,19 @@ package edu.internet2.tier.shibboleth.admin.ui.controller; +import edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBundle; +import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException; +import edu.internet2.tier.shibboleth.admin.ui.security.exception.GroupExistsConflictException; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; import edu.internet2.tier.shibboleth.admin.ui.service.AttributeBundleService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +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.GetMapping; +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; @@ -21,7 +29,13 @@ public ResponseEntity getAll() { return ResponseEntity.ok(attributeBundleService.findAll()); } - //POST + @Secured("ROLE_ADMIN") + @PostMapping + @Transactional + public ResponseEntity create(@RequestBody AttributeBundle bundle) throws ObjectIdExistsException { + AttributeBundle result = attributeBundleService.create(bundle); + return ResponseEntity.status(HttpStatus.CREATED).body(result); + } //DELETE diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleExceptionHandler.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleExceptionHandler.java new file mode 100644 index 000000000..21940d84f --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleExceptionHandler.java @@ -0,0 +1,22 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller; + +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; + +@ControllerAdvice(assignableTypes = {AttributeBundleController.class}) +public class AttributeBundleExceptionHandler { + @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 attribute bundle with resource 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/controller/EntityDescriptorController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java index f8e28fc75..99eb079f6 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java @@ -2,14 +2,10 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation; -import edu.internet2.tier.shibboleth.admin.ui.domain.versioning.Version; -import edu.internet2.tier.shibboleth.admin.ui.exception.EntityIdExistsException; +import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException; import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects; -import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository; -import edu.internet2.tier.shibboleth.admin.ui.security.model.User; -import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService; import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorVersionService; import lombok.extern.slf4j.Slf4j; @@ -17,13 +13,10 @@ import org.opensaml.core.xml.io.MarshallingException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.client.RestTemplateBuilder; -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.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -39,8 +32,6 @@ import java.net.URI; import java.util.ConcurrentModificationException; -import java.util.List; -import java.util.stream.Collectors; @RestController @RequestMapping("/api") @@ -73,7 +64,8 @@ public EntityDescriptorController(EntityDescriptorVersionService versionService) @PostMapping("/EntityDescriptor") @Transactional - public ResponseEntity create(@RequestBody EntityDescriptorRepresentation edRepresentation) throws ForbiddenException, EntityIdExistsException { + public ResponseEntity create(@RequestBody EntityDescriptorRepresentation edRepresentation) throws ForbiddenException, + ObjectIdExistsException { EntityDescriptorRepresentation persistedEd = entityDescriptorService.createNew(edRepresentation); return ResponseEntity.created(getResourceUriFor(persistedEd.getId())).body(persistedEd); } @@ -171,4 +163,4 @@ public ResponseEntity upload(@RequestParam String metadataUrl, @RequestParam .body(String.format("Error fetching XML metadata from the provided URL. Error: %s", e.getMessage())); } } -} +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerExceptionHandler.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerExceptionHandler.java index d0b18f415..fd48f68e6 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerExceptionHandler.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerExceptionHandler.java @@ -10,7 +10,7 @@ import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; -import edu.internet2.tier.shibboleth.admin.ui.exception.EntityIdExistsException; +import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException; import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; @@ -22,23 +22,23 @@ public ResponseEntity handleConcurrentModificationException(ConcurrentModific return ResponseEntity.status(HttpStatus.CONFLICT).body(new ErrorResponse(HttpStatus.CONFLICT, e.getMessage())); } - @ExceptionHandler({ EntityIdExistsException.class }) - public ResponseEntity handleEntityExistsException(EntityIdExistsException 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 entity descriptor with entity id [%s] already exists.", 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({ 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 entity descriptor with entity 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/BundleableAttributeType.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/BundleableAttributeType.java index 9977e90b1..84a80fce4 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/BundleableAttributeType.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/BundleableAttributeType.java @@ -1,5 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import edu.internet2.tier.shibboleth.admin.util.BundleableAttributeTypeValueSerializer; @@ -26,9 +27,10 @@ public enum BundleableAttributeType { public String label() {return label;} + @JsonCreator public static BundleableAttributeType valueOfLabel(String label) { for (BundleableAttributeType e : values()) { - if (e.name().equals(label)) { + if (e.label.equals(label)) { return e; } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/EntityIdExistsException.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/EntityIdExistsException.java deleted file mode 100644 index 990eab2c3..000000000 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/EntityIdExistsException.java +++ /dev/null @@ -1,8 +0,0 @@ -package edu.internet2.tier.shibboleth.admin.ui.exception; - -public class EntityIdExistsException extends Exception { - public EntityIdExistsException(String entityId) { - super(entityId); - } - -} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/ObjectIdExistsException.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/ObjectIdExistsException.java new file mode 100644 index 000000000..f2604acfe --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/ObjectIdExistsException.java @@ -0,0 +1,8 @@ +package edu.internet2.tier.shibboleth.admin.ui.exception; + +public class ObjectIdExistsException extends Exception { + public ObjectIdExistsException(String entityId) { + super(entityId); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepository.java index c2d52bb23..0c9bc2aed 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepository.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepository.java @@ -4,6 +4,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; +import java.util.Optional; /** * Repository to manage {@link edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBundle} instances. @@ -11,5 +12,7 @@ public interface AttributeBundleRepository extends JpaRepository { List findAll(); + Optional findByResourceId(String resourceId); + AttributeBundle save(AttributeBundle target); } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/AttributeBundleService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/AttributeBundleService.java index 413a14386..f77983e92 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/AttributeBundleService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/AttributeBundleService.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.service; import edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBundle; +import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException; import edu.internet2.tier.shibboleth.admin.ui.repository.AttributeBundleRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -12,6 +13,13 @@ public class AttributeBundleService { @Autowired AttributeBundleRepository attributeBundleRepository; + public AttributeBundle create(AttributeBundle bundle) throws ObjectIdExistsException { + if (attributeBundleRepository.findByResourceId(bundle.getResourceId()).isPresent()) { + throw new ObjectIdExistsException(bundle.getResourceId()); + } + return attributeBundleRepository.save(bundle); + } + public List findAll() { return attributeBundleRepository.findAll(); } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityDescriptorService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityDescriptorService.java index c8c39bbb3..50f29951c 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityDescriptorService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EntityDescriptorService.java @@ -3,7 +3,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.Attribute; import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation; -import edu.internet2.tier.shibboleth.admin.ui.exception.EntityIdExistsException; +import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException; import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; @@ -29,17 +29,18 @@ public interface EntityDescriptorService { * @param ed - JPA EntityDescriptor to base creation on * @return EntityDescriptorRepresentation of the created object * @throws ForbiddenException If user is unauthorized to perform this operation - * @throws EntityIdExistsException If any EntityDescriptor already exists with the same EntityId + * @throws ObjectIdExistsException If any EntityDescriptor already exists with the same EntityId */ - EntityDescriptorRepresentation createNew(EntityDescriptor ed) throws ForbiddenException, EntityIdExistsException; + EntityDescriptorRepresentation createNew(EntityDescriptor ed) throws ForbiddenException, ObjectIdExistsException; /** * @param edRepresentation Incoming representation to save * @return EntityDescriptorRepresentation * @throws ForbiddenException If user is unauthorized to perform this operation - * @throws EntityIdExistsException If the entity already exists + * @throws ObjectIdExistsException If the entity already exists */ - EntityDescriptorRepresentation createNew(EntityDescriptorRepresentation edRepresentation) throws ForbiddenException, EntityIdExistsException; + EntityDescriptorRepresentation createNew(EntityDescriptorRepresentation edRepresentation) throws ForbiddenException, + ObjectIdExistsException; /** * Map from opensaml implementation of entity descriptor model to front-end data representation of entity descriptor diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java index d23b16365..1b660ab9f 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImpl.java @@ -2,7 +2,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.*; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.*; -import edu.internet2.tier.shibboleth.admin.ui.exception.EntityIdExistsException; +import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException; import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects; @@ -66,18 +66,19 @@ public EntityDescriptor createDescriptorFromRepresentation(final EntityDescripto } @Override - public EntityDescriptorRepresentation createNew(EntityDescriptor ed) throws ForbiddenException, EntityIdExistsException { + public EntityDescriptorRepresentation createNew(EntityDescriptor ed) throws ForbiddenException, ObjectIdExistsException { return createNew(createRepresentationFromDescriptor(ed)); } @Override - public EntityDescriptorRepresentation createNew(EntityDescriptorRepresentation edRep) throws ForbiddenException, EntityIdExistsException { + public EntityDescriptorRepresentation createNew(EntityDescriptorRepresentation edRep) throws ForbiddenException, + ObjectIdExistsException { if (edRep.isServiceEnabled() && !userService.currentUserIsAdmin()) { throw new ForbiddenException("You do not have the permissions necessary to enable this service."); } if (entityDescriptorRepository.findByEntityID(edRep.getEntityId()) != null) { - throw new EntityIdExistsException(edRep.getEntityId()); + throw new ObjectIdExistsException(edRep.getEntityId()); } EntityDescriptor ed = (EntityDescriptor) createDescriptorFromRepresentation(edRep); diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy index 231e83601..af0b428e7 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.MapperFeature import com.fasterxml.jackson.databind.ObjectMapper import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBundle +import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException import edu.internet2.tier.shibboleth.admin.ui.repository.AttributeBundleRepository import edu.internet2.tier.shibboleth.admin.ui.service.AttributeBundleService import org.springframework.beans.factory.annotation.Autowired @@ -15,16 +16,20 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories import org.springframework.test.context.ContextConfiguration import org.springframework.test.web.servlet.setup.MockMvcBuilders import org.springframework.transaction.annotation.Transactional +import org.springframework.web.util.NestedServletException import spock.lang.Specification +import static org.hamcrest.CoreMatchers.containsString import static org.hamcrest.Matchers.containsInAnyOrder import static org.springframework.http.MediaType.APPLICATION_JSON 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.content +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.status -@DataJpaTest +@DataJpaTest(properties = ["spring.jackson.mapper.accept-case-insensitive-enums=true"]) @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") @ContextConfiguration(classes = [ShibUIConfiguration, ABCTConfig]) @@ -81,7 +86,48 @@ class AttributeBundleControllerTests extends Specification { .andExpect(jsonPath("\$.[0].attributes", containsInAnyOrder("eduPersonPrincipalName", "surname", "givenName"))) } - // can go away with merge to develop... + def "CREATE checks" () { + expect: + attributeBundleRepository.findAll().isEmpty() + + when: "add a bundle" + def json = """ + { + "name": "bundleName", + "resourceId": "randomIDVal", + "attributes": ["eduPersonPrincipalName", "surname", "givenName"] + } + """ + AttributeBundle bundle = objectMapper.readValue(json, AttributeBundle.class) + attributeBundleRepository.saveAndFlush(bundle) + + then: "bundle already exists" + try { + mockMvc.perform(post('/api/custom/entity/bundles').contentType(APPLICATION_JSON).content(json)) + false + } catch (NestedServletException expected) { + expected.getCause() instanceof ObjectIdExistsException + } + + when: "new bundle" + json = """ + { + "name": "bundle2", + "resourceId": "differentResourceId", + "attributes": ["eduPersonPrincipalName", "surname", "givenName"] + } + """ + + def result = mockMvc.perform(post('/api/custom/entity/bundles').contentType(APPLICATION_JSON).content(json)) + then: + result.andExpect(status().isCreated()) + .andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.name").value("bundle2")) + .andExpect(jsonPath("\$.resourceId").value("differentResourceId")) + .andExpect(jsonPath("\$.attributes", containsInAnyOrder("eduPersonPrincipalName", "surname", "givenName"))) + } + + // can go away with merge to develop and this extends the base test class @TestConfiguration private static class ABCTConfig { @Bean diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy index f7b44786a..74ca259a0 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerTests.groovy @@ -6,7 +6,7 @@ import edu.internet2.tier.shibboleth.admin.ui.configuration.Internationalization import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor -import edu.internet2.tier.shibboleth.admin.ui.exception.EntityIdExistsException +import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects @@ -372,7 +372,7 @@ class EntityDescriptorControllerTests extends Specification { mockMvc.perform(post('/api/EntityDescriptor').contentType(APPLICATION_JSON).content(postedJsonBody)) } catch (Exception e) { - e instanceof EntityIdExistsException + e instanceof ObjectIdExistsException } } @@ -618,7 +618,7 @@ class EntityDescriptorControllerTests extends Specification { mockMvc.perform(post("/api/EntityDescriptor").contentType(APPLICATION_XML).content(postedBody).param("spName", spName)) } catch (Exception e) { - e instanceof EntityIdExistsException + e instanceof ObjectIdExistsException } } From 9e36ffbbaffaa48cdab88e1cb7bddd8a7fabdfc1 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Fri, 10 Sep 2021 22:36:29 -0700 Subject: [PATCH 3/4] SHIBUI-2059 added logic for delete bundles --- .../controller/AttributeBundleController.java | 12 ++++++- .../ui/service/AttributeBundleService.java | 8 +++++ ...undleableAttributeTypeValueSerializer.java | 3 +- .../AttributeBundleControllerTests.groovy | 36 +++++++++++++++++++ .../AttributeBundleRepositoryTests.groovy | 4 +-- 5 files changed, 58 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleController.java index ca1c18af5..e438b77ae 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleController.java @@ -1,7 +1,9 @@ package edu.internet2.tier.shibboleth.admin.ui.controller; import edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBundle; +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.security.exception.GroupDeleteException; import edu.internet2.tier.shibboleth.admin.ui.security.exception.GroupExistsConflictException; import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; import edu.internet2.tier.shibboleth.admin.ui.service.AttributeBundleService; @@ -11,7 +13,9 @@ 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; @@ -37,7 +41,13 @@ public ResponseEntity create(@RequestBody AttributeBundle bundle) throws Obje return ResponseEntity.status(HttpStatus.CREATED).body(result); } - //DELETE + @Secured("ROLE_ADMIN") + @DeleteMapping("/{resourceId}") + @Transactional + public ResponseEntity delete(@PathVariable String resourceId) throws EntityNotFoundException { + attributeBundleService.deleteDefinition(resourceId); + return ResponseEntity.noContent().build(); + } //PUT } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/AttributeBundleService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/AttributeBundleService.java index f77983e92..15611a8b1 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/AttributeBundleService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/AttributeBundleService.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.service; import edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBundle; +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.AttributeBundleRepository; import org.springframework.beans.factory.annotation.Autowired; @@ -23,4 +24,11 @@ public AttributeBundle create(AttributeBundle bundle) throws ObjectIdExistsExcep public List findAll() { return attributeBundleRepository.findAll(); } + + public void deleteDefinition(String resourceId) throws EntityNotFoundException { + if (attributeBundleRepository.findByResourceId(resourceId).isEmpty()) { + throw new EntityNotFoundException(String.format("Unable to find attribute bundle with resource id: [%s] for deletion", resourceId)); + } + attributeBundleRepository.deleteById(resourceId); + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/BundleableAttributeTypeValueSerializer.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/BundleableAttributeTypeValueSerializer.java index 32a7b7cc8..55aa1ab44 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/BundleableAttributeTypeValueSerializer.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/BundleableAttributeTypeValueSerializer.java @@ -10,8 +10,7 @@ /** * This simplifies translation to the front end. We use the ENUM on the backend, but the BundleableAttributeType * is tagged to serialize using this helper. - * Note: The deserialize is done naturally by setting spring.jackson.mapper.accept-case-insensitive-enums=true in - * the application.properties and by the setup of the ENUM itself + * Note: The deserialize is done by the setup of the ENUM itself */ public class BundleableAttributeTypeValueSerializer extends StdSerializer { public BundleableAttributeTypeValueSerializer() { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy index af0b428e7..a9180071f 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.MapperFeature import com.fasterxml.jackson.databind.ObjectMapper import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBundle +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.AttributeBundleRepository import edu.internet2.tier.shibboleth.admin.ui.service.AttributeBundleService @@ -22,6 +23,7 @@ import spock.lang.Specification import static org.hamcrest.CoreMatchers.containsString import static org.hamcrest.Matchers.containsInAnyOrder 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.content @@ -127,6 +129,40 @@ class AttributeBundleControllerTests extends Specification { .andExpect(jsonPath("\$.attributes", containsInAnyOrder("eduPersonPrincipalName", "surname", "givenName"))) } + def "test delete" () { + expect: + attributeBundleRepository.findAll().isEmpty() + + when: + def json = """ + { + "name": "bundleName", + "resourceId": "randomIDVal", + "attributes": ["eduPersonPrincipalName", "surname", "givenName"] + } + """ + AttributeBundle bundle = objectMapper.readValue(json, AttributeBundle.class) + attributeBundleRepository.save(bundle) + + then: + attributeBundleRepository.findAll().size() == 1 + + // Delete something doesn't exist + try { + mockMvc.perform(delete("/api/custom/entity/bundles/randomIDValdoesntexist")) + false + } catch (NestedServletException expected) { + expected instanceof EntityNotFoundException + } + + when: "Delete what does exist" + def result = mockMvc.perform(delete("/api/custom/entity/bundles/randomIDVal")) + + then: + result.andExpect(status().isNoContent()) + attributeBundleRepository.findAll().isEmpty() + } + // can go away with merge to develop and this extends the base test class @TestConfiguration private static class ABCTConfig { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepositoryTests.groovy index 148cceda8..e1c23c70a 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepositoryTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepositoryTests.groovy @@ -17,7 +17,7 @@ import spock.lang.Specification @ContextConfiguration(classes = [ShibUIConfiguration]) class AttributeBundleRepositoryTests extends Specification { @Autowired - AttributeBundleRepository abRepo + AttributeBundleRepository attributeBundleRepository ObjectMapper objectMapper = new ObjectMapper().with { it.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) @@ -37,7 +37,7 @@ class AttributeBundleRepositoryTests extends Specification { AttributeBundle bundle = objectMapper.readValue(json, AttributeBundle.class) when: - def result = abRepo.save(bundle) + def result = attributeBundleRepository.save(bundle) then: result == bundle From 81c34056c9d0d08e0209a28da0a49480a51b2941 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Fri, 10 Sep 2021 23:04:07 -0700 Subject: [PATCH 4/4] SHIBUI-2059 added logic for update bundles --- .../controller/AttributeBundleController.java | 21 +++++--- .../AttributeBundleExceptionHandler.java | 9 +++- .../ui/service/AttributeBundleService.java | 12 +++++ .../AttributeBundleControllerTests.groovy | 51 ++++++++++++++++--- .../AttributeBundleRepositoryTests.groovy | 5 +- 5 files changed, 80 insertions(+), 18 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleController.java index e438b77ae..a9ac9160a 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleController.java @@ -17,6 +17,7 @@ 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.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -27,12 +28,6 @@ public class AttributeBundleController { @Autowired AttributeBundleService attributeBundleService; - @GetMapping - @Transactional(readOnly = true) - public ResponseEntity getAll() { - return ResponseEntity.ok(attributeBundleService.findAll()); - } - @Secured("ROLE_ADMIN") @PostMapping @Transactional @@ -49,5 +44,17 @@ public ResponseEntity delete(@PathVariable String resourceId) throws EntityNo return ResponseEntity.noContent().build(); } - //PUT + @GetMapping + @Transactional(readOnly = true) + public ResponseEntity getAll() { + return ResponseEntity.ok(attributeBundleService.findAll()); + } + + @Secured("ROLE_ADMIN") + @PutMapping + @Transactional + public ResponseEntity update(@RequestBody AttributeBundle bundle) throws EntityNotFoundException { + AttributeBundle result = attributeBundleService.updateBundle(bundle); + return ResponseEntity.ok(result); + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleExceptionHandler.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleExceptionHandler.java index 21940d84f..9f5266c3c 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleExceptionHandler.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleExceptionHandler.java @@ -1,5 +1,6 @@ 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; @@ -7,9 +8,15 @@ 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 = {AttributeBundleController.class}) -public class AttributeBundleExceptionHandler { +public class AttributeBundleExceptionHandler extends ResponseEntityExceptionHandler { + @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({ ObjectIdExistsException.class }) public ResponseEntity handleObjectIdExistsException(ObjectIdExistsException e, WebRequest request) { HttpHeaders headers = new HttpHeaders(); diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/AttributeBundleService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/AttributeBundleService.java index 15611a8b1..f246c7d1b 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/AttributeBundleService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/AttributeBundleService.java @@ -8,6 +8,7 @@ import org.springframework.stereotype.Service; import java.util.List; +import java.util.Optional; @Service public class AttributeBundleService { @@ -31,4 +32,15 @@ public void deleteDefinition(String resourceId) throws EntityNotFoundException { } attributeBundleRepository.deleteById(resourceId); } + + public AttributeBundle updateBundle(AttributeBundle bundle) throws EntityNotFoundException { + Optional dbBundle = attributeBundleRepository.findByResourceId(bundle.getResourceId()); + if (dbBundle.isEmpty()) { + throw new EntityNotFoundException(String.format("Unable to find attribute bundle with resource id: [%s] for update", bundle.getResourceId())); + } + AttributeBundle bundleToUpdate = dbBundle.get(); + bundleToUpdate.setName(bundle.getName()); + bundleToUpdate.setAttributes(bundle.getAttributes()); + return attributeBundleRepository.save(bundleToUpdate); + } } \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy index a9180071f..00e624b7e 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy @@ -20,14 +20,13 @@ import org.springframework.transaction.annotation.Transactional import org.springframework.web.util.NestedServletException import spock.lang.Specification -import static org.hamcrest.CoreMatchers.containsString import static org.hamcrest.Matchers.containsInAnyOrder 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.request.MockMvcRequestBuilders.put import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content -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.status @@ -42,10 +41,7 @@ class AttributeBundleControllerTests extends Specification { @Autowired AttributeBundleRepository attributeBundleRepository - ObjectMapper objectMapper = new ObjectMapper().with { - it.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) - it - } + ObjectMapper objectMapper = new ObjectMapper() def MockMvc @@ -163,6 +159,49 @@ class AttributeBundleControllerTests extends Specification { attributeBundleRepository.findAll().isEmpty() } + def "Update checks" () { + expect: + attributeBundleRepository.findAll().isEmpty() + + when: "add a bundle" + def json = """ + { + "name": "bundleName", + "resourceId": "randomIDVal", + "attributes": ["eduPersonPrincipalName", "surname", "givenName"] + } + """ + AttributeBundle bundle = objectMapper.readValue(json, AttributeBundle.class) + attributeBundleRepository.saveAndFlush(bundle) + + then: "bundle doesn't exist" + bundle.setResourceId("foo") + try { + mockMvc.perform(put('/api/custom/entity/bundles').contentType(APPLICATION_JSON).content(objectMapper.writeValueAsString(bundle))) + false + } catch (NestedServletException expected) { + expected.getCause() instanceof EntityNotFoundException + } + + when: "update bundle" + json = """ + { + "name": "bundle2", + "resourceId": "randomIDVal", + "attributes": ["eduPersonUniqueId", "employeeNumber", "givenName"] + } + """ + + def result = mockMvc.perform(put('/api/custom/entity/bundles').contentType(APPLICATION_JSON).content(json)) + + then: + result.andExpect(status().isOk()) + .andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.name").value("bundle2")) + .andExpect(jsonPath("\$.resourceId").value("randomIDVal")) + .andExpect(jsonPath("\$.attributes", containsInAnyOrder("eduPersonUniqueId", "employeeNumber", "givenName"))) + } + // can go away with merge to develop and this extends the base test class @TestConfiguration private static class ABCTConfig { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepositoryTests.groovy index e1c23c70a..0db6a9555 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepositoryTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepositoryTests.groovy @@ -19,10 +19,7 @@ class AttributeBundleRepositoryTests extends Specification { @Autowired AttributeBundleRepository attributeBundleRepository - ObjectMapper objectMapper = new ObjectMapper().with { - it.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) - it - } + ObjectMapper objectMapper = new ObjectMapper() def "test create and fetch" () { given: