From 95fd0f15ea1094978fbd261160da4091d599691c Mon Sep 17 00:00:00 2001 From: Joachim Metz Date: Fri, 19 Jun 2020 16:04:46 +0200 Subject: [PATCH] Added session configuration attribute container #109 (#2936) --- plaso/containers/artifacts.py | 32 ++++- plaso/containers/sessions.py | 136 ++++++++++++++++---- plaso/serializer/json_serializer.py | 26 +++- tests/containers/artifacts.py | 13 ++ tests/containers/sessions.py | 192 +++++++++++++++++++--------- tests/serializer/json_serializer.py | 1 - 6 files changed, 303 insertions(+), 97 deletions(-) diff --git a/plaso/containers/artifacts.py b/plaso/containers/artifacts.py index 54a7c6b60b..bb7cadb86e 100644 --- a/plaso/containers/artifacts.py +++ b/plaso/containers/artifacts.py @@ -219,6 +219,34 @@ def IsEquivalent(self, other): return False +class SourceConfigurationArtifact(ArtifactAttributeContainer): + """Source configuration artifact attribute container. + + The source configuration contains the configuration data of a source + that is (or going to be) processed such as volume in a storage media + image or a mounted directory. + + Attributes: + path_spec (dfvfs.PathSpec): path specification of the source that is + processed. + system_configuration (SystemConfigurationArtifact): system configuration of + a specific system installation, such as Windows or Linux, detected by + the pre-processing on the source. + """ + CONTAINER_TYPE = 'source_configuration' + + def __init__(self, path_spec=None): + """Initializes a source configuration artifact. + + Args: + path_spec (Optional[dfvfs.PathSpec]): path specification of the source + that is processed. + """ + super(SourceConfigurationArtifact, self).__init__() + self.path_spec = path_spec + self.system_configuration = None + + class SystemConfigurationArtifact(ArtifactAttributeContainer): """System configuration artifact attribute container. @@ -331,5 +359,5 @@ def GetUserDirectoryPathSegments(self): manager.AttributeContainersManager.RegisterAttributeContainers([ - EnvironmentVariableArtifact, HostnameArtifact, SystemConfigurationArtifact, - TimeZoneArtifact, UserAccountArtifact]) + EnvironmentVariableArtifact, HostnameArtifact, SourceConfigurationArtifact, + SystemConfigurationArtifact, TimeZoneArtifact, UserAccountArtifact]) diff --git a/plaso/containers/sessions.py b/plaso/containers/sessions.py index 03ba5f8c5c..1701b7935e 100644 --- a/plaso/containers/sessions.py +++ b/plaso/containers/sessions.py @@ -39,6 +39,8 @@ class Session(interface.AttributeContainer): product_name (str): name of the product that created the session for example "log2timeline". product_version (str): version of the product that created the session. + source_configurations (list[SourceConfiguration]): configuration of sources + that are (or going to be) processed. start_time (int): time that the session was started. Contains the number of micro seconds since January 1, 1970, 00:00:00 UTC. """ @@ -64,6 +66,7 @@ def __init__(self): self.preferred_year = None self.product_name = 'plaso' self.product_version = plaso.__version__ + self.source_configurations = None self.start_time = int(time.time() * 1000000) def CopyAttributesFromSessionCompletion(self, session_completion): @@ -94,25 +97,62 @@ def CopyAttributesFromSessionCompletion(self, session_completion): if session_completion.parsers_counter: self.parsers_counter = session_completion.parsers_counter + def CopyAttributesFromSessionConfiguration(self, session_configuration): + """Copies attributes from a session configuration. + + Args: + session_configuration (SessionConfiguration): session configuration + attribute container. + + Raises: + ValueError: if the identifier of the session configuration does not match + that of the session. + """ + if self.identifier != session_configuration.identifier: + raise ValueError('Session identifier mismatch.') + + self.artifact_filters = session_configuration.artifact_filters + self.command_line_arguments = session_configuration.command_line_arguments + self.debug_mode = session_configuration.debug_mode + self.enabled_parser_names = session_configuration.enabled_parser_names + self.filter_file = session_configuration.filter_file + self.parser_filter_expression = ( + session_configuration.parser_filter_expression) + self.preferred_encoding = session_configuration.preferred_encoding + self.preferred_time_zone = session_configuration.preferred_time_zone + self.source_configurations = session_configuration.source_configurations + def CopyAttributesFromSessionStart(self, session_start): """Copies attributes from a session start. Args: session_start (SessionStart): session start attribute container. """ - self.artifact_filters = session_start.artifact_filters - self.command_line_arguments = session_start.command_line_arguments - self.debug_mode = session_start.debug_mode - self.enabled_parser_names = session_start.enabled_parser_names - self.filter_file = session_start.filter_file self.identifier = session_start.identifier - self.parser_filter_expression = session_start.parser_filter_expression - self.preferred_encoding = session_start.preferred_encoding - self.preferred_time_zone = session_start.preferred_time_zone self.product_name = session_start.product_name self.product_version = session_start.product_version self.start_time = session_start.timestamp + # The following is for backward compatibility with older session start + # attribute containers. + self.artifact_filters = getattr( + session_start, 'artifact_filters', self.artifact_filters) + self.command_line_arguments = getattr( + session_start, 'command_line_arguments', self.command_line_arguments) + self.debug_mode = getattr( + session_start, 'debug_mode', self.debug_mode) + self.enabled_parser_names = getattr( + session_start, 'enabled_parser_names', self.enabled_parser_names) + self.filter_file = getattr( + session_start, 'filter_file', self.filter_file) + self.parser_filter_expression = getattr( + session_start, 'parser_filter_expression', + self.parser_filter_expression) + self.preferred_encoding = getattr( + session_start, 'preferred_encoding', self.preferred_encoding) + self.preferred_time_zone = getattr( + session_start, 'preferred_time_zone', self.preferred_time_zone) + def CreateSessionCompletion(self): """Creates a session completion. @@ -130,6 +170,26 @@ def CreateSessionCompletion(self): session_completion.timestamp = self.completion_time return session_completion + def CreateSessionConfiguration(self): + """Creates a session configuration. + + Returns: + SessionConfiguration: session configuration attribute container. + """ + session_configuration = SessionConfiguration() + session_configuration.artifact_filters = self.artifact_filters + session_configuration.command_line_arguments = self.command_line_arguments + session_configuration.debug_mode = self.debug_mode + session_configuration.enabled_parser_names = self.enabled_parser_names + session_configuration.filter_file = self.filter_file + session_configuration.identifier = self.identifier + session_configuration.parser_filter_expression = ( + self.parser_filter_expression) + session_configuration.preferred_encoding = self.preferred_encoding + session_configuration.preferred_time_zone = self.preferred_time_zone + session_configuration.source_configurations = self.source_configurations + return session_configuration + def CreateSessionStart(self): """Creates a session start. @@ -137,15 +197,7 @@ def CreateSessionStart(self): SessionStart: session start attribute container. """ session_start = SessionStart() - session_start.artifact_filters = self.artifact_filters - session_start.command_line_arguments = self.command_line_arguments - session_start.debug_mode = self.debug_mode - session_start.enabled_parser_names = self.enabled_parser_names - session_start.filter_file = self.filter_file session_start.identifier = self.identifier - session_start.parser_filter_expression = self.parser_filter_expression - session_start.preferred_encoding = self.preferred_encoding - session_start.preferred_time_zone = self.preferred_time_zone session_start.product_name = self.product_name session_start.product_version = self.product_version session_start.timestamp = self.start_time @@ -185,8 +237,13 @@ def __init__(self, identifier=None): self.timestamp = None -class SessionStart(interface.AttributeContainer): - """Session start attribute container. +class SessionConfiguration(interface.AttributeContainer): + """Session configuration attribute container. + + The session configuration contains various settings used within a session, + such as parser and collection filters that are used, and information about + the source being processed, such as the system configuration determined by + pre-processing. Attributes: artifact_filters (list[str]): names of artifact definitions that are @@ -201,23 +258,20 @@ class SessionStart(interface.AttributeContainer): preferred_encoding (str): preferred encoding. preferred_time_zone (str): preferred time zone. preferred_year (int): preferred year. - product_name (str): name of the product that created the session for - example "log2timeline". - product_version (str): version of the product that created the session. - timestamp (int): time that the session was started. Contains the number - of micro seconds since January 1, 1970, 00:00:00 UTC. + source_configurations (list[SourceConfiguration]): configuration of sources + that are (or going to be) processed. """ - CONTAINER_TYPE = 'session_start' + CONTAINER_TYPE = 'session_configuration' def __init__(self, identifier=None): - """Initializes a session start attribute container. + """Initializes a session configuration attribute container. Args: identifier (Optional[str]): unique identifier of the session. The identifier should match that of the corresponding - session completion information. + session start information. """ - super(SessionStart, self).__init__() + super(SessionConfiguration, self).__init__() self.artifact_filters = None self.command_line_arguments = None self.debug_mode = False @@ -228,10 +282,36 @@ def __init__(self, identifier=None): self.preferred_encoding = None self.preferred_time_zone = None self.preferred_year = None + self.source_configurations = None + + +class SessionStart(interface.AttributeContainer): + """Session start attribute container. + + Attributes: + identifier (str): unique identifier of the session. + product_name (str): name of the product that created the session for + example "log2timeline". + product_version (str): version of the product that created the session. + timestamp (int): time that the session was started. Contains the number + of micro seconds since January 1, 1970, 00:00:00 UTC. + """ + CONTAINER_TYPE = 'session_start' + + def __init__(self, identifier=None): + """Initializes a session start attribute container. + + Args: + identifier (Optional[str]): unique identifier of the session. + The identifier should match that of the corresponding + session completion information. + """ + super(SessionStart, self).__init__() + self.identifier = identifier self.product_name = None self.product_version = None self.timestamp = None manager.AttributeContainersManager.RegisterAttributeContainers([ - Session, SessionCompletion, SessionStart]) + Session, SessionCompletion, SessionConfiguration, SessionStart]) diff --git a/plaso/serializer/json_serializer.py b/plaso/serializer/json_serializer.py index ac12da8056..5cf8dac72f 100644 --- a/plaso/serializer/json_serializer.py +++ b/plaso/serializer/json_serializer.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""The json serializer object implementation.""" +"""The JSON serializer object implementation.""" from __future__ import unicode_literals @@ -18,7 +18,19 @@ class JSONAttributeContainerSerializer(interface.AttributeContainerSerializer): - """Class that implements the json attribute container serializer.""" + """JSON attribute container serializer.""" + + # Backwards compatibility for older session attribute containers that + # contain session configuration attributes. + _SESSION_START_LEGACY_ATTRIBUTE_NAMES = frozenset([ + 'artifact_filters', + 'command_line_arguments', + 'debug_mode', + 'enabled_parser_names', + 'filter_file', + 'parser_filter_expression', + 'preferred_encoding', + 'preferred_time_zone']) @classmethod def _ConvertAttributeContainerToDict(cls, attribute_container): @@ -227,10 +239,16 @@ def _ConvertDictToObject(cls, json_dict): attribute_name == 'event_row_identifier'): attribute_name = '_event_row_identifier' + # Backwards compatibility for older session attribute containers that + # contain session configuration attributes. + if (container_type == 'session_start' and + attribute_name in cls._SESSION_START_LEGACY_ATTRIBUTE_NAMES): + pass + # Be strict about which attributes to set in non event data attribute # containers. - if (container_type != 'event_data' and - attribute_name not in supported_attribute_names): + elif (container_type != 'event_data' and + attribute_name not in supported_attribute_names): if attribute_name not in ('__container_type__', '__type__'): logger.debug(( diff --git a/tests/containers/artifacts.py b/tests/containers/artifacts.py index ef0193c020..f4bada7ac6 100644 --- a/tests/containers/artifacts.py +++ b/tests/containers/artifacts.py @@ -110,6 +110,19 @@ def testGetAttributeNames(self): self.assertEqual(attribute_names, expected_attribute_names) +class SourceConfigurationArtifactTest(shared_test_lib.BaseTestCase): + """Tests for the source configuration artifact.""" + + def testGetAttributeNames(self): + """Tests the GetAttributeNames function.""" + attribute_container = artifacts.SourceConfigurationArtifact() + + expected_attribute_names = ['path_spec', 'system_configuration'] + + attribute_names = sorted(attribute_container.GetAttributeNames()) + self.assertEqual(attribute_names, expected_attribute_names) + + class SystemConfigurationArtifactTest(shared_test_lib.BaseTestCase): """Tests for the system configuration artifact.""" diff --git a/tests/containers/sessions.py b/tests/containers/sessions.py index b42f653e03..d3ca7e8711 100644 --- a/tests/containers/sessions.py +++ b/tests/containers/sessions.py @@ -4,11 +4,8 @@ from __future__ import unicode_literals -import time import unittest -import uuid -import plaso from plaso.containers import sessions from tests import test_lib as shared_test_lib @@ -17,87 +14,158 @@ class SessionTest(shared_test_lib.BaseTestCase): """Tests for the session attribute container.""" - # TODO: replace by GetAttributeNames test - def testCopyToDict(self): - """Tests the CopyToDict function.""" - session = sessions.Session() + def testGetAttributeNames(self): + """Tests the GetAttributeNames function.""" + attribute_container = sessions.Session() + + expected_attribute_names = [ + 'aborted', + 'analysis_reports_counter', + 'artifact_filters', + 'command_line_arguments', + 'completion_time', + 'debug_mode', + 'enabled_parser_names', + 'event_labels_counter', + 'filter_file', + 'identifier', + 'parser_filter_expression', + 'parsers_counter', + 'preferred_encoding', + 'preferred_time_zone', + 'preferred_year', + 'product_name', + 'product_version', + 'source_configurations', + 'start_time'] + + attribute_names = sorted(attribute_container.GetAttributeNames()) + + self.assertEqual(attribute_names, expected_attribute_names) + + def testCopyAttributesFromSessionCompletion(self): + """Tests the CopyAttributesFromSessionCompletion function.""" + attribute_container = sessions.Session() - self.assertIsNotNone(session.identifier) - self.assertIsNotNone(session.start_time) - self.assertIsNone(session.completion_time) + session_completion = sessions.SessionCompletion( + identifier=attribute_container.identifier) + attribute_container.CopyAttributesFromSessionCompletion(session_completion) + + with self.assertRaises(ValueError): + session_completion = sessions.SessionCompletion() + attribute_container.CopyAttributesFromSessionCompletion( + session_completion) + + def testCopyAttributesFromSessionConfiguration(self): + """Tests the CopyAttributesFromSessionConfiguration function.""" + attribute_container = sessions.Session() + + session_configuration = sessions.SessionConfiguration( + identifier=attribute_container.identifier) + attribute_container.CopyAttributesFromSessionConfiguration( + session_configuration) + + with self.assertRaises(ValueError): + session_configuration = sessions.SessionConfiguration() + attribute_container.CopyAttributesFromSessionConfiguration( + session_configuration) + + def testCopyAttributesFromSessionStart(self): + """Tests the CopyAttributesFromSessionStart function.""" + attribute_container = sessions.Session() + + session_start = sessions.SessionStart( + identifier=attribute_container.identifier) + attribute_container.CopyAttributesFromSessionStart(session_start) + + def testCreateSessionCompletion(self): + """Tests the CreateSessionCompletion function.""" + attribute_container = sessions.Session() - expected_dict = { - 'aborted': False, - 'analysis_reports_counter': session.analysis_reports_counter, - 'debug_mode': False, - 'event_labels_counter': session.event_labels_counter, - 'identifier': session.identifier, - 'parsers_counter': session.parsers_counter, - 'preferred_encoding': 'utf-8', - 'preferred_time_zone': 'UTC', - 'product_name': 'plaso', - 'product_version': plaso.__version__, - 'start_time': session.start_time} + session_completion = attribute_container.CreateSessionCompletion() + self.assertIsNotNone(session_completion) + self.assertEqual( + session_completion.identifier, attribute_container.identifier) - test_dict = session.CopyToDict() + def testCreateSessionConfiguration(self): + """Tests the CreateSessionConfiguration function.""" + attribute_container = sessions.Session() - self.assertEqual(test_dict, expected_dict) + session_configuration = attribute_container.CreateSessionConfiguration() + self.assertIsNotNone(session_configuration) + self.assertEqual( + session_configuration.identifier, attribute_container.identifier) - # TODO: add tests for CopyAttributesFromSessionCompletion - # TODO: add tests for CopyAttributesFromSessionStart - # TODO: add tests for CreateSessionCompletion - # TODO: add tests for CreateSessionStart + def testCreateSessionStart(self): + """Tests the CreateSessionStart function.""" + attribute_container = sessions.Session() + + session_start = attribute_container.CreateSessionStart() + self.assertIsNotNone(session_start) + self.assertEqual(session_start.identifier, attribute_container.identifier) class SessionCompletionTest(shared_test_lib.BaseTestCase): """Tests for the session completion attribute container.""" - # TODO: replace by GetAttributeNames test - def testCopyToDict(self): - """Tests the CopyToDict function.""" - timestamp = int(time.time() * 1000000) - session_identifier = '{0:s}'.format(uuid.uuid4().hex) - session_completion = sessions.SessionCompletion( - identifier=session_identifier) - session_completion.timestamp = timestamp + def testGetAttributeNames(self): + """Tests the GetAttributeNames function.""" + attribute_container = sessions.SessionCompletion() + + expected_attribute_names = [ + 'aborted', + 'analysis_reports_counter', + 'event_labels_counter', + 'identifier', + 'parsers_counter', + 'timestamp'] + + attribute_names = sorted(attribute_container.GetAttributeNames()) - self.assertEqual(session_completion.identifier, session_identifier) + self.assertEqual(attribute_names, expected_attribute_names) - expected_dict = { - 'aborted': False, - 'identifier': session_completion.identifier, - 'timestamp': timestamp} - test_dict = session_completion.CopyToDict() +class SessionConfiguration(shared_test_lib.BaseTestCase): + """Tests for the session configuration attribute container.""" - self.assertEqual(test_dict, expected_dict) + def testGetAttributeNames(self): + """Tests the GetAttributeNames function.""" + attribute_container = sessions.SessionConfiguration() + + expected_attribute_names = [ + 'artifact_filters', + 'command_line_arguments', + 'debug_mode', + 'enabled_parser_names', + 'filter_file', + 'identifier', + 'parser_filter_expression', + 'preferred_encoding', + 'preferred_time_zone', + 'preferred_year', + 'source_configurations'] + + attribute_names = sorted(attribute_container.GetAttributeNames()) + + self.assertEqual(attribute_names, expected_attribute_names) class SessionStartTest(shared_test_lib.BaseTestCase): """Tests for the session start attribute container.""" - # TODO: replace by GetAttributeNames test - def testCopyToDict(self): - """Tests the CopyToDict function.""" - timestamp = int(time.time() * 1000000) - session_identifier = '{0:s}'.format(uuid.uuid4().hex) - session_start = sessions.SessionStart(identifier=session_identifier) - session_start.timestamp = timestamp - session_start.product_name = 'plaso' - session_start.product_version = plaso.__version__ - - self.assertEqual(session_start.identifier, session_identifier) + def testGetAttributeNames(self): + """Tests the GetAttributeNames function.""" + attribute_container = sessions.SessionStart() - expected_dict = { - 'debug_mode': False, - 'identifier': session_start.identifier, - 'product_name': 'plaso', - 'product_version': plaso.__version__, - 'timestamp': timestamp} + expected_attribute_names = [ + 'identifier', + 'product_name', + 'product_version', + 'timestamp'] - test_dict = session_start.CopyToDict() + attribute_names = sorted(attribute_container.GetAttributeNames()) - self.assertEqual(test_dict, expected_dict) + self.assertEqual(attribute_names, expected_attribute_names) if __name__ == '__main__': diff --git a/tests/serializer/json_serializer.py b/tests/serializer/json_serializer.py index ba51612f93..f866a8d1df 100644 --- a/tests/serializer/json_serializer.py +++ b/tests/serializer/json_serializer.py @@ -381,7 +381,6 @@ def testReadAndWriteSerializedSessionStart(self): self.assertIsInstance(session_start, sessions.SessionStart) expected_session_start_dict = { - 'debug_mode': False, 'identifier': session_identifier, 'product_name': 'plaso', 'product_version': plaso.__version__,