1#!/usr/bin/env python 2# Copyright (c) 2012 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6from xml.dom import minidom 7from grit.format.policy_templates.writers import xml_formatted_writer 8 9 10def GetWriter(config): 11 '''Factory method for instanciating the ADMXWriter. Every Writer needs a 12 GetWriter method because the TemplateFormatter uses this method to 13 instantiate a Writer. 14 ''' 15 return ADMXWriter(['win'], config) 16 17 18class ADMXWriter(xml_formatted_writer.XMLFormattedWriter): 19 '''Class for generating an ADMX policy template. It is used by the 20 PolicyTemplateGenerator to write the admx file. 21 ''' 22 23 # DOM root node of the generated ADMX document. 24 _doc = None 25 26 # The ADMX "policies" element that contains the ADMX "policy" elements that 27 # are generated. 28 _active_policies_elem = None 29 30 def _AdmlString(self, name): 31 '''Creates a reference to the named string in an ADML file. 32 Args: 33 name: Name of the referenced ADML string. 34 ''' 35 return '$(string.' + name + ')' 36 37 def _AdmlStringExplain(self, name): 38 '''Creates a reference to the named explanation string in an ADML file. 39 Args: 40 name: Name of the referenced ADML explanation. 41 ''' 42 return '$(string.' + name + '_Explain)' 43 44 def _AdmlPresentation(self, name): 45 '''Creates a reference to the named presentation element in an ADML file. 46 Args: 47 name: Name of the referenced ADML presentation element. 48 ''' 49 return '$(presentation.' + name + ')' 50 51 def _AddPolicyNamespaces(self, parent, prefix, namespace): 52 '''Generates the ADMX "policyNamespace" element and adds the elements to the 53 passed parent element. The namespace of the generated ADMX document is 54 define via the ADMX "target" element. Used namespaces are declared with an 55 ADMX "using" element. ADMX "target" and "using" elements are children of the 56 ADMX "policyNamespace" element. 57 58 Args: 59 parent: The parent node to which all generated elements are added. 60 prefix: A logical name that can be used in the generated ADMX document to 61 refere to this namespace. 62 namespace: Namespace of the generated ADMX document. 63 ''' 64 policy_namespaces_elem = self.AddElement(parent, 'policyNamespaces') 65 attributes = { 66 'prefix': prefix, 67 'namespace': namespace, 68 } 69 self.AddElement(policy_namespaces_elem, 'target', attributes) 70 attributes = { 71 'prefix': 'windows', 72 'namespace': 'Microsoft.Policies.Windows', 73 } 74 self.AddElement(policy_namespaces_elem, 'using', attributes) 75 76 def _AddCategory(self, parent, name, display_name, 77 parent_category_name=None): 78 '''Adds an ADMX category element to the passed parent node. The following 79 snippet shows an example of a category element where "chromium" is the value 80 of the parameter name: 81 82 <category displayName="$(string.chromium)" name="chromium"/> 83 84 Each parent node can have only one category with a given name. Adding the 85 same category again with the same attributes is ignored, but adding it 86 again with different attributes is an error. 87 88 Args: 89 parent: The parent node to which all generated elements are added. 90 name: Name of the category. 91 display_name: Display name of the category. 92 parent_category_name: Name of the parent category. Defaults to None. 93 ''' 94 existing = filter(lambda e: e.getAttribute('name') == name, 95 parent.getElementsByTagName('category')) 96 if existing: 97 assert len(existing) == 1 98 assert existing[0].getAttribute('name') == name 99 assert existing[0].getAttribute('displayName') == display_name 100 return 101 attributes = { 102 'name': name, 103 'displayName': display_name, 104 } 105 category_elem = self.AddElement(parent, 'category', attributes) 106 if parent_category_name: 107 attributes = {'ref': parent_category_name} 108 self.AddElement(category_elem, 'parentCategory', attributes) 109 110 def _AddCategories(self, categories): 111 '''Generates the ADMX "categories" element and adds it to the categories 112 main node. The "categories" element defines the category for the policies 113 defined in this ADMX document. Here is an example of an ADMX "categories" 114 element: 115 116 <categories> 117 <category displayName="$(string.google)" name="google"/> 118 <category displayName="$(string.googlechrome)" name="googlechrome"> 119 <parentCategory ref="google"/> 120 </category> 121 </categories> 122 123 Args: 124 categories_path: The categories path e.g. ['google', 'googlechrome']. For 125 each level in the path a "category" element will be generated. Except 126 for the root level, each level refers to its parent. Since the root 127 level category has no parent it does not require a parent reference. 128 ''' 129 category_name = None 130 for category in categories: 131 parent_category_name = category_name 132 category_name = category 133 self._AddCategory(self._categories_elem, category_name, 134 self._AdmlString(category_name), parent_category_name) 135 136 def _AddSupportedOn(self, parent, supported_os): 137 '''Generates the "supportedOn" ADMX element and adds it to the passed 138 parent node. The "supportedOn" element contains information about supported 139 Windows OS versions. The following code snippet contains an example of a 140 "supportedOn" element: 141 142 <supportedOn> 143 <definitions> 144 <definition name="SUPPORTED_WINXPSP2" 145 displayName="$(string.SUPPORTED_WINXPSP2)"/> 146 </definitions> 147 ... 148 </supportedOn> 149 150 Args: 151 parent: The parent element to which all generated elements are added. 152 supported_os: List with all supported Win OSes. 153 ''' 154 supported_on_elem = self.AddElement(parent, 'supportedOn') 155 definitions_elem = self.AddElement(supported_on_elem, 'definitions') 156 attributes = { 157 'name': supported_os, 158 'displayName': self._AdmlString(supported_os) 159 } 160 self.AddElement(definitions_elem, 'definition', attributes) 161 162 def _AddStringPolicy(self, parent, name): 163 '''Generates ADMX elements for a String-Policy and adds them to the 164 passed parent node. 165 ''' 166 attributes = { 167 'id': name, 168 'valueName': name, 169 } 170 self.AddElement(parent, 'text', attributes) 171 172 def _AddIntPolicy(self, parent, name): 173 '''Generates ADMX elements for an Int-Policy and adds them to the passed 174 parent node. 175 ''' 176 attributes = { 177 'id': name, 178 'valueName': name, 179 'maxValue': '2000000000', 180 } 181 self.AddElement(parent, 'decimal', attributes) 182 183 def _AddEnumPolicy(self, parent, policy): 184 '''Generates ADMX elements for an Enum-Policy and adds them to the 185 passed parent element. 186 ''' 187 name = policy['name'] 188 items = policy['items'] 189 attributes = { 190 'id': name, 191 'valueName': name, 192 } 193 enum_elem = self.AddElement(parent, 'enum', attributes) 194 for item in items: 195 attributes = {'displayName': self._AdmlString(item['name'])} 196 item_elem = self.AddElement(enum_elem, 'item', attributes) 197 value_elem = self.AddElement(item_elem, 'value') 198 value_string = str(item['value']) 199 if policy['type'] == 'int-enum': 200 self.AddElement(value_elem, 'decimal', {'value': value_string}) 201 else: 202 self.AddElement(value_elem, 'string', {}, value_string) 203 204 def _AddListPolicy(self, parent, key, name): 205 '''Generates ADMX XML elements for a List-Policy and adds them to the 206 passed parent element. 207 ''' 208 attributes = { 209 # The ID must be in sync with ID of the corresponding element in the ADML 210 # file. 211 'id': name + 'Desc', 212 'valuePrefix': '', 213 'key': key + '\\' + name, 214 } 215 self.AddElement(parent, 'list', attributes) 216 217 def _AddMainPolicy(self, parent): 218 '''Generates ADMX elements for a Main-Policy amd adds them to the 219 passed parent element. 220 ''' 221 enabled_value_elem = self.AddElement(parent, 'enabledValue'); 222 self.AddElement(enabled_value_elem, 'decimal', {'value': '1'}) 223 disabled_value_elem = self.AddElement(parent, 'disabledValue'); 224 self.AddElement(disabled_value_elem, 'decimal', {'value': '0'}) 225 226 def _GetElements(self, policy_group_elem): 227 '''Returns the ADMX "elements" child from an ADMX "policy" element. If the 228 "policy" element has no "elements" child yet, a new child is created. 229 230 Args: 231 policy_group_elem: The ADMX "policy" element from which the child element 232 "elements" is returned. 233 234 Raises: 235 Exception: The policy_group_elem does not contain a ADMX "policy" element. 236 ''' 237 if policy_group_elem.tagName != 'policy': 238 raise Exception('Expected a "policy" element but got a "%s" element' 239 % policy_group_elem.tagName) 240 elements_list = policy_group_elem.getElementsByTagName('elements'); 241 if len(elements_list) == 0: 242 return self.AddElement(policy_group_elem, 'elements') 243 elif len(elements_list) == 1: 244 return elements_list[0] 245 else: 246 raise Exception('There is supposed to be only one "elements" node but' 247 ' there are %s.' % str(len(elements_list))) 248 249 def _WritePolicy(self, policy, name, key, parent): 250 '''Generates AMDX elements for a Policy. There are four different policy 251 types: Main-Policy, String-Policy, Enum-Policy and List-Policy. 252 ''' 253 policies_elem = self._active_policies_elem 254 policy_type = policy['type'] 255 policy_name = policy['name'] 256 if policy_type == 'external': 257 # This type can only be set through cloud policy. 258 return 259 260 attributes = { 261 'name': name, 262 'class': self.config['win_group_policy_class'], 263 'displayName': self._AdmlString(policy_name), 264 'explainText': self._AdmlStringExplain(policy_name), 265 'presentation': self._AdmlPresentation(policy_name), 266 'key': key, 267 } 268 # Store the current "policy" AMDX element in self for later use by the 269 # WritePolicy method. 270 policy_elem = self.AddElement(policies_elem, 'policy', 271 attributes) 272 self.AddElement(policy_elem, 'parentCategory', 273 {'ref': parent}) 274 self.AddElement(policy_elem, 'supportedOn', 275 {'ref': self.config['win_supported_os']}) 276 if policy_type == 'main': 277 self.AddAttribute(policy_elem, 'valueName', policy_name) 278 self._AddMainPolicy(policy_elem) 279 elif policy_type in ('string', 'dict'): 280 # 'dict' policies are configured as JSON-encoded strings on Windows. 281 parent = self._GetElements(policy_elem) 282 self._AddStringPolicy(parent, policy_name) 283 elif policy_type == 'int': 284 parent = self._GetElements(policy_elem) 285 self._AddIntPolicy(parent, policy_name) 286 elif policy_type in ('int-enum', 'string-enum'): 287 parent = self._GetElements(policy_elem) 288 self._AddEnumPolicy(parent, policy) 289 elif policy_type in ('list', 'string-enum-list'): 290 parent = self._GetElements(policy_elem) 291 self._AddListPolicy(parent, key, policy_name) 292 elif policy_type == 'group': 293 pass 294 else: 295 raise Exception('Unknown policy type %s.' % policy_type) 296 297 def WritePolicy(self, policy): 298 if self.CanBeMandatory(policy): 299 self._WritePolicy(policy, 300 policy['name'], 301 self.config['win_reg_mandatory_key_name'], 302 self._active_mandatory_policy_group_name) 303 304 def WriteRecommendedPolicy(self, policy): 305 self._WritePolicy(policy, 306 policy['name'] + '_recommended', 307 self.config['win_reg_recommended_key_name'], 308 self._active_recommended_policy_group_name) 309 310 def _BeginPolicyGroup(self, group, name, parent): 311 '''Generates ADMX elements for a Policy-Group. 312 ''' 313 attributes = { 314 'name': name, 315 'displayName': self._AdmlString(group['name'] + '_group'), 316 } 317 category_elem = self.AddElement(self._categories_elem, 318 'category', 319 attributes) 320 attributes = { 321 'ref': parent 322 } 323 self.AddElement(category_elem, 'parentCategory', attributes) 324 325 def BeginPolicyGroup(self, group): 326 self._BeginPolicyGroup(group, 327 group['name'], 328 self.config['win_mandatory_category_path'][-1]) 329 self._active_mandatory_policy_group_name = group['name'] 330 331 def EndPolicyGroup(self): 332 self._active_mandatory_policy_group_name = \ 333 self.config['win_mandatory_category_path'][-1] 334 335 def BeginRecommendedPolicyGroup(self, group): 336 self._BeginPolicyGroup(group, 337 group['name'] + '_recommended', 338 self.config['win_recommended_category_path'][-1]) 339 self._active_recommended_policy_group_name = group['name'] + '_recommended' 340 341 def EndRecommendedPolicyGroup(self): 342 self._active_recommended_policy_group_name = \ 343 self.config['win_recommended_category_path'][-1] 344 345 def BeginTemplate(self): 346 '''Generates the skeleton of the ADMX template. An ADMX template contains 347 an ADMX "PolicyDefinitions" element with four child nodes: "policies" 348 "policyNamspaces", "resources", "supportedOn" and "categories" 349 ''' 350 dom_impl = minidom.getDOMImplementation('') 351 self._doc = dom_impl.createDocument(None, 'policyDefinitions', None) 352 policy_definitions_elem = self._doc.documentElement 353 354 policy_definitions_elem.attributes['revision'] = '1.0' 355 policy_definitions_elem.attributes['schemaVersion'] = '1.0' 356 357 self._AddPolicyNamespaces(policy_definitions_elem, 358 self.config['admx_prefix'], 359 self.config['admx_namespace']) 360 self.AddElement(policy_definitions_elem, 'resources', 361 {'minRequiredRevision' : '1.0'}) 362 self._AddSupportedOn(policy_definitions_elem, 363 self.config['win_supported_os']) 364 self._categories_elem = self.AddElement(policy_definitions_elem, 365 'categories') 366 self._AddCategories(self.config['win_mandatory_category_path']) 367 self._AddCategories(self.config['win_recommended_category_path']) 368 self._active_policies_elem = self.AddElement(policy_definitions_elem, 369 'policies') 370 self._active_mandatory_policy_group_name = \ 371 self.config['win_mandatory_category_path'][-1] 372 self._active_recommended_policy_group_name = \ 373 self.config['win_recommended_category_path'][-1] 374 375 def GetTemplateText(self): 376 return self.ToPrettyXml(self._doc) 377