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 |