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
34import com.sun.net.httpserver.BasicAuthenticator;
35import com.sun.net.httpserver.HttpContext;
36import com.sun.net.httpserver.HttpExchange;
37import com.sun.net.httpserver.HttpHandler;
38import com.sun.net.httpserver.HttpServer;
39import java.io.IOException;
40import java.io.InputStream;
41import java.io.OutputStream;
42import java.net.InetSocketAddress;
43import java.net.PasswordAuthentication;
44import java.net.URI;
45import jdk.incubator.http.*;
46import java.util.concurrent.ExecutorService;
47import java.util.concurrent.Executors;
48import static java.nio.charset.StandardCharsets.US_ASCII;
49import static jdk.incubator.http.HttpRequest.BodyProcessor.fromString;
50import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
51import java.util.UUID;
52import java.util.concurrent.atomic.AtomicInteger;
53import java.util.function.Function;
54
55public 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}
274