Skip to content

Commit

Permalink
Merged in feature/SHIBUI-1880 (pull request #480)
Browse files Browse the repository at this point in the history
Feature/SHIBUI-1880

Approved-by: Jonathan Johnson
  • Loading branch information
chasegawa authored and Jonathan Johnson committed May 19, 2021
2 parents f1b4274 + 743954c commit a45e8b4
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,6 +35,10 @@
"/api/entities" }, // existing - included to break no existing code
method = RequestMethod.GET)
@Slf4j
/**
* EntitiesController is here to meet the requirements for this project being an MDQ. Despite similar logic to the
* EntitiesDescriptorController, the required endpoints that make this project an MDQ server are served by this controller.
*/
public class EntitiesController {
@Autowired
private EntityDescriptorService entityDescriptorService;
Expand All @@ -45,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")
Expand All @@ -56,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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.internet2.tier.shibboleth.admin.ui.domain.frontend;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;

Expand Down Expand Up @@ -188,6 +189,12 @@ public void setCreatedDate(LocalDateTime createdDate) {
public String getModifiedDate() {
return modifiedDate != null ? modifiedDate.toString() : null;
}

@JsonIgnore
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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package edu.internet2.tier.shibboleth.admin.ui.controller

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 groovy.json.JsonOutput
import net.shibboleth.ext.spring.resource.ResourceHelper
import net.shibboleth.utilities.java.support.resolver.CriteriaSet

import org.joda.time.DateTime
import org.opensaml.core.criterion.EntityIdCriterion
import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver
import org.opensaml.saml.metadata.resolver.MetadataResolver
import org.opensaml.saml.metadata.resolver.filter.MetadataFilterChain
import org.opensaml.saml.metadata.resolver.impl.FilesystemMetadataResolver
import org.opensaml.saml.metadata.resolver.impl.ResourceBackedMetadataResolver
import org.spockframework.spring.SpringBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.TestConfiguration
Expand All @@ -35,11 +41,26 @@ class EntitiesControllerIntegrationTests extends Specification {
@Autowired
private WebTestClient webClient

/*def setup() {
// yeah, don't ask... this is just shenanigans
// The API is changed. Doesn't work anymore. Not sure if we need it here
this.webClient.webClient.uriBuilderFactory.encodingMode = DefaultUriBuilderFactory.EncodingMode.NONE
}*/
def openSamlObjects = new OpenSamlObjects().with {
init()
it
}

def resource = ResourceHelper.of(new ClassPathResource("/metadata/aggregate.xml"))

def metadataResolver = new ResourceBackedMetadataResolver(resource).with {
it.id = 'test'
it.parserPool = openSamlObjects.parserPool
initialize()
it
}

// This stub will spit out the results from the resolver instead of actually finding them in the DB
@SpringBean
EntityDescriptorRepository edr = Stub(EntityDescriptorRepository) {
findByEntityID("http://test.scaldingspoon.org/test1") >> metadataResolver.resolveSingle(new CriteriaSet(new EntityIdCriterion("http://test.scaldingspoon.org/test1")))
findByEntityID("test") >> metadataResolver.resolveSingle(new CriteriaSet(new EntityIdCriterion("test")))
}

//todo review
def "GET /api/entities returns the proper json"() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ import edu.internet2.tier.shibboleth.admin.ui.configuration.Internationalization
import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration
import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration
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.ui.service.JPAEntityDescriptorServiceImpl
import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityServiceImpl
import net.shibboleth.ext.spring.resource.ResourceHelper
import net.shibboleth.utilities.java.support.resolver.CriteriaSet

import org.opensaml.core.criterion.EntityIdCriterion
import org.opensaml.saml.metadata.resolver.impl.ResourceBackedMetadataResolver
import org.spockframework.spring.SpringBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.domain.EntityScan
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
Expand All @@ -22,6 +27,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders
import spock.lang.Specification
import spock.lang.Subject

import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
Expand All @@ -47,12 +53,19 @@ class EntitiesControllerTests extends Specification {

@Autowired
UserService userService


// This stub will spit out the results from the resolver instead of actually finding them in the DB
@SpringBean
EntityDescriptorRepository edr = Stub(EntityDescriptorRepository) {
findByEntityID("http://test.scaldingspoon.org/test1") >> metadataResolver.resolveSingle(new CriteriaSet(new EntityIdCriterion("http://test.scaldingspoon.org/test1")))
findByEntityID("test") >> metadataResolver.resolveSingle(new CriteriaSet(new EntityIdCriterion("test")))
}

@Subject
def controller = new EntitiesController(
openSamlObjects: openSamlObjects,
entityDescriptorService: new JPAEntityDescriptorServiceImpl(openSamlObjects, new JPAEntityServiceImpl(openSamlObjects), userService),
metadataResolver: metadataResolver
entityDescriptorRepository: edr
)

def mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
Expand Down Expand Up @@ -116,14 +129,21 @@ class EntitiesControllerTests extends Specification {
"current":false
}
'''

when:
def result = mockMvc.perform(get('/api/entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1'))
def result = mockMvc.perform(get('/entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1'))

then:
def x = content()
// Response headers section 2.5
// from the spec https://www.ietf.org/archive/id/draft-young-md-query-14.txt
result.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(content().json(expectedBody, false))
.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.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))
}

def 'GET /entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1'() {
Expand Down Expand Up @@ -153,6 +173,7 @@ class EntitiesControllerTests extends Specification {
"current":false
}
'''

when:
def result = mockMvc.perform(get('/entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1'))

Expand All @@ -163,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))
}
Expand Down

0 comments on commit a45e8b4

Please sign in to comment.