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