From a2dd5576175160bd14ad58e7178c6f78122578f6 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Thu, 1 Jul 2021 14:32:09 -0700 Subject: [PATCH] SHIBUI-1848 Finished auth pieces for the entity descriptor controller when performing the various API calls --- .../EntityDescriptorController.java | 303 +++++++++--------- .../ui/security/service/UserService.java | 16 + .../JPAEntityDescriptorServiceImpl.java | 3 +- 3 files changed, 162 insertions(+), 160 deletions(-) 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 473047f2f..f952b2a4d 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 @@ -9,9 +9,9 @@ import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService; import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorVersionService; +import lombok.extern.slf4j.Slf4j; + import org.opensaml.core.xml.io.MarshallingException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.HttpHeaders; @@ -38,38 +38,39 @@ @RestController @RequestMapping("/api") +@Slf4j public class EntityDescriptorController { + private static URI getResourceUriFor(EntityDescriptor ed) { + return ServletUriComponentsBuilder + .fromCurrentServletMapping().path("/api/EntityDescriptor") + .pathSegment(ed.getResourceId()) + .build() + .toUri(); + } @Autowired private EntityDescriptorRepository entityDescriptorRepository; @Autowired - private OpenSamlObjects openSamlObjects; + private EntityDescriptorService entityDescriptorService; @Autowired - private EntityDescriptorService entityDescriptorService; + private OpenSamlObjects openSamlObjects; + + private RestTemplate restTemplate; @Autowired RestTemplateBuilder restTemplateBuilder; private UserService userService; - private RestTemplate restTemplate; - private EntityDescriptorVersionService versionService; - private static Logger LOGGER = LoggerFactory.getLogger(EntityDescriptorController.class); - public EntityDescriptorController(UserService userService, EntityDescriptorVersionService versionService) { this.userService = userService; this.versionService = versionService; } - @PostConstruct - public void initRestTemplate() { - this.restTemplate = restTemplateBuilder.build(); - } - @PostMapping("/EntityDescriptor") @Transactional public ResponseEntity create(@RequestBody EntityDescriptorRepresentation edRepresentation) { @@ -93,57 +94,44 @@ public ResponseEntity create(@RequestBody EntityDescriptorRepresentation edRe return ResponseEntity.created(getResourceUriFor(persistedEd)).body(entityDescriptorService.createRepresentationFromDescriptor(persistedEd)); } - @PostMapping(value = "/EntityDescriptor", consumes = "application/xml") + @Secured("ROLE_ADMIN") + @DeleteMapping(value = "/EntityDescriptor/{resourceId}") @Transactional - public ResponseEntity upload(@RequestBody byte[] entityDescriptorXml, @RequestParam String spName) throws Exception { - return handleUploadingEntityDescriptorXml(entityDescriptorXml, spName); + public ResponseEntity deleteOne(@PathVariable String resourceId) { + EntityDescriptor ed = entityDescriptorRepository.findByResourceId(resourceId); + if (ed == null) { + return ResponseEntity.notFound().build(); + } else if (ed.isServiceEnabled()) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(new ErrorResponse(HttpStatus.FORBIDDEN, "Deleting an enabled Metadata Source is not allowed. Disable the source and try again.")); + } else { + entityDescriptorRepository.delete(ed); + return ResponseEntity.noContent().build(); + } } - @PostMapping(value = "/EntityDescriptor", consumes = "application/x-www-form-urlencoded") - @Transactional - public ResponseEntity upload(@RequestParam String metadataUrl, @RequestParam String spName) throws Exception { - try { - byte[] xmlContents = this.restTemplate.getForObject(metadataUrl, byte[].class); - return handleUploadingEntityDescriptorXml(xmlContents, spName); - } catch (Throwable e) { - LOGGER.error("Error fetching XML metadata from the provided URL: [{}]. The error is: {}", metadataUrl, e); - LOGGER.error(e.getMessage(), e); - return ResponseEntity - .badRequest() - .body(String.format("Error fetching XML metadata from the provided URL. Error: %s", e.getMessage())); + private ResponseEntity entityDescriptorEnablePermissionsCheck(boolean serviceEnabled) { + User user = userService.getCurrentUser(); + if (user != null) { + if (serviceEnabled && !user.getRole().equals("ROLE_ADMIN")) { + return ResponseEntity.status(HttpStatus.FORBIDDEN) + .body(new ErrorResponse(HttpStatus.FORBIDDEN, "You do not have the permissions necessary to enable this service.")); + } } + return null; } - @PutMapping("/EntityDescriptor/{resourceId}") - @Transactional - public ResponseEntity update(@RequestBody EntityDescriptorRepresentation edRepresentation, @PathVariable String resourceId) { - User currentUser = userService.getCurrentUser(); - EntityDescriptor existingEd = entityDescriptorRepository.findByResourceId(resourceId); - if (existingEd == null) { - return ResponseEntity.notFound().build(); - } else { - if (currentUser != null && (currentUser.getRole().equals("ROLE_ADMIN") || currentUser.getUsername().equals(existingEd.getCreatedBy()))) { - if (!existingEd.isServiceEnabled()) { - ResponseEntity entityDescriptorEnablingDeniedResponse = entityDescriptorEnablePermissionsCheck(edRepresentation.isServiceEnabled()); - if (entityDescriptorEnablingDeniedResponse != null) { - return entityDescriptorEnablingDeniedResponse; - } - } - - // Verify we're the only one attempting to update the EntityDescriptor - if (edRepresentation.getVersion() != existingEd.hashCode()) { - return new ResponseEntity(HttpStatus.CONFLICT); - } - - entityDescriptorService.updateDescriptorFromRepresentation(existingEd, edRepresentation); - existingEd = entityDescriptorRepository.save(existingEd); - - return ResponseEntity.ok().body(entityDescriptorService.createRepresentationFromDescriptor(existingEd)); - } else { - return ResponseEntity.status(HttpStatus.FORBIDDEN).body(new ErrorResponse(HttpStatus.FORBIDDEN, - "You are not authorized to perform the requested operation.")); - } + private ResponseEntity existingEntityDescriptorCheck(String entityId) { + final EntityDescriptor ed = entityDescriptorRepository.findByEntityID(entityId); + if (ed != null) { + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(getResourceUriFor(ed)); + return ResponseEntity + .status(HttpStatus.CONFLICT) + .headers(headers) + .body(new ErrorResponse(String.valueOf(HttpStatus.CONFLICT.value()), String.format("The entity descriptor with entity id [%s] already exists.", entityId))); } + //No existing entity descriptor, which is an OK condition indicated by returning a null conflict response + return null; } @GetMapping("/EntityDescriptors") @@ -152,49 +140,30 @@ public ResponseEntity getAll() { User currentUser = userService.getCurrentUser(); if (currentUser != null) { return ResponseEntity.ok(entityDescriptorService.getAllRepresentationsBasedOnUserAccess()); -// .map(ed -> entityDescriptorService.createRepresentationFromDescriptor(ed)) -// .collect(Collectors.toList())); } else { return ResponseEntity.status(HttpStatus.FORBIDDEN).body(new ErrorResponse(HttpStatus.FORBIDDEN, "You are not authorized to perform the requested operation.")); } } - @GetMapping("/EntityDescriptor/{resourceId}") + @GetMapping("/EntityDescriptor/{resourceId}/Versions") @Transactional(readOnly = true) - public ResponseEntity getOne(@PathVariable String resourceId) { - User currentUser = userService.getCurrentUser(); + public ResponseEntity getAllVersions(@PathVariable String resourceId) { EntityDescriptor ed = entityDescriptorRepository.findByResourceId(resourceId); if (ed == null) { return ResponseEntity.notFound().build(); - } else { - if (currentUser != null && (currentUser.getRole().equals("ROLE_ADMIN") || currentUser.getUsername().equals(ed.getCreatedBy()))) { - EntityDescriptorRepresentation edr = entityDescriptorService.createRepresentationFromDescriptor(ed); - return ResponseEntity.ok(edr); - } else { - return ResponseEntity.status(HttpStatus.FORBIDDEN).body(new ErrorResponse(HttpStatus.FORBIDDEN, - "You are not authorized to perform the requested operation.")); - } } - } - - @GetMapping(value = "/EntityDescriptor/{resourceId}", produces = "application/xml") - @Transactional(readOnly = true) - public ResponseEntity getOneXml(@PathVariable String resourceId) throws MarshallingException { - User currentUser = userService.getCurrentUser(); - EntityDescriptor ed = entityDescriptorRepository.findByResourceId(resourceId); - if (ed == null) { + List versions = versionService.findVersionsForEntityDescriptor(resourceId); + if (versions.isEmpty()) { return ResponseEntity.notFound().build(); - } else { - if (currentUser != null && (currentUser.getRole().equals("ROLE_ADMIN") || currentUser.getUsername().equals(ed.getCreatedBy()))) { - final String xml = this.openSamlObjects.marshalToXmlString(ed); - return ResponseEntity.ok(xml); - } else { - return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); - } } + if(userService.isAuthorizedFor(ed.getCreatedBy(), ed.getGroup() == null ? null : ed.getGroup().getResourceId())) { + return ResponseEntity.ok(versions); + } + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); } + @Secured("ROLE_ADMIN") @Transactional(readOnly = true) @GetMapping(value = "/EntityDescriptor/disabledNonAdmin") public Iterable getDisabledAndNotOwnedByAdmin() { @@ -203,38 +172,43 @@ public Iterable getDisabledAndNotOwnedByAdmin() .collect(Collectors.toList()); } - @Secured("ROLE_ADMIN") - @DeleteMapping(value = "/EntityDescriptor/{resourceId}") - @Transactional - public ResponseEntity deleteOne(@PathVariable String resourceId) { - EntityDescriptor ed = entityDescriptorRepository.findByResourceId(resourceId); - if (ed == null) { - return ResponseEntity.notFound().build(); - } else if (ed.isServiceEnabled()) { - return ResponseEntity.status(HttpStatus.FORBIDDEN).body(new ErrorResponse(HttpStatus.FORBIDDEN, "Deleting an enabled Metadata Source is not allowed. Disable the source and try again.")); - } else { - entityDescriptorRepository.delete(ed); - return ResponseEntity.noContent().build(); + @GetMapping("/EntityDescriptor/{resourceId}") + @Transactional(readOnly = true) + public ResponseEntity getOne(@PathVariable String resourceId) { + User currentUser = userService.getCurrentUser(); + if (currentUser != null) { + EntityDescriptor ed = entityDescriptorRepository.findByResourceId(resourceId); + if (ed == null) { + return ResponseEntity.notFound().build(); + } else { + if (userService.isAuthorizedFor(ed.getCreatedBy(), ed.getGroup() == null ? null : ed.getGroup().getResourceId())) { + EntityDescriptorRepresentation edr = entityDescriptorService.createRepresentationFromDescriptor(ed); + return ResponseEntity.ok(edr); + } + } } - } + return ResponseEntity.status(HttpStatus.FORBIDDEN).body( + new ErrorResponse(HttpStatus.FORBIDDEN, "You are not authorized to perform the requested operation.")); - //Versioning endpoints + } - @GetMapping("/EntityDescriptor/{resourceId}/Versions") + @GetMapping(value = "/EntityDescriptor/{resourceId}", produces = "application/xml") @Transactional(readOnly = true) - public ResponseEntity getAllVersions(@PathVariable String resourceId) { - EntityDescriptor ed = entityDescriptorRepository.findByResourceId(resourceId); - if (ed == null) { - return ResponseEntity.notFound().build(); - } - List versions = versionService.findVersionsForEntityDescriptor(resourceId); - if (versions.isEmpty()) { - return ResponseEntity.notFound().build(); - } - if(isAuthorizedFor(ed.getCreatedBy())) { - return ResponseEntity.ok(versions); + public ResponseEntity getOneXml(@PathVariable String resourceId) throws MarshallingException { + User currentUser = userService.getCurrentUser(); + if (currentUser != null) { + EntityDescriptor ed = entityDescriptorRepository.findByResourceId(resourceId); + if (ed == null) { + return ResponseEntity.notFound().build(); + } else { + if (userService.isAuthorizedFor(ed.getCreatedBy(), ed.getGroup() == null ? null : ed.getGroup().getResourceId())) { + final String xml = this.openSamlObjects.marshalToXmlString(ed); + return ResponseEntity.ok(xml); + } + } } return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } @GetMapping("/EntityDescriptor/{resourceId}/Versions/{versionId}") @@ -245,47 +219,12 @@ public ResponseEntity getSpecificVersion(@PathVariable String resourceId, @Pa if (edRepresentation == null) { return ResponseEntity.notFound().build(); } - if(isAuthorizedFor(edRepresentation.getCreatedBy())) { + if(userService.isAuthorizedFor(edRepresentation.getCreatedBy(), edRepresentation.getGroupId())) { return ResponseEntity.ok(edRepresentation); } return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); } - //Private methods - - private static URI getResourceUriFor(EntityDescriptor ed) { - return ServletUriComponentsBuilder - .fromCurrentServletMapping().path("/api/EntityDescriptor") - .pathSegment(ed.getResourceId()) - .build() - .toUri(); - } - - private ResponseEntity existingEntityDescriptorCheck(String entityId) { - final EntityDescriptor ed = entityDescriptorRepository.findByEntityID(entityId); - if (ed != null) { - HttpHeaders headers = new HttpHeaders(); - headers.setLocation(getResourceUriFor(ed)); - return ResponseEntity - .status(HttpStatus.CONFLICT) - .headers(headers) - .body(new ErrorResponse(String.valueOf(HttpStatus.CONFLICT.value()), String.format("The entity descriptor with entity id [%s] already exists.", entityId))); - } - //No existing entity descriptor, which is an OK condition indicated by returning a null conflict response - return null; - } - - private ResponseEntity entityDescriptorEnablePermissionsCheck(boolean serviceEnabled) { - User user = userService.getCurrentUser(); - if (user != null) { - if (serviceEnabled && !user.getRole().equals("ROLE_ADMIN")) { - return ResponseEntity.status(HttpStatus.FORBIDDEN) - .body(new ErrorResponse(HttpStatus.FORBIDDEN, "You do not have the permissions necessary to enable this service.")); - } - } - return null; - } - private ResponseEntity handleUploadingEntityDescriptorXml(byte[] rawXmlBytes, String spName) throws Exception { final EntityDescriptor ed = EntityDescriptor.class.cast(openSamlObjects.unmarshalFromXml(rawXmlBytes)); @@ -300,17 +239,65 @@ private ResponseEntity handleUploadingEntityDescriptorXml(byte[] rawXmlBytes, .body(entityDescriptorService.createRepresentationFromDescriptor(persistedEd)); } - private boolean isAuthorizedFor(String username) { - User u = userService.getCurrentUser(); - - switch (userService.getCurrentUserAccess()) { - case ADMIN: - return true; - case GROUP: - case OWNER: - return u.getUsername().equals(username); - default: - return false; + @PostConstruct + public void initRestTemplate() { + this.restTemplate = restTemplateBuilder.build(); + } + + @PutMapping("/EntityDescriptor/{resourceId}") + @Transactional + public ResponseEntity update(@RequestBody EntityDescriptorRepresentation edRepresentation, @PathVariable String resourceId) { + User currentUser = userService.getCurrentUser(); + if (currentUser != null) { + EntityDescriptor existingEd = entityDescriptorRepository.findByResourceId(resourceId); + + if (existingEd == null) { + return ResponseEntity.notFound().build(); + } else { + if (userService.isAuthorizedFor(existingEd.getCreatedBy(), + existingEd.getGroup() == null ? null : existingEd.getGroup().getResourceId())) { + if (!existingEd.isServiceEnabled()) { + ResponseEntity entityDescriptorEnablingDeniedResponse = entityDescriptorEnablePermissionsCheck( + edRepresentation.isServiceEnabled()); + if (entityDescriptorEnablingDeniedResponse != null) { + return entityDescriptorEnablingDeniedResponse; + } + } + + // Verify we're the only one attempting to update the EntityDescriptor + if (edRepresentation.getVersion() != existingEd.hashCode()) { + return new ResponseEntity(HttpStatus.CONFLICT); + } + + entityDescriptorService.updateDescriptorFromRepresentation(existingEd, edRepresentation); + existingEd = entityDescriptorRepository.save(existingEd); + + return ResponseEntity.ok().body(entityDescriptorService.createRepresentationFromDescriptor(existingEd)); + } + } + } + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(new ErrorResponse(HttpStatus.FORBIDDEN, + "You are not authorized to perform the requested operation.")); + } + + @PostMapping(value = "/EntityDescriptor", consumes = "application/xml") + @Transactional + public ResponseEntity upload(@RequestBody byte[] entityDescriptorXml, @RequestParam String spName) throws Exception { + return handleUploadingEntityDescriptorXml(entityDescriptorXml, spName); + } + + @PostMapping(value = "/EntityDescriptor", consumes = "application/x-www-form-urlencoded") + @Transactional + public ResponseEntity upload(@RequestParam String metadataUrl, @RequestParam String spName) throws Exception { + try { + byte[] xmlContents = this.restTemplate.getForObject(metadataUrl, byte[].class); + return handleUploadingEntityDescriptorXml(xmlContents, spName); + } catch (Throwable e) { + log.error("Error fetching XML metadata from the provided URL: [{}]. The error is: {}", metadataUrl, e); + log.error(e.getMessage(), e); + return ResponseEntity + .badRequest() + .body(String.format("Error fetching XML metadata from the provided URL. Error: %s", e.getMessage())); } } 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 a8ce52358..220bb090c 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 @@ -51,6 +51,22 @@ public UserAccess getCurrentUserAccess() { } } + public boolean isAuthorizedFor(String objectCreatedBy, String objectGroupResourceId) { + User currentUser = getCurrentUser(); + String groupId = objectGroupResourceId == null ? "" : objectGroupResourceId; + + switch (getCurrentUserAccess()) { + case ADMIN: + return true; + case GROUP: + return objectCreatedBy.equals(currentUser.getUsername()) || groupId.equals(currentUser.getGroupId()); + case OWNER: + return objectCreatedBy.equals(currentUser.getUsername()); + default: + return false; + } + } + /** * Given a user with a defined User.role, update the User.roles collection with that role. * 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 7c03b72c0..348461bcf 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 @@ -101,8 +101,7 @@ public JPAEntityDescriptorServiceImpl(OpenSamlObjects openSamlObjects, EntitySer private EntityDescriptor buildDescriptorFromRepresentation(final EntityDescriptor ed, final EntityDescriptorRepresentation representation) { ed.setEntityID(representation.getEntityId()); - Group g = groupService.find(representation.getGroupId()); - ed.setGroup(g); + ed.setGroup(groupService.find(representation.getGroupId())); setupSPSSODescriptor(ed, representation); ed.setServiceProviderName(representation.getServiceProviderName());