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