From e08f9d7ca421e41f537bf7866503677b4f35fce0 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 18 May 2021 14:27:19 -0700 Subject: [PATCH] SHIBUI-1875 Modified response to include last modified header --- .../ui/controller/EntitiesController.java | 23 +++++++++++++++++-- .../EntityDescriptorRepresentation.java | 5 ++++ .../controller/EntitiesControllerTests.groovy | 6 ++--- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesController.java index 927de19b4..6adc4c95b 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesController.java @@ -2,12 +2,20 @@ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Date; import javax.servlet.http.HttpServletRequest; +import org.apache.http.client.utils.DateUtils; import org.opensaml.core.xml.io.MarshallingException; import org.opensaml.saml.saml2.metadata.EntityDescriptor; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.transaction.annotation.Transactional; @@ -49,7 +57,15 @@ public ResponseEntity getOne(final @PathVariable String entityId, HttpServlet return ResponseEntity.notFound().build(); } EntityDescriptorRepresentation entityDescriptorRepresentation = entityDescriptorService.createRepresentationFromDescriptor(entityDescriptor); - return ResponseEntity.ok(entityDescriptorRepresentation); + HttpHeaders headers = new HttpHeaders(); + headers.set("Last-Modified", formatModifiedDate(entityDescriptorRepresentation)); + return new ResponseEntity<>(entityDescriptorRepresentation, headers, HttpStatus.OK); + } + + private String formatModifiedDate(EntityDescriptorRepresentation entityDescriptorRepresentation) { + Instant instant = entityDescriptorRepresentation.getModifiedDateAsDate().toInstant(ZoneOffset.UTC); + Date date = Date.from(instant); + return DateUtils.formatDate(date, DateUtils.PATTERN_RFC1123); } @RequestMapping(value = "/{entityId:.*}", produces = "application/xml") @@ -60,7 +76,10 @@ public ResponseEntity getOneXml(final @PathVariable String entityId) throws M return ResponseEntity.notFound().build(); } final String xml = this.openSamlObjects.marshalToXmlString(entityDescriptor); - return ResponseEntity.ok(xml); + EntityDescriptorRepresentation entityDescriptorRepresentation = entityDescriptorService.createRepresentationFromDescriptor(entityDescriptor); + HttpHeaders headers = new HttpHeaders(); + headers.set("Last-Modified", formatModifiedDate(entityDescriptorRepresentation)); + return new ResponseEntity<>(xml, headers, HttpStatus.OK); } private EntityDescriptor getEntityDescriptor(final String entityId) throws ResolverException, UnsupportedEncodingException { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java index 2131696c4..c5e89e3e7 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/EntityDescriptorRepresentation.java @@ -188,6 +188,11 @@ public void setCreatedDate(LocalDateTime createdDate) { public String getModifiedDate() { return modifiedDate != null ? modifiedDate.toString() : null; } + + public LocalDateTime getModifiedDateAsDate() { + // we shouldn't have an ED without either modified or created date, so this is mostly for testing where data can be odd + return modifiedDate != null ? modifiedDate : createdDate != null ? createdDate : LocalDateTime.now(); + } public void setModifiedDate(LocalDateTime modifiedDate) { this.modifiedDate = modifiedDate; diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerTests.groovy index 48e3bc8ae..8f262b6a1 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerTests.groovy @@ -139,9 +139,9 @@ class EntitiesControllerTests extends Specification { result.andExpect(status().isOk()) .andExpect(header().exists(HttpHeaders.CONTENT_TYPE)) // MUST HAVE // .andExpect(header().exists(HttpHeaders.CONTENT_LENGTH)) // SHOULD HAVE - should end up from etag filter, so skipped for test -// .andExpect(header().exists(HttpHeaders.CACHE_CONTROL)) // SHOULD HAVE - should be included by Spring Security -// .andExpect(header().exists(HttpHeaders.LAST_MODIFIED)) // SHOULD HAVE - should end up from etag filter, so skipped for test +// .andExpect(header().exists(HttpHeaders.CACHE_CONTROL)) // SHOULD HAVE - should be included by Spring Security // .andExpect(header().exists(HttpHeaders.ETAG)) // MUST HAVE - is done by filter, so skipped for test + .andExpect(header().exists(HttpHeaders.LAST_MODIFIED)) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(expectedBody, false)) } @@ -184,8 +184,8 @@ class EntitiesControllerTests extends Specification { .andExpect(header().exists(HttpHeaders.CONTENT_TYPE)) // MUST HAVE // .andExpect(header().exists(HttpHeaders.CONTENT_LENGTH)) // SHOULD HAVE - should end up from etag filter, so skipped for test // .andExpect(header().exists(HttpHeaders.CACHE_CONTROL)) // SHOULD HAVE - should be included by Spring Security -// .andExpect(header().exists(HttpHeaders.LAST_MODIFIED)) // SHOULD HAVE - should end up from etag filter, so skipped for test // .andExpect(header().exists(HttpHeaders.ETAG)) // MUST HAVE - is done by filter, so skipped for test + .andExpect(header().exists(HttpHeaders.LAST_MODIFIED)) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(expectedBody, false)) }