1# Copyright 2019 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.common_lib import error
8
9
10class Policy(object):
11    """
12    Class structure for a single policy object.
13
14    A policy has the following attributes:
15        value
16        source
17        level
18        scope
19        name
20
21    All of the attributes are protected in @setter's. To set a value simply
22    create a policy object (e.g. "somePolicy = Policy()") and set the desired
23    value (somePolicy.value=True).
24
25    This class will be used by the policy_manager for configuring and checking
26    policies for Enterprise tests.
27
28    """
29
30    def __init__(self):
31        self._value = None
32        self._source = None
33        self._scope = None
34        self._level = None
35        self._name = None
36
37    def is_formatted_value(self, data):
38        """
39        Checks if the received value is a a dict containing all policy stats.
40
41        """
42        if not isinstance(data, dict):
43            return False
44        received_keys = set(['scope', 'source', 'level', 'value'])
45        return received_keys.issubset(set(data.keys()))
46
47    def get_policy_as_dict(self):
48        """
49        Returns the policy as a dict, in the same format as in the
50        chrome://policy json file.
51
52        """
53        return {self.name: {'scope': self._scope,
54                            'source': self._source,
55                            'level': self._level,
56                            'value': self._value}}
57
58    def set_policy_from_dict(self, data):
59        """
60        Sets the policy attributes from a provided dict matching the following
61        format:
62
63            {"scope": scopeValue, "source": sourceValue, "level": levelValue,
64             "value": value}
65
66        @param data: a dict representing the policy values, as specified above.
67
68        """
69        if not self.is_formatted_value(data):
70            raise error.TestError("""Incorrect input data provided. Value
71                provided must be dict with the keys: "scope", "source",
72                "level", "value". Got {}""".format(data))
73        self.scope = data['scope']
74        self.source = data['source']
75        self.level = data['level']
76        self.value = data['value']
77        if data.get('error', None):
78            logging.warning('\n[Policy Error] error reported with policy:\n{}'
79                            .format(data['error']))
80
81    @property
82    def value(self):
83        return self._value
84
85    @value.setter
86    def value(self, value):
87        self._value = value
88
89    @property
90    def name(self):
91        return self._name
92
93    @name.setter
94    def name(self, name):
95        self._name = name
96
97    @property
98    def level(self):
99        return self._level
100
101    @level.setter
102    def level(self, level):
103        self._level = level
104
105    @property
106    def scope(self):
107        return self._scope
108
109    @scope.setter
110    def scope(self, scope):
111        self._scope = scope
112
113    @property
114    def source(self):
115        return self._source
116
117    @source.setter
118    def source(self, source):
119        self._source = source
120
121    @property
122    def group(self):
123        return None
124
125    @group.setter
126    def group(self, group):
127        """
128        If setting a policy, you can also set it from a group ("user",
129        "suggested_user", "device"). Doing this will automatically set the other
130        policy attributes.
131
132        If the group is None, nothing will be set.
133
134        @param group: None or value of the policy group.
135
136        """
137        if not group:
138            return
139        self._source = 'cloud'
140        if group != 'suggested_user':
141            self._level = 'mandatory'
142        else:
143            self._level = 'recommended'
144        if group == 'device':
145            self._scope = 'machine'
146        else:
147            self._scope = 'user'
148
149    def __ne__(self, other):
150        return not self.__eq__(other)
151
152    def __eq__(self, other):
153        """
154        Override of the "==" statment. Verify one policy object verse another.
155
156        Will return True if the policy value, source, scope, and level are
157        equal, otherwise False.
158
159        There is a special check for the Network Policy, due to a displayed
160        policy having visual obfuscation (********).
161
162        """
163        if self.value != other.value:
164            if is_network_policy(other.name):
165                return check_obfuscation(other.value)
166            else:
167                logging.info('value configured {} != received {}'
168                             .format(self.value, other.value))
169                return False
170        if self.source != other.source:
171            logging.info('source configured {} != received {}'
172                         .format(self.source, other.source))
173            return False
174        if self.scope != other.scope:
175            logging.info('scope configured {} != received {}'
176                         .format(self.scope, other.scope))
177            return False
178        if self.level != other.level:
179            logging.info('level configured {} != received {}'
180                         .format(self.level, other.level))
181            return False
182        return True
183
184    def __repr__(self):
185        policy_data = self.get_policy_as_dict()
186        return "<POLICY DATA OBJECT> | {}".format(policy_data)
187
188
189def is_network_policy(policy_name):
190    """
191    Returns true if the policy name is that of an OpenNetworkConfiguration.
192
193    """
194    if policy_name.endswith('OpenNetworkConfiguration'):
195        return True
196
197
198def check_obfuscation(other_value):
199    """Checks if value is a network policy, and is obfuscated."""
200    DISPLAY_PWORD = '*' * 8
201
202    for network in other_value.get('NetworkConfigurations', []):
203        wifi = network.get('WiFi', {})
204        if 'Passphrase' in wifi and wifi['Passphrase'] != DISPLAY_PWORD:
205            return False
206        if ('EAP' in wifi and
207            'Password' in wifi['EAP'] and
208             wifi['EAP']['Password'] != DISPLAY_PWORD):
209            return False
210    for cert in other_value.get('Certificates', []):
211        if 'PKCS12' in cert:
212            if cert['PKCS12'] != DISPLAY_PWORD:
213                return False
214    return True
215