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