diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a2b4da1..730b081 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,14 +24,14 @@ jobs: - name: Install Dependencies run: | - pip -q install poetry pre-commit + pip -q install poetry pre-commit rdflib poetry install - name: Pre-commit Checks run: pre-commit run --all-files - name: Unit Tests - run: poetry run pytest + run: poetry run pytest --doctest-modules - name: Run Example run: poetry run python example.py > case.jsonld @@ -44,6 +44,11 @@ jobs: case-version: "case-1.3.0" extension-filter: "jsonld" + - name: Convert example + run: | + rdfpipe --output-format turtle case.jsonld > case.ttl + test 0 -eq $(grep 'file:' case.ttl | wc -l) || (echo "ERROR:ci.yml:Some graph IRIs do not supply a resolving prefix. Look for the string 'file:///' in the file case.ttl (created by running 'make check') to see these instances." >&2 ; exit 1) + # Always build the package as a sanity check to ensure no issues with the build system # exist as part of the CI process - name: Build Package diff --git a/.gitignore b/.gitignore index ad89e2d..2d7de5f 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ __pycache__/ # Build Artifacts build/ dist/ +case.ttl diff --git a/Makefile b/Makefile index 8abb4e9..5b2e0b2 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,26 @@ case.jsonld: \ _$@ mv _$@ $@ +case.ttl: \ + case.jsonld + source venv/bin/activate \ + && rdfpipe \ + --output-format turtle \ + $< \ + > _$@ + source venv/bin/activate \ + && case_validate \ + _$@ + @# In instances where graph nodes omit use of a prefix, a 'default' prefix-base is used that incorporates the local directory as a file URL. This is likely to be an undesired behavior, so for the generated example JSON-LD in the top source directory, check that "file:///" doesn't start any of the graph individuals' IRIs. + test \ + 0 \ + -eq \ + $$(grep 'file:' _$@ | wc -l) \ + || ( echo "ERROR:Makefile:Some graph IRIs do not supply a resolving prefix. Look for the string 'file:///' in the file _$@ to see these instances." >&2 ; exit 1) + mv _$@ $@ + check: \ - all + all \ + case.ttl source venv/bin/activate \ - && poetry run pytest + && poetry run pytest --doctest-modules diff --git a/case.jsonld b/case.jsonld index f87e17a..2e0aafe 100644 --- a/case.jsonld +++ b/case.jsonld @@ -1,5 +1,5 @@ { - "@id": "aef8e4c4-db83-59fd-8f71-b65cd7676c0a", + "@id": "kb:aef8e4c4-db83-59fd-8f71-b65cd7676c0a", "@context": { "@vocab": "http://caseontology.org/core#", "case-investigation": "https://ontology.caseontology.org/case/investigation/", @@ -15,36 +15,37 @@ "uco-tool": "https://ontology.unifiedcyberontology.org/uco/tool/", "uco-types": "https://ontology.unifiedcyberontology.org/uco/types/", "uco-vocabulary": "https://ontology.unifiedcyberontology.org/uco/vocabulary/", - "xsd": "http://www.w3.org/2001/XMLSchema#" + "xsd": "http://www.w3.org/2001/XMLSchema#", + "kb": "http://example.org/kb/" }, "@type": "uco-core:Bundle", "uco-core:description": "An Example Case File", "uco-core:object": [ { - "@id": "307df50e-0116-5c51-904c-d5aa69279018", + "@id": "kb:307df50e-0116-5c51-904c-d5aa69279018", "@type": "uco-identity:Organization", "uco-core:name": "Nikon" }, { - "@id": "1ccd06e1-1a39-53c2-8e70-86ad7c48ec5c", + "@id": "kb:1ccd06e1-1a39-53c2-8e70-86ad7c48ec5c", "@type": "uco-observable:ObservableObject", "uco-core:hasFacet": [ { - "@id": "e26d7f7d-081c-58e5-8990-77ec13d60d89", + "@id": "kb:e26d7f7d-081c-58e5-8990-77ec13d60d89", "@type": "uco-observable:DeviceFacet", "uco-observable:manufacturer": { - "@id": "307df50e-0116-5c51-904c-d5aa69279018" + "@id": "kb:307df50e-0116-5c51-904c-d5aa69279018" }, "uco-observable:model": "D750" } ] }, { - "@id": "fa07818e-5832-5d5f-81b4-f7e143e98252", + "@id": "kb:fa07818e-5832-5d5f-81b4-f7e143e98252", "@type": "uco-observable:ObservableObject", "uco-core:hasFacet": [ { - "@id": "9ab260ac-cdf5-5524-9047-3ea85ebfb6e2", + "@id": "kb:9ab260ac-cdf5-5524-9047-3ea85ebfb6e2", "@type": "uco-observable:FileFacet", "uco-observable:fileSystemType": "EXT4", "uco-observable:fileName": "IMG_0123.jpg", @@ -56,7 +57,7 @@ } }, { - "@id": "09cab366-d82b-57f8-a94c-9814bf7ef63d", + "@id": "kb:09cab366-d82b-57f8-a94c-9814bf7ef63d", "@type": "uco-observable:ContentDataFacet", "uco-observable:magicNumber": "/9j/ww==", "uco-observable:mimeType": "image/jpg", @@ -71,7 +72,7 @@ }, "uco-observable:hash": [ { - "@id": "84c0e1ca-a4da-59f3-b60a-096983862089", + "@id": "kb:84c0e1ca-a4da-59f3-b60a-096983862089", "@type": "uco-types:Hash", "uco-types:hashMethod": { "@type": "uco-vocabulary:HashNameVocab", @@ -85,7 +86,7 @@ ] }, { - "@id": "f3ce4eee-0a5d-54a7-9cc7-5ac043234d77", + "@id": "kb:f3ce4eee-0a5d-54a7-9cc7-5ac043234d77", "@type": "uco-observable:RasterPictureFacet", "uco-observable:pictureType": "jpg", "uco-observable:pictureHeight": { @@ -102,20 +103,20 @@ } }, { - "@id": "d6585cd5-ad4b-54c3-a649-d45d12d1605d", + "@id": "kb:d6585cd5-ad4b-54c3-a649-d45d12d1605d", "@type": "uco-observable:EXIFFacet", "uco-observable:exifData": { - "@id": "ab4cd39a-dea8-558b-a49e-4aedd9aff7f1", + "@id": "kb:ab4cd39a-dea8-558b-a49e-4aedd9aff7f1", "@type": "uco-types:ControlledDictionary", "uco-types:entry": [ { - "@id": "7721bd93-fff7-5e1d-a14e-2d627c399ebe", + "@id": "kb:7721bd93-fff7-5e1d-a14e-2d627c399ebe", "@type": "uco-types:ControlledDictionaryEntry", "uco-types:key": "Make", "uco-types:value": "Canon" }, { - "@id": "9ed3e5de-cde8-5cec-a71b-774e485da67d", + "@id": "kb:9ed3e5de-cde8-5cec-a71b-774e485da67d", "@type": "uco-types:ControlledDictionaryEntry", "uco-types:key": "Model", "uco-types:value": "Powershot" @@ -126,12 +127,12 @@ ] }, { - "@id": "2ed228fb-6c23-567a-8128-c891ccdb0dc6", + "@id": "kb:2ed228fb-6c23-567a-8128-c891ccdb0dc6", "@type": "uco-identity:Organization", "uco-core:name": "Apple" }, { - "@id": "268ab394-417e-579a-871b-84947adc9926", + "@id": "kb:268ab394-417e-579a-871b-84947adc9926", "@type": "case-investigation:InvestigativeAction", "uco-core:name": "annotated", "uco-action:startTime": { @@ -144,10 +145,10 @@ }, "uco-core:hasFacet": [ { - "@id": "102c218a-bc39-52e6-8228-abbc200378cf", + "@id": "kb:102c218a-bc39-52e6-8228-abbc200378cf", "@type": "uco-observable:DeviceFacet", "uco-observable:manufacturer": { - "@id": "2ed228fb-6c23-567a-8128-c891ccdb0dc6" + "@id": "kb:2ed228fb-6c23-567a-8128-c891ccdb0dc6" }, "uco-observable:deviceType": "iPhone", "uco-observable:model": "6XS", @@ -156,12 +157,12 @@ ] }, { - "@id": "f3f8c0fb-6a78-5547-aa4f-7c995d9a8732", + "@id": "kb:f3f8c0fb-6a78-5547-aa4f-7c995d9a8732", "@type": "uco-identity:Organization", "uco-core:name": "oneplus" }, { - "@id": "19ff5a9d-1d15-5a6f-858d-28528d5f90d9", + "@id": "kb:19ff5a9d-1d15-5a6f-858d-28528d5f90d9", "@type": "case-investigation:InvestigativeAction", "uco-core:name": "annotated", "uco-action:startTime": { @@ -174,10 +175,10 @@ }, "uco-core:hasFacet": [ { - "@id": "713d0464-a90c-52f3-88f4-1f0a4a9de091", + "@id": "kb:713d0464-a90c-52f3-88f4-1f0a4a9de091", "@type": "uco-observable:DeviceFacet", "uco-observable:manufacturer": { - "@id": "f3f8c0fb-6a78-5547-aa4f-7c995d9a8732" + "@id": "kb:f3f8c0fb-6a78-5547-aa4f-7c995d9a8732" }, "uco-observable:deviceType": "Android", "uco-observable:model": "8", @@ -186,7 +187,7 @@ ] }, { - "@id": "56004659-8649-5ef2-b2e9-e1dae56877f0", + "@id": "kb:56004659-8649-5ef2-b2e9-e1dae56877f0", "@type": "uco-observable:ObservableRelationship", "uco-core:isDirectional": { "@type": "xsd:boolean", @@ -194,38 +195,38 @@ }, "uco-core:kindOfRelationship": "Contained_Within", "uco-core:source": { - "@id": "1ccd06e1-1a39-53c2-8e70-86ad7c48ec5c" + "@id": "kb:1ccd06e1-1a39-53c2-8e70-86ad7c48ec5c" }, "uco-core:target": { - "@id": "fa07818e-5832-5d5f-81b4-f7e143e98252" + "@id": "kb:fa07818e-5832-5d5f-81b4-f7e143e98252" }, "uco-core:hasFacet": [ { - "@id": "862e79c2-ecab-5798-8c43-09ac916ab0d4", + "@id": "kb:862e79c2-ecab-5798-8c43-09ac916ab0d4", "@type": "uco-observable:PathRelationFacet", "uco-observable:path": "/sdcard/IMG_0123.jpg" } ] }, { - "@id": "72930cf3-7693-5c80-ae79-ca82151b5d30", + "@id": "kb:72930cf3-7693-5c80-ae79-ca82151b5d30", "@type": "uco-observable:ObservableObject", "uco-core:hasFacet": [ { - "@id": "5a33cc98-bf34-5011-82ee-1e664da00d29", + "@id": "kb:5a33cc98-bf34-5011-82ee-1e664da00d29", "@type": "uco-observable:EmailAccountFacet", "uco-observable:emailAddress": { - "@id": "94adcf50-cad5-5a2d-9be3-e1e42d698028" + "@id": "kb:94adcf50-cad5-5a2d-9be3-e1e42d698028" } } ] }, { - "@id": "94adcf50-cad5-5a2d-9be3-e1e42d698028", + "@id": "kb:94adcf50-cad5-5a2d-9be3-e1e42d698028", "@type": "uco-observable:ObservableObject", "uco-core:hasFacet": [ { - "@id": "069043f2-89ee-532e-892b-d90a55ed987c", + "@id": "kb:069043f2-89ee-532e-892b-d90a55ed987c", "@type": "uco-observable:EmailAddressFacet", "uco-observable:addressValue": "info@example.com", "uco-observable:displayName": "Example User" @@ -233,24 +234,24 @@ ] }, { - "@id": "742fedd8-ec56-5efd-bbae-eac6de464215", + "@id": "kb:742fedd8-ec56-5efd-bbae-eac6de464215", "@type": "uco-observable:ObservableObject", "uco-core:hasFacet": [ { - "@id": "1e2a360e-9b34-5fa9-978d-e28c455e59d0", + "@id": "kb:1e2a360e-9b34-5fa9-978d-e28c455e59d0", "@type": "uco-observable:EmailAccountFacet", "uco-observable:emailAddress": { - "@id": "a3f00ca2-db54-51a9-a281-1125bbd37783" + "@id": "kb:a3f00ca2-db54-51a9-a281-1125bbd37783" } } ] }, { - "@id": "a3f00ca2-db54-51a9-a281-1125bbd37783", + "@id": "kb:a3f00ca2-db54-51a9-a281-1125bbd37783", "@type": "uco-observable:ObservableObject", "uco-core:hasFacet": [ { - "@id": "f72553c7-7fa7-5354-aa68-7837e9e7b8c4", + "@id": "kb:f72553c7-7fa7-5354-aa68-7837e9e7b8c4", "@type": "uco-observable:EmailAddressFacet", "uco-observable:addressValue": "admin@example.com", "uco-observable:displayName": "Example Admin" @@ -258,11 +259,11 @@ ] }, { - "@id": "70e47458-fd7d-594d-816e-272d5bb1e440", + "@id": "kb:70e47458-fd7d-594d-816e-272d5bb1e440", "@type": "uco-observable:ObservableObject", "uco-core:hasFacet": [ { - "@id": "60d29bf6-1cb0-5974-866a-2f1d60ee4a51", + "@id": "kb:60d29bf6-1cb0-5974-866a-2f1d60ee4a51", "@type": "uco-observable:EmailMessageFacet", "uco-observable:subject": "Thoughts on Our Next Book Club Pick?", "uco-observable:body": "Hello fellow bookworms! It's that time again.", @@ -276,54 +277,54 @@ "@value": "2023-01-01T01:06:06.000006+00:00" }, "uco-observable:from": { - "@id": "94adcf50-cad5-5a2d-9be3-e1e42d698028" + "@id": "kb:94adcf50-cad5-5a2d-9be3-e1e42d698028" }, "uco-observable:to": [ { - "@id": "94adcf50-cad5-5a2d-9be3-e1e42d698028" + "@id": "kb:94adcf50-cad5-5a2d-9be3-e1e42d698028" }, { - "@id": "a3f00ca2-db54-51a9-a281-1125bbd37783" + "@id": "kb:a3f00ca2-db54-51a9-a281-1125bbd37783" } ] } ] }, { - "@id": "41bc2540-fdd8-5753-864c-00a5de7d0d2e", + "@id": "kb:41bc2540-fdd8-5753-864c-00a5de7d0d2e", "@type": "uco-observable:ObservableObject", "uco-core:hasFacet": [ { - "@id": "0f0b71e7-39a8-5249-83a5-b52888d5c8b7", + "@id": "kb:0f0b71e7-39a8-5249-83a5-b52888d5c8b7", "@type": "uco-observable:URLFacet", "uco-observable:fullValue": "www.docker.com/howto" } ] }, { - "@id": "9af4324d-cb7b-54d7-aba4-95714396435e", + "@id": "kb:9af4324d-cb7b-54d7-aba4-95714396435e", "@type": "uco-observable:ObservableObject", "uco-core:hasFacet": [ { - "@id": "d5797860-8d73-5a53-a6fc-660a4e8c64e2", + "@id": "kb:d5797860-8d73-5a53-a6fc-660a4e8c64e2", "@type": "uco-observable:ApplicationFacet", "uco-core:name": "Safari" } ] }, { - "@id": "997f05df-f309-588d-829f-c20e3cbadb57", + "@id": "kb:997f05df-f309-588d-829f-c20e3cbadb57", "@type": "uco-observable:ObservableObject", "uco-core:hasFacet": [ { - "@id": "697d25c6-00bd-571e-aebd-5a96e34ea4b5", + "@id": "kb:697d25c6-00bd-571e-aebd-5a96e34ea4b5", "@type": "uco-observable:URLHistoryFacet", "uco-observable:browserInformation": { - "@id": "9af4324d-cb7b-54d7-aba4-95714396435e" + "@id": "kb:9af4324d-cb7b-54d7-aba4-95714396435e" }, "uco-observable:urlHistoryEntry": [ { - "@id": "641fc5c4-49a3-573e-bdb2-d6d8dc000fee", + "@id": "kb:641fc5c4-49a3-573e-bdb2-d6d8dc000fee", "@type": "uco-observable:URLHistoryEntry", "uco-observable:browserUserProfile": "Jill", "uco-observable:expirationTime": { @@ -346,7 +347,7 @@ }, "uco-observable:pageTitle": "Docker tutorial", "uco-observable:url": { - "@id": "41bc2540-fdd8-5753-864c-00a5de7d0d2e" + "@id": "kb:41bc2540-fdd8-5753-864c-00a5de7d0d2e" }, "uco-observable:visitCount": { "@type": "xsd:integer", @@ -354,7 +355,7 @@ } }, { - "@id": "5860f49a-8471-5984-9610-402672d63f56", + "@id": "kb:5860f49a-8471-5984-9610-402672d63f56", "@type": "uco-observable:URLHistoryEntry", "uco-observable:browserUserProfile": "Tamasin", "uco-observable:expirationTime": { @@ -377,7 +378,7 @@ }, "uco-observable:pageTitle": "GitHub actions tutorial", "uco-observable:url": { - "@id": "41bc2540-fdd8-5753-864c-00a5de7d0d2e" + "@id": "kb:41bc2540-fdd8-5753-864c-00a5de7d0d2e" }, "uco-observable:visitCount": { "@type": "xsd:integer", @@ -389,33 +390,33 @@ ] }, { - "@id": "6e0ac6ce-6afc-58c6-b4b6-501fa5de2699", + "@id": "kb:6e0ac6ce-6afc-58c6-b4b6-501fa5de2699", "@type": "uco-observable:ObservableObject", "uco-core:hasFacet": [ { - "@id": "00f08d34-2abd-53e3-b758-cd5ba00300c9", + "@id": "kb:00f08d34-2abd-53e3-b758-cd5ba00300c9", "@type": "uco-observable:PhoneAccountFacet", "uco-observable:phoneNumber": "123456" } ] }, { - "@id": "3d6f634e-7c5d-5ff8-b996-8d738bb2793a", + "@id": "kb:3d6f634e-7c5d-5ff8-b996-8d738bb2793a", "@type": "uco-observable:ObservableObject", "uco-core:hasFacet": [ { - "@id": "17525359-a042-5503-ad5f-f8f9bcd53472", + "@id": "kb:17525359-a042-5503-ad5f-f8f9bcd53472", "@type": "uco-observable:PhoneAccountFacet", "uco-observable:phoneNumber": "987654" } ] }, { - "@id": "1c09087a-0577-5025-a23b-c3e0155a5bf7", + "@id": "kb:1c09087a-0577-5025-a23b-c3e0155a5bf7", "@type": "uco-observable:ObservableObject", "uco-core:hasFacet": [ { - "@id": "0336f3a3-ff74-5e34-97bb-14534316f393", + "@id": "kb:0336f3a3-ff74-5e34-97bb-14534316f393", "@type": "uco-observable:MessageFacet", "uco-observable:messageText": "Are you free this weekend?", "uco-observable:sentTime": { @@ -423,39 +424,39 @@ "@value": "2023-01-01T01:08:08.000008+00:00" }, "uco-observable:from": { - "@id": "6e0ac6ce-6afc-58c6-b4b6-501fa5de2699" + "@id": "kb:6e0ac6ce-6afc-58c6-b4b6-501fa5de2699" }, "uco-observable:to": [ { - "@id": "6e0ac6ce-6afc-58c6-b4b6-501fa5de2699" + "@id": "kb:6e0ac6ce-6afc-58c6-b4b6-501fa5de2699" }, { - "@id": "3d6f634e-7c5d-5ff8-b996-8d738bb2793a" + "@id": "kb:3d6f634e-7c5d-5ff8-b996-8d738bb2793a" } ], "uco-observable:application": { - "@id": "80c8f41b-e720-51db-b650-1df11b5f2acb" + "@id": "kb:80c8f41b-e720-51db-b650-1df11b5f2acb" } } ] }, { - "@id": "80c8f41b-e720-51db-b650-1df11b5f2acb", + "@id": "kb:80c8f41b-e720-51db-b650-1df11b5f2acb", "@type": "uco-observable:ObservableObject", "uco-core:hasFacet": [ { - "@id": "35fb4ae9-6e55-59b7-a797-6aff1494a314", + "@id": "kb:35fb4ae9-6e55-59b7-a797-6aff1494a314", "@type": "uco-observable:ApplicationFacet", "uco-core:name": "WhatsApp" } ] }, { - "@id": "6d5906a1-df3f-5aa3-8c0d-c76c570d8b96", + "@id": "kb:6d5906a1-df3f-5aa3-8c0d-c76c570d8b96", "@type": "uco-identity:Identity", "uco-core:hasFacet": [ { - "@id": "742d8932-3fe2-5c0f-afc4-9dd2e20533e3", + "@id": "kb:742d8932-3fe2-5c0f-afc4-9dd2e20533e3", "@type": "uco-identity:BirthInformationFacet", "uco-identity:birthdate": { "@type": "xsd:dateTime", @@ -463,7 +464,7 @@ } }, { - "@id": "24cd8876-1c97-5fce-9976-8733f6e901f6", + "@id": "kb:24cd8876-1c97-5fce-9976-8733f6e901f6", "@type": "uco-identity:SimpleNameFacet", "uco-identity:givenName": "Davey", "uco-identity:familyName": "Jones" @@ -471,11 +472,11 @@ ] }, { - "@id": "32599b39-84d2-5471-9452-c494171897fd", + "@id": "kb:32599b39-84d2-5471-9452-c494171897fd", "@type": "uco-location:Location", "uco-core:hasFacet": [ { - "@id": "6f716a17-3a29-513a-9e21-8bf6112c1d50", + "@id": "kb:6f716a17-3a29-513a-9e21-8bf6112c1d50", "@type": "uco-location:LatLongCoordinatesFacet", "uco-location:latitude": { "@type": "xsd:decimal", @@ -489,18 +490,18 @@ ] }, { - "@id": "3344007e-c9c4-5437-9766-4001fc9f3af5", + "@id": "kb:3344007e-c9c4-5437-9766-4001fc9f3af5", "@type": "case-investigation:Investigation", "uco-core:name": "Crime A", "case-investigation:focus": "Transfer of Illicit Materials", "uco-core:description": "Inquiry into the transfer of illicit materials and the devices used to do so", "uco-core:object": [ { - "@id": "fa07818e-5832-5d5f-81b4-f7e143e98252", + "@id": "kb:fa07818e-5832-5d5f-81b4-f7e143e98252", "@type": "uco-observable:ObservableObject", "uco-core:hasFacet": [ { - "@id": "9ab260ac-cdf5-5524-9047-3ea85ebfb6e2", + "@id": "kb:9ab260ac-cdf5-5524-9047-3ea85ebfb6e2", "@type": "uco-observable:FileFacet", "uco-observable:fileSystemType": "EXT4", "uco-observable:fileName": "IMG_0123.jpg", @@ -512,7 +513,7 @@ } }, { - "@id": "09cab366-d82b-57f8-a94c-9814bf7ef63d", + "@id": "kb:09cab366-d82b-57f8-a94c-9814bf7ef63d", "@type": "uco-observable:ContentDataFacet", "uco-observable:magicNumber": "/9j/ww==", "uco-observable:mimeType": "image/jpg", @@ -527,7 +528,7 @@ }, "uco-observable:hash": [ { - "@id": "84c0e1ca-a4da-59f3-b60a-096983862089", + "@id": "kb:84c0e1ca-a4da-59f3-b60a-096983862089", "@type": "uco-types:Hash", "uco-types:hashMethod": { "@type": "uco-vocabulary:HashNameVocab", @@ -541,7 +542,7 @@ ] }, { - "@id": "f3ce4eee-0a5d-54a7-9cc7-5ac043234d77", + "@id": "kb:f3ce4eee-0a5d-54a7-9cc7-5ac043234d77", "@type": "uco-observable:RasterPictureFacet", "uco-observable:pictureType": "jpg", "uco-observable:pictureHeight": { @@ -558,20 +559,20 @@ } }, { - "@id": "d6585cd5-ad4b-54c3-a649-d45d12d1605d", + "@id": "kb:d6585cd5-ad4b-54c3-a649-d45d12d1605d", "@type": "uco-observable:EXIFFacet", "uco-observable:exifData": { - "@id": "ab4cd39a-dea8-558b-a49e-4aedd9aff7f1", + "@id": "kb:ab4cd39a-dea8-558b-a49e-4aedd9aff7f1", "@type": "uco-types:ControlledDictionary", "uco-types:entry": [ { - "@id": "7721bd93-fff7-5e1d-a14e-2d627c399ebe", + "@id": "kb:7721bd93-fff7-5e1d-a14e-2d627c399ebe", "@type": "uco-types:ControlledDictionaryEntry", "uco-types:key": "Make", "uco-types:value": "Canon" }, { - "@id": "9ed3e5de-cde8-5cec-a71b-774e485da67d", + "@id": "kb:9ed3e5de-cde8-5cec-a71b-774e485da67d", "@type": "uco-types:ControlledDictionaryEntry", "uco-types:key": "Model", "uco-types:value": "Powershot" @@ -582,7 +583,7 @@ ] }, { - "@id": "268ab394-417e-579a-871b-84947adc9926", + "@id": "kb:268ab394-417e-579a-871b-84947adc9926", "@type": "case-investigation:InvestigativeAction", "uco-core:name": "annotated", "uco-action:startTime": { @@ -595,10 +596,10 @@ }, "uco-core:hasFacet": [ { - "@id": "102c218a-bc39-52e6-8228-abbc200378cf", + "@id": "kb:102c218a-bc39-52e6-8228-abbc200378cf", "@type": "uco-observable:DeviceFacet", "uco-observable:manufacturer": { - "@id": "2ed228fb-6c23-567a-8128-c891ccdb0dc6" + "@id": "kb:2ed228fb-6c23-567a-8128-c891ccdb0dc6" }, "uco-observable:deviceType": "iPhone", "uco-observable:model": "6XS", @@ -609,7 +610,7 @@ ] }, { - "@id": "bb9cd337-eb51-50dd-9cee-00f9e9acbf94", + "@id": "kb:bb9cd337-eb51-50dd-9cee-00f9e9acbf94", "@type": "uco-observable:Message", "uco-observable:state": "some state", "uco-observable:hasChanged": { @@ -620,45 +621,45 @@ "olo:slot": null, "uco-core:hasFacet": [ { - "@id": "73af707b-f00e-5327-8fd4-83467b4441c4", + "@id": "kb:73af707b-f00e-5327-8fd4-83467b4441c4", "@type": "uco-observable:MessageFacet" } ] }, { - "@id": "7c4d5cff-deb0-5031-b881-c572d6ef2fd7", + "@id": "kb:7c4d5cff-deb0-5031-b881-c572d6ef2fd7", "@type": "uco-observable:Message", "olo:length": null, "olo:slot": null, "uco-core:hasFacet": [ { - "@id": "551338f3-0bb6-5325-a63a-f164f8750a17", + "@id": "kb:551338f3-0bb6-5325-a63a-f164f8750a17", "@type": "uco-observable:MessageFacet" } ] }, { - "@id": "09b10c04-d04b-5808-a1b6-0392dd3a00e8", + "@id": "kb:09b10c04-d04b-5808-a1b6-0392dd3a00e8", "@type": "uco-observable:Message", "olo:length": null, "olo:slot": null, "uco-core:hasFacet": [ { - "@id": "a19e1e1f-3953-5fb9-92b6-2b46f85752b2", + "@id": "kb:a19e1e1f-3953-5fb9-92b6-2b46f85752b2", "@type": "uco-observable:MessageFacet" } ] }, { - "@id": "562c20c5-bef7-5919-a969-6c820a80e0d3", + "@id": "kb:562c20c5-bef7-5919-a969-6c820a80e0d3", "@type": "uco-observable:MessageThread", "uco-core:hasFacet": [ { - "@id": "6f79d4ae-d92c-5cad-bbe5-a0afde6f475a", + "@id": "kb:6f79d4ae-d92c-5cad-bbe5-a0afde6f475a", "@type": "uco-observable:MessageThreadFacet", "uco-observable:displayName": "some name", "uco-observable:messageThread": { - "@id": "56f74818-1d3d-51f9-8cb1-d6bdc8ecee60", + "@id": "kb:56f74818-1d3d-51f9-8cb1-d6bdc8ecee60", "@type": "uco-types:Thread", "co:size": { "@type": "xsd:nonNegativeInteger", @@ -666,13 +667,13 @@ }, "co:element": [ { - "@id": "09b10c04-d04b-5808-a1b6-0392dd3a00e8" + "@id": "kb:09b10c04-d04b-5808-a1b6-0392dd3a00e8" }, { - "@id": "7c4d5cff-deb0-5031-b881-c572d6ef2fd7" + "@id": "kb:7c4d5cff-deb0-5031-b881-c572d6ef2fd7" }, { - "@id": "bb9cd337-eb51-50dd-9cee-00f9e9acbf94" + "@id": "kb:bb9cd337-eb51-50dd-9cee-00f9e9acbf94" } ] } diff --git a/case_mapping/base.py b/case_mapping/base.py index 2f789f6..ffc7b9a 100644 --- a/case_mapping/base.py +++ b/case_mapping/base.py @@ -1,5 +1,6 @@ import json from datetime import datetime +from typing import Any from cdo_local_uuid import local_uuid @@ -20,8 +21,35 @@ def wrapper(self, *args, **kwargs): class FacetEntity(dict): - def __init__(self): - self["@id"] = str(local_uuid()) + def __init__( + self, + *args: Any, + prefix_iri: str = "http://example.org/kb/", + prefix_label: str = "kb", + **kwargs: Any, + ) -> None: + """ + :param prefix_iri: The IRI to be concatenated with the prefixed name's local part in order to form the node's absolute IRI. + :param prefix_label: The prefix separated from the compact IRI's local name by a colon. + + References + ========== + + .. [#turtle_prefixed_name] https://www.w3.org/TR/turtle/#prefixed-name + + Examples + ======== + + When instantiating a ``FacetEntity``, the JSON dictionary's ``@id`` key will start with the object's ``prefix_label``. + + >>> x = FacetEntity() + >>> assert x["@id"][0:3] == "kb:" + >>> y = FacetEntity(prefix_label="ex") + >>> assert y["@id"][0:3] == "ex:" + """ + self.prefix_iri = prefix_iri + self.prefix_label = prefix_label + self["@id"] = prefix_label + ":" + str(local_uuid()) def __str__(self): return json.dumps(self, indent=4) @@ -159,6 +187,9 @@ def __handle_var_type_errors(var_name, var_val, expected_type): class ObjectEntity(FacetEntity): + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + @unpack_args_array def append_facets(self, *args): """ diff --git a/case_mapping/uco/core.py b/case_mapping/uco/core.py index 29a57ca..d59288c 100644 --- a/case_mapping/uco/core.py +++ b/case_mapping/uco/core.py @@ -1,3 +1,5 @@ +from typing import Any + from ..base import ObjectEntity, unpack_args_array @@ -8,11 +10,13 @@ def __init__( uco_core_name=None, spec_version=None, description=None, - ): + *args: Any, + **kwargs: Any, + ) -> None: """ The main CASE Object for representing a case and its activities and objects. """ - super().__init__() + super().__init__(*args, **kwargs) self.build = [] self["@context"] = { "@vocab": "http://caseontology.org/core#", @@ -31,6 +35,17 @@ def __init__( "uco-vocabulary": "https://ontology.unifiedcyberontology.org/uco/vocabulary/", "xsd": "http://www.w3.org/2001/XMLSchema#", } + + # Assign caller-selectible prefix label and IRI, after checking + # for conflicts with hard-coded prefixes. + # https://www.w3.org/TR/turtle/#prefixed-name + if self.prefix_label in self["@context"]: + raise ValueError( + "Requested prefix label already in use in hard-coded dictionary: '%s'. Please revise caller to use another label." + % self.prefix_label + ) + self["@context"][self.prefix_label] = self.prefix_iri + self["@type"] = "uco-core:Bundle" self._str_vars( **{ diff --git a/case_mapping/uco/observable.py b/case_mapping/uco/observable.py index 2fb00ba..58e347d 100644 --- a/case_mapping/uco/observable.py +++ b/case_mapping/uco/observable.py @@ -269,7 +269,10 @@ def __init__( } if hash_method is not None or hash_value is not None or hash_value != "-": - data = {"@id": str(local_uuid()), "@type": "uco-types:Hash"} + data = { + "@id": self.prefix_label + ":" + str(local_uuid()), + "@type": "uco-types:Hash", + } if hash_method is not None: data["uco-types:hashMethod"] = { "@type": "uco-vocabulary:HashNameVocab", @@ -416,7 +419,7 @@ def __init__(self, browser=None, history_entries=None): self["uco-observable:urlHistoryEntry"] = [] for entry in history_entries: history_entry = {} - history_entry["@id"] = local_uuid() + history_entry["@id"] = self.prefix_label + ":" + local_uuid() history_entry["@type"] = "uco-observable:URLHistoryEntry" for key, var in entry.items(): if key in keys_str: @@ -858,14 +861,14 @@ def __init__(self, **kwargs): self["@type"] = "uco-observable:EXIFFacet" self["uco-observable:exifData"] = { - "@id": str(local_uuid()), + "@id": self.prefix_label + ":" + str(local_uuid()), "@type": "uco-types:ControlledDictionary", "uco-types:entry": [], } for k, v in kwargs.items(): if v not in ["", " "]: item = { - "@id": str(local_uuid()), + "@id": self.prefix_label + ":" + str(local_uuid()), "@type": "uco-types:ControlledDictionaryEntry", "uco-types:key": k, "uco-types:value": v, @@ -1356,7 +1359,7 @@ def __init__( self._node_reference_vars(**{"uco-observable:participant": participants}) self["uco-observable:messageThread"] = { - "@id": str(local_uuid()), + "@id": self.prefix_label + ":" + local_uuid(), "@type": "uco-types:Thread", }