[Autotest] Add policy_WiFiTypes test

Server/client tests to verify the OpenNetworkConfiguration user policy
works for various WiFi authentication types. The server test sets up an AP
and the client test sets the network policy and attempts to connect to the AP.

The authentication methods tested are:
* Open
* WPA-PSK
* PEAP

There is a control file for EAP-TLS networks, but it's disabled as I ran
into some issues installing client certificates. I'll re-enable that
test at a later date.

BUG=chromium:893333
TEST=on various lab machines in the wificell pool

Change-Id: I526770f9be7eac1feb96904301a6e71c7d80ebd7
Reviewed-on: https://chromium-review.googlesource.com/1200249
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Tested-by: Max Timkovich <timkovich@chromium.org>
Reviewed-by: Harpreet Grewal <harpreet@chromium.org>
diff --git a/client/cros/enterprise/enterprise_network_api.py b/client/cros/enterprise/enterprise_network_api.py
index c870ae0..e5e90e0 100644
--- a/client/cros/enterprise/enterprise_network_api.py
+++ b/client/cros/enterprise/enterprise_network_api.py
@@ -16,32 +16,74 @@
 NETWORK_TEST_EXTENSION_PATH = cntc.NETWORK_TEST_EXTENSION_PATH
 
 
-def create_network_policy(ssid, autoconnect=None):
+def create_network_policy(ssid, security=None, eap=None, password=None,
+                          identity=None, autoconnect=None, ca_cert=None):
     """
     Generate a network configuration policy dictionary for WiFi networks.
 
     @param ssid: Service set identifier for wireless local area network.
-    @param autoconnect: Whether network policy should autoconnect.
+    @param security: Security of network. Options are:
+        'None', 'WEP-PSK', 'WEP-8021X', 'WPA-PSK', and 'WPA-EAP'.
+    @param eap: EAP type, required if security is 'WEP-8021X' or 'WPA-EAP'.
+    @param identity: Username, if the network type requires it.
+    @param password: Password, if the network type requires it.
+    @param ca_cert: CA certificate in PEM format. Required
+        for EAP networks.
+    @param autoconnect: True iff network policy should autoconnect.
 
-    @returns conf: A dictionary in the format suitable to setting as a user
+    @returns conf: A dictionary in the format suitable to setting as a network
         policy.
 
     """
+    if security is None:
+        security = 'None'
+
     conf = {
         'NetworkConfigurations': [
-            {'GUID': 'policy_WiFi*',
+            {'GUID': 'policy_WiFi* Test',
              'Name': ssid,
              'Type': 'WiFi',
              'WiFi': {
                  'SSID': ssid,
-                 'Security': 'None'}
+                 'Security': security}
              }
-        ]
+        ],
     }
 
+    if ca_cert is not None:
+        conf['Certificates'] = [
+            {'GUID': 'CA_CERT',
+             'Type': 'Authority',
+             'X509': ca_cert}
+        ]
+
+    wifi_conf = conf['NetworkConfigurations'][0]['WiFi']
+
     if autoconnect is not None:
-        conf['NetworkConfigurations'][0]\
-                ['WiFi']['AutoConnect'] = autoconnect
+        wifi_conf['AutoConnect'] = autoconnect
+
+    if security == 'WPA-PSK':
+        if password is None:
+            raise error.TestError('Password is required for WPA-PSK networks.')
+        wifi_conf['Passphrase'] = password
+
+    if eap is not None:
+        eap_conf = {
+            'Outer': eap,
+            'Identity': identity,
+            'ServerCARefs': ['CA_CERT']
+        }
+
+        if password is not None:
+            eap_conf['Password'] = password
+
+        if eap == 'EAP-TLS':
+            eap_conf['ClientCertType'] = 'Pattern'
+            eap_conf['ClientCertPattern'] = {
+                'IssuerCARef': ['CA_CERT']
+            }
+
+        wifi_conf['EAP'] = eap_conf
 
     return conf
 
