diff --git a/mockserver-api/src/main/xsd/pl/touk/mockserver/api/common.xsd b/mockserver-api/src/main/xsd/pl/touk/mockserver/api/common.xsd index ac21246..953b839 100644 --- a/mockserver-api/src/main/xsd/pl/touk/mockserver/api/common.xsd +++ b/mockserver-api/src/main/xsd/pl/touk/mockserver/api/common.xsd @@ -18,5 +18,13 @@ + + + + + + + + diff --git a/mockserver-api/src/main/xsd/pl/touk/mockserver/api/request.xsd b/mockserver-api/src/main/xsd/pl/touk/mockserver/api/request.xsd index 6636b53..23786bc 100644 --- a/mockserver-api/src/main/xsd/pl/touk/mockserver/api/request.xsd +++ b/mockserver-api/src/main/xsd/pl/touk/mockserver/api/request.xsd @@ -20,6 +20,7 @@ + diff --git a/mockserver-tests/src/test/groovy/pl/touk/mockserver/tests/MockServerHttpsTest.groovy b/mockserver-tests/src/test/groovy/pl/touk/mockserver/tests/MockServerHttpsTest.groovy new file mode 100644 index 0000000..91b851a --- /dev/null +++ b/mockserver-tests/src/test/groovy/pl/touk/mockserver/tests/MockServerHttpsTest.groovy @@ -0,0 +1,80 @@ +package pl.touk.mockserver.tests + +import groovy.util.slurpersupport.GPathResult +import org.apache.http.client.methods.CloseableHttpResponse +import org.apache.http.client.methods.HttpPost +import org.apache.http.conn.ssl.SSLConnectionSocketFactory +import org.apache.http.conn.ssl.SSLContexts +import org.apache.http.entity.ContentType +import org.apache.http.entity.StringEntity +import org.apache.http.impl.client.CloseableHttpClient +import org.apache.http.impl.client.HttpClients +import pl.touk.mockserver.api.common.Https +import pl.touk.mockserver.api.request.AddMock +import pl.touk.mockserver.client.RemoteMockServer +import pl.touk.mockserver.client.Util +import pl.touk.mockserver.server.HttpMockServer +import spock.lang.Shared +import spock.lang.Specification + +import javax.net.ssl.SSLContext +import java.security.KeyStore + +class MockServerHttpsTest extends Specification { + + RemoteMockServer remoteMockServer + + HttpMockServer httpMockServer + + @Shared + SSLContext sslContext = SSLContexts.custom() + .loadTrustMaterial(trustStore()) + .build() + + @Shared + CloseableHttpClient client = HttpClients.custom() + .setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER) + .setSslcontext(sslContext) + .build() + + def setup() { + httpMockServer = new HttpMockServer(19000) + remoteMockServer = new RemoteMockServer('localhost', 19000) + } + + def cleanup() { + httpMockServer.stop() + } + + def 'should handle HTTPS server' () { + expect: + remoteMockServer.addMock(new AddMock( + name: 'testHttps', + path: 'testEndpoint', + port: 10443, + predicate: '''{req -> req.xml.name() == 'request'}''', + response: '''{req -> ""}''', + https: new Https( + keyPassword: 'changeit', + keystorePassword: 'changeit', + keystorePath: MockServerHttpsTest.classLoader.getResource('keystore.jks').path + ), + soap: false + )) + when: + HttpPost restPost = new HttpPost('https://localhost:10443/testEndpoint') + restPost.entity = new StringEntity('', ContentType.create("text/xml", "UTF-8")) + CloseableHttpResponse response = client.execute(restPost) + then: + GPathResult restPostResponse = Util.extractXmlResponse(response) + restPostResponse.name() == 'goodResponse-request' + and: + remoteMockServer.removeMock('testHttps')?.size() == 1 + } + + private KeyStore trustStore() { + KeyStore truststore = KeyStore.getInstance(KeyStore.defaultType) + truststore.load(new FileInputStream(MockServerHttpsTest.classLoader.getResource('truststore.jks').path), "changeit".toCharArray()); + return truststore + } +} diff --git a/mockserver-tests/src/test/resources/keystore.jks b/mockserver-tests/src/test/resources/keystore.jks new file mode 100644 index 0000000..d5e35d1 Binary files /dev/null and b/mockserver-tests/src/test/resources/keystore.jks differ diff --git a/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpMockServer.groovy b/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpMockServer.groovy index 082593a..859b712 100644 --- a/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpMockServer.groovy +++ b/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpMockServer.groovy @@ -2,6 +2,7 @@ package pl.touk.mockserver.server import com.sun.net.httpserver.HttpExchange import groovy.util.logging.Slf4j +import pl.touk.mockserver.api.common.Https import pl.touk.mockserver.api.common.ImportAlias import pl.touk.mockserver.api.common.Method import pl.touk.mockserver.api.request.AddMock @@ -41,7 +42,7 @@ class HttpMockServer { HttpMockServer(int port = 9999, ConfigObject initialConfiguration = new ConfigObject(), int threads = 10) { executor = Executors.newFixedThreadPool(threads) - httpServerWrapper = new HttpServerWrapper(port, executor) + httpServerWrapper = new HttpServerWrapper(port, executor, null) initialConfiguration.values()?.each { ConfigObject co -> addMock(co) @@ -108,7 +109,7 @@ class HttpMockServer { throw new RuntimeException('mock already registered') } Mock mock = mockFromRequest(request) - HttpServerWrapper child = getOrCreateChildServer(mock.port) + HttpServerWrapper child = getOrCreateChildServer(mock.port, mock.https) child.addMock(mock) saveConfiguration(request) mockNames << name @@ -121,7 +122,7 @@ class HttpMockServer { throw new RuntimeException('mock already registered') } Mock mock = mockFromConfig(co) - HttpServerWrapper child = getOrCreateChildServer(mock.port) + HttpServerWrapper child = getOrCreateChildServer(mock.port, null) child.addMock(mock) configuration.put(name, co) mockNames << name @@ -156,6 +157,7 @@ class HttpMockServer { mock.responseHeaders = request.responseHeaders mock.schema = request.schema mock.preserveHistory = request.preserveHistory != false + mock.https = request.https return mock } @@ -173,10 +175,10 @@ class HttpMockServer { return mock } - private HttpServerWrapper getOrCreateChildServer(int mockPort) { + private HttpServerWrapper getOrCreateChildServer(int mockPort, Https https) { HttpServerWrapper child = childServers[mockPort] if (!child) { - child = new HttpServerWrapper(mockPort, executor) + child = new HttpServerWrapper(mockPort, executor, https) childServers.put(mockPort, child) } return child diff --git a/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpServerWrapper.groovy b/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpServerWrapper.groovy index 6f8f9f4..9121c6b 100644 --- a/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpServerWrapper.groovy +++ b/mockserver/src/main/groovy/pl/touk/mockserver/server/HttpServerWrapper.groovy @@ -2,9 +2,17 @@ package pl.touk.mockserver.server import com.sun.net.httpserver.HttpHandler import com.sun.net.httpserver.HttpServer +import com.sun.net.httpserver.HttpsConfigurator +import com.sun.net.httpserver.HttpsServer import groovy.transform.PackageScope import groovy.util.logging.Slf4j +import pl.touk.mockserver.api.common.Https +import javax.net.ssl.KeyManagerFactory +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManager +import java.security.KeyStore +import java.security.SecureRandom import java.util.concurrent.Executor @Slf4j @@ -15,16 +23,37 @@ class HttpServerWrapper { private List executors = [] - HttpServerWrapper(int port, Executor executor) { + HttpServerWrapper(int port, Executor executor, Https https) { this.port = port InetSocketAddress addr = new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), port) - httpServer = HttpServer.create(addr, 0) + httpServer = buildServer(addr, https) httpServer.executor = executor log.info("Http server starting on port $port...") httpServer.start() log.info('Http server is started') } + private HttpServer buildServer(InetSocketAddress addr, Https https) { + if (https) { + HttpsServer httpsServer = HttpsServer.create(addr, 0) + httpsServer.httpsConfigurator = new HttpsConfigurator(buildSslContext(https)) + return httpsServer + } else { + return HttpServer.create(addr, 0) + } + } + + private SSLContext buildSslContext(Https https) { + KeyStore keyStore = KeyStore.getInstance(KeyStore.defaultType) + keyStore.load(new FileInputStream(https.keystorePath), https.keystorePassword.toCharArray()) + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.defaultAlgorithm) + kmf.init(keyStore, https.keyPassword.toCharArray()) + + SSLContext ssl = SSLContext.getInstance('TLSv1') + ssl.init(kmf.keyManagers, [] as TrustManager[], new SecureRandom()) + return ssl + } + void createContext(String context, HttpHandler handler) { httpServer.createContext(context, handler) } diff --git a/mockserver/src/main/groovy/pl/touk/mockserver/server/Mock.groovy b/mockserver/src/main/groovy/pl/touk/mockserver/server/Mock.groovy index ed37f89..e132ac9 100644 --- a/mockserver/src/main/groovy/pl/touk/mockserver/server/Mock.groovy +++ b/mockserver/src/main/groovy/pl/touk/mockserver/server/Mock.groovy @@ -5,6 +5,7 @@ import groovy.transform.PackageScope import groovy.util.logging.Slf4j import org.codehaus.groovy.control.CompilerConfiguration import org.codehaus.groovy.control.customizers.ImportCustomizer +import pl.touk.mockserver.api.common.Https import pl.touk.mockserver.api.common.Method import javax.xml.XMLConstants @@ -35,6 +36,7 @@ class Mock implements Comparable { private Validator validator Map imports = [:] boolean preserveHistory = true + Https https Mock(String name, String path, int port) { if (!(name)) {