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 +});