// Copyright (c) 2012 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 "chromeos/network/network_connection_handler.h" #include "base/bind.h" #include "base/callback.h" #include "base/file_util.h" #include "base/json/json_reader.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/strings/stringprintf.h" #include "chromeos/cert_loader.h" #include "chromeos/dbus/fake_dbus_thread_manager.h" #include "chromeos/dbus/shill_device_client.h" #include "chromeos/dbus/shill_manager_client.h" #include "chromeos/dbus/shill_profile_client.h" #include "chromeos/dbus/shill_service_client.h" #include "chromeos/network/managed_network_configuration_handler_impl.h" #include "chromeos/network/network_configuration_handler.h" #include "chromeos/network/network_profile_handler.h" #include "chromeos/network/network_state_handler.h" #include "chromeos/network/onc/onc_utils.h" #include "chromeos/tpm_token_loader.h" #include "components/onc/onc_constants.h" #include "crypto/nss_util.h" #include "crypto/nss_util_internal.h" #include "net/base/net_errors.h" #include "net/base/test_data_directory.h" #include "net/cert/nss_cert_database_chromeos.h" #include "net/cert/x509_certificate.h" #include "net/test/cert_test_util.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/cros_system_api/dbus/service_constants.h" namespace { const char* kSuccessResult = "success"; void ConfigureCallback(const dbus::ObjectPath& result) { } void ConfigureErrorCallback(const std::string& error_name, const std::string& error_message) { } } // namespace namespace chromeos { class NetworkConnectionHandlerTest : public testing::Test { public: NetworkConnectionHandlerTest() : user_("userhash"), test_manager_client_(NULL), test_service_client_(NULL) {} virtual ~NetworkConnectionHandlerTest() { } virtual void SetUp() OVERRIDE { ASSERT_TRUE(user_.constructed_successfully()); user_.FinishInit(); test_nssdb_.reset(new net::NSSCertDatabaseChromeOS( crypto::GetPublicSlotForChromeOSUser(user_.username_hash()), crypto::GetPrivateSlotForChromeOSUser( user_.username_hash(), base::Callback()))); test_nssdb_->SetSlowTaskRunnerForTest(message_loop_.message_loop_proxy()); TPMTokenLoader::InitializeForTest(); CertLoader::Initialize(); CertLoader* cert_loader = CertLoader::Get(); cert_loader->force_hardware_backed_for_test(); FakeDBusThreadManager* dbus_manager = new FakeDBusThreadManager; dbus_manager->SetFakeClients(); DBusThreadManager::InitializeForTesting(dbus_manager); test_manager_client_ = dbus_manager->GetShillManagerClient()->GetTestInterface(); test_service_client_ = dbus_manager->GetShillServiceClient()->GetTestInterface(); test_manager_client_->AddTechnology(shill::kTypeWifi, true /* enabled */); dbus_manager->GetShillDeviceClient()->GetTestInterface()->AddDevice( "/device/wifi1", shill::kTypeWifi, "wifi_device1"); test_manager_client_->AddTechnology(shill::kTypeCellular, true /* enabled */); dbus_manager->GetShillProfileClient()->GetTestInterface()->AddProfile( "profile_path", std::string() /* shared profile */); base::RunLoop().RunUntilIdle(); LoginState::Initialize(); network_state_handler_.reset(NetworkStateHandler::InitializeForTest()); network_config_handler_.reset( NetworkConfigurationHandler::InitializeForTest( network_state_handler_.get())); network_profile_handler_.reset(new NetworkProfileHandler()); network_profile_handler_->Init(); managed_config_handler_.reset(new ManagedNetworkConfigurationHandlerImpl()); managed_config_handler_->Init(network_state_handler_.get(), network_profile_handler_.get(), network_config_handler_.get(), NULL /* network_device_handler */); network_connection_handler_.reset(new NetworkConnectionHandler); network_connection_handler_->Init(network_state_handler_.get(), network_config_handler_.get(), managed_config_handler_.get()); base::RunLoop().RunUntilIdle(); } virtual void TearDown() OVERRIDE { managed_config_handler_.reset(); network_profile_handler_.reset(); network_connection_handler_.reset(); network_config_handler_.reset(); network_state_handler_.reset(); CertLoader::Shutdown(); TPMTokenLoader::Shutdown(); LoginState::Shutdown(); DBusThreadManager::Shutdown(); } protected: bool Configure(const std::string& json_string) { scoped_ptr json_dict = onc::ReadDictionaryFromJson(json_string); if (!json_dict) { LOG(ERROR) << "Error parsing json: " << json_string; return false; } DBusThreadManager::Get()->GetShillManagerClient()->ConfigureService( *json_dict, base::Bind(&ConfigureCallback), base::Bind(&ConfigureErrorCallback)); base::RunLoop().RunUntilIdle(); return true; } void Connect(const std::string& service_path) { const bool check_error_state = true; network_connection_handler_->ConnectToNetwork( service_path, base::Bind(&NetworkConnectionHandlerTest::SuccessCallback, base::Unretained(this)), base::Bind(&NetworkConnectionHandlerTest::ErrorCallback, base::Unretained(this)), check_error_state); base::RunLoop().RunUntilIdle(); } void Disconnect(const std::string& service_path) { network_connection_handler_->DisconnectNetwork( service_path, base::Bind(&NetworkConnectionHandlerTest::SuccessCallback, base::Unretained(this)), base::Bind(&NetworkConnectionHandlerTest::ErrorCallback, base::Unretained(this))); base::RunLoop().RunUntilIdle(); } void SuccessCallback() { result_ = kSuccessResult; } void ErrorCallback(const std::string& error_name, scoped_ptr error_data) { result_ = error_name; } std::string GetResultAndReset() { std::string result; result.swap(result_); return result; } std::string GetServiceStringProperty(const std::string& service_path, const std::string& key) { std::string result; const base::DictionaryValue* properties = test_service_client_->GetServiceProperties(service_path); if (properties) properties->GetStringWithoutPathExpansion(key, &result); return result; } void StartCertLoader() { CertLoader::Get()->StartWithNSSDB(test_nssdb_.get()); base::RunLoop().RunUntilIdle(); } void LoginToRegularUser() { LoginState::Get()->SetLoggedInState(LoginState::LOGGED_IN_ACTIVE, LoginState::LOGGED_IN_USER_REGULAR); base::RunLoop().RunUntilIdle(); } void ImportClientCertAndKey(const std::string& pkcs12_file, net::NSSCertDatabase* nssdb, net::CertificateList* loaded_certs) { std::string pkcs12_data; base::FilePath pkcs12_path = net::GetTestCertsDirectory().Append(pkcs12_file); ASSERT_TRUE(base::ReadFileToString(pkcs12_path, &pkcs12_data)); scoped_refptr module( net::CryptoModule::CreateFromHandle(nssdb->GetPrivateSlot().get())); ASSERT_EQ( net::OK, nssdb->ImportFromPKCS12(module, pkcs12_data, base::string16(), false, loaded_certs)); ASSERT_EQ(1U, loaded_certs->size()); } void SetupPolicy() { const char* kNetworkConfigs = "[ { \"GUID\": \"wifi1\"," " \"Name\": \"wifi1\"," " \"Type\": \"WiFi\"," " \"WiFi\": {" " \"Security\": \"WPA-PSK\"," " \"SSID\": \"wifi1\"," " \"Passphrase\": \"passphrase\"" " }" "} ]"; std::string error; scoped_ptr network_configs_value( base::JSONReader::ReadAndReturnError( kNetworkConfigs, base::JSON_ALLOW_TRAILING_COMMAS, NULL, &error)); ASSERT_TRUE(network_configs_value) << error; base::ListValue* network_configs = NULL; ASSERT_TRUE(network_configs_value->GetAsList(&network_configs)); base::DictionaryValue global_config; global_config.SetBooleanWithoutPathExpansion( ::onc::global_network_config::kAllowOnlyPolicyNetworksToAutoconnect, true); managed_config_handler_->SetPolicy(::onc::ONC_SOURCE_USER_POLICY, "", // userhash *network_configs, global_config); base::RunLoop().RunUntilIdle(); } scoped_ptr network_state_handler_; scoped_ptr network_config_handler_; scoped_ptr network_connection_handler_; scoped_ptr managed_config_handler_; scoped_ptr network_profile_handler_; crypto::ScopedTestNSSChromeOSUser user_; ShillManagerClient::TestInterface* test_manager_client_; ShillServiceClient::TestInterface* test_service_client_; scoped_ptr test_nssdb_; base::MessageLoopForUI message_loop_; std::string result_; private: DISALLOW_COPY_AND_ASSIGN(NetworkConnectionHandlerTest); }; namespace { const char* kConfigConnectable = "{ \"GUID\": \"wifi0\", \"Type\": \"wifi\", \"State\": \"idle\", " " \"Connectable\": true }"; const char* kConfigConnected = "{ \"GUID\": \"wifi1\", \"Type\": \"wifi\", \"State\": \"online\" }"; const char* kConfigConnecting = "{ \"GUID\": \"wifi2\", \"Type\": \"wifi\", \"State\": \"association\" }"; const char* kConfigRequiresPassphrase = "{ \"GUID\": \"wifi3\", \"Type\": \"wifi\", " " \"PassphraseRequired\": true }"; const char* kConfigRequiresActivation = "{ \"GUID\": \"cellular1\", \"Type\": \"cellular\"," " \"Cellular.ActivationState\": \"not-activated\" }"; } // namespace TEST_F(NetworkConnectionHandlerTest, NetworkConnectionHandlerConnectSuccess) { EXPECT_TRUE(Configure(kConfigConnectable)); Connect("wifi0"); EXPECT_EQ(kSuccessResult, GetResultAndReset()); EXPECT_EQ(shill::kStateOnline, GetServiceStringProperty("wifi0", shill::kStateProperty)); } // Handles basic failure cases. TEST_F(NetworkConnectionHandlerTest, NetworkConnectionHandlerConnectFailure) { Connect("no-network"); EXPECT_EQ(NetworkConnectionHandler::kErrorConfigureFailed, GetResultAndReset()); EXPECT_TRUE(Configure(kConfigConnected)); Connect("wifi1"); EXPECT_EQ(NetworkConnectionHandler::kErrorConnected, GetResultAndReset()); EXPECT_TRUE(Configure(kConfigConnecting)); Connect("wifi2"); EXPECT_EQ(NetworkConnectionHandler::kErrorConnecting, GetResultAndReset()); EXPECT_TRUE(Configure(kConfigRequiresPassphrase)); Connect("wifi3"); EXPECT_EQ(NetworkConnectionHandler::kErrorPassphraseRequired, GetResultAndReset()); EXPECT_TRUE(Configure(kConfigRequiresActivation)); Connect("cellular1"); EXPECT_EQ(NetworkConnectionHandler::kErrorActivationRequired, GetResultAndReset()); } namespace { const char* kConfigRequiresCertificateTemplate = "{ \"GUID\": \"wifi4\", \"Type\": \"wifi\", \"Connectable\": false," " \"Security\": \"802_1x\"," " \"UIData\": \"{" " \\\"certificate_type\\\": \\\"pattern\\\"," " \\\"certificate_pattern\\\": {" " \\\"Subject\\\": {\\\"CommonName\\\": \\\"%s\\\" }" " } }\" }"; } // namespace // Handle certificates. TEST_F(NetworkConnectionHandlerTest, ConnectCertificateMissing) { StartCertLoader(); EXPECT_TRUE(Configure( base::StringPrintf(kConfigRequiresCertificateTemplate, "unknown"))); Connect("wifi4"); EXPECT_EQ(NetworkConnectionHandler::kErrorCertificateRequired, GetResultAndReset()); } TEST_F(NetworkConnectionHandlerTest, ConnectWithCertificateSuccess) { StartCertLoader(); net::CertificateList certs; ImportClientCertAndKey("websocket_client_cert.p12", test_nssdb_.get(), &certs); EXPECT_TRUE(Configure( base::StringPrintf(kConfigRequiresCertificateTemplate, certs[0]->subject().common_name.c_str()))); Connect("wifi4"); EXPECT_EQ(kSuccessResult, GetResultAndReset()); } TEST_F(NetworkConnectionHandlerTest, ConnectWithCertificateRequestedBeforeCertsAreLoaded) { net::CertificateList certs; ImportClientCertAndKey("websocket_client_cert.p12", test_nssdb_.get(), &certs); EXPECT_TRUE(Configure( base::StringPrintf(kConfigRequiresCertificateTemplate, certs[0]->subject().common_name.c_str()))); Connect("wifi4"); // Connect request came before the cert loader loaded certificates, so the // connect request should have been throttled until the certificates are // loaded. EXPECT_EQ("", GetResultAndReset()); StartCertLoader(); // |StartCertLoader| should have triggered certificate loading. // When the certificates got loaded, the connection request should have // proceeded and eventually succeeded. EXPECT_EQ(kSuccessResult, GetResultAndReset()); } TEST_F(NetworkConnectionHandlerTest, NetworkConnectionHandlerDisconnectSuccess) { EXPECT_TRUE(Configure(kConfigConnected)); Disconnect("wifi1"); EXPECT_EQ(kSuccessResult, GetResultAndReset()); } TEST_F(NetworkConnectionHandlerTest, NetworkConnectionHandlerDisconnectFailure) { Connect("no-network"); EXPECT_EQ(NetworkConnectionHandler::kErrorConfigureFailed, GetResultAndReset()); EXPECT_TRUE(Configure(kConfigConnectable)); Disconnect("wifi0"); EXPECT_EQ(NetworkConnectionHandler::kErrorNotConnected, GetResultAndReset()); } namespace { const char* kConfigUnmanagedSharedConnected = "{ \"GUID\": \"wifi0\", \"Type\": \"wifi\", \"State\": \"online\" }"; const char* kConfigManagedSharedConnectable = "{ \"GUID\": \"wifi1\", \"Type\": \"wifi\", \"State\": \"idle\", " " \"Connectable\": true }"; } // namespace TEST_F(NetworkConnectionHandlerTest, ReconnectOnLoginEarlyPolicyLoading) { EXPECT_TRUE(Configure(kConfigUnmanagedSharedConnected)); EXPECT_TRUE(Configure(kConfigManagedSharedConnectable)); test_manager_client_->SetBestServiceToConnect("wifi1"); // User login shouldn't trigger any change because policy is not loaded yet. LoginToRegularUser(); EXPECT_EQ(shill::kStateOnline, GetServiceStringProperty("wifi0", shill::kStateProperty)); EXPECT_EQ(shill::kStateIdle, GetServiceStringProperty("wifi1", shill::kStateProperty)); // Policy application should disconnect from the shared and unmanaged network. SetupPolicy(); EXPECT_EQ(shill::kStateIdle, GetServiceStringProperty("wifi0", shill::kStateProperty)); EXPECT_EQ(shill::kStateIdle, GetServiceStringProperty("wifi1", shill::kStateProperty)); // Certificate loading should trigger connecting to the 'best' network. StartCertLoader(); EXPECT_EQ(shill::kStateIdle, GetServiceStringProperty("wifi0", shill::kStateProperty)); EXPECT_EQ(shill::kStateOnline, GetServiceStringProperty("wifi1", shill::kStateProperty)); } TEST_F(NetworkConnectionHandlerTest, ReconnectOnLoginLatePolicyLoading) { EXPECT_TRUE(Configure(kConfigUnmanagedSharedConnected)); EXPECT_TRUE(Configure(kConfigManagedSharedConnectable)); test_manager_client_->SetBestServiceToConnect("wifi1"); // User login and certificate loading shouldn't trigger any change until the // policy is loaded. LoginToRegularUser(); StartCertLoader(); EXPECT_EQ(shill::kStateOnline, GetServiceStringProperty("wifi0", shill::kStateProperty)); EXPECT_EQ(shill::kStateIdle, GetServiceStringProperty("wifi1", shill::kStateProperty)); SetupPolicy(); EXPECT_EQ(shill::kStateIdle, GetServiceStringProperty("wifi0", shill::kStateProperty)); EXPECT_EQ(shill::kStateOnline, GetServiceStringProperty("wifi1", shill::kStateProperty)); } } // namespace chromeos