diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..e897414 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,26 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python +{ + "name": "Python 3", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye", + "features": { + "ghcr.io/devcontainers-contrib/features/ruff:1": {}, + "ghcr.io/devcontainers-contrib/features/tox:2": {} + }, + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "python -m pip install .[dev]" + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f33a02c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for more information: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://containers.dev/guide/dependabot + +version: 2 +updates: + - package-ecosystem: "devcontainers" + directory: "/" + schedule: + interval: weekly diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c52881d..cd93dac 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -2,7 +2,7 @@ on: push: jobs: build: - name: Build exectuable 📦 + name: Build executable 📦 runs-on: windows-latest steps: - uses: actions/checkout@v4 @@ -11,7 +11,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.9" + python-version: "3.12" - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/compose-dev.yaml b/compose-dev.yaml new file mode 100644 index 0000000..a92f701 --- /dev/null +++ b/compose-dev.yaml @@ -0,0 +1,12 @@ +services: + app: + entrypoint: + - sleep + - infinity + image: docker/dev-environments-default:stable-1 + init: true + volumes: + - type: bind + source: /var/run/docker.sock + target: /var/run/docker.sock + diff --git a/pyproject.toml b/pyproject.toml index 6fb4b73..ee03764 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools >= 61.0"] +requires = ["setuptools >= 70.0"] build-backend = "setuptools.build_meta" @@ -8,7 +8,7 @@ name = "forensicsim" description = "A forensic open-source parser module for Autopsy that allows extracting the messages, comments, posts, contacts, calendar entries and reactions from a Microsoft Teams IndexedDB LevelDB database." readme = "README.md" license = {file = "LICENSE.md"} -requires-python = ">=3.9" +requires-python = ">=3.12" authors = [ { name = "Alexander Bilz", email = "github@alexbilz.com" }, { name = "Markus Bilz", email = "github@markusbilz.com" } @@ -113,14 +113,14 @@ commands = # Auto Formatting [testenv:format] commands = - python -m ruff src tests --fix + python -m ruff check --fix src tests python -m ruff format src # Syntax Checks [testenv:lint] commands = python -m mypy src/forensicsim/backend.py - python -m ruff --output-format=github src + python -m ruff check --output-format=github src python -m ruff format src --check # Pre-Commit diff --git a/tools/Forensicsim_Parser.py b/tools/Forensicsim_Parser.py index 1f82eba..b50446d 100644 --- a/tools/Forensicsim_Parser.py +++ b/tools/Forensicsim_Parser.py @@ -43,36 +43,39 @@ from java.lang import ProcessBuilder from java.util import ArrayList from java.util.logging import Level -from org.sleuthkit.autopsy.casemodule import Case, NoCurrentCaseException -from org.sleuthkit.autopsy.coreutils import ExecUtil, Logger, PlatformUtil +from org.sleuthkit.autopsy.casemodule import Case +from org.sleuthkit.autopsy.casemodule import NoCurrentCaseException +from org.sleuthkit.autopsy.coreutils import ExecUtil +from org.sleuthkit.autopsy.coreutils import Logger +from org.sleuthkit.autopsy.coreutils import PlatformUtil from org.sleuthkit.autopsy.datamodel import ContentUtils -from org.sleuthkit.autopsy.ingest import ( - DataSourceIngestModule, - DataSourceIngestModuleProcessTerminator, - IngestMessage, - IngestModule, - IngestModuleFactoryAdapter, - IngestServices, -) +from org.sleuthkit.autopsy.ingest import DataSourceIngestModule +from org.sleuthkit.autopsy.ingest import DataSourceIngestModuleProcessTerminator +from org.sleuthkit.autopsy.ingest import IngestMessage +from org.sleuthkit.autopsy.ingest import IngestModule +from org.sleuthkit.autopsy.ingest import IngestModuleFactoryAdapter +from org.sleuthkit.autopsy.ingest import IngestServices from org.sleuthkit.autopsy.ingest.IngestModule import IngestModuleException -from org.sleuthkit.datamodel import ( - BlackboardArtifact, - BlackboardAttribute, - CommunicationsManager, - TskCoreException, - TskData, -) +from org.sleuthkit.datamodel import BlackboardArtifact +from org.sleuthkit.datamodel import BlackboardAttribute +from org.sleuthkit.datamodel import CommunicationsManager +from org.sleuthkit.datamodel import TskCoreException +from org.sleuthkit.datamodel import TskData from org.sleuthkit.datamodel.Blackboard import BlackboardException from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper -from org.sleuthkit.datamodel.blackboardutils.attributes import MessageAttachments -from org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments import ( - URLAttachment, -) from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import ( CallMediaType, +) +from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import ( CommunicationDirection, +) +from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import ( MessageReadStatus, ) +from org.sleuthkit.datamodel.blackboardutils.attributes import MessageAttachments +from org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments import ( + URLAttachment, +) # Common Prefix Shared for all artefacts ARTIFACT_PREFIX = "Microsoft Teams" @@ -235,7 +238,7 @@ def _extract(self, content, path): # ignore relative paths if child_name == "." or child_name == "..": continue - elif child.isFile(): # noqa: RET507 + elif child.isFile(): ContentUtils.writeToFile(child, File(child_path)) elif child.isDir(): os.mkdir(child_path) @@ -521,6 +524,14 @@ def parse_messages(self, messages, helper, teams_leveldb_file_path): message_text = message["content"] # Group by the conversationId, these can be direct messages, but also posts thread_id = message["conversationId"] + # Additional Attributes + message_date_time_edited = 0 + message_date_time_deleted = 0 + + if "edittime" in message["properties"]: + message_date_time_edited = int(message["properties"]["edittime"]) + if "deletetime" in message["properties"]: + message_date_time_edited = int(message["properties"]["deletetime"]) additional_attributes = ArrayList() additional_attributes.add( @@ -694,16 +705,18 @@ def get_level_db_file(self, content, filepath): results = file_manager.findFiles(data_source, filename, dir_name) if results.isEmpty(): self.log(Level.INFO, "Unable to locate {}".format(filename)) - return None - return results.get( + return + db_file = results.get( 0 ) # Expect a single match so retrieve the first (and only) file + return db_file def date_to_long(self, formatted_date): # Timestamp dt = datetime.strptime(formatted_date[:19], "%Y-%m-%dT%H:%M:%S") time_struct = dt.timetuple() - return int(calendar.timegm(time_struct)) + timestamp = int(calendar.timegm(time_struct)) + return timestamp # Extract the direction of a phone call def deduce_call_direction(self, direction): @@ -771,7 +784,9 @@ def process(self, data_source, progress_bar): self.log( Level.INFO, - "Found {} {} directories to process.".format(no_directories_to_process, directory), + "Found {} {} directories to process.".format( + directories_to_process, directory + ), ) # Loop over all the files. On a multi user account these could be multiple one. @@ -842,4 +857,4 @@ def process(self, data_source, progress_bar): "Finished analysing the LeveLDB from Microsoft Teams.", ) IngestServices.getInstance().postMessage(message) - return IngestModule.ProcessResult.OK + return IngestModule.ProcessResult.OK \ No newline at end of file