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