1 /*
2  * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 import java.io.IOException;
25 import java.io.UncheckedIOException;
26 import java.net.Authenticator;
27 import java.net.HttpURLConnection;
28 import java.net.InetSocketAddress;
29 import java.net.MalformedURLException;
30 import java.net.PasswordAuthentication;
31 import java.net.Proxy;
32 import java.net.URL;
33 import java.util.Locale;
34 import java.util.concurrent.atomic.AtomicInteger;
35 import java.util.logging.Level;
36 import java.util.logging.Logger;
37 import java.util.stream.Stream;
38 import javax.net.ssl.HostnameVerifier;
39 import javax.net.ssl.HttpsURLConnection;
40 import javax.net.ssl.SSLContext;
41 import javax.net.ssl.SSLSession;
42 import jdk.test.lib.net.SimpleSSLContext;
43 
44 /*
45  * @test
46  * @bug 8169415
47  * @library /test/lib
48  * @modules java.logging
49  *          java.base/sun.net.www
50  *          jdk.httpserver/sun.net.httpserver
51  * @build jdk.test.lib.net.SimpleSSLContext HTTPTest HTTPTestServer HTTPTestClient
52  * @summary A simple HTTP test that starts an echo server supporting Digest
53  *          authentication, then starts a regular HTTP client to invoke it.
54  *          The client first does a GET request on "/", then follows on
55  *          with a POST request that sends "Hello World!" to the server.
56  *          The client expects to receive "Hello World!" in return.
57  *          The test supports several execution modes:
58  *            SERVER: The server performs Digest Server authentication;
59  *            PROXY:  The server pretends to be a proxy and performs
60  *                    Digest Proxy authentication;
61  *            SERVER307: The server redirects the client (307) to another
62  *                    server that perform Digest authentication;
63  *            PROXY305: The server attempts to redirect
64  *                    the client to a proxy using 305 code;
65  * @run main/othervm HTTPTest SERVER
66  * @run main/othervm HTTPTest PROXY
67  * @run main/othervm HTTPTest SERVER307
68  * @run main/othervm HTTPTest PROXY305
69  *
70  * @author danielfuchs
71  */
72 public class HTTPTest {
73 
74     public static final boolean DEBUG =
75          Boolean.parseBoolean(System.getProperty("test.debug", "false"));
76     public static enum HttpAuthType { SERVER, PROXY, SERVER307, PROXY305 };
77     public static enum HttpProtocolType { HTTP, HTTPS };
78     public static enum HttpSchemeType { NONE, BASICSERVER, BASIC, DIGEST };
79     public static final HttpAuthType DEFAULT_HTTP_AUTH_TYPE = HttpAuthType.SERVER;
80     public static final HttpProtocolType DEFAULT_PROTOCOL_TYPE = HttpProtocolType.HTTP;
81     public static final HttpSchemeType DEFAULT_SCHEME_TYPE = HttpSchemeType.DIGEST;
82 
83     public static class HttpTestAuthenticator extends Authenticator {
84         private final String realm;
85         private final String username;
86         // Used to prevent incrementation of 'count' when calling the
87         // authenticator from the server side.
88         private final ThreadLocal<Boolean> skipCount = new ThreadLocal<>();
89         // count will be incremented every time getPasswordAuthentication()
90         // is called from the client side.
91         final AtomicInteger count = new AtomicInteger();
92 
HttpTestAuthenticator(String realm, String username)93         public HttpTestAuthenticator(String realm, String username) {
94             this.realm = realm;
95             this.username = username;
96         }
97 
98         @Override
getPasswordAuthentication()99         protected PasswordAuthentication getPasswordAuthentication() {
100             if (skipCount.get() == null || skipCount.get().booleanValue() == false) {
101                 System.out.println("Authenticator called: " + count.incrementAndGet());
102             }
103             return new PasswordAuthentication(getUserName(),
104                     new char[] {'b','a','r'});
105         }
106 
107         // Called by the server side to get the password of the user
108         // being authentified.
getPassword(String user)109         public final char[] getPassword(String user) {
110             if (user.equals(username)) {
111                 skipCount.set(Boolean.TRUE);
112                 try {
113                     return getPasswordAuthentication().getPassword();
114                 } finally {
115                     skipCount.set(Boolean.FALSE);
116                 }
117             }
118             throw new SecurityException("User unknown: " + user);
119         }
120 
getUserName()121         public final String getUserName() {
122             return username;
123         }
getRealm()124         public final String getRealm() {
125             return realm;
126         }
127 
128     }
129     public static final HttpTestAuthenticator AUTHENTICATOR;
130     static {
131         AUTHENTICATOR = new HttpTestAuthenticator("dublin", "foox");
132         Authenticator.setDefault(AUTHENTICATOR);
133     }
134 
135     static {
136         try {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } })137             HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
138                 public boolean verify(String hostname, SSLSession session) {
139                     return true;
140                 }
141             });
SSLContext.setDefault(new SimpleSSLContext().get())142             SSLContext.setDefault(new SimpleSSLContext().get());
143         } catch (IOException ex) {
144             throw new ExceptionInInitializerError(ex);
145         }
146     }
147 
148     static final Logger logger = Logger.getLogger ("com.sun.net.httpserver");
149     static {
150         if (DEBUG) logger.setLevel(Level.ALL);
151         Stream.of(Logger.getLogger("").getHandlers())
152               .forEach(h -> h.setLevel(Level.ALL));
153     }
154 
155     static final int EXPECTED_AUTH_CALLS_PER_TEST = 1;
156 
main(String[] args)157     public static void main(String[] args) throws Exception {
158         // new HTTPTest().execute(HttpAuthType.SERVER.name());
159         new HTTPTest().execute(args);
160     }
161 
execute(String... args)162     public void execute(String... args) throws Exception {
163         Stream<HttpAuthType> modes;
164         if (args == null || args.length == 0) {
165             modes = Stream.of(HttpAuthType.values());
166         } else {
167             modes = Stream.of(args).map(HttpAuthType::valueOf);
168         }
169         modes.forEach(this::test);
170         System.out.println("Test PASSED - Authenticator called: "
171                  + expected(AUTHENTICATOR.count.get()));
172     }
173 
test(HttpAuthType mode)174     public void test(HttpAuthType mode) {
175         for (HttpProtocolType type: HttpProtocolType.values()) {
176             test(type, mode);
177         }
178     }
179 
getHttpSchemeType()180     public HttpSchemeType getHttpSchemeType() {
181         return DEFAULT_SCHEME_TYPE;
182     }
183 
test(HttpProtocolType protocol, HttpAuthType mode)184     public void test(HttpProtocolType protocol, HttpAuthType mode) {
185         if (mode == HttpAuthType.PROXY305 && protocol == HttpProtocolType.HTTPS ) {
186             // silently skip unsupported test combination
187             return;
188         }
189         System.out.println("\n**** Testing " + protocol + " "
190                            + mode + " mode ****\n");
191         int authCount = AUTHENTICATOR.count.get();
192         int expectedIncrement = 0;
193         try {
194             // Creates an HTTP server that echoes back whatever is in the
195             // request body.
196             HTTPTestServer server =
197                     HTTPTestServer.create(protocol,
198                                           mode,
199                                           AUTHENTICATOR,
200                                           getHttpSchemeType());
201             try {
202                 expectedIncrement += run(server, protocol, mode);
203             } finally {
204                 server.stop();
205             }
206         }  catch (IOException ex) {
207             ex.printStackTrace(System.err);
208             throw new UncheckedIOException(ex);
209         }
210         int count = AUTHENTICATOR.count.get();
211         if (count != authCount + expectedIncrement) {
212             throw new AssertionError("Authenticator called " + count(count)
213                         + " expected it to be called "
214                         + expected(authCount + expectedIncrement));
215         }
216     }
217 
218     /**
219      * Runs the test with the given parameters.
220      * @param server    The server
221      * @param protocol  The protocol (HTTP/HTTPS)
222      * @param mode      The mode (PROXY, SERVER, SERVER307...)
223      * @return The number of times the default authenticator should have been
224      *         called.
225      * @throws IOException in case of connection or protocol issues
226      */
run(HTTPTestServer server, HttpProtocolType protocol, HttpAuthType mode)227     public int run(HTTPTestServer server,
228                    HttpProtocolType protocol,
229                    HttpAuthType mode)
230             throws IOException
231     {
232         // Connect to the server with a GET request, then with a
233         // POST that contains "Hello World!"
234         HTTPTestClient.connect(protocol, server, mode, null);
235         // return the number of times the default authenticator is supposed
236         // to have been called.
237         return EXPECTED_AUTH_CALLS_PER_TEST;
238     }
239 
count(int count)240     public static String count(int count) {
241         switch(count) {
242             case 0: return "not even once";
243             case 1: return "once";
244             case 2: return "twice";
245             default: return String.valueOf(count) + " times";
246         }
247     }
248 
expected(int count)249     public static String expected(int count) {
250         switch(count) {
251             default: return count(count);
252         }
253     }
protocol(HttpProtocolType type)254     public static String protocol(HttpProtocolType type) {
255         return type.name().toLowerCase(Locale.US);
256     }
257 
url(HttpProtocolType protocol, InetSocketAddress address, String path)258     public static URL url(HttpProtocolType protocol, InetSocketAddress address,
259                           String path) throws MalformedURLException {
260         return new URL(protocol(protocol),
261                        address.getHostString(),
262                        address.getPort(), path);
263     }
264 
proxy(HTTPTestServer server, HttpAuthType authType)265     public static Proxy proxy(HTTPTestServer server, HttpAuthType authType) {
266         return (authType == HttpAuthType.PROXY)
267                ? new Proxy(Proxy.Type.HTTP, server.getAddress())
268                : null;
269     }
270 
openConnection(URL url, HttpAuthType authType, Proxy proxy)271     public static HttpURLConnection openConnection(URL url,
272                                                    HttpAuthType authType,
273                                                    Proxy proxy)
274                                     throws IOException {
275 
276         HttpURLConnection conn = (HttpURLConnection)
277                 (authType == HttpAuthType.PROXY
278                     ? url.openConnection(proxy)
279                     : url.openConnection());
280         return conn;
281     }
282 }
283