#1947: File and Folder hash checks
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
import math
|
import math
|
||||||
import os.path
|
import os.path
|
||||||
import shutil
|
import shutil
|
||||||
@@ -73,6 +75,9 @@ class FileSystemItemABC(SimComponent):
|
|||||||
visible_status: FileSystemItemStatus = FileSystemItemStatus.GOOD
|
visible_status: FileSystemItemStatus = FileSystemItemStatus.GOOD
|
||||||
"Visible status of the current FileSystemItem"
|
"Visible status of the current FileSystemItem"
|
||||||
|
|
||||||
|
previous_hash: Optional[str] = None
|
||||||
|
"Hash of the file contents or the description state"
|
||||||
|
|
||||||
def describe_state(self) -> Dict:
|
def describe_state(self) -> Dict:
|
||||||
"""
|
"""
|
||||||
Produce a dictionary describing the current state of this object.
|
Produce a dictionary describing the current state of this object.
|
||||||
@@ -102,7 +107,7 @@ class FileSystemItemABC(SimComponent):
|
|||||||
def _init_request_manager(self) -> RequestManager:
|
def _init_request_manager(self) -> RequestManager:
|
||||||
am = super()._init_request_manager()
|
am = super()._init_request_manager()
|
||||||
am.add_request("scan", RequestType(func=lambda request, context: self.scan())) # TODO implement request
|
am.add_request("scan", RequestType(func=lambda request, context: self.scan())) # TODO implement request
|
||||||
am.add_request("checkhash", RequestType(func=lambda request, context: ...)) # TODO implement request
|
am.add_request("checkhash", RequestType(func=lambda request, context: self.checkhash()))
|
||||||
am.add_request("delete", RequestType(func=lambda request, context: ...)) # TODO implement request
|
am.add_request("delete", RequestType(func=lambda request, context: ...)) # TODO implement request
|
||||||
am.add_request("restore", RequestType(func=lambda request, context: ...)) # TODO implement request
|
am.add_request("restore", RequestType(func=lambda request, context: ...)) # TODO implement request
|
||||||
am.add_request("repair", RequestType(func=lambda request, context: self.repair()))
|
am.add_request("repair", RequestType(func=lambda request, context: self.repair()))
|
||||||
@@ -115,6 +120,17 @@ class FileSystemItemABC(SimComponent):
|
|||||||
|
|
||||||
self.visible_status = self.status
|
self.visible_status = self.status
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def check_hash(self) -> bool:
|
||||||
|
"""
|
||||||
|
Checks the has of the file to detect any changes.
|
||||||
|
|
||||||
|
For current implementation, any change in file hash means it is compromised.
|
||||||
|
|
||||||
|
Return False if corruption is detected, otherwise True
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def repair(self) -> None:
|
def repair(self) -> None:
|
||||||
"""Repair the FileSystemItem."""
|
"""Repair the FileSystemItem."""
|
||||||
@@ -517,6 +533,30 @@ class Folder(FileSystemItemABC):
|
|||||||
|
|
||||||
self.fs.sys_log.info(f"Corrupted folder {self.name}")
|
self.fs.sys_log.info(f"Corrupted folder {self.name}")
|
||||||
|
|
||||||
|
def check_hash(self) -> bool:
|
||||||
|
"""
|
||||||
|
Runs a :func:`check_hash` on all files in the folder.
|
||||||
|
|
||||||
|
If a file under the folder is corrupted, the whole folder is considered corrupted.
|
||||||
|
|
||||||
|
TODO: For now this will just iterate through the files and run :func:`check_hash` and ignores
|
||||||
|
any other changes to the folder
|
||||||
|
|
||||||
|
Return False if corruption is detected, otherwise True
|
||||||
|
"""
|
||||||
|
# iterate through the files and run a check hash
|
||||||
|
no_corrupted_files = True
|
||||||
|
|
||||||
|
for file_id in self.files:
|
||||||
|
file = self.get_file_by_id(file_uuid=file_id)
|
||||||
|
no_corrupted_files = file.check_hash()
|
||||||
|
|
||||||
|
# if one file in the folder is corrupted, set the folder status to corrupted
|
||||||
|
if not no_corrupted_files:
|
||||||
|
self.corrupt()
|
||||||
|
|
||||||
|
return no_corrupted_files
|
||||||
|
|
||||||
|
|
||||||
class File(FileSystemItemABC):
|
class File(FileSystemItemABC):
|
||||||
"""
|
"""
|
||||||
@@ -629,3 +669,36 @@ class File(FileSystemItemABC):
|
|||||||
|
|
||||||
path = self.folder.name + "/" + self.name
|
path = self.folder.name + "/" + self.name
|
||||||
self.folder.fs.sys_log.info(f"Corrupted file {self.sim_path if self.sim_path else path}")
|
self.folder.fs.sys_log.info(f"Corrupted file {self.sim_path if self.sim_path else path}")
|
||||||
|
|
||||||
|
def check_hash(self) -> bool:
|
||||||
|
"""
|
||||||
|
Check if the file has been changed.
|
||||||
|
|
||||||
|
If changed, the file is considered corrupted.
|
||||||
|
|
||||||
|
Return False if corruption is detected, otherwise True
|
||||||
|
"""
|
||||||
|
current_hash = None
|
||||||
|
|
||||||
|
# if file is real, read the file contents
|
||||||
|
if self.real:
|
||||||
|
with open(self.sim_path, "rb") as f:
|
||||||
|
file_hash = hashlib.blake2b()
|
||||||
|
while chunk := f.read(8192):
|
||||||
|
file_hash.update(chunk)
|
||||||
|
|
||||||
|
current_hash = file_hash.hexdigest()
|
||||||
|
else:
|
||||||
|
# otherwise get describe_state dict and hash that
|
||||||
|
current_hash = hashlib.blake2b(json.dumps(self.describe_state(), sort_keys=True).encode()).hexdigest()
|
||||||
|
|
||||||
|
# if the previous hash is None, set the current hash to previous
|
||||||
|
if self.previous_hash is None:
|
||||||
|
self.previous_hash = current_hash
|
||||||
|
|
||||||
|
# if the previous hash and current hash do not match, mark file as corrupted
|
||||||
|
if self.previous_hash is not current_hash:
|
||||||
|
self.corrupt()
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|||||||
@@ -169,6 +169,60 @@ def test_folder_corrupt_repair(file_system):
|
|||||||
assert file.status == FileSystemItemStatus.GOOD
|
assert file.status == FileSystemItemStatus.GOOD
|
||||||
|
|
||||||
|
|
||||||
|
def test_simulated_file_check_hash(file_system):
|
||||||
|
file: File = file_system.create_file(file_name="test_file.txt", folder_name="test_folder")
|
||||||
|
|
||||||
|
assert file.check_hash() is True
|
||||||
|
|
||||||
|
# change simulated file size
|
||||||
|
file.sim_size = 0
|
||||||
|
assert file.check_hash() is False
|
||||||
|
assert file.status == FileSystemItemStatus.CORRUPTED
|
||||||
|
|
||||||
|
|
||||||
|
def test_real_file_check_hash(file_system):
|
||||||
|
file: File = file_system.create_file(file_name="test_file.txt", folder_name="test_folder", real=True)
|
||||||
|
|
||||||
|
assert file.check_hash() is True
|
||||||
|
|
||||||
|
# change file content
|
||||||
|
with open(file.sim_path, "a") as f:
|
||||||
|
f.write("get hacked scrub lol xD\n")
|
||||||
|
|
||||||
|
assert file.check_hash() is False
|
||||||
|
assert file.status == FileSystemItemStatus.CORRUPTED
|
||||||
|
|
||||||
|
|
||||||
|
def test_simulated_folder_check_hash(file_system):
|
||||||
|
folder: Folder = file_system.create_folder(folder_name="test_folder")
|
||||||
|
file_system.create_file(file_name="test_file.txt", folder_name="test_folder")
|
||||||
|
|
||||||
|
assert folder.check_hash() is True
|
||||||
|
|
||||||
|
# change simulated file size
|
||||||
|
file = folder.get_file(file_name="test_file.txt")
|
||||||
|
file.sim_size = 0
|
||||||
|
assert folder.check_hash() is False
|
||||||
|
assert folder.status == FileSystemItemStatus.CORRUPTED
|
||||||
|
|
||||||
|
|
||||||
|
def test_real_folder_check_hash(file_system):
|
||||||
|
folder: Folder = file_system.create_folder(folder_name="test_folder")
|
||||||
|
file_system.create_file(file_name="test_file.txt", folder_name="test_folder", real=True)
|
||||||
|
|
||||||
|
assert folder.check_hash() is True
|
||||||
|
|
||||||
|
# change simulated file size
|
||||||
|
file = folder.get_file(file_name="test_file.txt")
|
||||||
|
|
||||||
|
# change file content
|
||||||
|
with open(file.sim_path, "a") as f:
|
||||||
|
f.write("get hacked scrub lol xD\n")
|
||||||
|
|
||||||
|
assert folder.check_hash() is False
|
||||||
|
assert folder.status == FileSystemItemStatus.CORRUPTED
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Skipping until we tackle serialisation")
|
@pytest.mark.skip(reason="Skipping until we tackle serialisation")
|
||||||
def test_serialisation(file_system):
|
def test_serialisation(file_system):
|
||||||
"""Test to check that the object serialisation works correctly."""
|
"""Test to check that the object serialisation works correctly."""
|
||||||
|
|||||||
Reference in New Issue
Block a user