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
6
7from autotest_lib.client.cros import httpd
8from autotest_lib.client.common_lib import error
9from autotest_lib.client.cros import enterprise_policy_base
10
11POLICY_NAME = 'URLWhitelist'
12URL_HOST = 'http://localhost'
13URL_PORT = 8080
14URL_BASE = '%s:%d/%s' % (URL_HOST, URL_PORT, 'test_data')
15BLOCKED_URLS_LIST = [URL_BASE + website for website in
16                                    ['/website1.html',
17                                     '/website2.html',
18                                     '/website3.html']]
19SINGLE_WHITELISTED_FILE_DATA = BLOCKED_URLS_LIST[:1]
20MULTIPLE_WHITELISTED_FILES_DATA = BLOCKED_URLS_LIST[:2]
21BLOCKED_USER_MESSAGE = 'Webpage Blocked'
22BLOCKED_ERROR_MESSAGE = 'ERR_BLOCKED_BY_ADMINISTRATOR'
23SUPPORTING_POLICIES = {'URLBlacklist': BLOCKED_URLS_LIST}
24
25
26class policy_URLWhitelist(enterprise_policy_base.EnterprisePolicyTest):
27    """
28    Test effect of URLWhitleist policy on Chrome OS behavior.
29
30    Navigate to all the websites in the BLOCKED_URLS_LIST. Verify that the
31    websites specified by the URLWhitelist policy value are not blocked.
32    Also verify that the websites not in the URLWhitelist policy
33    value are blocked.
34
35    Two TEST_CASES (SingleWhitelistedFile, MultipleWhitelistedFiles) are
36    designed to verify that the functionality works regardless of whether a
37    a SINGLE website is specified in the URLWhitelist policy or if MULTIPLE
38    websites are specified.
39    The third TEST_CASE (NotSet) is designed to verify that all of the websites
40    are blocked since the URLWhitelistlist policy is set to None.
41
42    The test case shall pass if the URLs that are part of the URLWhitelist
43    policy value are not blocked.
44    The test case shall also pass if the URLs that are not part of the
45    URLWhitelist policy value are blocked.
46    The test case shall fail if the above behavior is not enforced.
47
48    """
49    version = 1
50    TEST_CASES = {
51                 'NotSet': '',
52                 'SingleWhitelistedFile': SINGLE_WHITELISTED_FILE_DATA,
53                 'MultipleWhitelistedFiles': MULTIPLE_WHITELISTED_FILES_DATA
54                 }
55
56    def initialize(self, args=()):
57        super(policy_URLWhitelist, self).initialize(args)
58        self.start_webserver(URL_PORT)
59
60    def _navigate_to_website(self, url):
61        """
62        Open a new tab in the browser and navigate to the URL.
63
64        @param url: the website that the browser is navigated to.
65        @returns: a chrome browser tab navigated to the URL.
66
67        """
68        tab = self.cr.browser.tabs.New()
69        logging.info('Navigating to URL:%s', url)
70        try:
71            tab.Navigate(url, timeout=10)
72        except Exception, err:
73            logging.error('Timeout Exception in navigating URL: %s\n %s',
74                    url, err)
75        tab.WaitForDocumentReadyStateToBeComplete()
76        return tab
77
78    def _scrape_text_from_website(self, tab):
79        """
80        Returns a list of the the text content displayed on the page
81        matching the page_scrape_cmd filter.
82
83        @param tab: tab containing the website to be parsed.
84        @raises: TestFail if the expected text content was not found on the
85                 page.
86
87        """
88        parsed_message_string = ''
89        parsed_message_list = []
90        page_scrape_cmd = 'document.getElementById("main-message").innerText;'
91        try:
92            parsed_message_string = tab.EvaluateJavaScript(page_scrape_cmd)
93        except Exception as err:
94                raise error.TestFail('Unable to find the expected '
95                                     'text content on the test '
96                                     'page: %s\n %r'%(tab.url, err))
97        logging.info('Parsed message:%s', parsed_message_string)
98        parsed_message_list = [str(word) for word in
99                               parsed_message_string.split('\n') if word]
100        return parsed_message_list
101
102    def _is_url_blocked(self, url):
103        """
104        Returns True if the URL is blocked else returns False.
105
106        @param url: The URL to be checked whether it is blocked.
107
108        """
109        parsed_message_list = []
110        tab = self._navigate_to_website(url)
111        parsed_message_list = self._scrape_text_from_website(tab)
112        if len(parsed_message_list) == 2 and \
113                parsed_message_list[0] == 'Website enabled' and \
114                parsed_message_list[1] == 'Website is enabled':
115            return False
116
117        # Check if the accurate user error message displayed on the error page.
118        if parsed_message_list[0] != BLOCKED_USER_MESSAGE or \
119                parsed_message_list[1] != BLOCKED_ERROR_MESSAGE:
120            logging.warning('The Blocked page user notification '
121                            'messages, %s and %s are not displayed on '
122                            'the blocked page. The messages may have '
123                            'been modified. Please check and update the '
124                            'messages in this file accordingly.',
125                            BLOCKED_USER_MESSAGE, BLOCKED_ERROR_MESSAGE)
126        return True
127
128    def _test_URLWhitelist(self, policy_value, policies_json):
129        """
130        Verify CrOS enforces URLWhitelist policy value.
131
132        Navigate to all the websites in the BLOCKED_URLS_LIST. Verify that
133        the websites specified by the URLWhitelist policy value are not
134        blocked. Also verify that the websites not in the URLWhitelist policy
135        value are blocked.
136
137        @param policy_value: policy value expected on chrome://policy page.
138        @param policies_json: policy JSON data to send to the fake DM server.
139        @raises: TestFail if url is blocked/not blocked based on the
140                 corresponding policy values.
141
142        """
143        url_is_blocked = None
144        logging.info('Running _test_Whitelist(%s, %s)',
145                     policy_value, policies_json)
146        self.setup_case(POLICY_NAME, policy_value, policies_json)
147
148        for url in BLOCKED_URLS_LIST:
149            url_is_blocked = self._is_url_blocked(url)
150            if policy_value:
151                if url in policy_value and url_is_blocked:
152                    raise error.TestFail('The URL %s should have been allowed'
153                                         ' by policy, but it was blocked' % url)
154                elif url not in policy_value and not url_is_blocked:
155                    raise error.TestFail('The URL %s should have been blocked'
156                                         ' by policy, but it was allowed' % url)
157
158            elif not url_is_blocked:
159                raise error.TestFail('The URL %s should have been blocked'
160                                      'by policy, but it was allowed' % url)
161
162    def _run_test_case(self, case):
163        """
164        Setup and run the test configured for the specified test case.
165
166        Set the expected |policy_value| and |policies_json| data based on the
167        test |case|. If the user specified an expected |value| in the command
168        line args, then use it to set the |policy_value| and blank out the
169        |policies_json|.
170
171        @param case: Name of the test case to run.
172
173        """
174        policy_value = None
175        policies_json = None
176
177        if self.is_value_given:
178            # If |value| was given in the command line args, then set expected
179            # |policy_value| to the given value, and |policies_json| to None.
180            policy_value = self.value
181            policies_json = None
182        else:
183            # Otherwise, set expected |policy_value| and setup |policies_json|
184            # data to the values required by the specified test |case|.
185            policies_json = SUPPORTING_POLICIES.copy()
186            if not self.TEST_CASES[case]:
187                policy_value = None
188            else:
189                policy_value = ','.join(self.TEST_CASES[case])
190                policies_json.update({'URLWhitelist': self.TEST_CASES[case]})
191
192        # Run test using the values configured for the test case.
193        self._test_URLWhitelist(policy_value, policies_json)
194
195    def run_once(self):
196        self.run_once_impl(self._run_test_case)
197