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         private final String name;
93 
HttpTestAuthenticator(String name, String realm, String username)94         public HttpTestAuthenticator(String name, String realm, String username) {
95             this.name = name;
96             this.realm = realm;
97             this.username = username;
98         }
99 
100         @Override
getPasswordAuthentication()101         protected PasswordAuthentication getPasswordAuthentication() {
102             if (skipCount.get() == null || skipCount.get().booleanValue() == false) {
103                 System.out.println("Authenticator " + name + " called: " + count.incrementAndGet());
104             }
105             return new PasswordAuthentication(getUserName(),
106                     new char[] {'b','a','r'});
107         }
108 
109         // Called by the server side to get the password of the user
110         // being authentified.
getPassword(String user)111         public final char[] getPassword(String user) {
112             if (user.equals(username)) {
113                 skipCount.set(Boolean.TRUE);
114                 try {
115                     return getPasswordAuthentication().getPassword();
116                 } finally {
117                     skipCount.set(Boolean.FALSE);
118                 }
119             }
120             throw new SecurityException("User unknown: " + user);
121         }
122 
123         @Override
toString()124         public String toString() {
125             return super.toString() + "[name=\"" + name + "\"]";
126         }
127 
getUserName()128         public final String getUserName() {
129             return username;
130         }
getRealm()131         public final String getRealm() {
132             return realm;
133         }
134 
135     }
136     public static final HttpTestAuthenticator AUTHENTICATOR;
137     static {
138         AUTHENTICATOR = new HttpTestAuthenticator("AUTHENTICATOR","dublin", "foox");
139         Authenticator.setDefault(AUTHENTICATOR);
140     }
141 
142     static {
143         try {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } })144             HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
145                 public boolean verify(String hostname, SSLSession session) {
146                     return true;
147                 }
148             });
SSLContext.setDefault(new SimpleSSLContext().get())149             SSLContext.setDefault(new SimpleSSLContext().get());
150         } catch (IOException ex) {
151             throw new ExceptionInInitializerError(ex);
152         }
153     }
154 
155     static final Logger logger = Logger.getLogger ("com.sun.net.httpserver");
156     static {
157         if (DEBUG) logger.setLevel(Level.ALL);
158         Stream.of(Logger.getLogger("").getHandlers())
159               .forEach(h -> h.setLevel(Level.ALL));
160     }
161 
162     static final int EXPECTED_AUTH_CALLS_PER_TEST = 1;
163 
main(String[] args)164     public static void main(String[] args) throws Exception {
165         // new HTTPTest().execute(HttpAuthType.SERVER.name());
166         new HTTPTest().execute(args);
167     }
168 
execute(String... args)169     public void execute(String... args) throws Exception {
170         Stream<HttpAuthType> modes;
171         if (args == null || args.length == 0) {
172             modes = Stream.of(HttpAuthType.values());
173         } else {
174             modes = Stream.of(args).map(HttpAuthType::valueOf);
175         }
176         modes.forEach(this::test);
177         System.out.println("Test PASSED - Authenticator called: "
178                  + expected(AUTHENTICATOR.count.get()));
179     }
180 
test(HttpAuthType mode)181     public void test(HttpAuthType mode) {
182         for (HttpProtocolType type: HttpProtocolType.values()) {
183             test(type, mode);
184         }
185     }
186 
getHttpSchemeType()187     public HttpSchemeType getHttpSchemeType() {
188         return DEFAULT_SCHEME_TYPE;
189     }
190 
test(HttpProtocolType protocol, HttpAuthType mode)191     public void test(HttpProtocolType protocol, HttpAuthType mode) {
192         if (mode == HttpAuthType.PROXY305 && protocol == HttpProtocolType.HTTPS ) {
193             // silently skip unsupported test combination
194             return;
195         }
196         System.out.println("\n**** Testing " + protocol + " "
197                            + mode + " mode ****\n");
198         int authCount = AUTHENTICATOR.count.get();
199         int expectedIncrement = 0;
200         try {
201             // Creates an HTTP server that echoes back whatever is in the
202             // request body.
203             HTTPTestServer server =
204                     HTTPTestServer.create(protocol,
205                                           mode,
206                                           AUTHENTICATOR,
207                                           getHttpSchemeType());
208             try {
209                 expectedIncrement += run(server, protocol, mode);
210             } finally {
211                 server.stop();
212             }
213         }  catch (IOException ex) {
214             ex.printStackTrace(System.err);
215             throw new UncheckedIOException(ex);
216         }
217         int count = AUTHENTICATOR.count.get();
218         if (count != authCount + expectedIncrement) {
219             throw new AssertionError("Authenticator called " + count(count)
220                         + " expected it to be called "
221                         + expected(authCount + expectedIncrement));
222         }
223     }
224 
225     /**
226      * Runs the test with the given parameters.
227      * @param server    The server
228      * @param protocol  The protocol (HTTP/HTTPS)
229      * @param mode      The mode (PROXY, SERVER, SERVER307...)
230      * @return The number of times the default authenticator should have been
231      *         called.
232      * @throws IOException in case of connection or protocol issues
233      */
run(HTTPTestServer server, HttpProtocolType protocol, HttpAuthType mode)234     public int run(HTTPTestServer server,
235                    HttpProtocolType protocol,
236                    HttpAuthType mode)
237             throws IOException
238     {
239         // Connect to the server with a GET request, then with a
240         // POST that contains "Hello World!"
241         HTTPTestClient.connect(protocol, server, mode, null);
242         // return the number of times the default authenticator is supposed
243         // to have been called.
244         return EXPECTED_AUTH_CALLS_PER_TEST;
245     }
246 
count(int count)247     public static String count(int count) {
248         switch(count) {
249             case 0: return "not even once";
250             case 1: return "once";
251             case 2: return "twice";
252             default: return String.valueOf(count) + " times";
253         }
254     }
255 
expected(int count)256     public static String expected(int count) {
257         switch(count) {
258             default: return count(count);
259         }
260     }
protocol(HttpProtocolType type)261     public static String protocol(HttpProtocolType type) {
262         return type.name().toLowerCase(Locale.US);
263     }
264 
url(HttpProtocolType protocol, InetSocketAddress address, String path)265     public static URL url(HttpProtocolType protocol, InetSocketAddress address,
266                           String path) throws MalformedURLException {
267         return new URL(protocol(protocol),
268                        address.getHostString(),
269                        address.getPort(), path);
270     }
271 
proxy(HTTPTestServer server, HttpAuthType authType)272     public static Proxy proxy(HTTPTestServer server, HttpAuthType authType) {
273         return (authType == HttpAuthType.PROXY)
274                ? new Proxy(Proxy.Type.HTTP, server.getAddress())
275                : null;
276     }
277 
openConnection(URL url, HttpAuthType authType, Proxy proxy)278     public static HttpURLConnection openConnection(URL url,
279                                                    HttpAuthType authType,
280                                                    Proxy proxy)
281                                     throws IOException {
282 
283         HttpURLConnection conn = (HttpURLConnection)
284                 (authType == HttpAuthType.PROXY
285                     ? url.openConnection(proxy)
286                     : url.openConnection());
287         return conn;
288     }
289 }
290