diff --git a/.gitattributes b/.gitattributes index cd5271c982..8749e5dadb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13,3 +13,9 @@ Doxyfile text *.py text diff=python *.pl text + +# ensure solr scripts that are bash scripts not ending with.sh are lf instead of crlf +/KeywordSearch/solr/bin/autopsy-solr eol=lf +/KeywordSearch/solr/bin/init.d/solr eol=lf +/KeywordSearch/solr/bin/post eol=lf +/KeywordSearch/solr/bin/solr eol=lf \ No newline at end of file diff --git a/.gitignore b/.gitignore index faa1b92a94..8338c52863 100755 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /*/build/ */nbproject/private/* /nbproject/private/* +/apidiff_output/ /Core/release/ /Core/src/org/sleuthkit/autopsy/coreutils/Version.properties @@ -23,7 +24,12 @@ !/CoreLibs/nbproject/project.xml !/CoreLibs/nbproject/project.properties -/Core/test/qa-functional/data/ +/CoreTestLibs/release/ +/CoreTestLibs/build/ +/CoreTestLibs/dist/ + +/Core/test/qa-functional/data/* +!/Core/test/qa-functional/data/PasswordDetection_img1_v1.img /KeywordSearch/release/ /KeywordSearch/build/ @@ -69,6 +75,7 @@ genfiles.properties *~ /netbeans-plat /docs/doxygen-user/user-docs +/docs/doxygen-dev/build-docs /jdiff-javadocs/* /jdiff-logs/* /gen_version.txt @@ -101,3 +108,4 @@ hs_err_pid*.log /thirdparty/yara/YaraJNIWrapper/nbproject/private/ /thirdparty/yara/yarabridge/.vs/ +*/path_list.txt diff --git a/Core/build.xml b/Core/build.xml index 73bda54795..deb035b609 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -10,6 +10,16 @@ + + + + + + + + + + @@ -21,6 +31,17 @@ + + + + + + @@ -73,32 +94,19 @@ - - - - + + + + - - - - - - - - - - + - + @@ -126,16 +134,16 @@ - - - - + + + + + tofile="${ext.dir}/SparseBitSet-1.1.jar"/> @@ -149,12 +157,11 @@ - + - @@ -178,11 +185,16 @@ + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/ivy.xml b/Core/ivy.xml index a4e10cc605..cedeba9b2d 100644 --- a/Core/ivy.xml +++ b/Core/ivy.xml @@ -3,58 +3,86 @@ - - - - - - - - + + + + + + + - - - - - - - - - + + - - - - + + - - + + + + - - - - - + + - - + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + diff --git a/Core/ivysettings.xml b/Core/ivysettings.xml index 9c3b496314..fd792f6844 100644 --- a/Core/ivysettings.xml +++ b/Core/ivysettings.xml @@ -6,4 +6,5 @@ + diff --git a/Core/manifest.mf b/Core/manifest.mf index da946df7b8..59baf3f8c2 100644 --- a/Core/manifest.mf +++ b/Core/manifest.mf @@ -2,7 +2,7 @@ Manifest-Version: 1.0 OpenIDE-Module: org.sleuthkit.autopsy.core/10 OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/core/Bundle.properties OpenIDE-Module-Layer: org/sleuthkit/autopsy/core/layer.xml -OpenIDE-Module-Implementation-Version: 34 +OpenIDE-Module-Implementation-Version: 37 OpenIDE-Module-Requires: org.openide.windows.WindowManager AutoUpdate-Show-In-Client: true AutoUpdate-Essential-Module: true diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index e46293370f..07dc595f2f 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -1,133 +1,83 @@ -file.reference.activemq-all-5.16.0.jar=release\\modules\\ext\\activemq-all-5.16.0.jar -file.reference.animal-sniffer-annotations-1.17.jar=release\\modules\\ext\\animal-sniffer-annotations-1.17.jar -file.reference.api-common-1.7.0.jar=release\\modules\\ext\\api-common-1.7.0.jar -file.reference.batik-awt-util-1.6.jar=release\\modules\\ext\\batik-awt-util-1.6.jar -file.reference.batik-dom-1.6.jar=release\\modules\\ext\\batik-dom-1.6.jar -file.reference.batik-svg-dom-1.6.jar=release\\modules\\ext\\batik-svg-dom-1.6.jar -file.reference.batik-svggen-1.6.jar=release\\modules\\ext\\batik-svggen-1.6.jar -file.reference.batik-util-1.6.jar=release\\modules\\ext\\batik-util-1.6.jar -file.reference.batik-xml-1.6.jar=release\\modules\\ext\\batik-xml-1.6.jar -file.reference.bcpkix-jdk15on-1.54.jar=release\\modules\\ext\\bcpkix-jdk15on-1.54.jar -file.reference.bcprov-ext-jdk15on-1.54.jar=release\\modules\\ext\\bcprov-ext-jdk15on-1.54.jar -file.reference.bcprov-jdk15on-1.52.jar=release\\modules\\ext\\bcprov-jdk15on-1.52.jar -file.reference.bcprov-jdk15on-1.54.jar=release\\modules\\ext\\bcprov-jdk15on-1.54.jar -file.reference.byte-buddy-1.10.13.jar=release\\modules\\ext\\byte-buddy-1.10.13.jar -file.reference.byte-buddy-agent-1.10.13.jar=release\\modules\\ext\\byte-buddy-agent-1.10.13.jar -file.reference.c3p0-0.9.5.jar=release\\modules\\ext\\c3p0-0.9.5.jar -file.reference.checker-compat-qual-2.5.3.jar=release\\modules\\ext\\checker-compat-qual-2.5.3.jar -file.reference.commons-beanutils-1.9.2.jar=release\\modules\\ext\\commons-beanutils-1.9.2.jar -file.reference.commons-codec-1.11.jar=release\\modules\\ext\\commons-codec-1.11.jar -file.reference.commons-collections-3.2.2.jar=release\\modules\\ext\\commons-collections-3.2.2.jar -file.reference.commons-dbcp2-2.1.1.jar=release\\modules\\ext\\commons-dbcp2-2.1.1.jar -file.reference.commons-digester-1.8.1.jar=release\\modules\\ext\\commons-digester-1.8.1.jar -file.reference.commons-lang-2.6.jar=release\\modules\\ext\\commons-lang-2.6.jar -file.reference.commons-lang3-3.5.jar=release\\modules\\ext\\commons-lang3-3.5.jar -file.reference.commons-logging-1.2.jar=release\\modules\\ext\\commons-logging-1.2.jar -file.reference.commons-pool2-2.4.2.jar=release\\modules\\ext\\commons-pool2-2.4.2.jar -file.reference.commons-validator-1.6.jar=release\\modules\\ext\\commons-validator-1.6.jar -file.reference.curator-client-2.8.0.jar=release\\modules\\ext\\curator-client-2.8.0.jar -file.reference.curator-framework-2.8.0.jar=release\\modules\\ext\\curator-framework-2.8.0.jar -file.reference.curator-recipes-2.8.0.jar=release\\modules\\ext\\curator-recipes-2.8.0.jar -file.reference.DatCon.jar=release\\modules\\ext\\DatCon.jar -file.reference.decodetect-core-0.3.jar=release\\modules\\ext\\decodetect-core-0.3.jar -file.reference.error_prone_annotations-2.3.2.jar=release\\modules\\ext\\error_prone_annotations-2.3.2.jar -file.reference.failureaccess-1.0.1.jar=release\\modules\\ext\\failureaccess-1.0.1.jar -file.reference.gax-1.44.0.jar=release\\modules\\ext\\gax-1.44.0.jar -file.reference.gax-grpc-1.44.0.jar=release\\modules\\ext\\gax-grpc-1.44.0.jar -file.reference.gax-httpjson-0.61.0.jar=release\\modules\\ext\\gax-httpjson-0.61.0.jar -file.reference.google-api-client-1.27.0.jar=release\\modules\\ext\\google-api-client-1.27.0.jar -file.reference.google-api-services-translate-v2-rev20170525-1.27.0.jar=release\\modules\\ext\\google-api-services-translate-v2-rev20170525-1.27.0.jar -file.reference.google-auth-library-credentials-0.15.0.jar=release\\modules\\ext\\google-auth-library-credentials-0.15.0.jar -file.reference.google-auth-library-oauth2-http-0.15.0.jar=release\\modules\\ext\\google-auth-library-oauth2-http-0.15.0.jar -file.reference.google-cloud-core-1.70.0.jar=release\\modules\\ext\\google-cloud-core-1.70.0.jar -file.reference.google-cloud-core-grpc-1.70.0.jar=release\\modules\\ext\\google-cloud-core-grpc-1.70.0.jar -file.reference.google-cloud-core-http-1.70.0.jar=release\\modules\\ext\\google-cloud-core-http-1.70.0.jar -file.reference.google-cloud-translate-1.70.0.jar=release\\modules\\ext\\google-cloud-translate-1.70.0.jar -file.reference.google-http-client-1.29.0.jar=release\\modules\\ext\\google-http-client-1.29.0.jar -file.reference.google-http-client-appengine-1.29.0.jar=release\\modules\\ext\\google-http-client-appengine-1.29.0.jar -file.reference.google-http-client-jackson2-1.29.0.jar=release\\modules\\ext\\google-http-client-jackson2-1.29.0.jar -file.reference.google-oauth-client-1.28.0.jar=release\\modules\\ext\\google-oauth-client-1.28.0.jar -file.reference.grpc-alts-1.19.0.jar=release\\modules\\ext\\grpc-alts-1.19.0.jar -file.reference.grpc-auth-1.19.0.jar=release\\modules\\ext\\grpc-auth-1.19.0.jar -file.reference.grpc-context-1.19.0.jar=release\\modules\\ext\\grpc-context-1.19.0.jar -file.reference.grpc-core-1.19.0.jar=release\\modules\\ext\\grpc-core-1.19.0.jar -file.reference.grpc-grpclb-1.19.0.jar=release\\modules\\ext\\grpc-grpclb-1.19.0.jar -file.reference.grpc-netty-shaded-1.19.0.jar=release\\modules\\ext\\grpc-netty-shaded-1.19.0.jar -file.reference.grpc-protobuf-1.19.0.jar=release\\modules\\ext\\grpc-protobuf-1.19.0.jar -file.reference.grpc-protobuf-lite-1.19.0.jar=release\\modules\\ext\\grpc-protobuf-lite-1.19.0.jar -file.reference.grpc-stub-1.19.0.jar=release\\modules\\ext\\grpc-stub-1.19.0.jar -file.reference.gson-2.7.jar=release\\modules\\ext\\gson-2.7.jar -file.reference.guava-27.1-android.jar=release\\modules\\ext\\guava-27.1-android.jar -file.reference.httpclient-4.5.5.jar=release\\modules\\ext\\httpclient-4.5.5.jar -file.reference.httpcore-4.4.9.jar=release\\modules\\ext\\httpcore-4.4.9.jar -file.reference.icepdf-core-6.2.2.jar=release\\modules\\ext\\icepdf-core-6.2.2.jar -file.reference.icepdf-viewer-6.2.2.jar=release\\modules\\ext\\icepdf-viewer-6.2.2.jar -file.reference.istack-commons-runtime-3.0.11.jar=release/modules/ext/istack-commons-runtime-3.0.11.jar -file.reference.j2objc-annotations-1.1.jar=release\\modules\\ext\\j2objc-annotations-1.1.jar -file.reference.jackcess-2.2.0.jar=release\\modules\\ext\\jackcess-2.2.0.jar -file.reference.jackcess-encrypt-2.1.4.jar=release\\modules\\ext\\jackcess-encrypt-2.1.4.jar -file.reference.jackson-annotations-2.9.0.jar=release\\modules\\ext\\jackson-annotations-2.9.0.jar -file.reference.jackson-core-2.9.7.jar=release\\modules\\ext\\jackson-core-2.9.7.jar -file.reference.jackson-databind-2.9.7.jar=release\\modules\\ext\\jackson-databind-2.9.7.jar -file.reference.jai_core-1.1.3.jar=release\\modules\\ext\\jai_core-1.1.3.jar -file.reference.jai_imageio-1.1.jar=release\\modules\\ext\\jai_imageio-1.1.jar -file.reference.javax.annotation-api-1.3.2.jar=release\\modules\\ext\\javax.annotation-api-1.3.2.jar -file.reference.javax.ws.rs-api-2.0.jar=release\\modules\\ext\\javax.ws.rs-api-2.0.jar -file.reference.jaxb-api-2.3.1.jar=release\\modules\\ext\\jaxb-api-2.3.1.jar -file.reference.jaxb-runtime-2.3.3.jar=release\\modules\\ext\\jaxb-runtime-2.3.3.jar -file.reference.jcommon-1.0.23.jar=release/modules/ext/jcommon-1.0.23.jar -file.reference.jdom-2.0.5-contrib.jar=release\\modules\\ext\\jdom-2.0.5-contrib.jar -file.reference.jdom-2.0.5.jar=release\\modules\\ext\\jdom-2.0.5.jar -file.reference.jericho-html-3.3.jar=release\\modules\\ext\\jericho-html-3.3.jar -file.reference.jfreechart-1.0.19.jar=release/modules/ext/jfreechart-1.0.19.jar -file.reference.jgraphx-4.1.0.jar=release\\modules\\ext\\jgraphx-4.1.0.jar -file.reference.jline-0.9.94.jar=release\\modules\\ext\\jline-0.9.94.jar -file.reference.jsoup-1.10.3.jar=release\\modules\\ext\\jsoup-1.10.3.jar -file.reference.jsr305-3.0.2.jar=release\\modules\\ext\\jsr305-3.0.2.jar -file.reference.junit-3.8.1.jar=release\\modules\\ext\\junit-3.8.1.jar -file.reference.jutf7-1.0.0.jar=release\\modules\\ext\\jutf7-1.0.0.jar -file.reference.jxmapviewer2-2.4.jar=release\\modules\\ext\\jxmapviewer2-2.4.jar -file.reference.jython-standalone-2.7.2.jar=release\\modules\\ext\\jython-standalone-2.7.2.jar -file.reference.libphonenumber-3.5.jar=release\\modules\\ext\\libphonenumber-3.5.jar -file.reference.listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar=release\\modules\\ext\\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar -file.reference.log4j-1.2.16.jar=release\\modules\\ext\\log4j-1.2.16.jar -file.reference.mchange-commons-java-0.2.9.jar=release\\modules\\ext\\mchange-commons-java-0.2.9.jar -file.reference.metadata-extractor-2.11.0.jar=release\\modules\\ext\\metadata-extractor-2.11.0.jar -file.reference.mockito-core-3.5.7.jar=release\\modules\\ext\\mockito-core-3.5.7.jar -file.reference.netty-3.7.0.Final.jar=release\\modules\\ext\\netty-3.7.0.Final.jar -file.reference.objenesis-3.1.jar=release\\modules\\ext\\objenesis-3.1.jar -file.reference.okhttp-2.7.5.jar=release\\modules\\ext\\okhttp-2.7.5.jar -file.reference.okio-1.6.0.jar=release\\modules\\ext\\okio-1.6.0.jar -file.reference.opencensus-api-0.19.2.jar=release\\modules\\ext\\opencensus-api-0.19.2.jar -file.reference.opencensus-contrib-grpc-metrics-0.19.2.jar=release\\modules\\ext\\opencensus-contrib-grpc-metrics-0.19.2.jar -file.reference.opencensus-contrib-http-util-0.19.2.jar=release\\modules\\ext\\opencensus-contrib-http-util-0.19.2.jar -file.reference.opennlp-tools-1.9.1.jar=release\\modules\\ext\\opennlp-tools-1.9.1.jar -file.reference.postgresql-42.2.18.jar=release\\modules\\ext\\postgresql-42.2.18.jar -file.reference.proto-google-cloud-translate-v3beta1-0.53.0.jar=release\\modules\\ext\\proto-google-cloud-translate-v3beta1-0.53.0.jar -file.reference.proto-google-common-protos-1.15.0.jar=release\\modules\\ext\\proto-google-common-protos-1.15.0.jar -file.reference.proto-google-iam-v1-0.12.0.jar=release\\modules\\ext\\proto-google-iam-v1-0.12.0.jar -file.reference.protobuf-java-3.7.0.jar=release\\modules\\ext\\protobuf-java-3.7.0.jar -file.reference.protobuf-java-util-3.7.0.jar=release\\modules\\ext\\protobuf-java-util-3.7.0.jar -file.reference.Rejistry-1.1-SNAPSHOT.jar=release\\modules\\ext\\Rejistry-1.1-SNAPSHOT.jar -file.reference.sevenzipjbinding-AllPlatforms.jar=release\\modules\\ext\\sevenzipjbinding-AllPlatforms.jar -file.reference.sevenzipjbinding.jar=release\\modules\\ext\\sevenzipjbinding.jar -file.reference.sleuthkit-4.10.1.jar=release/modules/ext/sleuthkit-4.10.1.jar -file.reference.sleuthkit-caseuco-4.10.1.jar=release/modules/ext/sleuthkit-caseuco-4.10.1.jar -file.reference.slf4j-api-1.7.6.jar=release\\modules\\ext\\slf4j-api-1.7.6.jar -file.reference.slf4j-log4j12-1.7.6.jar=release\\modules\\ext\\slf4j-log4j12-1.7.6.jar -file.reference.SparseBitSet-1.1.jar=release\\modules\\ext\\SparseBitSet-1.1.jar -file.reference.sqlite-jdbc-3.25.2.jar=release\\modules\\ext\\sqlite-jdbc-3.25.2.jar -file.reference.StixLib.jar=release\\modules\\ext\\StixLib.jar -file.reference.threetenbp-1.3.3.jar=release\\modules\\ext\\threetenbp-1.3.3.jar -file.reference.webp-imageio-sejda-0.1.0.jar=release\\modules\\ext\\webp-imageio-sejda-0.1.0.jar -file.reference.xmpcore-5.1.3.jar=release\\modules\\ext\\xmpcore-5.1.3.jar +file.reference.activemq-all-5.16.4.jar=release/modules/ext/activemq-all-5.16.4.jar +file.reference.audience-annotations-0.12.0.jar=release/modules/ext/audience-annotations-0.12.0.jar +file.reference.batik-awt-util-1.14.jar=release/modules/ext/batik-awt-util-1.14.jar +file.reference.batik-dom-1.14.jar=release/modules/ext/batik-dom-1.14.jar +file.reference.batik-svg-dom-1.14.jar=release/modules/ext/batik-svg-dom-1.14.jar +file.reference.batik-svggen-1.14.jar=release/modules/ext/batik-svggen-1.14.jar +file.reference.batik-util-1.14.jar=release/modules/ext/batik-util-1.14.jar +file.reference.batik-xml-1.14.jar=release/modules/ext/batik-xml-1.14.jar +file.reference.bcpkix-jdk15on-1.70.jar=release/modules/ext/bcpkix-jdk15on-1.70.jar +file.reference.bcprov-ext-jdk15on-1.70.jar=release/modules/ext/bcprov-ext-jdk15on-1.70.jar +file.reference.bcprov-jdk15on-1.70.jar=release/modules/ext/bcprov-jdk15on-1.70.jar +file.reference.c3p0-0.9.5.5.jar=release/modules/ext/c3p0-0.9.5.5.jar +file.reference.checker-qual-3.12.0.jar=release/modules/ext/checker-qual-3.12.0.jar +file.reference.commons-dbcp2-2.9.0.jar=release/modules/ext/commons-dbcp2-2.9.0.jar +file.reference.commons-io-2.11.0.jar=release/modules/ext/commons-io-2.11.0.jar +file.reference.commons-lang3-3.10.jar=release/modules/ext/commons-lang3-3.10.jar +file.reference.commons-logging-1.2.jar=release/modules/ext/commons-logging-1.2.jar +file.reference.commons-pool2-2.10.0.jar=release/modules/ext/commons-pool2-2.10.0.jar +file.reference.curator-client-5.2.1.jar=release/modules/ext/curator-client-5.2.1.jar +file.reference.curator-framework-5.2.1.jar=release/modules/ext/curator-framework-5.2.1.jar +file.reference.curator-recipes-5.2.1.jar=release/modules/ext/curator-recipes-5.2.1.jar +file.reference.DatCon.jar=release/modules/ext/DatCon.jar +file.reference.decodetect-core-0.3.jar=release/modules/ext/decodetect-core-0.3.jar +file.reference.error_prone_annotations-2.11.0.jar=release/modules/ext/error_prone_annotations-2.11.0.jar +file.reference.failureaccess-1.0.1.jar=release/modules/ext/failureaccess-1.0.1.jar +file.reference.guava-31.1-jre.jar=release/modules/ext/guava-31.1-jre.jar +file.reference.icepdf-core-6.2.2.jar=release/modules/ext/icepdf-core-6.2.2.jar +file.reference.icepdf-viewer-6.2.2.jar=release/modules/ext/icepdf-viewer-6.2.2.jar +file.reference.j2objc-annotations-1.3.jar=release/modules/ext/j2objc-annotations-1.3.jar +file.reference.jackcess-4.0.1.jar=release/modules/ext/jackcess-4.0.1.jar +file.reference.jackcess-encrypt-4.0.1.jar=release/modules/ext/jackcess-encrypt-4.0.1.jar +file.reference.jai_core-1.1.3.jar=release/modules/ext/jai_core-1.1.3.jar +file.reference.jai_imageio-1.1.jar=release/modules/ext/jai_imageio-1.1.jar +file.reference.java-diff-utils-4.11.jar=release/modules/ext/java-diff-utils-4.11.jar +file.reference.javax.ws.rs-api-2.1.1.jar=release/modules/ext/javax.ws.rs-api-2.1.1.jar +file.reference.jdom-2.0.5.jar=release/modules/ext/jdom-2.0.5.jar +file.reference.jfreechart-1.5.3.jar=release/modules/ext/jfreechart-1.5.3.jar +file.reference.jgraphx-4.2.2.jar=release/modules/ext/jgraphx-4.2.2.jar +file.reference.jsoup-1.14.3.jar=release/modules/ext/jsoup-1.14.3.jar +file.reference.jsr305-3.0.2.jar=release/modules/ext/jsr305-3.0.2.jar +file.reference.jutf7-1.0.0.jar=release/modules/ext/jutf7-1.0.0.jar +file.reference.jxmapviewer2-2.6.jar=release/modules/ext/jxmapviewer2-2.6.jar +file.reference.jython-standalone-2.7.2.jar=release/modules/ext/jython-standalone-2.7.2.jar +file.reference.libphonenumber-8.12.45.jar=release/modules/ext/libphonenumber-8.12.45.jar +file.reference.listenablefuture-1.0.jar=release/modules/ext/listenablefuture-1.0.jar +file.reference.logback-classic-1.2.10.jar=release/modules/ext/logback-classic-1.2.10.jar +file.reference.logback-core-1.2.10.jar=release/modules/ext/logback-core-1.2.10.jar +file.reference.mchange-commons-java-0.2.20.jar=release/modules/ext/mchange-commons-java-0.2.20.jar +file.reference.metadata-extractor-2.17.0.jar=release/modules/ext/metadata-extractor-2.17.0.jar +file.reference.netty-buffer-4.1.73.Final.jar=release/modules/ext/netty-buffer-4.1.73.Final.jar +file.reference.netty-codec-4.1.73.Final.jar=release/modules/ext/netty-codec-4.1.73.Final.jar +file.reference.netty-common-4.1.73.Final.jar=release/modules/ext/netty-common-4.1.73.Final.jar +file.reference.netty-handler-4.1.73.Final.jar=release/modules/ext/netty-handler-4.1.73.Final.jar +file.reference.netty-resolver-4.1.73.Final.jar=release/modules/ext/netty-resolver-4.1.73.Final.jar +file.reference.netty-tcnative-2.0.48.Final.jar=release/modules/ext/netty-tcnative-2.0.48.Final.jar +file.reference.netty-tcnative-classes-2.0.48.Final.jar=release/modules/ext/netty-tcnative-classes-2.0.48.Final.jar +file.reference.netty-transport-4.1.73.Final.jar=release/modules/ext/netty-transport-4.1.73.Final.jar +file.reference.netty-transport-classes-epoll-4.1.73.Final.jar=release/modules/ext/netty-transport-classes-epoll-4.1.73.Final.jar +file.reference.netty-transport-native-epoll-4.1.73.Final.jar=release/modules/ext/netty-transport-native-epoll-4.1.73.Final.jar +file.reference.netty-transport-native-unix-common-4.1.73.Final.jar=release/modules/ext/netty-transport-native-unix-common-4.1.73.Final.jar +file.reference.okhttp-2.7.5.jar=release/modules/ext/okhttp-2.7.5.jar +file.reference.okio-1.6.0.jar=release/modules/ext/okio-1.6.0.jar +file.reference.postgresql-42.3.5.jar=release/modules/ext/postgresql-42.3.5.jar +file.reference.Rejistry-1.1-SNAPSHOT.jar=release/modules/ext/Rejistry-1.1-SNAPSHOT.jar +file.reference.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbinding-AllPlatforms.jar +file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar +file.reference.sleuthkit-4.11.1.jar=release/modules/ext/sleuthkit-4.11.1.jar +file.reference.sleuthkit-caseuco-4.11.1.jar=release/modules/ext/sleuthkit-caseuco-4.11.1.jar +file.reference.snakeyaml-1.30.jar=release/modules/ext/snakeyaml-1.30.jar +file.reference.SparseBitSet-1.1.jar=release/modules/ext/SparseBitSet-1.1.jar +file.reference.spotbugs-annotations-4.6.0.jar=release/modules/ext/spotbugs-annotations-4.6.0.jar +file.reference.sqlite-jdbc-3.36.0.3.jar=release/modules/ext/sqlite-jdbc-3.36.0.3.jar +file.reference.xmpcore-6.1.11.jar=release/modules/ext/xmpcore-6.1.11.jar file.reference.YaraJNIWrapper.jar=release/modules/ext/YaraJNIWrapper.jar -file.reference.zookeeper-3.4.6.jar=release\\modules\\ext\\zookeeper-3.4.6.jar +file.reference.zookeeper-3.8.0.jar=release/modules/ext/zookeeper-3.8.0.jar +file.reference.zookeeper-jute-3.8.0.jar=release/modules/ext/zookeeper-jute-3.8.0.jar javac.source=11 javac.compilerargs=-Xlint -Xlint:-serial license.file=../LICENSE-2.0.txt nbm.homepage=http://www.sleuthkit.org/ nbm.module.author=Brian Carrier nbm.needs.restart=true -source.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0-sources.jar -spec.version.base=10.22 +spec.version.base=10.24 diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index f3fb59145e..79f393466c 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -269,6 +269,11 @@ org.netbeans.modules.nbjunit + + org.sleuthkit.autopsy.coretestlibs + + + qa-functional @@ -293,6 +298,10 @@ + + org.sleuthkit.autopsy.coretestlibs + + @@ -336,509 +345,365 @@ org.sleuthkit.autopsy.textextractors.configs org.sleuthkit.autopsy.textsummarizer org.sleuthkit.autopsy.texttranslation + org.sleuthkit.autopsy.url.analytics org.sleuthkit.datamodel org.sleuthkit.datamodel.blackboardutils org.sleuthkit.datamodel.blackboardutils.attributes - ext/batik-xml-1.6.jar - release\modules\ext\batik-xml-1.6.jar + ext/activemq-all-5.16.4.jar + release/modules/ext/activemq-all-5.16.4.jar - ext/commons-digester-1.8.1.jar - release\modules\ext\commons-digester-1.8.1.jar + ext/audience-annotations-0.12.0.jar + release/modules/ext/audience-annotations-0.12.0.jar - ext/jai_core-1.1.3.jar - release\modules\ext\jai_core-1.1.3.jar + ext/batik-awt-util-1.14.jar + release/modules/ext/batik-awt-util-1.14.jar - ext/gax-grpc-1.44.0.jar - release\modules\ext\gax-grpc-1.44.0.jar + ext/batik-dom-1.14.jar + release/modules/ext/batik-dom-1.14.jar - ext/failureaccess-1.0.1.jar - release\modules\ext\failureaccess-1.0.1.jar + ext/batik-svg-dom-1.14.jar + release/modules/ext/batik-svg-dom-1.14.jar - ext/grpc-protobuf-1.19.0.jar - release\modules\ext\grpc-protobuf-1.19.0.jar + ext/batik-svggen-1.14.jar + release/modules/ext/batik-svggen-1.14.jar - ext/opencensus-api-0.19.2.jar - release\modules\ext\opencensus-api-0.19.2.jar + ext/batik-util-1.14.jar + release/modules/ext/batik-util-1.14.jar - ext/batik-svg-dom-1.6.jar - release\modules\ext\batik-svg-dom-1.6.jar + ext/batik-xml-1.14.jar + release/modules/ext/batik-xml-1.14.jar - ext/gax-httpjson-0.61.0.jar - release\modules\ext\gax-httpjson-0.61.0.jar + ext/bcpkix-jdk15on-1.70.jar + release/modules/ext/bcpkix-jdk15on-1.70.jar - ext/sevenzipjbinding.jar - release\modules\ext\sevenzipjbinding.jar + ext/bcprov-ext-jdk15on-1.70.jar + release/modules/ext/bcprov-ext-jdk15on-1.70.jar - ext/mchange-commons-java-0.2.9.jar - release\modules\ext\mchange-commons-java-0.2.9.jar + ext/bcprov-jdk15on-1.70.jar + release/modules/ext/bcprov-jdk15on-1.70.jar - ext/api-common-1.7.0.jar - release\modules\ext\api-common-1.7.0.jar + ext/c3p0-0.9.5.5.jar + release/modules/ext/c3p0-0.9.5.5.jar - ext/jackson-databind-2.9.7.jar - release\modules\ext\jackson-databind-2.9.7.jar + ext/checker-qual-3.12.0.jar + release/modules/ext/checker-qual-3.12.0.jar - ext/okhttp-2.7.5.jar - release\modules\ext\okhttp-2.7.5.jar + ext/commons-dbcp2-2.9.0.jar + release/modules/ext/commons-dbcp2-2.9.0.jar - ext/proto-google-cloud-translate-v3beta1-0.53.0.jar - release\modules\ext\proto-google-cloud-translate-v3beta1-0.53.0.jar + ext/commons-io-2.11.0.jar + release/modules/ext/commons-io-2.11.0.jar - ext/byte-buddy-1.10.13.jar - release\modules\ext\byte-buddy-1.10.13.jar + ext/commons-lang3-3.10.jar + release/modules/ext/commons-lang3-3.10.jar - ext/error_prone_annotations-2.3.2.jar - release\modules\ext\error_prone_annotations-2.3.2.jar + ext/commons-logging-1.2.jar + release/modules/ext/commons-logging-1.2.jar - ext/libphonenumber-3.5.jar - release\modules\ext\libphonenumber-3.5.jar + ext/commons-pool2-2.10.0.jar + release/modules/ext/commons-pool2-2.10.0.jar - ext/StixLib.jar - release\modules\ext\StixLib.jar + ext/curator-client-5.2.1.jar + release/modules/ext/curator-client-5.2.1.jar - ext/google-auth-library-credentials-0.15.0.jar - release\modules\ext\google-auth-library-credentials-0.15.0.jar - - - ext/grpc-auth-1.19.0.jar - release\modules\ext\grpc-auth-1.19.0.jar - - - ext/j2objc-annotations-1.1.jar - release\modules\ext\j2objc-annotations-1.1.jar - - - ext/metadata-extractor-2.11.0.jar - release\modules\ext\metadata-extractor-2.11.0.jar - - - ext/commons-codec-1.11.jar - release\modules\ext\commons-codec-1.11.jar - - - ext/postgresql-42.2.18.jar - release\modules\ext\postgresql-42.2.18.jar - - - ext/commons-pool2-2.4.2.jar - release\modules\ext\commons-pool2-2.4.2.jar - - - ext/jxmapviewer2-2.4.jar - release\modules\ext\jxmapviewer2-2.4.jar - - - ext/jdom-2.0.5-contrib.jar - release\modules\ext\jdom-2.0.5-contrib.jar - - - ext/xmpcore-5.1.3.jar - release\modules\ext\xmpcore-5.1.3.jar - - - ext/batik-util-1.6.jar - release\modules\ext\batik-util-1.6.jar - - - ext/javax.annotation-api-1.3.2.jar - release\modules\ext\javax.annotation-api-1.3.2.jar - - - ext/jgraphx-4.1.0.jar - release\modules\ext\jgraphx-4.1.0.jar + ext/curator-framework-5.2.1.jar + release/modules/ext/curator-framework-5.2.1.jar +<<<<<<< HEAD ext/jython-standalone-2.7.2.jar release\modules\ext\jython-standalone-2.7.2.jar ext/jline-0.9.94.jar release\modules\ext\jline-0.9.94.jar +======= + ext/curator-recipes-5.2.1.jar + release/modules/ext/curator-recipes-5.2.1.jar +>>>>>>> c8f546a0d7078b675a4eb673ba25e4acd1523985 ext/DatCon.jar - release\modules\ext\DatCon.jar - - - ext/okio-1.6.0.jar - release\modules\ext\okio-1.6.0.jar - - - ext/bcprov-jdk15on-1.54.jar - release\modules\ext\bcprov-jdk15on-1.54.jar - - - ext/curator-framework-2.8.0.jar - release\modules\ext\curator-framework-2.8.0.jar - - - ext/commons-dbcp2-2.1.1.jar - release\modules\ext\commons-dbcp2-2.1.1.jar - - - ext/google-http-client-appengine-1.29.0.jar - release\modules\ext\google-http-client-appengine-1.29.0.jar - - - ext/proto-google-iam-v1-0.12.0.jar - release\modules\ext\proto-google-iam-v1-0.12.0.jar - - - ext/jackcess-encrypt-2.1.4.jar - release\modules\ext\jackcess-encrypt-2.1.4.jar - - - ext/google-http-client-1.29.0.jar - release\modules\ext\google-http-client-1.29.0.jar - - - ext/opennlp-tools-1.9.1.jar - release\modules\ext\opennlp-tools-1.9.1.jar - - - ext/bcprov-ext-jdk15on-1.54.jar - release\modules\ext\bcprov-ext-jdk15on-1.54.jar - - - ext/google-cloud-core-1.70.0.jar - release\modules\ext\google-cloud-core-1.70.0.jar - - - ext/protobuf-java-3.7.0.jar - release\modules\ext\protobuf-java-3.7.0.jar - - - ext/bcpkix-jdk15on-1.54.jar - release\modules\ext\bcpkix-jdk15on-1.54.jar - - - ext/sqlite-jdbc-3.25.2.jar - release\modules\ext\sqlite-jdbc-3.25.2.jar - - - ext/grpc-protobuf-lite-1.19.0.jar - release\modules\ext\grpc-protobuf-lite-1.19.0.jar - - - ext/httpcore-4.4.9.jar - release\modules\ext\httpcore-4.4.9.jar - - - ext/guava-27.1-android.jar - release\modules\ext\guava-27.1-android.jar - - - ext/bcprov-jdk15on-1.52.jar - release\modules\ext\bcprov-jdk15on-1.52.jar - - - ext/checker-compat-qual-2.5.3.jar - release\modules\ext\checker-compat-qual-2.5.3.jar - - - ext/animal-sniffer-annotations-1.17.jar - release\modules\ext\animal-sniffer-annotations-1.17.jar - - - ext/gax-1.44.0.jar - release\modules\ext\gax-1.44.0.jar - - - ext/jsoup-1.10.3.jar - release\modules\ext\jsoup-1.10.3.jar - - - ext/YaraJNIWrapper.jar - release/modules/ext/YaraJNIWrapper.jar - - - ext/grpc-context-1.19.0.jar - release\modules\ext\grpc-context-1.19.0.jar - - - ext/jackcess-2.2.0.jar - release\modules\ext\jackcess-2.2.0.jar - - - ext/slf4j-log4j12-1.7.6.jar - release\modules\ext\slf4j-log4j12-1.7.6.jar - - - ext/jericho-html-3.3.jar - release\modules\ext\jericho-html-3.3.jar - - - ext/google-cloud-core-grpc-1.70.0.jar - release\modules\ext\google-cloud-core-grpc-1.70.0.jar - - - ext/commons-validator-1.6.jar - release\modules\ext\commons-validator-1.6.jar - - - ext/slf4j-api-1.7.6.jar - release\modules\ext\slf4j-api-1.7.6.jar + release/modules/ext/DatCon.jar ext/decodetect-core-0.3.jar - release\modules\ext\decodetect-core-0.3.jar + release/modules/ext/decodetect-core-0.3.jar - ext/mockito-core-3.5.7.jar - release\modules\ext\mockito-core-3.5.7.jar + ext/error_prone_annotations-2.11.0.jar + release/modules/ext/error_prone_annotations-2.11.0.jar - ext/httpclient-4.5.5.jar - release\modules\ext\httpclient-4.5.5.jar + ext/failureaccess-1.0.1.jar + release/modules/ext/failureaccess-1.0.1.jar - ext/curator-recipes-2.8.0.jar - release\modules\ext\curator-recipes-2.8.0.jar + ext/guava-31.1-jre.jar + release/modules/ext/guava-31.1-jre.jar - ext/jackson-annotations-2.9.0.jar - release\modules\ext\jackson-annotations-2.9.0.jar + ext/icepdf-core-6.2.2.jar + release/modules/ext/icepdf-core-6.2.2.jar - ext/objenesis-3.1.jar - release\modules\ext\objenesis-3.1.jar + ext/icepdf-viewer-6.2.2.jar + release/modules/ext/icepdf-viewer-6.2.2.jar - ext/jackson-core-2.9.7.jar - release\modules\ext\jackson-core-2.9.7.jar + ext/j2objc-annotations-1.3.jar + release/modules/ext/j2objc-annotations-1.3.jar - ext/commons-lang3-3.5.jar - release\modules\ext\commons-lang3-3.5.jar + ext/jackcess-4.0.1.jar + release/modules/ext/jackcess-4.0.1.jar - ext/log4j-1.2.16.jar - release\modules\ext\log4j-1.2.16.jar + ext/jackcess-encrypt-4.0.1.jar + release/modules/ext/jackcess-encrypt-4.0.1.jar - ext/commons-logging-1.2.jar - release\modules\ext\commons-logging-1.2.jar + ext/jai_core-1.1.3.jar + release/modules/ext/jai_core-1.1.3.jar - ext/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar - release\modules\ext\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar - - - ext/protobuf-java-util-3.7.0.jar - release\modules\ext\protobuf-java-util-3.7.0.jar + ext/jai_imageio-1.1.jar + release/modules/ext/jai_imageio-1.1.jar +<<<<<<< HEAD ext/jaxb-api-2.3.1.jar release\modules\ext\jaxb-api-2.3.1.jar ext/commons-collections-3.2.2.jar release\modules\ext\commons-collections-3.2.2.jar +======= + ext/java-diff-utils-4.11.jar + release/modules/ext/java-diff-utils-4.11.jar +>>>>>>> c8f546a0d7078b675a4eb673ba25e4acd1523985 - ext/SparseBitSet-1.1.jar - release\modules\ext\SparseBitSet-1.1.jar - - - ext/grpc-grpclb-1.19.0.jar - release\modules\ext\grpc-grpclb-1.19.0.jar - - - ext/batik-svggen-1.6.jar - release\modules\ext\batik-svggen-1.6.jar - - - ext/c3p0-0.9.5.jar - release\modules\ext\c3p0-0.9.5.jar - - - ext/zookeeper-3.4.6.jar - release\modules\ext\zookeeper-3.4.6.jar - - - ext/grpc-alts-1.19.0.jar - release\modules\ext\grpc-alts-1.19.0.jar - - - ext/sleuthkit-caseuco-4.10.1.jar - release/modules/ext/sleuthkit-caseuco-4.10.1.jar + ext/javax.ws.rs-api-2.1.1.jar + release/modules/ext/javax.ws.rs-api-2.1.1.jar ext/jdom-2.0.5.jar - release\modules\ext\jdom-2.0.5.jar + release/modules/ext/jdom-2.0.5.jar - ext/gson-2.7.jar - release\modules\ext\gson-2.7.jar + ext/jfreechart-1.5.3.jar + release/modules/ext/jfreechart-1.5.3.jar - ext/google-api-client-1.27.0.jar - release\modules\ext\google-api-client-1.27.0.jar + ext/jgraphx-4.2.2.jar + release/modules/ext/jgraphx-4.2.2.jar + ext/jsoup-1.14.3.jar + release/modules/ext/jsoup-1.14.3.jar + + + ext/jutf7-1.0.0.jar + release/modules/ext/jutf7-1.0.0.jar + + + ext/jxmapviewer2-2.6.jar + release/modules/ext/jxmapviewer2-2.6.jar + + + ext/jython-standalone-2.7.2.jar + release/modules/ext/jython-standalone-2.7.2.jar + + + ext/libphonenumber-8.12.45.jar + release/modules/ext/libphonenumber-8.12.45.jar + + + ext/listenablefuture-1.0.jar + release/modules/ext/listenablefuture-1.0.jar + + +<<<<<<< HEAD ext/jaxb-runtime-2.3.3.jar release\modules\ext\jaxb-runtime-2.3.3.jar ext/opencensus-contrib-http-util-0.19.2.jar release\modules\ext\opencensus-contrib-http-util-0.19.2.jar +======= + ext/logback-classic-1.2.10.jar + release/modules/ext/logback-classic-1.2.10.jar +>>>>>>> c8f546a0d7078b675a4eb673ba25e4acd1523985 - ext/google-auth-library-oauth2-http-0.15.0.jar - release\modules\ext\google-auth-library-oauth2-http-0.15.0.jar + ext/logback-core-1.2.10.jar + release/modules/ext/logback-core-1.2.10.jar +<<<<<<< HEAD ext/commons-lang-2.6.jar release\modules\ext\commons-lang-2.6.jar +======= + ext/mchange-commons-java-0.2.20.jar + release/modules/ext/mchange-commons-java-0.2.20.jar - ext/jsr305-3.0.2.jar - release\modules\ext\jsr305-3.0.2.jar + ext/metadata-extractor-2.17.0.jar + release/modules/ext/metadata-extractor-2.17.0.jar +>>>>>>> c8f546a0d7078b675a4eb673ba25e4acd1523985 - ext/proto-google-common-protos-1.15.0.jar - release\modules\ext\proto-google-common-protos-1.15.0.jar + ext/netty-buffer-4.1.73.Final.jar + release/modules/ext/netty-buffer-4.1.73.Final.jar - ext/netty-3.7.0.Final.jar - release\modules\ext\netty-3.7.0.Final.jar + ext/netty-codec-4.1.73.Final.jar + release/modules/ext/netty-codec-4.1.73.Final.jar - ext/jfreechart-1.0.19.jar - release/modules/ext/jfreechart-1.0.19.jar + ext/netty-common-4.1.73.Final.jar + release/modules/ext/netty-common-4.1.73.Final.jar - ext/opencensus-contrib-grpc-metrics-0.19.2.jar - release\modules\ext\opencensus-contrib-grpc-metrics-0.19.2.jar + ext/netty-handler-4.1.73.Final.jar + release/modules/ext/netty-handler-4.1.73.Final.jar + ext/netty-resolver-4.1.73.Final.jar + release/modules/ext/netty-resolver-4.1.73.Final.jar + + +<<<<<<< HEAD ext/activemq-all-5.16.0.jar release\modules\ext\activemq-all-5.16.0.jar ext/jai_imageio-1.1.jar release\modules\ext\jai_imageio-1.1.jar +======= + ext/netty-tcnative-2.0.48.Final.jar + release/modules/ext/netty-tcnative-2.0.48.Final.jar +>>>>>>> c8f546a0d7078b675a4eb673ba25e4acd1523985 - ext/junit-3.8.1.jar - release\modules\ext\junit-3.8.1.jar + ext/netty-tcnative-classes-2.0.48.Final.jar + release/modules/ext/netty-tcnative-classes-2.0.48.Final.jar +<<<<<<< HEAD ext/istack-commons-runtime-3.0.11.jar release/modules/ext/istack-commons-runtime-3.0.11.jar ext/curator-client-2.8.0.jar release\modules\ext\curator-client-2.8.0.jar +======= + ext/netty-transport-4.1.73.Final.jar + release/modules/ext/netty-transport-4.1.73.Final.jar +>>>>>>> c8f546a0d7078b675a4eb673ba25e4acd1523985 - ext/grpc-core-1.19.0.jar - release\modules\ext\grpc-core-1.19.0.jar + ext/netty-transport-classes-epoll-4.1.73.Final.jar + release/modules/ext/netty-transport-classes-epoll-4.1.73.Final.jar - ext/javax.ws.rs-api-2.0.jar - release\modules\ext\javax.ws.rs-api-2.0.jar + ext/netty-transport-native-epoll-4.1.73.Final.jar + release/modules/ext/netty-transport-native-epoll-4.1.73.Final.jar - ext/jcommon-1.0.23.jar - release/modules/ext/jcommon-1.0.23.jar + ext/netty-transport-native-unix-common-4.1.73.Final.jar + release/modules/ext/netty-transport-native-unix-common-4.1.73.Final.jar - ext/icepdf-core-6.2.2.jar - release\modules\ext\icepdf-core-6.2.2.jar + ext/okhttp-2.7.5.jar + release/modules/ext/okhttp-2.7.5.jar +<<<<<<< HEAD ext/google-cloud-core-http-1.70.0.jar release\modules\ext\google-cloud-core-http-1.70.0.jar +======= + ext/okio-1.6.0.jar + release/modules/ext/okio-1.6.0.jar + + + ext/postgresql-42.3.5.jar + release/modules/ext/postgresql-42.3.5.jar +>>>>>>> c8f546a0d7078b675a4eb673ba25e4acd1523985 ext/Rejistry-1.1-SNAPSHOT.jar - release\modules\ext\Rejistry-1.1-SNAPSHOT.jar - - - ext/commons-beanutils-1.9.2.jar - release\modules\ext\commons-beanutils-1.9.2.jar - - - ext/batik-dom-1.6.jar - release\modules\ext\batik-dom-1.6.jar - - - ext/google-http-client-jackson2-1.29.0.jar - release\modules\ext\google-http-client-jackson2-1.29.0.jar - - - ext/threetenbp-1.3.3.jar - release\modules\ext\threetenbp-1.3.3.jar - - - ext/google-cloud-translate-1.70.0.jar - release\modules\ext\google-cloud-translate-1.70.0.jar - - - ext/grpc-stub-1.19.0.jar - release\modules\ext\grpc-stub-1.19.0.jar - - - ext/google-oauth-client-1.28.0.jar - release\modules\ext\google-oauth-client-1.28.0.jar + release/modules/ext/Rejistry-1.1-SNAPSHOT.jar ext/sevenzipjbinding-AllPlatforms.jar - release\modules\ext\sevenzipjbinding-AllPlatforms.jar + release/modules/ext/sevenzipjbinding-AllPlatforms.jar - ext/sleuthkit-4.10.1.jar - release/modules/ext/sleuthkit-4.10.1.jar + ext/sevenzipjbinding.jar + release/modules/ext/sevenzipjbinding.jar - ext/jutf7-1.0.0.jar - release\modules\ext\jutf7-1.0.0.jar + ext/sleuthkit-4.11.1.jar + release/modules/ext/sleuthkit-4.11.1.jar - ext/byte-buddy-agent-1.10.13.jar - release\modules\ext\byte-buddy-agent-1.10.13.jar + ext/sleuthkit-caseuco-4.11.1.jar + release/modules/ext/sleuthkit-caseuco-4.11.1.jar - ext/batik-awt-util-1.6.jar - release\modules\ext\batik-awt-util-1.6.jar + ext/snakeyaml-1.30.jar + release/modules/ext/snakeyaml-1.30.jar - ext/google-api-services-translate-v2-rev20170525-1.27.0.jar - release\modules\ext\google-api-services-translate-v2-rev20170525-1.27.0.jar + ext/SparseBitSet-1.1.jar + release/modules/ext/SparseBitSet-1.1.jar - ext/icepdf-viewer-6.2.2.jar - release\modules\ext\icepdf-viewer-6.2.2.jar + ext/spotbugs-annotations-4.6.0.jar + release/modules/ext/spotbugs-annotations-4.6.0.jar - ext/webp-imageio-sejda-0.1.0.jar - release\modules\ext\webp-imageio-sejda-0.1.0.jar + ext/sqlite-jdbc-3.36.0.3.jar + release/modules/ext/sqlite-jdbc-3.36.0.3.jar - ext/grpc-netty-shaded-1.19.0.jar - release\modules\ext\grpc-netty-shaded-1.19.0.jar + ext/xmpcore-6.1.11.jar + release/modules/ext/xmpcore-6.1.11.jar + + + ext/YaraJNIWrapper.jar + release/modules/ext/YaraJNIWrapper.jar + + + ext/zookeeper-3.8.0.jar + release/modules/ext/zookeeper-3.8.0.jar + + + ext/zookeeper-jute-3.8.0.jar + release/modules/ext/zookeeper-jute-3.8.0.jar diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java index 26d1c7a9bc..79a0d715b2 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2013-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,6 +29,7 @@ import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactItem; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TagName; @@ -46,6 +47,8 @@ import org.sleuthkit.datamodel.TskCoreException; }) public class AddBlackboardArtifactTagAction extends AddTagAction { + private static final long serialVersionUID = 1L; + // This class is a singleton to support multi-selection of nodes, since // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every // node in the array returns a reference to the same action object from Node.getActions(boolean). @@ -68,7 +71,7 @@ public class AddBlackboardArtifactTagAction extends AddTagAction { "AddBlackboardArtifactTagAction.singularTagResult"); String pluralTagResult = NbBundle.getMessage(this.getClass(), "AddBlackboardArtifactTagAction.pluralTagResult"); - return Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class).size() > 1 ? pluralTagResult : singularTagResult; + return Utilities.actionsGlobalContext().lookupAll(BlackboardArtifactItem.class).size() > 1 ? pluralTagResult : singularTagResult; } @Override @@ -82,8 +85,14 @@ public class AddBlackboardArtifactTagAction extends AddTagAction { * invocation of addTag(), we don't want to tag the same * BlackboardArtifact more than once, so we dedupe the * BlackboardArtifacts by stuffing them into a HashSet. + * + * RC (9/8/21): The documentation does NOT say that lookupAll() can + * return duplicates. That would be very broken. What motivated this + * "de-duping" ? */ - selectedArtifacts.addAll(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + for (BlackboardArtifactItem item : Utilities.actionsGlobalContext().lookupAll(BlackboardArtifactItem.class)) { + selectedArtifacts.add(item.getTskContent()); + } } else { for (Content content : getContentToTag()) { if (content instanceof BlackboardArtifact) { @@ -111,4 +120,10 @@ public class AddBlackboardArtifactTagAction extends AddTagAction { } }).start(); } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java index 9c059205f5..ff3dc08e04 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2020 Basis Technology Corp. + * Copyright 2013-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,22 +44,29 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; /** - * An abstract base class for Actions that allow users to tag SleuthKit data + * An abstract super class for Actions that allow users to tag Sleuth Kit data * model objects. */ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { private static final long serialVersionUID = 1L; private static final String NO_COMMENT = ""; - private final Collection content = new HashSet<>(); + private final Collection contentObjsToTag; + /** + * Constructs an instance of an abstract super class for Actions that allow + * users to tag Sleuth Kit data model objects. + * + * @param menuText The menu item text. + */ AddTagAction(String menuText) { super(menuText); + contentObjsToTag = new HashSet<>(); } @Override public JMenuItem getPopupPresenter() { - content.clear(); + contentObjsToTag.clear(); return new TagMenu(); } @@ -70,7 +77,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { * @return The specified content for this action. */ Collection getContentToTag() { - return Collections.unmodifiableCollection(content); + return Collections.unmodifiableCollection(contentObjsToTag); } /** @@ -83,8 +90,8 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { * apply to the Content specified. */ public JMenuItem getMenuForContent(Collection contentToTag) { - content.clear(); - content.addAll(contentToTag); + contentObjsToTag.clear(); + contentObjsToTag.addAll(contentToTag); return new TagMenu(); } @@ -111,6 +118,11 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { */ abstract protected void addTag(TagName tagName, String comment); + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + /** * Instances of this class implement a context menu user interface for * creating or selecting a tag name for a tag and specifying an optional tag @@ -126,7 +138,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { super(getActionDisplayName()); // Get the current set of tag names. - Map tagNamesMap = null; + Map tagNamesMap; List standardTagNames = TagsManager.getStandardTagNames(); Map tagSetMenuMap = new HashMap<>(); List standardTagMenuitems = new ArrayList<>(); @@ -240,5 +252,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { return tagNameItem; } + } + } diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties index a2feedc54f..4ca72069dc 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties @@ -45,3 +45,4 @@ OpenPythonModulesFolderAction.actionName.text=Python Plugins OpenPythonModulesFolderAction.errorMsg.folderNotFound=Python plugins folder not found: {0} CTL_OpenPythonModulesFolderAction=Python Plugins GetTagNameAndCommentDialog.tagCombo.toolTipText=Select tag to use +CTL_ExitAction=Exit \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED index 507e079cad..5c9a0ea3ac 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED @@ -96,3 +96,4 @@ OpenPythonModulesFolderAction.actionName.text=Python Plugins OpenPythonModulesFolderAction.errorMsg.folderNotFound=Python plugins folder not found: {0} CTL_OpenPythonModulesFolderAction=Python Plugins GetTagNameAndCommentDialog.tagCombo.toolTipText=Select tag to use +CTL_ExitAction=Exit diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/actions/Bundle_ja.properties index a4a460df69..037e719678 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle_ja.properties @@ -1,97 +1,83 @@ +#Thu Sep 30 10:26:59 UTC 2021 AddBlackboardArtifactTagAction.pluralTagResult=\u7d50\u679c\u30bf\u30b0\u3092\u8ffd\u52a0 AddBlackboardArtifactTagAction.singularTagResult=\u7d50\u679c\u30bf\u30b0\u3092\u8ffd\u52a0 AddBlackboardArtifactTagAction.taggingErr=\u30bf\u30b0\u4ed8\u3051\u30a8\u30e9\u30fc -# {0} - artifactName AddBlackboardArtifactTagAction.unableToTag.msg={0} \u3092\u30bf\u30b0\u4ed8\u3051\u3067\u304d\u307e\u305b\u3093\u3002 +AddBookmarkTagAction.bookmark.text=\u30d6\u30c3\u30af\u30de\u30fc\u30af AddContentTagAction.cannotApplyTagErr=\u30bf\u30b0\u3092\u9069\u7528\u3067\u304d\u307e\u305b\u3093 AddContentTagAction.pluralTagFile=\u30d5\u30a1\u30a4\u30eb\u30bf\u30b0\u3092\u8ffd\u52a0 AddContentTagAction.singularTagFile=\u30d5\u30a1\u30a4\u30eb\u30bf\u30b0\u3092\u8ffd\u52a0 -# {0} - fileName -# {1} - tagName AddContentTagAction.tagExists={0} \u304c {1} \u3068\u3057\u3066\u30bf\u30b0\u4ed8\u3051\u3055\u308c\u307e\u3057\u305f\u3002\u540c\u3058\u30bf\u30b0\u3092\u518d\u9069\u7528\u3067\u304d\u307e\u305b\u3093\u3002 AddContentTagAction.taggingErr=\u30bf\u30b0\u4ed8\u3051\u30a8\u30e9\u30fc -# {0} - fileName AddContentTagAction.unableToTag.msg={0} \u3092\u30bf\u30b0\u4ed8\u3051\u3067\u304d\u307e\u305b\u3093\u3002\u901a\u5e38\u306e\u30d5\u30a1\u30a4\u30eb\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 -# {0} - fileName AddContentTagAction.unableToTag.msg2={0} \u3092\u30bf\u30b0\u4ed8\u3051\u3067\u304d\u307e\u305b\u3093\u3002 +AddTagAction.bookmarkFile=\u30d6\u30c3\u30af\u30de\u30fc\u30af\u30d5\u30a1\u30a4\u30eb +AddTagAction.newTag=\u65b0\u898f\u30bf\u30b0... +AddTagAction.noTags=\u30bf\u30b0\u306a\u3057 +AddTagAction.quickTag=\u30af\u30a4\u30c3\u30af\u30bf\u30b0 +AddTagAction.tagAndComment=\u30bf\u30b0\u3068\u30b3\u30e1\u30f3\u30c8... +CTL_DumpThreadAction=\u30b9\u30ec\u30c3\u30c9 \u30c0\u30f3\u30d7 +CTL_ExitAction=\u51fa\u53e3 +CTL_OpenLogFolder=\u30ed\u30b0\u30d5\u30a9\u30eb\u30c0\u30fc\u3092\u958b\u304f +CTL_OpenOutputFolder=\u51fa\u529b\u30d5\u30a9\u30eb\u30c0\u30fc\u3092\u958b\u304f +CTL_OpenPythonModulesFolderAction=Python\u30d7\u30e9\u30b0\u30a4\u30f3 CTL_ShowIngestProgressSnapshotAction=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30b9\u30c6\u30fc\u30bf\u30b9\u8a73\u7d30 DeleteBlackboardArtifactTagAction.deleteTag=\u9078\u629e\u3057\u305f\u30bf\u30b0\u3092\u524a\u9664 DeleteBlackboardArtifactTagAction.tagDelErr=\u30bf\u30b0\u524a\u9664\u30a8\u30e9\u30fc -# {0} - tagName DeleteBlackboardArtifactTagAction.unableToDelTag.msg={0} \u3092Delete\u30bf\u30b0\u4ed8\u3051\u3067\u304d\u307e\u305b\u3093\u3002 DeleteContentTagAction.deleteTag=\u9078\u629e\u3057\u305f\u30bf\u30b0\u3092\u524a\u9664 DeleteContentTagAction.tagDelErr=\u30bf\u30b0\u524a\u9664\u30a8\u30e9\u30fc -# {0} - tagName DeleteContentTagAction.unableToDelTag.msg={0} \u3092Delete\u30bf\u30b0\u4ed8\u3051\u3067\u304d\u307e\u305b\u3093\u3002 DeleteFileBlackboardArtifactTagAction.deleteTag=\u7d50\u679c\u30bf\u30b0\u3092\u524a\u9664 -# {0} - artifactID DeleteFileBlackboardArtifactTagAction.deleteTag.alert=\u904e\u53bb\u306e\u691c\u7d22\u7d50\u679c {0} \u3092\u30bf\u30b0\u306a\u3057\u306b\u3067\u304d\u307e\u305b\u3093\u3002 -# {0} - artifactID DeleteFileBlackboardArtifactTagAction.deleteTags.alert=\u904e\u53bb\u306e\u691c\u7d22\u7d50\u679c {0} \u3092\u30bf\u30b0\u306a\u3057\u306b\u3067\u304d\u307e\u305b\u3093\u3002 DeleteFileContentTagAction.deleteTag=\u30d5\u30a1\u30a4\u30eb\u30bf\u30b0\u3092\u524a\u9664 -# {0} - fileID DeleteFileContentTagAction.deleteTag.alert=\u904e\u53bb\u306e\u691c\u7d22\u7d50\u679c {0} \u3092\u30bf\u30b0\u306a\u3057\u306b\u3067\u304d\u307e\u305b\u3093\u3002 ExitAction.confirmationDialog.message=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u306e\u5b9f\u884c\u4e2d\u3067\u3059\u3002\u7d42\u4e86\u3057\u3066\u3082\u3088\u308d\u3057\u3044\u3067\u3059\u304b? ExitAction.confirmationDialog.title=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u306e\u5b9f\u884c\u4e2d\u3067\u3059 -# {0} - \u4f8b\u5916\u30e1\u30c3\u30bb\u30fc\u30b8 -ExitAction.messageBox.caseCloseExceptionMessage=\u6b21\u306e\u30b1\u30fc\u30b9\u3092\u9589\u3058\u3066\u3044\u308b\u9593\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f: {0} -GetTagNameDialog.descriptionLabel.text=\u8a18\u8ff0: +ExitAction.messageBox.caseCloseExceptionMessage=\u6b21\u306e\u30b1\u30fc\u30b9\u3092\u9589\u3058\u3066\u3044\u308b\u9593\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\: {0} +GetTagNameAndCommentDialog.cancelButton.text=\u53d6\u308a\u6d88\u3057 +GetTagNameAndCommentDialog.cancelName=\u53d6\u308a\u6d88\u3057 +GetTagNameAndCommentDialog.commentLabel.text=\u30b3\u30e1\u30f3\u30c8\: +GetTagNameAndCommentDialog.commentText.text= +GetTagNameAndCommentDialog.commentText.toolTipText=\u4efb\u610f\u30bf\u30b0\u306e\u30b3\u30e1\u30f3\u30c8\u3092\u5165\u529b\u3059\u308b\u304b\u7a7a\u6b04\u306e\u307e\u307e\u306b\u3059\u308b +GetTagNameAndCommentDialog.newTagButton.text=\u65b0\u898f\u30bf\u30b0 +GetTagNameAndCommentDialog.noTags=\u30bf\u30b0\u306a\u3057 +GetTagNameAndCommentDialog.okButton.text=OK +GetTagNameAndCommentDialog.selectTag=\u30bf\u30b0\u3092\u9078\u629e +GetTagNameAndCommentDialog.tagCombo.toolTipText=\u4f7f\u7528\u3059\u308b\u30bf\u30b0\u3092\u9078\u629e +GetTagNameAndCommentDialog.tagLabel.text=\u30bf\u30b0\: +GetTagNameDialog.cancelButton.text=\u53d6\u308a\u6d88\u3057 +GetTagNameDialog.cancelName=\u53d6\u308a\u6d88\u3057 +GetTagNameDialog.createTag=\u30bf\u30b0\u3092\u4f5c\u6210 +GetTagNameDialog.descriptionLabel.text=\u8a18\u8ff0\: +GetTagNameDialog.dupTagErr=\u30bf\u30b0\u8907\u88fd\u30a8\u30e9\u30fc +GetTagNameDialog.illegalChars.msg=\u30bf\u30b0\u540d\u306b\u4e0d\u6b63\u306a\u6587\u5b57\u304c\u542b\u307e\u308c\u3066\u3044\u307e\u3059\u3002\n\u6b21\u306e\u8a18\u53f7\u3092\u542b\u3081\u3089\u308c\u307e\u305b\u3093\: \\ \: * ? " < > | , ; +GetTagNameDialog.illegalCharsErr=\u4e0d\u6b63\u306a\u6587\u5b57 +GetTagNameDialog.mustSupplyTtagName.msg=\u7d9a\u884c\u3059\u308b\u306b\u306f\u30bf\u30b0\u540d\u3092\u63d0\u4f9b\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002 +GetTagNameDialog.newTagPanel.border.title=\u65b0\u898f\u30bf\u30b0 GetTagNameDialog.notableCheckbox.text=\u30bf\u30b0\u306f\u9805\u76ee\u304c\u9855\u8457\u3067\u3042\u308b\u3068\u793a\u5506\u3057\u3066\u3044\u307e\u3059\u3002 +GetTagNameDialog.okButton.text=OK +GetTagNameDialog.preexistingLabel.text=\u524d\u304b\u3089\u5b58\u5728\u3059\u308b\u30bf\u30b0\u540d\: GetTagNameDialog.tagDescriptionIllegalCharacters.message=\u30bf\u30b0\u306e\u8a18\u8ff0\u306b\u30ab\u30f3\u30de(,)\u3084\u30bb\u30df\u30b3\u30ed\u30f3(;)\u3092\u542b\u3081\u3089\u308c\u307e\u305b\u3093 GetTagNameDialog.tagDescriptionIllegalCharacters.title=\u30bf\u30b0\u306e\u8a18\u8ff0\u306b\u7121\u52b9\u306a\u6587\u5b57\u304c\u3042\u308a\u307e\u3059 +GetTagNameDialog.tagNameAlreadyDef.msg={0} \u30bf\u30b0\u540d\u3092\u3059\u3067\u306b\u5b9a\u7fa9\u6e08\u307f\u3067\u3059\u3002 GetTagNameDialog.tagNameAlreadyExists.message=\u30bf\u30b0\u540d\u306f\u4e00\u610f\u3067\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002\u3053\u306e\u540d\u524d\u306e\u30bf\u30b0\u306f\u3059\u3067\u306b\u5b58\u5728\u3057\u307e\u3059\u3002 GetTagNameDialog.tagNameAlreadyExists.title=\u30bf\u30b0\u540d\u3092\u8907\u88fd -GetTagNameDialog.tagNameField.text= -GetTagNameDialog.cancelButton.text=\u53d6\u308a\u6d88\u3057 -GetTagNameDialog.okButton.text=OK -GetTagNameDialog.preexistingLabel.text=\u524d\u304b\u3089\u5b58\u5728\u3059\u308b\u30bf\u30b0\u540d: -GetTagNameDialog.newTagPanel.border.title=\u65b0\u898f\u30bf\u30b0 -GetTagNameDialog.tagNameLabel.text=\u30bf\u30b0\u540d: -GetTagNameAndCommentDialog.newTagButton.text=\u65b0\u898f\u30bf\u30b0 -GetTagNameAndCommentDialog.okButton.text=OK -GetTagNameAndCommentDialog.commentText.toolTipText=\u4efb\u610f\u30bf\u30b0\u306e\u30b3\u30e1\u30f3\u30c8\u3092\u5165\u529b\u3059\u308b\u304b\u7a7a\u6b04\u306e\u307e\u307e\u306b\u3059\u308b -GetTagNameAndCommentDialog.commentText.text= -GetTagNameAndCommentDialog.commentLabel.text=\u30b3\u30e1\u30f3\u30c8: -# \u3053\u306e\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3092\u5909\u66f4\u3059\u308b\u306b\u306f\u3001[\u30c4\u30fc\u30eb | \u30c6\u30f3\u30d7\u30ec\u30fc\u30c8] \u3092\u9078\u629e\u3057\u3001 -# \u30a8\u30c7\u30a3\u30bf\u30fc\u3067\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3092\u958b\u304d\u307e\u3059\u3002 -GetTagNameAndCommentDialog.cancelButton.text=\u53d6\u308a\u6d88\u3057 -GetTagNameAndCommentDialog.tagLabel.text=\u30bf\u30b0: -AddTagAction.bookmarkFile=\u30d6\u30c3\u30af\u30de\u30fc\u30af\u30d5\u30a1\u30a4\u30eb -AddTagAction.quickTag=\u30af\u30a4\u30c3\u30af\u30bf\u30b0 -AddTagAction.noTags=\u30bf\u30b0\u306a\u3057 -AddTagAction.newTag=\u65b0\u898f\u30bf\u30b0... -AddTagAction.tagAndComment=\u30bf\u30b0\u3068\u30b3\u30e1\u30f3\u30c8... -AddBookmarkTagAction.bookmark.text=\u30d6\u30c3\u30af\u30de\u30fc\u30af -GetTagNameAndCommentDialog.noTags=\u30bf\u30b0\u306a\u3057 -GetTagNameAndCommentDialog.selectTag=\u30bf\u30b0\u3092\u9078\u629e -GetTagNameAndCommentDialog.cancelName=\u53d6\u308a\u6d88\u3057 -GetTagNameDialog.createTag=\u30bf\u30b0\u3092\u4f5c\u6210 -GetTagNameDialog.cancelName=\u53d6\u308a\u6d88\u3057 -GetTagNameDialog.mustSupplyTtagName.msg=\u7d9a\u884c\u3059\u308b\u306b\u306f\u30bf\u30b0\u540d\u3092\u63d0\u4f9b\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002 GetTagNameDialog.tagNameErr=\u30bf\u30b0\u540d -GetTagNameDialog.illegalChars.msg=\u30bf\u30b0\u540d\u306b\u4e0d\u6b63\u306a\u6587\u5b57\u304c\u542b\u307e\u308c\u3066\u3044\u307e\u3059\u3002\n\u6b21\u306e\u8a18\u53f7\u3092\u542b\u3081\u3089\u308c\u307e\u305b\u3093: \\ : * ? " < > | , ; -GetTagNameDialog.illegalCharsErr=\u4e0d\u6b63\u306a\u6587\u5b57 -GetTagNameDialog.unableToAddTagNameToCase.msg={0} \u30bf\u30b0\u540d\u3092\u30b1\u30fc\u30b9\u306b\u8ffd\u52a0\u3067\u304d\u307e\u305b\u3093\u3002 -GetTagNameDialog.taggingErr=\u30bf\u30b0\u4ed8\u3051\u30a8\u30e9\u30fc -GetTagNameDialog.tagNameAlreadyDef.msg={0} \u30bf\u30b0\u540d\u3092\u3059\u3067\u306b\u5b9a\u7fa9\u6e08\u307f\u3067\u3059\u3002 -GetTagNameDialog.dupTagErr=\u30bf\u30b0\u8907\u88fd\u30a8\u30e9\u30fc GetTagNameDialog.tagNameExistsTskCore.msg=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u5185\u306b {0} \u30bf\u30b0\u540d\u304c\u3059\u3067\u306b\u5b58\u5728\u3057\u307e\u3059\u304c\u3001\u691c\u7d22\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 -OpenLogFolder.error1=\u30ed\u30b0\u30d5\u30a1\u30a4\u30eb\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093: {0} +GetTagNameDialog.tagNameField.text= +GetTagNameDialog.tagNameLabel.text=\u30bf\u30b0\u540d\: +GetTagNameDialog.taggingErr=\u30bf\u30b0\u4ed8\u3051\u30a8\u30e9\u30fc +GetTagNameDialog.unableToAddTagNameToCase.msg={0} \u30bf\u30b0\u540d\u3092\u30b1\u30fc\u30b9\u306b\u8ffd\u52a0\u3067\u304d\u307e\u305b\u3093\u3002 OpenLogFolder.CouldNotOpenLogFolder=\u30ed\u30b0\u30d5\u30a9\u30eb\u30c0\u30fc\u3092\u958b\u3051\u307e\u305b\u3093\u3067\u3057\u305f -CTL_OpenLogFolder=\u30ed\u30b0\u30d5\u30a9\u30eb\u30c0\u30fc\u3092\u958b\u304f -CTL_OpenOutputFolder=\u51fa\u529b\u30d5\u30a9\u30eb\u30c0\u30fc\u3092\u958b\u304f -OpenOutputFolder.error1=\u51fa\u529b\u30d5\u30a9\u30eb\u30c0\u30fc\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093: {0} -OpenOutputFolder.noCaseOpen=\u958b\u3044\u3066\u3044\u308b\u30b1\u30fc\u30b9\u306f\u306a\u3044\u305f\u3081\u3001\u73fe\u5728\u306e\u51fa\u529b\u30d5\u30a9\u30eb\u30c0\u30fc\u3092\u5229\u7528\u3067\u304d\u307e\u305b\u3093\u3002 +OpenLogFolder.error1=\u30ed\u30b0\u30d5\u30a1\u30a4\u30eb\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\: {0} OpenOutputFolder.CouldNotOpenOutputFolder=\u51fa\u529b\u30d5\u30a9\u30eb\u30c0\u30fc\u3092\u958b\u3051\u307e\u305b\u3093\u3067\u3057\u305f -# {0} - \u53e4\u3044\u30bf\u30b0\u540d -# {1} - artifactID +OpenOutputFolder.error1=\u51fa\u529b\u30d5\u30a9\u30eb\u30c0\u30fc\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\: {0} +OpenOutputFolder.noCaseOpen=\u958b\u3044\u3066\u3044\u308b\u30b1\u30fc\u30b9\u306f\u306a\u3044\u305f\u3081\u3001\u73fe\u5728\u306e\u51fa\u529b\u30d5\u30a9\u30eb\u30c0\u30fc\u3092\u5229\u7528\u3067\u304d\u307e\u305b\u3093\u3002 +OpenPythonModulesFolderAction.actionName.text=Python\u30d7\u30e9\u30b0\u30a4\u30f3 +OpenPythonModulesFolderAction.errorMsg.folderNotFound=Python\u30d7\u30e9\u30b0\u30a4\u30f3\u30d5\u30a9\u30eb\u30c0\u30fc\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\: {0} ReplaceBlackboardArtifactTagAction.replaceTag.alert=\u904e\u53bb\u306e\u691c\u7d22\u7d50\u679c {1} \u306e\u30bf\u30b0 {0} \u3092\u7f6e\u63db\u3067\u304d\u307e\u305b\u3093\u3002 -# {0} - \u53e4\u3044\u30bf\u30b0\u540d -# {1} - \u30b3\u30f3\u30c6\u30f3\u30c4\u30aa\u30d6\u30b8\u30a7\u30af\u30c8ID ReplaceContentTagAction.replaceTag.alert={1} \u306e\u30bf\u30b0 {0} \u3092\u7f6e\u63db\u3067\u304d\u307e\u305b\u3093\u3002 ReplaceTagAction.replaceTag=\u3067\u9078\u629e\u3057\u305f\u30bf\u30b0\u3092\u7f6e\u63db ShowIngestProgressSnapshotAction.actionName.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u9032\u884c\u72b6\u6cc1\u30b9\u30ca\u30c3\u30d7\u30b7\u30e7\u30c3\u30c8\u3092\u53d6\u5f97 -OpenPythonModulesFolderAction.actionName.text=Python\u30d7\u30e9\u30b0\u30a4\u30f3 -OpenPythonModulesFolderAction.errorMsg.folderNotFound=Python\u30d7\u30e9\u30b0\u30a4\u30f3\u30d5\u30a9\u30eb\u30c0\u30fc\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093: {0} -CTL_OpenPythonModulesFolderAction=Python\u30d7\u30e9\u30b0\u30a4\u30f3 -GetTagNameAndCommentDialog.tagCombo.toolTipText=\u4f7f\u7528\u3059\u308b\u30bf\u30b0\u3092\u9078\u629e diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java index 8c7c77652e..70b037e017 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java @@ -21,12 +21,12 @@ package org.sleuthkit.autopsy.actions; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.ExecutionException; import java.util.logging.Level; +import java.util.stream.Collectors; import javafx.application.Platform; import javafx.scene.control.Alert; import javax.swing.AbstractAction; @@ -40,6 +40,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactItem; import org.sleuthkit.autopsy.tags.TagUtils; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifactTag; @@ -158,7 +159,11 @@ public class DeleteFileBlackboardArtifactTagAction extends AbstractAction implem private static final long serialVersionUID = 1L; TagMenu() { - this(new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class))); + this(Utilities.actionsGlobalContext() + .lookupAll(BlackboardArtifactItem.class) + .stream() + .map((bai) -> (BlackboardArtifact) bai.getTskContent()) + .collect(Collectors.toSet())); } TagMenu(Collection selectedBlackboardArtifactsList) { diff --git a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java index 7240afca92..31162cc67b 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ExitAction.java @@ -40,7 +40,7 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; * The action associated with the Case/Exit menu item. It closes the current * case, if any, and shuts down the application. */ -@ActionRegistration(displayName = "Exit", iconInMenu = true) +@ActionRegistration(displayName = "#CTL_ExitAction", iconInMenu = true) @ActionReference(path = "Menu/Case", position = 1000, separatorBefore = 999) @ActionID(id = "org.sleuthkit.autopsy.casemodule.ExitAction", category = "Case") final public class ExitAction implements ActionListener { diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java index f82a30c05a..2617d08df3 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.actions; +import java.awt.Cursor; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; @@ -25,6 +26,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.ActionMap; @@ -33,6 +35,7 @@ import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JOptionPane; import javax.swing.KeyStroke; +import javax.swing.SwingWorker; import javax.swing.table.AbstractTableModel; import org.openide.util.ImageUtilities; import org.openide.util.NbBundle; @@ -163,7 +166,50 @@ public class GetTagNameDialog extends JDialog { return tagDisplayNames.get(rowIndex); } } + + /** + * A SwingWorker for creating a new TagName. + */ + private class AddTagNameWorker extends SwingWorker { + private final String name; + private final String description; + private final TskData.FileKnown status; + private final TagName.HTML_COLOR color; + + AddTagNameWorker(String name, String description, TskData.FileKnown status, TagName.HTML_COLOR color) { + this.name = name; + this.description = description; + this.status = status; + this.color = color; + } + + @Override + protected TagName doInBackground() throws Exception { + return Case.getCurrentCaseThrows().getServices().getTagsManager().addTagName(name, description, color, status); + } + + @Override + protected void done() { + try { + tagName = get(); + dispose(); + } catch (ExecutionException | InterruptedException ex) { + Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, "Error adding " + name + " tag name", ex); //NON-NLS + JOptionPane.showMessageDialog(GetTagNameDialog.this, + NbBundle.getMessage(GetTagNameDialog.this.getClass(), + "GetTagNameDialog.unableToAddTagNameToCase.msg", + name), + NbBundle.getMessage(this.getClass(), "GetTagNameDialog.taggingErr"), + JOptionPane.ERROR_MESSAGE); + tagName = null; + } + + okButton.setEnabled(true); + cancelButton.setEnabled(true); + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -351,32 +397,12 @@ public class GetTagNameDialog extends JDialog { tagName = tagNamesMap.get(tagDisplayName); if (tagName == null) { - try { - tagName = Case.getCurrentCaseThrows().getServices().getTagsManager().addTagName(tagDisplayName, userTagDescription, TagName.HTML_COLOR.NONE, status); - dispose(); - } catch (TskCoreException | NoCurrentCaseException ex) { - Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, "Error adding " + tagDisplayName + " tag name", ex); //NON-NLS - JOptionPane.showMessageDialog(this, - NbBundle.getMessage(this.getClass(), - "GetTagNameDialog.unableToAddTagNameToCase.msg", - tagDisplayName), - NbBundle.getMessage(this.getClass(), "GetTagNameDialog.taggingErr"), - JOptionPane.ERROR_MESSAGE); - tagName = null; - } catch (TagsManager.TagNameAlreadyExistsException ex) { - try { - tagName = Case.getCurrentCaseThrows().getServices().getTagsManager().getDisplayNamesToTagNamesMap().get(tagDisplayName); - } catch (TskCoreException | NoCurrentCaseException ex1) { - Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, tagDisplayName + " exists in database but an error occurred in retrieving it.", ex1); //NON-NLS - JOptionPane.showMessageDialog(this, - NbBundle.getMessage(this.getClass(), - "GetTagNameDialog.tagNameExistsTskCore.msg", - tagDisplayName), - NbBundle.getMessage(this.getClass(), "GetTagNameDialog.dupTagErr"), - JOptionPane.ERROR_MESSAGE); - tagName = null; - } - } + AddTagNameWorker worker = new AddTagNameWorker(tagDisplayName, userTagDescription, status, TagName.HTML_COLOR.NONE ); + okButton.setEnabled(false); + cancelButton.setEnabled(false); + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + worker.execute(); } else { JOptionPane.showMessageDialog(this, NbBundle.getMessage(this.getClass(), "GetTagNameDialog.tagNameAlreadyExists.message"), diff --git a/Core/src/org/sleuthkit/autopsy/actions/ThreadDumpAction.java b/Core/src/org/sleuthkit/autopsy/actions/ThreadDumpAction.java index d74511996c..dad577e8cb 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/ThreadDumpAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ThreadDumpAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2020 Basis Technology Corp. + * Copyright 2020-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,18 +24,10 @@ import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadInfo; -import java.lang.management.ThreadMXBean; import java.nio.file.Path; import java.nio.file.Paths; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Date; import java.util.concurrent.ExecutionException; import java.util.logging.Level; -import java.util.stream.Collectors; import javax.swing.SwingWorker; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; @@ -46,6 +38,8 @@ import org.openide.util.actions.CallableSystemAction; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.coreutils.ThreadUtils; +import org.sleuthkit.autopsy.coreutils.TimeStampUtils; /** * Action class for the Thread Dump help menu item. If there is no case open the @@ -63,8 +57,6 @@ public final class ThreadDumpAction extends CallableSystemAction implements Acti private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(ThreadDumpAction.class.getName()); - private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss-SSSS"); - @Override public void performAction() { (new ThreadDumper()).run(); @@ -114,26 +106,13 @@ public final class ThreadDumpAction extends CallableSystemAction implements Acti * @throws IOException */ private File createThreadDump() throws IOException { + + // generate thread dump + String threadDump = ThreadUtils.generateThreadDump(); + File dumpFile = createFilePath().toFile(); try (BufferedWriter writer = new BufferedWriter(new FileWriter(dumpFile, true))) { - ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); - ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds(), 100); - for (ThreadInfo threadInfo : threadInfos) { - writer.write(threadInfo.toString()); - writer.write("\n"); - } - - long[] deadlockThreadIds = threadMXBean.findDeadlockedThreads(); - if (deadlockThreadIds != null) { - writer.write("-------------------List of Deadlocked Thread IDs ---------------------"); - String idsList = (Arrays - .stream(deadlockThreadIds) - .boxed() - .collect(Collectors.toList())) - .stream().map(n -> String.valueOf(n)) - .collect(Collectors.joining("-", "{", "}")); - writer.write(idsList); - } + writer.write(threadDump); } return dumpFile; @@ -145,7 +124,7 @@ public final class ThreadDumpAction extends CallableSystemAction implements Acti * @return Path for dump file. */ private Path createFilePath() { - String fileName = "ThreadDump_" + DATE_FORMAT.format(new Date()) + ".txt"; + String fileName = "ThreadDump_" + TimeStampUtils.createTimeStamp() + ".txt"; if (Case.isCaseOpen()) { return Paths.get(Case.getCurrentCase().getLogDirectoryPath(), fileName); } diff --git a/Core/src/org/sleuthkit/autopsy/actions/ViewArtifactAction.java b/Core/src/org/sleuthkit/autopsy/actions/ViewArtifactAction.java new file mode 100644 index 0000000000..6da2b55862 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/ViewArtifactAction.java @@ -0,0 +1,76 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.actions; + +import java.awt.Cursor; +import java.awt.event.ActionEvent; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.SwingWorker; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * An action that navigates to an artifact. + */ +public class ViewArtifactAction extends AbstractAction { + + private static final Logger logger = Logger.getLogger(ViewArtifactAction.class.getName()); + private final BlackboardArtifact artifact; + + /** + * Main constructor. + * + * @param artifact The artifact to navigate to in the action. + * @param displayName The display name of the menu item. + */ + public ViewArtifactAction(BlackboardArtifact artifact, String displayName) { + super(displayName); + this.artifact = artifact; + } + + @Override + public void actionPerformed(ActionEvent e) { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + DirectoryTreeTopComponent.findInstance().viewArtifact(artifact); + return null; + } + + @Override + protected void done() { + try { + get(); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, "Unexpected interrupt while navigating to artifact.", ex); + } catch (ExecutionException ex) { + logger.log(Level.SEVERE, "Error navigating to artifact.", ex); + } + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + }.execute(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/actions/ViewOsAccountAction.java b/Core/src/org/sleuthkit/autopsy/actions/ViewOsAccountAction.java new file mode 100644 index 0000000000..200759fbf5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/ViewOsAccountAction.java @@ -0,0 +1,77 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.actions; + +import java.awt.Cursor; +import java.awt.event.ActionEvent; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.SwingWorker; +import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; +import org.sleuthkit.datamodel.OsAccount; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * An action that navigates to an os account. + */ +public class ViewOsAccountAction extends AbstractAction { + + private static final Logger logger = Logger.getLogger(ViewOsAccountAction.class.getName()); + + private final OsAccount osAccount; + + /** + * Main constructor. + * + * @param osAccount The os account to navigate to in the action. + * @param displayName The display name of the menu item. + */ + public ViewOsAccountAction(OsAccount osAccount, String displayName) { + super(displayName); + this.osAccount = osAccount; + } + + @Override + public void actionPerformed(ActionEvent e) { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + DirectoryTreeTopComponent.findInstance().viewOsAccount(osAccount); + return null; + } + + @Override + protected void done() { + try { + get(); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, "Unexpected interrupt while navigating to OS Account.", ex); + } catch (ExecutionException ex) { + logger.log(Level.SEVERE, "Error navigating to OS Account.", ex); + } + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + }.execute(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/allcasessearch/AllCasesSearchAction.java b/Core/src/org/sleuthkit/autopsy/allcasessearch/AllCasesSearchAction.java index 676a565de2..b4689db917 100755 --- a/Core/src/org/sleuthkit/autopsy/allcasessearch/AllCasesSearchAction.java +++ b/Core/src/org/sleuthkit/autopsy/allcasessearch/AllCasesSearchAction.java @@ -32,9 +32,9 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; * Action for accessing the Search Other Cases dialog. */ @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.allcasessearch.AllCasesSearchAction") -@ActionRegistration(displayName = "#CTL_OtherCasesSearchAction=Search All Cases", lazy = false) +@ActionRegistration(displayName = "#CTL_OtherCasesSearchAction=Search Central Repository", lazy = false) @ActionReference(path = "Menu/Tools", position = 201) -@NbBundle.Messages({"CTL_AllCasesSearchAction=Search All Cases"}) +@NbBundle.Messages({"CTL_AllCasesSearchAction=Search Central Repository"}) public class AllCasesSearchAction extends CallableSystemAction { @Override @@ -54,7 +54,7 @@ public class AllCasesSearchAction extends CallableSystemAction { } @NbBundle.Messages({ - "AllCasesSearchAction.getName.text=Search All Cases"}) + "AllCasesSearchAction.getName.text=Search Central Repository"}) @Override public String getName() { return Bundle.AllCasesSearchAction_getName_text(); diff --git a/Core/src/org/sleuthkit/autopsy/allcasessearch/AllCasesSearchDialog.form b/Core/src/org/sleuthkit/autopsy/allcasessearch/AllCasesSearchDialog.form index f378613c11..aca01ada64 100755 --- a/Core/src/org/sleuthkit/autopsy/allcasessearch/AllCasesSearchDialog.form +++ b/Core/src/org/sleuthkit/autopsy/allcasessearch/AllCasesSearchDialog.form @@ -24,10 +24,15 @@ - + - + + + + + + @@ -35,16 +40,23 @@ - - + + + + + + + + + + + + + + - - - - - @@ -60,17 +72,19 @@ - - - - + + + + + + - - - - + + + + @@ -85,16 +99,6 @@ - - - - - - - - - - @@ -158,5 +162,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/allcasessearch/AllCasesSearchDialog.java b/Core/src/org/sleuthkit/autopsy/allcasessearch/AllCasesSearchDialog.java index d8c82dc40b..077e642b42 100755 --- a/Core/src/org/sleuthkit/autopsy/allcasessearch/AllCasesSearchDialog.java +++ b/Core/src/org/sleuthkit/autopsy/allcasessearch/AllCasesSearchDialog.java @@ -21,8 +21,10 @@ package org.sleuthkit.autopsy.allcasessearch; import java.awt.Color; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; +import java.text.Collator; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.logging.Level; @@ -30,6 +32,7 @@ import javax.swing.JFrame; import javax.swing.SwingWorker; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; +import org.apache.commons.lang3.StringUtils; import org.openide.nodes.Node; import org.openide.util.NbBundle.Messages; import org.openide.windows.TopComponent; @@ -48,9 +51,9 @@ import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; @Messages({ - "AllCasesSearchDialog.dialogTitle.text=Search All Cases", + "AllCasesSearchDialog.dialogTitle.text=Search Central Repository", "AllCasesSearchDialog.resultsTitle.text=All Cases", - "AllCasesSearchDialog.resultsDescription.text=All Cases Search", + "AllCasesSearchDialog.resultsDescription.text=Search Central Repository", "AllCasesSearchDialog.emptyNode.text=No results found.", "AllCasesSearchDialog.validation.invalidHash=The supplied value is not a valid MD5 hash.", "AllCasesSearchDialog.validation.invalidEmail=The supplied value is not a valid e-mail address.", @@ -63,14 +66,14 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; "AllCasesSearchDialog.validation.invalidIccid=The supplied value is not a valid ICCID number.", "AllCasesSearchDialog.validation.genericMessage=The supplied value is not valid.", "# {0} - number of cases", - "AllCasesSearchDialog.caseLabel.text=The current Central Repository contains {0} case(s)." + "AllCasesSearchDialog.caseLabel.text=The Central Repository contains {0} case(s)." }) /** * The Search All Cases dialog allows users to search for specific types of * correlation properties in the Central Repository. */ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -final class AllCasesSearchDialog extends javax.swing.JDialog { + final class AllCasesSearchDialog extends javax.swing.JDialog { private static final Logger logger = Logger.getLogger(AllCasesSearchDialog.class.getName()); private static final long serialVersionUID = 1L; @@ -95,19 +98,21 @@ final class AllCasesSearchDialog extends javax.swing.JDialog { * @param type The correlation type. * @param value The value to be matched. */ - private void search(CorrelationAttributeInstance.Type type, String value) { + private void search(CorrelationAttributeInstance.Type type, String[] values) { new SwingWorker, Void>() { @Override protected List doInBackground() { List correlationInstances = new ArrayList<>(); - try { - correlationInstances = CentralRepository.getInstance().getArtifactInstancesByTypeValue(type, value); - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "Unable to connect to the Central Repository database.", ex); - } catch (CorrelationAttributeNormalizationException ex) { - logger.log(Level.SEVERE, "Unable to retrieve data from the Central Repository.", ex); + for (String value : values) { + try { + correlationInstances.addAll(CentralRepository.getInstance().getArtifactInstancesByTypeValue(type, value)); + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, "Unable to connect to the Central Repository database.", ex); + } catch (CorrelationAttributeNormalizationException ex) { + logger.log(Level.WARNING, "Unable to retrieve data from the Central Repository.", ex); + } } return correlationInstances; @@ -125,8 +130,8 @@ final class AllCasesSearchDialog extends javax.swing.JDialog { AllCasesSearchNode searchNode = new AllCasesSearchNode(correlationInstances); TableFilterNode tableFilterNode = new TableFilterNode(searchNode, true, searchNode.getName()); - String resultsText = String.format("%s (%s; \"%s\")", - Bundle.AllCasesSearchDialog_resultsTitle_text(), type.getDisplayName(), value); + String resultsText = String.format("%s (%s)", + Bundle.AllCasesSearchDialog_resultsTitle_text(), type.getDisplayName()); final TopComponent searchResultWin; if (correlationInstances.isEmpty()) { Node emptyNode = new TableFilterNode( @@ -155,26 +160,21 @@ final class AllCasesSearchDialog extends javax.swing.JDialog { private void initComponents() { correlationValueLabel = new javax.swing.JLabel(); - correlationValueTextField = new javax.swing.JTextField(); searchButton = new javax.swing.JButton(); correlationTypeComboBox = new javax.swing.JComboBox<>(); correlationTypeLabel = new javax.swing.JLabel(); errorLabel = new javax.swing.JLabel(); descriptionLabel = new javax.swing.JLabel(); casesLabel = new javax.swing.JLabel(); + correlationValueScrollPane = new javax.swing.JScrollPane(); + correlationValueTextArea = new javax.swing.JTextArea(); + normalizedLabel = new javax.swing.JLabel(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); setResizable(false); org.openide.awt.Mnemonics.setLocalizedText(correlationValueLabel, org.openide.util.NbBundle.getMessage(AllCasesSearchDialog.class, "AllCasesSearchDialog.correlationValueLabel.text")); // NOI18N - correlationValueTextField.setText(org.openide.util.NbBundle.getMessage(AllCasesSearchDialog.class, "AllCasesSearchDialog.correlationValueTextField.text")); // NOI18N - correlationValueTextField.addKeyListener(new java.awt.event.KeyAdapter() { - public void keyReleased(java.awt.event.KeyEvent evt) { - valueFieldKeyReleaseListener(evt); - } - }); - org.openide.awt.Mnemonics.setLocalizedText(searchButton, org.openide.util.NbBundle.getMessage(AllCasesSearchDialog.class, "AllCasesSearchDialog.searchButton.text")); // NOI18N searchButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -198,6 +198,13 @@ final class AllCasesSearchDialog extends javax.swing.JDialog { casesLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); org.openide.awt.Mnemonics.setLocalizedText(casesLabel, org.openide.util.NbBundle.getMessage(AllCasesSearchDialog.class, "AllCasesSearchDialog.casesLabel.text")); // NOI18N + correlationValueTextArea.setColumns(20); + correlationValueTextArea.setRows(5); + correlationValueTextArea.setText(org.openide.util.NbBundle.getMessage(AllCasesSearchDialog.class, "AllCasesSearchDialog.correlationValueTextArea.text")); // NOI18N + correlationValueScrollPane.setViewportView(correlationValueTextArea); + + org.openide.awt.Mnemonics.setLocalizedText(normalizedLabel, org.openide.util.NbBundle.getMessage(AllCasesSearchDialog.class, "AllCasesSearchDialog.normalizedLabel.text")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( @@ -205,20 +212,28 @@ final class AllCasesSearchDialog extends javax.swing.JDialog { .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(descriptionLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 430, Short.MAX_VALUE) + .addComponent(descriptionLabel) + .addGroup(layout.createSequentialGroup() + .addComponent(casesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(searchButton)) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(correlationValueLabel) .addComponent(correlationTypeLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(correlationTypeComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(correlationValueTextField) - .addComponent(errorLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addComponent(casesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGap(18, 18, 18) - .addComponent(searchButton))) + .addGroup(layout.createSequentialGroup() + .addComponent(normalizedLabel) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(correlationTypeComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(correlationValueScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 379, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE))) + .addGap(142, 142, 142)) + .addComponent(errorLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) .addContainerGap()) ); layout.setVerticalGroup( @@ -230,16 +245,18 @@ final class AllCasesSearchDialog extends javax.swing.JDialog { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(correlationTypeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(correlationTypeLabel)) - .addGap(15, 15, 15) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(correlationValueTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(correlationValueLabel)) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(correlationValueLabel) + .addComponent(correlationValueScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 190, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(normalizedLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 19, Short.MAX_VALUE) .addComponent(errorLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 20, Short.MAX_VALUE) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(searchButton) - .addComponent(casesLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(casesLabel, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(searchButton, javax.swing.GroupLayout.Alignment.TRAILING)) .addContainerGap()) ); @@ -251,50 +268,55 @@ final class AllCasesSearchDialog extends javax.swing.JDialog { private void searchButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchButtonActionPerformed CorrelationAttributeInstance.Type correlationType = selectedCorrelationType; - String correlationValue = correlationValueTextField.getText().trim(); + String correlationValue = correlationValueTextArea.getText().trim(); - if (validateInputs(correlationType, correlationValue)) { - search(correlationType, correlationValue); - dispose(); - } else { - String validationMessage; - switch (correlationType.getId()) { - case CorrelationAttributeInstance.FILES_TYPE_ID: - validationMessage = Bundle.AllCasesSearchDialog_validation_invalidHash(); - break; - case CorrelationAttributeInstance.DOMAIN_TYPE_ID: - validationMessage = Bundle.AllCasesSearchDialog_validation_invalidDomain(); - break; - case CorrelationAttributeInstance.EMAIL_TYPE_ID: - validationMessage = Bundle.AllCasesSearchDialog_validation_invalidEmail(); - break; - case CorrelationAttributeInstance.PHONE_TYPE_ID: - validationMessage = Bundle.AllCasesSearchDialog_validation_invalidPhone(); - break; - case CorrelationAttributeInstance.SSID_TYPE_ID: - validationMessage = Bundle.AllCasesSearchDialog_validation_invalidSsid(); - break; - case CorrelationAttributeInstance.MAC_TYPE_ID: - validationMessage = Bundle.AllCasesSearchDialog_validation_invalidMac(); - break; - case CorrelationAttributeInstance.IMEI_TYPE_ID: - validationMessage = Bundle.AllCasesSearchDialog_validation_invalidImei(); - break; - case CorrelationAttributeInstance.IMSI_TYPE_ID: - validationMessage = Bundle.AllCasesSearchDialog_validation_invalidImsi(); - break; - case CorrelationAttributeInstance.ICCID_TYPE_ID: - validationMessage = Bundle.AllCasesSearchDialog_validation_invalidIccid(); - break; - default: - validationMessage = Bundle.AllCasesSearchDialog_validation_genericMessage(); - break; + String[] correlationValueLines = correlationValue.split("\r\n|\n|\r"); +// for (String correlationValueLine : lines) { + + if (validateInputs(correlationType, correlationValueLines)) { + search(correlationType, correlationValueLines); + dispose(); + } else { + String validationMessage; + switch (correlationType.getId()) { + case CorrelationAttributeInstance.FILES_TYPE_ID: + validationMessage = Bundle.AllCasesSearchDialog_validation_invalidHash(); + break; + case CorrelationAttributeInstance.DOMAIN_TYPE_ID: + validationMessage = Bundle.AllCasesSearchDialog_validation_invalidDomain(); + break; + case CorrelationAttributeInstance.EMAIL_TYPE_ID: + validationMessage = Bundle.AllCasesSearchDialog_validation_invalidEmail(); + break; + case CorrelationAttributeInstance.PHONE_TYPE_ID: + validationMessage = Bundle.AllCasesSearchDialog_validation_invalidPhone(); + break; + case CorrelationAttributeInstance.SSID_TYPE_ID: + validationMessage = Bundle.AllCasesSearchDialog_validation_invalidSsid(); + break; + case CorrelationAttributeInstance.MAC_TYPE_ID: + validationMessage = Bundle.AllCasesSearchDialog_validation_invalidMac(); + break; + case CorrelationAttributeInstance.IMEI_TYPE_ID: + validationMessage = Bundle.AllCasesSearchDialog_validation_invalidImei(); + break; + case CorrelationAttributeInstance.IMSI_TYPE_ID: + validationMessage = Bundle.AllCasesSearchDialog_validation_invalidImsi(); + break; + case CorrelationAttributeInstance.ICCID_TYPE_ID: + validationMessage = Bundle.AllCasesSearchDialog_validation_invalidIccid(); + break; + default: + validationMessage = Bundle.AllCasesSearchDialog_validation_genericMessage(); + break; + } + + errorLabel.setText(validationMessage); + searchButton.setEnabled(false); + correlationValueTextArea.grabFocus(); } - errorLabel.setText(validationMessage); - searchButton.setEnabled(false); - correlationValueTextField.grabFocus(); - } + // } }//GEN-LAST:event_searchButtonActionPerformed private void correlationTypeComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_correlationTypeComboBoxActionPerformed @@ -302,11 +324,6 @@ final class AllCasesSearchDialog extends javax.swing.JDialog { errorLabel.setText(""); }//GEN-LAST:event_correlationTypeComboBoxActionPerformed - private void valueFieldKeyReleaseListener(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_valueFieldKeyReleaseListener - //make error message go away when the user enters anything in the value field - errorLabel.setText(""); - }//GEN-LAST:event_valueFieldKeyReleaseListener - /** * Validate the supplied input. * @@ -315,10 +332,12 @@ final class AllCasesSearchDialog extends javax.swing.JDialog { * * @return True if the input is valid for the given type; otherwise false. */ - private boolean validateInputs(CorrelationAttributeInstance.Type type, String value) { + private boolean validateInputs(CorrelationAttributeInstance.Type type, String[] values) { try { - CorrelationAttributeNormalizer.normalize(type, value); - } catch (CorrelationAttributeNormalizationException ex) { + for (String value : values) { + CorrelationAttributeNormalizer.normalize(type, value); + } + } catch (CorrelationAttributeNormalizationException | CentralRepoException ex) { // No need to log this. return false; } @@ -339,15 +358,33 @@ final class AllCasesSearchDialog extends javax.swing.JDialog { CentralRepository dbManager = CentralRepository.getInstance(); correlationTypes.clear(); correlationTypes.addAll(dbManager.getDefinedCorrelationTypes()); +// correlationTypes.addAll(java.util.Collections.sort(dbManager.getDefinedCorrelationTypes(), Collator.getInstance())); int numberOfCases = dbManager.getCases().size(); casesLabel.setText(Bundle.AllCasesSearchDialog_caseLabel_text(numberOfCases)); } catch (CentralRepoException ex) { logger.log(Level.SEVERE, "Unable to connect to the Central Repository database.", ex); } + List displayNames = new ArrayList<>(); for (CorrelationAttributeInstance.Type type : correlationTypes) { - correlationTypeComboBox.addItem(type.getDisplayName()); + String displayName = type.getDisplayName(); + if (displayName.toLowerCase().contains("addresses")) { + type.setDisplayName(displayName.replace("Addresses", "Address")); + } else if (displayName.toLowerCase().equals("files")) { + type.setDisplayName("File MD5"); + } else if (displayName.toLowerCase().endsWith("s") && !displayName.toLowerCase().endsWith("address")) { + type.setDisplayName(StringUtils.substring(displayName, 0, displayName.length() - 1)); + } else { + type.setDisplayName(displayName); + } + + displayNames.add(type.getDisplayName()); } + Collections.sort(displayNames); + for (String displayName : displayNames) { + correlationTypeComboBox.addItem(displayName); + } + correlationTypeComboBox.setSelectedIndex(0); correlationTypeComboBox.addItemListener(new ItemListener() { @@ -364,7 +401,7 @@ final class AllCasesSearchDialog extends javax.swing.JDialog { /* * Create listener for text input. */ - correlationValueTextField.getDocument().addDocumentListener(new DocumentListener() { + correlationValueTextArea.getDocument().addDocumentListener(new DocumentListener() { @Override public void changedUpdate(DocumentEvent e) { updateSearchButton(); @@ -440,7 +477,7 @@ final class AllCasesSearchDialog extends javax.swing.JDialog { text = ""; break; } - correlationValueTextFieldPrompt = new TextPrompt(text, correlationValueTextField); + correlationValueTextFieldPrompt = new TextPrompt(text, correlationValueTextArea); /** * Sets the foreground color and transparency of the text prompt. @@ -470,7 +507,7 @@ final class AllCasesSearchDialog extends javax.swing.JDialog { * been provided for the correlation property value. */ private void updateSearchButton() { - searchButton.setEnabled(correlationValueTextField.getText().isEmpty() == false); + searchButton.setEnabled(correlationValueTextArea.getText().isEmpty() == false); } /** @@ -486,9 +523,11 @@ final class AllCasesSearchDialog extends javax.swing.JDialog { private javax.swing.JComboBox correlationTypeComboBox; private javax.swing.JLabel correlationTypeLabel; private javax.swing.JLabel correlationValueLabel; - private javax.swing.JTextField correlationValueTextField; + private javax.swing.JScrollPane correlationValueScrollPane; + private javax.swing.JTextArea correlationValueTextArea; private javax.swing.JLabel descriptionLabel; private javax.swing.JLabel errorLabel; + private javax.swing.JLabel normalizedLabel; private javax.swing.JButton searchButton; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/allcasessearch/Bundle.properties b/Core/src/org/sleuthkit/autopsy/allcasessearch/Bundle.properties index e1a434785b..d8043261d8 100755 --- a/Core/src/org/sleuthkit/autopsy/allcasessearch/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/allcasessearch/Bundle.properties @@ -1,10 +1,11 @@ -AllCasesSearchDialog.descriptionLabel.text=Search the Central Repository for correlation properties with a specified value. The search is case insensitive. +AllCasesSearchDialog.descriptionLabel.text=Search the Central Repository for the given values. AllCasesSearchDialog.errorLabel.text=\ -AllCasesSearchDialog.correlationTypeLabel.text=Correlation Property Type: +AllCasesSearchDialog.correlationTypeLabel.text=Type: AllCasesSearchDialog.searchButton.AccessibleContext.accessibleDescription= AllCasesSearchDialog.searchButton.AccessibleContext.accessibleName=Search AllCasesSearchDialog.searchButton.text=Search -AllCasesSearchDialog.correlationValueTextField.text= -AllCasesSearchDialog.correlationValueLabel.text=Correlation Property Value: +AllCasesSearchDialog.correlationValueLabel.text=Value: AllCasesSearchDialog.casesLabel.text=\ +AllCasesSearchDialog.correlationValueTextArea.text= +AllCasesSearchDialog.normalizedLabel.text=Values will be normalized to ensure consistent case and formatting. diff --git a/Core/src/org/sleuthkit/autopsy/allcasessearch/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/allcasessearch/Bundle.properties-MERGED index 57f8219cd1..619e29f12a 100755 --- a/Core/src/org/sleuthkit/autopsy/allcasessearch/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/allcasessearch/Bundle.properties-MERGED @@ -1,7 +1,7 @@ -AllCasesSearchAction.getName.text=Search All Cases +AllCasesSearchAction.getName.text=Search Central Repository # {0} - number of cases -AllCasesSearchDialog.caseLabel.text=The current Central Repository contains {0} case(s). +AllCasesSearchDialog.caseLabel.text=The Central Repository contains {0} case(s). AllCasesSearchDialog.correlationValueTextField.domainExample=Example: "domain.com" AllCasesSearchDialog.correlationValueTextField.emailExample=Example: "user@host.com" AllCasesSearchDialog.correlationValueTextField.filesExample=Example: "f0e1d2c3b4a5968778695a4b3c2d1e0f" @@ -12,19 +12,20 @@ AllCasesSearchDialog.correlationValueTextField.macExample=Example: "0C-14-F2-01- AllCasesSearchDialog.correlationValueTextField.phoneExample=Example: "(800)123-4567" AllCasesSearchDialog.correlationValueTextField.ssidExample=Example: "WirelessNetwork-5G" AllCasesSearchDialog.correlationValueTextField.usbExample=Example: "4&1234567&0" -AllCasesSearchDialog.descriptionLabel.text=Search the Central Repository for correlation properties with a specified value. The search is case insensitive. -AllCasesSearchDialog.dialogTitle.text=Search All Cases +AllCasesSearchDialog.descriptionLabel.text=Search the Central Repository for the given values. +AllCasesSearchDialog.dialogTitle.text=Search Central Repository AllCasesSearchDialog.emptyNode.text=No results found. AllCasesSearchDialog.errorLabel.text=\ -AllCasesSearchDialog.correlationTypeLabel.text=Correlation Property Type: -AllCasesSearchDialog.resultsDescription.text=All Cases Search +AllCasesSearchDialog.correlationTypeLabel.text=Type: +AllCasesSearchDialog.resultsDescription.text=Search Central Repository AllCasesSearchDialog.resultsTitle.text=All Cases AllCasesSearchDialog.searchButton.AccessibleContext.accessibleDescription= AllCasesSearchDialog.searchButton.AccessibleContext.accessibleName=Search AllCasesSearchDialog.searchButton.text=Search -AllCasesSearchDialog.correlationValueTextField.text= -AllCasesSearchDialog.correlationValueLabel.text=Correlation Property Value: +AllCasesSearchDialog.correlationValueLabel.text=Value: AllCasesSearchDialog.casesLabel.text=\ +AllCasesSearchDialog.correlationValueTextArea.text= +AllCasesSearchDialog.normalizedLabel.text=Values will be normalized to ensure consistent case and formatting. AllCasesSearchDialog.validation.genericMessage=The supplied value is not valid. AllCasesSearchDialog.validation.invalidDomain=The supplied value is not a valid domain. AllCasesSearchDialog.validation.invalidEmail=The supplied value is not a valid e-mail address. @@ -43,4 +44,5 @@ CorrelationAttributeInstanceNode.columnName.device=Device CorrelationAttributeInstanceNode.columnName.known=Known CorrelationAttributeInstanceNode.columnName.name=Name CorrelationAttributeInstanceNode.columnName.path=Path -CTL_AllCasesSearchAction=Search All Cases +CorrelationAttributeInstanceNode.columnName.value=Value +CTL_AllCasesSearchAction=Search Central Repository diff --git a/Core/src/org/sleuthkit/autopsy/allcasessearch/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/allcasessearch/Bundle_ja.properties index 08a2809615..544d416bc3 100644 --- a/Core/src/org/sleuthkit/autopsy/allcasessearch/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/allcasessearch/Bundle_ja.properties @@ -1,30 +1,29 @@ - -AllCasesSearchAction.getName.text=\u3059\u3079\u3066\u306e\u30b1\u30fc\u30b9\u3092\u691c\u7d22 -# {0} - \u30b1\u30fc\u30b9\u6570 -AllCasesSearchDialog.caseLabel.text=\u73fe\u5728\u306e\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ec\u30dd\u30b8\u30c8\u30ea\u30fc\u306b\u306f {0} \u30b1\u30fc\u30b9\u304c\u542b\u307e\u308c\u3066\u3044\u307e\u305b\u3093\u3002 -AllCasesSearchDialog.correlationValueTextField.domainExample=\u4f8b: "domain.com" -AllCasesSearchDialog.correlationValueTextField.emailExample=\u4f8b: "user@host.com" -AllCasesSearchDialog.correlationValueTextField.filesExample=\u4f8b: "f0e1d2c3b4a5968778695a4b3c2d1e0f" -AllCasesSearchDialog.correlationValueTextField.iccidExample=\u4f8b: "89 91 19 1299 99 329451 0" -AllCasesSearchDialog.correlationValueTextField.imeiExample=\u4f8b: "351756061523999" -AllCasesSearchDialog.correlationValueTextField.imsiExample=\u4f8b: "310150123456789" -AllCasesSearchDialog.correlationValueTextField.macExample=\u4f8b: "0C-14-F2-01-AF-45" -AllCasesSearchDialog.correlationValueTextField.phoneExample=\u4f8b: "(800)123-4567" -AllCasesSearchDialog.correlationValueTextField.ssidExample=\u4f8b: "WirelessNetwork-5G" -AllCasesSearchDialog.correlationValueTextField.usbExample=\u4f8b: "4&1234567&0" -AllCasesSearchDialog.descriptionLabel.text=\u76f8\u95a2\u5206\u6790\u30d7\u30ed\u30d1\u30c6\u30a3\u304c\u306a\u3044\u304b\u6307\u5b9a\u5024\u3067\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ec\u30dd\u30b8\u30c8\u30ea\u30fc\u3092\u691c\u7d22\u3057\u307e\u3059\u3002\u691c\u7d22\u306f\u5927\u6587\u5b57\u5c0f\u6587\u5b57\u3092\u533a\u5225\u3057\u307e\u305b\u3093\u3002 -AllCasesSearchDialog.dialogTitle.text=\u3059\u3079\u3066\u306e\u30b1\u30fc\u30b9\u3092\u691c\u7d22 +#Thu Sep 30 10:26:58 UTC 2021 +AllCasesSearchAction.getName.text=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u691c\u7d22 +AllCasesSearchDialog.caseLabel.text=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306b\u306f{0}\u30b1\u30fc\u30b9\u304c\u542b\u307e\u308c\u3066\u3044\u307e\u3059\u3002 +AllCasesSearchDialog.casesLabel.text=\ +AllCasesSearchDialog.correlationTypeLabel.text=\u30bf\u30a4\u30d7\uff1a +AllCasesSearchDialog.correlationValueLabel.text=\u5024\uff1a +AllCasesSearchDialog.correlationValueTextField.domainExample=\u4f8b\: "domain.com" +AllCasesSearchDialog.correlationValueTextField.emailExample=\u4f8b\: "user@host.com" +AllCasesSearchDialog.correlationValueTextField.filesExample=\u4f8b\: "f0e1d2c3b4a5968778695a4b3c2d1e0f" +AllCasesSearchDialog.correlationValueTextField.iccidExample=\u4f8b\: "89 91 19 1299 99 329451 0" +AllCasesSearchDialog.correlationValueTextField.imeiExample=\u4f8b\: "351756061523999" +AllCasesSearchDialog.correlationValueTextField.imsiExample=\u4f8b\: "310150123456789" +AllCasesSearchDialog.correlationValueTextField.macExample=\u4f8b\: "0C-14-F2-01-AF-45" +AllCasesSearchDialog.correlationValueTextField.phoneExample=\u4f8b\: "(800)123-4567" +AllCasesSearchDialog.correlationValueTextField.ssidExample=\u4f8b\: "WirelessNetwork-5G" +AllCasesSearchDialog.correlationValueTextField.usbExample=\u4f8b\: "4&1234567&0" +AllCasesSearchDialog.descriptionLabel.text=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u3067\u6307\u5b9a\u3055\u308c\u305f\u5024\u3092\u691c\u7d22\u3057\u307e\u3059\u3002 +AllCasesSearchDialog.dialogTitle.text=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u691c\u7d22 AllCasesSearchDialog.emptyNode.text=\u8a72\u5f53\u3059\u308b\u7d50\u679c\u304c\u3042\u308a\u307e\u305b\u3093\u3002 AllCasesSearchDialog.errorLabel.text=\ -AllCasesSearchDialog.correlationTypeLabel.text=\u76f8\u95a2\u5206\u6790\u30d7\u30ed\u30d1\u30c6\u30a3\u30bf\u30a4\u30d7: -AllCasesSearchDialog.resultsDescription.text=\u3059\u3079\u3066\u306e\u30b1\u30fc\u30b9\u306e\u691c\u7d22 +AllCasesSearchDialog.normalizedLabel.text=\u5927\u6587\u5b57\u3068\u5c0f\u6587\u5b57\u3068\u66f8\u5f0f\u304c\u4e00\u8cab\u3059\u308b\u3088\u3046\u306b\u3001\u5024\u306f\u6b63\u898f\u5316\u3055\u308c\u307e\u3059\u3002 +AllCasesSearchDialog.resultsDescription.text=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u691c\u7d22 AllCasesSearchDialog.resultsTitle.text=\u3059\u3079\u3066\u306e\u30b1\u30fc\u30b9 AllCasesSearchDialog.searchButton.AccessibleContext.accessibleDescription= AllCasesSearchDialog.searchButton.AccessibleContext.accessibleName=\u691c\u7d22 AllCasesSearchDialog.searchButton.text=\u691c\u7d22 -AllCasesSearchDialog.correlationValueTextField.text= -AllCasesSearchDialog.correlationValueLabel.text=\u76f8\u95a2\u5206\u6790\u30d7\u30ed\u30d1\u30c6\u30a3\u5024: -AllCasesSearchDialog.casesLabel.text=\ AllCasesSearchDialog.validation.genericMessage=\u63d0\u4f9b\u3055\u308c\u305f\u5024\u306f\u6709\u52b9\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 AllCasesSearchDialog.validation.invalidDomain=\u63d0\u4f9b\u3055\u308c\u305f\u5024\u306f\u6709\u52b9\u306a\u30c9\u30e1\u30a4\u30f3\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 AllCasesSearchDialog.validation.invalidEmail=\u63d0\u4f9b\u3055\u308c\u305f\u5024\u306f\u6709\u52b9\u306a\u96fb\u5b50\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 @@ -36,6 +35,7 @@ AllCasesSearchDialog.validation.invalidMac=\u63d0\u4f9b\u3055\u308c\u305f\u5024\ AllCasesSearchDialog.validation.invalidPhone=\u63d0\u4f9b\u3055\u308c\u305f\u5024\u306f\u6709\u52b9\u306a\u96fb\u8a71\u756a\u53f7\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 AllCasesSearchDialog.validation.invalidSsid=\u63d0\u4f9b\u3055\u308c\u305f\u5024\u306f\u6709\u52b9\u306a\u30ef\u30a4\u30e4\u30ec\u30b9 \u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 AllCasesSearchNode.getName.text=\u305d\u306e\u4ed6\u306e\u30b1\u30fc\u30b9\u306e\u691c\u7d22 +CTL_AllCasesSearchAction=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u691c\u7d22 CorrelationAttributeInstanceNode.columnName.case=\u30b1\u30fc\u30b9 CorrelationAttributeInstanceNode.columnName.comment=\u30b3\u30e1\u30f3\u30c8 CorrelationAttributeInstanceNode.columnName.dataSource=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9 @@ -43,4 +43,4 @@ CorrelationAttributeInstanceNode.columnName.device=\u30c7\u30d0\u30a4\u30b9 CorrelationAttributeInstanceNode.columnName.known=\u65e2\u77e5 CorrelationAttributeInstanceNode.columnName.name=\u540d\u524d CorrelationAttributeInstanceNode.columnName.path=\u30d1\u30b9 -CTL_AllCasesSearchAction=\u3059\u3079\u3066\u306e\u30b1\u30fc\u30b9\u3092\u691c\u7d22 +CorrelationAttributeInstanceNode.columnName.value=\u5024 diff --git a/Core/src/org/sleuthkit/autopsy/allcasessearch/CorrelationAttributeInstanceNode.java b/Core/src/org/sleuthkit/autopsy/allcasessearch/CorrelationAttributeInstanceNode.java index 7c77b753d8..e2e57e68d5 100755 --- a/Core/src/org/sleuthkit/autopsy/allcasessearch/CorrelationAttributeInstanceNode.java +++ b/Core/src/org/sleuthkit/autopsy/allcasessearch/CorrelationAttributeInstanceNode.java @@ -84,6 +84,7 @@ public final class CorrelationAttributeInstanceNode extends DisplayableItemNode "CorrelationAttributeInstanceNode.columnName.name=Name", "CorrelationAttributeInstanceNode.columnName.case=Case", "CorrelationAttributeInstanceNode.columnName.dataSource=Data Source", + "CorrelationAttributeInstanceNode.columnName.value=Value", "CorrelationAttributeInstanceNode.columnName.known=Known", "CorrelationAttributeInstanceNode.columnName.path=Path", "CorrelationAttributeInstanceNode.columnName.comment=Comment", @@ -109,6 +110,7 @@ public final class CorrelationAttributeInstanceNode extends DisplayableItemNode final String dataSourceName = dataSource.getName(); final String known = centralRepoFile.getKnownStatus().getName(); final String comment = centralRepoFile.getComment(); + final String value = centralRepoFile.getCorrelationValue(); final String device = dataSource.getDeviceID(); final String NO_DESCR = ""; @@ -122,6 +124,9 @@ public final class CorrelationAttributeInstanceNode extends DisplayableItemNode sheetSet.put(new NodeProperty<>( Bundle.CorrelationAttributeInstanceNode_columnName_dataSource(), Bundle.CorrelationAttributeInstanceNode_columnName_dataSource(), NO_DESCR, dataSourceName)); + sheetSet.put(new NodeProperty<>( + Bundle.CorrelationAttributeInstanceNode_columnName_value(), + Bundle.CorrelationAttributeInstanceNode_columnName_value(), NO_DESCR, value)); sheetSet.put(new NodeProperty<>( Bundle.CorrelationAttributeInstanceNode_columnName_known(), Bundle.CorrelationAttributeInstanceNode_columnName_known(), NO_DESCR, known)); diff --git a/Core/src/org/sleuthkit/autopsy/apputils/ApplicationLoggers.java b/Core/src/org/sleuthkit/autopsy/apputils/ApplicationLoggers.java new file mode 100755 index 0000000000..1cd2cf734f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/apputils/ApplicationLoggers.java @@ -0,0 +1,103 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.apputils; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.Timestamp; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.FileHandler; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; + +/** + * A utility that creates and stores application loggers. + * + * TODO (Jira-7175): This code is the third copy of code that originally + * appeared in org.sleuthkit.autopsy.coreutils.Logger. The second copy is in + * org.sleuthkit.autopsy.experimental.autoingest.AutoIngestSystemLogger. This + * class should allow the replacement of AutoIngestSystemLogger and the + * elimination of duplicate code in coreutils.Logger through delegation + * (maintaining the public API for coreutils.Logger). + */ +final public class ApplicationLoggers { + + private static final int LOG_SIZE = 50000000; // In bytes, zero is unlimited. + private static final int LOG_FILE_COUNT = 10; + private static final String NEWLINE = System.lineSeparator(); + private static final Map loggers = new HashMap<>(); + + /** + * Gets the logger for a given application log file. The log file will be + * located in the var/log directory of the platform user directory and will + * have a name of the form [log name].log. + * + * @return The logger. + */ + synchronized public static Logger getLogger(String logName) { + Logger logger; + if (loggers.containsKey(logName)) { + logger = loggers.get(logName); + } else { + logger = Logger.getLogger(logName); + Path logFilePath = Paths.get(PlatformUtil.getUserDirectory().getAbsolutePath(), "var", "log", String.format("%s.log", logName)); + try { + FileHandler fileHandler = new FileHandler(logFilePath.toString(), LOG_SIZE, LOG_FILE_COUNT); + fileHandler.setEncoding(PlatformUtil.getLogFileEncoding()); + fileHandler.setFormatter(new Formatter() { + @Override + public String format(LogRecord record) { + Throwable thrown = record.getThrown(); + String stackTrace = ""; //NON-NLS + while (thrown != null) { + stackTrace += thrown.toString() + NEWLINE; + for (StackTraceElement traceElem : record.getThrown().getStackTrace()) { + stackTrace += "\t" + traceElem.toString() + NEWLINE; //NON-NLS + } + thrown = thrown.getCause(); + } + return (new Timestamp(record.getMillis())).toString() + " " //NON-NLS + + record.getSourceClassName() + " " //NON-NLS + + record.getSourceMethodName() + NEWLINE + + record.getLevel() + ": " //NON-NLS + + this.formatMessage(record) + NEWLINE + + stackTrace; + } + }); + logger.addHandler(fileHandler); + logger.setUseParentHandlers(false); + } catch (SecurityException | IOException ex) { + throw new RuntimeException(String.format("Error initializing file handler for %s", logFilePath), ex); //NON-NLS + } + loggers.put(logName, logger); + } + return logger; + } + + /** + * Prevents instantiation of this utility class. + */ + private ApplicationLoggers() { + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/apputils/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/apputils/Bundle.properties-MERGED new file mode 100644 index 0000000000..de7c28c948 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/apputils/Bundle.properties-MERGED @@ -0,0 +1,4 @@ +CTL_ResetWindowsAction=Reset Windows +ResetWindowAction.caseCloseFailure.text=Unable to close the current case, the software will restart and the windows locations will reset the next time the software is closed. +ResetWindowAction.caseSaveMetadata.text=Unable to save current case path, the software will restart and the windows locations will reset but the current case will not be opened upon restart. +ResetWindowAction.confirm.text=In order to perform the resetting of window locations the software will close and restart. If a case is currently open, it will be closed. If ingest or a search is currently running, it will be terminated. Are you sure you want to restart the software to reset all window locations? diff --git a/Core/src/org/sleuthkit/autopsy/apputils/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/apputils/Bundle_ja.properties new file mode 100644 index 0000000000..72dd3a972d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/apputils/Bundle_ja.properties @@ -0,0 +1,5 @@ +#Thu Sep 30 10:26:58 UTC 2021 +CTL_ResetWindowsAction=\u30a6\u30a3\u30f3\u30c9\u30a6\u3092\u30ea\u30bb\u30c3\u30c8 +ResetWindowAction.caseCloseFailure.text=\u73fe\u5728\u306e\u30b1\u30fc\u30b9\u3092\u9589\u3058\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3002\u30bd\u30d5\u30c8\u30a6\u30a7\u30a2\u306f\u518d\u8d77\u52d5\u3057\u3001\u6b21\u306b\u30bd\u30d5\u30c8\u30a6\u30a7\u30a2\u3092\u9589\u3058\u308b\u3068\u304d\u306b\u30a6\u30a3\u30f3\u30c9\u30a6\u306e\u5834\u6240\u304c\u30ea\u30bb\u30c3\u30c8\u3055\u308c\u307e\u3059\u3002 +ResetWindowAction.caseSaveMetadata.text=\u73fe\u5728\u306e\u30b1\u30fc\u30b9\u30d1\u30b9\u3092\u4fdd\u5b58\u3067\u304d\u307e\u305b\u3093\u3002\u30bd\u30d5\u30c8\u30a6\u30a7\u30a2\u306f\u518d\u8d77\u52d5\u3057\u3001\u30a6\u30a3\u30f3\u30c9\u30a6\u306e\u5834\u6240\u306f\u30ea\u30bb\u30c3\u30c8\u3055\u308c\u307e\u3059\u3002\u518d\u8d77\u52d5\u6642\u306b\u73fe\u5728\u306e\u30b1\u30fc\u30b9\u306f\u958b\u304b\u308c\u307e\u305b\u3093\u3002 +ResetWindowAction.confirm.text=\u30a6\u30a3\u30f3\u30c9\u30a6\u306e\u5834\u6240\u306e\u30ea\u30bb\u30c3\u30c8\u3092\u5b9f\u884c\u3059\u308b\u305f\u3081\u306b\u3001\u30bd\u30d5\u30c8\u30a6\u30a7\u30a2\u306f\u9589\u3058\u3066\u518d\u8d77\u52d5\u3057\u307e\u3059\u3002 \u30b1\u30fc\u30b9\u304c\u73fe\u5728\u958b\u3044\u3066\u3044\u308b\u5834\u5408\u306f\u3001\u9589\u3058\u3089\u308c\u307e\u3059\u3002 \u53d6\u308a\u8fbc\u307f\u307e\u305f\u306f\u691c\u7d22\u304c\u73fe\u5728\u5b9f\u884c\u4e2d\u306e\u5834\u5408\u3001\u305d\u308c\u306f\u7d42\u4e86\u3057\u307e\u3059\u3002 \u30bd\u30d5\u30c8\u30a6\u30a7\u30a2\u3092\u518d\u8d77\u52d5\u3057\u3066\u3001\u3059\u3079\u3066\u306e\u30a6\u30a3\u30f3\u30c9\u30a6\u306e\u5834\u6240\u3092\u30ea\u30bb\u30c3\u30c8\u3057\u3066\u3082\u3088\u308d\u3057\u3044\u3067\u3059\u304b\uff1f diff --git a/Core/src/org/sleuthkit/autopsy/apputils/ResetWindowsAction.java b/Core/src/org/sleuthkit/autopsy/apputils/ResetWindowsAction.java new file mode 100644 index 0000000000..25bacd3761 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/apputils/ResetWindowsAction.java @@ -0,0 +1,141 @@ +/* + * Autopsy + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.apputils; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.logging.Level; +import javax.swing.SwingUtilities; +import org.apache.commons.io.FileUtils; +import org.openide.LifecycleManager; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionReferences; +import org.openide.awt.ActionRegistration; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; +import org.openide.util.actions.CallableSystemAction; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; + +/** + * Class to open the Discovery dialog. Allows the user to run searches and see + * results in the DiscoveryTopComponent. + */ +@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.apputils.ResetWindowsAction") +@ActionReferences(value = { + @ActionReference(path = "Menu/Window", position = 205)}) +@ActionRegistration(displayName = "#CTL_ResetWindowsAction", lazy = false) +@NbBundle.Messages({"CTL_ResetWindowsAction=Reset Windows"}) +public final class ResetWindowsAction extends CallableSystemAction { + + private static final String DISPLAY_NAME = Bundle.CTL_ResetWindowsAction(); + private static final long serialVersionUID = 1L; + private final static Logger logger = Logger.getLogger(ResetWindowsAction.class.getName()); + private final static String WINDOWS2LOCAL = "Windows2Local"; + private final static String CASE_TO_REOPEN_FILE = "caseToOpen.txt"; + + @Override + public boolean isEnabled() { + return true; + } + + @NbBundle.Messages({"ResetWindowAction.confirm.text=In order to perform the resetting of window locations the software will close and restart. " + + "If a case is currently open, it will be closed. If ingest or a search is currently running, it will be terminated. " + + "Are you sure you want to restart the software to reset all window locations?", + "ResetWindowAction.caseCloseFailure.text=Unable to close the current case, " + + "the software will restart and the windows locations will reset the next time the software is closed.", + "ResetWindowAction.caseSaveMetadata.text=Unable to save current case path, " + + "the software will restart and the windows locations will reset but the current case will not be opened upon restart."}) + + @Override + public void performAction() { + SwingUtilities.invokeLater(() -> { + boolean response = MessageNotifyUtil.Message.confirm(Bundle.ResetWindowAction_confirm_text()); + if (response) { + //adding the shutdown hook, closing the current case, and marking for restart can be re-ordered if slightly different behavior is desired + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + try { + FileUtils.deleteDirectory(new File(PlatformUtil.getUserConfigDirectory() + File.separator + WINDOWS2LOCAL)); + } catch (IOException ex) { + //While we would like the user to be aware of this in the unlikely event that the directory can not be deleted + //Because our deletion is being attempted in a shutdown hook I don't know that we can pop up UI elements during the shutdown proces + logger.log(Level.SEVERE, "Unable to delete config directory, window locations will not be reset. To manually reset the windows please delete the following directory while the software is closed. " + PlatformUtil.getUserConfigDirectory() + File.separator + "Windows2Local", ex); + } + } + }); + try { + if (Case.isCaseOpen()) { + String caseMetadataFilePath = Case.getCurrentCase().getMetadata().getFilePath().toString(); + File caseToOpenFile = new File(ResetWindowsAction.getCaseToReopenFilePath()); + Charset encoding = null; //prevents writeStringToFile from having ambiguous arguments + FileUtils.writeStringToFile(caseToOpenFile, caseMetadataFilePath, encoding); + Case.closeCurrentCase(); + } + // The method markForRestart can not be undone once it is called. + LifecycleManager.getDefault().markForRestart(); + //we need to call exit last + LifecycleManager.getDefault().exit(); + } catch (CaseActionException ex) { + logger.log(Level.WARNING, Bundle.ResetWindowAction_caseCloseFailure_text(), ex); + MessageNotifyUtil.Message.show(Bundle.ResetWindowAction_caseCloseFailure_text(), MessageNotifyUtil.MessageType.ERROR); + } catch (IOException ex) { + logger.log(Level.WARNING, Bundle.ResetWindowAction_caseSaveMetadata_text(), ex); + MessageNotifyUtil.Message.show(Bundle.ResetWindowAction_caseSaveMetadata_text(), MessageNotifyUtil.MessageType.ERROR); + } + } + }); + } + + public static String getCaseToReopenFilePath(){ + return PlatformUtil.getUserConfigDirectory() + File.separator + CASE_TO_REOPEN_FILE; + } + + /** + * Set this action to be enabled/disabled + * + * @param value whether to enable this action or not + */ + @Override + + public void setEnabled(boolean value) { + super.setEnabled(value); + } + + @Override + public String getName() { + return DISPLAY_NAME; + } + + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + public boolean asynchronous() { + return false; // run on edt + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java index 53320d9313..310051b7f5 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java @@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.imagewriter.ImageWriterService; import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings; import org.sleuthkit.datamodel.AddDataSourceCallbacks; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.Host; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitJNI; import org.sleuthkit.datamodel.TskCoreException; @@ -316,7 +317,7 @@ class AddImageTask implements Runnable { boolean ignoreFatOrphanFiles; String md5; String sha1; - String sha256; + String sha256; ImageWriterSettings imageWriterSettings; ImageDetails(String deviceId, Image image, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, ImageWriterSettings imageWriterSettings) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java index 2b880093e9..58b1bb5a56 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java @@ -48,6 +48,7 @@ import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.ShortcutWizardDescriptorPanel; import org.sleuthkit.datamodel.Content; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Host; /** * The final panel of the add image wizard. It displays a progress bar and @@ -115,7 +116,7 @@ class AddImageWizardAddingProgressPanel extends ShortcutWizardDescriptorPanel { } }); } - + @Override public void setProgressMax(final int max) { // update the progress bar asynchronously @@ -302,7 +303,7 @@ class AddImageWizardAddingProgressPanel extends ShortcutWizardDescriptorPanel { private void startIngest() { if (!newContents.isEmpty() && readyToIngest && !ingested) { ingested = true; - if (dsProcessor != null && ! dsProcessor.supportsIngestStream()) { + if (dsProcessor != null && !dsProcessor.supportsIngestStream()) { IngestManager.getInstance().queueIngestJob(newContents, ingestJobSettings); } setStateFinished(); @@ -323,51 +324,56 @@ class AddImageWizardAddingProgressPanel extends ShortcutWizardDescriptorPanel { /** * Starts the Data source processing by kicking off the selected * DataSourceProcessor + * + * @param dsp The data source processor providing configuration for + * how to process the specific data source type. + * @param selectedHost The host to which this data source belongs or null + * for a default host. */ - void startDataSourceProcessing(DataSourceProcessor dsp) { + void startDataSourceProcessing(DataSourceProcessor dsp, Host selectedHost) { if (dsProcessor == null) { //this can only be run once final UUID dataSourceId = UUID.randomUUID(); newContents.clear(); cleanupTask = null; - readyToIngest = false; dsProcessor = dsp; - - // Add a cleanup task to interrupt the background process if the - // wizard exits while the background process is running. - cleanupTask = addImageAction.new CleanupTask() { - @Override - void cleanup() throws Exception { - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - cancelDataSourceProcessing(dataSourceId); - cancelled = true; - WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - } - }; - - cleanupTask.enable(); - new Thread(() -> { + // Add a cleanup task to interrupt the background process if the + // wizard exits while the background process is running. + cleanupTask = addImageAction.new CleanupTask() { + @Override + void cleanup() throws Exception { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + cancelDataSourceProcessing(dataSourceId); + cancelled = true; + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + }; + + cleanupTask.enable(); + try { Case.getCurrentCaseThrows().notifyAddingDataSource(dataSourceId); } catch (NoCurrentCaseException ex) { - Logger.getLogger(AddImageWizardAddingProgressVisual.class.getName()).log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS + Logger.getLogger(AddImageWizardAddingProgressVisual.class.getName()).log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS + } + + DataSourceProcessorCallback cbObj = new DataSourceProcessorCallback() { + @Override + public void doneEDT(DataSourceProcessorCallback.DataSourceProcessorResult result, List errList, List contents) { + dataSourceProcessorDone(dataSourceId, result, errList, contents); + } + }; + + // Kick off the DSProcessor + if (dsProcessor.supportsIngestStream()) { + // Set readyToIngest to false to prevent the wizard from starting ingest a second time. + readyToIngest = false; + dsProcessor.runWithIngestStream(selectedHost, ingestJobSettings, getDSPProgressMonitorImpl(), cbObj); + } else { + dsProcessor.run(selectedHost, getDSPProgressMonitorImpl(), cbObj); } }).start(); - DataSourceProcessorCallback cbObj = new DataSourceProcessorCallback() { - @Override - public void doneEDT(DataSourceProcessorCallback.DataSourceProcessorResult result, List errList, List contents) { - dataSourceProcessorDone(dataSourceId, result, errList, contents); - } - }; - setStateStarted(); - - // Kick off the DSProcessor - if (dsProcessor.supportsIngestStream()) { - dsProcessor.runWithIngestStream(ingestJobSettings, getDSPProgressMonitorImpl(), cbObj); - } else { - dsProcessor.run(getDSPProgressMonitorImpl(), cbObj); - } } } @@ -419,9 +425,14 @@ class AddImageWizardAddingProgressPanel extends ShortcutWizardDescriptorPanel { // TBD: there probably should be an error level for each error addErrors(err, critErr); } - - //notify the UI of the new content added to the case + final Level level = critErr ? Level.SEVERE : Level.WARNING; new Thread(() -> { + //log error messages as Severe if there was a critical error otherwise as Warning. + //logging performed off of UI thread + for (String err : errList) { + Logger.getLogger(AddImageWizardAddingProgressVisual.class.getName()).log(level, "DatasourceID: {0} Error Message: {1}", new Object[]{dataSourceId.toString(), err}); + } + //notify the UI of the new content added to the case try { if (!contents.isEmpty()) { Case.getCurrentCaseThrows().notifyDataSourceAdded(contents.get(0), dataSourceId); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java index d44b3ed522..320a2200f7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,6 +33,7 @@ import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.python.JythonModuleLoader; /** * visual component for the first panel of add image wizard. Allows the user to @@ -76,6 +77,14 @@ final class AddImageWizardDataSourceSettingsVisual extends JPanel { logger.log(Level.SEVERE, "discoverDataSourceProcessors(): A DataSourceProcessor already exists for type = {0}", dsProcessor.getDataSourceType()); //NON-NLS } } + + for (DataSourceProcessor dsProcessor : JythonModuleLoader.getDataSourceProcessorModules()) { + if (!datasourceProcessorsMap.containsKey(dsProcessor.getDataSourceType())) { + datasourceProcessorsMap.put(dsProcessor.getDataSourceType(), dsProcessor); + } else { + logger.log(Level.SEVERE, "discoverDataSourceProcessors(): A DataSourceProcessor already exists for type = {0}", dsProcessor.getDataSourceType()); //NON-NLS + } + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java index cfcbdd015c..7097b592c3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java @@ -28,6 +28,7 @@ import org.openide.WizardDescriptor; import org.openide.util.HelpCtx; import org.openide.util.NbBundle.Messages; import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.ingest.profile.IngestProfilePaths; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.ingest.IngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestJobSettingsPanel; @@ -43,7 +44,7 @@ import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.ShortcutWizardDescript @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddImageWizardIngestConfigPanel extends ShortcutWizardDescriptorPanel { - @Messages("AddImageWizardIngestConfigPanel.name.text=Configure Ingest Modules") + @Messages("AddImageWizardIngestConfigPanel.name.text=Configure Ingest") private final IngestJobSettingsPanel ingestJobSettingsPanel; /** * The visual component that displays this panel. If you need to access the @@ -189,7 +190,8 @@ class AddImageWizardIngestConfigPanel extends ShortcutWizardDescriptorPanel { } //Because this panel kicks off ingest during the wizard we need to //swap out the ingestJobSettings for the ones of the chosen profile before - IngestJobSettings ingestJobSettings = new IngestJobSettings(lastProfileUsed); + //use prefix to specify correct execution context for profiles. + IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestProfilePaths.getInstance().getIngestProfilePrefix() + lastProfileUsed); progressPanel.setIngestJobSettings(ingestJobSettings); //prepare ingest for being started } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIterator.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIterator.java index 045bf775b2..9ed66765d3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIterator.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIterator.java @@ -29,6 +29,7 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.ingest.IngestProfiles; import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.IngestProfileSelectionWizardPanel; import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.ShortcutWizardDescriptorPanel; +import org.sleuthkit.datamodel.Host; /** * The iterator class for the "Add Image" wizard panel. This class is used to @@ -41,8 +42,10 @@ class AddImageWizardIterator implements WizardDescriptor.Iterator getPanels() { if (panels == null) { panels = new ArrayList<>(); + hostPanel = new AddImageWizardSelectHostPanel(); + panels.add(hostPanel); + hostPanelIndex = panels.indexOf(hostPanel); AddImageWizardSelectDspPanel dspSelection = new AddImageWizardSelectDspPanel(); panels.add(dspSelection); AddImageWizardAddingProgressPanel progressPanel = new AddImageWizardAddingProgressPanel(action); - AddImageWizardDataSourceSettingsPanel dsPanel = new AddImageWizardDataSourceSettingsPanel(); AddImageWizardIngestConfigPanel ingestConfigPanel = new AddImageWizardIngestConfigPanel(progressPanel); panels.add(dsPanel); @@ -164,7 +169,7 @@ class AddImageWizardIterator implements WizardDescriptor.Iterator 0); //Users should be able to back up to select a different DSP } /** @@ -180,8 +185,10 @@ class AddImageWizardIterator implements WizardDescriptor.Iterator sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,6 +42,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datasourceprocessors.RawDSProcessor; import org.sleuthkit.autopsy.logicalimager.dsp.LogicalImagerDSProcessor; +import org.sleuthkit.autopsy.python.JythonModuleLoader; /** * Panel which displays the available DataSourceProcessors and allows selection @@ -196,6 +197,15 @@ final class AddImageWizardSelectDspVisual extends JPanel { logger.log(Level.SEVERE, "discoverDataSourceProcessors(): A DataSourceProcessor already exists for type = {0}", dsProcessor.getDataSourceType()); //NON-NLS } } + + for (DataSourceProcessor dsProcessor : JythonModuleLoader.getDataSourceProcessorModules()) { + if (!datasourceProcessorsMap.containsKey(dsProcessor.getDataSourceType())) { + datasourceProcessorsMap.put(dsProcessor.getDataSourceType(), dsProcessor); + } else { + logger.log(Level.SEVERE, "discoverDataSourceProcessors(): A DataSourceProcessor already exists for type = {0}", dsProcessor.getDataSourceType()); //NON-NLS + } + } + dspList.add(ImageDSProcessor.getType()); dspList.add(LocalDiskDSProcessor.getType()); dspList.add(LocalFilesDSProcessor.getType()); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectHostPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectHostPanel.java new file mode 100644 index 0000000000..738ea66494 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectHostPanel.java @@ -0,0 +1,96 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import java.awt.Component; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import javax.swing.event.ChangeListener; +import org.openide.WizardDescriptor; +import org.openide.util.ChangeSupport; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.datamodel.Host; +import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.ShortcutWizardDescriptorPanel; + +/** + * Create a wizard panel which contains a panel allowing the selection of a host + * for a data source. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +@Messages("AddImageWizardSelectHostPanel_title=Select Host To Add The Data Source To") +final class AddImageWizardSelectHostPanel extends ShortcutWizardDescriptorPanel implements PropertyChangeListener { + + private final AddImageWizardSelectHostVisual component = new AddImageWizardSelectHostVisual(); + private final ChangeSupport changeSupport = new ChangeSupport(this); + + AddImageWizardSelectHostPanel() { + component.addListener(this); + } + + @Override + public Component getComponent() { + return component; + } + + @Override + public HelpCtx getHelp() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + public void readSettings(WizardDescriptor data) { + } + + /** + * Returns or generates the selected host. If user specifies 'generate + * new...', then null will be returned. + * + * @return The selected host or null if to be auto generated. + */ + Host getSelectedHost() { + return component.getSelectedHost(); + } + + @Override + public void storeSettings(WizardDescriptor data) { + } + + @Override + public boolean isValid() { + return component.hasValidData(); + } + + @Override + public void addChangeListener(ChangeListener cl) { + changeSupport.addChangeListener(cl); + } + + @Override + public void removeChangeListener(ChangeListener cl) { + changeSupport.removeChangeListener(cl); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + changeSupport.fireChange(); + } + + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectHostVisual.form b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectHostVisual.form new file mode 100644 index 0000000000..bdb977932a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectHostVisual.form @@ -0,0 +1,163 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectHostVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectHostVisual.java new file mode 100644 index 0000000000..07eaf84104 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectHostVisual.java @@ -0,0 +1,374 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.util.Collection; +import java.util.Objects; +import java.util.Set; +import java.util.Vector; +import java.util.logging.Level; +import java.util.stream.Collectors; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import org.apache.commons.lang.StringUtils; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.hosts.HostNameValidator; +import org.sleuthkit.datamodel.Host; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Panel to be displayed as a part of the add datasource wizard. Provides the + * ability to select current host. + */ +@Messages({ + "AddImageWizardSelectHostVisual_title=Select Host" +}) +class AddImageWizardSelectHostVisual extends javax.swing.JPanel { + + /** + * A combo box item for a host (or null for default). + */ + private static class HostListItem { + + private final Host host; + + /** + * Main constructor. + * + * @param host The host. + */ + HostListItem(Host host) { + this.host = host; + } + + /** + * @return The host. + */ + Host getHost() { + return host; + } + + @Override + public String toString() { + if (host == null || host.getName() == null) { + return ""; + } else { + return host.getName(); + } + } + + @Override + public int hashCode() { + int hash = 7; + hash = 41 * hash + Objects.hashCode(this.host == null ? 0 : this.host.getHostId()); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final HostListItem other = (HostListItem) obj; + if (!Objects.equals( + this.host == null ? 0 : this.host.getHostId(), + other.host == null ? 0 : other.host.getHostId())) { + + return false; + } + return true; + } + + } + + private static final Logger logger = Logger.getLogger(AddImageWizardSelectHostVisual.class.getName()); + + private final PropertyChangeSupport changeSupport = new PropertyChangeSupport(this); + private Set sanitizedHostSet = null; + + /** + * Creates new form SelectHostPanel + */ + AddImageWizardSelectHostVisual() { + initComponents(); + + specifyNewHostTextField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void changedUpdate(DocumentEvent e) { + refresh(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + refresh(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + refresh(); + } + }); + + existingHostList.addListSelectionListener((evt) -> refresh()); + + loadHostData(); + refresh(); + } + + /** + * Add listener for validation change events. + * + * @param pcl The property change listener. + */ + void addListener(PropertyChangeListener pcl) { + changeSupport.addPropertyChangeListener(pcl); + } + + /** + * Remove listener from validation change events. + * + * @param pcl The property change listener. + */ + void removeListener(PropertyChangeListener pcl) { + changeSupport.removePropertyChangeListener(pcl); + } + + /** + * @return The currently selected host or null if no selection. This will + * generate a new host if 'Specify New Host Name' + */ + Host getSelectedHost() { + if (specifyNewHostRadio.isSelected() && StringUtils.isNotEmpty(specifyNewHostTextField.getText())) { + String newHostName = specifyNewHostTextField.getText(); + try { + return Case.getCurrentCaseThrows().getSleuthkitCase().getHostManager().newHost(newHostName); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.WARNING, String.format("Unable to create host '%s'.", newHostName), ex); + return null; + } + } else if (useExistingHostRadio.isSelected() + && existingHostList.getSelectedValue() != null + && existingHostList.getSelectedValue().getHost() != null) { + + return existingHostList.getSelectedValue().getHost(); + } else { + return null; + } + } + + /** + * Loads hosts from database and displays in combo box. + */ + private void loadHostData() { + try { + Collection hosts = Case.getCurrentCaseThrows().getSleuthkitCase().getHostManager().getAllHosts(); + sanitizedHostSet = HostNameValidator.getSanitizedHostNames(hosts); + + Vector hostListItems = hosts.stream() + .filter(h -> h != null) + .sorted((a, b) -> getNameOrEmpty(a).compareToIgnoreCase(getNameOrEmpty(b))) + .map((h) -> new HostListItem(h)) + .collect(Collectors.toCollection(Vector::new)); + + existingHostList.setListData(hostListItems); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.WARNING, "Unable to display host items with no current case.", ex); + } + } + + /** + * Returns the name of the host or an empty string if the host or host name + * is null. + * + * @param host The host. + * @return The host name or empty string. + */ + private String getNameOrEmpty(Host host) { + return host == null || host.getName() == null ? "" : host.getName(); + } + + private void refresh() { + specifyNewHostTextField.setEnabled(specifyNewHostRadio.isSelected()); + existingHostList.setEnabled(useExistingHostRadio.isSelected()); + + String prevValidationMessage = validationMessage.getText(); + String newValidationMessage = getValidationMessage(); + validationMessage.setText(newValidationMessage); + // if validation message changed (empty to non-empty or vice-versa) fire validation update + if (StringUtils.isBlank(prevValidationMessage) != StringUtils.isBlank(newValidationMessage)) { + changeSupport.firePropertyChange("validation", prevValidationMessage, newValidationMessage); + } + } + + @Messages({ + "AddImageWizardSelectHostVisual_getValidationMessage_noHostSelected=Please select an existing host.",}) + private String getValidationMessage() { + if (specifyNewHostRadio.isSelected()) { + // if problematic new name for host + return HostNameValidator.getValidationMessage(specifyNewHostTextField.getText(), null, sanitizedHostSet); + + // or use existing host and no host is selected + } else if (useExistingHostRadio.isSelected() + && (existingHostList.getSelectedValue() == null + || existingHostList.getSelectedValue().getHost() == null)) { + return Bundle.AddImageWizardSelectHostVisual_getValidationMessage_noHostSelected(); + } + + return null; + } + + @Override + public String getName() { + return Bundle.AddImageWizardSelectHostVisual_title(); + } + + boolean hasValidData() { + return StringUtils.isBlank(validationMessage.getText()); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + javax.swing.ButtonGroup radioButtonGroup = new javax.swing.ButtonGroup(); + generateNewRadio = new javax.swing.JRadioButton(); + specifyNewHostRadio = new javax.swing.JRadioButton(); + specifyNewHostTextField = new javax.swing.JTextField(); + useExistingHostRadio = new javax.swing.JRadioButton(); + javax.swing.JScrollPane jScrollPane1 = new javax.swing.JScrollPane(); + existingHostList = new javax.swing.JList<>(); + hostDescription = new javax.swing.JLabel(); + validationMessage = new javax.swing.JLabel(); + + radioButtonGroup.add(generateNewRadio); + generateNewRadio.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(generateNewRadio, org.openide.util.NbBundle.getMessage(AddImageWizardSelectHostVisual.class, "AddImageWizardSelectHostVisual.generateNewRadio.text")); // NOI18N + generateNewRadio.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + generateNewRadioActionPerformed(evt); + } + }); + + radioButtonGroup.add(specifyNewHostRadio); + org.openide.awt.Mnemonics.setLocalizedText(specifyNewHostRadio, org.openide.util.NbBundle.getMessage(AddImageWizardSelectHostVisual.class, "AddImageWizardSelectHostVisual.specifyNewHostRadio.text")); // NOI18N + specifyNewHostRadio.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + specifyNewHostRadioActionPerformed(evt); + } + }); + + specifyNewHostTextField.setText(org.openide.util.NbBundle.getMessage(AddImageWizardSelectHostVisual.class, "AddImageWizardSelectHostVisual.specifyNewHostTextField.text")); // NOI18N + + radioButtonGroup.add(useExistingHostRadio); + org.openide.awt.Mnemonics.setLocalizedText(useExistingHostRadio, org.openide.util.NbBundle.getMessage(AddImageWizardSelectHostVisual.class, "AddImageWizardSelectHostVisual.useExistingHostRadio.text")); // NOI18N + useExistingHostRadio.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + useExistingHostRadioActionPerformed(evt); + } + }); + + existingHostList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + jScrollPane1.setViewportView(existingHostList); + + org.openide.awt.Mnemonics.setLocalizedText(hostDescription, org.openide.util.NbBundle.getMessage(AddImageWizardSelectHostVisual.class, "AddImageWizardSelectHostVisual.hostDescription.text")); // NOI18N + + validationMessage.setForeground(java.awt.Color.RED); + org.openide.awt.Mnemonics.setLocalizedText(validationMessage, org.openide.util.NbBundle.getMessage(AddImageWizardSelectHostVisual.class, "AddImageWizardSelectHostVisual.validationMessage.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(validationMessage, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(generateNewRadio) + .addComponent(useExistingHostRadio) + .addComponent(hostDescription) + .addGroup(layout.createSequentialGroup() + .addGap(21, 21, 21) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 270, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(layout.createSequentialGroup() + .addComponent(specifyNewHostRadio) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(specifyNewHostTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 270, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGap(0, 13, Short.MAX_VALUE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(hostDescription) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(generateNewRadio) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(specifyNewHostRadio) + .addComponent(specifyNewHostTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(useExistingHostRadio) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(10, 10, 10) + .addComponent(validationMessage) + .addContainerGap(18, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void generateNewRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_generateNewRadioActionPerformed + refresh(); + }//GEN-LAST:event_generateNewRadioActionPerformed + + private void specifyNewHostRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_specifyNewHostRadioActionPerformed + refresh(); + }//GEN-LAST:event_specifyNewHostRadioActionPerformed + + private void useExistingHostRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useExistingHostRadioActionPerformed + refresh(); + }//GEN-LAST:event_useExistingHostRadioActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JList existingHostList; + private javax.swing.JRadioButton generateNewRadio; + private javax.swing.JLabel hostDescription; + private javax.swing.JRadioButton specifyNewHostRadio; + private javax.swing.JTextField specifyNewHostTextField; + private javax.swing.JRadioButton useExistingHostRadio; + private javax.swing.JLabel validationMessage; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddLocalFilesTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddLocalFilesTask.java index 185f696a97..6e4a71440f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddLocalFilesTask.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddLocalFilesTask.java @@ -28,6 +28,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgress import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.Host; import org.sleuthkit.datamodel.LocalFilesDataSource; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskDataException; @@ -42,6 +43,7 @@ class AddLocalFilesTask implements Runnable { private static final Logger LOGGER = Logger.getLogger(AddLocalFilesTask.class.getName()); private final String deviceId; private final String rootVirtualDirectoryName; + private final Host host; private final List localFilePaths; private final DataSourceProcessorProgressMonitor progress; private final DataSourceProcessorCallback callback; @@ -64,14 +66,16 @@ class AddLocalFilesTask implements Runnable { * form: LogicalFileSet[N] * @param localFilePaths A list of localFilePaths of local/logical * files and/or directories. + * @param host The host for this data source (may be null). * @param progressMonitor Progress monitor to report progress * during processing. * @param callback Callback to call when processing is done. */ - AddLocalFilesTask(String deviceId, String rootVirtualDirectoryName, List localFilePaths, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + AddLocalFilesTask(String deviceId, String rootVirtualDirectoryName, List localFilePaths, Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { this.deviceId = deviceId; this.rootVirtualDirectoryName = rootVirtualDirectoryName; this.localFilePaths = localFilePaths; + this.host = host; this.callback = callback; this.progress = progressMonitor; } @@ -88,7 +92,7 @@ class AddLocalFilesTask implements Runnable { try { progress.setIndeterminate(true); FileManager fileManager = Case.getCurrentCaseThrows().getServices().getFileManager(); - LocalFilesDataSource newDataSource = fileManager.addLocalFilesDataSource(deviceId, rootVirtualDirectoryName, "", localFilePaths, new ProgressUpdater()); + LocalFilesDataSource newDataSource = fileManager.addLocalFilesDataSource(deviceId, rootVirtualDirectoryName, "", host, localFilePaths, new ProgressUpdater()); newDataSources.add(newDataSource); } catch (TskDataException | TskCoreException | NoCurrentCaseException ex) { errors.add(ex.getMessage()); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 987323a52b..1a6186b2c2 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -4,7 +4,6 @@ CTL_CaseCloseAct=Close Case CTL_CaseNewAction=New Case CTL_CaseDetailsAction=Case Details CTL_CaseDeleteAction=Delete Case -Menu/Case/OpenRecentCase=Open Recent Case CTL_CaseDeleteAction=Delete Case OpenIDE-Module-Name=Case NewCaseVisualPanel1.caseNameLabel.text_1=Case Name: @@ -59,7 +58,7 @@ AddImageWizardChooseDataSourcePanel.moveFocusNext=Next > AddImageWizardChooseDataSourceVisual.getName.text=Select Data Source AddImageWizardIngestConfigPanel.dsProcDone.noErrs.text=*Data Source added. AddImageWizardIngestConfigPanel.dsProcDone.errs.text=*Errors encountered in adding Data Source. -AddImageWizardIngestConfigVisual.getName.text=Configure Ingest Modules +AddImageWizardIngestConfigVisual.getName.text=Configure Ingest AddImageWizardIterator.stepXofN=Step {0} of {1} AddLocalFilesTask.localFileAdd.progress.text=Adding: {0}/{1} Case.getCurCase.exception.noneOpen=Cannot get the current case; there is no case open\! @@ -146,13 +145,14 @@ AddImageWizardIngestConfigPanel.CANCEL_BUTTON.text=Cancel NewCaseVisualPanel1.CaseFolderOnCDriveError.text=Warning: Path to multi-user case folder is on \"C:\" drive NewCaseVisualPanel1.CaseFolderOnInternalDriveWindowsError.text=Warning: Path to case folder is on \"C:\" drive. Case folder is created on the target system NewCaseVisualPanel1.CaseFolderOnInternalDriveLinuxError.text=Warning: Path to case folder is on the target system. Create case folder in mounted drive. +NewCaseVisualPanel1.uncPath.error=Error: UNC paths are not allowed for Single-User cases CollaborationMonitor.addingDataSourceStatus.msg={0} adding data source CollaborationMonitor.analyzingDataSourceStatus.msg={0} analyzing {1} MissingImageDialog.lbWarning.text= MissingImageDialog.lbWarning.toolTipText= NewCaseVisualPanel1.caseParentDirWarningLabel.text= -NewCaseVisualPanel1.multiUserCaseRadioButton.text=Multi-user -NewCaseVisualPanel1.singleUserCaseRadioButton.text=Single-user +NewCaseVisualPanel1.multiUserCaseRadioButton.text=Multi-User +NewCaseVisualPanel1.singleUserCaseRadioButton.text=Single-User NewCaseVisualPanel1.caseTypeLabel.text=Case Type: SingleUserCaseConverter.BadDatabaseFileName=Database file does not exist! SingleUserCaseConverter.AlreadyMultiUser=Case is already multi-user! @@ -257,3 +257,9 @@ SolrNotConfiguredDialog.okButton.text=OK SolrNotConfiguredDialog.title=Solr 8 Server Not Configured SolrNotConfiguredDialog.EmptyKeywordSearchHostName=Solr 8 connection parameters are not configured. Please go to Tools->Options->Multi User. SolrNotConfiguredDialog.messageLabel.text=Multi-User cases are enabled but Solr 8 server has not been configured.
\nNew cases can only be created with Solr 8. Please go to Tools->Options->Multi User.\n +AddImageWizardSelectHostVisual.hostDescription.text=Hosts are used to organize data sources and other data. +AddImageWizardSelectHostVisual.useExistingHostRadio.text=Use existing host +AddImageWizardSelectHostVisual.specifyNewHostTextField.text= +AddImageWizardSelectHostVisual.specifyNewHostRadio.text=Specify new host name +AddImageWizardSelectHostVisual.generateNewRadio.text=Generate new host name based on data source name +AddImageWizardSelectHostVisual.validationMessage.text=\ diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED index e59c37fea7..528d3a5088 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED @@ -1,5 +1,8 @@ -AddImageWizardIngestConfigPanel.name.text=Configure Ingest Modules +AddImageWizardIngestConfigPanel.name.text=Configure Ingest AddImageWizardSelectDspVisual.multiUserWarning.text=This type of Data Source Processor is not available in multi-user mode +AddImageWizardSelectHostPanel_title=Select Host To Add The Data Source To +AddImageWizardSelectHostVisual_getValidationMessage_noHostSelected=Please select an existing host. +AddImageWizardSelectHostVisual_title=Select Host # {0} - exception message Case.closeException.couldNotCloseCase=Error closing case: {0} Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources @@ -52,8 +55,8 @@ Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}. Case.exceptionMessage.metadataUpdateError=Failed to update case metadata # {0} - exception message Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}. -Case.lockingException.couldNotAcquireExclusiveLock=Failed to get a exclusive lock on the case. -Case.lockingException.couldNotAcquireSharedLock=Failed to get an shared lock on the case. +Case.lockingException.couldNotAcquireExclusiveLock=Failed to get an exclusive lock on the case. +Case.lockingException.couldNotAcquireSharedLock=Failed to get a shared lock on the case. Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User. # {0} - image Case.openFileSystems.openingImage=Opening all filesystems for image: {0}... @@ -125,6 +128,7 @@ CTL_CaseCloseAct=Close Case CTL_CaseNewAction=New Case CTL_CaseDetailsAction=Case Details CTL_CaseDeleteAction=Delete Case +CTL_CaseDeleteAction=Delete Case CTL_CaseOpenAction=Open Case CTL_UnpackagePortableCaseAction=Unpack and Open Portable Case DeleteDataSourceAction.confirmationDialog.message=Are you sure you want to remove the selected data source from the case?\nNote that the case will be closed and re-opened during the removal. @@ -183,8 +187,6 @@ LogicalEvidenceFilePanel.pathValidation.getOpenCase.Error=Warning: Exception whi LogicalEvidenceFilePanel.validatePanel.nonL01Error.text=Only files with the .l01 file extension are supported here. LogicalFilesDspPanel.subTypeComboBox.l01FileOption.text=Logical evidence file (L01) LogicalFilesDspPanel.subTypeComboBox.localFilesOption.text=Local files and folders -Menu/Case/OpenRecentCase=Open Recent Case -CTL_CaseDeleteAction=Delete Case OpenIDE-Module-Name=Case NewCaseVisualPanel1.caseNameLabel.text_1=Case Name: NewCaseVisualPanel1.caseDirLabel.text=Base Directory: @@ -242,7 +244,7 @@ AddImageWizardChooseDataSourcePanel.moveFocusNext=Next > AddImageWizardChooseDataSourceVisual.getName.text=Select Data Source AddImageWizardIngestConfigPanel.dsProcDone.noErrs.text=*Data Source added. AddImageWizardIngestConfigPanel.dsProcDone.errs.text=*Errors encountered in adding Data Source. -AddImageWizardIngestConfigVisual.getName.text=Configure Ingest Modules +AddImageWizardIngestConfigVisual.getName.text=Configure Ingest AddImageWizardIterator.stepXofN=Step {0} of {1} AddLocalFilesTask.localFileAdd.progress.text=Adding: {0}/{1} Case.getCurCase.exception.noneOpen=Cannot get the current case; there is no case open\! @@ -341,8 +343,14 @@ RecentCases.exception.caseIdxOutOfRange.msg=Recent case index {0} is out of rang RecentCases.getName.text=Clear Recent Cases # {0} - case name RecentItems.openRecentCase.msgDlg.text=Case {0} no longer exists. -SelectDataSourceProcessorPanel.name.text=Select Type of Data Source To Add +SelectDataSourceProcessorPanel.name.text=Select Data Source Type StartupWindow.title.text=Welcome +# {0} - autFilePath +StartupWindowProvider.openCase.cantOpen=Unable to open previously open case with metadata file: {0} +# {0} - reOpenFilePath +StartupWindowProvider.openCase.deleteOpenFailure=Unable to open or delete file containing path {0} to previously open case. The previous case will not be opened. +# {0} - autFilePath +StartupWindowProvider.openCase.noFile=Unable to open previously open case because metadata file not found at: {0} UnpackagePortableCaseDialog.title.text=Unpackage Portable Case UnpackagePortableCaseDialog.UnpackagePortableCaseDialog.extensions=Portable case package (.zip, .zip.001) UnpackagePortableCaseDialog.validatePaths.badExtension=File extension must be .zip or .zip.001 @@ -364,13 +372,14 @@ AddImageWizardIngestConfigPanel.CANCEL_BUTTON.text=Cancel NewCaseVisualPanel1.CaseFolderOnCDriveError.text=Warning: Path to multi-user case folder is on \"C:\" drive NewCaseVisualPanel1.CaseFolderOnInternalDriveWindowsError.text=Warning: Path to case folder is on \"C:\" drive. Case folder is created on the target system NewCaseVisualPanel1.CaseFolderOnInternalDriveLinuxError.text=Warning: Path to case folder is on the target system. Create case folder in mounted drive. +NewCaseVisualPanel1.uncPath.error=Error: UNC paths are not allowed for Single-User cases CollaborationMonitor.addingDataSourceStatus.msg={0} adding data source CollaborationMonitor.analyzingDataSourceStatus.msg={0} analyzing {1} MissingImageDialog.lbWarning.text= MissingImageDialog.lbWarning.toolTipText= NewCaseVisualPanel1.caseParentDirWarningLabel.text= -NewCaseVisualPanel1.multiUserCaseRadioButton.text=Multi-user -NewCaseVisualPanel1.singleUserCaseRadioButton.text=Single-user +NewCaseVisualPanel1.multiUserCaseRadioButton.text=Multi-User +NewCaseVisualPanel1.singleUserCaseRadioButton.text=Single-User NewCaseVisualPanel1.caseTypeLabel.text=Case Type: SingleUserCaseConverter.BadDatabaseFileName=Database file does not exist! SingleUserCaseConverter.AlreadyMultiUser=Case is already multi-user! @@ -475,3 +484,9 @@ SolrNotConfiguredDialog.okButton.text=OK SolrNotConfiguredDialog.title=Solr 8 Server Not Configured SolrNotConfiguredDialog.EmptyKeywordSearchHostName=Solr 8 connection parameters are not configured. Please go to Tools->Options->Multi User. SolrNotConfiguredDialog.messageLabel.text=Multi-User cases are enabled but Solr 8 server has not been configured.
\nNew cases can only be created with Solr 8. Please go to Tools->Options->Multi User.\n +AddImageWizardSelectHostVisual.hostDescription.text=Hosts are used to organize data sources and other data. +AddImageWizardSelectHostVisual.useExistingHostRadio.text=Use existing host +AddImageWizardSelectHostVisual.specifyNewHostTextField.text= +AddImageWizardSelectHostVisual.specifyNewHostRadio.text=Specify new host name +AddImageWizardSelectHostVisual.generateNewRadio.text=Generate new host name based on data source name +AddImageWizardSelectHostVisual.validationMessage.text=\ diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties index 5f29c9ed07..fe2abb9561 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties @@ -1,4 +1,4 @@ -#Tue Aug 18 18:09:20 UTC 2020 +#Thu Sep 30 10:26:59 UTC 2021 AddImageAction.ingestConfig.ongoingIngest.msg=\u5225\u306e\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3067\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u9032\u884c\u4e2d\u3067\u3059\u3002\u65b0\u898f\u30bd\u30fc\u30b9\u3092\u4eca\u8ffd\u52a0\u3059\u308b\u3068\u3001\u73fe\u5728\u306e\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u306e\u51e6\u7406\u304c\u9045\u304f\u306a\u308b\u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002
\u7d9a\u884c\u3057\u3066\u65b0\u898f\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u4eca\u3059\u3050\u8ffd\u52a0\u3057\u307e\u3059\u304b? AddImageAction.ingestConfig.ongoingIngest.title=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u9032\u884c\u4e2d\u3067\u3059 AddImageAction.wizard.title=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u8ffd\u52a0 @@ -25,10 +25,17 @@ AddImageWizardChooseDataSourceVisual.getName.text=\u30c7\u30fc\u30bf\u30bd\u30fc AddImageWizardIngestConfigPanel.CANCEL_BUTTON.text=\u53d6\u308a\u6d88\u3057 AddImageWizardIngestConfigPanel.dsProcDone.errs.text=*\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306e\u8ffd\u52a0\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 AddImageWizardIngestConfigPanel.dsProcDone.noErrs.text=*\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u8ffd\u52a0\u3057\u307e\u3057\u305f\u3002 -AddImageWizardIngestConfigPanel.name.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\u3092\u69cb\u6210 -AddImageWizardIngestConfigVisual.getName.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\u3092\u69cb\u6210 +AddImageWizardIngestConfigPanel.name.text=\u53d6\u8fbc\u307f\u3092\u8a2d\u5b9a +AddImageWizardIngestConfigVisual.getName.text=\u53d6\u8fbc\u307f\u3092\u8a2d\u5b9a AddImageWizardIterator.stepXofN=\u624b\u9806 {0} / {1} AddImageWizardSelectDspVisual.multiUserWarning.text=\u3053\u306e\u30bf\u30a4\u30d7\u306e\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u30d7\u30ed\u30bb\u30c3\u30b5\u30fc\u306f\u30de\u30eb\u30c1\u30e6\u30fc\u30b6\u30fc\u30e2\u30fc\u30c9\u3067\u5229\u7528\u3067\u304d\u307e\u305b\u3093\u3002 +AddImageWizardSelectHostPanel_title=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u8ffd\u52a0\u3059\u308b\u30db\u30b9\u30c8\u3092\u9078\u629e\u3057\u3066\u4e0b\u3055\u3044\u3002 +AddImageWizardSelectHostVisual.generateNewRadio.text=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u540d\u306b\u57fa\u3065\u3044\u3066\u65b0\u3057\u3044\u30db\u30b9\u30c8\u540d\u3092\u751f\u6210 +AddImageWizardSelectHostVisual.hostDescription.text=\u30db\u30b9\u30c8\u306f\u3001\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3084\u305d\u306e\u4ed6\u306e\u30c7\u30fc\u30bf\u3092\u6574\u7406\u3059\u308b\u305f\u3081\u306b\u4f7f\u7528\u3055\u308c\u307e\u3059\u3002 +AddImageWizardSelectHostVisual.specifyNewHostRadio.text=\u65b0\u3057\u3044\u30db\u30b9\u30c8\u540d\u3092\u6307\u5b9a\u3057\u3066\u4e0b\u3055\u3044 +AddImageWizardSelectHostVisual.useExistingHostRadio.text=\u65e2\u5b58\u306e\u30db\u30b9\u30c8\u3092\u4f7f\u7528\u3059\u308b +AddImageWizardSelectHostVisual_getValidationMessage_noHostSelected=\u65e2\u5b58\u306e\u30db\u30b9\u30c8\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +AddImageWizardSelectHostVisual_title=\u30db\u30b9\u30c8\u3092\u9078\u629e\u3057\u3066\u4e0b\u3055\u3044 AddLocalFilesTask.localFileAdd.progress.text=\u6b21\u3092\u8ffd\u52a0\u4e2d\u3067\u3059\: {0}/{1} CTL_AddImage=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u8ffd\u52a0 CTL_AddImageButton=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u8ffd\u52a0 @@ -60,6 +67,7 @@ Case.deleteCaseFailureMessageBox.title=\u30b1\u30fc\u30b9\u3092\u524a\u9664\u306 Case.deleteReports.deleteFromDiskException.log.msg=\u30c7\u30a3\u30b9\u30af\u304b\u3089\u30ec\u30dd\u30fc\u30c8\u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3002 Case.deleteReports.deleteFromDiskException.msg=\u30c7\u30a3\u30b9\u30af\u304b\u3089\u30ec\u30dd\u30fc\u30c8 {0} \u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3002\n{1} \u304b\u3089\u624b\u52d5\u3067\u524a\u9664\u3067\u304d\u307e\u3059 Case.exception.errGetRootObj=\u30eb\u30fc\u30c8\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u306e\u53d6\u5f97\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 +Case.exceptionMessage.cancelled=\u30ad\u30e3\u30f3\u30bb\u30eb\u3002 Case.exceptionMessage.cancelledByUser=\u30e6\u30fc\u30b6\u30fc\u306b\u3088\u3063\u3066\u53d6\u308a\u6d88\u3055\u308c\u307e\u3057\u305f\u3002 Case.exceptionMessage.cannotDeleteCurrentCase=\u73fe\u5728\u306e\u30b1\u30fc\u30b9\u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3002\u6700\u521d\u306b\u9589\u3058\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002 Case.exceptionMessage.cannotGetLockToDeleteCase=\u5225\u306e\u30e6\u30fc\u30b6\u30fc\u307e\u305f\u306f\u30db\u30b9\u30c8\u304c\u958b\u3044\u3066\u3044\u308b\u305f\u3081\u73fe\u5728\u306e\u30b1\u30fc\u30b9\u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3002 @@ -74,9 +82,12 @@ Case.exceptionMessage.couldNotOpenRemoteEventChannel=\u30ea\u30e2\u30fc\u30c8\u3 Case.exceptionMessage.couldNotSaveCaseMetadata=\u30b1\u30fc\u30b9\u30e1\u30bf\u30c7\u30fc\u30bf\u3092\u4fdd\u5b58\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\:\n{0}\u3002 Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u540d\u3092\u30b1\u30fc\u30b9\u30e1\u30bf\u30c7\u30fc\u30bf\u30d5\u30a1\u30a4\u30eb\u306b\u4fdd\u5b58\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\:\n{0}\u3002 Case.exceptionMessage.couldNotUpdateCaseNodeData=\u5ea7\u6a19\u30b5\u30fc\u30d3\u30b9\u30ce\u30fc\u30c9\u30c7\u30fc\u30bf\u3092\u66f4\u65b0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\:\n{0}\u3002 +Case.exceptionMessage.dataSourceNotFound=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002 Case.exceptionMessage.deletionInterrupted=\u30b1\u30fc\u30b9 {0} \u306e\u524a\u9664\u304c\u53d6\u308a\u6d88\u3055\u308c\u307e\u3057\u305f\u3002 Case.exceptionMessage.emptyCaseDir=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30fc\u30d1\u30b9\u3092\u6307\u5b9a\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002 Case.exceptionMessage.emptyCaseName=\u30b1\u30fc\u30b9\u540d\u3092\u6307\u5b9a\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002 +Case.exceptionMessage.errorDeletingDataSourceFromCaseDb=\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u304b\u3089\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u524a\u9664\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 +Case.exceptionMessage.errorDeletingDataSourceFromTextIndex=\u30c6\u30ad\u30b9\u30c8\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u304b\u3089\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u524a\u9664\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 Case.exceptionMessage.errorsDeletingCase=\u30b1\u30fc\u30b9\u306e\u524a\u9664\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u8a73\u7d30\u306f\u3001\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30ed\u30b0\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002 Case.exceptionMessage.execExceptionWrapperMessage={0} Case.exceptionMessage.failedToConnectToCoordSvc=\u5ea7\u6a19\u30b5\u30fc\u30d3\u30b9\u306b\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\:\n{0}. @@ -86,16 +97,19 @@ Case.exceptionMessage.failedToReadMetadata=\u30b1\u30fc\u30b9\u30e1\u30bf\u30c7\ Case.exceptionMessage.metadataUpdateError=\u30b1\u30fc\u30b9\u30e1\u30bf\u30c7\u30fc\u30bf\u3092\u66f4\u65b0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f Case.exceptionMessage.unsupportedSchemaVersionMessage=\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u306a\u3044\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30b9\u30ad\u30fc\u30de\u30d0\u30fc\u30b8\u30e7\u30f3\u3067\u3059\:\n{0}\u3002 Case.getCurCase.exception.noneOpen=\u73fe\u5728\u306e\u30b1\u30fc\u30b9\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\u3002\u30b1\u30fc\u30b9\u304c\u958b\u304b\u308c\u3066\u3044\u307e\u305b\u3093\! +Case.lockingException.couldNotAcquireExclusiveLock=\u30b1\u30fc\u30b9\u306e\u6392\u4ed6\u30ed\u30c3\u30af\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +Case.lockingException.couldNotAcquireSharedLock=\u30b1\u30fc\u30b9\u306e\u5171\u6709\u30ed\u30c3\u30af\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 Case.metaDataFileCorrupt.exception.msg=\u30b1\u30fc\u30b9\u30e1\u30bf\u30c7\u30fc\u30bf\u30d5\u30a1\u30a4\u30eb(.aut)\u304c\u7834\u640d\u3057\u3066\u3044\u307e\u3059\u3002 Case.open.exception.multiUserCaseNotEnabled=\u30de\u30eb\u30c1\u30e6\u30fc\u30b6\u30fc\u30b1\u30fc\u30b9\u304c\u6709\u52b9\u3067\u306a\u3044\u5834\u5408\u306f\u30de\u30eb\u30c1\u30e6\u30fc\u30b6\u30fc\u30b1\u30fc\u30b9\u3092\u958b\u3051\u307e\u305b\u3093\u3002[\u30c4\u30fc\u30eb]\u3001[\u30aa\u30d7\u30b7\u30e7\u30f3]\u3001[\u30de\u30eb\u30c1\u30e6\u30fc\u30b6\u30fc] \u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002 Case.open.msgDlg.updated.msg=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30b9\u30ad\u30fc\u30de\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f\u3002\n\u6b21\u306e\u30d1\u30b9\u3092\u6301\u3064\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u30d0\u30c3\u30af\u30a2\u30c3\u30d7\u30b3\u30d4\u30fc\u3092\u4f5c\u6210\u3057\u307e\u3057\u305f\:\n {0} Case.open.msgDlg.updated.title=\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30b9\u30ad\u30fc\u30de\u306e\u66f4\u65b0 -Case.openFileSystems.openingImage=\u753b\u50cf\u306e\u3059\u3079\u3066\u306e\u30d5\u30a1\u30a4\u30eb\u30b7\u30b9\u30c6\u30e0\u3092\u958b\u304f\uff1a{0}\u2026 -Case.openFileSystems.retrievingImages=\u30b1\u30fc\u30b9\u306e\u753b\u50cf\u3092\u53d6\u5f97\u4e2d\uff1a{0}\u2026 +Case.openFileSystems.openingImage=\u30a4\u30e1\u30fc\u30b8\u7528\u306b\u3059\u3079\u3066\u306e\u30d5\u30a1\u30a4\u30eb\u30b7\u30b9\u30c6\u30e0\u3092\u958b\u304f\uff1a{0} ... +Case.openFileSystems.retrievingImages=\u30b1\u30fc\u30b9\u306e\u753b\u50cf\u3092\u53d6\u5f97\u3057\u3066\u3044\u307e\u3059\uff1a{0} ... Case.progressIndicatorCancelButton.label=\u53d6\u308a\u6d88\u3057 Case.progressIndicatorTitle.closingCase=\u30b1\u30fc\u30b9\u3092\u9589\u3058\u3066\u3044\u307e\u3059 Case.progressIndicatorTitle.creatingCase=\u30b1\u30fc\u30b9\u3092\u4f5c\u6210\u4e2d\u3067\u3059 Case.progressIndicatorTitle.deletingCase=\u30b1\u30fc\u30b9\u3092\u524a\u9664\u4e2d\u3067\u3059 +Case.progressIndicatorTitle.deletingDataSource=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306e\u524a\u9664\u4e2d Case.progressIndicatorTitle.openingCase=\u30b1\u30fc\u30b9\u3092\u958b\u3044\u3066\u3044\u307e\u3059 Case.progressMessage.cancelling=\u53d6\u308a\u6d88\u3057\u4e2d\u3067\u3059... Case.progressMessage.clearingTempDirectory=\u30b1\u30fc\u30b9\u306e\u4e00\u6642\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30fc\u3092\u6d88\u53bb\u4e2d\u3067\u3059... @@ -109,6 +123,7 @@ Case.progressMessage.creatingCaseNodeData=\u5ea7\u6a19\u30b5\u30fc\u30d3\u30b9\u Case.progressMessage.deletingCaseDatabase=\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u524a\u9664\u4e2d\u3067\u3059... Case.progressMessage.deletingCaseDirCoordSvcNode=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30fc\u306e\u5ea7\u6a19\u30b5\u30fc\u30d3\u30b9\u30ce\u30fc\u30c9\u30c7\u30fc\u30bf\u3092\u524a\u9664\u4e2d\u3067\u3059... Case.progressMessage.deletingCaseDirectory=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30fc\u3092\u524a\u9664\u4e2d\u3067\u3059... +Case.progressMessage.deletingDataSource=\u30b1\u30fc\u30b9\u304b\u3089\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u524a\u9664\u4e2d... Case.progressMessage.deletingResourcesCoordSvcNode=\u30b1\u30fc\u30b9\u30ea\u30bd\u30fc\u30b9\u306e\u5ea7\u6a19\u30b5\u30fc\u30d3\u30b9\u30ce\u30fc\u30c9\u30c7\u30fc\u30bf\u3092\u524a\u9664\u4e2d\u3067\u3059... Case.progressMessage.deletingTextIndex=\u30c6\u30ad\u30b9\u30c8\u7d22\u5f15\u3092\u524a\u9664\u4e2d\u3067\u3059... Case.progressMessage.fetchingCoordSvcNodeData=\u30b1\u30fc\u30b9\u306e\u5ea7\u6a19\u30b5\u30fc\u30d3\u30b9\u30ce\u30fc\u30c9\u30c7\u30fc\u30bf\u3092\u53d6\u5f97\u4e2d\u3067\u3059... @@ -179,6 +194,11 @@ CueBannerPanel.openCaseLabel.text=\u30b1\u30fc\u30b9\u3092\u958b\u304f CueBannerPanel.openRecentCaseButton.text= CueBannerPanel.openRecentCaseLabel.text=\u6700\u8fd1\u306e\u30b1\u30fc\u30b9\u3092\u958b\u304f CueBannerPanel.title.text=\u6700\u8fd1\u306e\u30b1\u30fc\u30b9\u3092\u958b\u304f +DeleteDataSourceAction.confirmationDialog.message=\u9078\u629e\u3057\u305f\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u30b1\u30fc\u30b9\u304b\u3089\u524a\u9664\u3057\u3066\u3082\u3088\u308d\u3057\u3044\u3067\u3059\u304b\uff1f\n\u6ce8\u610f\uff1a\u524a\u9664\u4e2d\u306b\u30b1\u30fc\u30b9\u304c\u9589\u3058\u3089\u308c\u3001\u518d\u3073\u958b\u304b\u308c\u307e\u3059\u3002\u300d +DeleteDataSourceAction.exceptionMessage.couldNotReopenCase=\u30b1\u30fc\u30b9\u3092\u518d\u958b\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\uff1a\n{0}\n\u8a73\u7d30\u306b\u3064\u3044\u3066\u306f\u3001\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30ed\u30b0\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +DeleteDataSourceAction.exceptionMessage.dataSourceDeletionError=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306e\u524a\u9664\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\uff1a\n{0}\n\u8a73\u7d30\u306b\u3064\u3044\u3066\u306f\u3001\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30ed\u30b0\u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +DeleteDataSourceAction.ingestRunningWarningDialog.message=\u53d6\u8fbc\u307f\u306e\u5b9f\u884c\u4e2d\u306f\u3001\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u30b1\u30fc\u30b9\u304b\u3089\u524a\u9664\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093\u3002 +DeleteDataSourceAction.name.text=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u524a\u9664\u3059\u308b EditOptionalCasePropertiesPanel.cancelButton.text=\u53d6\u308a\u6d88\u3057 EditOptionalCasePropertiesPanel.saveButton.text=\u4fdd\u5b58 GeneralFilter.encaseImageDesc.text=\u30a4\u30e1\u30fc\u30b8(*.e01)\u3092\u5305\u542b @@ -297,7 +317,6 @@ LogicalEvidenceFilePanel.selectButton.toolTipText=\u30ed\u30fc\u30ab\u30eb\u30d5 LogicalEvidenceFilePanel.validatePanel.nonL01Error.text=\u3053\u3053\u3067\u306f .l01\u30d5\u30a1\u30a4\u30eb\u62e1\u5f35\u5b50\u3092\u6301\u3064\u30d5\u30a1\u30a4\u30eb\u306e\u307f\u304c\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u3059\u3002 LogicalFilesDspPanel.subTypeComboBox.l01FileOption.text=\u8ad6\u7406\u8a3c\u62e0\u30d5\u30a1\u30a4\u30eb(L01) LogicalFilesDspPanel.subTypeComboBox.localFilesOption.text=\u30ed\u30fc\u30ab\u30eb\u30d5\u30a1\u30a4\u30eb\u304a\u3088\u3073\u30d5\u30a9\u30eb\u30c0\u30fc -Menu/Case/OpenRecentCase=\u6700\u8fd1\u306e\u30b1\u30fc\u30b9\u3092\u958b\u304f MissingImageDialog.ErrorSettingImage=\u30a4\u30e1\u30fc\u30b8\u30d1\u30b9\u306e\u8a2d\u5b9a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u3082\u3046\u4e00\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002 MissingImageDialog.browseButton.text=\u53c2\u7167 MissingImageDialog.cancelButton.text=\u53d6\u308a\u6d88\u3057 @@ -313,7 +332,7 @@ NewCaseVisualPanel1.CaseFolderOnCDriveError.text=\u8b66\u544a\: \u30de\u30eb\u30 NewCaseVisualPanel1.CaseFolderOnInternalDriveLinuxError.text=\u8b66\u544a\: \u30b1\u30fc\u30b9\u30d5\u30a9\u30eb\u30c0\u30fc\u306e\u30d1\u30b9\u306f\u30bf\u30fc\u30b2\u30c3\u30c8\u30b7\u30b9\u30c6\u30e0\u4e0a\u306b\u3042\u308a\u307e\u3059\u3002\u30de\u30a6\u30f3\u30c8\u3055\u308c\u305f\u30c9\u30e9\u30a4\u30d6\u5185\u306b\u30b1\u30fc\u30b9\u30d5\u30a9\u30eb\u30c0\u30fc\u3092\u4f5c\u6210\u3057\u307e\u3059\u3002 NewCaseVisualPanel1.CaseFolderOnInternalDriveWindowsError.text=\u8b66\u544a\: \u30b1\u30fc\u30b9\u30d5\u30a9\u30eb\u30c0\u30fc\u306e\u30d1\u30b9\u306f "C\:" \u30c9\u30e9\u30a4\u30d6\u306b\u3042\u308a\u307e\u3059\u3002\u30b1\u30fc\u30b9\u30d5\u30a9\u30eb\u30c0\u30fc\u306f\u30bf\u30fc\u30b2\u30c3\u30c8\u30b7\u30b9\u30c6\u30e0\u4e0a\u306b\u4f5c\u6210\u3055\u308c\u307e\u3059 NewCaseVisualPanel1.badCredentials.text=\u4e0d\u6b63\u306a\u30de\u30eb\u30c1\u30e6\u30fc\u30b6\u30fc\u8a2d\u5b9a(\u30c4\u30fc\u30eb]\u3001[\u30aa\u30d7\u30b7\u30e7\u30f3]\u3001[\u30de\u30eb\u30c1\u30e6\u30fc\u30b6\u30fc] \u3092\u53c2\u7167)\u304b\u3001\u30b5\u30fc\u30d3\u30b9\u304c\u30c0\u30a6\u30f3\u3057\u3066\u3044\u307e\u3059\u3002 -NewCaseVisualPanel1.caseDataStoredLabel.text_1=\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u306f\u6b21\u306e\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30fc\u306b\u4fdd\u5b58\u3055\u308c\u307e\u3059\uff1a +NewCaseVisualPanel1.caseDataStoredLabel.text_1=\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u306f\u6b21\u306e\u30d5\u30a9\u30eb\u30c0\u30fc\u306b\u4fdd\u5b58\u3055\u308c\u307e\u3059\uff1a NewCaseVisualPanel1.caseDirBrowse.selectButton.text=\u9078\u629e NewCaseVisualPanel1.caseDirBrowseButton.text=\u53c2\u7167 NewCaseVisualPanel1.caseDirLabel.text=\u30d9\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30fc\: @@ -324,8 +343,9 @@ NewCaseVisualPanel1.caseParentDirTextField.text= NewCaseVisualPanel1.caseParentDirWarningLabel.text= NewCaseVisualPanel1.caseTypeLabel.text=\u30b1\u30fc\u30b9\u30bf\u30a4\u30d7\: NewCaseVisualPanel1.getName.text=\u30b1\u30fc\u30b9\u60c5\u5831 -NewCaseVisualPanel1.multiUserCaseRadioButton.text=\u30de\u30eb\u30c1\u30e6\u30fc\u30b6\u30fc\t\t +NewCaseVisualPanel1.multiUserCaseRadioButton.text=\u30de\u30eb\u30c1\u30e6\u30fc\u30b6\u30fc NewCaseVisualPanel1.singleUserCaseRadioButton.text=\u30b7\u30f3\u30b0\u30eb\u30e6\u30fc\u30b6\u30fc +NewCaseVisualPanel1.uncPath.error=\u30a8\u30e9\u30fc\uff1a\u30b7\u30f3\u30b0\u30eb\u30e6\u30fc\u30b6\u30fc\u306e\u5834\u5408\u3001UNC\u30d1\u30b9\u306f\u8a31\u53ef\u3055\u308c\u3066\u3044\u307e\u305b\u3093 NewCaseVisualPanel2.getName.text=\u4efb\u610f\u60c5\u5831 NewCaseWizardAction.databaseProblem1.text=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u958b\u3051\u307e\u305b\u3093\u3002\u30b1\u30fc\u30b9\u306e\u4f5c\u6210\u3092\u53d6\u308a\u6d88\u3057\u4e2d\u3067\u3059\u3002 NewCaseWizardAction.databaseProblem2.text=\u30a8\u30e9\u30fc @@ -348,6 +368,7 @@ OpenMultiUserCaseDialog.title=\u30de\u30eb\u30c1\u30e6\u30fc\u30b6\u30fc\u30b1\u OpenMultiUserCasePanel.cancelButton.text=\u53d6\u308a\u6d88\u3057 OpenMultiUserCasePanel.openSelectedCaseButton.text=\u9078\u629e\u3057\u305f\u30b1\u30fc\u30b9\u3092\u958b\u304f OpenMultiUserCasePanel.openSingleUserCaseButton.tex=\u30b7\u30f3\u30b0\u30eb\u30e6\u30fc\u30b6\u30fc\u30b1\u30fc\u30b9\u3092\u958b\u304f... +OpenMultiUserCasePanel.openSingleUserCaseButton.text=\u5358\u72ec\u30e6\u30fc\u30b6\u30fc\u30b1\u30fc\u30b9\u3092\u958b\u304f... OpenMultiUserCasePanel.searchLabel.text=\u4efb\u610f\u306e\u30b1\u30fc\u30b9\u3092\u9078\u629e\u3057\u3001\u5165\u529b\u3092\u958b\u59cb\u3057\u3066\u30b1\u30fc\u30b9\u540d\u3067\u691c\u7d22 OpenRecentCasePanel.cancelButton.text=\u53d6\u308a\u6d88\u3057 OpenRecentCasePanel.colName.caseName=\u30b1\u30fc\u30b9\u540d @@ -383,13 +404,20 @@ ReviewModeCasePanel.StatusIconHeaderText=\u30b9\u30c6\u30fc\u30bf\u30b9 ReviewModeCasePanel.cannotOpenCase=\u30b1\u30fc\u30b9\u3092\u958b\u3051\u307e\u305b\u3093 ReviewModeCasePanel.caseIsLocked=\u30b7\u30f3\u30b0\u30eb\u30e6\u30fc\u30b6\u30fc\u304c\u30ed\u30c3\u30af\u3055\u308c\u3066\u3044\u307e\u3059\u3002 ReviewModeCasePanel.casePathNotFound=\u30b1\u30fc\u30b9\u30d1\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093 -SelectDataSourceProcessorPanel.name.text=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306e\u30bf\u30a4\u30d7\u3092\u9078\u629e\u3057\u3066\u8ffd\u52a0 +SelectDataSourceProcessorPanel.name.text=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u30bf\u30a4\u30d7\u306e\u9078\u629e SingleUserCaseConverter.AlreadyMultiUser=\u30b1\u30fc\u30b9\u306f\u3059\u3067\u306b\u30de\u30eb\u30c1\u30e6\u30fc\u30b6\u30fc\u3067\u3059\! SingleUserCaseConverter.BadDatabaseFileName=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb\u306f\u5b58\u5728\u3057\u307e\u305b\u3093\! SingleUserCaseConverter.CanNotOpenDatabase=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u958b\u3051\u307e\u305b\u3093 SingleUserCaseConverter.NonUniqueDatabaseName=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u540d\u304c\u4e00\u610f\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 SingleUserCaseConverter.UnableToCopySourceImages=\u30bd\u30fc\u30b9\u30a4\u30e1\u30fc\u30b8\u3092\u30b3\u30d4\u30fc\u3067\u304d\u307e\u305b\u3093 +SolrNotConfiguredDialog.EmptyKeywordSearchHostName=Solr8\u63a5\u7d9a\u30d1\u30e9\u30e1\u30fc\u30bf\u30fc\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002 [\u30c4\u30fc\u30eb]-> [\u30aa\u30d7\u30b7\u30e7\u30f3]-> [\u30de\u30eb\u30c1\u30e6\u30fc\u30b6\u30fc]\u306b\u79fb\u52d5\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +SolrNotConfiguredDialog.messageLabel.text=\u30de\u30eb\u30c1\u30e6\u30fc\u30b6\u30fc\u30b1\u30fc\u30b9\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u3059\u304c\u3001Solr8\u30b5\u30fc\u30d0\u30fc\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002
\n\u65b0\u3057\u3044\u30b1\u30fc\u30b9\u306fSolr8\u3067\u306e\u307f\u4f5c\u6210\u3067\u304d\u307e\u3059\u3002[\u30c4\u30fc\u30eb]-> [\u30aa\u30d7\u30b7\u30e7\u30f3]-> [\u30de\u30eb\u30c1\u30e6\u30fc\u30b6\u30fc]\u306b\u79fb\u52d5\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +SolrNotConfiguredDialog.okButton.text=OK +SolrNotConfiguredDialog.title=Solr8\u30b5\u30fc\u30d0\u30fc\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093 StartupWindow.title.text=\u3088\u3046\u3053\u305d +StartupWindowProvider.openCase.cantOpen=\u4ee5\u524d\u306b\u958b\u3044\u305f\u30b1\u30fc\u30b9\u3092\u30e1\u30bf\u30c7\u30fc\u30bf\u30d5\u30a1\u30a4\u30eb{0}\u3067\u958b\u304f\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3002 +StartupWindowProvider.openCase.deleteOpenFailure=\u4ee5\u524d\u306b\u958b\u3044\u305f\u30b1\u30fc\u30b9\u3078\u306e\u30d1\u30b9{0}\u3092\u542b\u3080\u30d5\u30a1\u30a4\u30eb\u3092\u958b\u3044\u305f\u308a\u524a\u9664\u3057\u305f\u308a\u3067\u304d\u307e\u305b\u3093\u3002 \u524d\u306e\u30b1\u30fc\u30b9\u306f\u958b\u304b\u308c\u307e\u305b\u3093\u3002 +StartupWindowProvider.openCase.noFile=\u30e1\u30bf\u30c7\u30fc\u30bf\u30d5\u30a1\u30a4\u30eb\u304c{0}\u306b\u898b\u3064\u304b\u3089\u306a\u3044\u305f\u3081\u3001\u4ee5\u524d\u306b\u958b\u3044\u305f\u30b1\u30fc\u30b9\u3092\u958b\u304f\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3002 UnpackagePortableCaseDialog.UnpackagePortableCaseDialog.extensions=\u30dd\u30fc\u30bf\u30d6\u30eb\u30b1\u30fc\u30b9\u30d1\u30c3\u30b1\u30fc\u30b8(.zip, .zip.001) UnpackagePortableCaseDialog.caseErrorLabel.text=jLabel1 UnpackagePortableCaseDialog.caseLabel.text=\u30dd\u30fc\u30bf\u30d6\u30eb\u30b1\u30fc\u30b9\: diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 4347c0d778..6f10bcf364 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012-2020 Basis Technology Corp. + * Copyright 2012-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,7 @@ import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils; import com.google.common.annotations.Beta; import com.google.common.eventbus.Subscribe; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.awt.Cursor; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; import java.awt.Frame; import java.awt.event.ActionEvent; @@ -29,6 +30,7 @@ import java.awt.event.ActionListener; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; +import java.lang.reflect.InvocationTargetException; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; @@ -39,6 +41,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.text.SimpleDateFormat; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -61,6 +64,7 @@ import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; +import org.apache.commons.lang3.StringUtils; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; @@ -81,7 +85,24 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent; import org.sleuthkit.autopsy.casemodule.events.DataSourceDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.DataSourceNameChangedEvent; +import org.sleuthkit.autopsy.casemodule.events.HostsAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.HostsAddedToPersonEvent; +import org.sleuthkit.autopsy.casemodule.events.HostsUpdatedEvent; +import org.sleuthkit.autopsy.casemodule.events.HostsDeletedEvent; +import org.sleuthkit.autopsy.casemodule.events.HostsRemovedFromPersonEvent; +import org.sleuthkit.autopsy.casemodule.events.OsAccountsAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.OsAccountsUpdatedEvent; +import org.sleuthkit.autopsy.casemodule.events.OsAccountsDeletedEvent; +import org.sleuthkit.autopsy.casemodule.events.OsAcctInstancesAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.PersonsAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.PersonsUpdatedEvent; +import org.sleuthkit.autopsy.casemodule.events.PersonsDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.TagNamesEvent.TagNamesAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.TagNamesEvent.TagNamesDeletedEvent; +import org.sleuthkit.autopsy.casemodule.events.TagNamesEvent.TagNamesUpdatedEvent; +import org.sleuthkit.autopsy.casemodule.events.TagSetsEvent.TagSetsAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.TagSetsEvent.TagSetsDeletedEvent; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.CaseNodeDataException; import org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils; import org.sleuthkit.autopsy.casemodule.services.Services; @@ -104,6 +125,8 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.coreutils.ThreadUtils; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.datamodel.hosts.OpenHostsAction; +import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.events.AutopsyEventException; import org.sleuthkit.autopsy.events.AutopsyEventPublisher; @@ -114,6 +137,7 @@ import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException; +import org.sleuthkit.autopsy.machinesettings.UserMachinePreferences; import org.sleuthkit.autopsy.progress.LoggingProgressIndicator; import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator; import org.sleuthkit.autopsy.progress.ProgressIndicator; @@ -127,13 +151,17 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.FileSystem; +import org.sleuthkit.datamodel.Host; import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.OsAccount; +import org.sleuthkit.datamodel.Person; import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TimelineManager; import org.sleuthkit.datamodel.SleuthkitCaseAdminUtil; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskDataException; +import org.sleuthkit.datamodel.TskEvent; import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException; /** @@ -141,9 +169,10 @@ import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException; */ public class Case { - private static final String CASE_TEMP_DIR = Case.class.getSimpleName(); private static final int CASE_LOCK_TIMEOUT_MINS = 1; private static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS = 1; + private static final String APP_NAME = UserPreferences.getAppName(); + private static final String TEMP_FOLDER = "Temp"; private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db"; private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS private static final String CACHE_FOLDER = "Cache"; //NON-NLS @@ -171,6 +200,9 @@ public class Case { private CollaborationMonitor collaborationMonitor; private Services caseServices; + private volatile boolean hasDataSource = false; + private volatile boolean hasData = false; + /* * Get a reference to the main window of the desktop application to use to * parent pop up dialogs and initialize the application name for use in @@ -409,7 +441,80 @@ public class Case { * An item in the central repository has had its comment modified. The * old value is null, the new value is string for current comment. */ - CR_COMMENT_CHANGED; + CR_COMMENT_CHANGED, + /** + * One or more OS accounts have been added to the case. + */ + OS_ACCOUNTS_ADDED, + /** + * One or more OS accounts in the case have been updated. + */ + OS_ACCOUNTS_UPDATED, + /** + * One or more OS accounts have been deleted from the case. + */ + OS_ACCOUNTS_DELETED, + /** + * One or more OS account instances have been added to the case. + */ + OS_ACCT_INSTANCES_ADDED, + /** + * One or more hosts have been added to the case. + */ + HOSTS_ADDED, + /** + * One or more hosts in the case have been updated. + */ + HOSTS_UPDATED, + /** + * One or more hosts have been deleted from the case. + */ + HOSTS_DELETED, + /** + * One or more persons have been added to the case. + */ + PERSONS_ADDED, + /** + * One or more persons in the case have been updated. + */ + PERSONS_UPDATED, + /** + * One or more persons been deleted from the case. + */ + PERSONS_DELETED, + /** + * One or more hosts have been added to a person. + */ + HOSTS_ADDED_TO_PERSON, + /** + * One or more hosts have been removed from a person. + */ + HOSTS_REMOVED_FROM_PERSON, + + /** + * One or more TagNames have been added. + */ + TAG_NAMES_ADDED, + + /** + * One or more TagNames have been updated. + */ + TAG_NAMES_UPDATED, + + /** + * One or more TagNames have been deleted. + */ + TAG_NAMES_DELETED, + + /** + * One or more TagSets have been added. + */ + TAG_SETS_ADDED, + + /** + * One or more TagSets have been removed. + */ + TAG_SETS_DELETED; }; @@ -425,23 +530,138 @@ public class Case { eventPublisher.publish(new TimelineEventAddedEvent(event)); } - @SuppressWarnings("deprecation") @Subscribe - public void publishArtifactsPostedEvent(Blackboard.ArtifactsPostedEvent event) { - for (BlackboardArtifact.Type artifactType : event.getArtifactTypes()) { - /* - * IngestServices.fireModuleDataEvent is deprecated to - * discourage ingest module writers from using it (they should - * use org.sleuthkit.datamodel.Blackboard.postArtifact(s) - * instead), but a way to publish - * Blackboard.ArtifactsPostedEvents from the SleuthKit layer as - * Autopsy ModuleDataEvents is still needed. - */ - IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent( - event.getModuleName(), - artifactType, - event.getArtifacts(artifactType))); + public void publishOsAccountsAddedEvent(TskEvent.OsAccountsAddedTskEvent event) { + hasData = true; + eventPublisher.publish(new OsAccountsAddedEvent(event.getOsAcounts())); + } + + @Subscribe + public void publishOsAccountsUpdatedEvent(TskEvent.OsAccountsUpdatedTskEvent event) { + eventPublisher.publish(new OsAccountsUpdatedEvent(event.getOsAcounts())); + } + + @Subscribe + public void publishOsAccountDeletedEvent(TskEvent.OsAccountsDeletedTskEvent event) { + try { + hasData = dbHasData(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex); } + eventPublisher.publish(new OsAccountsDeletedEvent(event.getOsAccountObjectIds())); + } + + @Subscribe + public void publishOsAccountInstancesAddedEvent(TskEvent.OsAcctInstancesAddedTskEvent event) { + eventPublisher.publish(new OsAcctInstancesAddedEvent(event.getOsAccountInstances())); + } + + /** + * Publishes an autopsy event from the sleuthkit HostAddedEvent + * indicating that hosts have been created. + * + * @param event The sleuthkit event for the creation of hosts. + */ + @Subscribe + public void publishHostsAddedEvent(TskEvent.HostsAddedTskEvent event) { + hasData = true; + eventPublisher.publish(new HostsAddedEvent(event.getHosts())); + } + + /** + * Publishes an autopsy event from the sleuthkit HostUpdateEvent + * indicating that hosts have been updated. + * + * @param event The sleuthkit event for the updating of hosts. + */ + @Subscribe + public void publishHostsUpdatedEvent(TskEvent.HostsUpdatedTskEvent event) { + eventPublisher.publish(new HostsUpdatedEvent(event.getHosts())); + } + + /** + * Publishes an autopsy event from the sleuthkit HostDeletedEvent + * indicating that hosts have been deleted. + * + * @param event The sleuthkit event for the deleting of hosts. + */ + @Subscribe + public void publishHostsDeletedEvent(TskEvent.HostsDeletedTskEvent event) { + try { + hasData = dbHasData(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex); + } + + eventPublisher.publish(new HostsDeletedEvent(event.getHostIds())); + } + + /** + * Publishes an autopsy event from the sleuthkit PersonAddedEvent + * indicating that persons have been created. + * + * @param event The sleuthkit event for the creation of persons. + */ + @Subscribe + public void publishPersonsAddedEvent(TskEvent.PersonsAddedTskEvent event) { + eventPublisher.publish(new PersonsAddedEvent(event.getPersons())); + } + + /** + * Publishes an autopsy event from the sleuthkit PersonChangedEvent + * indicating that persons have been updated. + * + * @param event The sleuthkit event for the updating of persons. + */ + @Subscribe + public void publishPersonsUpdatedEvent(TskEvent.PersonsUpdatedTskEvent event) { + eventPublisher.publish(new PersonsUpdatedEvent(event.getPersons())); + } + + /** + * Publishes an autopsy event from the sleuthkit PersonDeletedEvent + * indicating that persons have been deleted. + * + * @param event The sleuthkit event for the deleting of persons. + */ + @Subscribe + public void publishPersonsDeletedEvent(TskEvent.PersonsDeletedTskEvent event) { + eventPublisher.publish(new PersonsDeletedEvent(event.getPersonIds())); + } + + @Subscribe + public void publishHostsAddedToPersonEvent(TskEvent.HostsAddedToPersonTskEvent event) { + eventPublisher.publish(new HostsAddedToPersonEvent(event.getPerson(), event.getHosts())); + } + + @Subscribe + public void publisHostsRemovedFromPersonEvent(TskEvent.HostsRemovedFromPersonTskEvent event) { + eventPublisher.publish(new HostsRemovedFromPersonEvent(event.getPerson(), event.getHostIds())); + } + + @Subscribe + public void publicTagNamesAdded(TskEvent.TagNamesAddedTskEvent event) { + eventPublisher.publish(new TagNamesAddedEvent(event.getTagNames())); + } + + @Subscribe + public void publicTagNamesUpdated(TskEvent.TagNamesUpdatedTskEvent event) { + eventPublisher.publish(new TagNamesUpdatedEvent(event.getTagNames())); + } + + @Subscribe + public void publicTagNamesDeleted(TskEvent.TagNamesDeletedTskEvent event) { + eventPublisher.publish(new TagNamesDeletedEvent(event.getTagNameIds())); + } + + @Subscribe + public void publicTagSetsAdded(TskEvent.TagSetsAddedTskEvent event) { + eventPublisher.publish(new TagSetsAddedEvent(event.getTagSets())); + } + + @Subscribe + public void publicTagSetsDeleted(TskEvent.TagSetsDeletedTskEvent event) { + eventPublisher.publish(new TagSetsDeletedEvent(event.getTagSetIds())); } } @@ -720,13 +940,13 @@ public class Case { try { eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null)); logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS - currentCase = null; closedCase.doCloseCaseAction(); logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS } catch (CaseActionException ex) { logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS throw ex; } finally { + currentCase = null; if (RuntimeProperties.runningWithGUI()) { updateGUIForCaseClosed(); } @@ -1066,88 +1286,106 @@ public class Case { * Update the GUI to to reflect the current case. */ private static void updateGUIForCaseOpened(Case newCurrentCase) { - if (RuntimeProperties.runningWithGUI()) { - SwingUtilities.invokeLater(() -> { - /* - * If the case database was upgraded for a new schema and a - * backup database was created, notify the user. - */ - SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase(); - String backupDbPath = caseDb.getBackupDatabasePath(); - if (null != backupDbPath) { - JOptionPane.showMessageDialog( - mainFrame, - NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath), - NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"), - JOptionPane.INFORMATION_MESSAGE); - } - - /* - * Look for the files for the data sources listed in the case - * database and give the user the opportunity to locate any that - * are missing. - */ - Map imgPaths = getImagePaths(caseDb); - for (Map.Entry entry : imgPaths.entrySet()) { - long obj_id = entry.getKey(); - String path = entry.getValue(); - boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path)); - if (!fileExists) { - int response = JOptionPane.showConfirmDialog( - mainFrame, - NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path), - NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"), - JOptionPane.YES_NO_OPTION); - if (response == JOptionPane.YES_OPTION) { - MissingImageDialog.makeDialog(obj_id, caseDb); - } else { - logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS - - } - } - } - - /* - * Enable the case-specific actions. - */ - CallableSystemAction.get(AddImageAction.class).setEnabled(FeatureAccessUtils.canAddDataSources()); - CallableSystemAction.get(CaseCloseAction.class).setEnabled(true); - CallableSystemAction.get(CaseDetailsAction.class).setEnabled(true); - CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true); - CallableSystemAction.get(CaseDeleteAction.class).setEnabled(FeatureAccessUtils.canDeleteCurrentCase()); - CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true); - CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true); - CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(true); - CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false); - CallableSystemAction.get(OpenDiscoveryAction.class).setEnabled(true); - - /* - * Add the case to the recent cases tracker that supplies a list - * of recent cases to the recent cases menu item and the - * open/create case dialog. - */ - RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getMetadata().getFilePath().toString()); - - /* - * Open the top components (windows within the main application - * window). - * - * Note: If the core windows are not opened here, they will be - * opened via the DirectoryTreeTopComponent 'propertyChange()' - * method on a DATA_SOURCE_ADDED event. - */ - if (newCurrentCase.hasData()) { - CoreComponentControl.openCoreWindows(); - } - - /* - * Reset the main window title to: - * - * [curent case display name] - [application name]. - */ - mainFrame.setTitle(newCurrentCase.getDisplayName() + " - " + getNameForTitle()); - }); + /* + * If the case database was upgraded for a new schema and a backup + * database was created, notify the user. + */ + SleuthkitCase caseDb = newCurrentCase.getSleuthkitCase(); + String backupDbPath = caseDb.getBackupDatabasePath(); + if (null != backupDbPath) { + JOptionPane.showMessageDialog( + mainFrame, + NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", backupDbPath), + NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"), + JOptionPane.INFORMATION_MESSAGE); } + + /* + * Look for the files for the data sources listed in the case database + * and give the user the opportunity to locate any that are missing. + */ + Map imgPaths = getImagePaths(caseDb); + for (Map.Entry entry : imgPaths.entrySet()) { + long obj_id = entry.getKey(); + String path = entry.getValue(); + boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path)); + if (!fileExists) { + try { + // Using invokeAndWait means that the dialog will + // open on the EDT but this thread will wait for an + // answer. Using invokeLater would cause this loop to + // end before all of the dialogs appeared. + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + int response = JOptionPane.showConfirmDialog( + mainFrame, + NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path), + NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"), + JOptionPane.YES_NO_OPTION); + if (response == JOptionPane.YES_OPTION) { + MissingImageDialog.makeDialog(obj_id, caseDb); + } else { + logger.log(Level.SEVERE, "User proceeding with missing image files"); //NON-NLS + + } + } + + }); + } catch (InterruptedException | InvocationTargetException ex) { + logger.log(Level.SEVERE, "Failed to show missing image confirmation dialog", ex); //NON-NLS + } + } + } + + /* + * Enable the case-specific actions. + */ + CallableSystemAction.get(AddImageAction.class).setEnabled(FeatureAccessUtils.canAddDataSources()); + CallableSystemAction.get(OpenHostsAction.class).setEnabled(true); + CallableSystemAction.get(CaseCloseAction.class).setEnabled(true); + CallableSystemAction.get(CaseDetailsAction.class).setEnabled(true); + CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true); + CallableSystemAction.get(CaseDeleteAction.class).setEnabled(FeatureAccessUtils.canDeleteCurrentCase()); + CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true); + CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true); + CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(true); + CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false); + CallableSystemAction.get(OpenDiscoveryAction.class).setEnabled(true); + + /* + * Add the case to the recent cases tracker that supplies a list of + * recent cases to the recent cases menu item and the open/create case + * dialog. + */ + RecentCases.getInstance().addRecentCase(newCurrentCase.getDisplayName(), newCurrentCase.getMetadata().getFilePath().toString()); + final boolean hasData = newCurrentCase.hasData(); + + SwingUtilities.invokeLater(() -> { + /* + * Open the top components (windows within the main application + * window). + * + * Note: If the core windows are not opened here, they will be + * opened via the DirectoryTreeTopComponent 'propertyChange()' + * method on a DATA_SOURCE_ADDED event. + */ + mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + if (hasData) { + CoreComponentControl.openCoreWindows(); + } else { + //ensure that the DirectoryTreeTopComponent is open so that it's listener can open the core windows including making it visible. + DirectoryTreeTopComponent.findInstance(); + } + mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + + /* + * Reset the main window title to: + * + * [curent case display name] - [application name]. + */ + mainFrame.setTitle(newCurrentCase.getDisplayName() + " - " + getNameForTitle()); + }); } /* @@ -1166,6 +1404,7 @@ public class Case { * Disable the case-specific menu items. */ CallableSystemAction.get(AddImageAction.class).setEnabled(false); + CallableSystemAction.get(OpenHostsAction.class).setEnabled(false); CallableSystemAction.get(CaseCloseAction.class).setEnabled(false); CallableSystemAction.get(CaseDetailsAction.class).setEnabled(false); CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(false); @@ -1321,6 +1560,13 @@ public class Case { return hostPath.toString(); } + /** + * @return A subdirectory of java.io.tmpdir. + */ + private Path getBaseSystemTempPath() { + return Paths.get(System.getProperty("java.io.tmpdir"), APP_NAME, getName()); + } + /** * Gets the full path to the temp directory for this case, creating it if it * does not exist. @@ -1328,16 +1574,45 @@ public class Case { * @return The temp subdirectory path. */ public String getTempDirectory() { - // get temp folder scoped to the combination of case name and timestamp - // provided by getName() - Path path = Paths.get(UserPreferences.getAppTempDirectory(), CASE_TEMP_DIR, getName()); - File f = path.toFile(); - // verify that the folder exists - if (!f.exists()) { - f.mkdirs(); + // NOTE: UserPreferences may also be affected by changes in this method. + // See JIRA-7505 for more information. + Path basePath = null; + // get base temp path for the case based on user preference + switch (UserMachinePreferences.getTempDirChoice()) { + case CUSTOM: + String customDirectory = UserMachinePreferences.getCustomTempDirectory(); + basePath = (StringUtils.isBlank(customDirectory)) + ? null + : Paths.get(customDirectory, APP_NAME, getName()); + break; + case CASE: + basePath = Paths.get(getCaseDirectory()); + break; + case SYSTEM: + default: + // at this level, if the case directory is specified for a temp + // directory, return the system temp directory instead. + basePath = getBaseSystemTempPath(); + break; } - return path.toAbsolutePath().toString(); + basePath = basePath == null ? getBaseSystemTempPath() : basePath; + + // get sub directories based on multi user vs. single user + Path caseRelPath = (CaseType.MULTI_USER_CASE.equals(getCaseType())) + ? Paths.get(NetworkUtils.getLocalHostName(), TEMP_FOLDER) + : Paths.get(TEMP_FOLDER); + + File caseTempDir = basePath + .resolve(caseRelPath) + .toFile(); + + // ensure directory exists + if (!caseTempDir.exists()) { + caseTempDir.mkdirs(); + } + + return caseTempDir.getAbsolutePath(); } /** @@ -1462,26 +1737,21 @@ public class Case { } /** - * Queries whether or not the case has data, i.e., whether or not at least - * one data source has been added to the case. + * Returns true if there is any data in the case. * * @return True or false. */ public boolean hasData() { - boolean hasDataSources = false; - String query = "SELECT count(*) AS count FROM data_source_info"; - try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) { - ResultSet resultSet = dbQuery.getResultSet(); - if (resultSet.next()) { - long numDataSources = resultSet.getLong("count"); - if (numDataSources > 0) { - hasDataSources = true; - } - } - } catch (TskCoreException | SQLException ex) { - logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS - } - return hasDataSources; + return hasData; + } + + /** + * Returns true if there is one or more data sources in the case. + * + * @return True or false. + */ + public boolean hasDataSource() { + return hasDataSource; } /** @@ -1495,6 +1765,8 @@ public class Case { * notifyNewDataSource after the data source is added. */ public void notifyAddingDataSource(UUID eventId) { + hasDataSource = true; + hasData = true; eventPublisher.publish(new AddingDataSourceEvent(eventId)); } @@ -1681,6 +1953,8 @@ public class Case { String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS throw new TskCoreException(errorMsg, ex); } + hasData = true; + Report report = this.caseDb.addReport(normalizedLocalPath, srcModuleName, reportName, parent); eventPublisher.publish(new ReportAddedEvent(report)); return report; @@ -1709,6 +1983,15 @@ public class Case { public void deleteReports(Collection reports) throws TskCoreException { for (Report report : reports) { this.caseDb.deleteReport(report); + } + + try { + hasData = dbHasData(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Unable to retrieve the hasData status from the db", ex); + } + + for (Report report : reports) { eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), report, null)); } } @@ -1718,7 +2001,7 @@ public class Case { * * @return A CaseMetaData object. */ - CaseMetadata getMetadata() { + public CaseMetadata getMetadata() { return metadata; } @@ -2466,6 +2749,7 @@ public class Case { } else { throw new CaseActionException(Bundle.Case_open_exception_multiUserCaseNotEnabled()); } + updateDataParameters(); } catch (TskUnsupportedSchemaVersionException ex) { throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex); } catch (UserPreferencesException ex) { @@ -2828,8 +3112,8 @@ public class Case { * @throws CaseActionException If the lock cannot be acquired. */ @Messages({ - "Case.lockingException.couldNotAcquireSharedLock=Failed to get an shared lock on the case.", - "Case.lockingException.couldNotAcquireExclusiveLock=Failed to get a exclusive lock on the case." + "Case.lockingException.couldNotAcquireSharedLock=Failed to get a shared lock on the case.", + "Case.lockingException.couldNotAcquireExclusiveLock=Failed to get an exclusive lock on the case." }) private void acquireCaseLock(CaseLockType lockType) throws CaseActionException { String caseDir = metadata.getCaseDirectory(); @@ -3243,6 +3527,78 @@ public class Case { } } + /** + * Initialize the hasData and hasDataSource parameters by checking the + * database. + * + * hasDataSource will be true if any data Source exists the db. + * + * hasData will be true if hasDataSource is true or if there are entries in + * the tsk_object or tsk_host tables. + * + * @throws TskCoreException + */ + private void updateDataParameters() throws TskCoreException { + hasDataSource = dbHasDataSource(); + + if (!hasDataSource) { + hasData = dbHasData(); + } else { + hasData = true; + } + } + + /** + * Returns true of there are any data sources in the case database. + * + * @return True if this case as a data source. + * + * @throws TskCoreException + */ + private boolean dbHasDataSource() throws TskCoreException { + String query = "SELECT count(*) AS count FROM (SELECT * FROM data_source_info LIMIT 1)t"; + try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) { + ResultSet resultSet = dbQuery.getResultSet(); + if (resultSet.next()) { + return resultSet.getLong("count") > 0; + } + return false; + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS + throw new TskCoreException("Error accessing case databse", ex); + } + } + + /** + * Returns true if the case has data. A case has data if there is at least + * one row in either the tsk_objects or tsk_hosts table. + * + * @return True if there is data in this case. + * + * @throws TskCoreException + */ + private boolean dbHasData() throws TskCoreException { + // The LIMIT 1 in the subquery should limit the data returned and + // make the overall query more efficent. + String query = "SELECT SUM(cnt) total FROM " + + "(SELECT COUNT(*) AS cnt FROM " + + "(SELECT * FROM tsk_objects LIMIT 1)t " + + "UNION ALL " + + "SELECT COUNT(*) AS cnt FROM " + + "(SELECT * FROM tsk_hosts LIMIT 1)r) s"; + try (SleuthkitCase.CaseDbQuery dbQuery = caseDb.executeQuery(query)) { + ResultSet resultSet = dbQuery.getResultSet(); + if (resultSet.next()) { + return resultSet.getLong("total") > 0; + } else { + return false; + } + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS + throw new TskCoreException("Error accessing case databse", ex); + } + } + /** * Defines the signature for case action methods that can be passed as * arguments to the doCaseAction method. diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index 5f7ffe9ea7..96f9899dae 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -218,7 +218,7 @@ public final class CaseMetadata { * * @return The path to the metadata file */ - Path getFilePath() { + public Path getFilePath() { return metadataFilePath; } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index 0ba92c7bce..25789939b3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,6 +43,7 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; /** * The action associated with the Case/Open Case menu item via the layer.xml @@ -64,6 +65,8 @@ public final class CaseOpenAction extends CallableSystemAction implements Action private static final Logger LOGGER = Logger.getLogger(CaseOpenAction.class.getName()); private final FileFilter caseMetadataFileFilter; + private final JFileChooserFactory fileChooserHelper; + /** * Constructs the action associated with the Case/Open Case menu item via * the layer.xml file, a toolbar button, and the Open Case button of the @@ -72,6 +75,7 @@ public final class CaseOpenAction extends CallableSystemAction implements Action */ public CaseOpenAction() { caseMetadataFileFilter = new FileNameExtensionFilter(NbBundle.getMessage(CaseOpenAction.class, "CaseOpenAction.autFilter.title", Version.getName(), CaseMetadata.getFileExtension()), CaseMetadata.getFileExtension().substring(1)); + fileChooserHelper = new JFileChooserFactory(); } /** @@ -80,7 +84,7 @@ public final class CaseOpenAction extends CallableSystemAction implements Action * to open the case described by the file. */ void openCaseSelectionWindow() { - JFileChooser fileChooser = new JFileChooser(); + JFileChooser fileChooser = fileChooserHelper.getChooser(); fileChooser.setDragEnabled(false); fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); fileChooser.setMultiSelectionEnabled(false); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePreferences.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePreferences.java index 9967f1fb68..8f749199f7 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePreferences.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePreferences.java @@ -35,22 +35,22 @@ import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; * Read and update case preference file values. */ public final class CasePreferences { - + private static final String SETTINGS_FILE = "CasePreferences.properties"; //NON-NLS private static final String KEY_GROUP_BY_DATA_SOURCE = "groupByDataSource"; //NON-NLS private static final String VALUE_TRUE = "true"; //NON-NLS private static final String VALUE_FALSE = "false"; //NON-NLS - + private static final Logger logger = Logger.getLogger(CasePreferences.class.getName()); - + private static Boolean groupItemsInTreeByDataSource = false; - + /** * Prevent instantiation. */ private CasePreferences() { } - + static { Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), (PropertyChangeEvent evt) -> { if (evt.getNewValue() != null) { @@ -66,25 +66,27 @@ public final class CasePreferences { logger.log(Level.SEVERE, "No current case open.", ex); } } - + /** * Get the 'groupItemsInTreeByDataSource' value. This can be true, false, or * null. - * + * * @return The value. */ public static Boolean getGroupItemsInTreeByDataSource() { return groupItemsInTreeByDataSource; } - + /** * Set the 'groupItemsInTreeByDataSource' value to true or false. - * + * * @param value The value to use for the value change. */ public static void setGroupItemsInTreeByDataSource(boolean value) { groupItemsInTreeByDataSource = value; - DirectoryTreeTopComponent.getDefault().refreshContentTreeSafe(); + if (Case.isCaseOpen()) { + DirectoryTreeTopComponent.getDefault().refreshContentTreeSafe(); + } } /** @@ -120,7 +122,7 @@ public final class CasePreferences { } } } - + /** * Reset all values to their default states. */ diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java b/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java index e6a7772c73..ec5fd85782 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CollaborationMonitor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2015-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -237,7 +237,7 @@ final class CollaborationMonitor { Content dataSource = event.getDataSource(); if (dataSource != null) { String status = NbBundle.getMessage(CollaborationMonitor.class, "CollaborationMonitor.analyzingDataSourceStatus.msg", hostName, dataSource.getName()); - jobIdsTodataSourceAnalysisTasks.put(event.getDataSourceIngestJobId(), new Task(++nextTaskId, status)); + jobIdsTodataSourceAnalysisTasks.put(event.getIngestJobId(), new Task(++nextTaskId, status)); eventPublisher.publishRemotely(new CollaborationEvent(hostName, getCurrentTasks())); } } @@ -250,7 +250,7 @@ final class CollaborationMonitor { * @param event A data source analysis completed event. */ synchronized void removeDataSourceAnalysisTask(DataSourceAnalysisCompletedEvent event) { - jobIdsTodataSourceAnalysisTasks.remove(event.getDataSourceIngestJobId()); + jobIdsTodataSourceAnalysisTasks.remove(event.getIngestJobId()); eventPublisher.publishRemotely(new CollaborationEvent(hostName, getCurrentTasks())); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java index 207b83ed8a..9b174f28f7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2018 Basis Technology Corp. + * Copyright 2013-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; import org.sleuthkit.autopsy.ingest.IngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestStream; +import org.sleuthkit.datamodel.Host; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitJNI; import org.sleuthkit.datamodel.TskCoreException; @@ -50,8 +51,7 @@ import org.sleuthkit.datamodel.TskCoreException; * independently of the wizard. */ @ServiceProviders(value = { - @ServiceProvider(service = DataSourceProcessor.class) - , + @ServiceProvider(service = DataSourceProcessor.class), @ServiceProvider(service = AutoIngestDataSourceProcessor.class)} ) public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSourceProcessor { @@ -81,7 +81,7 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour private String md5; private String sha1; private String sha256; - private boolean setDataSourceOptionsCalled; + private Host host = null; static { filtersList.add(allFilter); @@ -181,50 +181,96 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour */ @Override public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - ingestStream = new DefaultIngestStream(); - readConfigSettings(); - try { - image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), - new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex); - final List errors = new ArrayList<>(); - errors.add(ex.getMessage()); - callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>()); - return; - } - - doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, progressMonitor, callback); + run(null, progressMonitor, callback); } - + /** * Adds a data source to the case database using a background task in a * separate thread and the settings provided by the selection and - * configuration panel. Files found during ingest will be sent directly to the - * IngestStream provided. Returns as soon as the background task is started. + * configuration panel. Returns as soon as the background task is started. * The background task uses a callback object to signal task completion and * return results. * - * This method should not be called unless isPanelValid returns true, and - * should only be called for DSPs that support ingest streams. - * - * @param settings The ingest job settings. - * @param progress Progress monitor that will be used by the + * This method should not be called unless isPanelValid returns true. + * + * @param host Host for this data source. + * @param progressMonitor Progress monitor that will be used by the * background task to report progress. - * @param callBack Callback that will be used by the background task + * @param callback Callback that will be used by the background task * to return results. */ @Override - public void runWithIngestStream(IngestJobSettings settings, DataSourceProcessorProgressMonitor progress, + public void run(Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + ingestStream = new DefaultIngestStream(); + readConfigSettings(); + this.host = host; + try { + image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), + new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, this.host); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex); + final List errors = new ArrayList<>(); + errors.add(ex.getMessage()); + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>()); + return; + } + + doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, progressMonitor, callback); + } + + /** + * Adds a data source to the case database using a background task in a + * separate thread and the settings provided by the selection and + * configuration panel. Files found during ingest will be sent directly to + * the IngestStream provided. Returns as soon as the background task is + * started. The background task uses a callback object to signal task + * completion and return results. + * + * This method should not be called unless isPanelValid returns true, and + * should only be called for DSPs that support ingest streams. + * + * @param settings The ingest job settings. + * @param progress Progress monitor that will be used by the background task + * to report progress. + * @param callBack Callback that will be used by the background task to + * return results. + */ + @Override + public void runWithIngestStream(IngestJobSettings settings, DataSourceProcessorProgressMonitor progress, DataSourceProcessorCallback callBack) { - + runWithIngestStream(null, settings, progress, callBack); + } + + /** + * Adds a data source to the case database using a background task in a + * separate thread and the settings provided by the selection and + * configuration panel. Files found during ingest will be sent directly to + * the IngestStream provided. Returns as soon as the background task is + * started. The background task uses a callback object to signal task + * completion and return results. + * + * This method should not be called unless isPanelValid returns true, and + * should only be called for DSPs that support ingest streams. + * + * @param host The host for this data source. + * @param settings The ingest job settings. + * @param progress Progress monitor that will be used by the background task + * to report progress. + * @param callBack Callback that will be used by the background task to + * return results. + */ + @Override + public void runWithIngestStream(Host host, IngestJobSettings settings, DataSourceProcessorProgressMonitor progress, + DataSourceProcessorCallback callBack) { + // Read the settings from the wizard readConfigSettings(); - + this.host = host; + // Set up the data source before creating the ingest stream try { image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), - new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId); + new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, this.host); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex); final List errors = new ArrayList<>(); @@ -238,51 +284,48 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour ingestStream = IngestManager.getInstance().openIngestStream(image, settings); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error starting ingest modules", ex); - final List errors = new ArrayList<>(); - errors.add(ex.getMessage()); - callBack.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>()); - return; + // There was an error with ingest, but the data source has already been added + // so proceed with the defaultIngestStream. Code in openIngestStream + // should have caused a dialog to popup with the errors. + ingestStream = new DefaultIngestStream(); } - doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, progress, callBack); } - + /** * Store the options from the config panel. */ private void readConfigSettings() { - if (!setDataSourceOptionsCalled) { - configPanel.storeSettings(); - deviceId = UUID.randomUUID().toString(); - imagePath = configPanel.getContentPaths(); - sectorSize = configPanel.getSectorSize(); - timeZone = configPanel.getTimeZone(); - ignoreFatOrphanFiles = configPanel.getNoFatOrphans(); - md5 = configPanel.getMd5(); - if (md5.isEmpty()) { - md5 = null; - } - sha1 = configPanel.getSha1(); - if (sha1.isEmpty()) { - sha1 = null; - } - sha256 = configPanel.getSha256(); - if (sha256.isEmpty()) { - sha256 = null; - } + configPanel.storeSettings(); + deviceId = UUID.randomUUID().toString(); + imagePath = configPanel.getContentPaths(); + sectorSize = configPanel.getSectorSize(); + timeZone = configPanel.getTimeZone(); + ignoreFatOrphanFiles = configPanel.getNoFatOrphans(); + md5 = configPanel.getMd5(); + if (md5.isEmpty()) { + md5 = null; + } + sha1 = configPanel.getSha1(); + if (sha1.isEmpty()) { + sha1 = null; + } + sha256 = configPanel.getSha256(); + if (sha256.isEmpty()) { + sha256 = null; } } - + /** * Check if this DSP supports ingest streams. - * + * * @return True if this DSP supports an ingest stream, false otherwise. */ @Override public boolean supportsIngestStream() { return true; - } + } /** * Adds a data source to the case database using a background task in a @@ -309,11 +352,11 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour ingestStream = new DefaultIngestStream(); try { image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), - new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId); + new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex); final List errors = new ArrayList<>(); - errors.add(ex.getMessage()); + errors.add(ex.getMessage()); callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>()); return; } @@ -327,10 +370,10 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour * selection and configuration panel. Returns as soon as the background task * is started and uses the callback object to signal task completion and * return results. - * - * The image should be loaded in the database and stored in "image" - * before calling this method. Additionally, an ingest stream should be initialized - * and stored in "ingestStream". + * + * The image should be loaded in the database and stored in "image" before + * calling this method. Additionally, an ingest stream should be initialized + * and stored in "ingestStream". * * @param deviceId An ASCII-printable identifier for the device * associated with the data source that is @@ -352,28 +395,28 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour */ private void doAddImageProcess(String deviceId, String imagePath, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - // If the data source or ingest stream haven't been initialized, stop processing - if (ingestStream == null) { - String message = "Ingest stream was not initialized before running the add image process on " + imagePath; - logger.log(Level.SEVERE, message); - final List errors = new ArrayList<>(); - errors.add(message); - callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>()); - return; - } - if (image == null) { - String message = "Image was not added to database before running the add image process on " + imagePath; - logger.log(Level.SEVERE, message); - final List errors = new ArrayList<>(); - errors.add(message); - callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>()); - return; - } + // If the data source or ingest stream haven't been initialized, stop processing + if (ingestStream == null) { + String message = "Ingest stream was not initialized before running the add image process on " + imagePath; + logger.log(Level.SEVERE, message); + final List errors = new ArrayList<>(); + errors.add(message); + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>()); + return; + } + if (image == null) { + String message = "Image was not added to database before running the add image process on " + imagePath; + logger.log(Level.SEVERE, message); + final List errors = new ArrayList<>(); + errors.add(message); + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>()); + return; + } - AddImageTask.ImageDetails imageDetails = new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, null); - addImageTask = new AddImageTask(imageDetails, - progressMonitor, - new StreamingAddDataSourceCallbacks(ingestStream), + AddImageTask.ImageDetails imageDetails = new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, null); + addImageTask = new AddImageTask(imageDetails, + progressMonitor, + new StreamingAddDataSourceCallbacks(ingestStream), new StreamingAddImageTaskCallback(ingestStream, callback)); new Thread(addImageTask).start(); } @@ -405,8 +448,8 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour imagePath = null; timeZone = null; ignoreFatOrphanFiles = false; + host = null; configPanel.reset(); - setDataSourceOptionsCalled = false; } private static boolean isAcceptedByFiler(File file, List filters) { @@ -428,7 +471,6 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour try { // verify that the image has a file system that TSK can process - Case currentCase = Case.getCurrentCaseThrows(); if (!DataSourceUtils.imageHasFileSystem(dataSourcePath)) { // image does not have a file system that TSK can process return 0; @@ -443,41 +485,53 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour @Override public void process(String deviceId, Path dataSourcePath, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) { + process(deviceId, dataSourcePath, null, progressMonitor, callBack); + } + + @Override + public void process(String deviceId, Path dataSourcePath, Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) { + // this method does not use the config panel this.deviceId = deviceId; this.imagePath = dataSourcePath.toString(); this.sectorSize = 0; this.timeZone = Calendar.getInstance().getTimeZone().getID(); + this.host = host; this.ignoreFatOrphanFiles = false; - setDataSourceOptionsCalled = true; - + ingestStream = new DefaultIngestStream(); try { image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), - new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId); + new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId, host); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex); final List errors = new ArrayList<>(); - errors.add(ex.getMessage()); + errors.add(ex.getMessage()); callBack.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>()); return; } - + doAddImageProcess(deviceId, dataSourcePath.toString(), sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, progressMonitor, callBack); } - + @Override public IngestStream processWithIngestStream(String deviceId, Path dataSourcePath, IngestJobSettings settings, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) { + return processWithIngestStream(deviceId, dataSourcePath, null, settings, progressMonitor, callBack); + } + + @Override + public IngestStream processWithIngestStream(String deviceId, Path dataSourcePath, Host host, IngestJobSettings settings, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) { + // this method does not use the config panel this.deviceId = deviceId; this.imagePath = dataSourcePath.toString(); this.sectorSize = 0; this.timeZone = Calendar.getInstance().getTimeZone().getID(); + this.host = host; this.ignoreFatOrphanFiles = false; - setDataSourceOptionsCalled = true; - + // Set up the data source before creating the ingest stream try { image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), - new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId); + new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, host); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex); final List errors = new ArrayList<>(); @@ -495,33 +549,10 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour errors.add(ex.getMessage()); callBack.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>()); return null; - } - + } + doAddImageProcess(deviceId, dataSourcePath.toString(), sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, progressMonitor, callBack); + return ingestStream; } - - /** - * Sets the configuration of the data source processor without using the - * selection and configuration panel. - * - * @param imagePath Path to the image file. - * @param timeZone The time zone to use when processing dates - * and times for the image, obtained from - * java.util.TimeZone.getID. - * @param ignoreFatOrphanFiles Whether to parse orphans if the image has a - * FAT filesystem. - * - * @deprecated Use the provided overload of the run method instead. - */ - @Deprecated - public void setDataSourceOptions(String imagePath, String timeZone, boolean ignoreFatOrphanFiles) { - this.deviceId = UUID.randomUUID().toString(); - this.imagePath = imagePath; - this.sectorSize = 0; - this.timeZone = Calendar.getInstance().getTimeZone().getID(); - this.ignoreFatOrphanFiles = ignoreFatOrphanFiles; - setDataSourceOptionsCalled = true; - } - } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java index 76f328a825..eda7149f66 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,6 +35,7 @@ import org.sleuthkit.autopsy.coreutils.DriveUtils; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.PathValidator; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; import org.sleuthkit.datamodel.HashUtility; /** @@ -48,8 +49,10 @@ public class ImageFilePanel extends JPanel implements DocumentListener { private static final long serialVersionUID = 1L; private static final String PROP_LASTIMAGE_PATH = "LBL_LastImage_PATH"; //NON-NLS private static final String[] SECTOR_SIZE_CHOICES = {"Auto Detect", "512", "1024", "2048", "4096"}; - private final JFileChooser fileChooser = new JFileChooser(); + private final JFileChooserFactory fileChooserHelper = new JFileChooserFactory(); + private JFileChooser fileChooser; private final String contextName; + private final List fileChooserFilters; /** * Creates new form ImageFilePanel @@ -73,14 +76,7 @@ public class ImageFilePanel extends JPanel implements DocumentListener { sectorSizeComboBox.setSelectedIndex(0); errorLabel.setVisible(false); - - fileChooser.setDragEnabled(false); - fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); - fileChooser.setMultiSelectionEnabled(false); - fileChooserFilters.forEach(fileChooser::addChoosableFileFilter); - if (fileChooserFilters.isEmpty() == false) { - fileChooser.setFileFilter(fileChooserFilters.get(0)); - } + this.fileChooserFilters = fileChooserFilters; } /** @@ -132,6 +128,21 @@ public class ImageFilePanel extends JPanel implements DocumentListener { private JTextField getSha256TextField() { return sha256HashTextField; } + + private JFileChooser getChooser() { + if(fileChooser == null) { + fileChooser = fileChooserHelper.getChooser(); + fileChooser.setDragEnabled(false); + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + fileChooser.setMultiSelectionEnabled(false); + fileChooserFilters.forEach(fileChooser::addChoosableFileFilter); + if (fileChooserFilters.isEmpty() == false) { + fileChooser.setFileFilter(fileChooserFilters.get(0)); + } + } + + return fileChooser; + } /** * This method is called from within the constructor to initialize the form. @@ -298,12 +309,13 @@ public class ImageFilePanel extends JPanel implements DocumentListener { String oldText = getContentPaths(); // set the current directory of the FileChooser if the ImagePath Field is valid File currentDir = new File(oldText); + JFileChooser chooser = getChooser(); if (currentDir.exists()) { - fileChooser.setCurrentDirectory(currentDir); + chooser.setCurrentDirectory(currentDir); } - if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { - String path = fileChooser.getSelectedFile().getPath(); + if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { + String path = chooser.getSelectedFile().getPath(); if (path.endsWith(".001")) { String zeroX3_path = StringUtils.removeEnd(path, ".001") + ".000"; if (new File(zeroX3_path).exists()) { @@ -469,7 +481,7 @@ public class ImageFilePanel extends JPanel implements DocumentListener { return false; } - if (!PathValidator.isValidForMultiUserCase(path, Case.getCurrentCase().getCaseType())) { + if (!PathValidator.isValidForCaseType(path, Case.getCurrentCase().getCaseType())) { errorLabel.setVisible(true); errorLabel.setText(Bundle.ImageFilePanel_validatePanel_dataSourceOnCDriveError()); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.form index c183e0de36..4ec9df679e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.form @@ -1,11 +1,6 @@
- - - - - @@ -16,6 +11,7 @@ + @@ -35,9 +31,6 @@ - - - @@ -60,9 +53,6 @@ - - - @@ -85,6 +75,12 @@ + + + + + + @@ -157,6 +153,12 @@ + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java index 9faa17f51c..300cce64c7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2016-2019 Basis Technology Corp. + * Copyright 2016-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,8 +26,12 @@ import java.util.Date; import java.util.EnumSet; import java.util.List; import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; import javax.swing.event.ListSelectionEvent; import javax.swing.table.AbstractTableModel; import org.openide.util.NbBundle.Messages; @@ -50,13 +54,14 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(IngestJobInfoPanel.class.getName()); private static final Set INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.STARTED, IngestManager.IngestJobEvent.CANCELLED, IngestManager.IngestJobEvent.COMPLETED); private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE); - - private List ingestJobs; + private static final int EXTRA_ROW_HEIGHT = 4; + private final List ingestJobs = new ArrayList<>(); private final List ingestJobsForSelectedDataSource = new ArrayList<>(); private IngestJobTableModel ingestJobTableModel = new IngestJobTableModel(); private IngestModuleTableModel ingestModuleTableModel = new IngestModuleTableModel(null); private final DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); private DataSource selectedDataSource; + private static SwingWorker refreshWorker = null; /** * Creates new form IngestJobInfoPanel @@ -76,23 +81,33 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel { this.ingestModuleTable.setModel(this.ingestModuleTableModel); }); - IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST , (PropertyChangeEvent evt) -> { + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, (PropertyChangeEvent evt) -> { if (evt.getPropertyName().equals(IngestManager.IngestJobEvent.STARTED.toString()) || evt.getPropertyName().equals(IngestManager.IngestJobEvent.CANCELLED.toString()) || evt.getPropertyName().equals(IngestManager.IngestJobEvent.COMPLETED.toString())) { refresh(); } }); - + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, (PropertyChangeEvent evt) -> { if (!(evt instanceof AutopsyEvent) || (((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.LOCAL)) { return; } - - if (CURRENT_CASE == Case.Events.valueOf(evt.getPropertyName())) { - refresh(); + + // Check whether we have a case open or case close event. + if ((CURRENT_CASE == Case.Events.valueOf(evt.getPropertyName()))) { + if (evt.getNewValue() != null) { + // Case open + refresh(); + } else { + // Case close + reset(); + } } }); + ingestJobTable.setRowHeight(ingestJobTable.getRowHeight() + EXTRA_ROW_HEIGHT); + ingestModuleTable.setRowHeight(ingestModuleTable.getRowHeight() + EXTRA_ROW_HEIGHT); + } /** @@ -111,32 +126,69 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel { } } this.ingestJobTableModel = new IngestJobTableModel(); - this.ingestJobTable.setModel(ingestJobTableModel); - //if there were ingest jobs select the first one by default - if (!ingestJobsForSelectedDataSource.isEmpty()) { - ingestJobTable.setRowSelectionInterval(0, 0); - } - this.repaint(); + + SwingUtilities.invokeLater(() -> { + this.ingestJobTable.setModel(ingestJobTableModel); + //if there were ingest jobs select the first one by default + if (!ingestJobsForSelectedDataSource.isEmpty()) { + ingestJobTable.setRowSelectionInterval(0, 0); + } + this.repaint(); + }); } /** * Get the updated complete list of ingest jobs. */ private void refresh() { - try { - if (Case.isCaseOpen()) { - SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - this.ingestJobs = skCase.getIngestJobs(); - setDataSource(selectedDataSource); - } else { - this.ingestJobs = new ArrayList<>(); - setDataSource(null); - } - - } catch (TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Failed to load ingest jobs.", ex); - JOptionPane.showMessageDialog(this, Bundle.IngestJobInfoPanel_loadIngestJob_error_text(), Bundle.IngestJobInfoPanel_loadIngestJob_error_title(), JOptionPane.ERROR_MESSAGE); + if (refreshWorker != null && !refreshWorker.isDone()) { + refreshWorker.cancel(true); } + refreshWorker = new SwingWorker() { + + @Override + protected Boolean doInBackground() throws Exception { + ingestJobs.clear(); + try { + if (Case.isCaseOpen()) { // Note - this will generally return true when handling a case close event + SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + ingestJobs.addAll(skCase.getIngestJobs()); + setDataSource(selectedDataSource); + } else { + setDataSource(null); + } + return true; + } catch (TskCoreException | NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Failed to load ingest jobs.", ex); + return false; + } + } + + @Override + protected void done() { + try { + if (!get()) { + JOptionPane.showMessageDialog(IngestJobInfoPanel.this, Bundle.IngestJobInfoPanel_loadIngestJob_error_text(), Bundle.IngestJobInfoPanel_loadIngestJob_error_title(), JOptionPane.ERROR_MESSAGE); + } + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.WARNING, "Error getting results from Ingest Job Info Panel's refresh worker", ex); + } catch (CancellationException ignored) { + logger.log(Level.INFO, "The refreshing of the IngestJobInfoPanel was cancelled"); + } + } + }; + refreshWorker.execute(); + } + + /** + * Reset the panel. + */ + private void reset() { + if (refreshWorker != null) { + refreshWorker.cancel(true); + } + this.ingestJobs.clear(); + setDataSource(null); } @Messages({"IngestJobInfoPanel.IngestJobTableModel.StartTime.header=Start Time", @@ -252,19 +304,18 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel { javax.swing.JScrollPane ingestModulesScrollPane = new javax.swing.JScrollPane(); ingestModuleTable = new javax.swing.JTable(); - setMaximumSize(new java.awt.Dimension(32767, 32767)); setLayout(new java.awt.BorderLayout()); - contentPanel.setMaximumSize(new java.awt.Dimension(32767, 32767)); contentPanel.setMinimumSize(new java.awt.Dimension(625, 150)); contentPanel.setPreferredSize(new java.awt.Dimension(625, 150)); contentPanel.setLayout(new java.awt.GridBagLayout()); ingestJobsScrollPane.setBorder(null); ingestJobsScrollPane.setMinimumSize(new java.awt.Dimension(16, 16)); - ingestJobsScrollPane.setPreferredSize(null); ingestJobTable.setModel(ingestJobTableModel); + ingestJobTable.setGridColor(javax.swing.UIManager.getDefaults().getColor("InternalFrame.borderColor")); + ingestJobTable.setIntercellSpacing(new java.awt.Dimension(4, 2)); ingestJobTable.getTableHeader().setReorderingAllowed(false); ingestJobsScrollPane.setViewportView(ingestJobTable); ingestJobTable.getColumnModel().getSelectionModel().setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); @@ -300,6 +351,8 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel { ingestModulesScrollPane.setPreferredSize(new java.awt.Dimension(254, 16)); ingestModuleTable.setModel(ingestModuleTableModel); + ingestModuleTable.setGridColor(javax.swing.UIManager.getDefaults().getColor("InternalFrame.borderColor")); + ingestModuleTable.setIntercellSpacing(new java.awt.Dimension(4, 2)); ingestModulesScrollPane.setViewportView(ingestModuleTable); gridBagConstraints = new java.awt.GridBagConstraints(); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java index 0551d15466..c659727d74 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2018 Basis Technology Corp. + * Copyright 2013-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.casemodule; import java.util.ArrayList; -import java.util.Calendar; import java.util.List; import java.util.UUID; import java.util.logging.Level; @@ -31,6 +30,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgress import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings; +import org.sleuthkit.datamodel.Host; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitJNI; import org.sleuthkit.datamodel.TskCoreException; @@ -56,9 +56,9 @@ public class LocalDiskDSProcessor implements DataSourceProcessor { private String drivePath; private int sectorSize; private String timeZone; + private Host host; private ImageWriterSettings imageWriterSettings; private boolean ignoreFatOrphanFiles; - private boolean setDataSourceOptionsCalled; /** * Constructs a local drive data source processor that implements the @@ -135,36 +135,56 @@ public class LocalDiskDSProcessor implements DataSourceProcessor { */ @Override public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - if (!setDataSourceOptionsCalled) { - deviceId = UUID.randomUUID().toString(); - drivePath = configPanel.getContentPath(); - sectorSize = configPanel.getSectorSize(); - timeZone = configPanel.getTimeZone(); - ignoreFatOrphanFiles = configPanel.getNoFatOrphans(); - if (configPanel.getImageWriterEnabled()) { - imageWriterSettings = configPanel.getImageWriterSettings(); - } else { - imageWriterSettings = null; - } + run(null, progressMonitor, callback); + } + + /** + * Adds a data source to the case database using a background task in a + * separate thread and the settings provided by the selection and + * configuration panel. Returns as soon as the background task is started. + * The background task uses a callback object to signal task completion and + * return results. + * + * This method should not be called unless isPanelValid returns true. + * + * @param host Host for this data source. + * @param progressMonitor Progress monitor that will be used by the + * background task to report progress. + * @param callback Callback that will be used by the background task + * to return results. + */ + @Override + public void run(Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + deviceId = UUID.randomUUID().toString(); + drivePath = configPanel.getContentPath(); + sectorSize = configPanel.getSectorSize(); + timeZone = configPanel.getTimeZone(); + ignoreFatOrphanFiles = configPanel.getNoFatOrphans(); + if (configPanel.getImageWriterEnabled()) { + imageWriterSettings = configPanel.getImageWriterSettings(); + } else { + imageWriterSettings = null; } + this.host = host; + Image image; try { image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), - new String[]{drivePath}, sectorSize, - timeZone, null, null, null, deviceId); + new String[]{drivePath}, sectorSize, + timeZone, null, null, null, deviceId, this.host); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding local disk with path " + drivePath + " to database", ex); final List errors = new ArrayList<>(); errors.add(ex.getMessage()); callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>()); return; - } + } addDiskTask = new AddImageTask( - new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings), + new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings), progressMonitor, - new StreamingAddDataSourceCallbacks(new DefaultIngestStream()), + new StreamingAddDataSourceCallbacks(new DefaultIngestStream()), new StreamingAddImageTaskCallback(new DefaultIngestStream(), callback)); new Thread(addDiskTask).start(); } @@ -220,19 +240,19 @@ public class LocalDiskDSProcessor implements DataSourceProcessor { Image image; try { image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), - new String[]{drivePath}, sectorSize, - timeZone, null, null, null, deviceId); + new String[]{drivePath}, sectorSize, + timeZone, null, null, null, deviceId); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding local disk with path " + drivePath + " to database", ex); final List errors = new ArrayList<>(); errors.add(ex.getMessage()); callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>()); return; - } - - addDiskTask = new AddImageTask(new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings), - progressMonitor, - new StreamingAddDataSourceCallbacks(new DefaultIngestStream()), + } + + addDiskTask = new AddImageTask(new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings), + progressMonitor, + new StreamingAddDataSourceCallbacks(new DefaultIngestStream()), new StreamingAddImageTaskCallback(new DefaultIngestStream(), callback)); new Thread(addDiskTask).start(); } @@ -261,30 +281,5 @@ public class LocalDiskDSProcessor implements DataSourceProcessor { drivePath = null; timeZone = null; ignoreFatOrphanFiles = false; - setDataSourceOptionsCalled = false; } - - /** - * Sets the configuration of the data source processor without using the - * configuration panel. - * - * @param drivePath Path to the local drive. - * @param timeZone The time zone to use when processing dates - * and times for the local drive, obtained from - * java.util.TimeZone.getID. - * @param ignoreFatOrphanFiles Whether to parse orphans if the image has a - * FAT filesystem. - * - * @deprecated Use the provided overload of the run method instead. - */ - @Deprecated - public void setDataSourceOptions(String drivePath, String timeZone, boolean ignoreFatOrphanFiles) { - this.deviceId = UUID.randomUUID().toString(); - this.drivePath = drivePath; - this.sectorSize = 0; - this.timeZone = Calendar.getInstance().getTimeZone().getID(); - this.ignoreFatOrphanFiles = ignoreFatOrphanFiles; - setDataSourceOptionsCalled = true; - } - } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index 469b93da66..b0031ed1c7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings; /** @@ -58,7 +59,8 @@ final class LocalDiskPanel extends JPanel { private static final long serialVersionUID = 1L; private LocalDisk localDisk; private boolean enableNext = false; - private final JFileChooser fc = new JFileChooser(); + private JFileChooser fc; + private final JFileChooserFactory chooserHelper; /** * Creates new form LocalDiskPanel @@ -68,6 +70,7 @@ final class LocalDiskPanel extends JPanel { customInit(); createTimeZoneList(); createSectorSizeList(); + chooserHelper = new JFileChooserFactory(); } /** @@ -261,6 +264,7 @@ final class LocalDiskPanel extends JPanel { }//GEN-LAST:event_pathTextFieldKeyReleased private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed + fc = chooserHelper.getChooser(); String oldText = pathTextField.getText(); // set the current directory of the FileChooser if the ImagePath Field is valid File currentFile = new File(oldText); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java index 4d5e3eac36..d75fabbae9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesDSProcessor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,6 +42,8 @@ import org.sleuthkit.autopsy.coreutils.ExecUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; +import org.sleuthkit.datamodel.Host; +import org.sleuthkit.datamodel.TskCoreException; /** * A local/logical files/logical evidence file(.lo1)/or directories data source @@ -76,7 +78,6 @@ public class LocalFilesDSProcessor implements DataSourceProcessor, AutoIngestDat * when the deprecated method setDataSourceOptions is removed. */ private List localFilePaths; - private boolean setDataSourceOptionsCalled; /** * Constructs a local/logical files and/or directories data source processor @@ -153,25 +154,44 @@ public class LocalFilesDSProcessor implements DataSourceProcessor, AutoIngestDat */ @Override public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - if (!setDataSourceOptionsCalled) { - localFilePaths = configPanel.getContentPaths(); - if (configPanel.subTypeIsLogicalEvidencePanel()) { - try { - //if the L01 option was chosen - localFilePaths = extractLogicalEvidenceFileContents(localFilePaths); - } catch (L01Exception ex) { - //contents of l01 could not be extracted don't add data source or run ingest - final List errors = new ArrayList<>(); - errors.add(ex.getMessage()); - callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>()); - return; - } catch (NoCurrentCaseException ex) { - logger.log(Level.WARNING, "Exception while getting open case.", ex); - return; - } + run(null, progressMonitor, callback); + } + + /** + * Adds a data source to the case database using a background task in a + * separate thread and the settings provided by the selection and + * configuration panel. Returns as soon as the background task is started. + * The background task uses a callback object to signal task completion and + * return results. + * + * This method should not be called unless isPanelValid returns true. + * + * @param host Host for this data source. + * @param progressMonitor Progress monitor that will be used by the + * background task to report progress. + * @param callback Callback that will be used by the background task + * to return results. + */ + @Override + public void run(Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + + localFilePaths = configPanel.getContentPaths(); + if (configPanel.subTypeIsLogicalEvidencePanel()) { + try { + //if the L01 option was chosen + localFilePaths = extractLogicalEvidenceFileContents(localFilePaths); + } catch (L01Exception ex) { + //contents of l01 could not be extracted don't add data source or run ingest + final List errors = new ArrayList<>(); + errors.add(ex.getMessage()); + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>()); + return; + } catch (NoCurrentCaseException ex) { + logger.log(Level.WARNING, "Exception while getting open case.", ex); + return; } } - run(UUID.randomUUID().toString(), configPanel.getFileSetName(), localFilePaths, progressMonitor, callback); + run(UUID.randomUUID().toString(), configPanel.getFileSetName(), localFilePaths, host, progressMonitor, callback); } /** @@ -197,7 +217,7 @@ public class LocalFilesDSProcessor implements DataSourceProcessor, AutoIngestDat command.add("-f"); command.add("files"); command.add("-t"); - File l01Dir = new File(Case.getCurrentCaseThrows().getModuleDirectory(), L01_EXTRACTION_DIR); + File l01Dir = new File(Case.getCurrentCaseThrows().getModuleDirectory(), L01_EXTRACTION_DIR); if (!l01Dir.exists()) { l01Dir.mkdirs(); } @@ -285,6 +305,34 @@ public class LocalFilesDSProcessor implements DataSourceProcessor, AutoIngestDat return executablePath; } + /** + * Adds a data source to the case database using a background task in a + * separate thread and the given settings instead of those provided by the + * selection and configuration panel. Returns as soon as the background task + * is started and uses the callback object to signal task completion and + * return results. + * + * @param deviceId An ASCII-printable identifier for the + * device associated with the data source + * that is intended to be unique across + * multiple cases (e.g., a UUID). + * @param rootVirtualDirectoryName The name to give to the virtual directory + * that will serve as the root for the + * local/logical files and/or directories + * that compose the data source. Pass the + * empty string to get a default name of the + * form: LogicalFileSet[N] + * @param localFilePaths A list of local/logical file and/or + * directory localFilePaths. + * @param host The host for this data source. + * @param progressMonitor Progress monitor for reporting progress + * during processing. + * @param callback Callback to call when processing is done. + */ + public void run(String deviceId, String rootVirtualDirectoryName, List localFilePaths, Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + new Thread(new AddLocalFilesTask(deviceId, rootVirtualDirectoryName, localFilePaths, host, progressMonitor, callback)).start(); + } + /** * Adds a data source to the case database using a background task in a * separate thread and the given settings instead of those provided by the @@ -309,7 +357,7 @@ public class LocalFilesDSProcessor implements DataSourceProcessor, AutoIngestDat * @param callback Callback to call when processing is done. */ public void run(String deviceId, String rootVirtualDirectoryName, List localFilePaths, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - new Thread(new AddLocalFilesTask(deviceId, rootVirtualDirectoryName, localFilePaths, progressMonitor, callback)).start(); + run(deviceId, rootVirtualDirectoryName, localFilePaths, null, progressMonitor, callback); } /** @@ -334,7 +382,6 @@ public class LocalFilesDSProcessor implements DataSourceProcessor, AutoIngestDat public void reset() { configPanel.select(); localFilePaths = null; - setDataSourceOptionsCalled = false; } @Override @@ -368,29 +415,13 @@ public class LocalFilesDSProcessor implements DataSourceProcessor, AutoIngestDat @Override public void process(String deviceId, Path dataSourcePath, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) { - List filePaths = Arrays.asList(new String[]{dataSourcePath.toString()}); - run(deviceId, "", filePaths, progressMonitor, callBack); + process(deviceId, dataSourcePath, null, progressMonitor, callBack); } - /** - * Sets the configuration of the data source processor without using the - * configuration panel. The data source processor will assign a UUID to the - * data source and will use the time zone of the machine executing this code - * when when processing dates and times for the image. - * - * @param paths A list of local/logical file and/or directory - * localFilePaths. - * - * @deprecated Use the provided overload of the run method instead. - */ - @Deprecated - public void setDataSourceOptions(String paths) { - // The LocalFilesPanel used to separate file paths with a comma and pass - // them as a string, but because file names are allowed to contain - // commas, this approach was buggy and replaced. We now pass a list of - // String paths. - this.localFilePaths = Arrays.asList(paths.split(",")); - setDataSourceOptionsCalled = true; + @Override + public void process(String deviceId, Path dataSourcePath, Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) { + List filePaths = Arrays.asList(new String[]{dataSourcePath.toString()}); + run(deviceId, "", filePaths, host, progressMonitor, callBack); } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java index 6703b16dc5..d55dab831e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java @@ -290,7 +290,7 @@ final class LocalFilesPanel extends javax.swing.JPanel { final Case.CaseType currentCaseType = Case.getCurrentCaseThrows().getCaseType(); for (String currentPath : pathsList) { - if (!PathValidator.isValidForMultiUserCase(currentPath, currentCaseType)) { + if (!PathValidator.isValidForCaseType(currentPath, currentCaseType)) { errorLabel.setVisible(true); errorLabel.setText(Bundle.LocalFilesPanel_pathValidation_dataSourceOnCDriveError()); return; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalEvidenceFilePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalEvidenceFilePanel.java index 103fd8fd6c..6d45be07b9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalEvidenceFilePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalEvidenceFilePanel.java @@ -191,7 +191,7 @@ final class LogicalEvidenceFilePanel extends javax.swing.JPanel implements Docum } // display warning if there is one (but don't disable "next" button) try { - if (!PathValidator.isValidForMultiUserCase(path, Case.getCurrentCaseThrows().getCaseType())) { + if (!PathValidator.isValidForCaseType(path, Case.getCurrentCaseThrows().getCaseType())) { errorLabel.setVisible(true); errorLabel.setText(Bundle.LogicalEvidenceFilePanel_pathValidation_dataSourceOnCDriveError()); return false; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java index 201d25ecef..e4edc6e7bb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012-2018 Basis Technology Corp. + * Copyright 2012-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,6 +32,7 @@ import org.openide.util.NbBundle; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.DriveUtils; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; @@ -45,7 +46,8 @@ class MissingImageDialog extends javax.swing.JDialog { long obj_id; SleuthkitCase db; - private final JFileChooser fileChooser = new JFileChooser(); + private JFileChooser fileChooser; + private final JFileChooserFactory chooserHelper; /** * Instantiate a MissingImageDialog. @@ -58,17 +60,8 @@ class MissingImageDialog extends javax.swing.JDialog { this.obj_id = obj_id; this.db = db; initComponents(); - - fileChooser.setDragEnabled(false); - fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); - fileChooser.setMultiSelectionEnabled(false); - - List fileFiltersList = ImageDSProcessor.getFileFiltersList(); - for (FileFilter fileFilter : fileFiltersList) { - fileChooser.addChoosableFileFilter(fileFilter); - } - fileChooser.setFileFilter(fileFiltersList.get(0)); - + + chooserHelper = new JFileChooserFactory(); selectButton.setEnabled(false); } @@ -270,6 +263,19 @@ class MissingImageDialog extends javax.swing.JDialog { private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed + if(fileChooser == null) { + fileChooser = chooserHelper.getChooser(); + fileChooser.setDragEnabled(false); + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + fileChooser.setMultiSelectionEnabled(false); + + List fileFiltersList = ImageDSProcessor.getFileFiltersList(); + for (FileFilter fileFilter : fileFiltersList) { + fileChooser.addChoosableFileFilter(fileFilter); + } + fileChooser.setFileFilter(fileFiltersList.get(0)); + } + String oldText = pathNameTextField.getText(); lbWarning.setText(""); // set the current directory of the FileChooser if the ImagePath Field is valid diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java index b9ac003d02..64def92cc2 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,9 +28,9 @@ import javax.swing.JPanel; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.sleuthkit.autopsy.casemodule.Case.CaseType; -import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.PathValidator; import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; /** * The JPanel for the first page of the new case wizard. @@ -38,7 +38,7 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil; @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class NewCaseVisualPanel1 extends JPanel implements DocumentListener { - private final JFileChooser fileChooser = new JFileChooser(); + private final JFileChooserFactory fileChooserHelper = new JFileChooserFactory(); private final NewCaseWizardPanel1 wizPanel; /** @@ -149,13 +149,22 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener { private void validateSettings() { /** * Check the base case directory for the selected case type and show a - * warning if it is a dubious choice. + * warning if it is a dubious choice. For single user cases, disable + * the "Next" button if path is invalid. */ caseParentDirWarningLabel.setVisible(false); String parentDir = getCaseParentDir(); - if (!PathValidator.isValidForMultiUserCase(parentDir, getCaseType())) { - caseParentDirWarningLabel.setVisible(true); - caseParentDirWarningLabel.setText(NbBundle.getMessage(this.getClass(), "NewCaseVisualPanel1.CaseFolderOnCDriveError.text")); + if (!PathValidator.isValidForCaseType(parentDir, getCaseType())) { + if (getCaseType() == CaseType.MULTI_USER_CASE) { + caseParentDirWarningLabel.setVisible(true); + caseParentDirWarningLabel.setText(NbBundle.getMessage(this.getClass(), "NewCaseVisualPanel1.CaseFolderOnCDriveError.text")); + } else { + // disable the "Next" button + caseParentDirWarningLabel.setVisible(true); + caseParentDirWarningLabel.setText(NbBundle.getMessage(this.getClass(), "NewCaseVisualPanel1.uncPath.error")); + wizPanel.setIsFinish(false); + return; + } } /** @@ -345,8 +354,9 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener { * @param evt the action event */ private void caseDirBrowseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_caseDirBrowseButtonActionPerformed + JFileChooser fileChooser = fileChooserHelper.getChooser(); fileChooser.setDragEnabled(false); - if (!caseParentDirTextField.getText().trim().equals("")) { + if (!caseParentDirTextField.getText().trim().isEmpty()) { fileChooser.setCurrentDirectory(new File(caseParentDirTextField.getText())); } fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java index 92f83ba26c..936249dad4 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java @@ -70,9 +70,7 @@ final class NewCaseWizardAction extends CallableSystemAction { final WizardDescriptor wizardDescriptor = new WizardDescriptor(getNewCaseWizardPanels()); wizardDescriptor.setTitleFormat(new MessageFormat("{0}")); wizardDescriptor.setTitle(NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.newCase.windowTitle.text")); - Dialog dialog = DialogDisplayer.getDefault().createDialog(wizardDescriptor); - // Workaround to ensure new case dialog is not hidden on macOS - dialog.setAlwaysOnTop(true); + Dialog dialog = DialogDisplayer.getDefault().createDialog(wizardDescriptor, WindowManager.getDefault().getMainWindow()); dialog.setVisible(true); dialog.toFront(); if (wizardDescriptor.getValue() == WizardDescriptor.FINISH_OPTION) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel2.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel2.java index b5119dc389..8550d5d81e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel2.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel2.java @@ -55,10 +55,8 @@ class NewCaseWizardPanel2 implements WizardDescriptor.ValidatingPanel startupWindows = Lookup.getDefault().lookupAll(StartupWindowInterface.class); int windowsCount = startupWindows.size(); - if (windowsCount == 1) { - startupWindowToUse = startupWindows.iterator().next(); - logger.log(Level.INFO, "Will use the default startup window: " + startupWindowToUse.toString()); //NON-NLS - } else if (windowsCount == 2) { - //pick the non default one - Iterator it = startupWindows.iterator(); - while (it.hasNext()) { - StartupWindowInterface window = it.next(); - if (!org.sleuthkit.autopsy.casemodule.StartupWindow.class.isInstance(window)) { - startupWindowToUse = window; - logger.log(Level.INFO, "Will use the custom startup window: " + startupWindowToUse.toString()); //NON-NLS - break; + switch (windowsCount) { + case 1: + startupWindowToUse = startupWindows.iterator().next(); + logger.log(Level.INFO, "Will use the default startup window: {0}", startupWindowToUse.toString()); //NON-NLS + break; + case 2: { + //pick the non default one + Iterator it = startupWindows.iterator(); + while (it.hasNext()) { + StartupWindowInterface window = it.next(); + if (!org.sleuthkit.autopsy.casemodule.StartupWindow.class.isInstance(window)) { + startupWindowToUse = window; + logger.log(Level.INFO, "Will use the custom startup window: {0}", startupWindowToUse.toString()); //NON-NLS + break; + } } + break; } - } else { - // select first non-Autopsy start up window - Iterator it = startupWindows.iterator(); - while (it.hasNext()) { - StartupWindowInterface window = it.next(); - if (!window.getClass().getCanonicalName().startsWith("org.sleuthkit.autopsy")) { - startupWindowToUse = window; - logger.log(Level.INFO, "Will use the custom startup window: " + startupWindowToUse.toString()); //NON-NLS - break; + default: { + // select first non-Autopsy start up window + Iterator it = startupWindows.iterator(); + while (it.hasNext()) { + StartupWindowInterface window = it.next(); + if (!window.getClass().getCanonicalName().startsWith("org.sleuthkit.autopsy")) { + startupWindowToUse = window; + logger.log(Level.INFO, "Will use the custom startup window: {0}", startupWindowToUse.toString()); //NON-NLS + break; + } } + break; } } @@ -121,6 +146,45 @@ public class StartupWindowProvider implements StartupWindowInterface { startupWindowToUse = new org.sleuthkit.autopsy.casemodule.StartupWindow(); } } + File openPreviousCaseFile = new File(ResetWindowsAction.getCaseToReopenFilePath()); + + if (openPreviousCaseFile.exists()) { + //do actual opening on another thread + new Thread(() -> { + String caseFilePath = ""; + String unableToOpenMessage = null; + try { + //avoid readFileToString having ambiguous arguments + Charset encoding = null; + caseFilePath = FileUtils.readFileToString(openPreviousCaseFile, encoding); + if (new File(caseFilePath).exists()) { + FileUtils.forceDelete(openPreviousCaseFile); + //close the startup window as we attempt to open the case + close(); + Case.openAsCurrentCase(caseFilePath); + + } else { + unableToOpenMessage = Bundle.StartupWindowProvider_openCase_noFile(caseFilePath); + logger.log(Level.WARNING, unableToOpenMessage); + } + } catch (IOException ex) { + unableToOpenMessage = Bundle.StartupWindowProvider_openCase_deleteOpenFailure(ResetWindowsAction.getCaseToReopenFilePath()); + logger.log(Level.WARNING, unableToOpenMessage, ex); + } catch (CaseActionException ex) { + unableToOpenMessage = Bundle.StartupWindowProvider_openCase_cantOpen(caseFilePath); + logger.log(Level.WARNING, unableToOpenMessage, ex); + } + + if (RuntimeProperties.runningWithGUI() && !StringUtils.isBlank(unableToOpenMessage)) { + final String message = unableToOpenMessage; + SwingUtilities.invokeLater(() -> { + MessageNotifyUtil.Message.warn(message); + //the case was not opened restore the startup window + open(); + }); + } + }).start(); + } } private void checkSolr() { @@ -147,9 +211,9 @@ public class StartupWindowProvider implements StartupWindowInterface { * @return True if running from command line, false otherwise */ private boolean isRunningFromCommandLine() { - + CommandLineOptionProcessor processor = Lookup.getDefault().lookup(CommandLineOptionProcessor.class); - if(processor != null) { + if (processor != null) { return processor.isRunFromCommandLine(); } return false; @@ -157,12 +221,12 @@ public class StartupWindowProvider implements StartupWindowInterface { /** * Get the default argument from the CommandLineOptionProcessor. - * - * @return If set, the default argument otherwise null. + * + * @return If set, the default argument otherwise null. */ - private String getDefaultArgument() { + private String getDefaultArgument() { CommandLineOptionProcessor processor = Lookup.getDefault().lookup(CommandLineOptionProcessor.class); - if(processor != null) { + if (processor != null) { return processor.getDefaultArgument(); } return null; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/UnpackagePortableCaseDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/UnpackagePortableCaseDialog.java index 3b86909953..94c708fdbb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/UnpackagePortableCaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/UnpackagePortableCaseDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsAddedEvent.java new file mode 100644 index 0000000000..f188c7d5a3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsAddedEvent.java @@ -0,0 +1,43 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.List; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.Host; + +/** + * Application events published when hosts have been added to the Sleuth Kit + * data model for a case. + */ +public final class HostsAddedEvent extends HostsEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an application event published when hosts have been added to + * the Sleuth Kit data model for a case. + * + * @param hosts The hosts that have been added. + */ + public HostsAddedEvent(List hosts) { + super(Case.Events.HOSTS_ADDED.name(), hosts); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsAddedToPersonEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsAddedToPersonEvent.java new file mode 100755 index 0000000000..fffb39d82c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsAddedToPersonEvent.java @@ -0,0 +1,92 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.Host; +import org.sleuthkit.datamodel.Person; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Application events published when one or more hosts have been added to a + * person. + */ +public final class HostsAddedToPersonEvent extends TskDataModelChangedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an application event published when one or more hosts have + * been added to a person. + * + * @param person The person. + * @param hosts The hosts. + */ + public HostsAddedToPersonEvent(Person person, List hosts) { + super(Case.Events.HOSTS_ADDED_TO_PERSON.toString(), Collections.singletonList(person), Person::getPersonId, hosts, Host::getHostId); + } + + /** + * Gets the person. + * + * @return The person. + */ + public Person getPerson() { + return getOldValue().get(0); + } + + /** + * Gets the hosts. + * + * @return The hosts. + */ + public List getHosts() { + return getNewValue(); + } + + @Override + protected List getOldValueObjects(SleuthkitCase caseDb, List ids) throws TskCoreException { + List persons = new ArrayList<>(); + for (Long id : ids) { + Optional person = caseDb.getPersonManager().getPerson(id); + if (person.isPresent()) { + persons.add(person.get()); + } + } + return persons; + } + + @Override + protected List getNewValueObjects(SleuthkitCase caseDb, List ids) throws TskCoreException { + List hosts = new ArrayList<>(); + for (Long id : ids) { + Optional host = caseDb.getHostManager().getHostById(id); + if (host.isPresent()) { + hosts.add(host.get()); + } + } + return hosts; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsDeletedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsDeletedEvent.java new file mode 100755 index 0000000000..24c2ae091b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsDeletedEvent.java @@ -0,0 +1,51 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.List; +import org.sleuthkit.autopsy.casemodule.Case; + +/** + * Application events published when hosts have been deleted from the Sleuth + * Kit data model for a case. + */ +public final class HostsDeletedEvent extends TskDataModelObjectsDeletedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an application event published when hosts have been deleted + * from the Sleuth Kit data model for a case. + * + * @param hostIds The host IDs of the deleted hosts. + */ + public HostsDeletedEvent(List hostIds) { + super(Case.Events.HOSTS_DELETED.name(), hostIds); + } + + /** + * Gets the host IDs of the hosts that have been deleted. + * + * @return The host IDs. + */ + public List getHostIds() { + return getOldValue(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsEvent.java new file mode 100644 index 0000000000..465d265083 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsEvent.java @@ -0,0 +1,69 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import org.sleuthkit.datamodel.Host; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A base class for application events published when hosts in the Sleuth Kit + * data model for a case have been added or updated. + */ +public class HostsEvent extends TskDataModelChangedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs the base class part of an application event published when + * hosts in the Sleuth Kit data model for a case have been added or updated. + * + * @param eventName The name of the Case.Events enum value for the event + * type. + * @param hosts The hosts. + */ + HostsEvent(String eventName, List hosts) { + super(eventName, null, null, hosts, Host::getHostId); + } + + /** + * Gets the hosts that have been added or updated. + * + * @return The hosts. + */ + public List getHosts() { + return getNewValue(); + } + + @Override + protected List getNewValueObjects(SleuthkitCase caseDb, List ids) throws TskCoreException { + List hosts = new ArrayList<>(); + for (Long id : ids) { + Optional host = caseDb.getHostManager().getHostById(id); + if (host.isPresent()) { + hosts.add(host.get()); + } + } + return hosts; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsRemovedFromPersonEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsRemovedFromPersonEvent.java new file mode 100755 index 0000000000..e23ef786ee --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsRemovedFromPersonEvent.java @@ -0,0 +1,85 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.Host; +import org.sleuthkit.datamodel.Person; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Application events published when one or more hosts have been removed from a + * person. + */ +public class HostsRemovedFromPersonEvent extends TskDataModelChangedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an application event published when one or more hosts have + * been removed from a person. + * + * @param person The person. + * @param hostIds The host IDs of the removed hosts. + */ + public HostsRemovedFromPersonEvent(Person person, List hostIds) { + super(Case.Events.HOSTS_REMOVED_FROM_PERSON.toString(), Collections.singletonList(person), Person::getPersonId, hostIds, (id -> id)); + } + + /** + * Gets the person. + * + * @return The person. + */ + public Person getPerson() { + return getOldValue().get(0); + } + + /** + * Gets the host IDs of the removed hosts. + * + * @return The host IDs. + */ + public List getHostIds() { + return getNewValue(); + } + + @Override + protected List getOldValueObjects(SleuthkitCase caseDb, List ids) throws TskCoreException { + List persons = new ArrayList<>(); + for (Long id : ids) { + Optional person = caseDb.getPersonManager().getPerson(id); + if (person.isPresent()) { + persons.add(person.get()); + } + } + return persons; + } + + @Override + protected List getNewValueObjects(SleuthkitCase caseDb, List ids) throws TskCoreException { + return ids; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsUpdatedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsUpdatedEvent.java new file mode 100755 index 0000000000..ec00fc5ad6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsUpdatedEvent.java @@ -0,0 +1,43 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.List; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.Host; + +/** + * Application events published when hosts in the Sleuth Kit data model for + * a case have been updated. + */ +public class HostsUpdatedEvent extends HostsEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an application event published when hosts in the Sleuth Kit + * data model for a case have been updated. + * + * @param hosts The updated persons. + */ + public HostsUpdatedEvent(List hosts) { + super(Case.Events.HOSTS_UPDATED.name(), hosts); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountsAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountsAddedEvent.java new file mode 100755 index 0000000000..f4273408c6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountsAddedEvent.java @@ -0,0 +1,43 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.List; +import static org.sleuthkit.autopsy.casemodule.Case.Events.OS_ACCOUNTS_ADDED; +import org.sleuthkit.datamodel.OsAccount; + +/** + * An application event published when OS accounts are added to the Sleuth Kit + * data model for a case. + */ +public final class OsAccountsAddedEvent extends OsAccountsEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an application event published when OS accounts are added to + * the Sleuth Kit data model for a case. + * + * @param osAccounts The OS accounts that were added. + */ + public OsAccountsAddedEvent(List osAccounts) { + super(OS_ACCOUNTS_ADDED.toString(), osAccounts); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountsDeletedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountsDeletedEvent.java new file mode 100755 index 0000000000..f3f65a0aed --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountsDeletedEvent.java @@ -0,0 +1,51 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.List; +import org.sleuthkit.autopsy.casemodule.Case; + +/** + * An application event published when OS accounts have been deleted from the + * Sleuth Kit data model for a case. + */ +public final class OsAccountsDeletedEvent extends TskDataModelObjectsDeletedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an application event published when OS accounts have been + * deleted from the Sleuth Kit data model for a case. + * + * @param osAccountObjectIds TSK object IDs of the deleted accounts. + */ + public OsAccountsDeletedEvent(List osAccountObjectIds) { + super(Case.Events.OS_ACCOUNTS_DELETED.toString(), osAccountObjectIds); + } + + /** + * Gets the Sleuth Kit object IDs of the deleted OS accounts. + * + * @return The object IDs. + */ + List getOsAccountObjectIds() { + return getOldValue(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountsEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountsEvent.java new file mode 100755 index 0000000000..434d8c8f60 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountsEvent.java @@ -0,0 +1,66 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.ArrayList; +import java.util.List; +import org.sleuthkit.datamodel.OsAccount; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A base class for application events published when OS accounts in the Sleuth + * Kit data model for a case have been added or updated. + */ +class OsAccountsEvent extends TskDataModelChangedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs the base class part of an application event published when + * OS accounts in the Sleuth Kit data model for a case have been added or + * updated. + * + * @param eventName The name of the Case.Events enum value for the event + * type. + * @param account The OS accounts. + */ + OsAccountsEvent(String eventName, List osAccounts) { + super(eventName, null, null, osAccounts, OsAccount::getId); + } + + /** + * Gets the OS accounts that have been added or updated. + * + * @return The OS accounts. + */ + public List getOsAccounts() { + return getNewValue(); + } + + @Override + protected List getNewValueObjects(SleuthkitCase caseDb, List ids) throws TskCoreException { + List osAccounts = new ArrayList<>(); + for (Long id : ids) { + osAccounts.add(caseDb.getOsAccountManager().getOsAccountByObjectId(id)); + } + return osAccounts; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountsUpdatedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountsUpdatedEvent.java new file mode 100755 index 0000000000..65767ccdfc --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountsUpdatedEvent.java @@ -0,0 +1,43 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.List; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.OsAccount; + +/** + * An application event published when OS accounts in the Sleuth Kit data model + * for a case have been updated. + */ +public final class OsAccountsUpdatedEvent extends OsAccountsEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an application event published when OS accounts in the Sleuth + * Kit data model for a case have been updated. + * + * @param osAccounts The OS accounts that were updated. + */ + public OsAccountsUpdatedEvent(List osAccounts) { + super(Case.Events.OS_ACCOUNTS_UPDATED.toString(), osAccounts); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAcctInstancesAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAcctInstancesAddedEvent.java new file mode 100755 index 0000000000..d752bf27fb --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAcctInstancesAddedEvent.java @@ -0,0 +1,59 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.List; +import static org.sleuthkit.autopsy.casemodule.Case.Events.OS_ACCT_INSTANCES_ADDED; +import org.sleuthkit.datamodel.OsAccountInstance; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * An application event published when OS account instances are added to the + * Sleuth Kit data model for a case. + */ +public final class OsAcctInstancesAddedEvent extends TskDataModelChangedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an application event published when OS account instances are + * added to the Sleuth Kit data model for a case. + * + * @param osAcctInstances The OS account instances that were added. + */ + public OsAcctInstancesAddedEvent(List osAcctInstances) { + super(OS_ACCT_INSTANCES_ADDED.toString(), null, null, osAcctInstances, OsAccountInstance::getInstanceId); + } + + /** + * Gets the OS account instances that have been added. + * + * @return The OS account instances. + */ + public List getOsAccountInstances() { + return getNewValue(); + } + + @Override + protected List getNewValueObjects(SleuthkitCase caseDb, List ids) throws TskCoreException { + return caseDb.getOsAccountManager().getOsAccountInstances(ids); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsAddedEvent.java new file mode 100644 index 0000000000..afd101eac3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsAddedEvent.java @@ -0,0 +1,43 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.List; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.Person; + +/** + * An application event published when persons have been added to the Sleuth Kit + * data model for a case. + */ +public class PersonsAddedEvent extends PersonsEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an application event published when persons have been added to + * the Sleuth Kit data model for a case. + * + * @param persons The persons that have been added. + */ + public PersonsAddedEvent(List persons) { + super(Case.Events.PERSONS_ADDED.name(), persons); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsDeletedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsDeletedEvent.java new file mode 100644 index 0000000000..46c024b7dd --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsDeletedEvent.java @@ -0,0 +1,51 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.List; +import org.sleuthkit.autopsy.casemodule.Case; + +/** + * Application events published when persons have been deleted from the Sleuth + * Kit data model for a case. + */ +public class PersonsDeletedEvent extends TskDataModelObjectsDeletedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an application event published when persons have been deleted + * from the Sleuth Kit data model for a case. + * + * @param personIds The IDs of the persons that have been deleted. + */ + public PersonsDeletedEvent(List personIds) { + super(Case.Events.PERSONS_DELETED.name(), personIds); + } + + /** + * Gets the person IDs of the persons that have been deleted. + * + * @return The person IDs. + */ + List getPersonIds() { + return getOldValue(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsEvent.java new file mode 100644 index 0000000000..2d3b322ea7 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsEvent.java @@ -0,0 +1,70 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import org.sleuthkit.datamodel.Person; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A base class for application events published when persons in the Sleuth Kit + * data model for a case have been added or updated. + */ +public class PersonsEvent extends TskDataModelChangedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs the base class part of an application event published when + * persons in the Sleuth Kit data model for a case have been added or + * updated. + * + * @param eventName The name of the Case.Events enum value for the event + * type. + * @param persons The persons. + */ + PersonsEvent(String eventName, List persons) { + super(eventName, null, null, persons, Person::getPersonId); + } + + /** + * Gets the persons that have been added or updated. + * + * @return The persons. + */ + public List getPersons() { + return getNewValue(); + } + + @Override + protected List getNewValueObjects(SleuthkitCase caseDb, List ids) throws TskCoreException { + List persons = new ArrayList<>(); + for (Long id : ids) { + Optional person = caseDb.getPersonManager().getPerson(id); + if (person.isPresent()) { + persons.add(person.get()); + } + } + return persons; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsUpdatedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsUpdatedEvent.java new file mode 100755 index 0000000000..572b832688 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsUpdatedEvent.java @@ -0,0 +1,43 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.List; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.Person; + +/** + * Application events published when persons in the Sleuth Kit data model for + * a case have been updated. + */ +public class PersonsUpdatedEvent extends PersonsEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an application event published when persons in the Sleuth Kit + * data model for a case have been updated. + * + * @param persons The updated persons. + */ + public PersonsUpdatedEvent(List persons) { + super(Case.Events.PERSONS_UPDATED.name(), persons); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/ReportAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/ReportAddedEvent.java index 7f35c625c5..9da76a4366 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/events/ReportAddedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/ReportAddedEvent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015-2018 Basis Technology Corp. + * Copyright 2015-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,70 +18,48 @@ */ package org.sleuthkit.autopsy.casemodule.events; -import java.io.Serializable; +import java.util.ArrayList; import java.util.List; -import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.datamodel.Report; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** - * Event published when a report is added to a case. + * An application event published when a report is added to a case. */ -public final class ReportAddedEvent extends AutopsyEvent implements Serializable { +public final class ReportAddedEvent extends TskDataModelChangedEvent { private static final long serialVersionUID = 1L; - private static final Logger logger = Logger.getLogger(DataSourceAddedEvent.class.getName()); - private transient Report report; /** - * Constructs an event published when a report is added to a case. + * Constructs an application event published when a report is added to a + * case. * - * @param report The data source that was added. + * @param report The report that was added. */ public ReportAddedEvent(Report report) { - /** - * Putting the object id of the report into newValue to allow for lazy - * loading of the Report object. - */ - super(Case.Events.REPORT_ADDED.toString(), null, report.getId()); - this.report = report; + super(Case.Events.REPORT_ADDED.toString(), null, null, Stream.of(report).collect(Collectors.toList()), Report::getId); } /** - * Gets the data source that was added. + * Gets the reoprt that was added to the case. * - * @return The data source. + * @return The report. */ + public Report getReport() { + List reports = getNewValue(); + return reports.get(0); + } + @Override - public Object getNewValue() { - /** - * The report field is set in the constructor, but it is transient so it - * will become null when the event is serialized for publication over a - * network. Doing a lazy load of the Report object may save database - * round trips from other nodes since subscribers to this event are - * often not interested in the event data. - */ - if (null != report) { - return report; - } - try { - long id = (Long) super.getNewValue(); - List reports = Case.getCurrentCaseThrows().getSleuthkitCase().getAllReports(); - for (Report thisReport : reports) { - if (thisReport.getId() == id) { - report = thisReport; - break; - } - } - return report; - } catch (NoCurrentCaseException | TskCoreException ex) { - logger.log(Level.SEVERE, "Error doing lazy load for remote event", ex); //NON-NLS - return null; - } + protected List getNewValueObjects(SleuthkitCase caseDb, List ids) throws TskCoreException { + Long id = ids.get(0); + List reports = new ArrayList<>(); + reports.add(caseDb.getReportById(id)); + return reports; } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/TagNamesEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagNamesEvent.java new file mode 100755 index 0000000000..60ac94ff23 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagNamesEvent.java @@ -0,0 +1,126 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.ArrayList; +import java.util.List; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TaggingManager; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A base class for TagName added and update events. + */ +public class TagNamesEvent extends TskDataModelChangedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Construct the base event for TagNames that have been added or updated. + * + * @param eventName The name of the event. + * @param tagNames The TagNames that have been modified. + */ + private TagNamesEvent(String eventName, List tagNames) { + super(eventName, null, null, tagNames, TagName::getId); + } + + /** + * Returns a list of the added or modified TagNames. + * + * @return The event list of TagNames. + */ + public List getTagNames() { + return getNewValue(); + } + + @Override + protected List getNewValueObjects(SleuthkitCase caseDb, List ids) throws TskCoreException { + List tagNames = new ArrayList<>(); + TaggingManager taggingMrg = caseDb.getTaggingManager(); + for (Long id : ids) { + tagNames.add(taggingMrg.getTagName(id)); + } + + return tagNames; + } + + /** + * Application events published when TagNames have been Added from the + * Sleuth Kit data model for a case. + */ + public static class TagNamesAddedEvent extends TagNamesEvent { + + private static final long serialVersionUID = 1L; + + /** + * Construct an application event published when TagNames have been + * added to the Sleuth Kit data model. + * + * @param tagNames The TagNames that have been added. + */ + public TagNamesAddedEvent(List tagNames) { + super(Case.Events.TAG_NAMES_ADDED.name(), tagNames); + } + } + + /** + * Application events published when TagNames have been updated from the + * Sleuth Kit data model for a case. + */ + public static class TagNamesUpdatedEvent extends TagNamesEvent { + + private static final long serialVersionUID = 1L; + + /** + * Construct an application event published when TagNames have been + * updated in the Sleuth Kit data model. + * + * @param tagNames The TagNames that have been updated. + */ + public TagNamesUpdatedEvent(List tagNames) { + super(Case.Events.TAG_NAMES_UPDATED.name(), tagNames); + } + } + + /** + * Application events published when TagNames have been deleted from the + * Sleuth Kit data model for a case. + */ + public static class TagNamesDeletedEvent extends TskDataModelObjectsDeletedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an application event published when the TagNames have been + * deleted from the Sleuth Kit data model for a case. + * + * @param tagNameIds The IDs of the TagNames that have been deleted. + */ + public TagNamesDeletedEvent(List tagNameIds) { + super(Case.Events.TAG_NAMES_DELETED.name(), tagNameIds); + } + + public List getTagNameIds() { + return getOldValue(); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/TagSetsEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagSetsEvent.java new file mode 100755 index 0000000000..0896241e7d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagSetsEvent.java @@ -0,0 +1,103 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.ArrayList; +import java.util.List; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TagSet; +import org.sleuthkit.datamodel.TaggingManager; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A base class for TagSet added and update events. + */ +public class TagSetsEvent extends TskDataModelChangedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Construct a new TagSetEvent. + * + * @param eventName + * @param tagSets + */ + private TagSetsEvent(String eventName, List tagSets) { + super(eventName, null, null, tagSets, TagSet::getId); + } + + /** + * Returns a list of the TagSet objects that were added or modified for this + * event. + * + * @return A list of TagSet objects. + */ + public List getTagSets() { + return this.getNewValue(); + } + + @Override + protected List getNewValueObjects(SleuthkitCase caseDb, List ids) throws TskCoreException { + List tagSets = new ArrayList<>(); + TaggingManager taggingMrg = caseDb.getTaggingManager(); + for (Long id : ids) { + tagSets.add(taggingMrg.getTagSet(id)); + } + return tagSets; + } + + /** + * Application events published when TagSets have been Added from the Sleuth + * Kit data model for a case. + */ + public static class TagSetsAddedEvent extends TagSetsEvent { + + private static final long serialVersionUID = 1L; + + /** + * Construct an application event published when TagSetss have been + * added to the Sleuth Kit data model. + * + * @param tagSets The TagSets that have been added. + */ + public TagSetsAddedEvent(List tagSets) { + super(Case.Events.TAG_SETS_ADDED.name(), tagSets); + } + } + + /** + * Application events published when TagSets have been deleted from the + * Sleuth Kit data model for a case. + */ + public static class TagSetsDeletedEvent extends TskDataModelObjectsDeletedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an application event published when the TagSets have been + * deleted from the Sleuth Kit data model for a case. + * + * @param tagNameIds The IDs of the TagNames that have been deleted. + */ + public TagSetsDeletedEvent(List tagNameIds) { + super(Case.Events.TAG_SETS_DELETED.name(), tagNameIds); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/TskDataModelChangedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/TskDataModelChangedEvent.java new file mode 100755 index 0000000000..83ced035fc --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/TskDataModelChangedEvent.java @@ -0,0 +1,205 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.stream.Collectors; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.events.AutopsyEvent; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * An abstract base class for application events published when one or more + * Sleuth Kit Data Model objects for a case change in some way. + * + * This class extends AutopsyEvent. The AutopsyEvent class extends + * PropertyChangeEvent to integrate with legacy use of JavaBeans + * PropertyChangeEvents and PropertyChangeListeners as an application event + * publisher-subcriber mechanism. Subclasses need to decide what constitutes + * "old" and "new" objects for them and are encouraged to provide getters for + * these values that do not require clients to cast the return values. + * + * The AutopsyEvent class implements Serializable to allow local event instances + * to be published to other Autopsy nodes over a network in serialized form. TSK + * Data Model objects are generally not serializable because they encapsulate a + * reference to a SleuthkitCase object that represents the case database and + * which has local JDBC Connection objects. For this reason, this class supports + * serialization of the unique numeric IDs (TSK object IDs, case database row + * IDs, etc.) of the subject TSK Data Model objects and the "reconstruction" of + * those objects on other Autopsy nodes by querying the case database by unique + * ID. + * + * @param The Sleuth Kit Data Model object type of the "old" data model + * objects. + * @param The Sleuth Kit Data Model object type of the "new" data model + * objects. + */ +public abstract class TskDataModelChangedEvent extends AutopsyEvent { + + private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(TskDataModelChangedEvent.class.getName()); + private final boolean hasOldValue; + private final List oldValueIds; + private transient List oldValueObjects; + private final boolean hasNewValue; + private final List newValueIds; + private transient List newValueObjects; + + /** + * Constructs the base class part for application events published when one + * or more Sleuth Kit Data Model objects for a case change in some way. + * + * @param eventName The event name. + * @param oldValueObjects A list of he Data Model objects that have been + * designated as the "old" objects in the event. + * May be null. + * @param oldValueGetIdMethod A method that can be applied to the "old" data + * model objects to get their unique numeric IDs + * (TSK object IDs, case database row IDs, etc.). + * May be null if there are no "old" objects. + * @param newValueObjects A list of he Data Model objects that have been + * designated as the "new" objects in the event. + * May be null. + * @param newValueGetIdMethod A method that can be applied to the "new" data + * model objects to get their unique numeric IDs + * (TSK object IDs, case database row IDs, etc.). + * May be null if there are no "new" objects. + */ + protected TskDataModelChangedEvent(String eventName, List oldValueObjects, Function oldValueGetIdMethod, List newValueObjects, Function newValueGetIdMethod) { + super(eventName, null, null); + oldValueIds = new ArrayList<>(); + this.oldValueObjects = new ArrayList<>(); + if (oldValueObjects != null) { + hasOldValue = true; + oldValueIds.addAll(oldValueObjects.stream() + .map(o -> oldValueGetIdMethod.apply(o)) + .collect(Collectors.toList())); + this.oldValueObjects.addAll(oldValueObjects); + } else { + hasOldValue = false; + } + newValueIds = new ArrayList<>(); + this.newValueObjects = new ArrayList<>(); + if (newValueObjects != null) { + hasNewValue = true; + newValueIds.addAll(newValueObjects.stream() + .map(o -> newValueGetIdMethod.apply(o)) + .collect(Collectors.toList())); + this.newValueObjects.addAll(newValueObjects); + } else { + hasNewValue = false; + } + } + + /** + * Gets a list of the Data Model objects that have been designated as the + * "old" objects in the event. + * + * @return The list of the "old" data model objects. May be empty. + */ + @Override + public List getOldValue() { + if (hasOldValue) { + if (oldValueObjects == null) { + try { + Case currentCase = Case.getCurrentCaseThrows(); + SleuthkitCase caseDb = currentCase.getSleuthkitCase(); + oldValueObjects = getOldValueObjects(caseDb, oldValueIds); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error getting oldValue() TSK Data Model objects for %s event (%s)", getPropertyName(), getSourceType()), ex); + return Collections.emptyList(); + } + } + return Collections.unmodifiableList(oldValueObjects); + } else { + return Collections.emptyList(); + } + } + + /** + * Gets a list of the Data Model objects that have been designated as the + * "new" objects in the event. + * + * @return The list of the "new" data model objects. May be empty. + */ + @Override + public List getNewValue() { + if (hasNewValue) { + if (newValueObjects == null) { + try { + Case currentCase = Case.getCurrentCaseThrows(); + SleuthkitCase caseDb = currentCase.getSleuthkitCase(); + newValueObjects = getNewValueObjects(caseDb, newValueIds); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error getting newValue() TSK Data Model objects for %s event (%s)", getPropertyName(), getSourceType()), ex); + return Collections.emptyList(); + } + } + return Collections.unmodifiableList(newValueObjects); + } else { + return Collections.emptyList(); + } + } + + /** + * Reconstructs the "old" Sleuth Kit Data Model objects associated with this + * application event, if any, using the given unique numeric IDs (TSK object + * IDs, case database row IDs, etc.) to query the given case database. + * + * @param caseDb The case database. + * @param ids The unique, numeric IDs (TSK object IDs, case database row + * IDs, etc.) of the Sleuth Kit Data Model objects. + * + * @return The objects. + * + * @throws org.sleuthkit.datamodel.TskCoreException If there is an error + * getting the Sleuth Kit + * Data Model objects. + */ + protected List getOldValueObjects(SleuthkitCase caseDb, List ids) throws TskCoreException { + return Collections.emptyList(); + } + + /** + * Reconstructs the "new" Sleuth Kit Data Model objects associated with this + * application event, if any, using the given unique numeric IDs (TSK object + * IDs, case database row IDs, etc.) to query the given case database. + * + * @param caseDb The case database. + * @param ids The unique, numeric IDs (TSK object IDs, case database row + * IDs, etc.) of the Sleuth Kit Data Model objects. + * + * @return The objects. + * + * @throws org.sleuthkit.datamodel.TskCoreException If there is an error + * getting the Sleuth Kit + * Data Model objects. + */ + protected List getNewValueObjects(SleuthkitCase caseDb, List ids) throws TskCoreException { + return Collections.emptyList(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/TskDataModelObjectsDeletedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/TskDataModelObjectsDeletedEvent.java new file mode 100755 index 0000000000..7e5929e4a1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/TskDataModelObjectsDeletedEvent.java @@ -0,0 +1,60 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.sleuthkit.autopsy.events.AutopsyEvent; + +/** + * An abstract base class for application events published when one or more + * Sleuth Kit Data Model objects for a case have been deleted. + * + * This class extends AutopsyEvent. The AutopsyEvent class extends + * PropertyChangeEvent to integrate with legacy use of JavaBeans + * PropertyChangeEvents and PropertyChangeListeners as an application event + * publisher-subcriber mechanism. Subclasses need to decide what constitutes + * "old" and "new" objects for them. + * + * For this class the "old" values are the unique numeric IDs (TSK object IDs, + * case database row IDs, etc.) of the deleted TSK Data Model objects. There are + * no "new" values. Subclasses are encouraged to provide less generic getters + * with descriptive names for the unique IDs than the override of the inherited + * getOldValue() method below. These getters can be implemented by delegating to + * getOldValue(). + */ +public class TskDataModelObjectsDeletedEvent extends AutopsyEvent { + + private static final long serialVersionUID = 1L; + + private final List deletedObjectIds; + + protected TskDataModelObjectsDeletedEvent(String eventName, List deletedObjectIds) { + super(eventName, null, null); + this.deletedObjectIds = new ArrayList<>(); + this.deletedObjectIds.addAll(deletedObjectIds); + } + + @Override + public List getOldValue() { + return Collections.unmodifiableList(deletedObjectIds); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesBrowserPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesBrowserPanel.form index ada4c7f484..9b8c2c8f72 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesBrowserPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesBrowserPanel.form @@ -23,25 +23,4 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesBrowserPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesBrowserPanel.java index 154e692663..04785cc83d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesBrowserPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/multiusercasesbrowser/MultiUserCasesBrowserPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017-2019 Basis Technology Corp. + * Copyright 2017-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.casemodule.multiusercasesbrowser; +import java.awt.BorderLayout; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -68,8 +69,7 @@ public final class MultiUserCasesBrowserPanel extends javax.swing.JPanel impleme outlineView = new org.openide.explorer.view.OutlineView(); outline = this.outlineView.getOutline(); configureOutlineView(); - caseTableScrollPane.add(outlineView); - caseTableScrollPane.setViewportView(outlineView); + add(outlineView, BorderLayout.CENTER); this.setVisible(true); } @@ -146,20 +146,11 @@ public final class MultiUserCasesBrowserPanel extends javax.swing.JPanel impleme // //GEN-BEGIN:initComponents private void initComponents() { - caseTableScrollPane = new javax.swing.JScrollPane(); - setMinimumSize(new java.awt.Dimension(0, 5)); setPreferredSize(new java.awt.Dimension(5, 5)); setLayout(new java.awt.BorderLayout()); - - caseTableScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - caseTableScrollPane.setMinimumSize(new java.awt.Dimension(0, 5)); - caseTableScrollPane.setOpaque(false); - caseTableScrollPane.setPreferredSize(new java.awt.Dimension(500, 500)); - add(caseTableScrollPane, java.awt.BorderLayout.CENTER); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JScrollPane caseTableScrollPane; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java index a1289a1b85..28065dacbc 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015-2019 Basis Technology Corp. + * Copyright 2015-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -56,7 +56,7 @@ public final class Blackboard implements Closeable { @Deprecated public synchronized void indexArtifact(BlackboardArtifact artifact) throws BlackboardException { try { - Case.getCurrentCase().getSleuthkitCase().getBlackboard().postArtifact(artifact, ""); + Case.getCurrentCase().getSleuthkitCase().getBlackboard().postArtifact(artifact, "", null); } catch (org.sleuthkit.datamodel.Blackboard.BlackboardException ex) { throw new BlackboardException(ex.getMessage(), ex); } @@ -117,6 +117,7 @@ public final class Blackboard implements Closeable { * @deprecated Do not use. */ @Deprecated + @Override public void close() throws IOException { /* * No-op maintained for backwards compatibility. Clients should not diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle_ja.properties index 3a6757eb65..0fb5047324 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle_ja.properties @@ -1,5 +1,5 @@ -#Tue Aug 18 18:09:21 UTC 2020 -OptionsCategory_Name_TagNamesOptions=\u30ab\u30b9\u30bf\u30e0\u30bf\u30b0 +#Mon Jul 12 13:21:59 UTC 2021 +OptionsCategory_Name_TagNamesOptions=\u30ab\u30b9\u30bf\u30e0\u30fb\u30bf\u30b0 OptionsCategory_TagNames=TagNames TagNameDefinition.predefTagNames.bookmark.text=\u30d6\u30c3\u30af\u30de\u30fc\u30af TagNameDefinition.predefTagNames.followUp.text=\u30d5\u30a9\u30ed\u30fc\u30a2\u30c3\u30d7 @@ -11,14 +11,21 @@ TagNameDialog.JOptionPane.tagNameEmpty.title=\u7a7a(\u672a\u5165\u529b)\u306e\u7 TagNameDialog.JOptionPane.tagNameIllegalCharacters.message=\u30bf\u30b0\u540d\u306b\u6b21\u306e\u8a18\u53f7\u3092\u542b\u3081\u3089\u308c\u307e\u305b\u3093\: \\ \: * ? " < > | , ; TagNameDialog.JOptionPane.tagNameIllegalCharacters.title=\u30bf\u30b0\u540d\u306b\u7121\u52b9\u306a\u6587\u5b57\u304c\u3042\u308a\u307e\u3059 TagNameDialog.cancelButton.text=\u53d6\u308a\u6d88\u3057 +TagNameDialog.descriptionLabel.text=\u8aac\u660e\uff1a TagNameDialog.editTitle.text=\u30bf\u30b0\u3092\u7de8\u96c6 TagNameDialog.newTagNameLabel.text=\u540d\u524d\: +TagNameDialog.notableCheckbox.text=\u30bf\u30b0\u306f\u30a2\u30a4\u30c6\u30e0\u304c\u6ce8\u76ee\u306b\u5024\u3059\u308b\u3053\u3068\u3092\u793a\u3057\u307e\u3059\u3002 TagNameDialog.okButton.text=OK TagNameDialog.tagNameTextField.text= TagNameDialog.title.text=\u65b0\u898f\u30bf\u30b0 TagOptionsPanel.TagNameDialog.tagNameAlreadyExists.message=\u30bf\u30b0\u540d\u306f\u4e00\u610f\u3067\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002\u3053\u306e\u540d\u524d\u306e\u30bf\u30b0\u306f\u3059\u3067\u306b\u5b58\u5728\u3057\u307e\u3059\u3002 TagOptionsPanel.TagNameDialog.tagNameAlreadyExists.title=\u30bf\u30b0\u540d\u3092\u8907\u88fd TagOptionsPanel.deleteTagNameButton.text=\u30bf\u30b0\u3092\u524a\u9664 +TagOptionsPanel.descriptionLabel.text=\u30bf\u30b0\u306e\u8aac\u660e\uff1a +TagOptionsPanel.editTagNameButton.text=\u30bf\u30b0\u306e\u7de8\u96c6 +TagOptionsPanel.ingestRunningWarningLabel.text=\u53d6\u8fbc\u307f\u306e\u5b9f\u884c\u4e2d\u306f\u3001\u65e2\u5b58\u306e\u30bf\u30b0\u3092\u5909\u66f4\u3067\u304d\u307e\u305b\u3093\u3002 +TagOptionsPanel.isNotableLabel.text=\u30bf\u30b0\u306f\u30a2\u30a4\u30c6\u30e0\u304c\u6ce8\u76ee\u306b\u5024\u3059\u308b\u3053\u3068\u3092\u793a\u3057\u307e\u3059\uff1a TagOptionsPanel.newTagNameButton.text=\u65b0\u898f\u30bf\u30b0 +TagOptionsPanel.panelDescriptionTextArea.text=\u30bf\u30b0\u3092\u4f5c\u6210\u3068\u7ba1\u7406\u3002 \u30bf\u30b0\u306f\u3001\u30b1\u30fc\u30b9\u5185\u306e\u30d5\u30a1\u30a4\u30eb\u3068\u7d50\u679c\u306b\u9069\u7528\u3067\u304d\u307e\u3059\u3002 \u6ce8\u76ee\u306e\u30bf\u30b0\u3092\u4f7f\u7528\u3059\u308b\u3068\u3001\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u3067\u30bf\u30b0\u304c\u4ed8\u3051\u3089\u308c\u305f\u30a2\u30a4\u30c6\u30e0\u306b\u6ce8\u76ee\u306e\u30d5\u30e9\u30b0\u304c\u4ed8\u3051\u3089\u308c\u307e\u3059\u3002 \u30bf\u30b0\u306e\u30b9\u30c6\u30fc\u30bf\u30b9\u3092\u5909\u66f4\u3059\u308b\u3068\u3001\u73fe\u5728\u306e\u30b1\u30fc\u30b9\u306e\u30a2\u30a4\u30c6\u30e0\u306e\u307f\u306b\u5f71\u97ff\u3057\u307e\u3059\u3002 TagOptionsPanel.tagTypesListLabel.text=\u30bf\u30b0\u540d\: TagsManager.notableTagEnding.text=\ (\u9855\u8457) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java index ece1c00736..34f26eb1ac 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2012-2019 Basis Technology Corp. + * Copyright 2012-2021 Basis Technology Corp. * Copyright 2012 42six Solutions. * Contact: aebadirad 42six com * Project Contact/Architect: carrier sleuthkit org @@ -36,6 +36,7 @@ import org.sleuthkit.autopsy.ingest.ModuleContentEvent; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.DerivedFile; +import org.sleuthkit.datamodel.Host; import org.sleuthkit.datamodel.LayoutFile; import org.sleuthkit.datamodel.LocalDirectory; import org.sleuthkit.datamodel.SleuthkitCase; @@ -59,7 +60,7 @@ import org.sleuthkit.datamodel.TskData; public class FileManager implements Closeable { private static final Logger LOGGER = Logger.getLogger(FileManager.class.getName()); - private SleuthkitCase caseDb; + private final SleuthkitCase caseDb; /** * Constructs a manager that provides methods for retrieving files from the @@ -82,10 +83,7 @@ public class FileManager implements Closeable { * @throws TskCoreException If there is a problem querying the case * database. */ - public synchronized List findFilesByMimeType(Collection mimeTypes) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } + public List findFilesByMimeType(Collection mimeTypes) throws TskCoreException { return caseDb.findAllFilesWhere(createFileTypeInCondition(mimeTypes)); } @@ -101,10 +99,7 @@ public class FileManager implements Closeable { * @throws TskCoreException If there is a problem querying the case * database. */ - public synchronized List findFilesByParentPath(long dataSourceObjectID, String parentPath) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } + public List findFilesByParentPath(long dataSourceObjectID, String parentPath) throws TskCoreException { return caseDb.findAllFilesWhere(createParentPathCondition(dataSourceObjectID, parentPath)); } @@ -120,10 +115,7 @@ public class FileManager implements Closeable { * @throws TskCoreException If there is a problem querying the case * database. */ - public synchronized List findFilesByMimeType(Content dataSource, Collection mimeTypes) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } + public List findFilesByMimeType(Content dataSource, Collection mimeTypes) throws TskCoreException { return caseDb.findAllFilesWhere("data_source_obj_id = " + dataSource.getId() + " AND " + createFileTypeInCondition(mimeTypes)); } @@ -137,12 +129,8 @@ public class FileManager implements Closeable { * * @throws TskCoreException */ - public synchronized List findFilesExactName(long parentId, String name) throws TskCoreException{ - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } - String whereClause = "name = '%s'"; - return caseDb.findAllFilesInFolderWhere(parentId, String.format(whereClause, name)); + public List findFilesExactName(long parentId, String name) throws TskCoreException{ + return caseDb.getFileManager().findFilesExactName(parentId, name); } /** @@ -182,10 +170,7 @@ public class FileManager implements Closeable { * @throws TskCoreException if there is a problem querying the case * database. */ - public synchronized List findFiles(String fileName) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } + public List findFiles(String fileName) throws TskCoreException { List result = new ArrayList<>(); List dataSources = caseDb.getRootObjects(); for (Content dataSource : dataSources) { @@ -210,10 +195,7 @@ public class FileManager implements Closeable { * @throws TskCoreException if there is a problem querying the case * database. */ - public synchronized List findFiles(String fileName, String parentSubString) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } + public List findFiles(String fileName, String parentSubString) throws TskCoreException { List result = new ArrayList<>(); List dataSources = caseDb.getRootObjects(); for (Content dataSource : dataSources) { @@ -236,16 +218,8 @@ public class FileManager implements Closeable { * @throws TskCoreException if there is a problem querying the case * database. */ - public synchronized List findFiles(String fileName, AbstractFile parent) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } - List result = new ArrayList<>(); - List dataSources = caseDb.getRootObjects(); - for (Content dataSource : dataSources) { - result.addAll(findFiles(dataSource, fileName, parent)); - } - return result; + public List findFiles(String fileName, AbstractFile parent) throws TskCoreException { + return caseDb.findFilesInFolder(fileName, parent); } /** @@ -262,10 +236,7 @@ public class FileManager implements Closeable { * @throws TskCoreException if there is a problem querying the case * database. */ - public synchronized List findFiles(Content dataSource, String fileName) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } + public List findFiles(Content dataSource, String fileName) throws TskCoreException { return caseDb.findFiles(dataSource, fileName); } @@ -287,35 +258,10 @@ public class FileManager implements Closeable { * @throws TskCoreException if there is a problem querying the case * database. */ - public synchronized List findFiles(Content dataSource, String fileName, String parentSubString) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } + public List findFiles(Content dataSource, String fileName, String parentSubString) throws TskCoreException { return caseDb.findFiles(dataSource, fileName, parentSubString); } - /** - * Finds all files and directories with a given file name and given parent - * file or directory in a given data source (image, local/logical files set, - * etc.). The name search is for full or partial matches and is case - * insensitive (a case insensitive SQL LIKE clause is used to query the case - * database). - * - * @param dataSource The data source. - * @param fileName The full name or a pattern to match on part of the name - * @param parent The parent file or directory. - * - * @return The matching files and directories. - * - * @throws TskCoreException if there is a problem querying the case - * database. - */ - public synchronized List findFiles(Content dataSource, String fileName, AbstractFile parent) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } - return findFiles(dataSource, fileName, parent.getName()); - } /** * Finds all files and directories with a given file name and path in a @@ -333,10 +279,7 @@ public class FileManager implements Closeable { * @throws TskCoreException if there is a problem querying the case * database. */ - public synchronized List openFiles(Content dataSource, String filePath) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } + public List openFiles(Content dataSource, String filePath) throws TskCoreException { return caseDb.openFiles(dataSource, filePath); } @@ -369,7 +312,7 @@ public class FileManager implements Closeable { * @throws TskCoreException if there is a problem adding the file to the * case database. */ - public synchronized DerivedFile addDerivedFile(String fileName, + public DerivedFile addDerivedFile(String fileName, String localPath, long size, long ctime, long crtime, long atime, long mtime, @@ -377,9 +320,6 @@ public class FileManager implements Closeable { Content parentObj, String rederiveDetails, String toolName, String toolVersion, String otherDetails, TskData.EncodingType encodingType) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } return caseDb.addDerivedFile(fileName, localPath, size, ctime, crtime, atime, mtime, isFile, parentObj, rederiveDetails, toolName, toolVersion, otherDetails, encodingType); @@ -415,15 +355,12 @@ public class FileManager implements Closeable { * @throws TskCoreException if there is a problem adding the file to the * case database. */ - public synchronized DerivedFile updateDerivedFile(DerivedFile derivedFile, String localPath, + public DerivedFile updateDerivedFile(DerivedFile derivedFile, String localPath, long size, long ctime, long crtime, long atime, long mtime, boolean isFile, String mimeType, String rederiveDetails, String toolName, String toolVersion, String otherDetails, TskData.EncodingType encodingType) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } return caseDb.updateDerivedFile(derivedFile, localPath, size, ctime, crtime, atime, mtime, isFile, mimeType, rederiveDetails, toolName, toolVersion, otherDetails, encodingType); @@ -440,10 +377,7 @@ public class FileManager implements Closeable { * @throws TskCoreException If there is a problem completing a case database * operation. */ - public synchronized List addCarvedFiles(CarvingResult carvingResult) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } + public List addCarvedFiles(CarvingResult carvingResult) throws TskCoreException { return caseDb.addCarvedFiles(carvingResult); } @@ -489,10 +423,41 @@ public class FileManager implements Closeable { * @throws TskDataException if any of the local file paths is for a file or * directory that does not exist or cannot be read. */ - public synchronized LocalFilesDataSource addLocalFilesDataSource(String deviceId, String rootVirtualDirectoryName, String timeZone, List localFilePaths, FileAddProgressUpdater progressUpdater) throws TskCoreException, TskDataException { - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } + public LocalFilesDataSource addLocalFilesDataSource(String deviceId, String rootVirtualDirectoryName, String timeZone, List localFilePaths, FileAddProgressUpdater progressUpdater) throws TskCoreException, TskDataException { + return addLocalFilesDataSource(deviceId, rootVirtualDirectoryName, timeZone, null, localFilePaths, progressUpdater); + } + + /** + * Adds a set of local/logical files and/or directories to the case database + * as data source. + * + * @param deviceId An ASCII-printable identifier for the + * device associated with the data source + * that is intended to be unique across + * multiple cases (e.g., a UUID). + * @param rootVirtualDirectoryName The name to give to the virtual directory + * that will serve as the root for the + * local/logical files and/or directories + * that compose the data source. Pass the + * empty string to get a default name of the + * form: LogicalFileSet[N] + * @param timeZone The time zone used to process the data + * source, may be the empty string. + * @param host The host for this data source (may be null). + * @param localFilePaths A list of local/logical file and/or + * directory localFilePaths. + * @param progressUpdater Called after each file/directory is added + * to the case database. + * + * @return A local files data source object. + * + * @throws TskCoreException If there is a problem completing a database + * operation. + * @throws TskDataException if any of the local file paths is for a file or + * directory that does not exist or cannot be read. + */ + public LocalFilesDataSource addLocalFilesDataSource(String deviceId, String rootVirtualDirectoryName, String timeZone, Host host, + List localFilePaths, FileAddProgressUpdater progressUpdater) throws TskCoreException, TskDataException { List localFiles = getFilesAndDirectories(localFilePaths); CaseDbTransaction trans = null; try { @@ -506,7 +471,7 @@ public class FileManager implements Closeable { * children to the case database. */ trans = caseDb.beginTransaction(); - LocalFilesDataSource dataSource = caseDb.addLocalFilesDataSource(deviceId, rootDirectoryName, timeZone, trans); + LocalFilesDataSource dataSource = caseDb.addLocalFilesDataSource(deviceId, rootDirectoryName, timeZone, host, trans); List filesAdded = new ArrayList<>(); for (java.io.File localFile : localFiles) { AbstractFile fileAdded = addLocalFile(trans, dataSource, localFile, TskData.EncodingType.NONE, progressUpdater); @@ -552,7 +517,7 @@ public class FileManager implements Closeable { * @throws TskCoreException If there is a problem querying the case * database. */ - private static synchronized String generateFilesDataSourceName(SleuthkitCase caseDb) throws TskCoreException { + private static String generateFilesDataSourceName(SleuthkitCase caseDb) throws TskCoreException { int localFileDataSourcesCounter = 0; try { List localFileDataSources = caseDb.getVirtualDirectoryRoots(); @@ -643,13 +608,13 @@ public class FileManager implements Closeable { */ @Deprecated @Override - public synchronized void close() throws IOException { + public void close() throws IOException { /* * No-op maintained for backwards compatibility. Clients should not * attempt to close case services. */ - } - + } + /** * Adds a set of local/logical files and/or directories to the case database * as data source. @@ -669,10 +634,7 @@ public class FileManager implements Closeable { * @deprecated Use addLocalFilesDataSource instead. */ @Deprecated - public synchronized VirtualDirectory addLocalFilesDirs(List localFilePaths, FileAddProgressUpdater progressUpdater) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } + public VirtualDirectory addLocalFilesDirs(List localFilePaths, FileAddProgressUpdater progressUpdater) throws TskCoreException { try { return addLocalFilesDataSource("", "", "", localFilePaths, progressUpdater).getRootDirectory(); } catch (TskDataException ex) { @@ -699,10 +661,7 @@ public class FileManager implements Closeable { * carvingResult instead. */ @Deprecated - public synchronized LayoutFile addCarvedFile(String fileName, long fileSize, long parentObjId, List layout) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } + public LayoutFile addCarvedFile(String fileName, long fileSize, long parentObjId, List layout) throws TskCoreException { Content parent = caseDb.getContentById(parentObjId); List carvedFiles = new ArrayList<>(); carvedFiles.add(new CarvingResult.CarvedFile(fileName, fileSize, layout)); @@ -726,10 +685,7 @@ public class FileManager implements Closeable { * carvingResult instead. */ @Deprecated - public synchronized List addCarvedFiles(List filesToAdd) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("File manager has been closed"); - } + public List addCarvedFiles(List filesToAdd) throws TskCoreException { return caseDb.addCarvedFiles(filesToAdd); } @@ -764,7 +720,7 @@ public class FileManager implements Closeable { * @deprecated Use the version with explicit EncodingType instead */ @Deprecated - public synchronized DerivedFile addDerivedFile(String fileName, + public DerivedFile addDerivedFile(String fileName, String localPath, long size, long ctime, long crtime, long atime, long mtime, @@ -798,5 +754,28 @@ public class FileManager implements Closeable { private AbstractFile addLocalFile(CaseDbTransaction trans, SpecialDirectory parentDirectory, java.io.File localFile, FileAddProgressUpdater progressUpdater) throws TskCoreException { return addLocalFile(trans, parentDirectory, localFile, TskData.EncodingType.NONE, progressUpdater); } + + /** + * Finds all files and directories with a given file name and given parent + * file or directory in a given data source (image, local/logical files set, + * etc.). The name search is for full or partial matches and is case + * insensitive (a case insensitive SQL LIKE clause is used to query the case + * database). + * + * @param dataSource The data source. + * @param fileName The full name or a pattern to match on part of the name + * @param parent The parent file or directory. + * + * @return The matching files and directories. + * + * @throws TskCoreException if there is a problem querying the case + * database. + * + * @deprecated Use version without the unnecessary dataSource argument + */ + @Deprecated + public List findFiles(Content dataSource, String fileName, AbstractFile parent) throws TskCoreException { + return findFiles(fileName, parent); + } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java index 17cff6d900..24f208f421 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java @@ -31,6 +31,7 @@ import javax.annotation.concurrent.Immutable; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.CentralRepoSettings; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.datamodel.TagName; @@ -61,19 +62,19 @@ final public class TagNameDefinition implements Comparable { private final TskData.FileKnown knownStatus; private static final List STANDARD_TAGS_DEFINITIONS = new ArrayList<>(); - private static final List OLD_CATEGORY_TAG_NAMES = new ArrayList<>(); + private static final List PROJECT_VIC_NAMES_NO_LONGER_USED = new ArrayList<>(); static { STANDARD_TAGS_DEFINITIONS.add(new TagNameDefinition(Bundle.TagNameDefinition_predefTagNames_bookmark_text(), "", TagName.HTML_COLOR.NONE, TskData.FileKnown.UNKNOWN)); STANDARD_TAGS_DEFINITIONS.add(new TagNameDefinition(Bundle.TagNameDefinition_predefTagNames_followUp_text(), "", TagName.HTML_COLOR.NONE, TskData.FileKnown.UNKNOWN)); STANDARD_TAGS_DEFINITIONS.add(new TagNameDefinition(Bundle.TagNameDefinition_predefTagNames_notableItem_text(), "", TagName.HTML_COLOR.NONE, TskData.FileKnown.BAD)); - OLD_CATEGORY_TAG_NAMES.add("CAT-1: Child Exploitation (Illegal)"); - OLD_CATEGORY_TAG_NAMES.add("CAT-2: Child Exploitation (Non-Illegal/Age Difficult)"); - OLD_CATEGORY_TAG_NAMES.add("CAT-3: CGI/Animation (Child Exploitive)"); - OLD_CATEGORY_TAG_NAMES.add("CAT-4: Exemplar/Comparison (Internal Use Only)"); - OLD_CATEGORY_TAG_NAMES.add("CAT-5: Non-pertinent"); - OLD_CATEGORY_TAG_NAMES.add("CAT-0: Uncategorized"); + PROJECT_VIC_NAMES_NO_LONGER_USED.add("CAT-1: Child Exploitation (Illegal)"); + PROJECT_VIC_NAMES_NO_LONGER_USED.add("CAT-2: Child Exploitation (Non-Illegal/Age Difficult)"); + PROJECT_VIC_NAMES_NO_LONGER_USED.add("CAT-3: CGI/Animation (Child Exploitive)"); + PROJECT_VIC_NAMES_NO_LONGER_USED.add("CAT-4: Exemplar/Comparison (Internal Use Only)"); + PROJECT_VIC_NAMES_NO_LONGER_USED.add("CAT-5: Non-pertinent"); + PROJECT_VIC_NAMES_NO_LONGER_USED.add("CAT-0: Uncategorized"); } /** @@ -236,7 +237,7 @@ final public class TagNameDefinition implements Comparable { TagName saveToCase(SleuthkitCase caseDb) { TagName tagName = null; try { - tagName = caseDb.addOrUpdateTagName(displayName, description, color, knownStatus); + tagName = caseDb.getTaggingManager().addOrUpdateTagName(displayName, description, color, knownStatus); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error saving tag name definition", ex); } @@ -259,7 +260,7 @@ final public class TagNameDefinition implements Comparable { */ static synchronized Set getTagNameDefinitions() { if (needsVersionUpdate()) { - updateTagDefinitions(); + updatePropertyFile(); } String tagsProperty = ModuleSettings.getConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY); @@ -311,7 +312,7 @@ final public class TagNameDefinition implements Comparable { /** * Updates the Tag Definition file to the current format. */ - private static void updateTagDefinitions() { + private static void updatePropertyFile() { Integer version = getPropertyFileVersion(); List definitions = new ArrayList<>(); @@ -355,18 +356,18 @@ final public class TagNameDefinition implements Comparable { } // Remove the standard and Project VIC tags from the list - List tagStrings = new ArrayList<>(); + List tagStringsToKeep = new ArrayList<>(); List standardTags = getStandardTagNames(); for (TagNameDefinition def : definitions) { if (!standardTags.contains(def.getDisplayName()) - && !OLD_CATEGORY_TAG_NAMES.contains(def.getDisplayName())) { - tagStrings.add(def.toSettingsFormat()); + && !PROJECT_VIC_NAMES_NO_LONGER_USED.contains(def.getDisplayName())) { + tagStringsToKeep.add(def.toSettingsFormat()); } } // Write out the version and the new tag list. ModuleSettings.setConfigSetting(TAGS_SETTINGS_NAME, TAG_SETTING_VERSION_KEY, Integer.toString(TAG_SETTINGS_VERSION)); - ModuleSettings.setConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY, String.join(";", tagStrings)); + ModuleSettings.setConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY, String.join(";", tagStringsToKeep)); } /** @@ -375,7 +376,7 @@ final public class TagNameDefinition implements Comparable { * @return A list of tag names, or empty list if none were found. */ private static List getCRNotableList() { - String notableTagsProp = ModuleSettings.getConfigSetting("CentralRepository", "db.badTags"); // NON-NLS + String notableTagsProp = ModuleSettings.getConfigSetting(CentralRepoSettings.getInstance().getModuleSettingsKey(), "db.badTags"); // NON-NLS if (notableTagsProp != null && !notableTagsProp.isEmpty()) { return Arrays.asList(notableTagsProp.split(",")); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagSetDefinition.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagSetDefinition.java index ad11becdff..6303e2736e 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagSetDefinition.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagSetDefinition.java @@ -88,7 +88,7 @@ final public class TagSetDefinition { } /** - * Returns a list of the defined TagSet objects. + * Returns a list of configured TagSets (from the user's config folder) * * @return A list of TagSetDefinition objects or empty list if none were * found. diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index 1a9b2e792d..a4bd51bcce 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -18,9 +18,12 @@ */ package org.sleuthkit.autopsy.casemodule.services; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -29,8 +32,11 @@ import java.util.Map; import java.util.Set; import java.util.logging.Level; import org.openide.util.NbBundle; +import org.openide.util.WeakListeners; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.events.TagNamesEvent; +import org.sleuthkit.autopsy.casemodule.events.TagNamesEvent.TagNamesDeletedEvent; import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -55,9 +61,42 @@ public class TagsManager implements Closeable { private static final Logger LOGGER = Logger.getLogger(TagsManager.class.getName()); private final SleuthkitCase caseDb; - private static String DEFAULT_TAG_SET_NAME = "Project VIC"; + // NOTE: This name is also hard coded in Image Gallery and Projet Vic module. + // They need to stay in sync + private static String PROJECT_VIC_TAG_SET_NAME = "Project VIC"; private static final Object lock = new Object(); + + private final Map allTagNameMap = Collections.synchronizedMap(new HashMap<>()); + + private final PropertyChangeListener listener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(Case.Events.TAG_NAMES_ADDED.name()) + || evt.getPropertyName().equals(Case.Events.TAG_NAMES_UPDATED.name())) { + TagNamesEvent tagEvent = (TagNamesEvent) evt; + List addTagNames = tagEvent.getTagNames(); + for (TagName tag : addTagNames) { + allTagNameMap.put(tag.getDisplayName(), tag); + } + } else if (evt.getPropertyName().equals(Case.Events.TAG_NAMES_DELETED.name())) { + TagNamesDeletedEvent tagEvent = (TagNamesDeletedEvent) evt; + List deletedIds = tagEvent.getTagNameIds(); + List keysToRemove = new ArrayList<>(); + for (TagName tagName : getAllTagNames()) { + if (deletedIds.contains(tagName.getId())) { + keysToRemove.add(tagName.getDisplayName()); + } + } + + for (String key : keysToRemove) { + allTagNameMap.remove(key); + } + } + } + }; + + private final PropertyChangeListener weakListener = WeakListeners.propertyChange(listener, null); static { @@ -175,8 +214,6 @@ public class TagsManager implements Closeable { /* * No current case, nothing more to add to the set. */ - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Failed to get list of TagNames from TagsManager.", ex); } return tagDisplayNames; } @@ -196,7 +233,7 @@ public class TagsManager implements Closeable { try { List tagSetList = Case.getCurrentCaseThrows().getSleuthkitCase().getTaggingManager().getTagSets(); for (TagSet tagSet : tagSetList) { - if (tagSet.getName().equals(DEFAULT_TAG_SET_NAME)) { + if (tagSet.getName().equals(PROJECT_VIC_TAG_SET_NAME)) { for (TagName tagName : tagSet.getTagNames()) { tagList.add(tagName.getDisplayName()); } @@ -237,7 +274,7 @@ public class TagsManager implements Closeable { } /** - * Creates a new TagSetDefinition file. + * Creates a new TagSetDefinition file that will be used for future cases * * @param tagSetDef The tag set definition. * @@ -258,26 +295,34 @@ public class TagsManager implements Closeable { TagsManager(SleuthkitCase caseDb) { this.caseDb = caseDb; - // Add standard tags and the Project VIC default tag set and tags. + // Add standard tags and any configured tag sets. TaggingManager taggingMgr = caseDb.getTaggingManager(); try { - List setList = taggingMgr.getTagSets(); - if (setList.isEmpty()) { + List tagSetsInCase = taggingMgr.getTagSets(); + if (tagSetsInCase.isEmpty()) { + + // add the standard tag names for (TagNameDefinition def : TagNameDefinition.getStandardTagNameDefinitions()) { - caseDb.addOrUpdateTagName(def.getDisplayName(), def.getDescription(), def.getColor(), def.getKnownStatus()); + taggingMgr.addOrUpdateTagName(def.getDisplayName(), def.getDescription(), def.getColor(), def.getKnownStatus()); } - //Assume new case and add tag sets + + //Assume new case and add all tag sets for (TagSetDefinition setDef : TagSetDefinition.readTagSetDefinitions()) { - List tagNameList = new ArrayList<>(); + List tagNamesInSet = new ArrayList<>(); for (TagNameDefinition tagNameDef : setDef.getTagNameDefinitions()) { - tagNameList.add(caseDb.addOrUpdateTagName(tagNameDef.getDisplayName(), tagNameDef.getDescription(), tagNameDef.getColor(), tagNameDef.getKnownStatus())); + tagNamesInSet.add(taggingMgr.addOrUpdateTagName(tagNameDef.getDisplayName(), tagNameDef.getDescription(), tagNameDef.getColor(), tagNameDef.getKnownStatus())); } - if (!tagNameList.isEmpty()) { - taggingMgr.addTagSet(setDef.getName(), tagNameList); + if (!tagNamesInSet.isEmpty()) { + taggingMgr.addTagSet(setDef.getName(), tagNamesInSet); } } + } + + for(TagName tagName: caseDb.getAllTagNames()) { + allTagNameMap.put(tagName.getDisplayName(), tagName); } + } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error updating standard tag name and tag set definitions", ex); } catch (IOException ex) { @@ -287,6 +332,10 @@ public class TagsManager implements Closeable { for (TagNameDefinition tagName : TagNameDefinition.getTagNameDefinitions()) { tagName.saveToCase(caseDb); } + + Case.addEventTypeSubscriber(Collections.singleton(Case.Events.TAG_NAMES_UPDATED), weakListener); + Case.addEventTypeSubscriber(Collections.singleton(Case.Events.TAG_NAMES_ADDED), weakListener); + Case.addEventTypeSubscriber(Collections.singleton(Case.Events.TAG_NAMES_DELETED), weakListener); } /** @@ -332,11 +381,12 @@ public class TagsManager implements Closeable { * Gets a list of all tag names currently in the case database. * * @return A list, possibly empty, of TagName objects. - * - * @throws TskCoreException If there is an error querying the case database. */ - public List getAllTagNames() throws TskCoreException { - return caseDb.getAllTagNames(); + public synchronized List getAllTagNames() { + + List tagNames = new ArrayList<>(); + tagNames.addAll(allTagNameMap.values()); + return tagNames; } /** @@ -434,7 +484,7 @@ public class TagsManager implements Closeable { */ public Map getDisplayNamesToTagNamesMap() throws TskCoreException { Map tagNames = new HashMap<>(); - for (TagName tagName : caseDb.getAllTagNames()) { + for (TagName tagName : getAllTagNames()) { tagNames.put(tagName.getDisplayName(), tagName); } return tagNames; @@ -516,13 +566,13 @@ public class TagsManager implements Closeable { public TagName addTagName(String displayName, String description, TagName.HTML_COLOR color, TskData.FileKnown knownStatus) throws TagNameAlreadyExistsException, TskCoreException { synchronized (lock) { try { - TagName tagName = caseDb.addOrUpdateTagName(displayName, description, color, knownStatus); + TagName tagName = caseDb.getTaggingManager().addOrUpdateTagName(displayName, description, color, knownStatus); Set customTypes = TagNameDefinition.getTagNameDefinitions(); customTypes.add(new TagNameDefinition(displayName, description, color, knownStatus)); TagNameDefinition.setTagNameDefinitions(customTypes); return tagName; } catch (TskCoreException ex) { - List existingTagNames = caseDb.getAllTagNames(); + List existingTagNames = getAllTagNames(); for (TagName tagName : existingTagNames) { if (tagName.getDisplayName().equals(displayName)) { throw new TagNameAlreadyExistsException(); @@ -1034,5 +1084,4 @@ public class TagsManager implements Closeable { @Override public void close() throws IOException { } - } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java b/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java index 5f47487f94..1bef9719bb 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.centralrepository; import java.awt.event.ActionEvent; +import java.util.List; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.Action; @@ -64,7 +65,13 @@ public final class AddEditCentralRepoCommentAction extends AbstractAction { correlationAttributeInstance = CorrelationAttributeUtil.getCorrAttrForFile(file); if (correlationAttributeInstance == null) { addToDatabase = true; - correlationAttributeInstance = CorrelationAttributeUtil.makeCorrAttrFromFile(file); + final List md5CorrelationAttr = CorrelationAttributeUtil.makeCorrAttrsForSearch(file); + if (!md5CorrelationAttr.isEmpty()) { + //for an abstract file the 'list' of attributes will be a single attribute or empty and is returning a list for consistency with other makeCorrAttrsForSearch methods per 7852 + correlationAttributeInstance = md5CorrelationAttr.get(0); + } else { + correlationAttributeInstance = null; + } } if (file.getSize() == 0) { putValue(Action.NAME, Bundle.AddEditCentralRepoCommentAction_menuItemText_addEditCentralRepoCommentEmptyFile()); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle_ja.properties index a7854c6085..990e91a73e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle_ja.properties @@ -1,3 +1,12 @@ -#Tue Aug 18 18:09:21 UTC 2020 -OpenIDE-Module-Long-Description=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u53d6\u8fbc\u30e2\u30b8\u30e5\u30fc\u30eb\u3068\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3002\n\n\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30dd\u30b8\u30c8\u30ea\u306e\u53d6\u8fbc\u30e2\u30b8\u30e5\u30fc\u30eb\u306f\u3001\u9078\u629e\u3057\u305f\u76f8\u95a2\u30bf\u30a4\u30d7\u3068\u4e00\u81f4\u3059\u308b\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u306e\u5c5e\u6027\u3092\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u4fdd\u5b58\u3057\u307e\u3059\u3002\n\u4fdd\u5b58\u3055\u308c\u305f\u5c5e\u6027\u306f\u3001\u4eca\u5f8c\u306e\u30b1\u30fc\u30b9\u3067\u3001\u53d6\u308a\u8fbc\u307f\u4e2d\u306b\u30d5\u30a1\u30a4\u30eb\u3068\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u3092\u95a2\u9023\u4ed8\u3051\u3066\u5206\u6790\u3059\u308b\u305f\u3081\u306b\u4f7f\u7528\u3055\u308c\u307e\u3059\u3002\u300d -OpenIDE-Module-Short-Description=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u8aad\u8fbc\u30e2\u30b8\u30e5\u30fc\u30eb +#Mon Jul 12 13:21:59 UTC 2021 +AddEditCentralRepoCommentAction.menuItemText.addEditCentralRepoComment=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30b3\u30e1\u30f3\u30c8\u306e\u8ffd\u52a0/\u7de8\u96c6 +AddEditCentralRepoCommentAction.menuItemText.addEditCentralRepoCommentEmptyFile=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30b3\u30e1\u30f3\u30c8\u306e\u8ffd\u52a0/\u7de8\u96c6\uff08\u7a7a\u306e\u30d5\u30a1\u30a4\u30eb\uff09 +AddEditCentralRepoCommentAction.menuItemText.addEditCentralRepoCommentNoMD5=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30b3\u30e1\u30f3\u30c8\u306e\u8ffd\u52a0/\u7de8\u96c6\uff08MD5\u30cf\u30c3\u30b7\u30e5\u7121\u3057\uff09 +CentralRepoCommentDialog.cancelButton.text=C&ancel +CentralRepoCommentDialog.commentLabel.text=\u30b3\u30e1\u30f3\u30c8\uff1a +CentralRepoCommentDialog.okButton.text=&OK +CentralRepoCommentDialog.title.addEditCentralRepoComment=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30b3\u30e1\u30f3\u30c8\u306e\u8ffd\u52a0/\u7de8\u96c6 +OpenIDE-Module-Display-Category=\u53d6\u8fbc\u307f\u30e2\u30b8\u30e5\u30fc\u30eb +OpenIDE-Module-Long-Description=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u53d6\u308a\u8fbc\u307f\u30e2\u30b8\u30e5\u30fc\u30eb\u3068\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\n\n\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u53d6\u308a\u8fbc\u307f\u30e2\u30b8\u30e5\u30fc\u30eb\u306f\u3001\u9078\u629e\u3057\u305f\u76f8\u95a2\u30bf\u30a4\u30d7\u306b\u4e00\u81f4\u3059\u308b\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u306e\u5c5e\u6027\u3092\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u683c\u7d0d\u3057\u307e\u3059\u3002\n\u4fdd\u5b58\u3055\u308c\u305f\u5c5e\u6027\u306f\u3001\u5c06\u6765\u306e\u30b1\u30fc\u30b9\u3067\u3001\u53d6\u308a\u8fbc\u307f\u4e2d\u306b\u30d5\u30a1\u30a4\u30eb\u3068\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u3092\u76f8\u4e92\u306b\u95a2\u9023\u4ed8\u3051\u3066\u5206\u6790\u3059\u308b\u305f\u3081\u306b\u4f7f\u7528\u3055\u308c\u307e\u3059\u3002 +OpenIDE-Module-Name=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea +OpenIDE-Module-Short-Description=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u53d6\u308a\u8fbc\u307f\u30e2\u30b8\u30e5\u30fc\u30eb diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoSettings.java new file mode 100644 index 0000000000..d24b6245a6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoSettings.java @@ -0,0 +1,112 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2022 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.centralrepository; + +import java.io.File; +import java.nio.file.Paths; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; + +/** + * Location for central repo settings and paths. + */ +public class CentralRepoSettings { + + private static final CentralRepoSettings instance = new CentralRepoSettings(); + + /** + * @return The singleton instance of this class. + */ + public static CentralRepoSettings getInstance() { + return instance; + } + + private static final String CENTRAL_REPOSITORY_FOLDER = "CentralRepository"; + private static final String CENTRAL_REPOSITORY_SETTINGS_NAME = "CentralRepository"; + private static final String CENTRAL_REPO_BASE_PATH = Paths.get( + PlatformUtil.getModuleConfigDirectory(), + CENTRAL_REPOSITORY_FOLDER).toString(); + + private static final String DEFAULT_DB_PARENT_PATH = Paths.get(CENTRAL_REPO_BASE_PATH, "LocalDatabase").toString(); + private static final String DEFAULT_DB_NAME = "central_repository.db"; + + // NOTE: if this changes, an equivalent fix will be needed in CentralRepoDatamodelTest for the String PROPERTIES_FILE + private static final String MODULE_SETTINGS_KEY = Paths.get( + Paths.get(PlatformUtil.getUserConfigDirectory()).relativize(Paths.get(PlatformUtil.getModuleConfigDirectory())).toString(), + CENTRAL_REPOSITORY_FOLDER, + CENTRAL_REPOSITORY_SETTINGS_NAME).toString(); + + private static final String MODULE_SETTINGS_PROPERTIES = Paths.get( + CENTRAL_REPO_BASE_PATH, + CENTRAL_REPOSITORY_SETTINGS_NAME + ".properties").toString(); + + private static final String DATABASE_NAME_KEY = "db.sqlite.dbName"; //NON-NLS + private static final String DATABASE_PATH_KEY = "db.sqlite.dbDirectory"; //NON-NLS + + /** + * @return The base path for central repository settings. + */ + public String getSettingsBaseFolder() { + return CENTRAL_REPO_BASE_PATH; + } + + /** + * @return The module settings key that places the settings file within + * getSettingsBaseFolder. + */ + public String getModuleSettingsKey() { + return MODULE_SETTINGS_KEY; + } + + /** + * @return The path to the central repo settings. + */ + public String getModuleSettingsFile() { + return MODULE_SETTINGS_PROPERTIES; + } + + /** + * @return The default database parent path for sqlite cr. + */ + public String getDefaultDbPath() { + return DEFAULT_DB_PARENT_PATH; + } + + /** + * @return The default sqlite database name. + */ + public String getDefaultDbName() { + return DEFAULT_DB_NAME; + } + + /** + * @return The properties key for the sqlite database name in the settings. + */ + public String getDatabaseNameKey() { + return DATABASE_NAME_KEY; + } + + /** + * @return The properties key for the sqlite database path in the settings. + */ + public String getDatabasePathKey() { + return DATABASE_PATH_KEY; + } + + +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/application/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/application/Bundle.properties-MERGED new file mode 100755 index 0000000000..458d4b520b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/application/Bundle.properties-MERGED @@ -0,0 +1,8 @@ +OtherOccurrences.csvHeader.attribute=Matched Attribute +OtherOccurrences.csvHeader.case=Case +OtherOccurrences.csvHeader.comment=Comment +OtherOccurrences.csvHeader.dataSource=Data Source +OtherOccurrences.csvHeader.device=Device +OtherOccurrences.csvHeader.known=Known +OtherOccurrences.csvHeader.path=Path +OtherOccurrences.csvHeader.value=Attribute Value diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/application/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/application/Bundle_ja.properties new file mode 100644 index 0000000000..280987922a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/application/Bundle_ja.properties @@ -0,0 +1,9 @@ +#Thu Sep 30 10:26:59 UTC 2021 +OtherOccurrences.csvHeader.attribute=\u4e00\u81f4\u3057\u305f\u5c5e\u6027 +OtherOccurrences.csvHeader.case=\u30b1\u30fc\u30b9 +OtherOccurrences.csvHeader.comment=\u30b3\u30e1\u30f3\u30c8 +OtherOccurrences.csvHeader.dataSource=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9 +OtherOccurrences.csvHeader.device=\u30c7\u30d0\u30a4\u30b9 +OtherOccurrences.csvHeader.known=\u65e2\u77e5 +OtherOccurrences.csvHeader.path=\u30d1\u30b9 +OtherOccurrences.csvHeader.value=\u5c5e\u6027\u5024 diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/application/NodeData.java b/Core/src/org/sleuthkit/autopsy/centralrepository/application/NodeData.java new file mode 100644 index 0000000000..0f2ada5220 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/application/NodeData.java @@ -0,0 +1,254 @@ +/* + * Central Repository + * + * Copyright 2018-2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.centralrepository.application; + +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.TskDataException; + +/** + * Class for populating the Other Occurrences tab + */ +public class NodeData { + + // For now hard code the string for the central repo files type, since + // getting it dynamically can fail. + private static final String FILE_TYPE_STR = "Files"; + private static final String CSV_ITEM_SEPARATOR = "\",\""; + + private final String caseName; + private String deviceID; + private String dataSourceName; + private final String filePath; + private final String typeStr; + private final String value; + private TskData.FileKnown known; + private String comment; + + private AbstractFile originalAbstractFile = null; + private CorrelationAttributeInstance originalCorrelationInstance = null; + + /** + * Create a node from a central repo instance. + * + * @param instance The central repo instance + * @param type The type of the instance + * @param value The value of the instance + */ + public NodeData(CorrelationAttributeInstance instance, CorrelationAttributeInstance.Type type, String value) { + caseName = instance.getCorrelationCase().getDisplayName(); + deviceID = instance.getCorrelationDataSource().getDeviceID(); + dataSourceName = instance.getCorrelationDataSource().getName(); + filePath = instance.getFilePath(); + this.typeStr = type.getDisplayName(); + this.value = value; + known = instance.getKnownStatus(); + comment = instance.getComment(); + + originalCorrelationInstance = instance; + } + + /** + * Create a node from an abstract file. + * + * @param newFile The abstract file + * @param autopsyCase The current case + * + * @throws CentralRepoException + */ + NodeData(AbstractFile newFile, Case autopsyCase) throws CentralRepoException { + caseName = autopsyCase.getDisplayName(); + try { + DataSource dataSource = autopsyCase.getSleuthkitCase().getDataSource(newFile.getDataSource().getId()); + deviceID = dataSource.getDeviceId(); + dataSourceName = dataSource.getName(); + } catch (TskDataException | TskCoreException ex) { + throw new CentralRepoException("Error loading data source for abstract file ID " + newFile.getId(), ex); + } + + filePath = newFile.getParentPath() + newFile.getName(); + typeStr = FILE_TYPE_STR; + value = newFile.getMd5Hash(); + known = newFile.getKnown(); + comment = ""; + + originalAbstractFile = newFile; + } + + /** + * Check if this node is a "file" type + * + * @return true if it is a file type + */ + boolean isFileType() { + return FILE_TYPE_STR.equals(typeStr); + } + + /** + * Update the known status for this node + * + * @param newKnownStatus The new known status + */ + void updateKnown(TskData.FileKnown newKnownStatus) { + known = newKnownStatus; + } + + /** + * Update the comment for this node + * + * @param newComment The new comment + */ + public void updateComment(String newComment) { + comment = newComment; + } + + /** + * Get the case name + * + * @return the case name + */ + public String getCaseName() { + return caseName; + } + + /** + * Get the device ID + * + * @return the device ID + */ + public String getDeviceID() { + return deviceID; + } + + /** + * Get the data source name + * + * @return the data source name + */ + public String getDataSourceName() { + return dataSourceName; + } + + /** + * Get the file path + * + * @return the file path + */ + public String getFilePath() { + return filePath; + } + + /** + * Get the type (as a string) + * + * @return the type + */ + public String getType() { + return typeStr; + } + + /** + * Get the value (MD5 hash for files) + * + * @return the value + */ + public String getValue() { + return value; + } + + /** + * Get the known status + * + * @return the known status + */ + public TskData.FileKnown getKnown() { + return known; + } + + /** + * Get the comment + * + * @return the comment + */ + public String getComment() { + return comment; + } + + /** + * Get the backing abstract file. Should only be called if + * isCentralRepoNode() is false + * + * @return the original abstract file + */ + public AbstractFile getAbstractFile() throws CentralRepoException { + if (originalAbstractFile == null) { + throw new CentralRepoException("AbstractFile is null"); + } + return originalAbstractFile; + } + + /** + * Get the backing CorrelationAttributeInstance. Should only be called if + * isCentralRepoNode() is true + * + * @return the original CorrelationAttributeInstance + * + * @throws CentralRepoException + */ + public CorrelationAttributeInstance getCorrelationAttributeInstance() throws CentralRepoException { + if (originalCorrelationInstance == null) { + throw new CentralRepoException("CorrelationAttributeInstance is null"); + } + return originalCorrelationInstance; + } + + /** + * Get the string to append between elements when writing the node instance + * data to a CSV + * + * @return the CSV_ITEM_SEPARATOR string + */ + public static String getCsvItemSeparator() { + return CSV_ITEM_SEPARATOR; + } + + /** + * Create a string representation of the node's data comma separated with a + * line separator ending + * + * @return a comma separated string representation of the node's data + */ + String toCsvString() { + StringBuilder line = new StringBuilder("\""); + line.append(getCaseName()).append(CSV_ITEM_SEPARATOR) + .append(getDataSourceName()).append(CSV_ITEM_SEPARATOR) + .append(getType()).append(CSV_ITEM_SEPARATOR) + .append(getValue()).append(CSV_ITEM_SEPARATOR) + .append(getKnown().toString()).append(CSV_ITEM_SEPARATOR) + .append(getFilePath()).append(CSV_ITEM_SEPARATOR) + .append(getComment()).append('"') + .append(System.getProperty("line.separator")); + return line.toString(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java b/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java new file mode 100755 index 0000000000..ae35d971bf --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java @@ -0,0 +1,288 @@ +/* + * Central Repository + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.centralrepository.application; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.logging.Level; +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTimeZone; +import org.joda.time.LocalDateTime; +import org.openide.nodes.Node; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.OsAccount; +import org.sleuthkit.datamodel.OsAccountInstance; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * Contains most of the methods for gathering data from the DB and CR for the + * OtherOccurrencesPanel. + */ +public final class OtherOccurrences { + + private static final Logger logger = Logger.getLogger(OtherOccurrences.class.getName()); + + private static final String UUID_PLACEHOLDER_STRING = "NoCorrelationAttributeInstance"; + + private OtherOccurrences() { + } + + /** + * Determine what attributes can be used for correlation based on the node. + * + * @param node The node to correlate + * @param osAccount the osAccount to correlate + * + * @return A list of attributes that can be used for correlation + */ + public static Collection getCorrelationAttributeFromOsAccount(Node node, OsAccount osAccount) { + Optional osAccountAddr = osAccount.getAddr(); + if (osAccountAddr.isPresent()) { + try { + for (OsAccountInstance instance : osAccount.getOsAccountInstances()) { + List correlationAttributeInstances = CorrelationAttributeUtil.makeCorrAttrsForSearch(instance); + if (!correlationAttributeInstances.isEmpty()) { + return correlationAttributeInstances; + } + } + } catch (TskCoreException ex) { + logger.log(Level.INFO, String.format("Unable to check create CorrelationAttribtueInstance for osAccount %s.", osAccountAddr.get()), ex); + } + } + return Collections.emptyList(); + } + + /** + * Query the central repo database (if enabled) and the case database to + * find all artifact instances correlated to the given central repository + * artifact. + * + * @param deviceId The device ID for the current data source. + * @param dataSourceName The name of the current data source. + * @param corAttr CorrelationAttribute to query for + * + * @return A collection of correlated artifact instances + */ + public static Map getCorrelatedInstances(String deviceId, String dataSourceName, CorrelationAttributeInstance corAttr) { + // @@@ Check exception + try { + final Case openCase = Case.getCurrentCaseThrows(); + String caseUUID = openCase.getName(); + HashMap nodeDataMap = new HashMap<>(); + + if (CentralRepository.isEnabled()) { + List instances = CentralRepository.getInstance().getArtifactInstancesByTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue()); + + for (CorrelationAttributeInstance artifactInstance : instances) { + + // Only add the attribute if it isn't the object the user selected. + // We consider it to be a different object if at least one of the following is true: + // - the case UUID is different + // - the data source name is different + // - the data source device ID is different + // - the object id for the underlying file is different + if (artifactInstance.getCorrelationCase().getCaseUUID().equals(caseUUID) + && (!StringUtils.isBlank(dataSourceName) && artifactInstance.getCorrelationDataSource().getName().equals(dataSourceName)) + && (!StringUtils.isBlank(deviceId) && artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId))) { + Long foundObjectId = artifactInstance.getFileObjectId(); + Long currentObjectId = corAttr.getFileObjectId(); + if (foundObjectId != null && currentObjectId != null && foundObjectId.equals(currentObjectId)) { + continue; + } + } + NodeData newNode = new NodeData(artifactInstance, corAttr.getCorrelationType(), corAttr.getCorrelationValue()); + UniquePathKey uniquePathKey = new UniquePathKey(newNode); + nodeDataMap.put(uniquePathKey, newNode); + } + } + return nodeDataMap; + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS + } catch (CorrelationAttributeNormalizationException ex) { + logger.log(Level.INFO, "Error getting artifact instances from database.", ex); // NON-NLS + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS + } + + return new HashMap<>( + 0); + } + + /** + * Adds the file to the nodeDataMap map if it does not already exist + * + * @param autopsyCase + * @param nodeDataMap + * @param newFile + * + * @throws TskCoreException + * @throws CentralRepoException + */ + public static void addOrUpdateNodeData(final Case autopsyCase, Map nodeDataMap, AbstractFile newFile) throws TskCoreException, CentralRepoException { + + NodeData newNode = new NodeData(newFile, autopsyCase); + + // If the caseDB object has a notable tag associated with it, update + // the known status to BAD + if (newNode.getKnown() != TskData.FileKnown.BAD) { + List fileMatchTags = autopsyCase.getServices().getTagsManager().getContentTagsByContent(newFile); + for (ContentTag tag : fileMatchTags) { + TskData.FileKnown tagKnownStatus = tag.getName().getKnownStatus(); + if (tagKnownStatus.equals(TskData.FileKnown.BAD)) { + newNode.updateKnown(TskData.FileKnown.BAD); + break; + } + } + } + + // Make a key to see if the file is already in the map + UniquePathKey uniquePathKey = new UniquePathKey(newNode); + + // If this node is already in the list, the only thing we need to do is + // update the known status to BAD if the caseDB version had known status BAD. + // Otherwise this is a new node so add the new node to the map. + if (nodeDataMap.containsKey(uniquePathKey)) { + if (newNode.getKnown() == TskData.FileKnown.BAD) { + NodeData prevInstance = nodeDataMap.get(uniquePathKey); + prevInstance.updateKnown(newNode.getKnown()); + } + } else { + nodeDataMap.put(uniquePathKey, newNode); + } + } + + /** + * Create a unique string to be used as a key for deduping data sources as + * best as possible + */ + public static String makeDataSourceString(String caseUUID, String deviceId, String dataSourceName) { + return caseUUID + deviceId + dataSourceName; + } + + /** + * Gets the list of Eam Cases and determines the earliest case creation + * date. Sets the label to display the earliest date string to the user. + */ + public static String getEarliestCaseDate() throws CentralRepoException { + String dateStringDisplay = ""; + + if (CentralRepository.isEnabled()) { + LocalDateTime earliestDate = LocalDateTime.now(DateTimeZone.UTC); + DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.US); + CentralRepository dbManager = CentralRepository.getInstance(); + List cases = dbManager.getCases(); + for (CorrelationCase aCase : cases) { + LocalDateTime caseDate; + try { + caseDate = LocalDateTime.fromDateFields(datetimeFormat.parse(aCase.getCreationDate())); + + if (caseDate.isBefore(earliestDate)) { + earliestDate = caseDate; + dateStringDisplay = aCase.getCreationDate(); + } + } catch (ParseException ex) { + throw new CentralRepoException("Failed to format case creation date " + aCase.getCreationDate(), ex); + } + } + } + + return dateStringDisplay; + } + + @NbBundle.Messages({ + "OtherOccurrences.csvHeader.case=Case", + "OtherOccurrences.csvHeader.device=Device", + "OtherOccurrences.csvHeader.dataSource=Data Source", + "OtherOccurrences.csvHeader.attribute=Matched Attribute", + "OtherOccurrences.csvHeader.value=Attribute Value", + "OtherOccurrences.csvHeader.known=Known", + "OtherOccurrences.csvHeader.path=Path", + "OtherOccurrences.csvHeader.comment=Comment" + }) + + /** + * Create a cvs file of occurrences for the given parameters. + * + * @param destFile Output file for the csv data. + * @param correlationAttList List of correclationAttributeInstances, should + * not be null. + * @param dataSourceName Name of the data source. + * @param deviceId Device id. + * + * @throws IOException + */ + public static void writeOtherOccurrencesToFileAsCSV(File destFile, Collection correlationAttList, String dataSourceName, String deviceId) throws IOException { + try (BufferedWriter writer = Files.newBufferedWriter(destFile.toPath())) { + //write headers + StringBuilder headers = new StringBuilder("\""); + headers.append(Bundle.OtherOccurrences_csvHeader_case()) + .append(NodeData.getCsvItemSeparator()).append(Bundle.OtherOccurrences_csvHeader_dataSource()) + .append(NodeData.getCsvItemSeparator()).append(Bundle.OtherOccurrences_csvHeader_attribute()) + .append(NodeData.getCsvItemSeparator()).append(Bundle.OtherOccurrences_csvHeader_value()) + .append(NodeData.getCsvItemSeparator()).append(Bundle.OtherOccurrences_csvHeader_known()) + .append(NodeData.getCsvItemSeparator()).append(Bundle.OtherOccurrences_csvHeader_path()) + .append(NodeData.getCsvItemSeparator()).append(Bundle.OtherOccurrences_csvHeader_comment()) + .append('"').append(System.getProperty("line.separator")); + writer.write(headers.toString()); + //write content + for (CorrelationAttributeInstance corAttr : correlationAttList) { + Map correlatedNodeDataMap = new HashMap<>(0); + // get correlation and reference set instances from DB + correlatedNodeDataMap.putAll(getCorrelatedInstances(deviceId, dataSourceName, corAttr)); + for (NodeData nodeData : correlatedNodeDataMap.values()) { + writer.write(nodeData.toCsvString()); + } + } + } + } + + /** + * Get a placeholder string to use in place of case uuid when it isn't + * available + * + * @return UUID_PLACEHOLDER_STRING + */ + public static String getPlaceholderUUID() { + return UUID_PLACEHOLDER_STRING; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/application/UniquePathKey.java b/Core/src/org/sleuthkit/autopsy/centralrepository/application/UniquePathKey.java new file mode 100644 index 0000000000..0ba064c58e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/application/UniquePathKey.java @@ -0,0 +1,117 @@ +/* + * Central Repository + * + * Copyright 2017-2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.centralrepository.application; + +import java.util.Objects; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * Used as a key to ensure we eliminate duplicates from the result set by not + * overwriting CR correlation instances. + */ +public final class UniquePathKey { + + private static final Logger logger = Logger.getLogger(UniquePathKey.class.getName()); + private final String dataSourceID; + private final String filePath; + private final String type; + private final String caseUUID; + + public UniquePathKey(NodeData nodeData) { + super(); + dataSourceID = nodeData.getDeviceID(); + if (nodeData.getFilePath() != null) { + filePath = nodeData.getFilePath().toLowerCase(); + } else { + filePath = null; + } + type = nodeData.getType(); + String tempCaseUUID; + try { + tempCaseUUID = nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(); + } catch (CentralRepoException ignored) { + //non central repo nodeData won't have a correlation case + try { + tempCaseUUID = Case.getCurrentCaseThrows().getName(); + //place holder value will be used since correlation attribute was unavailble + } catch (NoCurrentCaseException ex) { + logger.log(Level.WARNING, "Unable to get current case", ex); + tempCaseUUID = OtherOccurrences.getPlaceholderUUID(); + } + } + caseUUID = tempCaseUUID; + } + + @Override + public boolean equals(Object other) { + if (other instanceof UniquePathKey) { + UniquePathKey otherKey = (UniquePathKey) (other); + return (Objects.equals(otherKey.getDataSourceID(), this.getDataSourceID()) + && Objects.equals(otherKey.getFilePath(), this.getFilePath()) + && Objects.equals(otherKey.getType(), this.getType()) + && Objects.equals(otherKey.getCaseUUID(), this.getCaseUUID())); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(getDataSourceID(), getFilePath(), getType(), getCaseUUID()); + } + + /** + * Get the type of this UniquePathKey. + * + * @return the type + */ + String getType() { + return type; + } + + /** + * Get the file path for the UniquePathKey. + * + * @return the filePath + */ + String getFilePath() { + return filePath; + } + + /** + * Get the data source id for the UniquePathKey. + * + * @return the dataSourceID + */ + String getDataSourceID() { + return dataSourceID; + } + + /** + * Get the case uuid for the UniquePathKey + * + * @return the case UUID + */ + String getCaseUUID() { + return caseUUID; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties index fca33fe6f4..f4bbc18c1c 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties @@ -1,8 +1,10 @@ -DataContentViewerOtherCases.showCaseDetailsMenuItem.text=Show Case Details DataContentViewerOtherCases.table.toolTip.text=Click column name to sort. Right-click on the table for more options. -DataContentViewerOtherCases.exportToCSVMenuItem.text=Export all Other Occurrences to CSV -DataContentViewerOtherCases.showCommonalityMenuItem.text=Show Frequency -DataContentViewerOtherCases.earliestCaseDate.text=Earliest Case Date -DataContentViewerOtherCases.earliestCaseLabel.toolTipText= -DataContentViewerOtherCases.earliestCaseLabel.text=Central Repository Starting Date: -DataContentViewerOtherCases.foundInLabel.text= +OtherOccurrencesPanel.caseDatasourceFileSplitPane.toolTipText= +OtherOccurrencesPanel.earliestCaseLabel.toolTipText= +OtherOccurrencesPanel.earliestCaseLabel.text=Central Repository Starting Date: +OtherOccurrencesPanel.earliestCaseDate.text=Earliest Case Date +OtherOccurrencesPanel.foundInLabel.text= +OtherOccurrencesPanel.filesTable.toolTipText=Click column name to sort. Right-click on the table for more options. +OtherOccurrencesPanel.exportToCSVMenuItem.text=Export all Other Occurrences to CSV +OtherOccurrencesPanel.showCommonalityMenuItem.text=Show Frequency +OtherOccurrencesPanel.showCaseDetailsMenuItem.text=Show Case Details diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties-MERGED index b2606170ed..a97cc319da 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties-MERGED @@ -1,38 +1,6 @@ -DataContentViewerOtherCases.caseDetailsDialog.noCaseNameError=Error -DataContentViewerOtherCases.caseDetailsDialog.noDetails=No details for this case. -DataContentViewerOtherCases.caseDetailsDialog.noDetailsReference=No case details for Global reference properties. -DataContentViewerOtherCases.caseDetailsDialog.notSelected=No Row Selected -# {0} - commonality percentage -# {1} - correlation type -# {2} - correlation value -DataContentViewerOtherCases.correlatedArtifacts.byType={0}% of data sources have {2} (type: {1})\n -DataContentViewerOtherCases.correlatedArtifacts.failed=Failed to get frequency details. -DataContentViewerOtherCases.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate. -DataContentViewerOtherCases.correlatedArtifacts.title=Attribute Frequency -DataContentViewerOtherCases.dataSources.header.text=Data Source Name -DataContentViewerOtherCases.earliestCaseNotAvailable=\ Not Enabled. -DataContentViewerOtherCases.foundIn.text=Found %d instances in %d cases and %d data sources. -DataContentViewerOtherCases.noOpenCase.errMsg=No open case available. -DataContentViewerOtherCases.showCaseDetailsMenuItem.text=Show Case Details -DataContentViewerOtherCases.table.noArtifacts=Item has no attributes with which to search. -DataContentViewerOtherCases.table.noResultsFound=No results found. DataContentViewerOtherCases.table.toolTip.text=Click column name to sort. Right-click on the table for more options. -DataContentViewerOtherCases.exportToCSVMenuItem.text=Export all Other Occurrences to CSV -DataContentViewerOtherCases.showCommonalityMenuItem.text=Show Frequency -DataContentViewerOtherCases.earliestCaseDate.text=Earliest Case Date -DataContentViewerOtherCases.earliestCaseLabel.toolTipText= -DataContentViewerOtherCases.earliestCaseLabel.text=Central Repository Starting Date: -DataContentViewerOtherCases.foundInLabel.text= DataContentViewerOtherCases.title=Other Occurrences DataContentViewerOtherCases.toolTip=Displays instances of the selected file/artifact from other occurrences. -DataContentViewerOtherCasesModel.csvHeader.attribute=Matched Attribute -DataContentViewerOtherCasesModel.csvHeader.case=Case -DataContentViewerOtherCasesModel.csvHeader.comment=Comment -DataContentViewerOtherCasesModel.csvHeader.dataSource=Data Source -DataContentViewerOtherCasesModel.csvHeader.device=Device -DataContentViewerOtherCasesModel.csvHeader.known=Known -DataContentViewerOtherCasesModel.csvHeader.path=Path -DataContentViewerOtherCasesModel.csvHeader.value=Attribute Value OccurrencePanel.caseCreatedDateLabel.text=Created Date: OccurrencePanel.caseDetails.text=Case Details OccurrencePanel.caseNameLabel.text=Name: @@ -51,3 +19,29 @@ OtherOccurrencesDataSourcesTableModel.dataSourceName=Data Source Name OtherOccurrencesDataSourcesTableModel.noData=No Data. OtherOccurrencesFilesTableModel.fileName=File Name OtherOccurrencesFilesTableModel.noData=No Data. +OtherOccurrencesPanel.caseDatasourceFileSplitPane.toolTipText= +OtherOccurrencesPanel.caseDetailsDialog.noCaseNameError=Error +OtherOccurrencesPanel.caseDetailsDialog.noDetails=No details for this case. +OtherOccurrencesPanel.caseDetailsDialog.noDetailsReference=No case details for Global reference properties. +OtherOccurrencesPanel.caseDetailsDialog.notSelected=No Row Selected +# {0} - commonality percentage +# {1} - correlation type +# {2} - correlation value +OtherOccurrencesPanel.correlatedArtifacts.byType={0}% of data sources have {2} (type: {1})\n +OtherOccurrencesPanel.correlatedArtifacts.failed=Failed to get frequency details. +OtherOccurrencesPanel.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate. +OtherOccurrencesPanel.correlatedArtifacts.title=Attribute Frequency +OtherOccurrencesPanel.earliestCaseLabel.toolTipText= +OtherOccurrencesPanel.earliestCaseLabel.text=Central Repository Starting Date: +OtherOccurrencesPanel.earliestCaseDate.text=Earliest Case Date +OtherOccurrencesPanel.foundIn.text=Found %d instances in %d cases and %d data sources. +OtherOccurrencesPanel.foundInLabel.text= +OtherOccurrencesPanel.filesTable.toolTipText=Click column name to sort. Right-click on the table for more options. +OtherOccurrencesPanel.exportToCSVMenuItem.text=Export all Other Occurrences to CSV +OtherOccurrencesPanel.noOpenCase.errMsg=No open case available. +OtherOccurrencesPanel.showCommonalityMenuItem.text=Show Frequency +OtherOccurrencesPanel.showCaseDetailsMenuItem.text=Show Case Details +OtherOccurrencesPanel.table.noArtifacts=Item has no attributes with which to search. +OtherOccurrencesPanel.table.noResultsFound=No results found. +OtherOccurrencesPanel_earliestCaseNotAvailable=Not Available. +OtherOccurrencesPanel_table_loadingResults=Loading results diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle_ja.properties index ed1195288f..abd7a09fc7 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle_ja.properties @@ -1,53 +1,42 @@ -DataContentViewerOtherCases.caseDetailsDialog.noCaseNameError=\u30a8\u30e9\u30fc -DataContentViewerOtherCases.caseDetailsDialog.noDetails=\u3053\u306e\u30b1\u30fc\u30b9\u306e\u8a73\u7d30\u306f\u3042\u308a\u307e\u305b\u3093\u3002 -DataContentViewerOtherCases.caseDetailsDialog.noDetailsReference=\u30b0\u30ed\u30fc\u30d0\u30eb\u30ec\u30d5\u30a1\u30ec\u30f3\u30b9\u30d7\u30ed\u30d1\u30c6\u30a3\u306e\u30b1\u30fc\u30b9\u8a73\u7d30\u306f\u3042\u308a\u307e\u305b\u3093\u3002 -DataContentViewerOtherCases.caseDetailsDialog.notSelected=\u884c\u304c\u9078\u629e\u3055\u308c\u3066\u3044\u307e\u305b\u3093 -# {0} - \u5171\u6709\u6027\u306e\u5272\u5408 -# {1} - \u76f8\u95a2\u5206\u6790\u30bf\u30a4\u30d7 -# {2} - \u76f8\u95a2\u5206\u6790\u5024 -DataContentViewerOtherCases.correlatedArtifacts.byType=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306e {0}% \u306b {2} \u304c\u3042\u308a\u307e\u3059(\u30bf\u30a4\u30d7: {1})\n -DataContentViewerOtherCases.correlatedArtifacts.failed=\u983b\u5ea6\u306e\u8a73\u7d30\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 -DataContentViewerOtherCases.correlatedArtifacts.isEmpty=\u76f8\u95a2\u5206\u6790\u3059\u308b\u305f\u3081\u306e\u30d5\u30a1\u30a4\u30eb\u3084\u904e\u53bb\u306e\u691c\u7d22\u7d50\u679c\u304c\u3042\u308a\u307e\u305b\u3093\u3002 -DataContentViewerOtherCases.correlatedArtifacts.title=\u5c5e\u6027\u983b\u5ea6 -DataContentViewerOtherCases.dataSources.header.text=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u540d -DataContentViewerOtherCases.earliestCaseNotAvailable=\ \u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002 -DataContentViewerOtherCases.foundIn.text=%d \u306e\u30b1\u30fc\u30b9\u3068 %d \u306e\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u5185\u306b %d \u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u3057\u305f\u3002 -DataContentViewerOtherCases.noOpenCase.errMsg=\u958b\u3044\u3066\u3044\u308b\u30b1\u30fc\u30b9\u306f\u3042\u308a\u307e\u305b\u3093\u3002 -DataContentViewerOtherCases.showCaseDetailsMenuItem.text=\u30b1\u30fc\u30b9\u8a73\u7d30\u3092\u8868\u793a -DataContentViewerOtherCases.table.noArtifacts=\u9805\u76ee\u306b\u691c\u7d22\u306b\u5229\u7528\u3067\u304d\u308b\u5c5e\u6027\u306f\u3042\u308a\u307e\u305b\u3093\u3002 -DataContentViewerOtherCases.table.noResultsFound=\u8a72\u5f53\u3059\u308b\u7d50\u679c\u304c\u3042\u308a\u307e\u305b\u3093\u3002 +#Thu Sep 30 10:26:59 UTC 2021 DataContentViewerOtherCases.table.toolTip.text=\u5217\u540d\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u30bd\u30fc\u30c8\u3057\u307e\u3059\u3002\u30c6\u30fc\u30d6\u30eb\u3092\u53f3\u30af\u30ea\u30c3\u30af\u3057\u3066\u3055\u3089\u306a\u308b\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u8868\u793a\u3057\u307e\u3059\u3002 -DataContentViewerOtherCases.exportToCSVMenuItem.text=\u305d\u306e\u4ed6\u3059\u3079\u3066\u306e\u767a\u751f\u3092CSV\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8 -DataContentViewerOtherCases.showCommonalityMenuItem.text=\u983b\u5ea6\u3092\u8868\u793a -DataContentViewerOtherCases.earliestCaseDate.text=\u6700\u3082\u53e4\u3044\u30b1\u30fc\u30b9\u65e5\u4ed8 -DataContentViewerOtherCases.earliestCaseLabel.toolTipText= -DataContentViewerOtherCases.earliestCaseLabel.text=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ec\u30dd\u30b8\u30c8\u30ea\u30fc\u958b\u59cb\u65e5: -DataContentViewerOtherCases.foundInLabel.text= DataContentViewerOtherCases.title=\u305d\u306e\u4ed6\u306e\u767a\u751f DataContentViewerOtherCases.toolTip=\u305d\u306e\u4ed6\u306e\u767a\u751f\u304b\u3089\u9078\u629e\u3057\u305f\u30d5\u30a1\u30a4\u30eb/\u904e\u53bb\u306e\u691c\u7d22\u7d50\u679c\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u3092\u8868\u793a\u3057\u307e\u3059\u3002 -DataContentViewerOtherCasesModel.csvHeader.attribute=\u4e00\u81f4\u3057\u305f\u5c5e\u6027 -DataContentViewerOtherCasesModel.csvHeader.case=\u30b1\u30fc\u30b9 -DataContentViewerOtherCasesModel.csvHeader.comment=\u30b3\u30e1\u30f3\u30c8 -DataContentViewerOtherCasesModel.csvHeader.dataSource=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9 -DataContentViewerOtherCasesModel.csvHeader.device=\u30c7\u30d0\u30a4\u30b9 -DataContentViewerOtherCasesModel.csvHeader.known=\u65e2\u77e5 -DataContentViewerOtherCasesModel.csvHeader.path=\u30d1\u30b9 -DataContentViewerOtherCasesModel.csvHeader.value=\u5c5e\u6027\u5024 -OccurrencePanel.caseCreatedDateLabel.text=\u4f5c\u6210\u65e5: +OccurrencePanel.caseCreatedDateLabel.text=\u4f5c\u6210\u65e5\: OccurrencePanel.caseDetails.text=\u30b1\u30fc\u30b9\u8a73\u7d30 -OccurrencePanel.caseNameLabel.text=\u540d\u524d: +OccurrencePanel.caseNameLabel.text=\u540d\u524d\: OccurrencePanel.commonProperties.text=\u5171\u901a\u306e\u30d7\u30ed\u30d1\u30c6\u30a3 -OccurrencePanel.commonPropertyCommentLabel.text=\u30b3\u30e1\u30f3\u30c8: -OccurrencePanel.commonPropertyKnownStatusLabel.text=\u65e2\u77e5\u306e\u30b9\u30c6\u30fc\u30bf\u30b9: -OccurrencePanel.commonPropertyTypeLabel.text=\u30bf\u30a4\u30d7: -OccurrencePanel.commonPropertyValueLabel.text=\u5024: +OccurrencePanel.commonPropertyCommentLabel.text=\u30b3\u30e1\u30f3\u30c8\: +OccurrencePanel.commonPropertyKnownStatusLabel.text=\u65e2\u77e5\u306e\u30b9\u30c6\u30fc\u30bf\u30b9\: +OccurrencePanel.commonPropertyTypeLabel.text=\u30bf\u30a4\u30d7\: +OccurrencePanel.commonPropertyValueLabel.text=\u5024\: OccurrencePanel.dataSourceDetails.text=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u8a73\u7d30 -OccurrencePanel.dataSourceNameLabel.text=\u540d\u524d: +OccurrencePanel.dataSourceNameLabel.text=\u540d\u524d\: OccurrencePanel.fileDetails.text=\u30d5\u30a1\u30a4\u30eb\u8a73\u7d30 -OccurrencePanel.filePathLabel.text=\u30d5\u30a1\u30a4\u30eb\u30d1\u30b9: +OccurrencePanel.filePathLabel.text=\u30d5\u30a1\u30a4\u30eb\u30d1\u30b9\: OtherOccurrencesCasesTableModel.case=\u30b1\u30fc\u30b9 OtherOccurrencesCasesTableModel.noData=\u30c7\u30fc\u30bf\u304c\u3042\u308a\u307e\u305b\u3093\u3002 OtherOccurrencesDataSourcesTableModel.dataSourceName=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u540d OtherOccurrencesDataSourcesTableModel.noData=\u30c7\u30fc\u30bf\u304c\u3042\u308a\u307e\u305b\u3093\u3002 OtherOccurrencesFilesTableModel.fileName=\u30d5\u30a1\u30a4\u30eb\u540d OtherOccurrencesFilesTableModel.noData=\u30c7\u30fc\u30bf\u304c\u3042\u308a\u307e\u305b\u3093\u3002 +OtherOccurrencesPanel.caseDetailsDialog.noCaseNameError=\u30a8\u30e9\u30fc +OtherOccurrencesPanel.caseDetailsDialog.noDetails=\u3053\u306e\u30b1\u30fc\u30b9\u306e\u8a73\u7d30\u306f\u3042\u308a\u307e\u305b\u3093\u3002 +OtherOccurrencesPanel.caseDetailsDialog.noDetailsReference=\u30b0\u30ed\u30fc\u30d0\u30eb\u53c2\u7167\u30d7\u30ed\u30d1\u30c6\u30a3\u306e\u30b1\u30fc\u30b9\u306e\u8a73\u7d30\u306f\u3042\u308a\u307e\u305b\u3093\u3002 +OtherOccurrencesPanel.caseDetailsDialog.notSelected=\u884c\u304c\u9078\u629e\u3055\u308c\u3066\u3044\u307e\u305b\u3093 +OtherOccurrencesPanel.correlatedArtifacts.byType=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306e{0}\uff05\u306b{2}\uff08\u30bf\u30a4\u30d7\uff1a{1}\uff09\u304c\u3042\u308a\u307e\u3059 +OtherOccurrencesPanel.correlatedArtifacts.failed=\u767a\u751f\u7387\u306e\u8a73\u7d30\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +OtherOccurrencesPanel.correlatedArtifacts.isEmpty=\u95a2\u9023\u4ed8\u3051\u308b\u30d5\u30a1\u30a4\u30eb\u3084\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u306f\u3042\u308a\u307e\u305b\u3093\u3002 +OtherOccurrencesPanel.correlatedArtifacts.title=\u5c5e\u6027\u983b\u5ea6 +OtherOccurrencesPanel.earliestCaseDate.text=\u6700\u53e4\u306e\u30b1\u30fc\u30b9\u306e\u65e5\u4ed8 +OtherOccurrencesPanel.earliestCaseLabel.text=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u958b\u59cb\u65e5\uff1a +OtherOccurrencesPanel.exportToCSVMenuItem.text=\u305d\u306e\u4ed6\u306e\u3059\u3079\u3066\u306e\u767a\u751f\u3092CSV\u3067\u30a8\u30af\u30b9\u30dd\u30fc\u30c8 +OtherOccurrencesPanel.filesTable.toolTipText=\u5217\u540d\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u30bd\u30fc\u30c8\u3057\u307e\u3059\u3002\u30c6\u30fc\u30d6\u30eb\u3092\u53f3\u30af\u30ea\u30c3\u30af\u3057\u3066\u3055\u3089\u306a\u308b\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u8868\u793a\u3057\u307e\u3059\u3002 +OtherOccurrencesPanel.foundIn.text=%d\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u3057\u305f\uff08%d\u30b1\u30fc\u30b9\u3068%d\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3067\uff09\u3002 +OtherOccurrencesPanel.noOpenCase.errMsg=\u958b\u3044\u3066\u3044\u308b\u30b1\u30fc\u30b9\u306f\u3042\u308a\u307e\u305b\u3093\u3002 +OtherOccurrencesPanel.showCaseDetailsMenuItem.text=\u30b1\u30fc\u30b9\u306e\u8a73\u7d30\u3092\u8868\u793a +OtherOccurrencesPanel.showCommonalityMenuItem.text=\u983b\u5ea6\u3092\u8868\u793a +OtherOccurrencesPanel.table.noArtifacts=\u30a2\u30a4\u30c6\u30e0\u306b\u306f\u691c\u7d22\u3059\u308b\u5c5e\u6027\u304c\u3042\u308a\u307e\u305b\u3093\u3002 +OtherOccurrencesPanel.table.noResultsFound=\u8a72\u5f53\u3059\u308b\u7d50\u679c\u304c\u3042\u308a\u307e\u305b\u3093\u3002 +OtherOccurrencesPanel_earliestCaseNotAvailable=\u5229\u7528\u4e0d\u53ef\u3002 +OtherOccurrencesPanel_table_loadingResults=\u7d50\u679c\u306e\u8aad\u8fbc\u307f\u4e2d diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form index f8ceec50e7..ec0337e3b9 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form @@ -1,42 +1,6 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -56,282 +20,8 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index 56922c7bfb..51c0791a11 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2017-2020 Basis Technology Corp. + * Copyright 2017-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,334 +19,44 @@ package org.sleuthkit.autopsy.centralrepository.contentviewer; import java.awt.Component; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.io.BufferedWriter; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.awt.Cursor; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; -import javax.swing.JFileChooser; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import static javax.swing.JOptionPane.DEFAULT_OPTION; -import static javax.swing.JOptionPane.PLAIN_MESSAGE; -import static javax.swing.JOptionPane.ERROR_MESSAGE; import javax.swing.JPanel; -import javax.swing.filechooser.FileNameExtensionFilter; -import javax.swing.table.TableModel; -import javax.swing.table.TableRowSorter; -import org.joda.time.DateTimeZone; -import org.joda.time.LocalDateTime; +import org.apache.commons.lang.StringUtils; import org.openide.nodes.Node; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.ServiceProvider; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource; -import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardArtifactTag; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.ContentTag; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskException; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.contentviewers.utils.ViewerPriority; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactItem; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.OsAccount; /** * View correlation results from other cases */ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -@ServiceProvider(service = DataContentViewer.class, position = 9) +@ServiceProvider(service = DataContentViewer.class, position = 10) @Messages({"DataContentViewerOtherCases.title=Other Occurrences", - "DataContentViewerOtherCases.toolTip=Displays instances of the selected file/artifact from other occurrences.", - "DataContentViewerOtherCases.table.noArtifacts=Item has no attributes with which to search.", - "DataContentViewerOtherCases.table.noResultsFound=No results found."}) -public class DataContentViewerOtherCases extends JPanel implements DataContentViewer { + "DataContentViewerOtherCases.toolTip=Displays instances of the selected file/artifact from other occurrences."}) +public final class DataContentViewerOtherCases extends JPanel implements DataContentViewer { private static final long serialVersionUID = -1L; - private static final String UUID_PLACEHOLDER_STRING = "NoCorrelationAttributeInstance"; - private static final Logger LOGGER = Logger.getLogger(DataContentViewerOtherCases.class.getName()); - private static final CorrelationCaseWrapper NO_ARTIFACTS_CASE = new CorrelationCaseWrapper(Bundle.DataContentViewerOtherCases_table_noArtifacts()); - private static final CorrelationCaseWrapper NO_RESULTS_CASE = new CorrelationCaseWrapper(Bundle.DataContentViewerOtherCases_table_noResultsFound()); - private final OtherOccurrencesFilesTableModel filesTableModel; - private final OtherOccurrencesCasesTableModel casesTableModel; - private final OtherOccurrencesDataSourcesTableModel dataSourcesTableModel; - private OccurrencePanel occurrencePanel; - private final Collection correlationAttributes; - private String dataSourceName = ""; //the data source of the file which the content viewer is being populated for - private String deviceId = ""; //the device id of the data source for the file which the content viewer is being populated for - /** - * Could be null. - */ - private AbstractFile file; //the file which the content viewer is being populated for + private static final Logger logger = Logger.getLogger(DataContentViewerOtherCases.class.getName()); + private final OtherOccurrencesPanel otherOccurrencesPanel = new OtherOccurrencesPanel(); + + private OtherOccurrencesNodeWorker worker = null; /** * Creates new form DataContentViewerOtherCases */ public DataContentViewerOtherCases() { - this.filesTableModel = new OtherOccurrencesFilesTableModel(); - this.casesTableModel = new OtherOccurrencesCasesTableModel(); - this.dataSourcesTableModel = new OtherOccurrencesDataSourcesTableModel(); - this.correlationAttributes = new ArrayList<>(); - occurrencePanel = new OccurrencePanel(); initComponents(); - customizeComponents(); - - detailsPanelScrollPane.addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent componentEvent) { - //when its resized make sure the width of the panel resizes to match the scroll pane width to avoid a horizontal scroll bar - occurrencePanel.setPreferredSize(new java.awt.Dimension(detailsPanelScrollPane.getPreferredSize().width, occurrencePanel.getPreferredSize().height)); - detailsPanelScrollPane.setViewportView(occurrencePanel); - } - }); - reset(); - } - - /** - * Get a placeholder string to use in place of case uuid when it isn't - * available - * - * @return UUID_PLACEHOLDER_STRING - */ - static String getPlaceholderUUID() { - return UUID_PLACEHOLDER_STRING; - } - - private void customizeComponents() { - ActionListener actList = (ActionEvent e) -> { - JMenuItem jmi = (JMenuItem) e.getSource(); - if (jmi.equals(showCaseDetailsMenuItem)) { - showCaseDetails(filesTable.getSelectedRow()); - } else if (jmi.equals(exportToCSVMenuItem)) { - try { - saveToCSV(); - } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS - } - } else if (jmi.equals(showCommonalityMenuItem)) { - showCommonalityDetails(); - } - }; - - exportToCSVMenuItem.addActionListener(actList); - showCaseDetailsMenuItem.addActionListener(actList); - showCommonalityMenuItem.addActionListener(actList); - - // Configure column sorting. - TableRowSorter sorter = new TableRowSorter<>(filesTable.getModel()); - filesTable.setRowSorter(sorter); - casesTable.getSelectionModel().addListSelectionListener((e) -> { - if (Case.isCaseOpen()) { - updateOnCaseSelection(); - } - }); - dataSourcesTable.getSelectionModel().addListSelectionListener((e) -> { - if (Case.isCaseOpen()) { - updateOnDataSourceSelection(); - } - }); - - //alows resizing of the 4th section - filesTable.getSelectionModel().addListSelectionListener((e) -> { - if (Case.isCaseOpen()) { - occurrencePanel = new OccurrencePanel(); - updateOnFileSelection(); - } - }); - //sort tables alphabetically initially - casesTable.getRowSorter().toggleSortOrder(0); - dataSourcesTable.getRowSorter().toggleSortOrder(0); - filesTable.getRowSorter().toggleSortOrder(0); - } - - @Messages({"DataContentViewerOtherCases.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate.", - "# {0} - commonality percentage", - "# {1} - correlation type", - "# {2} - correlation value", - "DataContentViewerOtherCases.correlatedArtifacts.byType={0}% of data sources have {2} (type: {1})\n", - "DataContentViewerOtherCases.correlatedArtifacts.title=Attribute Frequency", - "DataContentViewerOtherCases.correlatedArtifacts.failed=Failed to get frequency details."}) - /** - * Show how common the selected correlationAttributes are with details - * dialog. - */ - private void showCommonalityDetails() { - if (correlationAttributes.isEmpty()) { - JOptionPane.showConfirmDialog(showCommonalityMenuItem, - Bundle.DataContentViewerOtherCases_correlatedArtifacts_isEmpty(), - Bundle.DataContentViewerOtherCases_correlatedArtifacts_title(), - DEFAULT_OPTION, PLAIN_MESSAGE); - } else { - StringBuilder msg = new StringBuilder(correlationAttributes.size()); - int percentage; - try { - CentralRepository dbManager = CentralRepository.getInstance(); - for (CorrelationAttributeInstance eamArtifact : correlationAttributes) { - try { - percentage = dbManager.getFrequencyPercentage(eamArtifact); - msg.append(Bundle.DataContentViewerOtherCases_correlatedArtifacts_byType(percentage, - eamArtifact.getCorrelationType().getDisplayName(), - eamArtifact.getCorrelationValue())); - } catch (CorrelationAttributeNormalizationException ex) { - LOGGER.log(Level.WARNING, String.format("Error getting commonality details for artifact with ID: %s.", eamArtifact.getID()), ex); - } - } - JOptionPane.showConfirmDialog(showCommonalityMenuItem, - msg.toString(), - Bundle.DataContentViewerOtherCases_correlatedArtifacts_title(), - DEFAULT_OPTION, PLAIN_MESSAGE); - } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "Error getting commonality details.", ex); - JOptionPane.showConfirmDialog(showCommonalityMenuItem, - Bundle.DataContentViewerOtherCases_correlatedArtifacts_failed(), - Bundle.DataContentViewerOtherCases_correlatedArtifacts_title(), - DEFAULT_OPTION, ERROR_MESSAGE); - } - } - } - - @Messages({"DataContentViewerOtherCases.caseDetailsDialog.notSelected=No Row Selected", - "DataContentViewerOtherCases.caseDetailsDialog.noDetails=No details for this case.", - "DataContentViewerOtherCases.caseDetailsDialog.noDetailsReference=No case details for Global reference properties.", - "DataContentViewerOtherCases.caseDetailsDialog.noCaseNameError=Error", - "DataContentViewerOtherCases.noOpenCase.errMsg=No open case available."}) - private void showCaseDetails(int selectedRowViewIdx) { - String caseDisplayName = Bundle.DataContentViewerOtherCases_caseDetailsDialog_noCaseNameError(); - String details = Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetails(); - try { - if (-1 != selectedRowViewIdx) { - CentralRepository dbManager = CentralRepository.getInstance(); - int selectedRowModelIdx = filesTable.convertRowIndexToModel(selectedRowViewIdx); - List rowList = filesTableModel.getListOfNodesForFile(selectedRowModelIdx); - if (!rowList.isEmpty()) { - if (rowList.get(0) instanceof OtherOccurrenceNodeInstanceData) { - CorrelationCase eamCasePartial = ((OtherOccurrenceNodeInstanceData) rowList.get(0)).getCorrelationAttributeInstance().getCorrelationCase(); - caseDisplayName = eamCasePartial.getDisplayName(); - // query case details - CorrelationCase eamCase = dbManager.getCaseByUUID(eamCasePartial.getCaseUUID()); - if (eamCase != null) { - details = eamCase.getCaseDetailsOptionsPaneDialog(); - } else { - details = Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetails(); - } - } else { - details = Bundle.DataContentViewerOtherCases_caseDetailsDialog_notSelected(); - } - } else { - details = Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetailsReference(); - } - } - } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "Error loading case details", ex); - } finally { - JOptionPane.showConfirmDialog(showCaseDetailsMenuItem, - details, - caseDisplayName, - DEFAULT_OPTION, PLAIN_MESSAGE); - } - } - - private void saveToCSV() throws NoCurrentCaseException { - if (casesTableModel.getRowCount() > 0) { - Calendar now = Calendar.getInstance(); - String fileName = String.format("%1$tY%1$tm%1$te%1$tI%1$tM%1$tS_other_data_sources.csv", now); - CSVFileChooser.setCurrentDirectory(new File(Case.getCurrentCaseThrows().getExportDirectory())); - CSVFileChooser.setSelectedFile(new File(fileName)); - CSVFileChooser.setFileFilter(new FileNameExtensionFilter("csv file", "csv")); - - int returnVal = CSVFileChooser.showSaveDialog(filesTable); - if (returnVal == JFileChooser.APPROVE_OPTION) { - - File selectedFile = CSVFileChooser.getSelectedFile(); - if (!selectedFile.getName().endsWith(".csv")) { // NON-NLS - selectedFile = new File(selectedFile.toString() + ".csv"); // NON-NLS - } - writeOtherOccurrencesToFileAsCSV(selectedFile); - } - } - } - - @Messages({ - "DataContentViewerOtherCasesModel.csvHeader.case=Case", - "DataContentViewerOtherCasesModel.csvHeader.device=Device", - "DataContentViewerOtherCasesModel.csvHeader.dataSource=Data Source", - "DataContentViewerOtherCasesModel.csvHeader.attribute=Matched Attribute", - "DataContentViewerOtherCasesModel.csvHeader.value=Attribute Value", - "DataContentViewerOtherCasesModel.csvHeader.known=Known", - "DataContentViewerOtherCasesModel.csvHeader.path=Path", - "DataContentViewerOtherCasesModel.csvHeader.comment=Comment" - }) - /** - * Write data for all cases in the content viewer to a CSV file - */ - private void writeOtherOccurrencesToFileAsCSV(File destFile) { - try (BufferedWriter writer = Files.newBufferedWriter(destFile.toPath())) { - //write headers - StringBuilder headers = new StringBuilder("\""); - headers.append(Bundle.DataContentViewerOtherCasesModel_csvHeader_case()) - .append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_dataSource()) - .append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_attribute()) - .append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_value()) - .append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_known()) - .append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_path()) - .append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.DataContentViewerOtherCasesModel_csvHeader_comment()) - .append('"').append(System.getProperty("line.separator")); - writer.write(headers.toString()); - //write content - for (CorrelationAttributeInstance corAttr : correlationAttributes) { - Map correlatedNodeDataMap = new HashMap<>(0); - // get correlation and reference set instances from DB - correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); - for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) { - writer.write(nodeData.toCsvString()); - } - } - } catch (IOException ex) { - LOGGER.log(Level.SEVERE, "Error writing selected rows to CSV.", ex); - } - } - - /** - * Reset the UI and clear cached data. - */ - private void reset() { - // start with empty table - casesTableModel.clearTable(); - dataSourcesTableModel.clearTable(); - filesTableModel.clearTable(); - correlationAttributes.clear(); - earliestCaseDate.setText(Bundle.DataContentViewerOtherCases_earliestCaseNotAvailable()); - foundInLabel.setText(""); - //calling getPreferredSize has a side effect of ensuring it has a preferred size which reflects the contents which are visible - occurrencePanel = new OccurrencePanel(); - occurrencePanel.getPreferredSize(); - detailsPanelScrollPane.setViewportView(occurrencePanel); + add(otherOccurrencesPanel); } @Override @@ -371,583 +81,75 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi @Override public void resetComponent() { - reset(); + otherOccurrencesPanel.reset(); } @Override public int isPreferred(Node node) { - return 1; - - } - - /** - * Get the associated BlackboardArtifact from a node, if it exists. - * - * @param node The node - * - * @return The associated BlackboardArtifact, or null - */ - private BlackboardArtifact - getBlackboardArtifactFromNode(Node node) { - BlackboardArtifactTag nodeBbArtifactTag = node.getLookup().lookup(BlackboardArtifactTag.class - ); - BlackboardArtifact nodeBbArtifact = node.getLookup().lookup(BlackboardArtifact.class - ); - - if (nodeBbArtifactTag != null) { - return nodeBbArtifactTag.getArtifact(); - } else if (nodeBbArtifact != null) { - return nodeBbArtifact; - } - - return null; - - } - - /** - * Get the associated AbstractFile from a node, if it exists. - * - * @param node The node - * - * @return The associated AbstractFile, or null - */ - private AbstractFile getAbstractFileFromNode(Node node) { - BlackboardArtifactTag nodeBbArtifactTag = node.getLookup().lookup(BlackboardArtifactTag.class - ); - ContentTag nodeContentTag = node.getLookup().lookup(ContentTag.class - ); - BlackboardArtifact nodeBbArtifact = node.getLookup().lookup(BlackboardArtifact.class - ); - AbstractFile nodeAbstractFile = node.getLookup().lookup(AbstractFile.class - ); - - if (nodeBbArtifactTag != null) { - Content content = nodeBbArtifactTag.getContent(); - if (content instanceof AbstractFile) { - return (AbstractFile) content; - } - } else if (nodeContentTag != null) { - Content content = nodeContentTag.getContent(); - if (content instanceof AbstractFile) { - return (AbstractFile) content; - } - } else if (nodeBbArtifact != null) { - Content content; - try { - content = nodeBbArtifact.getSleuthkitCase().getContentById(nodeBbArtifact.getObjectID()); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error retrieving blackboard artifact", ex); // NON-NLS - return null; - } - - if (content instanceof AbstractFile) { - return (AbstractFile) content; - } - } else if (nodeAbstractFile != null) { - return nodeAbstractFile; - } - - return null; - } - - /** - * Determine what attributes can be used for correlation based on the node. - * If EamDB is not enabled, get the default Files correlation. - * - * @param node The node to correlate - * - * @return A list of attributes that can be used for correlation - */ - private Collection getCorrelationAttributesFromNode(Node node) { - Collection ret = new ArrayList<>(); - - // correlate on blackboard artifact attributes if they exist and supported - BlackboardArtifact bbArtifact = getBlackboardArtifactFromNode(node); - if (bbArtifact != null && CentralRepository.isEnabled()) { - ret.addAll(CorrelationAttributeUtil.makeCorrAttrsForCorrelation(bbArtifact)); - } - - // we can correlate based on the MD5 if it is enabled - if (this.file != null && CentralRepository.isEnabled() && this.file.getSize() > 0) { - try { - - List artifactTypes = CentralRepository.getInstance().getDefinedCorrelationTypes(); - String md5 = this.file.getMd5Hash(); - if (md5 != null && !md5.isEmpty() && null != artifactTypes && !artifactTypes.isEmpty()) { - for (CorrelationAttributeInstance.Type aType : artifactTypes) { - if (aType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) { - CorrelationCase corCase = CentralRepository.getInstance().getCase(Case.getCurrentCase()); - try { - ret.add(new CorrelationAttributeInstance( - aType, - md5, - corCase, - CorrelationDataSource.fromTSKDataSource(corCase, file.getDataSource()), - file.getParentPath() + file.getName(), - "", - file.getKnown(), - file.getId())); - } catch (CorrelationAttributeNormalizationException ex) { - LOGGER.log(Level.INFO, String.format("Unable to check create CorrelationAttribtueInstance for value %s and type %s.", md5, aType.toString()), ex); - } - break; - } - } - } - } catch (CentralRepoException | TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS - } - // If EamDb not enabled, get the Files default correlation type to allow Other Occurances to be enabled. - } else if (this.file != null && this.file.getSize() > 0) { - String md5 = this.file.getMd5Hash(); - if (md5 != null && !md5.isEmpty()) { - try { - final CorrelationAttributeInstance.Type fileAttributeType - = CorrelationAttributeInstance.getDefaultCorrelationTypes() - .stream() - .filter(attrType -> attrType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) - .findAny() - .get(); - //The Central Repository is not enabled - ret.add(new CorrelationAttributeInstance(fileAttributeType, md5, null, null, "", "", TskData.FileKnown.UNKNOWN, this.file.getId())); - } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS - } catch (CorrelationAttributeNormalizationException ex) { - LOGGER.log(Level.INFO, String.format("Unable to create CorrelationAttributeInstance for value %s", md5), ex); // NON-NLS - } - } - } - - return ret; - } - - @Messages({"DataContentViewerOtherCases.earliestCaseNotAvailable= Not Enabled."}) - /** - * Gets the list of Eam Cases and determines the earliest case creation - * date. Sets the label to display the earliest date string to the user. - */ - private void setEarliestCaseDate() { - String dateStringDisplay = Bundle.DataContentViewerOtherCases_earliestCaseNotAvailable(); - - if (CentralRepository.isEnabled()) { - LocalDateTime earliestDate = LocalDateTime.now(DateTimeZone.UTC); - DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.US); - try { - CentralRepository dbManager = CentralRepository.getInstance(); - List cases = dbManager.getCases(); - for (CorrelationCase aCase : cases) { - LocalDateTime caseDate = LocalDateTime.fromDateFields(datetimeFormat.parse(aCase.getCreationDate())); - - if (caseDate.isBefore(earliestDate)) { - earliestDate = caseDate; - dateStringDisplay = aCase.getCreationDate(); - } - - } - - } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "Error getting list of cases from database.", ex); // NON-NLS - } catch (ParseException ex) { - LOGGER.log(Level.SEVERE, "Error parsing date of cases from database.", ex); // NON-NLS - } - - } - earliestCaseDate.setText(dateStringDisplay); - } - - /** - * Query the central repo database (if enabled) and the case database to - * find all artifact instances correlated to the given central repository - * artifact. If the central repo is not enabled, this will only return files - * from the current case with matching MD5 hashes. - * - * @param corAttr CorrelationAttribute to query for - * @param dataSourceName Data source to filter results - * @param deviceId Device Id to filter results - * - * @return A collection of correlated artifact instances - */ - private Map getCorrelatedInstances(CorrelationAttributeInstance corAttr, String dataSourceName, String deviceId) { - // @@@ Check exception - try { - final Case openCase = Case.getCurrentCaseThrows(); - String caseUUID = openCase.getName(); - HashMap nodeDataMap = new HashMap<>(); - - if (CentralRepository.isEnabled()) { - List instances = CentralRepository.getInstance().getArtifactInstancesByTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue()); - - for (CorrelationAttributeInstance artifactInstance : instances) { - - // Only add the attribute if it isn't the object the user selected. - // We consider it to be a different object if at least one of the following is true: - // - the case UUID is different - // - the data source name is different - // - the data source device ID is different - // - the file path is different - if (!artifactInstance.getCorrelationCase().getCaseUUID().equals(caseUUID) - || !artifactInstance.getCorrelationDataSource().getName().equals(dataSourceName) - || !artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId) - || !artifactInstance.getFilePath().equalsIgnoreCase(file.getParentPath() + file.getName())) { - - OtherOccurrenceNodeInstanceData newNode = new OtherOccurrenceNodeInstanceData(artifactInstance, corAttr.getCorrelationType(), corAttr.getCorrelationValue()); - UniquePathKey uniquePathKey = new UniquePathKey(newNode); - nodeDataMap.put(uniquePathKey, newNode); - } - } - } - if (corAttr.getCorrelationType().getDisplayName().equals("Files")) { - List caseDbFiles = getCaseDbMatches(corAttr, openCase); - - for (AbstractFile caseDbFile : caseDbFiles) { - addOrUpdateNodeData(openCase, nodeDataMap, caseDbFile); - } - } - return nodeDataMap; - } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS - } catch (CorrelationAttributeNormalizationException ex) { - LOGGER.log(Level.INFO, "Error getting artifact instances from database.", ex); // NON-NLS - } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS - } catch (TskCoreException ex) { - // do nothing. - // @@@ Review this behavior - LOGGER.log(Level.SEVERE, "Exception while querying open case.", ex); // NON-NLS - } - - return new HashMap<>(0); - } - - /** - * Get all other abstract files in the current case with the same MD5 as the - * selected node. - * - * @param corAttr The CorrelationAttribute containing the MD5 to search for - * @param openCase The current case - * - * @return List of matching AbstractFile objects - * - * @throws NoCurrentCaseException - * @throws TskCoreException - * @throws CentralRepoException - */ - private List getCaseDbMatches(CorrelationAttributeInstance corAttr, Case openCase) throws NoCurrentCaseException, TskCoreException, CentralRepoException { - String md5 = corAttr.getCorrelationValue(); - SleuthkitCase tsk = openCase.getSleuthkitCase(); - List matches = tsk.findAllFilesWhere(String.format("md5 = '%s'", new Object[]{md5})); - - List caseDbArtifactInstances = new ArrayList<>(); - for (AbstractFile fileMatch : matches) { - if (this.file.equals(fileMatch)) { - continue; // If this is the file the user clicked on - } - caseDbArtifactInstances.add(fileMatch); - } - return caseDbArtifactInstances; - - } - - /** - * Adds the file to the nodeDataMap map if it does not already exist - * - * @param autopsyCase - * @param nodeDataMap - * @param newFile - * - * @throws TskCoreException - * @throws CentralRepoException - */ - private void addOrUpdateNodeData(final Case autopsyCase, Map nodeDataMap, AbstractFile newFile) throws TskCoreException, CentralRepoException { - - OtherOccurrenceNodeInstanceData newNode = new OtherOccurrenceNodeInstanceData(newFile, autopsyCase); - - // If the caseDB object has a notable tag associated with it, update - // the known status to BAD - if (newNode.getKnown() != TskData.FileKnown.BAD) { - List fileMatchTags = autopsyCase.getServices().getTagsManager().getContentTagsByContent(newFile); - for (ContentTag tag : fileMatchTags) { - TskData.FileKnown tagKnownStatus = tag.getName().getKnownStatus(); - if (tagKnownStatus.equals(TskData.FileKnown.BAD)) { - newNode.updateKnown(TskData.FileKnown.BAD); - break; - } - } - } - - // Make a key to see if the file is already in the map - UniquePathKey uniquePathKey = new UniquePathKey(newNode); - - // If this node is already in the list, the only thing we need to do is - // update the known status to BAD if the caseDB version had known status BAD. - // Otherwise this is a new node so add the new node to the map. - if (nodeDataMap.containsKey(uniquePathKey)) { - if (newNode.getKnown() == TskData.FileKnown.BAD) { - OtherOccurrenceNodeInstanceData prevInstance = nodeDataMap.get(uniquePathKey); - prevInstance.updateKnown(newNode.getKnown()); - } - } else { - nodeDataMap.put(uniquePathKey, newNode); - } + return ViewerPriority.viewerPriority.LevelOne.getFlag(); } @Override public boolean isSupported(Node node) { - - // Is supported if one of the following is true: - // - The central repo is enabled and the node has correlatable content - // (either through the MD5 hash of the associated file or through a BlackboardArtifact) - // - The central repo is disabled and the backing file has a valid MD5 hash - this.file = this.getAbstractFileFromNode(node); - if (CentralRepository.isEnabled()) { - return !getCorrelationAttributesFromNode(node).isEmpty(); - } else { - return this.file != null - && this.file.getSize() > 0 - && ((this.file.getMd5Hash() != null) && (!this.file.getMd5Hash().isEmpty())); + //Ideally we would want to attempt to create correlation attributes for the node contents + //and if none could be created determine that it was not supported. + //However that winds up being more work than we really want to be performing in this method so we perform a quicker check. + //The result of this is that the Other Occurrences viewer could be enabled but without any correlation attributes in some situations. + // Is supported if: + // The central repo is enabled and the node is not null + if (CentralRepository.isEnabled() && node != null) { + // And the node has information which could be correlated on. + if (node.getLookup().lookup(OsAccount.class) != null) { + //the node has an associated OsAccount to correlate on + return true; + } + if (node.getLookup().lookup(BlackboardArtifactItem.class) != null) { + //it has a blackboard artifact which might have a correlation attribute + return true; + } + if (node.getLookup().lookup(BlackboardArtifactTag.class) != null) { + //Blackboard artifact tags may have their underlying artifact correlated on + return true; + } + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + //the AbstractFile lookup will handle the usecase for file tags as well + if (file != null && !StringUtils.isBlank(file.getMd5Hash())) { + //there is an abstractFile lookup and it has an MD5 so could be correlated on + return true; + } } + return false; + } @Override public void setNode(Node node) { + otherOccurrencesPanel.reset(); // reset the table to empty. + otherOccurrencesPanel.showPanelLoadingMessage(); - reset(); // reset the table to empty. if (node == null) { return; } - //could be null - this.file = this.getAbstractFileFromNode(node); - populateTable(node); - } - - /** - * Load the correlatable data into the table model. If there is no data - * available display the message on the status panel. - * - * @param node The node being viewed. - */ - @Messages({ - "DataContentViewerOtherCases.dataSources.header.text=Data Source Name", - "DataContentViewerOtherCases.foundIn.text=Found %d instances in %d cases and %d data sources." - }) - private void populateTable(Node node) { - try { - if (this.file != null) { - Content dataSource = this.file.getDataSource(); - dataSourceName = dataSource.getName(); - deviceId = Case.getCurrentCaseThrows().getSleuthkitCase().getDataSource(dataSource.getId()).getDeviceId(); - } - } catch (TskException | NoCurrentCaseException ex) { - // do nothing. - // @@@ Review this behavior + if (worker != null) { + worker.cancel(true); } - - // get the attributes we can correlate on - correlationAttributes.addAll(getCorrelationAttributesFromNode(node)); - Map caseNames = new HashMap<>(); - int totalCount = 0; - Set dataSources = new HashSet<>(); - for (CorrelationAttributeInstance corAttr : correlationAttributes) { - Map correlatedNodeDataMap = new HashMap<>(0); - - // get correlation and reference set instances from DB - correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); - for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) { - if (nodeData.isCentralRepoNode()) { - try { - dataSources.add(makeDataSourceString(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getDeviceID(), nodeData.getDataSourceName())); - caseNames.put(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getCorrelationAttributeInstance().getCorrelationCase()); - } catch (CentralRepoException ex) { - LOGGER.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName(), ex); - } - } else { - try { - dataSources.add(makeDataSourceString(Case.getCurrentCaseThrows().getName(), nodeData.getDeviceID(), nodeData.getDataSourceName())); - caseNames.put(Case.getCurrentCaseThrows().getName(), new CorrelationCase(Case.getCurrentCaseThrows().getName(), Case.getCurrentCaseThrows().getDisplayName())); - } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.WARNING, "No current case open for other occurrences", ex); - } - } - totalCount++; - } - } - for (CorrelationCase corCase : caseNames.values()) { - casesTableModel.addCorrelationCase(new CorrelationCaseWrapper(corCase)); - } - int caseCount = casesTableModel.getRowCount(); - if (correlationAttributes.isEmpty()) { - casesTableModel.addCorrelationCase(NO_ARTIFACTS_CASE); - } else if (caseCount == 0) { - casesTableModel.addCorrelationCase(NO_RESULTS_CASE); - } - setEarliestCaseDate(); - foundInLabel.setText(String.format(Bundle.DataContentViewerOtherCases_foundIn_text(), totalCount, caseCount, dataSources.size())); - if (caseCount > 0) { - casesTable.setRowSelectionInterval(0, 0); - } - } - - /** - * Create a unique string to be used as a key for deduping data sources as - * best as possible - */ - private String makeDataSourceString(String caseUUID, String deviceId, String dataSourceName) { - return caseUUID + deviceId + dataSourceName; - } - - /** - * Updates diplayed information to be correct for the current case selection - */ - private void updateOnCaseSelection() { - int[] selectedCaseIndexes = casesTable.getSelectedRows(); - dataSourcesTableModel.clearTable(); - filesTableModel.clearTable(); - - if (selectedCaseIndexes.length == 0) { - //special case when no cases are selected - occurrencePanel = new OccurrencePanel(); - occurrencePanel.getPreferredSize(); - detailsPanelScrollPane.setViewportView(occurrencePanel); - } else { - String currentCaseName; - try { - currentCaseName = Case.getCurrentCaseThrows().getName(); - } catch (NoCurrentCaseException ex) { - currentCaseName = null; - LOGGER.log(Level.WARNING, "Unable to get current case for other occurrences content viewer", ex); - } - for (CorrelationAttributeInstance corAttr : correlationAttributes) { - Map correlatedNodeDataMap = new HashMap<>(0); - - // get correlation and reference set instances from DB - correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); - for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) { - for (int selectedRow : selectedCaseIndexes) { - try { - if (nodeData.isCentralRepoNode()) { - if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)) != null - && casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())) { - dataSourcesTableModel.addNodeData(nodeData); - } - } else if (currentCaseName != null && (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(currentCaseName))) { - dataSourcesTableModel.addNodeData(nodeData); - } - } catch (CentralRepoException ex) { - LOGGER.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex); - } + worker = new OtherOccurrencesNodeWorker(node) { + @Override + public void done() { + try { + if (!isCancelled()) { + OtherOccurrencesData data = get(); + otherOccurrencesPanel.populateTable(data); + otherOccurrencesPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } + } catch (InterruptedException | ExecutionException ex) { + DataContentViewerOtherCases.logger.log(Level.SEVERE, "Failed to update OtherOccurrencesPanel", ex); } } - if (dataSourcesTable.getRowCount() > 0) { - dataSourcesTable.setRowSelectionInterval(0, 0); - } - } - } - - /** - * Updates diplayed information to be correct for the current data source - * selection - */ - private void updateOnDataSourceSelection() { - int[] selectedDataSources = dataSourcesTable.getSelectedRows(); - filesTableModel.clearTable(); - for (CorrelationAttributeInstance corAttr : correlationAttributes) { - Map correlatedNodeDataMap = new HashMap<>(0); - - // get correlation and reference set instances from DB - correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); - for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) { - for (int selectedDataSourceRow : selectedDataSources) { - try { - if (nodeData.isCentralRepoNode()) { - if (dataSourcesTableModel.getCaseUUIDForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID()) - && dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) { - filesTableModel.addNodeData(nodeData); - } - } else { - if (dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) { - filesTableModel.addNodeData(nodeData); - } - } - } catch (CentralRepoException ex) { - LOGGER.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex); - } - } - } - } - if (filesTable.getRowCount() > 0) { - filesTable.setRowSelectionInterval(0, 0); - } - } - - /** - * Update the data displayed in the details section to be correct for the - * currently selected File - */ - private void updateOnFileSelection() { - if (filesTable.getSelectedRowCount() == 1) { - //if there is one file selected update the deatils to show the data for that file - occurrencePanel = new OccurrencePanel(filesTableModel.getListOfNodesForFile(filesTable.convertRowIndexToModel(filesTable.getSelectedRow()))); - } else if (dataSourcesTable.getSelectedRowCount() == 1) { - //if no files were selected and only one data source is selected update the information to reflect the data source - String caseName = dataSourcesTableModel.getCaseNameForRow(dataSourcesTable.convertRowIndexToModel(dataSourcesTable.getSelectedRow())); - String dsName = dataSourcesTableModel.getValueAt(dataSourcesTable.convertRowIndexToModel(dataSourcesTable.getSelectedRow()), 0).toString(); - String caseCreatedDate = ""; - for (int row : casesTable.getSelectedRows()) { - if (casesTableModel.getValueAt(casesTable.convertRowIndexToModel(row), 0).toString().equals(caseName)) { - caseCreatedDate = getCaseCreatedDate(row); - break; - } - } - occurrencePanel = new OccurrencePanel(caseName, caseCreatedDate, dsName); - } else if (casesTable.getSelectedRowCount() == 1) { - //if no files were selected and a number of data source other than 1 are selected - //update the information to reflect the case - String createdDate = ""; - String caseName = ""; - if (casesTable.getRowCount() > 0) { - caseName = casesTableModel.getValueAt(casesTable.convertRowIndexToModel(casesTable.getSelectedRow()), 0).toString(); - } - if (caseName.isEmpty()) { - occurrencePanel = new OccurrencePanel(); - } else { - createdDate = getCaseCreatedDate(casesTable.getSelectedRow()); - occurrencePanel = new OccurrencePanel(caseName, createdDate); - } - } else { - //else display an empty details area - occurrencePanel = new OccurrencePanel(); - } - //calling getPreferredSize has a side effect of ensuring it has a preferred size which reflects the contents which are visible - occurrencePanel.getPreferredSize(); - detailsPanelScrollPane.setViewportView(occurrencePanel); - } - - /** - * Get the date a case was created - * - * @param caseTableRowIdx the row from the casesTable representing the case - * - * @return A string representing the date the case was created or an empty - * string if the date could not be determined - */ - private String getCaseCreatedDate(int caseTableRowIdx) { - try { - if (CentralRepository.isEnabled()) { - CorrelationCase partialCase; - partialCase = casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(caseTableRowIdx)); - if (partialCase == null) { - return ""; - } - return CentralRepository.getInstance().getCaseByUUID(partialCase.getCaseUUID()).getCreationDate(); - } else { - return Case.getCurrentCase().getCreatedDate(); - } - } catch (CentralRepoException ex) { - LOGGER.log(Level.WARNING, "Error getting case created date for row: " + caseTableRowIdx, ex); - } - return ""; + }; + otherOccurrencesPanel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + worker.execute(); } /** @@ -960,298 +162,12 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi private void initComponents() { java.awt.GridBagConstraints gridBagConstraints; - rightClickPopupMenu = new javax.swing.JPopupMenu(); - exportToCSVMenuItem = new javax.swing.JMenuItem(); - showCaseDetailsMenuItem = new javax.swing.JMenuItem(); - showCommonalityMenuItem = new javax.swing.JMenuItem(); - CSVFileChooser = new javax.swing.JFileChooser(); - tableContainerPanel = new javax.swing.JPanel(); - tablesViewerSplitPane = new javax.swing.JSplitPane(); - caseDatasourceFileSplitPane = new javax.swing.JSplitPane(); - caseDatasourceSplitPane = new javax.swing.JSplitPane(); - caseScrollPane = new javax.swing.JScrollPane(); - casesTable = new javax.swing.JTable(); - dataSourceScrollPane = new javax.swing.JScrollPane(); - dataSourcesTable = new javax.swing.JTable(); - filesTableScrollPane = new javax.swing.JScrollPane(); - filesTable = new javax.swing.JTable(); - detailsPanelScrollPane = new javax.swing.JScrollPane(); - jPanel1 = new javax.swing.JPanel(); - foundInLabel = new javax.swing.JLabel(); - earliestCaseDate = new javax.swing.JLabel(); - earliestCaseLabel = new javax.swing.JLabel(); - filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0)); - - rightClickPopupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() { - public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) { - } - public void popupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent evt) { - } - public void popupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) { - rightClickPopupMenuPopupMenuWillBecomeVisible(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(exportToCSVMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.exportToCSVMenuItem.text")); // NOI18N - rightClickPopupMenu.add(exportToCSVMenuItem); - - org.openide.awt.Mnemonics.setLocalizedText(showCaseDetailsMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.showCaseDetailsMenuItem.text")); // NOI18N - rightClickPopupMenu.add(showCaseDetailsMenuItem); - - org.openide.awt.Mnemonics.setLocalizedText(showCommonalityMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.showCommonalityMenuItem.text")); // NOI18N - rightClickPopupMenu.add(showCommonalityMenuItem); - setMinimumSize(new java.awt.Dimension(1000, 10)); setOpaque(false); setPreferredSize(new java.awt.Dimension(1000, 63)); - - tableContainerPanel.setPreferredSize(new java.awt.Dimension(600, 63)); - tableContainerPanel.setRequestFocusEnabled(false); - - tablesViewerSplitPane.setDividerLocation(450); - tablesViewerSplitPane.setResizeWeight(0.75); - - caseDatasourceFileSplitPane.setDividerLocation(300); - caseDatasourceFileSplitPane.setResizeWeight(0.66); - caseDatasourceFileSplitPane.setToolTipText(""); - - caseDatasourceSplitPane.setDividerLocation(150); - caseDatasourceSplitPane.setResizeWeight(0.5); - - caseScrollPane.setPreferredSize(new java.awt.Dimension(150, 30)); - - casesTable.setAutoCreateRowSorter(true); - casesTable.setModel(casesTableModel); - caseScrollPane.setViewportView(casesTable); - - caseDatasourceSplitPane.setLeftComponent(caseScrollPane); - - dataSourceScrollPane.setPreferredSize(new java.awt.Dimension(150, 30)); - - dataSourcesTable.setAutoCreateRowSorter(true); - dataSourcesTable.setModel(dataSourcesTableModel); - dataSourceScrollPane.setViewportView(dataSourcesTable); - - caseDatasourceSplitPane.setRightComponent(dataSourceScrollPane); - - caseDatasourceFileSplitPane.setLeftComponent(caseDatasourceSplitPane); - - filesTableScrollPane.setPreferredSize(new java.awt.Dimension(150, 30)); - - filesTable.setAutoCreateRowSorter(true); - filesTable.setModel(filesTableModel); - filesTable.setToolTipText(org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.table.toolTip.text")); // NOI18N - filesTable.setComponentPopupMenu(rightClickPopupMenu); - filesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); - filesTableScrollPane.setViewportView(filesTable); - - caseDatasourceFileSplitPane.setRightComponent(filesTableScrollPane); - - tablesViewerSplitPane.setLeftComponent(caseDatasourceFileSplitPane); - - detailsPanelScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - detailsPanelScrollPane.setPreferredSize(new java.awt.Dimension(300, 100)); - tablesViewerSplitPane.setRightComponent(detailsPanelScrollPane); - - jPanel1.setPreferredSize(new java.awt.Dimension(576, 22)); - jPanel1.setLayout(new java.awt.GridBagLayout()); - - foundInLabel.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); - org.openide.awt.Mnemonics.setLocalizedText(foundInLabel, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.foundInLabel.text")); // NOI18N - foundInLabel.setPreferredSize(new java.awt.Dimension(400, 16)); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 3; - gridBagConstraints.gridy = 0; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST; - gridBagConstraints.insets = new java.awt.Insets(2, 2, 0, 0); - jPanel1.add(foundInLabel, gridBagConstraints); - - org.openide.awt.Mnemonics.setLocalizedText(earliestCaseDate, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseDate.text")); // NOI18N - earliestCaseDate.setMaximumSize(new java.awt.Dimension(200, 16)); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 1; - gridBagConstraints.gridy = 0; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(0, 7, 0, 0); - jPanel1.add(earliestCaseDate, gridBagConstraints); - - org.openide.awt.Mnemonics.setLocalizedText(earliestCaseLabel, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.text")); // NOI18N - earliestCaseLabel.setToolTipText(org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.toolTipText")); // NOI18N - earliestCaseLabel.setMaximumSize(new java.awt.Dimension(260, 16)); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 0; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - jPanel1.add(earliestCaseLabel, gridBagConstraints); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 2; - gridBagConstraints.gridy = 0; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.weightx = 0.1; - jPanel1.add(filler1, gridBagConstraints); - - javax.swing.GroupLayout tableContainerPanelLayout = new javax.swing.GroupLayout(tableContainerPanel); - tableContainerPanel.setLayout(tableContainerPanelLayout); - tableContainerPanelLayout.setHorizontalGroup( - tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(tableContainerPanelLayout.createSequentialGroup() - .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(tablesViewerSplitPane) - .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addContainerGap()) - ); - tableContainerPanelLayout.setVerticalGroup( - tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(tableContainerPanelLayout.createSequentialGroup() - .addGap(0, 0, 0) - .addComponent(tablesViewerSplitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 37, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(12, 12, 12)) - ); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 1000, Short.MAX_VALUE) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 78, Short.MAX_VALUE) - .addGap(0, 0, 0)) - ); + setLayout(new java.awt.BorderLayout()); }// //GEN-END:initComponents - private void rightClickPopupMenuPopupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) {//GEN-FIRST:event_rightClickPopupMenuPopupMenuWillBecomeVisible - boolean enableCentralRepoActions = false; - if (CentralRepository.isEnabled() && filesTable.getSelectedRowCount() == 1) { - int rowIndex = filesTable.getSelectedRow(); - List selectedFile = filesTableModel.getListOfNodesForFile(rowIndex); - if (!selectedFile.isEmpty() && selectedFile.get(0) instanceof OtherOccurrenceNodeInstanceData) { - OtherOccurrenceNodeInstanceData instanceData = (OtherOccurrenceNodeInstanceData) selectedFile.get(0); - enableCentralRepoActions = instanceData.isCentralRepoNode(); - } - } - showCaseDetailsMenuItem.setVisible(enableCentralRepoActions); - showCommonalityMenuItem.setVisible(enableCentralRepoActions); - }//GEN-LAST:event_rightClickPopupMenuPopupMenuWillBecomeVisible - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JFileChooser CSVFileChooser; - private javax.swing.JSplitPane caseDatasourceFileSplitPane; - private javax.swing.JSplitPane caseDatasourceSplitPane; - private javax.swing.JScrollPane caseScrollPane; - private javax.swing.JTable casesTable; - private javax.swing.JScrollPane dataSourceScrollPane; - private javax.swing.JTable dataSourcesTable; - private javax.swing.JScrollPane detailsPanelScrollPane; - private javax.swing.JLabel earliestCaseDate; - private javax.swing.JLabel earliestCaseLabel; - private javax.swing.JMenuItem exportToCSVMenuItem; - private javax.swing.JTable filesTable; - private javax.swing.JScrollPane filesTableScrollPane; - private javax.swing.Box.Filler filler1; - private javax.swing.JLabel foundInLabel; - private javax.swing.JPanel jPanel1; - private javax.swing.JPopupMenu rightClickPopupMenu; - private javax.swing.JMenuItem showCaseDetailsMenuItem; - private javax.swing.JMenuItem showCommonalityMenuItem; - private javax.swing.JPanel tableContainerPanel; - private javax.swing.JSplitPane tablesViewerSplitPane; // End of variables declaration//GEN-END:variables - - /** - * Used as a key to ensure we eliminate duplicates from the result set by - * not overwriting CR correlation instances. - */ - private static final class UniquePathKey { - - private final String dataSourceID; - private final String filePath; - private final String type; - private final String caseUUID; - - UniquePathKey(OtherOccurrenceNodeInstanceData nodeData) { - super(); - dataSourceID = nodeData.getDeviceID(); - if (nodeData.getFilePath() != null) { - filePath = nodeData.getFilePath().toLowerCase(); - } else { - filePath = null; - } - type = nodeData.getType(); - String tempCaseUUID; - try { - tempCaseUUID = nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(); - } catch (CentralRepoException ignored) { - //non central repo nodeData won't have a correlation case - try { - tempCaseUUID = Case.getCurrentCaseThrows().getName(); - //place holder value will be used since correlation attribute was unavailble - } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.WARNING, "Unable to get current case", ex); - tempCaseUUID = UUID_PLACEHOLDER_STRING; - } - } - caseUUID = tempCaseUUID; - } - - @Override - public boolean equals(Object other) { - if (other instanceof UniquePathKey) { - UniquePathKey otherKey = (UniquePathKey) (other); - return (Objects.equals(otherKey.getDataSourceID(), this.getDataSourceID()) - && Objects.equals(otherKey.getFilePath(), this.getFilePath()) - && Objects.equals(otherKey.getType(), this.getType()) - && Objects.equals(otherKey.getCaseUUID(), this.getCaseUUID())); - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(getDataSourceID(), getFilePath(), getType(), getCaseUUID()); - } - - /** - * Get the type of this UniquePathKey. - * - * @return the type - */ - String getType() { - return type; - } - - /** - * Get the file path for the UniquePathKey. - * - * @return the filePath - */ - String getFilePath() { - return filePath; - } - - /** - * Get the data source id for the UniquePathKey. - * - * @return the dataSourceID - */ - String getDataSourceID() { - return dataSourceID; - } - - /** - * Get the case uuid for the UniquePathKey - * - * @return the case UUID - */ - String getCaseUUID() { - return caseUUID; - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.java index c66d4ce085..897259ecc0 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.centralrepository.contentviewer; +import org.sleuthkit.autopsy.centralrepository.application.NodeData; import java.awt.Color; import java.awt.Font; import java.util.ArrayList; @@ -28,7 +29,6 @@ import java.util.List; import java.util.Set; import java.util.logging.Level; import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.coreutils.Logger; @@ -50,7 +50,7 @@ final class OccurrencePanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; private int gridY = 0; - private final List nodeDataList; + private final List nodeDataList; private final Map caseNamesAndDates = new HashMap<>(); private final Set dataSourceNames = new HashSet<>(); private final Set filePaths = new HashSet<>(); @@ -97,7 +97,7 @@ final class OccurrencePanel extends javax.swing.JPanel { * @param nodeDataList the list of OtherOccurrenceNodeData representing * common properties for the file */ - OccurrencePanel(List nodeDataList) { + OccurrencePanel(List nodeDataList) { this.nodeDataList = nodeDataList; customizeComponents(); } @@ -148,9 +148,9 @@ final class OccurrencePanel extends javax.swing.JPanel { addItemToBag(gridY, 0, TOP_INSET, 0, commonPropertiesLabel); gridY++; //for each other occurrence - for (OtherOccurrenceNodeData occurrence : nodeDataList) { - if (occurrence instanceof OtherOccurrenceNodeInstanceData) { - String type = ((OtherOccurrenceNodeInstanceData) occurrence).getType(); + for (NodeData occurrence : nodeDataList) { + if (occurrence instanceof NodeData) { + String type = occurrence.getType(); if (!type.isEmpty()) { javax.swing.JLabel typeLabel = new javax.swing.JLabel(); org.openide.awt.Mnemonics.setLocalizedText(typeLabel, Bundle.OccurrencePanel_commonPropertyTypeLabel_text()); @@ -160,7 +160,7 @@ final class OccurrencePanel extends javax.swing.JPanel { addItemToBag(gridY, 1, VERTICAL_GAP, 0, typeFieldValue); gridY++; } - String value = ((OtherOccurrenceNodeInstanceData) occurrence).getValue(); + String value = occurrence.getValue(); if (!value.isEmpty()) { javax.swing.JLabel valueLabel = new javax.swing.JLabel(); org.openide.awt.Mnemonics.setLocalizedText(valueLabel, Bundle.OccurrencePanel_commonPropertyValueLabel_text()); @@ -170,7 +170,7 @@ final class OccurrencePanel extends javax.swing.JPanel { addItemToBag(gridY, 1, 0, 0, valueFieldValue); gridY++; } - TskData.FileKnown knownStatus = ((OtherOccurrenceNodeInstanceData) occurrence).getKnown(); + TskData.FileKnown knownStatus = occurrence.getKnown(); javax.swing.JLabel knownStatusLabel = new javax.swing.JLabel(); org.openide.awt.Mnemonics.setLocalizedText(knownStatusLabel, Bundle.OccurrencePanel_commonPropertyKnownStatusLabel_text()); addItemToBag(gridY, 0, 0, 0, knownStatusLabel); @@ -181,7 +181,7 @@ final class OccurrencePanel extends javax.swing.JPanel { } addItemToBag(gridY, 1, 0, 0, knownStatusValue); gridY++; - String comment = ((OtherOccurrenceNodeInstanceData) occurrence).getComment(); + String comment = occurrence.getComment(); if (!comment.isEmpty()) { javax.swing.JLabel commentLabel = new javax.swing.JLabel(); org.openide.awt.Mnemonics.setLocalizedText(commentLabel, Bundle.OccurrencePanel_commonPropertyCommentLabel_text()); @@ -201,22 +201,17 @@ final class OccurrencePanel extends javax.swing.JPanel { } String caseDate = ""; try { - OtherOccurrenceNodeInstanceData nodeData = ((OtherOccurrenceNodeInstanceData) occurrence); - if (nodeData.isCentralRepoNode()) { - if (CentralRepository.isEnabled()) { - CorrelationCase partialCase = nodeData.getCorrelationAttributeInstance().getCorrelationCase(); - caseDate = CentralRepository.getInstance().getCaseByUUID(partialCase.getCaseUUID()).getCreationDate(); - } - } else { - caseDate = Case.getCurrentCase().getCreatedDate(); + if (CentralRepository.isEnabled()) { + CorrelationCase partialCase = occurrence.getCorrelationAttributeInstance().getCorrelationCase(); + caseDate = CentralRepository.getInstance().getCaseByUUID(partialCase.getCaseUUID()).getCreationDate(); } } catch (CentralRepoException ex) { LOGGER.log(Level.WARNING, "Error getting case created date for other occurrence content viewer", ex); } //Collect the data that is necessary for the other sections - caseNamesAndDates.put(((OtherOccurrenceNodeInstanceData) occurrence).getCaseName(), caseDate); - dataSourceNames.add(((OtherOccurrenceNodeInstanceData) occurrence).getDataSourceName()); - filePaths.add(((OtherOccurrenceNodeInstanceData) occurrence).getFilePath()); + caseNamesAndDates.put(occurrence.getCaseName(), caseDate); + dataSourceNames.add(occurrence.getDataSourceName()); + filePaths.add(occurrence.getFilePath()); } } //end for each diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java deleted file mode 100644 index da4915e75f..0000000000 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2018 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.centralrepository.contentviewer; -/** - * Marker interface for Other Occurrences nodes. - */ -interface OtherOccurrenceNodeData { - -} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeInstanceData.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeInstanceData.java deleted file mode 100644 index 92ebc821f8..0000000000 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeInstanceData.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Central Repository - * - * Copyright 2018-2019 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.centralrepository.contentviewer; - -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; -import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.DataSource; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData; -import org.sleuthkit.datamodel.TskDataException; - -/** - * Class for populating the Other Occurrences tab - */ -class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData { - - // For now hard code the string for the central repo files type, since - // getting it dynamically can fail. - private static final String FILE_TYPE_STR = "Files"; - private static final String CSV_ITEM_SEPARATOR = "\",\""; - - private final String caseName; - private String deviceID; - private String dataSourceName; - private final String filePath; - private final String typeStr; - private final String value; - private TskData.FileKnown known; - private String comment; - - private AbstractFile originalAbstractFile = null; - private CorrelationAttributeInstance originalCorrelationInstance = null; - - /** - * Create a node from a central repo instance. - * - * @param instance The central repo instance - * @param type The type of the instance - * @param value The value of the instance - */ - OtherOccurrenceNodeInstanceData(CorrelationAttributeInstance instance, CorrelationAttributeInstance.Type type, String value) { - caseName = instance.getCorrelationCase().getDisplayName(); - deviceID = instance.getCorrelationDataSource().getDeviceID(); - dataSourceName = instance.getCorrelationDataSource().getName(); - filePath = instance.getFilePath(); - this.typeStr = type.getDisplayName(); - this.value = value; - known = instance.getKnownStatus(); - comment = instance.getComment(); - - originalCorrelationInstance = instance; - } - - /** - * Create a node from an abstract file. - * - * @param newFile The abstract file - * @param autopsyCase The current case - * - * @throws CentralRepoException - */ - OtherOccurrenceNodeInstanceData(AbstractFile newFile, Case autopsyCase) throws CentralRepoException { - caseName = autopsyCase.getDisplayName(); - try { - DataSource dataSource = autopsyCase.getSleuthkitCase().getDataSource(newFile.getDataSource().getId()); - deviceID = dataSource.getDeviceId(); - dataSourceName = dataSource.getName(); - } catch (TskDataException | TskCoreException ex) { - throw new CentralRepoException("Error loading data source for abstract file ID " + newFile.getId(), ex); - } - - filePath = newFile.getParentPath() + newFile.getName(); - typeStr = FILE_TYPE_STR; - value = newFile.getMd5Hash(); - known = newFile.getKnown(); - comment = ""; - - originalAbstractFile = newFile; - } - - /** - * Check if this node is a "file" type - * - * @return true if it is a file type - */ - boolean isFileType() { - return FILE_TYPE_STR.equals(typeStr); - } - - /** - * Update the known status for this node - * - * @param newKnownStatus The new known status - */ - void updateKnown(TskData.FileKnown newKnownStatus) { - known = newKnownStatus; - } - - /** - * Update the comment for this node - * - * @param newComment The new comment - */ - void updateComment(String newComment) { - comment = newComment; - } - - /** - * Check if this is a central repo node. - * - * @return true if this node was created from a central repo instance, false - * otherwise - */ - boolean isCentralRepoNode() { - return (originalCorrelationInstance != null); - } - - /** - * Get the case name - * - * @return the case name - */ - String getCaseName() { - return caseName; - } - - /** - * Get the device ID - * - * @return the device ID - */ - String getDeviceID() { - return deviceID; - } - - /** - * Get the data source name - * - * @return the data source name - */ - String getDataSourceName() { - return dataSourceName; - } - - /** - * Get the file path - * - * @return the file path - */ - String getFilePath() { - return filePath; - } - - /** - * Get the type (as a string) - * - * @return the type - */ - String getType() { - return typeStr; - } - - /** - * Get the value (MD5 hash for files) - * - * @return the value - */ - String getValue() { - return value; - } - - /** - * Get the known status - * - * @return the known status - */ - TskData.FileKnown getKnown() { - return known; - } - - /** - * Get the comment - * - * @return the comment - */ - String getComment() { - return comment; - } - - /** - * Get the backing abstract file. Should only be called if - * isCentralRepoNode() is false - * - * @return the original abstract file - */ - AbstractFile getAbstractFile() throws CentralRepoException { - if (originalAbstractFile == null) { - throw new CentralRepoException("AbstractFile is null"); - } - return originalAbstractFile; - } - - /** - * Get the backing CorrelationAttributeInstance. Should only be called if - * isCentralRepoNode() is true - * - * @return the original CorrelationAttributeInstance - * - * @throws CentralRepoException - */ - CorrelationAttributeInstance getCorrelationAttributeInstance() throws CentralRepoException { - if (originalCorrelationInstance == null) { - throw new CentralRepoException("CorrelationAttributeInstance is null"); - } - return originalCorrelationInstance; - } - - /** - * Get the string to append between elements when writing the node instance - * data to a CSV - * - * @return the CSV_ITEM_SEPARATOR string - */ - static String getCsvItemSeparator() { - return CSV_ITEM_SEPARATOR; - } - - /** - * Create a string representation of the node's data comma separated with a - * line separator ending - * - * @return a comma separated string representation of the node's data - */ - String toCsvString() { - StringBuilder line = new StringBuilder("\""); - line.append(getCaseName()).append(CSV_ITEM_SEPARATOR) - .append(getDataSourceName()).append(CSV_ITEM_SEPARATOR) - .append(getType()).append(CSV_ITEM_SEPARATOR) - .append(getValue()).append(CSV_ITEM_SEPARATOR) - .append(getKnown().toString()).append(CSV_ITEM_SEPARATOR) - .append(getFilePath()).append(CSV_ITEM_SEPARATOR) - .append(getComment()).append('"') - .append(System.getProperty("line.separator")); - return line.toString(); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeMessageData.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeMessageData.java deleted file mode 100755 index 99e530349a..0000000000 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeMessageData.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2018 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.centralrepository.contentviewer; - -/** - * Class for populating the Other Occurrences tab with a single message. - */ -final class OtherOccurrenceNodeMessageData implements OtherOccurrenceNodeData { - private final String displayMessage; - - OtherOccurrenceNodeMessageData(String displayMessage) { - this.displayMessage = displayMessage; - } - - String getDisplayMessage() { - return displayMessage; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceOneTypeWorker.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceOneTypeWorker.java new file mode 100755 index 0000000000..69efdb6cdf --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceOneTypeWorker.java @@ -0,0 +1,185 @@ +/* + * Central Repository + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.centralrepository.contentviewer; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import javax.swing.SwingWorker; +import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.application.NodeData; +import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences; +import org.sleuthkit.autopsy.centralrepository.application.UniquePathKey; +import org.sleuthkit.autopsy.centralrepository.contentviewer.OtherOccurrenceOneTypeWorker.OneTypeData; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * Swing worker for getting the Other Occurrence data for the Domain Discovery + * window. + * + * This logic differs a bit from the OtherOcurrencesNodeWorker. + */ +class OtherOccurrenceOneTypeWorker extends SwingWorker { + + private static final Logger logger = Logger.getLogger(OtherOccurrenceOneTypeWorker.class.getName()); + + private final CorrelationAttributeInstance.Type aType; + private final String value; + private final AbstractFile file; + private final String deviceId; + private final String dataSourceName; + + /** + * Construct the worker. + * + * @param aType + * @param value + * @param file Source file, this maybe null. + * @param deviceId DeviceID string, this maybe an empty string. + * @param dataSourceName DataSourceName, this maybe an empty string. + */ + OtherOccurrenceOneTypeWorker(CorrelationAttributeInstance.Type aType, String value, AbstractFile file, String deviceId, String dataSourceName) { + this.aType = aType; + this.value = value; + this.file = file; + this.deviceId = deviceId; + this.dataSourceName = dataSourceName; + } + + @Override + protected OneTypeData doInBackground() throws Exception { + Map caseNames = new HashMap<>(); + int totalCount = 0; + Set dataSources = new HashSet<>(); + Collection correlationAttributesToAdd = new ArrayList<>(); + String earliestDate = OtherOccurrences.getEarliestCaseDate(); + OneTypeData results = null; + + if (CentralRepository.isEnabled()) { + List instances; + instances = CentralRepository.getInstance().getArtifactInstancesByTypeValue(aType, value); + HashMap nodeDataMap = new HashMap<>(); + String caseUUID = Case.getCurrentCase().getName(); + for (CorrelationAttributeInstance artifactInstance : instances) { + if (isCancelled()) { + break; + } + + // Only add the attribute if it isn't the object the user selected. + // We consider it to be a different object if at least one of the following is true: + // - the case UUID is different + // - the data source name is different + // - the data source device ID is different + // - the file path is different + if (artifactInstance.getCorrelationCase().getCaseUUID().equals(caseUUID) + && (!StringUtils.isBlank(dataSourceName) && artifactInstance.getCorrelationDataSource().getName().equals(dataSourceName)) + && (!StringUtils.isBlank(deviceId) && artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId)) + && (file != null && artifactInstance.getFilePath().equalsIgnoreCase(file.getParentPath() + file.getName()))) { + + continue; + } + correlationAttributesToAdd.add(artifactInstance); + NodeData newNode = new NodeData(artifactInstance, aType, value); + UniquePathKey uniquePathKey = new UniquePathKey(newNode); + nodeDataMap.put(uniquePathKey, newNode); + } + + for (NodeData nodeData : nodeDataMap.values()) { + if (isCancelled()) { + break; + } + try { + dataSources.add(OtherOccurrences.makeDataSourceString(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getDeviceID(), nodeData.getDataSourceName())); + caseNames.put(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getCorrelationAttributeInstance().getCorrelationCase()); + } catch (CentralRepoException ex) { + logger.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName(), ex); + } + totalCount++; + } + } + + if (!isCancelled()) { + results = new OneTypeData(caseNames, totalCount, dataSources.size(), earliestDate, correlationAttributesToAdd); + } + + return results; + } + + /** + * Class to store the results of the worker thread. + */ + static final class OneTypeData { + + private final Map caseNames; + private final int totalCount; + private final int dataSourceCount; + private final Collection correlationAttributesToAdd; + private final String earliestCaseDate; + + /** + * Construct the results. + * + * @param caseNames Map of correlation cases. + * @param totalCount Total count of instances. + * @param dataSourceCount Data source count. + * @param earliestCaseDate Formatted string which contains the + * earliest case date. + * @param correlationAttributesToAdd The attributes to add to the main + * panel list. + */ + OneTypeData(Map caseNames, int totalCount, int dataSourceCount, String earliestCaseDate, Collection correlationAttributesToAdd) { + this.caseNames = caseNames; + this.totalCount = totalCount; + this.dataSourceCount = dataSourceCount; + this.correlationAttributesToAdd = correlationAttributesToAdd; + this.earliestCaseDate = earliestCaseDate; + } + + public Map getCaseNames() { + return caseNames; + } + + public int getTotalCount() { + return totalCount; + } + + public int getDataSourceCount() { + return dataSourceCount; + } + + public Collection getCorrelationAttributesToAdd() { + return correlationAttributesToAdd; + } + + public String getEarliestCaseDate() { + return earliestCaseDate; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesDataSourcesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesDataSourcesTableModel.java index 89d2a599ec..55e1dea428 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesDataSourcesTableModel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesDataSourcesTableModel.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.centralrepository.contentviewer; +import org.sleuthkit.autopsy.centralrepository.application.NodeData; import java.util.LinkedHashSet; import java.util.Objects; import java.util.Set; @@ -26,6 +27,7 @@ import javax.swing.table.AbstractTableModel; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.coreutils.Logger; @@ -139,11 +141,10 @@ final class OtherOccurrencesDataSourcesTableModel extends AbstractTableModel { * * @param newNodeData data to add to the table */ - void addNodeData(OtherOccurrenceNodeData newNodeData) { - OtherOccurrenceNodeInstanceData nodeData = (OtherOccurrenceNodeInstanceData) newNodeData; + void addNodeData(NodeData newNodeData) { String caseUUID; try { - caseUUID = nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(); + caseUUID = newNodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(); } catch (CentralRepoException ignored) { //non central repo nodeData won't have a correlation case try { @@ -151,10 +152,10 @@ final class OtherOccurrencesDataSourcesTableModel extends AbstractTableModel { //place holder value will be used since correlation attribute was unavailble } catch (NoCurrentCaseException ex) { logger.log(Level.WARNING, "Unable to get current case", ex); - caseUUID = DataContentViewerOtherCases.getPlaceholderUUID(); + caseUUID = OtherOccurrences.getPlaceholderUUID(); } } - dataSourceSet.add(new DataSourceColumnItem(nodeData.getCaseName(), nodeData.getDeviceID(), nodeData.getDataSourceName(), caseUUID)); + dataSourceSet.add(new DataSourceColumnItem(newNodeData.getCaseName(), newNodeData.getDeviceID(), newNodeData.getDataSourceName(), caseUUID)); fireTableDataChanged(); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableModel.java index 8f78d66ee8..284255bfae 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableModel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesFilesTableModel.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.centralrepository.contentviewer; +import org.sleuthkit.autopsy.centralrepository.application.NodeData; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -28,6 +29,7 @@ import org.openide.util.NbBundle.Messages; import org.apache.commons.io.FilenameUtils; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.coreutils.Logger; @@ -40,7 +42,7 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(OtherOccurrencesFilesTableModel.class.getName()); private final List nodeKeys = new ArrayList<>(); - private final Map> nodeMap = new HashMap<>(); + private final Map> nodeMap = new HashMap<>(); /** * Create a table model for displaying file names @@ -75,7 +77,7 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel { || nodeMap.get(nodeKeys.get(rowIdx)).isEmpty()) { return Bundle.OtherOccurrencesFilesTableModel_noData(); } - return FilenameUtils.getName(((OtherOccurrenceNodeInstanceData) nodeMap.get(nodeKeys.get(rowIdx)).get(0)).getFilePath()); + return FilenameUtils.getName( nodeMap.get(nodeKeys.get(rowIdx)).get(0).getFilePath()); } /** @@ -87,7 +89,7 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel { * @return a list of OtherOccurrenceNodeData for the specified index or an * empty list if no data was found */ - List getListOfNodesForFile(int rowIdx) { + List getListOfNodesForFile(int rowIdx) { //if anything would prevent this from working return an empty list if (nodeMap.isEmpty() || nodeKeys.isEmpty() || rowIdx < 0 || rowIdx >= nodeKeys.size() || nodeKeys.get(rowIdx) == null @@ -107,9 +109,9 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel { * * @param newNodeData data to add to the table */ - void addNodeData(OtherOccurrenceNodeData newNodeData) { - String newNodeKey = createNodeKey((OtherOccurrenceNodeInstanceData) newNodeData);//FilenameUtils.getName(((OtherOccurrenceNodeInstanceData)newNodeData).getFilePath()); - List nodeList = nodeMap.get(newNodeKey); + void addNodeData(NodeData newNodeData) { + String newNodeKey = createNodeKey(newNodeData); + List nodeList = nodeMap.get(newNodeKey); if (nodeList == null) { nodeKeys.add(newNodeKey); nodeList = new ArrayList<>(); @@ -119,7 +121,7 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel { fireTableDataChanged(); } - private String createNodeKey(OtherOccurrenceNodeInstanceData nodeData) { + private String createNodeKey(NodeData nodeData) { String caseUUID; try { caseUUID = nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(); @@ -130,7 +132,7 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel { //place holder value will be used since correlation attribute was unavailble } catch (NoCurrentCaseException ex) { logger.log(Level.WARNING, "Unable to get current case", ex); - caseUUID = DataContentViewerOtherCases.getPlaceholderUUID(); + caseUUID = OtherOccurrences.getPlaceholderUUID(); } } return nodeData.getCaseName() + nodeData.getDataSourceName() + nodeData.getDeviceID() + nodeData.getFilePath() + caseUUID; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java new file mode 100755 index 0000000000..8f0ed3b13b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java @@ -0,0 +1,207 @@ +/* + * Central Repository + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.centralrepository.contentviewer; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import javax.swing.SwingWorker; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.application.NodeData; +import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences; +import org.sleuthkit.autopsy.centralrepository.contentviewer.OtherOccurrencesNodeWorker.OtherOccurrencesData; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.TskContentItem; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.AnalysisResult; +import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.DataArtifact; +import org.sleuthkit.datamodel.OsAccount; +import org.sleuthkit.datamodel.TskException; + +/** + * A SwingWorker that gathers data for the OtherOccurencesPanel which appears in + * the dataContentViewerOtherCases panel. + */ +class OtherOccurrencesNodeWorker extends SwingWorker { + + private static final Logger logger = Logger.getLogger(OtherOccurrencesNodeWorker.class.getName()); + + private final Node node; + + /** + * Constructs a new instance for the given node. + * + * @param node + */ + OtherOccurrencesNodeWorker(Node node) { + this.node = node; + } + + @Override + protected OtherOccurrencesData doInBackground() throws Exception { + OtherOccurrencesData data = null; + if (CentralRepository.isEnabled()) { + OsAccount osAccount = node.getLookup().lookup(OsAccount.class); + String deviceId = ""; + String dataSourceName = ""; + Map caseNames = new HashMap<>(); + Case currentCase = Case.getCurrentCaseThrows(); + //the file is currently being used for determining a correlation instance is not the selected instance + // for the purposes of ignoring the currently selected item + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + try { + if (file != null) { + Content dataSource = file.getDataSource(); + deviceId = currentCase.getSleuthkitCase().getDataSource(dataSource.getId()).getDeviceId(); + dataSourceName = dataSource.getName(); + } + } catch (TskException ex) { + logger.log(Level.WARNING, "Exception occurred while trying to get the data source, current case, and device id for an AbstractFile in the other occurrences viewer", ex); + return data; + } + Collection correlationAttributes = new ArrayList<>(); + if (osAccount != null) { + correlationAttributes.addAll(OtherOccurrences.getCorrelationAttributeFromOsAccount(node, osAccount)); + } else { + TskContentItem contentItem = node.getLookup().lookup(TskContentItem.class); + Content content = null; + if (contentItem != null) { + content = contentItem.getTskContent(); + } else { //fallback and check ContentTags + ContentTag nodeContentTag = node.getLookup().lookup(ContentTag.class); + BlackboardArtifactTag nodeBbArtifactTag = node.getLookup().lookup(BlackboardArtifactTag.class); + if (nodeBbArtifactTag != null) { + content = nodeBbArtifactTag.getArtifact(); + } else if (nodeContentTag != null) { + content = nodeContentTag.getContent(); + } + } + if (content != null) { + if (content instanceof AbstractFile) { + correlationAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AbstractFile) content)); + } else if (content instanceof AnalysisResult) { + correlationAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AnalysisResult) content)); + } else if (content instanceof DataArtifact) { + correlationAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((DataArtifact) content)); + } + } + } + int totalCount = 0; + Set dataSources = new HashSet<>(); + String currentCaseName = Case.getCurrentCase().getName(); + for (CorrelationAttributeInstance corAttr : correlationAttributes) { + for (NodeData nodeData : OtherOccurrences.getCorrelatedInstances(deviceId, dataSourceName, corAttr).values()) { + try { + if(!currentCaseName.equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())) { + dataSources.add(OtherOccurrences.makeDataSourceString(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getDeviceID(), nodeData.getDataSourceName())); + caseNames.put(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getCorrelationAttributeInstance().getCorrelationCase()); + } + } catch (CentralRepoException ex) { + logger.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName(), ex); + } + totalCount++; + if (isCancelled()) { + break; + } + } + } + if (!isCancelled()) { + data = new OtherOccurrencesData(correlationAttributes, file, dataSourceName, deviceId, caseNames, totalCount, dataSources.size(), OtherOccurrences.getEarliestCaseDate()); + } + } + return data; + } + + /** + * Object to store all of the data gathered in the OtherOccurrencesWorker + * doInBackground method. + */ + static class OtherOccurrencesData { + + private final String deviceId; + private final AbstractFile file; + private final String dataSourceName; + private final Map caseMap; + private final int instanceDataCount; + private final int dataSourceCount; + private final String earliestCaseDate; + private final Collection correlationAttributes; + + private OtherOccurrencesData(Collection correlationAttributes, AbstractFile file, String dataSourceName, String deviceId, Map caseMap, int instanceCount, int dataSourceCount, String earliestCaseDate) { + this.file = file; + this.deviceId = deviceId; + this.dataSourceName = dataSourceName; + this.caseMap = caseMap; + this.instanceDataCount = instanceCount; + this.dataSourceCount = dataSourceCount; + this.earliestCaseDate = earliestCaseDate; + this.correlationAttributes = correlationAttributes; + } + + public String getDeviceId() { + return deviceId; + } + + public AbstractFile getFile() { + return file; + } + + public String getDataSourceName() { + return dataSourceName; + } + + public Map getCaseMap() { + return caseMap; + } + + public int getInstanceDataCount() { + return instanceDataCount; + } + + public int getDataSourceCount() { + return dataSourceCount; + } + + /** + * Returns the earliest date in the case. + * + * @return Formatted date string, or message that one was not found. + */ + public String getEarliestCaseDate() { + return earliestCaseDate; + } + + public Collection getCorrelationAttributes() { + return correlationAttributes; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.form b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.form new file mode 100644 index 0000000000..13284ecec9 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.form @@ -0,0 +1,319 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.java new file mode 100644 index 0000000000..40dbb4d70c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.java @@ -0,0 +1,896 @@ +/* + * Central Repository + * + * Copyright 2017-2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.centralrepository.contentviewer; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import org.sleuthkit.autopsy.centralrepository.application.NodeData; +import org.sleuthkit.autopsy.centralrepository.application.UniquePathKey; +import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences; +import java.awt.Cursor; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.io.File; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; +import java.util.logging.Level; +import javax.swing.JFileChooser; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import static javax.swing.JOptionPane.DEFAULT_OPTION; +import static javax.swing.JOptionPane.ERROR_MESSAGE; +import static javax.swing.JOptionPane.PLAIN_MESSAGE; +import javax.swing.SwingWorker; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.TableModel; +import javax.swing.table.TableRowSorter; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.contentviewer.OtherOccurrencesNodeWorker.OtherOccurrencesData; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * Panel for displaying other occurrences results. + */ +@NbBundle.Messages({ + "OtherOccurrencesPanel.table.noArtifacts=Item has no attributes with which to search.", + "OtherOccurrencesPanel.table.noResultsFound=No results found.", + "OtherOccurrencesPanel_table_loadingResults=Loading results" +}) +public final class OtherOccurrencesPanel extends javax.swing.JPanel { + + private static final CorrelationCaseWrapper NO_ARTIFACTS_CASE = new CorrelationCaseWrapper(Bundle.OtherOccurrencesPanel_table_noArtifacts()); + private static final CorrelationCaseWrapper NO_RESULTS_CASE = new CorrelationCaseWrapper(Bundle.OtherOccurrencesPanel_table_noResultsFound()); + private static final Logger logger = Logger.getLogger(OtherOccurrencesPanel.class.getName()); + private static final long serialVersionUID = 1L; + private final OtherOccurrencesFilesTableModel filesTableModel; + private final OtherOccurrencesCasesTableModel casesTableModel; + private final OtherOccurrencesDataSourcesTableModel dataSourcesTableModel; + private OccurrencePanel occurrencePanel; + private final Collection correlationAttributes; + private String dataSourceName = ""; //the data source of the file which the content viewer is being populated for + private String deviceId = ""; //the device id of the data source for the file which the content viewer is being populated for + private AbstractFile file = null; + + private SwingWorker worker; + + // Initializing the JFileChooser in a thread to prevent a block on the EDT + // see https://stackoverflow.com/questions/49792375/jfilechooser-is-very-slow-when-using-windows-look-and-feel + private final FutureTask futureFileChooser = new FutureTask<>(JFileChooser::new); + private JFileChooser CSVFileChooser; + + /** + * Creates new form OtherOccurrencesPanel + */ + public OtherOccurrencesPanel() { + this.filesTableModel = new OtherOccurrencesFilesTableModel(); + this.casesTableModel = new OtherOccurrencesCasesTableModel(); + this.dataSourcesTableModel = new OtherOccurrencesDataSourcesTableModel(); + this.correlationAttributes = new ArrayList<>(); + initComponents(); + customizeComponents(); + + ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("JFileChooser-background-thread-OtherOccurrencesPanel").build()); + executor.execute(futureFileChooser); + } + + private void customizeComponents() { + ActionListener actList = (ActionEvent e) -> { + JMenuItem jmi = (JMenuItem) e.getSource(); + if (jmi.equals(showCaseDetailsMenuItem)) { + showCaseDetails(filesTable.getSelectedRow()); + } else if (jmi.equals(exportToCSVMenuItem)) { + try { + saveToCSV(); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS + } + } else if (jmi.equals(showCommonalityMenuItem)) { + showCommonalityDetails(); + } + }; + + exportToCSVMenuItem.addActionListener(actList); + showCaseDetailsMenuItem.addActionListener(actList); + showCommonalityMenuItem.addActionListener(actList); + filesTable.setComponentPopupMenu(rightClickPopupMenu); + // Configure column sorting. + TableRowSorter sorter = new TableRowSorter<>(filesTable.getModel()); + filesTable.setRowSorter(sorter); + + //sort tables alphabetically initially + casesTable.getRowSorter().toggleSortOrder(0); + dataSourcesTable.getRowSorter().toggleSortOrder(0); + filesTable.getRowSorter().toggleSortOrder(0); + reset(); + casesTable.getSelectionModel().addListSelectionListener((e) -> { + if (Case.isCaseOpen()) { + updateOnCaseSelection(); + } + }); + dataSourcesTable.getSelectionModel().addListSelectionListener((e) -> { + if (Case.isCaseOpen()) { + updateOnDataSourceSelection(); + } + }); + + //alows resizing of the 4th section + filesTable.getSelectionModel().addListSelectionListener((e) -> { + if (Case.isCaseOpen()) { + occurrencePanel = new OccurrencePanel(); + updateOnFileSelection(); + } + }); + detailsPanelScrollPane.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent componentEvent) { + //when its resized make sure the width of the panel resizes to match the scroll pane width to avoid a horizontal scroll bar + occurrencePanel.setPreferredSize(new java.awt.Dimension(detailsPanelScrollPane.getPreferredSize().width, occurrencePanel.getPreferredSize().height)); + detailsPanelScrollPane.setViewportView(occurrencePanel); + } + }); + + } + + @NbBundle.Messages({"OtherOccurrencesPanel.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate.", + "# {0} - commonality percentage", + "# {1} - correlation type", + "# {2} - correlation value", + "OtherOccurrencesPanel.correlatedArtifacts.byType={0}% of data sources have {2} (type: {1})\n", + "OtherOccurrencesPanel.correlatedArtifacts.title=Attribute Frequency", + "OtherOccurrencesPanel.correlatedArtifacts.failed=Failed to get frequency details."}) + /** + * Show how common the selected correlationAttributes are with details + * dialog. + */ + private void showCommonalityDetails() { + if (correlationAttributes.isEmpty()) { + JOptionPane.showConfirmDialog(OtherOccurrencesPanel.this, + Bundle.OtherOccurrencesPanel_correlatedArtifacts_isEmpty(), + Bundle.OtherOccurrencesPanel_correlatedArtifacts_title(), + DEFAULT_OPTION, PLAIN_MESSAGE); + } else { + StringBuilder msg = new StringBuilder(correlationAttributes.size()); + int percentage; + this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + try { + // Leaving these calls on the EDT but adding wait cursor + CentralRepository dbManager = CentralRepository.getInstance(); + for (CorrelationAttributeInstance eamArtifact : correlationAttributes) { + try { + percentage = dbManager.getFrequencyPercentage(eamArtifact); + msg.append(Bundle.OtherOccurrencesPanel_correlatedArtifacts_byType(percentage, + eamArtifact.getCorrelationType().getDisplayName(), + eamArtifact.getCorrelationValue())); + } catch (CorrelationAttributeNormalizationException ex) { + logger.log(Level.WARNING, String.format("Error getting commonality details for artifact with ID: %s.", eamArtifact.getID()), ex); + } + } + this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + JOptionPane.showConfirmDialog(OtherOccurrencesPanel.this, + msg.toString(), + Bundle.OtherOccurrencesPanel_correlatedArtifacts_title(), + DEFAULT_OPTION, PLAIN_MESSAGE); + } catch (CentralRepoException ex) { + this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + logger.log(Level.SEVERE, "Error getting commonality details.", ex); + JOptionPane.showConfirmDialog(OtherOccurrencesPanel.this, + Bundle.OtherOccurrencesPanel_correlatedArtifacts_failed(), + Bundle.OtherOccurrencesPanel_correlatedArtifacts_title(), + DEFAULT_OPTION, ERROR_MESSAGE); + } + } + } + + @NbBundle.Messages({"OtherOccurrencesPanel.caseDetailsDialog.notSelected=No Row Selected", + "OtherOccurrencesPanel.caseDetailsDialog.noDetails=No details for this case.", + "OtherOccurrencesPanel.caseDetailsDialog.noDetailsReference=No case details for Global reference properties.", + "OtherOccurrencesPanel.caseDetailsDialog.noCaseNameError=Error", + "OtherOccurrencesPanel.noOpenCase.errMsg=No open case available."}) + private void showCaseDetails(int selectedRowViewIdx) { + String caseDisplayName = Bundle.OtherOccurrencesPanel_caseDetailsDialog_noCaseNameError(); + String details = Bundle.OtherOccurrencesPanel_caseDetailsDialog_noDetails(); + try { + if (-1 != selectedRowViewIdx && filesTableModel.getRowCount() > 0) { + CentralRepository dbManager = CentralRepository.getInstance(); + int selectedRowModelIdx = filesTable.convertRowIndexToModel(selectedRowViewIdx); + List rowList = filesTableModel.getListOfNodesForFile(selectedRowModelIdx); + if (!rowList.isEmpty()) { + CorrelationCase eamCasePartial = rowList.get(0).getCorrelationAttributeInstance().getCorrelationCase(); + caseDisplayName = eamCasePartial.getDisplayName(); + // query case details + CorrelationCase eamCase = dbManager.getCaseByUUID(eamCasePartial.getCaseUUID()); + if (eamCase != null) { + details = eamCase.getCaseDetailsOptionsPaneDialog(); + } else { + details = Bundle.OtherOccurrencesPanel_caseDetailsDialog_noDetails(); + } + } else { + details = Bundle.OtherOccurrencesPanel_caseDetailsDialog_noDetailsReference(); + } + } + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, "Error loading case details", ex); + } finally { + JOptionPane.showConfirmDialog(OtherOccurrencesPanel.this, + details, + caseDisplayName, + DEFAULT_OPTION, PLAIN_MESSAGE); + } + } + + private void saveToCSV() throws NoCurrentCaseException { + if (casesTableModel.getRowCount() > 0) { + + if (CSVFileChooser == null) { + try { + CSVFileChooser = futureFileChooser.get(); + } catch (InterruptedException | ExecutionException ex) { + // If something happened with the thread try and + // initalized the chooser now + logger.log(Level.WARNING, "A failure occurred in the JFileChooser background thread"); + CSVFileChooser = new JFileChooser(); + } + } + + Calendar now = Calendar.getInstance(); + String fileName = String.format("%1$tY%1$tm%1$te%1$tI%1$tM%1$tS_other_data_sources.csv", now); + CSVFileChooser.setCurrentDirectory(new File(Case.getCurrentCaseThrows().getExportDirectory())); + CSVFileChooser.setSelectedFile(new File(fileName)); + CSVFileChooser.setFileFilter(new FileNameExtensionFilter("csv file", "csv")); + + int returnVal = CSVFileChooser.showSaveDialog(filesTable); + if (returnVal == JFileChooser.APPROVE_OPTION) { + + File selectedFile = CSVFileChooser.getSelectedFile(); + if (!selectedFile.getName().endsWith(".csv")) { // NON-NLS + selectedFile = new File(selectedFile.toString() + ".csv"); // NON-NLS + } + CSVWorker csvWorker = new CSVWorker(selectedFile, dataSourceName, deviceId, Collections.unmodifiableCollection(correlationAttributes)); + csvWorker.execute(); + } + } + } + + @NbBundle.Messages({"OtherOccurrencesPanel_earliestCaseNotAvailable=Not Available."}) + /** + * Reset the UI and clear cached data. + */ + public void reset() { + // start with empty table + casesTableModel.clearTable(); + dataSourcesTableModel.clearTable(); + filesTableModel.clearTable(); + correlationAttributes.clear(); + earliestCaseDate.setText(Bundle.OtherOccurrencesPanel_earliestCaseNotAvailable()); + foundInLabel.setText(""); + //calling getPreferredSize has a side effect of ensuring it has a preferred size which reflects the contents which are visible + occurrencePanel = new OccurrencePanel(); + occurrencePanel.getPreferredSize(); + detailsPanelScrollPane.setViewportView(occurrencePanel); + } + + /** + * Populate the other occurrences table for one Correlation Attribute type + * and value. + * + * This method contains its own SwingWorker togather data. + * + * @param aType The correlation attribute type to display other occurrences + * for. + * @param value The value being correlated on. + */ + public void populateTableForOneType(CorrelationAttributeInstance.Type aType, String value) throws CentralRepoException { + if (worker != null) { + worker.cancel(true); + worker = null; + } + + casesTableModel.addCorrelationCase(NO_ARTIFACTS_CASE); + + worker = new OtherOccurrenceOneTypeWorker(aType, value, file, deviceId, dataSourceName) { + @Override + public void done() { + try { + if (isCancelled()) { + return; + } + + casesTableModel.clearTable(); + + OtherOccurrenceOneTypeWorker.OneTypeData data = get(); + correlationAttributes.addAll(data.getCorrelationAttributesToAdd()); + for (CorrelationCase corCase : data.getCaseNames().values()) { + casesTableModel.addCorrelationCase(new CorrelationCaseWrapper(corCase)); + } + int caseCount = casesTableModel.getRowCount(); + if (correlationAttributes.isEmpty()) { + casesTableModel.addCorrelationCase(NO_ARTIFACTS_CASE); + } else if (caseCount == 0) { + casesTableModel.addCorrelationCase(NO_RESULTS_CASE); + } else { + String earliestDate = data.getEarliestCaseDate(); + earliestCaseDate.setText(earliestDate.isEmpty() ? Bundle.OtherOccurrencesPanel_earliestCaseNotAvailable() : earliestDate); + foundInLabel.setText(String.format(Bundle.OtherOccurrencesPanel_foundIn_text(), data.getTotalCount(), caseCount, data.getDataSourceCount())); + if (caseCount > 0) { + casesTable.setRowSelectionInterval(0, 0); + } + } + + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Failed to update OtherOccurrence panel", ex); + } + } + }; + + worker.execute(); + } + + /** + * Makes a loading message appear in the case table. + */ + void showPanelLoadingMessage() { + casesTableModel.addCorrelationCase(NO_ARTIFACTS_CASE); + } + + /** + * Load the correlatable data into the table model. If there is no data + * available display the message on the status panel. + * + * @param data A data wrapper object. + */ + @NbBundle.Messages({ + "OtherOccurrencesPanel.foundIn.text=Found %d instances in %d cases and %d data sources." + }) + void populateTable(OtherOccurrencesData data) { + this.file = data.getFile(); + this.dataSourceName = data.getDataSourceName(); + this.deviceId = data.getDeviceId(); + + casesTableModel.clearTable(); + + correlationAttributes.addAll(data.getCorrelationAttributes()); + + for (CorrelationCase corCase : data.getCaseMap().values()) { + casesTableModel.addCorrelationCase(new CorrelationCaseWrapper(corCase)); + } + int caseCount = casesTableModel.getRowCount(); + if (correlationAttributes.isEmpty()) { + casesTableModel.addCorrelationCase(NO_ARTIFACTS_CASE); + } else if (caseCount == 0) { + casesTableModel.addCorrelationCase(NO_RESULTS_CASE); + } else { + String earliestDate = data.getEarliestCaseDate(); + earliestCaseDate.setText(earliestDate.isEmpty() ? Bundle.OtherOccurrencesPanel_earliestCaseNotAvailable() : earliestDate); + foundInLabel.setText(String.format(Bundle.OtherOccurrencesPanel_foundIn_text(), data.getInstanceDataCount(), caseCount, data.getDataSourceCount())); + if (caseCount > 0) { + casesTable.setRowSelectionInterval(0, 0); + } + } + } + + /** + * Updates displayed information to be correct for the current case + * selection + */ + private void updateOnCaseSelection() { + if (worker != null) { + worker.cancel(true); + worker = null; + } + + final int[] selectedCaseIndexes = casesTable.getSelectedRows(); + dataSourcesTableModel.clearTable(); + filesTableModel.clearTable(); + + if (selectedCaseIndexes.length == 0) { + //special case when no cases are selected + occurrencePanel = new OccurrencePanel(); + occurrencePanel.getPreferredSize(); + detailsPanelScrollPane.setViewportView(occurrencePanel); + + return; + } + + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + worker = new SelectionWorker(correlationAttributes, deviceId, dataSourceName) { + @Override + public void done() { + if (isCancelled()) { + return; + } + + try { + Map correlatedNodeDataMap = get(); + + String currentCaseName; + try { + currentCaseName = Case.getCurrentCaseThrows().getName(); + } catch (NoCurrentCaseException ex) { + currentCaseName = null; + logger.log(Level.WARNING, "Unable to get current case for other occurrences content viewer", ex); + } + if (casesTableModel.getRowCount() > 0) { + for (NodeData nodeData : correlatedNodeDataMap.values()) { + for (int selectedRow : selectedCaseIndexes) { + try { + if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)) != null + && casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())) { + dataSourcesTableModel.addNodeData(nodeData); + } + } catch (CentralRepoException ex) { + logger.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex); + } + } + } + } + if (dataSourcesTableModel.getRowCount() > 0) { + dataSourcesTable.setRowSelectionInterval(0, 0); + } + + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Failed to update OtherOccurrencesPanel on data source selection", ex); + } + } + }; + + worker.execute(); + } + + /** + * Updates displayed information to be correct for the current data source + * selection + */ + private void updateOnDataSourceSelection() { + if (worker != null) { + worker.cancel(true); + worker = null; + } + + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + final int[] selectedDataSources = dataSourcesTable.getSelectedRows(); + filesTableModel.clearTable(); + + worker = new SelectionWorker(correlationAttributes, deviceId, dataSourceName) { + @Override + public void done() { + if (isCancelled()) { + return; + } + + try { + Map correlatedNodeDataMap = get(); + if (dataSourcesTableModel.getRowCount() > 0) { + for (NodeData nodeData : correlatedNodeDataMap.values()) { + for (int selectedDataSourceRow : selectedDataSources) { + int rowModelIndex = dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow); + try { + if (dataSourcesTableModel.getCaseUUIDForRow(rowModelIndex).equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID()) + && dataSourcesTableModel.getDeviceIdForRow(rowModelIndex).equals(nodeData.getDeviceID())) { + filesTableModel.addNodeData(nodeData); + } + } catch (CentralRepoException ex) { + logger.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex); + } + } + } + } + + if (filesTableModel.getRowCount() > 0) { + filesTable.setRowSelectionInterval(0, 0); + } + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Failed to update OtherOccurrencesPanel on case selection", ex); + } finally { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + } + }; + + worker.execute(); + } + + /** + * Update the data displayed in the details section to be correct for the + * currently selected File + */ + private void updateOnFileSelection() { + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + try { + if (filesTableModel.getRowCount() > 0 && filesTable.getSelectedRowCount() == 1) { + //if there is one file selected update the deatils to show the data for that file + occurrencePanel = new OccurrencePanel(filesTableModel.getListOfNodesForFile(filesTable.convertRowIndexToModel(filesTable.getSelectedRow()))); + } else if (dataSourcesTableModel.getRowCount() > 0 && dataSourcesTable.getSelectedRowCount() == 1) { + //if no files were selected and only one data source is selected update the information to reflect the data source + String caseName = dataSourcesTableModel.getCaseNameForRow(dataSourcesTable.convertRowIndexToModel(dataSourcesTable.getSelectedRow())); + String dsName = dataSourcesTableModel.getValueAt(dataSourcesTable.convertRowIndexToModel(dataSourcesTable.getSelectedRow()), 0).toString(); + String caseCreatedDate = ""; + if (casesTableModel.getRowCount() > 0) { + for (int row : casesTable.getSelectedRows()) { + if (casesTableModel.getValueAt(casesTable.convertRowIndexToModel(row), 0).toString().equals(caseName)) { + caseCreatedDate = getCaseCreatedDate(row); + break; + } + } + } + occurrencePanel = new OccurrencePanel(caseName, caseCreatedDate, dsName); + } else if (casesTable.getSelectedRowCount() == 1) { + //if no files were selected and a number of data source other than 1 are selected + //update the information to reflect the case + String createdDate; + String caseName = ""; + if (casesTableModel.getRowCount() > 0) { + caseName = casesTableModel.getValueAt(casesTable.convertRowIndexToModel(casesTable.getSelectedRow()), 0).toString(); + } + if (caseName.isEmpty()) { + occurrencePanel = new OccurrencePanel(); + } else { + createdDate = getCaseCreatedDate(casesTable.getSelectedRow()); + occurrencePanel = new OccurrencePanel(caseName, createdDate); + } + } else { + //else display an empty details area + occurrencePanel = new OccurrencePanel(); + } + //calling getPreferredSize has a side effect of ensuring it has a preferred size which reflects the contents which are visible + occurrencePanel.getPreferredSize(); + detailsPanelScrollPane.setViewportView(occurrencePanel); + } finally { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + } + + /** + * Get the date a case was created + * + * @param caseTableRowIdx the row from the casesTable representing the case + * + * @return A string representing the date the case was created or an empty + * string if the date could not be determined + */ + private String getCaseCreatedDate(int caseTableRowIdx) { + try { + if (CentralRepository.isEnabled() && casesTableModel.getRowCount() > 0) { + CorrelationCase partialCase; + partialCase = casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(caseTableRowIdx)); + if (partialCase == null) { + return ""; + } + return CentralRepository.getInstance().getCaseByUUID(partialCase.getCaseUUID()).getCreationDate(); + } else { + return Case.getCurrentCase().getCreatedDate(); + } + } catch (CentralRepoException ex) { + logger.log(Level.WARNING, "Error getting case created date for row: " + caseTableRowIdx, ex); + } + return ""; + } + + /** + * SwingWorker used by the case and data source selection handler. + */ + private class SelectionWorker extends SwingWorker, Void> { + + private final Collection coAtInstances; + private final String deviceIdStr; + private final String dataSourceNameStr; + + /** + * Construct a new SelectionWorker. + * + * @param coAtInstances + * @param abstractFile + * @param deviceIdStr + * @param dataSourceNameStr + */ + SelectionWorker(Collection coAtInstances, String deviceIdStr, String dataSourceNameStr) { + this.coAtInstances = coAtInstances; + this.dataSourceNameStr = dataSourceNameStr; + this.deviceIdStr = deviceIdStr; + } + + @Override + protected Map doInBackground() throws Exception { + Map correlatedNodeDataMap = new HashMap<>(); + for (CorrelationAttributeInstance corAttr : coAtInstances) { + correlatedNodeDataMap.putAll(OtherOccurrences.getCorrelatedInstances(deviceIdStr, dataSourceNameStr, corAttr)); + + if (isCancelled()) { + return new HashMap<>(); + } + } + + return correlatedNodeDataMap; + } + } + + /** + * SwingWorker for creating the CSV dump file. + */ + private class CSVWorker extends SwingWorker { + + private final Collection correlationAttList; + private final String dataSourceName; + private final String deviceId; + private final File destFile; + + /** + * Construct a CSVWorker + * + * @param destFile Output file. + * @param sourceFile Input file. + * @param dataSourceName Name of current dataSource. + * @param deviceId Id of the selected device. + * @param correlationAttList + */ + CSVWorker(File destFile, String dataSourceName, String deviceId, Collection correlationAttList) { + this.destFile = destFile; + this.dataSourceName = dataSourceName; + this.deviceId = deviceId; + this.correlationAttList = correlationAttList; + } + + @Override + protected Void doInBackground() throws Exception { + OtherOccurrences.writeOtherOccurrencesToFileAsCSV(this.destFile, this.correlationAttList, this.dataSourceName, this.deviceId); + return null; + } + + @Override + public void done() { + try { + get(); + } catch (InterruptedException | ExecutionException ex) { + JOptionPane.showMessageDialog(OtherOccurrencesPanel.this, + "Failed to create csv file for Other Occurrences at\n" + destFile.getAbsolutePath(), + "Error Creating CSV", + JOptionPane.ERROR_MESSAGE); + + logger.log(Level.SEVERE, "Error writing selected rows to from OtherOccurrencePanel to " + destFile.getAbsolutePath(), ex); + } + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + rightClickPopupMenu = new javax.swing.JPopupMenu(); + exportToCSVMenuItem = new javax.swing.JMenuItem(); + showCaseDetailsMenuItem = new javax.swing.JMenuItem(); + showCommonalityMenuItem = new javax.swing.JMenuItem(); + tableContainerPanel = new javax.swing.JPanel(); + tablesViewerSplitPane = new javax.swing.JSplitPane(); + caseDatasourceFileSplitPane = new javax.swing.JSplitPane(); + caseDatasourceSplitPane = new javax.swing.JSplitPane(); + caseScrollPane = new javax.swing.JScrollPane(); + casesTable = new javax.swing.JTable(); + dataSourceScrollPane = new javax.swing.JScrollPane(); + dataSourcesTable = new javax.swing.JTable(); + filesTableScrollPane = new javax.swing.JScrollPane(); + filesTable = new javax.swing.JTable(); + detailsPanelScrollPane = new javax.swing.JScrollPane(); + jPanel1 = new javax.swing.JPanel(); + foundInLabel = new javax.swing.JLabel(); + earliestCaseDate = new javax.swing.JLabel(); + earliestCaseLabel = new javax.swing.JLabel(); + filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0)); + + rightClickPopupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() { + public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) { + } + public void popupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent evt) { + } + public void popupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) { + rightClickPopupMenuPopupMenuWillBecomeVisible(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(exportToCSVMenuItem, org.openide.util.NbBundle.getMessage(OtherOccurrencesPanel.class, "OtherOccurrencesPanel.exportToCSVMenuItem.text")); // NOI18N + rightClickPopupMenu.add(exportToCSVMenuItem); + + org.openide.awt.Mnemonics.setLocalizedText(showCaseDetailsMenuItem, org.openide.util.NbBundle.getMessage(OtherOccurrencesPanel.class, "OtherOccurrencesPanel.showCaseDetailsMenuItem.text")); // NOI18N + rightClickPopupMenu.add(showCaseDetailsMenuItem); + + org.openide.awt.Mnemonics.setLocalizedText(showCommonalityMenuItem, org.openide.util.NbBundle.getMessage(OtherOccurrencesPanel.class, "OtherOccurrencesPanel.showCommonalityMenuItem.text")); // NOI18N + rightClickPopupMenu.add(showCommonalityMenuItem); + + tableContainerPanel.setPreferredSize(new java.awt.Dimension(600, 63)); + tableContainerPanel.setRequestFocusEnabled(false); + + tablesViewerSplitPane.setDividerLocation(450); + tablesViewerSplitPane.setResizeWeight(0.75); + + caseDatasourceFileSplitPane.setDividerLocation(300); + caseDatasourceFileSplitPane.setResizeWeight(0.66); + caseDatasourceFileSplitPane.setToolTipText(org.openide.util.NbBundle.getMessage(OtherOccurrencesPanel.class, "OtherOccurrencesPanel.caseDatasourceFileSplitPane.toolTipText")); // NOI18N + + caseDatasourceSplitPane.setDividerLocation(150); + caseDatasourceSplitPane.setResizeWeight(0.5); + + caseScrollPane.setPreferredSize(new java.awt.Dimension(150, 30)); + + casesTable.setAutoCreateRowSorter(true); + casesTable.setModel(casesTableModel); + caseScrollPane.setViewportView(casesTable); + + caseDatasourceSplitPane.setLeftComponent(caseScrollPane); + + dataSourceScrollPane.setPreferredSize(new java.awt.Dimension(150, 30)); + + dataSourcesTable.setAutoCreateRowSorter(true); + dataSourcesTable.setModel(dataSourcesTableModel); + dataSourceScrollPane.setViewportView(dataSourcesTable); + + caseDatasourceSplitPane.setRightComponent(dataSourceScrollPane); + + caseDatasourceFileSplitPane.setLeftComponent(caseDatasourceSplitPane); + + filesTableScrollPane.setPreferredSize(new java.awt.Dimension(150, 30)); + + filesTable.setAutoCreateRowSorter(true); + filesTable.setModel(filesTableModel); + filesTable.setToolTipText(org.openide.util.NbBundle.getMessage(OtherOccurrencesPanel.class, "OtherOccurrencesPanel.filesTable.toolTipText")); // NOI18N + filesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + filesTableScrollPane.setViewportView(filesTable); + + caseDatasourceFileSplitPane.setRightComponent(filesTableScrollPane); + + tablesViewerSplitPane.setLeftComponent(caseDatasourceFileSplitPane); + + detailsPanelScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + detailsPanelScrollPane.setPreferredSize(new java.awt.Dimension(300, 100)); + tablesViewerSplitPane.setRightComponent(detailsPanelScrollPane); + + jPanel1.setPreferredSize(new java.awt.Dimension(576, 22)); + jPanel1.setLayout(new java.awt.GridBagLayout()); + + foundInLabel.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + org.openide.awt.Mnemonics.setLocalizedText(foundInLabel, org.openide.util.NbBundle.getMessage(OtherOccurrencesPanel.class, "OtherOccurrencesPanel.foundInLabel.text")); // NOI18N + foundInLabel.setPreferredSize(new java.awt.Dimension(400, 16)); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 3; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST; + gridBagConstraints.insets = new java.awt.Insets(2, 2, 0, 0); + jPanel1.add(foundInLabel, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(earliestCaseDate, org.openide.util.NbBundle.getMessage(OtherOccurrencesPanel.class, "OtherOccurrencesPanel.earliestCaseDate.text")); // NOI18N + earliestCaseDate.setMaximumSize(new java.awt.Dimension(200, 16)); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(0, 7, 0, 0); + jPanel1.add(earliestCaseDate, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(earliestCaseLabel, org.openide.util.NbBundle.getMessage(OtherOccurrencesPanel.class, "OtherOccurrencesPanel.earliestCaseLabel.text")); // NOI18N + earliestCaseLabel.setToolTipText(org.openide.util.NbBundle.getMessage(OtherOccurrencesPanel.class, "OtherOccurrencesPanel.earliestCaseLabel.toolTipText")); // NOI18N + earliestCaseLabel.setMaximumSize(new java.awt.Dimension(260, 16)); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + jPanel1.add(earliestCaseLabel, gridBagConstraints); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.weightx = 0.1; + jPanel1.add(filler1, gridBagConstraints); + + javax.swing.GroupLayout tableContainerPanelLayout = new javax.swing.GroupLayout(tableContainerPanel); + tableContainerPanel.setLayout(tableContainerPanelLayout); + tableContainerPanelLayout.setHorizontalGroup( + tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(tableContainerPanelLayout.createSequentialGroup() + .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(tablesViewerSplitPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 516, Short.MAX_VALUE)) + .addContainerGap()) + ); + tableContainerPanelLayout.setVerticalGroup( + tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(tableContainerPanelLayout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(tablesViewerSplitPane) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(12, 12, 12)) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 528, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 143, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + + private void rightClickPopupMenuPopupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) {//GEN-FIRST:event_rightClickPopupMenuPopupMenuWillBecomeVisible + boolean enableCentralRepoActions = false; + if (CentralRepository.isEnabled() && filesTable.getSelectedRowCount() == 1) { + int rowIndex = filesTable.getSelectedRow(); + List selectedFile = filesTableModel.getListOfNodesForFile(rowIndex); + if (!selectedFile.isEmpty() && selectedFile.get(0) instanceof NodeData) { + enableCentralRepoActions = true; + } + } + showCaseDetailsMenuItem.setVisible(enableCentralRepoActions); + showCommonalityMenuItem.setVisible(enableCentralRepoActions); + }//GEN-LAST:event_rightClickPopupMenuPopupMenuWillBecomeVisible + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JSplitPane caseDatasourceFileSplitPane; + private javax.swing.JSplitPane caseDatasourceSplitPane; + private javax.swing.JScrollPane caseScrollPane; + private javax.swing.JTable casesTable; + private javax.swing.JScrollPane dataSourceScrollPane; + private javax.swing.JTable dataSourcesTable; + private javax.swing.JScrollPane detailsPanelScrollPane; + private javax.swing.JLabel earliestCaseDate; + private javax.swing.JLabel earliestCaseLabel; + private javax.swing.JMenuItem exportToCSVMenuItem; + private javax.swing.JTable filesTable; + private javax.swing.JScrollPane filesTableScrollPane; + private javax.swing.Box.Filler filler1; + private javax.swing.JLabel foundInLabel; + private javax.swing.JPanel jPanel1; + private javax.swing.JPopupMenu rightClickPopupMenu; + private javax.swing.JMenuItem showCaseDetailsMenuItem; + private javax.swing.JMenuItem showCommonalityMenuItem; + private javax.swing.JPanel tableContainerPanel; + private javax.swing.JSplitPane tablesViewerSplitPane; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED index 2ed5a7ce87..a1f1432a70 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED @@ -12,21 +12,25 @@ CentralRepoDbChoice.PostgreSQL.Text=Custom PostgreSQL CentralRepoDbChoice.PostgreSQL_Multiuser.Text=PostgreSQL using multi-user settings CentralRepoDbChoice.Sqlite.Text=SQLite CentralRepoDbManager.connectionErrorMsg.text=Failed to connect to central repository database. -CentralRepositoryService.progressMsg.updatingSchema=Updating schema... +CentralRepositoryService.progressMsg.startingListener=Starting events listener... +CentralRepositoryService.progressMsg.updatingSchema=Checking for schema updates... +CentralRepositoryService.progressMsg.waitingForListeners=Finishing adding data to central repository database.... CentralRepositoryService.serviceName=Central Repository Service CorrelationAttributeInstance.invalidName.message=Invalid database table name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'. CorrelationAttributeInstance.nullName.message=Database name is null. CorrelationAttributeUtil.emailaddresses.text=Email Addresses -CorrelationType.DOMAIN.displayName=Domains -CorrelationType.EMAIL.displayName=Email Addresses -CorrelationType.FILES.displayName=Files +CorrelationType.DOMAIN.displayName=Domain +CorrelationType.EMAIL.displayName=Email Address +CorrelationType.FILES.displayName=File MD5 CorrelationType.ICCID.displayName=ICCID Number CorrelationType.IMEI.displayName=IMEI Number CorrelationType.IMSI.displayName=IMSI Number -CorrelationType.MAC.displayName=MAC Addresses -CorrelationType.PHONE.displayName=Phone Numbers -CorrelationType.SSID.displayName=Wireless Networks -CorrelationType.USBID.displayName=USB Devices +CorrelationType.MAC.displayName=MAC Address +CorrelationType.OS_ACCOUNT.displayName=Os Account +CorrelationType.PHONE.displayName=Phone Number +CorrelationType.PROG_NAME.displayName=Installed Program +CorrelationType.SSID.displayName=Wireless Network +CorrelationType.USBID.displayName=USB Device EamArtifactInstances.knownStatus.bad=Bad EamArtifactInstances.knownStatus.known=Known EamArtifactInstances.knownStatus.unknown=Unknown diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle_ja.properties index 7e0bda440b..1a2c7dd2e0 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle_ja.properties @@ -1,10 +1,53 @@ -#Tue Aug 18 18:09:20 UTC 2020 +#Thu Sep 30 10:26:59 UTC 2021 +AbstractSqlEamDb.badMajorSchema.message=\u30b9\u30ad\u30fc\u30de\u30d0\u30fc\u30b8\u30e7\u30f3\u306e\u5024\u304c\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093\uff08{0}\uff09-\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u304c\u7834\u640d\u3057\u3066\u3044\u307e\u3059\u3002 +AbstractSqlEamDb.badMinorSchema.message=\u30b9\u30ad\u30fc\u30de\u30de\u30a4\u30ca\u30fc\u30d0\u30fc\u30b8\u30e7\u30f3\uff08{0}\uff09\u306e\u5024\u304c\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093-\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u304c\u7834\u640d\u3057\u3066\u3044\u307e\u3059\u3002 +AbstractSqlEamDb.cannotUpgrage.message=\u73fe\u5728\u9078\u629e\u3055\u308c\u3066\u3044\u308b\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30d7\u30e9\u30c3\u30c8\u30d5\u30a9\u30fc\u30e0 "{0}"\u306f\u30a2\u30c3\u30d7\u30b0\u30ec\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093\u3002 +AbstractSqlEamDb.failedToReadMajorVersion.message=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u30b9\u30ad\u30fc\u30de\u30d0\u30fc\u30b8\u30e7\u30f3\u306e\u8aad\u53d6\u308a\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 +AbstractSqlEamDb.failedToReadMinorVersion.message=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u30b9\u30ad\u30fc\u30de\u30de\u30a4\u30ca\u30fc\u30d0\u30fc\u30b8\u30e7\u30f3\u306e\u8aad\u53d6\u308a\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 +AbstractSqlEamDb.upgradeSchema.incompatible=\u9078\u629e\u3057\u305f\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306f\u3001\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30d0\u30fc\u30b8\u30e7\u30f3\u3068\u4e92\u63db\u6027\u304c\u3042\u308a\u307e\u305b\u3093\u3002\u3053\u306e\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u4f7f\u7528\u3059\u308b\u306b\u306f\u3001\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u30a2\u30c3\u30d7\u30b0\u30ec\u30fc\u30c9\u3057\u3066\u304f\u3060\u3055\u3044\u3002 CentralRepoDbChoice.Disabled.Text=\u7121\u52b9 CentralRepoDbChoice.PostgreSQL.Text=\u30ab\u30b9\u30bf\u30e0PostgreSQL CentralRepoDbChoice.PostgreSQL_Multiuser.Text=\u30de\u30eb\u30c1\u30e6\u30fc\u30b6\u30fc\u8a2d\u5b9a\u4f7f\u7528PostgreSQL CentralRepoDbChoice.Sqlite.Text=SQLite CentralRepoDbManager.connectionErrorMsg.text=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fc\u30fb\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3078\u306e\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 -CentralRepositoryService.progressMsg.updatingSchema=\u30b9\u30ad\u30fc\u30de\u3092\u66f4\u65b0\u3057\u3066\u3044\u307e\u3059\u2026 +CentralRepositoryService.progressMsg.updatingSchema=\u30b9\u30ad\u30fc\u30de\u306e\u66f4\u65b0\u3092\u78ba\u8a8d\u4e2d.. +CentralRepositoryService.progressMsg.waitingForListeners=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u3078\u30c7\u30fc\u30bf\u306e\u8ffd\u52a0\u3092\u7d42\u4e86\u4e2d...\u3002 CentralRepositoryService.serviceName=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fc\u30fb\u30b5\u30fc\u30d3\u30b9 +CorrelationAttributeInstance.invalidName.message=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30c6\u30fc\u30d6\u30eb\u540d\u304c\u7121\u52b9\u3067\u3059\u3002 \u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30c6\u30fc\u30d6\u30eb\u540d\u306b\u306f\u5c0f\u6587\u5b57\u30a2\u30eb\u30d5\u30a1\u30d9\u30c3\u30c8\u3001\u6570\u5b57\u3001\u304a\u3088\u3073\u300c_\u300d\u306e\u307f\u304c\u4f7f\u7528\u3067\u304d\u307e\u3059\u3002 +CorrelationAttributeInstance.nullName.message=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u540d\u304cnull\u3067\u3059\u3002 CorrelationAttributeUtil.emailaddresses.text=\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9 -Persona.defaultName=\u540d\u524d\u306a\u3057 +CorrelationType.DOMAIN.displayName=\u30c9\u30e1\u30a4\u30f3 +CorrelationType.EMAIL.displayName=\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9 +CorrelationType.FILES.displayName=\u30d5\u30a1\u30a4\u30ebMD5 +CorrelationType.ICCID.displayName=ICCID\u756a\u53f7 +CorrelationType.IMEI.displayName=IMEI\u756a\u53f7 +CorrelationType.IMSI.displayName=IMSI\u756a\u53f7 +CorrelationType.MAC.displayName=MAC\u30a2\u30c9\u30ec\u30b9 +CorrelationType.OS_ACCOUNT.displayName=OS\u30a2\u30ab\u30a6\u30f3\u30c8 +CorrelationType.PHONE.displayName=\u96fb\u8a71\u756a\u53f7 +CorrelationType.PROG_NAME.displayName=\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3055\u308c\u305f\u30d7\u30ed\u30b0\u30e9\u30e0 +CorrelationType.SSID.displayName=\u7121\u7dda\u30cd\u30c3\u30c8\u30ef\u30fc\u30af +CorrelationType.USBID.displayName=USB Device +EamArtifactInstances.knownStatus.bad=\u4e0d\u53ef +EamArtifactInstances.knownStatus.known=\u65e2\u77e5 +EamArtifactInstances.knownStatus.unknown=\u4e0d\u660e +EamCase.title.caseDisplayName=\u30b1\u30fc\u30b9\u540d\uff1a +EamCase.title.caseNumber=\u30b1\u30fc\u30b9\u756a\u53f7\uff1a +EamCase.title.caseUUID=\u30b1\u30fc\u30b9UUID\uff1a +EamCase.title.creationDate=\u4f5c\u6210\u65e5\uff1a +EamCase.title.examinerEmail=\u5be9\u67fb\u5b98\u306e\u96fb\u5b50\u30e1\u30fc\u30eb\uff1a +EamCase.title.examinerName=\u5be9\u67fb\u5b98\u540d\uff1a +EamCase.title.examinerPhone=\u5be9\u67fb\u5b98\u306e\u96fb\u8a71\u756a\u53f7\uff1a +EamCase.title.notes=\u30ce\u30fc\u30c8\uff1a +EamCase.title.org=\u7d44\u7e54\uff1a +EamDbUtil.centralRepoConnectionFailed.message=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306b\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093\u3002 +EamDbUtil.centralRepoDisabled.message=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u304c\u7121\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u3059\u3002 +EamDbUtil.centralRepoUpgradeFailed.message=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u30a2\u30c3\u30d7\u30b0\u30ec\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 +EamDbUtil.exclusiveLockAquisitionFailure.message=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u6392\u4ed6\u30ed\u30c3\u30af\u304c\u3067\u304d\u307e\u305b\u3093\u3002 +Persona.defaultName=\u7121\u540d +PostgresEamDb.centralRepoDisabled.message=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30e2\u30b8\u30e5\u30fc\u30eb\u304c\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002 +PostgresEamDb.connectionFailed.message=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3078\u306e\u63a5\u7d9a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 +PostgresEamDb.multiUserLockError.message=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30ed\u30c3\u30af\u306e\u53d6\u5f97\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f +SqliteEamDb.centralRepositoryDisabled.message=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30e2\u30b8\u30e5\u30fc\u30eb\u304c\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002 +SqliteEamDb.connectionFailedMessage.message=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3078\u306e\u63a5\u7d9a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 +SqliteEamDb.databaseMissing.message=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u304c\u3042\u308a\u307e\u305b\u3093 diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccount.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccount.java index 88c74a4fe8..e0b97dee53 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccount.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccount.java @@ -40,6 +40,7 @@ public final class CentralRepoAccount { private final CentralRepoAccountType accountType; // type specific unique account identifier + // Stores what is in the DB which should have been normalized before insertion. private final String typeSpecificIdentifier; /** @@ -106,7 +107,7 @@ public final class CentralRepoAccount { } - public CentralRepoAccount(long accountId, CentralRepoAccountType accountType, String typeSpecificIdentifier) { + CentralRepoAccount(long accountId, CentralRepoAccountType accountType, String typeSpecificIdentifier) { this.accountId = accountId; this.accountType = accountType; this.typeSpecificIdentifier = typeSpecificIdentifier; @@ -115,6 +116,8 @@ public final class CentralRepoAccount { /** * Gets unique identifier (assigned by a provider) for the account. Example * includes an email address, a phone number, or a website username. + * + * This is the normalized for of the ID. * * @return type specific account id. */ @@ -336,7 +339,7 @@ public final class CentralRepoAccount { normalizedAccountIdentifier = accountIdentifier.toLowerCase(); } } catch (CorrelationAttributeNormalizationException ex) { - throw new InvalidAccountIDException("Invalid account identifier", ex); + throw new InvalidAccountIDException(String.format("Account id normaization failed, invalid account identifier %s", accountIdentifier), ex); } return normalizedAccountIdentifier; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java index d3a0e61ecf..3f1e82e464 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java @@ -24,6 +24,7 @@ import java.io.File; import java.sql.SQLException; import java.util.logging.Level; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.centralrepository.CentralRepoSettings; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; @@ -38,7 +39,7 @@ public class CentralRepoDbManager { private static final Logger logger = Logger.getLogger(CentralRepoDbManager.class.getName()); private static final String CENTRAL_REPO_DB_NAME = "central_repository"; - private static final String CENTRAL_REPOSITORY_SETTINGS_KEY = "CentralRepository"; + private static final String CENTRAL_REPOSITORY_SETTINGS_KEY = CentralRepoSettings.getInstance().getModuleSettingsKey(); private static final String DB_SELECTED_PLATFORM_KEY = "db.selectedPlatform"; private static final String DISABLED_DUE_TO_FAILURE_KEY = "disabledDueToFailure"; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUpgrader15To16.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUpgrader15To16.java new file mode 100644 index 0000000000..e19cfd8155 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUpgrader15To16.java @@ -0,0 +1,63 @@ +/* + * Central Repository + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.centralrepository.datamodel; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; + +/** + * This class updates CR schema to 1.6 + * + */ +public class CentralRepoDbUpgrader15To16 implements CentralRepoDbUpgrader { + + @Override + public void upgradeSchema(CaseDbSchemaVersionNumber dbSchemaVersion, Connection connection) throws CentralRepoException, SQLException { + + if (dbSchemaVersion.compareTo(new CaseDbSchemaVersionNumber(1, 6)) < 0) { + + try (Statement statement = connection.createStatement();) { + + CentralRepoPlatforms selectedPlatform = CentralRepoDbManager.getSavedDbChoice().getDbPlatform(); + + for (CorrelationAttributeInstance.Type type : CorrelationAttributeInstance.getDefaultCorrelationTypes()) { + String instance_type_dbname = CentralRepoDbUtil.correlationTypeToInstanceTableName(type); + + if ((type.getId() == CorrelationAttributeInstance.INSTALLED_PROGS_TYPE_ID) || + (type.getId() == CorrelationAttributeInstance.OSACCOUNT_TYPE_ID)){ + + // these are new Correlation types - new tables need to be created + statement.execute(String.format(RdbmsCentralRepoFactory.getCreateAccountInstancesTableTemplate(selectedPlatform), instance_type_dbname, instance_type_dbname)); + statement.execute(String.format(RdbmsCentralRepoFactory.getAddCaseIdIndexTemplate(), instance_type_dbname, instance_type_dbname)); + statement.execute(String.format(RdbmsCentralRepoFactory.getAddDataSourceIdIndexTemplate(), instance_type_dbname, instance_type_dbname)); + statement.execute(String.format(RdbmsCentralRepoFactory.getAddValueIndexTemplate(), instance_type_dbname, instance_type_dbname)); + statement.execute(String.format(RdbmsCentralRepoFactory.getAddKnownStatusIndexTemplate(), instance_type_dbname, instance_type_dbname)); + statement.execute(String.format(RdbmsCentralRepoFactory.getAddObjectIdIndexTemplate(), instance_type_dbname, instance_type_dbname)); + + // add new correlation type + CentralRepoDbUtil.insertCorrelationType(connection, type); + + } + } + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java index 5105aed2e9..410e78e52d 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java @@ -23,6 +23,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import javax.swing.SwingUtilities; @@ -31,6 +32,7 @@ import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import static org.sleuthkit.autopsy.centralrepository.datamodel.RdbmsCentralRepo.SOFTWARE_CR_DB_SCHEMA_VERSION; +import org.sleuthkit.autopsy.centralrepository.CentralRepoSettings; /** * @@ -38,7 +40,7 @@ import static org.sleuthkit.autopsy.centralrepository.datamodel.RdbmsCentralRepo public class CentralRepoDbUtil { private final static Logger LOGGER = Logger.getLogger(CentralRepoDbUtil.class.getName()); - private static final String CENTRAL_REPO_NAME = "CentralRepository"; + private static final String CENTRAL_REPO_NAME = CentralRepoSettings.getInstance().getModuleSettingsKey(); private static final String CENTRAL_REPO_USE_KEY = "db.useCentralRepo"; private static final String DEFAULT_ORG_NAME = "Not Specified"; @@ -129,7 +131,7 @@ public class CentralRepoDbUtil { /** * Inserts the specified correlation type into the database. * - * @param conn Open connection to use. + * @param conn Open connection to use. * @param correlationType New correlation type to add. * */ @@ -188,7 +190,6 @@ public class CentralRepoDbUtil { return true; } - /** * Get the default organization name * @@ -265,7 +266,7 @@ public class CentralRepoDbUtil { closePersonasTopComponent(); ModuleSettings.setConfigSetting(CENTRAL_REPO_NAME, CENTRAL_REPO_USE_KEY, Boolean.toString(centralRepoCheckBoxIsSelected)); } - + /** * Closes Personas top component if it exists. */ @@ -273,7 +274,7 @@ public class CentralRepoDbUtil { SwingUtilities.invokeLater(() -> { TopComponent personasWindow = WindowManager.getDefault().findTopComponent("PersonasTopComponent"); if (personasWindow != null && personasWindow.isOpened()) { - personasWindow.close(); + personasWindow.close(); } }); } @@ -349,9 +350,79 @@ public class CentralRepoDbUtil { * @return True If the specified correlation type has an account. */ static boolean correlationAttribHasAnAccount(CorrelationAttributeInstance.Type type) { - return (type.getId() >= CorrelationAttributeInstance.ADDITIONAL_TYPES_BASE_ID) - || type.getId() == CorrelationAttributeInstance.PHONE_TYPE_ID - || type.getId() == CorrelationAttributeInstance.EMAIL_TYPE_ID; + return (type.getId() >= CorrelationAttributeInstance.ADDITIONAL_TYPES_BASE_ID) + || type.getId() == CorrelationAttributeInstance.PHONE_TYPE_ID + || type.getId() == CorrelationAttributeInstance.EMAIL_TYPE_ID; } - + + /** + * Check if any of the specified attribute values in the CR have a non-empty + * and non-null comment. + * + * @param attributes The list of attributes which should have their type + * value matches checked for the presence of a comment. + * + * @return True if any of the type value matches in the CR have a comment in + * their respective comment column. False if there are no comments + * or if the CR is disabled. + * + * @throws CentralRepoException Thrown when there is an issue either getting + * the CentralRepository instance or executing + * a query. + */ + public static boolean commentExistsOnAttributes(List attributes) throws CentralRepoException { + boolean commentExists = false; + if (CentralRepository.isEnabled() && !attributes.isEmpty()) { + CentralRepository crInstance = CentralRepository.getInstance(); + //Query to check for the presence of a comment on any matching value in the specified table. + String sqlSelect = "SELECT EXISTS " + + "(SELECT 1 " + + "FROM "; + String sqlWhere = " WHERE value=? " + + "AND comment<>''" + + "LIMIT 1)"; + List params; + CommentExistsCallback commentCallback = new CommentExistsCallback(); + for (CorrelationAttributeInstance instance : attributes) { + params = new ArrayList<>(); + params.add(instance.getCorrelationValue()); + String sql = sqlSelect + CentralRepoDbUtil.correlationTypeToInstanceTableName(instance.getCorrelationType()) + sqlWhere; + crInstance.executeQuery(sql, params, commentCallback); + if (commentCallback.doesCommentExist()) { + //we are checking a binary condition so as soon as any query returns true we can stop + commentExists = true; + break; + } + } + } + return commentExists; + } + + /** + * Private implementation of the CentralRepositoryDbQueryCallback to parse + * the results of the query which checks if a type value pair has a comment. + */ + private static class CommentExistsCallback implements CentralRepositoryDbQueryCallback { + + private boolean commentExists = false; + + @Override + public void process(ResultSet rs) throws CentralRepoException, SQLException { + //there should only be 1 result here with 1 column + if (rs.next()) { + commentExists = rs.getBoolean(1); + } + } + + /** + * Identifies if a comment existed. + * + * @return True if a comment existed, false otherwise. + */ + boolean doesCommentExist() { + return commentExists; + } + + } + } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoFileInstance.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoFileInstance.java index bd243f812a..f186eccfbd 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoFileInstance.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoFileInstance.java @@ -114,7 +114,7 @@ public class CentralRepoFileInstance { /** * @param MD5Hash the MD5Hash to set */ - public void setMD5Hash(String MD5Hash) throws CorrelationAttributeNormalizationException { + public void setMD5Hash(String MD5Hash) throws CorrelationAttributeNormalizationException, CentralRepoException { this.MD5Hash = CorrelationAttributeNormalizer.normalize(CorrelationAttributeInstance.FILES_TYPE_ID, MD5Hash); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPostgresSettingsUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPostgresSettingsUtil.java index 86f929ba0e..4cce882453 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPostgresSettingsUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPostgresSettingsUtil.java @@ -25,6 +25,7 @@ package org.sleuthkit.autopsy.centralrepository.datamodel; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; +import org.sleuthkit.autopsy.centralrepository.CentralRepoSettings; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.core.UserPreferencesException; import org.sleuthkit.autopsy.coreutils.Logger; @@ -46,7 +47,7 @@ public class CentralRepoPostgresSettingsUtil { private static final String DBNAME_KEY = "db.postgresql.dbName"; private static final String HOST_KEY = "db.postgresql.host"; - private static final String MODULE_KEY = "CentralRepository"; + private static final String MODULE_KEY = CentralRepoSettings.getInstance().getModuleSettingsKey(); private static CentralRepoPostgresSettingsUtil instance = null; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java index 842c8e3f04..6127209811 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2020 Basis Technology Corp. + * Copyright 2015-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.centralrepository.datamodel; import java.sql.SQLException; import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.Set; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.casemodule.Case; @@ -66,7 +67,7 @@ public interface CentralRepository { * are no in-use connections when you call this method. * * @throws CentralRepoException if there is a problem closing the connection - * pool. + * pool. */ void shutdownConnections() throws CentralRepoException; @@ -103,20 +104,20 @@ public interface CentralRepository { /** * Add a new name/value pair in the db_info table. * - * @param name Key to set + * @param name Key to set * @param value Value to set * * @throws CentralRepoException */ - public void newDbInfo(String name, String value) throws CentralRepoException; + void newDbInfo(String name, String value) throws CentralRepoException; /** * Set the data source object id for a specific entry in the data_sources * table * - * @param rowId - the row id for the data_sources table entry + * @param rowId - the row id for the data_sources table entry * @param dataSourceObjectId - the object id for the data source from the - * caseDb + * caseDb */ void addDataSourceObjectId(int rowId, long dataSourceObjectId) throws CentralRepoException; @@ -129,17 +130,17 @@ public interface CentralRepository { * * @throws CentralRepoException */ - public String getDbInfo(String name) throws CentralRepoException; + String getDbInfo(String name) throws CentralRepoException; /** * Update the value for a name in the name/value db_info table. * - * @param name Name to find + * @param name Name to find * @param value Value to assign to name. * * @throws CentralRepoException */ - public void updateDbInfo(String name, String value) throws CentralRepoException; + void updateDbInfo(String name, String value) throws CentralRepoException; /** * Creates new Case in the database @@ -169,9 +170,11 @@ public interface CentralRepository { * user is not found in the examiner table. * * @param examinerLoginName user name to look for. + * * @return CentralRepoExaminer for the given user name. + * * @throws CentralRepoException If there is an error in looking up or - * inserting the user in the examiners table. + * inserting the user in the examiners table. */ CentralRepoExaminer getOrInsertExaminer(String examinerLoginName) throws CentralRepoException; @@ -217,7 +220,7 @@ public interface CentralRepository { * @param eamDataSource the data source to add * * @return - A CorrelationDataSource object with data source's central - * repository id + * repository id */ CorrelationDataSource newDataSource(CorrelationDataSource eamDataSource) throws CentralRepoException; @@ -246,8 +249,8 @@ public interface CentralRepository { /** * Retrieves Data Source details based on data source device ID * - * @param correlationCase the current CorrelationCase used for ensuring - * uniqueness of DataSource + * @param correlationCase the current CorrelationCase used for ensuring + * uniqueness of DataSource * @param caseDbDataSourceId the data source device ID number * * @return The data source @@ -258,8 +261,8 @@ public interface CentralRepository { * Retrieves Data Source details based on data source ID * * @param correlationCase the current CorrelationCase used for ensuring - * uniqueness of DataSource - * @param dataSourceId the data source ID number + * uniqueness of DataSource + * @param dataSourceId the data source ID number * * @return The data source */ @@ -276,7 +279,7 @@ public interface CentralRepository { * Changes the name of a data source in the DB * * @param eamDataSource The data source - * @param newName The new name + * @param newName The new name * * @throws CentralRepoException */ @@ -294,12 +297,12 @@ public interface CentralRepository { * Retrieves eamArtifact instances from the database that are associated * with the eamArtifactType and eamArtifactValues of the given eamArtifact. * - * @param aType EamArtifact.Type to search for + * @param aType EamArtifact.Type to search for * @param values The list of correlation values to get - * CorrelationAttributeInstances for + * CorrelationAttributeInstances for * * @return List of artifact instances for a given type with the specified - * values + * values * * @throws CorrelationAttributeNormalizationException * @throws CentralRepoException @@ -307,32 +310,39 @@ public interface CentralRepository { List getArtifactInstancesByTypeValues(CorrelationAttributeInstance.Type aType, List values) throws CentralRepoException, CorrelationAttributeNormalizationException; /** - * Retrieves eamArtifact instances from the database that are associated - * with the eamArtifactType and eamArtifactValue of the given eamArtifact. + * Retrieves correlation attribute instances from the central repository + * that match a given attribute type and value. * - * @param aType The type of the artifact - * @param value The correlation value + * @param type The correlation attribute type. + * @param value The correlation attribute value. * - * @return List of artifact instances for a given type/value + * @return The matching correlation attribute instances. * - * @throws CorrelationAttributeNormalizationException - * @throws CentralRepoException + * @throws CorrelationAttributeNormalizationException The exception is + * thrown if the supplied + * correlation attribute + * value cannot be + * normlaized. + * @throws CentralRepoException The exception is + * thrown if there is an + * error querying the + * central repository. */ - List getArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws CentralRepoException, CorrelationAttributeNormalizationException; + List getArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type type, String value) throws CentralRepoException, CorrelationAttributeNormalizationException; /** * Retrieves eamArtifact instances from the database that are associated * with the eamArtifactType and eamArtifactValues of the given eamArtifact * for the specified cases. * - * @param aType The type of the artifact - * @param values The list of correlation values to get - * CorrelationAttributeInstances for + * @param aType The type of the artifact + * @param values The list of correlation values to get + * CorrelationAttributeInstances for * @param caseIds The list of central repository case ids to get - * CorrelationAttributeInstances for + * CorrelationAttributeInstances for * * @return List of artifact instances for a given type with the specified - * values for the specified cases + * values for the specified cases * * @throws CorrelationAttributeNormalizationException * @throws CentralRepoException @@ -347,7 +357,7 @@ public interface CentralRepository { * @param value Value to search for * * @return Number of artifact instances having ArtifactType and - * ArtifactValue. + * ArtifactValue. */ Long getCountArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws CentralRepoException, CorrelationAttributeNormalizationException; @@ -372,6 +382,22 @@ public interface CentralRepository { */ Long getCountUniqueCaseDataSourceTuplesHavingTypeValue(CorrelationAttributeInstance.Type aType, String value) throws CentralRepoException, CorrelationAttributeNormalizationException; + /** + * Gets the count of cases that have an instance of a given correlation + * attribute. + * + * This count will ignore the specified instance of the correlation + * attribute and any other instances of this correlation attribute existing + * on the same object. + * + * @param instance The instance having its cases with other occurrences + * counted. + * + * @return Number of cases with additional instances of this + * CorrelationAttributeInstance. + */ + Long getCountCasesWithOtherInstances(CorrelationAttributeInstance instance) throws CentralRepoException, CorrelationAttributeNormalizationException; + /** * Retrieves number of data sources in the database. * @@ -386,7 +412,7 @@ public interface CentralRepository { * @param correlationDataSource Data source to search for * * @return Number of artifact instances having caseDisplayName and - * dataSource + * dataSource */ Long getCountArtifactInstancesByCaseDataSource(CorrelationDataSource correlationDataSource) throws CentralRepoException; @@ -415,7 +441,7 @@ public interface CentralRepository { * in the associated CorrelationAttribute object. * * @param eamArtifact The correlation attribute whose database instance will - * be updated. + * be updated. * * @throws CentralRepoException */ @@ -428,11 +454,11 @@ public interface CentralRepository { * Method exists to support instances added using Central Repository version * 1,1 and older * - * @param type The type of instance. - * @param correlationCase The case tied to the instance. + * @param type The type of instance. + * @param correlationCase The case tied to the instance. * @param correlationDataSource The data source tied to the instance. - * @param value The value tied to the instance. - * @param filePath The file path tied to the instance. + * @param value The value tied to the instance. + * @param filePath The file path tied to the instance. * * @return The correlation attribute if it exists; otherwise null. * @@ -445,10 +471,11 @@ public interface CentralRepository { * Find a correlation attribute in the Central Repository database given the * instance type, case, data source, object id. * - * @param type The type of instance. - * @param correlationCase The case tied to the instance. + * @param type The type of instance. + * @param correlationCase The case tied to the instance. * @param correlationDataSource The data source tied to the instance. - * @param objectID The object id of the file tied to the instance. + * @param objectID The object id of the file tied to the + * instance. * * @return The correlation attribute if it exists; otherwise null. * @@ -484,7 +511,7 @@ public interface CentralRepository { * @param value Value to search for * * @return List of cases containing this artifact with instances marked as - * bad + * bad * * @throws CentralRepoException */ @@ -498,7 +525,7 @@ public interface CentralRepository { * @param value Value to search for * * @return List of cases containing this artifact with instances marked as - * bad + * bad * * @throws CentralRepoException */ @@ -511,7 +538,7 @@ public interface CentralRepository { * * @throws CentralRepoException */ - public void deleteReferenceSet(int referenceSetID) throws CentralRepoException; + void deleteReferenceSet(int referenceSetID) throws CentralRepoException; /** * Check whether a reference set with the given parameters exists in the @@ -526,7 +553,7 @@ public interface CentralRepository { * * @throws CentralRepoException */ - public boolean referenceSetIsValid(int referenceSetID, String referenceSetName, String version) throws CentralRepoException; + boolean referenceSetIsValid(int referenceSetID, String referenceSetName, String version) throws CentralRepoException; /** * Check whether a reference set with the given name/version is in the @@ -540,7 +567,7 @@ public interface CentralRepository { * * @throws CentralRepoException */ - public boolean referenceSetExists(String referenceSetName, String version) throws CentralRepoException; + boolean referenceSetExists(String referenceSetName, String version) throws CentralRepoException; /** * Check if the given file hash is in this reference set. Only searches the @@ -553,15 +580,15 @@ public interface CentralRepository { * * @throws CentralRepoException */ - public boolean isFileHashInReferenceSet(String hash, int referenceSetID) throws CentralRepoException, CorrelationAttributeNormalizationException; + boolean isFileHashInReferenceSet(String hash, int referenceSetID) throws CentralRepoException, CorrelationAttributeNormalizationException; /** * Retrieves the given file HashHitInfo if the given file hash is in this * reference set. Only searches the reference_files table. * - * @param hash The hash to find in a search. + * @param hash The hash to find in a search. * @param referenceSetID The referenceSetID within which the file should - * exist. + * exist. * * @return The HashHitInfo if found or null if not found. * @@ -579,7 +606,7 @@ public interface CentralRepository { * * @return true if the hash is found in the reference set */ - public boolean isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) throws CentralRepoException, CorrelationAttributeNormalizationException; + boolean isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) throws CentralRepoException, CorrelationAttributeNormalizationException; /** * Is the artifact known as bad according to the reference entries? @@ -637,7 +664,7 @@ public interface CentralRepository { * Update an existing organization. * * @param updatedOrganization the values the Organization with the same ID - * will be updated to in the database. + * will be updated to in the database. * * @throws CentralRepoException */ @@ -689,7 +716,8 @@ public interface CentralRepository { * Add a new reference instance * * @param eamGlobalFileInstance The reference instance to add - * @param correlationType Correlation Type that this Reference Instance is + * @param correlationType Correlation Type that this Reference + * Instance is * * @throws CentralRepoException */ @@ -699,8 +727,8 @@ public interface CentralRepository { * Insert the bulk collection of Global File Instances * * @param globalInstances a Set of EamGlobalFileInstances to insert into the - * db. - * @param contentType the Type of the global instances + * db. + * @param contentType the Type of the global instances * * @throws CentralRepoException */ @@ -709,7 +737,7 @@ public interface CentralRepository { /** * Get all reference entries having a given correlation type and value * - * @param aType Type to use for matching + * @param aType Type to use for matching * @param aValue Value to use for matching * * @return List of all global file instances with a type and value @@ -734,7 +762,7 @@ public interface CentralRepository { * used to correlate artifacts. * * @return List of EamArtifact.Type's. If none are defined in the database, - * the default list will be returned. + * the default list will be returned. * * @throws CentralRepoException */ @@ -745,7 +773,7 @@ public interface CentralRepository { * artifacts. * * @return List of enabled EamArtifact.Type's. If none are defined in the - * database, the default list will be returned. + * database, the default list will be returned. * * @throws CentralRepoException */ @@ -756,7 +784,7 @@ public interface CentralRepository { * correlate artifacts. * * @return List of supported EamArtifact.Type's. If none are defined in the - * database, the default list will be returned. + * database, the default list will be returned. * * @throws CentralRepoException */ @@ -787,7 +815,7 @@ public interface CentralRepository { * * @throws CentralRepoException */ - public void upgradeSchema() throws CentralRepoException, SQLException, IncompatibleCentralRepoException; + void upgradeSchema() throws CentralRepoException, SQLException, IncompatibleCentralRepoException; /** * Gets an exclusive lock (if applicable). Will return the lock if @@ -798,14 +826,14 @@ public interface CentralRepository { * @return the lock, or null if locking is not supported * * @throws CentralRepoException if the coordination service is running but - * we fail to get the lock + * we fail to get the lock */ - public CoordinationService.Lock getExclusiveMultiUserDbLock() throws CentralRepoException; + CoordinationService.Lock getExclusiveMultiUserDbLock() throws CentralRepoException; /** * Process the Artifact instance in the EamDb * - * @param type EamArtifact.Type to search for + * @param type EamArtifact.Type to search for * @param instanceTableCallback callback to process the instance * * @throws CentralRepoException @@ -815,9 +843,9 @@ public interface CentralRepository { /** * Process the Artifact instance in the EamDb * - * @param type EamArtifact.Type to search for + * @param type EamArtifact.Type to search for * @param instanceTableCallback callback to process the instance - * @param whereClause query string to execute + * @param whereClause query string to execute * * @throws CentralRepoException */ @@ -826,18 +854,18 @@ public interface CentralRepository { /** * Process a SELECT query * - * @param selectClause query string to execute + * @param selectClause query string to execute * @param instanceTableCallback callback to process the instance * * @throws CentralRepoException */ - public void processSelectClause(String selectClause, InstanceTableCallback instanceTableCallback) throws CentralRepoException; + void processSelectClause(String selectClause, InstanceTableCallback instanceTableCallback) throws CentralRepoException; /** * Executes an INSERT/UPDATE/DELETE sql as a prepared statement, on the * central repository database. * - * @param sql sql to execute. + * @param sql sql to execute. * @param params List of query params to use, may be empty. * * @throws CentralRepoException If there is an error. @@ -848,8 +876,8 @@ public interface CentralRepository { * Executes a SELECT query sql as a prepared statement, on the central * repository database. * - * @param sql sql to execute. - * @param params List of query params to use, may be empty. + * @param sql sql to execute. + * @param params List of query params to use, may be empty. * @param queryCallback Query callback to handle the result of the query. * * @throws CentralRepoException If there is an error. @@ -860,10 +888,12 @@ public interface CentralRepository { * Get account type by type name. * * @param accountTypeName account type name to look for - * @return CR account type + * + * @return CR account type (if found) + * * @throws CentralRepoException */ - CentralRepoAccountType getAccountTypeByName(String accountTypeName) throws CentralRepoException; + Optional getAccountTypeByName(String accountTypeName) throws CentralRepoException; /** * Gets all account types. @@ -878,15 +908,17 @@ public interface CentralRepository { * Get an account from the accounts table matching the given type/ID. * Inserts a row if one doesn't exists. * - * @param crAccountType CR account type to look for or create + * @param crAccountType CR account type to look for or create * @param accountUniqueID type specific unique account id + * * @return CR account - * - * @throws CentralRepoException If there is an error accessing Central Repository. + * + * @throws CentralRepoException If there is an error accessing Central + * Repository. * @throws InvalidAccountIDException If the account identifier is not valid. */ CentralRepoAccount getOrCreateAccount(CentralRepoAccount.CentralRepoAccountType crAccountType, String accountUniqueID) throws InvalidAccountIDException, CentralRepoException; - + /** * Gets an account from the accounts table matching the given type/ID, if * one exists. @@ -896,7 +928,8 @@ public interface CentralRepository { * * @return CR account, if found, null otherwise. * - * @throws CentralRepoException If there is an error accessing Central Repository. + * @throws CentralRepoException If there is an error accessing Central + * Repository. * @throws InvalidAccountIDException If the account identifier is not valid. */ CentralRepoAccount getAccount(CentralRepoAccount.CentralRepoAccountType crAccountType, String accountUniqueID) throws InvalidAccountIDException, CentralRepoException; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepositoryService.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepositoryService.java old mode 100644 new mode 100755 index c60b5ab84a..e2f20524e3 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepositoryService.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepositoryService.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2018-2020 Basis Technology Corp. + * Copyright 2018-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,6 +23,7 @@ import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.appservices.AutopsyService; import org.sleuthkit.autopsy.progress.ProgressIndicator; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.eventlisteners.CaseEventListener; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.TskCoreException; @@ -33,6 +34,8 @@ import org.sleuthkit.datamodel.TskCoreException; @ServiceProvider(service = AutopsyService.class) public class CentralRepositoryService implements AutopsyService { + private CaseEventListener caseEventListener = new CaseEventListener(); + @Override @NbBundle.Messages({ "CentralRepositoryService.serviceName=Central Repository Service" @@ -42,7 +45,8 @@ public class CentralRepositoryService implements AutopsyService { } @NbBundle.Messages({ - "CentralRepositoryService.progressMsg.updatingSchema=Updating schema..." + "CentralRepositoryService.progressMsg.updatingSchema=Checking for schema updates...", + "CentralRepositoryService.progressMsg.startingListener=Starting events listener..." }) @Override public void openCaseResources(CaseContext context) throws AutopsyServiceException { @@ -53,18 +57,37 @@ public class CentralRepositoryService implements AutopsyService { ProgressIndicator progress = context.getProgressIndicator(); progress.progress(Bundle.CentralRepositoryService_progressMsg_updatingSchema()); updateSchema(); - if (context.cancelRequested()) { return; } dataUpgradeForVersion1dot2(context.getCase()); + if (context.cancelRequested()) { + return; + } + + progress.progress(Bundle.CentralRepositoryService_progressMsg_startingListener()); + caseEventListener = new CaseEventListener(); + caseEventListener.startUp(); + } + + @NbBundle.Messages({ + "CentralRepositoryService.progressMsg.waitingForListeners=Finishing adding data to central repository database...." + }) + @Override + public void closeCaseResources(CaseContext context) throws AutopsyServiceException { + ProgressIndicator progress = context.getProgressIndicator(); + progress.progress(Bundle.CentralRepositoryService_progressMsg_waitingForListeners()); + if (caseEventListener != null) { + caseEventListener.shutdown(); + } } /** - * Updates the central repository schema to the latest version. + * Updates the central repository database schema to the latest version. * - * @throws AutopsyServiceException + * @throws AutopsyServiceException The exception is thrown if there is an + * error updating the database schema. */ private void updateSchema() throws AutopsyServiceException { try { @@ -76,10 +99,11 @@ public class CentralRepositoryService implements AutopsyService { /** * Adds missing data source object IDs from data sources in this case to the - * corresponding records in the central repository. This is a data update to - * go with the v1.2 schema update. + * corresponding records in the central repository database. This is a data + * update to go with the v1.2 schema update. * - * @throws AutopsyServiceException + * @throws AutopsyServiceException The exception is thrown if there is an + * error updating the database. */ private void dataUpgradeForVersion1dot2(Case currentCase) throws AutopsyServiceException { try { diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java index 32121989e0..3b608f8f71 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java @@ -257,6 +257,8 @@ public class CorrelationAttributeInstance implements Serializable { public static final int IMEI_TYPE_ID = 7; public static final int IMSI_TYPE_ID = 8; public static final int ICCID_TYPE_ID = 9; + public static final int INSTALLED_PROGS_TYPE_ID = 10; + public static final int OSACCOUNT_TYPE_ID = 11; // An offset to assign Ids for additional correlation types. public static final int ADDITIONAL_TYPES_BASE_ID = 1000; @@ -267,19 +269,21 @@ public class CorrelationAttributeInstance implements Serializable { * @throws CentralRepoException if the Type's dbTableName has invalid * characters/format */ - @Messages({"CorrelationType.FILES.displayName=Files", - "CorrelationType.DOMAIN.displayName=Domains", - "CorrelationType.EMAIL.displayName=Email Addresses", - "CorrelationType.PHONE.displayName=Phone Numbers", - "CorrelationType.USBID.displayName=USB Devices", - "CorrelationType.SSID.displayName=Wireless Networks", - "CorrelationType.MAC.displayName=MAC Addresses", + @Messages({"CorrelationType.FILES.displayName=File MD5", + "CorrelationType.DOMAIN.displayName=Domain", + "CorrelationType.EMAIL.displayName=Email Address", + "CorrelationType.PHONE.displayName=Phone Number", + "CorrelationType.USBID.displayName=USB Device", + "CorrelationType.SSID.displayName=Wireless Network", + "CorrelationType.MAC.displayName=MAC Address", "CorrelationType.IMEI.displayName=IMEI Number", "CorrelationType.IMSI.displayName=IMSI Number", - "CorrelationType.ICCID.displayName=ICCID Number"}) + "CorrelationType.PROG_NAME.displayName=Installed Program", + "CorrelationType.ICCID.displayName=ICCID Number", + "CorrelationType.OS_ACCOUNT.displayName=Os Account"}) public static List getDefaultCorrelationTypes() throws CentralRepoException { List defaultCorrelationTypes = new ArrayList<>(); - + defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(FILES_TYPE_ID, Bundle.CorrelationType_FILES_displayName(), "file", true, true)); // NON-NLS defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(DOMAIN_TYPE_ID, Bundle.CorrelationType_DOMAIN_displayName(), "domain", true, true)); // NON-NLS defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(EMAIL_TYPE_ID, Bundle.CorrelationType_EMAIL_displayName(), "email_address", true, true)); // NON-NLS @@ -290,6 +294,8 @@ public class CorrelationAttributeInstance implements Serializable { defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(IMEI_TYPE_ID, Bundle.CorrelationType_IMEI_displayName(), "imei_number", true, true)); //NON-NLS defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(IMSI_TYPE_ID, Bundle.CorrelationType_IMSI_displayName(), "imsi_number", true, true)); //NON-NLS defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(ICCID_TYPE_ID, Bundle.CorrelationType_ICCID_displayName(), "iccid_number", true, true)); //NON-NLS + defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(INSTALLED_PROGS_TYPE_ID, Bundle.CorrelationType_PROG_NAME_displayName(), "installed_programs", true, true)); //NON-NLS + defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(OSACCOUNT_TYPE_ID, Bundle.CorrelationType_OS_ACCOUNT_displayName(), "os_accounts", true, true)); //NON-NLS // Create Correlation Types for Accounts. int correlationTypeId = ADDITIONAL_TYPES_BASE_ID; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java index 51b9b80f84..ade8910d2f 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java @@ -27,6 +27,7 @@ import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.commons.validator.routines.DomainValidator; import org.apache.commons.validator.routines.EmailValidator; +import org.sleuthkit.autopsy.coreutils.NetworkUtils; /** * Provides functions for normalizing data by attribute type before insertion or @@ -46,13 +47,13 @@ final public class CorrelationAttributeNormalizer { * * @return normalized data */ - public static String normalize(CorrelationAttributeInstance.Type attributeType, String data) throws CorrelationAttributeNormalizationException { + public static String normalize(CorrelationAttributeInstance.Type attributeType, String data) throws CorrelationAttributeNormalizationException, CentralRepoException { if (attributeType == null) { - throw new CorrelationAttributeNormalizationException("Attribute type was null."); + throw new CentralRepoException("Attribute type was null."); } if (data == null) { - throw new CorrelationAttributeNormalizationException("Correlation value was null."); + throw new CentralRepoException("Correlation value was null."); } String trimmedData = data.trim(); @@ -80,22 +81,18 @@ final public class CorrelationAttributeNormalizer { return normalizeIccid(trimmedData); default: - try { - // If the atttribute is not one of the above - // but is one of the other default correlation types, then let the data go as is - List defaultCorrelationTypes = CorrelationAttributeInstance.getDefaultCorrelationTypes(); - for (CorrelationAttributeInstance.Type defaultCorrelationType : defaultCorrelationTypes) { - if (defaultCorrelationType.getId() == attributeType.getId()) { - return trimmedData; - } + // If the atttribute is not one of the above + // but is one of the other default correlation types, then let the data go as is + List defaultCorrelationTypes = CorrelationAttributeInstance.getDefaultCorrelationTypes(); + for (CorrelationAttributeInstance.Type defaultCorrelationType : defaultCorrelationTypes) { + if (defaultCorrelationType.getId() == attributeType.getId()) { + return trimmedData; } - final String errorMessage = String.format( - "Validator function not found for attribute type: %s", - attributeType.getDisplayName()); - throw new CorrelationAttributeNormalizationException(errorMessage); - } catch (CentralRepoException ex) { - throw new CorrelationAttributeNormalizationException("Failed to get default correlation types.", ex); } + final String errorMessage = String.format( + "Validator function not found for attribute type: %s", + attributeType.getDisplayName()); + throw new CentralRepoException(errorMessage); } } @@ -108,19 +105,15 @@ final public class CorrelationAttributeNormalizer { * * @return normalized data */ - public static String normalize(int attributeTypeId, String data) throws CorrelationAttributeNormalizationException { - try { - List defaultTypes = CorrelationAttributeInstance.getDefaultCorrelationTypes(); - Optional typeOption = defaultTypes.stream().filter(attributeType -> attributeType.getId() == attributeTypeId).findAny(); + public static String normalize(int attributeTypeId, String data) throws CorrelationAttributeNormalizationException, CentralRepoException { + List defaultTypes = CorrelationAttributeInstance.getDefaultCorrelationTypes(); + Optional typeOption = defaultTypes.stream().filter(attributeType -> attributeType.getId() == attributeTypeId).findAny(); - if (typeOption.isPresent()) { - CorrelationAttributeInstance.Type type = typeOption.get(); - return CorrelationAttributeNormalizer.normalize(type, data); - } else { - throw new CorrelationAttributeNormalizationException(String.format("Given attributeTypeId did not correspond to any known Attribute: %s", attributeTypeId)); - } - } catch (CentralRepoException ex) { - throw new CorrelationAttributeNormalizationException(ex); + if (typeOption.isPresent()) { + CorrelationAttributeInstance.Type type = typeOption.get(); + return CorrelationAttributeNormalizer.normalize(type, data); + } else { + throw new CentralRepoException(String.format("Given attributeTypeId did not correspond to any known Attribute: %s", attributeTypeId)); } } @@ -144,11 +137,11 @@ final public class CorrelationAttributeNormalizer { private static String normalizeDomain(String data) throws CorrelationAttributeNormalizationException { DomainValidator validator = DomainValidator.getInstance(true); if (validator.isValid(data)) { - return data.toLowerCase(); + return NetworkUtils.extractDomain(data.toLowerCase()); } else { final String validIpAddressRegex = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"; if (data.matches(validIpAddressRegex)) { - return data; + return NetworkUtils.extractDomain(data); } else { throw new CorrelationAttributeNormalizationException(String.format("Data was expected to be a valid domain: %s", data)); } @@ -281,7 +274,7 @@ final public class CorrelationAttributeNormalizer { if (imsiWithoutSeperators.matches(validImsiRegex)) { return imsiWithoutSeperators; } else { - throw new CorrelationAttributeNormalizationException("Data provided was not a valid Imsi. : " + data); + throw new CorrelationAttributeNormalizationException("Data provided was not a valid IMSI. : " + data); } } @@ -305,7 +298,7 @@ final public class CorrelationAttributeNormalizer { if (macWithoutSeperators.matches(validMacRegex)) { return macWithoutSeperators; } else { - throw new CorrelationAttributeNormalizationException("Data provided was not a valid Imsi. : " + data); + throw new CorrelationAttributeNormalizationException("Data provided was not a valid MAC address. : " + data); } } @@ -334,7 +327,7 @@ final public class CorrelationAttributeNormalizer { if (imeiWithoutSeperators.matches(validImeiRegex)) { return imeiWithoutSeperators; } else { - throw new CorrelationAttributeNormalizationException("Data provided was not a valid Imsi. : " + data); + throw new CorrelationAttributeNormalizationException("Data provided was not a valid IMEI. : " + data); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java index 88f76c393c..68da375531 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2017-2020 Basis Technology Corp. + * Copyright 2017-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,8 +20,10 @@ package org.sleuthkit.autopsy.centralrepository.datamodel; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.logging.Level; import org.openide.util.NbBundle.Messages; @@ -31,12 +33,17 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount.Cent import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.AnalysisResult; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.DataArtifact; import org.sleuthkit.datamodel.HashUtility; import org.sleuthkit.datamodel.InvalidAccountIDException; +import org.sleuthkit.datamodel.OsAccount; +import org.sleuthkit.datamodel.OsAccountInstance; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -49,13 +56,22 @@ public class CorrelationAttributeUtil { private static final Logger logger = Logger.getLogger(CorrelationAttributeUtil.class.getName()); private static final List domainsToSkip = Arrays.asList("localhost", "127.0.0.1"); + // artifact ids that specifically have a TSK_DOMAIN attribute that should be handled by CR + private static final Set DOMAIN_ARTIFACT_TYPE_IDS = new HashSet<>(Arrays.asList( + ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID(), + ARTIFACT_TYPE.TSK_WEB_COOKIE.getTypeID(), + ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID(), + ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID(), + ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID() + )); + /** * Gets a string that is expected to be the same string that is stored in * the correlation_types table in the central repository as the display name * for the email address correlation attribute type. This string is * duplicated in the CorrelationAttributeInstance class. * - * TODO (Jira-6088): We should not have multiple deifnitions of this string. + * TODO (Jira-6088): We should not have multiple definitions of this string. * * @return The display name of the email address correlation attribute type. */ @@ -64,218 +80,372 @@ public class CorrelationAttributeUtil { return Bundle.CorrelationAttributeUtil_emailaddresses_text(); } - // Defines which artifact types act as the sources for CR data. - // Most notably, does not include KEYWORD HIT, CALLLOGS, MESSAGES, CONTACTS - // TSK_INTERESTING_ARTIFACT_HIT (See JIRA-6129 for more details on the - // interesting artifact hit). - - // IMPORTANT: This set should be updated for new artifacts types that need to - // be inserted into the CR. - private static final Set SOURCE_TYPES_FOR_CR_INSERT = new HashSet() {{ - add(ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID()); - add(ARTIFACT_TYPE.TSK_WEB_COOKIE.getTypeID()); - add(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID()); - add(ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID()); - add(ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID()); - add(ARTIFACT_TYPE.TSK_WIFI_NETWORK.getTypeID()); - add(ARTIFACT_TYPE.TSK_WIFI_NETWORK_ADAPTER.getTypeID()); - add(ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING.getTypeID()); - add(ARTIFACT_TYPE.TSK_BLUETOOTH_ADAPTER.getTypeID()); - add(ARTIFACT_TYPE.TSK_DEVICE_INFO.getTypeID()); - add(ARTIFACT_TYPE.TSK_SIM_ATTACHED.getTypeID()); - add(ARTIFACT_TYPE.TSK_WEB_FORM_ADDRESS.getTypeID()); - add(ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()); - }}; - - /** - * Makes zero to many correlation attribute instances from the attributes of - * artifacts that have correlatable data. The intention of this method is to - * use the results to save to the CR, not to correlate with them. If you - * want to correlate, please use makeCorrAttrsForCorrelation. An artifact that can - * have correlatable data != An artifact that should be the source of data - * in the CR, so results may be un-necessarily incomplete. - * - * @param artifact An artifact. - * - * @return A list, possibly empty, of correlation attribute instances for - * the artifact. - */ - public static List makeCorrAttrsToSave(BlackboardArtifact artifact) { - if(SOURCE_TYPES_FOR_CR_INSERT.contains(artifact.getArtifactTypeID())) { - // Restrict the correlation attributes to use for saving. - // The artifacts which are suitable for saving are a subset of the - // artifacts that are suitable for correlating. - return makeCorrAttrsForCorrelation(artifact); + public static List makeCorrAttrsToSave(DataArtifact artifact) { + int artifactTypeID = artifact.getArtifactTypeID(); + //The account fields in these types are expected to be saved in a TSK_ACCOUNT artifact, which will be processed + if (artifactTypeID == ARTIFACT_TYPE.TSK_CALLLOG.getTypeID() + || artifactTypeID == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() + || artifactTypeID == ARTIFACT_TYPE.TSK_CONTACT.getTypeID()) { + return Collections.emptyList(); } - // Return an empty collection. - return new ArrayList<>(); + return CorrelationAttributeUtil.makeCorrAttrsForSearch(artifact); } /** * Makes zero to many correlation attribute instances from the attributes of - * artifacts that have correlatable data. The intention of this method is to - * use the results to correlate with, not to save. If you - * want to save, please use makeCorrAttrsToSave. An artifact that can - * have correlatable data != An artifact that should be the source of data - * in the CR, so results may be too lenient. + * abstract file objects that have correlatable data. The intention of this + * method is to use the results to save to the CR, not to correlate with + * them. If you want to correlate, please use makeCorrAttrsForSearch. An + * artifact that can have correlatable data != An artifact that should be + * the source of data in the CR, so results may be un-necessarily + * incomplete. + * + * @param file A AbstractFile object. + * + * @return A list, possibly empty, of correlation attribute instances for + * the content. + */ + public static List makeCorrAttrsToSave(AbstractFile file) { + return makeCorrAttrsForSearch(file); + } + + public static List makeCorrAttrsToSave(AnalysisResult file) { + return Collections.emptyList(); + } + + /** + * Gets the correlation attributes for an OS account instance represented as + * an OS account plus a data source. + * + * @param account The OS account. + * @param dataSource The data source. + * + * @return The correlation attributes. + */ + public static List makeCorrAttrsToSave(OsAccount account, Content dataSource) { + List correlationAttrs = new ArrayList<>(); + if (CentralRepository.isEnabled()) { + Optional accountAddr = account.getAddr(); + if (accountAddr.isPresent() && !isSystemOsAccount(accountAddr.get())) { + try { + CorrelationCase correlationCase = CentralRepository.getInstance().getCase(Case.getCurrentCaseThrows()); + CorrelationAttributeInstance correlationAttributeInstance = new CorrelationAttributeInstance( + CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.OSACCOUNT_TYPE_ID), + accountAddr.get(), + correlationCase, + CorrelationDataSource.fromTSKDataSource(correlationCase, dataSource), + dataSource.getName(), + "", + TskData.FileKnown.KNOWN, + account.getId()); + correlationAttrs.add(correlationAttributeInstance); + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, String.format("Error querying central repository for OS account '%s'", accountAddr.get()), ex); //NON-NLS + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, String.format("Error getting current case for OS account '%s'", accountAddr.get()), ex); //NON-NLS + } catch (CorrelationAttributeNormalizationException ex) { + logger.log(Level.WARNING, String.format("Error normalizing correlation attribute for OS account '%s': %s", accountAddr.get(), ex.getMessage())); //NON-NLS + } + } + } + return correlationAttrs; + } + + /** + * Determines whether or not a given OS account address is a system account + * address. + * + * @param accountAddr The OS account address. + * + * @return True or false. + */ + private static boolean isSystemOsAccount(String accountAddr) { + return accountAddr.equals("S-1-5-18") || accountAddr.equals("S-1-5-19") || accountAddr.equals("S-1-5-20"); + } + + /** + * Makes zero to many correlation attribute instances from the attributes of + * AnalysisResult that have correlatable data. The intention of this method + * is to use the results to correlate with, not to save. If you want to + * save, please use makeCorrAttrsToSave. An artifact that can have data to + * search for != An artifact that should be the source of data in the CR, so + * results may be too lenient. * * IMPORTANT: The correlation attribute instances are NOT added to the * central repository by this method. * - * TODO (Jira-6088): The methods in this low-level, utility class should - * throw exceptions instead of logging them. The reason for this is that the - * clients of the utility class, not the utility class itself, should be in - * charge of error handling policy, per the Autopsy Coding Standard. Note - * that clients of several of these methods currently cannot determine - * whether receiving a null return value is an error or not, plus null - * checking is easy to forget, while catching exceptions is enforced. + * JIRA-TODO (Jira-6088) * - * @param artifact An artifact. + * @param analysisResult An AnalysisResult object. * * @return A list, possibly empty, of correlation attribute instances for - * the artifact. + * the AnalysisResult. + * + * @SuppressWarnings("deprecation") - we need to support already existing + * interesting file and artifact hits. */ - public static List makeCorrAttrsForCorrelation(BlackboardArtifact artifact) { + @SuppressWarnings("deprecation") + public static List makeCorrAttrsForSearch(AnalysisResult analysisResult) { List correlationAttrs = new ArrayList<>(); - try { - BlackboardArtifact sourceArtifact = getCorrAttrSourceArtifact(artifact); - if (sourceArtifact != null) { - int artifactTypeID = sourceArtifact.getArtifactTypeID(); - if (artifactTypeID == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) { - BlackboardAttribute setNameAttr = sourceArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME)); - if (setNameAttr != null && CorrelationAttributeUtil.getEmailAddressAttrDisplayName().equals(setNameAttr.getValueString())) { - makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD, CorrelationAttributeInstance.EMAIL_TYPE_ID); + + if (CentralRepository.isEnabled()) { + try { + int artifactTypeID = analysisResult.getArtifactTypeID(); + if (artifactTypeID == ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() || artifactTypeID == ARTIFACT_TYPE.TSK_INTERESTING_ITEM.getTypeID()) { + //because this attribute retrieval is only occuring when the analysis result is an interesting artifact hit + //and only one attribute is being retrieved the analysis result's own get attribute method can be used efficently + BlackboardAttribute assocArtifactAttr = analysisResult.getAttribute(BlackboardAttribute.Type.TSK_ASSOCIATED_ARTIFACT); + if (assocArtifactAttr != null) { + BlackboardArtifact sourceArtifact = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifact(assocArtifactAttr.getValueLong()); + if (sourceArtifact instanceof DataArtifact) { + correlationAttrs.addAll((CorrelationAttributeUtil.makeCorrAttrsForSearch((DataArtifact) sourceArtifact))); + } else if (sourceArtifact instanceof AnalysisResult) { + correlationAttrs.addAll((CorrelationAttributeUtil.makeCorrAttrsForSearch((AnalysisResult) sourceArtifact))); + } else { + String sourceName = sourceArtifact != null ? "SourceArtifact display name: " + sourceArtifact.getDisplayName() : "SourceArtifact was null"; + logger.log(Level.SEVERE, "Source artifact found through TSK_ASSOCIATED_ARTIFACT attribute was not a DataArtifact or " + + "an Analysis Result. AssociateArtifactAttr Value: {0} {1}", + new Object[]{assocArtifactAttr.getValueString(), sourceName}); + } } - } else if (artifactTypeID == ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID() - || artifactTypeID == ARTIFACT_TYPE.TSK_WEB_COOKIE.getTypeID() - || artifactTypeID == ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() - || artifactTypeID == ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID()) { - BlackboardAttribute domainAttr = sourceArtifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DOMAIN)); + } else { + if (artifactTypeID == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) { + //because this attribute retrieval is only occuring when the analysis result is a keyword hit + //and only one attribute is being retrieved the analysis result's own get attribute method can be used efficently + BlackboardAttribute setNameAttr = analysisResult.getAttribute(BlackboardAttribute.Type.TSK_SET_NAME); + if (setNameAttr != null && CorrelationAttributeUtil.getEmailAddressAttrDisplayName().equals(setNameAttr.getValueString())) { + /* + * We no longer save email instances from keyword + * search hits in the central repository, but we + * still want to be able to search for email address + * instances in the CR when we are presenting email + * address keyword hits. Also note that we may want + * to correlate on the source Content (parent) of + * the keyword hit as well, so we do not return at + * this point. + */ + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(analysisResult, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD, CorrelationAttributeInstance.EMAIL_TYPE_ID, analysisResult.getAttributes())); + } + + } + + Content parent = analysisResult.getParent(); + if (parent instanceof AbstractFile) { + correlationAttrs.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AbstractFile) parent)); + } else if (parent instanceof AnalysisResult) { + correlationAttrs.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AnalysisResult) parent)); + } else if (parent instanceof DataArtifact) { + correlationAttrs.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((DataArtifact) parent)); + } else if (parent instanceof OsAccount) { + for (OsAccountInstance osAccountInst : ((OsAccount) parent).getOsAccountInstances()) { + if (osAccountInst.getDataSource().equals(analysisResult.getDataSource())) { + /** + * We only need to add correlation attributes + * for a single OsAccountInstance. because we + * are generally searching based on type and + * value. + * + * However data source can also be used, so we + * would like to choose an OsAccountInstance + * which is associated with the same data source + * as the provided AnalysisResult for those use + * cases. For example to get the count of cases + * with other instances. + */ + correlationAttrs.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch(osAccountInst)); + break; + } + } + } + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get information regarding correlation attributes in regards to either the provided AnalysisResult, it's associated artifact, or it's parent.", ex); + } catch (NoCurrentCaseException ex) { + logger.log(Level.WARNING, "Attempted to retrieve correlation attributes for search with no currently open case.", ex); + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, "Failed to get correlation type from central repository.", ex); + } + } + return correlationAttrs; + } + + /** + * Makes zero to many correlation attribute instances from the attributes of + * a DataArtifact that have correlatable data. The intention of this method + * is to use the results to correlate with, not to save. If you want to + * save, please use makeCorrAttrsToSave. An artifact that can have data to + * search for != An artifact that should be the source of data in the CR, so + * results may be too lenient. + * + * IMPORTANT: The correlation attribute instances are NOT added to the + * central repository by this method. + * + * JIRA-TODO (Jira-6088) + * + * @param artifact A DataArtifact object. + * + * @return A list, possibly empty, of correlation attribute instances for + * the DataArtifact. + */ + public static List makeCorrAttrsForSearch(DataArtifact artifact) { + List correlationAttrs = new ArrayList<>(); + + if (CentralRepository.isEnabled()) { + try { + List attributes = artifact.getAttributes(); + + int artifactTypeID = artifact.getArtifactTypeID(); + if (DOMAIN_ARTIFACT_TYPE_IDS.contains(artifactTypeID)) { + BlackboardAttribute domainAttr = getAttribute(attributes, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DOMAIN)); if ((domainAttr != null) - && ! domainsToSkip.contains(domainAttr.getValueString())) { - makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, CorrelationAttributeInstance.DOMAIN_TYPE_ID); + && !domainsToSkip.contains(domainAttr.getValueString())) { + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, CorrelationAttributeInstance.DOMAIN_TYPE_ID, attributes)); } } else if (artifactTypeID == ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID()) { - makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID, CorrelationAttributeInstance.USBID_TYPE_ID); - makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID); - + // prefetch all the information as we will be calling makeCorrAttrFromArtifactAttr() multiple times + Content sourceContent = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(artifact.getObjectID()); + Content dataSource = sourceContent.getDataSource(); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID, CorrelationAttributeInstance.USBID_TYPE_ID, + attributes, sourceContent, dataSource)); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID, + attributes, sourceContent, dataSource)); } else if (artifactTypeID == ARTIFACT_TYPE.TSK_WIFI_NETWORK.getTypeID()) { - makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SSID, CorrelationAttributeInstance.SSID_TYPE_ID); - + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SSID, CorrelationAttributeInstance.SSID_TYPE_ID, attributes)); } else if (artifactTypeID == ARTIFACT_TYPE.TSK_WIFI_NETWORK_ADAPTER.getTypeID() || artifactTypeID == ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING.getTypeID() || artifactTypeID == ARTIFACT_TYPE.TSK_BLUETOOTH_ADAPTER.getTypeID()) { - makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID); - + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID, attributes)); } else if (artifactTypeID == ARTIFACT_TYPE.TSK_DEVICE_INFO.getTypeID()) { - makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMEI, CorrelationAttributeInstance.IMEI_TYPE_ID); - makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID); - makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID); + // prefetch all the information as we will be calling makeCorrAttrFromArtifactAttr() multiple times + Content sourceContent = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(artifact.getObjectID()); + Content dataSource = sourceContent.getDataSource(); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMEI, CorrelationAttributeInstance.IMEI_TYPE_ID, + attributes, sourceContent, dataSource)); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID, + attributes, sourceContent, dataSource)); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID, + attributes, sourceContent, dataSource)); } else if (artifactTypeID == ARTIFACT_TYPE.TSK_SIM_ATTACHED.getTypeID()) { - makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID); - makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID); + // prefetch all the information as we will be calling makeCorrAttrFromArtifactAttr() multiple times + Content sourceContent = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(artifact.getObjectID()); + Content dataSource = sourceContent.getDataSource(); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID, + attributes, sourceContent, dataSource)); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID, + attributes, sourceContent, dataSource)); } else if (artifactTypeID == ARTIFACT_TYPE.TSK_WEB_FORM_ADDRESS.getTypeID()) { - makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, CorrelationAttributeInstance.PHONE_TYPE_ID); - makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL, CorrelationAttributeInstance.EMAIL_TYPE_ID); + // prefetch all the information as we will be calling makeCorrAttrFromArtifactAttr() multiple times + Content sourceContent = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(artifact.getObjectID()); + Content dataSource = sourceContent.getDataSource(); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, CorrelationAttributeInstance.PHONE_TYPE_ID, + attributes, sourceContent, dataSource)); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL, CorrelationAttributeInstance.EMAIL_TYPE_ID, + attributes, sourceContent, dataSource)); } else if (artifactTypeID == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) { - makeCorrAttrFromAcctArtifact(correlationAttrs, sourceArtifact); + makeCorrAttrFromAcctArtifact(correlationAttrs, artifact, attributes); + } else if (artifactTypeID == ARTIFACT_TYPE.TSK_INSTALLED_PROG.getTypeID()) { + BlackboardAttribute setNameAttr = getAttribute(attributes, new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH)); + String pathAttrString = null; + if (setNameAttr != null) { + pathAttrString = setNameAttr.getValueString(); + } + if (pathAttrString != null && !pathAttrString.isEmpty()) { + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH, CorrelationAttributeInstance.INSTALLED_PROGS_TYPE_ID, attributes)); + } else { + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, CorrelationAttributeInstance.INSTALLED_PROGS_TYPE_ID, attributes)); + } } else if (artifactTypeID == ARTIFACT_TYPE.TSK_CONTACT.getTypeID() || artifactTypeID == ARTIFACT_TYPE.TSK_CALLLOG.getTypeID() || artifactTypeID == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) { - makeCorrAttrsFromCommunicationArtifacts(correlationAttrs, sourceArtifact); + correlationAttrs.addAll(makeCorrAttrsFromCommunicationArtifact(artifact, attributes)); } + } catch (CorrelationAttributeNormalizationException ex) { + logger.log(Level.WARNING, String.format("Error normalizing correlation attribute (%s): %s", artifact, ex.getMessage())); // NON-NLS + return correlationAttrs; + } catch (InvalidAccountIDException ex) { + logger.log(Level.WARNING, String.format("Invalid account identifier (artifactID: %d): %s", artifact.getId(), ex.getMessage())); // NON-NLS + return correlationAttrs; + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", artifact), ex); // NON-NLS + return correlationAttrs; + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error getting querying case database (%s)", artifact), ex); // NON-NLS + return correlationAttrs; + } catch (NoCurrentCaseException ex) { + logger.log(Level.WARNING, "Error getting current case", ex); // NON-NLS + return correlationAttrs; } - } catch (CorrelationAttributeNormalizationException ex) { - logger.log(Level.WARNING, String.format("Error normalizing correlation attribute (%s)", artifact), ex); // NON-NLS - return correlationAttrs; - } - catch (InvalidAccountIDException ex) { - logger.log(Level.WARNING, String.format("Invalid account identifier (artifactID: %d)", artifact.getId())); // NON-NLS - return correlationAttrs; - } - catch (CentralRepoException ex) { - logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", artifact), ex); // NON-NLS - return correlationAttrs; - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, String.format("Error getting querying case database (%s)", artifact), ex); // NON-NLS - return correlationAttrs; - } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Error getting current case", ex); // NON-NLS - return correlationAttrs; } return correlationAttrs; } + /** + * Gets a specific attribute from a list of attributes. + * + * @param attributes List of attributes + * @param attributeType Attribute type of interest + * + * @return Attribute of interest, null if not found. + * + * @throws TskCoreException + */ + private static BlackboardAttribute getAttribute(List attributes, BlackboardAttribute.Type attributeType) throws TskCoreException { + for (BlackboardAttribute attribute : attributes) { + if (attribute.getAttributeType().equals(attributeType)) { + return attribute; + } + } + return null; + } + /** * Makes a correlation attribute instance from a phone number attribute of * an artifact. * - * @param corrAttrInstances Correlation attributes will be added to this. - * @param artifact An artifact with a phone number attribute. + * @param artifact An artifact with a phone number attribute. + * @param attributes List of attributes. * - * @throws TskCoreException If there is an error querying the case database. - * @throws CentralRepoException If there is an error querying the central - * repository. + * @throws TskCoreException If there is an error + * querying the case + * database. + * @throws CentralRepoException If there is an error + * querying the central + * repository. * @throws CorrelationAttributeNormalizationException If there is an error - * in normalizing the attribute. + * in normalizing the + * attribute. */ - private static void makeCorrAttrsFromCommunicationArtifacts(List corrAttrInstances, BlackboardArtifact artifact) throws TskCoreException, CentralRepoException, CorrelationAttributeNormalizationException { - CorrelationAttributeInstance corrAttr = null; + private static List makeCorrAttrsFromCommunicationArtifact(BlackboardArtifact artifact, + List attributes) throws TskCoreException, CentralRepoException, CorrelationAttributeNormalizationException { /* * Extract the phone number from the artifact attribute. */ String value = null; - if (null != artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER))) { - value = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)).getValueString(); - } else if (null != artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM))) { - value = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM)).getValueString(); - } else if (null != artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO))) { - value = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO)).getValueString(); + if (null != getAttribute(attributes, new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER))) { + value = getAttribute(attributes, new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)).getValueString(); + } else if (null != getAttribute(attributes, new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM))) { + value = getAttribute(attributes, new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM)).getValueString(); + } else if (null != getAttribute(attributes, new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO))) { + value = getAttribute(attributes, new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO)).getValueString(); } - /* * Normalize the phone number. */ + List corrAttrInstances = new ArrayList<>(); if (value != null && CorrelationAttributeNormalizer.isValidPhoneNumber(value)) { - value = CorrelationAttributeNormalizer.normalizePhone(value); - corrAttr = makeCorrAttr(artifact, CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.PHONE_TYPE_ID), value); + CorrelationAttributeInstance corrAttr = makeCorrAttr(artifact, CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.PHONE_TYPE_ID), value); if (corrAttr != null) { corrAttrInstances.add(corrAttr); } } - } - - /** - * Gets the associated artifact of a "meta-artifact" such as an interesting - * artifact hit artifact. - * - * @param artifact An artifact. - * - * @return The associated artifact if the input artifact is a - * "meta-artifact", otherwise the input artifact. - * - * @throws NoCurrentCaseException If there is no open case. - * @throws TskCoreException If there is an error querying thew case - * database. - */ - private static BlackboardArtifact getCorrAttrSourceArtifact(BlackboardArtifact artifact) throws NoCurrentCaseException, TskCoreException { - BlackboardArtifact sourceArtifact = null; - if (BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() == artifact.getArtifactTypeID()) { - BlackboardAttribute assocArtifactAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT)); - if (assocArtifactAttr != null) { - sourceArtifact = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifact(assocArtifactAttr.getValueLong()); - } - } else { - sourceArtifact = artifact; - } - return sourceArtifact; + return corrAttrInstances; } /** @@ -288,30 +458,35 @@ public class CorrelationAttributeUtil { * * @param corrAttrInstances A list of correlation attribute instances. * @param acctArtifact An account artifact. + * @param attributes List of attributes. * * @return The correlation attribute instance. */ - private static void makeCorrAttrFromAcctArtifact(List corrAttrInstances, BlackboardArtifact acctArtifact) throws InvalidAccountIDException, TskCoreException, CentralRepoException { + private static void makeCorrAttrFromAcctArtifact(List corrAttrInstances, BlackboardArtifact acctArtifact, List attributes) throws InvalidAccountIDException, TskCoreException, CentralRepoException { // Get the account type from the artifact - BlackboardAttribute accountTypeAttribute = acctArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE)); + BlackboardAttribute accountTypeAttribute = getAttribute(attributes, new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE)); String accountTypeStr = accountTypeAttribute.getValueString(); - + // @@TODO Vik-6136: CR currently does not know of custom account types. // Ensure there is a predefined account type for this account. Account.Type predefinedAccountType = Account.Type.PREDEFINED_ACCOUNT_TYPES.stream().filter(type -> type.getTypeName().equalsIgnoreCase(accountTypeStr)).findAny().orElse(null); - + // do not create any correlation attribute instance for a Device account if (Account.Type.DEVICE.getTypeName().equalsIgnoreCase(accountTypeStr) == false && predefinedAccountType != null) { // Get the corresponding CentralRepoAccountType from the database. - CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(accountTypeStr); + Optional optCrAccountType = CentralRepository.getInstance().getAccountTypeByName(accountTypeStr); + if (!optCrAccountType.isPresent()) { + return; + } + CentralRepoAccountType crAccountType = optCrAccountType.get(); int corrTypeId = crAccountType.getCorrelationTypeId(); CorrelationAttributeInstance.Type corrType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId); // Get the account identifier - BlackboardAttribute accountIdAttribute = acctArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID)); + BlackboardAttribute accountIdAttribute = getAttribute(attributes, new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID)); String accountIdStr = accountIdAttribute.getValueString(); // add/get the account and get its accountId. @@ -330,30 +505,56 @@ public class CorrelationAttributeUtil { * Makes a correlation attribute instance from a specified attribute of an * artifact. The correlation attribute instance is added to an input list. * - * @param corrAttrInstances A list of correlation attribute instances. - * @param artifact An artifact. - * @param artAttrType The type of the atrribute of the artifact that - * is to be made into a correlatin attribute - * instance. - * @param typeId The type ID for the desired correlation - * attribute instance. + * @param artifact An artifact. + * @param artAttrType The type of the attribute of the artifact that is to + * be made into a correlation attribute instance. + * @param typeId The type ID for the desired correlation attribute + * instance. + * @param attributes List of attributes. + * @param sourceContent The source content object. + * @param dataSource The data source content object. * * @throws CentralRepoException If there is an error querying the central * repository. * @throws TskCoreException If there is an error querying the case * database. */ - private static void makeCorrAttrFromArtifactAttr(List corrAttrInstances, BlackboardArtifact artifact, ATTRIBUTE_TYPE artAttrType, int typeId) throws CentralRepoException, TskCoreException { - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(artAttrType)); + private static List makeCorrAttrFromArtifactAttr(BlackboardArtifact artifact, ATTRIBUTE_TYPE artAttrType, int typeId, + List attributes, Content sourceContent, Content dataSource) throws CentralRepoException, TskCoreException { + List corrAttrInstances = new ArrayList<>(); + BlackboardAttribute attribute = getAttribute(attributes, new BlackboardAttribute.Type(artAttrType)); if (attribute != null) { String value = attribute.getValueString(); if ((null != value) && (value.isEmpty() == false)) { - CorrelationAttributeInstance inst = makeCorrAttr(artifact, CentralRepository.getInstance().getCorrelationTypeById(typeId), value); + CorrelationAttributeInstance inst = makeCorrAttr(artifact, CentralRepository.getInstance().getCorrelationTypeById(typeId), value, sourceContent, dataSource); if (inst != null) { corrAttrInstances.add(inst); } } } + return corrAttrInstances; + } + + /** + * Makes a correlation attribute instance from a specified attribute of an + * artifact. The correlation attribute instance is added to an input list. + * + * @param artifact An artifact. + * @param artAttrType The type of the attribute of the artifact that is to + * be made into a correlation attribute instance. + * @param typeId The type ID for the desired correlation attribute + * instance. + * @param attributes List of attributes. + * + * @throws CentralRepoException If there is an error querying the central + * repository. + * @throws TskCoreException If there is an error querying the case + * database. + */ + private static List makeCorrAttrFromArtifactAttr(BlackboardArtifact artifact, ATTRIBUTE_TYPE artAttrType, int typeId, + List attributes) throws CentralRepoException, TskCoreException { + + return makeCorrAttrFromArtifactAttr(artifact, artAttrType, typeId, attributes, null, null); } /** @@ -374,25 +575,75 @@ public class CorrelationAttributeUtil { * @return The correlation attribute instance or null, if an error occurred. */ private static CorrelationAttributeInstance makeCorrAttr(BlackboardArtifact artifact, CorrelationAttributeInstance.Type correlationType, String value) { + return makeCorrAttr(artifact, correlationType, value, null, null); + } + + /** + * Makes a correlation attribute instance of a given type from an artifact. + * + * @param artifact The artifact. + * @param correlationType the correlation attribute type. + * @param value The correlation attribute value. + * @param sourceContent The source content object. + * @param dataSource The data source content object. + * + * TODO (Jira-6088): The methods in this low-level, utility class should + * throw exceptions instead of logging them. The reason for this is that the + * clients of the utility class, not the utility class itself, should be in + * charge of error handling policy, per the Autopsy Coding Standard. Note + * that clients of several of these methods currently cannot determine + * whether receiving a null return value is an error or not, plus null + * checking is easy to forget, while catching exceptions is enforced. + * + * @return The correlation attribute instance or null, if an error occurred. + */ + private static CorrelationAttributeInstance makeCorrAttr(BlackboardArtifact artifact, CorrelationAttributeInstance.Type correlationType, String value, + Content sourceContent, Content dataSource) { + Content srcContent = sourceContent; + Content dataSrc = dataSource; try { - Case currentCase = Case.getCurrentCaseThrows(); - AbstractFile bbSourceFile = currentCase.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); - if (null == bbSourceFile) { - logger.log(Level.SEVERE, "Error creating artifact instance. Abstract File was null."); // NON-NLS + if (srcContent == null) { + srcContent = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(artifact.getObjectID()); + } + if (null == srcContent) { + logger.log(Level.SEVERE, "Error creating artifact instance of type {0}. Failed to load content with ID: {1} associated with artifact with ID: {2}", + new Object[]{correlationType.getDisplayName(), artifact.getObjectID(), artifact.getId()}); // NON-NLS + return null; + } + if (dataSrc == null) { + dataSrc = srcContent.getDataSource(); + } + if (dataSrc == null) { + logger.log(Level.SEVERE, "Error creating artifact instance of type {0}. Failed to load data source for content with ID: {1}", + new Object[]{correlationType.getDisplayName(), artifact.getObjectID()}); // NON-NLS return null; } CorrelationCase correlationCase = CentralRepository.getInstance().getCase(Case.getCurrentCaseThrows()); - return new CorrelationAttributeInstance( - correlationType, - value, - correlationCase, - CorrelationDataSource.fromTSKDataSource(correlationCase, bbSourceFile.getDataSource()), - bbSourceFile.getParentPath() + bbSourceFile.getName(), - "", - TskData.FileKnown.UNKNOWN, - bbSourceFile.getId()); + if (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INSTALLED_PROG.getTypeID() + || !(srcContent instanceof AbstractFile)) { + return new CorrelationAttributeInstance( + correlationType, + value, + correlationCase, + CorrelationDataSource.fromTSKDataSource(correlationCase, dataSrc), + srcContent.getName(), + "", + TskData.FileKnown.UNKNOWN, + srcContent.getId()); + } else { + AbstractFile bbSourceFile = (AbstractFile) srcContent; + return new CorrelationAttributeInstance( + correlationType, + value, + correlationCase, + CorrelationDataSource.fromTSKDataSource(correlationCase, dataSrc), + bbSourceFile.getParentPath() + bbSourceFile.getName(), + "", + TskData.FileKnown.UNKNOWN, + bbSourceFile.getId()); + } } catch (TskCoreException ex) { logger.log(Level.SEVERE, String.format("Error getting querying case database (%s)", artifact), ex); // NON-NLS return null; @@ -400,16 +651,20 @@ public class CorrelationAttributeUtil { logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", artifact), ex); // NON-NLS return null; } catch (CorrelationAttributeNormalizationException ex) { - logger.log(Level.WARNING, String.format("Error creating correlation attribute instance (%s)", artifact), ex); // NON-NLS + logger.log(Level.WARNING, String.format("Error creating correlation attribute instance (%s): %s", artifact, ex.getMessage())); // NON-NLS return null; } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Error getting current case", ex); // NON-NLS + logger.log(Level.WARNING, "Error getting current case", ex); // NON-NLS return null; } } + // @@@ BC: This seems like it should go into a DB-specific class because it is + // much different from the other methods in this class. It is going to the DB for data. /** - * Gets the correlation attribute instance for a file. + * Gets the correlation attribute instance for a file. This method goes to + * the CR to get an actual instance. It does not simply package the data + * from file into a generic instance object. * * @param file The file. * @@ -426,7 +681,7 @@ public class CorrelationAttributeUtil { */ public static CorrelationAttributeInstance getCorrAttrForFile(AbstractFile file) { - if (!isSupportedAbstractFileType(file)) { + if (!CentralRepository.isEnabled() || !isSupportedAbstractFileType(file)) { return null; } @@ -449,7 +704,7 @@ public class CorrelationAttributeUtil { logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", file), ex); // NON-NLS return null; } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Error getting current case", ex); // NON-NLS + logger.log(Level.WARNING, "Error getting current case", ex); // NON-NLS return null; } @@ -460,7 +715,7 @@ public class CorrelationAttributeUtil { logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", file), ex); // NON-NLS return null; } catch (CorrelationAttributeNormalizationException ex) { - logger.log(Level.WARNING, String.format("Error creating correlation attribute instance (%s)", file), ex); // NON-NLS + logger.log(Level.WARNING, String.format("Error creating correlation attribute instance (%s): %s", file, ex.getMessage())); // NON-NLS return null; } @@ -478,7 +733,7 @@ public class CorrelationAttributeUtil { logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", file), ex); // NON-NLS return null; } catch (CorrelationAttributeNormalizationException ex) { - logger.log(Level.WARNING, String.format("Error creating correlation attribute instance (%s)", file), ex); // NON-NLS + logger.log(Level.WARNING, String.format("Error creating correlation attribute instance (%s): %s", file, ex.getMessage())); // NON-NLS return null; } } @@ -487,7 +742,8 @@ public class CorrelationAttributeUtil { } /** - * Makes a correlation attribute instance for a file. + * Makes a correlation attribute instance for a file. Will include the + * specific object ID. * * IMPORTANT: The correlation attribute instance is NOT added to the central * repository by this method. @@ -502,25 +758,26 @@ public class CorrelationAttributeUtil { * * @param file The file. * - * @return The correlation attribute instance or null, if an error occurred. + * @return The correlation attribute instance in a list, or an empty list if + * an error occurred. */ - public static CorrelationAttributeInstance makeCorrAttrFromFile(AbstractFile file) { - - if (!isSupportedAbstractFileType(file)) { - return null; + public static List makeCorrAttrsForSearch(AbstractFile file) { + List fileTypeList = new ArrayList<>(); // will be an empty or single element list as was decided in 7852 + if (!isSupportedAbstractFileType(file) || !CentralRepository.isEnabled()) { + return fileTypeList; } // We need a hash to make the correlation artifact instance. String md5 = file.getMd5Hash(); if (md5 == null || md5.isEmpty() || HashUtility.isNoDataMd5(md5)) { - return null; + return fileTypeList; } try { CorrelationAttributeInstance.Type filesType = CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID); CorrelationCase correlationCase = CentralRepository.getInstance().getCase(Case.getCurrentCaseThrows()); - return new CorrelationAttributeInstance( + fileTypeList.add(new CorrelationAttributeInstance( filesType, file.getMd5Hash(), correlationCase, @@ -528,21 +785,17 @@ public class CorrelationAttributeUtil { file.getParentPath() + file.getName(), "", TskData.FileKnown.UNKNOWN, - file.getId()); - + file.getId())); } catch (TskCoreException ex) { logger.log(Level.SEVERE, String.format("Error querying case database (%s)", file), ex); // NON-NLS - return null; } catch (CentralRepoException ex) { logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", file), ex); // NON-NLS - return null; } catch (CorrelationAttributeNormalizationException ex) { - logger.log(Level.WARNING, String.format("Error creating correlation attribute instance (%s)", file), ex); // NON-NLS - return null; + logger.log(Level.WARNING, String.format("Error creating correlation attribute instance (%s): %s", file, ex.getMessage())); // NON-NLS } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Error getting current case", ex); // NON-NLS - return null; + logger.log(Level.WARNING, "Error getting current case", ex); // NON-NLS } + return fileTypeList; } /** @@ -577,6 +830,18 @@ public class CorrelationAttributeUtil { } } + public static List makeCorrAttrsForSearch(OsAccountInstance osAccountInst) { + List correlationAttrs = new ArrayList<>(); + if (CentralRepository.isEnabled() && osAccountInst != null) { + try { + correlationAttrs.addAll(makeCorrAttrsToSave(osAccountInst.getOsAccount(), osAccountInst.getDataSource())); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error getting OS account from OS account instance '%s'", osAccountInst), ex); + } + } + return correlationAttrs; + } + /** * Prevent instantiation of this utility class. */ diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java index 92c23d77c8..cc8f739892 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2019 Basis Technology Corp. + * Copyright 2015-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -98,32 +98,33 @@ public class CorrelationDataSource implements Serializable { } /** - * Create a CorrelationDataSource object from a TSK Content object. This - * will add it to the central repository. + * Creates a central repository data source object from a case database data + * source. If the data source is not already present in the central + * repository, it is added. * - * @param correlationCase the current CorrelationCase used for ensuring - * uniqueness of DataSource - * @param dataSource the sleuthkit datasource that is being added to - * the central repository + * @param correlationCase The central repository case associated with the + * data aosurce. + * @param dataSource The case database data source. * - * @return + * @return The central repository data source. * - * @throws CentralRepoException + * @throws CentralRepoException This exception is thrown if there is an + * error creating the central repository data + * source. */ public static CorrelationDataSource fromTSKDataSource(CorrelationCase correlationCase, Content dataSource) throws CentralRepoException { + if (!CentralRepository.isEnabled()) { + throw new CentralRepoException(String.format("Central repository is not enabled, cannot create central repository data source for '%s'", dataSource)); + } + Case curCase; try { curCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { - throw new CentralRepoException("Autopsy case is closed"); - } - - CorrelationDataSource correlationDataSource = null; - boolean useCR = CentralRepository.isEnabled(); - if (useCR) { - correlationDataSource = CentralRepository.getInstance().getDataSource(correlationCase, dataSource.getId()); + throw new CentralRepoException("Error getting current case", ex); } + CorrelationDataSource correlationDataSource = CentralRepository.getInstance().getDataSource(correlationCase, dataSource.getId()); if (correlationDataSource == null) { String deviceId; String md5 = null; @@ -131,7 +132,7 @@ public class CorrelationDataSource implements Serializable { String sha256 = null; try { deviceId = curCase.getSleuthkitCase().getDataSource(dataSource.getId()).getDeviceId(); - + if (dataSource instanceof Image) { Image image = (Image) dataSource; md5 = image.getMd5(); @@ -139,15 +140,12 @@ public class CorrelationDataSource implements Serializable { sha256 = image.getSha256(); } } catch (TskDataException | TskCoreException ex) { - throw new CentralRepoException("Error getting data source info: " + ex.getMessage()); + throw new CentralRepoException("Error getting data source info from case database", ex); } - correlationDataSource = new CorrelationDataSource(correlationCase, deviceId, dataSource.getName(), dataSource.getId(), md5, sha1, sha256); - if (useCR) { - //add the correlation data source to the central repository and fill in the Central repository data source id in the object - correlationDataSource = CentralRepository.getInstance().newDataSource(correlationDataSource); - } + correlationDataSource = CentralRepository.getInstance().newDataSource(correlationDataSource); } + return correlationDataSource; } @@ -205,66 +203,68 @@ public class CorrelationDataSource implements Serializable { public String getName() { return name; } - + /** * @return the MD5 hash value */ public String getMd5() { return (md5Hash == null ? "" : md5Hash); } - + /** - * Set the MD5 hash value and persist to the Central Repository if available. - * + * Set the MD5 hash value and persist to the Central Repository if + * available. + * * @param md5Hash The MD5 hash value. + * * @throws CentralRepoException If there's an issue updating the Central - Repository. + * Repository. */ public void setMd5(String md5Hash) throws CentralRepoException { this.md5Hash = md5Hash; - + if (dataSourceObjectID != -1) { CentralRepository.getInstance().updateDataSourceMd5Hash(this); } } - + /** * @return the SHA-1 hash value */ public String getSha1() { return (sha1Hash == null ? "" : sha1Hash); } - + /** * Set the SHA-1 hash value and persist to the Central Repository if * available. - * + * * @param sha1Hash The SHA-1 hash value. */ public void setSha1(String sha1Hash) throws CentralRepoException { this.sha1Hash = sha1Hash; - + if (dataSourceObjectID != -1) { CentralRepository.getInstance().updateDataSourceSha1Hash(this); } } - + /** * @return the SHA-256 hash value */ public String getSha256() { return (sha256Hash == null ? "" : sha256Hash); } - + /** * Set the SHA-256 hash value and persist to the Central Repository if * available. - * + * * @param sha256Hash The SHA-256 hash value. */ public void setSha256(String sha256Hash) throws CentralRepoException { this.sha256Hash = sha256Hash; - + if (dataSourceObjectID != -1) { CentralRepository.getInstance().updateDataSourceSha256Hash(this); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java index 917c988e1c..59b0ebe627 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java @@ -26,8 +26,10 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.TskCoreException; /** * This class represents an association between a Persona and an Account. @@ -46,7 +48,7 @@ public class PersonaAccount { private final long dateAdded; private final CentralRepoExaminer examiner; - public PersonaAccount(long id, Persona persona, CentralRepoAccount account, String justification, Persona.Confidence confidence, long dateAdded, CentralRepoExaminer examiner) { + private PersonaAccount(long id, Persona persona, CentralRepoAccount account, String justification, Persona.Confidence confidence, long dateAdded, CentralRepoExaminer examiner) { this.id = id; this.persona = persona; this.account = account; @@ -206,10 +208,15 @@ public class PersonaAccount { ); // create account - CentralRepoAccount.CentralRepoAccountType crAccountType = getCRInstance().getAccountTypeByName(rs.getString("type_name")); + String accountTypeName = rs.getString("type_name"); + Optional optCrAccountType = getCRInstance().getAccountTypeByName(accountTypeName); + if (! optCrAccountType.isPresent()) { + // The CR account can not be null, so throw an exception + throw new CentralRepoException("Account type with name '" + accountTypeName + "' not found in Central Repository"); + } CentralRepoAccount account = new CentralRepoAccount( rs.getInt("account_id"), - crAccountType, + optCrAccountType.get(), rs.getString("account_unique_identifier")); // create persona account @@ -326,12 +333,12 @@ public class PersonaAccount { */ public static Collection getPersonaAccountsForAccount(Account account) throws CentralRepoException { String querySQL = PERSONA_ACCOUNTS_QUERY_CLAUSE - + " WHERE LOWER(accounts.account_unique_identifier) LIKE LOWER(?)" + + " WHERE LOWER(accounts.account_unique_identifier) = LOWER(?)" + " AND type_name = ?" + " AND personas.status_id != ?"; List queryParams = new ArrayList<>(); - queryParams.add("%" + account.getTypeSpecificID() + "%"); // substring match + queryParams.add(account.getTypeSpecificID()); // substring match queryParams.add(account.getAccountType().getTypeName()); queryParams.add(Persona.PersonaStatus.DELETED.getStatusId()); @@ -389,10 +396,15 @@ public class PersonaAccount { while (rs.next()) { // create account - CentralRepoAccount.CentralRepoAccountType crAccountType = getCRInstance().getAccountTypeByName(rs.getString("type_name")); + String accountTypeName = rs.getString("type_name"); + Optional optCrAccountType = getCRInstance().getAccountTypeByName(accountTypeName); + if (! optCrAccountType.isPresent()) { + // The CR account can not be null, so throw an exception + throw new CentralRepoException("Account type with name '" + accountTypeName + "' not found in Central Repository"); + } CentralRepoAccount account = new CentralRepoAccount( rs.getInt("account_id"), - crAccountType, + optCrAccountType.get(), rs.getString("account_unique_identifier")); accountsList.add(account); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepo.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepo.java index e72bfa9359..9e079c69db 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepo.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepo.java @@ -205,7 +205,7 @@ final class PostgresCentralRepo extends RdbmsCentralRepo { } } } - + @Override protected String getConflictClause() { return CONFLICT_CLAUSE; @@ -269,4 +269,5 @@ final class PostgresCentralRepo extends RdbmsCentralRepo { return columnExists; } + } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java index b48797e3fc..ee017de842 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2020 Basis Technology Corp. + * Copyright 2015-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,6 +37,7 @@ import java.time.LocalDate; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -68,7 +69,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { static final String SCHEMA_MINOR_VERSION_KEY = "SCHEMA_MINOR_VERSION"; static final String CREATION_SCHEMA_MAJOR_VERSION_KEY = "CREATION_SCHEMA_MAJOR_VERSION"; static final String CREATION_SCHEMA_MINOR_VERSION_KEY = "CREATION_SCHEMA_MINOR_VERSION"; - static final CaseDbSchemaVersionNumber SOFTWARE_CR_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 5); + static final CaseDbSchemaVersionNumber SOFTWARE_CR_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 6); protected final List defaultCorrelationTypes; @@ -78,7 +79,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { private static final int CASE_CACHE_TIMEOUT = 5; private static final int DATA_SOURCE_CACHE_TIMEOUT = 5; private static final int ACCOUNTS_CACHE_TIMEOUT = 5; - private static final Cache accountTypesCache = CacheBuilder.newBuilder().build(); + private static final Cache> accountTypesCache = CacheBuilder.newBuilder().build(); private static final Cache, CentralRepoAccount> accountsCache = CacheBuilder.newBuilder() .expireAfterWrite(ACCOUNTS_CACHE_TIMEOUT, TimeUnit.MINUTES). build(); @@ -211,8 +212,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting value for name.", ex); } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } @@ -504,8 +505,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting case details.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } @@ -564,8 +565,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting case details.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } @@ -601,8 +602,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting all cases.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } @@ -786,8 +787,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting data source.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } @@ -850,8 +851,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting data source.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } @@ -884,8 +885,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting all data sources.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } @@ -1089,7 +1090,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { String insertSQL; switch (CentralRepoDbManager.getSavedDbChoice().getDbPlatform()) { case POSTGRESQL: - insertSQL = "INSERT INTO accounts (account_type_id, account_unique_identifier) VALUES (?, ?) " + getConflictClause(); //NON-NLS + insertSQL = "INSERT INTO accounts (account_type_id, account_unique_identifier) VALUES (?, ?) " + getConflictClause(); //NON-NLS break; case SQLITE: insertSQL = "INSERT OR IGNORE INTO accounts (account_type_id, account_unique_identifier) VALUES (?, ?) "; //NON-NLS @@ -1097,7 +1098,6 @@ abstract class RdbmsCentralRepo implements CentralRepository { default: throw new CentralRepoException(String.format("Cannot add account to currently selected CR database platform %s", CentralRepoDbManager.getSavedDbChoice().getDbPlatform())); //NON-NLS } - try (Connection connection = connect(); PreparedStatement preparedStatement = connection.prepareStatement(insertSQL);) { @@ -1115,7 +1115,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { } @Override - public CentralRepoAccountType getAccountTypeByName(String accountTypeName) throws CentralRepoException { + public Optional getAccountTypeByName(String accountTypeName) throws CentralRepoException { try { return accountTypesCache.get(accountTypeName, () -> getCRAccountTypeFromDb(accountTypeName)); } catch (CacheLoader.InvalidCacheLoadException | ExecutionException ex) { @@ -1155,7 +1155,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { * * @throws CentralRepoException */ - private CentralRepoAccountType getCRAccountTypeFromDb(String accountTypeName) throws CentralRepoException { + private Optional getCRAccountTypeFromDb(String accountTypeName) throws CentralRepoException { String sql = "SELECT * FROM account_types WHERE type_name = ?"; try (Connection conn = connect(); @@ -1166,10 +1166,11 @@ abstract class RdbmsCentralRepo implements CentralRepository { if (resultSet.next()) { Account.Type acctType = new Account.Type(accountTypeName, resultSet.getString("display_name")); CentralRepoAccountType crAccountType = new CentralRepoAccountType(resultSet.getInt("id"), acctType, resultSet.getInt("correlation_type_id")); - accountTypesCache.put(accountTypeName, crAccountType); - return crAccountType; + accountTypesCache.put(accountTypeName, Optional.of(crAccountType)); + return Optional.of(crAccountType); } else { - throw new CentralRepoException("Failed to find entry for account type = " + accountTypeName); + accountTypesCache.put(accountTypeName, Optional.empty()); + return Optional.empty(); } } } catch (SQLException ex) { @@ -1191,7 +1192,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { * @return CentralRepoAccount for the give type/id. May return null if not * found. * - * @throws CentralRepoException If there is an error accessing Central Repository. + * @throws CentralRepoException If there is an error accessing Central + * Repository. * @throws InvalidAccountIDException If the account identifier is not valid. */ @Override @@ -1274,7 +1276,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { @Override public List getArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws CentralRepoException, CorrelationAttributeNormalizationException { if (value == null) { - throw new CorrelationAttributeNormalizationException("Cannot get artifact instances for null value"); + throw new CentralRepoException("Cannot get artifact instances for null value"); } return getArtifactInstancesByTypeValues(aType, Arrays.asList(value)); } @@ -1282,24 +1284,24 @@ abstract class RdbmsCentralRepo implements CentralRepository { @Override public List getArtifactInstancesByTypeValues(CorrelationAttributeInstance.Type aType, List values) throws CentralRepoException, CorrelationAttributeNormalizationException { if (aType == null) { - throw new CorrelationAttributeNormalizationException("Cannot get artifact instances for null type"); + throw new CentralRepoException("Cannot get artifact instances for null type"); } if (values == null || values.isEmpty()) { - throw new CorrelationAttributeNormalizationException("Cannot get artifact instances without specified values"); + throw new CentralRepoException("Cannot get artifact instances without specified values"); } - return getArtifactInstances(prepareGetInstancesSql(aType, values), aType); + return getCorrAttrInstances(prepareGetInstancesSql(aType, values), aType); } @Override public List getArtifactInstancesByTypeValuesAndCases(CorrelationAttributeInstance.Type aType, List values, List caseIds) throws CentralRepoException, CorrelationAttributeNormalizationException { if (aType == null) { - throw new CorrelationAttributeNormalizationException("Cannot get artifact instances for null type"); + throw new CentralRepoException("Cannot get artifact instances for null type"); } if (values == null || values.isEmpty()) { - throw new CorrelationAttributeNormalizationException("Cannot get artifact instances without specified values"); + throw new CentralRepoException("Cannot get artifact instances without specified values"); } if (caseIds == null || caseIds.isEmpty()) { - throw new CorrelationAttributeNormalizationException("Cannot get artifact instances without specified cases"); + throw new CentralRepoException("Cannot get artifact instances without specified cases"); } String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(aType); String sql @@ -1310,7 +1312,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { inValuesBuilder.append(sql); inValuesBuilder.append(caseIds.stream().map(String::valueOf).collect(Collectors.joining("', '"))); inValuesBuilder.append("')"); - return getArtifactInstances(inValuesBuilder.toString(), aType); + return getCorrAttrInstances(inValuesBuilder.toString(), aType); } /** @@ -1325,7 +1327,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { * * @throws CorrelationAttributeNormalizationException */ - private String prepareGetInstancesSql(CorrelationAttributeInstance.Type aType, List values) throws CorrelationAttributeNormalizationException { + private String prepareGetInstancesSql(CorrelationAttributeInstance.Type aType, List values) throws CorrelationAttributeNormalizationException, CentralRepoException { String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(aType); String sql = "SELECT " @@ -1359,40 +1361,44 @@ abstract class RdbmsCentralRepo implements CentralRepository { } /** - * Retrieves eamArtifact instances from the database that are associated - * with the eamArtifactType and eamArtifactValues of the given eamArtifact. + * Retrieves correlation attribute instances from the central repository + * that match a given SQL query and correlation attribute type. * - * @param aType The type of the artifact - * @param values The list of correlation values to get - * CorrelationAttributeInstances for + * @param sql The SQL query. + * @param attrType The correlation attribute type. * - * @return List of artifact instances for a given type with the specified - * values + * @return The correlation attribute instanes. * - * @throws CorrelationAttributeNormalizationException - * @throws CentralRepoException + * @throws CorrelationAttributeNormalizationException The exception is + * thrown if the supplied + * correlation attribute + * value cannot be + * normlaized. + * @throws CentralRepoException The exception is + * thrown if there is an + * error querying the + * central repository. */ - private List getArtifactInstances(String sql, CorrelationAttributeInstance.Type aType) throws CorrelationAttributeNormalizationException, CentralRepoException { + private List getCorrAttrInstances(String sql, CorrelationAttributeInstance.Type attrType) throws CorrelationAttributeNormalizationException, CentralRepoException { + List corrAttrs = new ArrayList<>(); Connection conn = connect(); - List artifactInstances = new ArrayList<>(); - CorrelationAttributeInstance artifactInstance; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { preparedStatement = conn.prepareStatement(sql); resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { - artifactInstance = getEamArtifactInstanceFromResultSet(resultSet, aType); - artifactInstances.add(artifactInstance); + CorrelationAttributeInstance corrAttr = getCorrAttrFromResultSet(resultSet, attrType); + corrAttrs.add(corrAttr); } } catch (SQLException ex) { - throw new CentralRepoException("Error getting artifact instances by artifactType and artifactValue.", ex); // NON-NLS + throw new CentralRepoException(String.format("Error getting correlation attributes using query %s", sql), ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } - return artifactInstances; + return corrAttrs; } /** @@ -1430,8 +1436,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting count of artifact instances by artifactType and artifactValue.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } @@ -1486,14 +1492,68 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error counting unique caseDisplayName/dataSource tuples having artifactType and artifactValue.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } return instanceCount; } + @Override + public Long getCountCasesWithOtherInstances(CorrelationAttributeInstance instance) throws CentralRepoException, CorrelationAttributeNormalizationException { + Long instanceCount = 0L; + if (instance != null) { + Long sourceObjID = instance.getFileObjectId(); + //The CorrelationAttributeInstance will have a CorrelationCase, however that correlation case's ID will be null if the case is not in the CR. + int correlationCaseId = instance.getCorrelationCase().getID(); + int correlationDataSourceId = instance.getCorrelationDataSource().getID(); + String normalizedValue = CorrelationAttributeNormalizer.normalize(instance.getCorrelationType(), instance.getCorrelationValue()); + Connection conn = connect(); + PreparedStatement preparedStatement = null; + String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(instance.getCorrelationType()); + ResultSet resultSet = null; + + try { + if (correlationCaseId > 0 && sourceObjID != null && correlationDataSourceId > 0) { + //The CorrelationCase is in the Central repository. + String sql + = "SELECT count(*) FROM (SELECT DISTINCT case_id FROM " //Get distinct cases with a matching value in the corresponding table from the central repository. + + tableName + + " WHERE value=? AND NOT (file_obj_id=? AND case_id=? AND data_source_id=?)) AS " //Check the file_obj_id AND case_id to ensure we ignore the currently selected instance. + + tableName + + "_other_case_count"; + preparedStatement = conn.prepareStatement(sql); + preparedStatement.setString(1, normalizedValue); + preparedStatement.setLong(2, sourceObjID); + preparedStatement.setInt(3, correlationCaseId); + preparedStatement.setInt(4, correlationDataSourceId); + } else { + //The CorrelationCase is NOT in the central repository. + String sql + = "SELECT count(*) FROM (SELECT DISTINCT case_id FROM " //Get all distinct cases with a matching value in the corresponding table from the central repository. + + tableName + + " WHERE value=? AS " + + tableName + + "_other_case_count"; + preparedStatement = conn.prepareStatement(sql); + preparedStatement.setString(1, normalizedValue); + } + resultSet = preparedStatement.executeQuery(); + resultSet.next(); + instanceCount = resultSet.getLong(1); + } catch (SQLException ex) { + throw new CentralRepoException("Error counting unique caseDisplayName/dataSource tuples having artifactType and artifactValue.", ex); // NON-NLS + } finally { + CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); + CentralRepoDbUtil.closeConnection(conn); + } + } + + return instanceCount; + } + @Override public Long getCountUniqueDataSources() throws CentralRepoException { Connection conn = connect(); @@ -1512,8 +1572,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error counting data sources.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } @@ -1559,8 +1619,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error counting artifact instances by caseName/dataSource.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } @@ -1886,8 +1946,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting notable artifact instances.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } @@ -1957,8 +2017,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting notable artifact instances.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } @@ -2044,9 +2104,9 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting/setting artifact instance knownStatus=" + knownStatus.getName(), ex); // NON-NLS } finally { + CentralRepoDbUtil.closeResultSet(resultSet); CentralRepoDbUtil.closeStatement(preparedUpdate); CentralRepoDbUtil.closeStatement(preparedQuery); - CentralRepoDbUtil.closeResultSet(resultSet); CentralRepoDbUtil.closeConnection(conn); } } @@ -2086,8 +2146,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting count of notable artifact instances.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } @@ -2141,8 +2201,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting notable artifact instances.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } @@ -2193,8 +2253,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting notable artifact instances.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } @@ -2337,8 +2397,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error determining if value (" + normalizeValued + ") is in reference set " + referenceSetID, ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } } @@ -2376,8 +2436,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error determining if value (" + normalizeValued + ") is in reference set " + referenceSetID, ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } @@ -2420,8 +2480,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error determining if artifact is notable by reference.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } @@ -2461,8 +2521,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting all artifact instances from instances table", ex); } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } } @@ -2507,8 +2567,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting all artifact instances from instances table", ex); } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } } @@ -2546,21 +2606,21 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error running query", ex); } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } } @Override public void executeCommand(String sql, List params) throws CentralRepoException { - + try (Connection conn = connect();) { - + PreparedStatement preparedStatement = conn.prepareStatement(sql); - - // Fill in the params - if (params != null) { + + // Fill in the params + if (params != null) { int paramIndex = 1; for (Object param : params) { preparedStatement.setObject(paramIndex, param); @@ -2580,10 +2640,9 @@ abstract class RdbmsCentralRepo implements CentralRepository { throw new CentralRepoException("Query callback is null"); } - - try ( Connection conn = connect();) { - PreparedStatement preparedStatement = conn.prepareStatement(sql); - + try (Connection conn = connect();) { + PreparedStatement preparedStatement = conn.prepareStatement(sql); + // fill in the params if (params != null) { int paramIndex = 1; @@ -2598,7 +2657,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { } } catch (SQLException ex) { throw new CentralRepoException(String.format("Error executing prepared statement for SQL query %s", sql), ex); - } + } } @Override @@ -2633,8 +2692,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error inserting new organization.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(generatedKeys); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } } @@ -2666,8 +2725,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting all organizations.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } } @@ -2699,8 +2758,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting organization by id.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } } @@ -2851,9 +2910,9 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error executing query when attempting to delete organization by id.", ex); // NON-NLS } finally { + CentralRepoDbUtil.closeResultSet(resultSet); CentralRepoDbUtil.closeStatement(checkIfUsedStatement); CentralRepoDbUtil.closeStatement(deleteOrgStatement); - CentralRepoDbUtil.closeResultSet(resultSet); CentralRepoDbUtil.closeConnection(conn); } } @@ -2915,9 +2974,9 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error inserting new global set.", ex); // NON-NLS } finally { + CentralRepoDbUtil.closeResultSet(resultSet); CentralRepoDbUtil.closeStatement(preparedStatement1); CentralRepoDbUtil.closeStatement(preparedStatement2); - CentralRepoDbUtil.closeResultSet(resultSet); CentralRepoDbUtil.closeConnection(conn); } } @@ -2952,8 +3011,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting reference set by id.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement1); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement1); CentralRepoDbUtil.closeConnection(conn); } } @@ -2991,8 +3050,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting reference sets.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement1); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement1); CentralRepoDbUtil.closeConnection(conn); } return results; @@ -3069,8 +3128,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { throw new CentralRepoException("Error testing whether reference set exists (name: " + referenceSetName + " version: " + version, ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement1); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement1); CentralRepoDbUtil.closeConnection(conn); } } @@ -3160,8 +3219,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting reference instances by type and value.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement1); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement1); CentralRepoDbUtil.closeConnection(conn); } @@ -3241,9 +3300,9 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error inserting new correlation type.", ex); // NON-NLS } finally { + CentralRepoDbUtil.closeResultSet(resultSet); CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeStatement(preparedStatementQuery); - CentralRepoDbUtil.closeResultSet(resultSet); CentralRepoDbUtil.closeConnection(conn); } return typeId; @@ -3295,9 +3354,9 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error inserting new correlation type.", ex); // NON-NLS } finally { + CentralRepoDbUtil.closeResultSet(resultSet); CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeStatement(preparedStatementQuery); - CentralRepoDbUtil.closeResultSet(resultSet); CentralRepoDbUtil.closeConnection(conn); } return typeId; @@ -3343,8 +3402,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting enabled correlation types.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } } @@ -3378,8 +3437,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting supported correlation types.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } } @@ -3472,8 +3531,8 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error getting correlation type by id.", ex); // NON-NLS } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeStatement(preparedStatement); CentralRepoDbUtil.closeConnection(conn); } } @@ -3588,7 +3647,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { * * @throws SQLException when an expected column name is not in the resultSet */ - private CorrelationAttributeInstance getEamArtifactInstanceFromResultSet(ResultSet resultSet, CorrelationAttributeInstance.Type aType) throws SQLException, CentralRepoException, CorrelationAttributeNormalizationException { + private CorrelationAttributeInstance getCorrAttrFromResultSet(ResultSet resultSet, CorrelationAttributeInstance.Type aType) throws SQLException, CentralRepoException, CorrelationAttributeNormalizationException { if (null == resultSet) { return null; } @@ -3974,6 +4033,9 @@ abstract class RdbmsCentralRepo implements CentralRepository { // Upgrade to 1.5 (new CentralRepoDbUpgrader14To15()).upgradeSchema(dbSchemaVersion, conn); + // Upgrade to 1.6 + (new CentralRepoDbUpgrader15To16()).upgradeSchema(dbSchemaVersion, conn); + updateSchemaVersion(conn); conn.commit(); logger.log(Level.INFO, String.format("Central Repository schema updated to version %s", SOFTWARE_CR_DB_SCHEMA_VERSION)); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepo.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepo.java index df0327469d..ce7f4705fc 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepo.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepo.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2020 Basis Technology Corp. + * Copyright 2015-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -58,8 +58,8 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { * * @return the singleton instance of SqliteEamDb * - * @throws CentralRepoException if one or more default correlation type(s) have an - * invalid db table name. + * @throws CentralRepoException if one or more default correlation type(s) + * have an invalid db table name. */ public synchronized static SqliteCentralRepo getInstance() throws CentralRepoException { if (instance == null) { @@ -71,9 +71,9 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { /** * - * @throws CentralRepoException if the AbstractSqlEamDb class has one or more - * default correlation type(s) having an invalid db - * table name. + * @throws CentralRepoException if the AbstractSqlEamDb class has one or + * more default correlation type(s) having an + * invalid db table name. */ private SqliteCentralRepo() throws CentralRepoException { dbSettings = new SqliteCentralRepoSettings(); @@ -144,7 +144,7 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { CentralRepoDbUtil.closeConnection(conn); } - RdbmsCentralRepoFactory centralRepoSchemaFactory = new RdbmsCentralRepoFactory(CentralRepoPlatforms.SQLITE, dbSettings); + RdbmsCentralRepoFactory centralRepoSchemaFactory = new RdbmsCentralRepoFactory(CentralRepoPlatforms.SQLITE, dbSettings); centralRepoSchemaFactory.insertDefaultDatabaseContent(); } finally { releaseExclusiveLock(); @@ -229,8 +229,9 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { @Override protected Connection getEphemeralConnection() { - return this.dbSettings.getEphemeralConnection(); - } + return this.dbSettings.getEphemeralConnection(); + } + /** * Add a new name/value pair in the db_info table. * @@ -625,6 +626,16 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { } } + @Override + public Long getCountCasesWithOtherInstances(CorrelationAttributeInstance instance) throws CentralRepoException, CorrelationAttributeNormalizationException { + try { + acquireSharedLock(); + return super.getCountCasesWithOtherInstances(instance); + } finally { + releaseSharedLock(); + } + } + @Override public Long getCountUniqueDataSources() throws CentralRepoException { try { @@ -830,9 +841,9 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { super.processSelectClause(selectClause, instanceTableCallback); } finally { releaseSharedLock(); - } - } - + } + } + @Override public void executeCommand(String sql, List params) throws CentralRepoException { try { @@ -842,7 +853,7 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { releaseExclusiveLock(); } } - + @Override public void executeQuery(String sql, List params, CentralRepositoryDbQueryCallback queryCallback) throws CentralRepoException { try { @@ -852,7 +863,7 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { releaseSharedLock(); } } - + /** * Check whether a reference set with the given name/version is in the * central repo. Used to check for name collisions when creating reference @@ -1214,8 +1225,8 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { * * @return the lock, or null if locking is not supported * - * @throws CentralRepoException if the coordination service is running but we fail - * to get the lock + * @throws CentralRepoException if the coordination service is running but + * we fail to get the lock */ @Override public CoordinationService.Lock getExclusiveMultiUserDbLock() throws CentralRepoException { diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepoSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepoSettings.java index a938dd166a..b0842077c0 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepoSettings.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepoSettings.java @@ -22,11 +22,14 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.logging.Level; import java.util.regex.Pattern; +import org.sleuthkit.autopsy.centralrepository.CentralRepoSettings; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.PlatformUtil; @@ -39,9 +42,17 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil; */ public final class SqliteCentralRepoSettings implements CentralRepoDbConnectivityManager { - public final static String DEFAULT_DBNAME = "central_repository.db"; // NON-NLS + public final static String DEFAULT_DBNAME = CentralRepoSettings.getInstance().getDefaultDbName(); // NON-NLS private final static Logger LOGGER = Logger.getLogger(SqliteCentralRepoSettings.class.getName()); - private final static String DEFAULT_DBDIRECTORY = PlatformUtil.getUserDirectory() + File.separator + "central_repository"; // NON-NLS + private final Path userConfigDir = Paths.get(PlatformUtil.getUserDirectory().getAbsolutePath()); + private final static String DEFAULT_DBDIRECTORY = CentralRepoSettings.getInstance().getDefaultDbPath(); + + //property names + private static final String PROFILE_NAME = CentralRepoSettings.getInstance().getModuleSettingsKey(); + private static final String DATABASE_NAME = CentralRepoSettings.getInstance().getDatabaseNameKey(); //NON-NLS + private static final String DATABASE_PATH = CentralRepoSettings.getInstance().getDatabasePathKey(); //NON-NLS + private static final String BULK_THRESHOLD = "db.sqlite.bulkThreshold"; //NON-NLS + private final static String JDBC_DRIVER = "org.sqlite.JDBC"; // NON-NLS private final static String JDBC_BASE_URI = "jdbc:sqlite:"; // NON-NLS private final static String VALIDATION_QUERY = "SELECT count(*) from sqlite_master"; // NON-NLS @@ -56,18 +67,18 @@ public final class SqliteCentralRepoSettings implements CentralRepoDbConnectivit } public void loadSettings() { - dbName = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.dbName"); // NON-NLS + dbName = ModuleSettings.getConfigSetting(PROFILE_NAME, DATABASE_NAME); // NON-NLS if (dbName == null || dbName.isEmpty()) { dbName = DEFAULT_DBNAME; } - dbDirectory = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.dbDirectory"); // NON-NLS + dbDirectory = readDbPath(); // NON-NLS if (dbDirectory == null || dbDirectory.isEmpty()) { dbDirectory = DEFAULT_DBDIRECTORY; } try { - String bulkThresholdString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.bulkThreshold"); // NON-NLS + String bulkThresholdString = ModuleSettings.getConfigSetting(PROFILE_NAME, BULK_THRESHOLD); // NON-NLS if (bulkThresholdString == null || bulkThresholdString.isEmpty()) { this.bulkThreshold = RdbmsCentralRepo.DEFAULT_BULK_THRESHHOLD; } else { @@ -96,9 +107,64 @@ public final class SqliteCentralRepoSettings implements CentralRepoDbConnectivit public void saveSettings() { createDbDirectory(); - ModuleSettings.setConfigSetting("CentralRepository", "db.sqlite.dbName", getDbName()); // NON-NLS - ModuleSettings.setConfigSetting("CentralRepository", "db.sqlite.dbDirectory", getDbDirectory()); // NON-NLS - ModuleSettings.setConfigSetting("CentralRepository", "db.sqlite.bulkThreshold", Integer.toString(getBulkThreshold())); // NON-NLS + ModuleSettings.setConfigSetting(PROFILE_NAME, DATABASE_NAME, getDbName()); // NON-NLS + saveDbPath(getDbDirectory()); // NON-NLS + ModuleSettings.setConfigSetting(PROFILE_NAME, BULK_THRESHOLD, Integer.toString(getBulkThreshold())); // NON-NLS + } + + /** + * Save CR database path. If the path is inside user directory (e.g. + * "C:\Users\USER_NAME\AppData\Roaming\autopsy"), trim that off and save it + * as a relative path (i.e it will not start with a “/†or drive letter). Otherwise, + * full path is saved. See JIRA-7348. + * + * @param fullPath Full path to the SQLite db file. + */ + private void saveDbPath(String fullPath) { + Path relativePath = Paths.get(fullPath); + // check if the path is within user directory + if (Paths.get(fullPath).startsWith(userConfigDir)) { + // relativize the path + relativePath = userConfigDir.relativize(relativePath); + } + // Use properties to persist the logo to use. + ModuleSettings.setConfigSetting(PROFILE_NAME, DATABASE_PATH, relativePath.toString()); + } + + /** + * Read CD database path from preferences file. Reverses the path relativization performed + * in saveDbPath(). If the stored path starts with either “/†or drive letter, + * it is a full path, and is returned to the caller. Otherwise, append current user + * directory to the saved relative path. See JIRA-7348. + * + * @return Full path to the SQLite CR database file. + */ + private String readDbPath() { + + String curPath = ModuleSettings.getConfigSetting(PROFILE_NAME, DATABASE_PATH); + + + //if has been set, validate it's correct, if not set, return null + if (curPath != null && !curPath.isEmpty()) { + + // check if the path is an absolute path (starts with either drive letter or "/") + Path driveLetterOrNetwork = Paths.get(curPath).getRoot(); + if (driveLetterOrNetwork != null) { + // absolute path + return curPath; + } + + // Path is a relative path. Reverse path relativization performed in saveDbPath() + Path absolutePath = userConfigDir.resolve(curPath); + curPath = absolutePath.toString(); + if (new File(curPath).canRead() == false) { + //use default + LOGGER.log(Level.INFO, "Path to SQLite Central Repository database is not valid: {0}", curPath); //NON-NLS + curPath = null; + } + } + + return curPath; } /** @@ -252,9 +318,9 @@ public final class SqliteCentralRepoSettings implements CentralRepoDbConnectivit } boolean isChanged() { - String dbNameString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.dbName"); // NON-NLS - String dbDirectoryString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.dbDirectory"); // NON-NLS - String bulkThresholdString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.bulkThreshold"); // NON-NLS + String dbNameString = ModuleSettings.getConfigSetting(PROFILE_NAME, DATABASE_NAME); // NON-NLS + String dbDirectoryString = readDbPath(); // NON-NLS + String bulkThresholdString = ModuleSettings.getConfigSetting(PROFILE_NAME, BULK_THRESHOLD); // NON-NLS return !dbName.equals(dbNameString) || !dbDirectory.equals(dbDirectoryString) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED index e95a759c4f..909275d224 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED @@ -5,11 +5,4 @@ CentralRepositoryNotificationDialog.bulletThree=Create personas that group accou CentralRepositoryNotificationDialog.bulletTwo=Identify where an item was previously seen CentralRepositoryNotificationDialog.finalRemarks=To limit what is stored, use the Central Repository options panel. CentralRepositoryNotificationDialog.header=Autopsy stores data about each case in its Central Repository. -IngestEventsListener.ingestmodule.name=Central Repository -IngestEventsListener.prevCaseComment.text=Previous Case: -# {0} - typeName -# {1} - count -IngestEventsListener.prevCount.text=Number of previous {0}: {1} -IngestEventsListener.prevExists.text=Previously Seen Devices (Central Repository) -IngestEventsListener.prevTaggedSet.text=Previously Tagged As Notable (Central Repository) Installer.centralRepoUpgradeFailed.title=Central repository disabled diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle_ja.properties index 0f4bae052b..d9daa542e3 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle_ja.properties @@ -1,5 +1,17 @@ -#Tue Aug 18 18:09:20 UTC 2020 +#Thu Sep 30 10:26:59 UTC 2021 +CaseEventsListener.module.name=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea +CaseEventsListener.prevCaseComment.text=\u4ee5\u524d\u306e\u30b1\u30fc\u30b9\u3067\u8a8d\u8b58\u3057\u305f\u30e6\u30fc\u30b6\u30fc +CaseEventsListener.prevExists.text=\u4ee5\u524d\u306b\u8a8d\u8b58\u3055\u308c\u305f\u30e6\u30fc\u30b6\u30fc\uff08\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\uff09 +CentralRepositoryNotificationDialog.bulletHeader=\u3053\u306e\u30c7\u30fc\u30bf\u306e\u4f7f\u7528\u76ee\u7684\u306f\uff1a +CentralRepositoryNotificationDialog.bulletOne=\u4e00\u822c\u7684\u306a\u30a2\u30a4\u30c6\u30e0\uff08\u30d5\u30a1\u30a4\u30eb\u3001\u30c9\u30e1\u30a4\u30f3\u3001\u30a2\u30ab\u30a6\u30f3\u30c8\uff09\u3092\u7121\u8996\u3059\u308b +CentralRepositoryNotificationDialog.bulletThree=\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u30b0\u30eb\u30fc\u30d7\u5316\u3059\u308b\u30da\u30eb\u30bd\u30ca\u3092\u4f5c\u6210\u3059\u308b +CentralRepositoryNotificationDialog.bulletTwo=\u30a2\u30a4\u30c6\u30e0\u304c\u4ee5\u524d\u306b\u898b\u3089\u308c\u305f\u5834\u6240\u3092\u7279\u5b9a\u3059\u308b +CentralRepositoryNotificationDialog.finalRemarks=\u4fdd\u5b58\u3059\u308b\u5185\u5bb9\u3092\u5236\u9650\u3059\u308b\u306b\u306f\u3001\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fb\u30aa\u30d7\u30b7\u30e7\u30f3\u30d1\u30cd\u30eb\u3092\u4f7f\u7528\u3057\u3066\u4e0b\u3055\u3044\u3002 +CentralRepositoryNotificationDialog.header=Autopsy\u306f\u3001\u5404\u30b1\u30fc\u30b9\u306b\u95a2\u3059\u308b\u30c7\u30fc\u30bf\u3092\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306b\u4fdd\u5b58\u3057\u307e\u3059\u3002 IngestEventsListener.ingestmodule.name=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea -Installer.initialCreateSqlite.messageDesc=\u51e6\u7406\u3059\u308b\u3059\u3079\u3066\u306e\u30cf\u30c3\u30b7\u30e5\u3068\u8b58\u5225\u5b50\u306b\u95a2\u3059\u308b\u60c5\u5831\u3092\u4fdd\u5b58\u3057\u307e\u3059\u3002 \u4ee5\u524d\u306b\u78ba\u8a8d\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb\u3092\u7121\u8996\u3057\u3001\u30b1\u30fc\u30b9\u9593\u306e\u95a2\u9023\u3092\u4f5c\u6210\u3067\u304d\u307e\u3059\u3002 -Installer.initialCreateSqlite.messageHeader=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fc\u304c\u7121\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u3059\u3002 \u6709\u52b9\u306b\u3057\u307e\u3059\u304b\uff1f -Installer.initialCreateSqlite.title=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fc\u3092 \u6709\u52b9\u306b\u3057\u307e\u3059\u304b\uff1f +IngestEventsListener.prevCaseComment.text=\u524d\u306e\u30b1\u30fc\u30b9\uff1a +IngestEventsListener.prevCount.text=\u524d\u306e{0}\u306e\u6570\uff1a{1} +IngestEventsListener.prevExists.text=\u4ee5\u524d\u898b\u305f\u30c7\u30d0\u30a4\u30b9\uff08\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\uff09 +IngestEventsListener.prevTaggedSet.text=\u4ee5\u524d\u306b\u6ce8\u76ee\u3059\u3079\u304d\u3068\u3057\u3066\u30bf\u30b0\u4ed8\u3051\u3055\u308c\u305f\uff08\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\uff09 +Installer.centralRepoUpgradeFailed.title=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u304c\u7121\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u3059\u3002 +caseeventlistener.evidencetag=\u8a3c\u62e0 diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java index 94a22492c1..6610fcbd86 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2017-2020 Basis Technology Corp. + * Copyright 2017-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.centralrepository.eventlisteners; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.Set; @@ -56,18 +57,18 @@ import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.datamodel.Tag; import org.sleuthkit.autopsy.events.AutopsyEvent; +import org.sleuthkit.datamodel.AnalysisResult; +import org.sleuthkit.datamodel.DataArtifact; /** - * Listen for case events and update entries in the Central Repository database - * accordingly + * An Autopsy events listener for case events relevant to the central + * repository. */ @Messages({"caseeventlistener.evidencetag=Evidence"}) -final class CaseEventListener implements PropertyChangeListener { +public final class CaseEventListener implements PropertyChangeListener { private static final Logger LOGGER = Logger.getLogger(CaseEventListener.class.getName()); - private final ExecutorService jobProcessingExecutor; - private static final String CASE_EVENT_THREAD_NAME = "Case-Event-Listener-%d"; - + private static final String CASE_EVENT_THREAD_NAME = "CR-Case-Event-Listener-%d"; private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of( Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED, Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED, Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED, @@ -76,12 +77,28 @@ final class CaseEventListener implements PropertyChangeListener { Case.Events.TAG_DEFINITION_CHANGED, Case.Events.CURRENT_CASE, Case.Events.DATA_SOURCE_NAME_CHANGED); + private final ExecutorService jobProcessingExecutor; - CaseEventListener() { + /** + * Contructs an Autopsy events listener for case events relevant to the + * central repository. + */ + public CaseEventListener() { jobProcessingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(CASE_EVENT_THREAD_NAME).build()); } - void shutdown() { + /** + * Starts up the listener. + */ + public void startUp() { + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, this); + } + + /** + * Shuts down the listener. + */ + public void shutdown() { + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, this); ThreadUtils.shutDownTaskExecutor(jobProcessingExecutor); } @@ -91,86 +108,73 @@ final class CaseEventListener implements PropertyChangeListener { return; } - CentralRepository dbManager; - try { - dbManager = CentralRepository.getInstance(); - } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "Failed to get instance of db manager.", ex); + if (!CentralRepository.isEnabled()) { return; } - // If any changes are made to which event types are handled the change - // must also be made to CASE_EVENTS_OF_INTEREST. + CentralRepository centralRepo; + try { + centralRepo = CentralRepository.getInstance(); + } catch (CentralRepoException ex) { + LOGGER.log(Level.SEVERE, "Failed to access central repository", ex); + return; + } + + /* + * IMPORTANT: If any changes are made to which event types are handled, + * the change must also be made to the contents of the + * CASE_EVENTS_OF_INTEREST set. + */ switch (Case.Events.valueOf(evt.getPropertyName())) { case CONTENT_TAG_ADDED: - case CONTENT_TAG_DELETED: { - jobProcessingExecutor.submit(new ContentTagTask(dbManager, evt)); - } - break; - + case CONTENT_TAG_DELETED: + jobProcessingExecutor.submit(new ContentTagTask(centralRepo, evt)); + break; case BLACKBOARD_ARTIFACT_TAG_DELETED: - case BLACKBOARD_ARTIFACT_TAG_ADDED: { - jobProcessingExecutor.submit(new BlackboardTagTask(dbManager, evt)); - } - break; - - case DATA_SOURCE_ADDED: { - jobProcessingExecutor.submit(new DataSourceAddedTask(dbManager, evt)); - } - break; - case TAG_DEFINITION_CHANGED: { + case BLACKBOARD_ARTIFACT_TAG_ADDED: + jobProcessingExecutor.submit(new ArtifactTagTask(centralRepo, evt)); + break; + case DATA_SOURCE_ADDED: + jobProcessingExecutor.submit(new DataSourceAddedTask(centralRepo, evt)); + break; + case TAG_DEFINITION_CHANGED: jobProcessingExecutor.submit(new TagDefinitionChangeTask(evt)); - } - break; - case CURRENT_CASE: { - jobProcessingExecutor.submit(new CurrentCaseTask(dbManager, evt)); - } - break; - case DATA_SOURCE_NAME_CHANGED: { - jobProcessingExecutor.submit(new DataSourceNameChangedTask(dbManager, evt)); - } - break; + break; + case CURRENT_CASE: + jobProcessingExecutor.submit(new CurrentCaseTask(centralRepo, evt)); + break; + case DATA_SOURCE_NAME_CHANGED: + jobProcessingExecutor.submit(new DataSourceNameChangedTask(centralRepo, evt)); + break; + default: + break; } } - /* - * Add all of our Case Event Listeners to the case. + /** + * Determines whether or not a tag has notable status. + * + * @param tag The tag. + * + * @return True or false. */ - void installListeners() { - Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, this); - } - - /* - * Remove all of our Case Event Listeners from the case. - */ - void uninstallListeners() { - Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, this); + private static boolean isNotableTag(Tag tag) { + return (tag != null && isNotableTagDefinition(tag.getName())); } /** - * Returns true if the tag has a notable status. + * Determines whether or not a tag definition calls for notable status. * - * @param t The tag to use in determination. + * @param tagDef The tag definition. * - * @return Whether or not it is a notable tag. + * @return True or false. */ - private static boolean isNotableTag(Tag t) { - return (t != null && isNotableTagName(t.getName())); + private static boolean isNotableTagDefinition(TagName tagDef) { + return (tagDef != null && TagsManager.getNotableTagDisplayNames().contains(tagDef.getDisplayName())); } /** - * Returns true if the tag name has a notable status. - * - * @param t The tag name to use in determination. - * - * @return Whether or not it is a notable tag name. - */ - private static boolean isNotableTagName(TagName t) { - return (t != null && TagsManager.getNotableTagDisplayNames().contains(t.getDisplayName())); - } - - /** - * Searches a list of tags for a tag with a notable status. + * Searches a list of tags for a tag with notable status. * * @param tags The tags to search. * @@ -180,13 +184,36 @@ final class CaseEventListener implements PropertyChangeListener { if (tags == null) { return false; } - return tags.stream() .filter(CaseEventListener::isNotableTag) .findFirst() .isPresent(); } + /** + * Sets the notable (known) status of a central repository correlation + * attribute corresponding to an artifact. + * + * @param centralRepo The central repository. + * @param artifact The artifact. + * @param notableStatus The new notable status. + */ + private static void setArtifactKnownStatus(CentralRepository centralRepo, BlackboardArtifact artifact, TskData.FileKnown notableStatus) { + List corrAttrInstances = new ArrayList<>(); + if (artifact instanceof DataArtifact) { + corrAttrInstances.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((DataArtifact) artifact)); + } else if (artifact instanceof AnalysisResult) { + corrAttrInstances.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AnalysisResult) artifact)); + } + for (CorrelationAttributeInstance corrAttrInstance : corrAttrInstances) { + try { + centralRepo.setAttributeInstanceKnownStatus(corrAttrInstance, notableStatus); + } catch (CentralRepoException ex) { + LOGGER.log(Level.SEVERE, String.format("Error setting correlation attribute instance known status", corrAttrInstance), ex); //NON-NLS + } + } + } + private final class ContentTagTask implements Runnable { private final CentralRepository dbManager; @@ -295,12 +322,12 @@ final class CaseEventListener implements PropertyChangeListener { * instance. */ private void setContentKnownStatus(AbstractFile af, TskData.FileKnown knownStatus) { - final CorrelationAttributeInstance eamArtifact = CorrelationAttributeUtil.makeCorrAttrFromFile(af); - - if (eamArtifact != null) { + final List md5CorrelationAttr = CorrelationAttributeUtil.makeCorrAttrsForSearch(af); + if (!md5CorrelationAttr.isEmpty()) { + //for an abstract file the 'list' of attributes will be a single attribute or empty and is returning a list for consistency with other makeCorrAttrsForSearch methods per 7852 // send update to Central Repository db try { - dbManager.setAttributeInstanceKnownStatus(eamArtifact, knownStatus); + dbManager.setAttributeInstanceKnownStatus(md5CorrelationAttr.get(0), knownStatus); } catch (CentralRepoException ex) { LOGGER.log(Level.SEVERE, "Error connecting to Central Repository database while setting artifact known status.", ex); //NON-NLS } @@ -308,12 +335,12 @@ final class CaseEventListener implements PropertyChangeListener { } } - private final class BlackboardTagTask implements Runnable { + private final class ArtifactTagTask implements Runnable { private final CentralRepository dbManager; private final PropertyChangeEvent event; - private BlackboardTagTask(CentralRepository db, PropertyChangeEvent evt) { + private ArtifactTagTask(CentralRepository db, PropertyChangeEvent evt) { dbManager = db; event = evt; } @@ -405,13 +432,12 @@ final class CaseEventListener implements PropertyChangeListener { TagsManager tagsManager = openCase.getServices().getTagsManager(); List tags = tagsManager.getBlackboardArtifactTagsByArtifact(bbArtifact); if (hasNotableTag(tags)) { - setArtifactKnownStatus(bbArtifact, TskData.FileKnown.BAD); + setArtifactKnownStatus(dbManager, bbArtifact, TskData.FileKnown.BAD); } else { - setArtifactKnownStatus(bbArtifact, TskData.FileKnown.UNKNOWN); + setArtifactKnownStatus(dbManager, bbArtifact, TskData.FileKnown.UNKNOWN); } } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Failed to obtain tags manager for case.", ex); - return; } } @@ -426,24 +452,6 @@ final class CaseEventListener implements PropertyChangeListener { return ((content instanceof AbstractFile) && (((AbstractFile) content).getKnown() == TskData.FileKnown.KNOWN)); } - /** - * Sets the known status of a blackboard artifact in the central - * repository. - * - * @param bbArtifact The blackboard artifact to set known status. - * @param knownStatus The new known status. - */ - private void setArtifactKnownStatus(BlackboardArtifact bbArtifact, TskData.FileKnown knownStatus) { - List convertedArtifacts = CorrelationAttributeUtil.makeCorrAttrsForCorrelation(bbArtifact); - for (CorrelationAttributeInstance eamArtifact : convertedArtifacts) { - try { - dbManager.setAttributeInstanceKnownStatus(eamArtifact, knownStatus); - } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "Error connecting to Central Repository database while setting artifact known status.", ex); //NON-NLS - } - } - } - } private final class TagDefinitionChangeTask implements Runnable { @@ -504,12 +512,7 @@ final class CaseEventListener implements PropertyChangeListener { } //if the Correlation Attribute will have no tags with a status which would prevent the current status from being changed if (!hasTagWithConflictingKnownStatus) { - //Get the correlation atttributes that correspond to the current BlackboardArtifactTag if their status should be changed - //with the initial set of correlation attributes this should be a single correlation attribute - List convertedArtifacts = CorrelationAttributeUtil.makeCorrAttrsForCorrelation(bbTag.getArtifact()); - for (CorrelationAttributeInstance eamArtifact : convertedArtifacts) { - CentralRepository.getInstance().setAttributeInstanceKnownStatus(eamArtifact, tagName.getKnownStatus()); - } + setArtifactKnownStatus(CentralRepository.getInstance(), bbTag.getArtifact(), tagName.getKnownStatus()); } } // Next update the files @@ -544,9 +547,10 @@ final class CaseEventListener implements PropertyChangeListener { if (!hasTagWithConflictingKnownStatus) { Content taggedContent = contentTag.getContent(); if (taggedContent instanceof AbstractFile) { - final CorrelationAttributeInstance eamArtifact = CorrelationAttributeUtil.makeCorrAttrFromFile((AbstractFile) taggedContent); - if (eamArtifact != null) { - CentralRepository.getInstance().setAttributeInstanceKnownStatus(eamArtifact, tagName.getKnownStatus()); + final List eamArtifact = CorrelationAttributeUtil.makeCorrAttrsForSearch((AbstractFile) taggedContent); + if (!eamArtifact.isEmpty()) { + //for an abstract file the 'list' of attributes will be a single attribute or empty and is returning a list for consistency with other makeCorrAttrsForSearch methods per 7852 + CentralRepository.getInstance().setAttributeInstanceKnownStatus(eamArtifact.get(0), tagName.getKnownStatus()); } } } @@ -616,7 +620,6 @@ final class CaseEventListener implements PropertyChangeListener { */ if ((null == event.getOldValue()) && (event.getNewValue() instanceof Case)) { Case curCase = (Case) event.getNewValue(); - IngestEventsListener.resetCeModuleInstanceCount(); if (!CentralRepository.isEnabled()) { return; @@ -668,6 +671,7 @@ final class CaseEventListener implements PropertyChangeListener { LOGGER.log(Level.SEVERE, "No open case", ex); } } - } // DATA_SOURCE_NAME_CHANGED + } } + } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java deleted file mode 100644 index 03fa640ca5..0000000000 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java +++ /dev/null @@ -1,527 +0,0 @@ -/* - * Central Repository - * - * Copyright 2017-2020 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.centralrepository.eventlisteners; - -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import static java.lang.Boolean.FALSE; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.EnumSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.logging.Level; -import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil; -import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.ingest.IngestManager; -import org.sleuthkit.autopsy.ingest.ModuleDataEvent; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Blackboard; -import org.sleuthkit.datamodel.BlackboardArtifact; -import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.autopsy.coreutils.ThreadUtils; -import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED; -import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT; -import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT; -import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME; -import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisEvent; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.Image; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; -import org.sleuthkit.autopsy.centralrepository.datamodel.Persona; -import org.sleuthkit.autopsy.centralrepository.datamodel.PersonaAccount; -import org.sleuthkit.datamodel.Account; -import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT; -import org.sleuthkit.datamodel.CommunicationsUtils; - -/** - * Listen for ingest events and update entries in the Central Repository - * database accordingly - */ -@NbBundle.Messages({"IngestEventsListener.ingestmodule.name=Central Repository"}) -public class IngestEventsListener { - - private static final Logger LOGGER = Logger.getLogger(CorrelationAttributeInstance.class.getName()); - private static final Set INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED); - private static final Set INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(DATA_ADDED); - private static final String MODULE_NAME = Bundle.IngestEventsListener_ingestmodule_name(); - private static int correlationModuleInstanceCount; - private static boolean flagNotableItems; - private static boolean flagSeenDevices; - private static boolean createCrProperties; - private static final String INGEST_EVENT_THREAD_NAME = "Ingest-Event-Listener-%d"; - private final ExecutorService jobProcessingExecutor; - private final PropertyChangeListener pcl1 = new IngestModuleEventListener(); - private final PropertyChangeListener pcl2 = new IngestJobEventListener(); - final Collection recentlyAddedCeArtifacts = new LinkedHashSet<>(); - - IngestEventsListener() { - jobProcessingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(INGEST_EVENT_THREAD_NAME).build()); - } - - void shutdown() { - ThreadUtils.shutDownTaskExecutor(jobProcessingExecutor); - } - - /* - * Add all of our Ingest Event Listeners to the IngestManager Instance. - */ - public void installListeners() { - IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl1); - IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl2); - } - - /* - * Remove all of our Ingest Event Listeners from the IngestManager Instance. - */ - public void uninstallListeners() { - IngestManager.getInstance().removeIngestModuleEventListener(pcl1); - IngestManager.getInstance().removeIngestJobEventListener(pcl2); - } - - /** - * Increase the number of IngestEventsListeners adding contents to the - * Central Repository. - */ - public synchronized static void incrementCorrelationEngineModuleCount() { - correlationModuleInstanceCount++; //Should be called once in the Central Repository module's startup method. - } - - /** - * Decrease the number of IngestEventsListeners adding contents to the - * Central Repository. - */ - public synchronized static void decrementCorrelationEngineModuleCount() { - if (getCeModuleInstanceCount() > 0) { //prevent it ingestJobCounter from going negative - correlationModuleInstanceCount--; //Should be called once in the Central Repository module's shutdown method. - } - } - - /** - * Reset the counter which keeps track of if the Central Repository Module - * is being run during injest to 0. - */ - synchronized static void resetCeModuleInstanceCount() { - correlationModuleInstanceCount = 0; //called when a case is opened in case for some reason counter was not reset - } - - /** - * Whether or not the Central Repository Module is enabled for any of the - * currently running ingest jobs. - * - * @return boolean True for Central Repository enabled, False for disabled - */ - public synchronized static int getCeModuleInstanceCount() { - return correlationModuleInstanceCount; - } - - /** - * Are notable items being flagged? - * - * @return True if flagging notable items; otherwise false. - */ - public synchronized static boolean isFlagNotableItems() { - return flagNotableItems; - } - - /** - * Are previously seen devices being flagged? - * - * @return True if flagging seen devices; otherwise false. - */ - public synchronized static boolean isFlagSeenDevices() { - return flagSeenDevices; - } - - /** - * Are correlation properties being created - * - * @return True if creating correlation properties; otherwise false. - */ - public synchronized static boolean shouldCreateCrProperties() { - return createCrProperties; - } - - /** - * Configure the listener to flag notable items or not. - * - * @param value True to flag notable items; otherwise false. - */ - public synchronized static void setFlagNotableItems(boolean value) { - flagNotableItems = value; - } - - /** - * Configure the listener to flag previously seen devices or not. - * - * @param value True to flag seen devices; otherwise false. - */ - public synchronized static void setFlagSeenDevices(boolean value) { - flagSeenDevices = value; - } - - /** - * Configure the listener to create correlation properties - * - * @param value True to create properties; otherwise false. - */ - public synchronized static void setCreateCrProperties(boolean value) { - createCrProperties = value; - } - - /** - * Make an Interesting Item artifact based on a new artifact being previously seen. - * @param originalArtifact Original artifact that we want to flag - * @param caseDisplayNames List of case names artifact was previously seen in - */ - @NbBundle.Messages({"IngestEventsListener.prevTaggedSet.text=Previously Tagged As Notable (Central Repository)", - "IngestEventsListener.prevCaseComment.text=Previous Case: "}) - static private void makeAndPostPreviousNotableArtifact(BlackboardArtifact originalArtifact, List caseDisplayNames) { - - Collection attributesForNewArtifact = Arrays.asList(new BlackboardAttribute( - TSK_SET_NAME, MODULE_NAME, - Bundle.IngestEventsListener_prevTaggedSet_text()), - new BlackboardAttribute( - TSK_COMMENT, MODULE_NAME, - Bundle.IngestEventsListener_prevCaseComment_text() + caseDisplayNames.stream().distinct().collect(Collectors.joining(","))), - new BlackboardAttribute( - TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, - originalArtifact.getArtifactID())); - makeAndPostInterestingArtifact(originalArtifact, attributesForNewArtifact); - } - - /** - * Create an Interesting Artifact hit for a device which was previously seen - * in the central repository. - * - * @param originalArtifact the artifact to create the interesting item for - * @param caseDisplayNames the case names the artifact was previously seen in - */ - @NbBundle.Messages({"IngestEventsListener.prevExists.text=Previously Seen Devices (Central Repository)", - "# {0} - typeName", - "# {1} - count", - "IngestEventsListener.prevCount.text=Number of previous {0}: {1}"}) - static private void makeAndPostPreviousSeenArtifact(BlackboardArtifact originalArtifact, List caseDisplayNames) { - Collection attributesForNewArtifact = Arrays.asList(new BlackboardAttribute( - TSK_SET_NAME, MODULE_NAME, - Bundle.IngestEventsListener_prevExists_text()), - new BlackboardAttribute( - TSK_COMMENT, MODULE_NAME, - Bundle.IngestEventsListener_prevCaseComment_text() + caseDisplayNames.stream().distinct().collect(Collectors.joining(","))), - new BlackboardAttribute( - TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, - originalArtifact.getArtifactID())); - makeAndPostInterestingArtifact(originalArtifact, attributesForNewArtifact); - } - - /** - * Make an interesting item artifact to flag the passed in artifact. - * @param originalArtifact Artifact in current case we want to flag - * @param attributesForNewArtifact Attributes to assign to the new Interesting items artifact - */ - private static void makeAndPostInterestingArtifact(BlackboardArtifact originalArtifact, Collection attributesForNewArtifact) { - try { - SleuthkitCase tskCase = originalArtifact.getSleuthkitCase(); - AbstractFile abstractFile = tskCase.getAbstractFileById(originalArtifact.getObjectID()); - Blackboard blackboard = tskCase.getBlackboard(); - // Create artifact if it doesn't already exist. - if (!blackboard.artifactExists(abstractFile, TSK_INTERESTING_ARTIFACT_HIT, attributesForNewArtifact)) { - BlackboardArtifact newInterestingArtifact = abstractFile.newArtifact(TSK_INTERESTING_ARTIFACT_HIT); - newInterestingArtifact.addAttributes(attributesForNewArtifact); - - try { - // index the artifact for keyword search - blackboard.postArtifact(newInterestingArtifact, MODULE_NAME); - } catch (Blackboard.BlackboardException ex) { - LOGGER.log(Level.SEVERE, "Unable to index blackboard artifact " + newInterestingArtifact.getArtifactID(), ex); //NON-NLS - } - } - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Failed to create BlackboardArtifact.", ex); // NON-NLS - } catch (IllegalStateException ex) { - LOGGER.log(Level.SEVERE, "Failed to create BlackboardAttribute.", ex); // NON-NLS - } - } - - private class IngestModuleEventListener implements PropertyChangeListener { - - @Override - public void propertyChange(PropertyChangeEvent evt) { - //if ingest is running we want there to check if there is a Central Repository module running - //sometimes artifacts are generated by DSPs or other sources while ingest is not running - //in these cases we still want to create correlation attributesForNewArtifact for those artifacts when appropriate - if (!IngestManager.getInstance().isIngestRunning() || getCeModuleInstanceCount() > 0) { - CentralRepository dbManager; - try { - dbManager = CentralRepository.getInstance(); - } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "Failed to connect to Central Repository database.", ex); - return; - } - switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) { - case DATA_ADDED: { - //if ingest isn't running create the interesting items otherwise use the ingest module setting to determine if we create interesting items - boolean flagNotable = !IngestManager.getInstance().isIngestRunning() || isFlagNotableItems(); - boolean flagPrevious = !IngestManager.getInstance().isIngestRunning() || isFlagSeenDevices(); - boolean createAttributes = !IngestManager.getInstance().isIngestRunning() || shouldCreateCrProperties(); - jobProcessingExecutor.submit(new DataAddedTask(dbManager, evt, flagNotable, flagPrevious, createAttributes)); - break; - } - default: - break; - } - } - } - } - - private class IngestJobEventListener implements PropertyChangeListener { - - @Override - public void propertyChange(PropertyChangeEvent evt) { - CentralRepository dbManager; - try { - dbManager = CentralRepository.getInstance(); - } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "Failed to connect to Central Repository database.", ex); - return; - } - - switch (IngestManager.IngestJobEvent.valueOf(evt.getPropertyName())) { - case DATA_SOURCE_ANALYSIS_COMPLETED: { - jobProcessingExecutor.submit(new AnalysisCompleteTask(dbManager, evt)); - break; - } - default: - break; - } - } - - } - - private final class AnalysisCompleteTask implements Runnable { - - private final CentralRepository dbManager; - private final PropertyChangeEvent event; - - private AnalysisCompleteTask(CentralRepository db, PropertyChangeEvent evt) { - dbManager = db; - event = evt; - } - - @Override - public void run() { - // clear the tracker to reduce memory usage - if (getCeModuleInstanceCount() == 0) { - recentlyAddedCeArtifacts.clear(); - } - //else another instance of the Central Repository Module is still being run. - - /* - * Ensure the data source in the Central Repository has hash values - * that match those in the case database. - */ - if (!CentralRepository.isEnabled()) { - return; - } - Content dataSource; - String dataSourceName = ""; - long dataSourceObjectId = -1; - try { - dataSource = ((DataSourceAnalysisEvent) event).getDataSource(); - /* - * We only care about Images for the purpose of - * updating hash values. - */ - if (!(dataSource instanceof Image)) { - return; - } - - dataSourceName = dataSource.getName(); - dataSourceObjectId = dataSource.getId(); - - Case openCase = Case.getCurrentCaseThrows(); - - CorrelationCase correlationCase = dbManager.getCase(openCase); - if (null == correlationCase) { - correlationCase = dbManager.newCase(openCase); - } - - CorrelationDataSource correlationDataSource = dbManager.getDataSource(correlationCase, dataSource.getId()); - if (correlationDataSource == null) { - // Add the data source. - CorrelationDataSource.fromTSKDataSource(correlationCase, dataSource); - } else { - // Sync the data source hash values if necessary. - if (dataSource instanceof Image) { - Image image = (Image) dataSource; - - String imageMd5Hash = image.getMd5(); - if (imageMd5Hash == null) { - imageMd5Hash = ""; - } - String crMd5Hash = correlationDataSource.getMd5(); - if (StringUtils.equals(imageMd5Hash, crMd5Hash) == false) { - correlationDataSource.setMd5(imageMd5Hash); - } - - String imageSha1Hash = image.getSha1(); - if (imageSha1Hash == null) { - imageSha1Hash = ""; - } - String crSha1Hash = correlationDataSource.getSha1(); - if (StringUtils.equals(imageSha1Hash, crSha1Hash) == false) { - correlationDataSource.setSha1(imageSha1Hash); - } - - String imageSha256Hash = image.getSha256(); - if (imageSha256Hash == null) { - imageSha256Hash = ""; - } - String crSha256Hash = correlationDataSource.getSha256(); - if (StringUtils.equals(imageSha256Hash, crSha256Hash) == false) { - correlationDataSource.setSha256(imageSha256Hash); - } - } - } - } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, String.format( - "Unable to fetch data from the Central Repository for data source '%s' (obj_id=%d)", - dataSourceName, dataSourceObjectId), ex); - } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "No current case opened.", ex); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, String.format( - "Unable to fetch data from the case database for data source '%s' (obj_id=%d)", - dataSourceName, dataSourceObjectId), ex); - } - } // DATA_SOURCE_ANALYSIS_COMPLETED - } - - private final class DataAddedTask implements Runnable { - - private final CentralRepository dbManager; - private final PropertyChangeEvent event; - private final boolean flagNotableItemsEnabled; - private final boolean flagPreviousItemsEnabled; - private final boolean createCorrelationAttributes; - - private DataAddedTask(CentralRepository db, PropertyChangeEvent evt, boolean flagNotableItemsEnabled, boolean flagPreviousItemsEnabled, boolean createCorrelationAttributes) { - this.dbManager = db; - this.event = evt; - this.flagNotableItemsEnabled = flagNotableItemsEnabled; - this.flagPreviousItemsEnabled = flagPreviousItemsEnabled; - this.createCorrelationAttributes = createCorrelationAttributes; - } - - @Override - public void run() { - if (!CentralRepository.isEnabled()) { - return; - } - final ModuleDataEvent mde = (ModuleDataEvent) event.getOldValue(); - Collection bbArtifacts = mde.getArtifacts(); - if (null == bbArtifacts) { //the ModuleDataEvents don't always have a collection of artifacts set - return; - } - List eamArtifacts = new ArrayList<>(); - - for (BlackboardArtifact bbArtifact : bbArtifacts) { - // makeCorrAttrToSave will filter out artifacts which should not be sources of CR data. - List convertedArtifacts = CorrelationAttributeUtil.makeCorrAttrsToSave(bbArtifact); - for (CorrelationAttributeInstance eamArtifact : convertedArtifacts) { - try { - // Only do something with this artifact if it's unique within the job - if (recentlyAddedCeArtifacts.add(eamArtifact.toString())) { - // Was it previously marked as bad? - // query db for artifact instances having this TYPE/VALUE and knownStatus = "Bad". - // if getKnownStatus() is "Unknown" and this artifact instance was marked bad in a previous case, - // create TSK_INTERESTING_ARTIFACT_HIT artifact on BB. - if (flagNotableItemsEnabled) { - List caseDisplayNames; - try { - caseDisplayNames = dbManager.getListCasesHavingArtifactInstancesKnownBad(eamArtifact.getCorrelationType(), eamArtifact.getCorrelationValue()); - if (!caseDisplayNames.isEmpty()) { - makeAndPostPreviousNotableArtifact(bbArtifact, - caseDisplayNames); - } - } catch (CorrelationAttributeNormalizationException ex) { - LOGGER.log(Level.INFO, String.format("Unable to flag notable item: %s.", eamArtifact.toString()), ex); - } - } - if (flagPreviousItemsEnabled - && (eamArtifact.getCorrelationType().getId() == CorrelationAttributeInstance.USBID_TYPE_ID - || eamArtifact.getCorrelationType().getId() == CorrelationAttributeInstance.ICCID_TYPE_ID - || eamArtifact.getCorrelationType().getId() == CorrelationAttributeInstance.IMEI_TYPE_ID - || eamArtifact.getCorrelationType().getId() == CorrelationAttributeInstance.IMSI_TYPE_ID - || eamArtifact.getCorrelationType().getId() == CorrelationAttributeInstance.MAC_TYPE_ID)) { - try { - //only alert to previous instances when they were in another case - List previousOccurences = dbManager.getArtifactInstancesByTypeValue(eamArtifact.getCorrelationType(), eamArtifact.getCorrelationValue()); - List caseDisplayNames; - for (CorrelationAttributeInstance instance : previousOccurences) { - if (!instance.getCorrelationCase().getCaseUUID().equals(eamArtifact.getCorrelationCase().getCaseUUID())) { - caseDisplayNames = dbManager.getListCasesHavingArtifactInstances(eamArtifact.getCorrelationType(), eamArtifact.getCorrelationValue()); - makeAndPostPreviousSeenArtifact(bbArtifact, caseDisplayNames); - break; - } - } - } catch (CorrelationAttributeNormalizationException ex) { - LOGGER.log(Level.INFO, String.format("Unable to flag notable item: %s.", eamArtifact.toString()), ex); - } - } - if (createCorrelationAttributes) { - eamArtifacts.add(eamArtifact); - } - } - } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "Error counting notable artifacts.", ex); - } - } - } - if (FALSE == eamArtifacts.isEmpty()) { - for (CorrelationAttributeInstance eamArtifact : eamArtifacts) { - try { - dbManager.addArtifactInstance(eamArtifact); - } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "Error adding artifact to database.", ex); //NON-NLS - } - } - } // DATA_ADDED - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java index b2ef0d437e..f15dec3029 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java @@ -18,34 +18,41 @@ */ package org.sleuthkit.autopsy.centralrepository.eventlisteners; +import java.io.File; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; +import java.nio.file.Paths; import java.util.Map; import java.util.logging.Level; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; +import org.apache.commons.io.FileUtils; import org.openide.modules.ModuleInstall; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbChoice; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; +import org.sleuthkit.autopsy.centralrepository.CentralRepoSettings; import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; /** - * Adds/removes application event listeners responsible for adding data to the - * central repository, sets up a default, single-user SQLite central repository + * Sets up a default, single-user SQLite central repository * if no central repository is configured, and updates the central repository * schema as required. */ public class Installer extends ModuleInstall { + private static final String LEGACY_DEFAULT_FOLDER = "central_repository"; + private static final String LEGACY_DEFAULT_DB_PARENT_PATH = Paths.get(PlatformUtil.getUserDirectory().getAbsolutePath(), LEGACY_DEFAULT_FOLDER).toAbsolutePath().toString(); + private static final String LEGACY_MODULE_SETTINGS_KEY = "CentralRepository"; + private static final Logger logger = Logger.getLogger(Installer.class.getName()); private static final long serialVersionUID = 1L; private static Installer instance; - private final CaseEventListener caseEventListener = new CaseEventListener(); - private final IngestEventsListener ingestEventListener = new IngestEventsListener(); /** * Gets the singleton "package installer" used by the registered Installer @@ -72,8 +79,7 @@ public class Installer extends ModuleInstall { } /* - * Adds/removes application event listeners responsible for adding data to - * the central repository and sets up a default, single-user SQLite central + * Sets up a default, single-user SQLite central * repository if no central repository is configured. * * Called by the registered Installer for the Autopsy-Core module located in @@ -82,17 +88,67 @@ public class Installer extends ModuleInstall { */ @Override public void restored() { - addApplicationEventListeners(); + // must happen first to move any legacy settings that exist. + upgradeSettingsPath(); setupDefaultCentralRepository(); } - + + + /** - * Adds the application event listeners responsible for adding data to the - * central repository. + * Path to module settings path. + * + * @param moduleName The full name of the module provided to ModuleSettings. + * + * @return The path on disk for that object. NOTE: This must be in sync with + * ModuleSettings. + */ + private String getSettingsFilePath(String moduleName) { + return Paths.get(PlatformUtil.getUserConfigDirectory(), moduleName + ".properties").toString(); + } + + /** + * Copies settings to new path location. */ - private void addApplicationEventListeners() { - caseEventListener.installListeners(); - ingestEventListener.installListeners(); + private void upgradeSettingsPath() { + File newSettingsFile = new File(getSettingsFilePath(CentralRepoSettings.getInstance().getModuleSettingsKey())); + File legacySettingsFile = new File(getSettingsFilePath(LEGACY_MODULE_SETTINGS_KEY)); + // new config has not been created, but legacy has, copy it. + if (!newSettingsFile.exists() && legacySettingsFile.exists()) { + Map prevSettings = ModuleSettings.getConfigSettings(LEGACY_MODULE_SETTINGS_KEY); + String prevPath = prevSettings.get(CentralRepoSettings.getInstance().getDatabasePathKey()); + File prevDirCheck = new File(prevPath); + // if a relative directory, make sure it is relative to user config. + if (!prevDirCheck.isAbsolute()) { + prevPath = Paths.get(PlatformUtil.getUserDirectory().getAbsolutePath(), prevPath).toAbsolutePath().toString(); + } + + // if old path is default path for sqlite db, copy it over to new location and update setting. + if (prevPath != null + && Paths.get(LEGACY_DEFAULT_DB_PARENT_PATH).toAbsolutePath().toString().equals(Paths.get(prevPath).toAbsolutePath().toString())) { + String prevDbName = prevSettings.get(CentralRepoSettings.getInstance().getDatabaseNameKey()); + File prevDir = new File(prevPath); + // copy all files starting with prevDbName in prevPath to new path location. + if (prevDir.exists() && prevDir.isDirectory()) { + new File(CentralRepoSettings.getInstance().getDefaultDbPath()).mkdirs(); + try { + for (File childFile : prevDir.listFiles((dir, name) -> name.startsWith(prevDbName))) { + FileUtils.copyFile(childFile, new File(CentralRepoSettings.getInstance().getDefaultDbPath(), childFile.getName())); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, "There was an error upgrading settings.", ex); + } + } + + // get the new relative path to store + String newRelPath = PlatformUtil.getUserDirectory().toPath().relativize(Paths.get(CentralRepoSettings.getInstance().getDefaultDbPath())).toString(); + // update path settings accordingly + prevSettings.put(CentralRepoSettings.getInstance().getDatabasePathKey(), newRelPath); + } + + // copy settings + ModuleSettings.setConfigSettings(CentralRepoSettings.getInstance().getModuleSettingsKey(), prevSettings); + } } /** @@ -102,7 +158,7 @@ public class Installer extends ModuleInstall { * (in other words, developers are exempt from seeing the notification). */ private void setupDefaultCentralRepository() { - Map centralRepoSettings = ModuleSettings.getConfigSettings("CentralRepository"); + Map centralRepoSettings = ModuleSettings.getConfigSettings(CentralRepoSettings.getInstance().getModuleSettingsKey()); String initializedStr = centralRepoSettings.get("initialized"); // check to see if the repo has been initialized asking to setup cr @@ -114,7 +170,7 @@ public class Installer extends ModuleInstall { // if it has been previously set up and is in use, mark as previously initialized and save the settings if (prevRepo) { initialized = true; - ModuleSettings.setConfigSetting("CentralRepository", "initialized", "true"); + ModuleSettings.setConfigSetting(CentralRepoSettings.getInstance().getModuleSettingsKey(), "initialized", "true"); } } @@ -140,7 +196,7 @@ public class Installer extends ModuleInstall { doMessageBoxIfRunningInGUI(ex); } - ModuleSettings.setConfigSetting("CentralRepository", "initialized", "true"); + ModuleSettings.setConfigSetting(CentralRepoSettings.getInstance().getModuleSettingsKey(), "initialized", "true"); } /** @@ -184,9 +240,5 @@ public class Installer extends ModuleInstall { * * THIS CODE IS NEVER EXECUTED. */ - caseEventListener.uninstallListeners(); - caseEventListener.shutdown(); - ingestEventListener.shutdown(); - ingestEventListener.uninstallListeners(); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/Bundle.properties index 8ca571695b..b00d4d5540 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/Bundle.properties @@ -1,4 +1,5 @@ IngestSettingsPanel.ingestSettingsLabel.text=Ingest Settings IngestSettingsPanel.flagTaggedNotableItemsCheckbox.text=Flag items previously tagged as notable -IngestSettingsPanel.flagPreviouslySeenDevicesCheckbox.text=Flag devices previously seen in other cases +IngestSettingsPanel.flagPreviouslySeenDevicesCheckbox.text=Flag devices and users previously seen in other cases IngestSettingsPanel.createCorrelationPropertiesCheckbox.text=Save items to the Central Repository +IngestSettingsPanel.flagUniqueAppsCheckbox.text=Flag apps and domains not seen in other cases diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/Bundle.properties-MERGED index 46a2f01a64..cf2f2f4a12 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/Bundle.properties-MERGED @@ -1,15 +1,30 @@ -CentralRepoIngestModel_name_header=Name:
-CentralRepoIngestModel_previous_case_header=
Previous Cases:
-CentralRepoIngestModule.errorMessage.isNotEnabled=Central repository settings are not initialized, cannot run Central Repository ingest module. -CentralRepoIngestModule.notfyBubble.title=Central Repository Not Initialized -CentralRepoIngestModule.prevCaseComment.text=Previous Case: -CentralRepoIngestModule.prevTaggedSet.text=Previously Tagged As Notable (Central Repository) -CentralRepoIngestModule_notable_message_header=A file in this data source was previously seen and tagged as Notable.
-# {0} - Name of file that is Notable -CentralRepoIngestModule_postToBB_knownBadMsg=Notable: {0} +CentralRepoIngestModule_artifact_type_inbox_msg_header=Artifact Type +CentralRepoIngestModule_cannotGetCrCaseErrMsg=Case not present in the central repository +CentralRepoIngestModule_cannotGetCrDataSourceErrMsg=Data source not present in the central repository +CentralRepoIngestModule_crDatabaseTypeMismatch=Mulit-user cases require a PostgreSQL central repository +CentralRepoIngestModule_crInaccessibleErrMsg=Error accessing central repository +CentralRepoIngestModule_crNotEnabledErrMsg=Central repository required, but not enabled +CentralRepoIngestModule_filename_inbox_msg_header=File Name +CentralRepoIngestModule_md5Hash_inbox_msg_header=MD5 Hash +CentralRepoIngestModule_missingFileCorrAttrTypeErrMsg=Correlation attribute type for files not found in the central repository +CentralRepoIngestModule_noCurrentCaseErrMsg=Error getting current case +CentralRepoIngestModule_notable_attr_inbox_msg_header=Notable Attribute +# {0} - Name of item that is Notable +CentralRepoIngestModule_notable_inbox_msg_subject=Notable: {0} +# {0} - list of cases +CentralRepoIngestModule_notableJustification=Previously marked as notable in cases {0} +CentralRepoIngestModule_notableSetName=Previously Tagged As Notable (Central Repository) +CentralRepoIngestModule_prev_cases_inbox_msg_header=Previous Cases +# {0} - list of cases +CentralRepoIngestModule_prevSeenJustification=Previously seen in cases {0} +CentralRepoIngestModule_prevSeenOsAcctConfig=Previously Seen Users (Central Repository) +CentralRepoIngestModule_prevSeenOsAcctSetName=Users seen in previous cases +CentralRepoIngestModule_prevSeenSetName=Previously Seen (Central Repository) +CentralRepoIngestModule_prevUnseenJustification=Previously seen in zero cases CentralRepoIngestModuleFactory.ingestmodule.desc=Saves properties to the central repository for later correlation CentralRepoIngestModuleFactory.ingestmodule.name=Central Repository IngestSettingsPanel.ingestSettingsLabel.text=Ingest Settings IngestSettingsPanel.flagTaggedNotableItemsCheckbox.text=Flag items previously tagged as notable -IngestSettingsPanel.flagPreviouslySeenDevicesCheckbox.text=Flag devices previously seen in other cases +IngestSettingsPanel.flagPreviouslySeenDevicesCheckbox.text=Flag devices and users previously seen in other cases IngestSettingsPanel.createCorrelationPropertiesCheckbox.text=Save items to the Central Repository +IngestSettingsPanel.flagUniqueAppsCheckbox.text=Flag apps and domains not seen in other cases diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/Bundle_ja.properties index 818a329626..ffdcbdad8c 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/Bundle_ja.properties @@ -1,7 +1,16 @@ -#Tue Aug 18 18:09:20 UTC 2020 +#Thu Sep 30 10:26:59 UTC 2021 CentralRepoIngestModel_name_header=\u540d\u524d\uff1a
CentralRepoIngestModel_previous_case_header=
\u4ee5\u524d\u306e\u30b1\u30fc\u30b9\uff1a
-CentralRepoIngestModule.errorMessage.isNotEnabled=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u8a2d\u5b9a\u304c\u521d\u671f\u5316\u3055\u308c\u3066\u3044\u306a\u3044\u305f\u3081\u3001\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u53d6\u8fbc\u30e2\u30b8\u30e5\u30fc\u30eb\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093\u3002 +CentralRepoIngestModule.errorMessage.isNotEnabled=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u8a2d\u5b9a\u304c\u521d\u671f\u5316\u3055\u308c\u3066\u304a\u3089\u305a\u3001\u53d6\u308a\u8fbc\u307f\u30e2\u30b8\u30e5\u30fc\u30eb\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093\u3002 +CentralRepoIngestModule.notfyBubble.title=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u304c\u521d\u671f\u5316\u3055\u308c\u3066\u3044\u307e\u305b\u3093 +CentralRepoIngestModule.prevCaseComment.text=\u524d\u306e\u30b1\u30fc\u30b9\uff1a +CentralRepoIngestModule.prevTaggedSet.text=\u4ee5\u524d\u306b\u6ce8\u76ee\u3059\u3079\u304d\u3068\u3057\u3066\u30bf\u30b0\u4ed8\u3051\u3055\u308c\u305f\uff08\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\uff09 +CentralRepoIngestModuleFactory.ingestmodule.desc=\u8ffd\u3063\u3066\u76f8\u95a2\u7528\u306b\u3001\u30d7\u30ed\u30d1\u30c6\u30a3\u3092\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306b\u4fdd\u5b58\u3057\u307e\u3059 CentralRepoIngestModuleFactory.ingestmodule.name=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea CentralRepoIngestModule_notable_message_header=\u3053\u306e\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u5185\u306e\u30d5\u30a1\u30a4\u30eb\u306f\u4ee5\u524d\u300c\u6ce8\u76ee\u300d\u3068\u3057\u3066\u30bf\u30b0\u4ed8\u3051\u3055\u308c\u3066\u3044\u307e\u3059\u3002
CentralRepoIngestModule_postToBB_knownBadMsg=\u6ce8\u76ee\uff1a {0} +IngestSettingsPanel.createCorrelationPropertiesCheckbox.text=\u30a2\u30a4\u30c6\u30e0\u3092\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306b\u4fdd\u5b58\u3059\u308b +IngestSettingsPanel.flagPreviouslySeenDevicesCheckbox.text=\u4ee5\u524d\u306b\u4ed6\u306e\u30b1\u30fc\u30b9\u3067\u8a8d\u8b58\u3055\u308c\u305f\u30c7\u30d0\u30a4\u30b9\u3068\u30e6\u30fc\u30b6\u30fc\u306b\u30d5\u30e9\u30b0\u3092\u7acb\u3066\u308b +IngestSettingsPanel.flagTaggedNotableItemsCheckbox.text=\u4ee5\u524d\u306b\u6ce8\u76ee\u3059\u3079\u304d\u30bf\u30b0\u4ed8\u3051\u305f\u30a2\u30a4\u30c6\u30e0\u306b\u30d5\u30e9\u30b0\u3092\u4ed8\u3051\u308b +IngestSettingsPanel.flagUniqueAppsCheckbox.text=\u4ed6\u306e\u30b1\u30fc\u30b9\u3067\u306f\u8a8d\u8b58\u3055\u308c\u3066\u306a\u3044\u30a2\u30d7\u30ea\u3068\u30c9\u30e1\u30a4\u30f3\u306b\u30d5\u30e9\u30b0\u3092\u7acb\u3066\u308b +IngestSettingsPanel.ingestSettingsLabel.text=\u53d6\u8fbc\u307f\u8a2d\u5b9a diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoDataArtifactIngestModule.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoDataArtifactIngestModule.java new file mode 100755 index 0000000000..0729cad326 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoDataArtifactIngestModule.java @@ -0,0 +1,345 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021-2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.centralrepository.ingestmodule; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import org.apache.commons.lang3.StringUtils; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoPlatforms; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource; +import static org.sleuthkit.autopsy.centralrepository.ingestmodule.CentralRepoIngestModuleUtils.getOccurrencesInOtherCases; +import static org.sleuthkit.autopsy.centralrepository.ingestmodule.CentralRepoIngestModuleUtils.makePrevNotableAnalysisResult; +import static org.sleuthkit.autopsy.centralrepository.ingestmodule.CentralRepoIngestModuleUtils.makePrevSeenAnalysisResult; +import static org.sleuthkit.autopsy.centralrepository.ingestmodule.CentralRepoIngestModuleUtils.makePrevUnseenAnalysisResult; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.DataArtifactIngestModule; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.datamodel.DataArtifact; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.OsAccount; +import org.sleuthkit.datamodel.OsAccountManager; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * A data artifact ingest module that adds correlation attributes for data + * artifacts and OS accounts to the central repository and makes analysis + * results based on previous occurences. When the ingest job is completed, + * ensures the data source in the central repository has hash values that match + * those in the case database. + */ +public class CentralRepoDataArtifactIngestModule implements DataArtifactIngestModule { + + private static final Logger LOGGER = Logger.getLogger(CentralRepoDataArtifactIngestModule.class.getName()); + private final boolean flagNotableItems; + private final boolean flagPrevSeenDevices; + private final boolean flagUniqueArtifacts; + private final boolean saveCorrAttrInstances; + private final Set corrAttrValuesAlreadyProcessed; + private CentralRepository centralRepo; + private IngestJobContext context; + + /** + * Constructs a data artifact ingest module that adds correlation attributes + * for data artifacts and OS accounts to the central repository and makes + * analysis results based on previous occurences. When the ingest job is + * completed, ensures the data source in the central repository has hash + * values that match those in the case database. + * + * @param settings The ingest job settings for this module. + */ + CentralRepoDataArtifactIngestModule(IngestSettings settings) { + flagNotableItems = settings.isFlagTaggedNotableItems(); + flagPrevSeenDevices = settings.isFlagPreviousDevices(); + flagUniqueArtifacts = settings.isFlagUniqueArtifacts(); + saveCorrAttrInstances = settings.shouldCreateCorrelationProperties(); + corrAttrValuesAlreadyProcessed = new LinkedHashSet<>(); + } + + @NbBundle.Messages({ + "CentralRepoIngestModule_crNotEnabledErrMsg=Central repository required, but not enabled", + "CentralRepoIngestModule_crInaccessibleErrMsg=Error accessing central repository", + "CentralRepoIngestModule_noCurrentCaseErrMsg=Error getting current case", + "CentralRepoIngestModule_crDatabaseTypeMismatch=Mulit-user cases require a PostgreSQL central repository" + }) + @Override + public void startUp(IngestJobContext context) throws IngestModuleException { + this.context = context; + + if (!CentralRepository.isEnabled()) { + throw new IngestModuleException(Bundle.CentralRepoIngestModule_crNotEnabledErrMsg()); // May be displayed to user. + } + + try { + centralRepo = CentralRepository.getInstance(); + } catch (CentralRepoException ex) { + throw new IngestModuleException(Bundle.CentralRepoIngestModule_crInaccessibleErrMsg(), ex); + } + + /* + * Don't allow a SQLite central repository to be used for a multi-user + * case. + */ + try { + Case currentCase = Case.getCurrentCaseThrows(); + if ((currentCase.getCaseType() == Case.CaseType.MULTI_USER_CASE) && (CentralRepoDbManager.getSavedDbChoice().getDbPlatform() == CentralRepoPlatforms.SQLITE)) { + throw new IngestModuleException(Bundle.CentralRepoIngestModule_crDatabaseTypeMismatch()); + } + } catch (NoCurrentCaseException ex) { + throw new IngestModuleException(Bundle.CentralRepoIngestModule_noCurrentCaseErrMsg(), ex); + } + } + + /** + * Translates the attributes of a data artifact into central repository + * correlation attributes and uses them to create analysis results and new + * central repository correlation attribute instances, depending on ingest + * job settings. + * + * @param artifact The data artifact. + * + * @return An ingest module process result. + */ + @Override + public ProcessResult process(DataArtifact artifact) { + if (flagNotableItems || flagPrevSeenDevices || flagUniqueArtifacts || saveCorrAttrInstances) { + for (CorrelationAttributeInstance corrAttr : CorrelationAttributeUtil.makeCorrAttrsToSave(artifact)) { + if (corrAttrValuesAlreadyProcessed.add(corrAttr.toString())) { + makeAnalysisResults(artifact, corrAttr); + if (saveCorrAttrInstances) { + try { + centralRepo.addAttributeInstanceBulk(corrAttr); + } catch (CentralRepoException ex) { + LOGGER.log(Level.SEVERE, String.format("Error adding correlation attribute '%s' to central repository for '%s' (job ID=%d)", corrAttr, artifact, context.getJobId()), ex); //NON-NLS + } + } + } + } + } + return ProcessResult.OK; + } + + /** + * Makes analysis results for a data artifact based on previous occurrences, + * if any, of a correlation attribute. + * + * @param artifact The data artifact. + * @param corrAttr A correlation attribute for the data artifact. + */ + private void makeAnalysisResults(DataArtifact artifact, CorrelationAttributeInstance corrAttr) { + List previousOccurrences = null; + if (flagNotableItems) { + previousOccurrences = getOccurrencesInOtherCases(corrAttr, context.getJobId()); + if (!previousOccurrences.isEmpty()) { + Set previousCases = new HashSet<>(); + for (CorrelationAttributeInstance occurrence : previousOccurrences) { + if (occurrence.getKnownStatus() == TskData.FileKnown.BAD) { + previousCases.add(occurrence.getCorrelationCase().getDisplayName()); + } + } + if (!previousCases.isEmpty()) { + makePrevNotableAnalysisResult(artifact, previousCases, corrAttr.getCorrelationType(), corrAttr.getCorrelationValue(), context.getDataSource().getId(), context.getJobId()); + } + } + } + + if (flagPrevSeenDevices + && (corrAttr.getCorrelationType().getId() == CorrelationAttributeInstance.USBID_TYPE_ID + || corrAttr.getCorrelationType().getId() == CorrelationAttributeInstance.ICCID_TYPE_ID + || corrAttr.getCorrelationType().getId() == CorrelationAttributeInstance.IMEI_TYPE_ID + || corrAttr.getCorrelationType().getId() == CorrelationAttributeInstance.IMSI_TYPE_ID + || corrAttr.getCorrelationType().getId() == CorrelationAttributeInstance.MAC_TYPE_ID + || corrAttr.getCorrelationType().getId() == CorrelationAttributeInstance.EMAIL_TYPE_ID + || corrAttr.getCorrelationType().getId() == CorrelationAttributeInstance.PHONE_TYPE_ID)) { + if (previousOccurrences == null) { + previousOccurrences = getOccurrencesInOtherCases(corrAttr, context.getJobId()); + } + if (!previousOccurrences.isEmpty()) { + Set previousCases = getPreviousCases(previousOccurrences); + if (!previousCases.isEmpty()) { + makePrevSeenAnalysisResult(artifact, previousCases, corrAttr.getCorrelationType(), corrAttr.getCorrelationValue(), context.getDataSource().getId(), context.getJobId()); + } + } + } + + if (flagUniqueArtifacts + && (corrAttr.getCorrelationType().getId() == CorrelationAttributeInstance.INSTALLED_PROGS_TYPE_ID + || corrAttr.getCorrelationType().getId() == CorrelationAttributeInstance.DOMAIN_TYPE_ID)) { + if (previousOccurrences == null) { + previousOccurrences = getOccurrencesInOtherCases(corrAttr, context.getJobId()); + } + if (previousOccurrences.isEmpty()) { + makePrevUnseenAnalysisResult(artifact, corrAttr.getCorrelationType(), corrAttr.getCorrelationValue(), context.getDataSource().getId(), context.getJobId()); + } + } + } + + /** + * Gets a unique set of previous cases, represented by their names, from a + * list of previous occurrences of correlation attributes. + * + * @param previousOccurrences The correlations attributes. + * + * @return The names of the previous cases. + */ + private Set getPreviousCases(List previousOccurrences) { + Set previousCases = new HashSet<>(); + for (CorrelationAttributeInstance occurrence : previousOccurrences) { + previousCases.add(occurrence.getCorrelationCase().getDisplayName()); + } + return previousCases; + } + + @Override + public void shutDown() { + analyzeOsAccounts(); + if (saveCorrAttrInstances) { + try { + centralRepo.commitAttributeInstancesBulk(); + } catch (CentralRepoException ex) { + LOGGER.log(Level.SEVERE, String.format("Error doing final bulk commit of correlation attributes (job ID=%d)", context.getJobId()), ex); // NON-NLS + } + } + syncDataSourceHashes(); + } + + /** + * Queries the case database for any OS accounts assoicated with the data + * source for the ingest job. The attributes of any OS account returned by + * the query are translated into central repository correlation attributes + * and used them to create analysis results and new central repository + * correlation attribute instances, depending on ingest job settings. + */ + @NbBundle.Messages({ + "CentralRepoIngestModule_prevSeenOsAcctSetName=Users seen in previous cases", + "CentralRepoIngestModule_prevSeenOsAcctConfig=Previously Seen Users (Central Repository)" + }) + private void analyzeOsAccounts() { + if (saveCorrAttrInstances || flagPrevSeenDevices) { + try { + OsAccountManager osAccountMgr = Case.getCurrentCaseThrows().getSleuthkitCase().getOsAccountManager(); + List osAccounts = osAccountMgr.getOsAccountsByDataSourceObjId(context.getDataSource().getId()); + for (OsAccount osAccount : osAccounts) { + for (CorrelationAttributeInstance corrAttr : CorrelationAttributeUtil.makeCorrAttrsToSave(osAccount, context.getDataSource())) { + if (flagPrevSeenDevices) { + makeAnalysisResults(osAccount, corrAttr); + } + if (saveCorrAttrInstances) { + try { + centralRepo.addAttributeInstanceBulk(corrAttr); + } catch (CentralRepoException ex) { + LOGGER.log(Level.SEVERE, String.format("Error adding correlation attribute '%s' to central repository for '%s'(job ID=%d)", corrAttr, osAccount, context.getJobId()), ex); //NON-NLS + } + } + } + } + } catch (NoCurrentCaseException | TskCoreException ex) { + LOGGER.log(Level.SEVERE, String.format("Error getting OS accounts for data source '%s' (job ID=%d)", context.getDataSource(), context.getJobId()), ex); + } + } + } + + /** + * Makes analysis results for an OS Account based on previous occurrences, + * if any, of a correlation attribute. + * + * @param artifact The data artifact. + * @param corrAttr A correlation attribute for the data artifact. + */ + private void makeAnalysisResults(OsAccount osAccount, CorrelationAttributeInstance corrAttr) { + if (flagPrevSeenDevices) { + List previousOccurrences = getOccurrencesInOtherCases(corrAttr, context.getJobId()); + if (!previousOccurrences.isEmpty()) { + Set previousCases = getPreviousCases(previousOccurrences); + if (!previousCases.isEmpty()) { + makePrevSeenAnalysisResult(osAccount, previousCases, corrAttr.getCorrelationType(), corrAttr.getCorrelationValue(), context.getDataSource().getId(), context.getJobId()); + } + } + } + } + + /** + * Ensures the data source in the central repository has hash values that + * match those in the case database. + */ + private void syncDataSourceHashes() { + if (!(context.getDataSource() instanceof Image)) { + return; + } + + try { + Case currentCase = Case.getCurrentCaseThrows(); + CorrelationCase correlationCase = centralRepo.getCase(currentCase); + if (correlationCase == null) { + correlationCase = centralRepo.newCase(currentCase); + } + + CorrelationDataSource correlationDataSource = centralRepo.getDataSource(correlationCase, context.getDataSource().getId()); + if (correlationDataSource == null) { + correlationDataSource = CorrelationDataSource.fromTSKDataSource(correlationCase, context.getDataSource()); + } + + Image image = (Image) context.getDataSource(); + String imageMd5Hash = image.getMd5(); + if (imageMd5Hash == null) { + imageMd5Hash = ""; + } + String crMd5Hash = correlationDataSource.getMd5(); + if (StringUtils.equals(imageMd5Hash, crMd5Hash) == false) { + correlationDataSource.setMd5(imageMd5Hash); + } + + String imageSha1Hash = image.getSha1(); + if (imageSha1Hash == null) { + imageSha1Hash = ""; + } + String crSha1Hash = correlationDataSource.getSha1(); + if (StringUtils.equals(imageSha1Hash, crSha1Hash) == false) { + correlationDataSource.setSha1(imageSha1Hash); + } + + String imageSha256Hash = image.getSha256(); + if (imageSha256Hash == null) { + imageSha256Hash = ""; + } + String crSha256Hash = correlationDataSource.getSha256(); + if (StringUtils.equals(imageSha256Hash, crSha256Hash) == false) { + correlationDataSource.setSha256(imageSha256Hash); + } + + } catch (CentralRepoException ex) { + LOGGER.log(Level.SEVERE, String.format("Error fetching data from the central repository for data source '%s' (job ID=%d)", context.getDataSource().getName(), context.getJobId()), ex); + } catch (NoCurrentCaseException | TskCoreException ex) { + LOGGER.log(Level.SEVERE, String.format("Error fetching data from the case database for data source '%s' (job ID=%d)", context.getDataSource().getName(), context.getJobId()), ex); + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModule.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModule.java index 210e361261..49e3fe2845 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModule.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2018-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,11 +18,10 @@ */ package org.sleuthkit.autopsy.centralrepository.ingestmodule; -import java.util.Arrays; -import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.logging.Level; -import java.util.stream.Collectors; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -32,89 +31,52 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; -import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoPlatforms; -import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager; -import org.sleuthkit.autopsy.centralrepository.eventlisteners.IngestEventsListener; -import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.healthmonitor.HealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.ingest.FileIngestModule; import org.sleuthkit.autopsy.ingest.IngestJobContext; -import org.sleuthkit.autopsy.ingest.IngestMessage; import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; -import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Blackboard; -import org.sleuthkit.datamodel.BlackboardArtifact; -import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT; -import org.sleuthkit.datamodel.BlackboardAttribute; -import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT; -import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME; import org.sleuthkit.datamodel.HashUtility; -import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import static org.sleuthkit.autopsy.centralrepository.ingestmodule.CentralRepoIngestModuleUtils.makePrevNotableAnalysisResult; /** - * Ingest module for inserting entries into the Central Repository database on - * ingest of a data source + * A file ingest module that adds correlation attributes for files to the + * central repository, and makes previously notable analysis results for files + * marked as notable in other cases. */ -@Messages({"CentralRepoIngestModule.prevTaggedSet.text=Previously Tagged As Notable (Central Repository)", - "CentralRepoIngestModule.prevCaseComment.text=Previous Case: "}) final class CentralRepoIngestModule implements FileIngestModule { - private static final String MODULE_NAME = CentralRepoIngestModuleFactory.getModuleName(); - - static final boolean DEFAULT_FLAG_TAGGED_NOTABLE_ITEMS = false; - static final boolean DEFAULT_FLAG_PREVIOUS_DEVICES = false; - static final boolean DEFAULT_CREATE_CR_PROPERTIES = true; - - private final static Logger logger = Logger.getLogger(CentralRepoIngestModule.class.getName()); - private final IngestServices services = IngestServices.getInstance(); + private static final Logger logger = Logger.getLogger(CentralRepoIngestModule.class.getName()); private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); - private static final IngestModuleReferenceCounter warningMsgRefCounter = new IngestModuleReferenceCounter(); - private long jobId; - private CorrelationCase eamCase; - private CorrelationDataSource eamDataSource; + private final boolean flagNotableItems; + private final boolean saveCorrAttrInstances; private CorrelationAttributeInstance.Type filesType; - private final boolean flagTaggedNotableItems; - private final boolean flagPreviouslySeenDevices; - private Blackboard blackboard; - private final boolean createCorrelationProperties; - + private IngestJobContext context; + private CentralRepository centralRepo; + /** - * Instantiate the Central Repository ingest module. + * Constructs a file ingest module that adds correlation attributes for + * files to the central repository, and makes previously notable analysis + * results for files marked as notable in other cases. * - * @param settings The ingest settings for the module instance. + * @param settings The ingest job settings. */ CentralRepoIngestModule(IngestSettings settings) { - flagTaggedNotableItems = settings.isFlagTaggedNotableItems(); - flagPreviouslySeenDevices = settings.isFlagPreviousDevices(); - createCorrelationProperties = settings.shouldCreateCorrelationProperties(); - } - + flagNotableItems = settings.isFlagTaggedNotableItems(); + saveCorrAttrInstances = settings.shouldCreateCorrelationProperties(); + } + @Override public ProcessResult process(AbstractFile abstractFile) { - if (CentralRepository.isEnabled() == false) { - /* - * Not signaling an error for now. This is a workaround for the way - * all newly didscovered ingest modules are automatically anabled. - * - * TODO (JIRA-2731): Add isEnabled API for ingest modules. - */ + if (!flagNotableItems && !saveCorrAttrInstances) { return ProcessResult.OK; } - try { - blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); - } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Exception while getting open case.", ex); - return ProcessResult.ERROR; - } - - if (!CorrelationAttributeUtil.isSupportedAbstractFileType(abstractFile)) { + if (!filesType.isEnabled()) { return ProcessResult.OK; } @@ -122,277 +84,119 @@ final class CentralRepoIngestModule implements FileIngestModule { return ProcessResult.OK; } - CentralRepository dbManager; - try { - dbManager = CentralRepository.getInstance(); - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); - return ProcessResult.ERROR; - } - - // only continue if we are correlating filesType - if (!filesType.isEnabled()) { + if (!CorrelationAttributeUtil.isSupportedAbstractFileType(abstractFile)) { return ProcessResult.OK; } - - // get the hash because we're going to correlate it + + /* + * The correlation attribute value for a file is its MD5 hash. This + * module cannot do anything with a file if the hash calculation has not + * been done, but the decision has been made to not do a hash + * calculation here if the file hashing and lookup module is not in this + * pipeline ahead of this module (affirmed per BC, 11/8/21). + */ String md5 = abstractFile.getMd5Hash(); if ((md5 == null) || (HashUtility.isNoDataMd5(md5))) { return ProcessResult.OK; } - /* - * Search the central repo to see if this file was previously marked as - * being bad. Create artifact if it was. - */ - if (abstractFile.getKnown() != TskData.FileKnown.KNOWN && flagTaggedNotableItems) { + if (flagNotableItems) { try { TimingMetric timingMetric = HealthMonitor.getTimingMetric("Central Repository: Notable artifact query"); - List caseDisplayNamesList = dbManager.getListCasesHavingArtifactInstancesKnownBad(filesType, md5); + Set otherCases = new HashSet<>(); + otherCases.addAll(centralRepo.getListCasesHavingArtifactInstancesKnownBad(filesType, md5)); HealthMonitor.submitTimingMetric(timingMetric); - if (!caseDisplayNamesList.isEmpty()) { - postCorrelatedBadFileToBlackboard(abstractFile, caseDisplayNamesList); + if (!otherCases.isEmpty()) { + makePrevNotableAnalysisResult(abstractFile, otherCases, filesType, md5, context.getDataSource().getId(), context.getJobId()); } } catch (CentralRepoException ex) { logger.log(Level.SEVERE, "Error searching database for artifact.", ex); // NON-NLS - return ProcessResult.ERROR; } catch (CorrelationAttributeNormalizationException ex) { - logger.log(Level.INFO, "Error searching database for artifact.", ex); // NON-NLS - return ProcessResult.ERROR; + logger.log(Level.INFO, "Error searching database for artifact: " + ex.getMessage()); // NON-NLS } } - // insert this file into the central repository - if (createCorrelationProperties) { - try { - CorrelationAttributeInstance cefi = new CorrelationAttributeInstance( - filesType, - md5, - eamCase, - eamDataSource, - abstractFile.getParentPath() + abstractFile.getName(), - null, - TskData.FileKnown.UNKNOWN // NOTE: Known status in the CR is based on tagging, not hashes like the Case Database. - , - abstractFile.getId()); - dbManager.addAttributeInstanceBulk(cefi); - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "Error adding artifact to bulk artifacts.", ex); // NON-NLS - return ProcessResult.ERROR; - } catch (CorrelationAttributeNormalizationException ex) { - logger.log(Level.INFO, "Error adding artifact to bulk artifacts.", ex); // NON-NLS - return ProcessResult.ERROR; + if (saveCorrAttrInstances) { + List corrAttrs = CorrelationAttributeUtil.makeCorrAttrsToSave(abstractFile); + for (CorrelationAttributeInstance corrAttr : corrAttrs) { + try { + centralRepo.addAttributeInstanceBulk(corrAttr); + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, "Error adding artifact to bulk artifacts.", ex); // NON-NLS + } } } + return ProcessResult.OK; } @Override public void shutDown() { - IngestEventsListener.decrementCorrelationEngineModuleCount(); - - if ((CentralRepository.isEnabled() == false) || (eamCase == null) || (eamDataSource == null)) { - return; + if (refCounter.decrementAndGet(context.getJobId()) == 0) { + try { + centralRepo.commitAttributeInstancesBulk(); + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, String.format("Error committing bulk insert of correlation attributes (job ID=%d)", context.getJobId()), ex); // NON-NLS + } } - CentralRepository dbManager; - try { - dbManager = CentralRepository.getInstance(); - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); - return; - } - try { - dbManager.commitAttributeInstancesBulk(); - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "Error doing bulk insert of artifacts.", ex); // NON-NLS - } - try { - Long count = dbManager.getCountArtifactInstancesByCaseDataSource(eamDataSource); - logger.log(Level.INFO, "{0} artifacts in db for case: {1} ds:{2}", new Object[]{count, eamCase.getDisplayName(), eamDataSource.getName()}); // NON-NLS - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "Error counting artifacts.", ex); // NON-NLS - } - - // TODO: once we implement shared cache, if refCounter is 1, then submit data in bulk. - refCounter.decrementAndGet(jobId); - } - - // see ArtifactManagerTimeTester for details + } + @Messages({ - "CentralRepoIngestModule.notfyBubble.title=Central Repository Not Initialized", - "CentralRepoIngestModule.errorMessage.isNotEnabled=Central repository settings are not initialized, cannot run Central Repository ingest module." + "CentralRepoIngestModule_missingFileCorrAttrTypeErrMsg=Correlation attribute type for files not found in the central repository", + "CentralRepoIngestModule_cannotGetCrCaseErrMsg=Case not present in the central repository", + "CentralRepoIngestModule_cannotGetCrDataSourceErrMsg=Data source not present in the central repository" }) @Override public void startUp(IngestJobContext context) throws IngestModuleException { - IngestEventsListener.incrementCorrelationEngineModuleCount(); + this.context = context; + + if (!CentralRepository.isEnabled()) { + throw new IngestModuleException(Bundle.CentralRepoIngestModule_crNotEnabledErrMsg()); + } + + try { + centralRepo = CentralRepository.getInstance(); + } catch (CentralRepoException ex) { + throw new IngestModuleException(Bundle.CentralRepoIngestModule_crInaccessibleErrMsg(), ex); + } /* - * Tell the IngestEventsListener to flag notable items based on the - * current module's configuration. This is a work around for the lack of - * an artifacts pipeline. Note that this can be changed by another - * module instance. All modules are affected by the value. While not - * ideal, this will be good enough until a better solution can be - * posited. - * - * Note: Flagging cannot be disabled if any other instances of the - * Central Repository module are running. This restriction is to prevent - * missing results in the case where the first module is flagging - * notable items, and the proceeding module (with flagging disabled) - * causes the first to stop flagging. + * Make sure the correlation attribute type definition is in the central + * repository. Currently (11/8/21) it is cached, but there is no harm in + * saving it here for use in process(). */ - if (IngestEventsListener.getCeModuleInstanceCount() == 1 || !IngestEventsListener.isFlagNotableItems()) { - IngestEventsListener.setFlagNotableItems(flagTaggedNotableItems); - } - if (IngestEventsListener.getCeModuleInstanceCount() == 1 || !IngestEventsListener.isFlagSeenDevices()) { - IngestEventsListener.setFlagSeenDevices(flagPreviouslySeenDevices); - } - if (IngestEventsListener.getCeModuleInstanceCount() == 1 || !IngestEventsListener.shouldCreateCrProperties()) { - IngestEventsListener.setCreateCrProperties(createCorrelationProperties); - } - - if (CentralRepository.isEnabled() == false) { - /* - * Not throwing the customary exception for now. This is a - * workaround for the way all newly didscovered ingest modules are - * automatically anabled. - * - * TODO (JIRA-2731): Add isEnabled API for ingest modules. - */ - if (RuntimeProperties.runningWithGUI()) { - if (1L == warningMsgRefCounter.incrementAndGet(jobId)) { - MessageNotifyUtil.Notify.warn(Bundle.CentralRepoIngestModule_notfyBubble_title(), Bundle.CentralRepoIngestModule_errorMessage_isNotEnabled()); - } - } - return; - } - Case autopsyCase; try { - autopsyCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Exception while getting open case.", ex); - throw new IngestModuleException("Exception while getting open case.", ex); - } - - // Don't allow sqlite central repo databases to be used for multi user cases - if ((autopsyCase.getCaseType() == Case.CaseType.MULTI_USER_CASE) - && (CentralRepoDbManager.getSavedDbChoice().getDbPlatform() == CentralRepoPlatforms.SQLITE)) { - logger.log(Level.SEVERE, "Cannot run Central Repository ingest module on a multi-user case with a SQLite central repository."); - throw new IngestModuleException("Cannot run on a multi-user case with a SQLite central repository."); // NON-NLS - } - jobId = context.getJobId(); - - CentralRepository centralRepoDb; - try { - centralRepoDb = CentralRepository.getInstance(); + filesType = centralRepo.getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID); } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "Error connecting to central repository database.", ex); // NON-NLS - throw new IngestModuleException("Error connecting to central repository database.", ex); // NON-NLS + throw new IngestModuleException(Bundle.CentralRepoIngestModule_missingFileCorrAttrTypeErrMsg(), ex); } - try { - filesType = centralRepoDb.getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID); - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "Error getting correlation type FILES in ingest module start up.", ex); // NON-NLS - throw new IngestModuleException("Error getting correlation type FILES in ingest module start up.", ex); // NON-NLS - } - - try { - eamCase = centralRepoDb.getCase(autopsyCase); - } catch (CentralRepoException ex) { - throw new IngestModuleException("Unable to get case from central repository database ", ex); - } - - try { - eamDataSource = CorrelationDataSource.fromTSKDataSource(eamCase, context.getDataSource()); - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "Error getting data source info.", ex); // NON-NLS - throw new IngestModuleException("Error getting data source info.", ex); // NON-NLS - } - // TODO: once we implement a shared cache, load/init it here w/ syncronized and define reference counter - // if we are the first thread / module for this job, then make sure the case - // and image exist in the DB before we associate artifacts with it. - if (refCounter.incrementAndGet(jobId) - == 1) { - // ensure we have this data source in the EAM DB + /* + * The first module instance started for this job makes sure the current + * case and data source are in the central repository. Currently + * (11/8/21), these are cached upon creation / first retreival. + */ + if (refCounter.incrementAndGet(context.getJobId()) == 1) { + Case currentCase; try { - if (null == centralRepoDb.getDataSource(eamCase, eamDataSource.getDataSourceObjectID())) { - centralRepoDb.newDataSource(eamDataSource); - } + currentCase = Case.getCurrentCaseThrows(); + } catch (NoCurrentCaseException ex) { + throw new IngestModuleException(Bundle.CentralRepoIngestModule_noCurrentCaseErrMsg(), ex); + } + + CorrelationCase centralRepoCase; + try { + centralRepoCase = centralRepo.getCase(currentCase); } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "Error adding data source to Central Repository.", ex); // NON-NLS - throw new IngestModuleException("Error adding data source to Central Repository.", ex); // NON-NLS + throw new IngestModuleException(Bundle.CentralRepoIngestModule_cannotGetCrCaseErrMsg(), ex); } - } - } - - /** - * Post a new interesting artifact for the file marked bad. - * - * @param abstractFile The file from which to create an artifact. - * @param caseDisplayNames Case names to be added to a TSK_COMMON attribute. - */ - private void postCorrelatedBadFileToBlackboard(AbstractFile abstractFile, List caseDisplayNames) { - - Collection attributes = Arrays.asList( - new BlackboardAttribute( - TSK_SET_NAME, MODULE_NAME, - Bundle.CentralRepoIngestModule_prevTaggedSet_text()), - new BlackboardAttribute( - TSK_COMMENT, MODULE_NAME, - Bundle.CentralRepoIngestModule_prevCaseComment_text() + caseDisplayNames.stream().distinct().collect(Collectors.joining(",")))); - try { - - // Create artifact if it doesn't already exist. - if (!blackboard.artifactExists(abstractFile, TSK_INTERESTING_FILE_HIT, attributes)) { - BlackboardArtifact tifArtifact = abstractFile.newArtifact(TSK_INTERESTING_FILE_HIT); - tifArtifact.addAttributes(attributes); - try { - // index the artifact for keyword search - blackboard.postArtifact(tifArtifact, MODULE_NAME); - } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, "Unable to index blackboard artifact " + tifArtifact.getArtifactID(), ex); //NON-NLS - } - // send inbox message - sendBadFileInboxMessage(tifArtifact, abstractFile.getName(), abstractFile.getMd5Hash(), caseDisplayNames); + try { + CorrelationDataSource.fromTSKDataSource(centralRepoCase, context.getDataSource()); + } catch (CentralRepoException ex) { + throw new IngestModuleException(Bundle.CentralRepoIngestModule_cannotGetCrDataSourceErrMsg(), ex); } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Failed to create BlackboardArtifact.", ex); // NON-NLS - } catch (IllegalStateException ex) { - logger.log(Level.SEVERE, "Failed to create BlackboardAttribute.", ex); // NON-NLS } } - @Messages({ - "CentralRepoIngestModule_notable_message_header=A file in this data source was previously seen and tagged as Notable.
", - "CentralRepoIngestModel_name_header=Name:
", - "CentralRepoIngestModel_previous_case_header=
Previous Cases:
", - "# {0} - Name of file that is Notable", - "CentralRepoIngestModule_postToBB_knownBadMsg=Notable: {0}" - }) - - /** - * Post a message to the ingest inbox alerting the user that a bad file was - * found. - * - * @param artifact badFile Blackboard Artifact - * @param name badFile's name - * @param md5Hash badFile's md5 hash - * @param caseDisplayNames List of cases that the artifact appears in. - */ - private void sendBadFileInboxMessage(BlackboardArtifact artifact, String name, String md5Hash, List caseDisplayNames) { - StringBuilder detailsSb = new StringBuilder(1024); - - detailsSb.append(Bundle.CentralRepoIngestModule_notable_message_header()).append(Bundle.CentralRepoIngestModel_name_header()); - detailsSb.append(name).append(Bundle.CentralRepoIngestModel_previous_case_header()); - for (String str : caseDisplayNames) { - detailsSb.append(str).append("
"); - } - detailsSb.append(""); - services.postMessage(IngestMessage.createDataMessage(CentralRepoIngestModuleFactory.getModuleName(), - Bundle.CentralRepoIngestModule_postToBB_knownBadMsg(name), - detailsSb.toString(), - name + md5Hash, - artifact)); - } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModuleFactory.java index 1c34f1ffad..8a5e122e6c 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModuleFactory.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2018 Basis Technology Corp. + * Copyright 2015-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,15 +26,18 @@ import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; import org.sleuthkit.autopsy.centralrepository.optionspanel.GlobalSettingsPanel; import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.ingest.DataArtifactIngestModule; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; import org.sleuthkit.autopsy.ingest.NoIngestModuleIngestJobSettings; /** - * Factory for Central Repository ingest modules + * Factory for Central Repository ingest modules. */ @ServiceProvider(service = org.sleuthkit.autopsy.ingest.IngestModuleFactory.class) -@NbBundle.Messages({"CentralRepoIngestModuleFactory.ingestmodule.name=Central Repository", - "CentralRepoIngestModuleFactory.ingestmodule.desc=Saves properties to the central repository for later correlation"}) +@NbBundle.Messages({ + "CentralRepoIngestModuleFactory.ingestmodule.name=Central Repository", + "CentralRepoIngestModuleFactory.ingestmodule.desc=Saves properties to the central repository for later correlation" +}) public class CentralRepoIngestModuleFactory extends IngestModuleFactoryAdapter { /** @@ -72,12 +75,12 @@ public class CentralRepoIngestModuleFactory extends IngestModuleFactoryAdapter { return new CentralRepoIngestModule((IngestSettings) settings); } /* - * Compatibility check for older versions. + * Earlier versions of the modules had no ingest job settings. Create a + * module with the default settings. */ if (settings instanceof NoIngestModuleIngestJobSettings) { - return new CentralRepoIngestModule(new IngestSettings()); + return new CentralRepoIngestModule((IngestSettings) getDefaultIngestJobSettings()); } - throw new IllegalArgumentException("Expected settings argument to be an instance of IngestSettings"); } @@ -92,7 +95,7 @@ public class CentralRepoIngestModuleFactory extends IngestModuleFactoryAdapter { globalOptionsPanel.load(); return globalOptionsPanel; } - + @Override public IngestModuleIngestJobSettings getDefaultIngestJobSettings() { return new IngestSettings(); @@ -109,13 +112,23 @@ public class CentralRepoIngestModuleFactory extends IngestModuleFactoryAdapter { return new IngestSettingsPanel((IngestSettings) settings); } /* - * Compatibility check for older versions. + * Earlier versions of the modules had no ingest job settings. Create a + * panel with the default settings. */ if (settings instanceof NoIngestModuleIngestJobSettings) { - return new IngestSettingsPanel(new IngestSettings()); + return new IngestSettingsPanel((IngestSettings) getDefaultIngestJobSettings()); } - throw new IllegalArgumentException("Expected settings argument to be an instance of IngestSettings"); } + @Override + public boolean isDataArtifactIngestModuleFactory() { + return true; + } + + @Override + public DataArtifactIngestModule createDataArtifactIngestModule(IngestModuleIngestJobSettings settings) { + return new CentralRepoDataArtifactIngestModule((IngestSettings) settings); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModuleUtils.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModuleUtils.java new file mode 100755 index 0000000000..676cb86ba3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModuleUtils.java @@ -0,0 +1,336 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021-2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.centralrepository.ingestmodule; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Level; +import java.util.stream.Collectors; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestMessage; +import org.sleuthkit.autopsy.ingest.IngestServices; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.AnalysisResult; +import org.sleuthkit.datamodel.Blackboard; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CORRELATION_TYPE; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CORRELATION_VALUE; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_OTHER_CASES; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.DataArtifact; +import org.sleuthkit.datamodel.Score; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Utility methods shared by the central repository ingest modules. + */ +class CentralRepoIngestModuleUtils { + + private static final Logger LOGGER = Logger.getLogger(CentralRepoDataArtifactIngestModule.class.getName()); + private static final int MAX_PREV_CASES_FOR_NOTABLE_SCORE = 10; + private static final int MAX_PREV_CASES_FOR_PREV_SEEN = 20; + private final static String MODULE_NAME = CentralRepoIngestModuleFactory.getModuleName(); + + /** + * Gets any previous occurrences of a given correlation attribute in cases + * other than the current case. + * + * @param corrAttr The correlation attribute. + * + * @return The other occurrences of the correlation attribute. + */ + static List getOccurrencesInOtherCases(CorrelationAttributeInstance corrAttr, long ingestJobId) { + List previousOccurrences = new ArrayList<>(); + try { + CentralRepository centralRepo = CentralRepository.getInstance(); + previousOccurrences = centralRepo.getArtifactInstancesByTypeValue(corrAttr.getCorrelationType(), corrAttr.getCorrelationValue()); + for (Iterator iterator = previousOccurrences.iterator(); iterator.hasNext();) { + CorrelationAttributeInstance prevOccurrence = iterator.next(); + if (prevOccurrence.getCorrelationCase().getCaseUUID().equals(corrAttr.getCorrelationCase().getCaseUUID())) { + iterator.remove(); + } + } + } catch (CorrelationAttributeNormalizationException ex) { + LOGGER.log(Level.WARNING, String.format("Error normalizing correlation attribute value for 's' (job ID=%d)", corrAttr, ingestJobId), ex); // NON-NLS + } catch (CentralRepoException ex) { + LOGGER.log(Level.SEVERE, String.format("Error getting previous occurences of correlation attribute 's' (job ID=%d)", corrAttr, ingestJobId), ex); // NON-NLS + } + return previousOccurrences; + } + + /** + * Makes a previously notable analysis result for a content. + * + * @param content The content. + * @param previousCases The names of the cases in which the artifact was + * deemed notable. + * @param corrAttrType The type of the matched correlation attribute. + * @param corrAttrValue The value of the matched correlation attribute. + * @param dataSourceObjId The data source object ID. + * @param ingestJobId The ingest job ID. + */ + @NbBundle.Messages({ + "CentralRepoIngestModule_notableSetName=Previously Tagged As Notable (Central Repository)", + "# {0} - list of cases", + "CentralRepoIngestModule_notableJustification=Previously marked as notable in cases {0}" + }) + static void makePrevNotableAnalysisResult(Content content, Set previousCases, CorrelationAttributeInstance.Type corrAttrType, String corrAttrValue, long dataSourceObjId, long ingestJobId) { + String prevCases = previousCases.stream().collect(Collectors.joining(",")); + String justification = Bundle.CentralRepoIngestModule_notableJustification(prevCases); + Collection attributes = Arrays.asList( + new BlackboardAttribute(TSK_SET_NAME, MODULE_NAME, Bundle.CentralRepoIngestModule_notableSetName()), + new BlackboardAttribute(TSK_CORRELATION_TYPE, MODULE_NAME, corrAttrType.getDisplayName()), + new BlackboardAttribute(TSK_CORRELATION_VALUE, MODULE_NAME, corrAttrValue), + new BlackboardAttribute(TSK_OTHER_CASES, MODULE_NAME, prevCases)); + Optional result = makeAndPostAnalysisResult(content, BlackboardArtifact.Type.TSK_PREVIOUSLY_NOTABLE, attributes, "", Score.SCORE_NOTABLE, justification, dataSourceObjId, ingestJobId); + if (result.isPresent()) { + postNotableMessage(content, previousCases, corrAttrValue, result.get()); + } + } + + /** + * Makes a previously seen analysis result for a content, unless the content + * is too common. + * + * @param content The content. + * @param previousCases The names of the cases in which the artifact was + * previously seen. + * @param corrAttrType The type of the matched correlation attribute. + * @param corrAttrValue The value of the matched correlation attribute. + * @param dataSourceObjId The data source object ID. + * @param ingestJobId The ingest job ID. + */ + @NbBundle.Messages({ + "CentralRepoIngestModule_prevSeenSetName=Previously Seen (Central Repository)", + "# {0} - list of cases", + "CentralRepoIngestModule_prevSeenJustification=Previously seen in cases {0}" + }) + static void makePrevSeenAnalysisResult(Content content, Set previousCases, CorrelationAttributeInstance.Type corrAttrType, String corrAttrValue, long dataSourceObjId, long ingestJobId) { + Optional score = calculateScore(previousCases.size()); + if (score.isPresent()) { + String prevCases = previousCases.stream().collect(Collectors.joining(",")); + String justification = Bundle.CentralRepoIngestModule_prevSeenJustification(prevCases); + Collection analysisResultAttributes = Arrays.asList( + new BlackboardAttribute(TSK_SET_NAME, MODULE_NAME, Bundle.CentralRepoIngestModule_prevSeenSetName()), + new BlackboardAttribute(TSK_CORRELATION_TYPE, MODULE_NAME, corrAttrType.getDisplayName()), + new BlackboardAttribute(TSK_CORRELATION_VALUE, MODULE_NAME, corrAttrValue), + new BlackboardAttribute(TSK_OTHER_CASES, MODULE_NAME, prevCases)); + makeAndPostAnalysisResult(content, BlackboardArtifact.Type.TSK_PREVIOUSLY_SEEN, analysisResultAttributes, "", score.get(), justification, dataSourceObjId, ingestJobId); + } + } + + /** + * Makes a previously unseen analysis result for a content. + * + * @param content The content. + * @param corrAttrType The type of the new correlation attribute. + * @param corrAttrValue The value of the new correlation attribute. + * @param dataSourceObjId The data source object ID. + * @param ingestJobId The ingest job ID. + */ + @NbBundle.Messages({ + "CentralRepoIngestModule_prevUnseenJustification=Previously seen in zero cases" + }) + static void makePrevUnseenAnalysisResult(Content content, CorrelationAttributeInstance.Type corrAttrType, String corrAttrValue, long dataSourceObjId, long ingestJobId) { + Collection attributesForNewArtifact = Arrays.asList( + new BlackboardAttribute(TSK_CORRELATION_TYPE, MODULE_NAME, corrAttrType.getDisplayName()), + new BlackboardAttribute(TSK_CORRELATION_VALUE, MODULE_NAME, corrAttrValue)); + makeAndPostAnalysisResult(content, BlackboardArtifact.Type.TSK_PREVIOUSLY_UNSEEN, attributesForNewArtifact, "", Score.SCORE_LIKELY_NOTABLE, Bundle.CentralRepoIngestModule_prevUnseenJustification(), dataSourceObjId, ingestJobId); + } + + /** + * Calculates a score based in a number of previous cases. + * + * @param numPreviousCases The number of previous cases. + * + * @return An Optional of a score, will be empty if there is no score + * because the number of previous cases is too high, indicating a + * common and therefore uninteresting item. + */ + static Optional calculateScore(int numPreviousCases) { + Score score = null; + if (numPreviousCases <= MAX_PREV_CASES_FOR_NOTABLE_SCORE) { + score = Score.SCORE_LIKELY_NOTABLE; + } else if (numPreviousCases > MAX_PREV_CASES_FOR_NOTABLE_SCORE && numPreviousCases <= MAX_PREV_CASES_FOR_PREV_SEEN) { + score = Score.SCORE_NONE; + } + return Optional.ofNullable(score); + } + + /** + * Makes a new analysis result of a given type for a content and posts it to + * the blackboard. + * + * @param content The content. + * @param analysisResultType The type of analysis result to make. + * @param analysisResultAttrs The attributes of the new analysis result. + * @param configuration The configuration for the new analysis result. + * @param score The score for the new analysis result. + * @param justification The justification for the new analysis result. + * @param dataSourceObjId The data source object ID. + * @param ingestJobId The ingest job ID. + * + * @return The analysis result or null if the result already existed or an + * error that prevented creation of the analysis result occurred. + */ + private static Optional makeAndPostAnalysisResult(Content content, BlackboardArtifact.Type analysisResultType, Collection analysisResultAttrs, String configuration, Score score, String justification, long dataSourceObjId, long ingestJobId) { + AnalysisResult analysisResult = null; + try { + Blackboard blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); + if (!blackboard.artifactExists(content, analysisResultType, analysisResultAttrs)) { + analysisResult = content.newAnalysisResult(analysisResultType, score, null, configuration, justification, analysisResultAttrs, dataSourceObjId).getAnalysisResult(); + try { + blackboard.postArtifact(analysisResult, MODULE_NAME, ingestJobId); + } catch (Blackboard.BlackboardException ex) { + LOGGER.log(Level.SEVERE, String.format("Error posting analysis result '%s' to blackboard for content 's' (job ID=%d)", analysisResult, content, ingestJobId), ex); //NON-NLS + } + } + } catch (NoCurrentCaseException | TskCoreException ex) { + LOGGER.log(Level.SEVERE, String.format("Error creating %s analysis result for content '%s' (job ID=%d)", analysisResultType, content, ingestJobId), ex); // NON-NLS + } + return Optional.ofNullable(analysisResult); + } + + /** + * Posts a message to the ingest messages inbox to notify the user that a + * notable content has been found, i.e., a previously notable analysis + * result has been created. + * + * @param content The notable content. + * @param otherCases The other cases in which the content was marked as + * notable. + * @param corrAttrValue The correlation attribute value used to identify + * the content, used by the ingest inbox as a unique + * key for message grouping. + * @param analysisResult The previously notable analysis result. + */ + @NbBundle.Messages({ + "# {0} - Name of item that is Notable", + "CentralRepoIngestModule_notable_inbox_msg_subject=Notable: {0}" + }) + private static void postNotableMessage(Content content, Set otherCases, String corrAttrValue, AnalysisResult analysisResult) { + String msgSubject = null; + String msgDetails = null; + String msgKey = corrAttrValue; + if (content instanceof AbstractFile) { + AbstractFile file = (AbstractFile) content; + msgSubject = Bundle.CentralRepoIngestModule_notable_inbox_msg_subject(file.getName()); + msgDetails = makeNotableFileMessage(file, otherCases); + } else if (content instanceof DataArtifact) { + DataArtifact artifact = (DataArtifact) content; + msgSubject = Bundle.CentralRepoIngestModule_notable_inbox_msg_subject(artifact.getDisplayName()); + msgDetails = makeNotableDataArtifactMessage(artifact, corrAttrValue, otherCases); + } else { + LOGGER.log(Level.SEVERE, "Unsupported Content, cannot post ingest inbox message"); + } + if (msgSubject != null && msgDetails != null) { + IngestServices.getInstance().postMessage( + IngestMessage.createDataMessage( + MODULE_NAME, + msgSubject, + msgDetails, + msgKey, + analysisResult)); + } + } + + /** + * Makes an ingest inbox message for a notable file. Uses similar HTML + * markup as is used for this purpose by the hash lookup ingest module. + * + * @param file The notable file. + * @param otherCases The cases other than the current case in which the file + * was marked as nmotable. + * + * @return The message. + */ + @NbBundle.Messages({ + "CentralRepoIngestModule_filename_inbox_msg_header=File Name", + "CentralRepoIngestModule_md5Hash_inbox_msg_header=MD5 Hash", + "CentralRepoIngestModule_prev_cases_inbox_msg_header=Previous Cases" + }) + private static String makeNotableFileMessage(AbstractFile file, Set otherCases) { + StringBuilder message = new StringBuilder(1024); + message.append(""); //NON-NLS + addTableRowMarkup(message, Bundle.CentralRepoIngestModule_filename_inbox_msg_header(), file.getName()); + addTableRowMarkup(message, Bundle.CentralRepoIngestModule_md5Hash_inbox_msg_header(), file.getMd5Hash()); + addTableRowMarkup(message, Bundle.CentralRepoIngestModule_prev_cases_inbox_msg_header(), otherCases.stream().collect(Collectors.joining(","))); + return message.toString(); + } + + /** + * Makes an ingest inbox message for a notable data artifact. Uses similar + * HTML markup as is used for this purpose by the hash lookup ingest module. + * + * @param artifact The data artifact + * @param corrAttrValue The notable attribute (correlation attribute value). + * @param otherCases The cases other than the current case in which the + * artifact was marked as nmotable. + * + * @return The message. + */ + @NbBundle.Messages({ + "CentralRepoIngestModule_artifact_type_inbox_msg_header=Artifact Type", + "CentralRepoIngestModule_notable_attr_inbox_msg_header=Notable Attribute" + }) + private static String makeNotableDataArtifactMessage(DataArtifact artifact, String corrAttrValue, Set otherCases) { + StringBuilder message = new StringBuilder(1024); + message.append("
"); //NON-NLS + addTableRowMarkup(message, Bundle.CentralRepoIngestModule_artifact_type_inbox_msg_header(), artifact.getDisplayName()); + addTableRowMarkup(message, Bundle.CentralRepoIngestModule_notable_attr_inbox_msg_header(), corrAttrValue); + addTableRowMarkup(message, Bundle.CentralRepoIngestModule_prev_cases_inbox_msg_header(), otherCases.stream().collect(Collectors.joining(","))); + message.append("
"); //NON-NLS + return message.toString(); + } + + /** + * Adds a table row to a notable item message (HTML). + * + * @param message The string builder for the message. + * @param headerText The table row header text. + * @param cellText The table row cell text. + */ + private static void addTableRowMarkup(StringBuilder message, String headerText, String cellText) { + message.append(""); //NON-NLS + message.append("").append(headerText).append(""); //NON-NLS + message.append("").append(cellText).append(""); //NON-NLS + message.append(""); //NON-NLS + } + + /* + * Prevents instatiation of this utility class. + */ + private CentralRepoIngestModuleUtils() { + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettings.java index 52d645bcac..027403490b 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettings.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettings.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2018 Basis Technology Corp. + * Copyright 2018-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,18 +26,24 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; final class IngestSettings implements IngestModuleIngestJobSettings { private static final long serialVersionUID = 1L; - + static final boolean DEFAULT_FLAG_TAGGED_NOTABLE_ITEMS = false; + static final boolean DEFAULT_FLAG_PREVIOUS_DEVICES = false; + static final boolean DEFAULT_FLAG_UNIQUE_DEVICES = false; + static final boolean DEFAULT_CREATE_CR_PROPERTIES = true; + private final boolean flagTaggedNotableItems; private final boolean flagPreviousDevices; private final boolean createCorrelationProperties; + private final boolean flagUniqueArtifacts; /** * Instantiate the ingest job settings with default values. */ IngestSettings() { - this.flagTaggedNotableItems = CentralRepoIngestModule.DEFAULT_FLAG_TAGGED_NOTABLE_ITEMS; - this.flagPreviousDevices = CentralRepoIngestModule.DEFAULT_FLAG_PREVIOUS_DEVICES; - this.createCorrelationProperties = CentralRepoIngestModule.DEFAULT_CREATE_CR_PROPERTIES; + this.flagTaggedNotableItems = DEFAULT_FLAG_TAGGED_NOTABLE_ITEMS; + this.flagPreviousDevices = DEFAULT_FLAG_PREVIOUS_DEVICES; + this.createCorrelationProperties = DEFAULT_CREATE_CR_PROPERTIES; + this.flagUniqueArtifacts = DEFAULT_FLAG_UNIQUE_DEVICES; } /** @@ -48,11 +54,14 @@ final class IngestSettings implements IngestModuleIngestJobSettings { * the Central Repository * @param createCorrelationProperties Create correlation properties in the * central repository + * @param flagUniqueArtifacts Flag unique artifacts that have not + * been seen in any other cases */ - IngestSettings(boolean flagTaggedNotableItems, boolean flagPreviousDevices, boolean createCorrelationProperties) { + IngestSettings(boolean flagTaggedNotableItems, boolean flagPreviousDevices, boolean createCorrelationProperties, boolean flagUniqueArtifacts) { this.flagTaggedNotableItems = flagTaggedNotableItems; this.flagPreviousDevices = flagPreviousDevices; this.createCorrelationProperties = createCorrelationProperties; + this.flagUniqueArtifacts = flagUniqueArtifacts; } @Override @@ -86,4 +95,14 @@ final class IngestSettings implements IngestModuleIngestJobSettings { boolean shouldCreateCorrelationProperties() { return createCorrelationProperties; } + + /** + * Are artifacts (apps, domains) previously unseen in other cases to be + * flagged? + * + * @return True if flagging; otherwise false. + */ + public boolean isFlagUniqueArtifacts() { + return flagUniqueArtifacts; + } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.form index fe8d28c6f4..102a2635a0 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.form @@ -26,10 +26,11 @@ + - + @@ -44,7 +45,9 @@ - + + + @@ -83,5 +86,12 @@ + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.java index befe405281..5d54bd0f81 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2018 Basis Technology Corp. + * Copyright 2018-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,11 +44,13 @@ final class IngestSettingsPanel extends IngestModuleIngestJobSettingsPanel { flagTaggedNotableItemsCheckbox.setSelected(settings.isFlagTaggedNotableItems()); flagPreviouslySeenDevicesCheckbox.setSelected(settings.isFlagPreviousDevices()); createCorrelationPropertiesCheckbox.setSelected(settings.shouldCreateCorrelationProperties()); + flagUniqueAppsCheckbox.setSelected(settings.isFlagUniqueArtifacts()); } @Override public IngestModuleIngestJobSettings getSettings() { - return new IngestSettings(flagTaggedNotableItemsCheckbox.isSelected(), flagPreviouslySeenDevicesCheckbox.isSelected(), createCorrelationPropertiesCheckbox.isSelected()); + return new IngestSettings(flagTaggedNotableItemsCheckbox.isSelected(), flagPreviouslySeenDevicesCheckbox.isSelected(), + createCorrelationPropertiesCheckbox.isSelected(), flagUniqueAppsCheckbox.isSelected()); } /** @@ -64,6 +66,7 @@ final class IngestSettingsPanel extends IngestModuleIngestJobSettingsPanel { flagTaggedNotableItemsCheckbox = new javax.swing.JCheckBox(); flagPreviouslySeenDevicesCheckbox = new javax.swing.JCheckBox(); createCorrelationPropertiesCheckbox = new javax.swing.JCheckBox(); + flagUniqueAppsCheckbox = new javax.swing.JCheckBox(); ingestSettingsLabel.setFont(ingestSettingsLabel.getFont().deriveFont(ingestSettingsLabel.getFont().getStyle() | java.awt.Font.BOLD)); org.openide.awt.Mnemonics.setLocalizedText(ingestSettingsLabel, org.openide.util.NbBundle.getMessage(IngestSettingsPanel.class, "IngestSettingsPanel.ingestSettingsLabel.text")); // NOI18N @@ -74,6 +77,8 @@ final class IngestSettingsPanel extends IngestModuleIngestJobSettingsPanel { org.openide.awt.Mnemonics.setLocalizedText(createCorrelationPropertiesCheckbox, org.openide.util.NbBundle.getMessage(IngestSettingsPanel.class, "IngestSettingsPanel.createCorrelationPropertiesCheckbox.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(flagUniqueAppsCheckbox, org.openide.util.NbBundle.getMessage(IngestSettingsPanel.class, "IngestSettingsPanel.flagUniqueAppsCheckbox.text")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -87,8 +92,9 @@ final class IngestSettingsPanel extends IngestModuleIngestJobSettingsPanel { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(flagTaggedNotableItemsCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(flagPreviouslySeenDevicesCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(createCorrelationPropertiesCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) - .addContainerGap(47, Short.MAX_VALUE)) + .addComponent(createCorrelationPropertiesCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(flagUniqueAppsCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .addContainerGap(16, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -101,7 +107,9 @@ final class IngestSettingsPanel extends IngestModuleIngestJobSettingsPanel { .addComponent(flagTaggedNotableItemsCheckbox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(flagPreviouslySeenDevicesCheckbox) - .addContainerGap(47, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(flagUniqueAppsCheckbox) + .addContainerGap(24, Short.MAX_VALUE)) ); }// //GEN-END:initComponents @@ -109,6 +117,7 @@ final class IngestSettingsPanel extends IngestModuleIngestJobSettingsPanel { private javax.swing.JCheckBox createCorrelationPropertiesCheckbox; private javax.swing.JCheckBox flagPreviouslySeenDevicesCheckbox; private javax.swing.JCheckBox flagTaggedNotableItemsCheckbox; + private javax.swing.JCheckBox flagUniqueAppsCheckbox; private javax.swing.JLabel ingestSettingsLabel; // End of variables declaration//GEN-END:variables diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/AddNewOrganizationDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/AddNewOrganizationDialog.java index 6a9aa67c1d..e2eea44ae4 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/AddNewOrganizationDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/AddNewOrganizationDialog.java @@ -52,8 +52,8 @@ class AddNewOrganizationDialog extends javax.swing.JDialog { * Creates new form AddNewOrganizationDialog */ @Messages({"AddNewOrganizationDialog.addNewOrg.msg=Add New Organization"}) - AddNewOrganizationDialog() { - super((JFrame) WindowManager.getDefault().getMainWindow(), + AddNewOrganizationDialog(javax.swing.JDialog parent) { + super(parent, Bundle.AddNewOrganizationDialog_addNewOrg_msg(), true); // NON-NLS textBoxes = new ArrayList<>(); @@ -67,8 +67,8 @@ class AddNewOrganizationDialog extends javax.swing.JDialog { } // populates the dialog with existing case information to edit - public AddNewOrganizationDialog(CentralRepoOrganization orgToEdit) { - super((JFrame) WindowManager.getDefault().getMainWindow(), + public AddNewOrganizationDialog(javax.swing.JDialog parent, CentralRepoOrganization orgToEdit) { + super(parent, Bundle.AddNewOrganizationDialog_addNewOrg_msg(), true); // NON-NLS organizationToEdit = orgToEdit; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED index 0fc6762cc3..f49116150d 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED @@ -39,9 +39,10 @@ GlobalSettingsPanel.askForCentralRepoDbChoice.sqliteChoice.text=Use SQLite GlobalSettingsPanel.onMultiUserChange.disabledMu.description=The Central Repository will be reconfigured to use a local SQLite database. GlobalSettingsPanel.onMultiUserChange.disabledMu.description2=Press Configure PostgreSQL to change to a PostgreSQL database. GlobalSettingsPanel.onMultiUserChange.disabledMu.title=Central Repository Change Necessary -GlobalSettingsPanel.onMultiUserChange.enable.description=Do you want to update the Central Repository to use this PostgreSQL database? -GlobalSettingsPanel.onMultiUserChange.enable.description2=The Central Repository stores hash values and accounts from past cases. -GlobalSettingsPanel.onMultiUserChange.enable.title=Use with Central Repository? +# {0} - server name +GlobalSettingsPanel.onMultiUserChange.enable.description=Do you want to update the Central Repository to use the PostgreSQL server on {0}? +GlobalSettingsPanel.onMultiUserChange.enable.description2=Any data in an existing SQLite Central Repository will not be transferred to the new database. +GlobalSettingsPanel.onMultiUserChange.enable.title=Central Repository GlobalSettingsPanel.testCurrentConfiguration.dbDoesNotExist.message=Database does not exist. GlobalSettingsPanel.validationErrMsg.ingestRunning=You cannot change settings while ingest is running. GlobalSettingsPanel.validationerrMsg.mustConfigure=Configure the database to enable this module. diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle_ja.properties index f92bd670f4..21537df87e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle_ja.properties @@ -1,4 +1,4 @@ -#Tue Aug 18 18:09:20 UTC 2020 +#Thu Sep 30 10:26:59 UTC 2021 AddNewOrganizationDialog.addNewOrg.msg=\u65b0\u898f\u7d44\u7e54\u3092\u8ffd\u52a0 AddNewOrganizationDialog.bnCancel.text=\u53d6\u308a\u6d88\u3057 AddNewOrganizationDialog.bnOK.text=OK @@ -93,9 +93,9 @@ GlobalSettingsPanel.manageOrganizationButton.text=\u7d44\u7e54\u3092\u7ba1\u7406 GlobalSettingsPanel.onMultiUserChange.disabledMu.description=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fc\u306f\u30ed\u30fc\u30ab\u30ebSQLite\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3068\u3057\u3066\u518d\u69cb\u6210\u3055\u308c\u307e\u3059 GlobalSettingsPanel.onMultiUserChange.disabledMu.description2=[PostgreSQL\u306e\u69cb\u6210]\u3092\u62bc\u3057\u3066\u3001PostgreSQL\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u5909\u66f4\u3057\u307e\u3059\u3002 GlobalSettingsPanel.onMultiUserChange.disabledMu.title=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fc\u306e\u5909\u66f4\u304c\u5fc5\u8981\u3067\u3059 -GlobalSettingsPanel.onMultiUserChange.enable.description=\u3053\u306ePostgreSQL\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u4f7f\u7528\u3059\u308b\u3088\u3046\u306b\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fc\u3092\u66f4\u65b0\u3057\u307e\u3059\u304b\uff1f -GlobalSettingsPanel.onMultiUserChange.enable.description2=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fc\u306b\u306f\u904e\u53bb\u306e\u30b1\u30fc\u30b9\u304b\u3089\u306e\u30cf\u30c3\u30b7\u30e5\u5024\u3068\u30a2\u30ab\u30a6\u30f3\u30c8\u304c\u4fdd\u5b58\u3055\u308c\u307e\u3059\u3002 -GlobalSettingsPanel.onMultiUserChange.enable.title=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fc\u3067\u4f7f\u7528\u3057\u307e\u3059\u304b\uff1f +GlobalSettingsPanel.onMultiUserChange.enable.description={0}\u3067PostgreSQL\u30b5\u30fc\u30d0\u30fc\u3092\u4f7f\u7528\u3059\u308b\u3088\u3046\u306b\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u66f4\u65b0\u3057\u307e\u3059\u304b\uff1f +GlobalSettingsPanel.onMultiUserChange.enable.description2=\u65e2\u5b58\u306eSQLite\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u30c7\u30fc\u30bf\u306f\u65b0\u3057\u3044\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u8ee2\u9001\u3055\u308c\u307e\u305b\u3093\u3002 +GlobalSettingsPanel.onMultiUserChange.enable.title=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea GlobalSettingsPanel.organizationPanel.border.title=\u7d44\u7e54 GlobalSettingsPanel.organizationTextArea.text=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ec\u30dd\u30b8\u30c8\u30ea\u30fc\u5185\u3067\u7d44\u7e54\u60c5\u5831\u3092\u8ffd\u8de1\u3067\u304d\u307e\u3059\u3002 GlobalSettingsPanel.pnCorrelationProperties.border.title=\u76f8\u95a2\u5206\u6790\u30d7\u30ed\u30d1\u30c6\u30a3 diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java index 7cb1e566dd..6c8c2a9b72 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java @@ -47,6 +47,7 @@ import java.util.logging.Level; import javax.swing.ImageIcon; import org.openide.util.ImageUtilities; import org.sleuthkit.autopsy.centralrepository.datamodel.DatabaseTestResult; +import org.sleuthkit.autopsy.centralrepository.datamodel.PostgresSettingsLoader; @@ -151,9 +152,10 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i * as of most recent change. */ @NbBundle.Messages({ - "GlobalSettingsPanel.onMultiUserChange.enable.title=Use with Central Repository?", - "GlobalSettingsPanel.onMultiUserChange.enable.description=Do you want to update the Central Repository to use this PostgreSQL database?", - "GlobalSettingsPanel.onMultiUserChange.enable.description2=The Central Repository stores hash values and accounts from past cases." + "GlobalSettingsPanel.onMultiUserChange.enable.title=Central Repository", + "# {0} - server name", + "GlobalSettingsPanel.onMultiUserChange.enable.description=Do you want to update the Central Repository to use the PostgreSQL server on {0}?", + "GlobalSettingsPanel.onMultiUserChange.enable.description2=Any data in an existing SQLite Central Repository will not be transferred to the new database." }) public static void onMultiUserChange(Component parent, boolean muPreviouslySelected, boolean muCurrentlySelected) { boolean crEnabled = CentralRepoDbUtil.allowUseOfCentralRepository(); @@ -161,10 +163,12 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i if (!muPreviouslySelected && muCurrentlySelected) { SwingUtilities.invokeLater(() -> { + PostgresCentralRepoSettings multiUserSettings + = new PostgresCentralRepoSettings(PostgresSettingsLoader.MULTIUSER_SETTINGS_LOADER); if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(parent, "" + "
" - + "

" + Bundle.GlobalSettingsPanel_onMultiUserChange_enable_description() + "

" + + "

" + Bundle.GlobalSettingsPanel_onMultiUserChange_enable_description(multiUserSettings.getHost()) + "

" + "

" + Bundle.GlobalSettingsPanel_onMultiUserChange_enable_description2() + "

" + "
" + "", diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageOrganizationsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageOrganizationsDialog.java index 85373c06d2..6614821f10 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageOrganizationsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageOrganizationsDialog.java @@ -376,7 +376,7 @@ public final class ManageOrganizationsDialog extends JDialog { private void deleteButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteButtonActionPerformed CentralRepoOrganization orgToDelete = organizationList.getSelectedValue(); if (orgToDelete != null) { - if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(), + if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(this, Bundle.ManageOrganizationsDialog_confirmDeletion_message(), Bundle.ManageOrganizationsDialog_confirmDeletion_title(), JOptionPane.YES_NO_OPTION)) { @@ -397,7 +397,7 @@ public final class ManageOrganizationsDialog extends JDialog { }//GEN-LAST:event_closeButtonActionPerformed private void newButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newButtonActionPerformed - AddNewOrganizationDialog dialogO = new AddNewOrganizationDialog(); + AddNewOrganizationDialog dialogO = new AddNewOrganizationDialog(this); if (dialogO.isChanged()) { try { newOrg = dialogO.getNewOrg(); @@ -411,7 +411,7 @@ public final class ManageOrganizationsDialog extends JDialog { private void editButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editButtonActionPerformed CentralRepoOrganization orgToEdit = organizationList.getSelectedValue(); if (orgToEdit != null) { - AddNewOrganizationDialog dialogO = new AddNewOrganizationDialog(orgToEdit); + AddNewOrganizationDialog dialogO = new AddNewOrganizationDialog(this, orgToEdit); if (dialogO.isChanged()) { try { newOrg = dialogO.getNewOrg(); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties index 0016a075ee..d96b33357c 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties @@ -79,5 +79,5 @@ CreatePersonaAccountDialog.identifierTextField.text= CreatePersonaAccountDialog.identiferLbl.text=Identifier: CreatePersonaAccountDialog.okBtn.text=OK PersonasTopComponent.introText.text=Personas represent an online identity. They span cases and are stored in the Central Repository based on accounts that were found in artifacts. You can create, edit, and delete personas here. -PersonasTopComponent.cbFilterByKeyword.text=Filter personas by keyword +PersonasTopComponent.cbFilterByKeyword.text=Filter personas by name or account PersonaDetailsPanel.nameField.text= diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED index a4a041c429..a7a8659131 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED @@ -125,7 +125,7 @@ CreatePersonaAccountDialog.identifierTextField.text= CreatePersonaAccountDialog.identiferLbl.text=Identifier: CreatePersonaAccountDialog.okBtn.text=OK PersonasTopComponent.introText.text=Personas represent an online identity. They span cases and are stored in the Central Repository based on accounts that were found in artifacts. You can create, edit, and delete personas here. -PersonasTopComponent.cbFilterByKeyword.text=Filter personas by keyword +PersonasTopComponent.cbFilterByKeyword.text=Filter personas by name or account PersonaDetailsPanel.nameField.text= PersonasTopComponent_delete_confirmation_msg=Are you sure you want to delete this persona? PersonasTopComponent_delete_confirmation_Title=Are you sure? diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle_ja.properties index d0f758edb8..f10efec41a 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle_ja.properties @@ -1,67 +1,67 @@ -#Tue Aug 18 18:09:20 UTC 2020 +#Mon Jul 12 13:21:59 UTC 2021 AddAliasDialog.accountsLbl.text=\u30a2\u30ab\u30a6\u30f3\u30c8\uff1a AddAliasDialog.cancelBtn.text=\u30ad\u30e3\u30f3\u30bb\u30eb AddAliasDialog.okBtn.text=OK -AddMetadataDialog.title.text=\u30e1\u30bf\u30c7\u30fc\u30bf\u3092\u8ffd\u52a0 +AddMetadataDialog.title.text=\u30e1\u30bf\u30c7\u30fc\u30bf\u3092\u8ffd\u52a0\u3059\u308b AddMetadataDialog_dup_Title=\u30e1\u30bf\u30c7\u30fc\u30bf\u8ffd\u52a0\u30a8\u30e9\u30fc -AddMetadataDialog_dup_msg=\u3053\u306e\u540d\u524d\u306e\u30e1\u30bf\u30c7\u30fc\u30bf\u30a8\u30f3\u30c8\u30ea\u306f\u65e2\u306b\u3053\u306e\u30da\u30eb\u30bd\u30ca\u306b\u8ffd\u52a0\u3055\u308c\u3066\u3044\u307e\u3059\u3002 +AddMetadataDialog_dup_msg=\u3053\u306e\u540d\u524d\u306e\u30e1\u30bf\u30c7\u30fc\u30bf\u306f\u3001\u30da\u30eb\u30bd\u30ca\u306b\u3059\u3067\u306b\u8ffd\u52a0\u3055\u308c\u3066\u3044\u307e\u3059\u3002 AddMetadataDialog_empty_name_Title=\u6b20\u843d\u3057\u3066\u3044\u308b\u30d5\u30a3\u30fc\u30eb\u30c9 -AddMetadataDialog_empty_name_msg=\u30e1\u30bf\u30c7\u30fc\u30bf\u30a8\u30f3\u30c8\u30ea\u306e\u540d\u524d\u307e\u305f\u306f\u5024\u3092\u7a7a\u767d\u306b\u306f\u51fa\u6765\u307e\u305b\u3093\u3002 +AddMetadataDialog_empty_name_msg=\u30e1\u30bf\u30c7\u30fc\u30bf\u306e\u540d\u524d\u307e\u305f\u306f\u5024\u3092\u7a7a\u767d\u306b\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093\u3002 CTL_OpenPersonas=\u30da\u30eb\u30bd\u30ca CTL_PersonaDetailsTopComponent=\u30da\u30eb\u30bd\u30ca\u306e\u8a73\u7d30 CTL_PersonasTopComponentAction=\u30da\u30eb\u30bd\u30ca CreatePersonaAccountDialog.cancelBtn.text=\u30ad\u30e3\u30f3\u30bb\u30eb -CreatePersonaAccountDialog.identiferLbl.text=\u8b58\u5225\u540d\uff1a +CreatePersonaAccountDialog.identiferLbl.text=\u8b58\u5225\u5b50\uff1a CreatePersonaAccountDialog.okBtn.text=OK -CreatePersonaAccountDialog.title.text=\u30a2\u30ab\u30a6\u30f3\u30c8\u306e\u4f5c\u6210 +CreatePersonaAccountDialog.title.text=\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u4f5c\u6210 CreatePersonaAccountDialog.typeLbl.text=\u30bf\u30a4\u30d7\uff1a CreatePersonaAccountDialog_error_msg=\u30a2\u30ab\u30a6\u30f3\u30c8\u306e\u4f5c\u6210\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 CreatePersonaAccountDialog_error_title=\u30a2\u30ab\u30a6\u30f3\u30c8\u306e\u30a8\u30e9\u30fc -CreatePersonaAccountDialog_invalid_account_Title=\u7121\u52b9\u306a\u30a2\u30ab\u30a6\u30f3\u30c8\u8b58\u5225\u540d -CreatePersonaAccountDialog_invalid_account_msg=\u30a2\u30ab\u30a6\u30f3\u30c8ID\u304c\u7121\u52b9\u3067\u3059\u3002 +CreatePersonaAccountDialog_invalid_account_Title=\u30a2\u30ab\u30a6\u30f3\u30c8\u8b58\u5225\u5b50\u304c\u7121\u52b9\u3067\u3059 +CreatePersonaAccountDialog_invalid_account_msg=\u30a2\u30ab\u30a6\u30f3\u30c8\u8b58\u5225\u5b50\u304c\u7121\u52b9\u3067\u3059\u3002 CreatePersonaAccountDialog_success_msg=\u30a2\u30ab\u30a6\u30f3\u30c8\u304c\u8ffd\u52a0\u3055\u308c\u307e\u3057\u305f\u3002 -CreatePersonaAccountDialog_success_title=\u30a2\u30ab\u30a6\u30f3\u30c8\u304c\u8ffd\u52a0\u3055\u308c\u307e\u3057\u305f +CreatePersonaAccountDialog_success_title=\u30a2\u30ab\u30a6\u30f3\u30c8\u304c\u8ffd\u52a0\u3055\u308c\u307e\u3057\u305f\u3002 OpenPersonasAction.displayName=\u30da\u30eb\u30bd\u30ca PersonaAccountDialog.cancelBtn.text=\u30ad\u30e3\u30f3\u30bb\u30eb PersonaAccountDialog.confidenceLbl.text=\u4fe1\u983c\u5ea6\uff1a -PersonaAccountDialog.identiferLbl.text=\u8b58\u5225\u540d\uff1a -PersonaAccountDialog.justificationLbl.text=\u8aac\u660e\uff1a +PersonaAccountDialog.identiferLbl.text=\u8b58\u5225\u5b50\uff1a +PersonaAccountDialog.justificationLbl.text=\u5f01\u660e\uff1a PersonaAccountDialog.okBtn.text=OK -PersonaAccountDialog.title.text=\u30a2\u30ab\u30a6\u30f3\u30c8\u306e\u8ffd\u52a0 +PersonaAccountDialog.title.text=\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u8ffd\u52a0 PersonaAccountDialog.typeLbl.text=\u30bf\u30a4\u30d7\uff1a -PersonaAccountDialog_dup_Title=\u30a2\u30ab\u30a6\u30f3\u30c8\u8ffd\u52a0\u30a8\u30e9\u30fc +PersonaAccountDialog_dup_Title=\u30a2\u30ab\u30a6\u30f3\u30c8\u8ffd\u52a0\u306e\u5931\u6557 PersonaAccountDialog_dup_msg=\u3053\u306e\u30a2\u30ab\u30a6\u30f3\u30c8\u306f\u3059\u3067\u306b\u30da\u30eb\u30bd\u30ca\u306b\u8ffd\u52a0\u3055\u308c\u3066\u3044\u307e\u3059\u3002 -PersonaAccountDialog_get_types_exception_Title=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fc\u30fb\u30a8\u30e9\u30fc -PersonaAccountDialog_get_types_exception_msg=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fc\u3078\u306e\u30a2\u30af\u30bb\u30b9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 -PersonaAccountDialog_identifier_empty_Title=\u8b58\u5225\u540d\u304c\u7a7a\u767d -PersonaAccountDialog_identifier_empty_msg=\u8b58\u5225\u540d\u30d5\u30a3\u30fc\u30eb\u30c9\u3092\u7a7a\u306b\u306f\u3067\u304d\u307e\u305b\u3093\u3002 -PersonaAccountDialog_invalid_account_Title=\u7121\u52b9\u306a\u30a2\u30ab\u30a6\u30f3\u30c8ID -PersonaAccountDialog_invalid_account_msg=\u30a2\u30ab\u30a6\u30f3\u30c8ID\u304c\u7121\u52b9\u3067\u3059\u3002 -PersonaAccountDialog_search_empty_Title=\u30a2\u30ab\u30a6\u30f3\u30c8\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093 -PersonaAccountDialog_search_empty_msg=\u6307\u5b9a\u3055\u308c\u305f\u8b58\u5225\u540d\u3068\u30bf\u30a4\u30d7\u306e\u30a2\u30ab\u30a6\u30f3\u30c8\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002 +PersonaAccountDialog_get_types_exception_Title=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u6545\u969c +PersonaAccountDialog_get_types_exception_msg=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u3078\u306e\u30a2\u30af\u30bb\u30b9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 +PersonaAccountDialog_identifier_empty_Title=\u7a7a\u306e\u8b58\u5225\u5b50 +PersonaAccountDialog_identifier_empty_msg=\u8b58\u5225\u5b50\u30d5\u30a3\u30fc\u30eb\u30c9\u3092\u7a7a\u767d\u306b\u306f\u3067\u304d\u307e\u305b\u3093\u3002 +PersonaAccountDialog_invalid_account_Title=\u30a2\u30ab\u30a6\u30f3\u30c8\u8b58\u5225\u5b50\u304c\u7121\u52b9\u3067\u3059 +PersonaAccountDialog_invalid_account_msg=\u30a2\u30ab\u30a6\u30f3\u30c8\u8b58\u5225\u5b50\u304c\u7121\u52b9\u3067\u3059\u3002 +PersonaAccountDialog_search_empty_Title=\u30a2\u30ab\u30a6\u30f3\u30c8\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f +PersonaAccountDialog_search_empty_msg=\u6307\u5b9a\u3055\u308c\u305f\u8b58\u5225\u5b50\u3068\u30bf\u30a4\u30d7\u306e\u30a2\u30ab\u30a6\u30f3\u30c8\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002 PersonaAccountDialog_search_failure_Title=\u30a2\u30ab\u30a6\u30f3\u30c8\u8ffd\u52a0\u30a8\u30e9\u30fc -PersonaAccountDialog_search_failure_msg=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fc\u30fb\u30a2\u30ab\u30a6\u30f3\u30c8\u306e\u691c\u7d22\u306b\u5931\u6557\u3057\u307e\u3057\u305f +PersonaAccountDialog_search_failure_msg=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fb\u30a2\u30ab\u30a6\u30f3\u30c8\u306e\u691c\u7d22\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 PersonaAliasDialog.aliasLbl.text=\u30a8\u30a4\u30ea\u30a2\u30b9\uff1a PersonaAliasDialog.cancelBtn.text_1=\u30ad\u30e3\u30f3\u30bb\u30eb PersonaAliasDialog.confidenceLbl.text=\u4fe1\u983c\u5ea6\uff1a -PersonaAliasDialog.justificationLbl.text=\u8aac\u660e\uff1a +PersonaAliasDialog.justificationLbl.text=\u5f01\u660e\uff1a PersonaAliasDialog.okBtn.text_1=OK PersonaAliasDialog.title.text=\u30a8\u30a4\u30ea\u30a2\u30b9\u3092\u8ffd\u52a0 -PersonaAliasDialog_dup_Title=\u30a8\u30a4\u30ea\u30a2\u30b9\u8ffd\u52a0\u30a8\u30e9\u30fc -PersonaAliasDialog_dup_msg=\u3053\u306e\u30a8\u30a4\u30ea\u30a2\u30b9\u306f\u65e2\u306b\u3053\u306e\u30da\u30eb\u30bd\u30ca\u306b\u8ffd\u52a0\u3055\u308c\u3066\u3044\u307e\u3059\u3002 -PersonaAliasDialog_empty_Title=\u30a8\u30a4\u30ea\u30a2\u30b9\u304c\u7a7a\u767d -PersonaAliasDialog_empty_msg=\u30a8\u30a4\u30ea\u30a2\u30b9\u3092\u7a7a\u767d\u306b\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093\u3002 +PersonaAliasDialog_dup_Title=\u30a8\u30a4\u30ea\u30a2\u30b9\u8ffd\u52a0\u306e\u5931\u6557 +PersonaAliasDialog_dup_msg=\u3053\u306e\u30a8\u30a4\u30ea\u30a2\u30b9\u306f\u3059\u3067\u306b\u3053\u306e\u30da\u30eb\u30bd\u30ca\u306b\u8ffd\u52a0\u3055\u308c\u3066\u3044\u307e\u3059\u3002 +PersonaAliasDialog_empty_Title=\u7a7a\u306e\u30a8\u30a4\u30ea\u30a2\u30b9 +PersonaAliasDialog_empty_msg=\u30a8\u30a4\u30ea\u30a2\u30b9\u3092\u7a7a\u767d\u306b\u306f\u3067\u304d\u307e\u305b\u3093\u3002 PersonaDetailsDialog.cancelBtn.text=\u30ad\u30e3\u30f3\u30bb\u30eb PersonaDetailsDialog.okBtn.text=OK -PersonaDetailsDialogCreateTitle=\u30da\u30eb\u30bd\u30ca\u3092\u4f5c\u6210 -PersonaDetailsDialogEditTitle=\u30da\u30eb\u30bd\u30ca\u3092\u7de8\u96c6 +PersonaDetailsDialogCreateTitle=\u30da\u30eb\u30bd\u30ca\u3092\u4f5c\u6210\u3059\u308b +PersonaDetailsDialogEditTitle=\u30da\u30eb\u30bd\u30ca\u306e\u7de8\u96c6 PersonaDetailsDialogViewTitle=\u30da\u30eb\u30bd\u30ca\u3092\u8868\u793a -PersonaDetailsPanel.accountsLbl.text=\u30a2\u30ab\u30a6\u30f3\u30c8\uff1a +PersonaDetailsPanel.accountsLbl.text=\u30a2\u30ab\u30a6\u30f3\u30c8 PersonaDetailsPanel.addAccountBtn.text=\u8ffd\u52a0 PersonaDetailsPanel.addAliasBtn.text=\u8ffd\u52a0 PersonaDetailsPanel.addMetadataBtn.text=\u8ffd\u52a0 -PersonaDetailsPanel.aliasesLabel.text=\u30a8\u30a4\u30ea\u30a2\u30b9\uff1a -PersonaDetailsPanel.casesLbl.text=\u898b\u3064\u304b\u3063\u305f\u30b1\u30fc\u30b9\uff1a +PersonaDetailsPanel.aliasesLabel.text=\u30a8\u30a4\u30ea\u30a2\u30b9 +PersonaDetailsPanel.casesLbl.text=\u898b\u3064\u3051\u305f\u30b1\u30fc\u30b9 PersonaDetailsPanel.commentLbl.text=\u30b3\u30e1\u30f3\u30c8\uff1a PersonaDetailsPanel.creationDateLbl.text=\u4f5c\u6210\u65e5\uff1a PersonaDetailsPanel.deleteAccountBtn.text=\u524a\u9664 @@ -71,42 +71,42 @@ PersonaDetailsPanel.editAccountBtn.text=\u7de8\u96c6 PersonaDetailsPanel.editAliasBtn.text=\u7de8\u96c6 PersonaDetailsPanel.editMetadataBtn.text=\u7de8\u96c6 PersonaDetailsPanel.examinerLbl.text=\u4f5c\u6210\u8005\uff1a -PersonaDetailsPanel.metadataLabel.text=\u30e1\u30bf\u30c7\u30fc\u30bf\uff1a +PersonaDetailsPanel.metadataLabel.text=\u30e1\u30bf\u30c7\u30fc\u30bf PersonaDetailsPanel.nameLbl.text=\u540d\u524d\uff1a -PersonaDetailsPanel_CentralRepoErr_Title=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fc\u306e\u30a8\u30e9\u30fc -PersonaDetailsPanel_CentralRepoErr_msg=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fc\u3078\u306e\u66f8\u304d\u8fbc\u307f\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 +PersonaDetailsPanel_CentralRepoErr_Title=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u30a8\u30e9\u30fc +PersonaDetailsPanel_CentralRepoErr_msg=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u3078\u306e\u66f8\u304d\u8fbc\u307f\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 PersonaDetailsPanel_EmptyComment_Title=\u7a7a\u767d\u306e\u30da\u30eb\u30bd\u30ca\u30b3\u30e1\u30f3\u30c8 -PersonaDetailsPanel_EmptyComment_msg=\u30da\u30eb\u30bd\u30ca\u306e\u30b3\u30e1\u30f3\u30c8\u306f\u7a7a\u767d\u306b\u3067\u304d\u307e\u305b\u3093\u3002 -PersonaDetailsPanel_EmptyName_Title=\u30da\u30eb\u30bd\u30ca\u540d\u304c\u7a7a\u767d -PersonaDetailsPanel_EmptyName_msg=\u30da\u30eb\u30bd\u30ca\u540d\u306f\u7a7a\u767d\u306b\u3067\u304d\u307e\u305b\u3093\u3002 -PersonaDetailsPanel_NotEnoughAccounts_Title=\u30a2\u30ab\u30a6\u30f3\u30c8\u304c\u3042\u308a\u307e\u305b\u3093 +PersonaDetailsPanel_EmptyComment_msg=\u30da\u30eb\u30bd\u30ca\u3092\u7a7a\u767d\u306b\u306f\u3067\u304d\u307e\u305b\u3093\u3002 +PersonaDetailsPanel_EmptyName_Title=\u7a7a\u767d\u306e\u30da\u30eb\u30bd\u30ca\u540d +PersonaDetailsPanel_EmptyName_msg=\u30da\u30eb\u30bd\u30ca\u540d\u3092\u7a7a\u767d\u306b\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093\u3002 +PersonaDetailsPanel_NotEnoughAccounts_Title=\u6b20\u843d\u3057\u3066\u308b\u30a2\u30ab\u30a6\u30f3\u30c8 PersonaDetailsPanel_NotEnoughAccounts_msg=\u30da\u30eb\u30bd\u30ca\u306b\u306f\u5c11\u306a\u304f\u3068\u30821\u3064\u306e\u30a2\u30ab\u30a6\u30f3\u30c8\u304c\u5fc5\u8981\u3067\u3059\u3002 -PersonaDetailsPanel_empty_justification_Title=\u8aac\u660e\u304c\u7a7a\u767d -PersonaDetailsPanel_empty_justification_msg=\u8aac\u660e\u30d5\u30a3\u30fc\u30eb\u30c9\u306f\u7a7a\u306b\u3067\u304d\u307e\u305b\u3093 +PersonaDetailsPanel_empty_justification_Title=\u7a7a\u767d\u306e\u5f01\u660e +PersonaDetailsPanel_empty_justification_msg=\u5f01\u660e\u30d5\u30a3\u30fc\u30eb\u30c9\u3092\u7a7a\u767d\u306b\u306f\u3067\u304d\u307e\u305b\u3093 PersonaDetailsPanel_load_exception_Title=\u521d\u671f\u5316\u30a8\u30e9\u30fc -PersonaDetailsPanel_load_exception_msg=\u30da\u30eb\u30bd\u30ca\u3092\u8aad\u307f\u8fbc\u3081\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +PersonaDetailsPanel_load_exception_msg=\u30da\u30eb\u30bd\u30ca\u306e\u8aad\u307f\u8fbc\u307f\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 PersonaMetadataDialog.cancelBtn.text=\u30ad\u30e3\u30f3\u30bb\u30eb PersonaMetadataDialog.confidenceLbl.text=\u4fe1\u983c\u5ea6\uff1a -PersonaMetadataDialog.justificationLbl.text=\u8aac\u660e\uff1a +PersonaMetadataDialog.justificationLbl.text=\u5f01\u660e\: PersonaMetadataDialog.nameLbl.text=\u540d\u524d\uff1a PersonaMetadataDialog.okBtn.text=OK PersonaMetadataDialog.valueLbl.text=\u5024\uff1a -PersonasTopComponent.cbFilterByKeyword.text=\u30ad\u30fc\u30ef\u30fc\u30c9\u3067\u30da\u30eb\u30bd\u30ca\u3092\u30d5\u30a3\u30eb\u30bf\u30ea\u30f3\u30b0\u3059\u308b -PersonasTopComponent.createAccountBtn.text=\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u4f5c\u6210 -PersonasTopComponent.createBtn.text=\u65b0\u3057\u3044\u30da\u30eb\u30bd\u30ca +PersonasTopComponent.cbFilterByKeyword.text=\u30da\u30eb\u30bd\u30ca\u3092\u540d\u524d\u307e\u305f\u306f\u30a2\u30ab\u30a6\u30f3\u30c8\u3067\u30d5\u30a3\u30eb\u30bf\u30ea\u30f3\u30b0\u3059\u308b +PersonasTopComponent.createAccountBtn.text=\u30a2\u30ab\u30a6\u30f3\u30c8\u4f5c\u6210 +PersonasTopComponent.createBtn.text=\u65b0\u30da\u30eb\u30bd\u30ca PersonasTopComponent.deleteBtn.text=\u30da\u30eb\u30bd\u30ca\u3092\u524a\u9664 -PersonasTopComponent.editBtn.text=\u30da\u30eb\u30bd\u30ca\u3092\u7de8\u96c6 -PersonasTopComponent.introText.text=\u30da\u30eb\u30bd\u30ca\u306f\u30aa\u30f3\u30e9\u30a4\u30f3ID\u3092\u8868\u3057\u307e\u3059\u3002 \u305d\u308c\u3089\u306f\u30b1\u30fc\u30b9\u306b\u307e\u305f\u304c\u308a\u3001\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u3067\u898b\u3064\u304b\u3063\u305f\u30a2\u30ab\u30a6\u30f3\u30c8\u306b\u57fa\u3065\u3044\u3066\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fc\u306b\u4fdd\u5b58\u3055\u308c\u307e\u3059\u3002 \u3053\u3053\u3067\u30da\u30eb\u30bd\u30ca\u3092\u4f5c\u6210\u3001\u7de8\u96c6\u3001\u524a\u9664\u3067\u304d\u307e\u3059\u3002 +PersonasTopComponent.editBtn.text=\u30da\u30eb\u30bd\u30ca\u306e\u7de8\u96c6 +PersonasTopComponent.introText.text=\u30da\u30eb\u30bd\u30ca\u306f\u30aa\u30f3\u30e9\u30a4\u30f3ID\u3092\u8868\u3057\u307e\u3059\u3002 \u305d\u308c\u3089\u306f\u30b1\u30fc\u30b9\u306b\u307e\u305f\u304c\u308a\u3001\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u3067\u898b\u3064\u304b\u3063\u305f\u30a2\u30ab\u30a6\u30f3\u30c8\u306b\u57fa\u3065\u3044\u3066\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u306b\u4fdd\u5b58\u3055\u308c\u307e\u3059\u3002 \u3053\u3053\u3067\u30da\u30eb\u30bd\u30ca\u3092\u4f5c\u6210\u3001\u7de8\u96c6\u3001\u524a\u9664\u3067\u304d\u307e\u3059\u3002 PersonasTopComponent.resultsTable.columnModel.title0=ID PersonasTopComponent.resultsTable.columnModel.title1=\u540d\u524d PersonasTopComponent.searchAccountRadio.text=\u30a2\u30ab\u30a6\u30f3\u30c8 PersonasTopComponent.searchBtn.text=\u8868\u793a PersonasTopComponent.searchNameRadio.text=\u540d\u524d PersonasTopComponent_Name=\u30da\u30eb\u30bd\u30ca -PersonasTopComponent_delete_confirmation_Title=\u3088\u308d\u3057\u3044\u3067\u3059\u304b? +PersonasTopComponent_delete_confirmation_Title=\u78ba\u304b\u3067\u3059\u304b\uff1f PersonasTopComponent_delete_confirmation_msg=\u3053\u306e\u30da\u30eb\u30bd\u30ca\u3092\u524a\u9664\u3057\u3066\u3082\u3088\u308d\u3057\u3044\u3067\u3059\u304b\uff1f PersonasTopComponent_delete_exception_Title=\u524a\u9664\u30a8\u30e9\u30fc -PersonasTopComponent_delete_exception_msg=\u30da\u30eb\u30bd\u30ca\u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 -PersonasTopComponent_noCR_msg=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fc\u304c\u7121\u52b9\u3067\u3059\u3002 -PersonasTopComponent_search_exception_Title=\u691c\u7d22\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 \u30b1\u30fc\u30b9\u3092\u958b\u3044\u3066\u3001\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fc\u30fb\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u5b8c\u5168\u306b\u521d\u671f\u5316\u3057\u3066\u304f\u3060\u3055\u3044\u3002 -PersonasTopComponent_search_exception_msg=\u30da\u30eb\u30bd\u30ca\u306e\u691c\u7d22\u30a8\u30e9\u30fc +PersonasTopComponent_delete_exception_msg=\u30da\u30eb\u30bd\u30ca\u306e\u524a\u9664\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 +PersonasTopComponent_noCR_msg=\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u304c\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002 +PersonasTopComponent_search_exception_Title=\u691c\u7d22\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 \u30b1\u30fc\u30b9\u3092\u958b\u3044\u3066\u3001\u30bb\u30f3\u30c8\u30e9\u30eb\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea\u30fb\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u521d\u671f\u5316\u3057\u3066\u307f\u3066\u304f\u3060\u3055\u3044\u3002 +PersonasTopComponent_search_exception_msg=\u30da\u30eb\u30bd\u30ca\u306e\u691c\u7d22\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsDialog.form index 70a0422367..d993262025 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsDialog.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsDialog.form @@ -8,6 +8,9 @@ + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsDialog.java index 94061921c1..b37a3d1c2b 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsDialog.java @@ -22,6 +22,7 @@ import java.awt.Component; import java.util.logging.Level; import javax.swing.JDialog; import javax.swing.JFrame; +import javax.swing.JOptionPane; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.centralrepository.datamodel.Persona; @@ -38,6 +39,8 @@ public class PersonaDetailsDialog extends JDialog { private static final Logger logger = Logger.getLogger(PersonaDetailsDialog.class.getName()); private final PersonaDetailsDialogCallback callback; + + private String popupMessageOnStartup = ""; @NbBundle.Messages({ "PersonaDetailsDialogCreateTitle=Create Persona", @@ -91,6 +94,11 @@ public class PersonaDetailsDialog extends JDialog { pdp = new org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsPanel(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + addWindowListener(new java.awt.event.WindowAdapter() { + public void windowOpened(java.awt.event.WindowEvent evt) { + formWindowOpened(evt); + } + }); org.openide.awt.Mnemonics.setLocalizedText(cancelBtn, org.openide.util.NbBundle.getMessage(PersonaDetailsDialog.class, "PersonaDetailsDialog.cancelBtn.text")); // NOI18N cancelBtn.setMaximumSize(new java.awt.Dimension(79, 23)); @@ -159,10 +167,20 @@ public class PersonaDetailsDialog extends JDialog { dispose(); }//GEN-LAST:event_cancelBtnActionPerformed + private void formWindowOpened(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowOpened + if(!popupMessageOnStartup.isEmpty()) { + JOptionPane.showMessageDialog(this, popupMessageOnStartup, "Persona Details", JOptionPane.INFORMATION_MESSAGE); + } + }//GEN-LAST:event_formWindowOpened + public PersonaDetailsPanel getDetailsPanel() { return this.pdp; } + public void setStartupPopupMessage(String message) { + popupMessageOnStartup = message; + } + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton cancelBtn; private javax.swing.JScrollPane jScrollPane1; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsPanel.form b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsPanel.form index ad0a16878f..7d1bbcee85 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsPanel.form @@ -22,6 +22,9 @@ -->
+ + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsPanel.java index bc4f6ec9c3..d8ce7e23b5 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsPanel.java @@ -225,6 +225,10 @@ public final class PersonaDetailsPanel extends javax.swing.JPanel { void addEditExistingAlias(PersonaAlias alias, String justification, Persona.Confidence confidence) { aliasesToEdit.put(alias, new PAlias(alias.getAlias(), justification, confidence)); } + + PersonaDetailsMode getMode() { + return mode; + } /** * A data bucket class for yet-to-be-created PersonaAccount @@ -384,6 +388,12 @@ public final class PersonaDetailsPanel extends javax.swing.JPanel { casesTablePane = new javax.swing.JScrollPane(); casesTable = new javax.swing.JTable(); + addComponentListener(new java.awt.event.ComponentAdapter() { + public void componentShown(java.awt.event.ComponentEvent evt) { + formComponentShown(evt); + } + }); + org.openide.awt.Mnemonics.setLocalizedText(examinerLbl, org.openide.util.NbBundle.getMessage(PersonaDetailsPanel.class, "PersonaDetailsPanel.examinerLbl.text")); // NOI18N examinerField.setEditable(false); @@ -622,6 +632,10 @@ public final class PersonaDetailsPanel extends javax.swing.JPanel { ); }// //GEN-END:initComponents + private void formComponentShown(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_formComponentShown + + }//GEN-LAST:event_formComponentShown + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel accountsLbl; private javax.swing.JTable accountsTable; diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/commandlineingest/Bundle.properties-MERGED index 90e7cc4a11..84598e3756 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/Bundle.properties-MERGED @@ -1,8 +1,9 @@ CommandLineIngestSettingPanel_empty_report_name_mgs=Report profile name was empty, no profile created. CommandLineIngestSettingPanel_existing_report_name_mgs=Report profile name was already exists, no profile created. +CommandLineIngestSettingPanel_invalid_report_name_mgs=Report profile name contained illegal characters, no profile created. CommandListIngestSettingsPanel_Default_Report_DisplayName=Default CommandListIngestSettingsPanel_Make_Config=Make new profile... -CommandListIngestSettingsPanel_Report_Name_Msg=Please supply a report profile name: +CommandListIngestSettingsPanel_Report_Name_Msg=Please supply a report profile name (letters, digits, and underscore characters only): OpenIDE-Module-Name=CommandLineAutopsy OptionsCategory_Keywords_Command_Line_Ingest_Settings=Command Line Ingest Settings OptionsCategory_Keywords_General=Options diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/commandlineingest/Bundle_ja.properties index 62c8f29e53..a22df43d63 100644 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/Bundle_ja.properties @@ -1,17 +1,33 @@ +#Thu Sep 30 10:26:59 UTC 2021 +AutopsyOptionsPanel.agencyLogoPathFieldValidationLabel.invalidPath.text=\u30d1\u30b9\u306f\u6709\u52b9\u3067\u306f\u3042\u308a\u307e\u305b\u3093 +CommandLineIngestSettingPanel_empty_report_name_mgs=\u30ec\u30dd\u30fc\u30c8\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u540d\u306f\u7a7a\u767d\u306a\u306e\u3067\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u306f\u4f5c\u6210\u3057\u307e\u305b\u3093\u3002 +CommandLineIngestSettingPanel_existing_report_name_mgs=\u30ec\u30dd\u30fc\u30c8\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u540d\u306f\u3059\u3067\u306b\u5b58\u5728\u3059\u308b\u306e\u3067\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u306f\u4f5c\u6210\u3057\u307e\u305b\u3093\u3002 +CommandLineIngestSettingPanel_invalid_report_name_mgs=\u30ec\u30dd\u30fc\u30c8\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u540d\u306b\u4e0d\u6b63\u306a\u6587\u5b57\u304c\u542b\u307e\u308c\u3066\u3044\u307e\u3059\u3002\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u306f\u4f5c\u6210\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002 +CommandLineIngestSettingsPanel.CannotAccess=\u30a2\u30af\u30bb\u30b9\u3067\u304d\u307e\u305b\u3093 +CommandLineIngestSettingsPanel.CheckPermissions=\u30a2\u30af\u30bb\u30b9\u6a29\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +CommandLineIngestSettingsPanel.ResultsDirectoryUnspecified=\u51fa\u529b\u30d5\u30a9\u30eb\u30c0\u30fc\u3092\u8a2d\u5b9a\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059 +CommandLineIngestSettingsPanel.bnEditIngestSettings.text=\u8a2d\u5b9a +CommandLineIngestSettingsPanel.bnEditIngestSettings.toolTipText=\u30b3\u30de\u30f3\u30c9\u30e9\u30a4\u30f3\u51e6\u7406\u30e2\u30fc\u30c9\u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306e\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30b8\u30e7\u30d6\u8a2d\u5b9a\u3067\u3059\u3002 +CommandLineIngestSettingsPanel.bnEditReportSettings.AccessibleContext.accessibleName=\u8a2d\u5b9a +CommandLineIngestSettingsPanel.bnEditReportSettings.actionCommand=\u30ec\u30dd\u30fc\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\u306e\u8a2d\u5b9a +CommandLineIngestSettingsPanel.bnEditReportSettings.text=\u8a2d\u5b9a +CommandLineIngestSettingsPanel.bnEditReportSettings.toolTipText=\u30b3\u30de\u30f3\u30c9\u30e9\u30a4\u30f3\u51e6\u7406\u30e2\u30fc\u30c9\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306e\u30ec\u30dd\u30fc\u30c8\u751f\u6210\u8a2d\u5b9a\u3002 +CommandLineIngestSettingsPanel.browseOutputFolderButton.text=\u53c2\u7167 +CommandLineIngestSettingsPanel.ingestDescriptionTextPane.text=\u30b3\u30de\u30f3\u30c9\u30e9\u30a4\u30f3\u304b\u3089\u4f7f\u7528\u3059\u308b\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u3092\u8a2d\u5b9a\u3057\u307e\u3059\u3002 +CommandLineIngestSettingsPanel.ingestProfileLabel.text=\u53d6\u8fbc\u307f\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\uff1a +CommandLineIngestSettingsPanel.jLabelInvalidResultsFolder.text=jLabelInvalidOutputFolder +CommandLineIngestSettingsPanel.jLabelSelectOutputFolder.text=\u51fa\u529b\u30d5\u30a9\u30eb\u30c0\u30fc\u3092\u9078\u629e\: +CommandLineIngestSettingsPanel.jTextPane1.text=\u53d6\u8fbc\u307f\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u306f\u3001\u4f7f\u7528\u3059\u308b\u53d6\u8fbc\u307f\u30d5\u30a3\u30eb\u30bf\u30fc\u3001\u30e2\u30b8\u30e5\u30fc\u30eb\u3001\u304a\u3088\u3073\u8a2d\u5b9a\u3092\u5b9a\u7fa9\u3057\u307e\u3059\u3002 +CommandLineIngestSettingsPanel.outputPathTextField.text= +CommandLineIngestSettingsPanel.outputPathTextField.toolTipText=\u30b3\u30de\u30f3\u30c9\u30e9\u30a4\u30f3\u51e6\u7406\u7528\u306e\u51fa\u529b\u30d5\u30a9\u30eb\u30c0\u30fc(\u30b3\u30de\u30f3\u30c9\u30e9\u30a4\u30f3\u51e6\u7406\u30e2\u30fc\u30c9\u3067\u30b1\u30fc\u30b9\u30d5\u30a9\u30eb\u30c0\u30fc\u304c\u4f5c\u6210\u3055\u308c\u308b\u5834\u6240)\u3067\u3059\u3002 +CommandLineIngestSettingsPanel.reportDescriptionTextPane.text=\u30ec\u30dd\u30fc\u30c8\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u306f\u3001\u4f7f\u7528\u3059\u308b\u30ec\u30dd\u30fc\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\u3068\u8a2d\u5b9a\u3092\u5b9a\u7fa9\u3057\u307e\u3059\u3002 +CommandLineIngestSettingsPanel.reportProfileLabel.text=\u30ec\u30dd\u30fc\u30c8\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\uff1a +CommandLinePanel.jLabel1.text=\u30b3\u30de\u30f3\u30c9\u30e9\u30a4\u30f3\u30bf\u30b9\u30af\u3092\u5b9f\u884c\u3057\u3066\u3044\u307e\u3059\u3002 +CommandLineStartupWindow.title.text=\u30b3\u30de\u30f3\u30c9\u30e9\u30a4\u30f3\u30e2\u30fc\u30c9\u3067\u5b9f\u884c\u4e2d\u3067\u3059 +CommandListIngestSettingsPanel_Default_Report_DisplayName=\u30c7\u30d5\u30a9\u30eb\u30c8 +CommandListIngestSettingsPanel_Make_Config=\u65b0\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u3092\u4f5c\u6210... +CommandListIngestSettingsPanel_Report_Name_Msg=\u30ec\u30dd\u30fc\u30c8\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u540d\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\uff08\u6587\u5b57\u3001\u6570\u5b57\u3001\u304a\u3088\u3073\u4e0b\u7dda\u6587\u5b57\u306e\u307f\uff09\uff1a OpenIDE-Module-Name=CommandLineAutopsy OptionsCategory_Keywords_Command_Line_Ingest_Settings=\u30b3\u30de\u30f3\u30c9\u30e9\u30a4\u30f3\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u8a2d\u5b9a OptionsCategory_Keywords_General=\u30aa\u30d7\u30b7\u30e7\u30f3 OptionsCategory_Name_Command_Line_Ingest=\u30b3\u30de\u30f3\u30c9\u30e9\u30a4\u30f3\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8 -CommandLineIngestSettingsPanel.ResultsDirectoryUnspecified=\u51fa\u529b\u30d5\u30a9\u30eb\u30c0\u30fc\u3092\u8a2d\u5b9a\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059 -AutopsyOptionsPanel.agencyLogoPathFieldValidationLabel.invalidPath.text=\u30d1\u30b9\u306f\u6709\u52b9\u3067\u306f\u3042\u308a\u307e\u305b\u3093 -CommandLineIngestSettingsPanel.CannotAccess=\u30a2\u30af\u30bb\u30b9\u3067\u304d\u307e\u305b\u3093 -CommandLineIngestSettingsPanel.CheckPermissions=\u30a2\u30af\u30bb\u30b9\u6a29\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 -CommandLineIngestSettingsPanel.jLabelSelectOutputFolder.text=\u51fa\u529b\u30d5\u30a9\u30eb\u30c0\u30fc\u3092\u9078\u629e: -CommandLineIngestSettingsPanel.jLabelInvalidResultsFolder.text=jLabelInvalidOutputFolder -CommandLineIngestSettingsPanel.outputPathTextField.toolTipText=\u30b3\u30de\u30f3\u30c9\u30e9\u30a4\u30f3\u51e6\u7406\u7528\u306e\u51fa\u529b\u30d5\u30a9\u30eb\u30c0\u30fc(\u30b3\u30de\u30f3\u30c9\u30e9\u30a4\u30f3\u51e6\u7406\u30e2\u30fc\u30c9\u3067\u30b1\u30fc\u30b9\u30d5\u30a9\u30eb\u30c0\u30fc\u304c\u4f5c\u6210\u3055\u308c\u308b\u5834\u6240)\u3067\u3059\u3002 -CommandLineIngestSettingsPanel.outputPathTextField.text= -CommandLineIngestSettingsPanel.browseOutputFolderButton.text=\u53c2\u7167 -CommandLineIngestSettingsPanel.bnEditIngestSettings.toolTipText=\u30b3\u30de\u30f3\u30c9\u30e9\u30a4\u30f3\u51e6\u7406\u30e2\u30fc\u30c9\u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306e\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30b8\u30e7\u30d6\u8a2d\u5b9a\u3067\u3059\u3002 -CommandLineIngestSettingsPanel.bnEditIngestSettings.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\u8a2d\u5b9a -CommandLinePanel.jLabel1.text=\u30b3\u30de\u30f3\u30c9\u30e9\u30a4\u30f3\u304b\u3089\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u3092\u5b9f\u884c\u4e2d\u3067\u3059 -CommandLineStartupWindow.title.text=\u30b3\u30de\u30f3\u30c9\u30e9\u30a4\u30f3\u30e2\u30fc\u30c9\u3067\u5b9f\u884c\u4e2d\u3067\u3059 diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineCommand.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineCommand.java index dd3a10530d..1cacabe67c 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineCommand.java +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineCommand.java @@ -35,7 +35,8 @@ class CommandLineCommand { RUN_INGEST, LIST_ALL_DATA_SOURCES, GENERATE_REPORTS, - OPEN_CASE_IN_UI; + OPEN_CASE_IN_UI, + LIST_ALL_INGEST_PROFILES; } /** @@ -45,7 +46,6 @@ class CommandLineCommand { CASE_NAME, CASE_TYPE, CASES_BASE_DIR_PATH, - CASE_FOLDER_PATH, DATA_SOURCE_PATH, DATA_SOURCE_ID, INGEST_PROFILE_NAME, diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java index edbcd7a76f..ffc31fff1c 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019-2020 Basis Technology Corp. + * Copyright 2019-2022 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,11 +18,9 @@ */ package org.sleuthkit.autopsy.commandlineingest; +import com.google.gson.GsonBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.io.File; -import java.io.FilenameFilter; -import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; @@ -40,15 +38,12 @@ import org.openide.util.Lookup; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case.CaseType; import org.sleuthkit.autopsy.casemodule.CaseActionException; -import org.sleuthkit.autopsy.casemodule.CaseDetails; -import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; import static org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS; import static org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult.NO_ERRORS; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.TimeStampUtils; import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSource; import org.sleuthkit.autopsy.datasourceprocessors.AddDataSourceCallback; @@ -60,9 +55,10 @@ import org.sleuthkit.autopsy.ingest.IngestJobStartResult; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestModuleError; import org.sleuthkit.autopsy.ingest.IngestProfiles; +import org.sleuthkit.autopsy.ingest.IngestProfiles.IngestProfile; +import org.sleuthkit.autopsy.ingest.profile.IngestProfilePaths; import org.sleuthkit.autopsy.modules.interestingitems.FilesSet; import org.sleuthkit.autopsy.modules.interestingitems.FilesSetsManager; -import org.sleuthkit.autopsy.progress.LoggingProgressIndicator; import org.sleuthkit.autopsy.report.infrastructure.ReportGenerator; import org.sleuthkit.autopsy.report.infrastructure.ReportProgressIndicator; import org.sleuthkit.datamodel.Content; @@ -73,13 +69,16 @@ import org.sleuthkit.datamodel.TskCoreException; * cause Autopsy to create a case, add a specified data source, run ingest on * that data source, list all data sources in the case, and generate reports. */ -public class CommandLineIngestManager extends CommandLineManager{ +public class CommandLineIngestManager extends CommandLineManager { private static final Logger LOGGER = Logger.getLogger(CommandLineIngestManager.class.getName()); private static final Set INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.CANCELLED, IngestManager.IngestJobEvent.COMPLETED); private Case caseForJob = null; private AutoIngestDataSource dataSource = null; - private static final String LOG_DIR_NAME = "Command Output"; + + static final int CL_SUCCESS = 0; + static final int CL_RUN_FAILURE = -1; + static final int CL_PROCESS_FAILURE = -2; public CommandLineIngestManager() { } @@ -88,7 +87,11 @@ public class CommandLineIngestManager extends CommandLineManager{ new Thread(new JobProcessingTask()).start(); } - public void stop() { + void stop() { + stop(CL_SUCCESS); + } + + void stop(int errorCode) { try { // close current case if there is one open Case.closeCurrentCase(); @@ -97,7 +100,11 @@ public class CommandLineIngestManager extends CommandLineManager{ } // shut down Autopsy - LifecycleManager.getDefault().exit(); + if (errorCode == CL_SUCCESS) { + LifecycleManager.getDefault().exit(); + } else { + LifecycleManager.getDefault().exit(errorCode); + } } private final class JobProcessingTask implements Runnable { @@ -121,6 +128,7 @@ public class CommandLineIngestManager extends CommandLineManager{ @Override public void run() { LOGGER.log(Level.INFO, "Job processing task started"); + int errorCode = CL_SUCCESS; try { // read command line inputs @@ -138,174 +146,209 @@ public class CommandLineIngestManager extends CommandLineManager{ commands = ((CommandLineOptionProcessor) processor).getCommands(); } } - - if (commands == null || commands.isEmpty()) { - LOGGER.log(Level.SEVERE, "No command line commands specified"); - System.out.println("No command line commands specified"); - return; - } - try { + if (commands == null || commands.isEmpty()) { + LOGGER.log(Level.SEVERE, "No command line commands specified"); + System.out.println("No command line commands specified"); + errorCode = CL_RUN_FAILURE; + return; + } + // Commands are already stored in order in which they should be executed for (CommandLineCommand command : commands) { CommandLineCommand.CommandType type = command.getType(); switch (type) { case CREATE_CASE: - try { - LOGGER.log(Level.INFO, "Processing 'Create Case' command"); - System.out.println("Processing 'Create Case' command"); - Map inputs = command.getInputs(); - String baseCaseName = inputs.get(CommandLineCommand.InputType.CASE_NAME.name()); - String rootOutputDirectory = inputs.get(CommandLineCommand.InputType.CASES_BASE_DIR_PATH.name()); - CaseType caseType = CaseType.SINGLE_USER_CASE; - String caseTypeString = inputs.get(CommandLineCommand.InputType.CASE_TYPE.name()); - if (caseTypeString != null && caseTypeString.equalsIgnoreCase(CommandLineOptionProcessor.CASETYPE_MULTI)) { - caseType = CaseType.MULTI_USER_CASE; - } - openCase(baseCaseName, rootOutputDirectory, caseType); - - String outputDirPath = getOutputDirPath(caseForJob); - OutputGenerator.saveCreateCaseOutput(caseForJob, outputDirPath, baseCaseName); - } catch (CaseActionException ex) { - String baseCaseName = command.getInputs().get(CommandLineCommand.InputType.CASE_NAME.name()); - LOGGER.log(Level.SEVERE, "Error creating or opening case " + baseCaseName, ex); - System.out.println("Error creating or opening case " + baseCaseName); - // Do not process any other commands - return; + try { + LOGGER.log(Level.INFO, "Processing 'Create Case' command"); + System.out.println("Processing 'Create Case' command"); + Map inputs = command.getInputs(); + String baseCaseName = inputs.get(CommandLineCommand.InputType.CASE_NAME.name()); + String rootOutputDirectory = inputs.get(CommandLineCommand.InputType.CASES_BASE_DIR_PATH.name()); + CaseType caseType = CaseType.SINGLE_USER_CASE; + String caseTypeString = inputs.get(CommandLineCommand.InputType.CASE_TYPE.name()); + if (caseTypeString != null && caseTypeString.equalsIgnoreCase(CommandLineOptionProcessor.CASETYPE_MULTI)) { + caseType = CaseType.MULTI_USER_CASE; } - break; + caseForJob = createCase(baseCaseName, rootOutputDirectory, caseType); + + String outputDirPath = getOutputDirPath(caseForJob); + OutputGenerator.saveCreateCaseOutput(caseForJob, outputDirPath, baseCaseName); + } catch (CaseActionException ex) { + String baseCaseName = command.getInputs().get(CommandLineCommand.InputType.CASE_NAME.name()); + LOGGER.log(Level.SEVERE, "Error creating or opening case " + baseCaseName, ex); + System.out.println("Error creating or opening case " + baseCaseName); + // Do not process any other commands + errorCode = CL_RUN_FAILURE; + return; + } + break; case ADD_DATA_SOURCE: try { - LOGGER.log(Level.INFO, "Processing 'Add Data Source' command"); - System.out.println("Processing 'Add Data Source' command"); - Map inputs = command.getInputs(); + LOGGER.log(Level.INFO, "Processing 'Add Data Source' command"); + System.out.println("Processing 'Add Data Source' command"); + Map inputs = command.getInputs(); - // open the case, if it hasn't been already opened by CREATE_CASE command - if (caseForJob == null) { - String caseDirPath = inputs.get(CommandLineCommand.InputType.CASE_FOLDER_PATH.name()); - caseForJob = CommandLineIngestManager.this.openCase(caseDirPath); - } - - String dataSourcePath = inputs.get(CommandLineCommand.InputType.DATA_SOURCE_PATH.name()); - dataSource = new AutoIngestDataSource("", Paths.get(dataSourcePath)); - runDataSourceProcessor(caseForJob, dataSource); - - String outputDirPath = getOutputDirPath(caseForJob); - OutputGenerator.saveAddDataSourceOutput(caseForJob, dataSource, outputDirPath); - } catch (InterruptedException | AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException | CaseActionException ex) { - String dataSourcePath = command.getInputs().get(CommandLineCommand.InputType.DATA_SOURCE_PATH.name()); - LOGGER.log(Level.SEVERE, "Error adding data source " + dataSourcePath, ex); - System.out.println("Error adding data source " + dataSourcePath); - // Do not process any other commands - return; + // open the case, if it hasn't been already opened by CREATE_CASE command + if (caseForJob == null) { + // find case output directory by name and open the case + String baseCaseName = inputs.get(CommandLineCommand.InputType.CASE_NAME.name()); + String rootOutputDirectory = inputs.get(CommandLineCommand.InputType.CASES_BASE_DIR_PATH.name()); + caseForJob = openExistingCase(baseCaseName, rootOutputDirectory); } - break; + + String dataSourcePath = inputs.get(CommandLineCommand.InputType.DATA_SOURCE_PATH.name()); + dataSource = new AutoIngestDataSource(UUID.randomUUID().toString(), Paths.get(dataSourcePath)); + runDataSourceProcessor(caseForJob, dataSource); + + String outputDirPath = getOutputDirPath(caseForJob); + OutputGenerator.saveAddDataSourceOutput(caseForJob, dataSource, outputDirPath); + } catch (InterruptedException | AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException | CaseActionException ex) { + String dataSourcePath = command.getInputs().get(CommandLineCommand.InputType.DATA_SOURCE_PATH.name()); + LOGGER.log(Level.SEVERE, "Error adding data source " + dataSourcePath, ex); + System.out.println("Error adding data source " + dataSourcePath); + // Do not process any other commands + errorCode = CL_RUN_FAILURE; + return; + } + break; case RUN_INGEST: try { - LOGGER.log(Level.INFO, "Processing 'Run Ingest' command"); - System.out.println("Processing 'Run Ingest' command"); - Map inputs = command.getInputs(); + LOGGER.log(Level.INFO, "Processing 'Run Ingest' command"); + System.out.println("Processing 'Run Ingest' command"); + Map inputs = command.getInputs(); - // open the case, if it hasn't been already opened by CREATE_CASE or ADD_DATA_SOURCE commands - if (caseForJob == null) { - String caseDirPath = inputs.get(CommandLineCommand.InputType.CASE_FOLDER_PATH.name()); - caseForJob = CommandLineIngestManager.this.openCase(caseDirPath); - } - - // populate the AutoIngestDataSource structure, if that hasn't been done by ADD_DATA_SOURCE command - if (dataSource == null) { - - String dataSourceId = inputs.get(CommandLineCommand.InputType.DATA_SOURCE_ID.name()); - Long dataSourceObjId = Long.valueOf(dataSourceId); - - // get Content object for the data source - Content content = null; - try { - content = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(dataSourceObjId); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Exception while trying to find data source with object ID " + dataSourceId, ex); - System.out.println("Exception while trying to find data source with object ID " + dataSourceId); - // Do not process any other commands - return; - } - - if (content == null) { - LOGGER.log(Level.SEVERE, "Unable to find data source with object ID {0}", dataSourceId); - System.out.println("Unable to find data source with object ID " + dataSourceId); - // Do not process any other commands - return; - } - - // populate the AutoIngestDataSource structure - dataSource = new AutoIngestDataSource("", Paths.get(content.getName())); - List contentList = Arrays.asList(new Content[]{content}); - List errorList = new ArrayList<>(); - dataSource.setDataSourceProcessorOutput(NO_ERRORS, errorList, contentList); - } - - // run ingest - String ingestProfile = inputs.get(CommandLineCommand.InputType.INGEST_PROFILE_NAME.name()); - analyze(dataSource, ingestProfile); - } catch (InterruptedException | CaseActionException ex) { - String dataSourcePath = command.getInputs().get(CommandLineCommand.InputType.DATA_SOURCE_PATH.name()); - LOGGER.log(Level.SEVERE, "Error running ingest on data source " + dataSourcePath, ex); - System.out.println("Error running ingest on data source " + dataSourcePath); - // Do not process any other commands - return; + // open the case, if it hasn't been already opened by CREATE_CASE command + if (caseForJob == null) { + // find case output directory by name and open the case + String baseCaseName = inputs.get(CommandLineCommand.InputType.CASE_NAME.name()); + String rootOutputDirectory = inputs.get(CommandLineCommand.InputType.CASES_BASE_DIR_PATH.name()); + caseForJob = openExistingCase(baseCaseName, rootOutputDirectory); } - break; + + // populate the AutoIngestDataSource structure, if that hasn't been done by ADD_DATA_SOURCE command + if (dataSource == null) { + + String dataSourceId = inputs.get(CommandLineCommand.InputType.DATA_SOURCE_ID.name()); + Long dataSourceObjId = Long.valueOf(dataSourceId); + + // get Content object for the data source + Content content = null; + try { + content = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(dataSourceObjId); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Exception while trying to find data source with object ID " + dataSourceId, ex); + System.out.println("Exception while trying to find data source with object ID " + dataSourceId); + // Do not process any other commands + errorCode = CL_RUN_FAILURE; + return; + } + + if (content == null) { + LOGGER.log(Level.SEVERE, "Unable to find data source with object ID {0}", dataSourceId); + System.out.println("Unable to find data source with object ID " + dataSourceId); + // Do not process any other commands + return; + } + + // populate the AutoIngestDataSource structure + dataSource = new AutoIngestDataSource("", Paths.get(content.getName())); + List contentList = Arrays.asList(new Content[]{content}); + List errorList = new ArrayList<>(); + dataSource.setDataSourceProcessorOutput(NO_ERRORS, errorList, contentList); + } + + // run ingest + String ingestProfile = inputs.get(CommandLineCommand.InputType.INGEST_PROFILE_NAME.name()); + analyze(dataSource, ingestProfile); + } catch (InterruptedException | CaseActionException | AnalysisStartupException ex) { + String dataSourcePath = command.getInputs().get(CommandLineCommand.InputType.DATA_SOURCE_PATH.name()); + LOGGER.log(Level.SEVERE, "Error running ingest on data source " + dataSourcePath, ex); + System.out.println("Error running ingest on data source " + dataSourcePath); + // Do not process any other commands + errorCode = CL_RUN_FAILURE; + return; + } + break; case LIST_ALL_DATA_SOURCES: try { - LOGGER.log(Level.INFO, "Processing 'List All Data Sources' command"); - System.out.println("Processing 'List All Data Sources' command"); - Map inputs = command.getInputs(); + LOGGER.log(Level.INFO, "Processing 'List All Data Sources' command"); + System.out.println("Processing 'List All Data Sources' command"); + Map inputs = command.getInputs(); - // open the case, if it hasn't been already opened by previous command - if (caseForJob == null) { - String caseDirPath = inputs.get(CommandLineCommand.InputType.CASE_FOLDER_PATH.name()); - caseForJob = CommandLineIngestManager.this.openCase(caseDirPath); - } - - String outputDirPath = getOutputDirPath(caseForJob); - OutputGenerator.listAllDataSources(caseForJob, outputDirPath); - } catch (CaseActionException ex) { - String caseDirPath = command.getInputs().get(CommandLineCommand.InputType.CASE_FOLDER_PATH.name()); - LOGGER.log(Level.SEVERE, "Error opening case in case directory: " + caseDirPath, ex); - System.out.println("Error opening case in case directory: " + caseDirPath); - // Do not process any other commands - return; + // open the case, if it hasn't been already opened by CREATE_CASE command + if (caseForJob == null) { + // find case output directory by name and open the case + String baseCaseName = inputs.get(CommandLineCommand.InputType.CASE_NAME.name()); + String rootOutputDirectory = inputs.get(CommandLineCommand.InputType.CASES_BASE_DIR_PATH.name()); + caseForJob = openExistingCase(baseCaseName, rootOutputDirectory); } - break; + + String outputDirPath = getOutputDirPath(caseForJob); + OutputGenerator.listAllDataSources(caseForJob, outputDirPath); + } catch (CaseActionException ex) { + String baseCaseName = command.getInputs().get(CommandLineCommand.InputType.CASE_NAME.name()); + String rootOutputDirectory = command.getInputs().get(CommandLineCommand.InputType.CASES_BASE_DIR_PATH.name()); + String msg = "Error opening case " + baseCaseName + " in directory: " + rootOutputDirectory; + LOGGER.log(Level.SEVERE, msg, ex); + System.out.println(msg); + errorCode = CL_RUN_FAILURE; + // Do not process any other commands + return; + } + break; case GENERATE_REPORTS: try { - LOGGER.log(Level.INFO, "Processing 'Generate Reports' command"); - System.out.println("Processing 'Generate Reports' command"); - Map inputs = command.getInputs(); + LOGGER.log(Level.INFO, "Processing 'Generate Reports' command"); + System.out.println("Processing 'Generate Reports' command"); + Map inputs = command.getInputs(); - // open the case, if it hasn't been already opened by previous command - if (caseForJob == null) { - String caseDirPath = inputs.get(CommandLineCommand.InputType.CASE_FOLDER_PATH.name()); - caseForJob = CommandLineIngestManager.this.openCase(caseDirPath); - } - // generate reports - String reportName = inputs.get(CommandLineCommand.InputType.REPORT_PROFILE_NAME.name()); - if (reportName == null) { - reportName = CommandLineIngestSettingsPanel.getDefaultReportingConfigName(); - } - - // generate reports - ReportProgressIndicator progressIndicator = new ReportProgressIndicator(new CommandLineProgressIndicator()); - ReportGenerator generator = new ReportGenerator(reportName, progressIndicator); - generator.generateReports(); - } catch (CaseActionException ex) { - String caseDirPath = command.getInputs().get(CommandLineCommand.InputType.CASE_FOLDER_PATH.name()); - LOGGER.log(Level.SEVERE, "Error opening case in case directory: " + caseDirPath, ex); - System.out.println("Error opening case in case directory: " + caseDirPath); - // Do not process any other commands - return; + // open the case, if it hasn't been already opened by CREATE_CASE command + if (caseForJob == null) { + // find case output directory by name and open the case + String baseCaseName = inputs.get(CommandLineCommand.InputType.CASE_NAME.name()); + String rootOutputDirectory = inputs.get(CommandLineCommand.InputType.CASES_BASE_DIR_PATH.name()); + caseForJob = openExistingCase(baseCaseName, rootOutputDirectory); } + // generate reports + String reportName = inputs.get(CommandLineCommand.InputType.REPORT_PROFILE_NAME.name()); + if (reportName == null) { + reportName = CommandLineIngestSettingsPanel.getDefaultReportingConfigName(); + } + + // generate reports + ReportProgressIndicator progressIndicator = new ReportProgressIndicator(new CommandLineProgressIndicator()); + ReportGenerator generator = new ReportGenerator(reportName, progressIndicator); + generator.generateReports(); + } catch (CaseActionException ex) { + String baseCaseName = command.getInputs().get(CommandLineCommand.InputType.CASE_NAME.name()); + String rootOutputDirectory = command.getInputs().get(CommandLineCommand.InputType.CASES_BASE_DIR_PATH.name()); + String msg = "Error opening case " + baseCaseName + " in directory: " + rootOutputDirectory; + LOGGER.log(Level.SEVERE, msg, ex); + System.out.println(msg); + errorCode = CL_RUN_FAILURE; + // Do not process any other commands + return; + } catch (Exception ex) { + String msg = "An exception occurred while generating report: " + ex.getMessage(); + LOGGER.log(Level.WARNING, msg, ex); + System.out.println(msg); + errorCode = CL_RUN_FAILURE; + // Do not process any other commands + return; + } + break; + case LIST_ALL_INGEST_PROFILES: + List profiles = IngestProfiles.getIngestProfiles(); + GsonBuilder gb = new GsonBuilder(); + System.out.println("Listing ingest profiles"); + for (IngestProfile profile : profiles) { + String jsonText = gb.create().toJson(profile); + System.out.println(jsonText); + } + System.out.println("Ingest profile list complete"); break; default: break; @@ -321,7 +364,7 @@ public class CommandLineIngestManager extends CommandLineManager{ */ LOGGER.log(Level.SEVERE, "Unexpected error", ex); System.out.println("Unexpected error. Exiting..."); - + errorCode = CL_RUN_FAILURE; } finally { try { Case.closeCurrentCase(); @@ -336,43 +379,10 @@ public class CommandLineIngestManager extends CommandLineManager{ System.out.println("Job processing task finished"); // shut down Autopsy - stop(); + stop(errorCode); } } - /** - * Creates a new case using arguments passed in from command line - * CREATE_CASE command. - * - * @param baseCaseName Case name - * @param rootOutputDirectory Full path to directory in which case - * output folder will be created - * @param caseType Type of case being created - * - * @throws CaseActionException - */ - private void openCase(String baseCaseName, String rootOutputDirectory, CaseType caseType) throws CaseActionException { - - LOGGER.log(Level.INFO, "Opening case {0} in directory {1}", new Object[]{baseCaseName, rootOutputDirectory}); - Path caseDirectoryPath = findCaseDirectory(Paths.get(rootOutputDirectory), baseCaseName); - if (null != caseDirectoryPath) { - // found an existing case directory for same case name. the input case name must be unique. Exit. - LOGGER.log(Level.SEVERE, "Case {0} already exists. Case name must be unique. Exiting", baseCaseName); - throw new CaseActionException("Case " + baseCaseName + " already exists. Case name must be unique. Exiting"); - } else { - caseDirectoryPath = createCaseFolderPath(Paths.get(rootOutputDirectory), baseCaseName); - - // Create the case directory - Case.createCaseDirectory(caseDirectoryPath.toString(), Case.CaseType.SINGLE_USER_CASE); - - CaseDetails caseDetails = new CaseDetails(baseCaseName); - Case.createAsCurrentCase(caseType, caseDirectoryPath.toString(), caseDetails); - } - - caseForJob = Case.getCurrentCase(); - LOGGER.log(Level.INFO, "Opened case {0}", caseForJob.getName()); - } - /** * Passes the data source for the current job through a data source * processor that adds it to the case database. @@ -381,10 +391,13 @@ public class CommandLineIngestManager extends CommandLineManager{ * @param dataSource The data source. * * @throws AutoIngestDataSourceProcessorException if there was a DSP - * processing error. + * processing error. * - * @throws InterruptedException running the job processing task while - * blocking, i.e., if auto ingest is shutting down. + * @throws InterruptedException running the job + * processing task while + * blocking, i.e., if + * auto ingest is + * shutting down. */ private void runDataSourceProcessor(Case caseForJob, AutoIngestDataSource dataSource) throws InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException { @@ -508,7 +521,7 @@ public class CommandLineIngestManager extends CommandLineManager{ // unable to find the user specified profile LOGGER.log(Level.SEVERE, "Unable to find ingest profile: {0}. Ingest cancelled!", ingestProfileName); System.out.println("Unable to find ingest profile: " + ingestProfileName + ". Ingest cancelled!"); - return; + throw new AnalysisStartupException("Unable to find ingest profile: " + ingestProfileName + ". Ingest cancelled!"); } // get FileSet filter associated with this profile @@ -517,7 +530,7 @@ public class CommandLineIngestManager extends CommandLineManager{ // unable to find the user specified profile LOGGER.log(Level.SEVERE, "Unable to find file filter {0} for ingest profile: {1}. Ingest cancelled!", new Object[]{selectedProfile.getFileIngestFilter(), ingestProfileName}); System.out.println("Unable to find file filter " + selectedProfile.getFileIngestFilter() + " for ingest profile: " + ingestProfileName + ". Ingest cancelled!"); - return; + throw new AnalysisStartupException("Unable to find file filter " + selectedProfile.getFileIngestFilter() + " for ingest profile: " + ingestProfileName + ". Ingest cancelled!"); } } @@ -531,7 +544,7 @@ public class CommandLineIngestManager extends CommandLineManager{ ingestJobSettings = new IngestJobSettings(UserPreferences.getCommandLineModeIngestModuleContextString()); } else { // load the custom ingest - ingestJobSettings = new IngestJobSettings(selectedProfile.toString()); + ingestJobSettings = new IngestJobSettings(IngestProfilePaths.getInstance().getIngestProfilePrefix() + selectedProfile.toString()); ingestJobSettings.setFileFilter(selectedFileSet); } @@ -543,28 +556,33 @@ public class CommandLineIngestManager extends CommandLineManager{ /* * Block until notified by the ingest job event * listener or until interrupted because auto ingest - * is shutting down. + * is shutting down. For very small jobs, it is + * possible that ingest has completed by the time we + * get here, so check periodically in case the event + * was missed. */ - ingestLock.wait(); + while (IngestManager.getInstance().isIngestRunning()) { + ingestLock.wait(60000); // Check every minute + } + LOGGER.log(Level.INFO, "Finished ingest modules analysis for {0} ", dataSource.getPath()); IngestJob.ProgressSnapshot jobSnapshot = ingestJob.getSnapshot(); - for (IngestJob.ProgressSnapshot.DataSourceProcessingSnapshot snapshot : jobSnapshot.getDataSourceSnapshots()) { - if (!snapshot.isCancelled()) { - List cancelledModules = snapshot.getCancelledDataSourceIngestModules(); - if (!cancelledModules.isEmpty()) { - LOGGER.log(Level.WARNING, String.format("Ingest module(s) cancelled for %s", dataSource.getPath())); - for (String module : snapshot.getCancelledDataSourceIngestModules()) { - LOGGER.log(Level.WARNING, String.format("%s ingest module cancelled for %s", module, dataSource.getPath())); - } - } - LOGGER.log(Level.INFO, "Analysis of data source completed"); - } else { - LOGGER.log(Level.WARNING, "Analysis of data source cancelled"); - IngestJob.CancellationReason cancellationReason = snapshot.getCancellationReason(); - if (IngestJob.CancellationReason.NOT_CANCELLED != cancellationReason && IngestJob.CancellationReason.USER_CANCELLED != cancellationReason) { - throw new AnalysisStartupException(String.format("Analysis cancelled due to %s for %s", cancellationReason.getDisplayName(), dataSource.getPath())); + IngestJob.ProgressSnapshot.DataSourceProcessingSnapshot snapshot = jobSnapshot.getDataSourceProcessingSnapshot(); + if (!snapshot.isCancelled()) { + List cancelledModules = snapshot.getCancelledDataSourceIngestModules(); + if (!cancelledModules.isEmpty()) { + LOGGER.log(Level.WARNING, String.format("Ingest module(s) cancelled for %s", dataSource.getPath())); + for (String module : snapshot.getCancelledDataSourceIngestModules()) { + LOGGER.log(Level.WARNING, String.format("%s ingest module cancelled for %s", module, dataSource.getPath())); } } + LOGGER.log(Level.INFO, "Analysis of data source completed"); + } else { + LOGGER.log(Level.WARNING, "Analysis of data source cancelled"); + IngestJob.CancellationReason cancellationReason = snapshot.getCancellationReason(); + if (IngestJob.CancellationReason.NOT_CANCELLED != cancellationReason && IngestJob.CancellationReason.USER_CANCELLED != cancellationReason) { + throw new AnalysisStartupException(String.format("Analysis cancelled due to %s for %s", cancellationReason.getDisplayName(), dataSource.getPath())); + } } } else if (!ingestJobStartResult.getModuleErrors().isEmpty()) { for (IngestModuleError error : ingestJobStartResult.getModuleErrors()) { @@ -633,59 +651,6 @@ public class CommandLineIngestManager extends CommandLineManager{ } } - /** - * Creates a case folder path. Does not create the folder described by - * the path. - * - * @param caseFoldersPath The root case folders path. - * @param caseName The name of the case. - * - * @return A case folder path with a time stamp suffix. - */ - private Path createCaseFolderPath(Path caseFoldersPath, String caseName) { - String folderName = caseName + "_" + TimeStampUtils.createTimeStamp(); - return Paths.get(caseFoldersPath.toString(), folderName); - } - - /** - * Searches a given folder for the most recently modified case folder - * for a case. - * - * @param folderToSearch The folder to be searched. - * @param caseName The name of the case for which a case folder is - * to be found. - * - * @return The path of the case folder, or null if it is not found. - */ - private Path findCaseDirectory(Path folderToSearch, String caseName) { - File searchFolder = new File(folderToSearch.toString()); - if (!searchFolder.isDirectory()) { - return null; - } - Path caseFolderPath = null; - String[] candidateFolders = searchFolder.list(new CaseFolderFilter(caseName)); - long mostRecentModified = 0; - for (String candidateFolder : candidateFolders) { - File file = new File(candidateFolder); - if (file.lastModified() >= mostRecentModified) { - mostRecentModified = file.lastModified(); - caseFolderPath = Paths.get(folderToSearch.toString(), file.getPath()); - } - } - return caseFolderPath; - } - - /** - * Returns full path to directory where command outputs should be saved. - * - * @param caseForJob Case object - * - * @return Full path to directory where command outputs should be saved - */ - private String getOutputDirPath(Case caseForJob) { - return caseForJob.getCaseDirectory() + File.separator + LOG_DIR_NAME; - } - /** * An ingest job event listener that allows the job processing task to * block until the analysis of a data source by the data source level @@ -770,50 +735,4 @@ public class CommandLineIngestManager extends CommandLineManager{ } } } - - private static class CaseFolderFilter implements FilenameFilter { - - private final String caseName; - private final static String CASE_METADATA_EXT = CaseMetadata.getFileExtension(); - - CaseFolderFilter(String caseName) { - this.caseName = caseName; - } - - @Override - public boolean accept(File folder, String fileName) { - File file = new File(folder, fileName); - if (fileName.length() > TimeStampUtils.getTimeStampLength() && file.isDirectory()) { - if (TimeStampUtils.endsWithTimeStamp(fileName)) { - if (null != caseName) { - String fileNamePrefix = fileName.substring(0, fileName.length() - TimeStampUtils.getTimeStampLength()); - if (fileNamePrefix.equals(caseName)) { - return hasCaseMetadataFile(file); - } - } else { - return hasCaseMetadataFile(file); - } - } - } - return false; - } - - /** - * Determines whether or not there is a case metadata file in a given - * folder. - * - * @param folder The file object representing the folder to search. - * - * @return True or false. - */ - private static boolean hasCaseMetadataFile(File folder) { - for (File file : folder.listFiles()) { - if (file.getName().toLowerCase().endsWith(CASE_METADATA_EXT) && file.isFile()) { - return true; - } - } - return false; - } - } - } diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestSettingsPanel.java index db60e7a0af..531f96a878 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestSettingsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019-2020 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -280,9 +280,10 @@ public class CommandLineIngestSettingsPanel extends javax.swing.JPanel { add(nodePanel, java.awt.BorderLayout.CENTER); }// //GEN-END:initComponents @Messages({ - "CommandListIngestSettingsPanel_Report_Name_Msg=Please supply a report profile name:", + "CommandListIngestSettingsPanel_Report_Name_Msg=Please supply a report profile name (letters, digits, and underscore characters only):", "CommandLineIngestSettingPanel_empty_report_name_mgs=Report profile name was empty, no profile created.", - "CommandLineIngestSettingPanel_existing_report_name_mgs=Report profile name was already exists, no profile created." + "CommandLineIngestSettingPanel_existing_report_name_mgs=Report profile name was already exists, no profile created.", + "CommandLineIngestSettingPanel_invalid_report_name_mgs=Report profile name contained illegal characters, no profile created." }) private void bnEditReportSettingsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnEditReportSettingsActionPerformed String reportName = getReportName(); @@ -298,6 +299,15 @@ public class CommandLineIngestSettingsPanel extends javax.swing.JPanel { } else if (doesReportProfileNameExist(reportName)) { JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), Bundle.CommandLineIngestSettingPanel_existing_report_name_mgs()); return; + } else { + // sanitize report name + String originalReportName = reportName; + reportName = reportName.replaceAll("[^A-Za-z0-9_]", ""); + if (reportName.isEmpty() || (!(originalReportName.equals(reportName)))) { + // report name contained only invalid characters, display error + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), Bundle.CommandLineIngestSettingPanel_invalid_report_name_mgs()); + return; + } } } diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineManager.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineManager.java index e52b3f4113..c40358bf8f 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineManager.java +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineManager.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2020 Basis Technology Corp. + * Copyright 2020-2022 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,22 +19,29 @@ package org.sleuthkit.autopsy.commandlineingest; import java.io.File; +import java.io.FilenameFilter; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.logging.Level; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.CaseDetails; +import org.sleuthkit.autopsy.casemodule.CaseMetadata; import static org.sleuthkit.autopsy.casemodule.CaseMetadata.getFileExtension; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.TimeStampUtils; /** * Base class for the command line managers. */ class CommandLineManager { + private static final String LOG_DIR_NAME = "Command Output"; private static final Logger LOGGER = Logger.getLogger(CommandLineOpenCaseManager.class.getName()); /** - * Opens existing case. + * Opens existing case using full path to case directory or full path to + * .aut file. * * @param casePath full path to case directory or full path to .aut file * @@ -89,4 +96,163 @@ class CommandLineManager { throw new CaseActionException("Case directory was not found"); } + /** + * Creates a new case using arguments passed in from command line + * CREATE_CASE command. + * + * @param baseCaseName Case name + * @param rootOutputDirectory Full path to directory in which case output + * folder will be created + * @param caseType Type of case being created + * + * @throws CaseActionException + */ + Case createCase(String baseCaseName, String rootOutputDirectory, Case.CaseType caseType) throws CaseActionException { + + LOGGER.log(Level.INFO, "Creating new case {0} in directory {1}", new Object[]{baseCaseName, rootOutputDirectory}); + Path caseDirectoryPath = findCaseDirectory(Paths.get(rootOutputDirectory), baseCaseName); + if (null != caseDirectoryPath) { + // found an existing case directory for same case name. the input case name must be unique. Exit. + LOGGER.log(Level.SEVERE, "Case {0} already exists. Case name must be unique. Exiting", baseCaseName); + throw new CaseActionException("Case " + baseCaseName + " already exists. Case name must be unique. Exiting"); + } else { + caseDirectoryPath = createCaseFolderPath(Paths.get(rootOutputDirectory), baseCaseName); + + // Create the case directory + Case.createCaseDirectory(caseDirectoryPath.toString(), caseType); + + CaseDetails caseDetails = new CaseDetails(baseCaseName); + Case.createAsCurrentCase(caseType, caseDirectoryPath.toString(), caseDetails); + } + + Case caseForJob = Case.getCurrentCase(); + LOGGER.log(Level.INFO, "Created case {0}", caseForJob.getName()); + return caseForJob; + } + + /** + * Creates a case folder path. Does not create the folder described by the + * path. + * + * @param caseFoldersPath The root case folders path. + * @param caseName The name of the case. + * + * @return A case folder path with a time stamp suffix. + */ + private Path createCaseFolderPath(Path caseFoldersPath, String caseName) { + String folderName = caseName + "_" + TimeStampUtils.createTimeStamp(); + return Paths.get(caseFoldersPath.toString(), folderName); + } + + /** + * Opens an existing case using arguments passed in from command line. + * + * @param caseName Case name + * @param rootOutputDirectory Full path to top level directory in which case + * output folder is located + * + * @throws CaseActionException + */ + Case openExistingCase(String caseName, String rootOutputDirectory) throws CaseActionException { + LOGGER.log(Level.INFO, "Opening case {0} in directory {1}", new Object[]{caseName, rootOutputDirectory}); + Path caseDirectoryPath = findCaseDirectory(Paths.get(rootOutputDirectory), caseName); + if (null != caseDirectoryPath) { + // found an existing case directory for same case name. + Path metadataFilePath = caseDirectoryPath.resolve(caseName + CaseMetadata.getFileExtension()); + Case.openAsCurrentCase(metadataFilePath.toString()); + } else { + // did not find existing case directory for same case name. Exit. + LOGGER.log(Level.SEVERE, "Case {0} doesn't exist. Exiting", caseName); + throw new CaseActionException("Case " + caseName + " doesn't exist. Exiting"); + } + + Case caseForJob = Case.getCurrentCase(); + LOGGER.log(Level.INFO, "Opened case {0}", caseForJob.getName()); + return caseForJob; + } + + /** + * Searches a given folder for the most recently modified case folder for a + * case. + * + * @param folderToSearch The folder to be searched. + * @param caseName The name of the case for which a case folder is to be + * found. + * + * @return The path of the case folder, or null if it is not found. + */ + private Path findCaseDirectory(Path folderToSearch, String caseName) { + File searchFolder = new File(folderToSearch.toString()); + if (!searchFolder.isDirectory()) { + return null; + } + Path caseFolderPath = null; + String[] candidateFolders = searchFolder.list(new CaseFolderFilter(caseName)); + long mostRecentModified = 0; + for (String candidateFolder : candidateFolders) { + File file = new File(candidateFolder); + if (file.lastModified() >= mostRecentModified) { + mostRecentModified = file.lastModified(); + caseFolderPath = Paths.get(folderToSearch.toString(), file.getPath()); + } + } + return caseFolderPath; + } + + /** + * Returns full path to directory where command outputs should be saved. + * + * @param caseForJob Case object + * + * @return Full path to directory where command outputs should be saved + */ + String getOutputDirPath(Case caseForJob) { + return caseForJob.getCaseDirectory() + File.separator + LOG_DIR_NAME; + } + + private static class CaseFolderFilter implements FilenameFilter { + + private final String caseName; + private final static String CASE_METADATA_EXT = CaseMetadata.getFileExtension(); + + CaseFolderFilter(String caseName) { + this.caseName = caseName; + } + + @Override + public boolean accept(File folder, String fileName) { + File file = new File(folder, fileName); + if (fileName.length() > TimeStampUtils.getTimeStampLength() && file.isDirectory()) { + if (TimeStampUtils.endsWithTimeStamp(fileName)) { + if (null != caseName) { + String fileNamePrefix = fileName.substring(0, fileName.length() - TimeStampUtils.getTimeStampLength()); + if (fileNamePrefix.equals(caseName)) { + return hasCaseMetadataFile(file); + } + } else { + return hasCaseMetadataFile(file); + } + } + } + return false; + } + + /** + * Determines whether or not there is a case metadata file in a given + * folder. + * + * @param folder The file object representing the folder to search. + * + * @return True or false. + */ + private static boolean hasCaseMetadataFile(File folder) { + for (File file : folder.listFiles()) { + if (file.getName().toLowerCase().endsWith(CASE_METADATA_EXT) && file.isFile()) { + return true; + } + } + return false; + } + } + } diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java index ad84bdcd0f..921c2e0a65 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019-2020 Basis Technology Corp. + * Copyright 2019-2022 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.commandlineingest; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.io.File; import java.util.ArrayList; import java.util.Collections; @@ -26,6 +28,8 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.sleuthkit.autopsy.coreutils.Logger; import org.netbeans.api.sendopts.CommandException; import org.netbeans.spi.sendopts.Env; @@ -48,10 +52,10 @@ public class CommandLineOptionProcessor extends OptionProcessor { private final Option dataSourcePathOption = Option.requiredArgument('s', "dataSourcePath"); private final Option dataSourceObjectIdOption = Option.requiredArgument('i', "dataSourceObjectId"); private final Option addDataSourceCommandOption = Option.withoutArgument('a', "addDataSource"); - private final Option caseDirOption = Option.requiredArgument('d', "caseDir"); private final Option runIngestCommandOption = Option.optionalArgument('r', "runIngest"); private final Option listAllDataSourcesCommandOption = Option.withoutArgument('l', "listAllDataSources"); private final Option generateReportsOption = Option.optionalArgument('g', "generateReports"); + private final Option listAllIngestProfileOption = Option.withoutArgument('p', "listAllIngestProfiles"); private final Option defaultArgument = Option.defaultArguments(); private boolean runFromCommandLine = false; @@ -62,6 +66,18 @@ public class CommandLineOptionProcessor extends OptionProcessor { final static String CASETYPE_SINGLE = "single"; private String defaultArgumentValue = null; + + private PropertyChangeSupport changes = new PropertyChangeSupport(this); + public static String PROCESSING_STARTED = "command line process started"; + public static String PROCESSING_COMPLETED = "command line process completed"; + + public enum ProcessState { + NOT_STARTED, + RUNNING, + COMPLETED + } + + private ProcessState state = ProcessState.NOT_STARTED; @Override protected Set
@@ -281,7 +281,7 @@ - + @@ -363,5 +363,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java index 94fb1bca86..50b0f484ad 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,13 +19,11 @@ package org.sleuthkit.autopsy.communications.relationships; import java.awt.CardLayout; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.DefaultListModel; import javax.swing.JPanel; -import javax.swing.SwingWorker; import org.netbeans.swing.outline.DefaultOutlineModel; import org.netbeans.swing.outline.Outline; import org.openide.explorer.view.OutlineView; @@ -33,11 +31,10 @@ import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.util.Lookup; import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.datamodel.Account; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.centralrepository.datamodel.Persona; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.datamodel.AccountFileInstance; /** * Account Summary View Panel. This panel shows a list of various counts related @@ -47,11 +44,15 @@ import org.sleuthkit.datamodel.AccountFileInstance; */ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsViewer { + private static final long serialVersionUID = 1L; + private final Lookup lookup; private final DefaultListModel fileRefListModel; private static final Logger logger = Logger.getLogger(SummaryViewer.class.getName()); + private SummaryPanelWorker worker; + @Messages({ "SummaryViewer_TabTitle=Summary", "SummaryViewer_FileRefNameColumn_Title=Path", @@ -62,7 +63,9 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi "SummaryViewer_Device_Account_Description=This account was referenced by a device in the case.", "SummaryViewer_Account_Description=This account represents a device in the case.", "SummaryViewer_Account_Description_MuliSelect=Summary information is not available when multiple accounts are selected.", - "SummaryViewer_Country_Code=Country: " + "SummaryViewer_Country_Code=Country: ", + "SummaryViewer_Select_account_for_persona= + + +
+
+ + +
{{updateStatusMessage}}
+ +
+ +

+

+ +

+ +

+

+

+ +

+

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ + Show omit options + Hide omit options + +

+ +
+ +

+ +

+ +

+ +

+ +

+ +

+
+ +

+ + Show term vector options + Hide term vector options + +

+
+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +
+ +

+ + Show sort options + Show sort options + +

+
+

+ +

+ +

+ +

+ +
+ +

+ + Show text analysis JSON + Hide text analysis JSON + +

+
+ +
+ +

+ + +

+ +
+
{{error}}
+
+
+
+
+
+
+ + + +
+
+ + +
+

 {{designerAPIError}}

+
+
+ + +
+
+ +
+

 {{apiWarning}}

+
+
+ +
+
+ +
+
+

No differences with {{diffSource}} found.

+
+ {{fieldsDiff=schemaDiff.fieldsDiff;""}} +

Found differences with {{diffSource}}:

+
+
+

Updated Fields

+ + + + + + + + + + + +
NameOldNew
{{ field }}

{{fieldDiff[0] | json}}

{{fieldDiff[1] | json}}

+
+
+

New Fields

+ + + + + + + + + + + + + + + + + + + + + +
NameTypeIndexedStoredMultiValuedDocValuesTokenized
{{f.name}}{{f.type}}
+
+
+

Removed Fields

+
    +
  • +

    {{f}}

    +
  • +
+
+
+ {{fieldTypesDiff=schemaDiff.fieldTypesDiff;""}} +
+
+

Updated Field Types

+ + + + + + + + + + + +
NameOldNew
{{ fieldType }}

{{fieldTypeDiff[0] | json}}

{{fieldTypeDiff[1] | json}}

+
+
+

New Field Types

+
    +
  • +

    {{fieldtype | json}}

    +
  • +
+
+
+

Removed Field Types

+
    +
  • +

    {{fieldtype}}

    +
  • +
+
+
+ {{dynamicFieldsDiff=schemaDiff.dynamicFieldsDiff;""}} +
+
+

Updated Dynamic Fields

+ + + + + + + + + + + +
NameOldNew
{{ dfield }}

{{dfieldDiff[0] | json}}

{{dfieldDiff[1] | json}}

+
+
+

New Dynamic Fields

+
    +
  • +

    {{dfield | json}}

    +
  • +
+
+
+

Removed Dynamic Fields

+
    +
  • +

    {{dfield.name}}

    +
  • +
+
+
+ {{copyFieldsDiff=schemaDiff.copyFieldsDiff;""}} +
+

Copy Fields

+ + + + + + + + + + + + + +
SourceDestination

{{cfield.source}}

{{cfield.dest}}

{{cfield.source}}

{{cfield.dest}}

+
+ +
+ +
+
+ +
+

Publish schema and associated configs to Zookeeper as a Solr ConfigSet.

+
+

Affected collections:

+
    +
  • {{coll}}
  • +
+
No existing collections
+
+
+
+ + +
+
+ +
+

Add new collection with published config?

+
+

+

+ +

+

+ +

+

+
+
+ + +
+
+
+ + +
+
{{error}}
+
+ + +
+
+ +
+

Warning: You've chosen to load an existing schema that is already being used by active collections. Making changes to the '{{confirmSchema}}' schema will impact existing documents in these collections. Please proceed with caution.

+

Collections using the {{confirmSchema}} schema:

+
  • {{coll}}
+

+ + +

+
+ +
+

Get started building your search app by defining a new schema. A schema defines all the fields in your search index. Solr comes with a _default schema that provides a good starting point to customize based on your specific data.

+
+

+
+ +
+
+

Copy an existing Configset (schema, solrconfig.xml, and supporting files) as the starting point for your new schema. The _default Configset includes a schema with field guessing enabled, dynamic fields for common field types, and field types for analyzing text data for a number of common languages supported by Lucene. For more information about Configsets, see:

+
+
+
+

{{addMessage}}

+

+ + +

+
+
+ + + + +
+ +
+
+

Sample Documents

+
+
+
+
+ +
+
+

Upload a JSON, CSS, or XML file containing sample documents or simply paste some sample documents into the text area below; the Schema Designer supports a maximum of 5MB and 1,000 documents. +

Click on the Analyze Documents button to have Solr determine the schema by looking at the sample values for each field. Sample documents are stored on the server so you can make changes to the schema and Schema Designer will automatically re-index the sample documents to apply the changes. +

+
+
+
+ + +
+
+

{{sampleMessage}}

+ +
+
+
+
+ +
+ +
+
+

Schema Editor

+
+ Filter fields by:  + + + + + +
+
+ +
+
+
+ + +
+ +
+
+ +

{{currentSchema}}

+
+
+ + +
+
+ + +
+
+

By default, schemas copied from the _default schema include field types (and associated files) for all built-in languages. Select the languages needed for your application and the designer will remove all the unnecessary field types for languages you don't need. For more information about text analysis and languages, see: +

+

+
+
+
+
+ + + +
+
+

Dynamic fields allow Solr to index fields that you did not explicitly define in your schema. Dynamic fields can make your application less brittle by providing some flexibility in the documents you can add to Solr. It is recommended to keep the default set of dynamic fields enabled for your schema. Unchecking this option removes all dynamic fields from your schema. For more information about dynamic fields, see: +

+

+
+
+
+
+ + + +
+
+

Field guessing (aka "schemaless mode") allows Solr to detect the "best" field type for unknown fields encountered during indexing. Field guessing also performs some field transformations, such as removing spaces from field names. If you use the schema designer to create your schema based on sample documents, you may not need to enable this feature. + However, with this feature disabled, you need to make sure the incoming data matches the schema exactly or indexing errors may occur. For more information about schemaless mode, see: +

+

+
+
+
+
+ + + +
+
+

Enabling this feature adds the _root_ and _nest_path_ fields to your schema.

For more information about indexing nested child documents, see: +

+

+
+
+
+
+
+ +
+ +
+

{{updateFileError}}

+
+
+
+

{{updateSelectedError}}

+
+
+
{{selectedType}}: {{selectedNode.name}}
+
+ + +
+
+

The field type defines how Solr should interpret data in a field and how the field can be queried. The properties on each field are inherited from the field type if not explicitly overridden in the field definition. In many cases, it's preferable to choose a different field type instead of overriding the field properties. For example, there's typically a multi-valued version of each type, such as strings is the multi-valued version of the string type. For more information about field types, see: +

+

+
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + +
+
+

Copy fields allow you to apply several distinct field types to a single piece of incoming information during indexing. For instance, a common approach is to copy important string fields into the catch-all '_text_' field to enable full-text matches on terms contained in string fields. Provide a comma-delimited list of fields to copy into during indexing. For more information about copy fields, see: +

+

+
+
+
+
+
+ +
+ +
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
Name +
+
+

This table provides a list of {{containerNodeLabel}} defined for your schema. Click on the name in the first column to edit properties. Click on {{containerNodeLabel}} in the Schema Editor tree on the left to return to this table view.

For more information about field properties, see:

+
+
+
TypeIndexedStoredMultiValuedDocValuesTokenized
{{f.name}}{{f.type}}
+
+
+ + + + + + + + + + + + + + + + + + + + + +
Type +
+
+

This table provides a list of field types defined for your schema. Click on the type name in the first column to edit properties for that type. Changing properties for a type impacts all fields linked to the type. Click on Field Types in the Schema Editor tree on the left to return to this table view.

For more information about field types, see:

+
+
ClassIndexedStoredMultiValuedDocValuesTokenized
{{f.name}}{{f.class}}
+
+
+
+
+ +
+
+

Text Analysis

+
+
This Functionality requires the /analysis/field Handler to be registered and active!
+
+
+
{{analysisError}}
+
+
+
+ + +
+
+ + +
+ +
+
+ + + + + + + + + +
+
+ {{component.short}} +
+
+
+ + + + +
+ + +
{{ caption }}
+
+
+
+
+ + + + + + +
+ + + + + + +
{{value.value}}
+
+
+
 
+
+
+
+
+
+
+
+ +
+
+
+

Query Tester

+
+
+
+ + + +
+
+

Main query parameter q specifies the ranked matching criteria for documents. If you don't specify a field name in the query, the default text field, typically _text_, is used. + If the query results do not contain expected documents, check how the default text field is populated, typically using copy field directives in the schema. +

For more information about query parameters, see: +

+

+
+
+
+
+ + asc desc + +
+
+

Sort fields must be single-valued and indexed (or have docValues enabled). If an expected field is not present in this list, check the active properties in the schema. +

For more information about query parameters, see: +

+

+
+
+
+
+ + + +
+
+ Fields must be indexed or have docValues enabled to compute facets.

For more information about faceting, see: + +
+
+
+
+ + +
+
+ + +
+
+ +
+
+
+
+
+
+
+

Query Results

+
+
+ +
+
+
+ +
+
Copy the documents from the query results to the Sample Documents text area as JSON. This allows you to refine your schema by changing the sample data sourced from the query results.
+
+
+
+ + + + + + + +
{{row.name}}{{row.name}}{{row.value}}
+
+
+
+
+
+
+ + + + diff --git a/KeywordSearch/solr/server/solr-webapp/webapp/partials/security.html b/KeywordSearch/solr/server/solr-webapp/webapp/partials/security.html new file mode 100755 index 0000000000..d8d3093dd7 --- /dev/null +++ b/KeywordSearch/solr/server/solr-webapp/webapp/partials/security.html @@ -0,0 +1,308 @@ + +
+
+

 Current user is not authenticated! Security panel is disabled.

+
+ +
+

 You do not have permission to view the security panel.

+
+ +
+

 WARNING: Security is not enabled for this server!

+
+

Use the bin/solr auth command-line tool to enable security and then reload this panel. For more information, see: Using security.json with Solr

+


Example usage of bin/solr auth to enable basic authentication:

+
+
+
+        bin/solr auth enable -type basicAuth -prompt true -z {{zkHost}} -blockUnknown true
+
+      
+
+
+

Create a security.json config file in your Solr home directory and then restart Solr (on all nodes). For more information, see: Using security.json with Solr

+
+
+ +
+ +
+
+

Security Settings

+
+
TLS enabled? + Authentication Plugin: {{authenticationPlugin}} +
+
+

When using the MultiAuthPlugin, changes made to Users and Roles, using the panels below, only affect Basic authentication.

+ Users and Roles for the other authentication schemes, such as the Bearer scheme (JWTAuthPlugin), are managed by an external provider. + Thus, not all users with access to the system are displayed below; only users managed by the BasicAuthPlugin are displayed on this screen. +

+
+
+
+
+ Authorization Plugin: {{authorizationPlugin}}
+
+ + + + +
+
+

If checked, un-authenticated requests to any Solr endpoint are blocked. If un-checked, then any endpoint that is not protected with a permission will be accessible by anonymous users. Only disable this check if you want to allow un-authenticated access to specific endpoints that are configured with role: null. All other endpoints should be protected with explicit role bindings that require authentication. For more information, see: +

+
+
+
+ +
+
+

If checked, Solr forwards user credentials when making distributed requests to other nodes in the cluster. If un-checked (the default), Solr will use the internal PKI authentication mechanism for distributed requests. For more information, see: +

+
+
+
+
+
+ +
+

 {{securityAPIError}}

+
+
+ +
+
+ +
+
{{userDialogHeader}}
+
+

+

+

+

+

+
+

{{validationError}}

+
+

+ + +

+
+
+ +
+
{{roleDialogHeader}}
+
+

+

+ +

+
+

{{validationError}}

+
+

+ + +

+
+
+ +
+
{{permDialogHeader}}
+
+
+
+
+

For requests where multiple permissions match, Solr applies the first permission that matches based on a complex ordering logic. In general, more specific permissions should be listed earlier in the configuration. The permission index (1-based) governs its position in the configuration. To re-order a permission, change the index to desired position. +

+
+
+
+
or Custom: +
+
+

Permissions allow you to grant access to protected resources to one or more roles. Solr provides a list of predefined permissions to cover common use cases, such as collection administration. Otherwise, you can define a custom permission for fine-grained control over the API path(s), collection(s), request method(s) and params. +

+
+
+
+

+

+

+
+ + + + + +
GET
POST
PUT
DELETE
+
+
+
+
+  =  +
+ + +
+
+
+
+
+

{{validationError}}

+
+

+ + +

+
+
+
+
+ +
+
+

Users

+

 Users are managed by an external provider.

+
+
+ Filter users by:  + + +
+ +
+
+ +
+ + + + + + + + + + + +
UsernameRoles
{{u.username}}{{displayList(u.roles)}}
+
+
+
+ +
+

Roles

+

 Roles are managed by an external provider.

+
+
+ Filter roles by:  + + +
+ +
+
+ +
+ + + + + + + + + + + +
RoleUsers
{{r.name}}{{displayList(r.users)}}
+
+
+
+
+ +
+

Permissions

+
+
+ Filter permissions by:  + + +
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + +
NameRolesCollectionPathMethodParams
{{p.name}}{{displayRoles(p.roles)}}{{p.collectionNames}}{{displayList(p.paths)}}{{displayList(p.method)}}{{displayParams(p.params)}}
+
+
+ + + + + + +
{{w}}
+
+
+
+
+
diff --git a/KeywordSearch/solr/server/start.jar b/KeywordSearch/solr/server/start.jar index 49aa128dd7..71bf9e91dd 100755 Binary files a/KeywordSearch/solr/server/start.jar and b/KeywordSearch/solr/server/start.jar differ diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java index d01e8837c3..c01a81ff66 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2012-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,15 +31,18 @@ import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import org.netbeans.api.progress.ProgressHandle; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; +import org.openide.util.Cancellable; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode; @@ -89,7 +92,8 @@ class AdHocSearchChildFactory extends ChildFactory { * Constructor * * @param queryRequests Query results - * @param saveResults Flag whether to save search results as KWS artifacts. + * @param saveResults Flag whether to save search results as KWS + * artifacts. */ AdHocSearchChildFactory(Collection queryRequests, boolean saveResults) { this.queryRequests = queryRequests; @@ -129,7 +133,7 @@ class AdHocSearchChildFactory extends ChildFactory { createFlatKeys(queryRequest.getQuery(), toPopulate); } - + // If there were no hits, make a single Node that will display that // no results were found. if (toPopulate.isEmpty()) { @@ -176,7 +180,7 @@ class AdHocSearchChildFactory extends ChildFactory { * Get file properties. */ Map properties = new LinkedHashMap<>(); - + /** * Add a snippet property, if available. */ @@ -204,7 +208,6 @@ class AdHocSearchChildFactory extends ChildFactory { properties.put(LOCATION.toString(), contentName); } - String hitName; BlackboardArtifact artifact = null; if (hit.isArtifactHit()) { @@ -414,21 +417,35 @@ class AdHocSearchChildFactory extends ChildFactory { this.saveResults = saveResults; } - protected void finalizeWorker() { - deregisterWriter(this); - EventQueue.invokeLater(progress::finish); - } - @Override protected Void doInBackground() throws Exception { - registerWriter(this); //register (synchronized on class) outside of writerLock to prevent deadlock - final String queryStr = query.getQueryString(); - final String queryDisp = queryStr.length() > QUERY_DISPLAY_LEN ? queryStr.substring(0, QUERY_DISPLAY_LEN - 1) + " ..." : queryStr; try { - progress = ProgressHandle.createHandle(NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.progress.saving", queryDisp), () -> BlackboardResultWriter.this.cancel(true)); - hits.process(progress, null, this, false, saveResults); + if (RuntimeProperties.runningWithGUI()) { + final String queryStr = query.getQueryString(); + final String queryDisp = queryStr.length() > QUERY_DISPLAY_LEN ? queryStr.substring(0, QUERY_DISPLAY_LEN - 1) + " ..." : queryStr; + SwingUtilities.invokeLater(() -> { + progress = ProgressHandle.createHandle( + NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.progress.saving", queryDisp), + new Cancellable() { + @Override + public boolean cancel() { + //progress.setDisplayName(displayName + " " + NbBundle.getMessage(this.getClass(), "SearchRunner.doInBackGround.cancelMsg")); + logger.log(Level.INFO, "Ad hoc search cancelled by user"); //NON-NLS + new Thread(() -> { + BlackboardResultWriter.this.cancel(true); + }).start(); + return true; + } + }); + }); + } + registerWriter(this); //register (synchronized on class) outside of writerLock to prevent deadlock + hits.process(this, false, saveResults, null); } finally { - finalizeWorker(); + deregisterWriter(this); + if (RuntimeProperties.runningWithGUI() && progress != null) { + EventQueue.invokeLater(progress::finish); + } } return null; } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java index cff13cc16e..37ddf15a7e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java @@ -52,15 +52,15 @@ import org.sleuthkit.datamodel.VirtualDirectory; /** * FilterNode containing properties and actions for keyword search. - * + * * Wraps the generic KeyValue node and customizes the property sheet and lookup */ class AdHocSearchFilterNode extends FilterNode { /** * Instantiate a KeywordSearchFilterNode. - * - * @param original The original source node. + * + * @param original The original source node. */ AdHocSearchFilterNode(Node original) { super(original, null, new ProxyLookup(original.getLookup())); @@ -105,10 +105,10 @@ class AdHocSearchFilterNode extends FilterNode { public Action[] getActions(boolean popup) { List actions = new ArrayList<>(); - actions.addAll(Arrays.asList(super.getActions(popup))); Content content = this.getOriginal().getLookup().lookup(Content.class); actions.addAll(content.accept(new GetPopupActionsContentVisitor())); - + actions.add(null); + actions.addAll(Arrays.asList(super.getActions(popup))); return actions.toArray(new Action[actions.size()]); } @@ -160,10 +160,20 @@ class AdHocSearchFilterNode extends FilterNode { private List getFileActions() { List actionsList = new ArrayList<>(); + + boolean hasAbstractFile = getOriginal().getLookup().lookup(AbstractFile.class) != null; + actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl"), AdHocSearchFilterNode.this)); - actionsList.add(new ExternalViewerAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.openExternViewActLbl"), getOriginal())); + + if (hasAbstractFile) { + actionsList.add(new ExternalViewerAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.openExternViewActLbl"), getOriginal())); + } + actionsList.add(null); - actionsList.add(ExtractAction.getInstance()); + if (hasAbstractFile) { + actionsList.add(ExtractAction.getInstance()); + } + actionsList.add(ExportCSVAction.getInstance()); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java index 6190f392e8..c9aa061400 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java @@ -148,7 +148,7 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { } /** - * Get a list of data source display name. + * Get a list of data source display names. * * @return The list of data source name */ diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties index 22df9fae94..831379e537 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties @@ -187,7 +187,7 @@ Server.query.exception.msg=Error running query: {0} Server.query2.exception.msg=Error running query: {0} Server.queryTerms.exception.msg=Error running terms query: {0} Server.connect.exception.msg=Failed to connect to Solr server: {0} -Server.openCore.exception.msg=Keyword search service not yet running +Server.openCore.exception.msg=Local keyword search service not yet running Server.openCore.exception.cantOpen.msg=Could not create or open index Server.openCore.exception.noIndexDir.msg=Index directory could not be created or is missing Server.request.exception.exception.msg=Could not issue Solr request @@ -210,7 +210,6 @@ KeywordSearchJobSettingsPanel.languagesLabel.text=Scripts enabled for string ext KeywordSearchGlobalLanguageSettingsPanel.enableUTF8Checkbox.text=Enable UTF8 text extraction KeywordSearchGlobalLanguageSettingsPanel.ingestSettingsLabel.text=Ingest settings for string extraction from unknown file types (changes effective on next ingest): KeywordSearchGlobalLanguageSettingsPanel.enableUTF16Checkbox.text=Enable UTF16LE and UTF16BE string extraction -KeywordSearchGlobalLanguageSettingsPanel.enableOcrCheckbox.text=Enable Optical Character Recognition (OCR) KeywordSearchGlobalLanguageSettingsPanel.languagesLabel.text=Enabled scripts (languages): KeywordSearchGlobalSearchSettingsPanel.timeRadioButton1.toolTipText=20 mins. (fastest ingest time) KeywordSearchGlobalSearchSettingsPanel.timeRadioButton1.text=20 minutes (slowest feedback, fastest ingest) @@ -320,3 +319,6 @@ ExtractedContentPanel.pageTotalLabel.text=- ExtractedContentPanel.pageOfLabel.text=of ExtractedContentPanel.pageCurLabel.text=- ExtractedContentPanel.pagesLabel.text=Page: +KeywordSearchJobSettingsPanel.ocrCheckBox.text=Enable Optical Character Recognition (OCR) +KeywordSearchJobSettingsPanel.limitedOcrCheckbox.text=Only process PDFs, MS Office docs and images which are over 100KB in size or extracted from another file (Beta) +KeywordSearchJobSettingsPanel.ocrOnlyCheckbox.text=Only index text extracted using OCR diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED index 7c80796861..388f951276 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED @@ -5,10 +5,24 @@ AccountsText.nextPage.exception.msg=No next page. AccountsText.previousItem.exception.msg=No previous item. AccountsText.previousPage.exception.msg=No previous page. CannotRunFileTypeDetection=Unable to run file type detection. +Collection.unableToIndexData.error=Unable to add data to text index. All future text indexing for the current case will be skipped. DropdownListSearchPanel.selected=Ad Hoc Search data source filter is selected DropdownSingleTermSearchPanel.selected=Ad Hoc Search data source filter is selected DropdownSingleTermSearchPanel.warning.text=Boundary characters ^ and $ do not match word boundaries. Consider\nreplacing with an explicit list of boundary characters, such as [ \\.,] DropdownSingleTermSearchPanel.warning.title=Warning +ExtractAllTermsReport.description.text=Extracts all unique words out of the current case. NOTE: The extracted words are lower-cased. +ExtractAllTermsReport.error.noOpenCase=No currently open case. +ExtractAllTermsReport.export.error=Error During Unique Word Extraction +ExtractAllTermsReport.exportComplete=Unique Word Extraction Complete +ExtractAllTermsReport.getName.text=Extract Unique Words +# {0} - Number of extracted terms +ExtractAllTermsReport.numberExtractedTerms=Extracted {0} terms... +ExtractAllTermsReport.search.ingestInProgressBody=Keyword Search Ingest is currently running.
Not all files have been indexed and unique word extraction might yield incomplete results.
Do you want to proceed with unique word extraction anyway? +# {0} - Keyword search commit frequency +ExtractAllTermsReport.search.noFilesInIdxMsg=No files are in index yet. Try again later. Index is updated every {0} minutes. +ExtractAllTermsReport.search.noFilesInIdxMsg2=No files are in index yet. Try again later +ExtractAllTermsReport.search.searchIngestInProgressTitle=Keyword Search Ingest in Progress +ExtractAllTermsReport.startExport=Starting Unique Word Extraction ExtractedContentPanel.setMarkup.panelTxt=Loading text... Please wait # {0} - Content name ExtractedContentPanel.SetMarkup.progress.loading=Loading text for {0} @@ -18,6 +32,8 @@ GlobalEditListPanel.warning.title=Warning IndexedText.errorMessage.errorGettingText=Error retrieving indexed text. IndexedText.warningMessage.knownFile=This file is a known file (based on MD5 hash) and does not have indexed text. IndexedText.warningMessage.noTextAvailable=No indexed text for this file. +KeywordSearchGlobalSearchSettingsPanel.customizeComponents.windowsLimitedOCR=Only process images which are over 100KB in size or extracted from a document. (Beta) (Requires Windows 64-bit) +KeywordSearchGlobalSearchSettingsPanel.customizeComponents.windowsOCR=Enable Optical Character Recognition (OCR) (Requires Windows 64-bit) KeywordSearchGlobalSettingsPanel.Title=Global Keyword Search Settings KeywordSearchIngestModule.init.badInitMsg=Keyword search server was not properly initialized, cannot run keyword search ingest. # {0} - Reason for not connecting to Solr @@ -36,7 +52,7 @@ KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found KeywordSearchResultFactory.query.exception.msg=Could not perform the query OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\n\The module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. +OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\nThe module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. OpenIDE-Module-Name=KeywordSearch OptionsCategory_Name_KeywordSearchOptions=Keyword Search OptionsCategory_Keywords_KeywordSearchOptions=Keyword Search @@ -217,6 +233,7 @@ Server.deleteCore.exception.msg=Failed to delete Solr colelction {0} Server.exceptionMessage.unableToBackupCollection=Unable to backup Solr collection Server.exceptionMessage.unableToCreateCollection=Unable to create Solr collection Server.exceptionMessage.unableToRestoreCollection=Unable to restore Solr collection +Server.getAllTerms.error=Extraction of all unique Solr terms failed: Server.start.exception.cantStartSolr.msg=Could not start Solr server process Server.start.exception.cantStartSolr.msg2=Could not start Solr server process Server.isRunning.exception.errCheckSolrRunning.msg=Error checking if Solr server is running @@ -231,7 +248,7 @@ Server.query.exception.msg=Error running query: {0} Server.query2.exception.msg=Error running query: {0} Server.queryTerms.exception.msg=Error running terms query: {0} Server.connect.exception.msg=Failed to connect to Solr server: {0} -Server.openCore.exception.msg=Keyword search service not yet running +Server.openCore.exception.msg=Local keyword search service not yet running Server.openCore.exception.cantOpen.msg=Could not create or open index Server.openCore.exception.noIndexDir.msg=Index directory could not be created or is missing Server.request.exception.exception.msg=Could not issue Solr request @@ -254,7 +271,6 @@ KeywordSearchJobSettingsPanel.languagesLabel.text=Scripts enabled for string ext KeywordSearchGlobalLanguageSettingsPanel.enableUTF8Checkbox.text=Enable UTF8 text extraction KeywordSearchGlobalLanguageSettingsPanel.ingestSettingsLabel.text=Ingest settings for string extraction from unknown file types (changes effective on next ingest): KeywordSearchGlobalLanguageSettingsPanel.enableUTF16Checkbox.text=Enable UTF16LE and UTF16BE string extraction -KeywordSearchGlobalLanguageSettingsPanel.enableOcrCheckbox.text=Enable Optical Character Recognition (OCR) KeywordSearchGlobalLanguageSettingsPanel.languagesLabel.text=Enabled scripts (languages): KeywordSearchGlobalSearchSettingsPanel.timeRadioButton1.toolTipText=20 mins. (fastest ingest time) KeywordSearchGlobalSearchSettingsPanel.timeRadioButton1.text=20 minutes (slowest feedback, fastest ingest) @@ -340,10 +356,14 @@ SolrSearch.checkingForLatestIndex.msg=Looking for text index with latest Solr an SolrSearch.complete.msg=Text index successfully opened SolrSearch.creatingNewIndex.msg=Creating new text index SolrSearch.findingIndexes.msg=Looking for existing text index directories +# {0} - futureVersion +# {1} - currentVersion +SolrSearch.futureIndexVersion.msg=The text index for the case is for Solr {0}. This version of Autopsy is compatible with Solr {1}. SolrSearch.indentifyingIndex.msg=Identifying text index to use SolrSearch.lookingForMetadata.msg=Looking for text index metadata file SolrSearch.openCore.msg=Opening text index. For large cases this may take several minutes. SolrSearch.readingIndexes.msg=Reading text index metadata file +SolrSearch.unableToFindIndex.msg=Unable to find index that can be used for this case # {0} - index folder path SolrSearchService.exceptionMessage.failedToDeleteIndexFiles=Failed to delete text index files at {0} SolrSearchService.exceptionMessage.noCurrentSolrCore=IndexMetadata did not contain a current Solr core so could not delete the case @@ -382,6 +402,9 @@ ExtractedContentPanel.pageTotalLabel.text=- ExtractedContentPanel.pageOfLabel.text=of ExtractedContentPanel.pageCurLabel.text=- ExtractedContentPanel.pagesLabel.text=Page: +KeywordSearchJobSettingsPanel.ocrCheckBox.text=Enable Optical Character Recognition (OCR) +KeywordSearchJobSettingsPanel.limitedOcrCheckbox.text=Only process PDFs, MS Office docs and images which are over 100KB in size or extracted from another file (Beta) +KeywordSearchJobSettingsPanel.ocrOnlyCheckbox.text=Only index text extracted using OCR TextZoomPanel.zoomInButton.text= TextZoomPanel.zoomOutButton.text= TextZoomPanel.zoomResetButton.text=Reset diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties index 58eab4b35a..46af934d7c 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties @@ -1,4 +1,4 @@ -#Tue Aug 18 18:09:21 UTC 2020 +#Thu Sep 30 10:26:59 UTC 2021 AbstractFileStringContentStream.getSize.exception.msg=\u6587\u5b57\u5217\u5168\u4f53\u304c\u5909\u63db\u3055\u308c\u308b\u307e\u3067\u3001\u5909\u63db\u3055\u308c\u305f\u6587\u5b57\u5217\u306e\u6587\u5b57\u6570\u306f\u308f\u304b\u308a\u307e\u305b\u3093 AbstractFileStringContentStream.getSrcInfo.text=\u30d5\u30a1\u30a4\u30eb\:{0} AbstractFileTikaTextExtract.index.exception.tikaParse.msg=\u4f8b\u5916\: \u6b21\u306e\u30d5\u30a1\u30a4\u30eb\u306eTika\u89e3\u6790\u30bf\u30b9\u30af\u5b9f\u884c\u6642\u306e\u4e88\u671f\u305b\u306c\u4f8b\u5916\: {0}\u3001{1} @@ -27,6 +27,7 @@ AddKeywordsDialog.regexRadioButton.text=\u6b63\u898f\u8868\u73fe AddKeywordsDialog.substringRadioButton.text=\u90e8\u5206\u4e00\u81f4 ByteContentStream.getSrcInfo.text=\u30d5\u30a1\u30a4\u30eb\:{0} CannotRunFileTypeDetection=\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u306e\u691c\u51fa\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093\u3002 +Collection.unableToIndexData.error=\u30c6\u30ad\u30b9\u30c8\u30fb\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306b\u30c7\u30fc\u30bf\u3092\u8ffd\u52a0\u3067\u304d\u307e\u305b\u3093\u3002 \u73fe\u5728\u306e\u30b1\u30fc\u30b9\u306b\u306f\u4eca\u5f8c\u306e\u30c6\u30ad\u30b9\u30c8\u30fb\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306f\u3059\u3079\u3066\u30b9\u30ad\u30c3\u30d7\u3055\u308c\u307e\u3059\u3002 DropdownListSearchPanel.dataSourceCheckBox.text=\u9078\u629e\u3057\u305f\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306b\u691c\u7d22\u3092\u5236\u9650\: DropdownListSearchPanel.jSaveSearchResults.text=\u691c\u7d22\u7d50\u679c\u3092\u4fdd\u5b58 DropdownListSearchPanel.jSaveSearchResults.toolTipText=\u30ad\u30fc\u30ef\u30fc\u30c9\u30d2\u30c3\u30c8\u306b\u3088\u308b\u904e\u53bb\u306e\u691c\u7d22\u7d50\u679c\u306e\u5f62\u3067\u7d50\u679c\u3092\u4fdd\u5b58\u305b\u305a\u306b\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u3092\u5b9f\u884c @@ -47,6 +48,17 @@ DropdownSingleTermSearchPanel.jSaveSearchResults.toolTipText=\u30ad\u30fc\u30ef\ DropdownSingleTermSearchPanel.selected=\u30a2\u30c9\u30db\u30c3\u30af\u691c\u7d22\u30bd\u30fc\u30b9\u30d5\u30a3\u30eb\u30bf\u30fc\u304c\u9078\u629e\u3055\u308c\u3066\u3044\u307e\u3059 DropdownSingleTermSearchPanel.warning.text=\u5883\u754c\u6587\u5b57 ^ \u3068 $ \u304c\u5358\u8a9e\u9818\u57df\u3068\u4e00\u81f4\u3057\u307e\u305b\u3093\u3002[ \\.,] \u306a\u3069\u306e\u660e\u793a\u7684\u306a\u5883\u754c\u6587\u5b57\u30ea\u30b9\u30c8\u3068\u306e\u7f6e\u63db\u3092\n\u691c\u8a0e\u3057\u3066\u304f\u3060\u3055\u3044 DropdownSingleTermSearchPanel.warning.title=\u8b66\u544a +ExtractAllTermsReport.description.text=\u73fe\u5728\u306e\u30b1\u30fc\u30b9\u304b\u3089\u3059\u3079\u3066\u306e\u30e6\u30cb\u30fc\u30af\u306a\u5358\u8a9e\u3092\u62bd\u51fa\u3057\u307e\u3059\u3002 \u6ce8\uff1a\u62bd\u51fa\u3055\u308c\u305f\u5358\u8a9e\u306f\u5c0f\u6587\u5b57\u3067\u3059\u3002 +ExtractAllTermsReport.error.noOpenCase=\u73fe\u5728\u958b\u3044\u3066\u3044\u308b\u30b1\u30fc\u30b9\u306f\u3042\u308a\u307e\u305b\u3093\u3002 +ExtractAllTermsReport.export.error=\u30e6\u30cb\u30fc\u30af\u306a\u5358\u8a9e\u306e\u62bd\u51fa\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f +ExtractAllTermsReport.exportComplete=\u30e6\u30cb\u30fc\u30af\u306a\u5358\u8a9e\u306e\u62bd\u51fa\u306f\u5b8c\u4e86\u3057\u307e\u3057\u305f +ExtractAllTermsReport.getName.text=\u30e6\u30cb\u30fc\u30af\u306a\u5358\u8a9e\u3092\u62bd\u51fa\u3059\u308b +ExtractAllTermsReport.numberExtractedTerms=\u62bd\u51fa\u3055\u308c\u305f{0}\u7528\u8a9e... +ExtractAllTermsReport.search.ingestInProgressBody= \u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u8aad\u8fbc\u306f\u73fe\u5728\u5b9f\u884c\u4e2d\u3067\u3059\u3002
\u3059\u3079\u3066\u306e\u30d5\u30a1\u30a4\u30eb\u304c\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306b\u767b\u9332\u3055\u308c\u3066\u3044\u308b\u308f\u3051\u3067\u306f\u306a\u304f\u3001\u30e6\u30cb\u30fc\u30af\u306a\u5358\u8a9e\u3092\u62bd\u51fa\u306f\u4e0d\u5b8c\u5168\u306a\u7d50\u679c\u306b\u306a\u308b\u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002
\u305d\u308c\u3067\u3082\u30e6\u30cb\u30fc\u30af\u306a\u5358\u8a9e\u306e\u62bd\u51fa\u3092\u7d9a\u884c\u3057\u307e\u3059\u304b\uff1f +ExtractAllTermsReport.search.noFilesInIdxMsg=\u307e\u3060\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306b\u767b\u9332\u3055\u308c\u3066\u3044\u308b\u30d5\u30a1\u30a4\u30eb\u306f\u3042\u308a\u307e\u305b\u3093\u3002 \u3042\u3068\u3067\u3082\u3046\u4e00\u5ea6\u8a66\u3057\u3066\u307f\u3066\u304f\u3060\u3055\u3044\u3002 \u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306f{0}\u5206\u3054\u3068\u306b\u66f4\u65b0\u3055\u308c\u307e\u3059\u3002 +ExtractAllTermsReport.search.noFilesInIdxMsg2=\u307e\u3060\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306b\u767b\u9332\u3055\u308c\u3066\u3044\u308b\u30d5\u30a1\u30a4\u30eb\u306f\u3042\u308a\u307e\u305b\u3093\u3002 \u3042\u3068\u3067\u3082\u3046\u4e00\u5ea6\u8a66\u3057\u3066\u307f\u3066\u304f\u3060\u3055\u3044\u3002 +ExtractAllTermsReport.search.searchIngestInProgressTitle=\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u9032\u884c\u4e2d\u3067\u3059 +ExtractAllTermsReport.startExport=\u30e6\u30cb\u30fc\u30af\u306a\u5358\u8a9e\u62bd\u51fa\u306e\u958b\u59cb ExtractedContentPanel.SetMarkup.progress.loading={0} \u306e\u30c6\u30ad\u30b9\u30c8\u3092\u8aad\u307f\u8fbc\u3093\u3067\u3044\u307e\u3059 ExtractedContentPanel.copyMenuItem.text=\u30b3\u30d4\u30fc ExtractedContentPanel.hitButtonsLabel.text=\u4e00\u81f4\u3059\u308b\u7d50\u679c @@ -186,7 +198,6 @@ KeywordSearchEditListPanel.selectAllMenuItem.text=\u3059\u3079\u3066\u9078\u629e KeywordSearchFilterNode.getFileActions.openExternViewActLbl=\u5916\u90e8\u30d3\u30e5\u30fc\u30ef\u30fc\u3067\u958b\u304f Ctrl+E KeywordSearchFilterNode.getFileActions.searchSameMd5=\u540c\u3058MD5\u30cf\u30c3\u30b7\u30e5\u3067\u30d5\u30a1\u30a4\u30eb\u3092\u691c\u7d22 KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl=\u65b0\u3057\u3044\u30a6\u30a3\u30f3\u30c9\u30a6\u3067\u8868\u793a -KeywordSearchGlobalLanguageSettingsPanel.enableOcrCheckbox.text=\u5149\u5b66\u6587\u5b57\u8a8d\u8b58(OCR)\u3092\u6709\u52b9\u5316 KeywordSearchGlobalLanguageSettingsPanel.enableUTF16Checkbox.text=UTF16LE\u304a\u3088\u3073UTF16BE\u6587\u5b57\u5217\u62bd\u51fa\u3092\u6709\u52b9\u5316 KeywordSearchGlobalLanguageSettingsPanel.enableUTF8Checkbox.text=UTF8\u30c6\u30ad\u30b9\u30c8\u62bd\u51fa\u3092\u6709\u52b9\u5316\u3059\u308b KeywordSearchGlobalLanguageSettingsPanel.ingestSettingsLabel.text=\u672a\u77e5\u306e\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u304b\u3089\u306e\u6587\u5b57\u5217\u62bd\u51fa\u306e\u305f\u3081\u306e\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u8a2d\u5b9a(\u5909\u66f4\u306f\u6b21\u306e\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u3067\u6709\u52b9\u306b\u306a\u308a\u307e\u3059)\: @@ -195,6 +206,8 @@ KeywordSearchGlobalLanguageSettingsPanel.languagesLabel.text=\u6709\u52b9\u5316\ KeywordSearchGlobalListSettingsPanel.component.featureName.text=\u30ad\u30fc\u30ef\u30fc\u30c9\u30ea\u30b9\u30c8\u3092\u4fdd\u5b58 KeywordSearchGlobalSearchSettingsPanel.chunksLabel.text=\u30ad\u30fc\u30ef\u30fc\u30c9\u7d22\u5f15\u5185\u306e\u30c1\u30e3\u30f3\u30af\: KeywordSearchGlobalSearchSettingsPanel.chunksValLabel.text=0 +KeywordSearchGlobalSearchSettingsPanel.customizeComponents.windowsLimitedOCR=\u30b5\u30a4\u30ba\u304c100KB\u3092\u8d85\u3048\u308b\u304b\u3001\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u304b\u3089\u62bd\u51fa\u3055\u308c\u305f\u753b\u50cf\u306e\u307f\u3092\u51e6\u7406\u3057\u307e\u3059\u3002 \uff08\u30d9\u30fc\u30bf\u6a5f\u80fd\uff09\uff08Windows 64\u30d3\u30c3\u30c8\u304c\u5fc5\u8981\uff09 +KeywordSearchGlobalSearchSettingsPanel.customizeComponents.windowsOCR=OCR\u6587\u5b57\u8a8d\u8b58\u3092\u6709\u52b9\u306b\u3059\u308b\uff08Windows 64\u30d3\u30c3\u30c8\u304c\u5fc5\u8981\uff09 KeywordSearchGlobalSearchSettingsPanel.filesIndexedLabel.text=\u30ad\u30fc\u30ef\u30fc\u30c9\u7d22\u5f15\u5185\u306e\u30d5\u30a1\u30a4\u30eb\: KeywordSearchGlobalSearchSettingsPanel.filesIndexedValue.text=0 KeywordSearchGlobalSearchSettingsPanel.frequencyLabel.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u4e2d\u306e\u7d50\u679c\u66f4\u65b0\u983b\u5ea6\: @@ -255,6 +268,9 @@ KeywordSearchJobSettingsPanel.languagesLabel.text=\u672a\u77e5\u306e\u30d5\u30a1 KeywordSearchJobSettingsPanel.languagesLabel.toolTipText=\u672a\u77e5\u306e\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u304b\u3089\u306e\u6587\u5b57\u5217\u62bd\u51fa\u306e\u305f\u3081\u306b\u6709\u52b9\u5316\u3055\u308c\u305f\u6587\u5b57\u5217\u3067\u3059\u3002\u8a73\u7d30\u8a2d\u5b9a\u3067\u5909\u66f4\u3092\u884c\u3048\u307e\u3059\u3002 KeywordSearchJobSettingsPanel.languagesValLabel.text=- KeywordSearchJobSettingsPanel.languagesValLabel.toolTipText= +KeywordSearchJobSettingsPanel.limitedOcrCheckbox.text=\u51e6\u7406\u3092\u3059\u308bPDF\u3001MS Office\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3001\u3068\u753b\u50cf\u306f100KB\u4ee5\u4e0a\u304b\u307e\u305f\u306f\u30d5\u30a1\u30a4\u30eb\u304b\u3089\u62bd\u51fa\u3057\u305f\u3082\u306e\uff08\u30d9\u30fc\u30bf\u6a5f\u80fd\uff09\u306b\u5236\u9650\u3057\u307e\u3059\u3002 +KeywordSearchJobSettingsPanel.ocrCheckBox.text=OCR\u3092\u6709\u52b9\u306b\u3059\u308b +KeywordSearchJobSettingsPanel.ocrOnlyCheckbox.text=OCR\u3092\u4f7f\u7528\u3057\u3066\u62bd\u51fa\u3055\u308c\u305f\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u30fb\u30c6\u30ad\u30b9\u30c8\u306e\u307f KeywordSearchJobSettingsPanel.titleLabel.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u4e2d\u306b\u6709\u52b9\u5316\u3059\u308b\u30ad\u30fc\u30ef\u30fc\u30c9\u30ea\u30b9\u30c8\u3092\u9078\u629e\: KeywordSearchListsAbstract.addList.errMsg1.msg=KeywordSearchListsAbstract\u66f4\u65b0\u306e\u30ea\u30c3\u30b9\u30f3\u4e2d\u306b\u30e2\u30b8\u30e5\u30fc\u30eb\u304c\u30a8\u30e9\u30fc\u3092\u767a\u751f\u3055\u305b\u307e\u3057\u305f\u3002\u30ed\u30b0\u3092\u78ba\u8a8d\u3057\u3066\u3069\u306e\u30e2\u30b8\u30e5\u30fc\u30eb\u304c\u5224\u65ad\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u4e00\u90e8\u306e\u30c7\u30fc\u30bf\u304c\u4e0d\u5b8c\u5168\u3067\u3042\u308b\u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002 KeywordSearchListsAbstract.addList.errMsg2.msg=KeywordSearchListsAbstract\u66f4\u65b0\u306e\u30ea\u30c3\u30b9\u30f3\u4e2d\u306b\u30e2\u30b8\u30e5\u30fc\u30eb\u304c\u30a8\u30e9\u30fc\u3092\u767a\u751f\u3055\u305b\u307e\u3057\u305f\u3002\u30ed\u30b0\u3092\u78ba\u8a8d\u3057\u3066\u3069\u306e\u30e2\u30b8\u30e5\u30fc\u30eb\u304c\u5224\u65ad\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u4e00\u90e8\u306e\u30c7\u30fc\u30bf\u304c\u4e0d\u5b8c\u5168\u3067\u3042\u308b\u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002 @@ -323,16 +339,21 @@ SearchRunner.query.exception.msg=\u6b21\u306e\u30af\u30a8\u30ea\u3092\u5b9f\u884 SearchRunner.updateTimer.title.text=SearchRunner\u66f4\u65b0\u30bf\u30a4\u30de\u30fc Server.addDoc.exception.msg=\u66f4\u65b0\u30cf\u30f3\u30c9\u30e9\u30fc\u7d4c\u7531\u3067\u6b21\u306e\u6587\u66f8\u3092\u8ffd\u52a0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\: {0} Server.addDoc.exception.msg2=\u66f4\u65b0\u30cf\u30f3\u30c9\u30e9\u30fc\u7d4c\u7531\u3067\u6b21\u306e\u6587\u66f8\u3092\u8ffd\u52a0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\: {0} +Server.addDocBatch.exception.msg=\u30d0\u30c3\u30c1\u51e6\u7406\u3055\u308c\u305f\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3092\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306b\u8ffd\u52a0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f Server.close.exception.msg=\u30b3\u30a2\u3092\u9589\u3058\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093 -Server.close.exception.msg2=\u30b3\u30a2\u3092\u9589\u3058\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093 Server.commit.exception.msg=\u7d22\u5f15\u3092\u78ba\u5b9a\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f -Server.connect.exception.msg=\u6b21\u306eSolr\u30b5\u30fc\u30d0\u30fc\u306b\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\: {0} -Server.deleteCore.exception.msg=Solr \u30b3\u30a2 {0} \u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f +Server.connect.exception.msg=Solr\u30b5\u30fc\u30d0\u30fc\u306b\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093{0} +Server.connectionInfoMissing.exception.msg=Solr\u30d0\u30fc\u30b8\u30e7\u30f3{0}\u30de\u30eb\u30c1\u30e6\u30fc\u30b6\u30fc\u63a5\u7d9a\u60c5\u5831\u304c\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u307e\u305b\u3093 +Server.deleteCore.exception.msg=Solr\u30b3\u30ec\u30af\u30b7\u30e7\u30f3{0}\u306e\u524a\u9664\u306b\u5931\u6557\u3057\u307e\u3057\u305f +Server.exceptionMessage.unableToBackupCollection=Solr\u30b3\u30ec\u30af\u30b7\u30e7\u30f3\u3092\u30d0\u30c3\u30af\u30a2\u30c3\u30d7\u3067\u304d\u307e\u305b\u3093 +Server.exceptionMessage.unableToCreateCollection=Solr\u30b3\u30ec\u30af\u30b7\u30e7\u30f3\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093 +Server.exceptionMessage.unableToRestoreCollection=Solr\u30b3\u30ec\u30af\u30b7\u30e7\u30f3\u3092\u5fa9\u5143\u3067\u304d\u307e\u305b\u3093 +Server.getAllTerms.error=\u3059\u3079\u3066\u306e\u30e6\u30cb\u30fc\u30af\u306aSolr\u7528\u8a9e\u306e\u62bd\u51fa\u306b\u5931\u6557\u3057\u307e\u3057\u305f\uff1a Server.isRunning.exception.errCheckSolrRunning.msg=Solr\u30b5\u30fc\u30d0\u30fc\u304c\u5b9f\u884c\u4e2d\u304b\u3069\u3046\u304b\u3092\u78ba\u8a8d\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f Server.isRunning.exception.errCheckSolrRunning.msg2=Solr\u30b5\u30fc\u30d0\u30fc\u304c\u5b9f\u884c\u4e2d\u304b\u3069\u3046\u304b\u3092\u78ba\u8a8d\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f Server.openCore.exception.alreadyOpen.msg=\u958b\u3044\u3066\u3044\u308bSolr\u306e\u30b3\u30a2\u304c\u3059\u3067\u306b\u3042\u308a\u307e\u3059\u3002\u6700\u521d\u306b\u30b3\u30a2\u3092\u660e\u793a\u7684\u306b\u9589\u3058\u3066\u304f\u3060\u3055\u3044\u3002 Server.openCore.exception.cantOpen.msg=\u7d22\u5f15\u3092\u4f5c\u6210\u307e\u305f\u306f\u958b\u3051\u307e\u305b\u3093\u3067\u3057\u305f -Server.openCore.exception.msg=\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u30b5\u30fc\u30d3\u30b9\u306f\u307e\u3060\u5b9f\u884c\u3057\u3066\u3044\u307e\u305b\u3093 +Server.openCore.exception.msg=\u30ed\u30fc\u30ab\u30eb\u30fb\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u306f\u307e\u3060\u5b9f\u884c\u3055\u308c\u3066\u3044\u307e\u305b\u3093 Server.openCore.exception.noIndexDir.msg=\u7d22\u5f15\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30fc\u3092\u4f5c\u6210\u3067\u304d\u306a\u304b\u3063\u305f\u304b\u3001\u898b\u3064\u304b\u308a\u307e\u305b\u3093 Server.query.exception.msg=\u6b21\u306e\u30af\u30a8\u30ea\u306e\u5b9f\u884c\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\: {0} Server.query2.exception.msg=\u6b21\u306e\u30af\u30a8\u30ea\u306e\u5b9f\u884c\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\: {0} @@ -343,12 +364,11 @@ Server.queryNumIdxDocs.exception.msg=\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3055\ Server.queryNumIdxFiles.exception.msg=\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb\u6570\u3092\u30af\u30a8\u30ea\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 Server.queryTerms.exception.msg=\u6b21\u306eterms(\u6574\u6570/\u6587\u5b57\u5217/\u914d\u5217)\u30af\u30a8\u30ea\u306e\u5b9f\u884c\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\: {0} Server.request.exception.exception.msg=Solr\u30ea\u30af\u30a8\u30b9\u30c8\u3092\u767a\u884c\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f -Server.commit.exception.msg=\u7d22\u5f15\u3092\u78ba\u5b9a\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f -Server.addDoc.exception.msg=\u66f4\u65b0\u30cf\u30f3\u30c9\u30e9\u30fc\u7d4c\u7531\u3067\u6b21\u306e\u6587\u66f8\u3092\u8ffd\u52a0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f: {0} -Server.close.exception.msg=\u30b3\u30a2\u3092\u9589\u3058\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093 +Server.serverList.exception.msg=\u30b5\u30fc\u30d0\u30fc{0}\u304b\u3089Solr\u30b5\u30fc\u30d0\u30fc\u306e\u30ea\u30b9\u30c8\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093 Server.solrServerNoPortException.msg=\u7d22\u5f15\u751f\u6210\u30b5\u30fc\u30d0\u30fc\u306f\u30dd\u30fc\u30c8 {0} \u306b\u30d0\u30a4\u30f3\u30c9\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u30c7\u30d5\u30a9\u30eb\u30c8\u306e {1} \u30dd\u30fc\u30c8\u306b\u5909\u66f4\u3059\u308b\u3053\u3068\u3092\u691c\u8a0e\u3057\u3066\u304f\u3060\u3055\u3044\u3002 Server.start.exception.cantStartSolr.msg=Solr\u30b5\u30fc\u30d0\u30fc\u30d7\u30ed\u30bb\u30b9\u3092\u958b\u59cb\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f Server.start.exception.cantStartSolr.msg2=Solr\u30b5\u30fc\u30d0\u30fc\u30d7\u30ed\u30bb\u30b9\u3092\u958b\u59cb\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f +Server.status.failed.msg=\u30ed\u30fc\u30ab\u30ebSolr\u30b5\u30fc\u30d0\u30fc\u304c\u30b9\u30c6\u30fc\u30bf\u30b9\u8981\u6c42\u306b\u5fdc\u7b54\u3057\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u3053\u308c\u306f\u3001\u30b5\u30fc\u30d0\u30fc\u306e\u8d77\u52d5\u306b\u5931\u6557\u3057\u305f\u304b\u3001\u521d\u671f\u5316\u306b\u6642\u9593\u304c\u304b\u304b\u308a\u3059\u304e\u3066\u3044\u308b\u3053\u3068\u304c\u539f\u56e0\u3067\u3042\u308b\u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002 SolrConnectionCheck.Hostname=\u7121\u52b9\u306a\u30db\u30b9\u30c8\u540d\u3067\u3059\u3002 SolrConnectionCheck.HostnameOrPort=\u7121\u52b9\u306a\u30db\u30b9\u30c8\u540d\u304a\u3088\u3073/\u307e\u305f\u306f\u30dd\u30fc\u30c8\u756a\u53f7\u3067\u3059\u3002 SolrConnectionCheck.MissingHostname=\u30db\u30b9\u30c8\u540d\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002 @@ -357,46 +377,17 @@ SolrSearch.checkingForLatestIndex.msg=\u6700\u65b0\u306eSolr\u3068\u30b9\u30ad\u SolrSearch.complete.msg=\u30c6\u30ad\u30b9\u30c8\u7d22\u5f15\u304c\u6b63\u5e38\u306b\u958b\u304d\u307e\u3057\u305f SolrSearch.creatingNewIndex.msg=\u65b0\u898f\u30c6\u30ad\u30b9\u30c8\u7d22\u5f15\u3092\u751f\u6210\u4e2d\u3067\u3059 SolrSearch.findingIndexes.msg=\u65e2\u5b58\u306e\u30c6\u30ad\u30b9\u30c8\u7d22\u5f15\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30fc\u3092\u691c\u7d22\u4e2d\u3067\u3059 +SolrSearch.futureIndexVersion.msg=\u30b1\u30fc\u30b9\u306e\u30c6\u30ad\u30b9\u30c8\u30fb\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306fSolr {0}\u7528\u3067\u3059\u3002 \u3053\u306e\u30d0\u30fc\u30b8\u30e7\u30f3\u306eAutopsy\u306f\u3001Solr {1}\u4e92\u63db\u6027\u3067\u3059\u3002 SolrSearch.indentifyingIndex.msg=\u4f7f\u7528\u3059\u308b\u30c6\u30ad\u30b9\u30c8\u7d22\u5f15\u3092\u7279\u5b9a\u4e2d\u3067\u3059 SolrSearch.lookingForMetadata.msg=\u30c6\u30ad\u30b9\u30c8\u7d22\u5f15\u306e\u30e1\u30bf\u30c7\u30fc\u30bf\u30d5\u30a1\u30a4\u30eb\u3092\u691c\u7d22\u4e2d\u3067\u3059 -SolrSearch.openCore.msg=\u30c6\u30ad\u30b9\u30c8\u7d22\u5f15\u3092\u8d77\u52d5\u4e2d\u3067\u3059 -SolrSearch.openGiantCore.msg=\u30c6\u30ad\u30b9\u30c8\u7d22\u5f15\u3092\u8d77\u52d5\u4e2d\u3067\u3059\u3002\u3053\u306e\u30b1\u30fc\u30b9\u306e\u30c6\u30ad\u30b9\u30c8\u7d22\u5f15\u306f\u975e\u5e38\u306b\u5927\u304d\u3044\u305f\u3081\u3001\u8d77\u52d5\u306b\u6642\u9593\u304c\u304b\u304b\u308b\u3053\u3068\u304c\u3042\u308a\u307e\u3059\u3002 -SolrSearch.openLargeCore.msg=\u30c6\u30ad\u30b9\u30c8\u7d22\u5f15\u3092\u8d77\u52d5\u4e2d\u3067\u3059\u3002\u3053\u308c\u306b\u306f\u6570\u5206\u304b\u304b\u308b\u3053\u3068\u304c\u3042\u308a\u307e\u3059\u3002 +SolrSearch.openCore.msg=\u30c6\u30ad\u30b9\u30c8\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3092\u958b\u304d\u307e\u3059\u3002 \u3053\u308c\u306b\u306f\u6570\u5206\u304b\u304b\u308b\u5834\u5408\u304c\u3042\u308a\u307e\u3059\u3002 SolrSearch.readingIndexes.msg=\u30c6\u30ad\u30b9\u30c8\u7d22\u5f15\u306e\u30e1\u30bf\u30c7\u30fc\u30bf\u30d5\u30a1\u30a4\u30eb\u3092\u8aad\u307f\u8fbc\u307f\u4e2d\u3067\u3059 -SolrSearchService.IndexReadOnlyDialog.msg=\u3053\u306e\u30b1\u30fc\u30b9\u306e\u30c6\u30ad\u30b9\u30c8\u7d22\u5f15\u306f\u8aad\u307f\u53d6\u308a\u5c02\u7528\u3067\u3059\u3002
\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u7d50\u679c\u3092\u78ba\u8a8d\u3057\u3001\u5b8c\u5168\u4e00\u81f4\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u3068\u90e8\u5206\u4e00\u81f4\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u3092\u5b9f\u884c\u3067\u304d\u307e\u3059
\u304c\u3001\u7d22\u5f15\u306b\u65b0\u898f\u30c6\u30ad\u30b9\u30c8\u3092\u8ffd\u52a0\u307e\u305f\u306f\u6b63\u898f\u8868\u73fe\u691c\u7d22\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093\u3002\u305d\u306e\u4ee3\u308f\u308a\u306b\u3001\u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u4ee5\u524d\u306e\u30d0\u30fc\u30b8\u30e7\u30f3\u3067
\u30b1\u30fc\u30b9\u3092\u958b\u3051\u307e\u3059\u3002 -SolrSearchService.IndexReadOnlyDialog.title=\u30c6\u30ad\u30b9\u30c8\u7d22\u5f15\u306f\u8aad\u307f\u53d6\u308a\u5c02\u7528\u3067\u3059 +SolrSearch.unableToFindIndex.msg=\u3053\u306e\u30b1\u30fc\u30b9\u3067\u4f7f\u7528\u3067\u304d\u308b\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093 +SolrSearchService.DeleteDataSource.msg=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9ID {0}\u306eSolr\u30c7\u30fc\u30bf\u306e\u524a\u9664\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f SolrSearchService.ServiceName=Solr\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u30b5\u30fc\u30d3\u30b9 SolrSearchService.exceptionMessage.failedToDeleteIndexFiles={0} \u306e\u30c6\u30ad\u30b9\u30c8\u7d22\u5f15\u30d5\u30a1\u30a4\u30eb\u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f SolrSearchService.exceptionMessage.noCurrentSolrCore=IndexMetadata\u306b\u306f\u73fe\u5728\u306eSolr\u306e\u30b3\u30a2\u304c\u542b\u307e\u308c\u3066\u3044\u306a\u304b\u3063\u305f\u305f\u3081\u3001\u30b1\u30fc\u30b9\u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f -# {0} - \u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30fc -SolrSearchService.exceptionMessage.noIndexMetadata=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30fc\u304b\u3089\u6b21\u306eIndexMetaData\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f: {0} -SolrSearchService.ServiceName=Solr\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u30b5\u30fc\u30d3\u30b9 -DropdownSingleTermSearchPanel.dataSourceCheckBox.text=\u9078\u629e\u3057\u305f\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306b\u691c\u7d22\u3092\u5236\u9650: -DropdownListSearchPanel.dataSourceCheckBox.text=\u9078\u629e\u3057\u305f\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306b\u691c\u7d22\u3092\u5236\u9650: -DropdownSingleTermSearchPanel.ingestIndexLabel.text=\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb: -DropdownSingleTermSearchPanel.jSaveSearchResults.toolTipText=\u30ad\u30fc\u30ef\u30fc\u30c9\u30d2\u30c3\u30c8\u306b\u3088\u308b\u904e\u53bb\u306e\u691c\u7d22\u7d50\u679c\u306e\u5f62\u3067\u7d50\u679c\u3092\u4fdd\u5b58\u305b\u305a\u306b\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u3092\u5b9f\u884c -DropdownSingleTermSearchPanel.jSaveSearchResults.text=\u691c\u7d22\u7d50\u679c\u3092\u4fdd\u5b58 -DropdownListSearchPanel.jSaveSearchResults.toolTipText=\u30ad\u30fc\u30ef\u30fc\u30c9\u30d2\u30c3\u30c8\u306b\u3088\u308b\u904e\u53bb\u306e\u691c\u7d22\u7d50\u679c\u306e\u5f62\u3067\u7d50\u679c\u3092\u4fdd\u5b58\u305b\u305a\u306b\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u3092\u5b9f\u884c -DropdownListSearchPanel.jSaveSearchResults.text=\u691c\u7d22\u7d50\u679c\u3092\u4fdd\u5b58 -GlobalEditListPanel.ingestWarningLabel.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u9032\u884c\u4e2d\u3067\u3059\u3002\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u5b8c\u4e86\u3059\u308b\u307e\u3067\u4e00\u90e8\u306e\u8a2d\u5b9a\u3092\u5229\u7528\u3067\u304d\u307e\u305b\u3093\u3002 -KeywordSearchGlobalLanguageSettingsPanel.ingestWarningLabel.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u9032\u884c\u4e2d\u3067\u3059\u3002\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u5b8c\u4e86\u3059\u308b\u307e\u3067\u4e00\u90e8\u306e\u8a2d\u5b9a\u3092\u5229\u7528\u3067\u304d\u307e\u305b\u3093\u3002 -KeywordSearchGlobalSearchSettingsPanel.ingestWarningLabel.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u9032\u884c\u4e2d\u3067\u3059\u3002\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u5b8c\u4e86\u3059\u308b\u307e\u3067\u4e00\u90e8\u306e\u8a2d\u5b9a\u3092\u5229\u7528\u3067\u304d\u307e\u305b\u3093\u3002 -ExtractedContentPanel.jLabel1.text=\u30c6\u30ad\u30b9\u30c8\u30bd\u30fc\u30b9: -ExtractedContentPanel.hitNextButton.text= -ExtractedContentPanel.hitPreviousButton.text= -ExtractedContentPanel.hitButtonsLabel.text=\u4e00\u81f4\u3059\u308b\u7d50\u679c -ExtractedContentPanel.hitTotalLabel.text=- -ExtractedContentPanel.hitOfLabel.text=/ -ExtractedContentPanel.hitCountLabel.text=- -ExtractedContentPanel.hitLabel.toolTipText= -ExtractedContentPanel.hitLabel.text=\u30da\u30fc\u30b8\u4e0a\u306e\u4e00\u81f4\u3059\u308b\u7d50\u679c: -ExtractedContentPanel.pageNextButton.text= -ExtractedContentPanel.pagePreviousButton.actionCommand=pagePreviousButton -ExtractedContentPanel.pagePreviousButton.text= -ExtractedContentPanel.pageButtonsLabel.text=\u30da\u30fc\u30b8 -ExtractedContentPanel.pageTotalLabel.text=- -ExtractedContentPanel.pageOfLabel.text=/ -ExtractedContentPanel.pageCurLabel.text=- -ExtractedContentPanel.pagesLabel.text=\u30da\u30fc\u30b8: +SolrSearchService.exceptionMessage.noIndexMetadata=\u30b1\u30fc\u30b9\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30fc\u304b\u3089\u6b21\u306eIndexMetaData\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\: {0} +SolrSearchService.exceptionMessage.unableToDeleteCollection=\u30b3\u30ec\u30af\u30b7\u30e7\u30f3{0}\u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093 +SolrSearchService.indexingError=\u9ed2\u677f\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u306b\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3092\u4ed8\u3051\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3002 TextZoomPanel.zoomResetButton.text=\u30ea\u30bb\u30c3\u30c8 - diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java index fd0f11ab74..2deb82d2f5 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java @@ -184,7 +184,14 @@ class Chunker implements Iterator, Iterable { return new StringBuilder(UTF_16.decode(UTF_16.encode(s))); } - private static StringBuilder sanitize(String s) { + /** + * Wrapper method that performs UTF-8 string sanitization. + * + * @param s String to be sanitized. + * + * @return Sanitized string. + */ + static StringBuilder sanitize(String s) { String normStr = Normalizer.normalize(s, Normalizer.Form.NFKC); return sanitizeToUTF8(replaceInvalidUTF16(normStr)); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractAllTermsReport.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractAllTermsReport.java new file mode 100755 index 0000000000..88178b42ea --- /dev/null +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractAllTermsReport.java @@ -0,0 +1,140 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.keywordsearch; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.logging.Level; +import javax.swing.JPanel; +import org.openide.util.NbBundle; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.report.GeneralReportModule; +import org.sleuthkit.autopsy.report.GeneralReportSettings; +import org.sleuthkit.autopsy.report.ReportProgressPanel; +import org.sleuthkit.autopsy.keywordsearch.infastructure.NoReportConfigurationPanel; + +/** + * Instances of this class plug in to the reporting infrastructure to provide a + * convenient way to extract all unique terms from Solr index. + */ +@ServiceProvider(service = GeneralReportModule.class) +public class ExtractAllTermsReport implements GeneralReportModule { + + private static final Logger logger = Logger.getLogger(ExtractAllTermsReport.class.getName()); + private static final String OUTPUT_FILE_NAME = "Unique Words.txt"; + + @NbBundle.Messages({ + "ExtractAllTermsReport.getName.text=Extract Unique Words"}) + @Override + public String getName() { + return Bundle.ExtractAllTermsReport_getName_text(); + } + + @NbBundle.Messages({ + "ExtractAllTermsReport.error.noOpenCase=No currently open case.", + "# {0} - Keyword search commit frequency", + "ExtractAllTermsReport.search.noFilesInIdxMsg=No files are in index yet. Try again later. Index is updated every {0} minutes.", + "ExtractAllTermsReport.search.noFilesInIdxMsg2=No files are in index yet. Try again later", + "ExtractAllTermsReport.search.searchIngestInProgressTitle=Keyword Search Ingest in Progress", + "ExtractAllTermsReport.search.ingestInProgressBody=Keyword Search Ingest is currently running.
Not all files have been indexed and unique word extraction might yield incomplete results.
Do you want to proceed with unique word extraction anyway?", + "ExtractAllTermsReport.startExport=Starting Unique Word Extraction", + "ExtractAllTermsReport.export.error=Error During Unique Word Extraction", + "ExtractAllTermsReport.exportComplete=Unique Word Extraction Complete" + }) + @Override + public void generateReport(GeneralReportSettings settings, ReportProgressPanel progressPanel) { + + if (!Case.isCaseOpen()) { + logger.log(Level.SEVERE, "No open case when attempting to run {0} report", Bundle.ExtractAllTermsReport_getName_text()); //NON-NLS + progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR, Bundle.ExtractAllTermsReport_error_noOpenCase()); + return; + } + + progressPanel.setIndeterminate(true); + progressPanel.start(); + progressPanel.updateStatusLabel("Extracting unique words..."); + + boolean isIngestRunning = IngestManager.getInstance().isIngestRunning(); + + int filesIndexed = 0; + try { // see if there are any indexed files + filesIndexed = KeywordSearch.getServer().queryNumIndexedFiles(); + } catch (KeywordSearchModuleException | NoOpenCoreException ignored) { + } + + if (filesIndexed == 0) { + if (isIngestRunning) { + progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR, Bundle.ExtractAllTermsReport_search_noFilesInIdxMsg(KeywordSearchSettings.getUpdateFrequency().getTime())); + } else { + progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR, Bundle.ExtractAllTermsReport_search_noFilesInIdxMsg2()); + } + progressPanel.setIndeterminate(false); + return; + } + + // check if keyword search module ingest is running (indexing, etc) + if (isIngestRunning) { + if (KeywordSearchUtil.displayConfirmDialog(Bundle.ExtractAllTermsReport_search_searchIngestInProgressTitle(), + Bundle.ExtractAllTermsReport_search_ingestInProgressBody(), KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN) == false) { + progressPanel.cancel(); + return; + } + } + + final Server server = KeywordSearch.getServer(); + try { + progressPanel.updateStatusLabel(Bundle.ExtractAllTermsReport_startExport()); + Path outputFile = Paths.get(settings.getReportDirectoryPath(), getRelativeFilePath()); + server.extractAllTermsForDataSource(outputFile, progressPanel); + } catch (KeywordSearchModuleException | NoOpenCoreException ex) { + logger.log(Level.SEVERE, "Exception while extracting unique terms", ex); //NON-NLS + progressPanel.setIndeterminate(false); + progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR, Bundle.ExtractAllTermsReport_export_error()); + return; + } + + progressPanel.setIndeterminate(false); + progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE, Bundle.ExtractAllTermsReport_exportComplete()); + } + + @Override + public boolean supportsDataSourceSelection() { + return false; + } + + @NbBundle.Messages({ + "ExtractAllTermsReport.description.text=Extracts all unique words out of the current case. NOTE: The extracted words are lower-cased."}) + @Override + public String getDescription() { + return Bundle.ExtractAllTermsReport_description_text(); + } + @Override + public JPanel getConfigurationPanel() { + return new NoReportConfigurationPanel(); + } + + @Override + public String getRelativeFilePath() { + return OUTPUT_FILE_NAME; + } + +} diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java index 8ff9c0fdcc..c64cee2565 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2020 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,10 +29,10 @@ import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.logging.Level; -import javax.swing.JLabel; import javax.swing.SizeRequirements; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; +import javax.swing.UIManager; import javax.swing.text.Element; import javax.swing.text.View; import javax.swing.text.ViewFactory; @@ -59,7 +59,7 @@ class ExtractedContentPanel extends javax.swing.JPanel implements ResizableTextP private static final Logger logger = Logger.getLogger(ExtractedContentPanel.class.getName()); // set font as close as possible to default - private static final Font DEFAULT_FONT = new JLabel().getFont(); + private static final Font DEFAULT_FONT = UIManager.getDefaults().getFont("Label.font"); private static final long serialVersionUID = 1L; private String contentName; @@ -72,7 +72,6 @@ class ExtractedContentPanel extends javax.swing.JPanel implements ResizableTextP ExtractedContentPanel() { initComponents(); additionalInit(); - setSources("", new ArrayList<>()); hitPreviousButton.setEnabled(false); hitNextButton.setEnabled(false); @@ -135,7 +134,7 @@ class ExtractedContentPanel extends javax.swing.JPanel implements ResizableTextP }; } }; - // get the style sheet for editing font size + // set new style sheet to clear default styles styleSheet = editorKit.getStyleSheet(); sourceComboBox.addItemListener(itemEvent -> { @@ -144,6 +143,7 @@ class ExtractedContentPanel extends javax.swing.JPanel implements ResizableTextP } }); extractedTextPane.setComponentPopupMenu(rightClickMenu); + copyMenuItem.addActionListener(actionEvent -> extractedTextPane.copy()); selectAllMenuItem.addActionListener(actionEvent -> extractedTextPane.selectAll()); @@ -156,11 +156,16 @@ class ExtractedContentPanel extends javax.swing.JPanel implements ResizableTextP if (zoomPanel instanceof TextZoomPanel) ((TextZoomPanel) this.zoomPanel).resetSize(); }); + + setSources("", new ArrayList<>()); } private void setStyleSheetSize(StyleSheet styleSheet, int size) { - styleSheet.addRule("body {font-family:\"" + DEFAULT_FONT.getFamily() + "\"; font-size:" + size + "pt; } "); + styleSheet.addRule( + "body { font-family:\"" + DEFAULT_FONT.getFamily() + "\"; font-size:" + size + "pt; } " + + "pre { font-family:\"" + DEFAULT_FONT.getFamily() + "\"; font-size:" + size + "pt; } " + ); } @@ -499,6 +504,8 @@ class ExtractedContentPanel extends javax.swing.JPanel implements ResizableTextP extractedTextPane.applyComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT); } + // refresh style + setStyleSheetSize(styleSheet, curSize); extractedTextPane.setText(safeText); extractedTextPane.setCaretPosition(0); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedTextViewer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedTextViewer.java index 7174267f7f..aac5757fc0 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedTextViewer.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedTextViewer.java @@ -373,32 +373,7 @@ public class ExtractedTextViewer implements TextViewer { @Override public int isPreferred(Node node) { - AdHocQueryResult adhocResult = node.getLookup().lookup(AdHocQueryResult.class); - if (adhocResult != null) { - //The presence of an AdHocQueryResult indicates that this is a Keyword Hit and has a high preference for this content viewer - return 7; - } - - BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class); - if (artifact == null) { - return 4; - } else if (artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) { - return 7; - } else if (artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) { - try { - BlackboardAttribute attribute = artifact.getAttribute(TSK_ACCOUNT_TYPE); - if (attribute != null && Account.Type.CREDIT_CARD.getTypeName().equals(attribute.getValueString())) { - return 7; - } else { - return 4; - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error getting TSK_ACCOUNT_TYPE attribute from artifact " + artifact.getArtifactID(), ex); - return 4; - } - } else { - return 4; - } + return 4; } /** diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java index 1eb30f5b6c..dedc81e35e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java @@ -32,7 +32,7 @@ import java.util.TreeMap; import java.util.logging.Level; import java.util.stream.Collectors; import javax.annotation.concurrent.GuardedBy; -import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.text.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.apache.solr.client.solrj.SolrQuery; @@ -532,14 +532,14 @@ class HighlightedText implements IndexedText { // Must be done before highlighting tags are added. If we were to // perform HTML escaping after adding the highlighting tags we would // not see highlighted text in the content viewer. - text = StringEscapeUtils.escapeHtml(text); + text = StringEscapeUtils.escapeHtml4(text); TreeRangeSet highlights = TreeRangeSet.create(); //for each keyword find the locations of hits and record them in the RangeSet for (String keyword : keywords) { //we also need to escape the keyword so that it matches the escaped text - final String escapedKeyword = StringEscapeUtils.escapeHtml(keyword); + final String escapedKeyword = StringEscapeUtils.escapeHtml4(keyword); int searchOffset = 0; int hitOffset = StringUtils.indexOfIgnoreCase(text, escapedKeyword, searchOffset); while (hitOffset != -1) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Index.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Index.java index 85afdbff07..f884654bb8 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Index.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Index.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.keywordsearch; import java.text.SimpleDateFormat; import java.util.Date; import org.apache.commons.lang.math.NumberUtils; -import org.sleuthkit.autopsy.coreutils.UNCPathUtilities; /** * This class encapsulates KWS index data. @@ -33,7 +32,6 @@ final class Index { private final String solrVersion; private final String indexName; private static final String DEFAULT_CORE_NAME = "text_index"; //NON-NLS - private final UNCPathUtilities uncPathUtilities = new UNCPathUtilities(); /** * Constructs a representation of a text index. @@ -47,7 +45,7 @@ final class Index { * need to be generated. */ Index(String indexPath, String solrVersion, String schemaVersion, String coreName, String caseName) { - this.indexPath = uncPathUtilities.convertPathToUNC(indexPath); + this.indexPath = indexPath; this.solrVersion = solrVersion; this.schemaVersion = schemaVersion; if (coreName == null || coreName.isEmpty()) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexFinder.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexFinder.java index 9219a80b74..dc9179291d 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexFinder.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexFinder.java @@ -21,10 +21,8 @@ package org.sleuthkit.autopsy.keywordsearch; import java.io.File; import java.nio.file.Paths; import java.util.List; -import java.util.logging.Level; import org.apache.commons.lang.math.NumberUtils; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.appservices.AutopsyService; /** @@ -32,11 +30,11 @@ import org.sleuthkit.autopsy.appservices.AutopsyService; */ class IndexFinder { - private static final Logger logger = Logger.getLogger(IndexFinder.class.getName()); private static final String KWS_OUTPUT_FOLDER_NAME = "keywordsearch"; private static final String KWS_DATA_FOLDER_NAME = "data"; private static final String INDEX_FOLDER_NAME = "index"; private static final String CURRENT_SOLR_VERSION = "8"; + private static final int CURRENT_SOLR_VERSION_INT = 8; private static final String CURRENT_SOLR_SCHEMA_VERSION = "2.3"; static String getCurrentSolrVersion() { @@ -47,7 +45,7 @@ class IndexFinder { return CURRENT_SOLR_SCHEMA_VERSION; } - static Index findLatestVersionIndexDir(List allIndexes) { + static Index findLatestVersionIndex(List allIndexes) { for (Index index : allIndexes) { if (index.getSolrVersion().equals(CURRENT_SOLR_VERSION) && index.getSchemaVersion().equals(CURRENT_SOLR_SCHEMA_VERSION)) { return index; @@ -56,14 +54,10 @@ class IndexFinder { return null; } - static Index createLatestVersionIndexDir(Case theCase) throws AutopsyService.AutopsyServiceException { + static Index createLatestVersionIndex(Case theCase) throws AutopsyService.AutopsyServiceException { String indexFolderName = "solr" + CURRENT_SOLR_VERSION + "_schema" + CURRENT_SOLR_SCHEMA_VERSION; // new index should be stored in "\ModuleOutput\keywordsearch\data\solrX_schemaY\index" File targetDirPath = Paths.get(theCase.getModuleDirectory(), KWS_OUTPUT_FOLDER_NAME, KWS_DATA_FOLDER_NAME, indexFolderName, INDEX_FOLDER_NAME).toFile(); //NON-NLS - if (!targetDirPath.mkdirs()) { - logger.log(Level.SEVERE, "Unable to create index directory: {0}", targetDirPath.toString()); - throw new AutopsyService.AutopsyServiceException("Unable to create text index directory " + targetDirPath.getAbsolutePath()); - } return new Index(targetDirPath.getAbsolutePath(), CURRENT_SOLR_VERSION, CURRENT_SOLR_SCHEMA_VERSION, "", theCase.getName()); } @@ -79,7 +73,11 @@ class IndexFinder { Index bestCandidateIndex = null; double solrVerFound = 0.0; double schemaVerFound = 0.0; - for (Index index : allIndexes) { + for (Index index : allIndexes) { + if (NumberUtils.toDouble(index.getSolrVersion()) > CURRENT_SOLR_VERSION_INT) { + // "legacy" Solr server cannot open "future" versions of Solr indexes + continue; + } // higher Solr version takes priority because it may negate index upgrade if (NumberUtils.toDouble(index.getSolrVersion()) >= solrVerFound) { // if same solr version, pick the one with highest schema version @@ -92,4 +90,23 @@ class IndexFinder { } return bestCandidateIndex; } + + /** + * Checks if a the list of indexes contains an index from a "future" version + * of Solr. This happens when a "legacy" version of Autopsy attempts to open + * a Solr index created by Autopsy that uses later version of Solr. + * + * @param allIndexes List of Index objects + * + * @return Version number of "future" index if present, empty string otherwise + */ + static String isFutureIndexPresent(List allIndexes) { + for (Index index : allIndexes) { + if (NumberUtils.toDouble(index.getSolrVersion()) > CURRENT_SOLR_VERSION_INT) { + // "legacy" Solr server cannot open "future" versions of Solr indexes + return index.getSolrVersion(); + } + } + return ""; + } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexMetadata.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexMetadata.java index 4594bb36b3..b43ed40798 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexMetadata.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IndexMetadata.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,7 +39,6 @@ import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; -import org.sleuthkit.autopsy.coreutils.UNCPathUtilities; import org.sleuthkit.autopsy.coreutils.XMLUtil; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -61,11 +60,10 @@ class IndexMetadata { private final static String SOLR_VERSION_ELEMENT_NAME = "SolrVersion"; //NON-NLS private final static String TEXT_INDEX_PATH_ELEMENT_NAME = "TextIndexPath"; //NON-NLS private List indexes = new ArrayList<>(); - private final UNCPathUtilities uncPathUtilities = new UNCPathUtilities(); IndexMetadata(String caseDirectory, Index index) throws TextIndexMetadataException { this.metadataFilePath = Paths.get(caseDirectory, METADATA_FILE_NAME); - this.caseDirectoryPath = Paths.get(uncPathUtilities.convertPathToUNC(caseDirectory)); + this.caseDirectoryPath = Paths.get(caseDirectory); this.indexes.add(index); writeToFile(); } @@ -73,7 +71,7 @@ class IndexMetadata { IndexMetadata(String caseDirectory, List indexes) throws TextIndexMetadataException { this.metadataFilePath = Paths.get(caseDirectory, METADATA_FILE_NAME); - this.caseDirectoryPath = Paths.get(uncPathUtilities.convertPathToUNC(caseDirectory)); + this.caseDirectoryPath = Paths.get(caseDirectory); this.indexes = indexes; writeToFile(); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IngestSearchRunner.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IngestSearchRunner.java old mode 100644 new mode 100755 index 937c9567fd..9cd33a8167 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IngestSearchRunner.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IngestSearchRunner.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 - 2017 Basis Technology Corp. + * Copyright 2014 - 2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,50 +36,61 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import static java.util.concurrent.TimeUnit.MILLISECONDS; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; +import javax.annotation.concurrent.GuardedBy; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; -import org.netbeans.api.progress.aggregate.AggregateProgressFactory; -import org.netbeans.api.progress.aggregate.AggregateProgressHandle; -import org.netbeans.api.progress.aggregate.ProgressContributor; +import org.netbeans.api.progress.ProgressHandle; import org.openide.util.Cancellable; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.StopWatch; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.ingest.IngestMessage; import org.sleuthkit.autopsy.ingest.IngestServices; /** - * Singleton keyword search manager: Launches search threads for each job and - * performs commits, both on timed intervals. + * Performs periodic and final keyword searches for ingest jobs. Periodic + * searches are done in background tasks. This represents a careful working + * around of the contract for IngestModule.process(). Final searches are done + * synchronously in the calling thread, as required by the contract for + * IngestModule.shutDown(). */ final class IngestSearchRunner { private static final Logger logger = Logger.getLogger(IngestSearchRunner.class.getName()); private static IngestSearchRunner instance = null; - private IngestServices services = IngestServices.getInstance(); + private final IngestServices services = IngestServices.getInstance(); private Ingester ingester = null; private long currentUpdateIntervalMs; - private volatile boolean periodicSearchTaskRunning = false; - private Future jobProcessingTaskFuture; - private final ScheduledThreadPoolExecutor jobProcessingExecutor; + private volatile boolean periodicSearchTaskRunning; + private volatile Future periodicSearchTaskHandle; + private final ScheduledThreadPoolExecutor periodicSearchTaskExecutor; private static final int NUM_SEARCH_SCHEDULING_THREADS = 1; - private static final String SEARCH_SCHEDULER_THREAD_NAME = "periodic-search-scheduler-%d"; + private static final String SEARCH_SCHEDULER_THREAD_NAME = "periodic-search-scheduling-%d"; + private final Map jobs = new ConcurrentHashMap<>(); // Ingest job ID to search job info + private final boolean usingNetBeansGUI = RuntimeProperties.runningWithGUI(); - // maps a jobID to the search - private Map jobs = new ConcurrentHashMap<>(); - - IngestSearchRunner() { + /* + * Constructs a singleton object that performs periodic and final keyword + * searches for ingest jobs. Periodic searches are done in background tasks. + * This represents a careful working around of the contract for + * IngestModule.process(). Final searches are done synchronously in the + * calling thread, as required by the contract for IngestModule.shutDown(). + */ + private IngestSearchRunner() { currentUpdateIntervalMs = ((long) KeywordSearchSettings.getUpdateFrequency().getTime()) * 60 * 1000; ingester = Ingester.getDefault(); - jobProcessingExecutor = new ScheduledThreadPoolExecutor(NUM_SEARCH_SCHEDULING_THREADS, new ThreadFactoryBuilder().setNameFormat(SEARCH_SCHEDULER_THREAD_NAME).build()); + periodicSearchTaskExecutor = new ScheduledThreadPoolExecutor(NUM_SEARCH_SCHEDULING_THREADS, new ThreadFactoryBuilder().setNameFormat(SEARCH_SCHEDULER_THREAD_NAME).build()); } /** + * Gets the ingest search runner singleton. * - * @return the singleton object + * @return The ingest search runner. */ public static synchronized IngestSearchRunner getInstance() { if (instance == null) { @@ -89,72 +100,75 @@ final class IngestSearchRunner { } /** + * Starts the search job for an ingest job. * - * @param jobContext - * @param keywordListNames + * @param jobContext The ingest job context. + * @param keywordListNames The names of the keyword search lists for the + * ingest job. */ public synchronized void startJob(IngestJobContext jobContext, List keywordListNames) { long jobId = jobContext.getJobId(); if (jobs.containsKey(jobId) == false) { - logger.log(Level.INFO, "Adding job {0}", jobId); //NON-NLS SearchJobInfo jobData = new SearchJobInfo(jobContext, keywordListNames); jobs.put(jobId, jobData); } - // keep track of how many threads / module instances from this job have asked for this + /* + * Keep track of the number of keyword search file ingest modules that + * are doing analysis for the ingest job, i.e., that have called this + * method. This is needed by endJob(). + */ jobs.get(jobId).incrementModuleReferenceCount(); - // start the timer, if needed + /* + * Start a periodic search task in the + */ if ((jobs.size() > 0) && (periodicSearchTaskRunning == false)) { - // reset the default periodic search frequency to the user setting - logger.log(Level.INFO, "Resetting periodic search time out to default value"); //NON-NLS currentUpdateIntervalMs = ((long) KeywordSearchSettings.getUpdateFrequency().getTime()) * 60 * 1000; - jobProcessingTaskFuture = jobProcessingExecutor.schedule(new PeriodicSearchTask(), currentUpdateIntervalMs, MILLISECONDS); + periodicSearchTaskHandle = periodicSearchTaskExecutor.schedule(new PeriodicSearchTask(), currentUpdateIntervalMs, MILLISECONDS); periodicSearchTaskRunning = true; } } /** - * Perform normal finishing of searching for this job, including one last - * commit and search. Blocks until the final search is complete. + * Finishes a search job for an ingest job. * - * @param jobId + * @param jobId The ingest job ID. */ public synchronized void endJob(long jobId) { + /* + * Only complete the job if this is the last keyword search file ingest + * module doing annalysis for this job. + */ SearchJobInfo job; - boolean readyForFinalSearch = false; job = jobs.get(jobId); if (job == null) { - return; + return; // RJCTODO: SEVERE } - - // Only do final search if this is the last module/thread in this job to call endJob() - if (job.decrementModuleReferenceCount() == 0) { + if (job.decrementModuleReferenceCount() != 0) { jobs.remove(jobId); - readyForFinalSearch = true; } - if (readyForFinalSearch) { - logger.log(Level.INFO, "Commiting search index before final search for search job {0}", job.getJobId()); //NON-NLS - commit(); - doFinalSearch(job); //this will block until it's done + /* + * Commit the index and do the final search. The final search is done in + * the ingest thread that shutDown() on the keyword search file ingest + * module, per the contract of IngestModule.shutDwon(). + */ + logger.log(Level.INFO, "Commiting search index before final search for search job {0}", job.getJobId()); //NON-NLS + commit(); + logger.log(Level.INFO, "Starting final search for search job {0}", job.getJobId()); //NON-NLS + doFinalSearch(job); + logger.log(Level.INFO, "Final search for search job {0} completed", job.getJobId()); //NON-NLS - // new jobs could have been added while we were doing final search - if (jobs.isEmpty()) { - // no more jobs left. stop the PeriodicSearchTask. - // A new one will be created for future jobs. - logger.log(Level.INFO, "No more search jobs. Stopping periodic search task"); //NON-NLS - periodicSearchTaskRunning = false; - jobProcessingTaskFuture.cancel(true); - } + if (jobs.isEmpty()) { + cancelPeriodicSearchSchedulingTask(); } } /** - * Immediate stop and removal of job from SearchRunner. Cancels the - * associated search worker if it's still running. + * Stops the search job for an ingest job. * - * @param jobId + * @param jobId The ingest job ID. */ public synchronized void stopJob(long jobId) { logger.log(Level.INFO, "Stopping search job {0}", jobId); //NON-NLS @@ -166,7 +180,10 @@ final class IngestSearchRunner { return; } - //stop currentSearcher + /* + * Request cancellation of the current keyword search, whether it is a + * preiodic search or a final search. + */ IngestSearchRunner.Searcher currentSearcher = job.getCurrentSearcher(); if ((currentSearcher != null) && (!currentSearcher.isDone())) { logger.log(Level.INFO, "Cancelling search job {0}", jobId); //NON-NLS @@ -176,19 +193,16 @@ final class IngestSearchRunner { jobs.remove(jobId); if (jobs.isEmpty()) { - // no more jobs left. stop the PeriodicSearchTask. - // A new one will be created for future jobs. - logger.log(Level.INFO, "No more search jobs. Stopping periodic search task"); //NON-NLS - periodicSearchTaskRunning = false; - jobProcessingTaskFuture.cancel(true); + cancelPeriodicSearchSchedulingTask(); } } /** - * Add these lists to all of the jobs. Used when user wants to search for a - * list while ingest has already started. + * Adds the given keyword list names to the set of keyword lists to be + * searched by ALL keyword search jobs. This supports adding one or more + * keyword search lists to ingest jobs already in progress. * - * @param keywordListNames + * @param keywordListNames The n ames of the additional keyword lists. */ public synchronized void addKeywordListsToAllJobs(List keywordListNames) { for (String listName : keywordListNames) { @@ -200,155 +214,171 @@ final class IngestSearchRunner { } /** - * Commits index and notifies listeners of index update + * Commits the Solr index for the current case and publishes an event + * indicating the current number of indexed items (this is no longer just + * files). */ private void commit() { ingester.commit(); - // Signal a potential change in number of text_ingested files + /* + * Publish an event advertising the number of indexed items. Note that + * this is no longer the number of indexed files, since the text of many + * items in addition to files is indexed. + */ try { final int numIndexedFiles = KeywordSearch.getServer().queryNumIndexedFiles(); KeywordSearch.fireNumIndexedFilesChange(null, numIndexedFiles); } catch (NoOpenCoreException | KeywordSearchModuleException ex) { - logger.log(Level.SEVERE, "Error executing Solr query to check number of indexed files", ex); //NON-NLS + logger.log(Level.SEVERE, "Error executing Solr query for number of indexed files", ex); //NON-NLS } } /** - * A final search waits for any still-running workers, and then executes a - * new one and waits until that is done. + * Performs the final keyword search for an ingest job. The search is done + * synchronously, as required by the contract for IngestModule.shutDown(). * - * @param job + * @param job The keyword search job info. */ private void doFinalSearch(SearchJobInfo job) { - // Run one last search as there are probably some new files committed - logger.log(Level.INFO, "Starting final search for search job {0}", job.getJobId()); //NON-NLS if (!job.getKeywordListNames().isEmpty()) { try { - // In case this job still has a worker running, wait for it to finish - logger.log(Level.INFO, "Checking for previous search for search job {0} before executing final search", job.getJobId()); //NON-NLS + /* + * Wait for any periodic searches being done in a SwingWorker + * pool thread to finish. + */ job.waitForCurrentWorker(); - IngestSearchRunner.Searcher finalSearcher = new IngestSearchRunner.Searcher(job, true); - job.setCurrentSearcher(finalSearcher); //save the ref - logger.log(Level.INFO, "Kicking off final search for search job {0}", job.getJobId()); //NON-NLS - finalSearcher.execute(); //start thread - - // block until the search is complete - logger.log(Level.INFO, "Waiting for final search for search job {0}", job.getJobId()); //NON-NLS - finalSearcher.get(); - logger.log(Level.INFO, "Final search for search job {0} completed", job.getJobId()); //NON-NLS - + job.setCurrentSearcher(finalSearcher); + /* + * Do the final search synchronously on the current ingest + * thread, per the contract specified + */ + finalSearcher.doInBackground(); } catch (InterruptedException | CancellationException ex) { logger.log(Level.INFO, "Final search for search job {0} interrupted or cancelled", job.getJobId()); //NON-NLS - } catch (ExecutionException ex) { + } catch (Exception ex) { logger.log(Level.SEVERE, String.format("Final search for search job %d failed", job.getJobId()), ex); //NON-NLS } } } /** - * Task to perform periodic searches for each job (does a single index commit first) + * Cancels the current periodic search scheduling task. */ - private final class PeriodicSearchTask implements Runnable { - - private final Logger logger = Logger.getLogger(IngestSearchRunner.PeriodicSearchTask.class.getName()); - - @Override - public void run() { - // If no jobs then cancel the task. If more job(s) come along, a new task will start up. - if (jobs.isEmpty() || jobProcessingTaskFuture.isCancelled()) { - logger.log(Level.INFO, "Exiting periodic search task"); //NON-NLS - periodicSearchTaskRunning = false; - return; - } - - commit(); - - logger.log(Level.INFO, "Starting periodic searches"); - final StopWatch stopWatch = new StopWatch(); - stopWatch.start(); - // NOTE: contents of "jobs" ConcurrentHashMap can be modified in stopJob() and endJob() while we are inside this loop - for (Iterator> iterator = jobs.entrySet().iterator(); iterator.hasNext();) { - SearchJobInfo job = iterator.next().getValue(); - - if (jobProcessingTaskFuture.isCancelled()) { - logger.log(Level.INFO, "Search has been cancelled. Exiting periodic search task."); //NON-NLS - periodicSearchTaskRunning = false; - return; - } - - // If no lists or the worker is already running then skip it - if (!job.getKeywordListNames().isEmpty() && !job.isWorkerRunning()) { - // Spawn a search thread for each job - logger.log(Level.INFO, "Executing periodic search for search job {0}", job.getJobId()); - Searcher searcher = new Searcher(job); // SwingWorker - job.setCurrentSearcher(searcher); //save the ref - searcher.execute(); //start thread - job.setWorkerRunning(true); - - try { - // wait for the searcher to finish - searcher.get(); - } catch (InterruptedException | ExecutionException ex) { - logger.log(Level.SEVERE, "Error performing keyword search: {0}", ex.getMessage()); //NON-NLS - services.postMessage(IngestMessage.createErrorMessage(KeywordSearchModuleFactory.getModuleName(), - NbBundle.getMessage(this.getClass(), - "SearchRunner.Searcher.done.err.msg"), ex.getMessage())); - }// catch and ignore if we were cancelled - catch (java.util.concurrent.CancellationException ex) { - } - } - } - stopWatch.stop(); - logger.log(Level.INFO, "All periodic searches cumulatively took {0} secs", stopWatch.getElapsedTimeSecs()); //NON-NLS - - // calculate "hold off" time - recalculateUpdateIntervalTime(stopWatch.getElapsedTimeSecs()); // ELDEBUG - - // schedule next PeriodicSearchTask - jobProcessingTaskFuture = jobProcessingExecutor.schedule(new PeriodicSearchTask(), currentUpdateIntervalMs, MILLISECONDS); - - // exit this thread - return; - } - - - private void recalculateUpdateIntervalTime(long lastSerchTimeSec) { - // If periodic search takes more than 1/4 of the current periodic search interval, then double the search interval - if (lastSerchTimeSec * 1000 < currentUpdateIntervalMs / 4) { - return; - } - // double the search interval - currentUpdateIntervalMs = currentUpdateIntervalMs * 2; - logger.log(Level.WARNING, "Last periodic search took {0} sec. Increasing search interval to {1} sec", new Object[]{lastSerchTimeSec, currentUpdateIntervalMs/1000}); - return; + private synchronized void cancelPeriodicSearchSchedulingTask() { + if (periodicSearchTaskHandle != null) { + logger.log(Level.INFO, "No more search jobs, stopping periodic search scheduling"); //NON-NLS + periodicSearchTaskHandle.cancel(true); + periodicSearchTaskRunning = false; } } /** - * Data structure to keep track of keyword lists, current results, and - * search running status for each jobid + * Task that runs in ScheduledThreadPoolExecutor to periodically start and + * wait for keyword search tasks for each keyword search job in progress. + * The keyword search tasks for individual ingest jobs are implemented as + * SwingWorkers to support legacy APIs. + */ + private final class PeriodicSearchTask implements Runnable { + + @Override + public void run() { + /* + * If there are no more jobs or this task has been cancelled, exit. + */ + if (jobs.isEmpty() || periodicSearchTaskHandle.isCancelled()) { + logger.log(Level.INFO, "Periodic search scheduling task has been cancelled, exiting"); //NON-NLS + periodicSearchTaskRunning = false; + return; + } + + /* + * Commit the Solr index for the current case before doing the + * searches. + */ + commit(); + + /* + * Do a keyword search for each ingest job in progress. When the + * searches are done, recalculate the "hold off" time between + * searches to prevent back-to-back periodic searches and schedule + * the nect periodic search task. + */ + final StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + for (Iterator> iterator = jobs.entrySet().iterator(); iterator.hasNext();) { + SearchJobInfo job = iterator.next().getValue(); + + if (periodicSearchTaskHandle.isCancelled()) { + logger.log(Level.INFO, "Periodic search scheduling task has been cancelled, exiting"); //NON-NLS + periodicSearchTaskRunning = false; + return; + } + + if (!job.getKeywordListNames().isEmpty() && !job.isWorkerRunning()) { + logger.log(Level.INFO, "Starting periodic search for search job {0}", job.getJobId()); + Searcher searcher = new Searcher(job, false); + job.setCurrentSearcher(searcher); + searcher.execute(); + job.setWorkerRunning(true); + try { + searcher.get(); + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, String.format("Error performing keyword search for ingest job %d", job.getJobId()), ex); //NON-NLS + services.postMessage(IngestMessage.createErrorMessage( + KeywordSearchModuleFactory.getModuleName(), + NbBundle.getMessage(this.getClass(), "SearchRunner.Searcher.done.err.msg"), ex.getMessage())); + } catch (java.util.concurrent.CancellationException ex) { + logger.log(Level.SEVERE, String.format("Keyword search for ingest job %d cancelled", job.getJobId()), ex); //NON-NLS + } + } + } + stopWatch.stop(); + logger.log(Level.INFO, "Periodic searches for all ingest jobs cumulatively took {0} secs", stopWatch.getElapsedTimeSecs()); //NON-NLS + recalculateUpdateIntervalTime(stopWatch.getElapsedTimeSecs()); // ELDEBUG + periodicSearchTaskHandle = periodicSearchTaskExecutor.schedule(new PeriodicSearchTask(), currentUpdateIntervalMs, MILLISECONDS); + } + + /** + * Sets the time interval between periodic keyword searches to avoid + * running back-to-back searches. If the most recent round of searches + * took longer that 1/4 of the current interval, doubles the interval. + * + * @param lastSerchTimeSec The time in seconds used to execute the most + * recent round of keword searches. + */ + private void recalculateUpdateIntervalTime(long lastSerchTimeSec) { + if (lastSerchTimeSec * 1000 < currentUpdateIntervalMs / 4) { + return; + } + currentUpdateIntervalMs *= 2; + logger.log(Level.WARNING, "Last periodic search took {0} sec. Increasing search interval to {1} sec", new Object[]{lastSerchTimeSec, currentUpdateIntervalMs / 1000}); + } + } + + /** + * A data structure to keep track of the keyword lists, current results, and + * search running status for an ingest job. */ private class SearchJobInfo { private final IngestJobContext jobContext; private final long jobId; private final long dataSourceId; - // mutable state: private volatile boolean workerRunning; - private List keywordListNames; //guarded by SearchJobInfo.this - - // Map of keyword to the object ids that contain a hit - private Map> currentResults; //guarded by SearchJobInfo.this + @GuardedBy("this") + private final List keywordListNames; + @GuardedBy("this") + private final Map> currentResults; // Keyword to object IDs of items with hits private IngestSearchRunner.Searcher currentSearcher; - private AtomicLong moduleReferenceCount = new AtomicLong(0); - private final Object finalSearchLock = new Object(); //used for a condition wait + private final AtomicLong moduleReferenceCount = new AtomicLong(0); + private final Object finalSearchLock = new Object(); private SearchJobInfo(IngestJobContext jobContext, List keywordListNames) { this.jobContext = jobContext; - this.jobId = jobContext.getJobId(); - this.dataSourceId = jobContext.getDataSource().getId(); + jobId = jobContext.getJobId(); + dataSourceId = jobContext.getDataSource().getId(); this.keywordListNames = new ArrayList<>(keywordListNames); currentResults = new HashMap<>(); workerRunning = false; @@ -410,139 +440,143 @@ final class IngestSearchRunner { } /** - * In case this job still has a worker running, wait for it to finish + * Waits for the current search task to complete. * * @throws InterruptedException */ private void waitForCurrentWorker() throws InterruptedException { synchronized (finalSearchLock) { while (workerRunning) { - logger.log(Level.INFO, "Waiting for previous worker to finish"); //NON-NLS - finalSearchLock.wait(); //wait() releases the lock - logger.log(Level.INFO, "Notified previous worker finished"); //NON-NLS + logger.log(Level.INFO, String.format("Waiting for previous search task for job %d to finish", jobId)); //NON-NLS + finalSearchLock.wait(); + logger.log(Level.INFO, String.format("Notified previous search task for job %d to finish", jobId)); //NON-NLS } } } /** - * Unset workerRunning and wake up thread(s) waiting on finalSearchLock + * Signals any threads waiting on the current search task to complete. */ private void searchNotify() { synchronized (finalSearchLock) { - logger.log(Level.INFO, "Notifying after finishing search"); //NON-NLS workerRunning = false; finalSearchLock.notify(); } } } - /** - * Searcher responsible for searching the current index and writing results - * to blackboard and the inbox. Also, posts results to listeners as Ingest - * data events. Searches entire index, and keeps track of only new results - * to report and save. Runs as a background thread. + /* + * A SwingWorker responsible for searching the Solr index of the current + * case for the keywords for an ingest job. Keyword hit analysis results are + * created and posted to the blackboard and notifications are sent to the + * ingest inbox. */ private final class Searcher extends SwingWorker { - /** + /* * Searcher has private copies/snapshots of the lists and keywords */ - private SearchJobInfo job; - private List keywords; //keywords to search - private List keywordListNames; // lists currently being searched - private List keywordLists; - private Map keywordToList; //keyword to list name mapping - private AggregateProgressHandle progressGroup; - private final Logger logger = Logger.getLogger(IngestSearchRunner.Searcher.class.getName()); + private final SearchJobInfo job; + private final List keywords; //keywords to search + private final List keywordListNames; // lists currently being searched + private final List keywordLists; + private final Map keywordToList; //keyword to list name mapping + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + private ProgressHandle progressIndicator; private boolean finalRun = false; - Searcher(SearchJobInfo job) { + Searcher(SearchJobInfo job, boolean finalRun) { this.job = job; + this.finalRun = finalRun; keywordListNames = job.getKeywordListNames(); keywords = new ArrayList<>(); keywordToList = new HashMap<>(); keywordLists = new ArrayList<>(); - //keywords are populated as searcher runs - } - - Searcher(SearchJobInfo job, boolean finalRun) { - this(job); - this.finalRun = finalRun; } @Override @Messages("SearchRunner.query.exception.msg=Error performing query:") protected Object doInBackground() throws Exception { - final String displayName = NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.doInBackGround.displayName") - + (finalRun ? (" - " + NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.doInBackGround.finalizeMsg")) : ""); - final String pgDisplayName = displayName + (" (" + NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.doInBackGround.pendingMsg") + ")"); - progressGroup = AggregateProgressFactory.createSystemHandle(pgDisplayName, null, new Cancellable() { - @Override - public boolean cancel() { - logger.log(Level.INFO, "Cancelling the searcher by user."); //NON-NLS - if (progressGroup != null) { - progressGroup.setDisplayName(displayName + " " + NbBundle.getMessage(this.getClass(), "SearchRunner.doInBackGround.cancelMsg")); - } - progressGroup.finish(); - return IngestSearchRunner.Searcher.this.cancel(true); - } - }, null); - - updateKeywords(); - - ProgressContributor[] subProgresses = new ProgressContributor[keywords.size()]; - int i = 0; - for (Keyword keywordQuery : keywords) { - subProgresses[i] = AggregateProgressFactory.createProgressContributor(keywordQuery.getSearchTerm()); - progressGroup.addContributor(subProgresses[i]); - i++; - } - - progressGroup.start(); - - final StopWatch stopWatch = new StopWatch(); - stopWatch.start(); try { - progressGroup.setDisplayName(displayName); - - int keywordsSearched = 0; + if (usingNetBeansGUI) { + /* + * If running in the NetBeans thick client application + * version of Autopsy, NetBeans progress handles (i.e., + * progress bars) are used to display search progress in the + * lower right hand corner of the main application window. + * + * A layer of abstraction to allow alternate representations + * of progress could be used here, as it is in other places + * in the application (see implementations and usage of + * org.sleuthkit.autopsy.progress.ProgressIndicator + * interface), to better decouple keyword search from the + * application's presentation layer. + */ + SwingUtilities.invokeAndWait(() -> { + final String displayName = NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.doInBackGround.displayName") + + (finalRun ? (" - " + NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.doInBackGround.finalizeMsg")) : ""); + progressIndicator = ProgressHandle.createHandle(displayName, new Cancellable() { + @Override + public boolean cancel() { + if (progressIndicator != null) { + progressIndicator.setDisplayName(displayName + " " + NbBundle.getMessage(this.getClass(), "SearchRunner.doInBackGround.cancelMsg")); + } + logger.log(Level.INFO, "Search cancelled by user"); //NON-NLS + new Thread(() -> { + IngestSearchRunner.Searcher.this.cancel(true); + }).start(); + return true; + } + }); + progressIndicator.start(); + progressIndicator.switchToIndeterminate(); + }); + } + updateKeywords(); for (Keyword keyword : keywords) { - if (this.isCancelled() || this.job.getJobContext().fileIngestIsCancelled()) { - logger.log(Level.INFO, "Cancel detected, bailing before new keyword processed: {0}", keyword.getSearchTerm()); //NON-NLS + if (isCancelled() || job.getJobContext().fileIngestIsCancelled()) { + logger.log(Level.INFO, "Cancellation requested, exiting before new keyword processed: {0}", keyword.getSearchTerm()); //NON-NLS return null; } - final KeywordList keywordList = keywordToList.get(keyword); - - //new subProgress will be active after the initial query - //when we know number of hits to start() with - if (keywordsSearched > 0) { - subProgresses[keywordsSearched - 1].finish(); + KeywordList keywordList = keywordToList.get(keyword); + if (usingNetBeansGUI) { + String searchTermStr = keyword.getSearchTerm(); + if (searchTermStr.length() > 50) { + searchTermStr = searchTermStr.substring(0, 49) + "..."; + } + final String progressMessage = keywordList.getName() + ": " + searchTermStr; + SwingUtilities.invokeLater(() -> { + progressIndicator.progress(progressMessage); + }); } - KeywordSearchQuery keywordSearchQuery = KeywordSearchUtil.getQueryForKeyword(keyword, keywordList); - // Filtering //limit search to currently ingested data sources //set up a filter with 1 or more image ids OR'ed - final KeywordQueryFilter dataSourceFilter = new KeywordQueryFilter(KeywordQueryFilter.FilterType.DATA_SOURCE, job.getDataSourceId()); + KeywordSearchQuery keywordSearchQuery = KeywordSearchUtil.getQueryForKeyword(keyword, keywordList); + KeywordQueryFilter dataSourceFilter = new KeywordQueryFilter(KeywordQueryFilter.FilterType.DATA_SOURCE, job.getDataSourceId()); keywordSearchQuery.addFilter(dataSourceFilter); - QueryResults queryResults; - // Do the actual search + QueryResults queryResults; try { queryResults = keywordSearchQuery.performQuery(); } catch (KeywordSearchModuleException | NoOpenCoreException ex) { logger.log(Level.SEVERE, "Error performing query: " + keyword.getSearchTerm(), ex); //NON-NLS - MessageNotifyUtil.Notify.error(Bundle.SearchRunner_query_exception_msg() + keyword.getSearchTerm(), ex.getCause().getMessage()); + if (usingNetBeansGUI) { + final String userMessage = Bundle.SearchRunner_query_exception_msg() + keyword.getSearchTerm(); + SwingUtilities.invokeLater(() -> { + MessageNotifyUtil.Notify.error(userMessage, ex.getCause().getMessage()); + }); + } //no reason to continue with next query if recovery failed //or wait for recovery to kick in and run again later //likely case has closed and threads are being interrupted return null; } catch (CancellationException e) { - logger.log(Level.INFO, "Cancel detected, bailing during keyword query: {0}", keyword.getSearchTerm()); //NON-NLS + logger.log(Level.INFO, "Cancellation requested, exiting during keyword query: {0}", keyword.getSearchTerm()); //NON-NLS return null; } @@ -551,42 +585,24 @@ final class IngestSearchRunner { QueryResults newResults = filterResults(queryResults); if (!newResults.getKeywords().isEmpty()) { - - // Write results to BB - //scale progress bar more more granular, per result sub-progress, within per keyword - int totalUnits = newResults.getKeywords().size(); - subProgresses[keywordsSearched].start(totalUnits); - int unitProgress = 0; - String queryDisplayStr = keyword.getSearchTerm(); - if (queryDisplayStr.length() > 50) { - queryDisplayStr = queryDisplayStr.substring(0, 49) + "..."; - } - subProgresses[keywordsSearched].progress(keywordList.getName() + ": " + queryDisplayStr, unitProgress); - // Create blackboard artifacts - newResults.process(null, subProgresses[keywordsSearched], this, keywordList.getIngestMessages(), true); - - } //if has results - - //reset the status text before it goes away - subProgresses[keywordsSearched].progress(""); - - ++keywordsSearched; - - } //for each keyword - - } //end try block - catch (Exception ex) { - logger.log(Level.WARNING, "searcher exception occurred", ex); //NON-NLS - } finally { - try { - finalizeSearcher(); - stopWatch.stop(); - logger.log(Level.INFO, "Searcher took {0} secs to run (final = {1})", new Object[]{stopWatch.getElapsedTimeSecs(), this.finalRun}); //NON-NLS - } finally { - // In case a thread is waiting on this worker to be done - job.searchNotify(); + newResults.process(this, keywordList.getIngestMessages(), true, job.getJobId()); + } } + } catch (Exception ex) { + logger.log(Level.SEVERE, String.format("Error performing keyword search for ingest job %d", job.getJobId()), ex); //NON-NLS + } finally { + if (progressIndicator != null) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + progressIndicator.finish(); + progressIndicator = null; + } + }); + } + // In case a thread is waiting on this worker to be done + job.searchNotify(); } return null; @@ -612,20 +628,6 @@ final class IngestSearchRunner { } } - /** - * Performs the cleanup that needs to be done right AFTER - * doInBackground() returns without relying on done() method that is not - * guaranteed to run. - */ - private void finalizeSearcher() { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - progressGroup.finish(); - } - }); - } - /** * This method filters out all of the hits found in earlier periodic * searches and returns only the results found by the most recent @@ -699,4 +701,5 @@ final class IngestSearchRunner { return newResults; } } + } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java index 576b65d581..052a0b0b16 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,6 +30,7 @@ import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.common.SolrInputDocument; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.healthmonitor.HealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; @@ -63,6 +64,7 @@ class Ingester { private static Ingester instance; private final LanguageSpecificContentIndexingHelper languageSpecificContentIndexingHelper = new LanguageSpecificContentIndexingHelper(); + private static final int LANGUAGE_DETECTION_STRING_SIZE = 4096; private Ingester() { } @@ -125,7 +127,55 @@ class Ingester { private Map getContentFields(SleuthkitVisitableItem item) { return item.accept(SOLR_FIELDS_VISITOR); } - + + /** + * Read and chunk the source text for indexing in Solr. Also performs + * language detection on the input text. + * + * @param The type of the Appendix provider that provides additional + * text to append to the final chunk. + * @param A subclass of SleuthkitVisibleItem. + * @param Reader The reader containing extracted text. + * @param source The source from which text will be extracted, chunked, and + * indexed. + * @param context The ingest job context that can be used to cancel this + * process. + * + * @return True if indexing was completed, false otherwise. + * + * @throws org.sleuthkit.autopsy.keywordsearch.Ingester.IngesterException + */ + // TODO (JIRA-3118): Cancelled text indexing does not propagate cancellation to clients + < T extends SleuthkitVisitableItem> boolean indexText(Reader sourceReader, long sourceID, String sourceName, T source, IngestJobContext context) throws Ingester.IngesterException { + boolean doLanguageDetection = true; + return indexText(sourceReader, sourceID, sourceName, source, context, doLanguageDetection); + } + + /** + * Read and chunk the source text for indexing in Solr. Does NOT perform + * language detection on the input strings. Per JIRA-7100, it was determined + * that language detection on extracted strings can take a really long time. + * + * @param The type of the Appendix provider that provides additional + * text to append to the final chunk. + * @param A subclass of SleuthkitVisibleItem. + * @param Reader The reader containing extracted text. + * @param source The source from which text will be extracted, chunked, and + * indexed. + * @param context The ingest job context that can be used to cancel this + * process. + * + * @return True if indexing was completed, false otherwise. + * + * @throws org.sleuthkit.autopsy.keywordsearch.Ingester.IngesterException + */ + // TODO (JIRA-3118): Cancelled text indexing does not propagate cancellation to clients + < T extends SleuthkitVisitableItem> boolean indexStrings(Reader sourceReader, long sourceID, String sourceName, T source, IngestJobContext context) throws Ingester.IngesterException { + // Per JIRA-7100, it was determined that language detection on extracted strings can take a really long time. + boolean doLanguageDetection = false; + return indexText(sourceReader, sourceID, sourceName, source, context, doLanguageDetection); + } + /** * Read and chunk the source text for indexing in Solr. * @@ -138,16 +188,18 @@ class Ingester { * and indexed. * @param context The ingest job context that can be used to cancel this * process. + * @param doLanguageDetection A flag whether to perform language detection on the input text/strings. * * @return True if indexing was completed, false otherwise. * * @throws org.sleuthkit.autopsy.keywordsearch.Ingester.IngesterException */ // TODO (JIRA-3118): Cancelled text indexing does not propagate cancellation to clients - < T extends SleuthkitVisitableItem> boolean indexText(Reader sourceReader, long sourceID, String sourceName, T source, IngestJobContext context) throws Ingester.IngesterException { + private < T extends SleuthkitVisitableItem> boolean indexText(Reader sourceReader, long sourceID, String sourceName, T source, IngestJobContext context, boolean doLanguageDetection) throws Ingester.IngesterException { int numChunks = 0; //unknown until chunking is done Map contentFields = Collections.unmodifiableMap(getContentFields(source)); + Optional language = Optional.empty(); //Get a reader for the content of the given source try (BufferedReader reader = new BufferedReader(sourceReader)) { Chunker chunker = new Chunker(reader); @@ -162,7 +214,14 @@ class Ingester { String chunkId = Server.getChunkIdString(sourceID, numChunks + 1); fields.put(Server.Schema.ID.toString(), chunkId); fields.put(Server.Schema.CHUNK_SIZE.toString(), String.valueOf(chunk.getBaseChunkLength())); - Optional language = languageSpecificContentIndexingHelper.detectLanguageIfNeeded(chunk); + + if (doLanguageDetection) { + int size = Math.min(chunk.getBaseChunkLength(), LANGUAGE_DETECTION_STRING_SIZE); + language = languageSpecificContentIndexingHelper.detectLanguageIfNeeded(chunk.toString().substring(0, size)); + + // only do language detection on the first chunk of the document + doLanguageDetection = false; + } language.ifPresent(lang -> languageSpecificContentIndexingHelper.updateLanguageSpecificFields(fields, chunk, lang)); try { //add the chunk text to Solr index @@ -232,7 +291,11 @@ class Ingester { //Make a SolrInputDocument out of the field map SolrInputDocument updateDoc = new SolrInputDocument(); for (String key : fields.keySet()) { - updateDoc.addField(key, fields.get(key)); + if (fields.get(key).getClass() == String.class) { + updateDoc.addField(key, Chunker.sanitize((String)fields.get(key)).toString()); + } else { + updateDoc.addField(key, fields.get(key)); + } } try { @@ -333,10 +396,10 @@ class Ingester { */ private Map getCommonAndMACTimeFields(AbstractFile file) { Map params = getCommonFields(file); - params.put(Server.Schema.CTIME.toString(), ContentUtils.getStringTimeISO8601(file.getCtime(), file)); - params.put(Server.Schema.ATIME.toString(), ContentUtils.getStringTimeISO8601(file.getAtime(), file)); - params.put(Server.Schema.MTIME.toString(), ContentUtils.getStringTimeISO8601(file.getMtime(), file)); - params.put(Server.Schema.CRTIME.toString(), ContentUtils.getStringTimeISO8601(file.getCrtime(), file)); + params.put(Server.Schema.CTIME.toString(), TimeZoneUtils.getFormattedTimeISO8601(file.getCtime())); + params.put(Server.Schema.ATIME.toString(), TimeZoneUtils.getFormattedTimeISO8601(file.getAtime())); + params.put(Server.Schema.MTIME.toString(), TimeZoneUtils.getFormattedTimeISO8601(file.getMtime())); + params.put(Server.Schema.CRTIME.toString(), TimeZoneUtils.getFormattedTimeISO8601(file.getCrtime())); return params; } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Installer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Installer.java index 04bcbf6940..126d7fa4ba 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Installer.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Installer.java @@ -18,6 +18,9 @@ */ package org.sleuthkit.autopsy.keywordsearch; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.logging.Level; import org.apache.solr.client.solrj.SolrServerException; import org.openide.modules.ModuleInstall; @@ -39,6 +42,7 @@ class Installer extends ModuleInstall { private static final Logger logger = Logger.getLogger(Installer.class.getName()); private static final long serialVersionUID = 1L; + private static final String KWS_START_THREAD_NAME = "KWS-server-start-%d"; @Override public void restored() { @@ -46,19 +50,29 @@ class Installer extends ModuleInstall { KeywordSearchSettings.setDefaults(); final Server server = KeywordSearch.getServer(); - try { - server.start(); - } catch (SolrServerNoPortException ex) { - logger.log(Level.SEVERE, "Failed to start Keyword Search server: ", ex); //NON-NLS - if (ex.getPortNumber() == server.getLocalSolrServerPort()) { - reportPortError(ex.getPortNumber()); - } else { - reportStopPortError(ex.getPortNumber()); + + ExecutorService jobProcessingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(KWS_START_THREAD_NAME).build()); + Runnable kwsStartTask = new Runnable() { + public void run() { + try { + server.start(); + } catch (SolrServerNoPortException ex) { + logger.log(Level.SEVERE, "Failed to start Keyword Search server: ", ex); //NON-NLS + if (ex.getPortNumber() == server.getLocalSolrServerPort()) { + reportPortError(ex.getPortNumber()); + } else { + reportStopPortError(ex.getPortNumber()); + } + } catch (KeywordSearchModuleException | SolrServerException ex) { + logger.log(Level.SEVERE, "Failed to start Keyword Search server: ", ex); //NON-NLS + reportInitError(ex.getMessage()); + } } - } catch (KeywordSearchModuleException | SolrServerException ex) { - logger.log(Level.SEVERE, "Failed to start Keyword Search server: ", ex); //NON-NLS - reportInitError(ex.getMessage()); - } + }; + + // start KWS service on the background thread. Currently all it does is start the embedded Solr server. + jobProcessingExecutor.submit(kwsStartTask); + jobProcessingExecutor.shutdown(); // tell executor no more work is coming } @Override diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordList.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordList.java index f2db250baa..ea2b150d7f 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordList.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordList.java @@ -165,7 +165,7 @@ public class KeywordList { /** * Gets the keywords included in the list * - * @return A colleciton of Keyword objects. + * @return A collection of Keyword objects. */ public List getKeywords() { return keywords; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalLanguageSettingsPanel.form b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalLanguageSettingsPanel.form index a4c3e517f9..6793f68e27 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalLanguageSettingsPanel.form +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalLanguageSettingsPanel.form @@ -16,32 +16,30 @@ - + + - - - - - - - - - - - - + + + - - - - - - + + + + + + + + + + + + - + @@ -50,18 +48,16 @@ - + - - - - + + @@ -105,7 +101,7 @@ - + @@ -139,16 +135,6 @@
- - - - - - - - - - diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalLanguageSettingsPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalLanguageSettingsPanel.java index 95ee4faf7f..97a3d7207a 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalLanguageSettingsPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalLanguageSettingsPanel.java @@ -70,12 +70,6 @@ class KeywordSearchGlobalLanguageSettingsPanel extends javax.swing.JPanel implem initScriptsCheckBoxes(); reloadScriptsCheckBoxes(); - - if (!PlatformUtil.isWindowsOS() || !PlatformUtil.is64BitOS()) { - enableOcrCheckbox.setText("Enable Optical Character Recognition (OCR) (Requires Windows 64-bit)"); - enableOcrCheckbox.setSelected(false); - enableOcrCheckbox.setEnabled(false); - } //allow panel to toggle its enabled status while it is open based on ingest events IngestManager.getInstance().addIngestJobEventListener(new PropertyChangeListener() { @@ -136,9 +130,6 @@ class KeywordSearchGlobalLanguageSettingsPanel extends javax.swing.JPanel implem = Boolean.parseBoolean(KeywordSearchSettings.getStringExtractOption(StringsExtractOptions.EXTRACT_UTF8.toString())); enableUTF8Checkbox.setSelected(utf8); - boolean ocr = KeywordSearchSettings.getOcrOption(); - enableOcrCheckbox.setSelected(ocr); - final List >> +in a JSON text, with the cost of bloating the size of JSON texts. + +This option may be useful when you embed JSON in HTML, but embedding +arbitrary JSON in HTML (by some HTML template toolkit or by string +interpolation) is risky in general. You must escape necessary +characters in correct order, depending on the context. + +C will not be affected in any way. + +=head2 indent_length + + $json = $json->indent_length($number_of_spaces) + $length = $json->get_indent_length + +This option is only useful when you also enable C or C. + +JSON::XS indents with three spaces when you C (if requested +by C or C), and the number cannot be changed. +JSON::PP allows you to change/get the number of indent spaces with these +mutator/accessor. The default number of spaces is three (the same as +JSON::XS), and the acceptable range is from C<0> (no indentation; +it'd be better to disable indentation by C) to C<15>. + +=head2 sort_by + + $json = $json->sort_by($code_ref) + $json = $json->sort_by($subroutine_name) + +If you just want to sort keys (names) in JSON objects when you +C, enable C option (see above) that allows you to +sort object keys alphabetically. + +If you do need to sort non-alphabetically for whatever reasons, +you can give a code reference (or a subroutine name) to C, +then the argument will be passed to Perl's C built-in function. + +As the sorting is done in the JSON::PP scope, you usually need to +prepend C to the subroutine name, and the special variables +C<$a> and C<$b> used in the subrontine used by C function. + +Example: + + my %ORDER = (id => 1, class => 2, name => 3); + $json->sort_by(sub { + ($ORDER{$JSON::PP::a} // 999) <=> ($ORDER{$JSON::PP::b} // 999) + or $JSON::PP::a cmp $JSON::PP::b + }); + print $json->encode([ + {name => 'CPAN', id => 1, href => 'http://cpan.org'} + ]); + # [{"id":1,"name":"CPAN","href":"http://cpan.org"}] + +Note that C affects all the plain hashes in the data structure. +If you need finer control, C necessary hashes with a module that +implements ordered hash (such as L and L). +C and C don't affect the key order in Cd +hashes. + + use Hash::Ordered; + tie my %hash, 'Hash::Ordered', + (name => 'CPAN', id => 1, href => 'http://cpan.org'); + print $json->encode([\%hash]); + # [{"name":"CPAN","id":1,"href":"http://cpan.org"}] # order is kept + +=head1 INCREMENTAL PARSING + +This section is also taken from JSON::XS. + +In some cases, there is the need for incremental parsing of JSON +texts. While this module always has to keep both JSON text and resulting +Perl data structure in memory at one time, it does allow you to parse a +JSON stream incrementally. It does so by accumulating text until it has +a full JSON object, which it then can decode. This process is similar to +using C to see if a full JSON object is available, but +is much more efficient (and can be implemented with a minimum of method +calls). + +JSON::PP will only attempt to parse the JSON text once it is sure it +has enough text to get a decisive result, using a very simple but +truly incremental parser. This means that it sometimes won't stop as +early as the full parser, for example, it doesn't detect mismatched +parentheses. The only thing it guarantees is that it starts decoding as +soon as a syntactically valid JSON text has been seen. This means you need +to set resource limits (e.g. C) to ensure the parser will stop +parsing in the presence if syntax errors. + +The following methods implement this incremental parser. + +=head2 incr_parse + + $json->incr_parse( [$string] ) # void context + + $obj_or_undef = $json->incr_parse( [$string] ) # scalar context + + @obj_or_empty = $json->incr_parse( [$string] ) # list context + +This is the central parsing function. It can both append new text and +extract objects from the stream accumulated so far (both of these +functions are optional). + +If C<$string> is given, then this string is appended to the already +existing JSON fragment stored in the C<$json> object. + +After that, if the function is called in void context, it will simply +return without doing anything further. This can be used to add more text +in as many chunks as you want. + +If the method is called in scalar context, then it will try to extract +exactly I JSON object. If that is successful, it will return this +object, otherwise it will return C. If there is a parse error, +this method will croak just as C would do (one can then use +C to skip the erroneous part). This is the most common way of +using the method. + +And finally, in list context, it will try to extract as many objects +from the stream as it can find and return them, or the empty list +otherwise. For this to work, there must be no separators (other than +whitespace) between the JSON objects or arrays, instead they must be +concatenated back-to-back. If an error occurs, an exception will be +raised as in the scalar context case. Note that in this case, any +previously-parsed JSON texts will be lost. + +Example: Parse some JSON arrays/objects in a given string and return +them. + + my @objs = JSON::PP->new->incr_parse ("[5][7][1,2]"); + +=head2 incr_text + + $lvalue_string = $json->incr_text + +This method returns the currently stored JSON fragment as an lvalue, that +is, you can manipulate it. This I works when a preceding call to +C in I successfully returned an object. Under +all other circumstances you must not call this function (I mean it. +although in simple tests it might actually work, it I fail under +real world conditions). As a special exception, you can also call this +method before having parsed anything. + +That means you can only use this function to look at or manipulate text +before or after complete JSON objects, not while the parser is in the +middle of parsing a JSON object. + +This function is useful in two cases: a) finding the trailing text after a +JSON object or b) parsing multiple JSON objects separated by non-JSON text +(such as commas). + +=head2 incr_skip + + $json->incr_skip + +This will reset the state of the incremental parser and will remove +the parsed text from the input buffer so far. This is useful after +C died, in which case the input buffer and incremental parser +state is left unchanged, to skip the text parsed so far and to reset the +parse state. + +The difference to C is that only text until the parse error +occurred is removed. + +=head2 incr_reset + + $json->incr_reset + +This completely resets the incremental parser, that is, after this call, +it will be as if the parser had never parsed anything. + +This is useful if you want to repeatedly parse JSON objects and want to +ignore any trailing data, which means you have to reset the parser after +each successful decode. + +=head1 MAPPING + +Most of this section is also taken from JSON::XS. + +This section describes how JSON::PP maps Perl values to JSON values and +vice versa. These mappings are designed to "do the right thing" in most +circumstances automatically, preserving round-tripping characteristics +(what you put in comes out as something equivalent). + +For the more enlightened: note that in the following descriptions, +lowercase I refers to the Perl interpreter, while uppercase I +refers to the abstract Perl language itself. + +=head2 JSON -> PERL + +=over 4 + +=item object + +A JSON object becomes a reference to a hash in Perl. No ordering of object +keys is preserved (JSON does not preserve object key ordering itself). + +=item array + +A JSON array becomes a reference to an array in Perl. + +=item string + +A JSON string becomes a string scalar in Perl - Unicode codepoints in JSON +are represented by the same codepoints in the Perl string, so no manual +decoding is necessary. + +=item number + +A JSON number becomes either an integer, numeric (floating point) or +string scalar in perl, depending on its range and any fractional parts. On +the Perl level, there is no difference between those as Perl handles all +the conversion details, but an integer may take slightly less memory and +might represent more values exactly than floating point numbers. + +If the number consists of digits only, JSON::PP will try to represent +it as an integer value. If that fails, it will try to represent it as +a numeric (floating point) value if that is possible without loss of +precision. Otherwise it will preserve the number as a string value (in +which case you lose roundtripping ability, as the JSON number will be +re-encoded to a JSON string). + +Numbers containing a fractional or exponential part will always be +represented as numeric (floating point) values, possibly at a loss of +precision (in which case you might lose perfect roundtripping ability, but +the JSON number will still be re-encoded as a JSON number). + +Note that precision is not accuracy - binary floating point values cannot +represent most decimal fractions exactly, and when converting from and to +floating point, JSON::PP only guarantees precision up to but not including +the least significant bit. + +When C is enabled, big integer values and any numeric +values will be converted into L and L +objects respectively, without becoming string scalars or losing +precision. + +=item true, false + +These JSON atoms become C and C, +respectively. They are overloaded to act almost exactly like the numbers +C<1> and C<0>. You can check whether a scalar is a JSON boolean by using +the C function. + +=item null + +A JSON null atom becomes C in Perl. + +=item shell-style comments (C<< # I >>) + +As a nonstandard extension to the JSON syntax that is enabled by the +C setting, shell-style comments are allowed. They can start +anywhere outside strings and go till the end of the line. + +=item tagged values (C<< (I)I >>). + +Another nonstandard extension to the JSON syntax, enabled with the +C setting, are tagged values. In this implementation, the +I must be a perl package/class name encoded as a JSON string, and the +I must be a JSON array encoding optional constructor arguments. + +See L, below, for details. + +=back + + +=head2 PERL -> JSON + +The mapping from Perl to JSON is slightly more difficult, as Perl is a +truly typeless language, so we can only guess which JSON type is meant by +a Perl value. + +=over 4 + +=item hash references + +Perl hash references become JSON objects. As there is no inherent +ordering in hash keys (or JSON objects), they will usually be encoded +in a pseudo-random order. JSON::PP can optionally sort the hash keys +(determined by the I flag and/or I property), so +the same data structure will serialise to the same JSON text (given +same settings and version of JSON::PP), but this incurs a runtime +overhead and is only rarely useful, e.g. when you want to compare some +JSON text against another for equality. + +=item array references + +Perl array references become JSON arrays. + +=item other references + +Other unblessed references are generally not allowed and will cause an +exception to be thrown, except for references to the integers C<0> and +C<1>, which get turned into C and C atoms in JSON. You can +also use C and C to improve +readability. + + to_json [\0, JSON::PP::true] # yields [false,true] + +=item JSON::PP::true, JSON::PP::false + +These special values become JSON true and JSON false values, +respectively. You can also use C<\1> and C<\0> directly if you want. + +=item JSON::PP::null + +This special value becomes JSON null. + +=item blessed objects + +Blessed objects are not directly representable in JSON, but C +allows various ways of handling objects. See L, +below, for details. + +=item simple scalars + +Simple Perl scalars (any scalar that is not a reference) are the most +difficult objects to encode: JSON::PP will encode undefined scalars as +JSON C values, scalars that have last been used in a string context +before encoding as JSON strings, and anything else as number value: + + # dump as number + encode_json [2] # yields [2] + encode_json [-3.0e17] # yields [-3e+17] + my $value = 5; encode_json [$value] # yields [5] + + # used as string, so dump as string + print $value; + encode_json [$value] # yields ["5"] + + # undef becomes null + encode_json [undef] # yields [null] + +You can force the type to be a JSON string by stringifying it: + + my $x = 3.1; # some variable containing a number + "$x"; # stringified + $x .= ""; # another, more awkward way to stringify + print $x; # perl does it for you, too, quite often + # (but for older perls) + +You can force the type to be a JSON number by numifying it: + + my $x = "3"; # some variable containing a string + $x += 0; # numify it, ensuring it will be dumped as a number + $x *= 1; # same thing, the choice is yours. + +You can not currently force the type in other, less obscure, ways. + +Since version 2.91_01, JSON::PP uses a different number detection logic +that converts a scalar that is possible to turn into a number safely. +The new logic is slightly faster, and tends to help people who use older +perl or who want to encode complicated data structure. However, this may +results in a different JSON text from the one JSON::XS encodes (and +thus may break tests that compare entire JSON texts). If you do +need the previous behavior for compatibility or for finer control, +set PERL_JSON_PP_USE_B environmental variable to true before you +C JSON::PP (or JSON.pm). + +Note that numerical precision has the same meaning as under Perl (so +binary to decimal conversion follows the same rules as in Perl, which +can differ to other languages). Also, your perl interpreter might expose +extensions to the floating point numbers of your platform, such as +infinities or NaN's - these cannot be represented in JSON, and it is an +error to pass those in. + +JSON::PP (and JSON::XS) trusts what you pass to C method +(or C function) is a clean, validated data structure with +values that can be represented as valid JSON values only, because it's +not from an external data source (as opposed to JSON texts you pass to +C or C, which JSON::PP considers tainted and +doesn't trust). As JSON::PP doesn't know exactly what you and consumers +of your JSON texts want the unexpected values to be (you may want to +convert them into null, or to stringify them with or without +normalisation (string representation of infinities/NaN may vary +depending on platforms), or to croak without conversion), you're advised +to do what you and your consumers need before you encode, and also not +to numify values that may start with values that look like a number +(including infinities/NaN), without validating. + +=back + +=head2 OBJECT SERIALISATION + +As JSON cannot directly represent Perl objects, you have to choose between +a pure JSON representation (without the ability to deserialise the object +automatically again), and a nonstandard extension to the JSON syntax, +tagged values. + +=head3 SERIALISATION + +What happens when C encounters a Perl object depends on the +C, C, C and C +settings, which are used in this order: + +=over 4 + +=item 1. C is enabled and the object has a C method. + +In this case, C creates a tagged JSON value, using a nonstandard +extension to the JSON syntax. + +This works by invoking the C method on the object, with the first +argument being the object to serialise, and the second argument being the +constant string C to distinguish it from other serialisers. + +The C method can return any number of values (i.e. zero or +more). These values and the paclkage/classname of the object will then be +encoded as a tagged JSON value in the following format: + + ("classname")[FREEZE return values...] + +e.g.: + + ("URI")["http://www.google.com/"] + ("MyDate")[2013,10,29] + ("ImageData::JPEG")["Z3...VlCg=="] + +For example, the hypothetical C C method might use the +objects C and C members to encode the object: + + sub My::Object::FREEZE { + my ($self, $serialiser) = @_; + + ($self->{type}, $self->{id}) + } + +=item 2. C is enabled and the object has a C method. + +In this case, the C method of the object is invoked in scalar +context. It must return a single scalar that can be directly encoded into +JSON. This scalar replaces the object in the JSON text. + +For example, the following C method will convert all L +objects to JSON strings when serialised. The fact that these values +originally were L objects is lost. + + sub URI::TO_JSON { + my ($uri) = @_; + $uri->as_string + } + +=item 3. C is enabled and the object is a C or C. + +The object will be serialised as a JSON number value. + +=item 4. C is enabled. + +The object will be serialised as a JSON null value. + +=item 5. none of the above + +If none of the settings are enabled or the respective methods are missing, +C throws an exception. + +=back + +=head3 DESERIALISATION + +For deserialisation there are only two cases to consider: either +nonstandard tagging was used, in which case C decides, +or objects cannot be automatically be deserialised, in which +case you can use postprocessing or the C or +C callbacks to get some real objects our of +your JSON. + +This section only considers the tagged value case: a tagged JSON object +is encountered during decoding and C is disabled, a parse +error will result (as if tagged values were not part of the grammar). + +If C is enabled, C will look up the C method +of the package/classname used during serialisation (it will not attempt +to load the package as a Perl module). If there is no such method, the +decoding will fail with an error. + +Otherwise, the C method is invoked with the classname as first +argument, the constant string C as second argument, and all the +values from the JSON array (the values originally returned by the +C method) as remaining arguments. + +The method must then return the object. While technically you can return +any Perl scalar, you might have to enable the C setting to +make that work in all cases, so better return an actual blessed reference. + +As an example, let's implement a C function that regenerates the +C from the C example earlier: + + sub My::Object::THAW { + my ($class, $serialiser, $type, $id) = @_; + + $class->new (type => $type, id => $id) + } + + +=head1 ENCODING/CODESET FLAG NOTES + +This section is taken from JSON::XS. + +The interested reader might have seen a number of flags that signify +encodings or codesets - C, C and C. There seems to be +some confusion on what these do, so here is a short comparison: + +C controls whether the JSON text created by C (and expected +by C) is UTF-8 encoded or not, while C and C only +control whether C escapes character values outside their respective +codeset range. Neither of these flags conflict with each other, although +some combinations make less sense than others. + +Care has been taken to make all flags symmetrical with respect to +C and C, that is, texts encoded with any combination of +these flag values will be correctly decoded when the same flags are used +- in general, if you use different flag settings while encoding vs. when +decoding you likely have a bug somewhere. + +Below comes a verbose discussion of these flags. Note that a "codeset" is +simply an abstract set of character-codepoint pairs, while an encoding +takes those codepoint numbers and I them, in our case into +octets. Unicode is (among other things) a codeset, UTF-8 is an encoding, +and ISO-8859-1 (= latin 1) and ASCII are both codesets I encodings at +the same time, which can be confusing. + +=over 4 + +=item C flag disabled + +When C is disabled (the default), then C/C generate +and expect Unicode strings, that is, characters with high ordinal Unicode +values (> 255) will be encoded as such characters, and likewise such +characters are decoded as-is, no changes to them will be done, except +"(re-)interpreting" them as Unicode codepoints or Unicode characters, +respectively (to Perl, these are the same thing in strings unless you do +funny/weird/dumb stuff). + +This is useful when you want to do the encoding yourself (e.g. when you +want to have UTF-16 encoded JSON texts) or when some other layer does +the encoding for you (for example, when printing to a terminal using a +filehandle that transparently encodes to UTF-8 you certainly do NOT want +to UTF-8 encode your data first and have Perl encode it another time). + +=item C flag enabled + +If the C-flag is enabled, C/C will encode all +characters using the corresponding UTF-8 multi-byte sequence, and will +expect your input strings to be encoded as UTF-8, that is, no "character" +of the input string must have any value > 255, as UTF-8 does not allow +that. + +The C flag therefore switches between two modes: disabled means you +will get a Unicode string in Perl, enabled means you get an UTF-8 encoded +octet/binary string in Perl. + +=item C or C flags enabled + +With C (or C) enabled, C will escape characters +with ordinal values > 255 (> 127 with C) and encode the remaining +characters as specified by the C flag. + +If C is disabled, then the result is also correctly encoded in those +character sets (as both are proper subsets of Unicode, meaning that a +Unicode string with all character values < 256 is the same thing as a +ISO-8859-1 string, and a Unicode string with all character values < 128 is +the same thing as an ASCII string in Perl). + +If C is enabled, you still get a correct UTF-8-encoded string, +regardless of these flags, just some more characters will be escaped using +C<\uXXXX> then before. + +Note that ISO-8859-1-I strings are not compatible with UTF-8 +encoding, while ASCII-encoded strings are. That is because the ISO-8859-1 +encoding is NOT a subset of UTF-8 (despite the ISO-8859-1 I being +a subset of Unicode), while ASCII is. + +Surprisingly, C will ignore these flags and so treat all input +values as governed by the C flag. If it is disabled, this allows you +to decode ISO-8859-1- and ASCII-encoded strings, as both strict subsets of +Unicode. If it is enabled, you can correctly decode UTF-8 encoded strings. + +So neither C nor C are incompatible with the C flag - +they only govern when the JSON output engine escapes a character or not. + +The main use for C is to relatively efficiently store binary data +as JSON, at the expense of breaking compatibility with most JSON decoders. + +The main use for C is to force the output to not contain characters +with values > 127, which means you can interpret the resulting string +as UTF-8, ISO-8859-1, ASCII, KOI8-R or most about any character set and +8-bit-encoding, and still get the same data structure back. This is useful +when your channel for JSON transfer is not 8-bit clean or the encoding +might be mangled in between (e.g. in mail), and works because ASCII is a +proper subset of most 8-bit and multibyte encodings in use in the world. + +=back + +=head1 BUGS + +Please report bugs on a specific behavior of this module to RT or GitHub +issues (preferred): + +L + +L + +As for new features and requests to change common behaviors, please +ask the author of JSON::XS (Marc Lehmann, Eschmorp[at]schmorp.deE) +first, by email (important!), to keep compatibility among JSON.pm backends. + +Generally speaking, if you need something special for you, you are advised +to create a new module, maybe based on L, which is smaller and +written in a much cleaner way than this module. + +=head1 SEE ALSO + +The F command line utility for quick experiments. + +L, L, and L for faster alternatives. +L and L for easy migration. + +L and L for older perl users. + +RFC4627 (L) + +RFC7159 (L) + +RFC8259 (L) + +=head1 AUTHOR + +Makamaka Hannyaharamitu, Emakamaka[at]cpan.orgE + +=head1 CURRENT MAINTAINER + +Kenichi Ishigaki, Eishigaki[at]cpan.orgE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2007-2016 by Makamaka Hannyaharamitu + +Most of the documentation is taken from JSON::XS by Marc Lehmann + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/thirdparty/rr-full/JSON/PP/Boolean.pm b/thirdparty/rr-full/JSON/PP/Boolean.pm new file mode 100644 index 0000000000..5d5b17c236 --- /dev/null +++ b/thirdparty/rr-full/JSON/PP/Boolean.pm @@ -0,0 +1,42 @@ +package JSON::PP::Boolean; + +use strict; +require overload; +local $^W; +overload::import('overload', + "0+" => sub { ${$_[0]} }, + "++" => sub { $_[0] = ${$_[0]} + 1 }, + "--" => sub { $_[0] = ${$_[0]} - 1 }, + fallback => 1, +); + +$JSON::PP::Boolean::VERSION = '4.05'; + +1; + +__END__ + +=head1 NAME + +JSON::PP::Boolean - dummy module providing JSON::PP::Boolean + +=head1 SYNOPSIS + + # do not "use" yourself + +=head1 DESCRIPTION + +This module exists only to provide overload resolution for Storable and similar modules. See +L for more info about this class. + +=head1 AUTHOR + +This idea is from L written by Marc Lehmann + +=head1 LICENSE + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + diff --git a/thirdparty/rr-full/license_p2x.txt b/thirdparty/rr-full/license_p2x.txt new file mode 100644 index 0000000000..e05bab8f2b --- /dev/null +++ b/thirdparty/rr-full/license_p2x.txt @@ -0,0 +1,34 @@ +LICENSE AGREEMENT +You should carefully read the following terms and conditions before using this software. Unless you have a different license agreement signed by IndigoSTAR Software, your use of this software indicates your acceptance of this license agreement and warranty. + +Registered Version + +Each registered copy of Perl2Exe may be used at a single workstation to create an unlimited number of exe files, subject to the following conditions: + +* A separate registered copy of Perl2Exe must be obtained for each workstation on which Perl2Exe will be used even if such use is only temporary. This is not a "concurrent use" license. + +* Exe files created by Perl2Exe are shipped with Run-time portions of Perl2Exe. No registered user, nor anyone else, may alter or modify the generated Exe files. You cannot give anyone else permission to modify the Exe files. + +* Exe files generated by the registered version of Perl2exe may be freely distributed. + +All rights not expressly granted in this license agreement are reserved entirely to IndigoSTAR Software + +Governing Law + +This agreement shall be governed by the laws of the Province of Ontario, Canada. + +Limited Warranty + +IndigoSTAR Software represents and warrants that the software and accompanying files will operate and function as documented, and that IndigoSTAR has full and sufficient right, title and authority to assign or grant the rights and/or licenses granted under this License Agreement. IndigoSTAR further warrants that neither the Software nor accompanying files infringe any intellectual property rights or similar rights of any 3rd party and agrees to indemnify you for any loss or damage related to a claim of infringement. + +Except for these limited warranties, this software and the accompanying files are sold "as is" and without warranties as to performance of merchantability or any other warranties whether expressed or implied. Because of the various hardware and software environments into which Perl2Exe may be put, NO WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE IS OFFERED. Good data processing procedure dictates that any program be thoroughly tested with non-critical data before relying on it. The user must assume the entire risk of using the program. Except for claims based on breach of the limited warranties or the indemnity provided above, the liability of either party for claims arising under this Agreement will be limited exclusively to the amount of fees paid under this agreement. + +Shareware Version + +You are hereby licensed to use the shareware evaluation version of Perl2Exe for evaluation purposes without charge for a period of 30 days. This is not free software. If you use this software after the 30 day evaluation period a registration fee is required. Under no circumstances are you licensed to distribute Exe files created by the shareware evaluation version of Perl2Exe. Unregistered use of Perl2Exe after the 30 day evaluation period is in violation of copyright laws. + +Distribution of Perl2Exe + +You are hereby licensed to make as many copies of the shareware evaluation version of this software and documentation as you wish; give exact copies of the original shareware version to anyone; and distribute the shareware version of the software and documentation in its unmodified form via electronic means. There is no charge for any of the above. + +You are specifically prohibited from charging, or requesting donations, for any such copies, however made; and from distributing the software and/or documentation with other products (commercial or otherwise) without prior written permission, with one exception: Disk Vendors approved by the Association of Shareware Professionals are permitted to redistribute Perl2Exe subject to the conditions in this license, without specific written permission. \ No newline at end of file diff --git a/thirdparty/rr-full/plugins/samparse.pl b/thirdparty/rr-full/plugins/samparse.pl index 8046708b3b..18e7955c69 100644 --- a/thirdparty/rr-full/plugins/samparse.pl +++ b/thirdparty/rr-full/plugins/samparse.pl @@ -23,6 +23,8 @@ #----------------------------------------------------------- package samparse; use strict; +use Encode::Unicode; +use JSON::PP; my %config = (hive => "SAM", hivemask => 2, @@ -130,6 +132,23 @@ sub pluginmain { my $f = $f_value->get_data(); my %f_val = parseF($f); + eval { + my $reset_data_value = $u->get_value("ResetData"); + my $reset_data = $reset_data_value->get_data(); + my $reset_data_hash = decode_json($reset_data); + my $reset_data_question_1 = $reset_data_hash->{'questions'}[0]; + my $reset_data_question_2 = $reset_data_hash->{'questions'}[1]; + my $reset_data_question_3 = $reset_data_hash->{'questions'}[2]; + my $question_1 = $reset_data_question_1->{'question'}; + ::rptMsg("Security Questions:"); + ::rptMsg(" Question 1 : ".$question_1); + ::rptMsg(" Answer 1 : ".$reset_data_question_1->{'answer'}); + ::rptMsg(" Question 2 : ".$reset_data_question_2->{'question'}); + ::rptMsg(" Answer 2 : ".$reset_data_question_2->{'answer'}); + ::rptMsg(" Question 3 : ".$reset_data_question_3->{'question'}); + ::rptMsg(" Answer 3 : ".$reset_data_question_3->{'answer'}); + }; + my $lastlogin; my $pwdreset; my $pwdfail; @@ -364,7 +383,8 @@ sub _translateSID { #--------------------------------------------------------------------- sub _uniToAscii { my $str = $_[0]; - $str =~ s/\x00//g; + Encode::from_to($str,'UTF-16LE','utf8'); + $str = Encode::decode_utf8($str); return $str; } diff --git a/thirdparty/rr-full/plugins/shellbags.pl b/thirdparty/rr-full/plugins/shellbags.pl index f4400cb4f0..b0e71ec299 100644 --- a/thirdparty/rr-full/plugins/shellbags.pl +++ b/thirdparty/rr-full/plugins/shellbags.pl @@ -779,7 +779,7 @@ sub parseFolderEntry { $tag = 0; } else { - $str .= $s; + $str .= $s; $cnt++; } } @@ -799,7 +799,7 @@ sub parseFolderEntry { $tag = 0; } else { - $str .= $s; + $str .= $s; $cnt++; } } @@ -858,13 +858,12 @@ sub parseFolderEntry { my $str = substr($data,$ofs,length($data) - 30); my $longname = (split(/\00\00/,$str,2))[0]; - $longname =~ s/\00//g; if ($longname ne "") { - $item{name} = $longname; + $item{name} = Utf16ToUtf8($longname); } else { - $item{name} = $shortname; + $item{name} = UTF16ToUtf8($shortname); } } return %item; @@ -957,7 +956,7 @@ sub parseFolderEntry2 { $item{name} = (split(/\00\00/,$str,2))[0]; $item{name} =~ s/\13\20/\2D\00/; - $item{name} =~ s/\00//g; + $item{name} = Utf16ToUtf8($item{name}); return %item; } @@ -1024,7 +1023,7 @@ sub shellItem0x52 { $tag = 0; } else { - $item{name} .= $d; + $item{name} .= $d; $cnt += 2; } } @@ -1119,4 +1118,15 @@ sub getNum48 { } } +#--------------------------------------------------------------------- +# Utf16ToUtf8() +#--------------------------------------------------------------------- +sub Utf16ToUtf8 { + my $str = $_[0]; + Encode::from_to($str,'UTF-16LE','utf8'); + $str = Encode::decode_utf8($str); + return $str; +} + + 1; \ No newline at end of file diff --git a/thirdparty/rr-full/plugins/shellbags_test.pl b/thirdparty/rr-full/plugins/shellbags_test.pl index 7ff3a5a4d5..3b068ea3ac 100644 --- a/thirdparty/rr-full/plugins/shellbags_test.pl +++ b/thirdparty/rr-full/plugins/shellbags_test.pl @@ -100,7 +100,7 @@ sub traverse { my $type = unpack("C",substr($values{$v},2,1)); my $size = unpack("v",substr($values{$v},0,2)); # probe($values{$v}); - + # Need to first check to see if the parent of the item was a zip folder # and if the 'zipsubfolder' value is set to 1 if (exists ${$parent}{zipsubfolder} && ${$parent}{zipsubfolder} == 1) { @@ -411,12 +411,13 @@ sub parseFolderItem { $longname =~ s/\x00//g; if ($longname ne "") { - $item{name} = $longname; + $item{name} = Utf16ToUtf8($longname); } else { - $item{name} = $shortname; + $item{name} = Utf16ToUtf8($shortname); } return %item; } + 1; diff --git a/thirdparty/rr-full/plugins/shellbags_xp.pl b/thirdparty/rr-full/plugins/shellbags_xp.pl index ce90cc3e7f..25082ea89b 100644 --- a/thirdparty/rr-full/plugins/shellbags_xp.pl +++ b/thirdparty/rr-full/plugins/shellbags_xp.pl @@ -776,13 +776,13 @@ sub parseFolderEntry { $str = substr($data,$ofs,length($data) - 30); my $longname = (split(/\x00\x00/,$str,2))[0]; - $longname =~ s/\x00//g; - + $longname = $longname.chr 0x00; + if ($longname ne "") { - $item{name} = $longname; + $item{name} = Utf16ToUtf8($longname); } else { - $item{name} = $shortname; + $item{name} = Utf16ToUtf8($shortname); } return %item; } @@ -871,7 +871,7 @@ sub parseFolderEntry2 { $item{name} = (split(/\x00\x00/,$str,2))[0]; $item{name} =~ s/\x13\x20/\x2D\x00/; - $item{name} =~ s/\x00//g; + $item{name} = Utf16ToUtf8($item{name}); return %item; } @@ -931,4 +931,14 @@ sub printData { return @display; } +#--------------------------------------------------------------------- +# Utf16ToUtf8() +#--------------------------------------------------------------------- +sub Utf16ToUtf8 { + my $str = $_[0]; + Encode::from_to($str,'UTF-16LE','utf8'); + my $str2 = Encode::decode_utf8($str); + return $str; +} + 1; diff --git a/thirdparty/rr-full/shellitems.pl b/thirdparty/rr-full/shellitems.pl index 34b9174c9d..93b71e7c20 100644 --- a/thirdparty/rr-full/shellitems.pl +++ b/thirdparty/rr-full/shellitems.pl @@ -27,6 +27,7 @@ # Author: H. Carvey, keydet89@yahoo.com #----------------------------------------------------------- use Time::Local; +use Encode::Unicode; my %guids = ("{bb64f8a7-bee7-4e1a-ab8d-7d8273f7fdb6}" => "Action Center", "{7a979262-40ce-46ff-aeee-7884ac3b6136}" => "Add Hardware", @@ -537,6 +538,7 @@ sub parseControlPanelEntry { #----------------------------------------------------------- sub parseFolderEntry { my $data = shift; + my $data_length = length($data); my %item = (); $item{type} = unpack("C",substr($data,2,1)); @@ -594,6 +596,9 @@ sub parseFolderEntry { } else { $cnt++; + if (($ofs + $cnt) > $data_length) { + return %item; + } } } $item{extver} = unpack("v",substr($data,$ofs + $cnt - 4,2)); @@ -630,10 +635,10 @@ sub parseFolderEntry { $longname =~ s/\x00//g; if ($longname ne "") { - $item{name} = $longname; + $item{name} = Utf16ToUtf8($longname); } else { - $item{name} = $shortname; + $item{name} = Utf16ToUtf8($shortname); } return %item; } @@ -712,7 +717,7 @@ sub parseFolderEntry2 { $item{name} = (split(/\x00\x00/,$str,2))[0]; $item{name} =~ s/\x13\x20/\x2D\x00/; - $item{name} =~ s/\x00//g; + $item{name} = Utf16ToUtf8($item{name}); return %item; } @@ -833,4 +838,14 @@ sub getNum48 { } } +#--------------------------------------------------------------------- +# Utf16ToUtf8() +#--------------------------------------------------------------------- +sub Utf16ToUtf8 { + my $str = $_[0]; + Encode::from_to($str,'UTF-16LE','utf8'); + $str = Encode::decode_utf8($str); + return $str; +} + 1; diff --git a/thirdparty/rr/license.txt b/thirdparty/rr/license.txt new file mode 100644 index 0000000000..1660cbd2ea --- /dev/null +++ b/thirdparty/rr/license.txt @@ -0,0 +1,22 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +This project is licensed under terms of the MIT License - +https://opensource.org/licenses/MIT + +See also: +https://en.wikipedia.org/wiki/MIT_License + +Questions, comments, etc., can be sent to keydet89 at yahoo dot com. \ No newline at end of file diff --git a/thirdparty/rr/license_p2x.txt b/thirdparty/rr/license_p2x.txt new file mode 100644 index 0000000000..e05bab8f2b --- /dev/null +++ b/thirdparty/rr/license_p2x.txt @@ -0,0 +1,34 @@ +LICENSE AGREEMENT +You should carefully read the following terms and conditions before using this software. Unless you have a different license agreement signed by IndigoSTAR Software, your use of this software indicates your acceptance of this license agreement and warranty. + +Registered Version + +Each registered copy of Perl2Exe may be used at a single workstation to create an unlimited number of exe files, subject to the following conditions: + +* A separate registered copy of Perl2Exe must be obtained for each workstation on which Perl2Exe will be used even if such use is only temporary. This is not a "concurrent use" license. + +* Exe files created by Perl2Exe are shipped with Run-time portions of Perl2Exe. No registered user, nor anyone else, may alter or modify the generated Exe files. You cannot give anyone else permission to modify the Exe files. + +* Exe files generated by the registered version of Perl2exe may be freely distributed. + +All rights not expressly granted in this license agreement are reserved entirely to IndigoSTAR Software + +Governing Law + +This agreement shall be governed by the laws of the Province of Ontario, Canada. + +Limited Warranty + +IndigoSTAR Software represents and warrants that the software and accompanying files will operate and function as documented, and that IndigoSTAR has full and sufficient right, title and authority to assign or grant the rights and/or licenses granted under this License Agreement. IndigoSTAR further warrants that neither the Software nor accompanying files infringe any intellectual property rights or similar rights of any 3rd party and agrees to indemnify you for any loss or damage related to a claim of infringement. + +Except for these limited warranties, this software and the accompanying files are sold "as is" and without warranties as to performance of merchantability or any other warranties whether expressed or implied. Because of the various hardware and software environments into which Perl2Exe may be put, NO WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE IS OFFERED. Good data processing procedure dictates that any program be thoroughly tested with non-critical data before relying on it. The user must assume the entire risk of using the program. Except for claims based on breach of the limited warranties or the indemnity provided above, the liability of either party for claims arising under this Agreement will be limited exclusively to the amount of fees paid under this agreement. + +Shareware Version + +You are hereby licensed to use the shareware evaluation version of Perl2Exe for evaluation purposes without charge for a period of 30 days. This is not free software. If you use this software after the 30 day evaluation period a registration fee is required. Under no circumstances are you licensed to distribute Exe files created by the shareware evaluation version of Perl2Exe. Unregistered use of Perl2Exe after the 30 day evaluation period is in violation of copyright laws. + +Distribution of Perl2Exe + +You are hereby licensed to make as many copies of the shareware evaluation version of this software and documentation as you wish; give exact copies of the original shareware version to anyone; and distribute the shareware version of the software and documentation in its unmodified form via electronic means. There is no charge for any of the above. + +You are specifically prohibited from charging, or requesting donations, for any such copies, however made; and from distributing the software and/or documentation with other products (commercial or otherwise) without prior written permission, with one exception: Disk Vendors approved by the Association of Shareware Professionals are permitted to redistribute Perl2Exe subject to the conditions in this license, without specific written permission. \ No newline at end of file diff --git a/thunderbirdparser/build.xml b/thunderbirdparser/build.xml index caabb5f0de..0690ae52ec 100644 --- a/thunderbirdparser/build.xml +++ b/thunderbirdparser/build.xml @@ -19,10 +19,7 @@ - - - - + diff --git a/thunderbirdparser/ivy.xml b/thunderbirdparser/ivy.xml index d2833c64c1..0efca69cb5 100644 --- a/thunderbirdparser/ivy.xml +++ b/thunderbirdparser/ivy.xml @@ -7,12 +7,9 @@ - - - - - - - + + + + diff --git a/thunderbirdparser/ivysettings.xml b/thunderbirdparser/ivysettings.xml index 8209151a1c..a776ab49c3 100644 --- a/thunderbirdparser/ivysettings.xml +++ b/thunderbirdparser/ivysettings.xml @@ -6,4 +6,5 @@ + diff --git a/thunderbirdparser/nbproject/project.properties b/thunderbirdparser/nbproject/project.properties index cfbcc365d4..9f01f8ac6d 100644 --- a/thunderbirdparser/nbproject/project.properties +++ b/thunderbirdparser/nbproject/project.properties @@ -1,20 +1,11 @@ -file.reference.apache-mime4j-core-0.8.0-SNAPSHOT.jar=release/modules/ext/apache-mime4j-core-0.8.0-SNAPSHOT.jar -file.reference.apache-mime4j-dom-0.8.0-SNAPSHOT.jar=release/modules/ext/apache-mime4j-dom-0.8.0-SNAPSHOT.jar -file.reference.apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT.jar=release/modules/ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT.jar -file.reference.commons-lang3-3.8.1.jar=release/modules/ext/commons-lang3-3.8.1.jar -file.reference.apache-mime4j-core-0.8.0.jar=release/modules/ext/apache-mime4j-core-0.8.0-SNAPSHOT.jar -file.reference.apache-mime4j-dom-0.8.0.jar=release/modules/ext/apache-mime4j-dom-0.8.0-SNAPSHOT.jar -file.reference.apache-mime4j-mbox-iterator-0.8.0.jar=release/modules/ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT.jar -file.reference.commons-validator-1.6.jar=release/modules/ext/commons-validator-1.6.jar -file.reference.guava-19.0.jar=release/modules/ext/guava-19.0.jar -file.reference.java-libpst-1.0-SNAPSHOT.jar=release/modules/ext/java-libpst-1.0-SNAPSHOT.jar -file.reference.ez-vcard-0.10.5.jar=release/modules/ext/ez-vcard-0.10.5.jar +file.reference.apache-mime4j-mbox-iterator-0.8.4.jar=release/modules/ext/apache-mime4j-mbox-iterator-0.8.4.jar +file.reference.commons-lang3-3.12.0.jar=release/modules/ext/commons-lang3-3.12.0.jar +file.reference.ez-vcard-0.11.3.jar=release/modules/ext/ez-vcard-0.11.3.jar +file.reference.java-libpst-0.9.5-SNAPSHOT.jar=release/modules/ext/java-libpst-0.9.5-SNAPSHOT.jar file.reference.vinnie-2.0.2.jar=release/modules/ext/vinnie-2.0.2.jar javac.source=11 javac.compilerargs=-Xlint -Xlint:-serial -javadoc.reference.guava-19.0.jar=release/modules/ext/guava-19.0-javadoc.jar license.file=../LICENSE-2.0.txt nbm.homepage=http://www.sleuthkit.org/autopsy/ nbm.needs.restart=true -source.reference.guava-19.0.jar=release/modules/ext/guava-19.0-sources.jar spec.version.base=4.0 diff --git a/thunderbirdparser/nbproject/project.xml b/thunderbirdparser/nbproject/project.xml index 94e45a2cc8..6c138cc4df 100644 --- a/thunderbirdparser/nbproject/project.xml +++ b/thunderbirdparser/nbproject/project.xml @@ -54,7 +54,16 @@ 10 - 10.22 + 10.24 + + + + org.sleuthkit.autopsy.corelibs + + + + 3 + 1.4 @@ -69,41 +78,25 @@ - ext/commons-lang3-3.8.1.jar - release/modules/ext/commons-lang3-3.8.1.jar + ext/apache-mime4j-mbox-iterator-0.8.4.jar + release/modules/ext/apache-mime4j-mbox-iterator-0.8.4.jar - ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT.jar - release/modules/ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT.jar + ext/commons-lang3-3.12.0.jar + release/modules/ext/commons-lang3-3.12.0.jar - ext/ez-vcard-0.10.5.jar - release/modules/ext/ez-vcard-0.10.5.jar + ext/ez-vcard-0.11.3.jar + release/modules/ext/ez-vcard-0.11.3.jar - ext/apache-mime4j-core-0.8.0-SNAPSHOT.jar - release/modules/ext/apache-mime4j-core-0.8.0-SNAPSHOT.jar - - - ext/java-libpst-1.0-SNAPSHOT.jar - release/modules/ext/java-libpst-1.0-SNAPSHOT.jar - - - ext/guava-19.0.jar - release/modules/ext/guava-19.0.jar - - - ext/apache-mime4j-dom-0.8.0-SNAPSHOT.jar - release/modules/ext/apache-mime4j-dom-0.8.0-SNAPSHOT.jar + ext/java-libpst-0.9.5-SNAPSHOT.jar + release/modules/ext/java-libpst-0.9.5-SNAPSHOT.jar ext/vinnie-2.0.2.jar release/modules/ext/vinnie-2.0.2.jar - - ext/commons-validator-1.6.jar - release/modules/ext/commons-validator-1.6.jar - diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/Bundle_ja.properties b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/Bundle_ja.properties index 2453e9652b..9f786a7b38 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/Bundle_ja.properties +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/Bundle_ja.properties @@ -1,21 +1,26 @@ -#Tue Aug 18 18:09:21 UTC 2020 +#Mon Jul 12 13:22:00 UTC 2021 MboxParser.handleAttch.errMsg.failedToCreateOnDisk=MBOX\u306e\u6dfb\u4ed8\u30d5\u30a1\u30a4\u30eb\u3092\u30c7\u30a3\u30b9\u30af\: {0}\u3078\u62bd\u51fa\u3059\u308b\u306e\u306b\u5931\u6557\u3057\u307e\u3057\u305f MboxParser.handleAttch.failedWriteToDisk=\u6dfb\u4ed8\u30d5\u30a1\u30a4\u30eb\u3092\u30c7\u30a3\u30b9\u30af\: {0}\u3078\u62bd\u51fa\u3059\u308b\u306e\u306b\u5931\u6557\u3057\u307e\u3057\u305f MboxParser.parse.errMsg.couldntFindCharset=\u9069\u5207\u306a\u6587\u5b57\u30bb\u30c3\u30c8\u30a8\u30f3\u30b3\u30fc\u30c0\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002 MboxParser.parse.errMsg.failedToParseNMsgs={0}\u500b\u306eEmail\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u62bd\u51fa\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 MboxParser.parse.errMsg.failedToReadFile=\u30c7\u30a3\u30b9\u30af\u304b\u3089mbox\u30d5\u30a1\u30a4\u30eb\u3092\u8aad\u307f\u53d6\u308c\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +MimeJ4MessageParser.handleAttch.noOpenCase.errMsg=\u30aa\u30fc\u30d7\u30f3\u30b1\u30fc\u30b9\u53d6\u5f97\u4e2d\u306e\u4f8b\u5916\u3002 OpenIDE-Module-Display-Category=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb OpenIDE-Module-Long-Description=\ Email\u30d1\u30fc\u30b5\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\u3002\n\n\u3053\u306e\u30e2\u30b8\u30e5\u30fc\u30eb\u306fMBOX\u3068PST e-mail\u30d5\u30a1\u30a4\u30eb\u3092\u62bd\u51fa\u3057\u3001Blackboard\u306b\u8f09\u305b\u307e\u3059\u3002 \nThunderbird\u306eMBOX\u30d5\u30a1\u30a4\u30eb\u306e\u30d5\u30a9\u30eb\u30c0\u69cb\u9020\u3092\u628a\u63e1\u3057\u3066\u3044\u307e\u3059\u3002 OpenIDE-Module-Name=Email\u30d1\u30fc\u30b5 OpenIDE-Module-Short-Description=MOBX\u3068PST\u30d5\u30a1\u30a4\u30eb\u3092\u30d1\u30fc\u30b9\u3057\u307e\u3059 PstParser.extractAttch.errMsg.failedToExtractToDisk=PST\u306e\u6dfb\u4ed8\u30d5\u30a1\u30a4\u30eb\u3092\u30c7\u30a3\u30b9\u30af\: {0}\u3078\u62bd\u51fa\u3059\u308b\u306e\u306b\u5931\u6557\u3057\u307e\u3057\u305f +PstParser.noOpenCase.errMsg=\u30aa\u30fc\u30d7\u30f3\u30b1\u30fc\u30b9\u53d6\u5f97\u4e2d\u306e\u4f8b\u5916\u3002 PstParser.parse.errMsg.failedToParseNMsgs={0}\u500b\u306eEmail\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u62bd\u51fa\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 +ThunderbirdMboxFileIngestModule.addArtifact.indexError.message=\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u3067\u63a2\u3057\u305f\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u306e\u96fb\u5b50\u30e1\u30fc\u30eb\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u4f5c\u6210\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 ThunderbirdMboxFileIngestModule.encryptionFileLevel=\u30d5\u30a1\u30a4\u30eb\u30ec\u30d9\u30eb\u6697\u53f7\u5316 +ThunderbirdMboxFileIngestModule.errorMessage.outOfDiskSpace=\u30c7\u30a3\u30b9\u30af\u5bb9\u91cf\u304c\u4e0d\u8db3\u3057\u3066\u3044\u307e\u3059\u3002 '{0}'\uff08id \= {1}\uff09\u3092\u30b3\u30d4\u30fc\u3057\u3066\u89e3\u6790\u3067\u304d\u307e\u305b\u3093\u3002 ThunderbirdMboxFileIngestModule.getDesc.text=\u3053\u306e\u30e2\u30b8\u30e5\u30fc\u30eb\u306fmbox\u304a\u3088\u3073pst/ost\u30d5\u30a1\u30a4\u30eb\u3092\u691c\u51fa\u3001\u30d1\u30fc\u30b9\u3057\u3001blackboard\u306eEmail\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u306b\u7d50\u679c\u3092\u6295\u5165\u3057\u307e\u3059\u3002 ThunderbirdMboxFileIngestModule.handleAttch.addAttachmentsErrorMsg=\u30e1\u30fc\u30eb\u30e1\u30c3\u30bb\u30fc\u30b8\u306b\u6dfb\u4ed8\u30d5\u30a1\u30a4\u30eb\u3092\u8ffd\u52a0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 ThunderbirdMboxFileIngestModule.handleAttch.errMsg={0}\u306e\u51e6\u7406\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f ThunderbirdMboxFileIngestModule.handleAttch.errMsg.details={0}\u306e\u540d\u79f0\u3092\u6301\u3064\u6dfb\u4ed8\u30d5\u30a1\u30a4\u30eb\u3092\u30b1\u30fc\u30b9\u306b\u8ffd\u52a0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 ThunderbirdMboxFileIngestModule.moduleName=Email\u30d1\u30fc\u30b5 +ThunderbirdMboxFileIngestModule.noOpenCase.errMsg=\u30aa\u30fc\u30d7\u30f3\u30b1\u30fc\u30b9\u53d6\u5f97\u4e2d\u306e\u4f8b\u5916\u3002 ThunderbirdMboxFileIngestModule.notAvail=\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093 ThunderbirdMboxFileIngestModule.processMBox.errProcFile.msg={0}\u306e\u51e6\u7406\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f ThunderbirdMboxFileIngestModule.processMBox.errProcFile.msg2={0}\u306e\u51e6\u7406\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f @@ -24,3 +29,5 @@ ThunderbirdMboxFileIngestModule.processPst.errMsg.outOfDiskSpace=\u30c7\u30a3\u3 ThunderbirdMboxFileIngestModule.processPst.errProcFile.details=Outlook 2003\u304a\u3088\u3073\u305d\u308c\u4ee5\u964d\u306e\u30d0\u30fc\u30b8\u30e7\u30f3\u304b\u3089\u306e\u30d5\u30a1\u30a4\u30eb\u3057\u304b\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002 ThunderbirdMboxFileIngestModule.processPst.errProcFile.msg={0}\u306e\u51e6\u7406\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f ThunderbirdMboxFileIngestModule.processPst.errProcFile.msg2={0}\u306e\u51e6\u7406\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f +ThunderbirdMboxFileIngestModule.processPst.indexError.message=\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u3067\u6697\u53f7\u5316\u3055\u308c\u305f\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u306e\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u4f5c\u6210\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 +VcardParser.addContactArtifact.indexError=\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u306e\u9023\u7d61\u5148\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u306e\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u4f5c\u6210\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java index 09a6637e6e..5e70a5eae2 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java @@ -43,7 +43,7 @@ class EmailMessage { private String localPath = ""; private boolean hasAttachment = false; private long sentDate = 0L; - private List attachments = new ArrayList<>(); + private final List attachments = new ArrayList<>(); private long id = -1L; private String messageID = ""; private String inReplyToID = ""; @@ -410,4 +410,16 @@ class EmailMessage { } } + + static class AttachedEmailMessage extends Attachment { + private final EmailMessage emailMessage; + + AttachedEmailMessage(EmailMessage emailMessage) { + this.emailMessage = emailMessage; + } + + EmailMessage getEmailMessage() { + return emailMessage; + } + } } diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MboxParser.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MboxParser.java index eb4efc7451..a0628c593b 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MboxParser.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MboxParser.java @@ -35,13 +35,16 @@ import java.util.Iterator; import java.util.List; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.apache.james.mime4j.dom.Message; import org.apache.james.mime4j.mboxiterator.CharBufferWrapper; import org.apache.james.mime4j.mboxiterator.MboxIterator; import org.apache.tika.parser.txt.CharsetDetector; import org.apache.tika.parser.txt.CharsetMatch; import org.apache.commons.validator.routines.EmailValidator; +import org.apache.james.mime4j.mboxiterator.MboxIterator.Builder; import org.openide.util.NbBundle; +import org.sleuthkit.datamodel.AbstractFile; /** * An Iterator for parsing mbox files. Wraps an instance of MBoxEmailIterator. @@ -51,17 +54,32 @@ class MboxParser extends MimeJ4MessageParser implements Iterator { private static final Logger logger = Logger.getLogger(MboxParser.class.getName()); private Iterator emailIterator = null; + + private MboxIterator mboxIterable; private MboxParser(String localPath) { setLocalPath(localPath); } - static boolean isValidMimeTypeMbox(byte[] buffer) { + static boolean isValidMimeTypeMbox(byte[] buffer, AbstractFile abstractFile) { String mboxHeaderLine = new String(buffer); if (mboxHeaderLine.startsWith("From ")) { - String[] mboxLineValues = mboxHeaderLine.split(" "); - EmailValidator validator = EmailValidator.getInstance(true, true); - return validator.isValid(mboxLineValues[1]); + String mimeType = abstractFile.getMIMEType(); + + // if it is not present, attempt to use the FileTypeDetector to determine + if (mimeType == null || mimeType.isEmpty()) { + FileTypeDetector fileTypeDetector = null; + try { + fileTypeDetector = new FileTypeDetector(); + } catch (FileTypeDetector.FileTypeDetectorInitException ex) { + logger.log(Level.WARNING, String.format("Unable to create file type detector for determining MIME type for file %s with id of %d", abstractFile.getName(), abstractFile.getId())); + return false; + } + mimeType = fileTypeDetector.getMIMEType(abstractFile); + } + if (mimeType.equalsIgnoreCase("application/mbox")) { + return true; + } } return false; //NON-NLS } @@ -115,7 +133,12 @@ class MboxParser extends MimeJ4MessageParser implements Iterator { // That will usually be one of the first ones. for (CharsetEncoder encoder : encoders) { try { - Iterable mboxIterable = MboxIterator.fromFile(mboxFile).charset(encoder.charset()).build(); + mboxIterable = MboxIterator + .fromFile(mboxFile) + // use more permissive from line from mbox iterator 0.8.0, but handling CRLF/LF + .fromLine("^From .*\r?\n") + .charset(encoder.charset()) + .build(); if (mboxIterable != null) { emailIterator = new MBoxEmailIterator(mboxIterable.iterator(), encoder, fileID, wholeMsg); } @@ -140,6 +163,13 @@ class MboxParser extends MimeJ4MessageParser implements Iterator { public EmailMessage next() { return emailIterator != null ? emailIterator.next() : null; } + + @Override + public void close() throws IOException{ + if(mboxIterable != null) { + mboxIterable.close(); + } + } /** * Get a list of the possible encoders for the given mboxFile using Tika's diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MimeJ4MessageParser.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MimeJ4MessageParser.java index 828968d750..ba5b24842f 100755 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MimeJ4MessageParser.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MimeJ4MessageParser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,8 +29,8 @@ import java.util.logging.Level; import org.apache.james.mime4j.dom.Body; import org.apache.james.mime4j.dom.Entity; import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.dom.MessageWriter; import org.apache.james.mime4j.dom.Multipart; -import org.apache.james.mime4j.dom.SingleBody; import org.apache.james.mime4j.dom.TextBody; import org.apache.james.mime4j.dom.address.AddressList; import org.apache.james.mime4j.dom.address.Mailbox; @@ -38,6 +38,7 @@ import org.apache.james.mime4j.dom.address.MailboxList; import org.apache.james.mime4j.dom.field.ContentDispositionField; import org.apache.james.mime4j.dom.field.ContentTypeField; import org.apache.james.mime4j.message.DefaultMessageBuilder; +import org.apache.james.mime4j.message.DefaultMessageWriter; import org.apache.james.mime4j.stream.Field; import org.apache.james.mime4j.stream.MimeConfig; import org.openide.util.NbBundle; @@ -50,7 +51,7 @@ import org.sleuthkit.datamodel.TskData; /** * Super class for email parsers that can use the james.mime4J.Message objects. */ -class MimeJ4MessageParser { +class MimeJ4MessageParser implements AutoCloseable{ private static final Logger logger = Logger.getLogger(MimeJ4MessageParser.class.getName()); @@ -164,7 +165,11 @@ class MimeJ4MessageParser { if (msg.isMultipart()) { handleMultipart(email, (Multipart) msg.getBody(), sourceFileID); } else { - handleTextBody(email, (TextBody) msg.getBody(), msg.getMimeType(), msg.getHeader().getFields()); + if(msg.getBody() instanceof TextBody) { + handleTextBody(email, (TextBody) msg.getBody(), msg.getMimeType(), msg.getHeader().getFields()); + } else { + handleAttachment(email, msg, sourceFileID, 1); + } } return email; @@ -227,8 +232,8 @@ class MimeJ4MessageParser { handleAttachment(email, e, fileID, index); } else if ((e.getMimeType().equals(HTML_TYPE) && (email.getHtmlBody() == null || email.getHtmlBody().isEmpty())) || (e.getMimeType().equals(ContentTypeField.TYPE_TEXT_PLAIN) && (email.getTextBody() == null || email.getTextBody().isEmpty()))) { - handleTextBody(email, (TextBody) e.getBody(), e.getMimeType(), e.getHeader().getFields()); - } else { + handleTextBody(email, (TextBody) e.getBody(), e.getMimeType(), e.getHeader().getFields()); + } else { handleAttachment(email, e, fileID, index); } } @@ -288,7 +293,7 @@ class MimeJ4MessageParser { * @param e */ @NbBundle.Messages({"MimeJ4MessageParser.handleAttch.noOpenCase.errMsg=Exception while getting open case."}) - private static void handleAttachment(EmailMessage email, Entity e, long fileID, int index) { + private void handleAttachment(EmailMessage email, Entity e, long fileID, int index) { String outputDirPath; String relModuleOutputPath; try { @@ -317,25 +322,31 @@ class MimeJ4MessageParser { String outPath = outputDirPath + uniqueFilename; Body body = e.getBody(); - if (body instanceof SingleBody) { + if (body != null) { long fileLength; try (EncodedFileOutputStream fos = new EncodedFileOutputStream(new FileOutputStream(outPath), TskData.EncodingType.XOR1)) { - ((SingleBody) body).writeTo(fos); + + EmailMessage.Attachment attach; + MessageWriter msgWriter = new DefaultMessageWriter(); + + if(body instanceof Message) { + msgWriter.writeMessage((Message)body, fos); + attach = new EmailMessage.AttachedEmailMessage(extractEmail((Message)body, email.getLocalPath(), fileID)); + } else { + msgWriter.writeBody(body, fos); + attach = new EmailMessage.Attachment(); + } fileLength = fos.getBytesWritten(); + attach.setName(filename); + attach.setLocalPath(relModuleOutputPath + uniqueFilename); + attach.setSize(fileLength); + attach.setEncodingType(TskData.EncodingType.XOR1); + email.addAttachment(attach); + } catch (IOException ex) { logger.log(Level.WARNING, "Failed to create file output stream for: " + outPath, ex); //NON-NLS - return; } - - EmailMessage.Attachment attach = new EmailMessage.Attachment(); - attach.setName(filename); - attach.setLocalPath(relModuleOutputPath + uniqueFilename); - attach.setSize(fileLength); - attach.setEncodingType(TskData.EncodingType.XOR1); - email.addAttachment(attach); } - - } /** @@ -370,4 +381,9 @@ class MimeJ4MessageParser { private static String getAddresses(AddressList addressList) { return (addressList == null) ? "" : getAddresses(addressList.flatten()); } + + @Override + public void close() throws IOException{ + + } } diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/PstParser.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/PstParser.java index f747ea2d9f..358c67a8a5 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/PstParser.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/PstParser.java @@ -28,6 +28,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Iterator; @@ -51,7 +52,7 @@ import org.sleuthkit.datamodel.TskData; * * @author jwallace */ -class PstParser { +class PstParser implements AutoCloseable{ private static final Logger logger = Logger.getLogger(PstParser.class.getName()); /** @@ -106,6 +107,11 @@ class PstParser { logger.log(Level.INFO, "Found encrypted PST file."); //NON-NLS return ParseResult.ENCRYPT; } + if (ex.getMessage().toLowerCase().startsWith("unable to")) { + logger.log(Level.WARNING, ex.getMessage()); + logger.log(Level.WARNING, String.format("Error in parsing PST file %s, file may be empty or corrupt", file.getName())); + return ParseResult.ERROR; + } String msg = file.getName() + ": Failed to create internal java-libpst PST file to parse:\n" + ex.getMessage(); //NON-NLS logger.log(Level.WARNING, msg, ex); return ParseResult.ERROR; @@ -120,6 +126,16 @@ class PstParser { return ParseResult.OK; } + + @Override + public void close() throws IOException{ + if(pstFile != null) { + RandomAccessFile file = pstFile.getFileHandle(); + if(file != null) { + file.close(); + } + } + } /** * Creates an EmailMessage iterator for pstFile. These Email objects will be @@ -266,16 +282,30 @@ class PstParser { */ private EmailMessage extractEmailMessage(PSTMessage msg, String localPath, long fileID) { EmailMessage email = new EmailMessage(); - email.setRecipients(msg.getDisplayTo()); - email.setCc(msg.getDisplayCC()); - email.setBcc(msg.getDisplayBCC()); - email.setSender(getSender(msg.getSenderName(), msg.getSenderEmailAddress())); + String toAddress = msg.getDisplayTo(); + String ccAddress = msg.getDisplayCC(); + String bccAddress = msg.getDisplayBCC(); + String receivedByName = msg.getReceivedByName(); + String receivedBySMTPAddress = msg.getReceivedBySMTPAddress(); + + if (toAddress.contains(receivedByName)) { + toAddress = toAddress.replace(receivedByName, receivedBySMTPAddress); + } + if (ccAddress.contains(receivedByName)) { + ccAddress = ccAddress.replace(receivedByName, receivedBySMTPAddress); + } + if (bccAddress.contains(receivedByName)) { + bccAddress = bccAddress.replace(receivedByName, receivedBySMTPAddress); + } + email.setRecipients(toAddress); + email.setCc(ccAddress); + email.setBcc(bccAddress); + email.setSender(getSender(msg.getSenderName(), msg.getSentRepresentingSMTPAddress())); email.setSentDate(msg.getMessageDeliveryTime()); email.setTextBody(msg.getBody()); if (false == msg.getTransportMessageHeaders().isEmpty()) { email.setHeaders("\n-----HEADERS-----\n\n" + msg.getTransportMessageHeaders() + "\n\n---END HEADERS--\n\n"); } - email.setHtmlBody(msg.getBodyHTML()); String rtf = ""; try { diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java index 12f3342dac..13ca786c3b 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2020 Basis Technology Corp. + * Copyright 2012-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.thunderbirdparser; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -29,6 +30,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -48,6 +51,7 @@ import org.sleuthkit.autopsy.ingest.IngestModule.ProcessResult; import org.sleuthkit.autopsy.ingest.IngestMonitor; import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.ModuleContentEvent; +import org.sleuthkit.autopsy.thunderbirdparser.EmailMessage.AttachedEmailMessage; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.AccountFileInstance; @@ -58,6 +62,7 @@ import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.DerivedFile; import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.Relationship; +import org.sleuthkit.datamodel.Score; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskDataException; @@ -72,6 +77,7 @@ import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments.Fil * structure and metadata. */ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { + private static final Logger logger = Logger.getLogger(ThunderbirdMboxFileIngestModule.class.getName()); private final IngestServices services = IngestServices.getInstance(); private FileManager fileManager; @@ -79,6 +85,10 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { private Blackboard blackboard; private CommunicationArtifactsHelper communicationArtifactsHelper; + // A cache of custom attributes for the VcardParser unique to each ingest run, but consistent across threads. + private static ConcurrentMap customAttributeCache = new ConcurrentHashMap<>(); + private static Object customAttributeCacheLock = new Object(); + private static final int MBOX_SIZE_TO_SPLIT = 1048576000; private Case currentCase; @@ -89,9 +99,16 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } @Override - @Messages ({"ThunderbirdMboxFileIngestModule.noOpenCase.errMsg=Exception while getting open case."}) + @Messages({"ThunderbirdMboxFileIngestModule.noOpenCase.errMsg=Exception while getting open case."}) public void startUp(IngestJobContext context) throws IngestModuleException { this.context = context; + + synchronized(customAttributeCacheLock) { + if (!customAttributeCache.isEmpty()) { + customAttributeCache.clear(); + } + } + try { currentCase = Case.getCurrentCaseThrows(); fileManager = Case.getCurrentCaseThrows().getServices().getFileManager(); @@ -112,8 +129,8 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } //skip unalloc - if ((abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)) || - (abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) { + if ((abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)) + || (abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) { return ProcessResult.OK; } @@ -124,31 +141,31 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { // check its signature boolean isMbox = false; boolean isEMLFile = false; - + try { byte[] t = new byte[64]; if (abstractFile.getSize() > 64) { int byteRead = abstractFile.read(t, 0, 64); if (byteRead > 0) { - isMbox = MboxParser.isValidMimeTypeMbox(t); + isMbox = MboxParser.isValidMimeTypeMbox(t, abstractFile); isEMLFile = EMLParser.isEMLFile(abstractFile, t); } } } catch (TskException ex) { logger.log(Level.WARNING, null, ex); } - + boolean isPstFile = PstParser.isPstFile(abstractFile); boolean isVcardFile = VcardParser.isVcardFile(abstractFile); - + if (context.fileIngestIsCancelled()) { return ProcessResult.OK; } - - if (isMbox || isEMLFile || isPstFile || isVcardFile ) { + + if (isMbox || isEMLFile || isPstFile || isVcardFile) { try { communicationArtifactsHelper = new CommunicationArtifactsHelper(currentCase.getSleuthkitCase(), - EmailParserModuleFactory.getModuleName(), abstractFile, Account.Type.EMAIL); + EmailParserModuleFactory.getModuleName(), abstractFile, Account.Type.EMAIL, context.getJobId()); } catch (TskCoreException ex) { logger.log(Level.SEVERE, String.format("Failed to create CommunicationArtifactsHelper for file with object id = %d", abstractFile.getId()), ex); return ProcessResult.ERROR; @@ -158,7 +175,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { if (isMbox) { return processMBox(abstractFile); } - + if (isEMLFile) { return processEMLFile(abstractFile); } @@ -166,11 +183,11 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { if (isPstFile) { return processPst(abstractFile); } - + if (isVcardFile) { return processVcard(abstractFile); } - + return ProcessResult.OK; } @@ -186,7 +203,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { String fileName; try { fileName = getTempPath() + File.separator + abstractFile.getName() - + "-" + String.valueOf(abstractFile.getId()); + + "-" + String.valueOf(abstractFile.getId()); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return ProcessResult.ERROR; @@ -204,51 +221,54 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return ProcessResult.OK; } - try { - ContentUtils.writeToFile(abstractFile, file, context::fileIngestIsCancelled); - } catch (IOException ex) { - logger.log(Level.WARNING, "Failed writing pst file to disk.", ex); //NON-NLS - return ProcessResult.OK; - } + try (PstParser parser = new PstParser(services)) { + try { + ContentUtils.writeToFile(abstractFile, file, context::fileIngestIsCancelled); + } catch (IOException ex) { + logger.log(Level.WARNING, "Failed writing pst file to disk.", ex); //NON-NLS + return ProcessResult.OK; + } - PstParser parser = new PstParser(services); - PstParser.ParseResult result = parser.open(file, abstractFile.getId()); - - - switch( result) { - case OK: - Iterator pstMsgIterator = parser.getEmailMessageIterator(); - if (pstMsgIterator != null) { - processEmails(parser.getPartialEmailMessages(), pstMsgIterator, abstractFile); - if (context.fileIngestIsCancelled()) { - return ProcessResult.OK; + PstParser.ParseResult result = parser.open(file, abstractFile.getId()); + + switch (result) { + case OK: + Iterator pstMsgIterator = parser.getEmailMessageIterator(); + if (pstMsgIterator != null) { + processEmails(parser.getPartialEmailMessages(), pstMsgIterator, abstractFile); + if (context.fileIngestIsCancelled()) { + return ProcessResult.OK; + } + } else { + // sometimes parser returns ParseResult=OK but there are no messages + postErrorMessage( + NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.processPst.errProcFile.msg", + abstractFile.getName()), + NbBundle.getMessage(this.getClass(), + "ThunderbirdMboxFileIngestModule.processPst.errProcFile.details")); + logger.log(Level.INFO, "PSTParser failed to parse {0}", abstractFile.getName()); //NON-NLS + return ProcessResult.ERROR; } - } else { - // sometimes parser returns ParseResult=OK but there are no messages - postErrorMessage( - NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.processPst.errProcFile.msg", - abstractFile.getName()), - NbBundle.getMessage(this.getClass(), - "ThunderbirdMboxFileIngestModule.processPst.errProcFile.details")); - logger.log(Level.INFO, "PSTParser failed to parse {0}", abstractFile.getName()); //NON-NLS - // delete the temp file - if (file.delete() == false) { - logger.log(Level.INFO, "Failed to delete temp file: {0}", file.getName()); //NON-NLS - } - return ProcessResult.ERROR; - } - break; + break; - case ENCRYPT: - // encrypted pst: Add encrypted file artifact - try { + case ENCRYPT: + // encrypted pst: Add encrypted file artifact + try { - BlackboardArtifact artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED); - artifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME, EmailParserModuleFactory.getModuleName(), NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.encryptionFileLevel"))); + String encryptionFileLevel = NbBundle.getMessage(this.getClass(), + "ThunderbirdMboxFileIngestModule.encryptionFileLevel"); + BlackboardArtifact artifact = abstractFile.newAnalysisResult( + BlackboardArtifact.Type.TSK_ENCRYPTION_DETECTED, + Score.SCORE_NOTABLE, null, null, encryptionFileLevel, Arrays.asList( + new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME, + EmailParserModuleFactory.getModuleName(), + encryptionFileLevel) + )) + .getAnalysisResult(); try { // index the artifact for keyword search - blackboard.postArtifact(artifact, EmailParserModuleFactory.getModuleName()); + blackboard.postArtifact(artifact, EmailParserModuleFactory.getModuleName(), context.getJobId()); } catch (Blackboard.BlackboardException ex) { MessageNotifyUtil.Notify.error(Bundle.ThunderbirdMboxFileIngestModule_processPst_indexError_message(), artifact.getDisplayName()); logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS @@ -257,25 +277,21 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { logger.log(Level.INFO, "Failed to add encryption attribute to file: {0}", abstractFile.getName()); //NON-NLS } break; - default: - // parsing error: log message - postErrorMessage( - NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.processPst.errProcFile.msg", - abstractFile.getName()), - NbBundle.getMessage(this.getClass(), - "ThunderbirdMboxFileIngestModule.processPst.errProcFile.details")); - logger.log(Level.INFO, "PSTParser failed to parse {0}", abstractFile.getName()); //NON-NLS - // delete the temp file - if (file.delete() == false) { - logger.log(Level.INFO, "Failed to delete temp file: {0}", file.getName()); //NON-NLS - } - return ProcessResult.ERROR; + default: + // parsing error: log message + postErrorMessage( + NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.processPst.errProcFile.msg", + abstractFile.getName()), + NbBundle.getMessage(this.getClass(), + "ThunderbirdMboxFileIngestModule.processPst.errProcFile.details")); + logger.log(Level.INFO, "PSTParser failed to parse {0}", abstractFile.getName()); //NON-NLS + return ProcessResult.ERROR; + } + } catch (Exception ex) { + logger.log(Level.WARNING, String.format("Failed to close temp pst file %s", file.getAbsolutePath())); + } finally { + file.delete(); } - - if (file.delete() == false) { - logger.log(Level.INFO, "Failed to delete temp file: {0}", file.getName()); //NON-NLS - } - return ProcessResult.OK; } @@ -303,7 +319,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { String fileName; try { fileName = getTempPath() + File.separator + abstractFile.getName() - + "-" + String.valueOf(abstractFile.getId()); + + "-" + String.valueOf(abstractFile.getId()); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return ProcessResult.ERROR; @@ -322,7 +338,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } if (abstractFile.getSize() < MBOX_SIZE_TO_SPLIT) { - + try { ContentUtils.writeToFile(abstractFile, file, context::fileIngestIsCancelled); } catch (IOException ex) { @@ -330,25 +346,25 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return ProcessResult.OK; } - processMboxFile(file, abstractFile, emailFolder); - if (context.fileIngestIsCancelled()) { - return ProcessResult.OK; - } - - if (file.delete() == false) { - logger.log(Level.INFO, "Failed to delete temp file: {0}", file.getName()); //NON-NLS + try { + processMboxFile(file, abstractFile, emailFolder); + if (context.fileIngestIsCancelled()) { + return ProcessResult.OK; + } + } finally { + file.delete(); } } else { - List mboxSplitOffsets = new ArrayList<>(); - try{ + List mboxSplitOffsets = new ArrayList<>(); + try { mboxSplitOffsets = findMboxSplitOffset(abstractFile, file); } catch (IOException ex) { logger.log(Level.WARNING, String.format("Failed finding split offsets for mbox file {0}.", fileName), ex); //NON-NLS return ProcessResult.OK; } - long startingOffset = 0; + long startingOffset = 0; for (Long mboxSplitOffset : mboxSplitOffsets) { File splitFile = new File(fileName + "-" + mboxSplitOffset); try { @@ -357,69 +373,72 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { logger.log(Level.WARNING, "Failed writing split mbox file to disk.", ex); //NON-NLS return ProcessResult.OK; } - processMboxFile(splitFile, abstractFile, emailFolder); - startingOffset = mboxSplitOffset; - if (splitFile.delete() == false) { - logger.log(Level.INFO, "Failed to delete temp file: {0}", splitFile); //NON-NLS + try { + processMboxFile(splitFile, abstractFile, emailFolder); + startingOffset = mboxSplitOffset; + } finally { + splitFile.delete(); } + if (context.fileIngestIsCancelled()) { return ProcessResult.OK; } } - } - + } + return ProcessResult.OK; } - + private List findMboxSplitOffset(AbstractFile abstractFile, File file) throws IOException { - + List mboxSplitOffset = new ArrayList<>(); - + byte[] buffer = new byte[7]; ReadContentInputStream in = new ReadContentInputStream(abstractFile); - in.skip(MBOX_SIZE_TO_SPLIT); + in.skip(MBOX_SIZE_TO_SPLIT); int len = in.read(buffer); while (len != -1) { len = in.read(buffer); - if (buffer[0] == 13 && buffer[1] == 10 && buffer[2] == 70 && buffer[3] == 114 && - buffer[4] == 111 && buffer[5] == 109 && buffer[6] == 32) { - mboxSplitOffset.add(in.getCurPosition() - 5 ); - in.skip(MBOX_SIZE_TO_SPLIT); + if (buffer[0] == 13 && buffer[1] == 10 && buffer[2] == 70 && buffer[3] == 114 + && buffer[4] == 111 && buffer[5] == 109 && buffer[6] == 32) { + mboxSplitOffset.add(in.getCurPosition() - 5); + in.skip(MBOX_SIZE_TO_SPLIT); } } - + return mboxSplitOffset; - + } - - + private void processMboxFile(File file, AbstractFile abstractFile, String emailFolder) { - - MboxParser emailIterator = MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId()); - List emails = new ArrayList<>(); - if(emailIterator != null) { - while(emailIterator.hasNext()) { - if (context.fileIngestIsCancelled()) { - return; + try (MboxParser emailIterator = MboxParser.getEmailIterator(emailFolder, file, abstractFile.getId())) { + List emails = new ArrayList<>(); + if (emailIterator != null) { + while (emailIterator.hasNext()) { + if (context.fileIngestIsCancelled()) { + return; + } + EmailMessage emailMessage = emailIterator.next(); + if (emailMessage != null) { + emails.add(emailMessage); + } } - EmailMessage emailMessage = emailIterator.next(); - if(emailMessage != null) { - emails.add(emailMessage); + + String errors = emailIterator.getErrors(); + if (!errors.isEmpty()) { + postErrorMessage( + NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.processMBox.errProcFile.msg2", + abstractFile.getName()), errors); } } - - String errors = emailIterator.getErrors(); - if (!errors.isEmpty()) { - postErrorMessage( - NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.processMBox.errProcFile.msg2", - abstractFile.getName()), errors); - } + processEmails(emails, MboxParser.getEmailIterator(emailFolder, file, abstractFile.getId()), abstractFile); + } catch (Exception ex) { + logger.log(Level.WARNING, String.format("Failed to close mbox temp file %s", file.getAbsolutePath())); } - processEmails(emails, MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId()), abstractFile); } - + /** * Parse and extract data from a vCard file. * @@ -435,7 +454,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { }) private ProcessResult processVcard(AbstractFile abstractFile) { try { - VcardParser parser = new VcardParser(currentCase, context); + VcardParser parser = new VcardParser(currentCase, context, customAttributeCache); parser.parse(abstractFile); } catch (IOException | NoCurrentCaseException ex) { logger.log(Level.WARNING, String.format("Exception while parsing the file '%s' (id=%d).", abstractFile.getName(), abstractFile.getId()), ex); //NON-NLS @@ -443,8 +462,8 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } return ProcessResult.OK; } - - private ProcessResult processEMLFile(AbstractFile abstractFile) { + + private ProcessResult processEMLFile(AbstractFile abstractFile) { try { EmailMessage message = EMLParser.parse(abstractFile); @@ -455,13 +474,9 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { List derivedFiles = new ArrayList<>(); AccountFileInstanceCache accountFileInstanceCache = new AccountFileInstanceCache(abstractFile, currentCase); - BlackboardArtifact msgArtifact = addEmailArtifact(message, abstractFile, accountFileInstanceCache); + createEmailArtifact(message, abstractFile, accountFileInstanceCache, derivedFiles); accountFileInstanceCache.clear(); - if ((msgArtifact != null) && (message.hasAttachment())) { - derivedFiles.addAll(handleAttachments(message.getAttachments(), abstractFile, msgArtifact)); - } - if (derivedFiles.isEmpty() == false) { for (AbstractFile derived : derivedFiles) { services.fireModuleContentEvent(new ModuleContentEvent(derived)); @@ -498,7 +513,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { /** * Get a module output folder. - * + * * @throws NoCurrentCaseException if there is no open case. * * @return the module output folder @@ -532,48 +547,43 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { * @param fullMessageIterator * @param abstractFile */ - private void processEmails(List partialEmailsForThreading, Iterator fullMessageIterator, + private void processEmails(List partialEmailsForThreading, Iterator fullMessageIterator, AbstractFile abstractFile) { - + // Create cache for accounts AccountFileInstanceCache accountFileInstanceCache = new AccountFileInstanceCache(abstractFile, currentCase); - + // Putting try/catch around this to catch any exception and still allow // the creation of the artifacts to continue. - try{ + try { EmailMessageThreader.threadMessages(partialEmailsForThreading); - } catch(Exception ex) { + } catch (Exception ex) { logger.log(Level.WARNING, String.format("Exception thrown parsing emails from %s", abstractFile.getName()), ex); } - + List derivedFiles = new ArrayList<>(); int msgCnt = 0; - while(fullMessageIterator.hasNext()) { + while (fullMessageIterator.hasNext()) { if (context.fileIngestIsCancelled()) { return; } - + EmailMessage current = fullMessageIterator.next(); - - if(current == null) { + + if (current == null) { continue; } - if(partialEmailsForThreading.size() > msgCnt) { + if (partialEmailsForThreading.size() > msgCnt) { EmailMessage threaded = partialEmailsForThreading.get(msgCnt++); - - if(threaded.getMessageID().equals(current.getMessageID()) && - threaded.getSubject().equals(current.getSubject())) { + + if (threaded.getMessageID().equals(current.getMessageID()) + && threaded.getSubject().equals(current.getSubject())) { current.setMessageThreadID(threaded.getMessageThreadID()); } } - - BlackboardArtifact msgArtifact = addEmailArtifact(current, abstractFile, accountFileInstanceCache); - - if ((msgArtifact != null) && (current.hasAttachment())) { - derivedFiles.addAll(handleAttachments(current.getAttachments(), abstractFile, msgArtifact )); - } + createEmailArtifact(current, abstractFile, accountFileInstanceCache, derivedFiles); } if (derivedFiles.isEmpty() == false) { @@ -586,6 +596,21 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } context.addFilesToJob(derivedFiles); } + + void createEmailArtifact(EmailMessage email, AbstractFile abstractFile, AccountFileInstanceCache accountFileInstanceCache, List derivedFiles) { + BlackboardArtifact msgArtifact = addEmailArtifact(email, abstractFile, accountFileInstanceCache); + + if ((msgArtifact != null) && (email.hasAttachment())) { + derivedFiles.addAll(handleAttachments(email.getAttachments(), abstractFile, msgArtifact)); + + for (EmailMessage.Attachment attach : email.getAttachments()) { + if (attach instanceof AttachedEmailMessage) { + createEmailArtifact(((AttachedEmailMessage) attach).getEmailMessage(), abstractFile, accountFileInstanceCache, derivedFiles); + } + } + } + } + /** * Add the given attachments as derived files and reschedule them for * ingest. @@ -597,8 +622,8 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { * @return List of attachments */ @NbBundle.Messages({ - "ThunderbirdMboxFileIngestModule.handleAttch.addAttachmentsErrorMsg=Failed to add attachments to email message." -}) + "ThunderbirdMboxFileIngestModule.handleAttch.addAttachmentsErrorMsg=Failed to add attachments to email message." + }) private List handleAttachments(List attachments, AbstractFile abstractFile, BlackboardArtifact messageArtifact) { List files = new ArrayList<>(); List fileAttachments = new ArrayList<>(); @@ -616,11 +641,9 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { DerivedFile df = fileManager.addDerivedFile(filename, relPath, size, cTime, crTime, aTime, mTime, true, abstractFile, "", EmailParserModuleFactory.getModuleName(), EmailParserModuleFactory.getModuleVersion(), "", encodingType); - - associateAttachmentWithMesssge(messageArtifact, df); - + files.add(df); - + fileAttachments.add(new FileAttachment(df)); } catch (TskCoreException ex) { postErrorMessage( @@ -631,57 +654,45 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { logger.log(Level.INFO, "", ex); } } - - + try { communicationArtifactsHelper.addAttachments(messageArtifact, new MessageAttachments(fileAttachments, Collections.emptyList())); } catch (TskCoreException ex) { - postErrorMessage( - NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.handleAttch.addAttachmentsErrorMsg"), - ""); - logger.log(Level.INFO, "Failed to add attachments to email message.", ex); + postErrorMessage( + NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.handleAttch.addAttachmentsErrorMsg"), + ""); + logger.log(Level.INFO, "Failed to add attachments to email message.", ex); } - + return files; } /** - * Creates a TSK_ASSOCIATED_OBJECT artifact between the attachment file and - * the message artifact. - */ - private BlackboardArtifact associateAttachmentWithMesssge(BlackboardArtifact message, AbstractFile attachedFile) throws TskCoreException { - Collection attributes = new ArrayList<>(); - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, EmailParserModuleFactory.getModuleName(), message.getArtifactID())); - - BlackboardArtifact bba = attachedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT); - bba.addAttributes(attributes); //write out to bb - return bba; - } - - /** - * Finds and returns a set of unique email addresses found in the input string + * Finds and returns a set of unique email addresses found in the input + * string * * @param input - input string, like the To/CC line from an email header - * + * * @return Set: set of email addresses found in the input string */ private Set findEmailAddresess(String input) { Pattern p = Pattern.compile("\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b", - Pattern.CASE_INSENSITIVE); + Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(input); Set emailAddresses = new HashSet<>(); while (m.find()) { - emailAddresses.add( m.group()); + emailAddresses.add(m.group()); } return emailAddresses; } - + /** * Add a blackboard artifact for the given e-mail message. * - * @param email The e-mail message. - * @param abstractFile The associated file. - * + * @param email The e-mail message. + * @param abstractFile The associated file. + * @param accountFileInstanceCache The current cache of account instances. + * * @return The generated e-mail message artifact. */ @Messages({"ThunderbirdMboxFileIngestModule.addArtifact.indexError.message=Failed to index email message detected artifact for keyword search."}) @@ -705,93 +716,90 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { List senderAddressList = new ArrayList<>(); String senderAddress; senderAddressList.addAll(findEmailAddresess(from)); - + if (context.fileIngestIsCancelled()) { return null; } - + AccountFileInstance senderAccountInstance = null; if (senderAddressList.size() == 1) { senderAddress = senderAddressList.get(0); try { - senderAccountInstance = accountFileInstanceCache.getAccountInstance(senderAddress); - } - catch(TskCoreException ex) { - logger.log(Level.WARNING, "Failed to create account for email address " + senderAddress, ex); //NON-NLS + senderAccountInstance = accountFileInstanceCache.getAccountInstance(senderAddress, context); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Failed to create account for email address " + senderAddress, ex); //NON-NLS } + } else { + logger.log(Level.WARNING, "Failed to find sender address, from = {0}", from); //NON-NLS } - else { - logger.log(Level.WARNING, "Failed to find sender address, from = {0}", from); //NON-NLS - } - + if (context.fileIngestIsCancelled()) { return null; } - + List recipientAddresses = new ArrayList<>(); recipientAddresses.addAll(findEmailAddresess(to)); recipientAddresses.addAll(findEmailAddresess(cc)); recipientAddresses.addAll(findEmailAddresess(bcc)); - + List recipientAccountInstances = new ArrayList<>(); for (String addr : recipientAddresses) { if (context.fileIngestIsCancelled()) { return null; } try { - AccountFileInstance recipientAccountInstance = accountFileInstanceCache.getAccountInstance(addr); + AccountFileInstance recipientAccountInstance = accountFileInstanceCache.getAccountInstance(addr, context); recipientAccountInstances.add(recipientAccountInstance); - } - catch(TskCoreException ex) { + } catch (TskCoreException ex) { logger.log(Level.WARNING, "Failed to create account for email address " + addr, ex); //NON-NLS } } - + addArtifactAttribute(headers, ATTRIBUTE_TYPE.TSK_HEADERS, bbattributes); addArtifactAttribute(from, ATTRIBUTE_TYPE.TSK_EMAIL_FROM, bbattributes); addArtifactAttribute(to, ATTRIBUTE_TYPE.TSK_EMAIL_TO, bbattributes); addArtifactAttribute(subject, ATTRIBUTE_TYPE.TSK_SUBJECT, bbattributes); - + addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_RCVD, bbattributes); addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_SENT, bbattributes); - + addArtifactAttribute(body, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN, bbattributes); - - addArtifactAttribute(((id < 0L) ? NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.notAvail") : String.valueOf(id)), + + addArtifactAttribute(((id < 0L) ? NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.notAvail") : String.valueOf(id)), ATTRIBUTE_TYPE.TSK_MSG_ID, bbattributes); - - addArtifactAttribute(((localPath.isEmpty() == false) ? localPath : ""), + + addArtifactAttribute(((localPath.isEmpty() == false) ? localPath : ""), ATTRIBUTE_TYPE.TSK_PATH, bbattributes); - + addArtifactAttribute(cc, ATTRIBUTE_TYPE.TSK_EMAIL_CC, bbattributes); addArtifactAttribute(bodyHTML, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_HTML, bbattributes); addArtifactAttribute(rtf, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_RTF, bbattributes); addArtifactAttribute(threadID, ATTRIBUTE_TYPE.TSK_THREAD_ID, bbattributes); - - + try { if (context.fileIngestIsCancelled()) { return null; } - - bbart = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG); - bbart.addAttributes(bbattributes); + + bbart = abstractFile.newDataArtifact( + new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG), + bbattributes); if (context.fileIngestIsCancelled()) { return null; } - + // Add account relationships - currentCase.getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, recipientAccountInstances, bbart,Relationship.Type.MESSAGE, dateL); + currentCase.getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, recipientAccountInstances, bbart, Relationship.Type.MESSAGE, dateL); if (context.fileIngestIsCancelled()) { return null; } - + try { // index the artifact for keyword search - blackboard.postArtifact(bbart, EmailParserModuleFactory.getModuleName()); + blackboard.postArtifact(bbart, EmailParserModuleFactory.getModuleName(), context.getJobId()); } catch (Blackboard.BlackboardException ex) { logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bbart.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error(Bundle.ThunderbirdMboxFileIngestModule_addArtifact_indexError_message(), bbart.getDisplayName()); @@ -802,11 +810,11 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return bbart; } - + /** * Add an attribute of a specified type to a supplied Collection. - * - * @param stringVal The attribute value. + * + * @param stringVal The attribute value. * @param attrType The type of attribute to be added. * @param bbattributes The Collection to which the attribute will be added. */ @@ -818,7 +826,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { /** * Add an attribute of a specified type to a supplied Collection. - * + * * @param stringVal The attribute value. * @param attrType The type of attribute to be added. * @param bbattributes The Collection to which the attribute will be added. @@ -828,10 +836,10 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { bbattributes.add(new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), stringVal)); } } - + /** * Add an attribute of a specified type to a supplied Collection. - * + * * @param longVal The attribute value. * @param attrType The type of attribute to be added. * @param bbattributes The Collection to which the attribute will be added. @@ -841,49 +849,52 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { bbattributes.add(new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), longVal)); } } - + /** - * Cache for storing AccountFileInstance. - * The idea is that emails will be used multiple times in a file and - * we shouldn't do a database lookup each time. + * Cache for storing AccountFileInstance. The idea is that emails will be + * used multiple times in a file and we shouldn't do a database lookup each + * time. */ static private class AccountFileInstanceCache { + private final Map cacheMap; private final AbstractFile file; private final Case currentCase; - + /** * Create a new cache. Caches are linked to a specific file. + * * @param file - * @param currentCase + * @param currentCase */ AccountFileInstanceCache(AbstractFile file, Case currentCase) { - cacheMap= new HashMap<>(); + cacheMap = new HashMap<>(); this.file = file; this.currentCase = currentCase; } - + /** * Get the account file instance from the cache or the database. - * + * * @param email The email for this account. - * + * @param context The current ingest job context. + * * @return The corresponding AccountFileInstance - * - * @throws TskCoreException + * + * @throws TskCoreException */ - AccountFileInstance getAccountInstance(String email) throws TskCoreException { + AccountFileInstance getAccountInstance(String email, IngestJobContext context) throws TskCoreException { if (cacheMap.containsKey(email)) { return cacheMap.get(email); } - - AccountFileInstance accountInstance = - currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, email, - EmailParserModuleFactory.getModuleName(), file); + + AccountFileInstance accountInstance + = currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, email, + EmailParserModuleFactory.getModuleName(), file, null, context.getJobId()); cacheMap.put(email, accountInstance); return accountInstance; } - + /** * Clears the cache. */ @@ -891,10 +902,10 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { cacheMap.clear(); } } - + /** * Post an error message for the user. - * + * * @param subj The error subject. * @param details The error details. */ @@ -905,7 +916,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { /** * Get the IngestServices object. - * + * * @return The IngestServices object. */ IngestServices getServices() { @@ -914,7 +925,11 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { @Override public void shutDown() { - // nothing to shut down + synchronized(customAttributeCacheLock) { + if (!customAttributeCache.isEmpty()) { + customAttributeCache.clear(); + } + } } - + } diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java index 886a3bc41f..ca1c328aca 100755 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java @@ -27,9 +27,12 @@ import ezvcard.property.Organization; import ezvcard.property.Photo; import ezvcard.property.Telephone; import ezvcard.property.Url; +import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; @@ -37,6 +40,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; import org.apache.commons.lang3.StringUtils; import org.openide.util.NbBundle; @@ -95,16 +99,22 @@ final class VcardParser { private final Blackboard blackboard; private final Case currentCase; private final SleuthkitCase tskCase; + /** + * A custom attribute cache provided to every VcardParser from the + * ThunderbirdMboxFileIngestModule, but unique to one ingest run. + */ + private final ConcurrentMap customAttributeCache; /** * Create a VcardParser object. */ - VcardParser(Case currentCase, IngestJobContext context) { + VcardParser(Case currentCase, IngestJobContext context, ConcurrentMap customAttributeCache) { this.context = context; this.currentCase = currentCase; tskCase = currentCase.getSleuthkitCase(); blackboard = tskCase.getBlackboard(); fileManager = currentCase.getServices().getFileManager(); + this.customAttributeCache = customAttributeCache; } /** @@ -144,7 +154,7 @@ final class VcardParser { * @throws NoCurrentCaseException If there is no open case. */ void parse(AbstractFile abstractFile) throws IOException, NoCurrentCaseException { - for (VCard vcard: Ezvcard.parse(new ReadContentInputStream(abstractFile)).all()) { + for (VCard vcard: Ezvcard.parse(new InputStreamReader(new BufferedInputStream(new ReadContentInputStream(abstractFile)), StandardCharsets.UTF_8)).all()) { addContactArtifact(vcard, abstractFile); } } @@ -222,11 +232,10 @@ final class VcardParser { org.sleuthkit.datamodel.Blackboard tskBlackboard = tskCase.getBlackboard(); try { // Create artifact if it doesn't already exist. - if (!tskBlackboard.artifactExists(abstractFile, BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT, attributes)) { - artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT); - artifact.addAttributes(attributes); + if (!tskBlackboard.artifactExists(abstractFile, BlackboardArtifact.Type.TSK_CONTACT, attributes)) { + artifact = abstractFile.newDataArtifact(new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT), attributes); - extractPhotos(vcard, abstractFile, artifact); + extractPhotos(vcard, abstractFile, artifact); // Add account relationships. if (deviceAccountInstance != null) { @@ -241,7 +250,7 @@ final class VcardParser { // Index the artifact for keyword search. try { - blackboard.postArtifact(artifact, EmailParserModuleFactory.getModuleName()); + blackboard.postArtifact(artifact, EmailParserModuleFactory.getModuleName(), context.getJobId()); } catch (Blackboard.BlackboardException ex) { logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error(Bundle.VcardParser_addContactArtifact_indexError(), artifact.getDisplayName()); @@ -389,6 +398,9 @@ final class VcardParser { String telephoneText = telephone.getText(); if (telephoneText == null || telephoneText.isEmpty()) { + if (telephone.getUri() == null) { + return; + } telephoneText = telephone.getUri().getNumber(); if (telephoneText == null || telephoneText.isEmpty()) { return; @@ -416,26 +428,29 @@ final class VcardParser { if (splitType != null && !splitType.isEmpty()) { attributeTypeName = "TSK_PHONE_NUMBER_" + splitType; } + + final String finalAttrTypeName = attributeTypeName; - try { - BlackboardAttribute.Type attributeType = tskCase.getAttributeType(attributeTypeName); - if (attributeType == null) { - try{ - // Add this attribute type to the case database. - attributeType = tskCase.getBlackboard().getOrAddAttributeType(attributeTypeName, - BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, - String.format("Phone Number (%s)", StringUtils.capitalize(splitType.toLowerCase()))); - - ThunderbirdMboxFileIngestModule.addArtifactAttribute(telephoneText, attributeType, attributes); - }catch (BlackboardException ex) { - logger.log(Level.WARNING, String.format("Unable to retrieve attribute type '%s' for file '%s' (id=%d).", attributeTypeName, abstractFile.getName(), abstractFile.getId()), ex); - } - } - - } catch (TskCoreException ex) { - logger.log(Level.WARNING, String.format("Unable to retrieve attribute type '%s' for file '%s' (id=%d).", attributeTypeName, abstractFile.getName(), abstractFile.getId()), ex); + // handled in computeIfAbsent to remove concurrency issues when adding to this concurrent hashmap. + BlackboardAttribute.Type attributeType + = this.customAttributeCache.computeIfAbsent(finalAttrTypeName, k -> { + try { + // Add this attribute type to the case database. + return tskCase.getBlackboard().getOrAddAttributeType(finalAttrTypeName, + BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, + String.format("Phone Number (%s)", StringUtils.capitalize(splitType.toLowerCase()))); + + } catch (BlackboardException ex) { + VcardParser.logger.log(Level.WARNING, String.format("Unable to retrieve attribute type '%s' for file '%s' (id=%d).", + finalAttrTypeName, abstractFile.getName(), abstractFile.getId()), ex); + return null; + } + }); + + if (attributeType != null) { + ThunderbirdMboxFileIngestModule.addArtifactAttribute(telephoneText, attributeType, attributes); } - } + } } } @@ -464,30 +479,37 @@ final class VcardParser { * ez-vcard. Therefore, we must read them manually * ourselves. */ - List splitEmailTypes = Arrays.asList( - type.getValue().toUpperCase().replaceAll("\\s+","").split(",")); + List splitEmailTypes = Arrays.asList( + type.getValue().toUpperCase().replaceAll("\\s+", "").split(",")); - if (splitEmailTypes.size() > 0) { - String splitType = splitEmailTypes.get(0); - String attributeTypeName = "TSK_EMAIL_" + splitType; - if(splitType.isEmpty()) { - attributeTypeName = "TSK_EMAIL"; - } - try { - BlackboardAttribute.Type attributeType = tskCase.getAttributeType(attributeTypeName); - if (attributeType == null) { - // Add this attribute type to the case database. - attributeType = tskCase.getBlackboard().getOrAddAttributeType(attributeTypeName, - BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, - String.format("Email (%s)", StringUtils.capitalize(splitType.toLowerCase()))); - } - ThunderbirdMboxFileIngestModule.addArtifactAttribute(email.getValue(), attributeType, attributes); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, String.format("Unable to retrieve attribute type '%s' for file '%s' (id=%d).", attributeTypeName, abstractFile.getName(), abstractFile.getId()), ex); - } catch (BlackboardException ex) { - logger.log(Level.SEVERE, String.format("Unable to add custom attribute type '%s' for file '%s' (id=%d).", attributeTypeName, abstractFile.getName(), abstractFile.getId()), ex); - } - } + if (splitEmailTypes.size() > 0) { + String splitType = splitEmailTypes.get(0); + String attributeTypeName = "TSK_EMAIL_" + splitType; + if (splitType.isEmpty()) { + attributeTypeName = "TSK_EMAIL"; + } + + final String finalAttributeTypeName = attributeTypeName; + + BlackboardAttribute.Type attributeType + = this.customAttributeCache.computeIfAbsent(finalAttributeTypeName, k -> { + try { + // Add this attribute type to the case database. + return tskCase.getBlackboard().getOrAddAttributeType(finalAttributeTypeName, + BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, + String.format("Email (%s)", StringUtils.capitalize(splitType.toLowerCase()))); + } catch (BlackboardException ex) { + logger.log(Level.SEVERE, String.format("Unable to add custom attribute type '%s' for file '%s' (id=%d).", + finalAttributeTypeName, abstractFile.getName(), abstractFile.getId()), ex); + } + + return null; + }); + + if (attributeType != null) { + ThunderbirdMboxFileIngestModule.addArtifactAttribute(email.getValue(), attributeType, attributes); + } + } } } @@ -503,6 +525,9 @@ final class VcardParser { private void addPhoneAccountInstances(Telephone telephone, AbstractFile abstractFile, Collection accountInstances) { String telephoneText = telephone.getText(); if (telephoneText == null || telephoneText.isEmpty()) { + if (telephone.getUri() == null) { + return; + } telephoneText = telephone.getUri().getNumber(); if (telephoneText == null || telephoneText.isEmpty()) { return; @@ -513,7 +538,7 @@ final class VcardParser { // Add phone number as a TSK_ACCOUNT. try { AccountFileInstance phoneAccountInstance = tskCase.getCommunicationsManager().createAccountFileInstance(Account.Type.PHONE, - telephoneText, EmailParserModuleFactory.getModuleName(), abstractFile); + telephoneText, EmailParserModuleFactory.getModuleName(), abstractFile, null, context.getJobId()); accountInstances.add(phoneAccountInstance); } catch(TskCoreException ex) { @@ -541,7 +566,7 @@ final class VcardParser { // Add e-mail as a TSK_ACCOUNT. try { AccountFileInstance emailAccountInstance = tskCase.getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, - emailValue, EmailParserModuleFactory.getModuleName(), abstractFile); + emailValue, EmailParserModuleFactory.getModuleName(), abstractFile, null, context.getJobId()); accountInstances.add(emailAccountInstance); } catch(TskCoreException ex) { @@ -567,7 +592,7 @@ final class VcardParser { DataSource dataSource = tskCase.getDataSource(dataSourceObjId); deviceId = dataSource.getDeviceId(); deviceAccountInstance = tskCase.getCommunicationsManager().createAccountFileInstance(Account.Type.DEVICE, - deviceId, EmailParserModuleFactory.getModuleName(), abstractFile); + deviceId, EmailParserModuleFactory.getModuleName(), abstractFile, null, context.getJobId()); } catch (TskCoreException ex) { logger.log(Level.WARNING, String.format( diff --git a/unix_setup.sh b/unix_setup.sh index 63e2daee9a..bfb4e8ad01 100644 --- a/unix_setup.sh +++ b/unix_setup.sh @@ -1,18 +1,44 @@ #!/bin/bash # -# Verifies programs are installed and copies native code into the Autopsy folder structure +# Verifies programs are installed and copies native code into the Application folder structure # # NOTE: update_sleuthkit_version.pl updates this value and relies # on it keeping the same name and whitespace. Don't change it. -TSK_VERSION=4.10.1 +TSK_VERSION=4.11.1 + + +usage() { + echo "Usage: unix_setup.sh [-j java_home] [-n application_name]" 1>&2; +} + +APPLICATION_NAME="autopsy"; + +while getopts "j:n:" o; do + case "${o}" in + n) + APPLICATION_NAME=${OPTARG} + ;; + j) + JAVA_PATH=${OPTARG} + ;; + *) + usage + exit 1 + ;; + esac +done # In the beginning... echo "---------------------------------------------" -echo "Checking prerequisites and preparing Autopsy:" +echo "Checking prerequisites and preparing ${APPLICATION_NAME}:" echo "---------------------------------------------" +# make sure cwd is same as script's +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +pushd $SCRIPTPATH + # Verify PhotoRec was installed echo -n "Checking for PhotoRec..." photorec_filepath=/usr/bin/photorec @@ -28,12 +54,22 @@ fi # Verify Java was installed and configured echo -n "Checking for Java..." -if [ -n "$JAVA_HOME" ]; then - if [ -x "$JAVA_HOME/bin/java" ]; then - echo "found in $JAVA_HOME" +if [ -n "$JAVA_PATH" ]; then + if [ -x "$JAVA_PATH/bin/java" ]; then + # only works on linux; not os x + awk '!/^\s*#?\s*jdkhome=.*$/' etc/$APPLICATION_NAME.conf > etc/$APPLICATION_NAME.conf.tmp && \ + mv etc/$APPLICATION_NAME.conf.tmp etc/$APPLICATION_NAME.conf && \ + echo "jdkhome=$JAVA_PATH" >> etc/$APPLICATION_NAME.conf else - echo "ERROR: Java was not found in $JAVA_HOME." - exit 1 + echo "ERROR: Java was not found in $JAVA_PATH." + exit 1 + fi +elif [ -n "$JAVA_HOME" ]; then + if [ -x "$JAVA_HOME/bin/java" ]; then + echo "found in $JAVA_HOME" + else + echo "ERROR: Java was not found in $JAVA_HOME." + exit 1 fi else echo "ERROR: JAVA_HOME environment variable must be defined." @@ -56,7 +92,7 @@ else fi ext_jar_filepath=$PWD/autopsy/modules/ext/sleuthkit-$TSK_VERSION.jar; -echo -n "Copying sleuthkit-$TSK_VERSION.jar into the Autopsy directory..." +echo -n "Copying sleuthkit-$TSK_VERSION.jar into the $APPLICATION_NAME directory..." rm -f "$ext_jar_filepath"; if [ "$?" -gt 0 ]; then #checking if remove operation failed echo "ERROR: Deleting $ext_jar_filepath failed." @@ -76,9 +112,14 @@ fi chmod u+x autopsy/markmckinnon/Export* chmod u+x autopsy/markmckinnon/parse* +# allow solr dependencies to execute +chmod -R u+x autopsy/solr/bin + # make sure it is executable -chmod u+x bin/autopsy +chmod u+x bin/$APPLICATION_NAME + +popd echo -echo "Autopsy is now configured. You can execute bin/autopsy to start it" -echo +echo "Application is now configured. You can execute bin/$APPLICATION_NAME to start it" +echo \ No newline at end of file