From 671b0791ecc8c46f1c573635b758d16779b14074 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 27 Sep 2022 10:10:16 -0700 Subject: [PATCH 01/56] SHIBUI-2394 incremental progress --- .../admin/ui/security/model/Approvers.java | 24 +++++++++++++ .../admin/ui/security/model/Group.java | 6 ++++ .../security/service/GroupServiceTests.groovy | 34 +++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java new file mode 100644 index 000000000..9d51e403d --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java @@ -0,0 +1,24 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.model; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +@Data +@NoArgsConstructor +@Entity(name = "approvers") +public class Approvers { + @Id + @Column(name = "resource_id") + private String resourceId = UUID.randomUUID().toString(); + + @ManyToMany + private Set approverGroups = new HashSet<>(); +} \ No newline at end of file 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 c0d579c30..ffba5ac0f 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,6 +1,8 @@ package edu.internet2.tier.shibboleth.admin.ui.security.model; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.UUID; @@ -8,6 +10,7 @@ import javax.persistence.Entity; import javax.persistence.EntityListeners; import javax.persistence.Id; +import javax.persistence.OneToMany; import javax.persistence.Transient; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -51,6 +54,9 @@ public class Group implements Owner { @Column(name = "validation_regex") private String validationRegex; + @OneToMany + private List approvalGroups = new ArrayList<>(); + /** * Define a Group object based on the user */ diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceTests.groovy index c88838875..43aed5f75 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceTests.groovy @@ -2,6 +2,7 @@ package edu.internet2.tier.shibboleth.admin.ui.security.service import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest import edu.internet2.tier.shibboleth.admin.ui.security.exception.InvalidGroupRegexException +import edu.internet2.tier.shibboleth.admin.ui.security.model.Approvers import edu.internet2.tier.shibboleth.admin.ui.security.model.Group import org.springframework.test.annotation.Rollback @@ -80,4 +81,37 @@ class GroupServiceTests extends AbstractBaseDataJpaTest { !groupService.doesStringMatchGroupPattern("AAA", "something") groupService.doesStringMatchGroupPattern("AAA", "/foobar/") } + + def "CRUD operations - approver groups" () { + given: + groupService.clearAllForTesting(); + HashSet apprGroups = new HashSet<>() + String[] groupNames = ['AAA', 'BBB', 'CCC', 'DDD'] + groupNames.each {name -> { + Group group = new Group().with({ + it.name = name + it.description = name + it.resourceId = name + it + }) + group = groupRepository.saveAndFlush(group) + if (!name.equals('AAA')) { + apprGroups.add(group) + } + }} + + when: "Adding approval list to a group" + Approvers approvers = new Approvers() + approvers.setApproverGroups(apprGroups) + List apprList = new ArrayList<>() + apprList.add(approvers) + Group aaaGroup = groupService.find('AAA') + aaaGroup.setApprovalGroups(apprList) + groupService.updateGroup(aaaGroup) + Group lookupGroup = groupService.find('AAA') + + then: + lookupGroup.getApprovalGroups().size() == 1 + } + } \ No newline at end of file From 8af010116b2df8a296a8da9a682139786619a39a Mon Sep 17 00:00:00 2001 From: chasegawa Date: Thu, 29 Sep 2022 14:08:12 -0700 Subject: [PATCH 02/56] SHIBUI-2394 Added Approvers list to groups and unit tests --- .../admin/ui/security/model/Approvers.java | 7 ++++-- .../admin/ui/security/model/Group.java | 12 +++++++++- .../repository/ApproversRepository.java | 7 ++++++ .../admin/ui/AbstractBaseDataJpaTest.groovy | 4 ++++ .../security/service/GroupServiceTests.groovy | 24 +++++++++++++++---- 5 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/ApproversRepository.java diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java index 9d51e403d..811e4ffc6 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java @@ -7,7 +7,10 @@ import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.UUID; @@ -19,6 +22,6 @@ public class Approvers { @Column(name = "resource_id") private String resourceId = UUID.randomUUID().toString(); - @ManyToMany - private Set approverGroups = new HashSet<>(); + @OneToMany + private List approverGroups = new ArrayList<>(); } \ No newline at end of file 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 ffba5ac0f..904f3abc5 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 @@ -55,7 +55,7 @@ public class Group implements Owner { private String validationRegex; @OneToMany - private List approvalGroups = new ArrayList<>(); + private List approversList = new ArrayList<>(); /** * Define a Group object based on the user @@ -86,4 +86,14 @@ public Set getOwnedItems() { } return ownedItems; } + + @Override + public int hashCode() { + return resourceId.hashCode(); + } + + @Override + public boolean equals(Object o) { + return o instanceof Group && this.resourceId.equals(((Group)o).resourceId); + } } \ 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 new file mode 100644 index 000000000..d7d20cef4 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/ApproversRepository.java @@ -0,0 +1,7 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.repository; + +import edu.internet2.tier.shibboleth.admin.ui.security.model.Approvers; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ApproversRepository extends JpaRepository { +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/AbstractBaseDataJpaTest.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/AbstractBaseDataJpaTest.groovy index da12d8bc2..85859855c 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/AbstractBaseDataJpaTest.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/AbstractBaseDataJpaTest.groovy @@ -4,6 +4,7 @@ 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.model.listener.GroupUpdatedEntityListener import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.UserUpdatedEntityListener +import edu.internet2.tier.shibboleth.admin.ui.security.repository.ApproversRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository @@ -33,6 +34,9 @@ import javax.persistence.EntityManager @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") abstract class AbstractBaseDataJpaTest extends Specification implements ResetsDatabaseTrait { + @Autowired + ApproversRepository approversRepository + @Autowired EntityManager entityManager diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceTests.groovy index 43aed5f75..608e8065a 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceTests.groovy @@ -85,7 +85,7 @@ class GroupServiceTests extends AbstractBaseDataJpaTest { def "CRUD operations - approver groups" () { given: groupService.clearAllForTesting(); - HashSet apprGroups = new HashSet<>() + List apprGroups = new ArrayList<>() String[] groupNames = ['AAA', 'BBB', 'CCC', 'DDD'] groupNames.each {name -> { Group group = new Group().with({ @@ -94,24 +94,38 @@ class GroupServiceTests extends AbstractBaseDataJpaTest { it.resourceId = name it }) - group = groupRepository.saveAndFlush(group) + group = groupRepository.save(group) + }} + entityManager.flush() + entityManager.clear() + + groupNames.each {name ->{ if (!name.equals('AAA')) { - apprGroups.add(group) + apprGroups.add(groupRepository.findByResourceId(name)) } }} when: "Adding approval list to a group" Approvers approvers = new Approvers() approvers.setApproverGroups(apprGroups) + approvers = approversRepository.save(approvers) + entityManager.flush() + entityManager.clear() + List apprList = new ArrayList<>() apprList.add(approvers) Group aaaGroup = groupService.find('AAA') - aaaGroup.setApprovalGroups(apprList) + aaaGroup.setApproversList(apprList) groupService.updateGroup(aaaGroup) Group lookupGroup = groupService.find('AAA') then: - lookupGroup.getApprovalGroups().size() == 1 + lookupGroup.getApproversList().size() == 1 + List approvalGroups = lookupGroup.getApproversList().get(0).getApproverGroups() + approvalGroups.size() == 3 + apprGroups.each {group -> { + assert approvalGroups.contains(group)} + } } } \ No newline at end of file From af200b1638688bd45181ec5bd55ba764f18e7de1 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Fri, 30 Sep 2022 13:24:46 -0700 Subject: [PATCH 03/56] SHIBUI-2394 Updates and additional unit testing for adding/editing/removing approvers to a group --- .../admin/ui/security/model/Group.java | 3 +- .../repository/ApproversRepository.java | 1 + .../ui/security/service/GroupServiceImpl.java | 39 ++++++++--- .../GroupsControllerIntegrationTests.groovy | 66 ++++++++++++++----- .../repository/GroupsRepositoryTests.groovy | 6 +- .../security/service/GroupServiceTests.groovy | 46 +++++++++---- 6 files changed, 119 insertions(+), 42 deletions(-) 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 904f3abc5..ac02c5067 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.Column; import javax.persistence.Entity; import javax.persistence.EntityListeners; +import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Transient; @@ -54,7 +55,7 @@ public class Group implements Owner { @Column(name = "validation_regex") private String validationRegex; - @OneToMany + @OneToMany(fetch = FetchType.LAZY) private List approversList = new ArrayList<>(); /** 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 d7d20cef4..642a18481 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 @@ -4,4 +4,5 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface ApproversRepository extends JpaRepository { + Approvers findByResourceId(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 f329a5be2..6eac2fde7 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 @@ -4,7 +4,9 @@ import edu.internet2.tier.shibboleth.admin.ui.security.exception.GroupDeleteException; import edu.internet2.tier.shibboleth.admin.ui.security.exception.GroupExistsConflictException; import edu.internet2.tier.shibboleth.admin.ui.security.exception.InvalidGroupRegexException; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Approvers; import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.ApproversRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository; import lombok.NoArgsConstructor; @@ -16,6 +18,7 @@ import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; +import java.util.ArrayList; import java.util.List; @Service @@ -25,28 +28,29 @@ public class GroupServiceImpl implements IGroupService { private static final String REGEX_MATCHER = "function validate(r, s){ return RegExp(r).test(s);};validate(rgx, str);"; private final ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript"); + @Autowired + protected ApproversRepository approversRepository; + @Autowired protected GroupsRepository groupRepository; @Autowired protected OwnershipRepository ownershipRepository; - public GroupServiceImpl(GroupsRepository repo, OwnershipRepository ownershipRepository) { - this.groupRepository = repo; - this.ownershipRepository = ownershipRepository; - } +// public GroupServiceImpl(GroupsRepository repo, OwnershipRepository ownershipRepository) { +// this.groupRepository = repo; +// this.ownershipRepository = ownershipRepository; +// } @Override @Transactional public Group createGroup(Group group) throws GroupExistsConflictException, InvalidGroupRegexException { - Group foundGroup = find(group.getResourceId()); // If already defined, we don't want to create a new one, nor do we want this call update the definition - if (foundGroup != null) { - throw new GroupExistsConflictException( - String.format("Call update (PUT) to modify the group with resource id: [%s] and name: [%s]", - foundGroup.getResourceId(), foundGroup.getName())); + if (groupRepository.existsById(group.getResourceId())) { + throw new GroupExistsConflictException(String.format("Call update (PUT) to modify the group with resource id: [%s] and name: [%s]", group.getResourceId(), group.getName())); } validateGroupRegex(group); + manageApproversList(group); return groupRepository.save(group); } @@ -59,6 +63,7 @@ public void deleteDefinition(String resourceId) throws PersistentEntityNotFound, "Unable to delete group with resource id: [%s] - remove all items owned by / associated with the group first", resourceId)); } + approversRepository.deleteAll(group.getApproversList()); groupRepository.delete(group); } @@ -117,6 +122,7 @@ public List findAll() { @Override public Group updateGroup(Group group) throws PersistentEntityNotFound, InvalidGroupRegexException { + manageApproversList(group); // have to make sure that approvers have been saved before a fetch or we can get data integrity errors on lookup... Group g = find(group.getResourceId()); if (g == null) { throw new PersistentEntityNotFound(String.format("Unable to find group with resource id: [%s] and name: [%s]", @@ -126,6 +132,21 @@ public Group updateGroup(Group group) throws PersistentEntityNotFound, InvalidGr return groupRepository.save(group); } + private void manageApproversList(Group group) { + if (group.getApproversList().isEmpty()) { + return; + } + List updatedApprovers = new ArrayList<>(); + group.getApproversList().forEach(approvers -> { + Approvers savedApprovers = approversRepository.findByResourceId(approvers.getResourceId()); + savedApprovers = savedApprovers == null ? approvers : savedApprovers; + savedApprovers.setApproverGroups(approvers.getApproverGroups()); + Approvers updatedApp = approversRepository.save(savedApprovers); + updatedApprovers.add(updatedApp); + }); + group.setApproversList(updatedApprovers); + } + /** * If the regex is blank simply return */ 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 bb4613f6b..6fae2ed87 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 @@ -4,6 +4,7 @@ import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound import edu.internet2.tier.shibboleth.admin.ui.security.exception.GroupDeleteException import edu.internet2.tier.shibboleth.admin.ui.security.exception.GroupExistsConflictException +import edu.internet2.tier.shibboleth.admin.ui.security.model.Approvers 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 @@ -47,36 +48,65 @@ class GroupsControllerIntegrationTests extends AbstractBaseDataJpaTest { @WithMockAdmin def 'POST new group persists properly'() { given: - def newGroup = [name: 'Foo', - description: 'Bar', - resourceId: 'FooBar'] - - def expectedJson = """ - { - "name":"Foo", - "description":"Bar", - "resourceId":"FooBar" - } -""" + def newGroup = [name: 'Foo', description: 'Bar', resourceId: 'FooBar'] + when: - def result = mockMvc.perform(post(RESOURCE_URI).contentType(MediaType.APPLICATION_JSON) - .content(JsonOutput.toJson(newGroup)).accept(MediaType.APPLICATION_JSON)) + def result = mockMvc.perform(post(RESOURCE_URI).contentType(MediaType.APPLICATION_JSON).content(JsonOutput.toJson(newGroup)).accept(MediaType.APPLICATION_JSON)) then: result.andExpect(status().isCreated()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(content().json(expectedJson, false)) - + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("\$.name").value("Foo")) + .andExpect(jsonPath("\$.resourceId").value("FooBar")) + .andExpect(jsonPath("\$.description").value("Bar")) - //'Try to create with an existing resource id' + //'Try to create with an existing resource id' try { mockMvc.perform(post(RESOURCE_URI).contentType(MediaType.APPLICATION_JSON) .content(JsonOutput.toJson(newGroup)) .accept(MediaType.APPLICATION_JSON)) - false + false // failure if the call didn't throw } catch (Throwable expected) { expected instanceof GroupExistsConflictException } + + when: "POST new group with approvers" + groupService.clearAllForTesting() + List apprGroups = new ArrayList<>() + String[] groupNames = ['AAA', 'BBB', 'CCC', 'DDD'] + groupNames.each {name -> { + Group group = new Group().with({ + it.name = name + it.description = name + it.resourceId = name + it + }) + groupRepository.save(group) + }} + entityManager.flush() + entityManager.clear() + + groupNames.each {name ->{ + if (!name.equals('AAA')) { + apprGroups.add(groupRepository.findByResourceId(name)) + } + }} + Approvers approvers = new Approvers() + approvers.setApproverGroups(apprGroups) + def apprList = new ArrayList<>() + apprList.add(approvers) + def newGroup2 = [name: 'Foo', description: 'Bar', resourceId: 'FooBar', approversList: apprList] + def result2 = mockMvc.perform(post(RESOURCE_URI).contentType(MediaType.APPLICATION_JSON).content(JsonOutput.toJson(newGroup2)).accept(MediaType.APPLICATION_JSON)) + + then: + result2.andExpect(status().isCreated()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("\$.name").value("Foo")) + .andExpect(jsonPath("\$.resourceId").value("FooBar")) + .andExpect(jsonPath("\$.description").value("Bar")) + .andExpect(jsonPath("\$.approversList[0].approverGroups[0].resourceId").value("BBB")) + .andExpect(jsonPath("\$.approversList[0].approverGroups[1].resourceId").value("CCC")) + .andExpect(jsonPath("\$.approversList[0].approverGroups[2].resourceId").value("DDD")) } @WithMockAdmin diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy index a81443166..f0dcfa68d 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy @@ -176,11 +176,11 @@ class GroupsRepositoryTests extends AbstractBaseDataJpaTest { def groupFromDb = gList.get(0) as Group groupFromDb == group - // update check + // update check - equality for groups should be by id groupFromDb.with { it.description = "some new text that wasn't there before" } - groupFromDb.equals(group) == false + groupFromDb.equals(group) == true when: groupsRepo.save(groupFromDb) @@ -189,7 +189,7 @@ class GroupsRepositoryTests extends AbstractBaseDataJpaTest { def gList2 = groupsRepo.findAll() gList2.size() == 1 def groupFromDb2 = gList2.get(0) as Group - groupFromDb2.equals(group) == false + groupFromDb2.equals(group) == true groupFromDb2 == groupFromDb // delete tests diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceTests.groovy index 608e8065a..16045bca7 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceTests.groovy @@ -84,7 +84,7 @@ class GroupServiceTests extends AbstractBaseDataJpaTest { def "CRUD operations - approver groups" () { given: - groupService.clearAllForTesting(); + groupService.clearAllForTesting() List apprGroups = new ArrayList<>() String[] groupNames = ['AAA', 'BBB', 'CCC', 'DDD'] groupNames.each {name -> { @@ -94,38 +94,62 @@ class GroupServiceTests extends AbstractBaseDataJpaTest { it.resourceId = name it }) - group = groupRepository.save(group) + groupRepository.save(group) }} entityManager.flush() entityManager.clear() + when: "Adding approval list to a group" groupNames.each {name ->{ if (!name.equals('AAA')) { apprGroups.add(groupRepository.findByResourceId(name)) } }} - - when: "Adding approval list to a group" Approvers approvers = new Approvers() approvers.setApproverGroups(apprGroups) - approvers = approversRepository.save(approvers) - entityManager.flush() - entityManager.clear() - - List apprList = new ArrayList<>() + def apprList = new ArrayList<>() apprList.add(approvers) Group aaaGroup = groupService.find('AAA') aaaGroup.setApproversList(apprList) groupService.updateGroup(aaaGroup) - Group lookupGroup = groupService.find('AAA') then: + def lookupGroup = groupService.find('AAA') lookupGroup.getApproversList().size() == 1 - List approvalGroups = lookupGroup.getApproversList().get(0).getApproverGroups() + def approvalGroups = lookupGroup.getApproversList().get(0).getApproverGroups() approvalGroups.size() == 3 apprGroups.each {group -> { assert approvalGroups.contains(group)} } + + when: "removing approver group from existing list" + approvers.getApproverGroups().remove(groupService.find('BBB')) + apprList = new ArrayList<>() + apprList.add(approvers) + aaaGroup.setApproversList(apprList) + groupService.updateGroup(aaaGroup) + + then: + def lookupGroup2 = groupService.find('AAA') + lookupGroup2.getApproversList().size() == 1 + def approvalGroups2 = lookupGroup2.getApproversList().get(0).getApproverGroups() + approvalGroups2.size() == 2 + apprGroups.each { group -> + { + if (group.getResourceId() != 'BBB') { + assert approvalGroups2.contains(group) + } + } + } + + when: "removing all approver groups" + apprList = new ArrayList<>() + aaaGroup.setApproversList(apprList) + groupService.updateGroup(aaaGroup) + + then: + def lookupGroup3 = groupService.find('AAA') + lookupGroup3.getApproversList().isEmpty() == true } } \ No newline at end of file From 5722aa8f7ec50299a70d0399f5b7934a572a35e4 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Fri, 30 Sep 2022 14:59:24 -0700 Subject: [PATCH 04/56] SHIBUI-2394 Incremental commit --- .../shibboleth/admin/ui/controller/ActivateController.java | 3 +-- .../ui/domain/frontend/EntityDescriptorRepresentation.java | 7 ++++++- .../admin/ui/service/JPAEntityDescriptorServiceImpl.java | 3 +-- backend/src/main/resources/i18n/messages.properties | 5 +++-- backend/src/main/resources/metadata-sources-ui-schema.json | 5 +++++ 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java index 487bd56c2..e43b88e1e 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java @@ -39,8 +39,7 @@ public class ActivateController { @PatchMapping(path = "/entityDescriptor/{resourceId}/{mode}") @Transactional - public ResponseEntity enableEntityDescriptor(@PathVariable String resourceId, @PathVariable String mode) throws - PersistentEntityNotFound, ForbiddenException { + public ResponseEntity enableEntityDescriptor(@PathVariable String resourceId, @PathVariable String mode) throws PersistentEntityNotFound, ForbiddenException { boolean status = "enable".equalsIgnoreCase(mode); EntityDescriptorRepresentation edr = entityDescriptorService.updateEntityDescriptorEnabledStatus(resourceId, status); return ResponseEntity.ok(edr); 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 61d24652e..46bf7a59d 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 @@ -18,6 +18,11 @@ public class EntityDescriptorRepresentation implements Serializable { private static final long serialVersionUID = 7753435553892353966L; + + @Setter + @Getter + private boolean approved; + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS"); private List assertionConsumerServices; @@ -247,4 +252,4 @@ public void setServiceProviderSsoDescriptor(ServiceProviderSsoDescriptorRepresen public void setVersion(int version) { this.version = version; } -} +} \ No newline at end of file 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 291f659f8..2fc27dfaa 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 @@ -465,8 +465,7 @@ public void updateDescriptorFromRepresentation(org.opensaml.saml.saml2.metadata. } @Override - public EntityDescriptorRepresentation updateEntityDescriptorEnabledStatus(String resourceId, boolean status) throws - PersistentEntityNotFound, ForbiddenException { + public EntityDescriptorRepresentation updateEntityDescriptorEnabledStatus(String resourceId, boolean status) throws PersistentEntityNotFound, ForbiddenException { EntityDescriptor ed = entityDescriptorRepository.findByResourceId(resourceId); if (ed == null) { throw new PersistentEntityNotFound("Entity with resourceid[" + resourceId + "] was not found for update"); diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index dcd97aee4..a76ccd5b1 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -189,6 +189,7 @@ label.new-group=New Group label.new-attribute=New Custom Entity Attribute label.edit-group=Edit Group +label.approved=Approved label.metadata-source=Metadata Source label.metadata-sources=Metadata Sources label.metadata-provider=Metadata Provider @@ -629,6 +630,7 @@ message.session-timeout-heading=Session timed out message.session-timeout-body=Your session has timed out. Please login again. message.session-timeout=An error has occurred while saving. Your session may have timed out. +tooltip.approved=Metadata Source is approved and can be enabled by authorized enabler tooltip.entity-id=An entityID is the SAML identifier that uniquely names a service provider. tooltip.service-provider-name=Service Provider Name (Dashboard Display Only) tooltip.force-authn=Disallows use (or reuse) of authentication results and login flows that don\u0027t provide a real-time proof of user presence in the login process @@ -792,5 +794,4 @@ value.algorithm-cbc-192=CBC (192) - http://www.w3.org/2001/04/xmlenc#aes192-cbc value.algorithm-cbc-128=CBC (128) - http://www.w3.org/2001/04/xmlenc#aes128-cbc value.algorithm-cbc-tripledes=CBC (TRIPLEDES) - http://www.w3.org/2001/04/xmlenc#tripledes-cbc -message.algorithms-unique=Each algorithm may only be used once. - +message.algorithms-unique=Each algorithm may only be used once. \ No newline at end of file diff --git a/backend/src/main/resources/metadata-sources-ui-schema.json b/backend/src/main/resources/metadata-sources-ui-schema.json index 93ad3ec81..b96ee7eec 100644 --- a/backend/src/main/resources/metadata-sources-ui-schema.json +++ b/backend/src/main/resources/metadata-sources-ui-schema.json @@ -5,6 +5,11 @@ "entityId" ], "properties": { + "approved": { + "title": "label.approved", + "description": "tooltip.approved", + "type": "boolean" + }, "serviceProviderName": { "title": "label.service-provider-name", "description": "tooltip.service-provider-name", From 1af89c5a26d36237c2b3548c36d05415ac7a8ae4 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 4 Oct 2022 09:07:06 -0700 Subject: [PATCH 05/56] SHIBUI-2394 Incremental commit - non working tests --- .../ui/controller/ActivateController.java | 6 +- .../ui/controller/ApprovalController.java | 31 ++++ ...> ApproveAndActivateExceptionHandler.java} | 4 +- .../EntityDescriptorController.java | 3 +- .../admin/ui/domain/EntityDescriptor.java | 20 ++- .../ui/security/service/UserService.java | 8 + .../ui/service/EntityDescriptorService.java | 2 + .../JPAEntityDescriptorServiceImpl.java | 44 ++++- .../src/main/resources/application.properties | 1 + .../controller/ActivateControllerTests.groovy | 157 ++++++++++++++++++ .../controller/ApproveControllerTests.groovy | 144 ++++++++++++++++ 11 files changed, 403 insertions(+), 17 deletions(-) create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApprovalController.java rename backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/{ActivateExceptionHandler.java => ApproveAndActivateExceptionHandler.java} (93%) create mode 100644 backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateControllerTests.groovy create mode 100644 backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java index e43b88e1e..14e5894f5 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java @@ -47,8 +47,7 @@ public ResponseEntity enableEntityDescriptor(@PathVariable String resourceId, @PatchMapping(path = "/MetadataResolvers/{metadataResolverId}/Filter/{resourceId}/{mode}") @Transactional - public ResponseEntity enableFilter(@PathVariable String metadataResolverId, @PathVariable String resourceId, @PathVariable String mode) throws - PersistentEntityNotFound, ForbiddenException, ScriptException { + public ResponseEntity enableFilter(@PathVariable String metadataResolverId, @PathVariable String resourceId, @PathVariable String mode) throws PersistentEntityNotFound, ForbiddenException, ScriptException { boolean status = "enable".equalsIgnoreCase(mode); MetadataFilter persistedFilter = filterService.updateFilterEnabledStatus(metadataResolverId, resourceId, status); return ResponseEntity.ok(persistedFilter); @@ -56,8 +55,7 @@ public ResponseEntity enableFilter(@PathVariable String metadataResolverId, @ @PatchMapping("/MetadataResolvers/{resourceId}/{mode}") @Transactional - public ResponseEntity enableProvider(@PathVariable String resourceId, @PathVariable String mode) throws - PersistentEntityNotFound, ForbiddenException, MetadataFileNotFoundException, InitializationException { + public ResponseEntity enableProvider(@PathVariable String resourceId, @PathVariable String mode) throws PersistentEntityNotFound, ForbiddenException, MetadataFileNotFoundException, InitializationException { boolean status = "enable".equalsIgnoreCase(mode); MetadataResolver existingResolver = metadataResolverService.findByResourceId(resourceId); existingResolver.setEnabled(status); diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApprovalController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApprovalController.java new file mode 100644 index 000000000..f3945ef47 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApprovalController.java @@ -0,0 +1,31 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller; + +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation; +import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; +import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound; +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.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/approve") +@Tags(value = {@Tag(name = "approve")}) +public class ApprovalController { + @Autowired + private EntityDescriptorService entityDescriptorService; + + @PatchMapping(path = "/entityDescriptor/{resourceId}/{mode}") + @Transactional + public ResponseEntity approveEntityDescriptor(@PathVariable String resourceId, @PathVariable String mode) throws PersistentEntityNotFound, ForbiddenException { +// boolean status = "approve".equalsIgnoreCase(mode); // can we un-approve? + EntityDescriptorRepresentation edr = entityDescriptorService.approveEntityDescriptor(resourceId); + return ResponseEntity.ok(edr); + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateExceptionHandler.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveAndActivateExceptionHandler.java similarity index 93% rename from backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateExceptionHandler.java rename to backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveAndActivateExceptionHandler.java index fe6f7c0f2..9f58ce8e1 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateExceptionHandler.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveAndActivateExceptionHandler.java @@ -16,8 +16,8 @@ import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; import edu.internet2.tier.shibboleth.admin.ui.exception.InitializationException; -@ControllerAdvice(assignableTypes = {ActivateController.class}) -public class ActivateExceptionHandler extends ResponseEntityExceptionHandler { +@ControllerAdvice(assignableTypes = {ActivateController.class, ApprovalController.class}) +public class ApproveAndActivateExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler({ PersistentEntityNotFound.class }) public ResponseEntity handleEntityNotFoundException(PersistentEntityNotFound e, WebRequest request) { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java index e57870cb9..0a6cda0b3 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java @@ -66,8 +66,7 @@ public EntityDescriptorController(EntityDescriptorVersionService versionService) @PostMapping("/EntityDescriptor") @Transactional - public ResponseEntity create(@RequestBody EntityDescriptorRepresentation edRepresentation) - throws ForbiddenException, ObjectIdExistsException, InvalidPatternMatchException { + public ResponseEntity create(@RequestBody EntityDescriptorRepresentation edRepresentation) throws ForbiddenException, ObjectIdExistsException, InvalidPatternMatchException { EntityDescriptorRepresentation persistedEd = entityDescriptorService.createNew(edRepresentation); return ResponseEntity.created(getResourceUriFor(persistedEd.getId())).body(persistedEd); } 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 185b43918..d1885ae14 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 @@ -1,26 +1,24 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.common.base.MoreObjects; import com.google.common.collect.Lists; - +import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownable; import edu.internet2.tier.shibboleth.admin.ui.security.model.OwnableType; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; - import org.hibernate.envers.Audited; import org.hibernate.envers.NotAudited; -import org.hibernate.envers.RelationTargetAuditMode; import org.opensaml.core.xml.XMLObject; import org.springframework.util.StringUtils; import javax.annotation.Nullable; import javax.persistence.CascadeType; +import javax.persistence.ElementCollection; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.OneToOne; import javax.persistence.OrderColumn; @@ -53,6 +51,10 @@ public class EntityDescriptor extends AbstractDescriptor implements org.opensaml @NotAudited private AttributeAuthorityDescriptor attributeAuthorityDescriptor; + @ElementCollection (fetch = FetchType.EAGER) + @EqualsAndHashCode.Exclude + private List approved = new ArrayList<>(); + @OneToOne(cascade = CascadeType.ALL) @NotAudited private AuthnAuthorityDescriptor authnAuthorityDescriptor; @@ -313,4 +315,12 @@ public OwnableType getOwnableType() { @Override public ActivatableType getActivatableType() { return ENTITY_DESCRIPTOR; } + + public void addApproval(Group group) { + approved.add(group.getName()); + } + + public int approvedCount() { + return approved.size(); + } } \ No newline at end of file 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 dfe21708a..25f152a90 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 @@ -53,6 +53,14 @@ public UserService(IGroupService groupService, OwnershipRepository ownershipRepo this.userRepository = userRepository; } + public boolean currentUserCanApprove(List approverGroups) { + if (currentUserIsAdmin()) { + return true; + } + Group currentUserGroup = getCurrentUserGroup(); + return approverGroups.contains(currentUserGroup); + } + public boolean currentUserCanEnable(IActivatable activatableObject) { if (currentUserIsAdmin()) { return true; } switch (activatableObject.getActivatableType()) { 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 fa8fc62f8..e5931f93a 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 @@ -122,4 +122,6 @@ EntityDescriptorRepresentation updateEntityDescriptorEnabledStatus(String resour boolean entityExists(String entityID); EntityDescriptorRepresentation updateGroupForEntityDescriptor(String resourceId, String groupId); + + EntityDescriptorRepresentation approveEntityDescriptor(String resourceId) throws PersistentEntityNotFound, ForbiddenException; } \ No newline at end of file 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 2fc27dfaa..21607029b 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 @@ -23,6 +23,7 @@ import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects; import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorProjection; import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Approvers; import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; import edu.internet2.tier.shibboleth.admin.ui.security.model.Owner; import edu.internet2.tier.shibboleth.admin.ui.security.model.OwnerType; @@ -74,6 +75,25 @@ public class JPAEntityDescriptorServiceImpl implements EntityDescriptorService { @Autowired private UserService userService; + @Override + public EntityDescriptorRepresentation approveEntityDescriptor(String resourceId) throws PersistentEntityNotFound, ForbiddenException { + EntityDescriptor ed = entityDescriptorRepository.findByResourceId(resourceId); + if (ed == null) { + throw new PersistentEntityNotFound("Entity with resourceid[" + resourceId + "] was not found for approval"); + } + int approvedCount = ed.approvedCount(); + List approversList = groupService.find(ed.getIdOfOwner()).getApproversList(); + if (!approversList.isEmpty() && approversList.size() > approvedCount) { + Approvers approvers = approversList.get(approvedCount); // yea for index zero - use the count to get the next approvers + if (!userService.currentUserCanApprove(approvers.getApproverGroups())) { + throw new ForbiddenException("You do not have the permissions necessary to approve this entity descriptor."); + } + ed.addApproval(userService.getCurrentUserGroup()); + ed = entityDescriptorRepository.save(ed); + } + return createRepresentationFromDescriptor(ed); + } + private EntityDescriptor buildDescriptorFromRepresentation(final EntityDescriptor ed, final EntityDescriptorRepresentation representation) { ed.setEntityID(representation.getEntityId()); ed.setIdOfOwner(representation.getIdOfOwner()); @@ -128,8 +148,7 @@ public EntityDescriptorRepresentation updateGroupForEntityDescriptor(String reso } @Override - public EntityDescriptorRepresentation createNew(EntityDescriptorRepresentation edRep) - throws ForbiddenException, ObjectIdExistsException, InvalidPatternMatchException { + public EntityDescriptorRepresentation createNew(EntityDescriptorRepresentation edRep) throws ForbiddenException, ObjectIdExistsException, InvalidPatternMatchException { if (edRep.isServiceEnabled() && !userService.currentUserIsAdmin()) { throw new ForbiddenException("You do not have the permissions necessary to enable this service."); } @@ -167,6 +186,7 @@ public EntityDescriptorRepresentation createRepresentationFromDescriptor(org.ope representation.setCreatedBy(ed.getCreatedBy()); representation.setCurrent(ed.isCurrent()); representation.setIdOfOwner(ed.getIdOfOwner()); + representation.setApproved(isEntityDescriptorApproved(ed)); if (ed.getSPSSODescriptor("") != null && ed.getSPSSODescriptor("").getSupportedProtocols().size() > 0) { ServiceProviderSsoDescriptorRepresentation serviceProviderSsoDescriptorRepresentation = representation.getServiceProviderSsoDescriptor(true); @@ -362,6 +382,17 @@ public EntityDescriptorRepresentation createRepresentationFromDescriptor(org.ope return representation; } + private boolean isEntityDescriptorApproved(EntityDescriptor ed) { + if (ed.isServiceEnabled()) { + return true; + } + Group ownerGroup = groupService.find(ed.getIdOfOwner()); + if (ownerGroup == null) { + ownerGroup = Group.ADMIN_GROUP; // This should only happen in the large number of tests that were written prior to group implementation + } + return ed.approvedCount() >= ownerGroup.getApproversList().size(); + } + @Override public void delete(String resourceId) throws ForbiddenException, PersistentEntityNotFound { EntityDescriptor ed = getEntityDescriptorByResourceId(resourceId); @@ -424,8 +455,7 @@ public Map getRelyingPartyOverridesRepresentationFromAttributeLi } @Override - public EntityDescriptorRepresentation update(EntityDescriptorRepresentation edRep) - throws ForbiddenException, PersistentEntityNotFound, InvalidPatternMatchException { + public EntityDescriptorRepresentation update(EntityDescriptorRepresentation edRep) throws ForbiddenException, PersistentEntityNotFound, InvalidPatternMatchException { EntityDescriptor existingEd = entityDescriptorRepository.findByResourceId(edRep.getId()); if (existingEd == null) { throw new PersistentEntityNotFound(String.format("The entity descriptor with entity id [%s] was not found for update.", edRep.getId())); @@ -473,6 +503,12 @@ public EntityDescriptorRepresentation updateEntityDescriptorEnabledStatus(String if (!userService.currentUserCanEnable(ed)) { throw new ForbiddenException("You do not have the permissions necessary to change the enable status of this entity descriptor."); } + // check to see if approvals have been completed + int approvedCount = ed.approvedCount(); + List approversList = groupService.find(ed.getIdOfOwner()).getApproversList(); + if (!ed.isServiceEnabled() && !userService.currentUserIsAdmin() && approversList.size() > approvedCount) { + throw new ForbiddenException("Approval must be completed before you can change the enable status of this entity descriptor."); + } ed.setServiceEnabled(status); ed = entityDescriptorRepository.save(ed); return createRepresentationFromDescriptor(ed); diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 109e7c30f..6593646f9 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -48,6 +48,7 @@ spring.liquibase.change-log=db/changelog/changelog.sql spring.jpa.hibernate.ddl-auto=update spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.show-sql=false +spring.jpa.properties.hibernate.show_sql=false spring.jpa.properties.hibernate.format_sql=true spring.jpa.properties.hibernate.check_nullability=true spring.jpa.hibernate.use-new-id-generator-mappings=true diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateControllerTests.groovy new file mode 100644 index 000000000..8dfd0cf29 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateControllerTests.groovy @@ -0,0 +1,157 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import com.fasterxml.jackson.databind.ObjectMapper +import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest +import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor +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.Approvers +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.service.EntityDescriptorService +import edu.internet2.tier.shibboleth.admin.ui.service.EntityService +import edu.internet2.tier.shibboleth.admin.ui.util.WithMockAdmin +import edu.internet2.tier.shibboleth.admin.util.EntityDescriptorConversionUtils +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.security.test.context.support.WithMockUser +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import spock.lang.Subject + +import javax.transaction.Transactional + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +// TODO: This is only checking activation for EntityDescriptors. Expanding for resolvers not included +class ActivateControllerTests extends AbstractBaseDataJpaTest { + @Subject + def controller + + @Autowired + ObjectMapper mapper + + @Autowired + EntityService entityService + + @Autowired + EntityDescriptorRepository entityDescriptorRepository + + @Autowired + private EntityDescriptorService entDescriptorService; + + @Autowired + OpenSamlObjects openSamlObjects + + def defaultEntityDescriptorResourceId + def mockMvc + + @Transactional + def setup() { + controller = new ActivateController() + controller.entityDescriptorService = entDescriptorService + mockMvc = MockMvcBuilders.standaloneSetup(controller).build() + + EntityDescriptorConversionUtils.setOpenSamlObjects(openSamlObjects) + EntityDescriptorConversionUtils.setEntityService(entityService) + + groupService.clearAllForTesting() + List apprGroups = new ArrayList<>() + String[] groupNames = ['BBB', 'CCC', 'AAA'] + groupNames.each {name -> { + Group group = new Group().with({ + it.name = name + it.description = name + it.resourceId = name + it + }) + if (name != "AAA") { + apprGroups.add(groupRepository.save(group)) + } else { + Approvers approvers = new Approvers() + approvers.setApproverGroups(apprGroups) + List apprList = new ArrayList<>() + apprList.add(approversRepository.save(approvers)) + group.setApproversList(apprList) + groupRepository.save(group) + } + }} + Group group = new Group().with({ + it.name = 'DDD' + it.description = 'DDD' + it.resourceId = 'DDD' + it + }) + groupRepository.save(group) + entityManager.flush() + entityManager.clear() + + Optional userRole = roleRepository.findByName("ROLE_ENABLE") + User user = new User(username: "AUser", roles:[userRole.get()], password: "foo") + user.setGroup(groupRepository.findByResourceId("AAA")) + userService.save(user) + user = new User(username: "BUser", roles:[userRole.get()], password: "foo") + user.setGroup(groupRepository.findByResourceId("BBB")) + userService.save(user) + user = new User(username: "DUser", roles:[userRole.get()], password: "foo") + user.setGroup(groupRepository.findByResourceId("DDD")) + userService.save(user) + + entityManager.flush() + entityManager.clear() + + EntityDescriptor entityDescriptor = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: false, idOfOwner: 'AAA') + entityDescriptor = entityDescriptorRepository.save(entityDescriptor) + + defaultEntityDescriptorResourceId = entityDescriptor.getResourceId() + } + + @WithMockUser(value = "AUser", roles = ["USER"]) + def 'Owner group cannot activate their own entity descriptor without approvals'() { + expect: + try { + mockMvc.perform(patch("/api/activate/entityDescriptor/" + defaultEntityDescriptorResourceId + "/enable")) + } + catch (Exception e) { + e instanceof ForbiddenException + } + } + + @WithMockUser(value = "DUser", roles = ["USER"]) + def 'non-owner group cannot activate entity descriptor'() { + expect: + try { + mockMvc.perform(patch("/api/activate/entityDescriptor/" + defaultEntityDescriptorResourceId + "/enable")) + } + catch (Exception e) { + e instanceof ForbiddenException + } + } + + @WithMockAdmin + def 'Admin can activate an entity descriptor without approval'() { + when: + def result = mockMvc.perform(patch("/api/activate/entityDescriptor/" + defaultEntityDescriptorResourceId + "/enable")) + + then: + result.andExpect(status().isOk()) + .andExpect(jsonPath("\$.id").value(defaultEntityDescriptorResourceId)) + .andExpect(jsonPath("\$.serviceEnabled").value(true)) + } + + @WithMockUser(value = "AUser", roles = ["USER"]) + def 'Owner group can enable their own entity descriptor with approvals'() { + when: + EntityDescriptor entityDescriptor = new EntityDescriptor(resourceId: 'uuid-2', entityID: 'eid2', serviceProviderName: 'sp1', serviceEnabled: false, idOfOwner: 'AAA') + entityDescriptor.addApproval(groupService.find("CCC")) + entityDescriptorRepository.save(entityDescriptor) + + def result = mockMvc.perform(patch("/api/activate/entityDescriptor/uuid-2/enable")) + + then: + result.andExpect(status().isOk()) + .andExpect(jsonPath("\$.id").value('uuid-2')) + .andExpect(jsonPath("\$.serviceEnabled").value(true)) + } +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy new file mode 100644 index 000000000..04ff8ceda --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy @@ -0,0 +1,144 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import com.fasterxml.jackson.databind.ObjectMapper +import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest +import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor +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.Approvers +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.service.EntityDescriptorService +import edu.internet2.tier.shibboleth.admin.ui.service.EntityService +import edu.internet2.tier.shibboleth.admin.util.EntityDescriptorConversionUtils +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.security.test.context.support.WithMockUser +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.web.client.RestTemplate +import spock.lang.Subject + +import javax.transaction.Transactional + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +class ApproveControllerTests extends AbstractBaseDataJpaTest { + @Subject + def controller + + @Autowired + ObjectMapper mapper + + @Autowired + EntityService entityService + + @Autowired + EntityDescriptorRepository entityDescriptorRepository + + @Autowired + private EntityDescriptorService entDescriptorService; + + @Autowired + OpenSamlObjects openSamlObjects + + def defaultEntityDescriptorResourceId + def mockMvc + + @Transactional + def setup() { + controller = new ApprovalController() + controller.entityDescriptorService = entDescriptorService + mockMvc = MockMvcBuilders.standaloneSetup(controller).build() + + EntityDescriptorConversionUtils.setOpenSamlObjects(openSamlObjects) + EntityDescriptorConversionUtils.setEntityService(entityService) + + groupService.clearAllForTesting() + List apprGroups = new ArrayList<>() + String[] groupNames = ['BBB', 'CCC', 'AAA'] + groupNames.each {name -> { + Group group = new Group().with({ + it.name = name + it.description = name + it.resourceId = name + it + }) + if (name != "AAA") { + apprGroups.add(groupRepository.save(group)) + } else { + Approvers approvers = new Approvers() + approvers.setApproverGroups(apprGroups) + List apprList = new ArrayList<>() + apprList.add(approversRepository.save(approvers)) + group.setApproversList(apprList) + groupRepository.save(group) + } + }} + Group group = new Group().with({ + it.name = 'DDD' + it.description = 'DDD' + it.resourceId = 'DDD' + it + }) + groupRepository.save(group) + entityManager.flush() + entityManager.clear() + + Optional userRole = roleRepository.findByName("ROLE_USER") + User user = new User(username: "AUser", roles:[userRole.get()], password: "foo") + user.setGroup(groupRepository.findByResourceId("AAA")) + userService.save(user) + user = new User(username: "BUser", roles:[userRole.get()], password: "foo") + user.setGroup(groupRepository.findByResourceId("BBB")) + userService.save(user) + user = new User(username: "DUser", roles:[userRole.get()], password: "foo") + user.setGroup(groupRepository.findByResourceId("DDD")) + userService.save(user) + + entityManager.flush() + entityManager.clear() + + EntityDescriptor entityDescriptor = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: false, idOfOwner: 'AAA') + entityDescriptor = entityDescriptorRepository.save(entityDescriptor) + + defaultEntityDescriptorResourceId = entityDescriptor.getResourceId() + } + + @WithMockUser(value = "AUser", roles = ["USER"]) + def 'Owner group cannot approve their own entity descriptor'() { + expect: + try { + mockMvc.perform(patch("/api/approve/entityDescriptor/" + defaultEntityDescriptorResourceId + "/approve")) + } + catch (Exception e) { + e instanceof ForbiddenException + } + } + + @WithMockUser(value = "DUser", roles = ["USER"]) + def 'non-approver group cannot approve entity descriptor'() { + expect: + try { + mockMvc.perform(patch("/api/approve/entityDescriptor/" + defaultEntityDescriptorResourceId + "/approve")) + } + catch (Exception e) { + e instanceof ForbiddenException + } + } + + @WithMockUser(value = "BUser", roles = ["USER"]) + def 'Approver group can approve an entity descriptor'() { + when: + def result = mockMvc.perform(patch("/api/approve/entityDescriptor/" + defaultEntityDescriptorResourceId + "/approve")) + + then: + result.andExpect(status().isOk()) + .andExpect(jsonPath("\$.id").value(defaultEntityDescriptorResourceId)) + .andExpect(jsonPath("\$.serviceEnabled").value(false)) + .andExpect(jsonPath("\$.approved").value(true)) + } + +} \ No newline at end of file From b9cfc4b1ca4b31240b760384eafe4fad46055457 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 4 Oct 2022 15:26:44 -0700 Subject: [PATCH 06/56] SHIBUI-2394 Fixed testing --- .../EntitiesControllerIntegrationTests.groovy | 340 ++++++++++-------- .../service/AuxiliaryIntegrationTests.groovy | 59 --- ...JPAEntityDescriptorServiceImplTests.groovy | 32 ++ 3 files changed, 229 insertions(+), 202 deletions(-) delete mode 100644 backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/AuxiliaryIntegrationTests.groovy diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy index d64fa8861..2843711c0 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy @@ -1,183 +1,237 @@ package edu.internet2.tier.shibboleth.admin.ui.controller +import com.fasterxml.jackson.databind.ObjectMapper +import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest +import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor +import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException +import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository -import net.shibboleth.ext.spring.resource.ResourceHelper -import net.shibboleth.utilities.java.support.resolver.CriteriaSet -import org.opensaml.core.criterion.EntityIdCriterion -import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver -import org.opensaml.saml.metadata.resolver.MetadataResolver -import org.opensaml.saml.metadata.resolver.filter.MetadataFilterChain -import org.opensaml.saml.metadata.resolver.impl.ResourceBackedMetadataResolver -import org.spockframework.spring.SpringBean +import edu.internet2.tier.shibboleth.admin.ui.security.model.Group +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership +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.service.EntityDescriptorVersionService +import edu.internet2.tier.shibboleth.admin.ui.service.EntityService +import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityDescriptorServiceImpl +import edu.internet2.tier.shibboleth.admin.ui.util.RandomGenerator +import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator +import edu.internet2.tier.shibboleth.admin.ui.util.WithMockAdmin +import edu.internet2.tier.shibboleth.admin.util.EntityDescriptorConversionUtils import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.context.TestConfiguration -import org.springframework.context.annotation.Bean -import org.springframework.core.io.ClassPathResource -import org.springframework.test.context.ActiveProfiles -import org.springframework.test.web.reactive.server.WebTestClient -import org.xmlunit.builder.DiffBuilder -import org.xmlunit.builder.Input -import org.xmlunit.diff.DefaultNodeMatcher -import org.xmlunit.diff.ElementSelectors -import spock.lang.Specification - -import java.time.Instant - -/** - * @author Bill Smith (wsmith@unicon.net) - */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@ActiveProfiles("no-auth") -class EntitiesControllerIntegrationTests extends Specification { +import org.springframework.security.test.context.support.WithMockUser +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.client.RestTemplate +import spock.lang.Subject +import javax.persistence.EntityManager + +import static org.springframework.http.MediaType.APPLICATION_XML +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +class EntitiesControllerIntegrationTests extends AbstractBaseDataJpaTest { @Autowired - private WebTestClient webClient + EntityDescriptorRepository entityDescriptorRepository - def openSamlObjects = new OpenSamlObjects().with { - init() - it - } + @Autowired + EntityManager entityManager - def resource = ResourceHelper.of(new ClassPathResource("/metadata/aggregate.xml")) + @Autowired + EntityService entityService + + @Autowired + TestObjectGenerator generator + + @Autowired + ObjectMapper mapper + + @Autowired + OpenSamlObjects openSamlObjects + + @Autowired + JPAEntityDescriptorServiceImpl jpaEntityDescriptorService - def metadataResolver = new ResourceBackedMetadataResolver(resource).with { - it.id = 'test' - it.parserPool = openSamlObjects.parserPool - initialize() - it + RandomGenerator randomGenerator + def mockRestTemplate = Mock(RestTemplate) + def mockMvc + + @Subject + def controller + + EntityDescriptorVersionService versionService = Mock() + + @Transactional + def setup() { + openSamlObjects.init() + + Group gb = new Group() + gb.setResourceId("testingGroupBBB") + gb.setName("Group BBB") + gb.setValidationRegex("^(?:https?:\\/\\/)?(?:[^.]+\\.)?shib\\.org(\\/.*)?\$") + gb = groupService.createGroup(gb) + + randomGenerator = new RandomGenerator() + + controller = new EntitiesController() + controller.openSamlObjects = openSamlObjects + controller.entityDescriptorService = jpaEntityDescriptorService + controller.entityDescriptorRepository = entityDescriptorRepository + + mockMvc = MockMvcBuilders.standaloneSetup(controller).build() + + Optional userRole = roleRepository.findByName("ROLE_USER") + User user = new User(username: "someUser", roles:[userRole.get()], password: "foo") + user.setGroup(gb) + userService.save(user) + + EntityDescriptorConversionUtils.setOpenSamlObjects(openSamlObjects) + EntityDescriptorConversionUtils.setEntityService(entityService) + } + + @WithMockAdmin + def 'GET /entities/{resourceId} non-existent'() { + expect: + try { + mockMvc.perform(get("/api/entities/uuid-1")) + } + catch (Exception e) { + e instanceof PersistentEntityNotFound + } } - - // This stub will spit out the results from the resolver instead of actually finding them in the DB - @SpringBean - EntityDescriptorRepository edr = Stub(EntityDescriptorRepository) { - findByEntityID("http://test.scaldingspoon.org/test1") >> metadataResolver.resolveSingle(new CriteriaSet(new EntityIdCriterion("http://test.scaldingspoon.org/test1"))) - findByEntityID("test") >> metadataResolver.resolveSingle(new CriteriaSet(new EntityIdCriterion("test"))) + + @WithMockAdmin + def 'GET /entities/{resourceId} existing'() { + given: + def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: "admingroup") + entityDescriptorRepository.save(entityDescriptorOne) + entityManager.flush() + entityManager.clear() + + when: + def result = mockMvc.perform(get("/api/entities/eid1")) + + then: + result.andExpect(status().isOk()) + .andExpect(jsonPath("\$.entityId").value("eid1")) + .andExpect(jsonPath("\$.serviceProviderName").value("sp1")) + .andExpect(jsonPath("\$.serviceEnabled").value(true)) + .andExpect(jsonPath("\$.idOfOwner").value("admingroup")) } - //todo review - def "GET /api/entities returns the proper json"() { + @WithMockUser(value = "someUser", roles = ["USER"]) + def 'GET /entities/{resourceId} existing, validate group access'() { given: - def expectedBody = ''' - { - "entityId":"http://test.scaldingspoon.org/test1", - "serviceProviderSsoDescriptor": { - "protocolSupportEnum":"SAML 2", - "nameIdFormats":["urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"] - }, - "assertionConsumerServices":[ - {"locationUrl":"https://test.scaldingspoon.org/test1/acs","binding":"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST","makeDefault":false} - ], - "serviceEnabled":false, - "attributeRelease":["givenName","employeeNumber"] - } - ''' + Group g = userService.getCurrentUserGroup() + + def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: "someUser") + def entityDescriptorTwo = new EntityDescriptor(resourceId: 'uuid-2', entityID: 'eid2', serviceProviderName: 'sp2', serviceEnabled: false, idOfOwner: Group.ADMIN_GROUP.getOwnerId()) + + entityDescriptorRepository.saveAndFlush(entityDescriptorOne) + entityDescriptorRepository.saveAndFlush(entityDescriptorTwo) + + ownershipRepository.saveAndFlush(new Ownership(g, entityDescriptorOne)) + ownershipRepository.saveAndFlush(new Ownership(Group.ADMIN_GROUP, entityDescriptorTwo)) when: - def result = this.webClient - .get() - .uri("/api/entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1") - .exchange() // someday, I'd like to know why IntelliJ "cannot resolve symbol 'exchange'" + def result = mockMvc.perform(get("/api/entities/eid1")) then: - result.expectStatus().isOk() - .expectBody() - .json(expectedBody) + result.andExpect(status().isOk()) + .andExpect(jsonPath("\$.entityId").value("eid1")) + .andExpect(jsonPath("\$.serviceProviderName").value("sp1")) + .andExpect(jsonPath("\$.serviceEnabled").value(true)) + .andExpect(jsonPath("\$.idOfOwner").value("someUser")) } - def "GET /api/entities/test is not found"() { + @WithMockUser(value = "someUser", roles = ["USER"]) + def 'GET /entities/{resourceId} existing, owned by some other user'() { when: - def result = this.webClient - .get() - .uri("/api/entities/test") - .exchange() + Group g = userService.getCurrentUserGroup() + + def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: g.getOwnerId()) + def entityDescriptorTwo = new EntityDescriptor(resourceId: 'uuid-2', entityID: 'eid2', serviceProviderName: 'sp2', serviceEnabled: false, idOfOwner: Group.ADMIN_GROUP.getOwnerId()) + + entityDescriptorRepository.saveAndFlush(entityDescriptorOne) + entityDescriptorRepository.saveAndFlush(entityDescriptorTwo) + + ownershipRepository.saveAndFlush(new Ownership(g, entityDescriptorOne)) + ownershipRepository.saveAndFlush(new Ownership(Group.ADMIN_GROUP, entityDescriptorTwo)) then: - result.expectStatus().isNotFound() + try { + mockMvc.perform(get("/api/entities/eid2")) + } + catch (Exception e) { + e instanceof ForbiddenException + } } - def "GET /api/entities/test XML is not found"() { + @WithMockAdmin + def 'GET /entities/{resourceId} existing (xml)'() { + given: + def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true) + entityDescriptorOne.setElementLocalName("EntityDescriptor") + entityDescriptorOne.setNamespacePrefix("md") + entityDescriptorOne.setNamespaceURI("urn:oasis:names:tc:SAML:2.0:metadata") + entityDescriptorRepository.save(entityDescriptorOne) + entityManager.flush() + + def expectedXML = """ +""" + when: - def result = this.webClient - .get() - .uri("/api/entities/test") - .header('Accept', 'application/xml') - .exchange() + def result = mockMvc.perform(get("/api/entities/eid1").accept(APPLICATION_XML)) then: - result.expectStatus().isNotFound() + result.andExpect(status().isOk()).andExpect(content().xml(expectedXML)) } - def "GET /api/entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1 XML returns proper XML"() { + @WithMockUser(value = "someUser", roles = ["USER"]) + def 'GET /entities/{resourceId} existing (xml), user-owned'() { given: - def expectedBody = ''' - - - - - internal - - - givenName - employeeNumber - - - - - urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified - - - -''' + Group g = userService.getCurrentUserGroup() + + def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: g.getOwnerId()) + entityDescriptorOne.setElementLocalName("EntityDescriptor") + entityDescriptorOne.setNamespacePrefix("md") + entityDescriptorOne.setNamespaceURI("urn:oasis:names:tc:SAML:2.0:metadata") + entityDescriptorOne = entityDescriptorRepository.saveAndFlush(entityDescriptorOne) + ownershipRepository.saveAndFlush(new Ownership(g,entityDescriptorOne)) + + def expectedXML = """ +""" + when: - def result = this.webClient - .get() - .uri("/api/entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1") - .header('Accept', 'application/xml') - .exchange() + def result = mockMvc.perform(get("/api/entities/eid1").accept(APPLICATION_XML)) then: - def resultBody = result.expectStatus().isOk() - //.expectHeader().contentType("application/xml;charset=ISO-8859-1") // should this really be ISO-8859-1? - // expectedBody encoding is UTF-8... - .expectHeader().contentType("application/xml;charset=UTF-8") - .expectBody(String.class) - .returnResult() - def diff = DiffBuilder.compare(Input.fromString(expectedBody)).withTest(Input.fromString(resultBody.getResponseBody())).ignoreComments().checkForSimilar().ignoreWhitespace().withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText)).build() - !diff.hasDifferences() + result.andExpect(status().isOk()).andExpect(content().xml(expectedXML)) } + @WithMockUser(value = "someUser", roles = ["USER"]) + def 'GET /entities/{resourceId} existing (xml), other user-owned'() { + when: + Group g = Group.ADMIN_GROUP + + def entityDescriptorOne = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: true, idOfOwner: g.getOwnerId()) + entityDescriptorOne.setElementLocalName("EntityDescriptor") + entityDescriptorOne.setNamespacePrefix("md") + entityDescriptorOne.setNamespaceURI("urn:oasis:names:tc:SAML:2.0:metadata") + entityDescriptorRepository.save(entityDescriptorOne) + entityManager.flush() - @TestConfiguration - static class Config { - @Autowired - OpenSamlObjects openSamlObjects - - @Bean - MetadataResolver metadataResolver() { - def resource = ResourceHelper.of(new ClassPathResource("/metadata/aggregate.xml")) - def aggregate = new ResourceBackedMetadataResolver(resource){ - @Override - Instant getLastRefresh() { - return null - } - } - - aggregate.with { - it.metadataFilter = new MetadataFilterChain() - it.id = 'testme' - it.parserPool = openSamlObjects.parserPool - it.initialize() - it - } - - return new ChainingMetadataResolver().with { - it.id = 'chain' - it.resolvers = [aggregate] - it.initialize() - it - } + then: + try { + mockMvc.perform(get("/api/entities/eid1").accept(APPLICATION_XML)) + } + catch (Exception e) { + e instanceof ForbiddenException } } } \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/AuxiliaryIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/AuxiliaryIntegrationTests.groovy deleted file mode 100644 index 4c572e2ad..000000000 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/AuxiliaryIntegrationTests.groovy +++ /dev/null @@ -1,59 +0,0 @@ -package edu.internet2.tier.shibboleth.admin.ui.service - -import com.fasterxml.jackson.databind.ObjectMapper -import edu.internet2.tier.shibboleth.admin.ui.configuration.JsonSchemaComponentsConfiguration -import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor -import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup -import edu.internet2.tier.shibboleth.admin.ui.jsonschema.LowLevelJsonSchemaValidator -import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects -import edu.internet2.tier.shibboleth.admin.ui.security.model.Group - -import org.springframework.core.io.DefaultResourceLoader -import org.springframework.core.io.ResourceLoader -import org.springframework.mock.http.MockHttpInputMessage -import spock.lang.Shared -import spock.lang.Specification - -import java.time.LocalDateTime - -class AuxiliaryIntegrationTests extends Specification { - OpenSamlObjects openSamlObjects = new OpenSamlObjects().with { - it.init() - it - } - - JPAEntityDescriptorServiceImpl entityDescriptorService - ObjectMapper objectMapper - ResourceLoader resourceLoader - - void setup() { - entityDescriptorService = new JPAEntityDescriptorServiceImpl() - entityDescriptorService.openSamlObjects = openSamlObjects - objectMapper = new ObjectMapper() - resourceLoader = new DefaultResourceLoader() - } - - def "SHIBUI-1723: after enabling saved entity descriptor, it should still have valid xml"() { - given: - def entityDescriptor = openSamlObjects.unmarshalFromXml(this.class.getResource('/metadata/SHIBUI-1723-1.xml').bytes) as EntityDescriptor - entityDescriptor.idOfOwner = "foo" - - def entityDescriptorRepresentation = entityDescriptorService.createRepresentationFromDescriptor(entityDescriptor).with { - it.serviceProviderName = 'testme' - it.contacts = [] - it.securityInfo.x509Certificates[0].name = 'testcert' - it.createdBy = 'root' - it.setCreatedDate(LocalDateTime.now()) - it.setModifiedDate(LocalDateTime.now()) - it - } - def json = objectMapper.writeValueAsString(entityDescriptorRepresentation) - def schemaUri = JsonSchemaLocationLookup.metadataSourcesSchema(new JsonSchemaComponentsConfiguration().jsonSchemaResourceLocationRegistry(this.resourceLoader, this.objectMapper)).uri - - when: - LowLevelJsonSchemaValidator.validatePayloadAgainstSchema(new MockHttpInputMessage(json.bytes), schemaUri) - - then: - noExceptionThrown() - } -} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy index e9a9aa217..9e65bf28a 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy @@ -2,6 +2,7 @@ package edu.internet2.tier.shibboleth.admin.ui.service import com.fasterxml.jackson.databind.ObjectMapper import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest +import edu.internet2.tier.shibboleth.admin.ui.configuration.JsonSchemaComponentsConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.AssertionConsumerServiceRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.ContactRepresentation @@ -11,6 +12,8 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.MduiRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.OrganizationRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.SecurityInfoRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.ServiceProviderSsoDescriptorRepresentation +import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup +import edu.internet2.tier.shibboleth.admin.ui.jsonschema.LowLevelJsonSchemaValidator import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.util.RandomGenerator import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator @@ -19,12 +22,16 @@ import org.skyscreamer.jsonassert.JSONAssert import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.json.JacksonTester import org.springframework.context.annotation.PropertySource +import org.springframework.core.io.DefaultResourceLoader +import org.springframework.mock.http.MockHttpInputMessage import org.xmlunit.builder.DiffBuilder import org.xmlunit.builder.Input import org.xmlunit.diff.DefaultNodeMatcher import org.xmlunit.diff.ElementSelectors import spock.lang.Ignore +import java.time.LocalDateTime + @PropertySource("classpath:application.yml") class JPAEntityDescriptorServiceImplTests extends AbstractBaseDataJpaTest { @Autowired @@ -763,4 +770,29 @@ class JPAEntityDescriptorServiceImplTests extends AbstractBaseDataJpaTest { return ed } + + def "SHIBUI-1723"() { + given: + def entityDescriptor = openSamlObjects.unmarshalFromXml(this.class.getResource('/metadata/SHIBUI-1723-1.xml').bytes) as EntityDescriptor + entityDescriptor.idOfOwner = "foo" + + def entityDescriptorRepresentation = service.createRepresentationFromDescriptor(entityDescriptor).with { + it.serviceProviderName = 'testme' + it.contacts = [] + it.securityInfo.x509Certificates[0].name = 'testcert' + it.createdBy = 'root' + it.setCreatedDate(LocalDateTime.now()) + it.setModifiedDate(LocalDateTime.now()) + it + } + def json = mapper.writeValueAsString(entityDescriptorRepresentation) + def resourceLoader = new DefaultResourceLoader() + def schemaUri = JsonSchemaLocationLookup.metadataSourcesSchema(new JsonSchemaComponentsConfiguration().jsonSchemaResourceLocationRegistry(resourceLoader, this.mapper)).uri + + when: + LowLevelJsonSchemaValidator.validatePayloadAgainstSchema(new MockHttpInputMessage(json.bytes), schemaUri) + + then: + noExceptionThrown() + } } \ No newline at end of file From 4672fc9b366c7b1607a1ceef18277e6fa726ea16 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 4 Oct 2022 16:27:00 -0700 Subject: [PATCH 07/56] SHIBUI-2394 Added ability to unapprove --- .../ui/controller/ApprovalController.java | 4 ++-- .../admin/ui/domain/EntityDescriptor.java | 6 +++++ .../ui/service/EntityDescriptorService.java | 2 +- .../JPAEntityDescriptorServiceImpl.java | 22 ++++++++++++------- .../controller/ApproveControllerTests.groovy | 21 ++++++++++++++++++ 5 files changed, 44 insertions(+), 11 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApprovalController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApprovalController.java index f3945ef47..32bf84afb 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApprovalController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApprovalController.java @@ -24,8 +24,8 @@ public class ApprovalController { @PatchMapping(path = "/entityDescriptor/{resourceId}/{mode}") @Transactional public ResponseEntity approveEntityDescriptor(@PathVariable String resourceId, @PathVariable String mode) throws PersistentEntityNotFound, ForbiddenException { -// boolean status = "approve".equalsIgnoreCase(mode); // can we un-approve? - EntityDescriptorRepresentation edr = entityDescriptorService.approveEntityDescriptor(resourceId); + boolean status = "approve".equalsIgnoreCase(mode); + EntityDescriptorRepresentation edr = entityDescriptorService.changeApproveStatusOfEntityDescriptor(resourceId, status); return ResponseEntity.ok(edr); } } \ No newline at end of file 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 d1885ae14..33e7ce6d1 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 @@ -323,4 +323,10 @@ public void addApproval(Group group) { public int approvedCount() { return approved.size(); } + + public void removeLastApproval() { + if (!approved.isEmpty()) { + approved.remove(approved.size() - 1); + } + } } \ No newline at end of file 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 e5931f93a..1f37b83bc 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 @@ -123,5 +123,5 @@ EntityDescriptorRepresentation updateEntityDescriptorEnabledStatus(String resour EntityDescriptorRepresentation updateGroupForEntityDescriptor(String resourceId, String groupId); - EntityDescriptorRepresentation approveEntityDescriptor(String resourceId) throws PersistentEntityNotFound, ForbiddenException; + EntityDescriptorRepresentation changeApproveStatusOfEntityDescriptor(String resourceId, boolean status) throws PersistentEntityNotFound, ForbiddenException; } \ No newline at end of file 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 21607029b..0b1f67932 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 @@ -76,19 +76,25 @@ public class JPAEntityDescriptorServiceImpl implements EntityDescriptorService { private UserService userService; @Override - public EntityDescriptorRepresentation approveEntityDescriptor(String resourceId) throws PersistentEntityNotFound, ForbiddenException { + public EntityDescriptorRepresentation changeApproveStatusOfEntityDescriptor(String resourceId, boolean status) throws PersistentEntityNotFound, ForbiddenException { EntityDescriptor ed = entityDescriptorRepository.findByResourceId(resourceId); if (ed == null) { throw new PersistentEntityNotFound("Entity with resourceid[" + resourceId + "] was not found for approval"); } - int approvedCount = ed.approvedCount(); - List approversList = groupService.find(ed.getIdOfOwner()).getApproversList(); - if (!approversList.isEmpty() && approversList.size() > approvedCount) { - Approvers approvers = approversList.get(approvedCount); // yea for index zero - use the count to get the next approvers - if (!userService.currentUserCanApprove(approvers.getApproverGroups())) { - throw new ForbiddenException("You do not have the permissions necessary to approve this entity descriptor."); + if (status) { // approve + int approvedCount = ed.approvedCount(); + List approversList = groupService.find(ed.getIdOfOwner()).getApproversList(); + if (!approversList.isEmpty() && approversList.size() > approvedCount) { + Approvers approvers = approversList.get( + approvedCount); // yea for index zero - use the count to get the next approvers + if (!userService.currentUserCanApprove(approvers.getApproverGroups())) { + throw new ForbiddenException("You do not have the permissions necessary to approve this entity descriptor."); + } + ed.addApproval(userService.getCurrentUserGroup()); + ed = entityDescriptorRepository.save(ed); } - ed.addApproval(userService.getCurrentUserGroup()); + } else { // un-approve + ed.removeLastApproval(); ed = entityDescriptorRepository.save(ed); } return createRepresentationFromDescriptor(ed); diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy index 04ff8ceda..b18c40aad 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy @@ -141,4 +141,25 @@ class ApproveControllerTests extends AbstractBaseDataJpaTest { .andExpect(jsonPath("\$.approved").value(true)) } + @WithMockUser(value = "BUser", roles = ["USER"]) + def 'Approver can approve and un-approve an entity descriptor'() { + when: + def result = mockMvc.perform(patch("/api/approve/entityDescriptor/" + defaultEntityDescriptorResourceId + "/approve")) + + then: + result.andExpect(status().isOk()) + .andExpect(jsonPath("\$.id").value(defaultEntityDescriptorResourceId)) + .andExpect(jsonPath("\$.serviceEnabled").value(false)) + .andExpect(jsonPath("\$.approved").value(true)) + + when: + def result2 = mockMvc.perform(patch("/api/approve/entityDescriptor/" + defaultEntityDescriptorResourceId + "/unapprove")) + + then: + result2.andExpect(status().isOk()) + .andExpect(jsonPath("\$.id").value(defaultEntityDescriptorResourceId)) + .andExpect(jsonPath("\$.serviceEnabled").value(false)) + .andExpect(jsonPath("\$.approved").value(false)) + + } } \ No newline at end of file From d7bd56b1b2f275d0efcf98238e95bfc6d5778284 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Wed, 5 Oct 2022 16:32:02 -0700 Subject: [PATCH 08/56] SHIBUI-2394 Added logic to get the list of groups a group can approve for --- .../CoreShibUiConfiguration.java | 4 +- .../admin/ui/security/model/Approvers.java | 2 +- .../admin/ui/security/model/Group.java | 10 ++++ .../listener/GroupUpdatedEntityListener.java | 18 ++++++- .../model/listener/ILazyLoaderHelper.java | 2 + .../security/repository/GroupsRepository.java | 13 +++++ .../ui/security/service/GroupServiceImpl.java | 5 -- .../ui/security/service/UserService.java | 4 ++ .../admin/ui/AbstractBaseDataJpaTest.groovy | 2 +- .../ui/BaseDataJpaTestConfiguration.groovy | 4 +- .../repository/GroupsRepositoryTests.groovy | 49 +++++++++++++++++++ 11 files changed, 100 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java index fdc85e20f..66e38316c 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java @@ -206,9 +206,9 @@ public EntityDescriptorConversionUtils EntityDescriptorConverstionUtilsInit(Enti } @Bean - public GroupUpdatedEntityListener groupUpdatedEntityListener(OwnershipRepository repo) { + public GroupUpdatedEntityListener groupUpdatedEntityListener(OwnershipRepository repo, GroupsRepository groupsRepository) { GroupUpdatedEntityListener listener = new GroupUpdatedEntityListener(); - listener.init(repo); + listener.init(repo, groupsRepository); return listener; } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java index 811e4ffc6..dd0c4bec4 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java @@ -22,6 +22,6 @@ public class Approvers { @Column(name = "resource_id") private String resourceId = UUID.randomUUID().toString(); - @OneToMany + @ManyToMany private List approverGroups = new ArrayList<>(); } \ No newline at end of file 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 ac02c5067..14597deb2 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 @@ -33,6 +33,9 @@ public class Group implements Owner { @JsonIgnore public static Group ADMIN_GROUP; + @Transient + List approveForList = new ArrayList<>(); + @Column(name = "group_description") String description; @@ -97,4 +100,11 @@ public int hashCode() { public boolean equals(Object o) { return o instanceof Group && this.resourceId.equals(((Group)o).resourceId); } + + public List getApproveForList() { + if (lazyLoaderHelper != null) { + lazyLoaderHelper.loadApproveForList(this); + } + return approveForList; + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/GroupUpdatedEntityListener.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/GroupUpdatedEntityListener.java index d477ae78c..5fa01c570 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/GroupUpdatedEntityListener.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/GroupUpdatedEntityListener.java @@ -2,6 +2,7 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository; import org.springframework.beans.factory.annotation.Autowired; @@ -9,17 +10,20 @@ import javax.persistence.PostPersist; import javax.persistence.PostRemove; import javax.persistence.PostUpdate; +import java.util.List; import java.util.Set; public class GroupUpdatedEntityListener implements ILazyLoaderHelper { + private static GroupsRepository groupsRepository; private static OwnershipRepository ownershipRepository; /** * @see https://stackoverflow.com/questions/12155632/injecting-a-spring-dependency-into-a-jpa-entitylistener */ @Autowired - public static void init(OwnershipRepository repo) { - GroupUpdatedEntityListener.ownershipRepository = repo; + public static void init(OwnershipRepository ownershipRepository, GroupsRepository groupsRepository) { + GroupUpdatedEntityListener.ownershipRepository = ownershipRepository; + GroupUpdatedEntityListener.groupsRepository = groupsRepository; } @PostPersist @@ -38,4 +42,14 @@ public void loadOwnedItems(Group group) { group.setOwnedItems(ownedItems); } + @Override + public void loadApproveForList(Group group) { + List result = group.getResourceId().equals(Group.ADMIN_GROUP.getResourceId()) ? + groupsRepository.findAllGroupIds() : + groupsRepository.getGroupIdsOfGroupsToApproveFor(group.getResourceId()); + if (result != null) { + group.setApproveForList(result); + } + } + } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/ILazyLoaderHelper.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/ILazyLoaderHelper.java index 689306932..b669845aa 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/ILazyLoaderHelper.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/listener/ILazyLoaderHelper.java @@ -4,6 +4,8 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.User; public interface ILazyLoaderHelper { + default void loadApproveForList(Group group) { } + default void loadOwnedItems(Group g) { } default void loadGroups(User u) { } 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 daf3ce265..7d92d3c18 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 @@ -5,9 +5,22 @@ import org.springframework.data.jpa.repository.JpaRepository; import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface GroupsRepository extends JpaRepository { void deleteByResourceId(String resourceId); Group findByResourceId(String id); + + @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); + + @Query(nativeQuery = true, value = "SELECT resource_id FROM user_groups") + List findAllGroupIds(); } \ 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 6eac2fde7..a9607534f 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 @@ -37,11 +37,6 @@ public class GroupServiceImpl implements IGroupService { @Autowired protected OwnershipRepository ownershipRepository; -// public GroupServiceImpl(GroupsRepository repo, OwnershipRepository ownershipRepository) { -// this.groupRepository = repo; -// this.ownershipRepository = ownershipRepository; -// } - @Override @Transactional public Group createGroup(Group group) throws GroupExistsConflictException, InvalidGroupRegexException { 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 25f152a90..873ba3df6 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 @@ -144,6 +144,10 @@ public Group getCurrentUserGroup() { } } + public List getGroupsCurrentUserCanApprove() { + return getCurrentUserGroup().getApproveForList(); + } + public Set getUserRoles(String username) { Optional user = userRepository.findByUsername(username); HashSet result = new HashSet<>(); diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/AbstractBaseDataJpaTest.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/AbstractBaseDataJpaTest.groovy index 85859855c..65195d7ae 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/AbstractBaseDataJpaTest.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/AbstractBaseDataJpaTest.groovy @@ -87,7 +87,7 @@ abstract class AbstractBaseDataJpaTest extends Specification implements ResetsDa } createAdminUser() - GroupUpdatedEntityListener.init(ownershipRepository) + GroupUpdatedEntityListener.init(ownershipRepository, groupRepository) UserUpdatedEntityListener.init(ownershipRepository, groupRepository) } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestConfiguration.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestConfiguration.groovy index 2c246889a..b8baf83f8 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestConfiguration.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestConfiguration.groovy @@ -54,9 +54,9 @@ class BaseDataJpaTestConfiguration { } @Bean - GroupUpdatedEntityListener groupUpdatedEntityListener(OwnershipRepository ownershipRepository) { + GroupUpdatedEntityListener groupUpdatedEntityListener(OwnershipRepository ownershipRepository, GroupsRepository groupsRepository) { GroupUpdatedEntityListener listener = new GroupUpdatedEntityListener() - listener.init(ownershipRepository) + listener.init(ownershipRepository, groupsRepository) return listener } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy index f0dcfa68d..b341d0ab7 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.security.repository import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest +import edu.internet2.tier.shibboleth.admin.ui.security.model.Approvers import edu.internet2.tier.shibboleth.admin.ui.security.model.Group import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership import org.springframework.beans.factory.annotation.Autowired @@ -205,4 +206,52 @@ class GroupsRepositoryTests extends AbstractBaseDataJpaTest { then: nothingThere == null } + + def "get list of groups that a group can approve for"() { + when: + groupService.clearAllForTesting() + List apprGroups = new ArrayList<>() + String[] groupNames = ['BBB', 'CCC', 'EEE', 'AAA'] + groupNames.each {name -> { + Group group = new Group().with({ + it.name = name + it.description = name + it.resourceId = name + it + }) + if (name != "AAA") { + apprGroups.add(groupRepository.save(group)) + } else { + Approvers approvers = new Approvers() + approvers.setApproverGroups(apprGroups) + List apprList = new ArrayList<>() + apprList.add(approversRepository.save(approvers)) + group.setApproversList(apprList) + groupRepository.save(group) + } + }} + Group group = new Group().with({ + it.name = 'DDD' + it.description = 'DDD' + it.resourceId = 'DDD' + it + }) + Approvers approvers = new Approvers() + apprGroups = new ArrayList<>() + apprGroups.add(groupRepository.findByResourceId('BBB')) + approvers.setApproverGroups(apprGroups) + List apprList = new ArrayList<>() + apprList.add(approversRepository.save(approvers)) + group.setApproversList(apprList) + groupRepository.save(group) + entityManager.flush() + entityManager.clear() + + then: + def result = groupRepository.getGroupIdsOfGroupsToApproveFor('BBB') + result.size() == 2 + result.contains('AAA') + result.contains('DDD') + groupRepository.findAllGroupIds().size() == 6 + } } \ No newline at end of file From c33925ee8a6b578faa17130595ff7f29f3090f05 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 6 Oct 2022 07:04:04 -0700 Subject: [PATCH 09/56] Split actions tab --- .../main/resources/i18n/messages.properties | 1 + ui/src/app/dashboard/view/ActionsTab.js | 76 +++++++++++++------ 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index a76ccd5b1..d020b1000 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -489,6 +489,7 @@ label.disable=Disable label.enabled=Enabled label.disabled=Disabled label.enable-metadata-sources=Enable Metadata Sources +label.approve-metadata-sources=Approve Metadata Sources label.title=Title label.author=Author label.creation-date=Creation Date diff --git a/ui/src/app/dashboard/view/ActionsTab.js b/ui/src/app/dashboard/view/ActionsTab.js index eda40b751..f48dc1ded 100644 --- a/ui/src/app/dashboard/view/ActionsTab.js +++ b/ui/src/app/dashboard/view/ActionsTab.js @@ -1,4 +1,5 @@ import React from 'react'; +import { useParams } from 'react-router'; import { MetadataActions } from '../../admin/container/MetadataActions'; import UserActions from '../../admin/container/UserActions'; import Spinner from '../../core/components/Spinner'; @@ -6,8 +7,15 @@ import Spinner from '../../core/components/Spinner'; import Translate from '../../i18n/components/translate'; import SourceList from '../../metadata/domain/source/component/SourceList'; +import Nav from 'react-bootstrap/Nav'; +import { Switch, Route, useRouteMatch, Redirect } from 'react-router-dom'; +import { NavLink } from 'react-router-dom'; +import Badge from 'react-bootstrap/Badge'; + export function ActionsTab({ sources, users, reloadSources, reloadUsers, loadingSources, loadingUsers }) { + const { path, url } = useRouteMatch(); + return ( <>
@@ -15,33 +23,55 @@ export function ActionsTab({ sources, users, reloadSources, reloadUsers, loading
- Enable Metadata Sources + Actions Required
-
- - {(enable) => - enable(s, e, reloadSources)}> - {loadingSources &&
} -
- } -
-
- -
-
-
-
-
-
- User Access Request -
-
+
+ +
- - {loadingUsers &&
} -
+ + + + + + + + {(enable) => + enable(s, e, reloadSources)}> + {loadingSources &&
} +
+ } +
+
+ + + {loadingUsers &&
} +
+ } /> +
From e2d841bf7de579bab7782ee451fc1cb4eb5a371f Mon Sep 17 00:00:00 2001 From: chasegawa Date: Mon, 10 Oct 2022 11:26:06 -0700 Subject: [PATCH 10/56] SHIBUI-2394 Incremental commit --- .../admin/ui/configuration/DevConfig.groovy | 32 ++++++- .../EntityDescriptorController.java | 6 ++ .../admin/ui/domain/EntityDescriptor.java | 14 ++- .../EntityDescriptorRepository.java | 7 ++ .../admin/ui/security/model/Group.java | 1 + .../ui/service/EntityDescriptorService.java | 2 + .../JPAEntityDescriptorServiceImpl.java | 19 +++- .../controller/ApproveControllerTests.groovy | 14 ++- .../AdminUserServiceTests.groovy | 6 +- ...JPAEntityDescriptorServiceImplTests.groovy | 91 +++++++++++++++++++ 10 files changed, 180 insertions(+), 12 deletions(-) diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy index a644a58a0..70892cbb7 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy @@ -12,9 +12,11 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ReloadableMetadat 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.repository.MetadataResolverRepository +import edu.internet2.tier.shibboleth.admin.ui.security.model.Approvers 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.ApproversRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository @@ -39,6 +41,7 @@ class DevConfig { private final OpenSamlObjects openSamlObjects private final RoleRepository roleRepository private final UserRepository userRepository + private final ApproversRepository approversRepository @Autowired private UserService userService @@ -49,7 +52,8 @@ class DevConfig { RoleRepository roleRepository, EntityDescriptorRepository entityDescriptorRepository, OpenSamlObjects openSamlObjects, - IGroupService groupService) { + IGroupService groupService, + ApproversRepository approversRepository) { this.userRepository = adminUserRepository this.metadataResolverRepository = metadataResolverRepository @@ -57,7 +61,7 @@ class DevConfig { this.entityDescriptorRepository = entityDescriptorRepository this.openSamlObjects = openSamlObjects this.groupsRepository = groupsRepository - + this.approversRepository = approversRepository groupService.ensureAdminGroupExists() } @@ -85,7 +89,29 @@ class DevConfig { } } groupsRepository.flush() - + + List apprGroups = new ArrayList<>() + String[] groupNames = ['XXX', 'YYY', 'ZZZ'] + groupNames.each {name -> { + Group group = new Group().with({ + it.name = name + it.description = name + it.resourceId = name + it + }) + if (name != "ZZZ") { + apprGroups.add(groupsRepository.save(group)) + } else { + Approvers approvers = new Approvers() + approvers.setApproverGroups(apprGroups) + List apprList = new ArrayList<>() + apprList.add(approversRepository.save(approvers)) + group.setApproversList(apprList) + groupsRepository.save(group) + } + }} + groupsRepository.flush() + if (roleRepository.count() == 0) { def roles = [new Role().with { name = 'ROLE_ADMIN' diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java index 0a6cda0b3..3e1f4db27 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java @@ -85,6 +85,12 @@ public ResponseEntity getAll() throws ForbiddenException { return ResponseEntity.ok(entityDescriptorService.getAllEntityDescriptorProjectionsBasedOnUserAccess()); } + @GetMapping("/EntityDescriptors/needsApproval") + @Transactional + public ResponseEntity getAllNeedingApproval() throws ForbiddenException { + return ResponseEntity.ok(entityDescriptorService.getAllEntityDescriptorProjectionsNeedingApprovalBasedOnUserAccess()); + } + @GetMapping("/EntityDescriptor/{resourceId}/Versions") @Transactional public ResponseEntity getAllVersions(@PathVariable String resourceId) throws PersistentEntityNotFound, ForbiddenException { 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 33e7ce6d1..872f78b1d 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 @@ -47,13 +47,17 @@ public class EntityDescriptor extends AbstractDescriptor implements org.opensaml @NotAudited private AffiliationDescriptor affiliationDescriptor; + @Getter + @Setter + private boolean approved; + @OneToOne(cascade = CascadeType.ALL) @NotAudited private AttributeAuthorityDescriptor attributeAuthorityDescriptor; @ElementCollection (fetch = FetchType.EAGER) @EqualsAndHashCode.Exclude - private List approved = new ArrayList<>(); + private List approvedBy = new ArrayList<>(); @OneToOne(cascade = CascadeType.ALL) @NotAudited @@ -317,16 +321,16 @@ public OwnableType getOwnableType() { } public void addApproval(Group group) { - approved.add(group.getName()); + approvedBy.add(group.getName()); } public int approvedCount() { - return approved.size(); + return approvedBy.size(); } public void removeLastApproval() { - if (!approved.isEmpty()) { - approved.remove(approved.size() - 1); + if (!approvedBy.isEmpty()) { + approvedBy.remove(approvedBy.size() - 1); } } } \ No newline at end of file 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 bb2b275d6..a4ff5c43f 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 @@ -3,6 +3,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor; 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; import java.util.stream.Stream; @@ -37,4 +38,10 @@ public interface EntityDescriptorRepository extends JpaRepository findAllByIdOfOwnerIsNull(); + + @Query(value = "select e from EntityDescriptor e" + + " where e.idOfOwner in (:groupIds)" + + " and e.serviceEnabled = false" + + " and e.approved = false") + List 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/model/Group.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Group.java index 14597deb2..cc2570320 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 @@ -34,6 +34,7 @@ public class Group implements Owner { public static Group ADMIN_GROUP; @Transient + @JsonIgnore List approveForList = new ArrayList<>(); @Column(name = "group_description") 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 1f37b83bc..8ee4adb52 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 @@ -124,4 +124,6 @@ EntityDescriptorRepresentation updateEntityDescriptorEnabledStatus(String resour EntityDescriptorRepresentation updateGroupForEntityDescriptor(String resourceId, String groupId); EntityDescriptorRepresentation changeApproveStatusOfEntityDescriptor(String resourceId, boolean status) throws PersistentEntityNotFound, ForbiddenException; + + List getAllEntityDescriptorProjectionsNeedingApprovalBasedOnUserAccess(); } \ No newline at end of file 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 0b1f67932..667477f09 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 @@ -91,10 +91,14 @@ public EntityDescriptorRepresentation changeApproveStatusOfEntityDescriptor(Stri throw new ForbiddenException("You do not have the permissions necessary to approve this entity descriptor."); } ed.addApproval(userService.getCurrentUserGroup()); + Group ownerGroup = groupService.find(ed.getIdOfOwner()); + ed.setApproved(ed.approvedCount() == ownerGroup.getApproversList().size()); // safe check in case of weird race conditions from the UI ed = entityDescriptorRepository.save(ed); } } else { // un-approve ed.removeLastApproval(); + Group ownerGroup = groupService.find(ed.getIdOfOwner()); + ed.setApproved(ed.approvedCount() == ownerGroup.getApproversList().size()); // safe check in case of weird race conditions from the UI ed = entityDescriptorRepository.save(ed); } return createRepresentationFromDescriptor(ed); @@ -434,6 +438,16 @@ public List getAllEntityDescriptorProjectionsBasedOn } } + /** + * Based on the current users group, find those entities that the user can approve that need approval + */ + @Override + public List getAllEntityDescriptorProjectionsNeedingApprovalBasedOnUserAccess() { + List groupsToApprove = userService.getGroupsCurrentUserCanApprove(); + List result = entityDescriptorRepository.getEntityDescriptorsNeedingApproval(groupsToApprove); + return result; + } + @Override public List getAttributeReleaseListFromAttributeList(List attributeList) { if (attributeList == null) { @@ -512,10 +526,13 @@ public EntityDescriptorRepresentation updateEntityDescriptorEnabledStatus(String // check to see if approvals have been completed int approvedCount = ed.approvedCount(); List approversList = groupService.find(ed.getIdOfOwner()).getApproversList(); - if (!ed.isServiceEnabled() && !userService.currentUserIsAdmin() && approversList.size() > approvedCount) { + if (status == true && !ed.isServiceEnabled() && !userService.currentUserIsAdmin() && approversList.size() > approvedCount) { throw new ForbiddenException("Approval must be completed before you can change the enable status of this entity descriptor."); } ed.setServiceEnabled(status); + if (status == true) { + ed.setApproved(true); + } ed = entityDescriptorRepository.save(ed); return createRepresentationFromDescriptor(ed); } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy index b18c40aad..2a5b46883 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy @@ -110,27 +110,34 @@ class ApproveControllerTests extends AbstractBaseDataJpaTest { @WithMockUser(value = "AUser", roles = ["USER"]) def 'Owner group cannot approve their own entity descriptor'() { expect: + entityDescriptorRepository.findByResourceId(defaultEntityDescriptorResourceId).isApproved() == false try { mockMvc.perform(patch("/api/approve/entityDescriptor/" + defaultEntityDescriptorResourceId + "/approve")) } catch (Exception e) { e instanceof ForbiddenException } + entityDescriptorRepository.findByResourceId(defaultEntityDescriptorResourceId).isApproved() == false } @WithMockUser(value = "DUser", roles = ["USER"]) def 'non-approver group cannot approve entity descriptor'() { expect: + entityDescriptorRepository.findByResourceId(defaultEntityDescriptorResourceId).isApproved() == false try { mockMvc.perform(patch("/api/approve/entityDescriptor/" + defaultEntityDescriptorResourceId + "/approve")) } catch (Exception e) { e instanceof ForbiddenException } + entityDescriptorRepository.findByResourceId(defaultEntityDescriptorResourceId).isApproved() == false } @WithMockUser(value = "BUser", roles = ["USER"]) def 'Approver group can approve an entity descriptor'() { + expect: + entityDescriptorRepository.findByResourceId(defaultEntityDescriptorResourceId).isApproved() == false + when: def result = mockMvc.perform(patch("/api/approve/entityDescriptor/" + defaultEntityDescriptorResourceId + "/approve")) @@ -139,10 +146,14 @@ class ApproveControllerTests extends AbstractBaseDataJpaTest { .andExpect(jsonPath("\$.id").value(defaultEntityDescriptorResourceId)) .andExpect(jsonPath("\$.serviceEnabled").value(false)) .andExpect(jsonPath("\$.approved").value(true)) + entityDescriptorRepository.findByResourceId(defaultEntityDescriptorResourceId).isApproved() } @WithMockUser(value = "BUser", roles = ["USER"]) def 'Approver can approve and un-approve an entity descriptor'() { + expect: + entityDescriptorRepository.findByResourceId(defaultEntityDescriptorResourceId).isApproved() == false + when: def result = mockMvc.perform(patch("/api/approve/entityDescriptor/" + defaultEntityDescriptorResourceId + "/approve")) @@ -151,6 +162,7 @@ class ApproveControllerTests extends AbstractBaseDataJpaTest { .andExpect(jsonPath("\$.id").value(defaultEntityDescriptorResourceId)) .andExpect(jsonPath("\$.serviceEnabled").value(false)) .andExpect(jsonPath("\$.approved").value(true)) + entityDescriptorRepository.findByResourceId(defaultEntityDescriptorResourceId).isApproved() when: def result2 = mockMvc.perform(patch("/api/approve/entityDescriptor/" + defaultEntityDescriptorResourceId + "/unapprove")) @@ -160,6 +172,6 @@ class ApproveControllerTests extends AbstractBaseDataJpaTest { .andExpect(jsonPath("\$.id").value(defaultEntityDescriptorResourceId)) .andExpect(jsonPath("\$.serviceEnabled").value(false)) .andExpect(jsonPath("\$.approved").value(false)) - + entityDescriptorRepository.findByResourceId(defaultEntityDescriptorResourceId).isApproved() == false } } \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserServiceTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserServiceTests.groovy index d40e2bd1c..95c8dc5e6 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserServiceTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserServiceTests.groovy @@ -5,6 +5,7 @@ import edu.internet2.tier.shibboleth.admin.ui.configuration.DevConfig 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.repository.MetadataResolverRepository +import edu.internet2.tier.shibboleth.admin.ui.security.repository.ApproversRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository @@ -74,9 +75,10 @@ class AdminUserServiceTests extends AbstractBaseDataJpaTest { @Bean DevConfig devConfig(UserRepository adminUserRepository, GroupsRepository groupsRepository, IGroupService groupService, MetadataResolverRepository metadataResolverRepository, OpenSamlObjects openSamlObjects, UserService userService, - RoleRepository roleRepository, EntityDescriptorRepository entityDescriptorRepository) { + RoleRepository roleRepository, EntityDescriptorRepository entityDescriptorRepository, + ApproversRepository approversRepository) { DevConfig dc = new DevConfig( adminUserRepository, groupsRepository, metadataResolverRepository, roleRepository, - entityDescriptorRepository, openSamlObjects, groupService).with { + entityDescriptorRepository, openSamlObjects, groupService, approversRepository).with { it.userService = userService it } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy index 9e65bf28a..6e7e2cf43 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy @@ -15,6 +15,12 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.ServiceProviderSso import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup import edu.internet2.tier.shibboleth.admin.ui.jsonschema.LowLevelJsonSchemaValidator import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects +import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorProjection +import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository +import edu.internet2.tier.shibboleth.admin.ui.security.model.Approvers +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.util.RandomGenerator import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator import edu.internet2.tier.shibboleth.admin.util.EntityDescriptorConversionUtils @@ -24,6 +30,8 @@ import org.springframework.boot.test.json.JacksonTester import org.springframework.context.annotation.PropertySource import org.springframework.core.io.DefaultResourceLoader import org.springframework.mock.http.MockHttpInputMessage +import org.springframework.security.test.context.support.WithMockUser +import org.springframework.transaction.annotation.Transactional import org.xmlunit.builder.DiffBuilder import org.xmlunit.builder.Input import org.xmlunit.diff.DefaultNodeMatcher @@ -52,14 +60,64 @@ class JPAEntityDescriptorServiceImplTests extends AbstractBaseDataJpaTest { RandomGenerator generator JacksonTester jacksonTester + @Autowired + EntityDescriptorRepository entityDescriptorRepository + + @Transactional def setup() { JacksonTester.initFields(this, mapper) generator = new RandomGenerator() EntityDescriptorConversionUtils.openSamlObjects = openSamlObjects EntityDescriptorConversionUtils.entityService = entityService openSamlObjects.init() + + groupService.clearAllForTesting() + List apprGroups = new ArrayList<>() + String[] groupNames = ['BBB', 'CCC', 'EEE', 'AAA'] + groupNames.each {name -> { + Group group = new Group().with({ + it.name = name + it.description = name + it.resourceId = name + it + }) + if (name != "AAA") { + apprGroups.add(groupRepository.save(group)) + } else { + Approvers approvers = new Approvers() + approvers.setApproverGroups(apprGroups) + List apprList = new ArrayList<>() + apprList.add(approversRepository.save(approvers)) + group.setApproversList(apprList) + groupRepository.save(group) + } + }} + Group group = new Group().with({ + it.name = 'DDD' + it.description = 'DDD' + it.resourceId = 'DDD' + it + }) + Approvers approvers = new Approvers() + apprGroups = new ArrayList<>() + apprGroups.add(groupRepository.findByResourceId('BBB')) + approvers.setApproverGroups(apprGroups) + List apprList = new ArrayList<>() + apprList.add(approversRepository.save(approvers)) + group.setApproversList(apprList) + groupRepository.save(group) + + Optional userRole = roleRepository.findByName("ROLE_USER") + User user = new User(username: "bbUser", roles:[userRole.get()], password: "foo") + user.setGroup(groupRepository.findByResourceId("BBB")) + userService.save(user) + + entityManager.flush() + entityManager.clear() } + + def "simple Entity Descriptor"() { when: def expected = ''' Date: Mon, 10 Oct 2022 14:57:34 -0700 Subject: [PATCH 11/56] SHIBUI-2394 corrections to services and tests --- .../admin/ui/security/model/Approvers.java | 18 +++++++++++ .../admin/ui/security/model/Group.java | 5 +++ .../ui/security/service/GroupServiceImpl.java | 31 ++++++++++++------- .../GroupsControllerIntegrationTests.groovy | 12 +++---- .../service/GroupServiceForTesting.groovy | 1 + .../security/service/GroupServiceTests.groovy | 22 +++++-------- 6 files changed, 58 insertions(+), 31 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java index dd0c4bec4..2a0739646 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java @@ -1,5 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.security.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import lombok.NoArgsConstructor; @@ -8,6 +9,7 @@ import javax.persistence.Id; import javax.persistence.ManyToMany; import javax.persistence.OneToMany; +import javax.persistence.Transient; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -20,8 +22,24 @@ public class Approvers { @Id @Column(name = "resource_id") + @JsonIgnore private String resourceId = UUID.randomUUID().toString(); @ManyToMany + @JsonIgnore private List approverGroups = new ArrayList<>(); + + @Transient + private List approverGroupIds = new ArrayList<>(); + + public List getApproverGroupIds() { + if (approverGroupIds.isEmpty()) { + approverGroups.forEach(group -> approverGroupIds.add(group.getResourceId())); + } + return approverGroupIds; + } + + public void setApproverGroups(List appGroups) { + this.approverGroups = appGroups; + } } \ No newline at end of file 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 cc2570320..2591e36b5 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 @@ -108,4 +108,9 @@ public List getApproveForList() { } return approveForList; } + + @Override + public String toString() { + return "Group resourceId=" + 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 a9607534f..917b84fd7 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 @@ -115,16 +115,13 @@ public List findAll() { return groupRepository.findAll(); } - @Override - public Group updateGroup(Group group) throws PersistentEntityNotFound, InvalidGroupRegexException { - manageApproversList(group); // have to make sure that approvers have been saved before a fetch or we can get data integrity errors on lookup... - Group g = find(group.getResourceId()); - if (g == null) { - throw new PersistentEntityNotFound(String.format("Unable to find group with resource id: [%s] and name: [%s]", - group.getResourceId(), group.getName())); + private List getGroupListFromIds(List approverGroupIds) { + List result = new ArrayList<>(); + for (String id : approverGroupIds) { + Group g = find(id); + result.add(g); } - validateGroupRegex(group); - return groupRepository.save(group); + return result; } private void manageApproversList(Group group) { @@ -134,14 +131,26 @@ private void manageApproversList(Group group) { List updatedApprovers = new ArrayList<>(); group.getApproversList().forEach(approvers -> { Approvers savedApprovers = approversRepository.findByResourceId(approvers.getResourceId()); - savedApprovers = savedApprovers == null ? approvers : savedApprovers; - savedApprovers.setApproverGroups(approvers.getApproverGroups()); + savedApprovers = savedApprovers == null ? approversRepository.save(approvers) : savedApprovers; + savedApprovers.setApproverGroups(getGroupListFromIds(approvers.getApproverGroupIds())); Approvers updatedApp = approversRepository.save(savedApprovers); updatedApprovers.add(updatedApp); }); group.setApproversList(updatedApprovers); } + @Override + public Group updateGroup(Group group) throws PersistentEntityNotFound, InvalidGroupRegexException { + manageApproversList(group); // have to make sure that approvers have been saved before a fetch or we can get data integrity errors on lookup... + Group g = find(group.getResourceId()); + if (g == null) { + throw new PersistentEntityNotFound(String.format("Unable to find group with resource id: [%s] and name: [%s]", + group.getResourceId(), group.getName())); + } + validateGroupRegex(group); + return groupRepository.save(group); + } + /** * If the regex is blank simply return */ 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 6fae2ed87..a4c46e1b1 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 @@ -72,7 +72,6 @@ class GroupsControllerIntegrationTests extends AbstractBaseDataJpaTest { when: "POST new group with approvers" groupService.clearAllForTesting() - List apprGroups = new ArrayList<>() String[] groupNames = ['AAA', 'BBB', 'CCC', 'DDD'] groupNames.each {name -> { Group group = new Group().with({ @@ -86,13 +85,14 @@ class GroupsControllerIntegrationTests extends AbstractBaseDataJpaTest { entityManager.flush() entityManager.clear() + List apprGroups = new ArrayList<>() groupNames.each {name ->{ if (!name.equals('AAA')) { - apprGroups.add(groupRepository.findByResourceId(name)) + apprGroups.add(name) } }} Approvers approvers = new Approvers() - approvers.setApproverGroups(apprGroups) + approvers.setApproverGroupIds(apprGroups) def apprList = new ArrayList<>() apprList.add(approvers) def newGroup2 = [name: 'Foo', description: 'Bar', resourceId: 'FooBar', approversList: apprList] @@ -104,9 +104,9 @@ class GroupsControllerIntegrationTests extends AbstractBaseDataJpaTest { .andExpect(jsonPath("\$.name").value("Foo")) .andExpect(jsonPath("\$.resourceId").value("FooBar")) .andExpect(jsonPath("\$.description").value("Bar")) - .andExpect(jsonPath("\$.approversList[0].approverGroups[0].resourceId").value("BBB")) - .andExpect(jsonPath("\$.approversList[0].approverGroups[1].resourceId").value("CCC")) - .andExpect(jsonPath("\$.approversList[0].approverGroups[2].resourceId").value("DDD")) + .andExpect(jsonPath("\$.approversList[0].approverGroupIds[0]").value("BBB")) + .andExpect(jsonPath("\$.approversList[0].approverGroupIds[1]").value("CCC")) + .andExpect(jsonPath("\$.approversList[0].approverGroupIds[2]").value("DDD")) } @WithMockAdmin diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceForTesting.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceForTesting.groovy index a3f223efb..6d18c2cd8 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceForTesting.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceForTesting.groovy @@ -14,6 +14,7 @@ class GroupServiceForTesting extends GroupServiceImpl { @Transactional void clearAllForTesting() { + approversRepository.deleteAll() groupRepository.deleteAll() ownershipRepository.clearAllOwnedByGroup() ensureAdminGroupExists() diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceTests.groovy index 16045bca7..6da1ff232 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceTests.groovy @@ -85,7 +85,7 @@ class GroupServiceTests extends AbstractBaseDataJpaTest { def "CRUD operations - approver groups" () { given: groupService.clearAllForTesting() - List apprGroups = new ArrayList<>() + List apprGroups = new ArrayList<>() String[] groupNames = ['AAA', 'BBB', 'CCC', 'DDD'] groupNames.each {name -> { Group group = new Group().with({ @@ -102,12 +102,12 @@ class GroupServiceTests extends AbstractBaseDataJpaTest { when: "Adding approval list to a group" groupNames.each {name ->{ if (!name.equals('AAA')) { - apprGroups.add(groupRepository.findByResourceId(name)) + apprGroups.add(name) } }} Approvers approvers = new Approvers() - approvers.setApproverGroups(apprGroups) - def apprList = new ArrayList<>() + approvers.setApproverGroupIds(apprGroups) + List apprList = new ArrayList<>() apprList.add(approvers) Group aaaGroup = groupService.find('AAA') aaaGroup.setApproversList(apprList) @@ -118,12 +118,12 @@ class GroupServiceTests extends AbstractBaseDataJpaTest { lookupGroup.getApproversList().size() == 1 def approvalGroups = lookupGroup.getApproversList().get(0).getApproverGroups() approvalGroups.size() == 3 - apprGroups.each {group -> { - assert approvalGroups.contains(group)} + approvalGroups.each {group -> { + assert apprGroups.contains(group.getResourceId())} } when: "removing approver group from existing list" - approvers.getApproverGroups().remove(groupService.find('BBB')) + approvers.setApproverGroupIds(Arrays.asList("CCC", "DDD")) apprList = new ArrayList<>() apprList.add(approvers) aaaGroup.setApproversList(apprList) @@ -134,13 +134,7 @@ class GroupServiceTests extends AbstractBaseDataJpaTest { lookupGroup2.getApproversList().size() == 1 def approvalGroups2 = lookupGroup2.getApproversList().get(0).getApproverGroups() approvalGroups2.size() == 2 - apprGroups.each { group -> - { - if (group.getResourceId() != 'BBB') { - assert approvalGroups2.contains(group) - } - } - } + approvalGroups2.forEach(group -> group.getResourceId().equals("CCC") || group.getResourceId().equals("DDD")) when: "removing all approver groups" apprList = new ArrayList<>() From a91b683924f83d45870bdc7ec1a7b468f7f17d9f Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 11 Oct 2022 15:59:00 -0700 Subject: [PATCH 12/56] SHIBUI-2394 corrections to code and tests --- backend/src/main/resources/application.properties | 1 + .../groovy/net/unicon/shibui/pac4j/Pac4JTestingConfig.groovy | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 6593646f9..c37e6c203 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -52,6 +52,7 @@ spring.jpa.properties.hibernate.show_sql=false spring.jpa.properties.hibernate.format_sql=true spring.jpa.properties.hibernate.check_nullability=true spring.jpa.hibernate.use-new-id-generator-mappings=true +spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true #Envers versioning spring.jpa.properties.org.hibernate.envers.store_data_at_delete=true diff --git a/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/Pac4JTestingConfig.groovy b/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/Pac4JTestingConfig.groovy index c2eb94ca7..9969741ff 100644 --- a/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/Pac4JTestingConfig.groovy +++ b/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/Pac4JTestingConfig.groovy @@ -31,9 +31,9 @@ class Pac4JTestingConfig { @Bean @Primary - GroupUpdatedEntityListener groupUpdatedEntityListener(OwnershipRepository repo) { + GroupUpdatedEntityListener groupUpdatedEntityListener(OwnershipRepository repo, GroupsRepository groupsRepository) { GroupUpdatedEntityListener listener = new GroupUpdatedEntityListener() - listener.init(repo) + listener.init(repo, groupsRepository) return listener } From abe47db0622734e6bf2b55b393214334e34f53f8 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Wed, 12 Oct 2022 11:00:01 -0700 Subject: [PATCH 13/56] added selector widget --- .../main/resources/i18n/messages.properties | 3 + ui/public/assets/schema/groups/group.json | 8 ++ ui/src/app/admin/Groups.js | 12 ++- ui/src/app/admin/component/GroupForm.js | 3 +- ui/src/app/admin/container/EditGroup.js | 3 +- ui/src/app/admin/container/NewGroup.js | 3 +- ui/src/app/admin/hooks.js | 9 +++ ui/src/app/form/component/index.js | 4 +- .../component/widgets/MultiSelectWidget.js | 79 +++++++++++++++++++ 9 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 ui/src/app/form/component/widgets/MultiSelectWidget.js diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index d020b1000..2c50bc6b8 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -510,6 +510,9 @@ label.compare-selected=Compare Selected label.restore-version=Restore Version ({ date }) label.group=Group +label.approvers-list=Approvers +tooltip.approvers-list=List of groups who are able to approve Metadata Sources. + label.saved=Saved label.by=By diff --git a/ui/public/assets/schema/groups/group.json b/ui/public/assets/schema/groups/group.json index 518293851..200ce4529 100644 --- a/ui/public/assets/schema/groups/group.json +++ b/ui/public/assets/schema/groups/group.json @@ -22,6 +22,14 @@ "title": "label.url-validation-regex", "description": "tooltip.url-validation-regex", "type": "string" + }, + "approversList": { + "title": "label.approvers-list", + "description": "tooltip.approvers-list", + "type": "array", + "items": { + "type": "string" + } } } } \ No newline at end of file diff --git a/ui/src/app/admin/Groups.js b/ui/src/app/admin/Groups.js index 8326f9cd1..1e1743d9c 100644 --- a/ui/src/app/admin/Groups.js +++ b/ui/src/app/admin/Groups.js @@ -20,10 +20,18 @@ export function Groups() { } /> - + + {(groups) => + + } + } /> - + + {(groups) => + + } + } /> diff --git a/ui/src/app/admin/component/GroupForm.js b/ui/src/app/admin/component/GroupForm.js index d4390d6d8..7c9cd7a59 100644 --- a/ui/src/app/admin/component/GroupForm.js +++ b/ui/src/app/admin/component/GroupForm.js @@ -8,7 +8,7 @@ import Translate from '../../i18n/components/translate'; import { useGroupUiSchema, useGroupUiValidator } from '../hooks'; import { FormContext, setFormDataAction, setFormErrorAction } from '../../form/FormManager'; -export function GroupForm ({group = {}, errors = [], loading = false, schema, onSave, onCancel}) { +export function GroupForm ({group = {}, errors = [], context = {}, loading = false, schema, onSave, onCancel}) { const { dispatch } = React.useContext(FormContext); const onChange = ({formData, errors}) => { @@ -42,6 +42,7 @@ export function GroupForm ({group = {}, errors = [], loading = false, schema, on
onChange(form)} validate={validator} diff --git a/ui/src/app/admin/container/EditGroup.js b/ui/src/app/admin/container/EditGroup.js index ad06368fb..175ac4e69 100644 --- a/ui/src/app/admin/container/EditGroup.js +++ b/ui/src/app/admin/container/EditGroup.js @@ -13,7 +13,7 @@ import { createNotificationAction, NotificationTypes, useNotificationDispatcher import { useTranslator } from '../../i18n/hooks'; import { BASE_PATH } from '../../App.constant'; -export function EditGroup() { +export function EditGroup({ groups }) { const { id } = useParams(); @@ -74,6 +74,7 @@ export function EditGroup() { {(data, errors) => <> { + // const inputType = (type || schema.type) === 'string' ? 'text' : `${type || schema.type}`; + + const opts = []; + + React.useEffect(() => console.log(formContext), [formContext]); + React.useEffect(() => console.log(props), [props]); + + const [touched, setTouched] = React.useState(false); + + const [multiSelections, setMultiSelections] = React.useState([]); + + return ( + + 0) ? "text-danger" : ""}`} htmlFor={`option-selector-${id}`}> + + + {(label || schema.title) && required ? : Item {id + 1}} + + {schema.description && } + + + {rawErrors?.length > 0 && touched && ( + + {rawErrors.map((error, i) => { + return ( + 0 ? 'sr-only' : ''}`}> + + {error} + + + ); + })} + + )} + + ); +}; + +export default MultiSelectWidget; \ No newline at end of file From 37156e5e98b59ef836c2bf2244830746da184914 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 13 Oct 2022 15:44:47 -0700 Subject: [PATCH 14/56] UI for additional approval --- ui/src/app/admin/Groups.js | 11 ++-- ui/src/app/admin/component/GroupForm.js | 22 ++++++-- ui/src/app/admin/container/ApprovalActions.js | 56 +++++++++++++++++++ ui/src/app/admin/container/NewGroup.js | 14 ++--- ui/src/app/admin/hooks.js | 38 +++++++++++-- ui/src/app/dashboard/view/ActionsTab.js | 15 ++++- ui/src/app/dashboard/view/Dashboard.js | 16 +++++- .../component/widgets/MultiSelectWidget.js | 35 +++++++----- ui/src/app/metadata/hooks/api.js | 6 ++ 9 files changed, 174 insertions(+), 39 deletions(-) create mode 100644 ui/src/app/admin/container/ApprovalActions.js diff --git a/ui/src/app/admin/Groups.js b/ui/src/app/admin/Groups.js index 1e1743d9c..0e2e062c5 100644 --- a/ui/src/app/admin/Groups.js +++ b/ui/src/app/admin/Groups.js @@ -1,9 +1,10 @@ -import React from 'react'; +import React, { Fragment } from 'react'; import { Switch, Route, useRouteMatch, Redirect } from 'react-router-dom'; import { GroupsProvider } from './hoc/GroupsProvider'; import { NewGroup } from './container/NewGroup'; import { EditGroup } from './container/EditGroup'; import { GroupsList } from './container/GroupsList'; +import Spinner from '../core/components/Spinner'; export function Groups() { @@ -21,15 +22,15 @@ export function Groups() { } /> - {(groups) => - + {(groups, onDelete, loading) => + { loading ?
: }
}
} /> - {(groups) => - + {(groups, onDelete, loading) => + { loading ?
: }
}
} /> diff --git a/ui/src/app/admin/component/GroupForm.js b/ui/src/app/admin/component/GroupForm.js index 7c9cd7a59..ce1999526 100644 --- a/ui/src/app/admin/component/GroupForm.js +++ b/ui/src/app/admin/component/GroupForm.js @@ -4,8 +4,9 @@ import Form from '../../form/Form'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSpinner, faSave } from '@fortawesome/free-solid-svg-icons'; import Translate from '../../i18n/components/translate'; +import set from 'lodash/set'; -import { useGroupUiSchema, useGroupUiValidator } from '../hooks'; +import { useGroupUiSchema, useGroupUiValidator, useGroupSchema, useGroupParser, useGroupFormatter} from '../hooks'; import { FormContext, setFormDataAction, setFormErrorAction } from '../../form/FormManager'; export function GroupForm ({group = {}, errors = [], context = {}, loading = false, schema, onSave, onCancel}) { @@ -18,6 +19,18 @@ export function GroupForm ({group = {}, errors = [], context = {}, loading = fal const uiSchema = useGroupUiSchema(); const validator = useGroupUiValidator(); + const groupSchema = React.useMemo(() => { + const filtered = context.groups.filter(g => !([group.resourceId].indexOf(g.resourceId) > -1)); + const enumList = filtered.map(g => g.resourceId); + const enumNames = filtered.map(g => g.name); + let s = { ...schema }; + s = set(s, 'properties.approversList.items.enum', enumList); + s = set(s, 'properties.approversList.items.enumNames', enumNames); + return s; + }, [schema, context.groups, group.resourceId]); + + const parser = useGroupParser(); + const formatter = useGroupFormatter(); return (<>
@@ -41,12 +54,11 @@ export function GroupForm ({group = {}, errors = [], context = {}, loading = fal
- onChange(form)} + onChange={(form) => onChange({ ...form, formData: parser(form.formData) })} validate={validator} - schema={schema} + schema={groupSchema} uiSchema={uiSchema} liveValidate={true}> <> diff --git a/ui/src/app/admin/container/ApprovalActions.js b/ui/src/app/admin/container/ApprovalActions.js new file mode 100644 index 000000000..13e1a605e --- /dev/null +++ b/ui/src/app/admin/container/ApprovalActions.js @@ -0,0 +1,56 @@ +import React from 'react'; +import { DeleteConfirmation } from '../../core/components/DeleteConfirmation'; +import { useMetadataActivator, useMetadataEntity } from '../../metadata/hooks/api'; + +import { NotificationContext, createNotificationAction, NotificationTypes } from '../../notifications/hoc/Notifications'; + +export function ApprovalActions ({type, children}) { + + const { dispatch } = React.useContext(NotificationContext); + + const { del, response } = useMetadataEntity(type, { + cachePolicy: 'no-cache' + }); + + const activator = useMetadataActivator(type); + + async function approveEntity(entity, enabled, cb = () => {}) { + await activator.patch(`/${type === 'source' ? entity.id : entity.resourceId}/${enabled ? 'approve' : 'unapprove'}`); + if (activator?.response.ok) { + dispatch(createNotificationAction( + `Metadata ${type} has been ${enabled ? 'enabled' : 'disabled'}.` + )); + cb(); + } else { + const { errorCode, errorMessage, cause } = activator?.response?.data; + dispatch(createNotificationAction( + `${errorCode}: ${errorMessage} ${cause ? `-${cause}` : ''}`, + NotificationTypes.ERROR + )); + } + } + + async function deleteEntity(id, cb = () => {}) { + await del(`/${id}`); + if (response.ok) { + dispatch(createNotificationAction( + `Metadata ${type} has been deleted.` + )); + cb(); + } else { + const { errorCode, errorMessage, cause } = activator?.response?.data; + dispatch(createNotificationAction( + `${errorCode}: ${errorMessage} ${cause ? `-${cause}` : ''}`, + NotificationTypes.ERROR + )); + } + } + + return ( + + {(block) => + <>{children(approveEntity, (id, cb) => block(() => deleteEntity(id, cb)))} + } + + ); +} \ No newline at end of file diff --git a/ui/src/app/admin/container/NewGroup.js b/ui/src/app/admin/container/NewGroup.js index 4d80e7e07..c24f4f516 100644 --- a/ui/src/app/admin/container/NewGroup.js +++ b/ui/src/app/admin/container/NewGroup.js @@ -66,13 +66,13 @@ export function NewGroup({ groups }) { {(data, errors) => <> save(data)} - onCancel={() => cancel()} /> + context={ { groups } } + group={data} + errors={errors} + schema={schema} + loading={loading} + onSave={(data) => save(data)} + onCancel={() => cancel()} /> } } diff --git a/ui/src/app/admin/hooks.js b/ui/src/app/admin/hooks.js index 1786db57a..102b58220 100644 --- a/ui/src/app/admin/hooks.js +++ b/ui/src/app/admin/hooks.js @@ -3,6 +3,8 @@ import isNil from 'lodash/isNil'; import {isValidRegex} from '../core/utility/is_valid_regex'; import API_BASE_PATH from '../App.constant'; +import set from 'lodash/set'; + export function useGroups (opts = { cachePolicy: 'no-cache' }) { return useFetch(`${API_BASE_PATH}/admin/groups`, opts); } @@ -23,6 +25,16 @@ export function useRole(id) { }); } +export function useGroupSchema (schema, groups, invalid = []) { + const filtered = groups.filter(g => !(invalid.indexOf(g.resourceId) > -1)); + const enumList = filtered.map(g => g.resourceId); + const enumNames = filtered.map(g => g.name); + let s = { ...schema }; + s = set(s, 'properties.approversList.items.enum', enumList); + s = set(s, 'properties.approversList.items.enumNames', enumNames); + return s; +} + export function useGroupUiSchema () { return { description: { @@ -31,15 +43,33 @@ export function useGroupUiSchema () { approversList: { 'ui:options': { 'widget': 'MultiSelectWidget', - 'enum': [ - 'Foo', - 'Bar' - ] } } }; } +export function useGroupFormatter () { + return (group) => ({ + ...group, + approversList: [ + ...(group?.approversList?.length ? group.approversList[0].approverGroupIds : [] ) + ] + }); +} + +export function useGroupParser () { + return (group = {}) => ({ + ...group, + approversList: [ + { + approverGroupIds: [ + ...group?.approversList + ] + } + ] + }); +} + export function useGroupUiValidator() { return (formData, errors) => { if (!isNil(formData?.validationRegex) && formData?.validationRegex !== '') { diff --git a/ui/src/app/dashboard/view/ActionsTab.js b/ui/src/app/dashboard/view/ActionsTab.js index f48dc1ded..a642ec001 100644 --- a/ui/src/app/dashboard/view/ActionsTab.js +++ b/ui/src/app/dashboard/view/ActionsTab.js @@ -11,8 +11,9 @@ import Nav from 'react-bootstrap/Nav'; import { Switch, Route, useRouteMatch, Redirect } from 'react-router-dom'; import { NavLink } from 'react-router-dom'; import Badge from 'react-bootstrap/Badge'; +import { ApprovalActions } from '../../admin/container/ApprovalActions'; -export function ActionsTab({ sources, users, reloadSources, reloadUsers, loadingSources, loadingUsers }) { +export function ActionsTab({ sources, users, reloadSources, reloadUsers, reloadApprovals, loadingSources, loadingUsers, loadingApprovals }) { const { path, url } = useRouteMatch(); @@ -52,7 +53,7 @@ export function ActionsTab({ sources, users, reloadSources, reloadUsers, loading
- +
@@ -71,7 +72,17 @@ export function ActionsTab({ sources, users, reloadSources, reloadUsers, loading {loadingUsers &&
} } /> + + + {(approve) => + approve(s, e, reloadApprovals)}> + {loadingApprovals &&
} +
+ } +
+ } />
+
diff --git a/ui/src/app/dashboard/view/Dashboard.js b/ui/src/app/dashboard/view/Dashboard.js index 7644b6def..a4ce21669 100644 --- a/ui/src/app/dashboard/view/Dashboard.js +++ b/ui/src/app/dashboard/view/Dashboard.js @@ -14,7 +14,7 @@ import { ActionsTab } from './ActionsTab'; import { useCurrentUserLoading, useIsAdmin } from '../../core/user/UserContext'; import useFetch from 'use-http'; import API_BASE_PATH from '../../App.constant'; -import { useNonAdminSources } from '../../metadata/hooks/api'; +import { useNonAdminSources, useUnapprovedSources} from '../../metadata/hooks/api'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSpinner } from '@fortawesome/free-solid-svg-icons'; import Badge from 'react-bootstrap/Badge'; @@ -31,12 +31,14 @@ export function Dashboard () { const [actions, setActions] = React.useState(0); const [users, setUsers] = React.useState([]); const [sources, setSources] = React.useState([]); + const [approvals, setApprovals] = React.useState([]); const { get, response, loading } = useFetch(`${API_BASE_PATH}`, { cachePolicy: 'no-cache' }); const sourceLoader = useNonAdminSources(); + const approvalLoader = useUnapprovedSources(); async function loadUsers() { const users = await get('/admin/users') @@ -52,14 +54,22 @@ export function Dashboard () { } } + async function loadApprovals() { + const s = await approvalLoader.get(); + if (response.ok) { + setApprovals(s); + } + } + /*eslint-disable react-hooks/exhaustive-deps*/ React.useEffect(() => { loadSources(); loadUsers(); + loadApprovals(); }, [location]); React.useEffect(() => { - setActions(users.length + sources.length); + setActions(users.length + sources.length + approvals.length); }, [users, sources]); return ( @@ -114,6 +124,8 @@ export function Dashboard () { users={users} reloadSources={loadSources} reloadUsers={loadUsers} + reloadApprovals={loadApprovals} + loadingApprovals={approvalLoader.loading} loadingSources={sourceLoader.loading} loadingUsers={loading} /> diff --git a/ui/src/app/form/component/widgets/MultiSelectWidget.js b/ui/src/app/form/component/widgets/MultiSelectWidget.js index 3cc4678b5..4184df094 100644 --- a/ui/src/app/form/component/widgets/MultiSelectWidget.js +++ b/ui/src/app/form/component/widgets/MultiSelectWidget.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useRef } from "react"; import ListGroup from "react-bootstrap/ListGroup"; import Form from "react-bootstrap/Form"; @@ -24,22 +24,28 @@ const MultiSelectWidget = ({ onBlur, onFocus, autofocus, - options, schema, rawErrors = [], formContext, ...props }) => { // const inputType = (type || schema.type) === 'string' ? 'text' : `${type || schema.type}`; - - const opts = []; + const typeahead = useRef(); - React.useEffect(() => console.log(formContext), [formContext]); - React.useEffect(() => console.log(props), [props]); + const [enums, setEnums] = React.useState(schema.items.enum); + const [enumNames, setEnumNames] = React.useState(schema.items.enumNames); + React.useEffect(() => { + const { items } = schema; + setEnums(items.enum); + setEnumNames(items.enumNames); + }, [schema]); + const [touched, setTouched] = React.useState(false); - const [multiSelections, setMultiSelections] = React.useState([]); + React.useEffect(() => { + + }, [schema]); return ( @@ -51,13 +57,14 @@ const MultiSelectWidget = ({ {schema.description && } enumNames[enums.indexOf(option)] } + onChange={ onChange } + options={enums} + placeholder="Choose approval groups..." + selected={value} /> {rawErrors?.length > 0 && touched && ( diff --git a/ui/src/app/metadata/hooks/api.js b/ui/src/app/metadata/hooks/api.js index a67f9ef5a..04cb14563 100644 --- a/ui/src/app/metadata/hooks/api.js +++ b/ui/src/app/metadata/hooks/api.js @@ -29,6 +29,12 @@ export function useNonAdminSources() { }); } +export function useUnapprovedSources() { + return useFetch(`${API_BASE_PATH}${getMetadataListPath('source')}/needsApproval`, { + cachePolicy: 'no-cache' + }); +} + export function getMetadataListPath(type) { return `/${lists[type]}`; } From c17c531817be478fb599f6375db368b51e6eabe4 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 18 Oct 2022 12:29:35 -0700 Subject: [PATCH 15/56] SHIBUI-2394 Adding User "canApprove" to json for the UI --- .../shibboleth/admin/ui/security/model/User.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 5ca34b4e6..fcf064fe7 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 @@ -150,4 +150,19 @@ public void setGroups(Set groups) { public void registerLoader(ILazyLoaderHelper lazyLoaderHelper) { this.lazyLoaderHelper = lazyLoaderHelper; } + + /** + * @return true if the user belongs to any group that can approve for other groups + */ + public boolean getCanApprove() { + if (Group.ADMIN_GROUP.equals(getGroup())) { + return true; + } + for (Group group : getUserGroups()) { + if (!group.getApproveForList().isEmpty()) { + return true; + } + } + return false; + } } \ No newline at end of file From 7e40fa39107b7f32eb9c710161dae8c3c7411ea9 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 18 Oct 2022 12:34:53 -0700 Subject: [PATCH 16/56] SHIBUI-2394 Adding "approved" to the (fetch all) values (the min values list) for GET all EntityDescriptors --- .../admin/ui/repository/EntityDescriptorProjection.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorProjection.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorProjection.java index 57cf02ab9..757b6f58b 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorProjection.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorProjection.java @@ -16,4 +16,5 @@ default String getEntityId() { LocalDateTime getCreatedDate(); boolean getServiceEnabled(); String getIdOfOwner(); + boolean getApproved(); } \ No newline at end of file From 76209a61986afd1416e8e02ca71b715452da7233 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 18 Oct 2022 13:28:51 -0700 Subject: [PATCH 17/56] Implemented support for additional approvers in UI --- .../main/resources/i18n/messages.properties | 8 ++- ui/src/app/admin/container/ApprovalActions.js | 12 ++-- ui/src/app/core/user/UserContext.js | 11 ++++ ui/src/app/dashboard/component/Scroller.js | 6 +- ui/src/app/dashboard/view/ActionsTab.js | 59 +++++++++-------- ui/src/app/dashboard/view/Dashboard.js | 64 ++++++++++--------- .../component/widgets/MultiSelectWidget.js | 1 + .../domain/source/component/SourceList.js | 22 ++++++- ui/src/app/metadata/hooks/api.js | 6 ++ ui/src/app/metadata/view/MetadataOptions.js | 2 +- 10 files changed, 121 insertions(+), 70 deletions(-) diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index 2c50bc6b8..812ae4383 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -798,4 +798,10 @@ value.algorithm-cbc-192=CBC (192) - http://www.w3.org/2001/04/xmlenc#aes192-cbc value.algorithm-cbc-128=CBC (128) - http://www.w3.org/2001/04/xmlenc#aes128-cbc value.algorithm-cbc-tripledes=CBC (TRIPLEDES) - http://www.w3.org/2001/04/xmlenc#tripledes-cbc -message.algorithms-unique=Each algorithm may only be used once. \ No newline at end of file +message.algorithms-unique=Each algorithm may only be used once. + +label.approve=Approve +label.disapprove=Unapprove +label.approval=Approval +value.approved=Approved +value.disapproved=Not Approved \ No newline at end of file diff --git a/ui/src/app/admin/container/ApprovalActions.js b/ui/src/app/admin/container/ApprovalActions.js index 13e1a605e..2d4119727 100644 --- a/ui/src/app/admin/container/ApprovalActions.js +++ b/ui/src/app/admin/container/ApprovalActions.js @@ -1,24 +1,24 @@ import React from 'react'; import { DeleteConfirmation } from '../../core/components/DeleteConfirmation'; -import { useMetadataActivator, useMetadataEntity } from '../../metadata/hooks/api'; +import { useMetadataApprover, useMetadataEntity } from '../../metadata/hooks/api'; import { NotificationContext, createNotificationAction, NotificationTypes } from '../../notifications/hoc/Notifications'; -export function ApprovalActions ({type, children}) { +export function ApprovalActions ({type = 'source', children}) { const { dispatch } = React.useContext(NotificationContext); - const { del, response } = useMetadataEntity(type, { + const { del, response } = useMetadataEntity('source', { cachePolicy: 'no-cache' }); - const activator = useMetadataActivator(type); + const activator = useMetadataApprover('source'); async function approveEntity(entity, enabled, cb = () => {}) { - await activator.patch(`/${type === 'source' ? entity.id : entity.resourceId}/${enabled ? 'approve' : 'unapprove'}`); + await activator.patch(`${type === 'source' ? entity.id : entity.resourceId}/${enabled ? 'approve' : 'unapprove'}`); if (activator?.response.ok) { dispatch(createNotificationAction( - `Metadata ${type} has been ${enabled ? 'enabled' : 'disabled'}.` + `Metadata ${type} has been ${enabled ? 'approved' : 'unapproved'}.` )); cb(); } else { diff --git a/ui/src/app/core/user/UserContext.js b/ui/src/app/core/user/UserContext.js index ddd1897ef..2390c4d3a 100644 --- a/ui/src/app/core/user/UserContext.js +++ b/ui/src/app/core/user/UserContext.js @@ -75,6 +75,15 @@ function useCanEnable() { return isAdmin || isEnabler; } +function useCanApprove() { + return true; +} + +function useIsApprover() { + const user = useCurrentUser(); + return user.canApprove; +} + function useUserGroup() { const user = useCurrentUser(); return (user?.userGroups && user.userGroups.length > 0) ? user.userGroups[0] : null; @@ -103,7 +112,9 @@ export { useCurrentUser, useIsAdmin, useIsAdminOrInGroup, + useIsApprover, useCanEnable, + useCanApprove, useCurrentUserLoading, useCurrentUserLoader, useUserGroupRegexValidator, diff --git a/ui/src/app/dashboard/component/Scroller.js b/ui/src/app/dashboard/component/Scroller.js index 10baf42cf..ee3626bae 100644 --- a/ui/src/app/dashboard/component/Scroller.js +++ b/ui/src/app/dashboard/component/Scroller.js @@ -13,7 +13,7 @@ export function Scroller ({ entities, children }) { React.useEffect(() => { let maxIndex = (page * PAGE_LIMIT) - 1, minIndex = 0; - const l = entities.filter((resolver, index) => (maxIndex >= index && index >= minIndex)); + const l = entities?.filter((resolver, index) => (maxIndex >= index && index >= minIndex)); setLimited(l); }, [entities, page]) @@ -23,9 +23,9 @@ export function Scroller ({ entities, children }) { return ( loadNext()} - hasMore={entities.length > limited.length} + hasMore={entities?.length > limited?.length} > { children(limited) } diff --git a/ui/src/app/dashboard/view/ActionsTab.js b/ui/src/app/dashboard/view/ActionsTab.js index a642ec001..9a3ace211 100644 --- a/ui/src/app/dashboard/view/ActionsTab.js +++ b/ui/src/app/dashboard/view/ActionsTab.js @@ -1,19 +1,18 @@ import React from 'react'; -import { useParams } from 'react-router'; import { MetadataActions } from '../../admin/container/MetadataActions'; import UserActions from '../../admin/container/UserActions'; import Spinner from '../../core/components/Spinner'; import Translate from '../../i18n/components/translate'; import SourceList from '../../metadata/domain/source/component/SourceList'; - +import { ProtectRoute } from '../../core/components/ProtectRoute'; import Nav from 'react-bootstrap/Nav'; import { Switch, Route, useRouteMatch, Redirect } from 'react-router-dom'; import { NavLink } from 'react-router-dom'; import Badge from 'react-bootstrap/Badge'; import { ApprovalActions } from '../../admin/container/ApprovalActions'; -export function ActionsTab({ sources, users, reloadSources, reloadUsers, reloadApprovals, loadingSources, loadingUsers, loadingApprovals }) { +export function ActionsTab({ sources, users, approvals, reloadSources, reloadUsers, reloadApprovals, loadingSources, loadingUsers, loadingApprovals }) { const { path, url } = useRouteMatch(); @@ -33,54 +32,62 @@ export function ActionsTab({ sources, users, reloadSources, reloadUsers, reloadA activeKey="/home" onSelect={(selectedKey) => alert(`selected ${selectedKey}`)} > + {sources !== null && - + Enable Metadata Sources { sources.length ? {sources.length} : '' } + } - - User Access Request - { users.length ? {users.length} : '' } + + Approve Metadata Sources + {users !== null && - - Approve Metadata Sources + + User Access Request + { users.length ? {users.length} : '' } + }
- + - - - {(enable) => - enable(s, e, reloadSources)}> - {loadingSources &&
} -
- } -
-
- - - {loadingUsers &&
} -
- } /> - + {(approve) => - approve(s, e, reloadApprovals)}> + approve(s, e, reloadApprovals)}> {loadingApprovals &&
}
}
} /> + + + + {(enable) => + enable(s, e, reloadSources)}> + {loadingSources &&
} +
+ } +
+
+
+ + + + {loadingUsers &&
} +
+
+ } />
diff --git a/ui/src/app/dashboard/view/Dashboard.js b/ui/src/app/dashboard/view/Dashboard.js index a4ce21669..76e9c5cbe 100644 --- a/ui/src/app/dashboard/view/Dashboard.js +++ b/ui/src/app/dashboard/view/Dashboard.js @@ -11,7 +11,7 @@ import { SourcesTab } from './SourcesTab'; import { ProvidersTab } from './ProvidersTab'; import { AdminTab } from './AdminTab'; import { ActionsTab } from './ActionsTab'; -import { useCurrentUserLoading, useIsAdmin } from '../../core/user/UserContext'; +import { useCurrentUserLoading, useIsAdmin, useIsApprover } from '../../core/user/UserContext'; import useFetch from 'use-http'; import API_BASE_PATH from '../../App.constant'; import { useNonAdminSources, useUnapprovedSources} from '../../metadata/hooks/api'; @@ -25,12 +25,13 @@ export function Dashboard () { const location = useLocation(); const isAdmin = useIsAdmin(); + const isApprover = useIsApprover(); const loadingUser = useCurrentUserLoading(); const [actions, setActions] = React.useState(0); - const [users, setUsers] = React.useState([]); - const [sources, setSources] = React.useState([]); + const [users, setUsers] = React.useState(null); + const [sources, setSources] = React.useState(null); const [approvals, setApprovals] = React.useState([]); const { get, response, loading } = useFetch(`${API_BASE_PATH}`, { @@ -49,28 +50,30 @@ export function Dashboard () { async function loadSources() { const s = await sourceLoader.get(); - if (response.ok) { + if (sourceLoader.response.ok) { setSources(s); } } async function loadApprovals() { - const s = await approvalLoader.get(); - if (response.ok) { - setApprovals(s); + const a = await approvalLoader.get(); + if (approvalLoader.response.ok) { + setApprovals(a); } } /*eslint-disable react-hooks/exhaustive-deps*/ React.useEffect(() => { - loadSources(); - loadUsers(); + if (isAdmin) { + loadSources(); + loadUsers(); + } loadApprovals(); }, [location]); React.useEffect(() => { - setActions(users.length + sources.length + approvals.length); - }, [users, sources]); + setActions((users?.length || 0) + (sources?.length || 0) + approvals.length); + }, [users, sources, approvals]); return (
@@ -97,14 +100,16 @@ export function Dashboard () { Admin - - - Action Required - {actions} - - } + {isApprover && + + + Action Required + {actions} + + + } @@ -112,24 +117,23 @@ export function Dashboard () { + + + } /> } /> - - - - - } /> diff --git a/ui/src/app/form/component/widgets/MultiSelectWidget.js b/ui/src/app/form/component/widgets/MultiSelectWidget.js index 4184df094..0752fcaf2 100644 --- a/ui/src/app/form/component/widgets/MultiSelectWidget.js +++ b/ui/src/app/form/component/widgets/MultiSelectWidget.js @@ -63,6 +63,7 @@ const MultiSelectWidget = ({ labelKey={ (option) => enumNames[enums.indexOf(option)] } onChange={ onChange } options={enums} + multiple placeholder="Choose approval groups..." selected={value} /> diff --git a/ui/src/app/metadata/domain/source/component/SourceList.js b/ui/src/app/metadata/domain/source/component/SourceList.js index 8e8a10825..6ae64eba9 100644 --- a/ui/src/app/metadata/domain/source/component/SourceList.js +++ b/ui/src/app/metadata/domain/source/component/SourceList.js @@ -16,7 +16,7 @@ import { useTranslator } from '../../../../i18n/hooks'; import { useCanEnable, useIsAdmin } from '../../../../core/user/UserContext'; import { GroupsProvider } from '../../../../admin/hoc/GroupsProvider'; -export default function SourceList({ entities, onDelete, onEnable, onChangeGroup, children }) { +export default function SourceList({ entities, onDelete, onEnable, onApprove, onChangeGroup, children }) { const translator = useTranslator(); const isAdmin = useIsAdmin(); @@ -24,7 +24,7 @@ export default function SourceList({ entities, onDelete, onEnable, onChangeGroup return ( - + {(limited) =>
@@ -35,6 +35,7 @@ export default function SourceList({ entities, onDelete, onEnable, onChangeGroup + {onApprove && } {isAdmin && onChangeGroup && } {onDelete && isAdmin && + {onApprove && + + } {isAdmin && onChangeGroup && + - {onApprove && } {isAdmin && onChangeGroup && } {onDelete && isAdmin && - {onApprove && - } + + {isAdmin && onChangeGroup && + @@ -51,6 +54,7 @@ export function GroupsList({ groups, onDelete }) { +
Author Created Date EnabledApprovalGroup @@ -61,7 +62,7 @@ export default function SourceList({ entities, onDelete, onEnable, onChangeGroup - {onEnable && canEnable ? + {onEnable && (canEnable || source.approved) ? + + onApprove(source, checked)} + checked={source.approved} + > + + + diff --git a/ui/src/app/metadata/hooks/api.js b/ui/src/app/metadata/hooks/api.js index 04cb14563..4caa66092 100644 --- a/ui/src/app/metadata/hooks/api.js +++ b/ui/src/app/metadata/hooks/api.js @@ -145,6 +145,12 @@ export function useMetadataActivator(type, opts = { return useFetch(`${API_BASE_PATH}/activate/${type === 'source' ? 'entityDescriptor' : 'MetadataResolvers'}/`, opts); } +export function useMetadataApprover(type, opts = { + cachePolicy: 'no-cache' +}) { + return useFetch(`${API_BASE_PATH}/approve/${type === 'source' ? 'entityDescriptor' : 'MetadataResolvers'}/`, opts); +} + export function useFilterActivator(providerId, opts = { cachePolicy: 'no-cache' }) { diff --git a/ui/src/app/metadata/view/MetadataOptions.js b/ui/src/app/metadata/view/MetadataOptions.js index 60a075f1f..f9fed4bf8 100644 --- a/ui/src/app/metadata/view/MetadataOptions.js +++ b/ui/src/app/metadata/view/MetadataOptions.js @@ -69,7 +69,7 @@ export function MetadataOptions ({reload}) { model={metadata} showGroup={type === 'source'}>
- {enable && canEnable && + {enable && (canEnable || metadata.approved) &&
- {onEnable && (canEnable || source.approved) ? + {onEnable && (canEnable && source.approved) ?
- {enable && (canEnable || metadata.approved) && + {enable && (canEnable && metadata.approved) &&
Entity ID Author Created DateApproval EnabledApprovalGroup @@ -62,38 +62,44 @@ export default function SourceList({ entities, onDelete, onEnable, onApprove, on - {onEnable && (canEnable && source.approved) ? + {onApprove ? onEnable(source, checked)} - checked={source.serviceEnabled} + aria-label={translator(source.approved ? 'label.disapprove' : 'label.approve')} + onChange={({ target: { checked } }) => onApprove(source, checked)} + checked={source.approved} > : - - + + - } + } + {onEnable && (canEnable && source.approved) ? onApprove(source, checked)} - checked={source.approved} + aria-label={translator(source.serviceEnabled ? 'label.disable' : 'label.enable')} + onChange={({ target: { checked } }) => onEnable(source, checked)} + checked={source.serviceEnabled} > + : + + + + } diff --git a/ui/src/app/metadata/view/MetadataOptions.js b/ui/src/app/metadata/view/MetadataOptions.js index 73b5a4802..5740e93ca 100644 --- a/ui/src/app/metadata/view/MetadataOptions.js +++ b/ui/src/app/metadata/view/MetadataOptions.js @@ -20,7 +20,8 @@ import { MetadataFilterConfigurationList } from '../domain/filter/component/Meta import { MetadataFilterTypes } from '../domain/filter'; import { useMetadataSchema } from '../hooks/schema'; import { FilterableProviders } from '../domain/provider'; -import { useCanEnable, useIsAdmin } from '../../core/user/UserContext'; +import { useCanEnable, useIsAdmin, useIsApprover } from '../../core/user/UserContext'; +import { ApprovalActions } from '../../admin/container/ApprovalActions'; export function MetadataOptions ({reload}) { @@ -54,6 +55,7 @@ export function MetadataOptions ({reload}) { const canEnable = useCanEnable(); const isAdmin = useIsAdmin(); + const canApprove = useIsApprover(); return ( @@ -78,6 +80,19 @@ export function MetadataOptions ({reload}) { } + {canApprove && + + {(approve) => + + } + + } {type === 'source' && remove && isAdmin && Group Description + Approvers + Actions
{group.name} {group.description || ''}{group.approversList?.length > 0 ? group.approversList[0].approverGroupIds.join(', ') : '-'} diff --git a/ui/src/app/metadata/domain/source/component/SourceList.js b/ui/src/app/metadata/domain/source/component/SourceList.js index b8af68e82..5095744f1 100644 --- a/ui/src/app/metadata/domain/source/component/SourceList.js +++ b/ui/src/app/metadata/domain/source/component/SourceList.js @@ -6,8 +6,9 @@ import Button from 'react-bootstrap/Button'; import Form from 'react-bootstrap/Form'; import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; + import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faTrash } from '@fortawesome/free-solid-svg-icons'; +import { faTrash, faCheck } from '@fortawesome/free-solid-svg-icons'; import FormattedDate from '../../../../core/components/FormattedDate'; import Translate from '../../../../i18n/components/translate'; @@ -63,15 +64,15 @@ export default function SourceList({ entities, onDelete, onEnable, onApprove, on {onApprove ? - onApprove(source, checked)} - checked={source.approved} - > - + size="sm" className="" + onClick={() => onApprove(source, !source.approved)}> + + + + + : From b47c71ed2549d0881b5d8d8c54ca80bde3db957e Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 28 Oct 2022 08:22:01 -0700 Subject: [PATCH 25/56] Fixed SHIBUI-2435 - enable button for admins --- ui/src/app/core/user/UserContext.js | 2 +- ui/src/app/metadata/domain/source/component/SourceList.js | 2 +- ui/src/app/metadata/view/MetadataOptions.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/src/app/core/user/UserContext.js b/ui/src/app/core/user/UserContext.js index dc39fc74d..1dff57b24 100644 --- a/ui/src/app/core/user/UserContext.js +++ b/ui/src/app/core/user/UserContext.js @@ -72,7 +72,7 @@ function useIsAdminOrInGroup() { function useCanEnable() { const isAdmin = useIsAdmin(); const isEnabler = useIsEnabler(); - return isAdmin || isEnabler; + return (approved) => isAdmin ? true : (isEnabler && approved); } function useIsApprover() { diff --git a/ui/src/app/metadata/domain/source/component/SourceList.js b/ui/src/app/metadata/domain/source/component/SourceList.js index e80868875..5a57691da 100644 --- a/ui/src/app/metadata/domain/source/component/SourceList.js +++ b/ui/src/app/metadata/domain/source/component/SourceList.js @@ -86,7 +86,7 @@ export default function SourceList({ entities, onDelete, onEnable, onApprove, on - {onEnable && (canEnable && source.approved) ? + {onEnable && canEnable(source.approved) ? Date: Fri, 28 Oct 2022 09:48:33 -0700 Subject: [PATCH 26/56] Fixed issue 2437 --- ui/src/app/admin/container/ApprovalActions.js | 31 ------------------- ui/src/app/admin/container/MetadataActions.js | 26 ++++++++++++++-- ui/src/app/dashboard/view/ActionsTab.js | 17 +++++----- ui/src/app/dashboard/view/Dashboard.js | 2 +- ui/src/app/dashboard/view/SourcesTab.js | 2 +- ui/src/app/metadata/view/MetadataOptions.js | 21 +++++-------- 6 files changed, 44 insertions(+), 55 deletions(-) delete mode 100644 ui/src/app/admin/container/ApprovalActions.js diff --git a/ui/src/app/admin/container/ApprovalActions.js b/ui/src/app/admin/container/ApprovalActions.js deleted file mode 100644 index c4b9ae3ed..000000000 --- a/ui/src/app/admin/container/ApprovalActions.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { useMetadataApprover } from '../../metadata/hooks/api'; - -import { NotificationContext, createNotificationAction, NotificationTypes } from '../../notifications/hoc/Notifications'; - -export function ApprovalActions ({type = 'source', children}) { - - const { dispatch } = React.useContext(NotificationContext); - - const approver = useMetadataApprover('source'); - - async function approveEntity(entity, enabled, cb = () => {}) { - await approver.patch(`${type === 'source' ? entity.id : entity.resourceId}/${enabled ? 'approve' : 'unapprove'}`); - if (approver?.response.ok) { - dispatch(createNotificationAction( - `Metadata ${type} has been ${enabled ? 'approved' : 'unapproved'}.` - )); - cb(); - } else { - const { errorCode, errorMessage, cause } = approver?.response?.data; - dispatch(createNotificationAction( - `${errorCode}: ${errorMessage} ${cause ? `-${cause}` : ''}`, - NotificationTypes.ERROR - )); - } - } - - return ( - <>{children(approveEntity)} - ); -} \ No newline at end of file diff --git a/ui/src/app/admin/container/MetadataActions.js b/ui/src/app/admin/container/MetadataActions.js index 4ed567d23..7fdcd752b 100644 --- a/ui/src/app/admin/container/MetadataActions.js +++ b/ui/src/app/admin/container/MetadataActions.js @@ -1,6 +1,6 @@ import React from 'react'; import { DeleteConfirmation } from '../../core/components/DeleteConfirmation'; -import { useMetadataActivator, useMetadataEntity } from '../../metadata/hooks/api'; +import { useMetadataActivator, useMetadataApprover, useMetadataEntity } from '../../metadata/hooks/api'; import { NotificationContext, createNotificationAction, NotificationTypes } from '../../notifications/hoc/Notifications'; @@ -46,10 +46,32 @@ export function MetadataActions ({type, children}) { } } + const approver = useMetadataApprover('source'); + + async function approveEntity(entity, enabled, cb = () => {}) { + await approver.patch(`${type === 'source' ? entity.id : entity.resourceId}/${enabled ? 'approve' : 'unapprove'}`); + if (approver?.response.ok) { + dispatch(createNotificationAction( + `Metadata ${type} has been ${enabled ? 'approved' : 'unapproved'}.` + )); + cb(); + } else { + const { errorCode, errorMessage, cause } = approver?.response?.data; + dispatch(createNotificationAction( + `${errorCode}: ${errorMessage} ${cause ? `-${cause}` : ''}`, + NotificationTypes.ERROR + )); + } + } + return ( {(block) => - <>{children(enableEntity, (id, cb) => block(() => deleteEntity(id, cb)))} + <>{children({ + enable: enableEntity, + remove: (id, cb) => block(() => deleteEntity(id, cb)), + approve: approveEntity + })} } ); diff --git a/ui/src/app/dashboard/view/ActionsTab.js b/ui/src/app/dashboard/view/ActionsTab.js index 9a3ace211..dcc6a9122 100644 --- a/ui/src/app/dashboard/view/ActionsTab.js +++ b/ui/src/app/dashboard/view/ActionsTab.js @@ -10,7 +10,6 @@ import Nav from 'react-bootstrap/Nav'; import { Switch, Route, useRouteMatch, Redirect } from 'react-router-dom'; import { NavLink } from 'react-router-dom'; import Badge from 'react-bootstrap/Badge'; -import { ApprovalActions } from '../../admin/container/ApprovalActions'; export function ActionsTab({ sources, users, approvals, reloadSources, reloadUsers, reloadApprovals, loadingSources, loadingUsers, loadingApprovals }) { @@ -62,19 +61,23 @@ export function ActionsTab({ sources, users, approvals, reloadSources, reloadUse - - {(approve) => - approve(s, e, reloadApprovals)}> + + {({approve, remove}) => + remove(id, reloadApprovals)} + onApprove={(s, e) => approve(s, e, reloadApprovals)}> {loadingApprovals &&
}
} -
+ } /> - {(enable) => - enable(s, e, reloadSources)}> + {({enable, remove}) => + remove(id, reloadSources)} + onEnable={(s, e) => enable(s, e, reloadSources)}> {loadingSources &&
}
} diff --git a/ui/src/app/dashboard/view/Dashboard.js b/ui/src/app/dashboard/view/Dashboard.js index 76e9c5cbe..496ad2da9 100644 --- a/ui/src/app/dashboard/view/Dashboard.js +++ b/ui/src/app/dashboard/view/Dashboard.js @@ -69,7 +69,7 @@ export function Dashboard () { loadUsers(); } loadApprovals(); - }, [location]); + }, [location, isAdmin]); React.useEffect(() => { setActions((users?.length || 0) + (sources?.length || 0) + approvals.length); diff --git a/ui/src/app/dashboard/view/SourcesTab.js b/ui/src/app/dashboard/view/SourcesTab.js index dbeeb0a06..20e9adb19 100644 --- a/ui/src/app/dashboard/view/SourcesTab.js +++ b/ui/src/app/dashboard/view/SourcesTab.js @@ -65,7 +65,7 @@ export function SourcesTab () { {(searched) => - {(enable, remove) => + {({enable, remove}) => remove(id, loadSources)} diff --git a/ui/src/app/metadata/view/MetadataOptions.js b/ui/src/app/metadata/view/MetadataOptions.js index 28f2b3d1e..b59a48946 100644 --- a/ui/src/app/metadata/view/MetadataOptions.js +++ b/ui/src/app/metadata/view/MetadataOptions.js @@ -21,7 +21,6 @@ import { MetadataFilterTypes } from '../domain/filter'; import { useMetadataSchema } from '../hooks/schema'; import { FilterableProviders } from '../domain/provider'; import { useCanEnable, useIsAdmin, useIsApprover } from '../../core/user/UserContext'; -import { ApprovalActions } from '../../admin/container/ApprovalActions'; export function MetadataOptions ({reload}) { @@ -59,7 +58,7 @@ export function MetadataOptions ({reload}) { return ( - {(enable, remove) => + {({enable, remove, approve}) => <>
{onApprove ? - : From ef7eb92f78ed8ab6f1ee5b91a0f23645229d44db Mon Sep 17 00:00:00 2001 From: chasegawa Date: Fri, 28 Oct 2022 15:03:53 -0700 Subject: [PATCH 32/56] SHIBUI-2394 Changes for 2440 odd behavior in fetch lists for action required screens --- .../admin/ui/service/JPAEntityDescriptorServiceImpl.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 b40c934cd..b57e517dd 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 @@ -181,9 +181,11 @@ public EntityDescriptorRepresentation changeApproveStatusOfEntityDescriptor(Stri if (status) { // approve int approvedCount = ed.approvedCount(); List approversList = groupService.find(ed.getIdOfOwner()).getApproversList(); - if (!approversList.isEmpty() && approversList.size() > approvedCount) { - Approvers approvers = approversList.get( - approvedCount); // yea for index zero - use the count to get the next approvers + if (approversList.isEmpty() && userService.currentUserIsAdmin()){ + ed.setApproved(true); + ed = entityDescriptorRepository.save(ed); + } else if (!approversList.isEmpty() && approversList.size() > approvedCount) { + Approvers approvers = approversList.get(approvedCount); // yea for index zero - use the count to get the next approvers if (!userService.currentUserCanApprove(approvers.getApproverGroups())) { throw new ForbiddenException("You do not have the permissions necessary to approve this entity descriptor."); } From c203f51cf9f5d6e3e89110fa06489aaffd121e52 Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Mon, 31 Oct 2022 12:10:12 -0400 Subject: [PATCH 33/56] Wip of the authorization API --- .../service/IPersistentEntityTupple.java | 14 ++++++++++++ .../service/IShibUiPermissionEvaluator.java | 22 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IPersistentEntityTupple.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IShibUiPermissionEvaluator.java diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IPersistentEntityTupple.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IPersistentEntityTupple.java new file mode 100644 index 000000000..c79c7b513 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IPersistentEntityTupple.java @@ -0,0 +1,14 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.service; + +import java.io.Serializable; + +/** + * Will be used as a key for PersmissionEvaluator return types + */ +public interface IPersistentEntityTupple extends Serializable { + + String getId(); + + Class getType(); + +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IShibUiPermissionEvaluator.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IShibUiPermissionEvaluator.java new file mode 100644 index 000000000..2482f34fd --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IShibUiPermissionEvaluator.java @@ -0,0 +1,22 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.service; + +import org.springframework.security.access.PermissionEvaluator; +import org.springframework.security.core.Authentication; + +import java.util.Collection; +import java.util.Map; + +public interface IShibUiPermissionEvaluator extends PermissionEvaluator { + + Collection getPersistentEntitiesWithPermission(Authentication authentication, Object permission); + + /** + * Get ALL persistent entities that user has access to + * @param authentication + * @return + */ + Map getPersistentEntities(Authentication authentication); + + Map getPersistentEntities(Authentication authentication, Class clazz); + +} From 3902884298595c5fff1ef5b1089826dd60559fff Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Mon, 31 Oct 2022 12:12:29 -0400 Subject: [PATCH 34/56] Typo rename --- ...sistentEntityTupple.java => IPersistentEntityTuple.java} | 2 +- .../ui/security/service/IShibUiPermissionEvaluator.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/{IPersistentEntityTupple.java => IPersistentEntityTuple.java} (76%) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IPersistentEntityTupple.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IPersistentEntityTuple.java similarity index 76% rename from backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IPersistentEntityTupple.java rename to backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IPersistentEntityTuple.java index c79c7b513..7bc796793 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IPersistentEntityTupple.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IPersistentEntityTuple.java @@ -5,7 +5,7 @@ /** * Will be used as a key for PersmissionEvaluator return types */ -public interface IPersistentEntityTupple extends Serializable { +public interface IPersistentEntityTuple extends Serializable { String getId(); diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IShibUiPermissionEvaluator.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IShibUiPermissionEvaluator.java index 2482f34fd..0f4a144bf 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IShibUiPermissionEvaluator.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IShibUiPermissionEvaluator.java @@ -15,8 +15,8 @@ public interface IShibUiPermissionEvaluator extends PermissionEvaluator { * @param authentication * @return */ - Map getPersistentEntities(Authentication authentication); + Map getPersistentEntities(Authentication authentication); + + Map getPersistentEntities(Authentication authentication, Class clazz); - Map getPersistentEntities(Authentication authentication, Class clazz); - } From a603d19b65cb4bf9fc011a40f30dfdb20b96ff92 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Mon, 31 Oct 2022 12:40:30 -0700 Subject: [PATCH 35/56] SHIBUI-2394 Changes for abstracting permission stuff --- .../permission/IPersistentEntityTuple.java | 22 +++++++++++++++++ .../IShibUiPermissionEvaluator.java | 24 +++++++++++++++++++ .../security/permission/PermissionType.java | 5 ++++ .../ui/security/permission/ShibUiService.java | 4 ++++ .../ui/security/permission/ShibUiType.java | 5 ++++ .../service/IPersistentEntityTuple.java | 14 ----------- .../service/IShibUiPermissionEvaluator.java | 22 ----------------- .../ui/security/service/UserService.java | 5 ++++ 8 files changed, 65 insertions(+), 36 deletions(-) create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IPersistentEntityTuple.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/PermissionType.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiService.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiType.java delete mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IPersistentEntityTuple.java delete mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IShibUiPermissionEvaluator.java diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IPersistentEntityTuple.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IPersistentEntityTuple.java new file mode 100644 index 000000000..d8ed1b4f4 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IPersistentEntityTuple.java @@ -0,0 +1,22 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.permission; + +import java.io.Serializable; + +/** + * Will be used as a key for PersmissionEvaluator return types + */ +public interface IPersistentEntityTuple extends Serializable { + /** + * Returns the database id of the database-entity. The id may originally be string, int, long, etc - it will be up to implementing + * code to correctly hand the id based on the type of entity when using the id to fetch. + * @return String the id of the entity. + */ + String getId(); + + /** + * The persistant entity type associated with the id + * @return the class of the database entity that the id is associated with + */ + Class getType(); + +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java new file mode 100644 index 000000000..6d3bb1944 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java @@ -0,0 +1,24 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.permission; + +import org.springframework.security.access.PermissionEvaluator; +import org.springframework.security.core.Authentication; + +import java.util.Collection; +import java.util.Map; + +public interface IShibUiPermissionEvaluator extends PermissionEvaluator { +// +// /** +// * For a given permission, find all the persistant entities a user has rights to. +// */ +// Collection getPersistentEntitiesWithPermission(Authentication authentication, Object permission); +// +// /** +// * Get ALL persistent entities that user has access to +// * @param authentication +// * @return a map. The key value will be the entity tuple and the value portions will be the set of permissions a user has on those objects +// */ +// Map getPersistentEntities(Authentication authentication); + + Collection getPersistentEntities(Authentication authentication, ShibUiType type, PermissionType permissionType); +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/PermissionType.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/PermissionType.java new file mode 100644 index 000000000..a0bf59af2 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/PermissionType.java @@ -0,0 +1,5 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.permission; + +public enum PermissionType { + admin, enable, approver, user; +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiService.java new file mode 100644 index 000000000..9a8271402 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiService.java @@ -0,0 +1,4 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.permission; + +public class ShibUiService { +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiType.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiType.java new file mode 100644 index 000000000..250f54eb3 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiType.java @@ -0,0 +1,5 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.permission; + +public enum ShibUiType { + approvable, entityDescriptor +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IPersistentEntityTuple.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IPersistentEntityTuple.java deleted file mode 100644 index 7bc796793..000000000 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IPersistentEntityTuple.java +++ /dev/null @@ -1,14 +0,0 @@ -package edu.internet2.tier.shibboleth.admin.ui.security.service; - -import java.io.Serializable; - -/** - * Will be used as a key for PersmissionEvaluator return types - */ -public interface IPersistentEntityTuple extends Serializable { - - String getId(); - - Class getType(); - -} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IShibUiPermissionEvaluator.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IShibUiPermissionEvaluator.java deleted file mode 100644 index 0f4a144bf..000000000 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/IShibUiPermissionEvaluator.java +++ /dev/null @@ -1,22 +0,0 @@ -package edu.internet2.tier.shibboleth.admin.ui.security.service; - -import org.springframework.security.access.PermissionEvaluator; -import org.springframework.security.core.Authentication; - -import java.util.Collection; -import java.util.Map; - -public interface IShibUiPermissionEvaluator extends PermissionEvaluator { - - Collection getPersistentEntitiesWithPermission(Authentication authentication, Object permission); - - /** - * Get ALL persistent entities that user has access to - * @param authentication - * @return - */ - Map getPersistentEntities(Authentication authentication); - - Map getPersistentEntities(Authentication authentication, Class clazz); - -} 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 44de0f9d6..684be9009 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 @@ -18,6 +18,7 @@ import lombok.NoArgsConstructor; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -108,6 +109,10 @@ public Optional findByUsername(String username) { return userRepository.findByUsername(username); } + public Authentication getCurrentUserAuthentication() { + return SecurityContextHolder.getContext().getAuthentication(); + } + public User getCurrentUser() { //TODO: Consider returning an Optional here User user = null; From 8dcfb122984854758e2c8f8f299d16aba321c95a Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 31 Oct 2022 13:19:31 -0700 Subject: [PATCH 36/56] Fixed issue with organization details --- ui/src/app/form/component/widgets/TextWidget.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/src/app/form/component/widgets/TextWidget.js b/ui/src/app/form/component/widgets/TextWidget.js index 2f8c183b0..2e2db043f 100644 --- a/ui/src/app/form/component/widgets/TextWidget.js +++ b/ui/src/app/form/component/widgets/TextWidget.js @@ -41,8 +41,10 @@ const TextWidget = ({ }; React.useEffect(() => { - onChange(fieldValue); - }, [fieldValue, onChange]); + if (fieldValue || touched) { + onChange(fieldValue); + } + }, [fieldValue, onChange, touched]); const translator = useTranslator(); From 61895fe6135031c88cb4da588fd915e175893abc Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 1 Nov 2022 16:14:26 -0700 Subject: [PATCH 37/56] SHIBUI-2394 Moving logic where auth checks are being done out of EntityDescriptorService implementations to allow for plugable implementations to make the determinations --- .../JPAMetadataResolverServiceImpl.groovy | 13 ++- .../CoreShibUiConfiguration.java | 8 ++ .../ui/controller/ActivateController.java | 7 +- .../EntityDescriptorController.java | 12 ++- .../admin/ui/domain/EntityDescriptor.java | 2 +- .../admin/ui/domain/IApprovable.java | 5 + .../permission/IPersistentEntityTuple.java | 22 ----- .../IShibUiPermissionEvaluator.java | 26 +++-- .../security/permission/PermissionType.java | 2 +- .../permission/ShibUiPermissibleType.java | 5 + .../permission/ShibUiPermissionDelegate.java | 87 +++++++++++++++++ .../ui/security/permission/ShibUiService.java | 4 - .../ui/security/permission/ShibUiType.java | 5 - .../ui/service/EntityDescriptorService.java | 4 +- .../EnversEntityDescriptorVersionService.java | 3 +- .../JPAEntityDescriptorServiceImpl.java | 95 ++++++++----------- .../ui/service/JPAFilterServiceImpl.java | 7 +- .../ui/service/MetadataResolverService.java | 2 +- .../ui/BaseDataJpaTestConfiguration.groovy | 9 ++ .../MetadataFiltersControllerTests.groovy | 2 +- ui/src/app/metadata/hooks/api.js | 4 +- ui/src/app/metadata/hooks/api.test.js | 6 +- 22 files changed, 204 insertions(+), 126 deletions(-) create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IApprovable.java delete mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IPersistentEntityTuple.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissibleType.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java delete mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiService.java delete mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiType.java diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImpl.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImpl.groovy index 0ca482f4c..146fa38d6 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImpl.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImpl.groovy @@ -31,6 +31,8 @@ import edu.internet2.tier.shibboleth.admin.ui.exception.InitializationException import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository +import edu.internet2.tier.shibboleth.admin.ui.security.permission.IShibUiPermissionEvaluator +import edu.internet2.tier.shibboleth.admin.ui.security.permission.PermissionType import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService import edu.internet2.tier.shibboleth.admin.util.OpenSamlChainingMetadataResolverUtil import groovy.util.logging.Slf4j @@ -79,6 +81,9 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService { @Autowired private ShibUIConfiguration shibUIConfiguration + @Autowired + private IShibUiPermissionEvaluator shibUiService; + @Autowired private UserService userService @@ -733,11 +738,13 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService { } } - public edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver updateMetadataResolverEnabledStatus(edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver updatedResolver) throws ForbiddenException, MetadataFileNotFoundException, InitializationException { - if (!userService.currentUserCanEnable(updatedResolver)) { - throw new ForbiddenException("You do not have the permissions necessary to change the enable status of this filter.") + public edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver updateMetadataResolverEnabledStatus(String resourceId, boolean status) throws ForbiddenException, MetadataFileNotFoundException, InitializationException { + edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver updatedResolver = findByResourceId(resourceId); + if (!shibUiService.hasPermission(userService.getCurrentUserAuthentication(), updatedResolver, PermissionType.enable)) { + throw new ForbiddenException("You do not have the permissions necessary to change the enable status of this resolver.") } + updatedResolver.setEnabled(status); edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver persistedResolver = metadataResolverRepository.save(updatedResolver) if (persistedResolver.getDoInitialization()) { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java index 5756babce..607615adc 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java @@ -10,6 +10,8 @@ import edu.internet2.tier.shibboleth.admin.ui.scheduled.MetadataProvidersScheduledTasks; import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.GroupUpdatedEntityListener; import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.UserUpdatedEntityListener; +import edu.internet2.tier.shibboleth.admin.ui.security.permission.IShibUiPermissionEvaluator; +import edu.internet2.tier.shibboleth.admin.ui.security.permission.ShibUiPermissionDelegate; import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository; @@ -230,4 +232,10 @@ public UserUpdatedEntityListener userUpdatedEntityListener(OwnershipRepository r listener.init(repo, groupRepo); return listener; } + + @Bean + public IShibUiPermissionEvaluator shibUiPermissionEvaluator(EntityDescriptorRepository entityDescriptorRepository, UserService userService) { + // TODO: @jj define type to return for Grouper integration + return new ShibUiPermissionDelegate(entityDescriptorRepository, userService); + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java index 763113303..49c9e0d90 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java @@ -56,10 +56,7 @@ public ResponseEntity enableFilter(@PathVariable String metadataResolverId, @ @Transactional public ResponseEntity enableProvider(@PathVariable String resourceId, @PathVariable String mode) throws PersistentEntityNotFound, ForbiddenException, MetadataFileNotFoundException, InitializationException { boolean status = "enable".equalsIgnoreCase(mode); - MetadataResolver existingResolver = metadataResolverService.findByResourceId(resourceId); - existingResolver.setEnabled(status); - existingResolver = metadataResolverService.updateMetadataResolverEnabledStatus(existingResolver); - - return ResponseEntity.ok(existingResolver); + MetadataResolver metadataResolver = metadataResolverService.updateMetadataResolverEnabledStatus(resourceId, status); + return ResponseEntity.ok(metadataResolver); } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java index 4606282c8..e8498a6c8 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java @@ -99,11 +99,14 @@ public ResponseEntity getAllVersions(@PathVariable String resourceId) throws return ResponseEntity.ok(versionService.findVersionsForEntityDescriptor(ed.getResourceId())); } + /** + * @throws ForbiddenException This call is used for the admin needs action list, therefore the user must be an admin + */ @Secured("ROLE_ADMIN") @Transactional - @GetMapping(value = "/EntityDescriptor/disabledNonAdmin") - public ResponseEntity getDisabledAndNotOwnedByAdmin() throws ForbiddenException { - return ResponseEntity.ok(entityDescriptorService.getAllDisabledAndNotOwnedByAdmin()); + @GetMapping(value = "/EntityDescriptor/disabledSources") + public ResponseEntity getDisabledMetadataSources() throws ForbiddenException { + return ResponseEntity.ok(entityDescriptorService.getDisabledMetadataSources()); } @GetMapping("/EntityDescriptor/{resourceId}") @@ -121,8 +124,7 @@ public ResponseEntity getOneXml(@PathVariable String resourceId) throws Marsh } @GetMapping("/EntityDescriptor/{resourceId}/Versions/{versionId}") - public ResponseEntity getSpecificVersion(@PathVariable String resourceId, @PathVariable String versionId) throws - PersistentEntityNotFound, ForbiddenException { + public ResponseEntity getSpecificVersion(@PathVariable String resourceId, @PathVariable String versionId) throws PersistentEntityNotFound, ForbiddenException { // this "get by resource id" verifies that both the ED exists and the user has proper access, so needs to remain EntityDescriptor ed = entityDescriptorService.getEntityDescriptorByResourceId(resourceId); EntityDescriptorRepresentation result = versionService.findSpecificVersionOfEntityDescriptor(ed.getResourceId(), versionId); 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 18d1b92ac..78d2b98fd 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 @@ -37,7 +37,7 @@ @Entity @EqualsAndHashCode(callSuper = true) @Audited -public class EntityDescriptor extends AbstractDescriptor implements org.opensaml.saml.saml2.metadata.EntityDescriptor, Ownable, IActivatable { +public class EntityDescriptor extends AbstractDescriptor implements org.opensaml.saml.saml2.metadata.EntityDescriptor, Ownable, IActivatable, IApprovable { @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "entitydesc_addlmetdatlocations_id") @OrderColumn diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IApprovable.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IApprovable.java new file mode 100644 index 000000000..f2e163b0b --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IApprovable.java @@ -0,0 +1,5 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain; + +public interface IApprovable { + String getIdOfOwner(); +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IPersistentEntityTuple.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IPersistentEntityTuple.java deleted file mode 100644 index d8ed1b4f4..000000000 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IPersistentEntityTuple.java +++ /dev/null @@ -1,22 +0,0 @@ -package edu.internet2.tier.shibboleth.admin.ui.security.permission; - -import java.io.Serializable; - -/** - * Will be used as a key for PersmissionEvaluator return types - */ -public interface IPersistentEntityTuple extends Serializable { - /** - * Returns the database id of the database-entity. The id may originally be string, int, long, etc - it will be up to implementing - * code to correctly hand the id based on the type of entity when using the id to fetch. - * @return String the id of the entity. - */ - String getId(); - - /** - * The persistant entity type associated with the id - * @return the class of the database entity that the id is associated with - */ - Class getType(); - -} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java index 6d3bb1944..989132216 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java @@ -1,24 +1,22 @@ package edu.internet2.tier.shibboleth.admin.ui.security.permission; +import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.core.Authentication; import java.util.Collection; -import java.util.Map; public interface IShibUiPermissionEvaluator extends PermissionEvaluator { -// -// /** -// * For a given permission, find all the persistant entities a user has rights to. -// */ -// Collection getPersistentEntitiesWithPermission(Authentication authentication, Object permission); -// -// /** -// * Get ALL persistent entities that user has access to -// * @param authentication -// * @return a map. The key value will be the entity tuple and the value portions will be the set of permissions a user has on those objects -// */ -// Map getPersistentEntities(Authentication authentication); - Collection getPersistentEntities(Authentication authentication, ShibUiType type, PermissionType permissionType); + /** + * Return a Collection of items matching the type describing those types that can be asked for and for which the authenticated + * user has the correct permission to access + * @param authentication The security Authorization + * @param type The permissible type that should be returned in the collection. This is an abstraction + * @param permissionType The type of permissions the user should have to access the items returned in the collection. Determining + * the relationship is up to the implementation + * @return Collection of objects representing the type described by the ShibUiPermissibleType enumeration + * @throws ForbiddenException if the user does not have the correct authority required + */ + Collection getPersistentEntities(Authentication authentication, ShibUiPermissibleType type, PermissionType permissionType) throws ForbiddenException; } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/PermissionType.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/PermissionType.java index a0bf59af2..921462ab7 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/PermissionType.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/PermissionType.java @@ -1,5 +1,5 @@ package edu.internet2.tier.shibboleth.admin.ui.security.permission; public enum PermissionType { - admin, enable, approver, user; + admin, approver, enable, fetch, viewOrEdit; } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissibleType.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissibleType.java new file mode 100644 index 000000000..5db069c5c --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissibleType.java @@ -0,0 +1,5 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.permission; + +public enum ShibUiPermissibleType { + entityDescriptorProjection // represents EntityDescriptorProjections +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java new file mode 100644 index 000000000..0f54f72d2 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java @@ -0,0 +1,87 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.permission; + +import edu.internet2.tier.shibboleth.admin.ui.domain.IActivatable; +import edu.internet2.tier.shibboleth.admin.ui.domain.IApprovable; +import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; +import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorProjection; +import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownable; +import edu.internet2.tier.shibboleth.admin.ui.security.service.UserAccess; +import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; +import lombok.AllArgsConstructor; +import org.springframework.security.core.Authentication; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +/** + * The ShibUiPermissionDelegate is the default service for SHIBUI, which delegates calls (primarily) to the the userService to determine + * whether a user has the correct abilty to act a particular way (possibly on certain objects). + */ +@AllArgsConstructor +public class ShibUiPermissionDelegate implements IShibUiPermissionEvaluator { + private EntityDescriptorRepository entityDescriptorRepository; + + private UserService userService; + + @Override + public Collection getPersistentEntities(Authentication authentication, ShibUiPermissibleType shibUiType, PermissionType permissionType) throws ForbiddenException { + switch (shibUiType) { + case entityDescriptorProjection: + switch (permissionType) { + case approver: + return getAllEntityDescriptorProjectionsNeedingApprovalBasedOnUserAccess(); + case enable: + // This particular list is used for an admin function, so the user must be an ADMIN + if (!hasPermission(authentication, null, PermissionType.admin)) { + throw new ForbiddenException(); + } + return entityDescriptorRepository.getEntityDescriptorsNeedingEnabling(); + case fetch: + if (!hasPermission(authentication, null, PermissionType.fetch)) { + throw new ForbiddenException("User has no access rights to get a list of Metadata Sources"); + } + return getAllEntityDescriptorProjectionsBasedOnUserAccess(); + } + } + return null; + } + + private List getAllEntityDescriptorProjectionsBasedOnUserAccess() { + if (userService.currentUserIsAdmin()) { + return entityDescriptorRepository.findAllReturnProjections(); + } else { + return entityDescriptorRepository.findAllByIdOfOwner(userService.getCurrentUser().getGroup().getOwnerId()); + } + } + + private List getAllEntityDescriptorProjectionsNeedingApprovalBasedOnUserAccess() { + List groupsToApprove = userService.getGroupsCurrentUserCanApprove(); + List result = entityDescriptorRepository.getEntityDescriptorsNeedingApproval(groupsToApprove); + return result; + } + + @Override + public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { + switch ((PermissionType) permission) { + case admin: // we don't care about the object - the user is an admin or not + return userService.currentUserIsAdmin(); + case approver: + if (userService.currentUserIsAdmin()) { return true; } + return targetDomainObject instanceof IApprovable ? userService.getGroupsCurrentUserCanApprove().contains(((IApprovable)targetDomainObject).getIdOfOwner()) : false; + case enable: + return targetDomainObject instanceof IActivatable ? userService.currentUserCanEnable((IActivatable) targetDomainObject) : false; + case fetch: + return userService.currentUserIsAdmin() || userService.getCurrentUserAccess().equals(UserAccess.GROUP); + case viewOrEdit: + return userService.canViewOrEditTarget((Ownable) targetDomainObject); + default: return false; + } + } + + @Override + public boolean hasPermission(Authentication authentication, Serializable targetId, String target, Object permission) { + return false; // Unused and Unimplemented - we don't need for this implementation to lookup objects + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiService.java deleted file mode 100644 index 9a8271402..000000000 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiService.java +++ /dev/null @@ -1,4 +0,0 @@ -package edu.internet2.tier.shibboleth.admin.ui.security.permission; - -public class ShibUiService { -} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiType.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiType.java deleted file mode 100644 index 250f54eb3..000000000 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiType.java +++ /dev/null @@ -1,5 +0,0 @@ -package edu.internet2.tier.shibboleth.admin.ui.security.permission; - -public enum ShibUiType { - approvable, entityDescriptor -} \ No newline at end of file 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 d31b57112..bd09a1901 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 @@ -65,7 +65,7 @@ EntityDescriptorRepresentation createNew(EntityDescriptorRepresentation edRepres * "admin" * @throws ForbiddenException - If user is not an ADMIN */ - Iterable getAllDisabledAndNotOwnedByAdmin() throws ForbiddenException; + Iterable getDisabledMetadataSources() throws ForbiddenException; /** * @return a list of EntityDescriptorProjections that a user has the rights to access @@ -125,5 +125,5 @@ EntityDescriptorRepresentation updateEntityDescriptorEnabledStatus(String resour EntityDescriptorRepresentation changeApproveStatusOfEntityDescriptor(String resourceId, boolean status) throws PersistentEntityNotFound, ForbiddenException; - List getAllEntityDescriptorProjectionsNeedingApprovalBasedOnUserAccess(); + List getAllEntityDescriptorProjectionsNeedingApprovalBasedOnUserAccess() throws ForbiddenException; } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EnversEntityDescriptorVersionService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EnversEntityDescriptorVersionService.java index 5857ac283..398517a51 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EnversEntityDescriptorVersionService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/EnversEntityDescriptorVersionService.java @@ -32,8 +32,7 @@ public List findVersionsForEntityDescriptor(String resourceId) throws P } @Override - public EntityDescriptorRepresentation findSpecificVersionOfEntityDescriptor(String resourceId, String versionId) throws - PersistentEntityNotFound { + public EntityDescriptorRepresentation findSpecificVersionOfEntityDescriptor(String resourceId, String versionId) throws PersistentEntityNotFound { Object edObject = enversVersionServiceSupport.findSpecificVersionOfPersistentEntity(resourceId, versionId, EntityDescriptor.class); if (edObject == null) { throw new PersistentEntityNotFound("Unable to find specific version requested - version: " + versionId); 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 b57e517dd..5d4ad3433 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 @@ -33,7 +33,9 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.Owner; import edu.internet2.tier.shibboleth.admin.ui.security.model.OwnerType; import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership; -import edu.internet2.tier.shibboleth.admin.ui.security.model.User; +import edu.internet2.tier.shibboleth.admin.ui.security.permission.IShibUiPermissionEvaluator; +import edu.internet2.tier.shibboleth.admin.ui.security.permission.PermissionType; +import edu.internet2.tier.shibboleth.admin.ui.security.permission.ShibUiPermissibleType; import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository; import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService; import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; @@ -82,6 +84,9 @@ public class JPAEntityDescriptorServiceImpl implements EntityDescriptorService { @Autowired private OwnershipRepository ownershipRepository; + @Autowired + private IShibUiPermissionEvaluator shibUiService; + @Autowired private UserService userService; @@ -178,26 +183,21 @@ public EntityDescriptorRepresentation changeApproveStatusOfEntityDescriptor(Stri if (ed == null) { throw new PersistentEntityNotFound("Entity with resourceid[" + resourceId + "] was not found for approval"); } + if (!shibUiService.hasPermission(userService.getCurrentUserAuthentication(), ed, PermissionType.approver)) { + throw new ForbiddenException("You do not have the permissions necessary to approve this entity descriptor."); + } if (status) { // approve - int approvedCount = ed.approvedCount(); - List approversList = groupService.find(ed.getIdOfOwner()).getApproversList(); - if (approversList.isEmpty() && userService.currentUserIsAdmin()){ - ed.setApproved(true); - ed = entityDescriptorRepository.save(ed); - } else if (!approversList.isEmpty() && approversList.size() > approvedCount) { - Approvers approvers = approversList.get(approvedCount); // yea for index zero - use the count to get the next approvers - if (!userService.currentUserCanApprove(approvers.getApproverGroups())) { - throw new ForbiddenException("You do not have the permissions necessary to approve this entity descriptor."); - } + int approvedCount = ed.approvedCount(); // total number of approvals so far + List theApprovers = groupService.find(ed.getIdOfOwner()).getApproversList(); + if (theApprovers.size() > approvedCount) { // don't add if we already have enough approvals ed.addApproval(userService.getCurrentUserGroup()); - Group ownerGroup = groupService.find(ed.getIdOfOwner()); - ed.setApproved(ed.approvedCount() == ownerGroup.getApproversList().size()); // safe check in case of weird race conditions from the UI - ed = entityDescriptorRepository.save(ed); } + ed.setApproved(ed.approvedCount() >= theApprovers.size()); // future check for multiple approvals needed + ed = entityDescriptorRepository.save(ed); } else { // un-approve ed.removeLastApproval(); Group ownerGroup = groupService.find(ed.getIdOfOwner()); - ed.setApproved(ed.approvedCount() == ownerGroup.getApproversList().size()); // safe check in case of weird race conditions from the UI + ed.setApproved(ed.approvedCount() >= ownerGroup.getApproversList().size()); // safe check in case of weird race conditions from the UI ed = entityDescriptorRepository.save(ed); } return createRepresentationFromDescriptor(ed); @@ -216,12 +216,13 @@ public EntityDescriptorRepresentation createNew(EntityDescriptor ed) throws Forb @Override public EntityDescriptorRepresentation createNew(EntityDescriptorRepresentation edRep) throws ForbiddenException, ObjectIdExistsException, InvalidPatternMatchException { - if (edRep.isServiceEnabled() && !userService.currentUserIsAdmin()) { - throw new ForbiddenException("You do not have the permissions necessary to enable this service."); + if (entityExists(edRep.getEntityId())) { + throw new ObjectIdExistsException(edRep.getEntityId()); } - if (entityDescriptorRepository.findByEntityID(edRep.getEntityId()) != null) { - throw new ObjectIdExistsException(edRep.getEntityId()); + EntityDescriptor ed = (EntityDescriptor) createDescriptorFromRepresentation(edRep); + if (ed.isServiceEnabled() && !shibUiService.hasPermission(userService.getCurrentUserAuthentication(), ed, PermissionType.enable)) { + throw new ForbiddenException("You do not have the permissions necessary to enable this entity descriptor."); } // "Create new" will use the current user's group as the owner @@ -229,9 +230,8 @@ public EntityDescriptorRepresentation createNew(EntityDescriptorRepresentation e edRep.setIdOfOwner(ownerId); validateEntityIdAndACSUrls(edRep); - EntityDescriptor ed = (EntityDescriptor) createDescriptorFromRepresentation(edRep); ed.setIdOfOwner(userService.getCurrentUserGroup().getOwnerId()); - if (userService.currentUserIsAdmin()) { + if (shibUiService.hasPermission(userService.getCurrentUserAuthentication(), null, PermissionType.admin)) { ed.setApproved(true); } @@ -250,7 +250,7 @@ public EntityDescriptorRepresentation createNewEntityDescriptorFromXMLOrigin(Ent if (ed.getProtocol() == EntityDescriptorProtocol.OIDC) { ed.getSPSSODescriptor("").addSupportedProtocol("http://openid.net/specs/openid-connect-core-1_0.html"); } - if (userService.currentUserIsAdmin()) { + if (shibUiService.hasPermission(userService.getCurrentUserAuthentication(), null, PermissionType.admin)) { ed.setApproved(true); } EntityDescriptor savedEntity = entityDescriptorRepository.save(ed); @@ -486,14 +486,6 @@ public boolean entityExists(String entityID) { return entityDescriptorRepository.findByEntityID(entityID) != null ; } - @Override - public Iterable getAllDisabledAndNotOwnedByAdmin() throws ForbiddenException { - if (!userService.currentUserIsAdmin()) { - throw new ForbiddenException(); - } - return entityDescriptorRepository.getEntityDescriptorsNeedingEnabling(); - } - /** * Get the "short" detail list of entity descriptors that match the current user's group. The intent is the list will be those * EDs that the user would see on the dashboard. @@ -501,28 +493,15 @@ public Iterable getAllDisabledAndNotOwnedByAdmin() t */ @Override public List getAllEntityDescriptorProjectionsBasedOnUserAccess() throws ForbiddenException { - switch (userService.getCurrentUserAccess()) { - case ADMIN: - List o = entityDescriptorRepository.findAllReturnProjections(); - return o; - case GROUP: - User user = userService.getCurrentUser(); - Group group = user.getGroup(); - List ed = entityDescriptorRepository.findAllByIdOfOwner(group.getOwnerId()); - return ed; - default: - throw new ForbiddenException(); - } + return (List) shibUiService.getPersistentEntities(userService.getCurrentUserAuthentication(), ShibUiPermissibleType.entityDescriptorProjection, PermissionType.fetch); } /** * Based on the current users group, find those entities that the user can approve that need approval */ @Override - public List getAllEntityDescriptorProjectionsNeedingApprovalBasedOnUserAccess() { - List groupsToApprove = userService.getGroupsCurrentUserCanApprove(); - List result = entityDescriptorRepository.getEntityDescriptorsNeedingApproval(groupsToApprove); - return result; + public List getAllEntityDescriptorProjectionsNeedingApprovalBasedOnUserAccess() throws ForbiddenException { + return (List) shibUiService.getPersistentEntities(userService.getCurrentUserAuthentication(), ShibUiPermissibleType.entityDescriptorProjection, PermissionType.approver); } @Override @@ -534,13 +513,18 @@ public List getAttributeReleaseListFromAttributeList(List att return ModelRepresentationConversions.getAttributeReleaseListFromAttributeList(attributeList); } + @Override + public Iterable getDisabledMetadataSources() throws ForbiddenException { + return (List) shibUiService.getPersistentEntities(userService.getCurrentUserAuthentication(), ShibUiPermissibleType.entityDescriptorProjection, PermissionType.enable); + } + @Override public EntityDescriptor getEntityDescriptorByResourceId(String resourceId) throws PersistentEntityNotFound, ForbiddenException { EntityDescriptor ed = entityDescriptorRepository.findByResourceId(resourceId); if (ed == null) { throw new PersistentEntityNotFound(String.format("The entity descriptor with entity id [%s] was not found.", resourceId)); } - if (!userService.canViewOrEditTarget(ed)) { + if (!shibUiService.hasPermission(userService.getCurrentUserAuthentication(), ed, PermissionType.viewOrEdit)) { throw new ForbiddenException(); } return ed; @@ -621,13 +605,13 @@ public EntityDescriptorRepresentation update(EntityDescriptorRepresentation edRe if (existingEd == null) { throw new PersistentEntityNotFound(String.format("The entity descriptor with entity id [%s] was not found for update.", edRep.getId())); } - if (edRep.isServiceEnabled() && !userService.currentUserCanEnable(existingEd)) { + if (edRep.isServiceEnabled() && !shibUiService.hasPermission(userService.getCurrentUserAuthentication(), existingEd, PermissionType.enable)) { throw new ForbiddenException("You do not have the permissions necessary to enable this service."); } if (StringUtils.isEmpty(edRep.getIdOfOwner())) { edRep.setIdOfOwner(StringUtils.isNotEmpty(existingEd.getIdOfOwner()) ? existingEd.getIdOfOwner() : userService.getCurrentUserGroup().getOwnerId()); } - if (!userService.canViewOrEditTarget(existingEd)) { + if (!shibUiService.hasPermission(userService.getCurrentUserAuthentication(), existingEd, PermissionType.viewOrEdit)) { throw new ForbiddenException(); } // Verify we're the only one attempting to update the EntityDescriptor @@ -656,22 +640,25 @@ public void updateDescriptorFromRepresentation(org.opensaml.saml.saml2.metadata. } @Override - public EntityDescriptorRepresentation updateEntityDescriptorEnabledStatus(String resourceId, boolean status) throws PersistentEntityNotFound, ForbiddenException { + public EntityDescriptorRepresentation updateEntityDescriptorEnabledStatus(String resourceId, boolean enabled) throws PersistentEntityNotFound, ForbiddenException { EntityDescriptor ed = entityDescriptorRepository.findByResourceId(resourceId); if (ed == null) { throw new PersistentEntityNotFound("Entity with resourceid[" + resourceId + "] was not found for update"); } - if (!userService.currentUserCanEnable(ed)) { + if (!shibUiService.hasPermission(userService.getCurrentUserAuthentication(), ed, PermissionType.enable)) { throw new ForbiddenException("You do not have the permissions necessary to change the enable status of this entity descriptor."); } // check to see if approvals have been completed int approvedCount = ed.approvedCount(); List approversList = groupService.find(ed.getIdOfOwner()).getApproversList(); - if (status == true && !ed.isServiceEnabled() && !userService.currentUserIsAdmin() && approversList.size() > approvedCount) { + if (enabled == true && + !ed.isServiceEnabled() && + !shibUiService.hasPermission(userService.getCurrentUserAuthentication(), null, PermissionType.admin) && + approversList.size() > approvedCount) { throw new ForbiddenException("Approval must be completed before you can change the enable status of this entity descriptor."); } - ed.setServiceEnabled(status); - if (status == true) { + ed.setServiceEnabled(enabled); + if (enabled == true) { ed.setApproved(true); } ed = entityDescriptorRepository.save(ed); diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAFilterServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAFilterServiceImpl.java index 928ad2607..03a72602e 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAFilterServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPAFilterServiceImpl.java @@ -8,6 +8,8 @@ import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound; import edu.internet2.tier.shibboleth.admin.ui.repository.FilterRepository; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.permission.IShibUiPermissionEvaluator; +import edu.internet2.tier.shibboleth.admin.ui.security.permission.PermissionType; import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -44,6 +46,9 @@ public class JPAFilterServiceImpl implements FilterService { @Autowired private MetadataResolverService metadataResolverService; + @Autowired + private IShibUiPermissionEvaluator shibUiService; + @Autowired private UserService userService; @@ -117,7 +122,7 @@ public MetadataFilter updateFilterEnabledStatus(String metadataResolverId, Strin MetadataFilter filterTobeUpdated = filterTobeUpdatedOptional.get(); - if (!userService.currentUserCanEnable(filterTobeUpdated)) { + if (!shibUiService.hasPermission(userService.getCurrentUserAuthentication(), filterTobeUpdated, PermissionType.enable)) { throw new ForbiddenException("You do not have the permissions necessary to change the enable status of this filter."); } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/MetadataResolverService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/MetadataResolverService.java index 6cccc3dd0..07dd94510 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/MetadataResolverService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/MetadataResolverService.java @@ -16,7 +16,7 @@ public interface MetadataResolverService { public void reloadFilters(String metadataResolverName); - public MetadataResolver updateMetadataResolverEnabledStatus(MetadataResolver existingResolver) throws ForbiddenException, MetadataFileNotFoundException, InitializationException; + public MetadataResolver updateMetadataResolverEnabledStatus(String resourceId, boolean status) throws ForbiddenException, MetadataFileNotFoundException, InitializationException; public Document generateExternalMetadataFilterConfiguration(); } \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestConfiguration.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestConfiguration.groovy index b8baf83f8..9c5b88df1 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestConfiguration.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestConfiguration.groovy @@ -9,12 +9,16 @@ import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.StringTrimModule 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.listener.GroupUpdatedEntityListener import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.UserUpdatedEntityListener +import edu.internet2.tier.shibboleth.admin.ui.security.permission.IShibUiPermissionEvaluator +import edu.internet2.tier.shibboleth.admin.ui.security.permission.ShibUiPermissionDelegate import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceForTesting import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceImpl +import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityServiceImpl import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator import edu.internet2.tier.shibboleth.admin.util.AttributeUtility @@ -104,4 +108,9 @@ class BaseDataJpaTestConfiguration { listener.init(ownershipRepository, groupRepo) return listener } + + @Bean + public IShibUiPermissionEvaluator shibUiPermissionEvaluator(EntityDescriptorRepository entityDescriptorRepository, UserService userService) { + return new ShibUiPermissionDelegate(entityDescriptorRepository, userService); + } } \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy index 6b54c7a0d..27bb76160 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy @@ -96,7 +96,7 @@ class MetadataFiltersControllerTests extends AbstractBaseDataJpaTest { } @Override - MetadataResolver updateMetadataResolverEnabledStatus(MetadataResolver existingResolver) throws ForbiddenException, MetadataFileNotFoundException, InitializationException { + MetadataResolver updateMetadataResolverEnabledStatus(String id, boolean status) throws ForbiddenException, MetadataFileNotFoundException, InitializationException { // This won't get called return null } diff --git a/ui/src/app/metadata/hooks/api.js b/ui/src/app/metadata/hooks/api.js index 98f77793b..f38e8a8fc 100644 --- a/ui/src/app/metadata/hooks/api.js +++ b/ui/src/app/metadata/hooks/api.js @@ -24,7 +24,7 @@ export function getMetadataPath(type) { } export function useNonAdminSources() { - return useFetch(`${API_BASE_PATH}${getMetadataPath('source')}/disabledNonAdmin`, { + return useFetch(`${API_BASE_PATH}${getMetadataPath('source')}/disabledSources`, { cachePolicy: 'no-cache' }); } @@ -173,4 +173,4 @@ export function useMetadataAttributes (opts = {}, onMount) { export function useMetadataAttribute(opts = {}, onMount) { // return useFetch(`${API_BASE_PATH}/custom/entity/attribute`, opts, onMount); -} \ No newline at end of file +} diff --git a/ui/src/app/metadata/hooks/api.test.js b/ui/src/app/metadata/hooks/api.test.js index 4f2133677..40c19e33b 100644 --- a/ui/src/app/metadata/hooks/api.test.js +++ b/ui/src/app/metadata/hooks/api.test.js @@ -82,7 +82,7 @@ describe('api hooks', () => { describe('useNonAdminSources', () => { it('should call useFetch', () => { const sources = useNonAdminSources(); - expect(useFetch).toHaveBeenCalledWith(`${API_BASE_PATH}${getMetadataPath('source')}/disabledNonAdmin`, { "cachePolicy": "no-cache" }) + expect(useFetch).toHaveBeenCalledWith(`${API_BASE_PATH}${getMetadataPath('source')}/disabledSources`, { "cachePolicy": "no-cache" }) }) }); @@ -92,7 +92,7 @@ describe('api hooks', () => { const opts = {}; const onMount = []; const sources = useMetadataEntities(type, opts, onMount); - + expect(useFetch).toHaveBeenCalledWith(`${API_BASE_PATH}${getMetadataListPath(type)}`, opts, onMount) }); @@ -283,4 +283,4 @@ describe('api hooks', () => { expect(mockPut).toHaveBeenCalled(); }); }); -}); \ No newline at end of file +}); From b2e65e4f448239386ce37fc2f37af4c147b59601 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 1 Nov 2022 16:33:52 -0700 Subject: [PATCH 38/56] SHIBUI-2394 Import cleanups --- ...lerVersionEndpointsIntegrationTests.groovy | 5 -- ...tadataResolverEnversVersioningTests.groovy | 5 +- .../interpreter/webdriverfactory/Firefox.java | 7 +- .../jp/vmi/selenium/selenese/Runner.java | 66 +++++++++---------- .../selenium/selenese/command/AttachFile.java | 27 ++++---- .../EntityDescriptorRepository.java | 1 - .../admin/ui/security/model/Approvers.java | 3 - .../ui/configuration/TestConfiguration.groovy | 7 +- .../controller/ApproveControllerTests.groovy | 1 - .../AttributeBundleControllerTests.groovy | 3 +- ...efinitionControllerIntegrationTests.groovy | 2 +- .../EntityDescriptorControllerTests.groovy | 1 - ...DescriptorOwnershipIntegrationTests.groovy | 27 +------- .../MetadataFiltersControllerTests.groovy | 2 +- ...tionOrderControllerIntegrationTests.groovy | 3 - .../ShibPropertiesControllerTests.groovy | 2 +- .../EntityAttributesFilterTargetTests.groovy | 4 +- ...ymorphicFiltersJacksonHandlingTests.groovy | 1 - .../domain/oidc/OAuthRPExtensionsTest.groovy | 4 -- ...adataResolverValidationServiceTests.groovy | 2 +- ...esourceBackedMetadataValidatorTests.groovy | 2 - .../AttributeBundleRepositoryTests.groovy | 2 +- .../ShibPropertySetRepositoryTests.groovy | 1 - .../GroupsControllerIntegrationTests.groovy | 9 ++- .../repository/GroupsRepositoryTests.groovy | 2 - .../service/GroupServiceForTesting.groovy | 2 - .../AdminUserServiceTests.groovy | 2 - .../service/AuxiliaryIntegrationTests.groovy | 1 - .../ui/service/EmailServiceImplTests.groovy | 4 +- .../EntityIdsSearchServiceTests.groovy | 4 +- ...JPAEntityDescriptorServiceImplTests.groovy | 1 - .../service/JPAEntityServiceImplTests.groovy | 1 - .../JPAFilterTargetServiceImplTests.groovy | 1 - ...JPAMetadataResolverServiceImplTests.groovy | 2 +- ...aceholderValueResolvingServiceTests.groovy | 3 +- ...ntityDescriptorConversionUtilsTests.groovy | 2 - .../admin/ui/util/TestObjectGenerator.groovy | 2 +- 37 files changed, 78 insertions(+), 136 deletions(-) diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerVersionEndpointsIntegrationTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerVersionEndpointsIntegrationTests.groovy index 92d53ff65..74821fcd2 100644 --- a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerVersionEndpointsIntegrationTests.groovy +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerVersionEndpointsIntegrationTests.groovy @@ -9,7 +9,6 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFil import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityRoleWhiteListFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.NameIdFormatFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.NameIdFormatFilterTarget -import edu.internet2.tier.shibboleth.admin.ui.domain.filters.SignatureValidationFilter import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FilesystemMetadataResolver @@ -19,13 +18,9 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.RegexScheme import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.TemplateScheme import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository - import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverVersionService import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator import edu.internet2.tier.shibboleth.admin.util.AttributeUtility - -import org.apache.commons.lang3.RandomStringUtils - import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.web.client.TestRestTemplate diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEnversVersioningTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEnversVersioningTests.groovy index 6d976a033..d828b8e19 100644 --- a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEnversVersioningTests.groovy +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEnversVersioningTests.groovy @@ -27,7 +27,10 @@ import javax.persistence.EntityManager import static edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.HttpMetadataResolverAttributes.HttpCachingType.file import static edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.HttpMetadataResolverAttributes.HttpCachingType.none -import static edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.* +import static edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getModifiedEntityNames +import static edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getRevisionEntityForRevisionIndex +import static edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.getTargetEntityForRevisionIndex +import static edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTestsSupport.updateAndGetRevisionHistoryOfMetadataResolver /** * Testing metadata resolver envers versioning diff --git a/backend/src/integration/groovy/com/sebuilder/interpreter/webdriverfactory/Firefox.java b/backend/src/integration/groovy/com/sebuilder/interpreter/webdriverfactory/Firefox.java index a7ddb5217..6f3c9290b 100644 --- a/backend/src/integration/groovy/com/sebuilder/interpreter/webdriverfactory/Firefox.java +++ b/backend/src/integration/groovy/com/sebuilder/interpreter/webdriverfactory/Firefox.java @@ -16,8 +16,6 @@ package com.sebuilder.interpreter.webdriverfactory; -import java.io.File; -import java.util.HashMap; import org.openqa.selenium.firefox.FirefoxBinary; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.firefox.FirefoxOptions; @@ -25,6 +23,9 @@ import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.RemoteWebDriver; +import java.io.File; +import java.util.HashMap; + public class Firefox implements WebDriverFactory { /** * @param config Key/value pairs treated as required capabilities, with the exception of: @@ -50,4 +51,4 @@ public RemoteWebDriver make(HashMap config) { options.setBinary(fb); return new FirefoxDriver(options); } -} +} \ No newline at end of file diff --git a/backend/src/integration/groovy/jp/vmi/selenium/selenese/Runner.java b/backend/src/integration/groovy/jp/vmi/selenium/selenese/Runner.java index e866b06ac..acc94ad8d 100644 --- a/backend/src/integration/groovy/jp/vmi/selenium/selenese/Runner.java +++ b/backend/src/integration/groovy/jp/vmi/selenium/selenese/Runner.java @@ -1,38 +1,7 @@ package jp.vmi.selenium.selenese; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintStream; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Deque; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.io.output.NullOutputStream; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.time.FastDateFormat; -import org.openqa.selenium.Alert; -import org.openqa.selenium.HasCapabilities; -import org.openqa.selenium.JavascriptExecutor; -import org.openqa.selenium.OutputType; -import org.openqa.selenium.TakesScreenshot; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebDriverException; -import org.openqa.selenium.remote.Augmenter; -import org.openqa.selenium.remote.RemoteWebDriver; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.assertthat.selenium_shutterbug.core.Shutterbug; import com.assertthat.selenium_shutterbug.utils.web.ScrollStrategy; - import jp.vmi.html.result.HtmlResult; import jp.vmi.html.result.HtmlResultHolder; import jp.vmi.junit.result.JUnitResult; @@ -55,9 +24,38 @@ import jp.vmi.selenium.selenese.utils.MouseUtils; import jp.vmi.selenium.selenese.utils.PathUtils; import jp.vmi.selenium.webdriver.WebDriverPreparator; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.output.NullOutputStream; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.FastDateFormat; +import org.openqa.selenium.Alert; +import org.openqa.selenium.HasCapabilities; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.OutputType; +import org.openqa.selenium.TakesScreenshot; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebDriverException; +import org.openqa.selenium.remote.Augmenter; +import org.openqa.selenium.remote.RemoteWebDriver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Deque; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; -import static jp.vmi.selenium.selenese.result.Unexecuted.*; -import static org.openqa.selenium.remote.CapabilityType.*; +import static jp.vmi.selenium.selenese.result.Unexecuted.UNEXECUTED; +import static org.openqa.selenium.remote.CapabilityType.TAKES_SCREENSHOT; /** * Provide Java API to run Selenese script. @@ -851,4 +849,4 @@ public void unhighlight() { void setupMaxTimeTimer(long maxTime) { this.maxTimeTimer = new MaxTimeActiveTimer(maxTime); } -} +} \ No newline at end of file diff --git a/backend/src/integration/groovy/jp/vmi/selenium/selenese/command/AttachFile.java b/backend/src/integration/groovy/jp/vmi/selenium/selenese/command/AttachFile.java index 02bf54d65..3b82c0298 100644 --- a/backend/src/integration/groovy/jp/vmi/selenium/selenese/command/AttachFile.java +++ b/backend/src/integration/groovy/jp/vmi/selenium/selenese/command/AttachFile.java @@ -25,25 +25,24 @@ package jp.vmi.selenium.selenese.command; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; - -import org.apache.commons.io.FilenameUtils; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.io.TemporaryFilesystem; - import com.google.common.io.Resources; - import jp.vmi.selenium.selenese.Context; import jp.vmi.selenium.selenese.result.Error; import jp.vmi.selenium.selenese.result.Result; import jp.vmi.selenium.selenese.result.Warning; +import org.apache.commons.io.FilenameUtils; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.io.TemporaryFilesystem; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; -import static jp.vmi.selenium.selenese.command.ArgumentType.*; -import static jp.vmi.selenium.selenese.result.Success.*; +import static jp.vmi.selenium.selenese.command.ArgumentType.LOCATOR; +import static jp.vmi.selenium.selenese.command.ArgumentType.VALUE; +import static jp.vmi.selenium.selenese.result.Success.SUCCESS; /** * Re-implementation of AttachFile. @@ -109,4 +108,4 @@ protected Result executeImpl(Context context, String... curArgs) { element.sendKeys(outputTo.getAbsolutePath()); return SUCCESS; } -} +} \ No newline at end of file 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 da36ea483..5719ae687 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 @@ -1,7 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.repository; import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor; -import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java index 2a0739646..670fc8407 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/Approvers.java @@ -8,12 +8,9 @@ import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.ManyToMany; -import javax.persistence.OneToMany; import javax.persistence.Transient; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.UUID; @Data diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy index 1a044baf2..e443888dc 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy @@ -12,9 +12,6 @@ import edu.internet2.tier.shibboleth.admin.ui.service.CustomEntityAttributesDefi import edu.internet2.tier.shibboleth.admin.ui.service.IndexWriterService import net.shibboleth.ext.spring.resource.ResourceHelper import net.shibboleth.utilities.java.support.component.ComponentInitializationException - -import javax.persistence.EntityManager - import org.apache.lucene.document.Document import org.apache.lucene.document.Field import org.apache.lucene.document.StringField @@ -27,14 +24,14 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.ComponentScan import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.Profile import org.springframework.core.io.ClassPathResource import org.springframework.data.domain.AuditorAware import org.springframework.mail.javamail.JavaMailSender import org.springframework.mail.javamail.JavaMailSenderImpl +import javax.persistence.EntityManager + /** * NOT A TEST - this is configuration FOR tests */ diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy index 2a5b46883..eaf337064 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy @@ -16,7 +16,6 @@ import edu.internet2.tier.shibboleth.admin.util.EntityDescriptorConversionUtils import org.springframework.beans.factory.annotation.Autowired import org.springframework.security.test.context.support.WithMockUser import org.springframework.test.web.servlet.setup.MockMvcBuilders -import org.springframework.web.client.RestTemplate import spock.lang.Subject import javax.transaction.Transactional diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy index 567639f36..9fa91edfc 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/AttributeBundleControllerTests.groovy @@ -1,11 +1,10 @@ package edu.internet2.tier.shibboleth.admin.ui.controller - import com.fasterxml.jackson.databind.ObjectMapper import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBundle -import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound 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.AttributeBundleRepository import edu.internet2.tier.shibboleth.admin.ui.service.AttributeBundleService import org.springframework.beans.factory.annotation.Autowired diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy index 1bcf387b2..029bc7a53 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy @@ -12,7 +12,7 @@ import org.springframework.core.io.ResourceLoader import org.springframework.test.context.ActiveProfiles import spock.lang.Specification -import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.* +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.JsonSchemaLocationBuilder import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.ALGORITHM_FILTER import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.DYNAMIC_HTTP_METADATA_RESOLVER import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.ENTITY_ATTRIBUTES_FILTERS 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 33de12c2f..b08d6ec8c 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 @@ -48,7 +48,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath class EntityDescriptorControllerTests extends AbstractBaseDataJpaTest { @Autowired diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorOwnershipIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorOwnershipIntegrationTests.groovy index 6462482d0..718621ee6 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorOwnershipIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorOwnershipIntegrationTests.groovy @@ -1,13 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.controller -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.SerializationFeature -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule + import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest -import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration -import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration -import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration -import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects @@ -15,37 +9,18 @@ import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorReposit 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.security.repository.OwnershipRepository -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.GroupServiceForTesting -import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceImpl -import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorVersionService import edu.internet2.tier.shibboleth.admin.ui.service.EntityService import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityDescriptorServiceImpl import edu.internet2.tier.shibboleth.admin.ui.util.WithMockAdmin import edu.internet2.tier.shibboleth.admin.util.EntityDescriptorConversionUtils 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.context.annotation.Bean -import org.springframework.context.annotation.Primary -import org.springframework.context.annotation.Profile -import org.springframework.data.jpa.repository.config.EnableJpaRepositories -import org.springframework.security.test.context.support.WithMockUser -import org.springframework.test.context.ActiveProfiles -import org.springframework.test.context.ContextConfiguration import org.springframework.test.web.servlet.setup.MockMvcBuilders import org.springframework.transaction.annotation.Transactional import org.springframework.web.client.RestTemplate -import spock.lang.Specification import spock.lang.Stepwise import spock.lang.Subject -import javax.persistence.EntityManager - import static org.springframework.http.MediaType.APPLICATION_JSON import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy index 27bb76160..1107af074 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerTests.groovy @@ -8,9 +8,9 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.exceptions.MetadataFileNotF import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.opensaml.OpenSamlChainingMetadataResolver -import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException import edu.internet2.tier.shibboleth.admin.ui.exception.InitializationException +import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.repository.FilterRepository import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersPositionOrderControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersPositionOrderControllerIntegrationTests.groovy index 4d163a660..6e6ec83cb 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersPositionOrderControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersPositionOrderControllerIntegrationTests.groovy @@ -3,15 +3,12 @@ package edu.internet2.tier.shibboleth.admin.ui.controller import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator import edu.internet2.tier.shibboleth.admin.util.AttributeUtility - import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.web.client.TestRestTemplate import org.springframework.test.context.ActiveProfiles - import spock.lang.Specification - /** * @author Dmitriy Kopylenko */ diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesControllerTests.groovy index 8545362c4..3a96695a7 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesControllerTests.groovy @@ -4,8 +4,8 @@ import com.fasterxml.jackson.databind.ObjectMapper import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibPropertySet import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibPropertySetting -import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound 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.ShibPropertySetRepository import edu.internet2.tier.shibboleth.admin.ui.repository.ShibPropertySettingRepository import edu.internet2.tier.shibboleth.admin.ui.service.ShibConfigurationService diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilterTargetTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilterTargetTests.groovy index 2ff346ee7..b2b55fcc6 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilterTargetTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/filters/EntityAttributesFilterTargetTests.groovy @@ -1,6 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.domain.filters -import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget + import spock.lang.Specification /** @@ -33,4 +33,4 @@ class EntityAttributesFilterTargetTests extends Specification { filterTarget.value.size() == 1 filterTarget.value == expectedList } -} +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/filters/PolymorphicFiltersJacksonHandlingTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/filters/PolymorphicFiltersJacksonHandlingTests.groovy index fa9239ec5..5e36544e5 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/filters/PolymorphicFiltersJacksonHandlingTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/filters/PolymorphicFiltersJacksonHandlingTests.groovy @@ -1,7 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.domain.filters import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.SerializationFeature import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/OAuthRPExtensionsTest.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/OAuthRPExtensionsTest.groovy index 806d5fa6d..6f5e8d473 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/OAuthRPExtensionsTest.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/OAuthRPExtensionsTest.groovy @@ -4,16 +4,12 @@ import com.fasterxml.jackson.databind.ObjectMapper import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation 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.service.EntityService import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityDescriptorServiceImpl -import edu.internet2.tier.shibboleth.admin.ui.util.RandomGenerator import edu.internet2.tier.shibboleth.admin.ui.util.WithMockAdmin import edu.internet2.tier.shibboleth.admin.util.EntityDescriptorConversionUtils import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.json.JacksonTester import org.springframework.context.annotation.PropertySource -import org.springframework.transaction.annotation.Transactional import javax.persistence.EntityManager diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/validator/MetadataResolverValidationServiceTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/validator/MetadataResolverValidationServiceTests.groovy index f0aaf8aa4..04e8657a4 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/validator/MetadataResolverValidationServiceTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/validator/MetadataResolverValidationServiceTests.groovy @@ -4,7 +4,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver import spock.lang.Specification import spock.lang.Subject -import static edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.validator.IMetadataResolverValidator.* +import static edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.validator.IMetadataResolverValidator.ValidationResult /** * @author Dmitriy Kopylenko diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/validator/ResourceBackedMetadataValidatorTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/validator/ResourceBackedMetadataValidatorTests.groovy index 66102f4fe..11cb27938 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/validator/ResourceBackedMetadataValidatorTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/validator/ResourceBackedMetadataValidatorTests.groovy @@ -4,8 +4,6 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ClasspathMetadata import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FileBackedHttpMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ResourceBackedMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.SvnMetadataResource -import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.validator.IMetadataResolverValidator -import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.validator.ResourceBackedIMetadataResolverValidator import spock.lang.Specification class ResourceBackedMetadataValidatorTests extends Specification { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepositoryTests.groovy index 0db6a9555..46818a442 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepositoryTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/AttributeBundleRepositoryTests.groovy @@ -1,6 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.repository -import com.fasterxml.jackson.databind.MapperFeature + import com.fasterxml.jackson.databind.ObjectMapper import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBundle diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/ShibPropertySetRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/ShibPropertySetRepositoryTests.groovy index edcf106d9..fc7fd9501 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/ShibPropertySetRepositoryTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/ShibPropertySetRepositoryTests.groovy @@ -2,7 +2,6 @@ package edu.internet2.tier.shibboleth.admin.ui.repository import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibPropertySet -import edu.internet2.tier.shibboleth.admin.ui.domain.shib.properties.ShibPropertySetting import org.springframework.beans.factory.annotation.Autowired import javax.persistence.EntityManager 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 a4c46e1b1..4fd70a37e 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 @@ -18,8 +18,13 @@ import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.setup.MockMvcBuilders import org.springframework.transaction.annotation.Transactional -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.* -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.* +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status @Rollback class GroupsControllerIntegrationTests extends AbstractBaseDataJpaTest { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy index b341d0ab7..92e69aa8c 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/repository/GroupsRepositoryTests.groovy @@ -9,8 +9,6 @@ import org.springframework.dao.DataIntegrityViolationException import org.springframework.test.annotation.Rollback import org.springframework.transaction.annotation.Transactional -import javax.persistence.EntityManager - /** * Tests to validate the repo and model for groups * @author chasegawa diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceForTesting.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceForTesting.groovy index 6d18c2cd8..6e2243e67 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceForTesting.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/GroupServiceForTesting.groovy @@ -3,8 +3,6 @@ package edu.internet2.tier.shibboleth.admin.ui.security.service import org.springframework.context.annotation.Profile import org.springframework.transaction.annotation.Transactional -import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceImpl - @Profile('test') class GroupServiceForTesting extends GroupServiceImpl { GroupServiceForTesting(GroupServiceImpl impl) { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserServiceTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserServiceTests.groovy index 95c8dc5e6..364fd14e1 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserServiceTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserServiceTests.groovy @@ -14,9 +14,7 @@ import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.TestConfiguration import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.ComponentScan import org.springframework.security.core.userdetails.UsernameNotFoundException -import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.ContextConfiguration /** diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/AuxiliaryIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/AuxiliaryIntegrationTests.groovy index 6ebb9ca6b..61fc89acd 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/AuxiliaryIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/AuxiliaryIntegrationTests.groovy @@ -3,7 +3,6 @@ package edu.internet2.tier.shibboleth.admin.ui.service import com.fasterxml.jackson.databind.ObjectMapper import edu.internet2.tier.shibboleth.admin.ui.configuration.JsonSchemaComponentsConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor -import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation import edu.internet2.tier.shibboleth.admin.ui.jsonschema.LowLevelJsonSchemaValidator import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/EmailServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/EmailServiceImplTests.groovy index 148def965..f99b5c1d6 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/EmailServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/EmailServiceImplTests.groovy @@ -2,10 +2,10 @@ package edu.internet2.tier.shibboleth.admin.ui.service import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.DevConfig -import edu.internet2.tier.shibboleth.admin.ui.configuration.auto.EmailConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.auto.EmailConfiguration import groovy.json.JsonOutput import groovy.json.JsonSlurper import org.springframework.beans.factory.annotation.Autowired @@ -66,4 +66,4 @@ class EmailServiceImplTests extends Specification { expectedNewUserEmailSubject == resultJson.items[0].Content.Headers.Subject[0] resultJson.items[0].Content.Body.contains(expectedTextEmailBody) } -} +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/EntityIdsSearchServiceTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/EntityIdsSearchServiceTests.groovy index a36e7c4ae..e51dd33a0 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/EntityIdsSearchServiceTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/EntityIdsSearchServiceTests.groovy @@ -1,9 +1,9 @@ package edu.internet2.tier.shibboleth.admin.ui.service -import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration -import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.domain.EntityScan import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy index bb673b01a..e1a8f4f59 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityDescriptorServiceImplTests.groovy @@ -15,7 +15,6 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.OrganizationRepres import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.SecurityInfoRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.ServiceProviderSsoDescriptorRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.OAuthRPExtensions -import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation import edu.internet2.tier.shibboleth.admin.ui.jsonschema.LowLevelJsonSchemaValidator import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityServiceImplTests.groovy index d60792b22..51401e187 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAEntityServiceImplTests.groovy @@ -3,7 +3,6 @@ package edu.internet2.tier.shibboleth.admin.ui.service import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation -import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.util.RandomGenerator import edu.internet2.tier.shibboleth.admin.ui.util.TestHelpers import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAFilterTargetServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAFilterTargetServiceImplTests.groovy index 376e23732..dd2587725 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAFilterTargetServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAFilterTargetServiceImplTests.groovy @@ -4,7 +4,6 @@ import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest import edu.internet2.tier.shibboleth.admin.ui.util.RandomGenerator import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator import org.springframework.beans.factory.annotation.Autowired -import spock.lang.Specification /** * @author Bill Smith (wsmith@unicon.net) diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy index f5f37da1f..a270c9519 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPAMetadataResolverServiceImplTests.groovy @@ -7,11 +7,11 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.AlgorithmDigestMethod import edu.internet2.tier.shibboleth.admin.ui.domain.EncryptionMethod import edu.internet2.tier.shibboleth.admin.ui.domain.SignatureDigestMethod import edu.internet2.tier.shibboleth.admin.ui.domain.XSString +import edu.internet2.tier.shibboleth.admin.ui.domain.filters.AlgorithmFilterTarget import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.RequiredValidUntilFilter -import edu.internet2.tier.shibboleth.admin.ui.domain.filters.AlgorithmFilterTarget import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ClasspathMetadataResource import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ExternalMetadataResolver diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/TokenPlaceholderValueResolvingServiceTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/TokenPlaceholderValueResolvingServiceTests.groovy index 8e3ba09f7..69fd1a81b 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/TokenPlaceholderValueResolvingServiceTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/TokenPlaceholderValueResolvingServiceTests.groovy @@ -5,7 +5,6 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.util.TestPropertyValues import org.springframework.core.env.ConfigurableEnvironment import org.springframework.test.context.ContextConfiguration - import spock.lang.Specification import spock.lang.Subject @@ -78,4 +77,4 @@ class TokenPlaceholderValueResolvingServiceTests extends Specification { then: 'Correct combined property values resolution is performed' combinedValue == "$IDP_HOME AND $REFRESH_INTERVAL" } -} +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/EntityDescriptorConversionUtilsTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/EntityDescriptorConversionUtilsTests.groovy index 204ffaf52..e1038f2e0 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/EntityDescriptorConversionUtilsTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/EntityDescriptorConversionUtilsTests.groovy @@ -1,6 +1,5 @@ package edu.internet2.tier.shibboleth.admin.ui.util - import edu.internet2.tier.shibboleth.admin.ui.domain.ContactPerson import edu.internet2.tier.shibboleth.admin.ui.domain.Description import edu.internet2.tier.shibboleth.admin.ui.domain.DisplayName @@ -9,7 +8,6 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor import edu.internet2.tier.shibboleth.admin.ui.domain.Extensions import edu.internet2.tier.shibboleth.admin.ui.domain.GivenName import edu.internet2.tier.shibboleth.admin.ui.domain.InformationURL -import edu.internet2.tier.shibboleth.admin.ui.domain.KeyDescriptor import edu.internet2.tier.shibboleth.admin.ui.domain.Logo import edu.internet2.tier.shibboleth.admin.ui.domain.NameIDFormat import edu.internet2.tier.shibboleth.admin.ui.domain.PrivacyStatementURL diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy index 972120ab4..bfdf5ee63 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/util/TestObjectGenerator.groovy @@ -8,6 +8,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.LocalizedName import edu.internet2.tier.shibboleth.admin.ui.domain.OrganizationDisplayName import edu.internet2.tier.shibboleth.admin.ui.domain.OrganizationName import edu.internet2.tier.shibboleth.admin.ui.domain.OrganizationURL +import edu.internet2.tier.shibboleth.admin.ui.domain.filters.AlgorithmFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget.EntityAttributesFilterTargetType @@ -17,7 +18,6 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.filters.NameIdFormatFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.NameIdFormatFilterTarget import edu.internet2.tier.shibboleth.admin.ui.domain.filters.RequiredValidUntilFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.SignatureValidationFilter -import edu.internet2.tier.shibboleth.admin.ui.domain.filters.AlgorithmFilter import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.FilterRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.FilterTargetRepresentation import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ClasspathMetadataResource From 7e19acc30271991e32c52a02bad9ea47db1f8d0c Mon Sep 17 00:00:00 2001 From: chasegawa Date: Wed, 2 Nov 2022 15:52:40 -0700 Subject: [PATCH 39/56] SHIBUI-2394 SHIBUI-2446 fixed wipeout controller --- .../shibboleth/admin/ui/controller/DangerController.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/DangerController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/DangerController.java index 7af217eb2..15c4a4a0a 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/DangerController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/DangerController.java @@ -9,6 +9,7 @@ import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolversPositionOrderContainerRepository; import edu.internet2.tier.shibboleth.admin.ui.repository.ShibPropertySetRepository; import edu.internet2.tier.shibboleth.admin.ui.repository.ShibPropertySettingRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.ApproversRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; @@ -28,6 +29,9 @@ @Profile("very-dangerous") @Slf4j public class DangerController { + @Autowired + private ApproversRepository approversRepositry; + @Autowired private CustomEntityAttributeDefinitionRepository attributeRepository; @@ -105,6 +109,7 @@ private void clearShibSettings() { } private void clearUsersAndGroups() { + approversRepositry.deleteAll(); groupRepository.deleteAll(); ownershipRepository.clearAllOwnedByGroup(); userRepository.findAll().forEach(user -> { From 5c20b23f282c0e63298c4d2f55e68ee097359686 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Wed, 2 Nov 2022 16:29:05 -0700 Subject: [PATCH 40/56] SHIBUI-2394 SHIBUI-2447 fixed issue with items with no defined approvers needing approval --- .../admin/ui/service/JPAEntityDescriptorServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 b57e517dd..8ea41ed1a 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 @@ -231,7 +231,7 @@ public EntityDescriptorRepresentation createNew(EntityDescriptorRepresentation e EntityDescriptor ed = (EntityDescriptor) createDescriptorFromRepresentation(edRep); ed.setIdOfOwner(userService.getCurrentUserGroup().getOwnerId()); - if (userService.currentUserIsAdmin()) { + if (userService.currentUserIsAdmin() || userService.getCurrentUserGroup().getApproversList().isEmpty()) { ed.setApproved(true); } @@ -250,7 +250,7 @@ public EntityDescriptorRepresentation createNewEntityDescriptorFromXMLOrigin(Ent if (ed.getProtocol() == EntityDescriptorProtocol.OIDC) { ed.getSPSSODescriptor("").addSupportedProtocol("http://openid.net/specs/openid-connect-core-1_0.html"); } - if (userService.currentUserIsAdmin()) { + if (userService.currentUserIsAdmin() || userService.getCurrentUserGroup().getApproversList().isEmpty()) { ed.setApproved(true); } EntityDescriptor savedEntity = entityDescriptorRepository.save(ed); From 09bccca55062071649da3464aa1c808427df3f8e Mon Sep 17 00:00:00 2001 From: chasegawa Date: Wed, 2 Nov 2022 17:03:01 -0700 Subject: [PATCH 41/56] SHIBUI-2394 fixed issue with version not displaying --- .../admin/ui/controller/RootUiViewController.java | 11 +++++++++++ ui/src/app/App.constant.js | 2 +- ui/src/app/core/components/VersionInfo.js | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/RootUiViewController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/RootUiViewController.java index 0af16ada6..80c861898 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/RootUiViewController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/RootUiViewController.java @@ -1,6 +1,10 @@ package edu.internet2.tier.shibboleth.admin.ui.controller; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.info.InfoEndpoint; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; @@ -15,6 +19,13 @@ @Controller public class RootUiViewController { + @Autowired InfoEndpoint infoEndpoint; + + @GetMapping(value = "/info") + public ResponseEntity getInfo() { + return ResponseEntity.ok(infoEndpoint.info()); + } + @RequestMapping("/") public String index() { return "redirect:/index.html"; diff --git a/ui/src/app/App.constant.js b/ui/src/app/App.constant.js index afa7a688c..ed133f11d 100644 --- a/ui/src/app/App.constant.js +++ b/ui/src/app/App.constant.js @@ -16,7 +16,7 @@ export const getActuatorPath = () => { export const BASE_PATH = getBasePath(); export const API_BASE_PATH = `${BASE_PATH}api`; -export const ACTUATOR_PATH = getActuatorPath(); +export const ACTUATOR_PATH = getBasePath(); export const FILTER_PLUGIN_TYPES = ['RequiredValidUntil', 'SignatureValidation', 'EntityRoleWhiteList']; diff --git a/ui/src/app/core/components/VersionInfo.js b/ui/src/app/core/components/VersionInfo.js index 1c88ad271..a833cec08 100644 --- a/ui/src/app/core/components/VersionInfo.js +++ b/ui/src/app/core/components/VersionInfo.js @@ -16,7 +16,7 @@ export function VersionInfo () { 'mode':'no-cors' } } - const { data = {} } = useFetch(`${ACTUATOR_PATH}actuator/info`, opts, []); + const { data = {} } = useFetch(`${ACTUATOR_PATH}/info`, opts, []); const [ versionData, setVersionData ] = React.useState(''); From 897f92195f8496254cafe5f03307a021417dc09b Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Fri, 4 Nov 2022 10:50:43 -0400 Subject: [PATCH 42/56] API, polishing --- .../admin/ui/repository/EntityDescriptorProjection.java | 3 ++- .../ui/security/permission/IShibUiPermissionEvaluator.java | 6 +++++- .../admin/ui/security/permission/PermissionType.java | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorProjection.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorProjection.java index 5b60fd5b3..94dada3d8 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorProjection.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorProjection.java @@ -2,6 +2,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptorProtocol; import lombok.Getter; +import org.hibernate.criterion.Projection; import java.time.LocalDateTime; @@ -53,4 +54,4 @@ public String getEntityId() { public EntityDescriptorProtocol getProtocol() { return protocol == null ? EntityDescriptorProtocol.SAML : protocol; } -} \ No newline at end of file +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java index 989132216..fde3ad023 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java @@ -1,6 +1,8 @@ package edu.internet2.tier.shibboleth.admin.ui.security.permission; +import edu.internet2.tier.shibboleth.admin.ui.domain.Auditable; import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; +import liquibase.pro.packaged.T; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.core.Authentication; @@ -19,4 +21,6 @@ public interface IShibUiPermissionEvaluator extends PermissionEvaluator { * @throws ForbiddenException if the user does not have the correct authority required */ Collection getPersistentEntities(Authentication authentication, ShibUiPermissibleType type, PermissionType permissionType) throws ForbiddenException; -} \ No newline at end of file + + Collection getAuditableEntities(Authentication authentication, Class auditableType, PermissionType permissionType); +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/PermissionType.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/PermissionType.java index 921462ab7..b807ecf32 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/PermissionType.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/PermissionType.java @@ -1,5 +1,5 @@ package edu.internet2.tier.shibboleth.admin.ui.security.permission; public enum PermissionType { - admin, approver, enable, fetch, viewOrEdit; -} \ No newline at end of file + admin, approve, enable, fetch, viewOrEdit; +} From c3c73b9f3aae5d67d9ac673ceb62734a1020d95d Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Fri, 4 Nov 2022 11:16:18 -0400 Subject: [PATCH 43/56] Polishing --- .../ui/security/permission/IShibUiPermissionEvaluator.java | 5 ++++- .../ui/security/permission/ShibUiPermissionDelegate.java | 6 +++--- .../admin/ui/service/JPAEntityDescriptorServiceImpl.java | 6 +++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java index fde3ad023..9351fac71 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java @@ -3,6 +3,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.Auditable; import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; import liquibase.pro.packaged.T; +import org.apache.commons.lang.NotImplementedException; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.core.Authentication; @@ -22,5 +23,7 @@ public interface IShibUiPermissionEvaluator extends PermissionEvaluator { */ Collection getPersistentEntities(Authentication authentication, ShibUiPermissibleType type, PermissionType permissionType) throws ForbiddenException; - Collection getAuditableEntities(Authentication authentication, Class auditableType, PermissionType permissionType); + default Collection getAuditableEntities(Authentication authentication, + Class auditableType, + PermissionType permissionType) throws ForbiddenException {throw new NotImplementedException();} } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java index 0f54f72d2..d211f6927 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java @@ -30,7 +30,7 @@ public Collection getPersistentEntities(Authentication authentication, ShibUiPer switch (shibUiType) { case entityDescriptorProjection: switch (permissionType) { - case approver: + case approve: return getAllEntityDescriptorProjectionsNeedingApprovalBasedOnUserAccess(); case enable: // This particular list is used for an admin function, so the user must be an ADMIN @@ -67,7 +67,7 @@ public boolean hasPermission(Authentication authentication, Object targetDomainO switch ((PermissionType) permission) { case admin: // we don't care about the object - the user is an admin or not return userService.currentUserIsAdmin(); - case approver: + case approve: if (userService.currentUserIsAdmin()) { return true; } return targetDomainObject instanceof IApprovable ? userService.getGroupsCurrentUserCanApprove().contains(((IApprovable)targetDomainObject).getIdOfOwner()) : false; case enable: @@ -84,4 +84,4 @@ public boolean hasPermission(Authentication authentication, Object targetDomainO public boolean hasPermission(Authentication authentication, Serializable targetId, String target, Object permission) { return false; // Unused and Unimplemented - we don't need for this implementation to lookup objects } -} \ No newline at end of file +} 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 5d4ad3433..182f239cc 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,7 +183,7 @@ public EntityDescriptorRepresentation changeApproveStatusOfEntityDescriptor(Stri if (ed == null) { throw new PersistentEntityNotFound("Entity with resourceid[" + resourceId + "] was not found for approval"); } - if (!shibUiService.hasPermission(userService.getCurrentUserAuthentication(), ed, PermissionType.approver)) { + if (!shibUiService.hasPermission(userService.getCurrentUserAuthentication(), ed, PermissionType.approve)) { throw new ForbiddenException("You do not have the permissions necessary to approve this entity descriptor."); } if (status) { // approve @@ -501,7 +501,7 @@ public List getAllEntityDescriptorProjectionsBasedOn */ @Override public List getAllEntityDescriptorProjectionsNeedingApprovalBasedOnUserAccess() throws ForbiddenException { - return (List) shibUiService.getPersistentEntities(userService.getCurrentUserAuthentication(), ShibUiPermissibleType.entityDescriptorProjection, PermissionType.approver); + return (List) shibUiService.getPersistentEntities(userService.getCurrentUserAuthentication(), ShibUiPermissibleType.entityDescriptorProjection, PermissionType.approve); } @Override @@ -691,4 +691,4 @@ private void validateEntityIdAndACSUrls(EntityDescriptorRepresentation edRep) th } } } -} \ No newline at end of file +} From b88e9e78942ce4f7f3b18623d0c2e266151155ee Mon Sep 17 00:00:00 2001 From: chasegawa Date: Mon, 7 Nov 2022 09:53:23 -0700 Subject: [PATCH 44/56] SHIBUI-2394 test changes --- .../permission/ShibUiPermissionDelegate.java | 45 +++- .../ui/security/service/UserService.java | 31 +-- .../JPAEntityDescriptorServiceImpl.java | 28 +-- .../admin/ui/AbstractBaseDataJpaTest.groovy | 5 + ...ityDescriptorVersionControllerTests.groovy | 5 +- ...yDescriptorFilesScheduledTasksTests.groovy | 8 +- .../ShibUiPermissionDelegateTests.groovy | 202 ++++++++++++++++++ 7 files changed, 265 insertions(+), 59 deletions(-) create mode 100644 backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegateTests.groovy diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java index d211f6927..0560b569b 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java @@ -1,23 +1,29 @@ package edu.internet2.tier.shibboleth.admin.ui.security.permission; +import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor; import edu.internet2.tier.shibboleth.admin.ui.domain.IActivatable; import edu.internet2.tier.shibboleth.admin.ui.domain.IApprovable; import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorProjection; import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository; import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownable; +import edu.internet2.tier.shibboleth.admin.ui.security.model.User; import edu.internet2.tier.shibboleth.admin.ui.security.service.UserAccess; import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; import lombok.AllArgsConstructor; import org.springframework.security.core.Authentication; import java.io.Serializable; +import java.util.Arrays; import java.util.Collection; import java.util.List; /** - * The ShibUiPermissionDelegate is the default service for SHIBUI, which delegates calls (primarily) to the the userService to determine - * whether a user has the correct abilty to act a particular way (possibly on certain objects). + * The ShibUiPermissionDelegate is the default service for SHIBUI, which delegates calls (primarily) to the the UserService to determine + * whether a user has the correct abilty to act a particular way (possibly on certain objects). Because the Authentication being + * supplied to this implmentation comes from the user service, we ignore it and defer to the UserService (which is ultimately using + * the Authentication from the security context anyway). + * */ @AllArgsConstructor public class ShibUiPermissionDelegate implements IShibUiPermissionEvaluator { @@ -26,7 +32,7 @@ public class ShibUiPermissionDelegate implements IShibUiPermissionEvaluator { private UserService userService; @Override - public Collection getPersistentEntities(Authentication authentication, ShibUiPermissibleType shibUiType, PermissionType permissionType) throws ForbiddenException { + public Collection getPersistentEntities(Authentication ignored, ShibUiPermissibleType shibUiType, PermissionType permissionType) throws ForbiddenException { switch (shibUiType) { case entityDescriptorProjection: switch (permissionType) { @@ -34,12 +40,12 @@ public Collection getPersistentEntities(Authentication authentication, ShibUiPer return getAllEntityDescriptorProjectionsNeedingApprovalBasedOnUserAccess(); case enable: // This particular list is used for an admin function, so the user must be an ADMIN - if (!hasPermission(authentication, null, PermissionType.admin)) { + if (!hasPermission(ignored, null, PermissionType.admin)) { throw new ForbiddenException(); } return entityDescriptorRepository.getEntityDescriptorsNeedingEnabling(); case fetch: - if (!hasPermission(authentication, null, PermissionType.fetch)) { + if (!hasPermission(ignored, null, PermissionType.fetch)) { throw new ForbiddenException("User has no access rights to get a list of Metadata Sources"); } return getAllEntityDescriptorProjectionsBasedOnUserAccess(); @@ -63,7 +69,7 @@ private List getAllEntityDescriptorProjectionsNeedin } @Override - public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { + public boolean hasPermission(Authentication ignored, Object targetDomainObject, Object permission) { switch ((PermissionType) permission) { case admin: // we don't care about the object - the user is an admin or not return userService.currentUserIsAdmin(); @@ -71,7 +77,7 @@ public boolean hasPermission(Authentication authentication, Object targetDomainO if (userService.currentUserIsAdmin()) { return true; } return targetDomainObject instanceof IApprovable ? userService.getGroupsCurrentUserCanApprove().contains(((IApprovable)targetDomainObject).getIdOfOwner()) : false; case enable: - return targetDomainObject instanceof IActivatable ? userService.currentUserCanEnable((IActivatable) targetDomainObject) : false; + return targetDomainObject instanceof IActivatable ? currentUserCanEnable((IActivatable) targetDomainObject) : false; case fetch: return userService.currentUserIsAdmin() || userService.getCurrentUserAccess().equals(UserAccess.GROUP); case viewOrEdit: @@ -84,4 +90,27 @@ public boolean hasPermission(Authentication authentication, Object targetDomainO public boolean hasPermission(Authentication authentication, Serializable targetId, String target, Object permission) { return false; // Unused and Unimplemented - we don't need for this implementation to lookup objects } -} + + private boolean currentUserCanEnable(IActivatable activatableObject) { + if (userService.currentUserIsAdmin()) { return true; } + switch (activatableObject.getActivatableType()) { + case ENTITY_DESCRIPTOR: { + return currentUserHasExpectedRole(Arrays.asList("ROLE_ENABLE" )) && userService.getCurrentUserGroup().getOwnerId().equals(((EntityDescriptor) activatableObject).getIdOfOwner()); + } + // Currently filters and providers dont have ownership, so we just look for the right role + case FILTER: + case METADATA_RESOLVER: + return currentUserHasExpectedRole(Arrays.asList("ROLE_ENABLE" )); + default: + return false; + } + } + + /** + * This basic logic assumes users only have a single role (despite users having a list of roles, we assume only 1 currently) + */ + private boolean currentUserHasExpectedRole(List acceptedRoles) { + User user = userService.getCurrentUser(); + return acceptedRoles.contains(user.getRole()); + } +} \ No newline at end of file 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 684be9009..097f745fb 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 @@ -56,37 +56,10 @@ public UserService(IGroupService groupService, OwnershipRepository ownershipRepo this.userRepository = userRepository; } - public boolean currentUserCanApprove(List approverGroups) { - if (currentUserIsAdmin()) { - return true; - } - Group currentUserGroup = getCurrentUserGroup(); - return approverGroups.contains(currentUserGroup); - } - - public boolean currentUserCanEnable(IActivatable activatableObject) { - if (currentUserIsAdmin()) { return true; } - switch (activatableObject.getActivatableType()) { - case ENTITY_DESCRIPTOR: { - return currentUserHasExpectedRole(Arrays.asList("ROLE_ENABLE" )) && getCurrentUserGroup().getOwnerId().equals(((EntityDescriptor) activatableObject).getIdOfOwner()); - } - // Currently filters and providers dont have ownership, so we just look for the right role - case FILTER: - case METADATA_RESOLVER: - return currentUserHasExpectedRole(Arrays.asList("ROLE_ENABLE" )); - default: - return false; - } - } - /** - * This basic logic assumes users only have a single role (despite users having a list of roles, we assume only 1 currently) + * @deprecated don't call this, call the ShibUiPermissionDelegate method hasPermission(...) */ - private boolean currentUserHasExpectedRole(List acceptedRoles) { - User user = getCurrentUser(); - return acceptedRoles.contains(user.getRole()); - } - + @Deprecated public boolean currentUserIsAdmin() { User user = getCurrentUser(); return user != null && user.getRole().equals("ROLE_ADMIN"); 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 182f239cc..a1c80b9dc 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 @@ -85,7 +85,7 @@ public class JPAEntityDescriptorServiceImpl implements EntityDescriptorService { private OwnershipRepository ownershipRepository; @Autowired - private IShibUiPermissionEvaluator shibUiService; + private IShibUiPermissionEvaluator shibUiAuthorizationDelegate; @Autowired private UserService userService; @@ -183,7 +183,7 @@ public EntityDescriptorRepresentation changeApproveStatusOfEntityDescriptor(Stri if (ed == null) { throw new PersistentEntityNotFound("Entity with resourceid[" + resourceId + "] was not found for approval"); } - if (!shibUiService.hasPermission(userService.getCurrentUserAuthentication(), ed, PermissionType.approve)) { + if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), ed, PermissionType.approve)) { throw new ForbiddenException("You do not have the permissions necessary to approve this entity descriptor."); } if (status) { // approve @@ -221,7 +221,7 @@ public EntityDescriptorRepresentation createNew(EntityDescriptorRepresentation e } EntityDescriptor ed = (EntityDescriptor) createDescriptorFromRepresentation(edRep); - if (ed.isServiceEnabled() && !shibUiService.hasPermission(userService.getCurrentUserAuthentication(), ed, PermissionType.enable)) { + if (ed.isServiceEnabled() && !shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), ed, PermissionType.enable)) { throw new ForbiddenException("You do not have the permissions necessary to enable this entity descriptor."); } @@ -231,7 +231,7 @@ public EntityDescriptorRepresentation createNew(EntityDescriptorRepresentation e validateEntityIdAndACSUrls(edRep); ed.setIdOfOwner(userService.getCurrentUserGroup().getOwnerId()); - if (shibUiService.hasPermission(userService.getCurrentUserAuthentication(), null, PermissionType.admin)) { + if (shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), null, PermissionType.admin)) { ed.setApproved(true); } @@ -250,7 +250,7 @@ public EntityDescriptorRepresentation createNewEntityDescriptorFromXMLOrigin(Ent if (ed.getProtocol() == EntityDescriptorProtocol.OIDC) { ed.getSPSSODescriptor("").addSupportedProtocol("http://openid.net/specs/openid-connect-core-1_0.html"); } - if (shibUiService.hasPermission(userService.getCurrentUserAuthentication(), null, PermissionType.admin)) { + if (shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), null, PermissionType.admin)) { ed.setApproved(true); } EntityDescriptor savedEntity = entityDescriptorRepository.save(ed); @@ -493,7 +493,7 @@ public boolean entityExists(String entityID) { */ @Override public List getAllEntityDescriptorProjectionsBasedOnUserAccess() throws ForbiddenException { - return (List) shibUiService.getPersistentEntities(userService.getCurrentUserAuthentication(), ShibUiPermissibleType.entityDescriptorProjection, PermissionType.fetch); + return (List) shibUiAuthorizationDelegate.getPersistentEntities(userService.getCurrentUserAuthentication(), ShibUiPermissibleType.entityDescriptorProjection, PermissionType.fetch); } /** @@ -501,7 +501,7 @@ public List getAllEntityDescriptorProjectionsBasedOn */ @Override public List getAllEntityDescriptorProjectionsNeedingApprovalBasedOnUserAccess() throws ForbiddenException { - return (List) shibUiService.getPersistentEntities(userService.getCurrentUserAuthentication(), ShibUiPermissibleType.entityDescriptorProjection, PermissionType.approve); + return (List) shibUiAuthorizationDelegate.getPersistentEntities(userService.getCurrentUserAuthentication(), ShibUiPermissibleType.entityDescriptorProjection, PermissionType.approve); } @Override @@ -515,7 +515,7 @@ public List getAttributeReleaseListFromAttributeList(List att @Override public Iterable getDisabledMetadataSources() throws ForbiddenException { - return (List) shibUiService.getPersistentEntities(userService.getCurrentUserAuthentication(), ShibUiPermissibleType.entityDescriptorProjection, PermissionType.enable); + return (List) shibUiAuthorizationDelegate.getPersistentEntities(userService.getCurrentUserAuthentication(), ShibUiPermissibleType.entityDescriptorProjection, PermissionType.enable); } @Override @@ -524,7 +524,7 @@ public EntityDescriptor getEntityDescriptorByResourceId(String resourceId) throw if (ed == null) { throw new PersistentEntityNotFound(String.format("The entity descriptor with entity id [%s] was not found.", resourceId)); } - if (!shibUiService.hasPermission(userService.getCurrentUserAuthentication(), ed, PermissionType.viewOrEdit)) { + if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), ed, PermissionType.viewOrEdit)) { throw new ForbiddenException(); } return ed; @@ -605,13 +605,13 @@ public EntityDescriptorRepresentation update(EntityDescriptorRepresentation edRe if (existingEd == null) { throw new PersistentEntityNotFound(String.format("The entity descriptor with entity id [%s] was not found for update.", edRep.getId())); } - if (edRep.isServiceEnabled() && !shibUiService.hasPermission(userService.getCurrentUserAuthentication(), existingEd, PermissionType.enable)) { + if (edRep.isServiceEnabled() && !shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), existingEd, PermissionType.enable)) { throw new ForbiddenException("You do not have the permissions necessary to enable this service."); } if (StringUtils.isEmpty(edRep.getIdOfOwner())) { edRep.setIdOfOwner(StringUtils.isNotEmpty(existingEd.getIdOfOwner()) ? existingEd.getIdOfOwner() : userService.getCurrentUserGroup().getOwnerId()); } - if (!shibUiService.hasPermission(userService.getCurrentUserAuthentication(), existingEd, PermissionType.viewOrEdit)) { + if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), existingEd, PermissionType.viewOrEdit)) { throw new ForbiddenException(); } // Verify we're the only one attempting to update the EntityDescriptor @@ -645,7 +645,7 @@ public EntityDescriptorRepresentation updateEntityDescriptorEnabledStatus(String if (ed == null) { throw new PersistentEntityNotFound("Entity with resourceid[" + resourceId + "] was not found for update"); } - if (!shibUiService.hasPermission(userService.getCurrentUserAuthentication(), ed, PermissionType.enable)) { + if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), ed, PermissionType.enable)) { throw new ForbiddenException("You do not have the permissions necessary to change the enable status of this entity descriptor."); } // check to see if approvals have been completed @@ -653,7 +653,7 @@ public EntityDescriptorRepresentation updateEntityDescriptorEnabledStatus(String List approversList = groupService.find(ed.getIdOfOwner()).getApproversList(); if (enabled == true && !ed.isServiceEnabled() && - !shibUiService.hasPermission(userService.getCurrentUserAuthentication(), null, PermissionType.admin) && + !shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), null, PermissionType.admin) && approversList.size() > approvedCount) { throw new ForbiddenException("Approval must be completed before you can change the enable status of this entity descriptor."); } @@ -691,4 +691,4 @@ private void validateEntityIdAndACSUrls(EntityDescriptorRepresentation edRep) th } } } -} +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/AbstractBaseDataJpaTest.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/AbstractBaseDataJpaTest.groovy index 65195d7ae..791cbbc2e 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/AbstractBaseDataJpaTest.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/AbstractBaseDataJpaTest.groovy @@ -1,5 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui +import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor +import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository 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.model.listener.GroupUpdatedEntityListener @@ -37,6 +39,9 @@ abstract class AbstractBaseDataJpaTest extends Specification implements ResetsDa @Autowired ApproversRepository approversRepository + @Autowired + EntityDescriptorRepository entityDescriptorRepository + @Autowired EntityManager entityManager diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorVersionControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorVersionControllerTests.groovy index 0dbb40471..fd8838b4e 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorVersionControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorVersionControllerTests.groovy @@ -43,9 +43,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @ContextConfiguration(classes=[EDCLocalConfig]) class EntityDescriptorVersionControllerTests extends AbstractBaseDataJpaTest { - @Autowired - EntityDescriptorRepository entityDescriptorRepository - @Autowired private TestEntityManager testEntityManager @@ -166,4 +163,4 @@ class EntityDescriptorVersionControllerTests extends AbstractBaseDataJpaTest { return new EnversVersionServiceSupport(entityManager) } } -} +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/scheduled/EntityDescriptorFilesScheduledTasksTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/scheduled/EntityDescriptorFilesScheduledTasksTests.groovy index b9e9856dd..f25d67682 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/scheduled/EntityDescriptorFilesScheduledTasksTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/scheduled/EntityDescriptorFilesScheduledTasksTests.groovy @@ -28,7 +28,7 @@ class EntityDescriptorFilesScheduledTasksTests extends AbstractBaseDataJpaTest { def directory - def entityDescriptorRepository = Mock(EntityDescriptorRepository) + def entityDescriptorRepo = Mock(EntityDescriptorRepository) def entityDescriptorFilesScheduledTasks @@ -38,7 +38,7 @@ class EntityDescriptorFilesScheduledTasksTests extends AbstractBaseDataJpaTest { randomGenerator = new RandomGenerator() tempPath = tempPath + randomGenerator.randomRangeInt(10000, 20000) EntityDescriptorConversionUtils.setOpenSamlObjects(openSamlObjects) - entityDescriptorFilesScheduledTasks = new EntityDescriptorFilesScheduledTasks(tempPath, entityDescriptorRepository, openSamlObjects, new FileCheckingFileWritingService()) + entityDescriptorFilesScheduledTasks = new EntityDescriptorFilesScheduledTasks(tempPath, entityDescriptorRepo, openSamlObjects, new FileCheckingFileWritingService()) directory = new File(tempPath) directory.mkdir() } @@ -74,7 +74,7 @@ class EntityDescriptorFilesScheduledTasksTests extends AbstractBaseDataJpaTest { } it }) - 1 * entityDescriptorRepository.findAllStreamByServiceEnabled(true) >> [entityDescriptor].stream() + 1 * entityDescriptorRepo.findAllStreamByServiceEnabled(true) >> [entityDescriptor].stream() when: if (directory.exists()) { @@ -107,7 +107,7 @@ class EntityDescriptorFilesScheduledTasksTests extends AbstractBaseDataJpaTest { def file = new File(directory, randomGenerator.randomId() + ".xml") file.text = "Delete me!" - 1 * entityDescriptorRepository.findAllStreamByServiceEnabled(true) >> [entityDescriptor].stream() + 1 * entityDescriptorRepo.findAllStreamByServiceEnabled(true) >> [entityDescriptor].stream() when: entityDescriptorFilesScheduledTasks.removeDanglingEntityDescriptorFiles() diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegateTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegateTests.groovy new file mode 100644 index 000000000..f3c3ab8fd --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegateTests.groovy @@ -0,0 +1,202 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.permission + +import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest +import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation +import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException +import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorProjection +import edu.internet2.tier.shibboleth.admin.ui.security.model.Approvers +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.service.JPAEntityDescriptorServiceImpl +import edu.internet2.tier.shibboleth.admin.ui.util.WithMockAdmin +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.security.test.context.support.WithMockUser +import org.springframework.test.annotation.Rollback +import org.springframework.transaction.annotation.Transactional + +@Rollback +class ShibUiPermissionDelegateTests extends AbstractBaseDataJpaTest { + ShibUiPermissionDelegate delegate + + @Autowired + JPAEntityDescriptorServiceImpl jpaEntityDescriptorService + + def entityDescriptor + def entityDescriptor2 + def entityDescriptor3 + + @Transactional + def setup() { + delegate = new ShibUiPermissionDelegate(entityDescriptorRepository, userService) + createDevUsersAndGroups() + } + + def createDevUsersAndGroups() { + def groups = [ + new Group().with { + it.name = "A1" + it.description = "AAA Group" + it.resourceId = "AAA" + it + }, + new Group().with { + it.name = "B1" + it.description = "BBB Group" + it.resourceId = "BBB" + it + }] + groups.each { + try { + groupRepository.save(it) + } catch (Throwable e) { + // Must already exist (from a unit test) + } + } + groupRepository.flush() + + List apprGroups = new ArrayList<>() + String[] groupNames = ['XXX', 'YYY', 'ZZZ'] + groupNames.each {name -> { + Group group = new Group().with({ + it.name = name + it.description = name + it.resourceId = name + it + }) + if (name != "ZZZ") { + apprGroups.add(groupRepository.save(group)) + } else { + Approvers approvers = new Approvers() + approvers.setApproverGroups(apprGroups) + List apprList = new ArrayList<>() + apprList.add(approversRepository.save(approvers)) + group.setApproversList(apprList) + groupRepository.save(group) + } + }} + groupRepository.flush() + + if (roleRepository.count() == 0) { + def roles = [new Role().with { + name = 'ROLE_ADMIN' + it + }, new Role().with { + name = 'ROLE_USER' + it + }, new Role().with { + name = 'ROLE_NONE' + it + }, new Role().with { + name = 'ROLE_ENABLE' + it + }] + roles.each { + roleRepository.save(it) + } + } + roleRepository.flush() + if (userRepository.count() < 2) { + userRepository.deleteAll() + def users = [new User().with { + username = 'admin' + password = '{noop}adminpass' + firstName = 'Joe' + lastName = 'Doe' + emailAddress = 'joe@institution.edu' + roles.add(roleRepository.findByName('ROLE_ADMIN').get()) + it + }, new User().with { + username = 'enableZ' + password = '{noop}nonadminpass' + firstName = 'Peter' + lastName = 'Vandelay' + emailAddress = 'peter@institution.edu' + setGroupId('ZZZ') + roles.add(roleRepository.findByName('ROLE_ENABLE').get()) + it + }, new User().with { + username = 'Approver' + password = '{noop}password' + firstName = 'Bad' + lastName = 'robot' + emailAddress = 'badboy@institution.edu' + setGroupId('XXX') + roles.add(roleRepository.findByName('ROLE_USER').get()) + it + }, new User().with { + username = 'Submitter' + password = '{noop}password' + firstName = 'Bad' + lastName = 'robot2' + emailAddress = 'badboy2@institution.edu' + setGroupId('ZZZ') + roles.add(roleRepository.findByName('ROLE_NONE').get()) + it + }] + users.each { + userService.save(it) + } + } + entityManager.flush() + entityManager.clear() + + entityDescriptor = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: false, idOfOwner: 'ZZZ') + def edid = jpaEntityDescriptorService.createNew(entityDescriptor).getId() + entityManager.flush() + entityDescriptor2 = new EntityDescriptor(resourceId: 'uuid-2', entityID: 'eid2', serviceProviderName: 'sp2', serviceEnabled: false, idOfOwner: 'XXX') + def edid2 = jpaEntityDescriptorService.createNew(entityDescriptor2).getId() + entityManager.flush() + entityDescriptor3 = new EntityDescriptor(resourceId: 'uuid-3', entityID: 'eid3', serviceProviderName: 'sp3', serviceEnabled: false, idOfOwner: 'YYY') + def edid3 = jpaEntityDescriptorService.createNew(entityDescriptor3).getId() + entityManager.flush() + + jpaEntityDescriptorService.updateGroupForEntityDescriptor(edid, 'ZZZ') + jpaEntityDescriptorService.updateGroupForEntityDescriptor(edid2, 'XXX') + jpaEntityDescriptorService.updateGroupForEntityDescriptor(edid3, 'YYY') + entityManager.flush() + } + + @WithMockAdmin + def testAdmin() { + expect: + delegate.hasPermission(userService.getCurrentUserAuthentication(), "doesn't matter", PermissionType.admin) + delegate.hasPermission(null, "doesn't matter", PermissionType.admin) + } + + @WithMockUser(username = "Approver", roles = ["USER"]) + def testApproverPerms() { + expect: + userRepository.findAll().size() == 4 + !delegate.hasPermission(null, "doesn't matter", PermissionType.admin) + !delegate.hasPermission(null, entityDescriptor, PermissionType.enable) + !delegate.hasPermission(null, entityDescriptor2, PermissionType.enable) + !delegate.hasPermission(null, entityDescriptor3, PermissionType.enable) + + delegate.hasPermission(null, entityDescriptor, PermissionType.approve) + !delegate.hasPermission(null, entityDescriptor2, PermissionType.approve) + !delegate.hasPermission(null, entityDescriptor3, PermissionType.approve) + + delegate.hasPermission(null, entityDescriptor, PermissionType.viewOrEdit) + delegate.hasPermission(null, entityDescriptor2, PermissionType.viewOrEdit) + !delegate.hasPermission(null, entityDescriptor3, PermissionType.viewOrEdit) + + when: + def Collection fetch = delegate.getPersistentEntities(null, ShibUiPermissibleType.entityDescriptorProjection, PermissionType.fetch) + def Collection approve = delegate.getPersistentEntities(null, ShibUiPermissibleType.entityDescriptorProjection, PermissionType.approve) + + then: + fetch.size() == 1 + ((EntityDescriptorProjection)fetch.iterator().next()).getEntityID().equals("eid2") + + approve.size() == 1 + ((EntityDescriptorProjection)approve.iterator().next()).getEntityID().equals("eid1") + + when: + delegate.getPersistentEntities(null, ShibUiPermissibleType.entityDescriptorProjection, PermissionType.enable) + + then: + thrown (ForbiddenException) + } +} \ No newline at end of file From 7272a1603e42eea6e46b1051969d7a0c3d3d7b3f Mon Sep 17 00:00:00 2001 From: chasegawa Date: Mon, 7 Nov 2022 10:59:42 -0700 Subject: [PATCH 45/56] SHIBUI-2394 test changes --- .../EntityDescriptorControllerTests.groovy | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) 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 b08d6ec8c..e5f542e3a 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 @@ -131,6 +131,25 @@ class EntityDescriptorControllerTests extends AbstractBaseDataJpaTest { entityDescriptorRepository.findAll().size() == 0 } + @WithMockUser(value = "someUser", roles = ["USER"]) + def 'DELETE as non-admin'() { + given: + def entityDescriptor = new EntityDescriptor(resourceId: 'uuid-1', entityID: 'eid1', serviceProviderName: 'sp1', serviceEnabled: false) + entityDescriptorRepository.save(entityDescriptor) + + when: 'pre-check' + entityManager.flush() + + then: + entityDescriptorRepository.findAll().size() == 1 + try { + result = mockMvc.perform(delete("/api/EntityDescriptor/uuid-1")) + } + catch (Exception e) { + e instanceof ForbiddenException + } + } + @WithMockAdmin def 'GET /EntityDescriptors with empty repository as admin'() { given: From 6e94768ba25cfef8756cc3728af3956e8a499f69 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 8 Nov 2022 15:56:29 -0700 Subject: [PATCH 46/56] 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()) From 500b960c7a5f0f31e3bfde0fb6ed7f0474ab6e88 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 8 Nov 2022 17:03:11 -0700 Subject: [PATCH 47/56] SHIBUI-2394 Update for when approvers are added or removed from a group --- .../admin/ui/security/repository/GroupsRepository.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 82f0adea3..a435a95e9 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 @@ -12,9 +12,9 @@ public interface GroupsRepository extends JpaRepository { @Modifying @Query(nativeQuery = true, 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)") + " WHERE approvers_list_resource_id IN (SELECT approvers_list_resource_id " + + " FROM user_group_approvers " + + " WHERE user_groups_resource_id = :resourceId)") void clearApproversForGroup(@Param("resourceId") String resourceId); void deleteByResourceId(String resourceId); From bce1fde624d396035c3c01556b3ede649ccc6eae Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 8 Nov 2022 20:13:09 -0700 Subject: [PATCH 48/56] SHIBUI-2394 Fixes for group/approver edits and removing all approvers --- .../ui/security/repository/ApproversRepository.java | 11 ++++++++--- .../ui/security/repository/GroupsRepository.java | 8 ++------ .../admin/ui/security/service/GroupServiceImpl.java | 4 +++- 3 files changed, 13 insertions(+), 10 deletions(-) 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 18a0af321..313d24a9f 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,17 +2,22 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.Approvers; 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 ApproversRepository extends JpaRepository { + @Modifying + @Query(nativeQuery = true, value="DELETE FROM approvers_user_groups WHERE approver_groups_resource_id IN (:ids)") + void deleteGroupAssociationsForIds(@Param("ids") List ids); + 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)") + value = "SELECT approvers_list_resource_id " + + " FROM user_groups_approvers " + + " WHERE user_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 a435a95e9..9d877a62c 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 @@ -10,12 +10,8 @@ public interface GroupsRepository extends JpaRepository { @Modifying - @Query(nativeQuery = true, - value = "DELETE user_groups_approvers " + - " WHERE approvers_list_resource_id IN (SELECT approvers_list_resource_id " + - " FROM user_group_approvers " + - " WHERE user_groups_resource_id = :resourceId)") - void clearApproversForGroup(@Param("resourceId") String resourceId); + @Query(nativeQuery = true, value = "DELETE FROM user_groups_approvers WHERE approvers_list_resource_id IN (:approverIds)") + void clearApproversByApproverIds(@Param("approverIds") List approverIds); void deleteByResourceId(String resourceId); 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 438fe1aaf..2e18e48ed 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 @@ -129,8 +129,10 @@ private void manageApproversList(Group group) { AtomicInteger approversCount = new AtomicInteger(); group.getApproversList().forEach(a -> approversCount.addAndGet(a.getApproverGroupIds().size())); if (approversCount.intValue() == 0) { + // Need to manually manage the join tables List ids = approversRepository.getApproverIdsForGroup(group.getResourceId()); - groupRepository.clearApproversForGroup(group.getResourceId()); + groupRepository.clearApproversByApproverIds(ids); + approversRepository.deleteGroupAssociationsForIds(ids); approversRepository.deleteAllById(ids); group.setApproversList(new ArrayList<>()); return; From c60b1d2f4d1c06e33cc737d4c48ce1da728cdc57 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Wed, 9 Nov 2022 13:19:12 -0700 Subject: [PATCH 49/56] Fixed ids for automated tests --- .../filter/component/MetadataFilterConfigurationListItem.js | 2 +- ui/src/app/metadata/domain/provider/component/ProviderList.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/app/metadata/domain/filter/component/MetadataFilterConfigurationListItem.js b/ui/src/app/metadata/domain/filter/component/MetadataFilterConfigurationListItem.js index c749a180f..dc8ec7581 100644 --- a/ui/src/app/metadata/domain/filter/component/MetadataFilterConfigurationListItem.js +++ b/ui/src/app/metadata/domain/filter/component/MetadataFilterConfigurationListItem.js @@ -53,7 +53,7 @@ export function MetadataFilterConfigurationListItem ({ filter, isLast, isFirst, { filter['@type'] } } checked={filter.filterEnabled} disabled={loading} diff --git a/ui/src/app/metadata/domain/provider/component/ProviderList.js b/ui/src/app/metadata/domain/provider/component/ProviderList.js index 9a9ac90d4..08188dba0 100644 --- a/ui/src/app/metadata/domain/provider/component/ProviderList.js +++ b/ui/src/app/metadata/domain/provider/component/ProviderList.js @@ -75,7 +75,7 @@ export function ProviderList({ children, entities, reorder = true, first, last, onEnable(provider, checked)} checked={provider.enabled} From 64364d523e4bb324ed2f90699d04b3750b717833 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Wed, 9 Nov 2022 14:06:10 -0700 Subject: [PATCH 50/56] SHIBUI-2394 Various test fixes in support of 2394. --- .../admin/ui/SeleniumSIDETest.groovy | 4 +- .../integration/resources/SHIBUI-1334-1.side | 2 +- .../integration/resources/SHIBUI-1407-1.side | 2 +- .../integration/resources/SHIBUI-1503-1.side | 16 +++ .../integration/resources/SHIBUI-1503-2.side | 16 +++ .../integration/resources/SHIBUI-1503-3.side | 38 +++---- .../integration/resources/SHIBUI-1674-2.side | 13 ++- .../integration/resources/SHIBUI-1740-1.side | 57 +++++++--- .../integration/resources/SHIBUI-1740-2.side | 105 ++++++++++-------- .../integration/resources/SHIBUI-1743-2.side | 4 +- 10 files changed, 165 insertions(+), 92 deletions(-) diff --git a/backend/src/integration/groovy/edu/internet2/tier/shibboleth/admin/ui/SeleniumSIDETest.groovy b/backend/src/integration/groovy/edu/internet2/tier/shibboleth/admin/ui/SeleniumSIDETest.groovy index fed08da45..d7dd72abb 100644 --- a/backend/src/integration/groovy/edu/internet2/tier/shibboleth/admin/ui/SeleniumSIDETest.groovy +++ b/backend/src/integration/groovy/edu/internet2/tier/shibboleth/admin/ui/SeleniumSIDETest.groovy @@ -156,11 +156,11 @@ class SeleniumSIDETest extends Specification { 'SHIBUI-2267: Verify new RPO CRUD' | '/SHIBUI-2267.side' 'SHIBUI-2380: OIDC metadata source CRUD' | '/SHIBUI-2380.side' 'SHIBUI-1674: Verify metadata source tooltips' | '/SHIBUI-1674-1.side' -// 'SHIBUI-1674: Verify metadata provider tooltips' | '/SHIBUI-1674-2.side' + 'SHIBUI-1674: Verify metadata provider tooltips' | '/SHIBUI-1674-2.side' 'SHIBUI-1674: Verify advanced menu tooltips' | '/SHIBUI-1674-3.side' 'SHIBUI-2270: Verify property set CRUD' | '/SHIBUI-2270-1.side' 'SHIBUI-2270: Verify full property set' | '/SHIBUI-2270-2.side' 'SHIBUI-2268: Verify Algorithm Filter' | '/SHIBUI-2268.side' 'SHIBUI-2269: Verify XML generation of external filters' | '/SHIBUI-2269.side' } -} \ No newline at end of file +} diff --git a/backend/src/integration/resources/SHIBUI-1334-1.side b/backend/src/integration/resources/SHIBUI-1334-1.side index 28458a417..56a095d0b 100644 --- a/backend/src/integration/resources/SHIBUI-1334-1.side +++ b/backend/src/integration/resources/SHIBUI-1334-1.side @@ -2748,7 +2748,7 @@ "id": "bde2bbbb-df66-4e07-a770-ec9125fe3e81", "comment": "", "command": "pause", - "target": "7000", + "target": "10000", "targets": [], "value": "" }, { diff --git a/backend/src/integration/resources/SHIBUI-1407-1.side b/backend/src/integration/resources/SHIBUI-1407-1.side index 7ab010496..b0b65dbb5 100644 --- a/backend/src/integration/resources/SHIBUI-1407-1.side +++ b/backend/src/integration/resources/SHIBUI-1407-1.side @@ -2514,7 +2514,7 @@ "id": "39637add-5eb4-40d0-b840-8eb1972ede0f", "comment": "", "command": "pause", - "target": "10000", + "target": "15000", "targets": [], "value": "" }, { diff --git a/backend/src/integration/resources/SHIBUI-1503-1.side b/backend/src/integration/resources/SHIBUI-1503-1.side index cb0be998d..35736f797 100644 --- a/backend/src/integration/resources/SHIBUI-1503-1.side +++ b/backend/src/integration/resources/SHIBUI-1503-1.side @@ -509,6 +509,22 @@ ["xpath=//div[4]/a", "xpath:position"] ], "value": "" + }, { + "id": "a43898de-b92d-443e-8686-fba526f403ec", + "comment": "", + "command": "click", + "target": "id=enable-btn", + "targets": [ + ["id=enable-btn", "id"], + ["linkText=Enable Metadata Sources1", "linkText"], + ["css=#enable-btn", "css:finder"], + ["xpath=//a[@id='enable-btn']", "xpath:attributes"], + ["xpath=//div[@id='root']/div/main/div/section/div/div[2]/div/div/a", "xpath:idRelative"], + ["xpath=//a[contains(@href, '/dashboard/admin/actions/enable')]", "xpath:href"], + ["xpath=//div[2]/div/div/a", "xpath:position"], + ["xpath=//a[contains(.,'Enable Metadata Sources1')]", "xpath:innerText"] + ], + "value": "" }, { "id": "b1a8c4b1-d164-4f32-adb3-6cfb76951f28", "comment": "", diff --git a/backend/src/integration/resources/SHIBUI-1503-2.side b/backend/src/integration/resources/SHIBUI-1503-2.side index 65ccf98a9..febe7bd02 100644 --- a/backend/src/integration/resources/SHIBUI-1503-2.side +++ b/backend/src/integration/resources/SHIBUI-1503-2.side @@ -101,6 +101,22 @@ "target": "css=.nav-item > .d-flex", "targets": [], "value": "" + }, { + "id": "1f9a1cc1-00d5-4ecd-a0d1-3cec1683286d", + "comment": "", + "command": "click", + "target": "id=user-access-btn", + "targets": [ + ["id=user-access-btn", "id"], + ["linkText=User Access Request2", "linkText"], + ["css=#user-access-btn", "css:finder"], + ["xpath=//a[@id='user-access-btn']", "xpath:attributes"], + ["xpath=//div[@id='root']/div/main/div/section/div/div[2]/div/div[3]/a", "xpath:idRelative"], + ["xpath=//a[contains(@href, '/dashboard/admin/actions/useraccess')]", "xpath:href"], + ["xpath=//div[2]/div/div[3]/a", "xpath:position"], + ["xpath=//a[contains(.,'User Access Request2')]", "xpath:innerText"] + ], + "value": "" }, { "id": "a98143b5-647f-4e7e-b920-f6e6875d7372", "comment": "", diff --git a/backend/src/integration/resources/SHIBUI-1503-3.side b/backend/src/integration/resources/SHIBUI-1503-3.side index 60250abbd..3eaaf09e2 100644 --- a/backend/src/integration/resources/SHIBUI-1503-3.side +++ b/backend/src/integration/resources/SHIBUI-1503-3.side @@ -102,14 +102,14 @@ "id": "659e4909-239b-4895-aa54-8bf3a6bd57cd", "comment": "", "command": "waitForElementVisible", - "target": "xpath=//table/tbody/tr[td[.='none2']]", + "target": "xpath=//table/tbody/tr[td[.='Approver']]", "targets": [], "value": "30000" }, { "id": "dc06ff49-c076-4f60-95d1-a42514cc6038", "comment": "", "command": "select", - "target": "xpath=//table/tbody/tr[td[.='none2']]/td[4]/select", + "target": "xpath=//table/tbody/tr[td[.='Approver']]/td[4]/select", "targets": [], "value": "label=ROLE_USER" }, { @@ -168,7 +168,7 @@ ["xpath=//input[@name='username']", "xpath:attributes"], ["xpath=//input", "xpath:position"] ], - "value": "none2" + "value": "Approver" }, { "id": "c8bf8ea5-1f75-4a40-aca4-9dfa6a6056dc", "comment": "", @@ -180,7 +180,7 @@ ["xpath=//input[@name='password']", "xpath:attributes"], ["xpath=//tr[2]/td[2]/input", "xpath:position"] ], - "value": "none2pass" + "value": "password" }, { "id": "ba66c45f-2436-4fe7-a5a9-31b55ffe8118", "comment": "", @@ -214,21 +214,21 @@ ["xpath=//a[contains(.,'Metadata Sources')]", "xpath:innerText"] ], "value": "Metadata Sources" - },{ - "id": "4ec2c493-85e4-403b-9b09-031c5728f498", - "comment": "", - "command": "open", - "target": "/api/heheheheheheheWipeout", - "targets": [], - "value": "" - }, { - "id": "e074980a-8f21-4c22-8412-c4b6fcdcd1a4", - "comment": "", - "command": "assertText", - "target": "css=body", - "targets": [], - "value": "yes, you did it" - }] + }, { + "id": "4ec2c493-85e4-403b-9b09-031c5728f498", + "comment": "", + "command": "open", + "target": "/api/heheheheheheheWipeout", + "targets": [], + "value": "" + }, { + "id": "e074980a-8f21-4c22-8412-c4b6fcdcd1a4", + "comment": "", + "command": "assertText", + "target": "css=body", + "targets": [], + "value": "yes, you did it" + }] }], "suites": [{ "id": "173aaf44-c763-416e-ab3c-d5afd5ffcd29", diff --git a/backend/src/integration/resources/SHIBUI-1674-2.side b/backend/src/integration/resources/SHIBUI-1674-2.side index 7e24d99e2..725b2dee1 100644 --- a/backend/src/integration/resources/SHIBUI-1674-2.side +++ b/backend/src/integration/resources/SHIBUI-1674-2.side @@ -984,9 +984,12 @@ "id": "aea0e033-111e-4a5d-8038-ec222786a695", "comment": "", "command": "mouseOver", - "target": "css=.row:nth-child(4) .svg-inline--fa:nth-child(2)", + "target": "css=#root_metadataFilters_2_removeEmptyEntitiesDescriptors-group .info-icon", "targets": [ - ["css=.row:nth-child(4) .svg-inline--fa:nth-child(2)", "css:finder"] + ["css=#root_metadataFilters_2_removeEmptyEntitiesDescriptors-group .info-icon", "css:finder"], + ["xpath=(//button[@type='button'])[13]", "xpath:attributes"], + ["xpath=//div[@id='root_metadataFilters_2_removeEmptyEntitiesDescriptors-group']/div/div/div/label/span/button", "xpath:idRelative"], + ["xpath=//div[4]/div/div/div/div/div/label/span/button", "xpath:position"] ], "value": "" }, { @@ -994,7 +997,9 @@ "comment": "", "command": "assertText", "target": "css=div[role=\"tooltip\"]", - "targets": [], + "targets": [ + ["css=#root_metadataFilters_2_removeRolelessEntityDescriptors-group path", "css:finder"] + ], "value": "Controls whether to keep entities descriptors that contain no entity descriptors. Note: If this attribute is set to false, the resulting output may not be schema-valid since an element must include at least one child element, either an element or an element." }, { "id": "148a84ef-0353-425d-9a63-79ccaa01478d", @@ -1337,7 +1342,7 @@ "command": "assertText", "target": "css=div[role=\"tooltip\"]", "targets": [], - "value": "Whether to remove any existing formats from a role if any are added by the filter (unmodified roles will be untouched regardless of this setting)" + "value": "Whether to use the SHA1 Signing Algorithm. In cryptography, SHA-1 (Secure Hash Algorithm 1) is cryptographically broken but still widely used. It takes an input and produces a 160-bit (20-byte) hash value." }, { "id": "59d268fc-f9ba-4c9c-b412-f17ca72b67d1", "comment": "", diff --git a/backend/src/integration/resources/SHIBUI-1740-1.side b/backend/src/integration/resources/SHIBUI-1740-1.side index f2b1ed457..776bc06ea 100644 --- a/backend/src/integration/resources/SHIBUI-1740-1.side +++ b/backend/src/integration/resources/SHIBUI-1740-1.side @@ -59,6 +59,27 @@ "target": "id=advanced-nav-dropdown-toggle", "targets": [], "value": "30000" + }, { + "id": "dc2bc3fa-631d-43be-b9b9-d92bcf1619ec", + "comment": "", + "command": "open", + "target": "/api/heheheheheheheWipeout", + "targets": [], + "value": "" + }, { + "id": "e3ef0c57-5d19-4f25-b1b3-63f1520fbf07", + "comment": "", + "command": "assertText", + "target": "css=body", + "targets": [], + "value": "yes, you did it" + }, { + "id": "7a7878a7-4258-42b9-b3d9-6b67d582faa6", + "comment": "", + "command": "open", + "target": "/", + "targets": [], + "value": "" }, { "id": "0bdcd2aa-3e9e-41be-96d2-abf567538990", "comment": "", @@ -186,9 +207,9 @@ "id": "62b9e743-cc16-4931-9064-06c15b057318", "comment": "", "command": "click", - "target": "xpath=//td[contains(.,'Test Group')]/parent::*/td[3]/a", + "target": "xpath=//td[contains(.,'Test Group')]/parent::*/td[4]/a", "targets": [ - ["css=tr:nth-child(6) .text-primary path", "css:finder"] + ["css=tr:nth-child(10) .text-primary path", "css:finder"] ], "value": "" }, { @@ -279,7 +300,7 @@ "id": "3f463655-29df-4c52-bc53-b96b60b845fd", "comment": "", "command": "click", - "target": "xpath=//td[contains(.,'Test Group')]/parent::*/td[3]/button", + "target": "xpath=//td[contains(.,'Test Group')]/parent::*/td[4]/button", "targets": [ ["css=tr:nth-child(6) .text-danger > .svg-inline--fa", "css:finder"] ], @@ -302,21 +323,21 @@ "target": "xpath=//li[contains(.,'Deleted group successfully.')]", "targets": [], "value": "Deleted group successfully." - },{ - "id": "4ec2c493-85e4-403b-9b09-031c5728f498", - "comment": "", - "command": "open", - "target": "/api/heheheheheheheWipeout", - "targets": [], - "value": "" - }, { - "id": "e074980a-8f21-4c22-8412-c4b6fcdcd1a4", - "comment": "", - "command": "assertText", - "target": "css=body", - "targets": [], - "value": "yes, you did it" - }] + }, { + "id": "4ec2c493-85e4-403b-9b09-031c5728f498", + "comment": "", + "command": "open", + "target": "/api/heheheheheheheWipeout", + "targets": [], + "value": "" + }, { + "id": "e074980a-8f21-4c22-8412-c4b6fcdcd1a4", + "comment": "", + "command": "assertText", + "target": "css=body", + "targets": [], + "value": "yes, you did it" + }] }], "suites": [{ "id": "bb170239-568b-4e90-991e-1a5882465aaa", diff --git a/backend/src/integration/resources/SHIBUI-1740-2.side b/backend/src/integration/resources/SHIBUI-1740-2.side index c1a7fedd5..196be44df 100644 --- a/backend/src/integration/resources/SHIBUI-1740-2.side +++ b/backend/src/integration/resources/SHIBUI-1740-2.side @@ -99,11 +99,11 @@ "id": "bfae23fa-f6ab-4cb1-a056-e16e309194d2", "comment": "", "command": "assertText", - "target": "css=tr:nth-child(4) > td:nth-child(1)", + "target": "css=tr:nth-child(7) > td:nth-child(1)", "targets": [ - ["css=tr:nth-child(4) > td:nth-child(1)", "css:finder"], - ["xpath=//div[@id='root']/div/main/div/section/div/div[2]/div[2]/table/tbody/tr[4]/td", "xpath:idRelative"], - ["xpath=//tr[4]/td", "xpath:position"], + ["css=tr:nth-child(7) > td:nth-child(1)", "css:finder"], + ["xpath=//div[@id='root']/div/main/div/section/div/div[2]/div[2]/table/tbody/tr[7]/td", "xpath:idRelative"], + ["xpath=//tr[7]/td", "xpath:position"], ["xpath=//td[contains(.,'nonadmin')]", "xpath:innerText"] ], "value": "nonadmin" @@ -116,9 +116,9 @@ ["css=tr:nth-child(5) > td:nth-child(1)", "css:finder"], ["xpath=//div[@id='root']/div/main/div/section/div/div[2]/div[2]/table/tbody/tr[5]/td", "xpath:idRelative"], ["xpath=//tr[5]/td", "xpath:position"], - ["xpath=//td[contains(.,'none')]", "xpath:innerText"] + ["xpath=//td[contains(.,'YYY')]", "xpath:innerText"] ], - "value": "none" + "value": "YYY" }, { "id": "346cec03-bbdd-435b-bdf4-ffaffd159b12", "comment": "", @@ -130,16 +130,17 @@ ["xpath=//tr[4]/td[2]", "xpath:position"], ["xpath=//td[contains(.,'default user-group')]", "xpath:innerText"] ], - "value": "default user-group" + "value": "XXX" }, { "id": "9fb41858-7d3d-4454-a07b-8a88ce2d726e", "comment": "", "command": "assertText", - "target": "css=tr:nth-child(5) > td:nth-child(2)", + "target": "css=tr:nth-child(7) > td:nth-child(2)", "targets": [ - ["css=tr:nth-child(5) > td:nth-child(2)", "css:finder"], - ["xpath=//div[@id='root']/div/main/div/section/div/div[2]/div[2]/table/tbody/tr[5]/td[2]", "xpath:idRelative"], - ["xpath=//tr[5]/td[2]", "xpath:position"] + ["css=tr:nth-child(7) > td:nth-child(2)", "css:finder"], + ["xpath=//div[@id='root']/div/main/div/section/div/div[2]/div[2]/table/tbody/tr[7]/td[2]", "xpath:idRelative"], + ["xpath=//tr[7]/td[2]", "xpath:position"], + ["xpath=//td[contains(.,'default user-group')]", "xpath:innerText"] ], "value": "default user-group" }, { @@ -211,43 +212,57 @@ "id": "70bb9b86-12d0-493e-815a-9fae0ea6e2d3", "comment": "", "command": "assertValue", - "target": "id=role-none", + "target": "id=role-Approver", "targets": [ - ["id=role-none", "id"], - ["name=role-none", "name"], - ["css=#role-none", "css:finder"], - ["xpath=//select[@id='role-none']", "xpath:attributes"], - ["xpath=//div[@id='root']/div/main/div/section/div/div[2]/div/div/table/tbody/tr[3]/td[3]/select", "xpath:idRelative"], - ["xpath=//tr[3]/td[3]/select", "xpath:position"] + ["id=role-Approver", "id"], + ["name=role-Approver", "name"], + ["css=#role-Approver", "css:finder"], + ["xpath=//select[@id='role-Approver']", "xpath:attributes"], + ["xpath=//div[@id='root']/div/main/div/section/div/div[2]/div/div/table/tbody/tr[3]/td[4]/select", "xpath:idRelative"], + ["xpath=//tr[3]/td[4]/select", "xpath:position"] ], "value": "ROLE_NONE" }, { "id": "3cdd6916-5fad-4fb7-a422-b10cc38f1fb2", "comment": "", "command": "assertEditable", - "target": "id=role-none", - "targets": [], + "target": "id=role-Approver", + "targets": [ + ["id=role-Approver", "id"], + ["name=role-Approver", "name"], + ["css=#role-Approver", "css:finder"], + ["xpath=//select[@id='role-Approver']", "xpath:attributes"], + ["xpath=//div[@id='root']/div/main/div/section/div/div[2]/div/div/table/tbody/tr[3]/td[4]/select", "xpath:idRelative"], + ["xpath=//tr[3]/td[4]/select", "xpath:position"] + ], "value": "" }, { "id": "d90dd945-c2f4-428c-8bb9-8557a1a93ef9", "comment": "", "command": "assertValue", - "target": "id=group-none", + "target": "id=group-Approver", "targets": [ - ["id=group-none", "id"], - ["name=group-none", "name"], - ["css=#group-none", "css:finder"], - ["xpath=//select[@id='group-none']", "xpath:attributes"], - ["xpath=//div[@id='root']/div/main/div/section/div/div[2]/div/div/table/tbody/tr[3]/td[4]/span/select", "xpath:idRelative"], - ["xpath=//tr[3]/td[4]/span/select", "xpath:position"] + ["id=group-Approver", "id"], + ["name=group-Approver", "name"], + ["css=#group-Approver", "css:finder"], + ["xpath=//select[@id='group-Approver']", "xpath:attributes"], + ["xpath=//div[@id='root']/div/main/div/section/div/div[2]/div/div/table/tbody/tr[3]/td[5]/span/select", "xpath:idRelative"], + ["xpath=//tr[3]/td[5]/span/select", "xpath:position"] ], - "value": "none" + "value": "Approver" }, { "id": "d6d6cae9-acb7-46de-9ec7-0a76a8f6b61e", "comment": "", "command": "assertEditable", - "target": "id=group-none", - "targets": [], + "target": "id=group-Approver", + "targets": [ + ["id=group-Approver", "id"], + ["name=group-Approver", "name"], + ["css=#group-Approver", "css:finder"], + ["xpath=//select[@id='group-Approver']", "xpath:attributes"], + ["xpath=//div[@id='root']/div/main/div/section/div/div[2]/div/div/table/tbody/tr[3]/td[5]/span/select", "xpath:idRelative"], + ["xpath=//tr[3]/td[5]/span/select", "xpath:position"] + ], "value": "" }, { "id": "7d9099ef-bb52-4abd-b6b4-5ed70fdc56db", @@ -298,21 +313,21 @@ ["xpath=//tr[4]/td[4]/span/select", "xpath:position"] ], "value": "" - },{ - "id": "4ec2c493-85e4-403b-9b09-031c5728f498", - "comment": "", - "command": "open", - "target": "/api/heheheheheheheWipeout", - "targets": [], - "value": "" - }, { - "id": "e074980a-8f21-4c22-8412-c4b6fcdcd1a4", - "comment": "", - "command": "assertText", - "target": "css=body", - "targets": [], - "value": "yes, you did it" - }] + }, { + "id": "4ec2c493-85e4-403b-9b09-031c5728f498", + "comment": "", + "command": "open", + "target": "/api/heheheheheheheWipeout", + "targets": [], + "value": "" + }, { + "id": "e074980a-8f21-4c22-8412-c4b6fcdcd1a4", + "comment": "", + "command": "assertText", + "target": "css=body", + "targets": [], + "value": "yes, you did it" + }] }], "suites": [{ "id": "16a0d393-c257-46c4-9a0f-dc2a84dfc23b", diff --git a/backend/src/integration/resources/SHIBUI-1743-2.side b/backend/src/integration/resources/SHIBUI-1743-2.side index 88fd04ec4..95930e493 100644 --- a/backend/src/integration/resources/SHIBUI-1743-2.side +++ b/backend/src/integration/resources/SHIBUI-1743-2.side @@ -126,9 +126,9 @@ "id": "a9865fff-afe0-4786-8a32-18eb1f920424", "comment": "", "command": "click", - "target": "css=tr:nth-child(4) .text-primary path", + "target": "css=tr:nth-child(7) .text-primary path", "targets": [ - ["css=tr:nth-child(4) .text-primary path", "css:finder"] + ["css=tr:nth-child(7) .text-primary path", "css:finder"] ], "value": "" }, { From e88aad32b689e341a2581a11fb628476bbf39320 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Wed, 9 Nov 2022 14:08:57 -0700 Subject: [PATCH 51/56] Fixed button toggle issue --- ui/src/app/dashboard/view/ProvidersTab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/dashboard/view/ProvidersTab.js b/ui/src/app/dashboard/view/ProvidersTab.js index d986f3f68..a8627ab5d 100644 --- a/ui/src/app/dashboard/view/ProvidersTab.js +++ b/ui/src/app/dashboard/view/ProvidersTab.js @@ -47,7 +47,7 @@ export function ProvidersTab () { {(searched) => - {(enable) => + {({enable}) => Date: Wed, 9 Nov 2022 16:18:18 -0700 Subject: [PATCH 52/56] SHIBUI-2394 Fixes for websecurity not liking semicolons --- .../admin/ui/configuration/auto/WebSecurityConfig.java | 1 + .../src/main/java/net/unicon/shibui/pac4j/WebSecurity.java | 1 + 2 files changed, 2 insertions(+) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java index f04f2f716..93491c72e 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java @@ -67,6 +67,7 @@ private HttpFirewall allowUrlEncodedSlashHttpFirewall() { StrictHttpFirewall firewall = new StrictHttpFirewall(); firewall.setAllowUrlEncodedSlash(true); firewall.setAllowUrlEncodedDoubleSlash(true); + firewall.setAllowSemicolon(true); return firewall; } diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java index cc5ce8e25..2c6ba0099 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java @@ -116,6 +116,7 @@ public void configure(org.springframework.security.config.annotation.web.builder StrictHttpFirewall firewall = new StrictHttpFirewall(); firewall.setAllowUrlEncodedSlash(true); firewall.setAllowUrlEncodedDoubleSlash(true); + firewall.setAllowSemicolon(true); web.httpFirewall(firewall); // These don't need to be secured From c2062a668e3b0b9ca93969bb925f2614f5820e03 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Thu, 10 Nov 2022 16:27:23 -0700 Subject: [PATCH 53/56] SHIBUI-2452 "unlocking" the MDQ endpoint --- .../admin/ui/configuration/auto/WebSecurityConfig.java | 2 +- .../src/main/java/net/unicon/shibui/pac4j/WebSecurity.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java index 93491c72e..f2135109e 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java @@ -88,7 +88,7 @@ protected void configure(HttpSecurity http) throws Exception { .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .and() .authorizeRequests() - .antMatchers("/unsecured/**/*").permitAll() + .antMatchers("/unsecured/**/*","/entities/**/*").permitAll() .anyRequest().hasAnyRole(acceptedAuthenticationRoles) .and() .exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> response.sendRedirect("/unsecured/error.html")) diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java index 2c6ba0099..afc7ae437 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java @@ -64,7 +64,7 @@ public Pac4jWebSecurityConfigurerAdapter(final Config config, UserService userSe @Override protected void configure(HttpSecurity http) throws Exception { - http.authorizeRequests().antMatchers("/unsecured/**/*").permitAll(); + http.authorizeRequests().antMatchers("/unsecured/**/*","/entities/**/*").permitAll(); // adding the authorizer bypasses the default behavior of checking CSRF in Pac4J's default securitylogic+defaultauthorizationchecker final SecurityFilter securityFilter = new SecurityFilter(this.config, PAC4J_CLIENT_NAME, DefaultAuthorizers.IS_AUTHENTICATED); @@ -120,7 +120,7 @@ public void configure(org.springframework.security.config.annotation.web.builder web.httpFirewall(firewall); // These don't need to be secured - web.ignoring().antMatchers("/favicon.ico", "/unsecured/**/*", "/assets/**/*.png", "/static/**/*", "/**/*.css"); + web.ignoring().antMatchers("/favicon.ico", "/unsecured/**/*", "/assets/**/*.png", "/static/**/*", "/**/*.css", "/entities/**/*"); } } From 4e4b78bc0d27ac393ba15cbc7827a6ea2005efd5 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 14 Nov 2022 09:54:27 -0700 Subject: [PATCH 54/56] Removed guid from ids --- ui/src/app/metadata/domain/source/component/SourceList.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ui/src/app/metadata/domain/source/component/SourceList.js b/ui/src/app/metadata/domain/source/component/SourceList.js index e43791a67..b2961eb48 100644 --- a/ui/src/app/metadata/domain/source/component/SourceList.js +++ b/ui/src/app/metadata/domain/source/component/SourceList.js @@ -70,7 +70,7 @@ export default function SourceList({ entities, onDelete, onEnable, onApprove, on {onApprove ? onChangeGroup(source, event.target.value)} value={source.idOfOwner ? source.idOfOwner : ''} @@ -135,6 +135,7 @@ export default function SourceList({ entities, onDelete, onEnable, onApprove, on {group.description || ''} {group.approversList?.length > 0 ? group.approversList[0].approverGroupIds.join(', ') : '-'} - + Edit -