shill: vpn: Load and save OpenVPN service properties.

BUG=chromium-os:27650
TEST=unit tests

Change-Id: If620f34ecbf82be018b546c290e3fa1baa6fc001
Reviewed-on: https://gerrit.chromium.org/gerrit/18650
Tested-by: Darin Petkov <petkov@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Commit-Ready: Darin Petkov <petkov@chromium.org>
diff --git a/mock_vpn_driver.h b/mock_vpn_driver.h
index 08492a9..58b398e 100644
--- a/mock_vpn_driver.h
+++ b/mock_vpn_driver.h
@@ -20,6 +20,10 @@
                                     int interface_index));
   MOCK_METHOD2(Connect, void(const VPNServiceRefPtr &service, Error *error));
   MOCK_METHOD0(Disconnect, void());
+  MOCK_METHOD2(Load, bool(StoreInterface *storage,
+                          const std::string &storage_id));
+  MOCK_METHOD2(Save, bool(StoreInterface *storage,
+                          const std::string &storage_id));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockVPNDriver);
diff --git a/openvpn_driver.cc b/openvpn_driver.cc
index af9ace2..4e31029 100644
--- a/openvpn_driver.cc
+++ b/openvpn_driver.cc
@@ -17,6 +17,7 @@
 #include "shill/error.h"
 #include "shill/manager.h"
 #include "shill/rpc_task.h"
+#include "shill/store_interface.h"
 #include "shill/vpn.h"
 #include "shill/vpn_service.h"
 
@@ -36,6 +37,61 @@
 const char kOpenVPNRouteVPNGateway[] = "route_vpn_gateway";
 const char kOpenVPNTrustedIP[] = "trusted_ip";
 const char kOpenVPNTunMTU[] = "tun_mtu";
+
+// TODO(petkov): Move to chromeos/dbus/service_constants.h.
+const char kOpenVPNCertProperty[] = "OpenVPN.Cert";
+const char kOpenVPNKeyProperty[] = "OpenVPN.Key";
+const char kOpenVPNPingProperty[] = "OpenVPN.Ping";
+const char kOpenVPNPingExitProperty[] = "OpenVPN.PingExit";
+const char kOpenVPNPingRestartProperty[] = "OpenVPN.PingRestart";
+const char kOpenVPNTLSAuthProperty[] = "OpenVPN.TLSAuth";
+const char kOpenVPNVerbProperty[] = "OpenVPN.Verb";
+const char kVPNMTUProperty[] = "VPN.MTU";
+
+const struct {
+  const char *property;
+  bool crypted;
+} kProperties[] = {
+  { flimflam::kOpenVPNAuthNoCacheProperty, false },
+  { flimflam::kOpenVPNAuthProperty, false },
+  { flimflam::kOpenVPNAuthRetryProperty, false },
+  { flimflam::kOpenVPNAuthUserPassProperty, false },
+  { flimflam::kOpenVPNCaCertNSSProperty, false },
+  { flimflam::kOpenVPNCaCertProperty, false },
+  { flimflam::kOpenVPNCipherProperty, false },
+  { flimflam::kOpenVPNCompLZOProperty, false },
+  { flimflam::kOpenVPNCompNoAdaptProperty, false },
+  { flimflam::kOpenVPNKeyDirectionProperty, false },
+  { flimflam::kOpenVPNNsCertTypeProperty, false },
+  { flimflam::kOpenVPNPasswordProperty, true },
+  { flimflam::kOpenVPNPinProperty, false },
+  { flimflam::kOpenVPNPortProperty, false },
+  { flimflam::kOpenVPNProtoProperty, false },
+  { flimflam::kOpenVPNProviderProperty, false },
+  { flimflam::kOpenVPNPushPeerInfoProperty, false },
+  { flimflam::kOpenVPNRemoteCertEKUProperty, false },
+  { flimflam::kOpenVPNRemoteCertKUProperty, false },
+  { flimflam::kOpenVPNRemoteCertTLSProperty, false },
+  { flimflam::kOpenVPNRenegSecProperty, false },
+  { flimflam::kOpenVPNServerPollTimeoutProperty, false },
+  { flimflam::kOpenVPNShaperProperty, false },
+  { flimflam::kOpenVPNStaticChallengeProperty, false },
+  { flimflam::kOpenVPNTLSAuthContentsProperty, false },
+  { flimflam::kOpenVPNTLSRemoteProperty, false },
+  { flimflam::kOpenVPNUserProperty, false },
+  { flimflam::kProviderHostProperty, false },
+  { flimflam::kProviderNameProperty, false },
+  { flimflam::kProviderTypeProperty, false },
+  { kOpenVPNCertProperty, false },
+  { kOpenVPNKeyProperty, false },
+  { kOpenVPNPingExitProperty, false },
+  { kOpenVPNPingProperty, false },
+  { kOpenVPNPingRestartProperty, false },
+  { kOpenVPNTLSAuthProperty, false },
+  { kOpenVPNVerbProperty, false },
+  { kVPNMTUProperty, false },
+  { NULL, false },
+};
 }  // namespace
 
 // static
