Skip to content

Commit

Permalink
Move drafting:fileLocalPath to PathRelationshipFacet, updating class …
Browse files Browse the repository at this point in the history
…hierarchy

A prior patch in this series tried updating a non-existent property
`uco-observable:fileLocalPath` to a `FileFacet` attached to the subject
file within the SD card.  This highlighted that the property is not
defined in the ontology, and this patch was written to describe why it
should not be.

"Local" is a confusing term, due to absence of context - to what
directory is this path local?  Is that directory through the user
interface on the phone, which would confuse in details from a running
operating system?  Or is the directory local to the extraction computer,
which would confuse in details from the investigation?

This patch removes the ambiguity inherent in `drafting:fileLocalPath`
and moves the path to a `PathRelationFacet`, which attaches to an
`ObservableRelationship` that can define the basis of "local".

This patch adds a guess that the path's new spelling suggested an
extraction directory special to the investigation.  Chain of custody
details are left for a future patch to implement.

A side-effect of this patch is that some of the API usage was found to
not make use of the subclass hierarchy.  `super()` and anonymous
argument & parameter forwarding is now used to delegate handling generic
properties to their originating superclasses.  E.g., note how a
`uco-observable:File` picked up a `core:description` property by the
property's addition as a keyword parameter on `base.ObjectEntity`.

A follow-on patch will regenerate Make-managed files.

Signed-off-by: Alex Nelson <[email protected]>
  • Loading branch information
ajnelson-nist committed May 7, 2024
1 parent 7c99c9d commit 40a08c5
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 58 deletions.
18 changes: 16 additions & 2 deletions case_mapping/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json
from datetime import datetime
from typing import Any
from typing import Any, List, Optional

from cdo_local_uuid import local_uuid

Expand Down Expand Up @@ -187,8 +187,22 @@ def __handle_var_type_errors(var_name, var_val, expected_type):


class ObjectEntity(FacetEntity):
def __init__(self, *args: Any, **kwargs: Any) -> None:
def __init__(
self,
*args: Any,
description: Optional[str] = None,
facets: Optional[List[FacetEntity]] = None,
**kwargs: Any,
) -> None:
"""
:param facets: This will contain specific properties for this object
"""
super().__init__(*args, **kwargs)
self["@type"] = "uco-core:UcoObject"
if description is not None:
self["uco-core:description"] = description
if isinstance(facets, list):
self.append_facets(*facets)

@unpack_args_array
def append_facets(self, *args):
Expand Down
56 changes: 55 additions & 1 deletion case_mapping/uco/core.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from typing import Any
from datetime import datetime
from typing import Any, Optional

from pytz import timezone

from ..base import ObjectEntity, unpack_args_array

Expand Down Expand Up @@ -80,4 +83,55 @@ def append_to_uco_core_description(self, *args):
self._append_strings("uco-core:description", *args)


class Relationship(ObjectEntity):
def __init__(
self,
*args: Any,
source: ObjectEntity,
target: ObjectEntity,
start_time: Optional[datetime] = None,
end_time: Optional[datetime] = None,
kind_of_relationship: str,
directional: bool = False,
**kwargs: Any,
) -> None:
"""
This object represents an assertion that one or more objects are related to another object in some way
:param source: A UcoObject
:param target: A UcoObject
:param start_time: The time, in ISO8601 time format, the action was started (e.g., "2020-09-29T12:13:01Z")
:param end_time: The time, in ISO8601 time format, the action completed (e.g., "2020-09-29T12:13:43Z")
:param kind_of_relationship: How these items relate from source to target (e.g., "Contained_Within")
:param directional: A boolean whether a relationship assertion is limited to the context FROM a source object(s) TO a target object.
"""
super().__init__(*args, **kwargs)
self["@type"] = "uco-core:Relationship"
self._bool_vars(**{"uco-core:isDirectional": directional})
self._str_vars(**{"uco-core:kindOfRelationship": kind_of_relationship})
self._datetime_vars(
**{
"uco-core:startTime": start_time,
"uco-core:endTime": end_time,
}
)
self._node_reference_vars(
**{"uco-core:source": source, "uco-core:target": target}
)

