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 10b0a742b..1a3ecf0a4 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 @@ -1,23 +1,46 @@ package edu.internet2.tier.shibboleth.admin.ui.controller; +import javax.script.ScriptException; + 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; -import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository; -import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; +import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter; +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation; +import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; +import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; +import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService; +import edu.internet2.tier.shibboleth.admin.ui.service.FilterService; @RestController @RequestMapping("/api/activate") public class ActivateController { + @Autowired - private UserService userService; + private EntityDescriptorService entityDescriptorService; @Autowired - private EntityDescriptorRepository entityDescriptorRepo; - - + private FilterService filterService; + @PatchMapping(path = "/entityDescriptor/{resourceId}/{mode}") + @Transactional + public ResponseEntity enableEntityDescriptor(@PathVariable String resourceId, @PathVariable String mode) throws EntityNotFoundException, ForbiddenException { + boolean status = "enable".equalsIgnoreCase(mode); + EntityDescriptorRepresentation edr = entityDescriptorService.updateEntityDescriptorEnabledStatus(resourceId, status); + return ResponseEntity.ok(edr); + } -// Enable/disable for : entity descriptor, provider, filter + @PatchMapping(path = "/MetadataResolvers/{metadataResolverId}/Filter/{resourceId}/{mode}") + @Transactional + public ResponseEntity enableFilter(@PathVariable String metadataResolverId, @PathVariable String resourceId, @PathVariable String mode) throws EntityNotFoundException, ForbiddenException, ScriptException { + boolean status = "enable".equalsIgnoreCase(mode); + MetadataFilter persistedFilter = filterService.updateFilterEnabledStatus(metadataResolverId, resourceId, status); + return ResponseEntity.ok(persistedFilter); + } +// Enable/disable for : , provider } 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/ActivateExceptionHandler.java new file mode 100644 index 000000000..85264b582 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateExceptionHandler.java @@ -0,0 +1,32 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller; + +import javax.script.ScriptException; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; +import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; + +@ControllerAdvice(assignableTypes = {ActivateController.class}) +public class ActivateExceptionHandler extends ResponseEntityExceptionHandler { + + @ExceptionHandler({ EntityNotFoundException.class }) + public ResponseEntity handleEntityNotFoundException(EntityNotFoundException e, WebRequest request) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse(HttpStatus.NOT_FOUND, e.getMessage())); + } + + @ExceptionHandler({ ForbiddenException.class }) + public ResponseEntity handleForbiddenAccess(ForbiddenException e, WebRequest request) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(new ErrorResponse(String.valueOf(HttpStatus.FORBIDDEN.value()), e.getMessage())); + } + + @ExceptionHandler({ ScriptException.class }) + 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())); + } +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/ForbiddenException.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/ForbiddenException.java new file mode 100644 index 000000000..66f2e61c5 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/ForbiddenException.java @@ -0,0 +1,11 @@ +package edu.internet2.tier.shibboleth.admin.ui.exception; + +public class ForbiddenException extends Exception { + public ForbiddenException() { + super("You are not authorized to perform the requested operation."); + } + + public ForbiddenException(String message) { + super(message); + } +} 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 a2d11a1ab..898e99e00 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 @@ -1,15 +1,17 @@ package edu.internet2.tier.shibboleth.admin.ui.security.service; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.springframework.security.core.context.SecurityContextHolder; + 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.RoleRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; -import org.apache.commons.lang.StringUtils; -import org.springframework.security.core.context.SecurityContextHolder; - -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; /** * @author Bill Smith (wsmith@unicon.net) @@ -70,4 +72,12 @@ public Set getUserRoles(String username) { } return result; } + + /** + * Current logic is pretty dumb, this will need to change/expand once a user can have more than one role. + */ + public boolean currentUserHasExpectedRole(List acceptedRoles) { + User user = getCurrentUser(); + return acceptedRoles.contains(user.getRole()); + } } 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 ea57c4d06..5d635d493 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 @@ -2,6 +2,9 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.Attribute; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation; +import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; +import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; + import org.opensaml.saml.saml2.metadata.EntityDescriptor; import java.util.List; @@ -54,4 +57,6 @@ public interface EntityDescriptorService { */ Map getRelyingPartyOverridesRepresentationFromAttributeList(List attributeList); + EntityDescriptorRepresentation updateEntityDescriptorEnabledStatus(String resourceId, boolean status) throws EntityNotFoundException, ForbiddenException; + } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/FilterService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/FilterService.java index 2ef9ab08e..6d752928b 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/FilterService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/FilterService.java @@ -1,7 +1,12 @@ package edu.internet2.tier.shibboleth.admin.ui.service; +import javax.script.ScriptException; + import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter; +import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.FilterRepresentation; +import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; +import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; /** * Main backend facade API that defines operations pertaining to manipulating {@link EntityAttributesFilter} objects. @@ -25,4 +30,6 @@ public interface FilterService { * @return FilterRepresentation front end representation */ FilterRepresentation createRepresentationFromFilter(final EntityAttributesFilter entityAttributesFilter); + + MetadataFilter updateFilterEnabledStatus(String metadataResolverId, String resourceId, boolean status) throws EntityNotFoundException, ForbiddenException, ScriptException; } 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 05477d616..ab93343c8 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 @@ -42,7 +42,10 @@ 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.exception.EntityNotFoundException; +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.service.UserService; import edu.internet2.tier.shibboleth.admin.util.MDDCConstants; import edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions; @@ -76,8 +79,8 @@ * @since 1.0 */ public class JPAEntityDescriptorServiceImpl implements EntityDescriptorService { - - private static final Logger LOGGER = LoggerFactory.getLogger(JPAEntityDescriptorServiceImpl.class); + @Autowired + private EntityDescriptorRepository entityDescriptorRepository; @Autowired private OpenSamlObjects openSamlObjects; @@ -709,4 +712,19 @@ public List getAttributeReleaseListFromAttributeList(List att public Map getRelyingPartyOverridesRepresentationFromAttributeList(List attributeList) { return ModelRepresentationConversions.getRelyingPartyOverridesRepresentationFromAttributeList(attributeList); } + + @Override + public EntityDescriptorRepresentation updateEntityDescriptorEnabledStatus(String resourceId, boolean status) throws EntityNotFoundException, ForbiddenException { + EntityDescriptor ed = entityDescriptorRepository.findByResourceId(resourceId); + if (ed == null) { + throw new EntityNotFoundException("Entity with resourceid[" + resourceId + "] was not found for update"); + } + // @TODO: when merged with groups, this should maybe be merged with group check as they have to have the role in the right group + if (!userService.currentUserHasExpectedRole(Arrays.asList(new String[] { "ROLE_ADMIN", "ROLE_ENABLE" }))) { + throw new ForbiddenException("You do not have the permissions necessary to change the enable status of this entity descriptor."); + } + ed.setServiceEnabled(status); + ed = entityDescriptorRepository.save(ed); + return createRepresentationFromDescriptor(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 0693b538a..6810fa243 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 @@ -1,13 +1,24 @@ package edu.internet2.tier.shibboleth.admin.ui.service; import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter; +import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.FilterRepresentation; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver; +import edu.internet2.tier.shibboleth.admin.ui.exception.EntityNotFoundException; +import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; +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.service.UserService; + import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.interceptor.TransactionAspectSupport; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Optional; + +import javax.script.ScriptException; /** * Default implementation of {@link FilterService} @@ -16,18 +27,27 @@ * @author Bill Smith (wsmith@unicon.net) */ public class JPAFilterServiceImpl implements FilterService { - - private static final Logger LOGGER = LoggerFactory.getLogger(JPAFilterServiceImpl.class); - @Autowired EntityDescriptorService entityDescriptorService; - + @Autowired EntityService entityService; + @Autowired + FilterRepository filterRepository; + @Autowired FilterTargetService filterTargetService; + + @Autowired + private MetadataResolverRepository metadataResolverRepository; + + @Autowired + private MetadataResolverService metadataResolverService; + @Autowired + private UserService userService; + @Override public EntityAttributesFilter createFilterFromRepresentation(FilterRepresentation representation) { //TODO? use OpenSamlObjects.buildDefaultInstanceOfType(EntityAttributesFilter.class)? @@ -66,4 +86,52 @@ public FilterRepresentation createRepresentationFromFilter(EntityAttributesFilte representation.setVersion(entityAttributesFilter.hashCode()); return representation; } + + private void reloadFiltersAndHandleScriptException(String resolverResourceId) throws ScriptException { + try { + metadataResolverService.reloadFilters(resolverResourceId); + } catch (Throwable ex) { + //explicitly mark transaction for rollback when we get ScriptException as we call reloadFilters + //after persistence call. Then re-throw the exception with pertinent message + if (ex instanceof ScriptException) { + TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); + throw new ScriptException("Caught invalid script parsing error when reloading filters. Please fix the script data"); + } + } + } + + /** + * Logic taken directly from the MetadataFiltersController and then modified slightly. + */ + @Override + public MetadataFilter updateFilterEnabledStatus(String metadataResolverId, String resourceId, boolean status) + throws EntityNotFoundException, ForbiddenException, ScriptException { + + MetadataResolver metadataResolver = metadataResolverRepository.findByResourceId(metadataResolverId); + // Now we operate directly on the filter attached to MetadataResolver, + // Instead of fetching filter separately, to accommodate correct envers versioning with uni-directional one-to-many + Optional filterTobeUpdatedOptional = metadataResolver.getMetadataFilters().stream() + .filter(it -> it.getResourceId().equals(resourceId)).findFirst(); + if (filterTobeUpdatedOptional.isEmpty()) { + throw new EntityNotFoundException("Filter with resource id[" + resourceId + "] not found"); + } + + // @TODO: when merged with groups, this should maybe be merged with group check as they have to have the role in the right group + if (!userService.currentUserHasExpectedRole(Arrays.asList(new String[] { "ROLE_ADMIN", "ROLE_ENABLE" }))) { + throw new ForbiddenException("You do not have the permissions necessary to change the enable status of this filter."); + } + + MetadataFilter filterTobeUpdated = filterTobeUpdatedOptional.get(); + filterTobeUpdated.setFilterEnabled(status); + MetadataFilter persistedFilter = filterRepository.save(filterTobeUpdated); + + // To support envers versioning from MetadataResolver side + metadataResolver.markAsModified(); + metadataResolverRepository.save(metadataResolver); + + // TODO: do we need to reload filters here? + reloadFiltersAndHandleScriptException(metadataResolver.getResourceId()); + + return persistedFilter; + } }