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 }