/* * #%L * Wisdom-Framework * %% * Copyright (C) 2013 - 2015 Wisdom Framework * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package org.wisdom.framework.vertx; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLContexts; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.wisdom.api.Controller; import org.wisdom.api.DefaultController; import org.wisdom.api.configuration.ApplicationConfiguration; import org.wisdom.api.content.ContentEngine; import org.wisdom.api.exceptions.ExceptionMapper; import org.wisdom.api.http.HttpMethod; import org.wisdom.api.http.Request; import org.wisdom.api.http.Result; import org.wisdom.api.http.Status; import org.wisdom.api.router.Route; import org.wisdom.api.router.RouteBuilder; import org.wisdom.api.router.Router; import org.wisdom.test.parents.FakeConfiguration; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.security.*; import java.security.cert.CertificateException; import java.util.Collections; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.*; /** * Check the behavior of the {@link Server}. */ public class ServerTest extends VertxBaseTest { private WisdomVertxServer wisdom; private ApplicationConfiguration application; private Router router; @Before public void setUp() { wisdom = new WisdomVertxServer(); application = mock(ApplicationConfiguration.class); when(application.getIntegerWithDefault(eq("vertx.http.port"), anyInt())).thenReturn(0); when(application.getIntegerWithDefault(eq("vertx.https.port"), anyInt())).thenReturn(0); when(application.getIntegerWithDefault("vertx.acceptBacklog", -1)).thenReturn(-1); when(application.getIntegerWithDefault("vertx.receiveBufferSize", -1)).thenReturn(-1); when(application.getIntegerWithDefault("vertx.sendBufferSize", -1)).thenReturn(-1); when(application.getStringArray("wisdom.websocket.subprotocols")).thenReturn(new String[0]); when(application.getStringArray("vertx.websocket-subprotocols")).thenReturn(new String[0]); when(application.getBaseDir()).thenReturn(new File("target/junk/server/conf")); wisdom.configuration = application; wisdom.vertx = vertx; router = mock(Router.class); ContentEngine contentEngine = mock(ContentEngine.class); wisdom.accessor = new ServiceAccessor( null, application, router, contentEngine, null, null, Collections.<ExceptionMapper>emptyList() ); } @After public void tearDown() { if (wisdom != null) { wisdom.stop(); } } @Test public void testCreationFromConfiguration() throws InterruptedException { FakeConfiguration configuration = new FakeConfiguration(ImmutableMap.<String, Object>builder() .put("port", 0) .put("ssl", true) .put("authentication", true) .build()); when(application.getConfiguration("vertx.servers")).thenReturn(new FakeConfiguration( Collections.<String, Object>emptyMap())); Server server = Server.from(wisdom.accessor, vertx, "test", configuration); wisdom.servers.add(server); wisdom.start(); waitForHttpsStart(wisdom); assertThat(server.port()).isNotEqualTo(-1).isNotEqualTo(0); assertThat(server.ssl()).isTrue(); assertThat(server.accept("/foo")).isTrue(); } /** * This methods checks HTTP, HTTPS and HTTPS with Mutual Authentication. */ @Test public void testCreationOfThreeServersFromConfiguration() throws InterruptedException, IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, KeyManagementException, UnrecoverableKeyException { FakeConfiguration s1 = new FakeConfiguration(ImmutableMap.<String, Object>builder() .put("port", 0) .put("ssl", false) .put("authentication", false) .build()); FakeConfiguration s2 = new FakeConfiguration(ImmutableMap.<String, Object>builder() .put("port", 0) .put("ssl", true) .put("authentication", false) .build()); FakeConfiguration s3 = new FakeConfiguration(ImmutableMap.<String, Object>builder() .put("port", 0) .put("ssl", true) .put("authentication", true) .build()); // Server HTTPS File root = new File(""); final File serverKeyStore = new File(root.getAbsolutePath() + "/src/test/resources/keystore/server/server.jks"); assertThat(serverKeyStore).isFile(); when(application.get("https.keyStore")).thenReturn( serverKeyStore.getAbsolutePath()); when(application.get("https.trustStore")).thenReturn( new File(root.getAbsolutePath() + "/src/test/resources/keystore/server/server.jks").getAbsolutePath()); when(application.getWithDefault("https.keyStoreType", "JKS")).thenReturn("JKS"); when(application.getWithDefault("https.trustStoreType", "JKS")).thenReturn("JKS"); when(application.getWithDefault("https.keyStorePassword", "")).thenReturn("wisdom"); when(application.getWithDefault("https.trustStorePassword", "")).thenReturn("wisdom"); when(application.getWithDefault("https.keyStoreAlgorithm", KeyManagerFactory.getDefaultAlgorithm())) .thenReturn(KeyManagerFactory.getDefaultAlgorithm()); when(application.getWithDefault("https.trustStoreAlgorithm", KeyManagerFactory.getDefaultAlgorithm())) .thenReturn(KeyManagerFactory.getDefaultAlgorithm()); when(application.getConfiguration("vertx.servers")).thenReturn( new FakeConfiguration( ImmutableMap.<String, Object>of( "s1", s1, "s2", s2, "s3", s3 ) )); Controller controller = new DefaultController() { @SuppressWarnings("unused") public Result index() { return ok("Alright"); } }; Route route = new RouteBuilder().route(HttpMethod.GET) .on("/") .to(controller, "index"); when(router.getRouteFor(anyString(), anyString(), any(Request.class))).thenReturn(route); wisdom.start(); waitForStart(wisdom); waitForHttpsStart(wisdom); assertThat(wisdom.servers).hasSize(3); // Check rendering for (Server server : wisdom.servers) { String r; KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); FileInputStream instream = new FileInputStream("src/test/resources/keystore/client/client1.jks"); trustStore.load(instream, "wisdom".toCharArray()); // Trust own CA and all self-signed certs SSLContext sslcontext = SSLContexts.custom() .loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()) .loadKeyMaterial(trustStore, "wisdom".toCharArray()) .build(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext, new String[]{"TLSv1", "SSLv3"}, null, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); CloseableHttpClient httpclient = HttpClients.custom() .setSSLSocketFactory(sslsf) .build(); if (server.ssl()) { HttpGet httpget = new HttpGet("https://localhost:" + server.port()); final CloseableHttpResponse response = httpclient.execute(httpget); r = EntityUtils.toString(response.getEntity()); } else { r = org.apache.http.client.fluent.Request .Get("http://localhost:" + server.port()).execute().returnContent().asString(); } assertThat(r).isEqualToIgnoringCase("Alright"); } } @Test public void testAllow() throws InterruptedException, IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, KeyManagementException, UnrecoverableKeyException { FakeConfiguration s1 = new FakeConfiguration(ImmutableMap.<String, Object>builder() .put("port", 0) .put("ssl", false) .put("authentication", false) .put("allow", ImmutableList.of("/foo*")) .build()); FakeConfiguration s2 = new FakeConfiguration(ImmutableMap.<String, Object>builder() .put("port", 0) .put("ssl", false) .put("authentication", false) .put("allow", ImmutableList.of("/bar*")) .build()); when(application.getConfiguration("vertx.servers")).thenReturn( new FakeConfiguration( ImmutableMap.<String, Object>of( "s1", s1, "s2", s2 ) )); Controller controller = new DefaultController() { @SuppressWarnings("unused") public Result index() { return ok("Alright"); } }; final Route route1 = new RouteBuilder().route(HttpMethod.GET) .on("/foo") .to(controller, "index"); final Route route2 = new RouteBuilder().route(HttpMethod.GET) .on("/bar") .to(controller, "index"); doAnswer(new Answer<Route>() { @Override public Route answer(InvocationOnMock mock) throws Throwable { String url = (String) mock.getArguments()[1]; if (url.equals("/foo")) { return route1; } if (url.equals("/bar")) { return route2; } return null; } }).when(router).getRouteFor(anyString(), anyString(), any(Request.class)); wisdom.start(); waitForStart(wisdom); assertThat(wisdom.servers).hasSize(2); for (Server server : wisdom.servers) { if (server.name().equalsIgnoreCase("s1")) { // Accept /foo, Deny /bar HttpResponse r = org.apache.http.client.fluent.Request.Get( "http://localhost:" + server.port() + "/foo").execute().returnResponse(); assertThat(r.getStatusLine().getStatusCode()).isEqualTo(Status.OK); EntityUtils.consumeQuietly(r.getEntity()); r = org.apache.http.client.fluent.Request.Get( "http://localhost:" + server.port() + "/bar").execute().returnResponse(); assertThat(r.getStatusLine().getStatusCode()).isEqualTo(Status.FORBIDDEN); EntityUtils.consumeQuietly(r.getEntity()); } else { // Accept /foo, Deny /bar HttpResponse r = org.apache.http.client.fluent.Request.Get( "http://localhost:" + server.port() + "/bar").execute().returnResponse(); assertThat(r.getStatusLine().getStatusCode()).isEqualTo(Status.OK); EntityUtils.consumeQuietly(r.getEntity()); r = org.apache.http.client.fluent.Request.Get( "http://localhost:" + server.port() + "/foo").execute().returnResponse(); assertThat(r.getStatusLine().getStatusCode()).isEqualTo(Status.FORBIDDEN); EntityUtils.consumeQuietly(r.getEntity()); } } } @Test public void testDeny() throws InterruptedException, IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, KeyManagementException, UnrecoverableKeyException { FakeConfiguration s1 = new FakeConfiguration(ImmutableMap.<String, Object>builder() .put("port", 0) .put("ssl", false) .put("authentication", false) .put("deny", ImmutableList.of("/bar*")) .build()); FakeConfiguration s2 = new FakeConfiguration(ImmutableMap.<String, Object>builder() .put("port", 0) .put("ssl", false) .put("authentication", false) .put("deny", ImmutableList.of("/foo*")) .build()); when(application.getConfiguration("vertx.servers")).thenReturn( new FakeConfiguration( ImmutableMap.<String, Object>of( "s1", s1, "s2", s2 ) )); Controller controller = new DefaultController() { @SuppressWarnings("unused") public Result index() { return ok("Alright"); } }; final Route route1 = new RouteBuilder().route(HttpMethod.GET) .on("/foo") .to(controller, "index"); final Route route2 = new RouteBuilder().route(HttpMethod.GET) .on("/bar") .to(controller, "index"); doAnswer(new Answer<Route>() { @Override public Route answer(InvocationOnMock mock) throws Throwable { String url = (String) mock.getArguments()[1]; if (url.equals("/foo")) { return route1; } if (url.equals("/bar")) { return route2; } return null; } }).when(router).getRouteFor(anyString(), anyString(), any(Request.class)); wisdom.start(); waitForStart(wisdom); assertThat(wisdom.servers).hasSize(2); for (Server server : wisdom.servers) { if (server.name().equalsIgnoreCase("s1")) { // Accept /foo, Deny /bar HttpResponse r = org.apache.http.client.fluent.Request.Get( "http://localhost:" + server.port() + "/foo").execute().returnResponse(); assertThat(r.getStatusLine().getStatusCode()).isEqualTo(Status.OK); EntityUtils.consumeQuietly(r.getEntity()); r = org.apache.http.client.fluent.Request.Get( "http://localhost:" + server.port() + "/bar").execute().returnResponse(); assertThat(r.getStatusLine().getStatusCode()).isEqualTo(Status.FORBIDDEN); EntityUtils.consumeQuietly(r.getEntity()); } else { // Accept /foo, Deny /bar HttpResponse r = org.apache.http.client.fluent.Request.Get( "http://localhost:" + server.port() + "/bar").execute().returnResponse(); assertThat(r.getStatusLine().getStatusCode()).isEqualTo(Status.OK); EntityUtils.consumeQuietly(r.getEntity()); r = org.apache.http.client.fluent.Request.Get( "http://localhost:" + server.port() + "/foo").execute().returnResponse(); assertThat(r.getStatusLine().getStatusCode()).isEqualTo(Status.FORBIDDEN); EntityUtils.consumeQuietly(r.getEntity()); } } } @Test public void testDenyWithRedirect() throws InterruptedException, IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, KeyManagementException, UnrecoverableKeyException { FakeConfiguration s1 = new FakeConfiguration(ImmutableMap.<String, Object>builder() .put("port", 0) .put("ssl", false) .put("authentication", false) .put("deny", ImmutableList.of("/bar*")) .put("onDenied", "/foo") .build()); when(application.getConfiguration("vertx.servers")).thenReturn( new FakeConfiguration( ImmutableMap.<String, Object>of( "s1", s1 ) )); Controller controller = new DefaultController() { @SuppressWarnings("unused") public Result index() { return ok("Alright"); } @SuppressWarnings("unused") public Result bar() { return ok("bar"); } }; final Route route1 = new RouteBuilder().route(HttpMethod.GET) .on("/foo") .to(controller, "index"); final Route route2 = new RouteBuilder().route(HttpMethod.GET) .on("/bar") .to(controller, "bar"); doAnswer(new Answer<Route>() { @Override public Route answer(InvocationOnMock mock) throws Throwable { String url = (String) mock.getArguments()[1]; if (url.equals("/foo")) { return route1; } if (url.equals("/bar")) { return route2; } return null; } }).when(router).getRouteFor(anyString(), anyString(), any(Request.class)); wisdom.start(); waitForStart(wisdom); assertThat(wisdom.servers).hasSize(1); for (Server server : wisdom.servers) { // foo allowed HttpResponse r = org.apache.http.client.fluent.Request.Get( "http://localhost:" + server.port() + "/foo").execute().returnResponse(); assertThat(r.getStatusLine().getStatusCode()).isEqualTo(Status.OK); // bar denied => redirected r = org.apache.http.client.fluent.Request.Get( "http://localhost:" + server.port() + "/bar").execute().returnResponse(); assertThat(r.getStatusLine().getStatusCode()).isEqualTo(Status.OK); assertThat(EntityUtils.toString(r.getEntity())).contains("Alright"); } } }