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