From ab5f4fa3ab22794e799749ce0e1ad984f22a2cf7 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Wed, 9 Aug 2017 22:44:06 +0100 Subject: [PATCH] Refactor the List and Choice Requirements These are required by the Automagic interface, and so to prevent importing from outside the interfaces namespace, these two classes were moved into interfaces. --- volatility/cli/__init__.py | 4 +- .../framework/configuration/requirements.py | 84 ++----------------- volatility/framework/interfaces/automagic.py | 5 +- .../framework/interfaces/configuration.py | 68 +++++++++++++++ 4 files changed, 80 insertions(+), 81 deletions(-) diff --git a/volatility/cli/__init__.py b/volatility/cli/__init__.py index 14b7155e8..52b440aba 100644 --- a/volatility/cli/__init__.py +++ b/volatility/cli/__init__.py @@ -198,7 +198,7 @@ class CommandLine(object): additional["type"] = requirement.instance_type if isinstance(requirement, requirements.BooleanRequirement): additional["action"] = "store_true" - elif isinstance(requirement, requirements.ListRequirement): + elif isinstance(requirement, interfaces.configuration.ListRequirement): if requirement.min_elements != requirement.max_elements: if requirement.min_elements > 0: additional["nargs"] = "+" @@ -208,7 +208,7 @@ class CommandLine(object): else: additional["nargs"] = requirement.max_elements additional["type"] = requirement.element_type.instance_type - elif isinstance(requirement, requirements.ChoiceRequirement): + elif isinstance(requirement, interfaces.configuration.ChoiceRequirement): additional["type"] = str additional["choices"] = requirement.choices else: diff --git a/volatility/framework/configuration/requirements.py b/volatility/framework/configuration/requirements.py index 6d0167a35..6597fafb7 100644 --- a/volatility/framework/configuration/requirements.py +++ b/volatility/framework/configuration/requirements.py @@ -7,17 +7,17 @@ etc) as well as indicating what they expect to be in the context (such as partic import logging -from volatility.framework import constants, interfaces +from volatility.framework.interfaces import configuration as interfaces_configuration vollog = logging.getLogger(__name__) # Allow these two to be imported directly from requirements # This helps prevent import loops since other interfaces need to be able to check instances of this -TranslationLayerRequirement = interfaces.configuration.TranslationLayerRequirement -SymbolRequirement = interfaces.configuration.SymbolRequirement +TranslationLayerRequirement = interfaces_configuration.TranslationLayerRequirement +SymbolRequirement = interfaces_configuration.SymbolRequirement -class MultiRequirement(interfaces.configuration.RequirementInterface): +class MultiRequirement(interfaces_configuration.RequirementInterface): """Class to hold multiple requirements. Technically the Interface could handle this, but it's an interface, so this is a concrete implementation. @@ -27,90 +27,22 @@ class MultiRequirement(interfaces.configuration.RequirementInterface): return self.unsatisfied_children(context, config_path) -class BooleanRequirement(interfaces.configuration.InstanceRequirement): +class BooleanRequirement(interfaces_configuration.InstanceRequirement): """A requirement type that contains a boolean value""" # Note, this must be a separate class in order to differentiate between Booleans and other instance requirements -class IntRequirement(interfaces.configuration.InstanceRequirement): +class IntRequirement(interfaces_configuration.InstanceRequirement): """A requirement type that contains a single integer""" instance_type = int -class StringRequirement(interfaces.configuration.InstanceRequirement): +class StringRequirement(interfaces_configuration.InstanceRequirement): """A requirement type that contains a single unicode string""" # TODO: Maybe add string length limits? instance_type = str -class BytesRequirement(interfaces.configuration.InstanceRequirement): +class BytesRequirement(interfaces_configuration.InstanceRequirement): """A requirement type that contains a byte string""" instance_type = bytes - - -class ChoiceRequirement(interfaces.configuration.RequirementInterface): - """Allows one from a choice of strings""" - - def __init__(self, choices, *args, **kwargs): - """Constructs the object - - :param choices: A list of possible string options that can be chosen from - :type choices: list of str - """ - super().__init__(*args, **kwargs) - if not isinstance(choices, list) or any([not isinstance(choice, str) for choice in choices]): - raise TypeError("ChoiceRequirement takes a list of strings as choices") - self.choices = choices - - def unsatisfied(self, context, config_path): - """Validates the provided value to ensure it is one of the available choices""" - value = self.config_value(context, config_path) - if value not in self.choices: - vollog.log(constants.LOGLEVEL_V, "ValueError - Value is not within the set of available choices") - return [interfaces.configuration.path_join(config_path, self.name)] - return [] - - -class ListRequirement(interfaces.configuration.RequirementInterface): - """Allows for a list of a specific type of requirement (all of which must be met for this requirement to be met) to be specified - - This roughly correlates to allowing a number of arguments to follow a command line parameter, - such as a list of integers or a list of strings. - - It is distinct from a multi-requirement which stores the subrequirements in a dictionary, not a list, - and does not allow for a dynamic number of values. - """ - - def __init__(self, element_type, max_elements, min_elements, *args, **kwargs): - """Constructs the object - - :param element_type: The (requirement) type of each element within the list - :type element_type: InstanceRequirement - :param max_elements; The maximum number of acceptable elements this list can contain - :type max_elements: int - :param min_elements: The minimum number of acceptable elements this list can contain - :type min_elements: int - """ - super().__init__(*args, **kwargs) - if not isinstance(element_type, interfaces.configuration.InstanceRequirement): - raise TypeError("ListRequirements can only contain simple InstanceRequirements") - self.element_type = element_type - self.min_elements = min_elements - self.max_elements = max_elements - - def unsatisfied(self, context, config_path): - """Check the types on each of the returned values and their number and then call the element type's check for each one""" - value = self.config_value(context, config_path) - self._check_type(value, list) - if not (self.min_elements <= len(value) <= self.max_elements): - vollog.log(constants.LOGLEVEL_V, "TypeError - List option provided more or less elements than allowed.") - return [interfaces.configuration.path_join(config_path, self.name)] - if not all([self._check_type(element, self.element_type) for element in value]): - vollog.log(constants.LOGLEVEL_V, "TypeError - At least one element in the list is not of the correct type.") - return [interfaces.configuration.path_join(config_path, self.name)] - result = [] - for element in value: - subresult = self.element_type.unsatisfied(context, element) - for subvalue in subresult: - result.append(subvalue) - return result diff --git a/volatility/framework/interfaces/automagic.py b/volatility/framework/interfaces/automagic.py index 8e769a075..0f7792f1a 100644 --- a/volatility/framework/interfaces/automagic.py +++ b/volatility/framework/interfaces/automagic.py @@ -6,7 +6,6 @@ Automagic objects attempt to automatically fill configuration values that a user from abc import ABCMeta, abstractmethod from volatility.framework import validity -from volatility.framework.configuration import requirements from volatility.framework.interfaces import configuration as interfaces_configuration @@ -38,8 +37,8 @@ class AutomagicInterface(interfaces_configuration.ConfigurableInterface, metacla super().__init__(context, config_path) for requirement in self.get_requirements(): if not isinstance(requirement, (interfaces_configuration.InstanceRequirement, - requirements.ChoiceRequirement, - requirements.ListRequirement)): + interfaces_configuration.ChoiceRequirement, + interfaces_configuration.ListRequirement)): raise ValueError( "Automagic requirements must be an InstanceRequirement, ChoiceRequirement or ListRequirement") diff --git a/volatility/framework/interfaces/configuration.py b/volatility/framework/interfaces/configuration.py index 45fc86802..0b5ec54a1 100644 --- a/volatility/framework/interfaces/configuration.py +++ b/volatility/framework/interfaces/configuration.py @@ -613,3 +613,71 @@ class SymbolRequirement(ConstructableRequirementInterface): return False context.symbol_space.append(obj) return True + + +class ChoiceRequirement(RequirementInterface): + """Allows one from a choice of strings""" + + def __init__(self, choices, *args, **kwargs): + """Constructs the object + + :param choices: A list of possible string options that can be chosen from + :type choices: list of str + """ + super().__init__(*args, **kwargs) + if not isinstance(choices, list) or any([not isinstance(choice, str) for choice in choices]): + raise TypeError("ChoiceRequirement takes a list of strings as choices") + self.choices = choices + + def unsatisfied(self, context, config_path): + """Validates the provided value to ensure it is one of the available choices""" + value = self.config_value(context, config_path) + if value not in self.choices: + vollog.log(constants.LOGLEVEL_V, "ValueError - Value is not within the set of available choices") + return [path_join(config_path, self.name)] + return [] + + +class ListRequirement(RequirementInterface): + """Allows for a list of a specific type of requirement (all of which must be met for this requirement to be met) to be specified + + This roughly correlates to allowing a number of arguments to follow a command line parameter, + such as a list of integers or a list of strings. + + It is distinct from a multi-requirement which stores the subrequirements in a dictionary, not a list, + and does not allow for a dynamic number of values. + """ + + def __init__(self, element_type, max_elements, min_elements, *args, **kwargs): + """Constructs the object + + :param element_type: The (requirement) type of each element within the list + :type element_type: InstanceRequirement + :param max_elements; The maximum number of acceptable elements this list can contain + :type max_elements: int + :param min_elements: The minimum number of acceptable elements this list can contain + :type min_elements: int + """ + super().__init__(*args, **kwargs) + if not isinstance(element_type, InstanceRequirement): + raise TypeError("ListRequirements can only contain simple InstanceRequirements") + self.element_type = element_type + self.min_elements = min_elements + self.max_elements = max_elements + + def unsatisfied(self, context, config_path): + """Check the types on each of the returned values and their number and then call the element type's check for each one""" + value = self.config_value(context, config_path) + self._check_type(value, list) + if not (self.min_elements <= len(value) <= self.max_elements): + vollog.log(constants.LOGLEVEL_V, "TypeError - List option provided more or less elements than allowed.") + return [path_join(config_path, self.name)] + if not all([self._check_type(element, self.element_type) for element in value]): + vollog.log(constants.LOGLEVEL_V, "TypeError - At least one element in the list is not of the correct type.") + return [path_join(config_path, self.name)] + result = [] + for element in value: + subresult = self.element_type.unsatisfied(context, element) + for subvalue in subresult: + result.append(subvalue) + return result