def test_encrypt_subfield(): field = Setting(value={'name': 'ANSIBLE'}) encrypted = field.value = encryption.encrypt_field(field, 'value', subfield='name') assert encryption.decrypt_field(field, 'value', subfield='name') == 'ANSIBLE' assert encrypted.startswith('$encrypted$UTF8$AESCBC$')
def test_encrypt_field_force_disable_unicode(): value = u"NothingSpecial" field = Setting(value=value) encrypted = field.value = encryption.encrypt_field(field, 'value', skip_utf8=True) assert "UTF8" not in encrypted assert encryption.decrypt_field(field, 'value') == value
def _handle_encryption(self, method, key, value): if value is not empty and self.registry.is_setting_encrypted(key): # If the setting exists in the database, we'll use its primary key # as part of the AES key when encrypting/decrypting obj_id = self.cache.get(Setting.get_cache_id_key(key), default=empty) if obj_id is empty: logger.info( 'Efficiency notice: Corresponding id not stored in cache %s', Setting.get_cache_id_key(key)) obj_id = getattr(self._get_setting_from_db(key), 'pk', None) elif obj_id == SETTING_CACHE_NONE: obj_id = None return method(TransientSetting(pk=obj_id, value=value), 'value') # If the field in question isn't an "encrypted" field, this function is # a no-op; it just returns the provided value return value
def test_setting_signleton_retrieve_hierachy(api_request, dummy_setting): with dummy_setting('FOO_BAR', field_class=fields.IntegerField, default=0, category='FooBar', category_slug='foobar'): response = api_request( 'get', reverse('api:setting_singleton_detail', kwargs={'category_slug': 'foobar'})) assert response.data['FOO_BAR'] == 0 s = Setting(key='FOO_BAR', value=1) s.save() response = api_request( 'get', reverse('api:setting_singleton_detail', kwargs={'category_slug': 'foobar'})) assert response.data['FOO_BAR'] == 1
def handle_setting_change(key, for_delete=False): # When a setting changes or is deleted, remove its value from cache along # with any other settings that depend on it. setting_keys = [key] for dependent_key in settings_registry.get_dependent_settings(key): # Note: Doesn't handle multiple levels of dependencies! setting_keys.append(dependent_key) # NOTE: This block is probably duplicated. cache_keys = set([Setting.get_cache_key(k) for k in setting_keys]) cache.delete_many(cache_keys) # Send setting_changed signal with new value for each setting. for setting_key in setting_keys: setting_changed.send(sender=Setting, setting=setting_key, value=getattr(settings, setting_key, None), enter=not bool(for_delete))
def handle_setting_change(key, for_delete=False): # When a setting changes or is deleted, remove its value from cache along # with any other settings that depend on it. setting_keys = [key] for dependent_key in settings_registry.get_dependent_settings(key): # Note: Doesn't handle multiple levels of dependencies! setting_keys.append(dependent_key) # NOTE: This block is probably duplicated. cache_keys = {Setting.get_cache_key(k) for k in setting_keys} cache.delete_many(cache_keys) # if we have changed a setting, we want to avoid mucking with the in-memory cache entirely settings._awx_conf_memoizedcache.clear() # Send setting_changed signal with new value for each setting. for setting_key in setting_keys: setting_changed.send(sender=Setting, setting=setting_key, value=getattr(settings, setting_key, None), enter=not bool(for_delete))
def _get_local(self, name, validate=True): self._preload_cache() cache_key = Setting.get_cache_key(name) try: cache_value = self.cache.get(cache_key, default=empty) except ValueError: cache_value = empty if cache_value == SETTING_CACHE_NOTSET: value = empty elif cache_value == SETTING_CACHE_NONE: value = None elif cache_value == SETTING_CACHE_EMPTY_LIST: value = [] elif cache_value == SETTING_CACHE_EMPTY_DICT: value = {} else: value = cache_value field = self.registry.get_setting_field(name) if value is empty: setting = None setting_id = None # this value is read-only, however we *do* want to fetch its value from the database if not field.read_only or name == 'INSTALL_UUID': setting = Setting.objects.filter( key=name, user__isnull=True).order_by('pk').first() if setting: if getattr(field, 'encrypted', False): value = decrypt_field(setting, 'value') setting_id = setting.id else: value = setting.value else: value = SETTING_CACHE_NOTSET if SETTING_CACHE_DEFAULTS: try: value = field.get_default() if getattr(field, 'encrypted', False): setting_id = SETTING_CACHE_NONE except SkipField: pass # If None implies not set, convert when reading the value. if value is None and SETTING_CACHE_NOTSET == SETTING_CACHE_NONE: value = SETTING_CACHE_NOTSET if cache_value != value: if setting_id: logger.debug('Saving id in cache for encrypted setting %s', cache_key) self.cache.cache.set(Setting.get_cache_id_key(cache_key), setting_id) self.cache.set(cache_key, get_cache_value(value), timeout=SETTING_CACHE_TIMEOUT) if value == SETTING_CACHE_NOTSET and not SETTING_CACHE_DEFAULTS: try: value = field.get_default() except SkipField: pass if value not in (empty, SETTING_CACHE_NOTSET): try: if field.read_only: internal_value = field.to_internal_value(value) field.run_validators(internal_value) return internal_value else: if validate: return field.run_validation(value) else: return value except Exception: logger.warning( 'The current value "%r" for setting "%s" is invalid.', value, name, exc_info=True) return empty
def _preload_cache(self): # Ensure we're only modifying local preload timeout from one thread. with self._awx_conf_preload_lock: # If local preload timeout has not expired, skip preloading. if self._awx_conf_preload_expires and self._awx_conf_preload_expires > time.time( ): return # Otherwise update local preload timeout. self.__dict__['_awx_conf_preload_expires'] = time.time( ) + SETTING_CACHE_TIMEOUT # Check for any settings that have been defined in Python files and # make those read-only to avoid overriding in the database. if not self._awx_conf_init_readonly: defaults_snapshot = self._get_default('DEFAULTS_SNAPSHOT') for key in get_writeable_settings(self.registry): init_default = defaults_snapshot.get(key, None) try: file_default = self._get_default(key) except AttributeError: file_default = None if file_default != init_default and file_default is not None: logger.debug('Setting %s has been marked read-only!', key) self.registry._registry[key]['read_only'] = True self.registry._registry[key]['defined_in_file'] = True self.__dict__['_awx_conf_init_readonly'] = True # If local preload timer has expired, check to see if another process # has already preloaded the cache and skip preloading if so. if self.cache.get('_awx_conf_preload_expires', default=empty) is not empty: return # Initialize all database-configurable settings with a marker value so # to indicate from the cache that the setting is not configured without # a database lookup. settings_to_cache = get_settings_to_cache(self.registry) setting_ids = {} # Load all settings defined in the database. for setting in Setting.objects.filter( key__in=settings_to_cache.keys(), user__isnull=True).order_by('pk'): if settings_to_cache[setting.key] != SETTING_CACHE_NOTSET: continue if self.registry.is_setting_encrypted(setting.key): setting_ids[setting.key] = setting.id value = decrypt_field(setting, 'value') else: value = setting.value settings_to_cache[setting.key] = get_cache_value(value) # Load field default value for any settings not found in the database. if SETTING_CACHE_DEFAULTS: for key, value in settings_to_cache.items(): if value != SETTING_CACHE_NOTSET: continue field = self.registry.get_setting_field(key) try: settings_to_cache[key] = get_cache_value( field.get_default()) if self.registry.is_setting_encrypted(key): # No database pk, so None will be passed to encryption algorithm setting_ids[key] = SETTING_CACHE_NOTSET except SkipField: pass # Generate a cache key for each setting and store them all at once. settings_to_cache = dict([(Setting.get_cache_key(k), v) for k, v in settings_to_cache.items()]) for k, id_val in setting_ids.items(): logger.debug('Saving id in cache for encrypted setting %s, %s', Setting.get_cache_id_key(k), id_val) self.cache.cache.set(Setting.get_cache_id_key(k), id_val) settings_to_cache[ '_awx_conf_preload_expires'] = self._awx_conf_preload_expires self.cache.set_many(settings_to_cache, timeout=SETTING_CACHE_TIMEOUT)
class SettingsWrapper(UserSettingsHolder): @classmethod def initialize(cls, cache=None, registry=None): """ Used to initialize and wrap the Django settings context. :param cache: the Django cache backend to use for caching setting values. ``django.core.cache`` is used by default. :param registry: the settings registry instance used. The global ``awx.conf.settings_registry`` is used by default. """ if not getattr(settings, '_awx_conf_settings', False): settings_wrapper = cls( settings._wrapped, cache=cache or django_cache, registry=registry or settings_registry ) settings._wrapped = settings_wrapper def __init__(self, default_settings, cache, registry): """ This constructor is generally not called directly, but by ``SettingsWrapper.initialize`` at app startup time when settings are parsed. """ # These values have to be stored via self.__dict__ in this way to get # around the magic __setattr__ method on this class (which is used to # store API-assigned settings in the database). self.__dict__['default_settings'] = default_settings self.__dict__['_awx_conf_settings'] = self self.__dict__['_awx_conf_preload_expires'] = None self.__dict__['_awx_conf_preload_lock'] = threading.RLock() self.__dict__['_awx_conf_init_readonly'] = False self.__dict__['cache'] = EncryptedCacheProxy(cache, registry) self.__dict__['registry'] = registry @cached_property def all_supported_settings(self): return self.registry.get_registered_settings() def _preload_cache(self): # Ensure we're only modifying local preload timeout from one thread. with self._awx_conf_preload_lock: # If local preload timeout has not expired, skip preloading. if self._awx_conf_preload_expires and self._awx_conf_preload_expires > time.time(): return # Otherwise update local preload timeout. self.__dict__['_awx_conf_preload_expires'] = time.time() + SETTING_CACHE_TIMEOUT # Check for any settings that have been defined in Python files and # make those read-only to avoid overriding in the database. if not self._awx_conf_init_readonly and 'migrate_to_database_settings' not in sys.argv: defaults_snapshot = self._get_default('DEFAULTS_SNAPSHOT') for key in get_writeable_settings(self.registry): init_default = defaults_snapshot.get(key, None) try: file_default = self._get_default(key) except AttributeError: file_default = None if file_default != init_default and file_default is not None: logger.debug('Setting %s has been marked read-only!', key) self.registry._registry[key]['read_only'] = True self.registry._registry[key]['defined_in_file'] = True self.__dict__['_awx_conf_init_readonly'] = True # If local preload timer has expired, check to see if another process # has already preloaded the cache and skip preloading if so. if self.cache.get('_awx_conf_preload_expires', default=empty) is not empty: return # Initialize all database-configurable settings with a marker value so # to indicate from the cache that the setting is not configured without # a database lookup. settings_to_cache = get_settings_to_cache(self.registry) setting_ids = {} # Load all settings defined in the database. for setting in Setting.objects.filter(key__in=settings_to_cache.keys(), user__isnull=True).order_by('pk'): if settings_to_cache[setting.key] != SETTING_CACHE_NOTSET: continue if self.registry.is_setting_encrypted(setting.key): setting_ids[setting.key] = setting.id try: value = decrypt_field(setting, 'value') except ValueError, e: #TODO: Remove in Tower 3.3 logger.debug('encountered error decrypting field: %s - attempting fallback to old', e) value = old_decrypt_field(setting, 'value') else: value = setting.value settings_to_cache[setting.key] = get_cache_value(value) # Load field default value for any settings not found in the database. if SETTING_CACHE_DEFAULTS: for key, value in settings_to_cache.items(): if value != SETTING_CACHE_NOTSET: continue field = self.registry.get_setting_field(key) try: settings_to_cache[key] = get_cache_value(field.get_default()) if self.registry.is_setting_encrypted(key): # No database pk, so None will be passed to encryption algorithm setting_ids[key] = SETTING_CACHE_NOTSET except SkipField: pass # Generate a cache key for each setting and store them all at once. settings_to_cache = dict([(Setting.get_cache_key(k), v) for k, v in settings_to_cache.items()]) for k, id_val in setting_ids.items(): logger.debug('Saving id in cache for encrypted setting %s, %s', Setting.get_cache_id_key(k), id_val) self.cache.cache.set(Setting.get_cache_id_key(k), id_val) settings_to_cache['_awx_conf_preload_expires'] = self._awx_conf_preload_expires self.cache.set_many(settings_to_cache, timeout=SETTING_CACHE_TIMEOUT)
def test_encrypt_field(): field = Setting(pk=123, value='ANSIBLE') encrypted = field.value = encryption.encrypt_field(field, 'value') assert encryption.decrypt_field(field, 'value') == 'ANSIBLE' assert encrypted.startswith('$encrypted$UTF8$AESCBC$')
def test_encrypt_field_with_empty_value(): encrypted = encryption.encrypt_field(Setting(value=None), 'value') assert encrypted is None
def test_encrypt_field_with_ask(): encrypted = encryption.encrypt_field(Setting(value='ASK'), 'value', ask=True) assert encrypted == 'ASK'
def test_encrypt_field_with_unicode_string(): value = u'Iñtërnâtiônàlizætiøn' field = Setting(value=value) encrypted = field.value = encryption.encrypt_field(field, 'value') assert encryption.decrypt_field(field, 'value') == value assert encrypted.startswith('$encrypted$UTF8$AESCBC$')
def test_encrypt_field_without_pk(): field = Setting(value='ANSIBLE') encrypted = field.value = encryption.encrypt_field(field, 'value') assert encryption.decrypt_field(field, 'value') == 'ANSIBLE' assert encrypted.startswith('$encrypted$AESCBC$')