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