From 4566764a90f11d4525c426849e42a0a50d7299e5 Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Tue, 13 Dec 2016 15:57:12 -0800 Subject: [PATCH] Move preferences functions and classes into munkiprefs.py --- code/client/munkilib/munkicommon.py | 172 ++----------------------- code/client/munkilib/munkiprefs.py | 191 ++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+), 161 deletions(-) create mode 100755 code/client/munkilib/munkiprefs.py diff --git a/code/client/munkilib/munkicommon.py b/code/client/munkilib/munkicommon.py index de6e4a49f..569a1539b 100755 --- a/code/client/munkilib/munkicommon.py +++ b/code/client/munkilib/munkicommon.py @@ -46,6 +46,7 @@ from xml.dom import minidom import munkistatus +import munkiprefs import FoundationPlist import LaunchServices @@ -54,14 +55,6 @@ # No name 'Foo' in module 'Bar' warnings. Disable them. # pylint: disable=E0611 from Foundation import NSDate, NSMetadataQuery, NSPredicate, NSRunLoop -from Foundation import CFPreferencesAppSynchronize -from Foundation import CFPreferencesCopyAppValue -from Foundation import CFPreferencesCopyKeyList -from Foundation import CFPreferencesSetValue -from Foundation import kCFPreferencesAnyUser -from Foundation import kCFPreferencesCurrentUser -from Foundation import kCFPreferencesCurrentHost - from SystemConfiguration import SCDynamicStoreCopyConsoleUser # pylint: enable=E0611 @@ -83,7 +76,7 @@ # our preferences "bundle_id" -BUNDLE_ID = 'ManagedInstalls' +BUNDLE_ID = munkiprefs.BUNDLE_ID # the following two items are not used internally by munki # any longer, but remain for backwards compatibility with @@ -110,10 +103,6 @@ class Error(Exception): """Class for domain specific exceptions.""" -class PreferencesError(Error): - """There was an error reading the preferences plist.""" - - class TimeoutError(Error): """Timeout limit exceeded since last I/O.""" @@ -1135,154 +1124,15 @@ def isApplication(pathname): # managed installs preferences/metadata ##################################################### -class Preferences(object): - """Class which directly reads/writes Apple CF preferences.""" - - def __init__(self, bundle_id, user=kCFPreferencesAnyUser): - """Init. - - Args: - bundle_id: str, like 'ManagedInstalls' - """ - if bundle_id.endswith('.plist'): - bundle_id = bundle_id[:-6] - self.bundle_id = bundle_id - self.user = user - - def __iter__(self): - """Iterator for keys in the specific 'level' of preferences; this - will fail to iterate all available keys for the preferences domain - since OS X reads from multiple 'levels' and composites them.""" - keys = CFPreferencesCopyKeyList( - self.bundle_id, self.user, kCFPreferencesCurrentHost) - if keys is not None: - for i in keys: - yield i - - def __contains__(self, pref_name): - """Since this uses CFPreferencesCopyAppValue, it will find a preference - regardless of the 'level' at which it is stored""" - pref_value = CFPreferencesCopyAppValue(pref_name, self.bundle_id) - return pref_value is not None - - def __getitem__(self, pref_name): - """Get a preference value. Normal OS X preference search path applies""" - return CFPreferencesCopyAppValue(pref_name, self.bundle_id) - - def __setitem__(self, pref_name, pref_value): - """Sets a preference. if the user is kCFPreferencesCurrentUser, the - preference actually gets written at the 'ByHost' level due to the use - of kCFPreferencesCurrentHost""" - CFPreferencesSetValue( - pref_name, pref_value, self.bundle_id, self.user, - kCFPreferencesCurrentHost) - CFPreferencesAppSynchronize(self.bundle_id) - - def __delitem__(self, pref_name): - """Delete a preference""" - self.__setitem__(pref_name, None) - - def __repr__(self): - """Return a text representation of the class""" - return '<%s %s>' % (self.__class__.__name__, self.bundle_id) - - def get(self, pref_name, default=None): - """Return a preference or the default value""" - if not pref_name in self: - return default - else: - return self.__getitem__(pref_name) - - -class ManagedInstallsPreferences(Preferences): - """Preferences which are read using 'normal' OS X preferences precedence: - Managed Preferences (MCX or Configuration Profile) - ~/Library/Preferences/ByHost/ManagedInstalls.XXXX.plist - ~/Library/Preferences/ManagedInstalls.plist - /Library/Preferences/ManagedInstalls.plist - Preferences are written to - /Library/Preferences/ManagedInstalls.plist - Since this code is usually run as root, ~ is root's home dir""" - def __init__(self): - Preferences.__init__(self, 'ManagedInstalls', kCFPreferencesAnyUser) - - -class SecureManagedInstallsPreferences(Preferences): - """Preferences which are read using 'normal' OS X preferences precedence: - Managed Preferences (MCX or Configuration Profile) - ~/Library/Preferences/ByHost/ManagedInstalls.XXXX.plist - ~/Library/Preferences/ManagedInstalls.plist - /Library/Preferences/ManagedInstalls.plist - Preferences are written to - ~/Library/Preferences/ByHost/ManagedInstalls.XXXX.plist - Since this code is usually run as root, ~ is root's home dir""" - def __init__(self): - Preferences.__init__(self, 'ManagedInstalls', kCFPreferencesCurrentUser) - - -def reload_prefs(): - """Uses CFPreferencesAppSynchronize(BUNDLE_ID) - to make sure we have the latest prefs. Call this - if you have modified /Library/Preferences/ManagedInstalls.plist - or /var/root/Library/Preferences/ManagedInstalls.plist directly""" - CFPreferencesAppSynchronize(BUNDLE_ID) - - -def set_pref(pref_name, pref_value): - """Sets a preference, writing it to - /Library/Preferences/ManagedInstalls.plist. - This should normally be used only for 'bookkeeping' values; - values that control the behavior of munki may be overridden - elsewhere (by MCX, for example)""" - try: - CFPreferencesSetValue( - pref_name, pref_value, BUNDLE_ID, - kCFPreferencesAnyUser, kCFPreferencesCurrentHost) - CFPreferencesAppSynchronize(BUNDLE_ID) - except BaseException: - pass - - -def pref(pref_name): - """Return a preference. Since this uses CFPreferencesCopyAppValue, - Preferences can be defined several places. Precedence is: - - MCX - - /var/root/Library/Preferences/ManagedInstalls.plist - - /Library/Preferences/ManagedInstalls.plist - - default_prefs defined here. - """ - default_prefs = { - 'ManagedInstallDir': '/Library/Managed Installs', - 'SoftwareRepoURL': 'http://munki/repo', - 'ClientIdentifier': '', - 'LogFile': '/Library/Managed Installs/Logs/ManagedSoftwareUpdate.log', - 'LoggingLevel': 1, - 'LogToSyslog': False, - 'InstallAppleSoftwareUpdates': False, - 'AppleSoftwareUpdatesOnly': False, - 'SoftwareUpdateServerURL': '', - 'DaysBetweenNotifications': 1, - 'LastNotifiedDate': NSDate.dateWithTimeIntervalSince1970_(0), - 'UseClientCertificate': False, - 'SuppressUserNotification': False, - 'SuppressAutoInstall': False, - 'SuppressStopButtonOnInstall': False, - 'PackageVerificationMode': 'hash', - 'FollowHTTPRedirects': 'none', - 'UnattendedAppleUpdates': False, - 'PerformAuthRestarts': False, - } - pref_value = CFPreferencesCopyAppValue(pref_name, BUNDLE_ID) - if pref_value is None: - pref_value = default_prefs.get(pref_name) - # we're using a default value. We'll write it out to - # /Library/Preferences/.plist for admin - # discoverability - set_pref(pref_name, pref_value) - if isinstance(pref_value, NSDate): - # convert NSDate/CFDates to strings - pref_value = str(pref_value) - return pref_value +# these classes and functions were originally defined in this module; +# these assignments are for backwards-compatibility with other code that +# called them from this module +Preferences = munkiprefs.Preferences +ManagedInstallsPreferences = munkiprefs.ManagedInstallsPreferences +SecureManagedInstallsPreferences = munkiprefs.SecureManagedInstallsPreferences +reload_prefs = munkiprefs.reload_prefs +set_pref = munkiprefs.set_pref +pref = munkiprefs.pref ##################################################### # Apple package utilities diff --git a/code/client/munkilib/munkiprefs.py b/code/client/munkilib/munkiprefs.py new file mode 100755 index 000000000..98f40e5ec --- /dev/null +++ b/code/client/munkilib/munkiprefs.py @@ -0,0 +1,191 @@ +#!/usr/bin/python +# encoding: utf-8 +# +# Copyright 2009-2016 Greg Neagle. +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +munkiprefs + +Created by Greg Neagle on 2016-12-13. + +Preferences functions and classes used by the munki tools. +""" +# PyLint cannot properly find names inside Cocoa libraries, so issues bogus +# No name 'Foo' in module 'Bar' warnings. Disable them. +# pylint: disable=E0611 +from Foundation import NSDate +from Foundation import CFPreferencesAppSynchronize +from Foundation import CFPreferencesCopyAppValue +from Foundation import CFPreferencesCopyKeyList +from Foundation import CFPreferencesSetValue +from Foundation import kCFPreferencesAnyUser +from Foundation import kCFPreferencesCurrentUser +from Foundation import kCFPreferencesCurrentHost +# pylint: enable=E0611 + +##################################################### +# managed installs preferences/metadata +##################################################### + +# our preferences "bundle_id" +BUNDLE_ID = 'ManagedInstalls' + +class Preferences(object): + """Class which directly reads/writes Apple CF preferences.""" + + def __init__(self, bundle_id, user=kCFPreferencesAnyUser): + """Init. + + Args: + bundle_id: str, like 'ManagedInstalls' + """ + if bundle_id.endswith('.plist'): + bundle_id = bundle_id[:-6] + self.bundle_id = bundle_id + self.user = user + + def __iter__(self): + """Iterator for keys in the specific 'level' of preferences; this + will fail to iterate all available keys for the preferences domain + since OS X reads from multiple 'levels' and composites them.""" + keys = CFPreferencesCopyKeyList( + self.bundle_id, self.user, kCFPreferencesCurrentHost) + if keys is not None: + for i in keys: + yield i + + def __contains__(self, pref_name): + """Since this uses CFPreferencesCopyAppValue, it will find a preference + regardless of the 'level' at which it is stored""" + pref_value = CFPreferencesCopyAppValue(pref_name, self.bundle_id) + return pref_value is not None + + def __getitem__(self, pref_name): + """Get a preference value. Normal OS X preference search path applies""" + return CFPreferencesCopyAppValue(pref_name, self.bundle_id) + + def __setitem__(self, pref_name, pref_value): + """Sets a preference. if the user is kCFPreferencesCurrentUser, the + preference actually gets written at the 'ByHost' level due to the use + of kCFPreferencesCurrentHost""" + CFPreferencesSetValue( + pref_name, pref_value, self.bundle_id, self.user, + kCFPreferencesCurrentHost) + CFPreferencesAppSynchronize(self.bundle_id) + + def __delitem__(self, pref_name): + """Delete a preference""" + self.__setitem__(pref_name, None) + + def __repr__(self): + """Return a text representation of the class""" + return '<%s %s>' % (self.__class__.__name__, self.bundle_id) + + def get(self, pref_name, default=None): + """Return a preference or the default value""" + if not pref_name in self: + return default + else: + return self.__getitem__(pref_name) + + +class ManagedInstallsPreferences(Preferences): + """Preferences which are read using 'normal' OS X preferences precedence: + Managed Preferences (MCX or Configuration Profile) + ~/Library/Preferences/ByHost/ManagedInstalls.XXXX.plist + ~/Library/Preferences/ManagedInstalls.plist + /Library/Preferences/ManagedInstalls.plist + Preferences are written to + /Library/Preferences/ManagedInstalls.plist + Since this code is usually run as root, ~ is root's home dir""" + def __init__(self): + Preferences.__init__(self, 'ManagedInstalls', kCFPreferencesAnyUser) + + +class SecureManagedInstallsPreferences(Preferences): + """Preferences which are read using 'normal' OS X preferences precedence: + Managed Preferences (MCX or Configuration Profile) + ~/Library/Preferences/ByHost/ManagedInstalls.XXXX.plist + ~/Library/Preferences/ManagedInstalls.plist + /Library/Preferences/ManagedInstalls.plist + Preferences are written to + ~/Library/Preferences/ByHost/ManagedInstalls.XXXX.plist + Since this code is usually run as root, ~ is root's home dir""" + def __init__(self): + Preferences.__init__(self, 'ManagedInstalls', kCFPreferencesCurrentUser) + + +def reload_prefs(): + """Uses CFPreferencesAppSynchronize(BUNDLE_ID) + to make sure we have the latest prefs. Call this + if you have modified /Library/Preferences/ManagedInstalls.plist + or /var/root/Library/Preferences/ManagedInstalls.plist directly""" + CFPreferencesAppSynchronize(BUNDLE_ID) + + +def set_pref(pref_name, pref_value): + """Sets a preference, writing it to + /Library/Preferences/ManagedInstalls.plist. + This should normally be used only for 'bookkeeping' values; + values that control the behavior of munki may be overridden + elsewhere (by MCX, for example)""" + try: + CFPreferencesSetValue( + pref_name, pref_value, BUNDLE_ID, + kCFPreferencesAnyUser, kCFPreferencesCurrentHost) + CFPreferencesAppSynchronize(BUNDLE_ID) + except BaseException: + pass + + +def pref(pref_name): + """Return a preference. Since this uses CFPreferencesCopyAppValue, + Preferences can be defined several places. Precedence is: + - MCX + - /var/root/Library/Preferences/ManagedInstalls.plist + - /Library/Preferences/ManagedInstalls.plist + - default_prefs defined here. + """ + default_prefs = { + 'ManagedInstallDir': '/Library/Managed Installs', + 'SoftwareRepoURL': 'http://munki/repo', + 'ClientIdentifier': '', + 'LogFile': '/Library/Managed Installs/Logs/ManagedSoftwareUpdate.log', + 'LoggingLevel': 1, + 'LogToSyslog': False, + 'InstallAppleSoftwareUpdates': False, + 'AppleSoftwareUpdatesOnly': False, + 'SoftwareUpdateServerURL': '', + 'DaysBetweenNotifications': 1, + 'LastNotifiedDate': NSDate.dateWithTimeIntervalSince1970_(0), + 'UseClientCertificate': False, + 'SuppressUserNotification': False, + 'SuppressAutoInstall': False, + 'SuppressStopButtonOnInstall': False, + 'PackageVerificationMode': 'hash', + 'FollowHTTPRedirects': 'none', + 'UnattendedAppleUpdates': False, + 'PerformAuthRestarts': False, + } + pref_value = CFPreferencesCopyAppValue(pref_name, BUNDLE_ID) + if pref_value is None: + pref_value = default_prefs.get(pref_name) + # we're using a default value. We'll write it out to + # /Library/Preferences/.plist for admin + # discoverability + set_pref(pref_name, pref_value) + if isinstance(pref_value, NSDate): + # convert NSDate/CFDates to strings + pref_value = str(pref_value) + return pref_value