diff --git a/README.md b/README.md
index 4033766c8..a7f5b9994 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,17 @@ allowed with:
-Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true
```
+In Apache HTTPD, you'll need something like:
+
+```
+
+ AllowEncodedSlashes NoDecode
+ ServerName shibui.unicon.net
+ ProxyPass / http://localhost:8080/ nocanon
+ ProxyPassReverse / http://localhost:8080/
+
+```
+
### Running as an executable
`java -jar shibui.war`
diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/TomcatConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/TomcatConfiguration.java
new file mode 100644
index 000000000..e3c2d019c
--- /dev/null
+++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/TomcatConfiguration.java
@@ -0,0 +1,13 @@
+package edu.internet2.tier.shibboleth.admin.ui.configuration;
+
+import org.springframework.boot.web.server.WebServerFactoryCustomizer;
+import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
+import org.springframework.stereotype.Component;
+
+@Component
+public class TomcatConfiguration implements WebServerFactoryCustomizer {
+ @Override
+ public void customize(ConfigurableServletWebServerFactory factory) {
+ System.setProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "true");
+ }
+}
diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy
index 62a15c646..2ed405784 100644
--- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy
+++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy
@@ -1,22 +1,41 @@
package edu.internet2.tier.shibboleth.admin.ui.controller
+import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects
+import net.shibboleth.ext.spring.resource.ResourceHelper
+import org.joda.time.DateTime
+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.ResourceBackedMetadataResolver
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.boot.test.context.TestConfiguration
+import org.springframework.context.annotation.Bean
+import org.springframework.core.io.ClassPathResource
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.util.DefaultUriBuilderFactory
+import org.xmlunit.builder.DiffBuilder
+import org.xmlunit.builder.Input
+import org.xmlunit.diff.DefaultNodeMatcher
+import org.xmlunit.diff.ElementSelectors
import spock.lang.Specification
/**
* @author Bill Smith (wsmith@unicon.net)
*/
-// TODO: implement
-// @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
-// @ActiveProfiles("no-auth")
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@ActiveProfiles("no-auth")
class EntitiesControllerIntegrationTests extends Specification {
@Autowired
private WebTestClient webClient
+ def setup() {
+ // yeah, don't ask... this is just shenanigans
+ this.webClient.webClient.uriBuilderFactory.encodingMode = DefaultUriBuilderFactory.EncodingMode.NONE
+ }
+
def "GET /api/entities returns the proper json"() {
given:
def expectedBody = '''
@@ -55,24 +74,108 @@ class EntitiesControllerIntegrationTests extends Specification {
'''
when:
- def x = 1
- // TODO: implement
- /*
def result = this.webClient
.get()
.uri("/api/entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1")
- .exchange()
- */
+ .exchange() // someday, I'd like to know why IntelliJ "cannot resolve symbol 'exchange'"
then:
- assert 1
- // TODO: implement
- /*
result.expectStatus().isOk()
- .expectBody().consumeWith(
- { response -> new String(response.getResponseBody()) == expectedBody }
- )
- //.expectedBody() // some other json comparison
- */
+ .expectBody()
+ .json(expectedBody)
+ }
+
+ def "GET /api/entities/test is not found"() {
+ when:
+ def result = this.webClient
+ .get()
+ .uri("/api/entities/test")
+ .exchange()
+
+ then:
+ result.expectStatus().isNotFound()
+ }
+
+ def "GET /api/entities/test XML is not found"() {
+ when:
+ def result = this.webClient
+ .get()
+ .uri("/api/entities/test")
+ .header('Accept', 'application/xml')
+ .exchange()
+
+ then:
+ result.expectStatus().isNotFound()
+ }
+
+ def "GET /api/entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1 XML returns proper XML"() {
+ given:
+ def expectedBody = '''
+
+
+
+
+ internal
+
+
+ givenName
+ employeeNumber
+
+
+
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
+
+
+
+'''
+ when:
+ def result = this.webClient
+ .get()
+ .uri("/api/entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1")
+ .header('Accept', 'application/xml')
+ .exchange()
+
+ then:
+ def resultBody = result.expectStatus().isOk()
+ //.expectHeader().contentType("application/xml;charset=ISO-8859-1") // should this really be ISO-8859-1?
+ // expectedBody encoding is UTF-8...
+ .expectHeader().contentType("application/xml;charset=UTF-8")
+ .expectBody(String.class)
+ .returnResult()
+ def diff = DiffBuilder.compare(Input.fromString(expectedBody)).withTest(Input.fromString(resultBody.getResponseBody())).ignoreComments().checkForSimilar().ignoreWhitespace().withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText)).build()
+ !diff.hasDifferences()
+ }
+
+ @TestConfiguration
+ static class Config {
+ @Autowired
+ OpenSamlObjects openSamlObjects
+
+ @Bean
+ MetadataResolver metadataResolver() {
+ def resource = ResourceHelper.of(new ClassPathResource("/metadata/aggregate.xml"))
+ def aggregate = new ResourceBackedMetadataResolver(resource){
+ @Override
+ DateTime getLastRefresh() {
+ return null
+ }
+ }
+
+ aggregate.with {
+ it.metadataFilter = new MetadataFilterChain()
+ it.id = 'testme'
+ it.parserPool = openSamlObjects.parserPool
+ it.initialize()
+ it
+ }
+
+ return new ChainingMetadataResolver().with {
+ it.id = 'chain'
+ it.resolvers = [aggregate]
+ it.initialize()
+ it
+ }
+ }
}
}