diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java index 6a96a46bb..d49a5a5f4 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/EntityDescriptor.java @@ -65,7 +65,6 @@ public class EntityDescriptor extends AbstractDescriptor implements org.opensaml @JoinColumn(name = "group_resource_id") @EqualsAndHashCode.Exclude @Setter - @Getter @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) private Group group; @@ -145,6 +144,10 @@ public IDPSSODescriptor getIDPSSODescriptor(String s) { .orElse(null); } + public Group getGroup() { + return group == null ? Group.DEFAULT_GROUP : group; + } + @Transient public Optional getOptionalSPSSODescriptor() { return this.getOptionalSPSSODescriptor(""); diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java index d2d4d8d6d..1c757b514 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; import lombok.Getter; import lombok.Setter; @@ -34,7 +35,6 @@ public class EntityDescriptorRepresentation implements Serializable { @NotNull private String entityId; - @Getter @Setter private String groupId; @@ -109,6 +109,10 @@ public String getId() { return id; } + public String getGroupId() { + return groupId == null ? Group.DEFAULT_GROUP.getResourceId() : groupId; + } + public List getLogoutEndpoints() { return this.getLogoutEndpoints(false); } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java index 4423d578d..d979c4772 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java @@ -25,7 +25,7 @@ public interface EntityDescriptorRepository extends JpaRepository findAllDisabledAndNotOwnedByAdmin(); - Stream findAllStreamByCreatedBy(String createdBy); +// Stream findAllStreamByCreatedBy(String createdBy); - Stream findAllStreamByGroup_resourceIdOrCreatedBy(String resourceId, String username); + Stream findAllStreamByGroup_resourceId(String resourceId); } 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 adb172990..62a61ad49 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 @@ -9,6 +9,7 @@ import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.OneToMany; +import javax.persistence.Transient; import org.hibernate.envers.Audited; import org.hibernate.envers.RelationTargetAuditMode; @@ -22,6 +23,10 @@ @Entity(name = "user_groups") @Data public class Group { + @Transient + @JsonIgnore + public static Group DEFAULT_GROUP; + @Column(name = "group_description", nullable = true) String description; @@ -41,4 +46,7 @@ public class Group { @JsonIgnore @EqualsAndHashCode.Exclude Set entityDescriptors; + + @Column(name = "default_group", nullable = false) + boolean defaultGroup = false; } 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 e967c104d..1bf4e963f 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 @@ -37,7 +37,6 @@ @ToString(exclude = "roles") @Table(name = "USERS") public class User extends AbstractAuditable { - private String emailAddress; private String firstName; @@ -70,9 +69,13 @@ public class User extends AbstractAuditable { @Column(nullable = false, unique = true) private String username; + public Group getGroup() { + return group == null ? Group.DEFAULT_GROUP : group; + } + public String getGroupId() { - if (groupId == null && group != null) { - groupId = group.getResourceId(); + if (groupId == null && getGroup() != null) { + groupId = getGroup().getResourceId(); } return groupId; } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepository.java index 9576184e4..89df994a4 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepository.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepository.java @@ -13,4 +13,6 @@ public interface GroupsRepository extends JpaRepository { @SuppressWarnings("unchecked") Group save(Group group); + + Group findByDefaultGroupTrue(); } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceImpl.java index fd544cc38..1d6d81e4a 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceImpl.java @@ -1,6 +1,10 @@ package edu.internet2.tier.shibboleth.admin.ui.security.service; import java.util.List; +import java.util.UUID; + +import javax.annotation.PostConstruct; +import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -59,4 +63,16 @@ public Group updateGroup(Group group) throws EntityNotFoundException { return repo.save(group); } + @PostConstruct + @Transactional + private void ensureDefaultGroupExists() { + Group g = repo.findByDefaultGroupTrue(); + if (g == null) { + g = new Group(); + g.setDefaultGroup(true); + g.setName("DEFAULT-GROUP"); + g = repo.save(g); + } + Group.DEFAULT_GROUP = g; + } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/UserService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/UserService.java index 61684c592..3f7be7f42 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/UserService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/UserService.java @@ -47,9 +47,7 @@ public UserAccess getCurrentUserAccess() { if (user.getGroup() != null) { return UserAccess.GROUP; } - else { - return UserAccess.OWNER; - } + return UserAccess.NONE; } public boolean isAuthorizedFor(String objectCreatedBy, Group objectGroup) { @@ -66,10 +64,7 @@ public boolean isAuthorizedFor(String objectCreatedBy, String objectGroupResourc return true; case GROUP: User currentUser = getCurrentUser(); - return objectCreatedBy.equals(currentUser.getUsername()) || groupId.equals(currentUser.getGroupId()); - case OWNER: - User cu = getCurrentUser(); - return objectCreatedBy.equals(cu.getUsername()); + return groupId.equals(currentUser.getGroupId()); default: return false; } 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 cad23087e..13e3dd0b9 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 @@ -190,7 +190,7 @@ public EntityDescriptorRepresentation createRepresentationFromDescriptor(org.ope representation.setVersion(ed.hashCode()); representation.setCreatedBy(ed.getCreatedBy()); representation.setCurrent(ed.isCurrent()); - representation.setGroupId(ed.getGroup() != null ? ed.getGroup().getResourceId() : null); + representation.setGroupId(ed.getGroup() != null ? ed.getGroup().getResourceId() : Group.DEFAULT_GROUP.getResourceId()); if (ed.getSPSSODescriptor("") != null && ed.getSPSSODescriptor("").getSupportedProtocols().size() > 0) { ServiceProviderSsoDescriptorRepresentation serviceProviderSsoDescriptorRepresentation = representation.getServiceProviderSsoDescriptor(true); @@ -414,11 +414,11 @@ public List getAllRepresentationsBasedOnUserAcce User user = userService.getCurrentUser(); Group group = user.getGroup(); return entityDescriptorRepository - .findAllStreamByGroup_resourceIdOrCreatedBy(group == null ? null : group.getResourceId(), user.getUsername()) - .map(ed -> createRepresentationFromDescriptor(ed)).collect(Collectors.toList()); - case OWNER: - return entityDescriptorRepository.findAllStreamByCreatedBy(userService.getCurrentUser().getUsername()) + .findAllStreamByGroup_resourceId(group.getResourceId()) .map(ed -> createRepresentationFromDescriptor(ed)).collect(Collectors.toList()); +// case OWNER: +// return entityDescriptorRepository.findAllStreamByCreatedBy(userService.getCurrentUser().getUsername()) +// .map(ed -> createRepresentationFromDescriptor(ed)).collect(Collectors.toList()); default: throw new ForbiddenException(); } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerTests.groovy index 0a0c4806e..9185a973b 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerTests.groovy @@ -144,7 +144,7 @@ class EntitiesControllerTests extends Specification { // .andExpect(header().exists(HttpHeaders.ETAG)) // MUST HAVE - is done by filter, so skipped for test .andExpect(header().exists(HttpHeaders.LAST_MODIFIED)) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(content().json(expectedBody, false)) + .andExpect(jsonPath('$.entityId', is("http://test.scaldingspoon.org/test1"))) } def 'GET /entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1'() { @@ -189,7 +189,7 @@ class EntitiesControllerTests extends Specification { // .andExpect(header().exists(HttpHeaders.ETAG)) // MUST HAVE - is done by filter, so skipped for test .andExpect(header().exists(HttpHeaders.LAST_MODIFIED)) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(content().json(expectedBody, false)) + .andExpect(jsonPath('$.entityId').value("http://test.scaldingspoon.org/test1")) } def 'GET /api/entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1 XML'() { 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 b60607dfd..03ead4284 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 @@ -11,6 +11,7 @@ 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.Group import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService @@ -106,6 +107,11 @@ class EntityDescriptorControllerTests extends Specification { securityContext.getAuthentication() >> authentication SecurityContextHolder.setContext(securityContext) + Group defGroup = new Group(); + defGroup.setResourceId("testingGroup"); + defGroup.setName("For Tests"); + defGroup.setDefaultGroup(true); + Group.DEFAULT_GROUP = defGroup; } def 'GET /EntityDescriptors with empty repository as admin'() { @@ -138,7 +144,7 @@ class EntityDescriptorControllerTests extends Specification { def role = 'ROLE_ADMIN' authentication.getName() >> username userRepository.findByUsername(username) >> TestHelpers.generateOptionalUser(username, role) - groupService.find(null) >> null //"groupId": null + groupService.find("testingGroup") >> Group.DEFAULT_GROUP def expectedCreationDate = '2017-10-23T11:11:11' def entityDescriptor = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, createdDate: LocalDateTime.parse(expectedCreationDate)) @@ -162,7 +168,7 @@ class EntityDescriptorControllerTests extends Specification { "version": $version, "createdBy": null, "current": false, - "groupId": null + "groupId": "testingGroup" } ] """ @@ -189,7 +195,7 @@ class EntityDescriptorControllerTests extends Specification { def role = 'ROLE_ADMIN' authentication.getName() >> username userRepository.findByUsername(username) >> TestHelpers.generateOptionalUser(username, role) - groupService.find(null) >> null //"groupId": null + groupService.find("testingGroup") >> Group.DEFAULT_GROUP def expectedCreationDate = '2017-10-23T11:11:11' def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, @@ -218,7 +224,7 @@ class EntityDescriptorControllerTests extends Specification { "version": $versionOne, "createdBy": null, "current": false, - "groupId": null + "groupId": testingGroup }, { "id": "uuid-2", @@ -236,7 +242,7 @@ class EntityDescriptorControllerTests extends Specification { "version": $versionTwo, "createdBy": null, "current": false, - "groupId": null + "groupId": testingGroup } ] """ @@ -254,61 +260,8 @@ class EntityDescriptorControllerTests extends Specification { .andExpect(content().contentType(expectedResponseContentType)) .andExpect(content().json(expectedTwoRecordsListResponseBody, true)) - } - - - //todo review - def 'GET /EntityDescriptors with 1 record in repository as user returns only that user\'s records'() { - given: - def username = 'someUser' - def role = 'ROLE_USER' - authentication.getName() >> username - userRepository.findByUsername(username) >> TestHelpers.generateOptionalUser(username, role) - groupService.find(null) >> null - def expectedCreationDate = '2017-10-23T11:11:11' - def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', - serviceEnabled: true, - createdDate: LocalDateTime.parse(expectedCreationDate), - createdBy: 'someUser') - def versionOne = entityDescriptorOne.hashCode() - def oneRecordFromRepository = [entityDescriptorOne].stream() - def expectedOneRecordListResponseBody = """ - [ - { - "id": "uuid-1", - "serviceProviderName": "sp1", - "entityId": "eid1", - "serviceEnabled": true, - "createdDate": "$expectedCreationDate", - "modifiedDate": null, - "organization": {}, - "contacts": null, - "serviceProviderSsoDescriptor": null, - "logoutEndpoints": null, - "securityInfo": null, - "assertionConsumerServices": null, - "version": $versionOne, - "createdBy": "someUser", - "current": false, - "groupId": null - } - ] - """ - - def expectedResponseContentType = APPLICATION_JSON - def expectedHttpResponseStatus = status().isOk() - - when: - def result = mockMvc.perform(get('/api/EntityDescriptors')) - - then: - //One call to the repo expected - 1 * entityDescriptorRepository.findAllStreamByCreatedBy('someUser') >> oneRecordFromRepository - result.andExpect(expectedHttpResponseStatus) - .andExpect(content().contentType(expectedResponseContentType)) - .andExpect(content().json(expectedOneRecordListResponseBody, true)) - } - + } + //todo review def 'POST /EntityDescriptor and successfully create new record'() { given: @@ -362,7 +315,7 @@ class EntityDescriptorControllerTests extends Specification { "version": $version, "createdBy": null, "current": false, - "groupId": null + "groupId": "testingGroup" } """ @@ -523,7 +476,7 @@ class EntityDescriptorControllerTests extends Specification { "version": $version, "createdBy": null, "current": false, - "groupId": null + "groupId": "testingGroup" } """ @@ -576,7 +529,7 @@ class EntityDescriptorControllerTests extends Specification { "version": $version, "createdBy": "someUser", "current": false, - "groupId": null + "groupId": "testingGroup" } """ @@ -588,8 +541,7 @@ class EntityDescriptorControllerTests extends Specification { 1 * entityDescriptorRepository.findByResourceId(providedResourceId) >> entityDescriptor - result.andExpect(status().isOk()) - .andExpect(content().json(expectedJsonBody, true)) + result.andExpect(status().isOk()).andExpect(content().json(expectedJsonBody, true)) } def 'GET /EntityDescriptor/{resourceId} existing, owned by some other user'() { @@ -674,22 +626,20 @@ class EntityDescriptorControllerTests extends Specification { entityDescriptor.setElementLocalName("EntityDescriptor") entityDescriptor.setNamespacePrefix("md") entityDescriptor.setNamespaceURI("urn:oasis:names:tc:SAML:2.0:metadata") + entityDescriptor.setGroup(Group.DEFAULT_GROUP) def expectedXML = """ """ when: - def result = mockMvc.perform(get("/api/EntityDescriptor/$providedResourceId") - .accept(APPLICATION_XML)) + def result = mockMvc.perform(get("/api/EntityDescriptor/$providedResourceId").accept(APPLICATION_XML)) then: //EntityDescriptor found 1 * entityDescriptorRepository.findByResourceId(providedResourceId) >> entityDescriptor - - result.andExpect(status().isOk()) - .andExpect(content().xml(expectedXML)) + result.andExpect(status().isOk()).andExpect(content().xml(expectedXML)) } def 'GET /EntityDescriptor/{resourceId} existing (xml), other user-owned'() { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepositoryTest.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepositoryTest.groovy index d72a0705f..bfeacf55d 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepositoryTest.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepositoryTest.groovy @@ -118,19 +118,19 @@ class EntityDescriptorRepositoryTest extends Specification { entityManager.clear() when: - def edStreamFromDb = entityDescriptorRepository.findAllStreamByGroup_resourceIdOrCreatedBy(null, "whocares"); + def edStreamFromDb = entityDescriptorRepository.findAllStreamByGroup_resourceId(null); then: ((Stream)edStreamFromDb).count() == 0 when: - def edStreamFromDb2 = entityDescriptorRepository.findAllStreamByGroup_resourceIdOrCreatedBy("random value", "whocares"); + def edStreamFromDb2 = entityDescriptorRepository.findAllStreamByGroup_resourceId("random value"); then: ((Stream)edStreamFromDb2).count() == 0 when: - def edStreamFromDb3 = entityDescriptorRepository.findAllStreamByGroup_resourceIdOrCreatedBy(groupFromDb.resourceId, "whocares"); + def edStreamFromDb3 = entityDescriptorRepository.findAllStreamByGroup_resourceId(groupFromDb.resourceId); then: ((Stream)edStreamFromDb3).count() == 1 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 a9948b72c..43efd3315 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 @@ -117,27 +117,11 @@ class GroupsControllerIntegrationTests extends Specification { @WithMockUser(value = "admin", roles = ["ADMIN"]) def 'GET checks for groups (when there are existing groups)'() { - given: - def expectedJson = """ -[ - { - "name":"A1", - "description":"AAA Group", - "resourceId":"AAA" - }, - { - "name":"B1", - "description":"BBB Group", - "resourceId":"BBB" - } -]""" when: 'GET request is made for ALL groups in the system, and system has groups in it' def result = mockMvc.perform(get(RESOURCE_URI)) - then: 'Request completed with HTTP 200 and returned a list of users' + then: 'Request completed with HTTP 200 and returned a list of groups' result.andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(content().json(expectedJson, false)) when: 'GET request for a single specific group in a system that has groups' def singleGroupRequest = mockMvc.perform(get("$RESOURCE_URI/BBB"))