def set_start_accessed_time(self) -> None:
"""Set the time when this relationship initiated."""
self._addtime(_type="start")

def set_end_accessed_time(self) -> None:
"""Set the time when this relationship completed."""
self._addtime(_type="end")

def _addtime(self, _type: str) -> None:
time = datetime.now(timezone("UTC"))
self[f"uco-core:{_type}Time"] = {
"@type": "xsd:dateTime",
"@value": time.isoformat(),
}


directory = {"uco-core:Bundle": Bundle}
107 changes: 53 additions & 54 deletions case_mapping/uco/observable.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
from typing import Any, Dict, List, Optional, Union

from cdo_local_uuid import local_uuid
from pytz import timezone

from ..base import FacetEntity, ObjectEntity, unpack_args_array
from .core import Relationship
from .identity import Identity


Expand Down Expand Up @@ -365,19 +365,26 @@ def __init__(self, name=None, address=None):
)


class ObservableObject(ObjectEntity):
def __init__(self, has_changed=None, state=None, facets=None):
class Observable(ObjectEntity):
def __init__(
self,
*args: Any,
**kwargs: Any,
) -> None:
super().__init__(*args, **kwargs)


class ObservableObject(Observable):
def __init__(self, *args: Any, has_changed=None, state=None, **kwargs: Any) -> None:
"""
An observable object is a grouping of characteristics unique to a distinct article or unit within the digital domain.
:param has_changed:
:param state:
:param facets: This will contain specific properties for this object
"""
super().__init__()
super().__init__(*args, **kwargs)
self["@type"] = "uco-observable:ObservableObject"
self._str_vars(**{"uco-observable:state": state})
self._bool_vars(**{"uco-observable:hasChanged": has_changed})
self.append_facets(facets)


