From 6e94768ba25cfef8756cc3728af3956e8a499f69 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 8 Nov 2022 15:56:29 -0700 Subject: [PATCH] SHIBUI-2394 Update for when approvers are added or removed from a group --- .../admin/ui/domain/EntityDescriptor.java | 1 + .../EntityDescriptorRepository.java | 5 ++++ .../security/controller/GroupController.java | 6 +++++ .../repository/ApproversRepository.java | 10 ++++++++ .../security/repository/GroupsRepository.java | 23 +++++++++++++------ .../ui/security/service/GroupServiceImpl.java | 12 ++++++++-- .../ui/service/EntityDescriptorService.java | 3 +++ .../JPAEntityDescriptorServiceImpl.java | 18 +++++++++++++++ .../GroupsControllerIntegrationTests.groovy | 9 ++++++-- 9 files changed, 76 insertions(+), 11 deletions(-) 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 78d2b98fd..1d85e8158 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 @@ -58,6 +58,7 @@ public class EntityDescriptor extends AbstractDescriptor implements org.opensaml @ElementCollection (fetch = FetchType.EAGER) @EqualsAndHashCode.Exclude + @Getter private List approvedBy = new ArrayList<>(); @OneToOne(cascade = CascadeType.ALL) 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 5719ae687..1cfc5ca0b 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 @@ -13,6 +13,10 @@ * Repository to manage {@link EntityDescriptor} instances. */ public interface EntityDescriptorRepository extends JpaRepository { + + @Query(value="SELECT e.resourceId FROM EntityDescriptor e WHERE e.idOfOwner = :groupId AND e.serviceEnabled = false") + List findAllResourceIdsByIdOfOwnerAndNotEnabled(@Param("groupId") String groupId); + @Query(value = "select new edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorProjection(e.entityID, e.resourceId, e.serviceProviderName, e.createdBy, " + "e.createdDate, e.serviceEnabled, e.idOfOwner, e.protocol, e.approved) " + "from EntityDescriptor e") @@ -56,4 +60,5 @@ public interface EntityDescriptorRepository extends JpaRepository getEntityDescriptorsNeedingApproval(@Param("groupIds") List groupIds); + } \ No newline at end of file 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 8293c9b04..bb283b140 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 @@ -6,8 +6,10 @@ import edu.internet2.tier.shibboleth.admin.ui.security.exception.InvalidGroupRegexException; import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService; +import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tags; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -29,6 +31,9 @@ public class GroupController { @Autowired private IGroupService groupService; + @Autowired + private EntityDescriptorService entityDescriptorService; + @Secured("ROLE_ADMIN") @PostMapping @Transactional @@ -66,6 +71,7 @@ public ResponseEntity getOne(@PathVariable String resourceId) throws Persiste @Transactional public ResponseEntity update(@RequestBody Group group) throws PersistentEntityNotFound, InvalidGroupRegexException { Group result = groupService.updateGroup(group); + entityDescriptorService.checkApprovalStatusOfEntitiesForGroup(result); return ResponseEntity.ok(result); } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/ApproversRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/ApproversRepository.java index 642a18481..18a0af321 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/ApproversRepository.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/ApproversRepository.java @@ -2,7 +2,17 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.Approvers; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; public interface ApproversRepository extends JpaRepository { Approvers findByResourceId(String resourceId); + + @Query(nativeQuery = true, + value = "SELECT resource_id FROM approvers WHERE resource_id IN (SELECT approvers_resource_id " + + " FROM approvers_user_groups " + + " WHERE approver_groups_resource_id = :resourceId)") + List getApproverIdsForGroup(@Param("resourceId") String resourceId); } \ No newline at end of file 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 0b0cdeee5..82f0adea3 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 @@ -2,24 +2,33 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; public interface GroupsRepository extends JpaRepository { - void deleteByResourceId(String resourceId); - - Group findByResourceId(String id); - + @Modifying @Query(nativeQuery = true, - value = "SELECT DISTINCT user_groups_resource_id " + - " FROM user_groups_approvers " + + value = "DELETE user_groups_approvers " + " WHERE approvers_list_resource_id IN (SELECT approvers_resource_id " + " FROM approvers_user_groups " + " WHERE approver_groups_resource_id = :resourceId)") - List getGroupIdsOfGroupsToApproveFor(@Param("resourceId") String resourceId); + void clearApproversForGroup(@Param("resourceId") String resourceId); + + void deleteByResourceId(String resourceId); + + Group findByResourceId(String id); @Query(nativeQuery = true, value = "SELECT resource_id FROM user_groups") List findAllGroupIds(); + + @Query(nativeQuery = true, + value = "SELECT DISTINCT user_groups_resource_id " + + " FROM user_groups_approvers " + + " WHERE approvers_list_resource_id IN (SELECT approvers_resource_id " + + " FROM approvers_user_groups " + + " WHERE approver_groups_resource_id = :resourceId)") + List getGroupIdsOfGroupsToApproveFor(@Param("resourceId") String resourceId); } \ No newline at end of file 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 917b84fd7..438fe1aaf 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 @@ -20,6 +20,7 @@ import javax.script.ScriptException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; @Service @NoArgsConstructor @@ -125,7 +126,13 @@ private List getGroupListFromIds(List approverGroupIds) { } private void manageApproversList(Group group) { - if (group.getApproversList().isEmpty()) { + AtomicInteger approversCount = new AtomicInteger(); + group.getApproversList().forEach(a -> approversCount.addAndGet(a.getApproverGroupIds().size())); + if (approversCount.intValue() == 0) { + List ids = approversRepository.getApproverIdsForGroup(group.getResourceId()); + groupRepository.clearApproversForGroup(group.getResourceId()); + approversRepository.deleteAllById(ids); + group.setApproversList(new ArrayList<>()); return; } List updatedApprovers = new ArrayList<>(); @@ -148,7 +155,8 @@ public Group updateGroup(Group group) throws PersistentEntityNotFound, InvalidGr group.getResourceId(), group.getName())); } validateGroupRegex(group); - return groupRepository.save(group); + Group result = groupRepository.save(group); + return result; } /** 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 bd09a1901..c71dfe971 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 @@ -8,6 +8,7 @@ import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException; import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound; import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorProjection; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; import java.util.ConcurrentModificationException; import java.util.List; @@ -19,6 +20,8 @@ * @since 1.0 */ public interface EntityDescriptorService { + void checkApprovalStatusOfEntitiesForGroup(Group group); + /** * Map from front-end data representation of entity descriptor to opensaml implementation of entity descriptor model * 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 542022ce5..93a1dbce6 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 @@ -183,6 +183,10 @@ public EntityDescriptorRepresentation changeApproveStatusOfEntityDescriptor(Stri if (ed == null) { throw new PersistentEntityNotFound("Entity with resourceid[" + resourceId + "] was not found for approval"); } + return changeApproveStatusOfEntityDescriptor(ed, status); + } + + private EntityDescriptorRepresentation changeApproveStatusOfEntityDescriptor(EntityDescriptor ed, boolean status) throws PersistentEntityNotFound, ForbiddenException { if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), ed, PermissionType.approve)) { throw new ForbiddenException("You do not have the permissions necessary to approve this entity descriptor."); } @@ -203,6 +207,20 @@ public EntityDescriptorRepresentation changeApproveStatusOfEntityDescriptor(Stri return createRepresentationFromDescriptor(ed); } + /** + * Update the approval status of entities that were in some approval state but the group approvers were added/removed. + */ + @Override + public void checkApprovalStatusOfEntitiesForGroup(Group group) { + entityDescriptorRepository.findAllResourceIdsByIdOfOwnerAndNotEnabled(group.getResourceId()).forEach(id -> { + EntityDescriptor ed = entityDescriptorRepository.findByResourceId(id); + int approvedCount = ed.approvedCount(); // total number of approvals so far + List theApprovers = groupService.find(ed.getIdOfOwner()).getApproversList(); + ed.setApproved(approvedCount >= theApprovers.size()); + ed = entityDescriptorRepository.save(ed); + }); + } + @Override public EntityDescriptor createDescriptorFromRepresentation(final EntityDescriptorRepresentation representation) { EntityDescriptor ed = openSamlObjects.buildDefaultInstanceOfType(EntityDescriptor.class); 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 4fd70a37e..49b0b1f19 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 @@ -9,6 +9,8 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.Group import edu.internet2.tier.shibboleth.admin.ui.security.model.Role import edu.internet2.tier.shibboleth.admin.ui.security.model.User import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository +import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityDescriptorServiceImpl +import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityServiceImpl import edu.internet2.tier.shibboleth.admin.ui.util.WithMockAdmin import groovy.json.JsonOutput import org.springframework.beans.factory.annotation.Autowired @@ -31,6 +33,9 @@ class GroupsControllerIntegrationTests extends AbstractBaseDataJpaTest { @Autowired GroupsRepository groupsRepository + @Autowired + JPAEntityDescriptorServiceImpl service + static RESOURCE_URI = '/api/admin/groups' MockMvc mockMvc @@ -39,6 +44,7 @@ class GroupsControllerIntegrationTests extends AbstractBaseDataJpaTest { def setup() { GroupController groupController = new GroupController().with ({ it.groupService = this.groupService + it.entityDescriptorService = this.service it }) mockMvc = MockMvcBuilders.standaloneSetup(groupController).build() @@ -129,8 +135,7 @@ class GroupsControllerIntegrationTests extends AbstractBaseDataJpaTest { groupAAA.setName("NOT AAA") when: - def result = mockMvc.perform(put(RESOURCE_URI).contentType(MediaType.APPLICATION_JSON) - .content(JsonOutput.toJson(groupAAA)).accept(MediaType.APPLICATION_JSON)) + def result = mockMvc.perform(put(RESOURCE_URI).contentType(MediaType.APPLICATION_JSON).content(JsonOutput.toJson(groupAAA)).accept(MediaType.APPLICATION_JSON)) then: result.andExpect(status().isOk())