OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "net/http/http_auth_controller.h" |
| 6 |
| 7 #include "base/string_util.h" |
| 8 #include "net/base/host_resolver.h" |
| 9 #include "net/base/net_util.h" |
| 10 #include "net/http/http_auth_handler_factory.h" |
| 11 #include "net/http/http_network_session.h" |
| 12 #include "net/http/http_request_headers.h" |
| 13 #include "net/http/http_request_info.h" |
| 14 |
| 15 namespace net { |
| 16 |
| 17 namespace { |
| 18 |
| 19 // Returns a log message for all the response headers related to the auth |
| 20 // challenge. |
| 21 std::string AuthChallengeLogMessage(HttpResponseHeaders* headers) { |
| 22 std::string msg; |
| 23 std::string header_val; |
| 24 void* iter = NULL; |
| 25 while (headers->EnumerateHeader(&iter, "proxy-authenticate", &header_val)) { |
| 26 msg.append("\n Has header Proxy-Authenticate: "); |
| 27 msg.append(header_val); |
| 28 } |
| 29 |
| 30 iter = NULL; |
| 31 while (headers->EnumerateHeader(&iter, "www-authenticate", &header_val)) { |
| 32 msg.append("\n Has header WWW-Authenticate: "); |
| 33 msg.append(header_val); |
| 34 } |
| 35 |
| 36 // RFC 4559 requires that a proxy indicate its support of NTLM/Negotiate |
| 37 // authentication with a "Proxy-Support: Session-Based-Authentication" |
| 38 // response header. |
| 39 iter = NULL; |
| 40 while (headers->EnumerateHeader(&iter, "proxy-support", &header_val)) { |
| 41 msg.append("\n Has header Proxy-Support: "); |
| 42 msg.append(header_val); |
| 43 } |
| 44 |
| 45 return msg; |
| 46 } |
| 47 |
| 48 } // namespace |
| 49 |
| 50 HttpAuthController::HttpAuthController( |
| 51 HttpAuth::Target target, |
| 52 const GURL& auth_url, |
| 53 scoped_refptr<HttpNetworkSession> session, |
| 54 const BoundNetLog& net_log) |
| 55 : target_(target), |
| 56 auth_url_(auth_url), |
| 57 auth_origin_(auth_url.GetOrigin()), |
| 58 auth_path_(HttpAuth::AUTH_PROXY ? std::string() : auth_url.path()), |
| 59 embedded_identity_used_(false), |
| 60 default_credentials_used_(false), |
| 61 session_(session), |
| 62 net_log_(net_log) { |
| 63 } |
| 64 |
| 65 int HttpAuthController::MaybeGenerateAuthToken(const HttpRequestInfo* request, |
| 66 CompletionCallback* callback) { |
| 67 bool needs_auth = HaveAuth() || SelectPreemptiveAuth(); |
| 68 if (!needs_auth) |
| 69 return OK; |
| 70 const std::wstring* username = NULL; |
| 71 const std::wstring* password = NULL; |
| 72 if (identity_.source != HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS) { |
| 73 username = &identity_.username; |
| 74 password = &identity_.password; |
| 75 } |
| 76 DCHECK(auth_token_.empty()); |
| 77 return handler_->GenerateAuthToken(username, password, request, callback, |
| 78 &auth_token_); |
| 79 } |
| 80 |
| 81 bool HttpAuthController::SelectPreemptiveAuth() { |
| 82 DCHECK(!HaveAuth()); |
| 83 DCHECK(identity_.invalid); |
| 84 |
| 85 // Don't do preemptive authorization if the URL contains a username/password, |
| 86 // since we must first be challenged in order to use the URL's identity. |
| 87 if (auth_url_.has_username()) |
| 88 return false; |
| 89 |
| 90 // SelectPreemptiveAuth() is on the critical path for each request, so it |
| 91 // is expected to be fast. LookupByPath() is fast in the common case, since |
| 92 // the number of http auth cache entries is expected to be very small. |
| 93 // (For most users in fact, it will be 0.) |
| 94 HttpAuthCache::Entry* entry = session_->auth_cache()->LookupByPath( |
| 95 auth_origin_, auth_path_); |
| 96 if (!entry) |
| 97 return false; |
| 98 |
| 99 // Try to create a handler using the previous auth challenge. |
| 100 scoped_ptr<HttpAuthHandler> handler_preemptive; |
| 101 int rv_create = session_->http_auth_handler_factory()-> |
| 102 CreatePreemptiveAuthHandlerFromString(entry->auth_challenge(), target_, |
| 103 auth_origin_, |
| 104 entry->IncrementNonceCount(), |
| 105 net_log_, &handler_preemptive); |
| 106 if (rv_create != OK) |
| 107 return false; |
| 108 |
| 109 // Set the state |
| 110 identity_.source = HttpAuth::IDENT_SRC_PATH_LOOKUP; |
| 111 identity_.invalid = false; |
| 112 identity_.username = entry->username(); |
| 113 identity_.password = entry->password(); |
| 114 handler_.swap(handler_preemptive); |
| 115 return true; |
| 116 } |
| 117 |
| 118 void HttpAuthController::AddAuthorizationHeader( |
| 119 HttpRequestHeaders* authorization_headers) { |
| 120 DCHECK(HaveAuth()); |
| 121 DCHECK(!auth_token_.empty()); |
| 122 authorization_headers->SetHeader( |
| 123 HttpAuth::GetAuthorizationHeaderName(target_), auth_token_); |
| 124 auth_token_.clear(); |
| 125 } |
| 126 |
| 127 int HttpAuthController::HandleAuthChallenge( |
| 128 scoped_refptr<HttpResponseHeaders> headers, |
| 129 int load_flags, |
| 130 bool establishing_tunnel) { |
| 131 DCHECK(headers); |
| 132 DCHECK(auth_origin_.is_valid()); |
| 133 |
| 134 LOG(INFO) << "The " << HttpAuth::GetAuthTargetString(target_) << " " |
| 135 << auth_origin_ << " requested auth" |
| 136 << AuthChallengeLogMessage(headers.get()); |
| 137 |
| 138 // The auth we tried just failed, hence it can't be valid. Remove it from |
| 139 // the cache so it won't be used again. |
| 140 // TODO(wtc): IsFinalRound is not the right condition. In a multi-round |
| 141 // auth sequence, the server may fail the auth in round 1 if our first |
| 142 // authorization header is broken. We should inspect response_.headers to |
| 143 // determine if the server already failed the auth or wants us to continue. |
| 144 // See http://crbug.com/21015. |
| 145 if (HaveAuth() && handler_->IsFinalRound()) { |
| 146 InvalidateRejectedAuthFromCache(); |
| 147 handler_.reset(); |
| 148 identity_ = HttpAuth::Identity(); |
| 149 } |
| 150 |
| 151 identity_.invalid = true; |
| 152 |
| 153 if (target_ != HttpAuth::AUTH_SERVER || |
| 154 !(load_flags & LOAD_DO_NOT_SEND_AUTH_DATA)) { |
| 155 // Find the best authentication challenge that we support. |
| 156 HttpAuth::ChooseBestChallenge(session_->http_auth_handler_factory(), |
| 157 headers, target_, auth_origin_, net_log_, |
| 158 &handler_); |
| 159 } |
| 160 |
| 161 if (!handler_.get()) { |
| 162 if (establishing_tunnel) { |
| 163 LOG(ERROR) << "Can't perform auth to the " |
| 164 << HttpAuth::GetAuthTargetString(target_) << " " |
| 165 << auth_origin_ << " when establishing a tunnel" |
| 166 << AuthChallengeLogMessage(headers.get()); |
| 167 |
| 168 // We are establishing a tunnel, we can't show the error page because an |
| 169 // active network attacker could control its contents. Instead, we just |
| 170 // fail to establish the tunnel. |
| 171 DCHECK(target_ == HttpAuth::AUTH_PROXY); |
| 172 return ERR_PROXY_AUTH_REQUESTED; |
| 173 } |
| 174 // We found no supported challenge -- let the transaction continue |
| 175 // so we end up displaying the error page. |
| 176 return OK; |
| 177 } |
| 178 |
| 179 if (handler_->NeedsIdentity()) { |
| 180 // Pick a new auth identity to try, by looking to the URL and auth cache. |
| 181 // If an identity to try is found, it is saved to identity_. |
| 182 SelectNextAuthIdentityToTry(); |
| 183 } else { |
| 184 // Proceed with the existing identity or a null identity. |
| 185 // |
| 186 // TODO(wtc): Add a safeguard against infinite transaction restarts, if |
| 187 // the server keeps returning "NTLM". |
| 188 identity_.invalid = false; |
| 189 } |
| 190 |
| 191 // From this point on, we are restartable. |
| 192 |
| 193 if (identity_.invalid) { |
| 194 // We have exhausted all identity possibilities, all we can do now is |
| 195 // pass the challenge information back to the client. |
| 196 PopulateAuthChallenge(); |
| 197 } |
| 198 |
| 199 // SPN determination (for Negotiate) requires a DNS lookup to find the |
| 200 // canonical name. This needs to be done asynchronously to prevent blocking |
| 201 // the IO thread. |
| 202 if (handler_->NeedsCanonicalName()) |
| 203 return ERR_AUTH_NEEDS_CANONICAL_NAME; |
| 204 |
| 205 return OK; |
| 206 } |
| 207 |
| 208 int HttpAuthController::ResolveCanonicalName(CompletionCallback* callback) { |
| 209 DCHECK(handler_.get()); |
| 210 return handler_->ResolveCanonicalName(session_->host_resolver(), callback); |
| 211 } |
| 212 |
| 213 void HttpAuthController::ResetAuth(const std::wstring& username, |
| 214 const std::wstring& password) { |
| 215 DCHECK(identity_.invalid || (username.empty() && password.empty())); |
| 216 |
| 217 if (identity_.invalid) { |
| 218 // Update the username/password. |
| 219 identity_.source = HttpAuth::IDENT_SRC_EXTERNAL; |
| 220 identity_.invalid = false; |
| 221 identity_.username = username; |
| 222 identity_.password = password; |
| 223 } |
| 224 |
| 225 DCHECK(identity_.source != HttpAuth::IDENT_SRC_PATH_LOOKUP); |
| 226 |
| 227 // Add the auth entry to the cache before restarting. We don't know whether |
| 228 // the identity is valid yet, but if it is valid we want other transactions |
| 229 // to know about it. If an entry for (origin, handler->realm()) already |
| 230 // exists, we update it. |
| 231 // |
| 232 // If identity_.source is HttpAuth::IDENT_SRC_NONE or |
| 233 // HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS, identity_ contains no |
| 234 // identity because identity is not required yet or we're using default |
| 235 // credentials. |
| 236 // |
| 237 // TODO(wtc): For NTLM_SSPI, we add the same auth entry to the cache in |
| 238 // round 1 and round 2, which is redundant but correct. It would be nice |
| 239 // to add an auth entry to the cache only once, preferrably in round 1. |
| 240 // See http://crbug.com/21015. |
| 241 switch (identity_.source) { |
| 242 case HttpAuth::IDENT_SRC_NONE: |
| 243 case HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS: |
| 244 break; |
| 245 default: |
| 246 session_->auth_cache()->Add(auth_origin_, handler_->realm(), |
| 247 handler_->scheme(), handler_->challenge(), |
| 248 identity_.username, identity_.password, |
| 249 auth_path_); |
| 250 break; |
| 251 } |
| 252 } |
| 253 |
| 254 void HttpAuthController::InvalidateRejectedAuthFromCache() { |
| 255 DCHECK(HaveAuth()); |
| 256 |
| 257 // TODO(eroman): this short-circuit can be relaxed. If the realm of |
| 258 // the preemptively used auth entry matches the realm of the subsequent |
| 259 // challenge, then we can invalidate the preemptively used entry. |
| 260 // Otherwise as-is we may send the failed credentials one extra time. |
| 261 if (identity_.source == HttpAuth::IDENT_SRC_PATH_LOOKUP) |
| 262 return; |
| 263 |
| 264 // Clear the cache entry for the identity we just failed on. |
| 265 // Note: we require the username/password to match before invalidating |
| 266 // since the entry in the cache may be newer than what we used last time. |
| 267 session_->auth_cache()->Remove(auth_origin_, handler_->realm(), |
| 268 handler_->scheme(), identity_.username, |
| 269 identity_.password); |
| 270 } |
| 271 |
| 272 bool HttpAuthController::SelectNextAuthIdentityToTry() { |
| 273 DCHECK(handler_.get()); |
| 274 DCHECK(identity_.invalid); |
| 275 |
| 276 // Try to use the username/password encoded into the URL first. |
| 277 if (target_ == HttpAuth::AUTH_SERVER && auth_url_.has_username() && |
| 278 !embedded_identity_used_) { |
| 279 identity_.source = HttpAuth::IDENT_SRC_URL; |
| 280 identity_.invalid = false; |
| 281 // Extract the username:password from the URL. |
| 282 GetIdentityFromURL(auth_url_, |
| 283 &identity_.username, |
| 284 &identity_.password); |
| 285 embedded_identity_used_ = true; |
| 286 // TODO(eroman): If the password is blank, should we also try combining |
| 287 // with a password from the cache? |
| 288 return true; |
| 289 } |
| 290 |
| 291 // Check the auth cache for a realm entry. |
| 292 HttpAuthCache::Entry* entry = |
| 293 session_->auth_cache()->Lookup(auth_origin_, handler_->realm(), |
| 294 handler_->scheme()); |
| 295 |
| 296 if (entry) { |
| 297 identity_.source = HttpAuth::IDENT_SRC_REALM_LOOKUP; |
| 298 identity_.invalid = false; |
| 299 identity_.username = entry->username(); |
| 300 identity_.password = entry->password(); |
| 301 return true; |
| 302 } |
| 303 |
| 304 // Use default credentials (single sign on) if this is the first attempt |
| 305 // at identity. Do not allow multiple times as it will infinite loop. |
| 306 // We use default credentials after checking the auth cache so that if |
| 307 // single sign-on doesn't work, we won't try default credentials for future |
| 308 // transactions. |
| 309 if (!default_credentials_used_ && handler_->AllowsDefaultCredentials()) { |
| 310 identity_.source = HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS; |
| 311 identity_.invalid = false; |
| 312 default_credentials_used_ = true; |
| 313 return true; |
| 314 } |
| 315 |
| 316 return false; |
| 317 } |
| 318 |
| 319 void HttpAuthController::PopulateAuthChallenge() { |
| 320 // Populates response_.auth_challenge with the authentication challenge info. |
| 321 // This info is consumed by URLRequestHttpJob::GetAuthChallengeInfo(). |
| 322 |
| 323 auth_info_ = new AuthChallengeInfo; |
| 324 auth_info_->is_proxy = target_ == HttpAuth::AUTH_PROXY; |
| 325 auth_info_->host_and_port = ASCIIToWide(GetHostAndPort(auth_origin_)); |
| 326 auth_info_->scheme = ASCIIToWide(handler_->scheme()); |
| 327 // TODO(eroman): decode realm according to RFC 2047. |
| 328 auth_info_->realm = ASCIIToWide(handler_->realm()); |
| 329 } |
| 330 |
| 331 } // namespace net |
OLD | NEW |