diff --git a/client/cros/enterprise/enterprise_policy_base.py b/client/cros/enterprise/enterprise_policy_base.py
index b9acf96..9bb1fde 100755
--- a/client/cros/enterprise/enterprise_policy_base.py
+++ b/client/cros/enterprise/enterprise_policy_base.py
@@ -431,10 +431,23 @@
             elif not isinstance(value_shown, list): # List with one element.
                 value_shown = [value_shown]
 
-        if not expected_value == value_shown:
+        # Special case for user and device network configurations.
+        # Passphrases are hidden on the policy page, so the passphrase
+        # field needs to be converted to asterisks to be compared.
+        sanitized_expected_value = copy.deepcopy(expected_value)
+        SANITIZED_PASSWORD = '*' * 8
+        if policy_name.endswith('OpenNetworkConfiguration'):
+            for network in sanitized_expected_value['NetworkConfigurations']:
+                wifi = network['WiFi']
+                if 'Passphrase' in wifi:
+                    wifi['Passphrase'] = SANITIZED_PASSWORD
+                if 'EAP' in wifi and 'Password' in wifi['EAP']:
+                    wifi['EAP']['Password'] = SANITIZED_PASSWORD
+
+        if sanitized_expected_value != value_shown:
             raise error.TestError('chrome://policy shows the incorrect value '
                                   'for %s!  Expected %s, got %s.' % (
-                                          policy_name, expected_value,
+                                          policy_name, sanitized_expected_value,
                                           value_shown))
 
 
diff --git a/client/site_tests/policy_WiFiAutoconnect/policy_WiFiAutoconnect.py b/client/site_tests/policy_WiFiAutoconnect/policy_WiFiAutoconnect.py
index 8f23eba..6cda37a 100644
--- a/client/site_tests/policy_WiFiAutoconnect/policy_WiFiAutoconnect.py
+++ b/client/site_tests/policy_WiFiAutoconnect/policy_WiFiAutoconnect.py
@@ -16,7 +16,8 @@
 
     def cleanup(self):
         """Re-enable ethernet after the test is completed."""
-        self.net_api.chrome_net_context.enable_network_device('Ethernet')
+        if hasattr(self, 'net_api'):
+            self.net_api.chrome_net_context.enable_network_device('Ethernet')
         super(policy_WiFiAutoconnect, self).cleanup()
 
 
