Repository: cxf Updated Branches: refs/heads/master 94585ae07 -> e34329f6f
Missing files from last commit Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/e34329f6 Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/e34329f6 Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/e34329f6 Branch: refs/heads/master Commit: e34329f6fa0ed306d43a239f462ce277b4d60b46 Parents: 94585ae Author: Colm O hEigeartaigh <[email protected]> Authored: Thu Dec 11 11:58:52 2014 +0000 Committer: Colm O hEigeartaigh <[email protected]> Committed: Thu Dec 11 11:58:52 2014 +0000 ---------------------------------------------------------------------- .../org/apache/cxf/systest/https/BusServer.java | 73 ++ .../cxf/systest/https/HTTPSConduitTest.java | 755 +++++++++++++++++++ .../https/HTTPSProxyAuthConduitTest.java | 111 +++ .../systest/https/HTTPSProxyConduitTest.java | 100 +++ .../org/apache/cxf/systest/https/Server.java | 100 +++ .../org/apache/cxf/systest/https/Mortimer.cxf | 47 ++ .../org/apache/cxf/systest/https/greeting.wsdl | 166 ++++ 7 files changed, 1352 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/e34329f6/systests/transports/src/test/java/org/apache/cxf/systest/https/BusServer.java ---------------------------------------------------------------------- diff --git a/systests/transports/src/test/java/org/apache/cxf/systest/https/BusServer.java b/systests/transports/src/test/java/org/apache/cxf/systest/https/BusServer.java new file mode 100644 index 0000000..09df754 --- /dev/null +++ b/systests/transports/src/test/java/org/apache/cxf/systest/https/BusServer.java @@ -0,0 +1,73 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.cxf.systest.https; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.cxf.Bus; +import org.apache.cxf.BusFactory; +import org.apache.cxf.testutil.common.AbstractBusTestServerBase; +import org.apache.cxf.testutil.common.TestUtil; + +/** + * This server just instantiates a Bus, full stop. + * Everything else is designed to be spring-loaded. + */ +public class BusServer extends AbstractBusTestServerBase { + public static final Map<String, String> PORTMAP = new HashMap<String, String>(); + public static void resetPortMap() { + PORTMAP.clear(); + for (int x = 0; x < 9; x++) { + PORTMAP.put("PORT" + x, TestUtil.getNewPortNumber(BusServer.class, x)); + } + } + public static String getPort(int x) { + if (PORTMAP.isEmpty()) { + for (int y = 0; y < 9; y++) { + PORTMAP.put("PORT" + y, TestUtil.getPortNumber(BusServer.class, y)); + } + } + return PORTMAP.get("PORT" + x); + } + + + protected void run() { + // + // Just instantiate the Bus; services will be instantiated + // and published automatically through Spring + // + final BusFactory factory = BusFactory.newInstance(); + Bus bus = factory.createBus(); + setBus(bus); + BusFactory.setDefaultBus(bus); + BusFactory.setThreadDefaultBus(bus); + } + + public static void main(String[] args) { + try { + BusServer s = new BusServer(); + s.start(); + } catch (Exception ex) { + ex.printStackTrace(); + System.exit(-1); + } + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/e34329f6/systests/transports/src/test/java/org/apache/cxf/systest/https/HTTPSConduitTest.java ---------------------------------------------------------------------- diff --git a/systests/transports/src/test/java/org/apache/cxf/systest/https/HTTPSConduitTest.java b/systests/transports/src/test/java/org/apache/cxf/systest/https/HTTPSConduitTest.java new file mode 100644 index 0000000..c375e7e --- /dev/null +++ b/systests/transports/src/test/java/org/apache/cxf/systest/https/HTTPSConduitTest.java @@ -0,0 +1,755 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.cxf.systest.https; + + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.xml.namespace.QName; + +import org.apache.cxf.Bus; +import org.apache.cxf.BusFactory; +import org.apache.cxf.bus.spring.BusApplicationContext; +import org.apache.cxf.bus.spring.SpringBusFactory; +import org.apache.cxf.common.util.Base64Utility; +import org.apache.cxf.configuration.jsse.TLSClientParameters; +import org.apache.cxf.configuration.security.AuthorizationPolicy; +import org.apache.cxf.endpoint.Client; +import org.apache.cxf.frontend.ClientProxy; +import org.apache.cxf.message.Message; +import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase; +import org.apache.cxf.transport.http.HTTPConduit; +import org.apache.cxf.transport.http.MessageTrustDecider; +import org.apache.cxf.transport.http.URLConnectionInfo; +import org.apache.cxf.transport.http.UntrustedURLConnectionIOException; +import org.apache.cxf.transport.http.auth.HttpAuthHeader; +import org.apache.cxf.transport.http.auth.HttpAuthSupplier; +import org.apache.cxf.transport.https.HttpsURLConnectionInfo; +import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; +import org.apache.hello_world.Greeter; +import org.apache.hello_world.services.SOAPService; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.context.ApplicationContext; + +/** + * This class tests several issues and Conduit policies based + * on a set up of redirecting servers. + * <pre> + * + * Http Redirection: + * + * Poltim(https:9005) ----> Mortimer (http:9000) + * + * HttpS redirection/Trust: + * + * Tarpin(https:9003) ----> Gordy(https:9001) ----> Bethal(https:9002) + * + * Hostname Verifier Test + * + * Morpit (https:9008) + * + * </pre>HTTPConduitTest + * The Bethal server issues 401 with differing realms depending on the + * User name given in the authorization header. + * <p> + * The Morpit has a CN that is not equal to "localhost" to kick in + * the Hostname Verifier. + */ +public class HTTPSConduitTest extends AbstractBusClientServerTestBase { + private static final boolean IN_PROCESS = true; + + private static TLSClientParameters tlsClientParameters = new TLSClientParameters(); + private static List<String> servers = new ArrayList<String>(); + + private static Map<String, String> addrMap = new TreeMap<String, String>(); + + static { + try { + //System.setProperty("javax.net.debug", "all"); + URL key = Server.class.getResource("../../../../../keys/Morpit.jks"); + String keystore = new File(key.toURI()).getAbsolutePath(); + //System.out.println("Keystore: " + keystore); + KeyManager[] kmgrs = getKeyManagers(getKeyStore("JKS", keystore, "password"), "password"); + + key = Server.class.getResource("../../../../../keys/Truststore.jks"); + + String truststore = new File(key.toURI()).getAbsolutePath(); + //System.out.println("Truststore: " + truststore); + TrustManager[] tmgrs = getTrustManagers(getKeyStore("JKS", truststore, "password")); + + tlsClientParameters.setKeyManagers(kmgrs); + tlsClientParameters.setTrustManagers(tmgrs); + } catch (Exception e) { + throw new RuntimeException("Static initialization failed", e); + } + } + + private final QName serviceName = + new QName("http://apache.org/hello_world", "SOAPService"); + private final QName bethalQ = + new QName("http://apache.org/hello_world", "Bethal"); + private final QName gordyQ = + new QName("http://apache.org/hello_world", "Gordy"); + private final QName tarpinQ = + new QName("http://apache.org/hello_world", "Tarpin"); + private final QName poltimQ = + new QName("http://apache.org/hello_world", "Poltim"); + + public HTTPSConduitTest() { + } + + + public static String getPort(String s) { + return BusServer.PORTMAP.get(s); + } + + @BeforeClass + public static void allocatePorts() { + BusServer.resetPortMap(); + addrMap.clear(); + addrMap.put("Mortimer", "http://localhost:" + getPort("PORT0") + "/"); + addrMap.put("Tarpin", "https://localhost:" + getPort("PORT1") + "/"); + addrMap.put("Poltim", "https://localhost:" + getPort("PORT2") + "/"); + addrMap.put("Gordy", "https://localhost:" + getPort("PORT3") + "/"); + addrMap.put("Bethal", "https://localhost:" + getPort("PORT4") + "/"); + addrMap.put("Morpit", "https://localhost:" + getPort("PORT5") + "/"); + tlsClientParameters.setDisableCNCheck(true); + servers.clear(); + } + + + /** + * This function is used to start up a server. It only "starts" a + * server if it hasn't been started before, hence its static nature. + * <p> + * This approach is used to start the needed servers for a particular test + * instead of starting them all in "startServers". This single needed + * server approach allieviates the pain in starting them all just to run + * a particular test in the debugger. + */ + public synchronized boolean startServer(String name) { + if (servers.contains(name)) { + return true; + } + Bus bus = BusFactory.getThreadDefaultBus(false); + URL serverC = + Server.class.getResource(name + ".cxf"); + BusFactory.setDefaultBus(null); + BusFactory.setThreadDefaultBus(null); + boolean server = launchServer(Server.class, null, + new String[] { + name, + addrMap.get(name), + serverC.toString() }, + IN_PROCESS); + if (server) { + servers.add(name); + } + BusFactory.setDefaultBus(null); + BusFactory.setThreadDefaultBus(bus); + return server; + } + + @AfterClass + public static void cleanUp() { + Bus b = BusFactory.getDefaultBus(false); + if (b != null) { + b.shutdown(true); + } + b = BusFactory.getThreadDefaultBus(false); + if (b != null) { + b.shutdown(true); + } + } + + public static KeyStore getKeyStore(String ksType, String file, String ksPassword) + throws GeneralSecurityException, + IOException { + + String type = ksType != null + ? ksType + : KeyStore.getDefaultType(); + + char[] password = ksPassword != null + ? ksPassword.toCharArray() + : null; + + // We just use the default Keystore provider + KeyStore keyStore = KeyStore.getInstance(type); + + keyStore.load(new FileInputStream(file), password); + + return keyStore; + } + + public static KeyManager[] getKeyManagers(KeyStore keyStore, String keyPassword) + throws GeneralSecurityException, + IOException { + // For tests, we just use the default algorithm + String alg = KeyManagerFactory.getDefaultAlgorithm(); + + char[] keyPass = keyPassword != null + ? keyPassword.toCharArray() + : null; + + // For tests, we just use the default provider. + KeyManagerFactory fac = KeyManagerFactory.getInstance(alg); + + fac.init(keyStore, keyPass); + + return fac.getKeyManagers(); + } + + public static TrustManager[] getTrustManagers(KeyStore keyStore) + throws GeneralSecurityException, + IOException { + // For tests, we just use the default algorithm + String alg = TrustManagerFactory.getDefaultAlgorithm(); + + // For tests, we just use the default provider. + TrustManagerFactory fac = TrustManagerFactory.getInstance(alg); + + fac.init(keyStore); + + return fac.getTrustManagers(); + } + + //methods that a subclass can override to inject a Proxy into the flow + //and assert the proxy was appropriately called + public void configureProxy(Client c) { + } + public void resetProxyCount() { + } + public void assertProxyRequestCount(int i) { + } + + /** + * We use this class to reset the default bus. + * Note: This may not always work in the future. + * I was lucky in that "defaultBus" is actually a + * protected static. + */ + class DefaultBusFactory extends SpringBusFactory { + public Bus createBus(URL config) { + Bus bus = super.createBus(config, true); + BusFactory.setDefaultBus(bus); + BusFactory.setThreadDefaultBus(bus); + return bus; + } + } + + /** + * This methods tests a basic https connection to Bethal. + * It supplies an authorization policy with preemptive user/pass + * to avoid the 401. + */ + @Test + public void testHttpsBasicConnectionWithConfig() throws Exception { + startServer("Bethal"); + + URL config = getClass().getResource("BethalClientConfig.cxf"); + + // We go through the back door, setting the default bus. + new DefaultBusFactory().createBus(config); + URL wsdl = getClass().getResource("greeting.wsdl"); + assertNotNull("WSDL is null", wsdl); + + SOAPService service = new SOAPService(wsdl, serviceName); + assertNotNull("Service is null", service); + + Greeter bethal = service.getPort(bethalQ, Greeter.class); + + assertNotNull("Port is null", bethal); + updateAddressPort(bethal, getPort("PORT4")); + verifyBethalClient(bethal); + } + + @Test + public void testGetClientFromSpringContext() throws Exception { + startServer("Bethal"); + + BusFactory.setDefaultBus(null); + // The client bean configuration file + URL beans = getClass().getResource("BethalClientBeans.xml"); + // We go through the back door, setting the default bus. + Bus bus = new DefaultBusFactory().createBus(beans); + + ApplicationContext context = bus.getExtension(BusApplicationContext.class); + Greeter bethal = (Greeter)context.getBean("Bethal"); + updateAddressPort(bethal, getPort("PORT4")); + // verify the client side's setting + verifyBethalClient(bethal); + } + + // we just verify the configurations are loaded successfully + private void verifyBethalClient(Greeter bethal) { + Client client = ClientProxy.getClient(bethal); + + HTTPConduit http = + (HTTPConduit) client.getConduit(); + + HTTPClientPolicy httpClientPolicy = http.getClient(); + assertEquals("the httpClientPolicy's autoRedirect should be true", + true, httpClientPolicy.isAutoRedirect()); + TLSClientParameters tlsParameters = http.getTlsClientParameters(); + assertNotNull("the http conduit's tlsParameters should not be null", tlsParameters); + + + // If we set any name, but Edward, Mary, or George, + // and a password of "password" we will get through + // Bethal. + AuthorizationPolicy authPolicy = http.getAuthorization(); + assertEquals("Set the wrong user name from the configuration", + "Betty", authPolicy.getUserName()); + assertEquals("Set the wrong pass word form the configuration", + "password", authPolicy.getPassword()); + + configureProxy(ClientProxy.getClient(bethal)); + + String answer = bethal.sayHi(); + answer = bethal.sayHi(); + answer = bethal.sayHi(); + answer = bethal.sayHi(); + answer = bethal.sayHi(); + assertTrue("Unexpected answer: " + answer, + "Bonjour from Bethal".equals(answer)); + + //With HTTPS, it will just be a CONNECT to the proxy and all the + //data is encrypted. Thus, the proxy cannot distinquish the requests + assertProxyRequestCount(0); + } + + /** + * This methods tests a basic https connection to Bethal. + * It supplies an authorization policy with premetive user/pass + * to avoid the 401. + */ + @Test + public void testHttpsBasicConnection() throws Exception { + startServer("Bethal"); + + URL wsdl = getClass().getResource("greeting.wsdl"); + assertNotNull("WSDL is null", wsdl); + + SOAPService service = new SOAPService(wsdl, serviceName); + assertNotNull("Service is null", service); + + Greeter bethal = service.getPort(bethalQ, Greeter.class); + assertNotNull("Port is null", bethal); + updateAddressPort(bethal, getPort("PORT4")); + + // Okay, I'm sick of configuration files. + // This also tests dynamic configuration of the conduit. + Client client = ClientProxy.getClient(bethal); + HTTPConduit http = + (HTTPConduit) client.getConduit(); + + HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy(); + + httpClientPolicy.setAutoRedirect(false); + // If we set any name, but Edward, Mary, or George, + // and a password of "password" we will get through + // Bethal. + AuthorizationPolicy authPolicy = new AuthorizationPolicy(); + authPolicy.setUserName("Betty"); + authPolicy.setPassword("password"); + + http.setClient(httpClientPolicy); + http.setTlsClientParameters(tlsClientParameters); + http.setAuthorization(authPolicy); + + configureProxy(client); + String answer = bethal.sayHi(); + assertTrue("Unexpected answer: " + answer, + "Bonjour from Bethal".equals(answer)); + assertProxyRequestCount(0); + } + + + @Test + public void testHttpsRedirectToHttpFail() throws Exception { + startServer("Mortimer"); + startServer("Poltim"); + + URL wsdl = getClass().getResource("greeting.wsdl"); + assertNotNull("WSDL is null", wsdl); + + SOAPService service = new SOAPService(wsdl, serviceName); + assertNotNull("Service is null", service); + + Greeter poltim = service.getPort(poltimQ, Greeter.class); + assertNotNull("Port is null", poltim); + updateAddressPort(poltim, getPort("PORT2")); + + // Okay, I'm sick of configuration files. + // This also tests dynamic configuration of the conduit. + Client client = ClientProxy.getClient(poltim); + HTTPConduit http = + (HTTPConduit) client.getConduit(); + + HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy(); + + httpClientPolicy.setAutoRedirect(true); + + http.setClient(httpClientPolicy); + http.setTlsClientParameters(tlsClientParameters); + configureProxy(client); + poltim.sayHi(); + //client -> poltim is https and thus not recorded but then redirected to mortimer + //client -> mortimer is http and recoreded + assertProxyRequestCount(1); + } + + class MyHttpsTrustDecider extends MessageTrustDecider { + + private String[] trustName; + private int called; + + MyHttpsTrustDecider(String name) { + trustName = new String[] {name}; + } + + MyHttpsTrustDecider(String[] name) { + trustName = name; + } + + public int wasCalled() { + return called; + } + + public void establishTrust( + String conduitName, + URLConnectionInfo cinfo, + Message message + ) throws UntrustedURLConnectionIOException { + + called++; + + HttpsURLConnectionInfo ci = (HttpsURLConnectionInfo) cinfo; + boolean trusted = false; + for (int i = 0; i < trustName.length; i++) { + trusted = trusted + || ci.getPeerPrincipal() + .toString().contains("OU=" + trustName[i]); + } + if (!trusted) { + throw new UntrustedURLConnectionIOException( + "Peer Principal \"" + + ci.getPeerPrincipal() + + "\" does not contain " + + getTrustNames()); + } + } + + private String getTrustNames() { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < trustName.length; i++) { + sb.append("\"OU="); + sb.append(trustName[i]); + sb.append("\""); + if (i < trustName.length - 1) { + sb.append(", "); + } + } + return sb.toString(); + } + } + + @Test + public void testHttpsTrust() throws Exception { + startServer("Bethal"); + + URL wsdl = getClass().getResource("greeting.wsdl"); + assertNotNull("WSDL is null", wsdl); + + SOAPService service = new SOAPService(wsdl, serviceName); + assertNotNull("Service is null", service); + + Greeter bethal = service.getPort(bethalQ, Greeter.class); + assertNotNull("Port is null", bethal); + updateAddressPort(bethal, getPort("PORT4")); + + // Okay, I'm sick of configuration files. + // This also tests dynamic configuration of the conduit. + Client client = ClientProxy.getClient(bethal); + HTTPConduit http = + (HTTPConduit) client.getConduit(); + + HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy(); + + httpClientPolicy.setAutoRedirect(false); + // If we set any name, but Edward, Mary, or George, + // and a password of "password" we will get through + // Bethal. + AuthorizationPolicy authPolicy = new AuthorizationPolicy(); + authPolicy.setUserName("Betty"); + authPolicy.setPassword("password"); + + http.setClient(httpClientPolicy); + http.setTlsClientParameters(tlsClientParameters); + http.setAuthorization(authPolicy); + + // Our expected server should be OU=Bethal + http.setTrustDecider(new MyHttpsTrustDecider("Bethal")); + + configureProxy(client); + String answer = bethal.sayHi(); + assertTrue("Unexpected answer: " + answer, + "Bonjour from Bethal".equals(answer)); + assertProxyRequestCount(0); + + + // Nobody will not equal OU=Bethal + MyHttpsTrustDecider trustDecider = + new MyHttpsTrustDecider("Nobody"); + http.setTrustDecider(trustDecider); + try { + answer = bethal.sayHi(); + fail("Unexpected answer from Bethal: " + answer); + } catch (Exception e) { + //e.printStackTrace(); + //assertTrue("Trust Decider was not called", + // 0 > trustDecider.wasCalled()); + } + assertProxyRequestCount(0); + } + + @Test + public void testHttpsTrustRedirect() throws Exception { + startServer("Tarpin"); + startServer("Gordy"); + startServer("Bethal"); + + URL wsdl = getClass().getResource("greeting.wsdl"); + assertNotNull("WSDL is null", wsdl); + + SOAPService service = new SOAPService(wsdl, serviceName); + assertNotNull("Service is null", service); + + Greeter tarpin = service.getPort(tarpinQ, Greeter.class); + assertNotNull("Port is null", tarpin); + updateAddressPort(tarpin, getPort("PORT1")); + + // Okay, I'm sick of configuration files. + // This also tests dynamic configuration of the conduit. + Client client = ClientProxy.getClient(tarpin); + HTTPConduit http = + (HTTPConduit) client.getConduit(); + + HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy(); + + httpClientPolicy.setAutoRedirect(true); + // If we set any name, but Edward, Mary, or George, + // and a password of "password" we will get through + // Bethal. + AuthorizationPolicy authPolicy = new AuthorizationPolicy(); + authPolicy.setUserName("Betty"); + authPolicy.setPassword("password"); + + http.setClient(httpClientPolicy); + http.setTlsClientParameters(tlsClientParameters); + http.setAuthorization(authPolicy); + + // We get redirected from Tarpin, to Gordy, to Bethal. + MyHttpsTrustDecider trustDecider = + new MyHttpsTrustDecider( + new String[] {"Tarpin", "Gordy", "Bethal"}); + http.setTrustDecider(trustDecider); + + // We actually get our answer from Bethal at the end of the + // redirects. + configureProxy(ClientProxy.getClient(tarpin)); + String answer = tarpin.sayHi(); + assertProxyRequestCount(0); + + assertTrue("Trust Decider wasn't called correctly", + 3 == trustDecider.wasCalled()); + assertTrue("Unexpected answer: " + answer, + "Bonjour from Bethal".equals(answer)); + + // Limit the redirects to 1, since there are two, this should fail. + http.getClient().setMaxRetransmits(1); + + try { + answer = tarpin.sayHi(); + fail("Unexpected answer from Tarpin: " + answer); + } catch (Exception e) { + //e.printStackTrace(); + } + assertProxyRequestCount(0); + + // Set back to unlimited. + http.getClient().setMaxRetransmits(-1); + + // Effectively we will not trust Gordy in the middle. + trustDecider = + new MyHttpsTrustDecider( + new String[] {"Tarpin", "Bethal"}); + http.setTrustDecider(trustDecider); + + try { + answer = tarpin.sayHi(); + fail("Unexpected answer from Tarpin: " + answer); + } catch (Exception e) { + //e.printStackTrace(); + assertTrue("Trust Decider wasn't called correctly", + 2 == trustDecider.wasCalled()); + } + assertProxyRequestCount(0); + } + + public class MyBasicAuthSupplier implements HttpAuthSupplier { + + String realm; + String user; + String pass; + + /** + * This will loop from Cronus, to Andromeda, to Zorantius + */ + MyBasicAuthSupplier() { + } + + MyBasicAuthSupplier(String r, String u, String p) { + realm = r; + user = u; + pass = p; + } + + /** + * If we don't have the realm set, then we loop + * through the realms. + */ + public String getAuthorization( + AuthorizationPolicy authPolicy, + URI currentURI, + Message message, + String fullHeader + ) { + String reqestedRealm = new HttpAuthHeader(fullHeader).getRealm(); + if (realm != null && realm.equals(reqestedRealm)) { + return createUserPass(user, pass); + } + if ("Andromeda".equals(reqestedRealm)) { + // This will get us another 401 to Zorantius + return createUserPass("Edward", "password"); + } + if ("Zorantius".equals(reqestedRealm)) { + // George will get us another 401 to Cronus + return createUserPass("George", "password"); + } + if ("Cronus".equals(reqestedRealm)) { + // Mary will get us another 401 to Andromeda + return createUserPass("Mary", "password"); + } + return null; + } + + private String createUserPass(String usr, String pwd) { + String userpass = usr + ":" + pwd; + String token = Base64Utility.encode(userpass.getBytes()); + return "Basic " + token; + } + + public boolean requiresRequestCaching() { + return false; + } + + } + + /** + * This tests redirects through Gordy to Bethal. Bethal will + * supply a series of 401s. See PushBack401. + */ + @Test + public void testHttpsRedirect401Response() throws Exception { + startServer("Gordy"); + startServer("Bethal"); + + URL wsdl = getClass().getResource("greeting.wsdl"); + assertNotNull("WSDL is null", wsdl); + + SOAPService service = new SOAPService(wsdl, serviceName); + assertNotNull("Service is null", service); + + Greeter gordy = service.getPort(gordyQ, Greeter.class); + assertNotNull("Port is null", gordy); + updateAddressPort(gordy, getPort("PORT3")); + + // Okay, I'm sick of configuration files. + // This also tests dynamic configuration of the conduit. + Client client = ClientProxy.getClient(gordy); + HTTPConduit http = + (HTTPConduit) client.getConduit(); + + HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy(); + + httpClientPolicy.setAutoRedirect(true); + http.setClient(httpClientPolicy); + http.setTlsClientParameters(tlsClientParameters); + + // We get redirected from Gordy, to Bethal. + http.setTrustDecider( + new MyHttpsTrustDecider( + new String[] {"Gordy", "Bethal"})); + + // Without preemptive user/pass Bethal returns a + // 401 for realm Cronus. If we supply any name other + // than Edward, George, or Mary, with the pass of "password" + // we should succeed. + http.setAuthSupplier( + new MyBasicAuthSupplier("Cronus", "Betty", "password")); + + // We actually get our answer from Bethal at the end of the + // redirects. + String answer = gordy.sayHi(); + assertTrue("Unexpected answer: " + answer, + "Bonjour from Bethal".equals(answer)); + + // The loop auth supplier, + // We should die with looping realms. + http.setAuthSupplier(new MyBasicAuthSupplier()); + + try { + answer = gordy.sayHi(); + fail("Unexpected answer from Gordy: " + answer); + } catch (Exception e) { + //e.printStackTrace(); + } + } + +} + http://git-wip-us.apache.org/repos/asf/cxf/blob/e34329f6/systests/transports/src/test/java/org/apache/cxf/systest/https/HTTPSProxyAuthConduitTest.java ---------------------------------------------------------------------- diff --git a/systests/transports/src/test/java/org/apache/cxf/systest/https/HTTPSProxyAuthConduitTest.java b/systests/transports/src/test/java/org/apache/cxf/systest/https/HTTPSProxyAuthConduitTest.java new file mode 100644 index 0000000..2750f21 --- /dev/null +++ b/systests/transports/src/test/java/org/apache/cxf/systest/https/HTTPSProxyAuthConduitTest.java @@ -0,0 +1,111 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.cxf.systest.https; + +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.cxf.configuration.security.ProxyAuthorizationPolicy; +import org.apache.cxf.endpoint.Client; +import org.apache.cxf.transport.http.HTTPConduit; +import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; + +import org.jboss.netty.handler.codec.http.HttpRequest; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; + +import org.littleshoot.proxy.DefaultHttpProxyServer; +import org.littleshoot.proxy.HttpFilter; +import org.littleshoot.proxy.HttpRequestFilter; +import org.littleshoot.proxy.ProxyAuthorizationHandler; + + +/** + * + */ +public class HTTPSProxyAuthConduitTest extends HTTPSConduitTest { + static final int PROXY_PORT = Integer.parseInt(allocatePort(HTTPSProxyAuthConduitTest.class)); + static DefaultHttpProxyServer proxy; + static CountingFilter requestFilter = new CountingFilter(); + + static class CountingFilter implements HttpRequestFilter { + AtomicInteger count = new AtomicInteger(); + public void filter(HttpRequest httpRequest) { + count.incrementAndGet(); + } + + public void reset() { + count.set(0); + } + public int getCount() { + return count.get(); + } + } + + public HTTPSProxyAuthConduitTest() { + } + + + @AfterClass + public static void stopProxy() { + proxy.stop(); + proxy = null; + } + + @BeforeClass + public static void startProxy() { + proxy = new DefaultHttpProxyServer(PROXY_PORT, requestFilter, new HashMap<String, HttpFilter>()); + proxy.addProxyAuthenticationHandler(new ProxyAuthorizationHandler() { + public boolean authenticate(String userName, String password) { + return "password".equals(password) && "CXF".equals(userName); + } + }); + proxy.start(); + } + @Before + public void resetCount() { + requestFilter.reset(); + } + + public void configureProxy(Client client) { + HTTPConduit cond = (HTTPConduit)client.getConduit(); + HTTPClientPolicy pol = cond.getClient(); + if (pol == null) { + pol = new HTTPClientPolicy(); + cond.setClient(pol); + } + pol.setProxyServer("localhost"); + pol.setProxyServerPort(PROXY_PORT); + ProxyAuthorizationPolicy auth = new ProxyAuthorizationPolicy(); + auth.setUserName("CXF"); + auth.setPassword("password"); + cond.setProxyAuthorization(auth); + } + + public void resetProxyCount() { + requestFilter.reset(); + } + public void assertProxyRequestCount(int i) { + assertEquals("Unexpected request count", i, requestFilter.getCount()); + } + +} http://git-wip-us.apache.org/repos/asf/cxf/blob/e34329f6/systests/transports/src/test/java/org/apache/cxf/systest/https/HTTPSProxyConduitTest.java ---------------------------------------------------------------------- diff --git a/systests/transports/src/test/java/org/apache/cxf/systest/https/HTTPSProxyConduitTest.java b/systests/transports/src/test/java/org/apache/cxf/systest/https/HTTPSProxyConduitTest.java new file mode 100644 index 0000000..8171576 --- /dev/null +++ b/systests/transports/src/test/java/org/apache/cxf/systest/https/HTTPSProxyConduitTest.java @@ -0,0 +1,100 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.cxf.systest.https; + +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.cxf.endpoint.Client; +import org.apache.cxf.transport.http.HTTPConduit; +import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; + +import org.jboss.netty.handler.codec.http.HttpRequest; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; + +import org.littleshoot.proxy.DefaultHttpProxyServer; +import org.littleshoot.proxy.HttpFilter; +import org.littleshoot.proxy.HttpRequestFilter; + + +/** + * + */ +public class HTTPSProxyConduitTest extends HTTPSConduitTest { + static final int PROXY_PORT = Integer.parseInt(allocatePort(HTTPSProxyConduitTest.class)); + static DefaultHttpProxyServer proxy; + static CountingFilter requestFilter = new CountingFilter(); + + static class CountingFilter implements HttpRequestFilter { + AtomicInteger count = new AtomicInteger(); + public void filter(HttpRequest httpRequest) { + count.incrementAndGet(); + } + + public void reset() { + count.set(0); + } + public int getCount() { + return count.get(); + } + } + + public HTTPSProxyConduitTest() { + } + + + @AfterClass + public static void stopProxy() { + proxy.stop(); + proxy = null; + } + + @BeforeClass + public static void startProxy() { + proxy = new DefaultHttpProxyServer(PROXY_PORT, requestFilter, new HashMap<String, HttpFilter>()); + proxy.start(); + } + @Before + public void resetCount() { + requestFilter.reset(); + } + + public void configureProxy(Client client) { + HTTPConduit cond = (HTTPConduit)client.getConduit(); + HTTPClientPolicy pol = cond.getClient(); + if (pol == null) { + pol = new HTTPClientPolicy(); + cond.setClient(pol); + } + pol.setProxyServer("localhost"); + pol.setProxyServerPort(PROXY_PORT); + } + + public void resetProxyCount() { + requestFilter.reset(); + } + public void assertProxyRequestCount(int i) { + assertEquals("Unexpected request count", i, requestFilter.getCount()); + } + +} http://git-wip-us.apache.org/repos/asf/cxf/blob/e34329f6/systests/transports/src/test/java/org/apache/cxf/systest/https/Server.java ---------------------------------------------------------------------- diff --git a/systests/transports/src/test/java/org/apache/cxf/systest/https/Server.java b/systests/transports/src/test/java/org/apache/cxf/systest/https/Server.java new file mode 100644 index 0000000..12f3bbd --- /dev/null +++ b/systests/transports/src/test/java/org/apache/cxf/systest/https/Server.java @@ -0,0 +1,100 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.cxf.systest.https; + +import java.net.URL; + +import javax.xml.namespace.QName; + +import org.apache.cxf.bus.spring.SpringBusFactory; +import org.apache.cxf.jaxws.EndpointImpl; +import org.apache.cxf.systest.http.GreeterImpl; +import org.apache.cxf.testutil.common.AbstractBusTestServerBase; + +public class Server extends AbstractBusTestServerBase { + public static final String PORT = allocatePort(Server.class); + + private String name; + private String address; + private URL configFileURL; + private EndpointImpl ep; + + public Server(String[] args) throws Exception { + this(args[0], args[1], args[2]); + } + + public Server(String n, String addr, String conf) throws Exception { + name = n; + address = addr; + configFileURL = new URL(conf); + //System.out.println("Starting " + name + // + " Server at " + address + // + " with config " + configFileURL); + + } + public void tearDown() throws Exception { + if (ep != null) { + ep.stop(); + ep = null; + } + } + + protected void run() { + // We use a null binding id in the call to EndpointImpl + // constructor. Why? + final String nullBindingID = null; + + // We need to specify to use defaults on constructing the + // bus, because our configuration file doesn't have + // everything needed. + final boolean useDefaults = true; + + // We configure a new bus for this server. + setBus(new SpringBusFactory().createBus(configFileURL, useDefaults)); + + // This impl class must have the appropriate annotations + // to match the WSDL file that we are using. + Object implementor = new GreeterImpl(name); + + // I don't know why this works. + ep = + new EndpointImpl( + getBus(), + implementor, + nullBindingID, + this.getClass().getResource("greeting.wsdl").toString()); + // How the hell do I know what the name of the + // http-destination is from using this call? + ep.setEndpointName(new QName("http://apache.org/hello_world", name)); + ep.publish(address); + } + + + public static void main(String[] args) { + try { + Server s = new Server(args); + s.start(); + } catch (Exception ex) { + ex.printStackTrace(); + System.exit(-1); + } + } +} + http://git-wip-us.apache.org/repos/asf/cxf/blob/e34329f6/systests/transports/src/test/resources/org/apache/cxf/systest/https/Mortimer.cxf ---------------------------------------------------------------------- diff --git a/systests/transports/src/test/resources/org/apache/cxf/systest/https/Mortimer.cxf b/systests/transports/src/test/resources/org/apache/cxf/systest/https/Mortimer.cxf new file mode 100644 index 0000000..4a26c85 --- /dev/null +++ b/systests/transports/src/test/resources/org/apache/cxf/systest/https/Mortimer.cxf @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +--> + +<!-- + ** This file configures the Mortimer Server. + ** It is an http server. + --> + + +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:sec="http://cxf.apache.org/configuration/security" + xmlns:http="http://cxf.apache.org/transports/http/configuration" + xmlns:httpj="http://cxf.apache.org/transports/http-jetty/configuration" + xmlns:jaxws="http://java.sun.com/xml/ns/jaxws" + xsi:schemaLocation=" + http://cxf.apache.org/configuration/security + http://cxf.apache.org/schemas/configuration/security.xsd + http://cxf.apache.org/transports/http/configuration + http://cxf.apache.org/schemas/configuration/http-conf.xsd + http://cxf.apache.org/transports/http-jetty/configuration + http://cxf.apache.org/schemas/configuration/http-jetty.xsd + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd"> + + <http:destination name="{http://apache.org/hello_world}Mortimer.http-destination"> + <!-- Nothing to Configure here for Mortimer --> + </http:destination> + +</beans> http://git-wip-us.apache.org/repos/asf/cxf/blob/e34329f6/systests/transports/src/test/resources/org/apache/cxf/systest/https/greeting.wsdl ---------------------------------------------------------------------- diff --git a/systests/transports/src/test/resources/org/apache/cxf/systest/https/greeting.wsdl b/systests/transports/src/test/resources/org/apache/cxf/systest/https/greeting.wsdl new file mode 100644 index 0000000..c12bdfe --- /dev/null +++ b/systests/transports/src/test/resources/org/apache/cxf/systest/https/greeting.wsdl @@ -0,0 +1,166 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +--> +<wsdl:definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://apache.org/hello_world" xmlns:x1="http://apache.org/hello_world/types" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="HelloWorld" targetNamespace="http://apache.org/hello_world"> + <wsdl:types> + <schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://apache.org/hello_world/types" targetNamespace="http://apache.org/hello_world/types" elementFormDefault="qualified"> + <simpleType name="MyStringType"> + <restriction base="string"> + <maxLength value="30"/> + </restriction> + </simpleType> + <element name="sayHi"> + <complexType/> + </element> + <element name="sayHiResponse"> + <complexType> + <sequence> + <element name="responseType" type="string"/> + </sequence> + </complexType> + </element> + <element name="greetMe"> + <complexType> + <sequence> + <element name="requestType" type="tns:MyStringType"/> + </sequence> + </complexType> + </element> + <element name="greetMeResponse"> + <complexType> + <sequence> + <element name="responseType" type="string"/> + </sequence> + </complexType> + </element> + <element name="pingMe"> + <complexType/> + </element> + <element name="pingMeResponse"> + <complexType/> + </element> + <element name="faultDetail"> + <complexType> + <sequence> + <element name="minor" type="short"/> + <element name="major" type="short"/> + </sequence> + </complexType> + </element> + </schema> + </wsdl:types> + <wsdl:message name="sayHiRequest"> + <wsdl:part element="x1:sayHi" name="in"/> + </wsdl:message> + <wsdl:message name="sayHiResponse"> + <wsdl:part element="x1:sayHiResponse" name="out"/> + </wsdl:message> + <wsdl:message name="greetMeRequest"> + <wsdl:part element="x1:greetMe" name="in"/> + </wsdl:message> + <wsdl:message name="greetMeResponse"> + <wsdl:part element="x1:greetMeResponse" name="out"/> + </wsdl:message> + <wsdl:message name="pingMeRequest"> + <wsdl:part name="in" element="x1:pingMe"/> + </wsdl:message> + <wsdl:message name="pingMeResponse"> + <wsdl:part name="out" element="x1:pingMeResponse"/> + </wsdl:message> + <wsdl:message name="pingMeFault"> + <wsdl:part name="faultDetail" element="x1:faultDetail"/> + </wsdl:message> + <wsdl:portType name="Greeter"> + <wsdl:operation name="sayHi"> + <wsdl:input message="tns:sayHiRequest" name="sayHiRequest"/> + <wsdl:output message="tns:sayHiResponse" name="sayHiResponse"/> + </wsdl:operation> + <wsdl:operation name="greetMe"> + <wsdl:input message="tns:greetMeRequest" name="greetMeRequest"/> + <wsdl:output message="tns:greetMeResponse" name="greetMeResponse"/> + </wsdl:operation> + <wsdl:operation name="pingMe"> + <wsdl:input name="pingMeRequest" message="tns:pingMeRequest"/> + <wsdl:output name="pingMeResponse" message="tns:pingMeResponse"/> + <wsdl:fault name="pingMeFault" message="tns:pingMeFault"/> + </wsdl:operation> + </wsdl:portType> + <wsdl:binding name="Greeter_SOAPBinding" type="tns:Greeter"> + <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/> + <wsdl:operation name="sayHi"> + <soap:operation soapAction="" style="document"/> + <wsdl:input name="sayHiRequest"> + <soap:body use="literal"/> + </wsdl:input> + <wsdl:output name="sayHiResponse"> + <soap:body use="literal"/> + </wsdl:output> + </wsdl:operation> + <wsdl:operation name="greetMe"> + <soap:operation soapAction="" style="document"/> + <wsdl:input name="greetMeRequest"> + <soap:body use="literal"/> + </wsdl:input> + <wsdl:output name="greetMeResponse"> + <soap:body use="literal"/> + </wsdl:output> + </wsdl:operation> + <wsdl:operation name="pingMe"> + <soap:operation style="document"/> + <wsdl:input> + <soap:body use="literal"/> + </wsdl:input> + <wsdl:output> + <soap:body use="literal"/> + </wsdl:output> + <wsdl:fault name="pingMeFault"> + <soap:fault name="pingMeFault" use="literal"/> + </wsdl:fault> + </wsdl:operation> + </wsdl:binding> + <wsdl:service name="SOAPService"> + <wsdl:port binding="tns:Greeter_SOAPBinding" name="Mortimer"> + <soap:address location="http://localhost:9000/Mortimer"/> + </wsdl:port> + <wsdl:port binding="tns:Greeter_SOAPBinding" name="Tarpin"> + <soap:address location="https://localhost:9003/Tarpin"/> + </wsdl:port> + <wsdl:port binding="tns:Greeter_SOAPBinding" name="Rethwel"> + <soap:address location="http://localhost:9004/Rethwel"/> + </wsdl:port> + <wsdl:port binding="tns:Greeter_SOAPBinding" name="Gordy"> + <soap:address location="https://localhost:9001/Gordy"/> + </wsdl:port> + <wsdl:port binding="tns:Greeter_SOAPBinding" name="Bethal"> + <soap:address location="https://localhost:9002/Bethal"/> + </wsdl:port> + <wsdl:port binding="tns:Greeter_SOAPBinding" name="Hurlon"> + <soap:address location="http://localhost:9006/Hurlon"/> + </wsdl:port> + <wsdl:port binding="tns:Greeter_SOAPBinding" name="Poltim"> + <soap:address location="https://localhost:9005/Poltim"/> + </wsdl:port> + <wsdl:port binding="tns:Greeter_SOAPBinding" name="Abost"> + <soap:address location="https://localhost:9007/Abost"/> + </wsdl:port> + <wsdl:port binding="tns:Greeter_SOAPBinding" name="Morpit"> + <soap:address location="https://localhost:9008/Morpit"/> + </wsdl:port> + </wsdl:service> +</wsdl:definitions>
