1 /*
   2  * Copyright (c) 2016, 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 /**
  25  * @test
  26  * @modules jdk.incubator.httpclient
  27  *          jdk.httpserver
  28  * @run main/othervm MultiAuthTest
  29  * @summary Basic Authentication test with multiple clients issuing
  30  *          multiple requests. Includes password changes
  31  *          on server and client side.
  32  */
  33 
  34 import com.sun.net.httpserver.BasicAuthenticator;
  35 import com.sun.net.httpserver.HttpContext;
  36 import com.sun.net.httpserver.HttpExchange;
  37 import com.sun.net.httpserver.HttpHandler;
  38 import com.sun.net.httpserver.HttpServer;
  39 import java.io.IOException;
  40 import java.io.InputStream;
  41 import java.io.OutputStream;
  42 import java.net.InetSocketAddress;
  43 import java.net.PasswordAuthentication;
  44 import java.net.URI;
  45 import jdk.incubator.http.*;
  46 import java.util.concurrent.ExecutorService;
  47 import java.util.concurrent.Executors;
  48 import static java.nio.charset.StandardCharsets.US_ASCII;
  49 import static jdk.incubator.http.HttpRequest.BodyProcessor.fromString;
  50 import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
  51 import java.util.UUID;
  52 import java.util.concurrent.atomic.AtomicInteger;
  53 import java.util.function.Function;
  54 
  55 public class MultiAuthTest {
  56 
  57     static volatile boolean ok;
  58     static final String RESPONSE = "Hello world";
  59     static final String POST_BODY = "This is the POST body " + UUID.randomUUID();
  60 
  61     static HttpServer createServer(ExecutorService e, BasicAuthenticator sa) throws Exception {
  62         HttpServer server = HttpServer.create(new InetSocketAddress(0), 10);
  63         Handler h = new Handler();
  64         HttpContext serverContext = server.createContext("/test", h);
  65         serverContext.setAuthenticator(sa);
  66         server.setExecutor(e);
  67         server.start();
  68         return server;
  69     }
  70 
  71     public interface HttpRequestBuilderFactory extends Function<URI, HttpRequest.Builder> {
  72 
  73         default HttpRequest.Builder request(URI uri) {
  74             return this.apply(uri);
  75         }
  76     }
  77 
  78 
  79     public static void main(String[] args) throws Exception {
  80         ExecutorService e = Executors.newCachedThreadPool();
  81         ServerAuth sa = new ServerAuth("foo realm");
  82         HttpServer server = createServer(e, sa);
  83         int port = server.getAddress().getPort();
  84         System.out.println("Server port = " + port);
  85 
  86         ClientAuth ca = new ClientAuth();
  87         HttpClient client1 = HttpClient.newBuilder()
  88                                        .authenticator(ca)
  89                                        .build();
  90         HttpClient client2 = HttpClient.newBuilder()
  91                                        .authenticator(ca)
  92                                        .build();
  93         HttpClient client3 = HttpClient.newHttpClient();
  94 
  95         try {
  96             URI uri = new URI("http://127.0.0.1:" + port + "/test/foo");
  97             System.out.println("URI: " + uri);
  98 
  99             System.out.println("\nTesting with client #1, Authenticator #1");
 100             test(client1, ca, uri, 1, null);
 101             System.out.println("Testing again with client #1, Authenticator #1");
 102             test(client1, ca, uri, 1, null);
 103             System.out.println("Testing with client #2, Authenticator #1");
 104             test(client2, ca, uri, 2, null);
 105 
 106             System.out.println("Testing with default client"
 107                                + " (HttpClient.newHttpClient()), no authenticator");
 108             test(HttpClient.newHttpClient(), ca, uri, 2, IOException.class);
 109 
 110             System.out.println("\nSetting default authenticator\n");
 111             java.net.Authenticator.setDefault(ca);
 112 
 113             System.out.println("Testing default client"
 114                                + " (HttpClient.newHttpClient()), no authenticator");
 115             test(HttpClient.newHttpClient(), ca, uri, 3, IOException.class);
 116 
 117             System.out.println("Testing with client #4, no authenticator");
 118             test(client3, ca, uri, 4, IOException.class);
 119 
 120             String oldpwd = sa.passwd;
 121             sa.passwd = "changed";
 122             System.out.println("\nChanged server password\n");
 123 
 124             sa.passwd = "changed";
 125             System.out.println("\nChanged server password\n");
 126 
 127             System.out.println("Testing with client #1, Authenticator #1"
 128                                 + " (count=" + ca.count.get() +")");
 129             test(client1, ca, uri, 7, IOException.class);
 130             System.out.println("Testing again with client #1, Authenticator #1"
 131                                 + " (count=" + ca.count.get() +")");
 132             test(client1, ca, uri, 10, IOException.class);
 133             System.out.println("Testing with client #2, Authenticator #1"
 134                                 + " (count=" + ca.count.get() +")");
 135             test(client2, ca, uri, 14, IOException.class);
 136 
 137             System.out.println("\nRestored server password"
 138                                 + " (count=" + ca.count.get() +")\n");
 139             sa.passwd = oldpwd;
 140 
 141             int count = ca.count.get(); // depends on retry limit...
 142             System.out.println("Testing with client #1, Authenticator #1");
 143             test(client1, ca, uri, count+1, null);
 144             System.out.println("Testing again with client #1, Authenticator #1");
 145             test(client1, ca, uri, count+1, null);
 146             System.out.println("Testing with client #2, Authenticator #1");
 147             test(client2, ca, uri, count+2, null);
 148 
 149             sa.passwd = ca.passwd = "changed#2";
 150             System.out.println("\nChanged password on both sides\n");
 151 
 152             System.out.println("Testing with client #1, Authenticator #1");
 153             test(client1, ca, uri, count+3, null);
 154             System.out.println("Testing again with client #1, Authenticator #1");
 155             test(client1, ca, uri, count+3, null);
 156             System.out.println("Testing with client #2, Authenticator #1");
 157             test(client2, ca, uri, count+4, null);
 158         } finally {
 159             server.stop(0);
 160             e.shutdownNow();
 161         }
 162         System.out.println("OK");
 163     }
 164 
 165     static void test(HttpClient client,
 166                      ClientAuth ca,
 167                      URI uri,
 168                      int expectCount,
 169                      Class<? extends Exception> expectFailure)
 170         throws IOException, InterruptedException
 171     {
 172         HttpRequest req = HttpRequest.newBuilder(uri).GET().build();
 173 
 174         HttpResponse resp;
 175         try {
 176             resp = client.send(req, asString());
 177             ok = resp.statusCode() == 200 &&
 178                 resp.body().equals(RESPONSE);
 179             if (expectFailure != null) {
 180                 throw new RuntimeException("Expected " + expectFailure.getName()
 181                          +" not raised");
 182             }
 183         } catch (IOException io) {
 184             if (expectFailure != null) {
 185                 if (expectFailure.isInstance(io)) {
 186                     System.out.println("Got expected exception: " + io);
 187                     return;
 188                 }
 189             }
 190             throw io;
 191         }
 192 
 193         if (!ok || ca.count.get() != expectCount)
 194             throw new RuntimeException("Test failed: ok=" + ok
 195                  + " count=" + ca.count.get() + " (expected=" + expectCount+")");
 196 
 197         // repeat same request, should succeed but no additional authenticator calls
 198         resp = client.send(req, asString());
 199         ok = resp.statusCode() == 200 &&
 200                 resp.body().equals(RESPONSE);
 201 
 202         if (!ok || ca.count.get() != expectCount)
 203             throw new RuntimeException("Test failed: ok=" + ok
 204                  + " count=" + ca.count.get() + " (expected=" + expectCount+")");
 205 
 206         // try a POST
 207         req = HttpRequest.newBuilder(uri)
 208                          .POST(fromString(POST_BODY))
 209                          .build();
 210         resp = client.send(req, asString());
 211         ok = resp.statusCode() == 200;
 212 
 213         if (!ok || ca.count.get() != expectCount)
 214             throw new RuntimeException("Test failed");
 215 
 216     }
 217 
 218     static class ServerAuth extends BasicAuthenticator {
 219 
 220         volatile String passwd = "passwd";
 221 
 222         ServerAuth(String realm) {
 223             super(realm);
 224         }
 225 
 226         @Override
 227         public boolean checkCredentials(String username, String password) {
 228             if (!"user".equals(username) || !passwd.equals(password)) {
 229                 return false;
 230             }
 231             return true;
 232         }
 233 
 234     }
 235 
 236     static class ClientAuth extends java.net.Authenticator {
 237         final AtomicInteger count = new AtomicInteger();
 238         volatile String passwd = "passwd";
 239 
 240         @Override
 241         protected PasswordAuthentication getPasswordAuthentication() {
 242             count.incrementAndGet();
 243             return new PasswordAuthentication("user", passwd.toCharArray());
 244         }
 245     }
 246 
 247    static class Handler implements HttpHandler {
 248         static volatile boolean ok;
 249 
 250         @Override
 251         public void handle(HttpExchange he) throws IOException {
 252             String method = he.getRequestMethod();
 253             InputStream is = he.getRequestBody();
 254             if (method.equalsIgnoreCase("POST")) {
 255                 String requestBody = new String(is.readAllBytes(), US_ASCII);
 256                 if (!requestBody.equals(POST_BODY)) {
 257                     he.sendResponseHeaders(500, -1);
 258                     ok = false;
 259                 } else {
 260                     he.sendResponseHeaders(200, -1);
 261                     ok = true;
 262                 }
 263             } else { // GET
 264                 he.sendResponseHeaders(200, RESPONSE.length());
 265                 OutputStream os = he.getResponseBody();
 266                 os.write(RESPONSE.getBytes(US_ASCII));
 267                 os.close();
 268                 ok = true;
 269             }
 270         }
 271 
 272    }
 273 }