diff --git a/client/site_tests/policy_WiFiTypes/control b/client/site_tests/policy_WiFiTypes/control
new file mode 100644
index 0000000..9431384
--- /dev/null
+++ b/client/site_tests/policy_WiFiTypes/control
@@ -0,0 +1,23 @@
+# 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.
+
+AUTHOR = 'timkovich'
+NAME = 'policy_WiFiTypes'
+TIME = 'SHORT'
+TEST_CATEGORY = 'General'
+TEST_CLASS = 'enterprise'
+TEST_TYPE = 'client'
+
+DOC = '''
+This test should be run through the 'policy_WiFiTypesServer' test.
+
+'policy_WiFiTypes' sets the OpenNetworkConfiguration policy and attempts to
+connect to the given AP. Fails if the DUT does not connect.
+
+'''
+
+args_dict = utils.args_to_dict(args)
+
+job.run_test('policy_WiFiTypes', **args_dict)
+
diff --git a/client/site_tests/policy_WiFiTypes/policy_WiFiTypes.py b/client/site_tests/policy_WiFiTypes/policy_WiFiTypes.py
new file mode 100644
index 0000000..e951710
--- /dev/null
+++ b/client/site_tests/policy_WiFiTypes/policy_WiFiTypes.py
@@ -0,0 +1,60 @@
+# 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.
+
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.cros.enterprise import enterprise_policy_base
+from autotest_lib.client.cros.enterprise import enterprise_network_api
+
+
+class policy_WiFiTypes(enterprise_policy_base.EnterprisePolicyTest):
+    version = 1
+
+
+    def cleanup(self):
+        """Re-enable ethernet after the test is completed."""
+        if hasattr(self, 'net_api'):
+            self.net_api.chrome_net_context.enable_network_device('Ethernet')
+        super(policy_WiFiTypes, self).cleanup()
+
+
+    def run_once(self, ssid='', security=None, eap=None, password=None,
+                 identity=None, autoconnect=None, ca_cert=None):
+        """
+        Setup and run the test configured for the specified test case.
+
+        @param ssid: Service set identifier for wireless local area network.
+        @param security: Security of network. Options are:
+            'None', 'WEP-PSK', 'WEP-8021X', 'WPA-PSK', and 'WPA-EAP'.
+        @param eap: EAP type, required if security is 'WEP-8021X' or 'WPA-EAP'.
+        @param identity: Username, if the network type requires it.
+        @param password: Password, if the network type requires it.
+        @param ca_cert: CA certificate in PEM format. Required
+            for EAP networks.
+        @param autoconnect: True iff network policy should autoconnect.
+
+        """
+        network_policy = enterprise_network_api.create_network_policy(
+                ssid,
+                security=security,
+                eap=eap,
+                password=password,
+                identity=identity,
+                autoconnect=autoconnect,
+                ca_cert=ca_cert
+        )
+
+        self.setup_case(
+            user_policies={'OpenNetworkConfiguration': network_policy},
+            extension_paths=[enterprise_network_api.NETWORK_TEST_EXTENSION_PATH]
+        )
+
+        self.net_api = enterprise_network_api.\
+                ChromeEnterpriseNetworkContext(self.cr)
+        # Disable ethernet so device will default to WiFi
+        self.net_api.disable_network_device('Ethernet')
+
+        self.net_api.connect_to_network(ssid)
+
+        if not self.net_api.is_network_connected(ssid):
+            raise error.TestFail('Did not connect to network (%s)' % ssid)
diff --git a/server/site_tests/policy_WiFiTypesServer/control b/server/site_tests/policy_WiFiTypesServer/control
new file mode 100644
index 0000000..16f009b
--- /dev/null
+++ b/server/site_tests/policy_WiFiTypesServer/control
@@ -0,0 +1,25 @@
+# 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.
+
+AUTHOR = 'timkovich'
+TIME = 'SHORT'
+NAME = 'policy_WiFiTypesServer'
+TEST_TYPE = 'Server'
+DEPENDENCIES = 'wificell'
+
+DOC = """
+'policy_WiFiTypesServer' test configures multiple APs and runs the client
+side 'policy_WiFiTypes' test on APs with various authentication types
+and checks that they all are able to connect.
+
+"""
+
+def run(machine):
+    host = hosts.create_host(machine)
+    job.run_test('policy_WiFiTypesServer',
+                 raw_cmdline_args=args,
+                 host=host)
+
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/policy_WiFiTypesServer/control.open b/server/site_tests/policy_WiFiTypesServer/control.open
new file mode 100644
index 0000000..4bdda04
--- /dev/null
+++ b/server/site_tests/policy_WiFiTypesServer/control.open
@@ -0,0 +1,32 @@
+# 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.
+
+AUTHOR = 'timkovich'
+TIME = 'SHORT'
+NAME = 'policy_WiFiTypesServer.open'
+TEST_TYPE = 'Server'
+ATTRIBUTES = 'suite:ent-wificell'
+DEPENDENCIES = 'wificell'
+
+DOC = """
+'policy_WiFiTypesServer.open' test configures an open network and runs the
+client side 'policy_WiFiTypes' test that sets the user network policy and
+attempts to connect.
+
+"""
+
+from autotest_lib.server.cros.network import hostap_config
+
+def run(machine):
+    n_mode = hostap_config.HostapConfig.MODE_11N_MIXED
+    ap_config = hostap_config.HostapConfig(channel=6, mode=n_mode)
+
+    host = hosts.create_host(machine)
+    job.run_test('policy_WiFiTypesServer',
+                 raw_cmdline_args=args,
+                 host=host,
+                 ap_config=ap_config)
+
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/policy_WiFiTypesServer/control.wpa_eap_tls b/server/site_tests/policy_WiFiTypesServer/control.wpa_eap_tls
new file mode 100644
index 0000000..55caacf
--- /dev/null
+++ b/server/site_tests/policy_WiFiTypesServer/control.wpa_eap_tls
@@ -0,0 +1,48 @@
+# 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.
+
+AUTHOR = 'timkovich'
+TIME = 'SHORT'
+NAME = 'policy_WiFiTypesServer.wpa_eap_tls'
+TEST_TYPE = 'Server'
+# TODO(timkovich): Disabled for now: crbug.com/883522
+# ATTRIBUTES = 'suite:ent-wificell'
+DEPENDENCIES = 'wificell'
+
+DOC = """
+'policy_WiFiTypesServer.wpa_eap_tls' test configures an EAP-TLS network and
+runs the client side 'policy_WiFiTypes' test that configures the user network
+policy and attempts to connect to the AP.
+
+"""
+
+from autotest_lib.client.common_lib.cros import site_eap_certs
+from autotest_lib.client.common_lib.cros.network import xmlrpc_security_types
+from autotest_lib.server.cros.network import hostap_config
+
+def run(machine):
+    eap_config = xmlrpc_security_types.WPAEAPConfig(
+            server_ca_cert=site_eap_certs.ca_cert_1,
+            server_cert=site_eap_certs.server_cert_1,
+            server_key=site_eap_certs.server_private_key_1,
+            client_ca_cert=site_eap_certs.ca_cert_1,
+            client_cert=site_eap_certs.client_cert_1,
+            client_key=site_eap_certs.client_private_key_1)
+    ap_config = hostap_config.HostapConfig(
+            frequency=2412,
+            mode=hostap_config.HostapConfig.MODE_11G,
+            security_config=eap_config)
+
+    host = hosts.create_host(machine)
+    job.run_test('policy_WiFiTypesServer',
+                 raw_cmdline_args=args,
+                 host=host,
+                 ap_config=ap_config,
+                 security='WPA-EAP',
+                 eap='EAP-TLS',
+                 identity='chromeos',
+                 ca_cert=site_eap_certs.ca_cert_1)
+
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/policy_WiFiTypesServer/control.wpa_peap b/server/site_tests/policy_WiFiTypesServer/control.wpa_peap
new file mode 100644
index 0000000..cccf164
--- /dev/null
+++ b/server/site_tests/policy_WiFiTypesServer/control.wpa_peap
@@ -0,0 +1,55 @@
+# 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.
+
+AUTHOR = 'timkovich'
+TIME = 'SHORT'
+NAME = 'policy_WiFiTypesServer.wpa_peap'
+TEST_TYPE = 'Server'
+ATTRIBUTES = 'suite:ent-wificell'
+DEPENDENCIES = 'wificell'
+
+DOC = """
+'policy_WiFiTypesServer.wpa_peap' test configures a PEAP network and runs the
+client side 'policy_WiFiTypes' test that configures the user network policy and
+attempts to connect to the AP.
+
+"""
+
+from autotest_lib.client.common_lib.cros import site_eap_certs
+from autotest_lib.client.common_lib.cros.network import xmlrpc_security_types
+from autotest_lib.server.cros.network import hostap_config
+
+def run(machine):
+    PEAP = xmlrpc_security_types.Tunneled1xConfig.LAYER1_TYPE_PEAP
+    MSCHAPV2 = xmlrpc_security_types.Tunneled1xConfig.LAYER2_TYPE_MSCHAPV2
+    identity = 'chromeos'
+    password = 'chromeos'
+
+    eap_config = xmlrpc_security_types.Tunneled1xConfig(
+            site_eap_certs.ca_cert_1,
+            site_eap_certs.server_cert_1,
+            site_eap_certs.server_private_key_1,
+            site_eap_certs.ca_cert_1,
+            identity,
+            password,
+            inner_protocol=MSCHAPV2,
+            outer_protocol=PEAP)
+    ap_config = hostap_config.HostapConfig(
+            frequency=2412,
+            mode=hostap_config.HostapConfig.MODE_11G,
+            security_config=eap_config)
+
+    host = hosts.create_host(machine)
+    job.run_test('policy_WiFiTypesServer',
+                 raw_cmdline_args=args,
+                 host=host,
+                 ap_config=ap_config,
+                 security='WPA-EAP',
+                 eap='PEAP',
+                 identity=identity,
+                 password=password,
+                 ca_cert=site_eap_certs.ca_cert_1)
+
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/policy_WiFiTypesServer/control.wpa_psk b/server/site_tests/policy_WiFiTypesServer/control.wpa_psk
new file mode 100644
index 0000000..360fedb
--- /dev/null
+++ b/server/site_tests/policy_WiFiTypesServer/control.wpa_psk
@@ -0,0 +1,42 @@
+# 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.
+
+AUTHOR = 'timkovich'
+TIME = 'SHORT'
+NAME = 'policy_WiFiTypesServer.wpa_psk'
+TEST_TYPE = 'Server'
+ATTRIBUTES = 'suite:ent-wificell'
+DEPENDENCIES = 'wificell'
+
+DOC = """
+'policy_WiFiTypesServer.wpa_psk' test configures an WPA-PSK network and runs
+the client side 'policy_WiFiTypes' test that sets the user network policy and
+attempts to connect.
+
+"""
+
+from autotest_lib.client.common_lib.cros.network import xmlrpc_security_types
+from autotest_lib.server.cros.network import hostap_config
+
+def run(machine):
+    password = 'chromeos'
+    wpa_config = xmlrpc_security_types.WPAConfig(
+            psk=password,
+            wpa_mode=xmlrpc_security_types.WPAConfig.MODE_PURE_WPA2,
+            wpa2_ciphers=[xmlrpc_security_types.WPAConfig.CIPHER_CCMP])
+    ap_config = hostap_config.HostapConfig(
+            channel=6,
+            mode=hostap_config.HostapConfig.MODE_11N_MIXED,
+            security_config=wpa_config)
+
+    host = hosts.create_host(machine)
+    job.run_test('policy_WiFiTypesServer',
+                 raw_cmdline_args=args,
+                 host=host,
+                 ap_config=ap_config,
+                 security='WPA-PSK',
+                 password=password)
+
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/policy_WiFiTypesServer/policy_WiFiTypesServer.py b/server/site_tests/policy_WiFiTypesServer/policy_WiFiTypesServer.py
new file mode 100644
index 0000000..4609e60
--- /dev/null
+++ b/server/site_tests/policy_WiFiTypesServer/policy_WiFiTypesServer.py
@@ -0,0 +1,50 @@
+# Copyright (c) 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.
+
+from autotest_lib.server import autotest
+from autotest_lib.server import site_linux_system
+from autotest_lib.server.cros.network import wifi_cell_test_base
+
+
+class policy_WiFiTypesServer(wifi_cell_test_base.WiFiCellTestBase):
+    version = 1
+
+
+    def run_once(self, host, ap_config, security=None, eap=None, password=None,
+                 identity=None, autoconnect=None, ca_cert=None):
+        """
+        Set up an AP for a WiFi authentication type then run the client test.
+
+        @param host: A host object representing the DUT.
+        @param ap_config: HostapConfig object representing how to configure
+            the router.
+        @param security: Security of network. Options are:
+            'None', 'WEP-PSK', 'WEP-8021X', 'WPA-PSK', and 'WPA-EAP'.
+        @param eap: EAP type, required if security is 'WEP-8021X' or 'WPA-EAP'.
+        @param identity: Username, if the network type requires it.
+        @param password: Password, if the network type requires it.
+        @param ca_cert: CA certificate in PEM format. Required
+            for EAP networks.
+        @param autoconnect: True iff network policy should autoconnect.
+
+        """
+        self.context.router.require_capabilities(
+                [site_linux_system.LinuxSystem.CAPABILITY_MULTI_AP])
+        self.context.router.deconfig()
+
+        # Configure the AP
+        self.context.configure(ap_config)
+
+        client_at = autotest.Autotest(host)
+        client_at.run_test('policy_WiFiTypes',
+                           ssid=self.context.router.get_ssid(),
+                           security=security,
+                           eap=eap,
+                           password=password,
+                           identity=identity,
+                           autoconnect=autoconnect,
+                           ca_cert=ca_cert,
+                           check_client_result=True)
+
+        self.context.router.deconfig()