From c1113339db07b526e55da24a26017ebc59fae97a Mon Sep 17 00:00:00 2001 From: chasegawa Date: Wed, 23 Jun 2021 16:36:15 -0700 Subject: [PATCH] SHIBUI-1848 Basic controller and supporting items for CRUD operations around user groups --- .../admin/ui/controller/GroupController.java | 115 ++++++++++++++ .../shibboleth/admin/ui/domain/Group.java | 26 ++++ .../admin/ui/repository/GroupRepository.java | 16 ++ .../admin/ui/service/GroupServiceImpl.java | 36 +++++ .../admin/ui/service/IGroupService.java | 17 +++ .../ui/repository/GroupRepositoryTests.groovy | 144 ++++++++++++++++++ 6 files changed, 354 insertions(+) create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/GroupController.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Group.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/GroupRepository.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/GroupServiceImpl.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/IGroupService.java create mode 100644 backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/GroupRepositoryTests.groovy diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/GroupController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/GroupController.java new file mode 100644 index 000000000..bb54d113b --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/GroupController.java @@ -0,0 +1,115 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller; + +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.stereotype.Controller; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import edu.internet2.tier.shibboleth.admin.ui.domain.Group; +import edu.internet2.tier.shibboleth.admin.ui.service.IGroupService; + +@Controller +@RequestMapping(value = "/api/groups") +public class GroupController { + @Autowired + private IGroupService groupService; + + @PostMapping + @Transactional + public ResponseEntity create(@RequestBody Group group) { + // If already defined, we can't create a new one, nor will this call update the definition + Group g = groupService.find(group.getResourceId()); + + if (g != null) { + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(ServletUriComponentsBuilder.fromCurrentServletMapping().path("/api/groups").build().toUri()); + + return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).headers(headers) + .body(new ErrorResponse(String.valueOf(HttpStatus.METHOD_NOT_ALLOWED.value()), + String.format("The group with resource id: [%s] and name: [%s] already exists.", + group.getResourceId(), group.getName()))); + } + + Group result = groupService.createOrUpdateGroup(g); + return ResponseEntity.status(HttpStatus.CREATED).body(result); + } + + @PutMapping + @Transactional + public ResponseEntity update(@RequestBody Group group) { + Group g = groupService.find(group.getResourceId()); + + if (g == null) { + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(ServletUriComponentsBuilder.fromCurrentServletMapping().path("/api/groups").build().toUri()); + + return ResponseEntity.status(HttpStatus.NOT_FOUND).headers(headers) + .body(new ErrorResponse(String.valueOf(HttpStatus.NOT_FOUND.value()), + String.format("Unable to find group with resource id: [%s] and name: [%s]", + group.getResourceId(), group.getName()))); + } + + Group result = groupService.createOrUpdateGroup(g); + return ResponseEntity.ok(result); + } + + @GetMapping + @Transactional(readOnly = true) + public ResponseEntity getAll() { + return ResponseEntity.ok(groupService.findAll()); + } + + @GetMapping("/{resourceId}") + @Transactional(readOnly = true) + public ResponseEntity getOne(@PathVariable String resourceId) { + Group g = groupService.find(resourceId); + + if (g == null) { + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(ServletUriComponentsBuilder.fromCurrentServletMapping().path("/api/groups").build().toUri()); + + return ResponseEntity.status(HttpStatus.NOT_FOUND).headers(headers) + .body(new ErrorResponse(String.valueOf(HttpStatus.NOT_FOUND.value()), + String.format("Unable to find group with resource id: [%s]", resourceId))); + } + return ResponseEntity.ok(g); + } + + @DeleteMapping("/{resourceId}") + @Transactional + public ResponseEntity delete(@PathVariable String resourceId) { + Group g = groupService.find(resourceId); + + if (g == null) { + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(ServletUriComponentsBuilder.fromCurrentServletMapping().path("/api/groups").build().toUri()); + + return ResponseEntity.status(HttpStatus.NOT_FOUND).headers(headers) + .body(new ErrorResponse(String.valueOf(HttpStatus.NOT_FOUND.value()), + String.format("Unable to find group with resource id: [%s]", resourceId))); + } + try { + groupService.deleteDefinition(g); + } + catch (Exception e) { + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(ServletUriComponentsBuilder.fromCurrentServletMapping().path("/api/groups").build().toUri()); + + return ResponseEntity.status(HttpStatus.CONFLICT).headers(headers) + .body(new ErrorResponse(String.valueOf(HttpStatus.CONFLICT.value()), String.format( + "Unable to delete group with resource id: [%s] - remove all users from group", + resourceId))); + } + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Group.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Group.java new file mode 100644 index 000000000..92d836b59 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/Group.java @@ -0,0 +1,26 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain; + +import java.util.UUID; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.envers.Audited; + +import lombok.Data; + +@Entity(name = "user_groups") +@Audited +@Data +public class Group { + @Column(name = "group_description", nullable = true) + String description; + + @Column(nullable = false) + String name; + + @Id + @Column(name = "resource_id", nullable = false) + String resourceId = UUID.randomUUID().toString(); +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/GroupRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/GroupRepository.java new file mode 100644 index 000000000..231e00348 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/GroupRepository.java @@ -0,0 +1,16 @@ +package edu.internet2.tier.shibboleth.admin.ui.repository; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; + +import edu.internet2.tier.shibboleth.admin.ui.domain.Group; + +public interface GroupRepository extends JpaRepository { + List findAll(); + + Group findByResourceId(String id); + + @SuppressWarnings("unchecked") + Group save(Group group); +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/GroupServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/GroupServiceImpl.java new file mode 100644 index 000000000..03d4085ce --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/GroupServiceImpl.java @@ -0,0 +1,36 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import edu.internet2.tier.shibboleth.admin.ui.domain.Group; +import edu.internet2.tier.shibboleth.admin.ui.repository.GroupRepository; + +@Service +public class GroupServiceImpl implements IGroupService { + @Autowired + private GroupRepository repo; + + @Override + public Group createOrUpdateGroup(Group group) { + return repo.save(group); + } + + @Override + public void deleteDefinition(Group group) { + repo.delete(group); + } + + @Override + public Group find(String resourceId) { + return repo.findByResourceId(resourceId); + } + + @Override + public List findAll() { + return repo.findAll(); + } + +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/IGroupService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/IGroupService.java new file mode 100644 index 000000000..1c5f92e93 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/IGroupService.java @@ -0,0 +1,17 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import java.util.List; + +import edu.internet2.tier.shibboleth.admin.ui.domain.Group; + +public interface IGroupService { + + Group createOrUpdateGroup(Group g); + + void deleteDefinition(Group g); + + Group find(String resourceId); + + List findAll(); + +} diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/GroupRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/GroupRepositoryTests.groovy new file mode 100644 index 000000000..e7f59b23c --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/GroupRepositoryTests.groovy @@ -0,0 +1,144 @@ +package edu.internet2.tier.shibboleth.admin.ui.repository + +import javax.persistence.EntityManager + +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 edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration +import edu.internet2.tier.shibboleth.admin.ui.domain.CustomEntityAttributeDefinition +import edu.internet2.tier.shibboleth.admin.ui.domain.Group +import spock.lang.Specification + +/** + * Tests to validate the repo and model for groups + * @author chasegawa + */ +@DataJpaTest +@ContextConfiguration(classes=[InternationalizationConfiguration]) +@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) +@EntityScan("edu.internet2.tier.shibboleth.admin.ui") +class GroupRepositoryTests extends Specification { + @Autowired + GroupRepository repo + + @Autowired + EntityManager entityManager + + def "simple create test"() { + given: + def group = new Group().with { + it.name = "group-name" + it.description = "some description" + it + } + + // Confirm empty state + when: + def groups = repo.findAll() + + then: + groups.size() == 0 + + // save check + when: + repo.save(group) + entityManager.flush() + entityManager.clear() + + then: + // save check + def gList = repo.findAll() + gList.size() == 1 + def groupFromDb = gList.get(0).asType(Group) + groupFromDb.equals(group) == true + + // fetch checks + repo.findByResourceId("not an id") == null + repo.findByResourceId(groupFromDb.resourceId).equals(group) + } + + def "expected error"() { + given: + def group = new Group().with { + it.description = "some description" + it + } + + // Confirm empty state + when: + def gList = repo.findAll() + + then: + gList.size() == 0 + + // save check + when: + repo.save(group) + entityManager.flush() + entityManager.clear() + + then: + // Missing non-nullable field (name) should thrown error + final def exception = thrown(javax.persistence.PersistenceException) + } + + def "basic CRUD operations validated"() { + given: + def group = new Group().with { + it.name = "group-name" + it.description = "some description" + it + } + + // Confirm empty state + when: + def groups = repo.findAll() + + then: + groups.size() == 0 + + // save check + when: + repo.save(group) + entityManager.flush() + entityManager.clear() + + then: + // save check + def gList = repo.findAll() + gList.size() == 1 + def groupFromDb = gList.get(0).asType(Group) + groupFromDb.equals(group) == true + + // update check + groupFromDb.with { + it.description = "some new text that wasn't there before" + } + groupFromDb.equals(group) == false + + when: + repo.save(groupFromDb) + entityManager.flush() + entityManager.clear() + + then: + def gList2 = repo.findAll() + gList2.size() == 1 + def groupFromDb2 = gList2.get(0).asType(Group) + groupFromDb2.equals(group) == false + groupFromDb2.equals(groupFromDb) == true + + // delete tests + when: + repo.delete(groupFromDb2) + entityManager.flush() + entityManager.clear() + + then: + repo.findAll().size() == 0 + } +} \ No newline at end of file