Home | History | Annotate | Download | only in policy_ProxySettings
      1 # Copyright 2015 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 import threading
      7 
      8 from autotest_lib.client.common_lib import error
      9 from autotest_lib.client.cros import enterprise_policy_base
     10 from SocketServer import ThreadingTCPServer, StreamRequestHandler
     11 
     12 POLICY_NAME = 'ProxySettings'
     13 PROXY_HOST = 'localhost'
     14 PROXY_PORT = 3128
     15 FIXED_PROXY = '''{
     16   "ProxyBypassList": "www.google.com,www.googleapis.com",
     17   "ProxyMode": "fixed_servers",
     18   "ProxyServer": "localhost:%s"
     19 }''' % PROXY_PORT
     20 DIRECT_PROXY = '''{
     21   "ProxyMode": "direct"
     22 }'''
     23 TEST_URL = 'http://www.wired.com/'
     24 
     25 
     26 class ProxyHandler(StreamRequestHandler):
     27     """Provide request handler for the Threaded Proxy Listener."""
     28 
     29     def handle(self):
     30         """Get URL of request from first line.
     31 
     32         Read the first line of the request, up to 40 characters, and look
     33         for the URL of the request. If found, save it to the URL list.
     34 
     35         Note: All requests are sent an HTTP 504 error.
     36         """
     37         # Capture URL in first 40 chars of request.
     38         data = self.rfile.readline(40).strip()
     39         logging.info('ProxyHandler::handle(): <%s>', data)
     40         self.server.store_requests_recieved(data)
     41         self.wfile.write('HTTP/1.1 504 Gateway Timeout\r\n'
     42                          'Connection: close\r\n\r\n')
     43 
     44 
     45 class ThreadedProxyServer(ThreadingTCPServer):
     46     """Provide a Threaded Proxy Server to service and save requests.
     47 
     48     Define a Threaded Proxy Server which services requests, and allows the
     49     handler to save all requests.
     50     """
     51 
     52     def __init__(self, server_address, HandlerClass):
     53         """Constructor.
     54 
     55         @param server_address: tuple of server IP and port to listen on.
     56         @param HandlerClass: the RequestHandler class to instantiate per req.
     57         """
     58         self.reset_requests_received()
     59         ThreadingTCPServer.__init__(self, server_address, HandlerClass)
     60 
     61     def store_requests_recieved(self, request):
     62         """Add receieved request to list.
     63 
     64         @param request: request received by the proxy server.
     65         """
     66         self._requests_recieved.append(request)
     67 
     68     def get_requests_recieved(self):
     69         """Get list of received requests."""
     70         return self._requests_recieved
     71 
     72     def reset_requests_received(self):
     73         """Clear list of received requests."""
     74         self._requests_recieved = []
     75 
     76 
     77 class ProxyListener(object):
     78     """Provide a Proxy Listener to detect connect requests.
     79 
     80     Define a proxy listener to detect when a CONNECT request is seen at the
     81     given |server_address|, and record all requests received. Requests
     82     recieved are exposed to the caller.
     83     """
     84 
     85     def __init__(self, server_address):
     86         """Constructor.
     87 
     88         @param server_address: tuple of server IP and port to listen on.
     89         """
     90         self._server = ThreadedProxyServer(server_address, ProxyHandler)
     91         self._thread = threading.Thread(target=self._server.serve_forever)
     92 
     93     def run(self):
     94         """Start the server by activating it's thread."""
     95         self._thread.start()
     96 
     97     def stop(self):
     98         """Stop the server and its threads."""
     99         self._server.shutdown()
    100         self._server.socket.close()
    101         self._thread.join()
    102 
    103     def store_requests_recieved(self, request):
    104         """Add receieved request to list.
    105 
    106         @param request: request received by the proxy server.
    107         """
    108         self._requests_recieved.append(request)
    109 
    110     def get_requests_recieved(self):
    111         """Get list of received requests."""
    112         return self._server.get_requests_recieved()
    113 
    114     def reset_requests_received(self):
    115         """Clear list of received requests."""
    116         self._server.reset_requests_received()
    117 
    118 
    119 class policy_ProxySettings(enterprise_policy_base.EnterprisePolicyTest):
    120     """Test effect of ProxySettings policy on Chrome OS behavior.
    121 
    122     This test verifies the behavior of Chrome OS for specific configurations
    123     of the ProxySettings use policy: None (undefined), ProxyMode=direct,
    124     ProxyMode=fixed_servers. None means that the policy value is not set. This
    125     induces the default behavior, equivalent to what is seen by an un-managed
    126     user.
    127 
    128     When ProxySettings is None (undefined), or ProxyMode=direct, then no proxy
    129     server should be used. When ProxyMode=fixed_servers, then the proxy server
    130     address specified by the ProxyServer entry should be used.
    131     """
    132     version = 1
    133     TEST_CASES = {
    134         'FixedProxy_UseFixedProxy': FIXED_PROXY,
    135         'DirectProxy_UseNoProxy': DIRECT_PROXY,
    136         'NotSet_UseNoProxy': None
    137     }
    138 
    139     def initialize(self, args=()):
    140         super(policy_ProxySettings, self).initialize(args)
    141         self._proxy_server = ProxyListener(('', PROXY_PORT))
    142         self._proxy_server.run()
    143 
    144     def cleanup(self):
    145         self._proxy_server.stop()
    146         super(policy_ProxySettings, self).cleanup()
    147 
    148     def _test_proxy_configuration(self, policy_value, policies_json):
    149         """Verify CrOS enforces the specified ProxySettings configuration.
    150 
    151         @param policy_value: policy value expected on chrome://policy page.
    152         @param policies_json: policy JSON data to send to the fake DM server.
    153         """
    154         logging.info('Running _test_proxy_configuration(%s, %s)',
    155                      policy_value, policies_json)
    156         self.setup_case(POLICY_NAME, policy_value, policies_json)
    157 
    158         self._proxy_server.reset_requests_received()
    159         self.navigate_to_url(TEST_URL)
    160         proxied_requests = self._proxy_server.get_requests_recieved()
    161 
    162         # Determine whether TEST_URL is in |proxied_requests|. Comprehension
    163         # is conceptually equivalent to `TEST_URL in proxied_requests`;
    164         # however, we must do partial matching since TEST_URL and the
    165         # elements inside |proxied_requests| are not necessarily equal, i.e.,
    166         # TEST_URL is a substring of the received request.
    167         matching_requests = [request for request in proxied_requests
    168                              if TEST_URL in request]
    169         logging.info('matching_requests: %s', matching_requests)
    170 
    171         if policy_value is None or 'direct' in policy_value:
    172             if matching_requests:
    173                 raise error.TestFail('Requests should not have been sent '
    174                                      'through the proxy server.')
    175         elif 'fixed_servers' in policy_value:
    176             if not matching_requests:
    177                 raise error.TestFail('Requests should have been sent '
    178                                      'through the proxy server.')
    179 
    180     def run_test_case(self, case):
    181         """Setup and run the test configured for the specified test case.
    182 
    183         Set the expected |policy_value| and |policies_json| data based on the
    184         test |case|. If the user gave an expected |value| on the command line,
    185         then set |policy_value| to |value|, and |policies_json| to None.
    186 
    187         @param case: Name of the test case to run.
    188 
    189         """
    190         if self.is_value_given:
    191             # If |value| was given in the command line args, then set expected
    192             # |policy_value| to the given value, and |policies_json| to None.
    193             policy_value = self.value
    194             policies_json = None
    195         else:
    196             # Otherwise, set expected |policy_value| and setup |policies_json|
    197             # data to the values required by the specified test |case|.
    198             policy_value = self.TEST_CASES[case]
    199             policies_json = {POLICY_NAME: self.TEST_CASES[case]}
    200 
    201         self._test_proxy_configuration(policy_value, policies_json)
    202