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