blob: 042ad693d5200c06ee1968d7edaf22af6496acd6 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1995-2003 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.net.www.protocol.http;
27
28import java.io.*;
29import java.net.*;
30import java.util.Hashtable;
31import java.util.LinkedList;
32import java.util.ListIterator;
33import java.util.Enumeration;
34import java.util.HashMap;
35
36import sun.net.www.HeaderParser;
37
38
39/**
40 * AuthenticationInfo: Encapsulate the information needed to
41 * authenticate a user to a server.
42 *
43 * @author Jon Payne
44 * @author Herb Jellinek
45 * @author Bill Foote
46 */
47// REMIND: It would be nice if this class understood about partial matching.
48// If you're authorized for foo.com, chances are high you're also
49// authorized for baz.foo.com.
50// NB: When this gets implemented, be careful about the uncaching
51// policy in HttpURLConnection. A failure on baz.foo.com shouldn't
52// uncache foo.com!
53
54abstract class AuthenticationInfo extends AuthCacheValue implements Cloneable {
55
56 // Constants saying what kind of authroization this is. This determines
57 // the namespace in the hash table lookup.
58 static final char SERVER_AUTHENTICATION = 's';
59 static final char PROXY_AUTHENTICATION = 'p';
60
61 /**
62 * If true, then simultaneous authentication requests to the same realm/proxy
63 * are serialized, in order to avoid a user having to type the same username/passwords
64 * repeatedly, via the Authenticator. Default is false, which means that this
65 * behavior is switched off.
66 */
67 static boolean serializeAuth;
68
69 static {
70 serializeAuth = java.security.AccessController.doPrivileged(
71 new sun.security.action.GetBooleanAction(
72 "http.auth.serializeRequests")).booleanValue();
73 }
74
75 /* AuthCacheValue: */
76
77 transient protected PasswordAuthentication pw;
78
79 public PasswordAuthentication credentials() {
80 return pw;
81 }
82
83 public AuthCacheValue.Type getAuthType() {
84 return type == SERVER_AUTHENTICATION ?
85 AuthCacheValue.Type.Server:
86 AuthCacheValue.Type.Proxy;
87 }
88 public String getHost() {
89 return host;
90 }
91 public int getPort() {
92 return port;
93 }
94 public String getRealm() {
95 return realm;
96 }
97 public String getPath() {
98 return path;
99 }
100 public String getProtocolScheme() {
101 return protocol;
102 }
103
104 /**
105 * requests is used to ensure that interaction with the
106 * Authenticator for a particular realm is single threaded.
107 * ie. if multiple threads need to get credentials from the user
108 * at the same time, then all but the first will block until
109 * the first completes its authentication.
110 */
111 static private HashMap requests = new HashMap ();
112
113 /* check if a request for this destination is in progress
114 * return false immediately if not. Otherwise block until
115 * request is finished and return true
116 */
117 static private boolean requestIsInProgress (String key) {
118 if (!serializeAuth) {
119 /* behavior is disabled. Revert to concurrent requests */
120 return false;
121 }
122 synchronized (requests) {
123 Thread t, c;
124 c = Thread.currentThread();
125 if ((t=(Thread)requests.get(key))==null) {
126 requests.put (key, c);
127 return false;
128 }
129 if (t == c) {
130 return false;
131 }
132 while (requests.containsKey(key)) {
133 try {
134 requests.wait ();
135 } catch (InterruptedException e) {}
136 }
137 }
138 /* entry may be in cache now. */
139 return true;
140 }
141
142 /* signal completion of an authentication (whether it succeeded or not)
143 * so that other threads can continue.
144 */
145 static private void requestCompleted (String key) {
146 synchronized (requests) {
147 boolean waspresent = requests.remove (key) != null;
148 assert waspresent;
149 requests.notifyAll();
150 }
151 }
152
153 //public String toString () {
154 //return ("{"+type+":"+authType+":"+protocol+":"+host+":"+port+":"+realm+":"+path+"}");
155 //}
156
157 // REMIND: This cache just grows forever. We should put in a bounded
158 // cache, or maybe something using WeakRef's.
159
160 /** The type (server/proxy) of authentication this is. Used for key lookup */
161 char type;
162
163 /** The authentication type (basic/digest). Also used for key lookup */
164 char authType;
165
166 /** The protocol/scheme (i.e. http or https ). Need to keep the caches
167 * logically separate for the two protocols. This field is only used
168 * when constructed with a URL (the normal case for server authentication)
169 * For proxy authentication the protocol is not relevant.
170 */
171 String protocol;
172
173 /** The host we're authenticating against. */
174 String host;
175
176 /** The port on the host we're authenticating against. */
177 int port;
178
179 /** The realm we're authenticating against. */
180 String realm;
181
182 /** The shortest path from the URL we authenticated against. */
183 String path;
184
185 /** Use this constructor only for proxy entries */
186 AuthenticationInfo(char type, char authType, String host, int port, String realm) {
187 this.type = type;
188 this.authType = authType;
189 this.protocol = "";
190 this.host = host.toLowerCase();
191 this.port = port;
192 this.realm = realm;
193 this.path = null;
194 }
195
196 public Object clone() {
197 try {
198 return super.clone ();
199 } catch (CloneNotSupportedException e) {
200 // Cannot happen because Cloneable implemented by AuthenticationInfo
201 return null;
202 }
203 }
204
205 /*
206 * Constructor used to limit the authorization to the path within
207 * the URL. Use this constructor for origin server entries.
208 */
209 AuthenticationInfo(char type, char authType, URL url, String realm) {
210 this.type = type;
211 this.authType = authType;
212 this.protocol = url.getProtocol().toLowerCase();
213 this.host = url.getHost().toLowerCase();
214 this.port = url.getPort();
215 if (this.port == -1) {
216 this.port = url.getDefaultPort();
217 }
218 this.realm = realm;
219
220 String urlPath = url.getPath();
221 if (urlPath.length() == 0)
222 this.path = urlPath;
223 else {
224 this.path = reducePath (urlPath);
225 }
226
227 }
228
229 /*
230 * reduce the path to the root of where we think the
231 * authorization begins. This could get shorter as
232 * the url is traversed up following a successful challenge.
233 */
234 static String reducePath (String urlPath) {
235 int sepIndex = urlPath.lastIndexOf('/');
236 int targetSuffixIndex = urlPath.lastIndexOf('.');
237 if (sepIndex != -1)
238 if (sepIndex < targetSuffixIndex)
239 return urlPath.substring(0, sepIndex+1);
240 else
241 return urlPath;
242 else
243 return urlPath;
244 }
245
246 /**
247 * Returns info for the URL, for an HTTP server auth. Used when we
248 * don't yet know the realm
249 * (i.e. when we're preemptively setting the auth).
250 */
251 static AuthenticationInfo getServerAuth(URL url) {
252 int port = url.getPort();
253 if (port == -1) {
254 port = url.getDefaultPort();
255 }
256 String key = SERVER_AUTHENTICATION + ":" + url.getProtocol().toLowerCase()
257 + ":" + url.getHost().toLowerCase() + ":" + port;
258 return getAuth(key, url);
259 }
260
261 /**
262 * Returns info for the URL, for an HTTP server auth. Used when we
263 * do know the realm (i.e. when we're responding to a challenge).
264 * In this case we do not use the path because the protection space
265 * is identified by the host:port:realm only
266 */
267 static AuthenticationInfo getServerAuth(URL url, String realm, char atype) {
268 int port = url.getPort();
269 if (port == -1) {
270 port = url.getDefaultPort();
271 }
272 String key = SERVER_AUTHENTICATION + ":" + atype + ":" + url.getProtocol().toLowerCase()
273 + ":" + url.getHost().toLowerCase() + ":" + port + ":" + realm;
274 AuthenticationInfo cached = getAuth(key, null);
275 if ((cached == null) && requestIsInProgress (key)) {
276 /* check the cache again, it might contain an entry */
277 cached = getAuth(key, null);
278 }
279 return cached;
280 }
281
282
283 /**
284 * Return the AuthenticationInfo object from the cache if it's path is
285 * a substring of the supplied URLs path.
286 */
287 static AuthenticationInfo getAuth(String key, URL url) {
288 if (url == null) {
289 return (AuthenticationInfo)cache.get (key, null);
290 } else {
291 return (AuthenticationInfo)cache.get (key, url.getPath());
292 }
293 }
294
295 /**
296 * Returns a firewall authentication, for the given host/port. Used
297 * for preemptive header-setting. Note, the protocol field is always
298 * blank for proxies.
299 */
300 static AuthenticationInfo getProxyAuth(String host, int port) {
301 String key = PROXY_AUTHENTICATION + "::" + host.toLowerCase() + ":" + port;
302 AuthenticationInfo result = (AuthenticationInfo) cache.get(key, null);
303 return result;
304 }
305
306 /**
307 * Returns a firewall authentication, for the given host/port and realm.
308 * Used in response to a challenge. Note, the protocol field is always
309 * blank for proxies.
310 */
311 static AuthenticationInfo getProxyAuth(String host, int port, String realm, char atype) {
312 String key = PROXY_AUTHENTICATION + ":" + atype + "::" + host.toLowerCase()
313 + ":" + port + ":" + realm;
314 AuthenticationInfo cached = (AuthenticationInfo) cache.get(key, null);
315 if ((cached == null) && requestIsInProgress (key)) {
316 /* check the cache again, it might contain an entry */
317 cached = (AuthenticationInfo) cache.get(key, null);
318 }
319 return cached;
320 }
321
322
323 /**
324 * Add this authentication to the cache
325 */
326 void addToCache() {
327 cache.put (cacheKey(true), this);
328 if (supportsPreemptiveAuthorization()) {
329 cache.put (cacheKey(false), this);
330 }
331 endAuthRequest();
332 }
333
334 void endAuthRequest () {
335 if (!serializeAuth) {
336 return;
337 }
338 synchronized (requests) {
339 requestCompleted (cacheKey(true));
340 }
341 }
342
343 /**
344 * Remove this authentication from the cache
345 */
346 void removeFromCache() {
347 cache.remove(cacheKey(true), this);
348 if (supportsPreemptiveAuthorization()) {
349 cache.remove(cacheKey(false), this);
350 }
351 }
352
353 /**
354 * @return true if this authentication supports preemptive authorization
355 */
356 abstract boolean supportsPreemptiveAuthorization();
357
358 /**
359 * @return the name of the HTTP header this authentication wants set.
360 * This is used for preemptive authorization.
361 */
362 abstract String getHeaderName();
363
364 /**
365 * Calculates and returns the authentication header value based
366 * on the stored authentication parameters. If the calculation does not depend
367 * on the URL or the request method then these parameters are ignored.
368 * @param url The URL
369 * @param method The request method
370 * @return the value of the HTTP header this authentication wants set.
371 * Used for preemptive authorization.
372 */
373 abstract String getHeaderValue(URL url, String method);
374
375 /**
376 * Set header(s) on the given connection. Subclasses must override
377 * This will only be called for
378 * definitive (i.e. non-preemptive) authorization.
379 * @param conn The connection to apply the header(s) to
380 * @param p A source of header values for this connection, if needed.
381 * @param raw The raw header field (if needed)
382 * @return true if all goes well, false if no headers were set.
383 */
384 abstract boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw);
385
386 /**
387 * Check if the header indicates that the current auth. parameters are stale.
388 * If so, then replace the relevant field with the new value
389 * and return true. Otherwise return false.
390 * returning true means the request can be retried with the same userid/password
391 * returning false means we have to go back to the user to ask for a new
392 * username password.
393 */
394 abstract boolean isAuthorizationStale (String header);
395
396 /**
397 * Check for any expected authentication information in the response
398 * from the server
399 */
400 abstract void checkResponse (String header, String method, URL url)
401 throws IOException;
402
403 /**
404 * Give a key for hash table lookups.
405 * @param includeRealm if you want the realm considered. Preemptively
406 * setting an authorization is done before the realm is known.
407 */
408 String cacheKey(boolean includeRealm) {
409 // This must be kept in sync with the getXXXAuth() methods in this
410 // class.
411 if (includeRealm) {
412 return type + ":" + authType + ":" + protocol + ":"
413 + host + ":" + port + ":" + realm;
414 } else {
415 return type + ":" + protocol + ":" + host + ":" + port;
416 }
417 }
418
419 String s1, s2; /* used for serialization of pw */
420
421 private void readObject(ObjectInputStream s)
422 throws IOException, ClassNotFoundException
423 {
424 s.defaultReadObject ();
425 pw = new PasswordAuthentication (s1, s2.toCharArray());
426 s1 = null; s2= null;
427 }
428
429 private synchronized void writeObject(java.io.ObjectOutputStream s)
430 throws IOException
431 {
432 s1 = pw.getUserName();
433 s2 = new String (pw.getPassword());
434 s.defaultWriteObject ();
435 }
436}