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