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