@@ -335,12 +391,12 @@
   options->push_back("--syslog");
 
   // TODO(petkov): Enable verbosity based on shill logging options too.
-  AppendValueOption("OpenVPN.Verb", "--verb", options);
+  AppendValueOption(kOpenVPNVerbProperty, "--verb", options);
 
-  AppendValueOption("VPN.MTU", "--mtu", options);
+  AppendValueOption(kVPNMTUProperty, "--mtu", options);
   AppendValueOption(flimflam::kOpenVPNProtoProperty, "--proto", options);
   AppendValueOption(flimflam::kOpenVPNPortProperty, "--port", options);
-  AppendValueOption("OpenVPN.TLSAuth", "--tls-auth", options);
+  AppendValueOption(kOpenVPNTLSAuthProperty, "--tls-auth", options);
 
   // TODO(petkov): Implement this.
   LOG_IF(ERROR, args_.ContainsString(flimflam::kOpenVPNTLSAuthContentsProperty))
@@ -367,15 +423,15 @@
       << "Support for NSS CA not implemented yet.";
 
   // Client-side ping support.
-  AppendValueOption("OpenVPN.Ping", "--ping", options);
-  AppendValueOption("OpenVPN.PingExit", "--ping-exit", options);
-  AppendValueOption("OpenVPN.PingRestart", "--ping-restart", options);
+  AppendValueOption(kOpenVPNPingProperty, "--ping", options);
+  AppendValueOption(kOpenVPNPingExitProperty, "--ping-exit", options);
+  AppendValueOption(kOpenVPNPingRestartProperty, "--ping-restart", options);
 
   AppendValueOption(flimflam::kOpenVPNCaCertProperty, "--ca", options);
-  AppendValueOption("OpenVPN.Cert", "--cert", options);
+  AppendValueOption(kOpenVPNCertProperty, "--cert", options);
   AppendValueOption(
       flimflam::kOpenVPNNsCertTypeProperty, "--ns-cert-type", options);
-  AppendValueOption("OpenVPN.Key", "--key", options);
+  AppendValueOption(kOpenVPNKeyProperty, "--key", options);
 
   // TODO(petkov): Implement this.
   LOG_IF(ERROR, args_.ContainsString(flimflam::kOpenVPNClientCertIdProperty))
@@ -477,4 +533,33 @@
   Cleanup(Service::kStateIdle);
 }
 
+bool OpenVPNDriver::Load(StoreInterface *storage, const string &storage_id) {
+  for (int i = 0; kProperties[i].property; i++) {
+    const string property = kProperties[i].property;
+    string value;
+    bool loaded = kProperties[i].crypted ?
+        storage->GetCryptedString(storage_id, property, &value) :
+        storage->GetString(storage_id, property, &value);
+    if (loaded) {
+      args_.SetString(property, value);
+    }
+  }
+  return true;
+}
+
+bool OpenVPNDriver::Save(StoreInterface *storage, const string &storage_id) {
+  for (int i = 0; kProperties[i].property; i++) {
+    const string property = kProperties[i].property;
+    const string value = args_.LookupString(property, "");
+    if (value.empty()) {
+      storage->DeleteKey(storage_id, property);
+    } else if (kProperties[i].crypted) {
+      storage->SetCryptedString(storage_id, property, value);
+    } else {
+      storage->SetString(storage_id, property, value);
+    }
+  }
+  return true;
+}
+
 }  // namespace shill