class FacetUrlHistory(FacetEntity):
Expand Down Expand Up @@ -988,6 +995,12 @@ def __init__(
self._bool_vars(**{"uco-observable:isSecure": secure})


class File(ObservableObject):
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self["@type"] = "uco-observable:File"


class FacetFile(FacetEntity):
def __init__(
self,
Expand All @@ -1001,7 +1014,6 @@ def __init__(
file_modified_time: Optional[datetime] = None,
file_created_time: Optional[datetime] = None,
file_size_bytes: Union[int, None] = None,
file_local_path: Optional[str] = None,
file_mime_type: Optional[str] = None,
):
"""
Expand All @@ -1016,7 +1028,6 @@ def __init__(
:param file_created_time: The datetime the file was created
:param file_modified_time: The datetime the file was last modified
:param file_metadata_changed_time: The last change to metadata of a file but not necessarily the file contents
:param file_local_path: Represents the local path of the file
:param file_mime_type: A generic (string) tag/label of e file, or example 'text/html' or 'audio/mp3.
"""
super().__init__()
Expand All @@ -1025,7 +1036,6 @@ def __init__(
**{
"uco-observable:fileName": file_name,
"uco-observable:filePath": file_path,
"drafting:fileLocalPath": file_local_path,
"uco-observable:extension": file_extension,
"uco-observable:allocationStatus": file_allocation_status,
"uco-observable:mimeType": file_mime_type,
Expand Down Expand Up @@ -1176,7 +1186,7 @@ def __init__(


class FacetPathRelation(FacetEntity):
def __init__(self, path):
def __init__(self, path: str) -> None:
"""
This CASE object specifies the location of one object within another containing object.
:param path: The full path to the object (e.g, "/sdcard/IMG_0123.jpg")
Expand Down Expand Up @@ -1228,53 +1238,42 @@ def __init__(
)


class ObservableRelationship(ObjectEntity):
class ObservableRelationship(Observable, Relationship):
def __init__(
self,
source,
target,
start_time=None,
end_time=None,
kind_of_relationship=None,
directional=False,
):
"""
This object represents an assertion that one or more objects are related to another object in some way
:param source: An observable object
:param target: An observable object
:param start_time: The time, in ISO8601 time format, the action was started (e.g., "2020-09-29T12:13:01Z")
:param end_time: The time, in ISO8601 time format, the action completed (e.g., "2020-09-29T12:13:43Z")
:param kind_of_relationship: How these items relate from source to target (e.g., "Contained_Within")
:param directional: A boolean whether a relationship assertion is limited to the context FROM a source object(s) TO a target object.
"""
super().__init__()
self["@type"] = "uco-observable:ObservableRelationship"
self._bool_vars(**{"uco-core:isDirectional": directional})
self._str_vars(**{"uco-core:kindOfRelationship": kind_of_relationship})
self._datetime_vars(
**{
"uco-observable:startTime": start_time,
"uco-observable:endTime": end_time,
}
)
self._node_reference_vars(
**{"uco-core:source": source, "uco-core:target": target}
*args: Any,
source: ObjectEntity,
target: ObjectEntity,
start_time: Optional[datetime] = None,
end_time: Optional[datetime] = None,
kind_of_relationship: str,
directional: bool = False,
**kwargs: Any,
) -> None:
"""
This object represents an assertion that one or more observable objects are related to another object in some way. Other parameters are as in uco.core.Relationship.
:param source: An observable object (specialized over Relationship)
:param target: An observable object (specialized over Relationship)
"""
if not isinstance(source, Observable):
raise TypeError(
"The source of an ObservableRelationship must be an Observable."
)
if not isinstance(target, Observable):
raise TypeError(
"The target of an ObservableRelationship must be an Observable."
)
super().__init__(
*args,
source=source,
target=target,
start_time=start_time,
end_time=end_time,
kind_of_relationship=kind_of_relationship,
directional=directional,
**kwargs,
)

def set_start_accessed_time(self):
"""Set the time when this action initiated."""
self._addtime(_type="start")

def set_end_accessed_time(self):
"""Set the time when this action completed."""
self._addtime(_type="end")

def _addtime(self, _type):
time = datetime.now(timezone("UTC"))
self[f"uco-observable:{_type}Time"] = {
"@type": "xsd:dateTime",
"@value": time.isoformat(),
}
self["@type"] = "uco-observable:ObservableRelationship"


class FacetApplicationAccount(FacetEntity):
Expand Down
34 changes: 33 additions & 1 deletion example.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ def _next_timestamp() -> datetime:
file_modified_time=f_date_modified,
file_created_time=f_date_created,
file_size_bytes=35002,
file_local_path="files/Image/ImG_0123.jpg",
file_mime_type="image/jpeg",
)

Expand Down Expand Up @@ -498,6 +497,39 @@ def _next_timestamp() -> datetime:
bundle.append_to_uco_object(provenance_rec_object)


###############################
# Recording an extracted file #
###############################

# TODO - Record provenance of extracted file.

# Record extracted file.
extracted_file_file_facet1 = uco.observable.FacetFile(
file_extension="jpg",
file_name="IMG_0123.jpg",
file_is_directory=False,
)
extracted_file_1 = uco.observable.File(facets=[extracted_file_file_facet1])
bundle.append_to_uco_object(extracted_file_1)

extraction_root_directory_file_facet1 = uco.observable.FacetFile(file_is_directory=True)
extraction_root_directory1 = uco.observable.File(
description="Root directory of file extractions for this case.",
facets=[extraction_root_directory_file_facet1],
)
bundle.append_to_uco_object(extraction_root_directory1)

path_relation_facet1 = uco.observable.FacetPathRelation(path="files/Image/ImG_0123.jpg")
path_relation = uco.observable.ObservableRelationship(
directional=True,
kind_of_relationship="Contained_Within",
source=extracted_file_1,
target=extraction_root_directory1,
facets=[path_relation_facet1],
)
bundle.append_to_uco_object(path_relation)


##################
# Print the case #
##################
Expand Down

0 comments on commit 40a08c5

Please sign in to comment.