• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5import logging
6import threading
7
8from autotest_lib.client.common_lib import error
9from autotest_lib.client.cros import enterprise_policy_base
10from SocketServer import ThreadingTCPServer, StreamRequestHandler
11
12POLICY_NAME = 'ProxySettings'
13PROXY_HOST = 'localhost'
14PROXY_PORT = 3128
15FIXED_PROXY = '''{
16  "ProxyBypassList": "www.google.com,www.googleapis.com",
17  "ProxyMode": "fixed_servers",
18  "ProxyServer": "localhost:%s"
19}''' % PROXY_PORT
20DIRECT_PROXY = '''{
21  "ProxyMode": "direct"
22}'''
23TEST_URL = 'http://www.wired.com/'
24
25
26class 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
45class 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
77class 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
119class 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