spigots: Add basic OpenVPN support

Change-Id: Ic81b42104e7c579ad742ea689d640033933d9262
diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 5488779..dad9803 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -249,6 +249,9 @@
   "networkconfigurations": {
     "message": "Network Configurations"
   },
+  "openvpnType": {
+    "message": "OpenVPN"
+  },
   "password": {
     "message": "Password"
   },
diff --git a/main.html b/main.html
index c42a844..988cb0a 100644
--- a/main.html
+++ b/main.html
@@ -457,30 +457,31 @@
   <select id="vpn-type">
     <option i18n="l2tpIpsecPsk" value="L2TP-IPsec-PSK"></option>
     <option i18n="l2tpIpsecCert" value="L2TP-IPsec-Cert"></option>
+    <option i18n="openvpnType" value="OpenVPN"></option>
   </select>
-  <div id="l2tpipsec-psk-div">
+  <div id="vpn-psk-div">
     <h2 i18n="presharedKey"></h2>
     <p class="help" i18n="helpPresharedKey"></p>
-    <input type="password" id="ipsec-psk"></input>
+    <input type="password" id="vpn-psk"></input>
   </div>
-  <div id="l2tpipsec-cert-div">
+  <div id="vpn-cert-div">
     <h2 i18n="serverCertificateAuthority"></h2>
     <p class="help" i18n="helpServerCertificateAuthority"></p>
-    <select id="ipsec-server-ca">
+    <select id="vpn-server-ca">
     </select>
     <h2 i18n="clientCertificateAuthority"></h2>
     <p class="help" i18n="helpClientCertificateAuthority"></p>
-    <select id="ipsec-client-ca">
+    <select id="vpn-client-ca">
     </select>
     <h2 i18n="enrollmentUri"></h2>
     <p class="help" i18n="helpEnrollmentUri"></p>
-    <input id="ipsec-enrollment-uri" type="text"></input>
+    <input id="vpn-enrollment-uri" type="text"></input>
   </div>
   <div class="checkable">
-    <input type="checkbox" id="l2tp-save-credentials"></input>
+    <input type="checkbox" id="vpn-save-credentials"></input>
     <span i18n="specifyUsernameAndPassword"></span>
   </div>
-  <div id="l2tp-cred">
+  <div id="vpn-user-cred">
     <h2 i18n="username"></h2>
     <p class="help" i18n="helpUsername"></p>
     <input id="vpn-username" type="text"></input>
diff --git a/main.js b/main.js
index 53dd28a..b0a380b 100644
--- a/main.js
+++ b/main.js
@@ -284,16 +284,16 @@
 vpnDialog.setUiVisibility = function() {
   var setting = $('#vpn-type').val();
   if (setting == 'L2TP-IPsec-PSK') {
-    $('#l2tpipsec-psk-div').show();
-    $('#l2tpipsec-cert-div').hide();
-  } else {
-    $('#l2tpipsec-psk-div').hide();
-    $('#l2tpipsec-cert-div').show();
+    $('#vpn-psk-div').show();
+    $('#vpn-cert-div').hide();
+  } else if (setting == 'L2TP-IPsec-Cert' || setting == 'OpenVPN') {
+    $('#vpn-psk-div').hide();
+    $('#vpn-cert-div').show();
   }
-  if ($('#l2tp-save-credentials').is(':checked'))
-    $('#l2tp-cred').show();
+  if ($('#vpn-save-credentials').is(':checked'))
+    $('#vpn-user-cred').show();
   else
-    $('#l2tp-cred').hide();
+    $('#vpn-user-cred').hide();
 };
 
 /**
@@ -444,12 +444,41 @@
   return network;
 };
 
+vpnDialog.getUserCredentialsFromUi = function(container) {
+  var saveCredentials = $('#vpn-save-credentials').is(':checked');
+  container.SaveCredentials = saveCredentials;
+  if (saveCredentials) {
+    container.Username = $('#vpn-username').val();
+    container.Password = $('#vpn-password').val();
+  } else {
+    delete container.Username;
+    delete container.Password;
+  }
+}
+
+vpnDialog.getCertsFromUi = function(container) {
+  var serverCa = $('#vpn-server-ca').val();
+  if (serverCa != 'empty') {
+    container.ServerCARef = serverCa;
+  }
+  container.ClientCertType = 'Pattern';
+  var clientCa = $('#vpn-client-ca').val();
+  onc.setUpAssocArray(container, 'ClientCertPattern');
+  if (clientCa != 'empty') {
+    container.ClientCertPattern.IssuerCARef = clientCa;
+  }
+  if ($('#vpn-enrollment-uri').val()) {
+    container.ClientCertPattern.EnrollmentUri = $('#vpn-enrollment-uri').val();
+  } else {
+    delete container.ClientCertPattern.EnrollmentUri;
+  }
+}
+
 /**
  * Validate and convert the VPN configuration to ONC.
- * @param {Object} result  Object containing any errors or warnings
  * @result {Object}      ONC NetworkConfiguration object for VPN.
  */
