Skip to content

Commit

Permalink
SHIBUI-2394
Browse files Browse the repository at this point in the history
Update for when approvers are added or removed from a group
  • Loading branch information
chasegawa committed Nov 8, 2022
1 parent ef8b9bc commit 6e94768
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public class EntityDescriptor extends AbstractDescriptor implements org.opensaml

@ElementCollection (fetch = FetchType.EAGER)
@EqualsAndHashCode.Exclude
@Getter
private List<String> approvedBy = new ArrayList<>();

@OneToOne(cascade = CascadeType.ALL)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
* Repository to manage {@link EntityDescriptor} instances.
*/
public interface EntityDescriptorRepository extends JpaRepository<EntityDescriptor, Long> {

@Query(value="SELECT e.resourceId FROM EntityDescriptor e WHERE e.idOfOwner = :groupId AND e.serviceEnabled = false")
List<String> 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")
Expand Down Expand Up @@ -56,4 +60,5 @@ public interface EntityDescriptorRepository extends JpaRepository<EntityDescript
" and e.serviceEnabled = false" +
" and e.approved = false")
List<EntityDescriptorProjection> getEntityDescriptorsNeedingApproval(@Param("groupIds") List<String> groupIds);

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,6 +31,9 @@ public class GroupController {
@Autowired
private IGroupService groupService;

@Autowired
private EntityDescriptorService entityDescriptorService;

@Secured("ROLE_ADMIN")
@PostMapping
@Transactional
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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, String> {
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<String> getApproverIdsForGroup(@Param("resourceId") String resourceId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Group, String> {
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<String> 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<String> 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<String> getGroupIdsOfGroupsToApproveFor(@Param("resourceId") String resourceId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import javax.script.ScriptException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@Service
@NoArgsConstructor
Expand Down Expand Up @@ -125,7 +126,13 @@ private List<Group> getGroupListFromIds(List<String> 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<String> ids = approversRepository.getApproverIdsForGroup(group.getResourceId());
groupRepository.clearApproversForGroup(group.getResourceId());
approversRepository.deleteAllById(ids);
group.setApproversList(new ArrayList<>());
return;
}
List<Approvers> updatedApprovers = new ArrayList<>();
Expand All @@ -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;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
Expand All @@ -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<Approvers> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -31,6 +33,9 @@ class GroupsControllerIntegrationTests extends AbstractBaseDataJpaTest {
@Autowired
GroupsRepository groupsRepository

@Autowired
JPAEntityDescriptorServiceImpl service

static RESOURCE_URI = '/api/admin/groups'

MockMvc mockMvc
Expand All @@ -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()
Expand Down Expand Up @@ -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())
Expand Down

0 comments on commit 6e94768

Please sign in to comment.