package com.vtence.molecule.middlewares; import com.vtence.molecule.Request; import com.vtence.molecule.Response; import com.vtence.molecule.http.HttpMethod; import org.junit.Test; import java.util.concurrent.TimeUnit; import static com.vtence.molecule.http.HttpMethod.DELETE; import static com.vtence.molecule.http.HttpMethod.GET; import static com.vtence.molecule.http.HttpMethod.HEAD; import static com.vtence.molecule.http.HttpMethod.OPTIONS; import static com.vtence.molecule.http.HttpMethod.PATCH; import static com.vtence.molecule.http.HttpMethod.POST; import static com.vtence.molecule.http.HttpMethod.PUT; import static com.vtence.molecule.testing.ResponseAssert.assertThat; import static java.util.Arrays.asList; public class ForceSSLTest { ForceSSL ssl = new ForceSSL(); Request request = new Request(); Response response = new Response(); @Test public void doesNotRedirectRequestsThatAreAlreadySecure() throws Exception { assertIsNotRedirected(secure(request)); } @Test public void redirectsToHttpsWhenRequestIsInsecure() throws Exception { forceSSL(request.method(GET) .secure(false) .serverHost("example.com") .uri("/over/there?name=ferret#nose")); assertThat(response).hasStatusCode(301) .isRedirectedTo("https://example.com/over/there?name=ferret#nose") .isDone(); } @Test public void redirectsToCustomHost() throws Exception { ssl.redirectTo("ssl.example.com:443"); forceSSL(request.serverHost("example.com").uri("/")); assertThat(response).isRedirectedTo("https://ssl.example.com:443/") .isDone(); } @Test public void redirectsPermanentlyOnGetAndHead() throws Exception { for (HttpMethod method: asList(HEAD, GET)) { assertIsRedirectPermanently(request.method(method)); } } @Test public void redirectsTemporaryOnOtherVerbs() throws Exception { for (HttpMethod method: asList(OPTIONS, POST, PATCH, PUT, DELETE)) { assertIsRedirectedTemporary(request.method(method)); } } @Test public void doesNotRedirectProxiedHttps() throws Exception { ssl.redirectOn("X-Forwarded-Proto"); assertIsNotRedirected(request.header("X-Forwarded-Proto", "https")); assertIsRedirectedTemporary(request.header("X-Forwarded-Proto", "http")); } @Test public void includesHSTSHeaderByDefaultWithOneYearValidity() throws Exception { forceSSL(secure(request)); assertThat(response).hasHeader("Strict-Transport-Security", "max-age=31536000"); } @Test public void disablingHSTSHeaderClearsBrowserSettings() throws Exception { ssl.hsts(false); forceSSL(secure(request)); assertThat(response).hasHeader("Strict-Transport-Security", "max-age=0"); } @Test public void configuresHSTSHeaderExpiry() throws Exception { ssl.expires(TimeUnit.DAYS.toSeconds(180)); forceSSL(secure(request)); assertThat(response).hasHeader("Strict-Transport-Security", "max-age=15552000"); } @Test public void includesSubdomainsInSecurityHeadersIfRequested() throws Exception { ssl.includesSubdomains(true); forceSSL(secure(request)); assertThat(response).hasHeader("Strict-Transport-Security", "max-age=31536000; includeSubdomains"); } @Test public void prefersAppSecurityHeaders() throws Exception { ssl.connectTo((req, resp) -> resp.header("Strict-Transport-Security", "provided")); forceSSL(secure(request)); assertThat(response).hasHeader("Strict-Transport-Security", "provided"); } @Test public void canBeDisabledForDevelopmentMode() throws Exception { ssl.enable(false); assertIsNotRedirected(request); } private void assertIsNotRedirected(Request request) throws Exception { forceSSL(request); assertThat(response).hasNoHeader("Location"); } private void assertIsRedirectPermanently(Request request) throws Exception { forceSSL(request); assertThat(response).isDone().hasStatusCode(301); } private void assertIsRedirectedTemporary(Request request) throws Exception { forceSSL(request); assertThat(response).isDone().hasStatusCode(307); } private void forceSSL(Request request) throws Exception { ssl.handle(request, response); response.done(); } private Request secure(Request request) { return request.secure(true); } }