blob: 25b739dabf9fa4c78216176cb80d13601f3c87b9 [file] [log] [blame]
// Copyright 2018 The Chromium OS 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 "shill/vpn/vpn_driver.h"
#include <vector>
#include <base/stl_util.h>
#include <base/strings/string_number_conversions.h>
#include <chromeos/dbus/service_constants.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "shill/fake_store.h"
#include "shill/mock_connection.h"
#include "shill/mock_control.h"
#include "shill/mock_device_info.h"
#include "shill/mock_manager.h"
#include "shill/mock_metrics.h"
#include "shill/mock_service.h"
#include "shill/property_store.h"
#include "shill/test_event_dispatcher.h"
using std::string;
using std::vector;
using testing::_;
using testing::NiceMock;
using testing::Return;
using testing::Test;
namespace shill {
namespace {
const char kVPNHostProperty[] = "VPN.Host";
const char kOTPProperty[] = "VPN.OTP";
const char kPinProperty[] = "VPN.PIN";
const char kPSKProperty[] = "VPN.PSK";
const char kPasswordProperty[] = "VPN.Password";
const char kPortProperty[] = "VPN.Port";
const char kPin[] = "5555";
const char kPassword[] = "random-password";
const char kPort[] = "1234";
const char kStorageID[] = "vpn_service_id";
} // namespace
class VPNDriverUnderTest : public VPNDriver {
public:
explicit VPNDriverUnderTest(Manager* manager);
VPNDriverUnderTest(const VPNDriverUnderTest&) = delete;
VPNDriverUnderTest& operator=(const VPNDriverUnderTest&) = delete;
~VPNDriverUnderTest() override = default;
// Inherited from VPNDriver.
MOCK_METHOD(void,
ConnectAsync,
(const VPNService::DriverEventCallback& callback),
(override));
MOCK_METHOD(void, Disconnect, (), (override));
MOCK_METHOD(IPConfig::Properties, GetIPProperties, (), (const, override));
MOCK_METHOD(string, GetProviderType, (), (const, override));
MOCK_METHOD(IfType, GetIfType, (), (const, override));
private:
static const Property kProperties[];
};
// static
const VPNDriverUnderTest::Property VPNDriverUnderTest::kProperties[] = {
{kEapCaCertPemProperty, Property::kArray},
{kVPNHostProperty, 0},
{kL2tpIpsecCaCertPemProperty, Property::kArray},
{kOTPProperty, Property::kEphemeral},
{kPinProperty, Property::kWriteOnly},
{kPSKProperty, Property::kCredential},
{kPasswordProperty, Property::kCredential},
{kPortProperty, 0},
{kProviderTypeProperty, 0},
};
VPNDriverUnderTest::VPNDriverUnderTest(Manager* manager)
: VPNDriver(manager, nullptr, kProperties, base::size(kProperties)) {}
class VPNDriverTest : public Test {
public:
VPNDriverTest()
: manager_(&control_, &dispatcher_, &metrics_),
device_info_(&manager_),
driver_(&manager_) {}
~VPNDriverTest() override = default;
protected:
const base::CancelableClosure& connect_timeout_callback() const {
return driver_.connect_timeout_callback_;
}
bool IsConnectTimeoutStarted() { return driver_.IsConnectTimeoutStarted(); }
int connect_timeout_seconds() const {
return driver_.connect_timeout_seconds();
}
string credential_prefix() const { return VPNDriver::kCredentialPrefix; }
void StartConnectTimeout(int timeout_seconds) {
driver_.StartConnectTimeout(timeout_seconds);
}
void StopConnectTimeout() { driver_.StopConnectTimeout(); }
void SetArg(const string& arg, const string& value) {
driver_.args()->Set<string>(arg, value);
}
void SetArgArray(const string& arg, const vector<string>& value) {
driver_.args()->Set<Strings>(arg, value);
}
KeyValueStore* GetArgs() { return driver_.args(); }
bool GetProviderPropertyString(const PropertyStore& store,
const string& key,
string* value);
bool GetProviderPropertyStrings(const PropertyStore& store,
const string& key,
vector<string>* value);
MockControl control_;
EventDispatcherForTest dispatcher_;
MockMetrics metrics_;
MockManager manager_;
NiceMock<MockDeviceInfo> device_info_;
VPNDriverUnderTest driver_;
};
bool VPNDriverTest::GetProviderPropertyString(const PropertyStore& store,
const string& key,
string* value) {
KeyValueStore provider_properties;
Error error;
EXPECT_TRUE(store.GetKeyValueStoreProperty(kProviderProperty,
&provider_properties, &error));
if (!provider_properties.Contains<string>(key)) {
return false;
}
if (value != nullptr) {
*value = provider_properties.Get<string>(key);
}
return true;
}
bool VPNDriverTest::GetProviderPropertyStrings(const PropertyStore& store,
const string& key,
vector<string>* value) {
KeyValueStore provider_properties;
Error error;
EXPECT_TRUE(store.GetKeyValueStoreProperty(kProviderProperty,
&provider_properties, &error));
if (!provider_properties.Contains<Strings>(key)) {
return false;
}
if (value != nullptr) {
*value = provider_properties.Get<Strings>(key);
}
return true;
}
TEST_F(VPNDriverTest, Load) {
FakeStore storage;
GetArgs()->Set<string>(kVPNHostProperty, "1.2.3.4");
GetArgs()->Set<string>(kPSKProperty, "1234");
GetArgs()->Set<Strings>(kL2tpIpsecCaCertPemProperty,
vector<string>{"cleared-cert0", "cleared-cert1"});
vector<string> kCaCerts{"cert0", "cert1"};
storage.SetStringList(kStorageID, kEapCaCertPemProperty, kCaCerts);
storage.SetString(kStorageID, kPortProperty, kPort);
storage.SetString(kStorageID, kPinProperty, kPin);
storage.SetString(kStorageID, credential_prefix() + kPasswordProperty,
kPassword);
EXPECT_TRUE(driver_.Load(&storage, kStorageID));
EXPECT_EQ(kCaCerts, GetArgs()->Get<Strings>(kEapCaCertPemProperty));
EXPECT_EQ(kPort, GetArgs()->Lookup<string>(kPortProperty, ""));
EXPECT_EQ(kPin, GetArgs()->Lookup<string>(kPinProperty, ""));
EXPECT_EQ(kPassword, GetArgs()->Lookup<string>(kPasswordProperty, ""));
// Properties missing from the persistent store should be deleted.
EXPECT_FALSE(GetArgs()->Contains<string>(kVPNHostProperty));
EXPECT_FALSE(GetArgs()->Contains<Strings>(kL2tpIpsecCaCertPemProperty));
EXPECT_FALSE(GetArgs()->Contains<string>(kPSKProperty));
}
TEST_F(VPNDriverTest, Save) {
SetArg(kProviderTypeProperty, kProviderOpenVpn);
SetArg(kPinProperty, kPin);
SetArg(kPortProperty, kPort);
SetArg(kPasswordProperty, kPassword);
SetArg(kOTPProperty, "987654");
const vector<string> kCaCerts{"cert0", "cert1"};
SetArgArray(kEapCaCertPemProperty, kCaCerts);
FakeStore storage;
EXPECT_TRUE(driver_.Save(&storage, kStorageID, true));
vector<string> ca_pem;
string provider_type, port, pin, password;
EXPECT_TRUE(
storage.GetStringList(kStorageID, kEapCaCertPemProperty, &ca_pem));
EXPECT_EQ(ca_pem, kCaCerts);
EXPECT_TRUE(
storage.GetString(kStorageID, kProviderTypeProperty, &provider_type));
EXPECT_EQ(provider_type, kProviderOpenVpn);
EXPECT_TRUE(storage.GetString(kStorageID, kPortProperty, &port));
EXPECT_EQ(port, kPort);
EXPECT_TRUE(storage.GetString(kStorageID, kPinProperty, &pin));
EXPECT_EQ(pin, kPin);
EXPECT_TRUE(storage.GetString(
kStorageID, credential_prefix() + kPasswordProperty, &password));
EXPECT_EQ(password, kPassword);
EXPECT_FALSE(storage.GetString(kStorageID, credential_prefix() + kPSKProperty,
nullptr));
}
TEST_F(VPNDriverTest, SaveNoCredentials) {
SetArg(kPasswordProperty, kPassword);
SetArg(kPSKProperty, "");
FakeStore storage;
EXPECT_TRUE(driver_.Save(&storage, kStorageID, false));
EXPECT_FALSE(storage.GetString(
kStorageID, credential_prefix() + kPasswordProperty, nullptr));
EXPECT_FALSE(storage.GetString(kStorageID, credential_prefix() + kPSKProperty,
nullptr));
EXPECT_FALSE(storage.GetString(kStorageID, kEapCaCertPemProperty, nullptr));
EXPECT_FALSE(
storage.GetString(kStorageID, kL2tpIpsecCaCertPemProperty, nullptr));
}
TEST_F(VPNDriverTest, UnloadCredentials) {
SetArg(kOTPProperty, "654321");
SetArg(kPasswordProperty, kPassword);
SetArg(kPortProperty, kPort);
driver_.UnloadCredentials();
EXPECT_FALSE(GetArgs()->Contains<string>(kOTPProperty));
EXPECT_FALSE(GetArgs()->Contains<string>(kPasswordProperty));
EXPECT_EQ(kPort, GetArgs()->Lookup<string>(kPortProperty, ""));
}
TEST_F(VPNDriverTest, InitPropertyStore) {
// Figure out if the store is actually hooked up to the driver argument
// KeyValueStore.
PropertyStore store;
driver_.InitPropertyStore(&store);
// An un-set property should not be readable.
{
Error error;
EXPECT_FALSE(store.GetStringProperty(kPortProperty, nullptr, &error));
EXPECT_EQ(Error::kInvalidArguments, error.type());
}
{
Error error;
EXPECT_FALSE(
store.GetStringsProperty(kEapCaCertPemProperty, nullptr, &error));
EXPECT_EQ(Error::kInvalidArguments, error.type());
}
EXPECT_FALSE(GetProviderPropertyString(store, kPortProperty, nullptr));
EXPECT_FALSE(
GetProviderPropertyStrings(store, kEapCaCertPemProperty, nullptr));
const string kProviderType = "boo";
SetArg(kPortProperty, kPort);
SetArg(kPasswordProperty, kPassword);
SetArg(kProviderTypeProperty, kProviderType);
SetArg(kVPNHostProperty, "");
const vector<string> kCaCerts{"cert1"};
SetArgArray(kEapCaCertPemProperty, kCaCerts);
SetArgArray(kL2tpIpsecCaCertPemProperty, vector<string>());
// We should not be able to read a property out of the driver args using the
// key to the args directly.
{
Error error;
EXPECT_FALSE(store.GetStringProperty(kPortProperty, nullptr, &error));
EXPECT_EQ(Error::kInvalidArguments, error.type());
}
{
Error error;
EXPECT_FALSE(
store.GetStringsProperty(kEapCaCertPemProperty, nullptr, &error));
EXPECT_EQ(Error::kInvalidArguments, error.type());
}
// We should instead be able to find it within the "Provider" stringmap.
{
string value;
EXPECT_TRUE(GetProviderPropertyString(store, kPortProperty, &value));
EXPECT_EQ(kPort, value);
}
{
vector<string> value;
EXPECT_TRUE(
GetProviderPropertyStrings(store, kEapCaCertPemProperty, &value));
EXPECT_EQ(kCaCerts, value);
}
// We should be able to read empty properties from the "Provider" stringmap.
{
string value;
EXPECT_TRUE(GetProviderPropertyString(store, kVPNHostProperty, &value));
EXPECT_TRUE(value.empty());
}
{
vector<string> value;
EXPECT_TRUE(
GetProviderPropertyStrings(store, kL2tpIpsecCaCertPemProperty, &value));
EXPECT_TRUE(value.empty());
}
// Properties that start with the prefix "Provider." should be mapped to the
// name in the Properties dict with the prefix removed.
{
string value;
EXPECT_TRUE(GetProviderPropertyString(store, kTypeProperty, &value));
EXPECT_EQ(kProviderType, value);
}
// If we clear a property, we should no longer be able to find it.
{
Error error;
EXPECT_TRUE(store.ClearProperty(kPortProperty, &error));
EXPECT_TRUE(error.IsSuccess());
EXPECT_FALSE(GetProviderPropertyString(store, kPortProperty, nullptr));
}
{
Error error;
EXPECT_TRUE(store.ClearProperty(kEapCaCertPemProperty, &error));
EXPECT_TRUE(error.IsSuccess());
EXPECT_FALSE(
GetProviderPropertyStrings(store, kEapCaCertPemProperty, nullptr));
}
// A second attempt to clear this property should return an error.
{
Error error;
EXPECT_FALSE(store.ClearProperty(kPortProperty, &error));
EXPECT_EQ(Error::kNotFound, error.type());
}
{
Error error;
EXPECT_FALSE(store.ClearProperty(kEapCaCertPemProperty, &error));
EXPECT_EQ(Error::kNotFound, error.type());
}
// Test write only properties.
EXPECT_FALSE(GetProviderPropertyString(store, kPinProperty, nullptr));
// Write properties to the driver args using the PropertyStore interface.
{
const string kValue = "some-value";
Error error;
EXPECT_TRUE(store.SetStringProperty(kPinProperty, kValue, &error));
EXPECT_EQ(kValue, GetArgs()->Get<string>(kPinProperty));
}
{
const vector<string> kValue{"some-value"};
Error error;
EXPECT_TRUE(
store.SetStringsProperty(kEapCaCertPemProperty, kValue, &error));
EXPECT_EQ(kValue, GetArgs()->Get<Strings>(kEapCaCertPemProperty));
}
}
TEST_F(VPNDriverTest, ConnectTimeout) {
EXPECT_TRUE(connect_timeout_callback().IsCancelled());
EXPECT_FALSE(IsConnectTimeoutStarted());
StartConnectTimeout(0);
EXPECT_EQ(0, connect_timeout_seconds());
EXPECT_FALSE(connect_timeout_callback().IsCancelled());
EXPECT_TRUE(IsConnectTimeoutStarted());
StartConnectTimeout(10); // This should take no effect.
EXPECT_EQ(0, connect_timeout_seconds());
dispatcher_.DispatchPendingEvents();
EXPECT_TRUE(connect_timeout_callback().IsCancelled());
EXPECT_FALSE(IsConnectTimeoutStarted());
}
TEST_F(VPNDriverTest, StartStopConnectTimeout) {
EXPECT_FALSE(IsConnectTimeoutStarted());
EXPECT_EQ(0, connect_timeout_seconds());
const int kTimeout = 123;
StartConnectTimeout(kTimeout);
EXPECT_TRUE(IsConnectTimeoutStarted());
EXPECT_EQ(kTimeout, connect_timeout_seconds());
StartConnectTimeout(kTimeout - 20);
EXPECT_EQ(kTimeout, connect_timeout_seconds());
StopConnectTimeout();
EXPECT_FALSE(IsConnectTimeoutStarted());
EXPECT_EQ(0, connect_timeout_seconds());
}
} // namespace shill