Fix UnlockEndpointIsDelayed again.
[chromium-blink-merge.git] / net / http / http_auth_handler_negotiate.cc
blob557051cad6d551a2fa5a0c6afd38b553247c2cd4
1 // Copyright (c) 2012 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.
5 #include "net/http/http_auth_handler_negotiate.h"
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/logging.h"
10 #include "base/profiler/scoped_tracker.h"
11 #include "base/strings/stringprintf.h"
12 #include "net/base/address_family.h"
13 #include "net/base/net_errors.h"
14 #include "net/dns/host_resolver.h"
15 #include "net/dns/single_request_host_resolver.h"
16 #include "net/http/http_auth_filter.h"
17 #include "net/http/url_security_manager.h"
19 namespace net {
21 HttpAuthHandlerNegotiate::Factory::Factory()
22 : disable_cname_lookup_(false),
23 use_port_(false),
24 resolver_(NULL),
25 #if defined(OS_WIN)
26 max_token_length_(0),
27 first_creation_(true),
28 #endif
29 is_unsupported_(false) {
32 HttpAuthHandlerNegotiate::Factory::~Factory() {
35 void HttpAuthHandlerNegotiate::Factory::set_host_resolver(
36 HostResolver* resolver) {
37 resolver_ = resolver;
40 int HttpAuthHandlerNegotiate::Factory::CreateAuthHandler(
41 HttpAuthChallengeTokenizer* challenge,
42 HttpAuth::Target target,
43 const GURL& origin,
44 CreateReason reason,
45 int digest_nonce_count,
46 const BoundNetLog& net_log,
47 scoped_ptr<HttpAuthHandler>* handler) {
48 #if defined(OS_WIN)
49 if (is_unsupported_ || reason == CREATE_PREEMPTIVE)
50 return ERR_UNSUPPORTED_AUTH_SCHEME;
51 if (max_token_length_ == 0) {
52 int rv = DetermineMaxTokenLength(auth_library_.get(), NEGOSSP_NAME,
53 &max_token_length_);
54 if (rv == ERR_UNSUPPORTED_AUTH_SCHEME)
55 is_unsupported_ = true;
56 if (rv != OK)
57 return rv;
59 // TODO(cbentzel): Move towards model of parsing in the factory
60 // method and only constructing when valid.
61 scoped_ptr<HttpAuthHandler> tmp_handler(
62 new HttpAuthHandlerNegotiate(auth_library_.get(), max_token_length_,
63 url_security_manager(), resolver_,
64 disable_cname_lookup_, use_port_));
65 if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
66 return ERR_INVALID_RESPONSE;
67 handler->swap(tmp_handler);
68 return OK;
69 #elif defined(OS_POSIX)
70 if (is_unsupported_)
71 return ERR_UNSUPPORTED_AUTH_SCHEME;
72 if (!auth_library_->Init()) {
73 is_unsupported_ = true;
74 return ERR_UNSUPPORTED_AUTH_SCHEME;
76 // TODO(ahendrickson): Move towards model of parsing in the factory
77 // method and only constructing when valid.
78 scoped_ptr<HttpAuthHandler> tmp_handler(
79 new HttpAuthHandlerNegotiate(auth_library_.get(), url_security_manager(),
80 resolver_, disable_cname_lookup_,
81 use_port_));
82 if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
83 return ERR_INVALID_RESPONSE;
84 handler->swap(tmp_handler);
85 return OK;
86 #endif
89 HttpAuthHandlerNegotiate::HttpAuthHandlerNegotiate(
90 AuthLibrary* auth_library,
91 #if defined(OS_WIN)
92 ULONG max_token_length,
93 #endif
94 URLSecurityManager* url_security_manager,
95 HostResolver* resolver,
96 bool disable_cname_lookup,
97 bool use_port)
98 #if defined(OS_WIN)
99 : auth_system_(auth_library, "Negotiate", NEGOSSP_NAME, max_token_length),
100 #elif defined(OS_POSIX)
101 : auth_system_(auth_library, "Negotiate", CHROME_GSS_SPNEGO_MECH_OID_DESC),
102 #endif
103 disable_cname_lookup_(disable_cname_lookup),
104 use_port_(use_port),
105 resolver_(resolver),
106 already_called_(false),
107 has_credentials_(false),
108 auth_token_(NULL),
109 next_state_(STATE_NONE),
110 url_security_manager_(url_security_manager) {
113 HttpAuthHandlerNegotiate::~HttpAuthHandlerNegotiate() {
116 std::string HttpAuthHandlerNegotiate::CreateSPN(
117 const AddressList& address_list, const GURL& origin) {
118 // Kerberos Web Server SPNs are in the form HTTP/<host>:<port> through SSPI,
119 // and in the form HTTP@<host>:<port> through GSSAPI
120 // http://msdn.microsoft.com/en-us/library/ms677601%28VS.85%29.aspx
122 // However, reality differs from the specification. A good description of
123 // the problems can be found here:
124 // http://blog.michelbarneveld.nl/michel/archive/2009/11/14/the-reason-why-kb911149-and-kb908209-are-not-the-soluton.aspx
126 // Typically the <host> portion should be the canonical FQDN for the service.
127 // If this could not be resolved, the original hostname in the URL will be
128 // attempted instead. However, some intranets register SPNs using aliases
129 // for the same canonical DNS name to allow multiple web services to reside
130 // on the same host machine without requiring different ports. IE6 and IE7
131 // have hotpatches that allow the default behavior to be overridden.
132 // http://support.microsoft.com/kb/911149
133 // http://support.microsoft.com/kb/938305
135 // According to the spec, the <port> option should be included if it is a
136 // non-standard port (i.e. not 80 or 443 in the HTTP case). However,
137 // historically browsers have not included the port, even on non-standard
138 // ports. IE6 required a hotpatch and a registry setting to enable
139 // including non-standard ports, and IE7 and IE8 also require the same
140 // registry setting, but no hotpatch. Firefox does not appear to have an
141 // option to include non-standard ports as of 3.6.
142 // http://support.microsoft.com/kb/908209
144 // Without any command-line flags, Chrome matches the behavior of Firefox
145 // and IE. Users can override the behavior so aliases are allowed and
146 // non-standard ports are included.
147 int port = origin.EffectiveIntPort();
148 std::string server = address_list.canonical_name();
149 if (server.empty())
150 server = origin.host();
151 #if defined(OS_WIN)
152 static const char kSpnSeparator = '/';
153 #elif defined(OS_POSIX)
154 static const char kSpnSeparator = '@';
155 #endif
156 if (port != 80 && port != 443 && use_port_) {
157 return base::StringPrintf("HTTP%c%s:%d", kSpnSeparator, server.c_str(),
158 port);
159 } else {
160 return base::StringPrintf("HTTP%c%s", kSpnSeparator, server.c_str());
164 HttpAuth::AuthorizationResult HttpAuthHandlerNegotiate::HandleAnotherChallenge(
165 HttpAuthChallengeTokenizer* challenge) {
166 return auth_system_.ParseChallenge(challenge);
169 // Require identity on first pass instead of second.
170 bool HttpAuthHandlerNegotiate::NeedsIdentity() {
171 return auth_system_.NeedsIdentity();
174 bool HttpAuthHandlerNegotiate::AllowsDefaultCredentials() {
175 if (target_ == HttpAuth::AUTH_PROXY)
176 return true;
177 if (!url_security_manager_)
178 return false;
179 return url_security_manager_->CanUseDefaultCredentials(origin_);
182 bool HttpAuthHandlerNegotiate::AllowsExplicitCredentials() {
183 return auth_system_.AllowsExplicitCredentials();
186 // The Negotiate challenge header looks like:
187 // WWW-Authenticate: NEGOTIATE auth-data
188 bool HttpAuthHandlerNegotiate::Init(HttpAuthChallengeTokenizer* challenge) {
189 #if defined(OS_POSIX)
190 if (!auth_system_.Init()) {
191 VLOG(1) << "can't initialize GSSAPI library";
192 return false;
194 // GSSAPI does not provide a way to enter username/password to
195 // obtain a TGT. If the default credentials are not allowed for
196 // a particular site (based on whitelist), fall back to a
197 // different scheme.
198 if (!AllowsDefaultCredentials())
199 return false;
200 #endif
201 if (CanDelegate())
202 auth_system_.Delegate();
203 auth_scheme_ = HttpAuth::AUTH_SCHEME_NEGOTIATE;
204 score_ = 4;
205 properties_ = ENCRYPTS_IDENTITY | IS_CONNECTION_BASED;
206 HttpAuth::AuthorizationResult auth_result =
207 auth_system_.ParseChallenge(challenge);
208 return (auth_result == HttpAuth::AUTHORIZATION_RESULT_ACCEPT);
211 int HttpAuthHandlerNegotiate::GenerateAuthTokenImpl(
212 const AuthCredentials* credentials, const HttpRequestInfo* request,
213 const CompletionCallback& callback, std::string* auth_token) {
214 DCHECK(callback_.is_null());
215 DCHECK(auth_token_ == NULL);
216 auth_token_ = auth_token;
217 if (already_called_) {
218 DCHECK((!has_credentials_ && credentials == NULL) ||
219 (has_credentials_ && credentials->Equals(credentials_)));
220 next_state_ = STATE_GENERATE_AUTH_TOKEN;
221 } else {
222 already_called_ = true;
223 if (credentials) {
224 has_credentials_ = true;
225 credentials_ = *credentials;
227 next_state_ = STATE_RESOLVE_CANONICAL_NAME;
229 int rv = DoLoop(OK);
230 if (rv == ERR_IO_PENDING)
231 callback_ = callback;
232 return rv;
235 void HttpAuthHandlerNegotiate::OnIOComplete(int result) {
236 // TODO(vadimt): Remove ScopedTracker below once crbug.com/436634 is fixed.
237 tracked_objects::ScopedTracker tracking_profile(
238 FROM_HERE_WITH_EXPLICIT_FUNCTION(
239 "436634 HttpAuthHandlerNegotiate::OnIOComplete"));
241 int rv = DoLoop(result);
242 if (rv != ERR_IO_PENDING)
243 DoCallback(rv);
246 void HttpAuthHandlerNegotiate::DoCallback(int rv) {
247 DCHECK(rv != ERR_IO_PENDING);
248 DCHECK(!callback_.is_null());
249 CompletionCallback callback = callback_;
250 callback_.Reset();
251 callback.Run(rv);
254 int HttpAuthHandlerNegotiate::DoLoop(int result) {
255 DCHECK(next_state_ != STATE_NONE);
257 int rv = result;
258 do {
259 State state = next_state_;
260 next_state_ = STATE_NONE;
261 switch (state) {
262 case STATE_RESOLVE_CANONICAL_NAME:
263 DCHECK_EQ(OK, rv);
264 rv = DoResolveCanonicalName();
265 break;
266 case STATE_RESOLVE_CANONICAL_NAME_COMPLETE:
267 rv = DoResolveCanonicalNameComplete(rv);
268 break;
269 case STATE_GENERATE_AUTH_TOKEN:
270 DCHECK_EQ(OK, rv);
271 rv = DoGenerateAuthToken();
272 break;
273 case STATE_GENERATE_AUTH_TOKEN_COMPLETE:
274 rv = DoGenerateAuthTokenComplete(rv);
275 break;
276 default:
277 NOTREACHED() << "bad state";
278 rv = ERR_FAILED;
279 break;
281 } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
283 return rv;
286 int HttpAuthHandlerNegotiate::DoResolveCanonicalName() {
287 next_state_ = STATE_RESOLVE_CANONICAL_NAME_COMPLETE;
288 if (disable_cname_lookup_ || !resolver_)
289 return OK;
291 // TODO(cbentzel): Add reverse DNS lookup for numeric addresses.
292 DCHECK(!single_resolve_.get());
293 HostResolver::RequestInfo info(HostPortPair(origin_.host(), 0));
294 info.set_host_resolver_flags(HOST_RESOLVER_CANONNAME);
295 single_resolve_.reset(new SingleRequestHostResolver(resolver_));
296 return single_resolve_->Resolve(
297 info,
298 DEFAULT_PRIORITY,
299 &address_list_,
300 base::Bind(&HttpAuthHandlerNegotiate::OnIOComplete,
301 base::Unretained(this)),
302 net_log_);
305 int HttpAuthHandlerNegotiate::DoResolveCanonicalNameComplete(int rv) {
306 DCHECK_NE(ERR_IO_PENDING, rv);
307 if (rv != OK) {
308 // Even in the error case, try to use origin_.host instead of
309 // passing the failure on to the caller.
310 VLOG(1) << "Problem finding canonical name for SPN for host "
311 << origin_.host() << ": " << ErrorToString(rv);
312 rv = OK;
315 next_state_ = STATE_GENERATE_AUTH_TOKEN;
316 spn_ = CreateSPN(address_list_, origin_);
317 address_list_ = AddressList();
318 return rv;
321 int HttpAuthHandlerNegotiate::DoGenerateAuthToken() {
322 next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE;
323 AuthCredentials* credentials = has_credentials_ ? &credentials_ : NULL;
324 // TODO(cbentzel): This should possibly be done async.
325 return auth_system_.GenerateAuthToken(credentials, spn_, auth_token_);
328 int HttpAuthHandlerNegotiate::DoGenerateAuthTokenComplete(int rv) {
329 DCHECK_NE(ERR_IO_PENDING, rv);
330 auth_token_ = NULL;
331 return rv;
334 bool HttpAuthHandlerNegotiate::CanDelegate() const {
335 // TODO(cbentzel): Should delegation be allowed on proxies?
336 if (target_ == HttpAuth::AUTH_PROXY)
337 return false;
338 if (!url_security_manager_)
339 return false;
340 return url_security_manager_->CanDelegate(origin_);
343 } // namespace net