Support Kerberos on Android
As part of this, allow asynchronous token return. This should be
allowed anyway, but is particularly important on Android, where getting
a token may cause user interaction.
BUG=474943
Review URL: https://codereview.chromium.org/1128043007
Cr-Commit-Position: refs/heads/master@{#338039}
diff --git a/build/android/pylib/gtest/test_package_apk.py b/build/android/pylib/gtest/test_package_apk.py
index 16ef21c6..a679b03 100644
--- a/build/android/pylib/gtest/test_package_apk.py
+++ b/build/android/pylib/gtest/test_package_apk.py
@@ -43,6 +43,11 @@
else:
self._package_info = constants.PACKAGE_INFO['gtest']
+ if suite_name == 'net_unittests':
+ self._extras = {'RunInSubThread': ''}
+ else:
+ self._extras = []
+
def _CreateCommandLineFileOnDevice(self, device, options):
device.WriteFile(self._package_info.cmdline_file,
self.suite_name + ' ' + options)
@@ -74,7 +79,8 @@
device.StartActivity(
intent.Intent(package=self._package_info.package,
activity=self._package_info.activity,
- action='android.intent.action.MAIN'),
+ action='android.intent.action.MAIN',
+ extras=self._extras),
# No wait since the runner waits for FIFO creation anyway.
blocking=False,
force_stop=force_stop)
diff --git a/chrome/browser/io_thread.cc b/chrome/browser/io_thread.cc
index 6d2307533e..43eb98bc 100644
--- a/chrome/browser/io_thread.cc
+++ b/chrome/browser/io_thread.cc
@@ -505,6 +505,8 @@
auth_delegate_whitelist_ = local_state->GetString(
prefs::kAuthNegotiateDelegateWhitelist);
gssapi_library_name_ = local_state->GetString(prefs::kGSSAPILibraryName);
+ auth_android_negotiate_account_type_ =
+ local_state->GetString(prefs::kAuthAndroidNegotiateAccountType);
pref_proxy_config_tracker_.reset(
ProxyServiceFactory::CreatePrefProxyConfigTrackerOfLocalState(
local_state));
@@ -1039,6 +1041,8 @@
registry->RegisterStringPref(prefs::kAuthNegotiateDelegateWhitelist,
std::string());
registry->RegisterStringPref(prefs::kGSSAPILibraryName, std::string());
+ registry->RegisterStringPref(prefs::kAuthAndroidNegotiateAccountType,
+ std::string());
registry->RegisterStringPref(
data_reduction_proxy::prefs::kDataReductionProxy, std::string());
registry->RegisterBooleanPref(prefs::kEnableReferrers, true);
@@ -1067,9 +1071,9 @@
scoped_ptr<net::HttpAuthHandlerRegistryFactory> registry_factory(
net::HttpAuthHandlerRegistryFactory::Create(
- supported_schemes, globals_->url_security_manager.get(),
- resolver, gssapi_library_name_, negotiate_disable_cname_lookup_,
- negotiate_enable_port_));
+ supported_schemes, globals_->url_security_manager.get(), resolver,
+ gssapi_library_name_, auth_android_negotiate_account_type_,
+ negotiate_disable_cname_lookup_, negotiate_enable_port_));
return registry_factory.release();
}
diff --git a/chrome/browser/io_thread.h b/chrome/browser/io_thread.h
index 1066a9b..54a90ff0 100644
--- a/chrome/browser/io_thread.h
+++ b/chrome/browser/io_thread.h
@@ -457,6 +457,7 @@
std::string auth_server_whitelist_;
std::string auth_delegate_whitelist_;
std::string gssapi_library_name_;
+ std::string auth_android_negotiate_account_type_;
// This is an instance of the default SSLConfigServiceManager for the current
// platform and it gets SSL preferences from local_state object.
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 46ec969..716be31 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -1686,6 +1686,11 @@
// String that specifies the name of a custom GSSAPI library to load.
const char kGSSAPILibraryName[] = "auth.gssapi_library_name";
+// String that specifies the Android account type to use for Negotiate
+// authentication.
+const char kAuthAndroidNegotiateAccountType[] =
+ "auth.android_negotiate_account_type";
+
// Boolean that specifies whether to allow basic auth prompting on cross-
// domain sub-content requests.
const char kAllowCrossOriginAuthPrompt[] = "auth.allow_cross_origin_prompt";
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 4b4fa6b..6223df1 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -693,6 +693,7 @@
extern const char kAuthServerWhitelist[];
extern const char kAuthNegotiateDelegateWhitelist[];
extern const char kGSSAPILibraryName[];
+extern const char kAuthAndroidNegotiateAccountType[];
extern const char kAllowCrossOriginAuthPrompt[];
extern const char kBuiltInDnsClientEnabled[];
diff --git a/google_apis/gcm/tools/mcs_probe.cc b/google_apis/gcm/tools/mcs_probe.cc
index eadd3b5..9d0881b0 100644
--- a/google_apis/gcm/tools/mcs_probe.cc
+++ b/google_apis/gcm/tools/mcs_probe.cc
@@ -377,14 +377,9 @@
transport_security_state_.reset(new net::TransportSecurityState());
url_security_manager_.reset(net::URLSecurityManager::Create(NULL, NULL));
- http_auth_handler_factory_.reset(
- net::HttpAuthHandlerRegistryFactory::Create(
- std::vector<std::string>(1, "basic"),
- url_security_manager_.get(),
- host_resolver_.get(),
- std::string(),
- false,
- false));
+ http_auth_handler_factory_.reset(net::HttpAuthHandlerRegistryFactory::Create(
+ std::vector<std::string>(1, "basic"), url_security_manager_.get(),
+ host_resolver_.get(), std::string(), std::string(), false, false));
http_server_properties_.reset(new net::HttpServerPropertiesImpl());
host_mapping_rules_.reset(new net::HostMappingRules());
proxy_service_.reset(net::ProxyService::CreateDirectWithNetLog(&net_log_));
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 8454d7a..e9ad1d2 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -1216,6 +1216,7 @@
"android/java/src/org/chromium/net/AndroidNetworkLibrary.java",
"android/java/src/org/chromium/net/AndroidPrivateKey.java",
"android/java/src/org/chromium/net/GURLUtils.java",
+ "android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java",
"android/java/src/org/chromium/net/NetworkChangeNotifier.java",
"android/java/src/org/chromium/net/ProxyChangeListener.java",
"android/java/src/org/chromium/net/X509Util.java",
@@ -1225,6 +1226,7 @@
generate_jni("net_test_jni_headers") {
sources = [
"android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java",
+ "test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticator.java",
]
jni_package = "net"
}
@@ -1408,14 +1410,20 @@
if (use_kerberos) {
defines += [ "USE_KERBEROS" ]
- } else {
+ }
+
+ # These are excluded on Android, because the actual Kerberos support, which
+ # these test, is in a separate app on Android.
+ if (!use_kerberos || is_android) {
sources -= [
"http/http_auth_gssapi_posix_unittest.cc",
- "http/http_auth_handler_negotiate_unittest.cc",
"http/mock_gssapi_library_posix.cc",
"http/mock_gssapi_library_posix.h",
]
}
+ if (!use_kerberos) {
+ sources -= [ "http/http_auth_handler_negotiate_unittest.cc" ]
+ }
if (use_openssl || (!is_desktop_linux && !is_chromeos && !is_ios)) {
# Only include this test when on Posix and using NSS for
diff --git a/net/android/BUILD.gn b/net/android/BUILD.gn
index 358e1f7..abe4654 100644
--- a/net/android/BUILD.gn
+++ b/net/android/BUILD.gn
@@ -30,6 +30,7 @@
DEPRECATED_java_in_dir = "../test/android/javatests/src"
deps = [
"//base:base_java",
+ ":net_java",
]
srcjar_deps = [ ":net_java_test_support_enums_srcjar" ]
}
@@ -78,6 +79,17 @@
]
}
+junit_binary("net_junit_tests") {
+ java_files =
+ [ "junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java" ]
+ deps = [
+ ":net_java",
+ "//base:base_java",
+ "//base:base_java_test_support",
+ "//third_party/junit:hamcrest",
+ ]
+}
+
# TODO(GYP)
if (false) {
unittest_apk("net_unittests_apk") {
diff --git a/net/android/dummy_spnego_authenticator.cc b/net/android/dummy_spnego_authenticator.cc
new file mode 100644
index 0000000..d42f64c
--- /dev/null
+++ b/net/android/dummy_spnego_authenticator.cc
@@ -0,0 +1,204 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#include "net/android/dummy_spnego_authenticator.h"
+
+#include "base/android/jni_string.h"
+#include "base/base64.h"
+#include "net/test/jni/DummySpnegoAuthenticator_jni.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+// iso.org.dod.internet.security.mechanism.snego (1.3.6.1.5.5.2)
+// From RFC 4178, which uses SNEGO not SPNEGO.
+static const unsigned char kSpnegoOid[] = {0x2b, 0x06, 0x01, 0x05, 0x05, 0x02};
+gss_OID_desc CHROME_GSS_SPNEGO_MECH_OID_DESC_VAL = {
+ arraysize(kSpnegoOid),
+ const_cast<unsigned char*>(kSpnegoOid)};
+
+gss_OID CHROME_GSS_SPNEGO_MECH_OID_DESC = &CHROME_GSS_SPNEGO_MECH_OID_DESC_VAL;
+
+namespace {
+
+// gss_OID helpers.
+// NOTE: gss_OID's do not own the data they point to, which should be static.
+void ClearOid(gss_OID dest) {
+ if (!dest)
+ return;
+ dest->length = 0;
+ dest->elements = NULL;
+}
+
+void SetOid(gss_OID dest, const void* src, size_t length) {
+ if (!dest)
+ return;
+ ClearOid(dest);
+ if (!src)
+ return;
+ dest->length = length;
+ if (length)
+ dest->elements = const_cast<void*>(src);
+}
+
+void CopyOid(gss_OID dest, const gss_OID_desc* src) {
+ if (!dest)
+ return;
+ ClearOid(dest);
+ if (!src)
+ return;
+ SetOid(dest, src->elements, src->length);
+}
+
+} // namespace
+
+namespace test {
+
+GssContextMockImpl::GssContextMockImpl()
+ : lifetime_rec(0), ctx_flags(0), locally_initiated(0), open(0) {
+ ClearOid(&mech_type);
+}
+
+GssContextMockImpl::GssContextMockImpl(const GssContextMockImpl& other)
+ : src_name(other.src_name),
+ targ_name(other.targ_name),
+ lifetime_rec(other.lifetime_rec),
+ ctx_flags(other.ctx_flags),
+ locally_initiated(other.locally_initiated),
+ open(other.open) {
+ CopyOid(&mech_type, &other.mech_type);
+}
+
+GssContextMockImpl::GssContextMockImpl(const char* src_name_in,
+ const char* targ_name_in,
+ uint32_t lifetime_rec_in,
+ const gss_OID_desc& mech_type_in,
+ uint32_t ctx_flags_in,
+ int locally_initiated_in,
+ int open_in)
+ : src_name(src_name_in ? src_name_in : ""),
+ targ_name(targ_name_in ? targ_name_in : ""),
+ lifetime_rec(lifetime_rec_in),
+ ctx_flags(ctx_flags_in),
+ locally_initiated(locally_initiated_in),
+ open(open_in) {
+ CopyOid(&mech_type, &mech_type_in);
+}
+
+GssContextMockImpl::~GssContextMockImpl() {
+ ClearOid(&mech_type);
+}
+
+} // namespace test
+
+namespace android {
+
+DummySpnegoAuthenticator::SecurityContextQuery::SecurityContextQuery(
+ const std::string& in_expected_package,
+ uint32_t in_response_code,
+ uint32_t in_minor_response_code,
+ const test::GssContextMockImpl& in_context_info,
+ const std::string& in_expected_input_token,
+ const std::string& in_output_token)
+ : expected_package(in_expected_package),
+ response_code(in_response_code),
+ minor_response_code(in_minor_response_code),
+ context_info(in_context_info),
+ expected_input_token(in_expected_input_token),
+ output_token(in_output_token) {
+}
+
+DummySpnegoAuthenticator::SecurityContextQuery::SecurityContextQuery(
+ const std::string& in_expected_package,
+ uint32_t in_response_code,
+ uint32_t in_minor_response_code,
+ const test::GssContextMockImpl& in_context_info,
+ const char* in_expected_input_token,
+ const char* in_output_token)
+ : expected_package(in_expected_package),
+ response_code(in_response_code),
+ minor_response_code(in_minor_response_code),
+ context_info(in_context_info) {
+ if (in_expected_input_token)
+ expected_input_token = in_expected_input_token;
+ if (in_output_token)
+ output_token = in_output_token;
+}
+
+DummySpnegoAuthenticator::SecurityContextQuery::SecurityContextQuery()
+ : response_code(0), minor_response_code(0) {
+}
+
+DummySpnegoAuthenticator::SecurityContextQuery::~SecurityContextQuery() {
+}
+
+base::android::ScopedJavaLocalRef<jstring>
+DummySpnegoAuthenticator::SecurityContextQuery::GetTokenToReturn(
+ JNIEnv* env,
+ jobject /*obj*/) {
+ return base::android::ConvertUTF8ToJavaString(env, output_token.c_str());
+}
+int DummySpnegoAuthenticator::SecurityContextQuery::GetResult(JNIEnv* /*env*/,
+ jobject /*obj*/) {
+ return response_code;
+}
+
+void DummySpnegoAuthenticator::SecurityContextQuery::CheckGetTokenArguments(
+ JNIEnv* env,
+ jobject /*obj*/,
+ jstring j_incoming_token) {
+ std::string incoming_token =
+ base::android::ConvertJavaStringToUTF8(env, j_incoming_token);
+ EXPECT_EQ(expected_input_token, incoming_token);
+}
+
+// Needed to satisfy "complex class" clang requirements.
+DummySpnegoAuthenticator::DummySpnegoAuthenticator() {
+}
+
+DummySpnegoAuthenticator::~DummySpnegoAuthenticator() {
+}
+
+void DummySpnegoAuthenticator::EnsureTestAccountExists() {
+ Java_DummySpnegoAuthenticator_ensureTestAccountExists(
+ base::android::AttachCurrentThread());
+}
+
+void DummySpnegoAuthenticator::RemoveTestAccounts() {
+ Java_DummySpnegoAuthenticator_removeTestAccounts(
+ base::android::AttachCurrentThread());
+}
+
+void DummySpnegoAuthenticator::ExpectSecurityContext(
+ const std::string& expected_package,
+ uint32_t response_code,
+ uint32_t minor_response_code,
+ const test::GssContextMockImpl& context_info,
+ const std::string& expected_input_token,
+ const std::string& output_token) {
+ SecurityContextQuery query(expected_package, response_code,
+ minor_response_code, context_info,
+ expected_input_token, output_token);
+ expected_security_queries_.push_back(query);
+ Java_DummySpnegoAuthenticator_setNativeAuthenticator(
+ base::android::AttachCurrentThread(), reinterpret_cast<intptr_t>(this));
+}
+
+bool DummySpnegoAuthenticator::RegisterJni(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+long DummySpnegoAuthenticator::GetNextQuery(JNIEnv* /*env*/,
+ jobject /* obj */) {
+ CheckQueueNotEmpty();
+ current_query_ = expected_security_queries_.front();
+ expected_security_queries_.pop_front();
+ return reinterpret_cast<intptr_t>(¤t_query_);
+}
+
+void DummySpnegoAuthenticator::CheckQueueNotEmpty() {
+ ASSERT_FALSE(expected_security_queries_.empty());
+}
+
+} // namespace android
+} // namespace net
diff --git a/net/android/dummy_spnego_authenticator.h b/net/android/dummy_spnego_authenticator.h
new file mode 100644
index 0000000..a7d4a97
--- /dev/null
+++ b/net/android/dummy_spnego_authenticator.h
@@ -0,0 +1,140 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_ANDROID_DUMMY_SPNEGO_AUTHENTICATOR_H_
+#define NET_ANDROID_DUMMY_SPNEGO_AUTHENTICATOR_H_
+
+#include <jni.h>
+#include <cstdint>
+#include <list>
+#include <string>
+
+#include "base/android/scoped_java_ref.h"
+
+// Provides an interface for controlling the DummySpnegoAuthenticator service.
+// This includes a basic stub of the Mock GSSAPI library, so that OS independent
+// Negotiate authentication tests can be run on Android.
+namespace net {
+
+// These constant values are arbitrary, and different from the real GSSAPI
+// values, but must match those used in DummySpnegoAuthenticator.java
+#define GSS_S_COMPLETE 0
+#define GSS_S_CONTINUE_NEEDED 1
+#define GSS_S_FAILURE 2
+
+class gss_buffer_desc;
+
+typedef struct gss_OID_desc_struct {
+ uint32_t length;
+ void* elements;
+} gss_OID_desc, *gss_OID;
+
+extern gss_OID CHROME_GSS_SPNEGO_MECH_OID_DESC;
+
+namespace test {
+
+// Copy of class in Mock GSSAPI library.
+class GssContextMockImpl {
+ public:
+ GssContextMockImpl();
+ GssContextMockImpl(const GssContextMockImpl& other);
+ GssContextMockImpl(const char* src_name,
+ const char* targ_name,
+ uint32_t lifetime_rec,
+ const gss_OID_desc& mech_type,
+ uint32_t ctx_flags,
+ int locally_initiated,
+ int open);
+ ~GssContextMockImpl();
+
+ void Assign(const GssContextMockImpl& other);
+
+ std::string src_name;
+ std::string targ_name;
+ int32_t lifetime_rec;
+ gss_OID_desc mech_type;
+ int32_t ctx_flags;
+ int locally_initiated;
+ int open;
+};
+
+} // namespace test
+
+namespace android {
+
+// Interface to Java DummySpnegoAuthenticator.
+class DummySpnegoAuthenticator {
+ public:
+ struct SecurityContextQuery {
+ SecurityContextQuery(const std::string& expected_package,
+ uint32_t response_code,
+ uint32_t minor_response_code,
+ const test::GssContextMockImpl& context_info,
+ const std::string& expected_input_token,
+ const std::string& output_token);
+ SecurityContextQuery(const std::string& expected_package,
+ uint32_t response_code,
+ uint32_t minor_response_code,
+ const test::GssContextMockImpl& context_info,
+ const char* expected_input_token,
+ const char* output_token);
+ SecurityContextQuery();
+ ~SecurityContextQuery();
+
+ // Note that many of these fields only exist for compatibility with the
+ // non-Android version of the tests. Only the response_code and tokens are
+ // used or checked on Android.
+ std::string expected_package;
+ uint32_t response_code;
+ uint32_t minor_response_code;
+ test::GssContextMockImpl context_info;
+ std::string expected_input_token;
+ std::string output_token;
+
+ // Java callable members
+ base::android::ScopedJavaLocalRef<jstring> GetTokenToReturn(JNIEnv* env,
+ jobject obj);
+ int GetResult(JNIEnv* env, jobject obj);
+
+ // Called from Java to check the arguments passed to the GetToken. Has to
+ // be in C++ since these tests are driven by googletest, and can only report
+ // failures through the googletest C++ API.
+ void CheckGetTokenArguments(JNIEnv* env,
+ jobject obj,
+ jstring incoming_token);
+ };
+
+ DummySpnegoAuthenticator();
+
+ ~DummySpnegoAuthenticator();
+
+ void ExpectSecurityContext(const std::string& expected_package,
+ uint32_t response_code,
+ uint32_t minor_response_code,
+ const test::GssContextMockImpl& context_info,
+ const std::string& expected_input_token,
+ const std::string& output_token);
+
+ static bool RegisterJni(JNIEnv* env);
+
+ static void EnsureTestAccountExists();
+ static void RemoveTestAccounts();
+
+ long GetNextQuery(JNIEnv* env, jobject obj);
+
+ private:
+ // Abandon the test if the query queue is empty. Has to be a void function to
+ // allow use of ASSERT_FALSE.
+ void CheckQueueNotEmpty();
+
+ std::list<SecurityContextQuery> expected_security_queries_;
+ // Needed to keep the current query alive once it has been pulled from the
+ // queue. This is simpler than transferring its ownership to Java.
+ SecurityContextQuery current_query_;
+};
+
+} // namespace android
+} // namespace net
+
+#endif // NET_ANDROID_DUMMY_SPNEGO_AUTHENTICATOR_DRIVER_H
diff --git a/net/android/http_auth_negotiate_android.cc b/net/android/http_auth_negotiate_android.cc
new file mode 100644
index 0000000..a8e9cc3
--- /dev/null
+++ b/net/android/http_auth_negotiate_android.cc
@@ -0,0 +1,154 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/android/http_auth_negotiate_android.h"
+
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/location.h"
+#include "base/thread_task_runner_handle.h"
+#include "jni/HttpNegotiateAuthenticator_jni.h"
+#include "net/base/auth.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_auth_challenge_tokenizer.h"
+#include "net/http/http_auth_multi_round_parse.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ScopedJavaLocalRef;
+
+namespace net {
+namespace android {
+
+JavaNegotiateResultWrapper::JavaNegotiateResultWrapper(
+ const scoped_refptr<base::TaskRunner>& callback_task_runner,
+ const base::Callback<void(int, const std::string&)>& thread_safe_callback)
+ : callback_task_runner_(callback_task_runner),
+ thread_safe_callback_(thread_safe_callback) {
+}
+
+JavaNegotiateResultWrapper::~JavaNegotiateResultWrapper() {
+}
+
+void JavaNegotiateResultWrapper::SetResult(JNIEnv* env,
+ jobject obj,
+ int result,
+ jstring token) {
+ // This will be called on the UI thread, so we have to post a task back to the
+ // correct thread to actually save the result
+ std::string raw_token = ConvertJavaStringToUTF8(env, token);
+ // Always post, even if we are on the same thread. This guarantees that the
+ // result will be delayed until after the request has completed, which
+ // simplifies the logic. In practice the result will only ever come back on
+ // the original thread in an obscure error case.
+ callback_task_runner_->PostTask(
+ FROM_HERE, base::Bind(thread_safe_callback_, result, raw_token));
+ // We will always get precisely one call to set result for each call to
+ // getNextAuthToken, so we can now delete the callback object, and must
+ // do so to avoid a memory leak.
+ delete this;
+}
+
+HttpAuthNegotiateAndroid::HttpAuthNegotiateAndroid(
+ const std::string& account_type)
+ : account_type_(account_type),
+ can_delegate_(false),
+ first_challenge_(true),
+ auth_token_(nullptr),
+ weak_factory_(this) {
+ DCHECK(!account_type.empty());
+ JNIEnv* env = AttachCurrentThread();
+ java_authenticator_.Reset(Java_HttpNegotiateAuthenticator_create(
+ env, ConvertUTF8ToJavaString(env, account_type).obj()));
+}
+
+HttpAuthNegotiateAndroid::~HttpAuthNegotiateAndroid() {
+}
+
+bool HttpAuthNegotiateAndroid::Register(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+bool HttpAuthNegotiateAndroid::Init() {
+ return true;
+}
+
+bool HttpAuthNegotiateAndroid::NeedsIdentity() const {
+ return false;
+}
+
+bool HttpAuthNegotiateAndroid::AllowsExplicitCredentials() const {
+ return false;
+}
+
+HttpAuth::AuthorizationResult HttpAuthNegotiateAndroid::ParseChallenge(
+ net::HttpAuthChallengeTokenizer* tok) {
+ if (first_challenge_) {
+ first_challenge_ = false;
+ return net::ParseFirstRoundChallenge("negotiate", tok);
+ }
+ std::string decoded_auth_token;
+ return net::ParseLaterRoundChallenge("negotiate", tok, &server_auth_token_,
+ &decoded_auth_token);
+}
+
+int HttpAuthNegotiateAndroid::GenerateAuthToken(
+ const AuthCredentials* credentials,
+ const std::string& spn,
+ std::string* auth_token,
+ const net::CompletionCallback& callback) {
+ DCHECK(auth_token);
+ DCHECK(completion_callback_.is_null());
+ DCHECK(!callback.is_null());
+
+ auth_token_ = auth_token;
+ completion_callback_ = callback;
+ scoped_refptr<base::SingleThreadTaskRunner> callback_task_runner =
+ base::ThreadTaskRunnerHandle::Get();
+ base::Callback<void(int, const std::string&)> thread_safe_callback =
+ base::Bind(&HttpAuthNegotiateAndroid::SetResultInternal,
+ weak_factory_.GetWeakPtr());
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> java_server_auth_token =
+ ConvertUTF8ToJavaString(env, server_auth_token_);
+ ScopedJavaLocalRef<jstring> java_spn = ConvertUTF8ToJavaString(env, spn);
+ ScopedJavaLocalRef<jstring> java_account_type =
+ ConvertUTF8ToJavaString(env, account_type_);
+
+ // It is intentional that callback_wrapper is not owned or deleted by the
+ // HttpAuthNegotiateAndroid object. The Java code will call the callback
+ // asynchronously on a different thread, and needs an object to call it on. As
+ // such, the callback_wrapper must not be deleted until the callback has been
+ // called, whatever happens to the HttpAuthNegotiateAndroid object.
+ //
+ // Unfortunately we have no automated way of managing C++ objects owned by
+ // Java, so the Java code must simply be written to guarantee that the
+ // callback is, in the end, called.
+ JavaNegotiateResultWrapper* callback_wrapper = new JavaNegotiateResultWrapper(
+ callback_task_runner, thread_safe_callback);
+ Java_HttpNegotiateAuthenticator_getNextAuthToken(
+ env, java_authenticator_.obj(),
+ reinterpret_cast<intptr_t>(callback_wrapper), java_spn.obj(),
+ java_server_auth_token.obj(), can_delegate_);
+ return ERR_IO_PENDING;
+}
+
+void HttpAuthNegotiateAndroid::Delegate() {
+ can_delegate_ = true;
+}
+
+void HttpAuthNegotiateAndroid::SetResultInternal(int result,
+ const std::string& raw_token) {
+ DCHECK(auth_token_);
+ DCHECK(!completion_callback_.is_null());
+ if (result == OK)
+ *auth_token_ = "Negotiate " + raw_token;
+ base::ResetAndReturn(&completion_callback_).Run(result);
+}
+
+} // namespace android
+} // namespace net
diff --git a/net/android/http_auth_negotiate_android.h b/net/android/http_auth_negotiate_android.h
new file mode 100644
index 0000000..56990ce
--- /dev/null
+++ b/net/android/http_auth_negotiate_android.h
@@ -0,0 +1,132 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_ANDROID_HTTP_AUTH_NEGOTIATE_ANDROID_H_
+#define NET_ANDROID_HTTP_AUTH_NEGOTIATE_ANDROID_H_
+
+#include <jni.h>
+#include <string>
+
+#include "base/android/jni_android.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/http/http_auth.h"
+
+namespace net {
+
+class HttpAuthChallengeTokenizer;
+
+namespace android {
+
+// This class provides a threadsafe wrapper for SetResult, which is called from
+// Java. A new instance of this class is needed for each call, and the instance
+// destroys itself when the callback is received. It is written to allow
+// setResult to be called on any thread, but in practice they will be called
+// on the application's main thread.
+//
+// We cannot use a Callback object here, because there is no way of invoking the
+// Run method from Java.
+class NET_EXPORT_PRIVATE JavaNegotiateResultWrapper {
+ public:
+ scoped_refptr<base::TaskRunner> callback_task_runner_;
+ base::Callback<void(int, const std::string&)> thread_safe_callback_;
+
+ JavaNegotiateResultWrapper(
+ const scoped_refptr<base::TaskRunner>& callback_task_runner,
+ const base::Callback<void(int, const std::string&)>&
+ thread_safe_callback);
+
+ void SetResult(JNIEnv* env, jobject obj, int result, jstring token);
+
+ private:
+ // Class is only allowed to delete itself, nobody else is allowed to delete.
+ ~JavaNegotiateResultWrapper();
+};
+
+// Class providing Negotiate (SPNEGO/Kerberos) authentication support on
+// Android. The actual authentication is done through an Android authenticator
+// provided by third parties who want Kerberos support. This class simply
+// provides a bridge to the Java code, and hence to the service. See
+// https://drive.google.com/open?id=1G7WAaYEKMzj16PTHT_cIYuKXJG6bBcrQ7QQBQ6ihOcQ&authuser=1
+// for the full details.
+class NET_EXPORT_PRIVATE HttpAuthNegotiateAndroid {
+ public:
+ // Creates an object for one negotiation session. |account_type| is the
+ // Android account type, used by Android to find the correct authenticator.
+ explicit HttpAuthNegotiateAndroid(const std::string& account_type);
+ ~HttpAuthNegotiateAndroid();
+
+ // Register the JNI for this class.
+ static bool Register(JNIEnv* env);
+
+ // Does nothing, but needed for compatibility with the Negotiate
+ // authenticators for other O.S.. Always returns true.
+ bool Init();
+
+ // True if authentication needs the identity of the user from Chrome.
+ bool NeedsIdentity() const;
+
+ // True authentication can use explicit credentials included in the URL.
+ bool AllowsExplicitCredentials() const;
+
+ // Parse a received Negotiate challenge.
+ HttpAuth::AuthorizationResult ParseChallenge(
+ net::HttpAuthChallengeTokenizer* tok);
+
+ // Generates an authentication token.
+ //
+ // The return value is an error code. The authentication token will be
+ // returned in |*auth_token|. If the result code is not |OK|, the value of
+ // |*auth_token| is unspecified.
+ //
+ // If the operation cannot be completed synchronously, |ERR_IO_PENDING| will
+ // be returned and the real result code will be passed to the completion
+ // callback. Otherwise the result code is returned immediately from this
+ // call.
+ //
+ // If the AndroidAuthNegotiate object is deleted before completion then the
+ // callback will not be called.
+ //
+ // If no immediate result is returned then |auth_token| must remain valid
+ // until the callback has been called.
+ //
+ // |spn| is the Service Principal Name of the server that the token is
+ // being generated for.
+ //
+ // If this is the first round of a multiple round scheme, credentials are
+ // obtained using |*credentials|. If |credentials| is NULL, the default
+ // credentials are used instead.
+ int GenerateAuthToken(const AuthCredentials* credentials,
+ const std::string& spn,
+ std::string* auth_token,
+ const net::CompletionCallback& callback);
+
+ // Delegation is allowed on the Kerberos ticket. This allows certain servers
+ // to act as the user, such as an IIS server retrieving data from a
+ // Kerberized MSSQL server.
+ void Delegate();
+
+ private:
+ void SetResultInternal(int result, const std::string& token);
+
+ std::string account_type_;
+ bool can_delegate_;
+ bool first_challenge_;
+ std::string server_auth_token_;
+ std::string* auth_token_;
+ base::android::ScopedJavaGlobalRef<jobject> java_authenticator_;
+ net::CompletionCallback completion_callback_;
+
+ base::WeakPtrFactory<HttpAuthNegotiateAndroid> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpAuthNegotiateAndroid);
+};
+
+} // namespace android
+} // namespace net
+
+#endif // NET_ANDROID_HTTP_AUTH_NEGOTIATE_ANDROID_H_
diff --git a/net/android/http_auth_negotiate_android_unittest.cc b/net/android/http_auth_negotiate_android_unittest.cc
new file mode 100644
index 0000000..b1e178f
--- /dev/null
+++ b/net/android/http_auth_negotiate_android_unittest.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/run_loop.h"
+#include "net/android/dummy_spnego_authenticator.h"
+#include "net/android/http_auth_negotiate_android.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_auth_challenge_tokenizer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace android {
+
+TEST(HttpAuthNegotiateAndroidTest, GenerateAuthToken) {
+ DummySpnegoAuthenticator::EnsureTestAccountExists();
+
+ std::string auth_token;
+
+ DummySpnegoAuthenticator authenticator;
+ net::test::GssContextMockImpl mockContext;
+ authenticator.ExpectSecurityContext("Negotiate", GSS_S_COMPLETE, 0,
+ mockContext, "", "DummyToken");
+
+ HttpAuthNegotiateAndroid auth("org.chromium.test.DummySpnegoAuthenticator");
+ EXPECT_TRUE(auth.Init());
+
+ TestCompletionCallback callback;
+ EXPECT_EQ(OK, callback.GetResult(auth.GenerateAuthToken(
+ nullptr, "Dummy", &auth_token, callback.callback())));
+
+ EXPECT_EQ("Negotiate DummyToken", auth_token);
+
+ DummySpnegoAuthenticator::RemoveTestAccounts();
+}
+
+TEST(HttpAuthNegotiateAndroidTest, ParseChallenge_FirstRound) {
+ // The first round should just consist of an unadorned "Negotiate" header.
+ HttpAuthNegotiateAndroid auth("org.chromium.test.DummySpnegoAuthenticator");
+ std::string challenge_text = "Negotiate";
+ HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth.ParseChallenge(&challenge));
+}
+
+TEST(HttpAuthNegotiateAndroidTest, ParseChallenge_UnexpectedTokenFirstRound) {
+ // If the first round challenge has an additional authentication token, it
+ // should be treated as an invalid challenge from the server.
+ HttpAuthNegotiateAndroid auth("org.chromium.test.DummySpnegoAuthenticator");
+ std::string challenge_text = "Negotiate Zm9vYmFy";
+ HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
+ auth.ParseChallenge(&challenge));
+}
+
+TEST(HttpAuthNegotiateAndroidTest, ParseChallenge_TwoRounds) {
+ // The first round should just have "Negotiate", and the second round should
+ // have a valid base64 token associated with it.
+ HttpAuthNegotiateAndroid auth("org.chromium.test.DummySpnegoAuthenticator");
+ std::string first_challenge_text = "Negotiate";
+ HttpAuthChallengeTokenizer first_challenge(first_challenge_text.begin(),
+ first_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth.ParseChallenge(&first_challenge));
+
+ std::string second_challenge_text = "Negotiate Zm9vYmFy";
+ HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
+ second_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth.ParseChallenge(&second_challenge));
+}
+
+TEST(HttpAuthNegotiateAndroidTest, ParseChallenge_MissingTokenSecondRound) {
+ // If a later-round challenge is simply "Negotiate", it should be treated as
+ // an authentication challenge rejection from the server or proxy.
+ HttpAuthNegotiateAndroid auth("org.chromium.test.DummySpnegoAuthenticator");
+ std::string first_challenge_text = "Negotiate";
+ HttpAuthChallengeTokenizer first_challenge(first_challenge_text.begin(),
+ first_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth.ParseChallenge(&first_challenge));
+
+ std::string second_challenge_text = "Negotiate";
+ HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
+ second_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ auth.ParseChallenge(&second_challenge));
+}
+
+} // namespace android
+} // namespace net
diff --git a/net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java b/net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java
new file mode 100644
index 0000000..38dba5a
--- /dev/null
+++ b/net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java
@@ -0,0 +1,139 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.net;
+
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.VisibleForTesting;
+
+import java.io.IOException;
+
+/**
+ * Class to get Auth Tokens for HTTP Negotiate authentication (typically used for Kerberos) An
+ * instance of this class is created for each separate negotiation.
+ */
+@JNINamespace("net::android")
+public class HttpNegotiateAuthenticator {
+ private Bundle mSpnegoContext = null;
+ private final String mAccountType;
+ private AccountManagerFuture<Bundle> mFuture;
+
+ private HttpNegotiateAuthenticator(String accountType) {
+ assert !android.text.TextUtils.isEmpty(accountType);
+ mAccountType = accountType;
+ }
+
+ /**
+ * @param accountType The Android account type to use.
+ */
+ @VisibleForTesting
+ @CalledByNative
+ static HttpNegotiateAuthenticator create(String accountType) {
+ return new HttpNegotiateAuthenticator(accountType);
+ }
+
+ /**
+ * @param nativeResultObject The C++ object used to return the result. For correct C++ memory
+ * management we must call nativeSetResult precisely once with this object.
+ * @param principal The principal (must be host based).
+ * @param authToken The incoming auth token.
+ * @param canDelegate True if we can delegate.
+ */
+ @VisibleForTesting
+ @CalledByNative
+ void getNextAuthToken(final long nativeResultObject, final String principal, String authToken,
+ boolean canDelegate) {
+ assert principal != null;
+ String authTokenType = HttpNegotiateConstants.SPNEGO_TOKEN_TYPE_BASE + principal;
+ Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
+ if (activity == null) {
+ nativeSetResult(nativeResultObject, NetError.ERR_UNEXPECTED, null);
+ return;
+ }
+ AccountManager am = AccountManager.get(activity);
+ String features[] = {HttpNegotiateConstants.SPNEGO_FEATURE};
+
+ Bundle options = new Bundle();
+
+ if (authToken != null) {
+ options.putString(HttpNegotiateConstants.KEY_INCOMING_AUTH_TOKEN, authToken);
+ }
+ if (mSpnegoContext != null) {
+ options.putBundle(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT, mSpnegoContext);
+ }
+ options.putBoolean(HttpNegotiateConstants.KEY_CAN_DELEGATE, canDelegate);
+
+ mFuture = am.getAuthTokenByFeatures(mAccountType, authTokenType, features, activity, null,
+ options, new AccountManagerCallback<Bundle>() {
+
+ @Override
+ public void run(AccountManagerFuture<Bundle> future) {
+ try {
+ Bundle result = future.getResult();
+ mSpnegoContext =
+ result.getBundle(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT);
+ int status;
+ switch (result.getInt(HttpNegotiateConstants.KEY_SPNEGO_RESULT,
+ HttpNegotiateConstants.ERR_UNEXPECTED)) {
+ case HttpNegotiateConstants.OK:
+ status = 0;
+ break;
+ case HttpNegotiateConstants.ERR_UNEXPECTED:
+ status = NetError.ERR_UNEXPECTED;
+ break;
+ case HttpNegotiateConstants.ERR_ABORTED:
+ status = NetError.ERR_ABORTED;
+ break;
+ case HttpNegotiateConstants.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS:
+ status = NetError.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
+ break;
+ case HttpNegotiateConstants.ERR_INVALID_RESPONSE:
+ status = NetError.ERR_INVALID_RESPONSE;
+ break;
+ case HttpNegotiateConstants.ERR_INVALID_AUTH_CREDENTIALS:
+ status = NetError.ERR_INVALID_AUTH_CREDENTIALS;
+ break;
+ case HttpNegotiateConstants.ERR_UNSUPPORTED_AUTH_SCHEME:
+ status = NetError.ERR_UNSUPPORTED_AUTH_SCHEME;
+ break;
+ case HttpNegotiateConstants.ERR_MISSING_AUTH_CREDENTIALS:
+ status = NetError.ERR_MISSING_AUTH_CREDENTIALS;
+ break;
+ case HttpNegotiateConstants
+ .ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS:
+ status = NetError.ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
+ break;
+ case HttpNegotiateConstants.ERR_MALFORMED_IDENTITY:
+ status = NetError.ERR_MALFORMED_IDENTITY;
+ break;
+ default:
+ status = NetError.ERR_UNEXPECTED;
+ }
+ nativeSetResult(nativeResultObject, status,
+ result.getString(AccountManager.KEY_AUTHTOKEN));
+ } catch (OperationCanceledException | AuthenticatorException
+ | IOException e) {
+ nativeSetResult(nativeResultObject, NetError.ERR_ABORTED, null);
+ }
+ }
+
+ }, new Handler(ThreadUtils.getUiThreadLooper()));
+ }
+
+ @VisibleForTesting
+ native void nativeSetResult(
+ long nativeJavaNegotiateResultWrapper, int status, String authToken);
+}
diff --git a/net/android/java/src/org/chromium/net/HttpNegotiateConstants.java b/net/android/java/src/org/chromium/net/HttpNegotiateConstants.java
new file mode 100644
index 0000000..fa17647
--- /dev/null
+++ b/net/android/java/src/org/chromium/net/HttpNegotiateConstants.java
@@ -0,0 +1,51 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.net;
+
+/**
+ * Constants used by Chrome in SPNEGO authentication requests to the Android Account Manager.
+ */
+public class HttpNegotiateConstants {
+ // Option bundle keys
+ //
+ // The token provided by in the HTTP 401 response (Base64 encoded string)
+ public static final String KEY_INCOMING_AUTH_TOKEN = "incomingAuthToken";
+ // The SPNEGO Context from the previous transaction (Bundle) - also used in the response bundle
+ public static final String KEY_SPNEGO_CONTEXT = "spnegoContext";
+ // True if delegation is allowed
+ public static final String KEY_CAN_DELEGATE = "canDelegate";
+
+ // Response bundle keys
+ //
+ // The returned status from the authenticator.
+ public static final String KEY_SPNEGO_RESULT = "spnegoResult";
+
+ // Name of SPNEGO feature
+ public static final String SPNEGO_FEATURE = "SPNEGO";
+ // Prefix of token type. Full token type is "SPNEGO:HOSTBASED:<spn>"
+ public static final String SPNEGO_TOKEN_TYPE_BASE = "SPNEGO:HOSTBASED:";
+
+ // Returned status codes
+ // All OK. Returned token is valid.
+ public static final int OK = 0;
+ // An unexpected error. This may be caused by a programming mistake or an invalid assumption.
+ public static final int ERR_UNEXPECTED = 1;
+ // Request aborted due to user action.
+ public static final int ERR_ABORTED = 2;
+ // An unexpected, but documented, SSPI or GSSAPI status code was returned.
+ public static final int ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS = 3;
+ // The server's response was invalid.
+ public static final int ERR_INVALID_RESPONSE = 4;
+ // Credentials could not be established during HTTP Authentication.
+ public static final int ERR_INVALID_AUTH_CREDENTIALS = 5;
+ // An HTTP Authentication scheme was tried which is not supported on this machine.
+ public static final int ERR_UNSUPPORTED_AUTH_SCHEME = 6;
+ // (GSSAPI) No Kerberos credentials were available during HTTP Authentication.
+ public static final int ERR_MISSING_AUTH_CREDENTIALS = 7;
+ // An undocumented SSPI or GSSAPI status code was returned.
+ public static final int ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS = 8;
+ // The identity used for authentication is invalid.
+ public static final int ERR_MALFORMED_IDENTITY = 9;
+}
diff --git a/net/android/junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java b/net/android/junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java
new file mode 100644
index 0000000..8647407
--- /dev/null
+++ b/net/android/junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java
@@ -0,0 +1,222 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.net;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+
+import org.chromium.base.BaseChromiumApplication;
+import org.chromium.testing.local.LocalRobolectricTestRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowAccountManager;
+
+import java.io.IOException;
+
+/**
+ * Robolectric tests for HttpNegotiateAuthenticator
+ */
+@RunWith(LocalRobolectricTestRunner.class)
+@Config(manifest = Config.NONE,
+ shadows = HttpNegotiateAuthenticatorTest.ExtendedShadowAccountManager.class,
+ application = BaseChromiumApplication.class)
+public class HttpNegotiateAuthenticatorTest {
+ static int sCallCount = 0;
+ static String sAccountTypeReceived;
+ static String sAuthTokenTypeReceived;
+ static String sFeaturesReceived[];
+ static Bundle sAddAccountOptionsReceived;
+ static Bundle sAuthTokenOptionsReceived;
+ static AccountManagerCallback<Bundle> sCallbackReceived;
+ static Handler sHandlerReceived;
+
+ /**
+ * Robolectic's ShadowAccountManager doesn't implement getAccountsByTypeAndFeature so extend it.
+ * We simply check the call is correct, and don't try to emulate it Note: Shadow classes need to
+ * be public and static.
+ */
+ @Implements(AccountManager.class)
+ public static class ExtendedShadowAccountManager extends ShadowAccountManager {
+ @Implementation
+ public AccountManagerFuture<Bundle> getAuthTokenByFeatures(String accountType,
+ String authTokenType, String[] features, Activity activity,
+ Bundle addAccountOptions, Bundle getAuthTokenOptions,
+ AccountManagerCallback<Bundle> callback, Handler handler) {
+ sCallCount++;
+ sAccountTypeReceived = accountType;
+ sAuthTokenTypeReceived = authTokenType;
+ sFeaturesReceived = features;
+ sAddAccountOptionsReceived = addAccountOptions;
+ sAuthTokenOptionsReceived = getAuthTokenOptions;
+ sCallbackReceived = callback;
+ sHandlerReceived = handler;
+
+ return null;
+ }
+ }
+
+ /**
+ * Test of {@link HttpNegotiateAuthenticator#getNextAuthToken}
+ */
+ @Test
+ public void testGetNextAuthToken() {
+ HttpNegotiateAuthenticator authenticator =
+ HttpNegotiateAuthenticator.create("Dummy_Account");
+ Robolectric.buildActivity(Activity.class).create().start().resume().visible();
+ authenticator.getNextAuthToken(0, "test_principal", "", true);
+ assertThat("getAuthTokenByFeatures called precisely once", sCallCount, equalTo(1));
+ assertThat("Received account type matches input", sAccountTypeReceived,
+ equalTo("Dummy_Account"));
+ assertThat("AuthTokenType is \"SPNEGO:HOSTBASED:test_principal\"", sAuthTokenTypeReceived,
+ equalTo("SPNEGO:HOSTBASED:test_principal"));
+ assertThat("Features are precisely {\"SPNEGO\"}", sFeaturesReceived,
+ equalTo(new String[] {"SPNEGO"}));
+ assertThat("No account options requested", sAddAccountOptionsReceived, nullValue());
+ assertThat("There is no existing context",
+ sAuthTokenOptionsReceived.get(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT),
+ nullValue());
+ assertThat("The existing token is empty",
+ sAuthTokenOptionsReceived.getString(HttpNegotiateConstants.KEY_INCOMING_AUTH_TOKEN),
+ equalTo(""));
+ assertThat("Delegation is allowed",
+ sAuthTokenOptionsReceived.getBoolean(HttpNegotiateConstants.KEY_CAN_DELEGATE),
+ equalTo(true));
+ assertThat("getAuthTokenByFeatures was called with a callback", sCallbackReceived,
+ notNullValue());
+ assertThat("getAuthTokenByFeatures was called with a handler", sHandlerReceived,
+ notNullValue());
+ }
+
+ /**
+ * Test of callback called when getting the auth token completes.
+ */
+ @Test
+ public void testAccountManagerCallbackRun() {
+ // Spy on the authenticator so that we can override and intercept the native method call.
+ HttpNegotiateAuthenticator authenticator =
+ spy(HttpNegotiateAuthenticator.create("Dummy_Account"));
+ doNothing().when(authenticator).nativeSetResult(anyLong(), anyInt(), anyString());
+
+ Robolectric.buildActivity(Activity.class).create().start().resume().visible();
+
+ // Call getNextAuthToken to get the callback
+ authenticator.getNextAuthToken(1234, "test_principal", "", true);
+
+ // Avoid warning when creating mock accountManagerFuture, can't take .class of an
+ // instantiated generic type, yet compiler complains if I leave it uninstantiated.
+ @SuppressWarnings("unchecked")
+ AccountManagerFuture<Bundle> accountManagerFuture = mock(AccountManagerFuture.class);
+ Bundle resultBundle = new Bundle();
+ Bundle context = new Bundle();
+ context.putString("String", "test_context");
+ resultBundle.putInt(HttpNegotiateConstants.KEY_SPNEGO_RESULT, HttpNegotiateConstants.OK);
+ resultBundle.putBundle(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT, context);
+ resultBundle.putString(AccountManager.KEY_AUTHTOKEN, "output_token");
+ try {
+ when(accountManagerFuture.getResult()).thenReturn(resultBundle);
+ } catch (OperationCanceledException | AuthenticatorException | IOException e) {
+ // Can never happen - artifact of Mockito.
+ fail();
+ }
+ sCallbackReceived.run(accountManagerFuture);
+ verify(authenticator).nativeSetResult(1234, 0, "output_token");
+
+ // Check that the next call to getNextAuthToken uses the correct context
+ authenticator.getNextAuthToken(5678, "test_principal", "", true);
+ assertThat("The spnego context is preserved between calls",
+ sAuthTokenOptionsReceived.getBundle(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT),
+ equalTo(context));
+
+ // Test exception path
+ try {
+ when(accountManagerFuture.getResult()).thenThrow(new OperationCanceledException());
+ } catch (OperationCanceledException | AuthenticatorException | IOException e) {
+ // Can never happen - artifact of Mockito.
+ fail();
+ }
+ sCallbackReceived.run(accountManagerFuture);
+ verify(authenticator).nativeSetResult(5678, NetError.ERR_ABORTED, null);
+ }
+
+ private void checkErrorReturn(Integer spnegoError, int expectedError) {
+ // Spy on the authenticator so that we can override and intercept the native method call.
+ HttpNegotiateAuthenticator authenticator =
+ spy(HttpNegotiateAuthenticator.create("Dummy_Account"));
+ doNothing().when(authenticator).nativeSetResult(anyLong(), anyInt(), anyString());
+
+ Robolectric.buildActivity(Activity.class).create().start().resume().visible();
+
+ // Call getNextAuthToken to get the callback
+ authenticator.getNextAuthToken(1234, "test_principal", "", true);
+
+ // Avoid warning when creating mock accountManagerFuture, can't take .class of an
+ // instantiated generic type, yet compiler complains if I leave it uninstantiated.
+ @SuppressWarnings("unchecked")
+ AccountManagerFuture<Bundle> accountManagerFuture = mock(AccountManagerFuture.class);
+ Bundle resultBundle = new Bundle();
+ if (spnegoError != null) {
+ resultBundle.putInt(HttpNegotiateConstants.KEY_SPNEGO_RESULT, spnegoError);
+ }
+ try {
+ when(accountManagerFuture.getResult()).thenReturn(resultBundle);
+ } catch (OperationCanceledException | AuthenticatorException | IOException e) {
+ // Can never happen - artifact of Mockito.
+ fail();
+ }
+ sCallbackReceived.run(accountManagerFuture);
+ verify(authenticator).nativeSetResult(anyLong(), eq(expectedError), anyString());
+ }
+
+ /**
+ * Test of callback error returns when getting the auth token completes.
+ */
+ @Test
+ public void testAccountManagerCallbackErrorReturns() {
+ checkErrorReturn(null, NetError.ERR_UNEXPECTED);
+ checkErrorReturn(HttpNegotiateConstants.ERR_UNEXPECTED, NetError.ERR_UNEXPECTED);
+ checkErrorReturn(HttpNegotiateConstants.ERR_ABORTED, NetError.ERR_ABORTED);
+ checkErrorReturn(HttpNegotiateConstants.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS,
+ NetError.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS);
+ checkErrorReturn(
+ HttpNegotiateConstants.ERR_INVALID_RESPONSE, NetError.ERR_INVALID_RESPONSE);
+ checkErrorReturn(HttpNegotiateConstants.ERR_INVALID_AUTH_CREDENTIALS,
+ NetError.ERR_INVALID_AUTH_CREDENTIALS);
+ checkErrorReturn(HttpNegotiateConstants.ERR_UNSUPPORTED_AUTH_SCHEME,
+ NetError.ERR_UNSUPPORTED_AUTH_SCHEME);
+ checkErrorReturn(HttpNegotiateConstants.ERR_MISSING_AUTH_CREDENTIALS,
+ NetError.ERR_MISSING_AUTH_CREDENTIALS);
+ checkErrorReturn(HttpNegotiateConstants.ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS,
+ NetError.ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS);
+ checkErrorReturn(
+ HttpNegotiateConstants.ERR_MALFORMED_IDENTITY, NetError.ERR_MALFORMED_IDENTITY);
+ // 9999 is not a valid return value
+ checkErrorReturn(9999, NetError.ERR_UNEXPECTED);
+ }
+}
diff --git a/net/android/net_jni_registrar.cc b/net/android/net_jni_registrar.cc
index 1175d96..dec74ecc1 100644
--- a/net/android/net_jni_registrar.cc
+++ b/net/android/net_jni_registrar.cc
@@ -8,6 +8,7 @@
#include "base/android/jni_registrar.h"
#include "net/android/android_private_key.h"
#include "net/android/gurl_utils.h"
+#include "net/android/http_auth_negotiate_android.h"
#include "net/android/keystore.h"
#include "net/android/network_change_notifier_android.h"
#include "net/android/network_library.h"
@@ -27,6 +28,7 @@
{"AndroidKeyStore", RegisterKeyStore},
{"AndroidNetworkLibrary", RegisterNetworkLibrary},
{"GURLUtils", RegisterGURLUtils},
+ {"HttpAuthNegotiateAndroid", HttpAuthNegotiateAndroid::Register},
{"NetworkChangeNotifierAndroid", NetworkChangeNotifierAndroid::Register},
{"ProxyConfigService", ProxyConfigServiceAndroid::Register},
{"X509Util", RegisterX509Util},
diff --git a/net/android/unittest_support/AndroidManifest.xml b/net/android/unittest_support/AndroidManifest.xml
new file mode 100644
index 0000000..d7493de
--- /dev/null
+++ b/net/android/unittest_support/AndroidManifest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2014 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.chromium.native_test"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="22" />
+ <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
+ <uses-permission android:name="android.permission.BLUETOOTH"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+ <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+ <uses-permission android:name="android.permission.USE_CREDENTIALS"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+
+ <application android:label="NativeTests"
+ android:name="org.chromium.base.BaseChromiumApplication">
+ <activity android:name=".NativeUnitTestActivity"
+ android:label="NativeTest"
+ android:configChanges="orientation|keyboardHidden">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <service android:name="org.chromium.net.test.DummySpnegoAuthenticatorService"
+ android:exported = "false">
+ <intent-filter>
+ <action android:name="android.accounts.AccountAuthenticator" />
+ </intent-filter>
+ <meta-data android:name="android.accounts.AccountAuthenticator"
+ android:resource="@xml/dummy_spnego_authenticator" />
+ </service>
+ </application>
+
+ <instrumentation android:name="org.chromium.native_test.NativeTestInstrumentationTestRunner"
+ android:targetPackage="org.chromium.native_test"
+ android:label="Instrumentation entry point for org.chromium.native_test"/>
+
+</manifest>
diff --git a/net/android/unittest_support/res/mipmap-hdpi/app_icon.png b/net/android/unittest_support/res/mipmap-hdpi/app_icon.png
new file mode 100644
index 0000000..da881485
--- /dev/null
+++ b/net/android/unittest_support/res/mipmap-hdpi/app_icon.png
Binary files differ
diff --git a/net/android/unittest_support/res/mipmap-mdpi/app_icon.png b/net/android/unittest_support/res/mipmap-mdpi/app_icon.png
new file mode 100644
index 0000000..24611dec
--- /dev/null
+++ b/net/android/unittest_support/res/mipmap-mdpi/app_icon.png
Binary files differ
diff --git a/net/android/unittest_support/res/mipmap-xhdpi/app_icon.png b/net/android/unittest_support/res/mipmap-xhdpi/app_icon.png
new file mode 100644
index 0000000..98c91899
--- /dev/null
+++ b/net/android/unittest_support/res/mipmap-xhdpi/app_icon.png
Binary files differ
diff --git a/net/android/unittest_support/res/mipmap-xxhdpi/app_icon.png b/net/android/unittest_support/res/mipmap-xxhdpi/app_icon.png
new file mode 100644
index 0000000..1fbc0e7
--- /dev/null
+++ b/net/android/unittest_support/res/mipmap-xxhdpi/app_icon.png
Binary files differ
diff --git a/net/android/unittest_support/res/mipmap-xxxhdpi/app_icon.png b/net/android/unittest_support/res/mipmap-xxxhdpi/app_icon.png
new file mode 100644
index 0000000..5b09d42
--- /dev/null
+++ b/net/android/unittest_support/res/mipmap-xxxhdpi/app_icon.png
Binary files differ
diff --git a/net/android/unittest_support/res/xml/dummy_spnego_account_preferences.xml b/net/android/unittest_support/res/xml/dummy_spnego_account_preferences.xml
new file mode 100644
index 0000000..38901dce
--- /dev/null
+++ b/net/android/unittest_support/res/xml/dummy_spnego_account_preferences.xml
@@ -0,0 +1 @@
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"/>
diff --git a/net/android/unittest_support/res/xml/dummy_spnego_authenticator.xml b/net/android/unittest_support/res/xml/dummy_spnego_authenticator.xml
new file mode 100644
index 0000000..aab1b93b
--- /dev/null
+++ b/net/android/unittest_support/res/xml/dummy_spnego_authenticator.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:accountType="org.chromium.test.DummySpnegoAuthenticator"
+ android:label="DummySpengoAuthenticator"
+ android:icon="@mipmap/app_icon"
+ android:smallIcon="@mipmap/app_icon"
+ android:customTokens="true"
+ android:accountPreferences="@xml/dummy_spnego_account_preferences"/>
diff --git a/net/http/http_auth_gssapi_posix.cc b/net/http/http_auth_gssapi_posix.cc
index 388cc64..0b87b33 100644
--- a/net/http/http_auth_gssapi_posix.cc
+++ b/net/http/http_auth_gssapi_posix.cc
@@ -16,7 +16,7 @@
#include "base/threading/thread_restrictions.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
-#include "net/http/http_auth_challenge_tokenizer.h"
+#include "net/http/http_auth_multi_round_parse.h"
// These are defined for the GSSAPI library:
// Paraphrasing the comments from gssapi.h:
@@ -687,39 +687,18 @@
HttpAuth::AuthorizationResult HttpAuthGSSAPI::ParseChallenge(
HttpAuthChallengeTokenizer* tok) {
- // Verify the challenge's auth-scheme.
- if (!base::LowerCaseEqualsASCII(tok->scheme(),
- base::StringToLowerASCII(scheme_).c_str()))
- return HttpAuth::AUTHORIZATION_RESULT_INVALID;
-
- std::string encoded_auth_token = tok->base64_param();
-
- if (encoded_auth_token.empty()) {
- // If a context has already been established, an empty Negotiate challenge
- // should be treated as a rejection of the current attempt.
- if (scoped_sec_context_.get() != GSS_C_NO_CONTEXT)
- return HttpAuth::AUTHORIZATION_RESULT_REJECT;
- DCHECK(decoded_server_auth_token_.empty());
- return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
- } else {
- // If a context has not already been established, additional tokens should
- // not be present in the auth challenge.
- if (scoped_sec_context_.get() == GSS_C_NO_CONTEXT)
- return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+ if (scoped_sec_context_.get() == GSS_C_NO_CONTEXT) {
+ return net::ParseFirstRoundChallenge(scheme_, tok);
}
-
- // Make sure the additional token is base64 encoded.
- std::string decoded_auth_token;
- bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token);
- if (!base64_rv)
- return HttpAuth::AUTHORIZATION_RESULT_INVALID;
- decoded_server_auth_token_ = decoded_auth_token;
- return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+ std::string encoded_auth_token;
+ return net::ParseLaterRoundChallenge(scheme_, tok, &encoded_auth_token,
+ &decoded_server_auth_token_);
}
int HttpAuthGSSAPI::GenerateAuthToken(const AuthCredentials* credentials,
const std::string& spn,
- std::string* auth_token) {
+ std::string* auth_token,
+ const CompletionCallback& /*callback*/) {
DCHECK(auth_token);
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
diff --git a/net/http/http_auth_gssapi_posix.h b/net/http/http_auth_gssapi_posix.h
index ab967c96..73502c9 100644
--- a/net/http/http_auth_gssapi_posix.h
+++ b/net/http/http_auth_gssapi_posix.h
@@ -9,6 +9,7 @@
#include "base/gtest_prod_util.h"
#include "base/native_library.h"
+#include "net/base/completion_callback.h"
#include "net/base/net_export.h"
#include "net/http/http_auth.h"
@@ -22,7 +23,7 @@
// Chrome supports OSX 10.6, which doesn't have access to GSS.framework. Chrome
// always dlopens libgssapi_krb5.dylib, which is provided by
-// Kerberos.framework. On OSX 10.7+ this is an ABI comptabile shim that loads
+// Kerberos.framework. On OSX 10.7+ this is an ABI compatible shim that loads
// GSS.framework.
#include <Kerberos/gssapi.h>
#elif defined(OS_FREEBSD)
@@ -246,19 +247,35 @@
HttpAuthChallengeTokenizer* tok);
// Generates an authentication token.
- // The return value is an error code. If it's not |OK|, the value of
+ //
+ // The return value is an error code. The authentication token will be
+ // returned in |*auth_token|. If the result code is not |OK|, the value of
// |*auth_token| is unspecified.
+ //
+ // If the operation cannot be completed synchronously, |ERR_IO_PENDING| will
+ // be returned and the real result code will be passed to the completion
+ // callback. Otherwise the result code is returned immediately from this
+ // call.
+ //
+ // If the HttpAuthGSSAPI object is deleted before completion then the callback
+ // will not be called.
+ //
+ // If no immediate result is returned then |auth_token| must remain valid
+ // until the callback has been called.
+ //
// |spn| is the Service Principal Name of the server that the token is
// being generated for.
+ //
// If this is the first round of a multiple round scheme, credentials are
// obtained using |*credentials|. If |credentials| is NULL, the default
// credentials are used instead.
int GenerateAuthToken(const AuthCredentials* credentials,
const std::string& spn,
- std::string* auth_token);
+ std::string* auth_token,
+ const CompletionCallback& callback);
// Delegation is allowed on the Kerberos ticket. This allows certain servers
- // to act as the user, such as an IIS server retrieiving data from a
+ // to act as the user, such as an IIS server retrieving data from a
// Kerberized MSSQL server.
void Delegate();
diff --git a/net/http/http_auth_gssapi_posix_unittest.cc b/net/http/http_auth_gssapi_posix_unittest.cc
index 6f93334..79a248f 100644
--- a/net/http/http_auth_gssapi_posix_unittest.cc
+++ b/net/http/http_auth_gssapi_posix_unittest.cc
@@ -71,6 +71,12 @@
out_buffer);
}
+void UnexpectedCallback(int result) {
+ // At present getting tokens from gssapi is fully synchronous, so the callback
+ // should never be called.
+ ADD_FAILURE();
+}
+
} // namespace
TEST(HttpAuthGSSAPIPOSIXTest, GSSAPIStartup) {
@@ -204,7 +210,8 @@
EstablishInitialContext(&mock_library);
std::string auth_token;
EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, "HTTP/intranet.google.com",
- &auth_token));
+ &auth_token,
+ base::Bind(&UnexpectedCallback)));
std::string second_challenge_text = "Negotiate Zm9vYmFy";
HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
@@ -241,7 +248,8 @@
EstablishInitialContext(&mock_library);
std::string auth_token;
EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, "HTTP/intranet.google.com",
- &auth_token));
+ &auth_token,
+ base::Bind(&UnexpectedCallback)));
std::string second_challenge_text = "Negotiate";
HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
second_challenge_text.end());
@@ -264,7 +272,8 @@
EstablishInitialContext(&mock_library);
std::string auth_token;
EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, "HTTP/intranet.google.com",
- &auth_token));
+ &auth_token,
+ base::Bind(&UnexpectedCallback)));
std::string second_challenge_text = "Negotiate =happyjoy=";
HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
second_challenge_text.end());
diff --git a/net/http/http_auth_handler_factory.cc b/net/http/http_auth_handler_factory.cc
index decb20e..4f09c9f 100644
--- a/net/http/http_auth_handler_factory.cc
+++ b/net/http/http_auth_handler_factory.cc
@@ -53,7 +53,9 @@
registry_factory->RegisterSchemeFactory(
"digest", new HttpAuthHandlerDigest::Factory());
-#if defined(USE_KERBEROS)
+// On Android Chrome needs an account type configured to enable Kerberos,
+// so the default factory should not include Kerberos.
+#if defined(USE_KERBEROS) && !defined(OS_ANDROID)
HttpAuthHandlerNegotiate::Factory* negotiate_factory =
new HttpAuthHandlerNegotiate::Factory();
#if defined(OS_POSIX)
@@ -63,7 +65,7 @@
#endif
negotiate_factory->set_host_resolver(host_resolver);
registry_factory->RegisterSchemeFactory("negotiate", negotiate_factory);
-#endif // defined(USE_KERBEROS)
+#endif // defined(USE_KERBEROS) && !defined(OS_ANDROID)
HttpAuthHandlerNTLM::Factory* ntlm_factory =
new HttpAuthHandlerNTLM::Factory();
@@ -131,6 +133,7 @@
URLSecurityManager* security_manager,
HostResolver* host_resolver,
const std::string& gssapi_library_name,
+ const std::string& auth_android_negotiate_account_type,
bool negotiate_disable_cname_lookup,
bool negotiate_enable_port) {
HttpAuthHandlerRegistryFactory* registry_factory =
@@ -154,7 +157,9 @@
if (IsSupportedScheme(supported_schemes, "negotiate")) {
HttpAuthHandlerNegotiate::Factory* negotiate_factory =
new HttpAuthHandlerNegotiate::Factory();
-#if defined(OS_POSIX)
+#if defined(OS_ANDROID)
+ negotiate_factory->set_library(&auth_android_negotiate_account_type);
+#elif defined(OS_POSIX)
negotiate_factory->set_library(
new GSSAPISharedLibrary(gssapi_library_name));
#elif defined(OS_WIN)
diff --git a/net/http/http_auth_handler_factory.h b/net/http/http_auth_handler_factory.h
index 06f7f34..30b1896c 100644
--- a/net/http/http_auth_handler_factory.h
+++ b/net/http/http_auth_handler_factory.h
@@ -169,7 +169,12 @@
// |host_resolver| must not be NULL.
//
// |gssapi_library_name| specifies the name of the GSSAPI library that will
- // be loaded on all platforms except Windows.
+ // be loaded on Posix platforms other than Android. |gssapi_library_name| is
+ // ignored on Android and Windows.
+ //
+ // |auth_android_negotiate_account_type| is an Android account type, used to
+ // find the appropriate authenticator service on Android. It is ignored on
+ // non-Android platforms.
//
// |negotiate_disable_cname_lookup| and |negotiate_enable_port| both control
// how Negotiate does SPN generation, by default these should be false.
@@ -178,6 +183,7 @@
URLSecurityManager* security_manager,
HostResolver* host_resolver,
const std::string& gssapi_library_name,
+ const std::string& auth_android_negotiate_account_type,
bool negotiate_disable_cname_lookup,
bool negotiate_enable_port);
diff --git a/net/http/http_auth_handler_factory_unittest.cc b/net/http/http_auth_handler_factory_unittest.cc
index 2aa79588..be0c3b0 100644
--- a/net/http/http_auth_handler_factory_unittest.cc
+++ b/net/http/http_auth_handler_factory_unittest.cc
@@ -172,7 +172,8 @@
server_origin,
BoundNetLog(),
&handler);
-#if defined(USE_KERBEROS)
+// Note the default factory doesn't support Kerberos on Android
+#if defined(USE_KERBEROS) && !defined(OS_ANDROID)
EXPECT_EQ(OK, rv);
ASSERT_FALSE(handler.get() == NULL);
EXPECT_EQ(HttpAuth::AUTH_SCHEME_NEGOTIATE, handler->auth_scheme());
@@ -183,7 +184,7 @@
#else
EXPECT_EQ(ERR_UNSUPPORTED_AUTH_SCHEME, rv);
EXPECT_TRUE(handler.get() == NULL);
-#endif // defined(USE_KERBEROS)
+#endif // defined(USE_KERBEROS) && !defined(OS_ANDROID)
}
}
diff --git a/net/http/http_auth_handler_negotiate.cc b/net/http/http_auth_handler_negotiate.cc
index 422ddd7..b02913b 100644
--- a/net/http/http_auth_handler_negotiate.cc
+++ b/net/http/http_auth_handler_negotiate.cc
@@ -61,10 +61,14 @@
new HttpAuthHandlerNegotiate(auth_library_.get(), max_token_length_,
url_security_manager(), resolver_,
disable_cname_lookup_, use_port_));
- if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
- return ERR_INVALID_RESPONSE;
- handler->swap(tmp_handler);
- return OK;
+#elif defined(OS_ANDROID)
+ if (is_unsupported_ || auth_library_->empty() || reason == CREATE_PREEMPTIVE)
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ // TODO(cbentzel): Move towards model of parsing in the factory
+ // method and only constructing when valid.
+ scoped_ptr<HttpAuthHandler> tmp_handler(new HttpAuthHandlerNegotiate(
+ auth_library_.get(), url_security_manager(), resolver_,
+ disable_cname_lookup_, use_port_));
#elif defined(OS_POSIX)
if (is_unsupported_)
return ERR_UNSUPPORTED_AUTH_SCHEME;
@@ -78,11 +82,11 @@
new HttpAuthHandlerNegotiate(auth_library_.get(), url_security_manager(),
resolver_, disable_cname_lookup_,
use_port_));
+#endif
if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
return ERR_INVALID_RESPONSE;
handler->swap(tmp_handler);
return OK;
-#endif
}
HttpAuthHandlerNegotiate::HttpAuthHandlerNegotiate(
@@ -94,7 +98,9 @@
HostResolver* resolver,
bool disable_cname_lookup,
bool use_port)
-#if defined(OS_WIN)
+#if defined(OS_ANDROID)
+ : auth_system_(*auth_library),
+#elif defined(OS_WIN)
: auth_system_(auth_library, "Negotiate", NEGOSSP_NAME, max_token_length),
#elif defined(OS_POSIX)
: auth_system_(auth_library, "Negotiate", CHROME_GSS_SPNEGO_MECH_OID_DESC),
@@ -315,8 +321,10 @@
int HttpAuthHandlerNegotiate::DoGenerateAuthToken() {
next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE;
AuthCredentials* credentials = has_credentials_ ? &credentials_ : NULL;
- // TODO(cbentzel): This should possibly be done async.
- return auth_system_.GenerateAuthToken(credentials, spn_, auth_token_);
+ return auth_system_.GenerateAuthToken(
+ credentials, spn_, auth_token_,
+ base::Bind(&HttpAuthHandlerNegotiate::OnIOComplete,
+ base::Unretained(this)));
}
int HttpAuthHandlerNegotiate::DoGenerateAuthTokenComplete(int rv) {
diff --git a/net/http/http_auth_handler_negotiate.h b/net/http/http_auth_handler_negotiate.h
index fc596fa..d8b9d9d 100644
--- a/net/http/http_auth_handler_negotiate.h
+++ b/net/http/http_auth_handler_negotiate.h
@@ -13,7 +13,9 @@
#include "net/http/http_auth_handler.h"
#include "net/http/http_auth_handler_factory.h"
-#if defined(OS_WIN)
+#if defined(OS_ANDROID)
+#include "net/android/http_auth_negotiate_android.h"
+#elif defined(OS_WIN)
#include "net/http/http_auth_sspi_win.h"
#elif defined(OS_POSIX)
#include "net/http/http_auth_gssapi_posix.h"
@@ -32,7 +34,12 @@
class NET_EXPORT_PRIVATE HttpAuthHandlerNegotiate : public HttpAuthHandler {
public:
-#if defined(OS_WIN)
+#if defined(OS_ANDROID)
+ typedef net::android::HttpAuthNegotiateAndroid AuthSystem;
+ // For Android this isn't a library, but for the Android Account type, which
+ // indirectly identifies the Kerberos/SPNEGO authentication app.
+ typedef const std::string AuthLibrary;
+#elif defined(OS_WIN)
typedef SSPILibrary AuthLibrary;
typedef HttpAuthSSPI AuthSystem;
#elif defined(OS_POSIX)
@@ -65,8 +72,8 @@
// Sets the system library to use, thereby assuming ownership of
// |auth_library|.
- void set_library(AuthLibrary* auth_library) {
- auth_library_.reset(auth_library);
+ void set_library(AuthLibrary* auth_provider) {
+ auth_library_.reset(auth_provider);
}
int CreateAuthHandler(HttpAuthChallengeTokenizer* challenge,
@@ -89,7 +96,7 @@
scoped_ptr<AuthLibrary> auth_library_;
};
- HttpAuthHandlerNegotiate(AuthLibrary* sspi_library,
+ HttpAuthHandlerNegotiate(AuthLibrary* auth_library,
#if defined(OS_WIN)
ULONG max_token_length,
#endif
diff --git a/net/http/http_auth_handler_negotiate_unittest.cc b/net/http/http_auth_handler_negotiate_unittest.cc
index eaee8e5..49ebdad6 100644
--- a/net/http/http_auth_handler_negotiate_unittest.cc
+++ b/net/http/http_auth_handler_negotiate_unittest.cc
@@ -4,6 +4,8 @@
#include "net/http/http_auth_handler_negotiate.h"
+#include <string>
+
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "net/base/net_errors.h"
@@ -11,7 +13,9 @@
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_request_info.h"
#include "net/http/mock_allow_url_security_manager.h"
-#if defined(OS_WIN)
+#if defined(OS_ANDROID)
+#include "net/android/dummy_spnego_authenticator.h"
+#elif defined(OS_WIN)
#include "net/http/mock_sspi_library_win.h"
#elif defined(OS_POSIX)
#include "net/http/mock_gssapi_library_posix.h"
@@ -21,7 +25,9 @@
namespace net {
-#if defined(OS_WIN)
+#if defined(OS_ANDROID)
+typedef net::android::DummySpnegoAuthenticator MockAuthLibrary;
+#elif defined(OS_WIN)
typedef MockSSPILibrary MockAuthLibrary;
#elif defined(OS_POSIX)
typedef test::MockGSSAPILibrary MockAuthLibrary;
@@ -38,10 +44,22 @@
url_security_manager_.reset(new MockAllowURLSecurityManager());
factory_.reset(new HttpAuthHandlerNegotiate::Factory());
factory_->set_url_security_manager(url_security_manager_.get());
+#if defined(OS_ANDROID)
+ std::string* authenticator =
+ new std::string("org.chromium.test.DummySpnegoAuthenticator");
+ factory_->set_library(authenticator);
+ MockAuthLibrary::EnsureTestAccountExists();
+#endif
+#if defined(OS_WIN) || (defined(OS_POSIX) && !defined(OS_ANDROID))
factory_->set_library(auth_library_);
+#endif
factory_->set_host_resolver(resolver_.get());
}
+#if defined(OS_ANDROID)
+ void TearDown() override { MockAuthLibrary::RemoveTestAccounts(); }
+#endif
+
void SetupMocks(MockAuthLibrary* mock_library) {
#if defined(OS_WIN)
security_package_.reset(new SecPkgInfoW);
@@ -113,21 +131,21 @@
0, // Context flags
1, // Locally initiated
1); // Open
- test::MockGSSAPILibrary::SecurityContextQuery queries[] = {
- test::MockGSSAPILibrary::SecurityContextQuery(
- "Negotiate", // Package name
- GSS_S_CONTINUE_NEEDED, // Major response code
- 0, // Minor response code
- context1, // Context
- NULL, // Expected input token
- kAuthResponse), // Output token
- test::MockGSSAPILibrary::SecurityContextQuery(
- "Negotiate", // Package name
- GSS_S_COMPLETE, // Major response code
- 0, // Minor response code
- context2, // Context
- kAuthResponse, // Expected input token
- kAuthResponse) // Output token
+ MockAuthLibrary::SecurityContextQuery queries[] = {
+ MockAuthLibrary::SecurityContextQuery(
+ "Negotiate", // Package name
+ GSS_S_CONTINUE_NEEDED, // Major response code
+ 0, // Minor response code
+ context1, // Context
+ NULL, // Expected input token
+ kAuthResponse), // Output token
+ MockAuthLibrary::SecurityContextQuery(
+ "Negotiate", // Package name
+ GSS_S_COMPLETE, // Major response code
+ 0, // Minor response code
+ context2, // Context
+ kAuthResponse, // Expected input token
+ kAuthResponse) // Output token
};
for (size_t i = 0; i < arraysize(queries); ++i) {
@@ -154,13 +172,13 @@
0, // Context flags
1, // Locally initiated
0); // Open
- test::MockGSSAPILibrary::SecurityContextQuery query(
- "Negotiate", // Package name
- major_status, // Major response code
- minor_status, // Minor response code
- context, // Context
- NULL, // Expected input token
- NULL); // Output token
+ MockAuthLibrary::SecurityContextQuery query(
+ "Negotiate", // Package name
+ major_status, // Major response code
+ minor_status, // Minor response code
+ context, // Context
+ NULL, // Expected input token
+ NULL); // Output token
mock_library->ExpectSecurityContext(query.expected_package,
query.response_code,
@@ -223,8 +241,8 @@
TestCompletionCallback callback;
HttpRequestInfo request_info;
std::string token;
- EXPECT_EQ(OK, auth_handler->GenerateAuthToken(NULL, &request_info,
- callback.callback(), &token));
+ EXPECT_EQ(OK, callback.GetResult(auth_handler->GenerateAuthToken(
+ NULL, &request_info, callback.callback(), &token)));
#if defined(OS_WIN)
EXPECT_EQ("HTTP/alias", auth_handler->spn());
#elif defined(OS_POSIX)
@@ -241,8 +259,8 @@
TestCompletionCallback callback;
HttpRequestInfo request_info;
std::string token;
- EXPECT_EQ(OK, auth_handler->GenerateAuthToken(NULL, &request_info,
- callback.callback(), &token));
+ EXPECT_EQ(OK, callback.GetResult(auth_handler->GenerateAuthToken(
+ NULL, &request_info, callback.callback(), &token)));
#if defined(OS_WIN)
EXPECT_EQ("HTTP/alias", auth_handler->spn());
#elif defined(OS_POSIX)
@@ -259,8 +277,8 @@
TestCompletionCallback callback;
HttpRequestInfo request_info;
std::string token;
- EXPECT_EQ(OK, auth_handler->GenerateAuthToken(NULL, &request_info,
- callback.callback(), &token));
+ EXPECT_EQ(OK, callback.GetResult(auth_handler->GenerateAuthToken(
+ NULL, &request_info, callback.callback(), &token)));
#if defined(OS_WIN)
EXPECT_EQ("HTTP/alias:500", auth_handler->spn());
#elif defined(OS_POSIX)
@@ -277,8 +295,8 @@
TestCompletionCallback callback;
HttpRequestInfo request_info;
std::string token;
- EXPECT_EQ(OK, auth_handler->GenerateAuthToken(NULL, &request_info,
- callback.callback(), &token));
+ EXPECT_EQ(OK, callback.GetResult(auth_handler->GenerateAuthToken(
+ NULL, &request_info, callback.callback(), &token)));
#if defined(OS_WIN)
EXPECT_EQ("HTTP/canonical.example.com", auth_handler->spn());
#elif defined(OS_POSIX)
diff --git a/net/http/http_auth_handler_ntlm.cc b/net/http/http_auth_handler_ntlm.cc
index 51a32326..0bf7260 100644
--- a/net/http/http_auth_handler_ntlm.cc
+++ b/net/http/http_auth_handler_ntlm.cc
@@ -33,10 +33,8 @@
const AuthCredentials* credentials, const HttpRequestInfo* request,
const CompletionCallback& callback, std::string* auth_token) {
#if defined(NTLM_SSPI)
- return auth_sspi_.GenerateAuthToken(
- credentials,
- CreateSPN(origin_),
- auth_token);
+ return auth_sspi_.GenerateAuthToken(credentials, CreateSPN(origin_),
+ auth_token, callback);
#else // !defined(NTLM_SSPI)
// TODO(cbentzel): Shouldn't be hitting this case.
if (!credentials) {
diff --git a/net/http/http_auth_multi_round_parse.cc b/net/http/http_auth_multi_round_parse.cc
new file mode 100644
index 0000000..efee2bb
--- /dev/null
+++ b/net/http/http_auth_multi_round_parse.cc
@@ -0,0 +1,58 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/base64.h"
+#include "base/strings/string_util.h"
+#include "net/http/http_auth_challenge_tokenizer.h"
+#include "net/http/http_auth_multi_round_parse.h"
+
+namespace net {
+
+namespace {
+
+// Check that the scheme in the challenge matches the expected scheme
+bool SchemeIsValid(const std::string& scheme,
+ HttpAuthChallengeTokenizer* challenge) {
+ // There is no guarantee that challenge->scheme() is valid ASCII, but
+ // LowerCaseEqualsASCII will do the right thing even if it isn't.
+ return base::LowerCaseEqualsASCII(challenge->scheme(),
+ base::StringToLowerASCII(scheme).c_str());
+}
+
+} // namespace
+
+HttpAuth::AuthorizationResult ParseFirstRoundChallenge(
+ const std::string& scheme,
+ HttpAuthChallengeTokenizer* challenge) {
+ // Verify the challenge's auth-scheme.
+ if (!SchemeIsValid(scheme, challenge))
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+
+ std::string encoded_auth_token = challenge->base64_param();
+ if (!encoded_auth_token.empty()) {
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+ }
+ return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+}
+
+HttpAuth::AuthorizationResult ParseLaterRoundChallenge(
+ const std::string& scheme,
+ HttpAuthChallengeTokenizer* challenge,
+ std::string* encoded_token,
+ std::string* decoded_token) {
+ // Verify the challenge's auth-scheme.
+ if (!SchemeIsValid(scheme, challenge))
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+
+ *encoded_token = challenge->base64_param();
+ if (encoded_token->empty())
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT;
+
+ // Make sure the additional token is base64 encoded.
+ if (!base::Base64Decode(*encoded_token, decoded_token))
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+ return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+}
+
+} // namespace net
diff --git a/net/http/http_auth_multi_round_parse.h b/net/http/http_auth_multi_round_parse.h
new file mode 100644
index 0000000..2fb6347
--- /dev/null
+++ b/net/http/http_auth_multi_round_parse.h
@@ -0,0 +1,29 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_HTTP_HTTP_AUTH_MULTI_ROUND_PARSE_H_
+#define NET_HTTP_HTTP_AUTH_MULTI_ROUND_PARSE_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+#include "net/http/http_auth.h"
+
+namespace net {
+
+class HttpAuthChallengeTokenizer;
+
+NET_EXPORT_PRIVATE HttpAuth::AuthorizationResult ParseFirstRoundChallenge(
+ const std::string& scheme,
+ HttpAuthChallengeTokenizer* challenge);
+
+NET_EXPORT_PRIVATE HttpAuth::AuthorizationResult ParseLaterRoundChallenge(
+ const std::string& scheme,
+ HttpAuthChallengeTokenizer* challenge,
+ std::string* encoded_token,
+ std::string* decoded_token);
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_MULTI_ROUND_PARSE_H_
diff --git a/net/http/http_auth_multi_round_parse_unittest.cc b/net/http/http_auth_multi_round_parse_unittest.cc
new file mode 100644
index 0000000..1be5ad1
--- /dev/null
+++ b/net/http/http_auth_multi_round_parse_unittest.cc
@@ -0,0 +1,78 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/http/http_auth_challenge_tokenizer.h"
+#include "net/http/http_auth_multi_round_parse.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(HttpAuthHandlerNegotiateParseTest, ParseFirstRoundChallenge) {
+ // The first round should just consist of an unadorned header with the scheme
+ // name.
+ std::string challenge_text = "DummyScheme";
+ HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ ParseFirstRoundChallenge("dummyscheme", &challenge));
+}
+
+TEST(HttpAuthHandlerNegotiateParseTest,
+ ParseFirstNegotiateChallenge_UnexpectedToken) {
+ // If the first round challenge has an additional authentication token, it
+ // should be treated as an invalid challenge from the server.
+ std::string challenge_text = "Negotiate Zm9vYmFy";
+ HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
+ ParseFirstRoundChallenge("negotiate", &challenge));
+}
+
+TEST(HttpAuthHandlerNegotiateParseTest,
+ ParseFirstNegotiateChallenge_BadScheme) {
+ std::string challenge_text = "DummyScheme";
+ HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
+ ParseFirstRoundChallenge("negotiate", &challenge));
+}
+
+TEST(HttpAuthHandlerNegotiateParseTest, ParseLaterRoundChallenge) {
+ // Later rounds should always have a Base64 encoded token.
+ std::string challenge_text = "Negotiate Zm9vYmFy";
+ HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ std::string encoded_token;
+ std::string decoded_token;
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ ParseLaterRoundChallenge("negotiate", &challenge, &encoded_token,
+ &decoded_token));
+ EXPECT_EQ("Zm9vYmFy", encoded_token);
+ EXPECT_EQ("foobar", decoded_token);
+}
+
+TEST(HttpAuthHandlerNegotiateParseTest,
+ ParseAnotherNegotiateChallenge_MissingToken) {
+ std::string challenge_text = "Negotiate";
+ HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ std::string encoded_token;
+ std::string decoded_token;
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ ParseLaterRoundChallenge("negotiate", &challenge, &encoded_token,
+ &decoded_token));
+}
+
+TEST(HttpAuthHandlerNegotiateParseTest,
+ ParseAnotherNegotiateChallenge_InvalidToken) {
+ std::string challenge_text = "Negotiate ***";
+ HttpAuthChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ std::string encoded_token;
+ std::string decoded_token;
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
+ ParseLaterRoundChallenge("negotiate", &challenge, &encoded_token,
+ &decoded_token));
+}
+}
diff --git a/net/http/http_auth_sspi_win.cc b/net/http/http_auth_sspi_win.cc
index c935d33..f59ce4d 100644
--- a/net/http/http_auth_sspi_win.cc
+++ b/net/http/http_auth_sspi_win.cc
@@ -13,7 +13,7 @@
#include "base/strings/utf_string_conversions.h"
#include "net/base/net_errors.h"
#include "net/http/http_auth.h"
-#include "net/http/http_auth_challenge_tokenizer.h"
+#include "net/http/http_auth_multi_round_parse.h"
namespace net {
@@ -282,37 +282,18 @@
HttpAuth::AuthorizationResult HttpAuthSSPI::ParseChallenge(
HttpAuthChallengeTokenizer* tok) {
- // Verify the challenge's auth-scheme.
- if (!base::LowerCaseEqualsASCII(tok->scheme(),
- base::StringToLowerASCII(scheme_).c_str()))
- return HttpAuth::AUTHORIZATION_RESULT_INVALID;
-
- std::string encoded_auth_token = tok->base64_param();
- if (encoded_auth_token.empty()) {
- // If a context has already been established, an empty challenge
- // should be treated as a rejection of the current attempt.
- if (SecIsValidHandle(&ctxt_))
- return HttpAuth::AUTHORIZATION_RESULT_REJECT;
- DCHECK(decoded_server_auth_token_.empty());
- return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
- } else {
- // If a context has not already been established, additional tokens should
- // not be present in the auth challenge.
- if (!SecIsValidHandle(&ctxt_))
- return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+ if (!SecIsValidHandle(&ctxt_)) {
+ return net::ParseFirstRoundChallenge(scheme_, tok);
}
-
- std::string decoded_auth_token;
- bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token);
- if (!base64_rv)
- return HttpAuth::AUTHORIZATION_RESULT_INVALID;
- decoded_server_auth_token_ = decoded_auth_token;
- return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+ std::string encoded_auth_token;
+ return net::ParseLaterRoundChallenge(scheme_, tok, &encoded_auth_token,
+ &decoded_server_auth_token_);
}
int HttpAuthSSPI::GenerateAuthToken(const AuthCredentials* credentials,
const std::string& spn,
- std::string* auth_token) {
+ std::string* auth_token,
+ const CompletionCallback& /*callback*/) {
// Initial challenge.
if (!SecIsValidHandle(&cred_)) {
int rv = OnFirstRound(credentials);
diff --git a/net/http/http_auth_sspi_win.h b/net/http/http_auth_sspi_win.h
index befc1bf1..1d524fa1 100644
--- a/net/http/http_auth_sspi_win.h
+++ b/net/http/http_auth_sspi_win.h
@@ -17,6 +17,7 @@
#include <string>
#include "base/strings/string16.h"
+#include "net/base/completion_callback.h"
#include "net/base/net_export.h"
#include "net/http/http_auth.h"
@@ -120,19 +121,36 @@
HttpAuth::AuthorizationResult ParseChallenge(
HttpAuthChallengeTokenizer* tok);
- // Generates an authentication token for the service specified by the
- // Service Principal Name |spn| and stores the value in |*auth_token|.
- // If the return value is not |OK|, then the value of |*auth_token| is
- // unspecified. ERR_IO_PENDING is not a valid return code.
+ // Generates an authentication token.
+ //
+ // The return value is an error code. The authentication token will be
+ // returned in |*auth_token|. If the result code is not |OK|, the value of
+ // |*auth_token| is unspecified.
+ //
+ // If the operation cannot be completed synchronously, |ERR_IO_PENDING| will
+ // be returned and the real result code will be passed to the completion
+ // callback. Otherwise the result code is returned immediately from this
+ // call.
+ //
+ // If the HttpAuthSPPI object is deleted before completion then the callback
+ // will not be called.
+ //
+ // If no immediate result is returned then |auth_token| must remain valid
+ // until the callback has been called.
+ //
+ // |spn| is the Service Principal Name of the server that the token is
+ // being generated for.
+ //
// If this is the first round of a multiple round scheme, credentials are
- // obtained using |*credentials|. If |credentials| is NULL, the credentials
- // for the currently logged in user are used instead.
+ // obtained using |*credentials|. If |credentials| is NULL, the default
+ // credentials are used instead.
int GenerateAuthToken(const AuthCredentials* credentials,
const std::string& spn,
- std::string* auth_token);
+ std::string* auth_token,
+ const CompletionCallback& callback);
// Delegation is allowed on the Kerberos ticket. This allows certain servers
- // to act as the user, such as an IIS server retrieiving data from a
+ // to act as the user, such as an IIS server retrieving data from a
// Kerberized MSSQL server.
void Delegate();
diff --git a/net/http/http_auth_sspi_win_unittest.cc b/net/http/http_auth_sspi_win_unittest.cc
index 586822d2..feebaf0c 100644
--- a/net/http/http_auth_sspi_win_unittest.cc
+++ b/net/http/http_auth_sspi_win_unittest.cc
@@ -25,6 +25,12 @@
const ULONG kMaxTokenLength = 100;
+void UnexpectedCallback(int result) {
+ // At present getting tokens from gssapi is fully synchronous, so the callback
+ // should never be called.
+ ADD_FAILURE();
+}
+
} // namespace
TEST(HttpAuthSSPITest, SplitUserAndDomain) {
@@ -84,7 +90,8 @@
// Generate an auth token and create another thing.
std::string auth_token;
EXPECT_EQ(OK, auth_sspi.GenerateAuthToken(NULL, "HTTP/intranet.google.com",
- &auth_token));
+ &auth_token,
+ base::Bind(&UnexpectedCallback)));
std::string second_challenge_text = "Negotiate Zm9vYmFy";
HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
@@ -120,7 +127,8 @@
std::string auth_token;
EXPECT_EQ(OK, auth_sspi.GenerateAuthToken(NULL, "HTTP/intranet.google.com",
- &auth_token));
+ &auth_token,
+ base::Bind(&UnexpectedCallback)));
std::string second_challenge_text = "Negotiate";
HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
second_challenge_text.end());
@@ -142,7 +150,8 @@
std::string auth_token;
EXPECT_EQ(OK, auth_sspi.GenerateAuthToken(NULL, "HTTP/intranet.google.com",
- &auth_token));
+ &auth_token,
+ base::Bind(&UnexpectedCallback)));
std::string second_challenge_text = "Negotiate =happyjoy=";
HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
second_challenge_text.end());
diff --git a/net/http/http_auth_unittest.cc b/net/http/http_auth_unittest.cc
index 52e8294..a81a409 100644
--- a/net/http/http_auth_unittest.cc
+++ b/net/http/http_auth_unittest.cc
@@ -69,57 +69,56 @@
HttpAuth::Scheme challenge_scheme;
const char* challenge_realm;
} tests[] = {
- {
- // Basic is the only challenge type, pick it.
- "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
- "www-authenticate: Basic realm=\"BasicRealm\"\n",
+ {
+ // Basic is the only challenge type, pick it.
+ "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
+ "www-authenticate: Basic realm=\"BasicRealm\"\n",
- HttpAuth::AUTH_SCHEME_BASIC,
- "BasicRealm",
- },
- {
- // Fake is the only challenge type, but it is unsupported.
- "Y: Digest realm=\"FooBar\", nonce=\"aaaaaaaaaa\"\n"
- "www-authenticate: Fake realm=\"FooBar\"\n",
+ HttpAuth::AUTH_SCHEME_BASIC,
+ "BasicRealm",
+ },
+ {
+ // Fake is the only challenge type, but it is unsupported.
+ "Y: Digest realm=\"FooBar\", nonce=\"aaaaaaaaaa\"\n"
+ "www-authenticate: Fake realm=\"FooBar\"\n",
- HttpAuth::AUTH_SCHEME_MAX,
- "",
- },
- {
- // Pick Digest over Basic.
- "www-authenticate: Basic realm=\"FooBar\"\n"
- "www-authenticate: Fake realm=\"FooBar\"\n"
- "www-authenticate: nonce=\"aaaaaaaaaa\"\n"
- "www-authenticate: Digest realm=\"DigestRealm\", nonce=\"aaaaaaaaaa\"\n",
+ HttpAuth::AUTH_SCHEME_MAX,
+ "",
+ },
+ {
+ // Pick Digest over Basic.
+ "www-authenticate: Basic realm=\"FooBar\"\n"
+ "www-authenticate: Fake realm=\"FooBar\"\n"
+ "www-authenticate: nonce=\"aaaaaaaaaa\"\n"
+ "www-authenticate: Digest realm=\"DigestRealm\", nonce=\"aaaaaaaaaa\"\n",
- HttpAuth::AUTH_SCHEME_DIGEST,
- "DigestRealm",
- },
- {
- // Handle an empty header correctly.
- "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
- "www-authenticate:\n",
+ HttpAuth::AUTH_SCHEME_DIGEST,
+ "DigestRealm",
+ },
+ {
+ // Handle an empty header correctly.
+ "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
+ "www-authenticate:\n",
- HttpAuth::AUTH_SCHEME_MAX,
- "",
- },
- {
- "WWW-Authenticate: Negotiate\n"
- "WWW-Authenticate: NTLM\n",
+ HttpAuth::AUTH_SCHEME_MAX,
+ "",
+ },
+ {
+ "WWW-Authenticate: Negotiate\n"
+ "WWW-Authenticate: NTLM\n",
-#if defined(USE_KERBEROS)
- // Choose Negotiate over NTLM on all platforms.
- // TODO(ahendrickson): This may be flaky on Linux and OSX as it
- // relies on being able to load one of the known .so files
- // for gssapi.
- HttpAuth::AUTH_SCHEME_NEGOTIATE,
+#if defined(USE_KERBEROS) && !defined(OS_ANDROID)
+ // Choose Negotiate over NTLM on all platforms.
+ // TODO(ahendrickson): This may be flaky on Linux and OSX as it
+ // relies on being able to load one of the known .so files
+ // for gssapi.
+ HttpAuth::AUTH_SCHEME_NEGOTIATE,
#else
- // On systems that don't use Kerberos fall back to NTLM.
- HttpAuth::AUTH_SCHEME_NTLM,
+ // On systems that don't use Kerberos fall back to NTLM.
+ HttpAuth::AUTH_SCHEME_NTLM,
#endif // defined(USE_KERBEROS)
- "",
- }
- };
+ "",
+ }};
GURL origin("http://www.example.com");
std::set<HttpAuth::Scheme> disabled_schemes;
MockAllowURLSecurityManager url_security_manager;
diff --git a/net/net.gyp b/net/net.gyp
index b231499..a997712 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -10,11 +10,11 @@
'net_test_extra_libs': [],
'linux_link_kerberos%': 0,
'conditions': [
- ['chromeos==1 or embedded==1 or OS=="android" or OS=="ios"', {
- # Disable Kerberos on ChromeOS, Android and iOS, at least for now.
+ ['chromeos==1 or embedded==1 or OS=="ios"', {
+ # Disable Kerberos on ChromeOS and iOS, at least for now.
# It needs configuration (krb5.conf and so on).
'use_kerberos%': 0,
- }, { # chromeos == 0 and embedded==0 and OS!="android" and OS!="ios"
+ }, { # chromeos == 0 and embedded==0 and OS!="ios"
'use_kerberos%': 1,
}],
['OS=="android" and target_arch != "ia32"', {
@@ -222,14 +222,21 @@
'defines': [
'USE_KERBEROS',
],
- }, { # use_kerberos == 0
+ }],
+ [ 'use_kerberos==0 or OS == "android"', {
+ # These are excluded on Android, because the actual Kerberos support,
+ # which these test, is in a separate app on Android.
'sources!': [
'http/http_auth_gssapi_posix_unittest.cc',
- 'http/http_auth_handler_negotiate_unittest.cc',
'http/mock_gssapi_library_posix.cc',
'http/mock_gssapi_library_posix.h',
],
}],
+ [ 'use_kerberos==0', {
+ 'sources!': [
+ 'http/http_auth_handler_negotiate_unittest.cc',
+ ],
+ }],
[ 'use_openssl == 1 or (desktop_linux == 0 and chromeos == 0 and OS != "ios")', {
# Only include this test when on Posix and using NSS for
# cert verification or on iOS (which also uses NSS for certs).
@@ -1355,6 +1362,7 @@
'android/java/src/org/chromium/net/AndroidNetworkLibrary.java',
'android/java/src/org/chromium/net/AndroidPrivateKey.java',
'android/java/src/org/chromium/net/GURLUtils.java',
+ 'android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java',
'android/java/src/org/chromium/net/NetStringUtil.java',
'android/java/src/org/chromium/net/NetworkChangeNotifier.java',
'android/java/src/org/chromium/net/ProxyChangeListener.java',
@@ -1371,6 +1379,7 @@
'sources': [
'android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java',
'test/android/javatests/src/org/chromium/net/test/EmbeddedTestServer.java',
+ 'test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticator.java',
],
'variables': {
'jni_gen_package': 'net/test',
@@ -1418,6 +1427,7 @@
'net_test_support',
'url_request_failed_job_java',
'../base/base.gyp:base_java',
+ 'net_java',
'<@(net_test_extra_libs)',
],
'includes': [ '../build/java.gypi' ],
@@ -1493,6 +1503,7 @@
'dependencies': [
'net_java',
'net_javatests',
+ 'net_java_test_support',
'net_unittests',
],
'conditions': [
@@ -1514,6 +1525,8 @@
'variables': {
'test_suite_name': 'net_unittests',
'isolate_file': 'net_unittests.isolate',
+ 'android_manifest_path': 'android/unittest_support/AndroidManifest.xml',
+ 'resource_dir': 'android/unittest_support/res',
'conditions': [
['v8_use_external_startup_data==1', {
'asset_location': '<(PRODUCT_DIR)/net_unittests_apk/assets',
@@ -1530,6 +1543,26 @@
},
'includes': [ '../build/apk_test.gypi' ],
},
+ {
+ 'target_name': 'net_junit_tests',
+ 'type': 'none',
+ 'dependencies': [
+ 'net_java',
+ '../base/base.gyp:base',
+ '../base/base.gyp:base_java_test_support',
+ '../testing/android/junit/junit_test.gyp:junit_test_support',
+ ],
+ 'variables': {
+ 'main_class': 'org.chromium.testing.local.JunitTestMain',
+ 'src_paths': [
+ 'android/junit/',
+ ],
+ },
+ 'includes': [
+ '../build/host_jar.gypi',
+ ],
+ },
+
],
}],
['OS == "android" or OS == "linux"', {
diff --git a/net/net.gypi b/net/net.gypi
index 19f5e90..630be11 100644
--- a/net/net.gypi
+++ b/net/net.gypi
@@ -184,6 +184,8 @@
'android/cert_verify_result_android.h',
'android/gurl_utils.cc',
'android/gurl_utils.h',
+ 'android/http_auth_negotiate_android.cc',
+ 'android/http_auth_negotiate_android.h',
'android/keystore.cc',
'android/keystore.h',
'android/keystore_openssl.cc',
@@ -657,6 +659,8 @@
'http/http_auth_handler_ntlm.h',
'http/http_auth_handler_ntlm_portable.cc',
'http/http_auth_handler_ntlm_win.cc',
+ 'http/http_auth_multi_round_parse.cc',
+ 'http/http_auth_multi_round_parse.h',
'http/http_auth_sspi_win.cc',
'http/http_auth_sspi_win.h',
'http/http_basic_state.cc',
@@ -1289,6 +1293,9 @@
'extras/sqlite/sqlite_persistent_cookie_store.h',
],
'net_test_sources': [
+ 'android/dummy_spnego_authenticator.cc',
+ 'android/dummy_spnego_authenticator.h',
+ 'android/http_auth_negotiate_android_unittest.cc',
'android/keystore_unittest.cc',
'android/network_change_notifier_android_unittest.cc',
'base/address_list_unittest.cc',
@@ -1439,6 +1446,7 @@
'http/http_auth_handler_mock.h',
'http/http_auth_handler_negotiate_unittest.cc',
'http/http_auth_handler_unittest.cc',
+ 'http/http_auth_multi_round_parse_unittest.cc',
'http/http_auth_sspi_win_unittest.cc',
'http/http_auth_unittest.cc',
'http/http_basic_state_unittest.cc',
diff --git a/net/net_common.gypi b/net/net_common.gypi
index 7a888a8a..18d4f03 100644
--- a/net/net_common.gypi
+++ b/net/net_common.gypi
@@ -390,6 +390,8 @@
'cert/cert_database_openssl.cc',
'cert/cert_verify_proc_openssl.cc',
'cert/test_root_certs_openssl.cc',
+ 'http/http_auth_gssapi_posix.cc',
+ 'http/http_auth_gssapi_posix.h',
],
},
],
diff --git a/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticator.java b/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticator.java
new file mode 100644
index 0000000..1c8b0c0
--- /dev/null
+++ b/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticator.java
@@ -0,0 +1,186 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.net.test;
+
+import android.accounts.AbstractAccountAuthenticator;
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorException;
+import android.accounts.NetworkErrorException;
+import android.accounts.OperationCanceledException;
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.base.NativeClassQualifiedName;
+import org.chromium.net.HttpNegotiateConstants;
+
+import java.io.IOException;
+
+/**
+ * Dummy Android authenticator, to test SPNEGO/Keberos support on Android. This is deliberately
+ * minimal, and is not intended as an example of how to write a real SPNEGO Authenticator.
+ */
+@JNINamespace("net::android")
+public class DummySpnegoAuthenticator extends AbstractAccountAuthenticator {
+ private static final String ACCOUNT_TYPE = "org.chromium.test.DummySpnegoAuthenticator";
+ private static final String ACCOUNT_NAME = "DummySpnegoAccount";
+ private static int sResult;
+ private static String sToken;
+ private static boolean sCheckArguments;
+ private static long sNativeDummySpnegoAuthenticator;
+ private static final int GSS_S_COMPLETE = 0;
+ private static final int GSS_S_CONTINUE_NEEDED = 1;
+ private static final int GSS_S_FAILURE = 2;
+
+ /**
+ * @param context
+ */
+ public DummySpnegoAuthenticator(Context context) {
+ super(context);
+ }
+
+ @Override
+ public Bundle addAccount(AccountAuthenticatorResponse arg0, String accountType, String arg2,
+ String[] arg3, Bundle arg4) throws NetworkErrorException {
+ Bundle result = new Bundle();
+ result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_REQUEST);
+ result.putString(AccountManager.KEY_ERROR_MESSAGE, "Can't add new SPNEGO accounts");
+ return result;
+ }
+
+ @Override
+ public Bundle confirmCredentials(AccountAuthenticatorResponse arg0, Account arg1, Bundle arg2)
+ throws NetworkErrorException {
+ Bundle result = new Bundle();
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
+ return result;
+ }
+
+ @Override
+ public Bundle editProperties(AccountAuthenticatorResponse arg0, String arg1) {
+ return new Bundle();
+ }
+
+ @Override
+ public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
+ String authTokenType, Bundle options) throws NetworkErrorException {
+ long nativeQuery = nativeGetNextQuery(sNativeDummySpnegoAuthenticator);
+ String incomingToken = options.getString(HttpNegotiateConstants.KEY_INCOMING_AUTH_TOKEN);
+ nativeCheckGetTokenArguments(nativeQuery, incomingToken);
+ Bundle result = new Bundle();
+ result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+ result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+ result.putString(AccountManager.KEY_AUTHTOKEN, nativeGetTokenToReturn(nativeQuery));
+ result.putInt(HttpNegotiateConstants.KEY_SPNEGO_RESULT,
+ decodeResult(nativeGetResult(nativeQuery)));
+ return result;
+ }
+
+ /**
+ * @param nativeGetResult
+ * @return
+ */
+ private int decodeResult(int gssApiResult) {
+ // This only handles the result values currently used in the tests.
+ switch (gssApiResult) {
+ case GSS_S_COMPLETE:
+ case GSS_S_CONTINUE_NEEDED:
+ return 0;
+ case GSS_S_FAILURE:
+ return HttpNegotiateConstants.ERR_MISSING_AUTH_CREDENTIALS;
+ default:
+ return HttpNegotiateConstants.ERR_UNEXPECTED;
+ }
+ }
+
+ @Override
+ public String getAuthTokenLabel(String arg0) {
+ return "Spnego " + arg0;
+ }
+
+ @Override
+ public Bundle hasFeatures(AccountAuthenticatorResponse arg0, Account arg1, String[] features)
+ throws NetworkErrorException {
+ Bundle result = new Bundle();
+ for (String feature : features) {
+ if (!feature.equals(HttpNegotiateConstants.SPNEGO_FEATURE)) {
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+ return result;
+ }
+ }
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
+ return result;
+ }
+
+ @Override
+ public Bundle updateCredentials(AccountAuthenticatorResponse arg0, Account arg1, String arg2,
+ Bundle arg3) throws NetworkErrorException {
+ Bundle result = new Bundle();
+ result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_REQUEST);
+ result.putString(AccountManager.KEY_ERROR_MESSAGE, "Can't add new SPNEGO accounts");
+ return result;
+ }
+
+ /**
+ * Called from tests, sets up the test account, if it doesn't already exist
+ */
+ @CalledByNative
+ private static void ensureTestAccountExists() {
+ Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
+ AccountManager am = AccountManager.get(activity);
+ Account account = new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
+ am.addAccountExplicitly(account, null, null);
+ }
+
+ /**
+ * Called from tests to tidy up test accounts.
+ */
+ @SuppressWarnings("deprecation")
+ @CalledByNative
+ private static void removeTestAccounts() {
+ Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
+ AccountManager am = AccountManager.get(activity);
+ String features[] = {HttpNegotiateConstants.SPNEGO_FEATURE};
+ try {
+ Account accounts[] =
+ am.getAccountsByTypeAndFeatures(ACCOUNT_TYPE, features, null, null).getResult();
+ for (Account account : accounts) {
+ // Deprecated, but the replacement not available on Android JB.
+ am.removeAccount(account, null, null).getResult();
+ }
+ } catch (OperationCanceledException | AuthenticatorException | IOException e) {
+ // Should never happen. This is tidy-up after the tests. Ignore.
+ }
+ }
+
+ @CalledByNative
+ private static void setNativeAuthenticator(long nativeDummySpnegoAuthenticator) {
+ sNativeDummySpnegoAuthenticator = nativeDummySpnegoAuthenticator;
+ }
+
+ /**
+ * Send the relevant decoded arguments of getAuthToken to C++ for checking by googletest checks
+ * If the checks fail then the C++ unit test using this authenticator will fail.
+ *
+ * @param authTokenType
+ * @param spn
+ * @param incomingToken
+ */
+ @NativeClassQualifiedName("DummySpnegoAuthenticator::SecurityContextQuery")
+ private native void nativeCheckGetTokenArguments(long nativeQuery, String incomingToken);
+
+ @NativeClassQualifiedName("DummySpnegoAuthenticator::SecurityContextQuery")
+ private native String nativeGetTokenToReturn(long nativeQuery);
+
+ @NativeClassQualifiedName("DummySpnegoAuthenticator::SecurityContextQuery")
+ private native int nativeGetResult(long nativeQuery);
+
+ private native long nativeGetNextQuery(long nativeDummySpnegoAuthenticator);
+}
diff --git a/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticatorService.java b/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticatorService.java
new file mode 100644
index 0000000..cc732a6
--- /dev/null
+++ b/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticatorService.java
@@ -0,0 +1,22 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.net.test;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Authenticator service for testing SPNEGO (Kerberos) support.
+ */
+public class DummySpnegoAuthenticatorService extends Service {
+ private static DummySpnegoAuthenticator sAuthenticator = null;
+
+ @Override
+ public IBinder onBind(Intent arg0) {
+ if (sAuthenticator == null) sAuthenticator = new DummySpnegoAuthenticator(this);
+ return sAuthenticator.getIBinder();
+ }
+}
diff --git a/net/test/run_all_unittests.cc b/net/test/run_all_unittests.cc
index 25016ca..7da7991 100644
--- a/net/test/run_all_unittests.cc
+++ b/net/test/run_all_unittests.cc
@@ -15,6 +15,8 @@
#include "base/android/jni_android.h"
#include "base/android/jni_registrar.h"
#include "base/test/test_file_util.h"
+#include "base/test/test_ui_thread_android.h"
+#include "net/android/dummy_spnego_authenticator.h"
#include "net/android/net_jni_registrar.h"
#include "url/android/url_jni_registrar.h"
#endif
@@ -32,9 +34,12 @@
#if defined(OS_ANDROID)
const base::android::RegistrationMethod kNetTestRegisteredMethods[] = {
- {"NetAndroid", net::android::RegisterJni},
- {"TestFileUtil", base::RegisterContentUriTestUtils},
- {"UrlAndroid", url::android::RegisterJni},
+ {"DummySpnegoAuthenticator",
+ net::android::DummySpnegoAuthenticator::RegisterJni},
+ {"NetAndroid", net::android::RegisterJni},
+ {"TestFileUtil", base::RegisterContentUriTestUtils},
+ {"TestUiThreadAndroid", base::RegisterTestUiThreadAndroid},
+ {"UrlAndroid", url::android::RegisterJni},
};
// Register JNI bindings for android. Doing it early as the test suite setup