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 49c9e0d90..eaddb0d3d 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 @@ -2,11 +2,15 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.exceptions.MetadataFileNotFoundException; import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter; +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.DynamicRegistrationRepresentation; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation; +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo; import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver; 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.exception.UnsupportedShibUiOperationException; +import edu.internet2.tier.shibboleth.admin.ui.service.DynamicRegistrationService; import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService; import edu.internet2.tier.shibboleth.admin.ui.service.FilterService; import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverService; @@ -26,6 +30,8 @@ @RequestMapping("/api/activate") @Tags(value = {@Tag(name = "activate")}) public class ActivateController { + @Autowired + private DynamicRegistrationService dynamicRegistrationService; @Autowired private EntityDescriptorService entityDescriptorService; @@ -36,6 +42,16 @@ public class ActivateController { @Autowired private MetadataResolverService metadataResolverService; + @PatchMapping(path = "/DynamicRegistration/{resourceId}/{mode}") + @Transactional + public ResponseEntity enableDynamicRegistration(@PathVariable String resourceId, @PathVariable String mode) throws PersistentEntityNotFound, ForbiddenException, UnsupportedShibUiOperationException { + if ("enable".equalsIgnoreCase(mode)) { + DynamicRegistrationRepresentation drr = dynamicRegistrationService.enableDynamicRegistration(resourceId); + return ResponseEntity.ok(drr); + } + throw new UnsupportedShibUiOperationException("Disable is not a valid operation for Dynamic Registrations at this time"); + } + @PatchMapping(path = "/entityDescriptor/{resourceId}/{mode}") @Transactional public ResponseEntity enableEntityDescriptor(@PathVariable String resourceId, @PathVariable String mode) throws PersistentEntityNotFound, ForbiddenException { @@ -43,7 +59,7 @@ public ResponseEntity enableEntityDescriptor(@PathVariable String resourceId, EntityDescriptorRepresentation edr = entityDescriptorService.updateEntityDescriptorEnabledStatus(resourceId, status); return ResponseEntity.ok(edr); } - + @PatchMapping(path = "/MetadataResolvers/{metadataResolverId}/Filter/{resourceId}/{mode}") @Transactional public ResponseEntity enableFilter(@PathVariable String metadataResolverId, @PathVariable String resourceId, @PathVariable String mode) throws PersistentEntityNotFound, ForbiddenException, ScriptException { 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 32bf84afb..e11c2c827 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 @@ -1,8 +1,11 @@ package edu.internet2.tier.shibboleth.admin.ui.controller; +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.DynamicRegistrationRepresentation; 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.exception.UnsupportedShibUiOperationException; +import edu.internet2.tier.shibboleth.admin.ui.service.DynamicRegistrationService; 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; @@ -18,9 +21,20 @@ @RequestMapping("/api/approve") @Tags(value = {@Tag(name = "approve")}) public class ApprovalController { + @Autowired + private DynamicRegistrationService dynamicRegistrationService; + @Autowired private EntityDescriptorService entityDescriptorService; + @PatchMapping(path = "/DynamicRegistration/{resourceId}/{mode}") + @Transactional + public ResponseEntity approveDynamicRegistration(@PathVariable String resourceId, @PathVariable String mode) throws PersistentEntityNotFound, ForbiddenException { + boolean status = "approve".equalsIgnoreCase(mode); + DynamicRegistrationRepresentation drr = dynamicRegistrationService.approveDynamicRegistration(resourceId, status); + return ResponseEntity.ok(drr); + } + @PatchMapping(path = "/entityDescriptor/{resourceId}/{mode}") @Transactional public ResponseEntity approveEntityDescriptor(@PathVariable String resourceId, @PathVariable String mode) throws PersistentEntityNotFound, ForbiddenException { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveAndActivateExceptionHandler.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveAndActivateExceptionHandler.java index 0c8efc28f..48436eba5 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveAndActivateExceptionHandler.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveAndActivateExceptionHandler.java @@ -4,6 +4,7 @@ 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.exception.UnsupportedShibUiOperationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -42,6 +43,9 @@ public ResponseEntity handleMetadataFileNotFoundException(MetadataFileNotFoun public ResponseEntity handleScriptException(ScriptException e, WebRequest request) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ErrorResponse(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()), e.getMessage())); } - - + + @ExceptionHandler({ UnsupportedShibUiOperationException.class }) + public ResponseEntity handleUnsupportedShibUiOperationException(UnsupportedShibUiOperationException e, WebRequest request) { + return ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED).body(new ErrorResponse(String.valueOf(HttpStatus.NOT_IMPLEMENTED.value()), e.getMessage())); + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/DynamicRegistrationInfo.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/DynamicRegistrationInfo.java index 5bc8e6e34..7ad3198dc 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/DynamicRegistrationInfo.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/DynamicRegistrationInfo.java @@ -4,6 +4,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.ActivatableType; 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.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.Data; @@ -79,4 +80,8 @@ public void removeLastApproval() { public int approvedCount() { return approvedBy.size(); } + + public void addApproval(Group currentUserGroup) { + approvedBy.add(currentUserGroup.getName()); + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/UnsupportedShibUiOperationException.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/UnsupportedShibUiOperationException.java new file mode 100644 index 000000000..e90508131 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/UnsupportedShibUiOperationException.java @@ -0,0 +1,11 @@ +package edu.internet2.tier.shibboleth.admin.ui.exception; + +public class UnsupportedShibUiOperationException extends Exception { + public UnsupportedShibUiOperationException() { + super("Operation unsupport in ShibUI at this time"); + } + + public UnsupportedShibUiOperationException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/DynamicRegistrationService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/DynamicRegistrationService.java index 1026b8399..bb4fe2cbf 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/DynamicRegistrationService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/DynamicRegistrationService.java @@ -6,15 +6,18 @@ import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound; public interface DynamicRegistrationService { - Object getAllDynamicRegistrationsBasedOnUserAccess() throws ForbiddenException; + DynamicRegistrationRepresentation approveDynamicRegistration(String resourceId, boolean status) + throws PersistentEntityNotFound, ForbiddenException; DynamicRegistrationRepresentation createNew(DynamicRegistrationRepresentation dynRegRepresentation) throws ObjectIdExistsException; void delete(String resourceId) throws ForbiddenException, PersistentEntityNotFound; - DynamicRegistrationRepresentation update(DynamicRegistrationRepresentation dynRegRepresentation) - throws PersistentEntityNotFound, ForbiddenException; + DynamicRegistrationRepresentation enableDynamicRegistration(String resourceId) throws PersistentEntityNotFound, ForbiddenException; + + Object getAllDynamicRegistrationsBasedOnUserAccess() throws ForbiddenException; + + DynamicRegistrationRepresentation update(DynamicRegistrationRepresentation dynRegRepresentation) throws PersistentEntityNotFound, ForbiddenException; - DynamicRegistrationRepresentation updateGroupForDynamicRegistration(String resourceId, String groupId) - throws ForbiddenException, PersistentEntityNotFound; + DynamicRegistrationRepresentation updateGroupForDynamicRegistration(String resourceId, String groupId) throws ForbiddenException, PersistentEntityNotFound; } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPADynamicRegistrationServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPADynamicRegistrationServiceImpl.java index 0f8bd7ab4..6da71ff2c 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPADynamicRegistrationServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPADynamicRegistrationServiceImpl.java @@ -2,10 +2,12 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.DynamicRegistrationRepresentation; +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation; import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo; import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; 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.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; @@ -47,6 +49,36 @@ public class JPADynamicRegistrationServiceImpl implements DynamicRegistrationSer @Autowired UserService userService; + @Override + public DynamicRegistrationRepresentation approveDynamicRegistration(String resourceId, boolean status) throws PersistentEntityNotFound, ForbiddenException { + DynamicRegistrationInfo dri = repository.findByResourceId(resourceId); + if (dri == null) { + throw new PersistentEntityNotFound("Dynamic Registration with resourceid[ " + resourceId + " ] was not found for approval"); + } + return changeApproveStatusOfDynamicRepresentation(dri, status); + } + + private DynamicRegistrationRepresentation changeApproveStatusOfDynamicRepresentation(DynamicRegistrationInfo dri, boolean status) throws ForbiddenException { + if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), dri, PermissionType.approve)) { + throw new ForbiddenException("You do not have the permissions necessary to approve this dynamic registration."); + } + if (status) { // approve + int approvedCount = dri.approvedCount(); // total number of approvals so far + List theApprovers = groupService.find(dri.getIdOfOwner()).getApproversList(); + if (theApprovers.size() > approvedCount) { // don't add if we already have enough approvals + dri.addApproval(userService.getCurrentUserGroup()); + } + dri.setApproved(dri.approvedCount() >= theApprovers.size()); // future check for multiple approvals needed + dri = repository.save(dri); + } else { // un-approve + dri.removeLastApproval(); + Group ownerGroup = groupService.find(dri.getIdOfOwner()); + dri.setApproved(dri.approvedCount() >= ownerGroup.getApproversList().size()); // safe check in case of weird race conditions from the UI + dri = repository.save(dri); + } + return new DynamicRegistrationRepresentation(dri); + } + @Override public DynamicRegistrationRepresentation createNew(DynamicRegistrationRepresentation dynRegRepresentation) throws ObjectIdExistsException { if (entityExists(dynRegRepresentation.getResourceId())) { @@ -76,16 +108,28 @@ public void delete(String resourceId) throws ForbiddenException, PersistentEntit if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), null, PermissionType.admin)) { throw new ForbiddenException("Deleting a Dynamic Registration Source is only allowed by an admin."); } - - DynamicRegistrationInfo ed = repository.findByResourceId(resourceId); - if (ed==null) { + DynamicRegistrationInfo dri = repository.findByResourceId(resourceId); + if (dri == null) { throw new PersistentEntityNotFound("Dynamic Registration not found for resource id: " + resourceId); } - if (ed.isEnabled()) { + if (dri.isEnabled()) { throw new ForbiddenException("Deleting an enabled Dynamic Registration Source is not allowed."); } - ownershipRepository.deleteEntriesForOwnedObject(ed); - repository.delete(ed); + ownershipRepository.deleteEntriesForOwnedObject(dri); + repository.delete(dri); + } + + @Override + public DynamicRegistrationRepresentation enableDynamicRegistration(String resourceId) throws PersistentEntityNotFound, ForbiddenException { + DynamicRegistrationInfo existingDri = repository.findByResourceId(resourceId); + if (existingDri == null) { + throw new PersistentEntityNotFound(String.format("The dynamic registration with id [%s] was not found for update.", existingDri.getResourceId())); + } + if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), existingDri, PermissionType.enable)) { + throw new ForbiddenException("You do not have the permissions necessary to enable this service"); + } + // TODO do something... + return new DynamicRegistrationRepresentation(existingDri); } private boolean entityExists(String id) { @@ -102,7 +146,7 @@ public DynamicRegistrationRepresentation update(DynamicRegistrationRepresentatio throws PersistentEntityNotFound, ForbiddenException, ConcurrentModificationException { DynamicRegistrationInfo existingDri = repository.findByResourceId(dynRegRepresentation.getResourceId()); if (existingDri == null) { - throw new PersistentEntityNotFound(String.format("The dynamic registration with entity id [%s] was not found for update.", existingDri.getResourceId())); + throw new PersistentEntityNotFound(String.format("The dynamic registration with id [%s] was not found for update.", existingDri.getResourceId())); } if (dynRegRepresentation.isEnabled() && !shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), existingDri, PermissionType.enable)) { throw new ForbiddenException("You do not have the permissions necessary to enable this service."); @@ -132,7 +176,7 @@ public DynamicRegistrationRepresentation update(DynamicRegistrationRepresentatio public DynamicRegistrationRepresentation updateGroupForDynamicRegistration(String resourceId, String groupId) throws ForbiddenException, PersistentEntityNotFound { DynamicRegistrationInfo existingDri = repository.findByResourceId(resourceId); if (existingDri == null) { - throw new PersistentEntityNotFound(String.format("The dynamic registration with entity id [%s] was not found for update.", existingDri.getResourceId())); + throw new PersistentEntityNotFound(String.format("The dynamic registration with id [%s] was not found for update.", existingDri.getResourceId())); } if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), existingDri, PermissionType.admin)) { throw new ForbiddenException("You do not have the permissions necessary to change the group for this service."); 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 index 8dfd0cf29..ea407a5b9 100644 --- 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 @@ -3,6 +3,9 @@ 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.domain.frontend.DynamicRegistrationRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.GrantType 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 @@ -10,6 +13,8 @@ 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.DynamicRegistrationInfoRepository +import edu.internet2.tier.shibboleth.admin.ui.service.DynamicRegistrationService 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 @@ -32,6 +37,12 @@ class ActivateControllerTests extends AbstractBaseDataJpaTest { @Autowired ObjectMapper mapper + @Autowired + DynamicRegistrationInfoRepository dynamicRegistrationInfoRepository + + @Autowired + DynamicRegistrationService dynamicRegistrationService + @Autowired EntityService entityService @@ -51,6 +62,7 @@ class ActivateControllerTests extends AbstractBaseDataJpaTest { def setup() { controller = new ActivateController() controller.entityDescriptorService = entDescriptorService + controller.dynamicRegistrationService = dynamicRegistrationService mockMvc = MockMvcBuilders.standaloneSetup(controller).build() EntityDescriptorConversionUtils.setOpenSamlObjects(openSamlObjects) @@ -105,6 +117,23 @@ class ActivateControllerTests extends AbstractBaseDataJpaTest { entityDescriptor = entityDescriptorRepository.save(entityDescriptor) defaultEntityDescriptorResourceId = entityDescriptor.getResourceId() + + def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-1', enabled: false, idOfOwner: "AAA", applicationType: 'apptype', + approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', + redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', + tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) + dynamicRegistrationInfoRepository.saveAndFlush(dynReg) + } + + @WithMockUser(value = "AUser", roles = ["USER"]) + def 'Owner group cannot activate their own dynamic registration without approvals'() { + expect: + try { + mockMvc.perform(patch("/api/activate/DynamicRegistration/uuid-1/enable")) + } + catch (Exception e) { + e instanceof ForbiddenException + } } @WithMockUser(value = "AUser", roles = ["USER"]) @@ -118,6 +147,17 @@ class ActivateControllerTests extends AbstractBaseDataJpaTest { } } + @WithMockUser(value = "DUser", roles = ["USER"]) + def 'non-owner group cannot activate dynamic registration'() { + expect: + try { + mockMvc.perform(patch("/api/activate/DynamicRegistration/uuid-1/enable")) + } + catch (Exception e) { + e instanceof ForbiddenException + } + } + @WithMockUser(value = "DUser", roles = ["USER"]) def 'non-owner group cannot activate entity descriptor'() { expect: @@ -129,6 +169,24 @@ class ActivateControllerTests extends AbstractBaseDataJpaTest { } } +// @WithMockAdmin +// def 'Admin can activate an dynamic registration without approval'() { +// given: +// def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-2', enabled: false, applicationType: 'apptype', +// approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', +// redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', +// tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) +// dynamicRegistrationService.createNew(new DynamicRegistrationRepresentation(dynReg)) +// +// when: +// def result = mockMvc.perform(patch("/api/activate/DynamicRegistration/uuid-2/enable")) +// +// then: +// result.andExpect(status().isOk()) +// .andExpect(jsonPath("\$.resourceId").value("uuid-2")) +// .andExpect(jsonPath("\$.enabled").value(true)) +// } + @WithMockAdmin def 'Admin can activate an entity descriptor without approval'() { when: @@ -140,6 +198,26 @@ class ActivateControllerTests extends AbstractBaseDataJpaTest { .andExpect(jsonPath("\$.serviceEnabled").value(true)) } +// @WithMockUser(value = "AUser", roles = ["USER"]) +// def 'Owner group can enable their own dynamic registration with approvals'() { +// when: +// def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-2', enabled: false, idOfOwner: "AAA", applicationType: 'apptype', +// approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', +// redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', +// tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) +// dynReg = dynamicRegistrationService.createNew(dynReg) +// dynReg.addApproval(groupService.find("CCC")) +// dynamicRegistrationService.update(dynReg) +// entityManager.flush() +// +// def result = mockMvc.perform(patch("/api/activate/DynamicRegistration/uuid-2/enable")) +// +// then: +// result.andExpect(status().isOk()) +// .andExpect(jsonPath("\$.resourceId").value('uuid-2')) +// .andExpect(jsonPath("\$.enabled").value(true)) +// } + @WithMockUser(value = "AUser", roles = ["USER"]) def 'Owner group can enable their own entity descriptor with approvals'() { when: 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 eaf337064..b676ef724 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 @@ -3,6 +3,8 @@ 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.domain.oidc.DynamicRegistrationInfo +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.GrantType 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 @@ -10,6 +12,8 @@ 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.DynamicRegistrationInfoRepository +import edu.internet2.tier.shibboleth.admin.ui.service.DynamicRegistrationService 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 @@ -31,6 +35,12 @@ class ApproveControllerTests extends AbstractBaseDataJpaTest { @Autowired ObjectMapper mapper + @Autowired + DynamicRegistrationInfoRepository dynamicRegistrationInfoRepository + + @Autowired + DynamicRegistrationService dynamicRegistrationService + @Autowired EntityService entityService @@ -50,6 +60,7 @@ class ApproveControllerTests extends AbstractBaseDataJpaTest { def setup() { controller = new ApprovalController() controller.entityDescriptorService = entDescriptorService + controller.dynamicRegistrationService = dynamicRegistrationService mockMvc = MockMvcBuilders.standaloneSetup(controller).build() EntityDescriptorConversionUtils.setOpenSamlObjects(openSamlObjects) @@ -104,6 +115,25 @@ class ApproveControllerTests extends AbstractBaseDataJpaTest { entityDescriptor = entityDescriptorRepository.save(entityDescriptor) defaultEntityDescriptorResourceId = entityDescriptor.getResourceId() + + def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-1', enabled: false, idOfOwner: "AAA", applicationType: 'apptype', + approved: false, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', + redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', + tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) + dynamicRegistrationInfoRepository.saveAndFlush(dynReg) + } + + @WithMockUser(value = "AUser", roles = ["USER"]) + def 'Owner group cannot approve their own dynamic registration'() { + expect: + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() == false + try { + mockMvc.perform(patch("/api/approve/DynamicRegistration/uuid-1/approve")) + } + catch (Exception e) { + e instanceof ForbiddenException + } + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() == false } @WithMockUser(value = "AUser", roles = ["USER"]) @@ -119,6 +149,19 @@ class ApproveControllerTests extends AbstractBaseDataJpaTest { entityDescriptorRepository.findByResourceId(defaultEntityDescriptorResourceId).isApproved() == false } + @WithMockUser(value = "DUser", roles = ["USER"]) + def 'non-approver group cannot approve dynamic registration'() { + expect: + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() == false + try { + mockMvc.perform(patch("/api/approve/DynamicRegistration/uuid-1/approve")) + } + catch (Exception e) { + e instanceof ForbiddenException + } + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() == false + } + @WithMockUser(value = "DUser", roles = ["USER"]) def 'non-approver group cannot approve entity descriptor'() { expect: @@ -132,6 +175,22 @@ class ApproveControllerTests extends AbstractBaseDataJpaTest { entityDescriptorRepository.findByResourceId(defaultEntityDescriptorResourceId).isApproved() == false } + @WithMockUser(value = "BUser", roles = ["USER"]) + def 'Approver group can approve an dynamic registration'() { + expect: + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() == false + + when: + def result = mockMvc.perform(patch("/api/approve/DynamicRegistration/uuid-1/approve")) + + then: + result.andExpect(status().isOk()) + .andExpect(jsonPath("\$.resourceId").value("uuid-1")) + .andExpect(jsonPath("\$.enabled").value(false)) + .andExpect(jsonPath("\$.approved").value(true)) + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() + } + @WithMockUser(value = "BUser", roles = ["USER"]) def 'Approver group can approve an entity descriptor'() { expect: @@ -148,6 +207,32 @@ class ApproveControllerTests extends AbstractBaseDataJpaTest { entityDescriptorRepository.findByResourceId(defaultEntityDescriptorResourceId).isApproved() } + @WithMockUser(value = "BUser", roles = ["USER"]) + def 'Approver can approve and un-approve an dynamic registration'() { + expect: + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() == false + + when: + def result = mockMvc.perform(patch("/api/approve/DynamicRegistration/uuid-1/approve")) + + then: + result.andExpect(status().isOk()) + .andExpect(jsonPath("\$.resourceId").value("uuid-1")) + .andExpect(jsonPath("\$.enabled").value(false)) + .andExpect(jsonPath("\$.approved").value(true)) + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() + + when: + def result2 = mockMvc.perform(patch("/api/approve/DynamicRegistration/uuid-1/unapprove")) + + then: + result2.andExpect(status().isOk()) + .andExpect(jsonPath("\$.resourceId").value("uuid-1")) + .andExpect(jsonPath("\$.enabled").value(false)) + .andExpect(jsonPath("\$.approved").value(false)) + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() == false + } + @WithMockUser(value = "BUser", roles = ["USER"]) def 'Approver can approve and un-approve an entity descriptor'() { expect: