J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 1995-2003 Sun Microsystems, Inc. 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. Sun designates this |
| 8 | * particular file as subject to the "Classpath" exception as provided |
| 9 | * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| 22 | * CA 95054 USA or visit www.sun.com if you need additional information or |
| 23 | * have any questions. |
| 24 | */ |
| 25 | |
| 26 | package sun.net.www.protocol.http; |
| 27 | |
| 28 | import java.io.*; |
| 29 | import java.net.*; |
| 30 | import java.util.Hashtable; |
| 31 | import java.util.LinkedList; |
| 32 | import java.util.ListIterator; |
| 33 | import java.util.Enumeration; |
| 34 | import java.util.HashMap; |
| 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 | abstract class AuthenticationInfo extends AuthCacheValue implements Cloneable { |
| 55 | |
| 56 | // Constants saying what kind of authroization this is. This determines |
| 57 | // the namespace in the hash table lookup. |
| 58 | static final char SERVER_AUTHENTICATION = 's'; |
| 59 | static final char PROXY_AUTHENTICATION = 'p'; |
| 60 | |
| 61 | /** |
| 62 | * If true, then simultaneous authentication requests to the same realm/proxy |
| 63 | * are serialized, in order to avoid a user having to type the same username/passwords |
| 64 | * repeatedly, via the Authenticator. Default is false, which means that this |
| 65 | * behavior is switched off. |
| 66 | */ |
| 67 | static boolean serializeAuth; |
| 68 | |
| 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 | transient protected 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 | public String getHost() { |
| 89 | return host; |
| 90 | } |
| 91 | public int getPort() { |
| 92 | return port; |
| 93 | } |
| 94 | public String getRealm() { |
| 95 | return realm; |
| 96 | } |
| 97 | public String getPath() { |
| 98 | return path; |
| 99 | } |
| 100 | public String getProtocolScheme() { |
| 101 | return protocol; |
| 102 | } |
| 103 | |
| 104 | /** |
| 105 | * requests is used to ensure that interaction with the |
| 106 | * Authenticator for a particular realm is single threaded. |
| 107 | * ie. if multiple threads need to get credentials from the user |
| 108 | * at the same time, then all but the first will block until |
| 109 | * the first completes its authentication. |
| 110 | */ |
| 111 | static private HashMap requests = new HashMap (); |
| 112 | |
| 113 | /* check if a request for this destination is in progress |
| 114 | * return false immediately if not. Otherwise block until |
| 115 | * request is finished and return true |
| 116 | */ |
| 117 | static private boolean requestIsInProgress (String key) { |
| 118 | if (!serializeAuth) { |
| 119 | /* behavior is disabled. Revert to concurrent requests */ |
| 120 | return false; |
| 121 | } |
| 122 | synchronized (requests) { |
| 123 | Thread t, c; |
| 124 | c = Thread.currentThread(); |
| 125 | if ((t=(Thread)requests.get(key))==null) { |
| 126 | requests.put (key, c); |
| 127 | return false; |
| 128 | } |
| 129 | if (t == c) { |
| 130 | return false; |
| 131 | } |
| 132 | while (requests.containsKey(key)) { |
| 133 | try { |
| 134 | requests.wait (); |
| 135 | } catch (InterruptedException e) {} |
| 136 | } |
| 137 | } |
| 138 | /* entry may be in cache now. */ |
| 139 | return true; |
| 140 | } |
| 141 | |
| 142 | /* signal completion of an authentication (whether it succeeded or not) |
| 143 | * so that other threads can continue. |
| 144 | */ |
| 145 | static private void requestCompleted (String key) { |
| 146 | synchronized (requests) { |
| 147 | boolean waspresent = requests.remove (key) != null; |
| 148 | assert waspresent; |
| 149 | requests.notifyAll(); |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | //public String toString () { |
| 154 | //return ("{"+type+":"+authType+":"+protocol+":"+host+":"+port+":"+realm+":"+path+"}"); |
| 155 | //} |
| 156 | |
| 157 | // REMIND: This cache just grows forever. We should put in a bounded |
| 158 | // cache, or maybe something using WeakRef's. |
| 159 | |
| 160 | /** The type (server/proxy) of authentication this is. Used for key lookup */ |
| 161 | char type; |
| 162 | |
| 163 | /** The authentication type (basic/digest). Also used for key lookup */ |
| 164 | char authType; |
| 165 | |
| 166 | /** The protocol/scheme (i.e. http or https ). Need to keep the caches |
| 167 | * logically separate for the two protocols. This field is only used |
| 168 | * when constructed with a URL (the normal case for server authentication) |
| 169 | * For proxy authentication the protocol is not relevant. |
| 170 | */ |
| 171 | String protocol; |
| 172 | |
| 173 | /** The host we're authenticating against. */ |
| 174 | String host; |
| 175 | |
| 176 | /** The port on the host we're authenticating against. */ |
| 177 | int port; |
| 178 | |
| 179 | /** The realm we're authenticating against. */ |
| 180 | String realm; |
| 181 | |
| 182 | /** The shortest path from the URL we authenticated against. */ |
| 183 | String path; |
| 184 | |
| 185 | /** Use this constructor only for proxy entries */ |
| 186 | AuthenticationInfo(char type, char authType, String host, int port, String realm) { |
| 187 | this.type = type; |
| 188 | this.authType = authType; |
| 189 | this.protocol = ""; |
| 190 | this.host = host.toLowerCase(); |
| 191 | this.port = port; |
| 192 | this.realm = realm; |
| 193 | this.path = null; |
| 194 | } |
| 195 | |
| 196 | public Object clone() { |
| 197 | try { |
| 198 | return super.clone (); |
| 199 | } catch (CloneNotSupportedException e) { |
| 200 | // Cannot happen because Cloneable implemented by AuthenticationInfo |
| 201 | return null; |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | /* |
| 206 | * Constructor used to limit the authorization to the path within |
| 207 | * the URL. Use this constructor for origin server entries. |
| 208 | */ |
| 209 | AuthenticationInfo(char type, char authType, URL url, String realm) { |
| 210 | this.type = type; |
| 211 | this.authType = authType; |
| 212 | this.protocol = url.getProtocol().toLowerCase(); |
| 213 | this.host = url.getHost().toLowerCase(); |
| 214 | this.port = url.getPort(); |
| 215 | if (this.port == -1) { |
| 216 | this.port = url.getDefaultPort(); |
| 217 | } |
| 218 | this.realm = realm; |
| 219 | |
| 220 | String urlPath = url.getPath(); |
| 221 | if (urlPath.length() == 0) |
| 222 | this.path = urlPath; |
| 223 | else { |
| 224 | this.path = reducePath (urlPath); |
| 225 | } |
| 226 | |
| 227 | } |
| 228 | |
| 229 | /* |
| 230 | * reduce the path to the root of where we think the |
| 231 | * authorization begins. This could get shorter as |
| 232 | * the url is traversed up following a successful challenge. |
| 233 | */ |
| 234 | static String reducePath (String urlPath) { |
| 235 | int sepIndex = urlPath.lastIndexOf('/'); |
| 236 | int targetSuffixIndex = urlPath.lastIndexOf('.'); |
| 237 | if (sepIndex != -1) |
| 238 | if (sepIndex < targetSuffixIndex) |
| 239 | return urlPath.substring(0, sepIndex+1); |
| 240 | else |
| 241 | return urlPath; |
| 242 | else |
| 243 | return urlPath; |
| 244 | } |
| 245 | |
| 246 | /** |
| 247 | * Returns info for the URL, for an HTTP server auth. Used when we |
| 248 | * don't yet know the realm |
| 249 | * (i.e. when we're preemptively setting the auth). |
| 250 | */ |
| 251 | static AuthenticationInfo getServerAuth(URL url) { |
| 252 | int port = url.getPort(); |
| 253 | if (port == -1) { |
| 254 | port = url.getDefaultPort(); |
| 255 | } |
| 256 | String key = SERVER_AUTHENTICATION + ":" + url.getProtocol().toLowerCase() |
| 257 | + ":" + url.getHost().toLowerCase() + ":" + port; |
| 258 | return getAuth(key, url); |
| 259 | } |
| 260 | |
| 261 | /** |
| 262 | * Returns info for the URL, for an HTTP server auth. Used when we |
| 263 | * do know the realm (i.e. when we're responding to a challenge). |
| 264 | * In this case we do not use the path because the protection space |
| 265 | * is identified by the host:port:realm only |
| 266 | */ |
| 267 | static AuthenticationInfo getServerAuth(URL url, String realm, char atype) { |
| 268 | int port = url.getPort(); |
| 269 | if (port == -1) { |
| 270 | port = url.getDefaultPort(); |
| 271 | } |
| 272 | String key = SERVER_AUTHENTICATION + ":" + atype + ":" + url.getProtocol().toLowerCase() |
| 273 | + ":" + url.getHost().toLowerCase() + ":" + port + ":" + realm; |
| 274 | AuthenticationInfo cached = getAuth(key, null); |
| 275 | if ((cached == null) && requestIsInProgress (key)) { |
| 276 | /* check the cache again, it might contain an entry */ |
| 277 | cached = getAuth(key, null); |
| 278 | } |
| 279 | return cached; |
| 280 | } |
| 281 | |
| 282 | |
| 283 | /** |
| 284 | * Return the AuthenticationInfo object from the cache if it's path is |
| 285 | * a substring of the supplied URLs path. |
| 286 | */ |
| 287 | static AuthenticationInfo getAuth(String key, URL url) { |
| 288 | if (url == null) { |
| 289 | return (AuthenticationInfo)cache.get (key, null); |
| 290 | } else { |
| 291 | return (AuthenticationInfo)cache.get (key, url.getPath()); |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | /** |
| 296 | * Returns a firewall authentication, for the given host/port. Used |
| 297 | * for preemptive header-setting. Note, the protocol field is always |
| 298 | * blank for proxies. |
| 299 | */ |
| 300 | static AuthenticationInfo getProxyAuth(String host, int port) { |
| 301 | String key = PROXY_AUTHENTICATION + "::" + host.toLowerCase() + ":" + port; |
| 302 | AuthenticationInfo result = (AuthenticationInfo) cache.get(key, null); |
| 303 | return result; |
| 304 | } |
| 305 | |
| 306 | /** |
| 307 | * Returns a firewall authentication, for the given host/port and realm. |
| 308 | * Used in response to a challenge. Note, the protocol field is always |
| 309 | * blank for proxies. |
| 310 | */ |
| 311 | static AuthenticationInfo getProxyAuth(String host, int port, String realm, char atype) { |
| 312 | String key = PROXY_AUTHENTICATION + ":" + atype + "::" + host.toLowerCase() |
| 313 | + ":" + port + ":" + realm; |
| 314 | AuthenticationInfo cached = (AuthenticationInfo) cache.get(key, null); |
| 315 | if ((cached == null) && requestIsInProgress (key)) { |
| 316 | /* check the cache again, it might contain an entry */ |
| 317 | cached = (AuthenticationInfo) cache.get(key, null); |
| 318 | } |
| 319 | return cached; |
| 320 | } |
| 321 | |
| 322 | |
| 323 | /** |
| 324 | * Add this authentication to the cache |
| 325 | */ |
| 326 | void addToCache() { |
| 327 | cache.put (cacheKey(true), this); |
| 328 | if (supportsPreemptiveAuthorization()) { |
| 329 | cache.put (cacheKey(false), this); |
| 330 | } |
| 331 | endAuthRequest(); |
| 332 | } |
| 333 | |
| 334 | void endAuthRequest () { |
| 335 | if (!serializeAuth) { |
| 336 | return; |
| 337 | } |
| 338 | synchronized (requests) { |
| 339 | requestCompleted (cacheKey(true)); |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | /** |
| 344 | * Remove this authentication from the cache |
| 345 | */ |
| 346 | void removeFromCache() { |
| 347 | cache.remove(cacheKey(true), this); |
| 348 | if (supportsPreemptiveAuthorization()) { |
| 349 | cache.remove(cacheKey(false), this); |
| 350 | } |
| 351 | } |
| 352 | |
| 353 | /** |
| 354 | * @return true if this authentication supports preemptive authorization |
| 355 | */ |
| 356 | abstract boolean supportsPreemptiveAuthorization(); |
| 357 | |
| 358 | /** |
| 359 | * @return the name of the HTTP header this authentication wants set. |
| 360 | * This is used for preemptive authorization. |
| 361 | */ |
| 362 | abstract String getHeaderName(); |
| 363 | |
| 364 | /** |
| 365 | * Calculates and returns the authentication header value based |
| 366 | * on the stored authentication parameters. If the calculation does not depend |
| 367 | * on the URL or the request method then these parameters are ignored. |
| 368 | * @param url The URL |
| 369 | * @param method The request method |
| 370 | * @return the value of the HTTP header this authentication wants set. |
| 371 | * Used for preemptive authorization. |
| 372 | */ |
| 373 | abstract String getHeaderValue(URL url, String method); |
| 374 | |
| 375 | /** |
| 376 | * Set header(s) on the given connection. Subclasses must override |
| 377 | * This will only be called for |
| 378 | * definitive (i.e. non-preemptive) authorization. |
| 379 | * @param conn The connection to apply the header(s) to |
| 380 | * @param p A source of header values for this connection, if needed. |
| 381 | * @param raw The raw header field (if needed) |
| 382 | * @return true if all goes well, false if no headers were set. |
| 383 | */ |
| 384 | abstract boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw); |
| 385 | |
| 386 | /** |
| 387 | * Check if the header indicates that the current auth. parameters are stale. |
| 388 | * If so, then replace the relevant field with the new value |
| 389 | * and return true. Otherwise return false. |
| 390 | * returning true means the request can be retried with the same userid/password |
| 391 | * returning false means we have to go back to the user to ask for a new |
| 392 | * username password. |
| 393 | */ |
| 394 | abstract boolean isAuthorizationStale (String header); |
| 395 | |
| 396 | /** |
| 397 | * Check for any expected authentication information in the response |
| 398 | * from the server |
| 399 | */ |
| 400 | abstract void checkResponse (String header, String method, URL url) |
| 401 | throws IOException; |
| 402 | |
| 403 | /** |
| 404 | * Give a key for hash table lookups. |
| 405 | * @param includeRealm if you want the realm considered. Preemptively |
| 406 | * setting an authorization is done before the realm is known. |
| 407 | */ |
| 408 | String cacheKey(boolean includeRealm) { |
| 409 | // This must be kept in sync with the getXXXAuth() methods in this |
| 410 | // class. |
| 411 | if (includeRealm) { |
| 412 | return type + ":" + authType + ":" + protocol + ":" |
| 413 | + host + ":" + port + ":" + realm; |
| 414 | } else { |
| 415 | return type + ":" + protocol + ":" + host + ":" + port; |
| 416 | } |
| 417 | } |
| 418 | |
| 419 | String s1, s2; /* used for serialization of pw */ |
| 420 | |
| 421 | private void readObject(ObjectInputStream s) |
| 422 | throws IOException, ClassNotFoundException |
| 423 | { |
| 424 | s.defaultReadObject (); |
| 425 | pw = new PasswordAuthentication (s1, s2.toCharArray()); |
| 426 | s1 = null; s2= null; |
| 427 | } |
| 428 | |
| 429 | private synchronized void writeObject(java.io.ObjectOutputStream s) |
| 430 | throws IOException |
| 431 | { |
| 432 | s1 = pw.getUserName(); |
| 433 | s2 = new String (pw.getPassword()); |
| 434 | s.defaultWriteObject (); |
| 435 | } |
| 436 | } |