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.length() == 0)
 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 }