diff --git a/openvpn_driver.h b/openvpn_driver.h
index 86b1899..374258b 100644
--- a/openvpn_driver.h
+++ b/openvpn_driver.h
@@ -52,6 +52,9 @@
                               int interface_index);
   virtual void Disconnect();
 
+  virtual bool Load(StoreInterface *storage, const std::string &storage_id);
+  virtual bool Save(StoreInterface *storage, const std::string &storage_id);
+
  private:
   friend class OpenVPNDriverTest;
   FRIEND_TEST(OpenVPNDriverTest, AppendFlag);
diff --git a/openvpn_driver_unittest.cc b/openvpn_driver_unittest.cc
index 77ebeaf..e4ede64 100644
--- a/openvpn_driver_unittest.cc
+++ b/openvpn_driver_unittest.cc
@@ -21,6 +21,7 @@
 #include "shill/mock_manager.h"
 #include "shill/mock_metrics.h"
 #include "shill/mock_service.h"
+#include "shill/mock_store.h"
 #include "shill/mock_vpn.h"
 #include "shill/mock_vpn_service.h"
 #include "shill/nice_mock_control.h"
@@ -32,7 +33,9 @@
 using std::string;
 using std::vector;
 using testing::_;
+using testing::AnyNumber;
 using testing::DoAll;
+using testing::Ne;
 using testing::NiceMock;
 using testing::Return;
 using testing::SetArgumentPointee;
@@ -82,6 +85,10 @@
     driver_->args_ = args_;
   }
 
+  KeyValueStore *GetArgs() {
+    return &driver_->args_;
+  }
+
   // Used to assert that a flag appears in the options.
   void ExpectInFlags(const vector<string> &options, const string &flag,
                      const string &value);
@@ -518,4 +525,47 @@
   EXPECT_TRUE(file_util::PathExists(FilePath(SYSROOT).Append(vpn_script)));
 }
 
+TEST_F(OpenVPNDriverTest, Load) {
+  MockStore storage;
+  static const char kStorageID[] = "vpn_service_id";
+  const string port = "1234";
+  const string password = "random-password";
+  EXPECT_CALL(storage, GetString(kStorageID, _, _))
+      .WillRepeatedly(Return(false));
+  EXPECT_CALL(storage, GetString(kStorageID, flimflam::kOpenVPNPortProperty, _))
+      .WillOnce(DoAll(SetArgumentPointee<2>(port), Return(true)));
+  EXPECT_CALL(storage, GetCryptedString(kStorageID,
+                                        flimflam::kOpenVPNPasswordProperty,
+                                        _))
+      .WillOnce(DoAll(SetArgumentPointee<2>(password), Return(true)));
+  EXPECT_TRUE(driver_->Load(&storage, kStorageID));
+  EXPECT_EQ(port,
+            GetArgs()->LookupString(flimflam::kOpenVPNPortProperty, ""));
+  EXPECT_EQ(password,
+            GetArgs()->LookupString(flimflam::kOpenVPNPasswordProperty, ""));
+}
+
+TEST_F(OpenVPNDriverTest, Store) {
+  const string key_direction = "1";
+  const string password = "foobar";
+  args_.SetString(flimflam::kOpenVPNKeyDirectionProperty, key_direction);
+  args_.SetString(flimflam::kOpenVPNPasswordProperty, password);
+  SetArgs();
+  MockStore storage;
+  static const char kStorageID[] = "vpn_service_id";
+  EXPECT_CALL(storage,
+              SetString(kStorageID,
+                        flimflam::kOpenVPNKeyDirectionProperty,
+                        key_direction))
+      .WillOnce(Return(true));
+  EXPECT_CALL(storage, SetCryptedString(kStorageID,
+                                        flimflam::kOpenVPNPasswordProperty,
+                                        password))
+      .WillOnce(Return(true));
+  EXPECT_CALL(storage, DeleteKey(kStorageID, _)).Times(AnyNumber());
+  EXPECT_CALL(storage, DeleteKey(kStorageID, flimflam::kOpenVPNAuthProperty))
+      .Times(1);
+  EXPECT_TRUE(driver_->Save(&storage, kStorageID));
+}
+
 }  // namespace shill