-vpnDialog.getFromUi = function(result) {
+vpnDialog.getFromUi = function() {
   var network = {};
   if (vpnDialog.oncBase)
     network = vpnDialog.oncBase;
@@ -458,50 +487,36 @@
   network.Type = 'VPN';
   onc.setUpAssocArray(network, 'VPN');
   network.VPN.Host = $('#vpn-host').val();
-  onc.setUpAssocArray(network.VPN, 'IPsec');
-  network.VPN.IPsec.IKEVersion = 1;
-  onc.setUpAssocArray(network.VPN, 'L2TP');
-  var saveCredentials = $('#l2tp-save-credentials').is(':checked');
-  network.VPN.L2TP.SaveCredentials = saveCredentials;
-  network.VPN.Type = 'L2TP-IPsec';
+  var vpnType = $('#vpn-type').val();
+  if (vpnType == 'L2TP-IPsec-PSK' || vpnType == 'L2TP-IPsec-Cert') {
+    network.VPN.Type = 'L2TP-IPsec';
+    onc.setUpAssocArray(network.VPN, 'IPsec');
+    network.VPN.IPsec.IKEVersion = 1;
+    onc.setUpAssocArray(network.VPN, 'L2TP');
+    vpnDialog.getUserCredentialsFromUi(network.VPN.L2TP);
+  } else {
+    network.VPN.Type = 'OpenVPN';
+    onc.setUpAssocArray(network.VPN, 'OpenVPN');
+    vpnDialog.getUserCredentialsFromUi(network.VPN.OpenVPN);
+  }
   if ($('#vpn-proxy-url').val())
     network.ProxyURL = $('#vpn-proxy-url').val();
   else
     delete network.ProxyURL;
-  if (saveCredentials) {
-    network.VPN.L2TP.Username = $('#vpn-username').val();
-    network.VPN.L2TP.Password = $('#vpn-password').val();
-  } else {
-    delete network.VPN.L2TP.Username;
-    delete network.VPN.L2TP.Password;
-  }
-  if ($('#vpn-type').val() == 'L2TP-IPsec-Cert') {
-    network.VPN.IPsec.AuthenticationType = 'Cert';
-    var serverCa = $('#ipsec-server-ca').val();
-    if (serverCa != 'empty') {
-      network.VPN.IPsec.ServerCARef = serverCa;
-    }
-    network.VPN.IPsec.ClientCertType = 'Pattern';
-    var clientCa = $('#ipsec-client-ca').val();
-    onc.setUpAssocArray(network.VPN.IPsec, 'ClientCertPattern');
-    if (clientCa != 'empty') {
-      network.VPN.IPsec.ClientCertPattern.IssuerCARef = clientCa;
-    }
-    if ($('#ipsec-enrollment-uri').val()) {
-      network.VPN.IPsec.ClientCertPattern.EnrollmentUri =
-        $('#ipsec-enrollment-uri').val();
-    } else {
-      delete network.VPN.IPsec.ClientCertPattern.EnrollmentUri;
-    }
+  if (vpnType == 'L2TP-IPsec-Cert') {
+    container.AuthenticationType = 'Cert';
+    vpnDialog.getCertsFromUi(network.VPN.IPsec);
     delete network.VPN.IPsec.PSK;
-  } else if ($('#vpn-type').val() == 'L2TP-IPsec-PSK') {
+  } else if (vpnType == 'L2TP-IPsec-PSK') {
     network.VPN.IPsec.AuthenticationType = 'PSK';
-    if ($('#ipsec-psk').val())
-      network.VPN.IPsec.PSK = $('#ipsec-psk').val();
+    if ($('#vpn-psk').val())
+      network.VPN.IPsec.PSK = $('#vpn-psk').val();
     else
       delete network.VPN.IPsec.PSK;
     delete network.VPN.IPsec.ServerCARef;
     delete network.VPN.IPsec.ClientCertPattern;
+  } else if ($('#vpn-type').val() == 'OpenVPN') {
+    vpnDialog.getCertsFromUi(network.VPN.OpenVPN);
   }
   return network;
 };
@@ -1108,17 +1123,17 @@
 vpnDialog.init = function() {
   $('#vpn-name').val('');
   $('#vpn-host').val('');
-  $('#ipsec-psk').val('');
-  $('#l2tp-save-credentials')[0].checked = false;
+  $('#vpn-psk').val('');
+  $('#vpn-save-credentials')[0].checked = false;
   $('#vpn-username').val('');
   $('#vpn-password').val('');
-  $('#ipsec-enrollment-uri').val('');
+  $('#vpn-enrollment-uri').val('');
   $('#vpn-guid').val(main.createGuid());
-  ui.updateCertificateDropdown($('#ipsec-server-ca')[0], true);
-  ui.updateCertificateDropdown($('#ipsec-client-ca')[0], true);
+  ui.updateCertificateDropdown($('#vpn-server-ca')[0], true);
+  ui.updateCertificateDropdown($('#vpn-client-ca')[0], true);
   $('#apply-button', '#vpn-dialog').click(vpnDialog.onApplyPress);
   $('#vpn-type').change(vpnDialog.setUiVisibility);
-  $('#l2tp-save-credentials').change(vpnDialog.setUiVisibility);
+  $('#vpn-save-credentials').change(vpnDialog.setUiVisibility);
   vpnDialog.setUiVisibility();
   $('#vpn-name').focus();
 };
@@ -1214,6 +1229,36 @@
 };
 
 /**
+ * Set up the UI with an object from ONC that has certificate information.
+ * @param {Object} netconfig  VPN ONC object
+ **/
+vpnDialog.setCertToUi = function(vpnConfig) {
+  if (onc.findCert(vpnConfig.ServerCARef, main.oncCurrent) >= 0)
+    $('#vpn-server-ca').val(vpnConfig.ServerCARef);
+  if (vpnConfig.ClientCertType == 'Pattern') {
+    if (onc.findCert(vpnConfig.ClientCertPattern.IssuerCARef,
+                     main.oncCurrent) >= 0) {
+      $('#vpn-client-ca').val(vpnConfig.ClientCertPattern.IssuerCARef);
+    }
+    if ('EnrollmentUri' in vpnConfig.ClientCertPattern)
+      $('#vpn-enrollment-uri').val(vpnConfig.ClientCertPattern.EnrollmentUri);
+  }
+}
+
+/**
+ * Set up the UI with an object from ONC that has user credentials.
+ * @param {Object} netconfig  VPN ONC object
+ **/
+vpnDialog.setUserCredentialsToUi = function(vpnConfig) {
+  if ('Username' in vpnConfig)
+    $('#vpn-username').val(vpnConfig.Username);
+  if ('Password' in vpnConfig)
+    $('#vpn-password').val(vpnConfig.Password);
+  if ('SaveCredentials' in vpnConfig && vpnConfig.SaveCredentials)
+    $('#vpn-save-credentials')[0].checked = true;
+}
+
+/**
  * Set up the UI with given VPN ONC configuration.
  * @param {Object} netconfig  VPN ONC object
  **/
@@ -1226,31 +1271,22 @@
   var vpnConfig = netConfig.VPN;
   $('#vpn-type').val(vpnConfig.Type);
   $('#vpn-host').val(vpnConfig.Host);
-  var ipsecConfig = vpnConfig.IPsec;
-  if (ipsecConfig.AuthenticationType == 'PSK') {
-    $('#vpn-type').val('L2TP-IPsec-PSK');
-    if ('PSK' in ipsecConfig)
-      $('#ipsec-psk').val(ipsecConfig.PSK);
-  } else if (ipsecConfig.AuthenticationType == 'Cert') {
-    $('#vpn-type').val('L2TP-IPsec-Cert');
-    if (onc.findCert(ipsecConfig.ServerCARef, main.oncCurrent) >= 0)
-      $('#ipsec-server-ca').val(ipsecConfig.ServerCARef);
-    if (ipsecConfig.ClientCertType == 'Pattern') {
-      if (onc.findCert(ipsecConfig.ClientCertPattern.IssuerCARef,
-                       main.oncCurrent) >= 0) {
-        $('#ipsec-client-ca').val(ipsecConfig.ClientCertPattern.IssuerCARef);
-      }
-      if ('EnrollmentUri' in ipsecConfig.ClientCertPattern)
-        $('#ipsec-enrollment-uri').val(ipsecConfig.ClientCertPattern.EnrollmentUri);
+  if (vpnConfig.Type == 'L2TP-IPsec') {
+    var ipsecConfig = vpnConfig.IPsec;
+    if (ipsecConfig.AuthenticationType == 'PSK') {
+      $('#vpn-type').val('L2TP-IPsec-PSK');
+      if ('PSK' in ipsecConfig)
+        $('#vpn-psk').val(ipsecConfig.PSK);
+    } else if (ipsecConfig.AuthenticationType == 'Cert') {
+      $('#vpn-type').val('L2TP-IPsec-Cert');
+      vpnDialog.setCertToUi(ipsecConfig);
     }
+    vpnDialog.setUserCredentialsToUi(vpnConfig.L2TP);
+  } else {
+    $('#vpn-type').val('OpenVPN');
+    vpnDialog.setCertToUi(vpnConfig.OpenVPN);
+    vpnDialog.setUserCredentialsToUi(vpnConfig.OpenVPN);
   }
-  var l2tpConfig = vpnConfig.L2TP;
-  if ('Username' in l2tpConfig)
-    $('#vpn-username').val(l2tpConfig.Username);
-  if ('Password' in l2tpConfig)
-    $('#vpn-password').val(l2tpConfig.Password);
-  if ('SaveCredentials' in l2tpConfig && l2tpConfig.SaveCredentials)
-    $('#l2tp-save-credentials')[0].checked = true;
 };
 
 /**
@@ -1547,6 +1583,45 @@
       };
 
       /**
+       * Validate a VPN NetworkConfiguration ONC object's user credentials.
+       * @param (Object) container  ONC container for user credentials.
+       * @param {Integer} index  Index into NetworkConfiguration ONC object.
+       * @param {Object} oncData  ONC configuration
+       * @param {Object} result  Result object indicating errors and warnings.
+       * @returns {Object}  Result of validation.
+       */
+      onc.validateVpnUserCredentials = function(container, index,
+                                                oncData, result) {
+        var netConfig = oncData.NetworkConfigurations[index];
+        if (!('Username' in container) || container.Username == '') {
+          result.warnings.push(['warningIdentityMissing', netConfig.Name]);
+        }
+        if (!('Password' in container) || container.Password == '') {
+          result.warnings.push(['warningPasswordMissing', netConfig.Name]);
+        }
+      }
+
+      /**
+       * Validate a VPN NetworkConfiguration ONC object's certificates.
+       * @param (Object) container  ONC container for certificates.
+       * @param {Integer} index  Index into NetworkConfiguration ONC object.
+       * @param {Object} oncData  ONC configuration
+       * @param {Object} result  Result object indicating errors and warnings.
+       * @returns {Object}  Result of validation.
+       */
+      onc.validateVpnCerts = function(container, index, oncData, result) {
+        var netConfig = oncData.NetworkConfigurations[index];
+        if (!('ServerCARef' in container))
+          result.errors.push(['errorMissingVPNServerCA', netConfig.Name]);
+        else if (onc.findCert(container.ServerCARef, oncData) < 0) {
+          result.errors.push(['errorBadCertReference',
+                              netConfig.Name, 'ServerCARef',
+                              container.ServerCARef]);
+        }
+        onc.validateClientCert(container, index, oncData, result);
+      }
+
+      /**
        * Validate a VPN NetworkConfiguration ONC object.
        * @param {Integer} index  Index into NetworkConfiguration ONC object.
        * @param {Object} oncData  ONC configuration
@@ -1563,61 +1638,55 @@
         if (!('Type' in vpnConfig)) {
           result.errors.push(['errorLoadRequiredObjectMissing', 'VPN.Type']);
         }
-        if (vpnConfig.Type != 'L2TP-IPsec') {
+        if (!('Host' in vpnConfig) || vpnConfig.Host == '') {
+          result.errors.push(['errorLoadRequiredObjectMissing', 'VPN.Host']);
+        }
+        if (vpnConfig.Type == 'L2TP-IPsec') {
+          if (!('L2TP' in vpnConfig)) {
+            result.errors.push(['errorLoadRequiredObjectMissing',
+                                'VPN.L2TP']);
+            return result;
+          }
+          onc.validateVpnUserCredentials(vpnConfig.L2TP, index,
+                                         oncData, result)
+          if (!('IPsec' in vpnConfig)) {
+            result.errors.push(['errorLoadRequiredObjectMissing',
+                                'VPN.IPsec']);
+            return result;
+          }
+          var ipsecConfig = vpnConfig.IPsec;
+          if (!('IKEVersion' in ipsecConfig)) {
+            result.errors.push(['errorLoadRequiredObjectMissing',
+                                'VPN.IPsec.IKEVersion']);
+            return result;
+          }
+          if (!('AuthenticationType' in ipsecConfig)) {
+            result.errors.push(['errorLoadRequiredObjectMissing',
+                                'VPN.IPsec.AuthenticationType']);
+            return result;
+          }
+          if (ipsecConfig.AuthenticationType != 'PSK' &&
+              ipsecConfig.AuthenticationType != 'Cert') {
+            result.errors.push(['errorUnsupportedValue',
+                                'VPN.IPsec.AuthenticationType',
+                                ipsecConfig.AuthenticationType]);
+          }
+          if (ipsecConfig.AuthenticationType == 'PSK') {
+            if (!('PSK' in ipsecConfig))
+              result.warnings.push(['warningPreSharedKeyMissing', netConfig.Name]);
+          } else {
+            onc.validateVpnCerts(ipsecConfig, index, oncData, result);
+          }
+        } else if (vpnConfig.Type == 'OpenVPN') {
+          onc.validateVpnUserCredentials(vpnConfig.OpenVPN, index,
+                                         oncData, result)
+          onc.validateVpnCerts(vpnConfig.OpenVPN, index, oncData, result);
+        } else {
           result.warnings.push(['errorUnsupportedVPNType', netConfig.Name,
                                 vpnConfig.Type]);
           result.hasOpaqueEntity = true;
           return result;
         }
-        if (!('Host' in vpnConfig) || vpnConfig.Host == '') {
-          result.errors.push(['errorLoadRequiredObjectMissing', 'VPN.Host']);
-        }
-        if (!('L2TP' in vpnConfig)) {
-          result.errors.push(['errorLoadRequiredObjectMissing',
-                              'VPN.L2TP']);
-          return result;
-        }
-        var l2tpConfig = vpnConfig.L2TP;
-        if (!('Username' in l2tpConfig) || l2tpConfig.Username == '') {
-          result.warnings.push(['warningIdentityMissing', netConfig.Name]);
-        }
-        if (!('Password' in l2tpConfig) || l2tpConfig.Password == '') {
-          result.warnings.push(['warningPasswordMissing', netConfig.Name]);
-        }
-        if (!('IPsec' in vpnConfig)) {
-          result.errors.push(['errorLoadRequiredObjectMissing',
-                              'VPN.IPsec']);
-          return result;
-        }
-        var ipsecConfig = vpnConfig.IPsec;
-        if (!('IKEVersion' in ipsecConfig)) {
-          result.errors.push(['errorLoadRequiredObjectMissing',
-                              'VPN.IPsec.IKEVersion']);
-          return result;
-        }
-        if (!('AuthenticationType' in ipsecConfig)) {
-          result.errors.push(['errorLoadRequiredObjectMissing',
-                              'VPN.IPsec.AuthenticationType']);
-          return result;
-        }
-        if (ipsecConfig.AuthenticationType != 'PSK' &&
-            ipsecConfig.AuthenticationType != 'Cert') {
-          result.errors.push(['errorUnsupportedValue',
-                              'VPN.IPsec.AuthenticationType',
-                              ipsecConfig.AuthenticationType]);
-        }
-        if (ipsecConfig.AuthenticationType == 'PSK') {
-          if (!('PSK' in ipsecConfig))
-            result.warnings.push(['warningPreSharedKeyMissing', netConfig.Name]);
-        } else {
-          if (!('ServerCARef' in ipsecConfig))
-            result.errors.push(['errorMissingVPNServerCA', netConfig.Name]);
-          else if (onc.findCert(ipsecConfig.ServerCARef, oncData) < 0) {
-            result.errors.push(['errorBadCertReference',
-                                netConfig.Name, 'ServerCARef', ipsecConfig.ServerCARef]);
-          }
-          onc.validateClientCert(ipsecConfig, index, oncData, result);
-        }
         return result;
       };
     })();
@@ -1668,8 +1737,6 @@
  */
 loadDialog.onApplyPress = function() {
   // Ignore apply button if nothing was loaded or there are errors.
-  if (!loadDialog.oncToLoadResult || loadDialog.oncToLoadResult.errors.length)
-    return;
   main.oncCurrent = loadDialog.oncToLoad;
   ui.dismissDialog();
 };