├── README.md └── profileManagerKeyExtractor.py /README.md: -------------------------------------------------------------------------------- 1 | # This script was just an early test and proof of concept. @mosen has written a real tool for this now, please check that out instead: https://github.com/mosen/profiledocs 2 | 3 | ## ProfileManagerKeyExtractor 4 | 5 | Script to extract payload information from the Profile Manager source code. 6 | 7 | ## Disclaimer 8 | **This script is currently just a proof of concept, NOT a finished tool.** 9 | **Many of the regexes and python implementations are not refined, just the first I could think of to do the job.** 10 | 11 | ## Description 12 | 13 | Here's a link to my companion blog post for this script: [Extracting Payload Keys From Profile Manager](http://erikberglund.github.io/2016/Extracting_Payload_Keys_From_Profile_Manager/) 14 | 15 | I've written this script to test the possibility of extracting payload information directly from a Profile Manager installation to circumvent having to create and export profiles from the GUI in order to find the keys Profile Manager includes in a profile. 16 | 17 | The test was mostly successful, but there are still work that needs to be done to the parsing. For example: 18 | 19 | * Some values are missed in parsing as the regexes aren't matching everything they should. 20 | * Payload values with dicts or arrays of dicts are not handled. 21 | * Strings with the prefix **internal_use** reference a value somewhere else in the code that I haven't looked into: 22 | 23 | ```bash 24 | key: "internal_use_flag_useCommonAlwaysOnTunnelConfig" 25 | ``` 26 | 27 | ## Usage 28 | 29 | Run this script on a machine which has Server.app located in the Applications folder. 30 | 31 | Use the `-l/--list` flag to get a list of all available KnobSets for the current version of Profile Manager: 32 | 33 | ```console 34 | $ ./profileManagerKeyExtractor.py -l 35 | adCertKnobSets 36 | airplayKnobSets 37 | airprintKnobSets 38 | apnKnobSets 39 | appConfigurationKnobSets 40 | appLockKnobSets 41 | calDavKnobSets 42 | cardDavKnobSets 43 | certificateKnobSets 44 | cfprefsKnobSets 45 | directoryKnobSets 46 | dockKnobSets 47 | energySaverKnobSets 48 | exchangeKnobSets 49 | finderKnobSets 50 | fontsKnobSets 51 | generalKnobSets 52 | globalHttpProxyKnobSets 53 | googleAccountKnobSets 54 | homeScreenLayoutKnobSets 55 | iChatKnobSets 56 | identificationKnobSets 57 | interfaceKnobSets 58 | ldapKnobSets 59 | lockScreenMessageKnobSets 60 | loginItemKnobSets 61 | loginWindowKnobSets 62 | macRestrictionsKnobSets 63 | mailKnobSets 64 | managedDomainsKnobSets 65 | mobilityKnobSets 66 | networkUsageRulesKnobSets 67 | notificationKnobSets 68 | osxserverAccountKnobSets 69 | parentalControlsKnobSets 70 | passcodeKnobSets 71 | printingKnobSets 72 | privacyKnobSets 73 | proxiesKnobSets 74 | restrictionsKnobSets 75 | scepKnobSets 76 | singleSignOnKnobSets 77 | softwareUpdateKnobSets 78 | subscribedCalendarKnobSets 79 | timeMachineKnobSets 80 | universalAccessKnobSets 81 | vpnKnobSets 82 | webClipKnobSets 83 | webContentFilterKnobSets 84 | xsanKnobSets 85 | ``` 86 | 87 | Then, use the `-k/--knobset` flag followed by a KnobSet from the list to print it's payload keys and information. 88 | 89 | ```console 90 | $ ./profileManagerKeyExtractor.py -k calDavKnobSets 91 | 92 | Payload Name: CalDAV 93 | Payload Type: com.apple.caldav.account 94 | Unique: NO 95 | UserLevel: YES 96 | SystemLevel: NO 97 | Platforms: iOS,OSX 98 | 99 | PayloadKey: CalDAVAccountDescription 100 | Title: Account Description 101 | Description: The display name of the account 102 | Type: String 103 | Hint String: optional 104 | DefaultValue: My Calendar Account 105 | 106 | PayloadKey: CalDAVHostName 107 | Title: Account Hostname and Port 108 | Description: The CalDAV hostname or IP address and port number 109 | Type: String 110 | 111 | PayloadKey: CalDAVPort 112 | Title: 113 | Description: 114 | Type: Number 115 | DefaultValue: 8443 116 | 117 | PayloadKey: CalDAVPrincipalURL 118 | Title: Principal URL 119 | Description: The Principal URL for the CalDAV account 120 | Type: String 121 | 122 | PayloadKey: CalDAVUsername 123 | Title: Account User name 124 | Description: The CalDAV user name 125 | Type: String 126 | Hint String: Required (OTA) 127 | Set on device (Manual) 128 | 129 | PayloadKey: CalDAVPassword 130 | Title: Account Password 131 | Description: The CalDAV password 132 | Type: String 133 | Hint String: Optional (OTA) 134 | Set on device (Manual) 135 | 136 | PayloadKey: CalDAVUseSSL 137 | Title: Use SSL 138 | Description: Enable Secure Socket Layer communication with CalDAV server 139 | Type: Boolean 140 | DefaultValue: YES 141 | ``` 142 | -------------------------------------------------------------------------------- /profileManagerKeyExtractor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | import os.path 5 | import mmap 6 | import re 7 | import sys 8 | 9 | '''/////////////////////////////////////////////////////''' 10 | '''// VARIABLES //''' 11 | '''/////////////////////////////////////////////////////''' 12 | 13 | # Paths 14 | file_pm_javascript_packed = "/Applications/Server.app/Contents/ServerRoot/usr/share/devicemgr/frontend/admin/common/app/javascript-packed.js" 15 | filr_pm_localized_strings = "/Applications/Server.app/Contents/ServerRoot/usr/share/devicemgr/frontend/admin/en.lproj/app/javascript_localizedStrings.js" 16 | folder_backend_models = "/Applications/Server.app/Contents/ServerRoot/usr/share/devicemgr/backend/app/models" 17 | 18 | '''/////////////////////////////////////////////////////''' 19 | '''// CLASSES //''' 20 | '''/////////////////////////////////////////////////////''' 21 | 22 | class sgr_color: 23 | clr = '\033[0m' 24 | bld = '\033[1m' 25 | 26 | class payload(object): 27 | 28 | # Title / label 29 | @property 30 | def title(self): 31 | return self._title 32 | @title.setter 33 | def title(self, value): 34 | self._title = expandLocalizedString(cleanString(value)) 35 | 36 | # Description 37 | @property 38 | def description(self): 39 | return self._description 40 | @description.setter 41 | def description(self, value): 42 | self._description = expandLocalizedString(cleanString(value)) 43 | 44 | # Key 45 | @property 46 | def key(self): 47 | return self._key 48 | @key.setter 49 | def key(self, value): 50 | self._key = value 51 | 52 | # Value Type 53 | @property 54 | def type(self): 55 | return self._type 56 | @type.setter 57 | def type(self, value): 58 | self._type = value 59 | 60 | # Hint String 61 | @property 62 | def hint_string(self): 63 | return self._hint_string 64 | @hint_string.setter 65 | def hint_string(self, value): 66 | isAutoPushMatch = re.search(r'\"isAutoPush\"\)(\?.*?)(;|$)', value, re.DOTALL) 67 | if isAutoPushMatch: 68 | hint_string_list = [] 69 | isAutoPush = re.search('\?(.*?):', isAutoPushMatch.group(1), re.DOTALL) 70 | isNotAutoPush = re.search('\:(.*?);?$', isAutoPushMatch.group(1), re.DOTALL) 71 | if isAutoPush: 72 | hint_string_list.append(expandLocalizedString(cleanString(isAutoPush.group(1))).capitalize() + ' (OTA)') 73 | if isNotAutoPush: 74 | hint_string_list.append(expandLocalizedString(cleanString(isNotAutoPush.group(1))).capitalize() + ' (Manual)') 75 | if hint_string_list: 76 | self._hint_string = hint_string_list 77 | else: 78 | self._hint_string = expandLocalizedString(cleanString(value)) 79 | 80 | # Required 81 | @property 82 | def required(self): 83 | return self._required 84 | @required.setter 85 | def required(self, value): 86 | self._required = value 87 | 88 | # Optional 89 | @property 90 | def optional(self): 91 | return self._optional 92 | @optional.setter 93 | def optional(self, value): 94 | self._optional = value 95 | 96 | # Default Value 97 | @property 98 | def default_value(self): 99 | return self._default_value 100 | @default_value.setter 101 | def default_value(self, value): 102 | self._default_value = expandLocalizedString(cleanString(value)) 103 | 104 | # Available Values 105 | @property 106 | def available_values(self): 107 | return self._available_values 108 | @available_values.setter 109 | def available_values(self, value): 110 | self._available_values = value 111 | 112 | class knobSet(object): 113 | 114 | # PayloadType 115 | @property 116 | def payload_type(self): 117 | return self._payload_type 118 | @payload_type.setter 119 | def payload_type(self, value): 120 | self._payload_type = value 121 | 122 | # PayloadName 123 | @property 124 | def payload_name(self): 125 | return self._payload_name 126 | @payload_name.setter 127 | def payload_name(self, value): 128 | self._payload_name = value 129 | 130 | # Payloads 131 | @property 132 | def payloads(self): 133 | return self._payloads 134 | @payloads.setter 135 | def payloads(self, value): 136 | self._payloads = value 137 | 138 | # Platforms 139 | @property 140 | def platforms(self): 141 | return self._platforms 142 | @platforms.setter 143 | def platforms(self, value): 144 | self._platforms = value 145 | 146 | # SystemLevel 147 | @property 148 | def system_level(self): 149 | return self._system_level 150 | @system_level.setter 151 | def system_level(self, value): 152 | self._system_level = value.replace('\n', '') 153 | 154 | # UserLevel 155 | @property 156 | def user_level(self): 157 | return self._user_level 158 | @user_level.setter 159 | def user_level(self, value): 160 | self._user_level = value.replace('\n', '') 161 | 162 | # Unique 163 | @property 164 | def unique(self): 165 | return self._unique 166 | @unique.setter 167 | def unique(self, value): 168 | if value == 'false': 169 | self._unique = 'NO' 170 | elif value == 'true': 171 | self._unique = 'YES' 172 | else: 173 | print 'UNKNOWN UNIQUE: ' + value 174 | 175 | '''/////////////////////////////////////////////////////''' 176 | '''// FUNCTIONS //''' 177 | '''/////////////////////////////////////////////////////''' 178 | 179 | lowercase = lambda s: s[:1].lower() + s[1:] if s else '' 180 | 181 | # FIXME - cleanString removes the .loc(), but instead that means it's localized so that should be the trigger to expand a localized string. 182 | 183 | def cleanString(string): 184 | if string: 185 | return re.sub(r'\.loc\(\)', '', string).replace('\n', '') 186 | 187 | def expandLocalizedString(string): 188 | quoteMatch = '["\']' 189 | if string.startswith('"'): 190 | aString = string.replace("\'", "\\'") 191 | quoteMatch = '"' 192 | elif string.startswith("'"): 193 | aString = string.replace('"', '\"') 194 | quoteMatch = '\'' 195 | else: 196 | aString = string 197 | 198 | bString = re.sub(r'(^"|"$)', '', aString) 199 | cString = bString.replace('(', '\(') 200 | cleanedString = cString.replace(')', '\)') 201 | 202 | with open(filr_pm_localized_strings, 'r') as f: 203 | file_content = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ) 204 | 205 | # Get the localized string associated with passed string 206 | locString = re.search('["\']' + cleanedString + '["\']' + ': ["\'](.*?)["\'],', file_content, re.DOTALL) 207 | if locString: 208 | return locString.group(1) 209 | return string 210 | 211 | def knobSetList(file_path): 212 | with open(file_path, 'r') as f: 213 | file_content = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ) 214 | 215 | # Get all knobSetProperty names 216 | knobSetListMatch = re.search('knobSetProperties:\[([",a-zA-Z\n]+)\],', file_content) 217 | if knobSetListMatch: 218 | return knobSetListMatch.group(1).translate(None, ' "\n').split(',') 219 | 220 | def parseChildViews(knobSetPayload, knobSetExtendMatch, knobSetRealName, child_views, string): 221 | 222 | # Create an empty variable for the view last processed, used later to prevent infinite looping. 223 | last_views = [] 224 | 225 | # Loop through all view names from list child_views 226 | for view in child_views: 227 | 228 | # Get Title 229 | viewContentLabel = re.search(knobSetRealName + 'Label:.*?\({(.*?)\)},.*?:(Admin|SC)', string, re.DOTALL) 230 | if viewContentLabel: 231 | title = re.search('value:(["\'].*?)[,}]', viewContentLabel.group(1), re.DOTALL) 232 | if title: 233 | knobSetPayload.title = title.group(1) 234 | 235 | # Get Description 236 | viewContentDescription = re.search(knobSetRealName + 'Description:.*?\({(.*?)},.*?:(Admin|SC)', string, re.DOTALL) 237 | if viewContentDescription: 238 | description = re.search('value:(["\'].*?)[,}]', viewContentDescription.group(1), re.DOTALL) 239 | if description: 240 | knobSetPayload.description = description.group(1) 241 | 242 | # Get Available Values 243 | if view == knobSetRealName + 'SelectView': 244 | viewContentSelection = re.search(knobSetRealName + 'SelectView:.*?\({(.*?},.*?):(Admin|SC)', string, re.DOTALL) 245 | if viewContentSelection: 246 | viewContentSelectionObjects = re.search('objects:\[(.*?)\],', viewContentSelection.group(1), re.DOTALL) 247 | if viewContentSelectionObjects: 248 | available_values = re.findall(r'\{.+?\}', viewContentSelectionObjects.group(1).replace('\n', '')) 249 | if available_values: 250 | knobSetPayload.available_values = available_values 251 | 252 | # Get the contents of the view's method, try three different regexes to match multiple scenarios. 253 | viewContent = re.search(view + ':.*?\({(.*?}\)?,[a-zA-Z]+):(Admin|SC)', string, re.DOTALL) 254 | if not viewContent: 255 | 256 | # Matches last method 257 | viewContent = re.search(view + ':.*?\({(.*?)}\)}\)', string, re.DOTALL) 258 | if not viewContent: 259 | 260 | # Matches any method "starting" with view name as some have different names 261 | viewContent = re.search(view + '[a-zA-Z]+:.*?\({(.*?}\)?,[a-zA-Z]+):(Admin|SC)', string, re.DOTALL) 262 | 263 | if viewContent: 264 | 265 | # Check if method body contains variable (field)[Cc]ontentValueKey 266 | contentValueKey = re.search('[Cc]ontentValueKey:"(.*?)"', viewContent.group(1), re.DOTALL) 267 | if contentValueKey: 268 | 269 | # FIXME - Here 270 | 271 | # Check if contentValueKey matches current knob set name. 272 | # If it matches, get title, and description info from this body. 273 | if (( contentValueKey.group(1).lower() == knobSetRealName.lower() ) or ( contentValueKey.group(1).lower() == re.sub(r'^_', '', knobSetRealName.lower())) or ( contentValueKey.group(1).lower() == 'name' )): 274 | label = re.search('label:(["\'].*?)(?:,[a-zA-Z]+|})', viewContent.group(1), re.DOTALL) 275 | if label: 276 | knobSetPayload.title = label.group(1) 277 | 278 | title = re.search('title:(["\'].*?)(?:,[a-zA-Z]+|})', viewContent.group(1), re.DOTALL) 279 | if title: 280 | knobSetPayload.title = title.group(1) 281 | 282 | description = re.search('description:(["\'].*?)(?:,[a-zA-Z]+|})', viewContent.group(1), re.DOTALL) 283 | if description: 284 | knobSetPayload.description = description.group(1) 285 | 286 | fieldObjects = re.search('(?:(?:field)?[Oo]bjects|items):\[(.*?)\],', viewContent.group(1), re.DOTALL) 287 | if fieldObjects: 288 | available_values = re.findall(r'\{.+?\}', fieldObjects.group(1).replace('\n', '')) 289 | if available_values: 290 | knobSetPayload.available_values = available_values 291 | 292 | hint = re.search('[Hh]int:(.*?)[,}]', viewContent.group(1), re.DOTALL) 293 | if hint: 294 | knobSetPayload.hint_string = hint.group(1) 295 | break 296 | 297 | # FIXME - If contentValueKey didn't match current knob set name, get all funcion names in knobSetExtendMatch. 298 | '''#if view.startswith(knobSetRealName) or view.startswith(re.sub(r'^_', '', knobSetRealName)):''' 299 | 300 | # This can be removed. 301 | functionContentMatch = re.findall('[,}]([_a-zA-Z]+:function.*?\.property\(\".*?\")', knobSetExtendMatch[0].replace('\n', ''), re.DOTALL) 302 | if functionContentMatch: 303 | 304 | # Check if contentValueKey matches the property value of any function in knobSetExtendMatch 305 | # Check if that property value matches current knob set name. 306 | # If it matches, get title, and description info from this body. 307 | functionContent = re.search(contentValueKey.group(1) + ':function.*?\.property\(\"(.*?)\"', knobSetExtendMatch[0].replace('\n', ''), re.DOTALL) 308 | if functionContent and functionContent.group(1).lower() == knobSetRealName.lower(): 309 | label = re.search('label:(["\'].*?)(?:,[a-zA-Z]+|})', viewContent.group(1), re.DOTALL) 310 | if label: 311 | knobSetPayload.title = label.group(1) 312 | 313 | title = re.search('title:(["\'].*?)(?:,[a-zA-Z]+|})', viewContent.group(1), re.DOTALL) 314 | if title: 315 | knobSetPayload.title = title.group(1) 316 | 317 | description = re.search('description:(["\'].*?)(?:,[a-zA-Z]+|})', viewContent.group(1), re.DOTALL) 318 | if description: 319 | knobSetPayload.description = description.group(1) 320 | 321 | fieldObjects = re.search('(?:(?:field)?[Oo]bjects|items):\[(.*?)\],', viewContent.group(1), re.DOTALL) 322 | if fieldObjects: 323 | available_values = re.findall(r'\{.+?\}', fieldObjects.group(1).replace('\n', '')) 324 | if available_values: 325 | knobSetPayload.available_values = available_values 326 | 327 | hint = re.search('[Hh]int:(.*?)[,}]', viewContent.group(1), re.DOTALL) 328 | if hint: 329 | knobSetPayload.hint_string = hint.group(1) 330 | break 331 | 332 | # If string doesn't contain 'fieldContentValueKey' it might contain additinal subviews. 333 | # Loop through those as well. 334 | knobSetSubViews = re.search('childViews:\[(.*?)\],', viewContent.group(1), re.DOTALL) 335 | if knobSetSubViews and not set(knobSetSubViews.group(1).translate(None, ' "').split(',')).intersection(last_views): 336 | last_views = knobSetSubViews.group(1).translate(None, ' "').split(',') 337 | parseChildViews(knobSetPayload, knobSetExtendMatch, knobSetRealName, last_views, string) 338 | 339 | def knobSetInfo(file_path, knobSetPropertyName): 340 | 341 | # Instantiate a new knobSet class instance 342 | newKnobSet = knobSet() 343 | 344 | # Open javascript-packed.js for parsing 345 | with open(file_path, 'r') as f: 346 | file_content = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ) 347 | 348 | # Get KnobSet 'Name' used in functions 349 | knobSetNameMatch = re.search(',Admin.([a-zA-Z]+?KnobSet).*?profilePropertyName[=:]"' + knobSetPropertyName, file_content) 350 | if not knobSetNameMatch: 351 | print 'Could not find KnobSet Name for: ' + knobSetPropertyName 352 | sys.exit() 353 | else: 354 | knobSetName = knobSetNameMatch.group(1) 355 | 356 | # Get KnobSet 'DisplayName' string to extract the file name for the ruby model 357 | knobSetNumLinesMatch = re.search(',Admin.' + knobSetName + '.*?numLines[=:]"(.*?)"', file_content) 358 | if not knobSetNumLinesMatch: 359 | print 'Could not find KnobSet DisplayName for: ' + knobSetPropertyName 360 | sys.exit() 361 | else: 362 | knobSetDisplayName = re.sub(r'(^_global_?|^_|_num_lines$)', '', knobSetNumLinesMatch.group(1)) 363 | 364 | # Get path to ruby model and verify it exists. 365 | modelPath = folder_backend_models + '/' + knobSetDisplayName + '.rb' 366 | if not os.path.isfile(modelPath): 367 | print 'File doesn\'t exist: ' + modelPath 368 | 369 | # Open ruby model file for parsing 370 | with open(modelPath, 'r') as fmodel: 371 | model_file_content = mmap.mmap(fmodel.fileno(), 0, prot=mmap.PROT_READ) 372 | 373 | # Get PayloadType (or Types if the KnobSet allows multiple) 374 | payloadType = re.search(r'@@payload_type[ ]+=[ ]+?[\'"]?(.*?)[\'"]?[\n]', model_file_content, re.DOTALL) 375 | if payloadType: 376 | newKnobSet.payload_type = payloadType.group(1) 377 | else: 378 | payloadTypes = re.search(r'@@payload_types[ ]+=[ ]+?{(.*?)}', model_file_content, re.DOTALL) 379 | if payloadTypes: 380 | payloadTypeStringList = [] 381 | for payloadTypeListItem in payloadTypes.group(1).translate(None, ' \n').split(','): 382 | if not payloadTypeListItem: 383 | continue 384 | payloadTypeGroup = re.search(r'^(.*?)\=\>(.*?)$', payloadTypeListItem, re.DOTALL) 385 | if payloadTypeGroup: 386 | payloadTypeStringList.append(payloadTypeGroup.group(1) + ': ' + payloadTypeGroup.group(2)) 387 | newKnobSet.payload_type = payloadTypeStringList 388 | 389 | # Get boolean wether this payload is unique or supports multiple payloads 390 | isUnique = re.search(r'@@is_unique[ ]+=[ ]+?[\'"]?(.*?)[\'"]?[\n]', model_file_content, re.DOTALL) 391 | if isUnique: 392 | newKnobSet.unique= isUnique.group(1) 393 | 394 | # Get the name for the KnobSet, shown as the title for the payload group 395 | payloadName = re.search(r'@@payload_name[ ]+=[ ]+?[\'"]?(.*?)[\'"]?[\n]', model_file_content, re.DOTALL) 396 | if payloadName: 397 | newKnobSet.payload_name = payloadName.group(1) 398 | 399 | # Get SCMixin Info for 'knobSetName' 400 | knobSetSCMixinMatch = re.findall('SC.mixin\(Admin.' + knobSetName + ',{(.*?)}\),Admin.' , file_content, re.DOTALL) 401 | if not knobSetSCMixinMatch: 402 | knobSetSCMixinMatch = re.findall('Admin.' + knobSetName + '.mixin\({(.*?)}\),Admin.' , file_content, re.DOTALL) 403 | if not knobSetSCMixinMatch: 404 | print 'Could not find mixin for: ' + knobSetSCMixinMatch 405 | sys.exit() 406 | 407 | # Get what platforms payload supports 408 | platformTypes = re.search('platformTypes:"(.*?)"', knobSetSCMixinMatch[0], re.DOTALL) 409 | if platformTypes: 410 | newKnobSet.platforms = platformTypes.group(1).split() 411 | 412 | # Get if payload is available as 'System Level' 413 | systemLevel = re.search('systemLevel:"(.*?)"', knobSetSCMixinMatch[0], re.DOTALL) 414 | if not systemLevel: 415 | systemLevel = re.findall('Admin.' + knobSetName + '.systemLevel=(.*?),Admin.' , file_content, re.DOTALL) 416 | if not systemLevel: 417 | 418 | # FIXME - THIS IS NOT RIGHT 419 | systemLevel = re.findall('Admin.' + knobSetName + '.*?systemLevel[=:](.*?),.*?}\),Admin.' , file_content, re.DOTALL) 420 | if systemLevel: 421 | newKnobSet.system_level = systemLevel[0] 422 | else: 423 | newKnobSet.system_level = systemLevel.group(1).split() 424 | 425 | # Get if payload is available as 'User Level' 426 | userLevel = re.search('userLevel:"(.*?)"', knobSetSCMixinMatch[0], re.DOTALL) 427 | if not userLevel: 428 | userLevel = re.findall('Admin.' + knobSetName + '.userLevel=(.*?),Admin.' , file_content, re.DOTALL) 429 | if not userLevel: 430 | 431 | # FIXME - THIS IS NOT RIGHT 432 | userLevel = re.findall('Admin.' + knobSetName + '.*?userLevel[=:](.*?)}\),Admin.' , file_content, re.DOTALL) 433 | if userLevel: 434 | newKnobSet.user_level = userLevel[0] 435 | else: 436 | newKnobSet.user_level = userLevel.group(1).split() 437 | 438 | # Get KnobSetExtendView Info for 'knobSetName' 439 | knobSetViewExtendMatch = re.findall(',Admin.' + knobSetName + 'View=Admin.KnobSetView.extend\({(.*?)}\),Admin.' , file_content, re.DOTALL) 440 | if not knobSetViewExtendMatch: 441 | print 'Could not find KnobSetExtendView for: ' + knobSetName 442 | sys.exit() 443 | 444 | # Get all child view names from KnobSetExtendedView 445 | knobSetViewsMatch = re.findall('childViews:\[(.*?)\],', knobSetViewExtendMatch[0].replace('\n', ''), re.DOTALL) 446 | if knobSetViewsMatch: 447 | knobSetViews = (",".join(knobSetViewsMatch)).translate(None, ' "').split(',') 448 | 449 | # Get KnobSetExtend Info for 'knobSetName' 450 | knobSetExtendMatch = re.findall(',Admin.' + knobSetName + '=Admin.KnobSet.extend\({(.*?)}\),Admin.' , file_content, re.DOTALL) 451 | if not knobSetNameMatch: 452 | print 'Could not find KnobSetExtend for' + knobSet 453 | sys.exit() 454 | 455 | knobSetPayloads = [] 456 | knobSetProperties = re.findall(',?([_a-zA-Z]+:SC.Record.attr\(.*?)}\)', knobSetExtendMatch[0].replace('\n', ''), re.DOTALL) 457 | for knobSetProperty in knobSetProperties: 458 | 459 | # Instantiate new payload class 460 | knobSetPayload = payload() 461 | 462 | # Get the knobSetProperty: name 463 | knobSetRealNameMatch = re.search(r'^(.*?):', knobSetProperty, re.DOTALL) 464 | if not knobSetRealNameMatch: 465 | print 'Could not find KnobSetRealName for' + knobSet 466 | sys.exit() 467 | elif knobSetRealNameMatch.group(1).startswith('real'): 468 | knobSetRealName = lowercase(re.sub(r'^real', '', knobSetRealNameMatch.group(1))) 469 | else: 470 | knobSetRealName = knobSetRealNameMatch.group(1) 471 | 472 | # Get the knobSetProperty: title, description 473 | parseChildViews(knobSetPayload, knobSetExtendMatch, knobSetRealName, knobSetViews, knobSetViewExtendMatch[0].replace('\n', '')) 474 | 475 | # Get Payload Key 476 | propertyKeyMatch = re.search('key:"(.*?)"', knobSetProperty, re.DOTALL) 477 | if propertyKeyMatch: 478 | knobSetPayload.key = propertyKeyMatch.group(1).replace('\n', '') 479 | 480 | # Get Payload Value Type 481 | propertyTypeMatch = re.search('SC.Record.attr\((.*?),', knobSetProperty, re.DOTALL) 482 | if propertyTypeMatch: 483 | knobSetPayload.type = propertyTypeMatch.group(1).replace('\n', '') 484 | 485 | # Get Payload Required 486 | propertyRequiredMatch = re.search('isRequired:(YES|NO)', knobSetProperty, re.DOTALL) 487 | if propertyRequiredMatch: 488 | knobSetPayload.required = propertyRequiredMatch.group(1).replace('\n', '') 489 | 490 | # Get Payload Default Value 491 | try: 492 | #print 'knobSetProperty: ' + knobSetProperty 493 | if knobSetPayload.type == 'Array' : 494 | regex_string = 'defaultValue:.*\[(.*?)\]' 495 | else: 496 | regex_string = 'defaultValue:(.*)[,}]?' 497 | except AttributeError: 498 | regex_string = 'defaultValue:(.*)[,}]?' 499 | 500 | propertyDefaultValueMatch = re.search(r'%s' % regex_string, knobSetProperty, re.DOTALL) 501 | if propertyDefaultValueMatch: 502 | try: 503 | if knobSetPayload.type == 'Array': 504 | knobSetPayload.default_value = re.findall(r'\{.+?\}', propertyDefaultValueMatch.group(1).replace('\n', '')) 505 | else: 506 | knobSetPayload.default_value = propertyDefaultValueMatch.group(1).replace('\n', '') 507 | except AttributeError: 508 | knobSetPayload.default_value = propertyDefaultValueMatch.group(1).replace('\n', '') 509 | 510 | # Add payload to array 511 | knobSetPayloads.append(knobSetPayload) 512 | 513 | # Return all payloads found 514 | newKnobSet.payloads = knobSetPayloads 515 | return newKnobSet 516 | 517 | def printKnobSet(file_path, knobSetPropertyName): 518 | 519 | # Populate a new knobset class instance for selected knobSet 520 | newKnobSet = knobSetInfo(file_path, knobSetPropertyName) 521 | 522 | # Add newline 523 | print '' 524 | 525 | try: 526 | print '%18s' % 'Payload Name: ' + newKnobSet.payload_name 527 | except AttributeError: 528 | print '%17s' % 'Payload Name:' 529 | 530 | try: 531 | if isinstance(newKnobSet.payload_type, list): 532 | firstValue = True 533 | for payloadType in newKnobSet.payload_type: 534 | if firstValue: 535 | firstValue = False 536 | print '%18s' % 'Payload Types: ' + payloadType 537 | else: 538 | print '%18s' % '' + payloadType 539 | else: 540 | print '%18s' % 'Payload Type: ' + newKnobSet.payload_type 541 | except AttributeError: 542 | print '%17s' % 'Payload Type:' 543 | 544 | try: 545 | print '%18s' % 'Unique: ' + newKnobSet.unique 546 | except AttributeError: 547 | print '%17s' % 'Unique:' 548 | 549 | # Using the values in the knobSet class, print to stdout: 550 | # User Level 551 | try: 552 | print '%18s' % 'UserLevel: ' + newKnobSet.user_level 553 | except AttributeError: 554 | print '%17s' % 'UserLevel:' 555 | 556 | # System Level 557 | try: 558 | print '%18s' % 'SystemLevel: ' + newKnobSet.system_level 559 | except AttributeError: 560 | print '%17s' % 'SystemLevel:' 561 | 562 | # Platforms 563 | try: 564 | sys.stdout.write('%18s' % 'Platforms: ') 565 | print(','.join(newKnobSet.platforms)) 566 | except AttributeError: 567 | pass 568 | 569 | # PayloadKey Info 570 | for p in newKnobSet.payloads: 571 | print '\n%18s' % 'PayloadKey: ' + sgr_color.bld + p.key + sgr_color.clr 572 | 573 | try: 574 | print '%18s' % 'Title: ' + expandLocalizedString(p.title) 575 | except AttributeError: 576 | print '%17s' % 'Title:' 577 | 578 | try: 579 | print '%18s' % 'Description: ' + p.description 580 | except AttributeError: 581 | print '%17s' % 'Description:' 582 | 583 | print '%18s' % 'Type: ' + p.type 584 | 585 | try: 586 | print '%18s' % 'Required: ' + p.required 587 | except AttributeError: 588 | pass 589 | 590 | try: 591 | print '%18s' % 'Optional: ' + p.optional 592 | except AttributeError: 593 | pass 594 | 595 | try: 596 | if p.hint_string and isinstance(p.hint_string, list): 597 | firstValue = True 598 | for hint_string in p.hint_string: 599 | if firstValue: 600 | firstValue = False 601 | print '%18s' % 'Hint String: ' + hint_string 602 | else: 603 | print '%18s' % '' + hint_string 604 | elif p.default_value: 605 | print '%18s' % 'Hint String: ' + p.hint_string 606 | except AttributeError: 607 | pass 608 | 609 | try: 610 | firstValue = True 611 | for available_value in p.available_values: 612 | value = re.search('value:(["\']?.*?)[,}]', available_value, re.DOTALL) 613 | if value: 614 | if firstValue: 615 | firstValue = False 616 | print '%18s' % 'AvailableValues: ' + value.group(1) 617 | else: 618 | print '%18s' % '' + value.group(1) 619 | except AttributeError: 620 | pass 621 | 622 | try: 623 | if p.default_value and isinstance(p.default_value, list): 624 | sys.stdout.write('%18s' % 'DefaultValue: ') 625 | print("\t\n\t\t\t".join(p.default_value)) 626 | elif p.default_value: 627 | print '%18s' % 'DefaultValue: ' + p.default_value 628 | except AttributeError: 629 | pass 630 | sys.exit() 631 | 632 | def main(argv): 633 | 634 | # Parse input arguments 635 | parser = argparse.ArgumentParser() 636 | parser.add_argument('-f', '--file', type=str) 637 | parser.add_argument('-l', '--list', action='store_true') 638 | parser.add_argument('-k', '--knobset', type=str) 639 | args = parser.parse_args() 640 | 641 | # Get path to javascript file. 642 | if args.file: 643 | file_path = args.file 644 | else: 645 | file_path = file_pm_javascript_packed 646 | 647 | # Verify passed file path is valid 648 | if not os.path.isfile(file_path): 649 | print 'file doesn\'t exist: ' + file_path 650 | sys.exit() 651 | 652 | # Create array of all available KnobSet property names from the 'knobSetProperties' array. 653 | knobSets = knobSetList(file_path) 654 | if not knobSets: 655 | print 'Found no KnobSet array in passed file, are you sure Server.app is installed and the file path is correct?' 656 | sys.exit() 657 | 658 | # If option -l/--list was passed, list all KnobSets available 659 | if args.list: 660 | print("\n".join(sorted(knobSets))) 661 | sys.exit() 662 | 663 | # If option -k/--knobset was passed, print info for passed KnobSet 664 | if args.knobset: 665 | if not args.knobset in knobSets: 666 | print args.knobset + ' is not a valid KnobSet.' 667 | print 'To see all available KnobSets, use the -l flag' 668 | sys.exit() 669 | printKnobSet(file_path, args.knobset) 670 | 671 | print 'No option was passed.' 672 | print 'To see all available KnobSets, use the -l flag' 673 | 674 | if __name__ == "__main__": 675 | main(sys.argv[1:]) --------------------------------------------------------------------------------