diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupController.java index 54ba0f863..da6d5edae 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupController.java @@ -99,18 +99,16 @@ public ResponseEntity delete(@PathVariable String resourceId) { .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) { + if (!g.getUsers().isEmpty()) { HttpHeaders headers = new HttpHeaders(); headers.setLocation(ServletUriComponentsBuilder.fromCurrentServletMapping().path("/api/admin/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", + "Unable to delete group with resource id: [%s] - remove all users from group first", resourceId))); } + groupService.deleteDefinition(g); return ResponseEntity.noContent().build(); } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Group.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Group.java index 65880b3b8..db903e988 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Group.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Group.java @@ -1,17 +1,21 @@ package edu.internet2.tier.shibboleth.admin.ui.security.model; +import java.util.Set; import java.util.UUID; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.Id; +import javax.persistence.OneToMany; -import org.hibernate.envers.Audited; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; +import lombok.EqualsAndHashCode; @Entity(name = "user_groups") -@Audited @Data public class Group { @Column(name = "group_description", nullable = true) @@ -23,4 +27,9 @@ public class Group { @Id @Column(name = "resource_id") String resourceId = UUID.randomUUID().toString(); + + @OneToMany(mappedBy = "group", cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JsonIgnore + @EqualsAndHashCode.Exclude + Set users; } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/User.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/User.java index c30c4ceff..df4a96e13 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/User.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/User.java @@ -10,12 +10,15 @@ import lombok.ToString; import org.apache.commons.lang.StringUtils; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; import javax.persistence.Table; import javax.persistence.Transient; import java.util.HashSet; @@ -30,23 +33,25 @@ @NoArgsConstructor @Getter @Setter -@EqualsAndHashCode(callSuper = true, exclude = "roles") +@EqualsAndHashCode(callSuper = true) @ToString(exclude = "roles") @Table(name = "USERS") public class User extends AbstractAuditable { - @Column(nullable = false, unique = true) - private String username; - - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - @Column(nullable = false) - private String password; + private String emailAddress; private String firstName; + @ManyToOne + @JoinColumn(name = "resource_id") + @EqualsAndHashCode.Exclude + private Group group; + private String lastName; - private String emailAddress; + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + @Column(nullable = false) + private String password; @Transient private String role; @@ -54,8 +59,12 @@ public class User extends AbstractAuditable { @JsonIgnore @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) + @EqualsAndHashCode.Exclude private Set roles = new HashSet<>(); + @Column(nullable = false, unique = true) + private String username; + public String getRole() { if (StringUtils.isBlank(this.role)) { Set roles = this.getRoles(); diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupsControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupsControllerIntegrationTests.groovy index ed2030e4d..4b83af244 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupsControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupsControllerIntegrationTests.groovy @@ -33,6 +33,7 @@ class GroupsControllerIntegrationTests extends Specification { private MockMvc mockMvc static RESOURCE_URI = '/api/admin/groups' + static USERS_RESOURCE_URI = '/api/admin/users' @Rollback @WithMockUser(value = "admin", roles = ["ADMIN"]) @@ -147,33 +148,81 @@ class GroupsControllerIntegrationTests extends Specification { nonexistentGroupRequest.andExpect(status().isNotFound()) } +// @Rollback +// @WithMockUser(value = "admin", roles = ["ADMIN"]) +// def 'DELETE ONE existing group'() { +// when: 'GET request for a single specific group in a system that has groups' +// def result = mockMvc.perform(get("$RESOURCE_URI/BBB")) +// +// then: 'GET request for a single specific group completed with HTTP 200' +// result.andExpect(status().isOk()) +// +// when: 'DELETE request is made' +// result = mockMvc.perform(delete("$RESOURCE_URI/BBB")) +// +// then: 'DELETE was successful' +// result.andExpect(status().isNoContent()) +// +// when: 'GET request for a single specific group just deleted' +// result = mockMvc.perform(get("$RESOURCE_URI/BBB")) +// +// then: 'The group not found' +// result.andExpect(status().isNotFound()) +// +// when: 'DELETE request for a single specific group that does not exist' +// result = mockMvc.perform(delete("$RESOURCE_URI/CCCC")) +// +// then: 'The group not found' +// result.andExpect(status().isNotFound()) +// } + @Rollback @WithMockUser(value = "admin", roles = ["ADMIN"]) - def 'DELETE ONE existing group'() { - when: 'GET request for a single specific group in a system that has groups' - def result = mockMvc.perform(get("$RESOURCE_URI/BBB")) + def 'DELETE performs correctly when group attached to a user'() { + given: + def group = [name: 'A1', + description: 'AAA Group', + resourceId: 'AAA'] + def newUser = [firstName: 'Foo', + lastName: 'Bar', + username: 'FooBar', + password: 'somepass', + emailAddress: 'foo@institution.edu', + role: 'ROLE_USER', + group: group] - then: 'GET request for a single specific group completed with HTTP 200' + when: + def result = mockMvc.perform(post(USERS_RESOURCE_URI) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonOutput.toJson(newUser)) + .accept(MediaType.APPLICATION_JSON)) + + then: result.andExpect(status().isOk()) - - when: 'DELETE request is made' - result = mockMvc.perform(delete("$RESOURCE_URI/BBB")) - - then: 'DELETE was successful' - result.andExpect(status().isNoContent()) - - when: 'GET request for a single specific group just deleted' - result = mockMvc.perform(get("$RESOURCE_URI/BBB")) - - then: 'The group not found' - result.andExpect(status().isNotFound()) + + when: + def userresult = mockMvc.perform(get("$USERS_RESOURCE_URI/$newUser.username")) + def expectedJson = """ +{ + "modifiedBy" : admin, + "firstName" : "Foo", + "emailAddress" : "foo@institution.edu", + "role" : "ROLE_USER", + "username" : "FooBar", + "createdBy" : admin, + "lastName" : "Bar", + "group" : {"description":"AAA Group","name":"A1","resourceId":"AAA"} +}""" + then: 'Request completed with HTTP 200 and returned one user' + userresult.andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(expectedJson, false)) - when: 'DELETE request for a single specific group that does not exist' - result = mockMvc.perform(delete("$RESOURCE_URI/CCCC")) - - then: 'The group not found' - result.andExpect(status().isNotFound()) - //ADD conflict test when the group has users + when: 'DELETE request is made' + result = mockMvc.perform(delete("$RESOURCE_URI/$group.resourceId")) + + then: 'DELETE was not successful' + result.andExpect(status().isConflict()) } }