diff --git a/vpn_driver.h b/vpn_driver.h
index b829951..1f26fe6 100644
--- a/vpn_driver.h
+++ b/vpn_driver.h
@@ -14,6 +14,7 @@
 namespace shill {
 
 class Error;
+class StoreInterface;
 
 class VPNDriver {
  public:
@@ -23,6 +24,8 @@
                               int interface_index) = 0;
   virtual void Connect(const VPNServiceRefPtr &service, Error *error) = 0;
   virtual void Disconnect() = 0;
+  virtual bool Load(StoreInterface *storage, const std::string &storage_id) = 0;
+  virtual bool Save(StoreInterface *storage, const std::string &storage_id) = 0;
 };
 
 }  // namespace shill
diff --git a/vpn_service.cc b/vpn_service.cc
index 6264cb8..e2583fe 100644
--- a/vpn_service.cc
+++ b/vpn_service.cc
@@ -70,4 +70,14 @@
   return "/";
 }
 
+bool VPNService::Load(StoreInterface *storage) {
+  return Service::Load(storage) &&
+      driver_->Load(storage, GetStorageIdentifier());
+}
+
+bool VPNService::Save(StoreInterface *storage) {
+  return Service::Save(storage) &&
+      driver_->Save(storage, GetStorageIdentifier());
+}
+
 }  // namespace shill
diff --git a/vpn_service.h b/vpn_service.h
index a004cc7..203357f 100644
--- a/vpn_service.h
+++ b/vpn_service.h
@@ -28,6 +28,8 @@
   virtual void Connect(Error *error);
   virtual void Disconnect(Error *error);
   virtual std::string GetStorageIdentifier() const;
+  virtual bool Load(StoreInterface *storage);
+  virtual bool Save(StoreInterface *storage);
 
   VPNDriver *driver() const { return driver_.get(); }
 
diff --git a/vpn_service_unittest.cc b/vpn_service_unittest.cc
index 10a90b4..9e603f1 100644
--- a/vpn_service_unittest.cc
+++ b/vpn_service_unittest.cc
@@ -11,9 +11,12 @@
 #include "shill/nice_mock_control.h"
 #include "shill/mock_adaptors.h"
 #include "shill/mock_metrics.h"
+#include "shill/mock_store.h"
 #include "shill/mock_vpn_driver.h"
 
 using testing::_;
+using testing::NiceMock;
+using testing::Return;
 
 namespace shill {
 
@@ -84,4 +87,21 @@
   EXPECT_EQ(Error::kNotSupported, error.type());
 }
 
+TEST_F(VPNServiceTest, Load) {
+  NiceMock<MockStore> storage;
+  static const char kStorageID[] = "storage-id";
+  service_->set_storage_id(kStorageID);
+  EXPECT_CALL(storage, ContainsGroup(kStorageID)).WillOnce(Return(true));
+  EXPECT_CALL(*driver_, Load(&storage, kStorageID)).WillOnce(Return(true));
+  EXPECT_TRUE(service_->Load(&storage));
+}
+
+TEST_F(VPNServiceTest, Save) {
+  NiceMock<MockStore> storage;
+  static const char kStorageID[] = "storage-id";
+  service_->set_storage_id(kStorageID);
+  EXPECT_CALL(*driver_, Save(&storage, kStorageID)).WillOnce(Return(true));
+  EXPECT_TRUE(service_->Save(&storage));
+}
+
 }  // namespace shill