mirror of
https://github.com/elisspace/autopsy.git
synced 2026-09-06 02:24:30 +00:00
Merge pull request #3053 from millmanorama/2960-only_store_artifact_id_in_KeywordHit
2960 only store artifact id in keyword hit
This commit is contained in:
@@ -18,71 +18,99 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.keywordsearch;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Stores the fact that file or an artifact associated with a file had a keyword
|
||||
* hit. All instances make both the document id of the Solr document where the
|
||||
* keyword was found and the object Id available to clients. Artifact keyword
|
||||
* hits also make the artifact available to clients.
|
||||
* Represents the fact that a file or an artifact associated with a file had a
|
||||
* keyword hit. All instances make both the document id of the Solr document
|
||||
* where the keyword was found and the object id of the file available to
|
||||
* clients. Keyword hits on the indexed text of an artifact also make the
|
||||
* artifact available to clients.
|
||||
*/
|
||||
class KeywordHit implements Comparable<KeywordHit> {
|
||||
|
||||
private static final String GET_CONTENT_ID_FROM_ARTIFACT_ID = "SELECT obj_id FROM blackboard_artifacts WHERE artifact_id = ";
|
||||
|
||||
private final String solrDocumentId;
|
||||
private final long solrObjectId;
|
||||
private final int chunkId;
|
||||
private final String snippet;
|
||||
private final long contentID;
|
||||
private final BlackboardArtifact artifact;
|
||||
private final boolean hitOnArtifact;
|
||||
private final String hit;
|
||||
|
||||
public String getHit() {
|
||||
return hit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param solrDocumentId The id of the document this hit is in.
|
||||
* @param snippet A small amount of text from the document containing
|
||||
* the hit.
|
||||
* @param hit The exact text from the document that was the hit.
|
||||
* For some searches (ie substring, regex) this will be
|
||||
* different than the search term.
|
||||
*
|
||||
* @throws TskCoreException If there is a problem getting the underlying
|
||||
* content associated with a hit on the text of an
|
||||
* artifact.
|
||||
*/
|
||||
KeywordHit(String solrDocumentId, String snippet, String hit) throws TskCoreException {
|
||||
this.snippet = StringUtils.stripToEmpty(snippet);
|
||||
this.hit = hit;
|
||||
this.solrDocumentId = solrDocumentId;
|
||||
|
||||
/**
|
||||
/*
|
||||
* Parse the Solr document id to get the Solr object id and chunk id.
|
||||
* The Solr object id will either be a file id or an artifact id from
|
||||
* the case database.
|
||||
* The Solr object id will either be the object id of a file id or an
|
||||
* artifact id from the case database.
|
||||
*
|
||||
* For every object (file or artifact) there will at least two Solr
|
||||
* documents. One contains object metadata (chunk #1) and the second and
|
||||
* subsequent documents contain chunks of the text.
|
||||
*/
|
||||
final int separatorIndex = solrDocumentId.indexOf(Server.CHUNK_ID_SEPARATOR);
|
||||
if (-1 != separatorIndex) {
|
||||
this.solrObjectId = Long.parseLong(solrDocumentId.substring(0, separatorIndex));
|
||||
this.chunkId = Integer.parseInt(solrDocumentId.substring(separatorIndex + 1));
|
||||
} else {
|
||||
String[] split = solrDocumentId.split(Server.CHUNK_ID_SEPARATOR);
|
||||
if (split.length == 1) {
|
||||
//chunk 0 has only the bare document id without the chunk id.
|
||||
this.solrObjectId = Long.parseLong(solrDocumentId);
|
||||
this.chunkId = 0;
|
||||
} else {
|
||||
this.solrObjectId = Long.parseLong(split[0]);
|
||||
this.chunkId = Integer.parseInt(split[1]);
|
||||
}
|
||||
|
||||
/*
|
||||
* If the high order bit of the object id is set (ie, it is negative),
|
||||
* the hit was in an artifact, look up the artifact.
|
||||
*/
|
||||
if (this.solrObjectId < 0) {
|
||||
//artifacts have negative obj ids
|
||||
hitOnArtifact = this.solrObjectId < 0;
|
||||
|
||||
if (hitOnArtifact) {
|
||||
// If the hit was in an artifact, look up the source content for the artifact.
|
||||
SleuthkitCase caseDb = Case.getCurrentCase().getSleuthkitCase();
|
||||
this.artifact = caseDb.getBlackboardArtifact(this.solrObjectId);
|
||||
contentID = artifact.getObjectID();
|
||||
try (SleuthkitCase.CaseDbQuery executeQuery =
|
||||
caseDb.executeQuery(GET_CONTENT_ID_FROM_ARTIFACT_ID + this.solrObjectId);
|
||||
ResultSet resultSet = executeQuery.getResultSet();) {
|
||||
if (resultSet.next()) {
|
||||
contentID = resultSet.getLong("obj_id");
|
||||
} else {
|
||||
throw new TskCoreException("Failed to get obj_id for artifact with artifact_id =" + this.solrObjectId + ". No matching artifact was found.");
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
throw new TskCoreException("Error getting obj_id for artifact with artifact_id =" + this.solrObjectId, ex);
|
||||
}
|
||||
} else {
|
||||
//else the object id is for content.
|
||||
this.artifact = null;
|
||||
contentID = this.solrObjectId;
|
||||
}
|
||||
}
|
||||
|
||||
String getHit() {
|
||||
return hit;
|
||||
}
|
||||
|
||||
String getSolrDocumentId() {
|
||||
return this.solrDocumentId;
|
||||
}
|
||||
@@ -113,17 +141,20 @@ class KeywordHit implements Comparable<KeywordHit> {
|
||||
* @return
|
||||
*/
|
||||
boolean isArtifactHit() {
|
||||
return (null != this.artifact);
|
||||
return hitOnArtifact;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this hit is in the indexed text of an artifact, get that artifact.
|
||||
*
|
||||
* @return The artifact whose indexed text this hit is in, or null if it is
|
||||
* not an artifacts hit.
|
||||
* @return The artifact whose indexed text this hit is in.
|
||||
*/
|
||||
BlackboardArtifact getArtifact() {
|
||||
return this.artifact;
|
||||
Optional<Long> getArtifactID() {
|
||||
if (hitOnArtifact) {
|
||||
return Optional.of(solrObjectId);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -186,12 +186,20 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValueQueryContent> {
|
||||
properties.put(TSK_KEYWORD_PREVIEW.getDisplayName(), hit.getSnippet());
|
||||
}
|
||||
|
||||
String hitName = hit.isArtifactHit()
|
||||
? hit.getArtifact().getDisplayName() + " Artifact" //NON-NLS
|
||||
: contentName;
|
||||
|
||||
String hitName;
|
||||
if (hit.isArtifactHit()) {
|
||||
try {
|
||||
hitName = tskCase.getBlackboardArtifact(hit.getArtifactID().get()).getDisplayName() + " Artifact"; //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error getting blckboard artifact by id", ex);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
hitName = contentName;
|
||||
}
|
||||
hitNumber++;
|
||||
tempList.add(new KeyValueQueryContent(hitName, properties, hitNumber, hit.getSolrObjectId(), content, queryRequest, queryResults));
|
||||
|
||||
}
|
||||
|
||||
// Add all the nodes to toPopulate at once. Minimizes node creation
|
||||
|
||||
@@ -225,9 +225,11 @@ class LuceneQuery implements KeywordSearchQuery {
|
||||
}
|
||||
}
|
||||
|
||||
if (hit.isArtifactHit()) {
|
||||
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, hit.getArtifact().getArtifactID()));
|
||||
}
|
||||
|
||||
hit.getArtifactID().ifPresent(artifactID
|
||||
-> attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID))
|
||||
);
|
||||
|
||||
|
||||
try {
|
||||
bba.addAttributes(attributes); //write out to bb
|
||||
|
||||
@@ -417,7 +417,7 @@ final class RegexQuery implements KeywordSearchQuery {
|
||||
final BlackboardAttribute ccnAttribute = parsedTrackAttributeMap.get(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER));
|
||||
if (ccnAttribute == null || StringUtils.isBlank(ccnAttribute.getValueString())) {
|
||||
if (hit.isArtifactHit()) {
|
||||
LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getArtifact().getArtifactID())); //NON-NLS
|
||||
LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getArtifactID().get())); //NON-NLS
|
||||
} else {
|
||||
LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s', object id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getContentID())); //NON-NLS
|
||||
}
|
||||
@@ -480,9 +480,10 @@ final class RegexQuery implements KeywordSearchQuery {
|
||||
if (snippet != null) {
|
||||
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet));
|
||||
}
|
||||
if (hit.isArtifactHit()) {
|
||||
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, hit.getArtifact().getArtifactID()));
|
||||
}
|
||||
|
||||
hit.getArtifactID().ifPresent(artifactID
|
||||
-> attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID))
|
||||
);
|
||||
|
||||
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.REGEX.ordinal()));
|
||||
|
||||
|
||||
@@ -359,7 +359,7 @@ final class TermsComponentQuery implements KeywordSearchQuery {
|
||||
final BlackboardAttribute ccnAttribute = parsedTrackAttributeMap.get(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CARD_NUMBER));
|
||||
if (ccnAttribute == null || StringUtils.isBlank(ccnAttribute.getValueString())) {
|
||||
if (hit.isArtifactHit()) {
|
||||
LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", searchTerm, hit.getSnippet(), hit.getArtifact().getArtifactID())); //NON-NLS
|
||||
LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", searchTerm, hit.getSnippet(), hit.getArtifactID().get())); //NON-NLS
|
||||
} else {
|
||||
LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s', object id = %d", searchTerm, hit.getSnippet(), hit.getContentID())); //NON-NLS
|
||||
}
|
||||
@@ -398,7 +398,7 @@ final class TermsComponentQuery implements KeywordSearchQuery {
|
||||
* hit.
|
||||
*/
|
||||
if (content instanceof AbstractFile) {
|
||||
AbstractFile file = (AbstractFile)content;
|
||||
AbstractFile file = (AbstractFile) content;
|
||||
if (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS
|
||||
|| file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) {
|
||||
attributes.add(new BlackboardAttribute(KEYWORD_SEARCH_DOCUMENT_ID, MODULE_NAME, hit.getSolrDocumentId()));
|
||||
@@ -422,9 +422,10 @@ final class TermsComponentQuery implements KeywordSearchQuery {
|
||||
if (snippet != null) {
|
||||
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet));
|
||||
}
|
||||
if (hit.isArtifactHit()) {
|
||||
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, hit.getArtifact().getArtifactID()));
|
||||
}
|
||||
|
||||
hit.getArtifactID().ifPresent(
|
||||
artifactID -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID))
|
||||
);
|
||||
|
||||
// TermsComponentQuery is now being used exclusively for substring searches.
|
||||
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.SUBSTRING.ordinal()));
|
||||
@@ -472,12 +473,12 @@ final class TermsComponentQuery implements KeywordSearchQuery {
|
||||
* Creates an attribute of the the given type to the given artifact with a
|
||||
* value parsed from the snippet for a credit account number hit.
|
||||
*
|
||||
* @param attributesMap A map of artifact attribute objects, used to avoid
|
||||
* creating duplicate attributes.
|
||||
* @param attrType The type of attribute to create.
|
||||
* @param groupName The group name of the regular expression that was
|
||||
* used to parse the attribute data.
|
||||
* @param matcher A matcher for the snippet.
|
||||
* @param attributeMap A map of artifact attribute objects, used to avoid
|
||||
* creating duplicate attributes.
|
||||
* @param attrType The type of attribute to create.
|
||||
* @param groupName The group name of the regular expression that was
|
||||
* used to parse the attribute data.
|
||||
* @param matcher A matcher for the snippet.
|
||||
*/
|
||||
static private void addAttributeIfNotAlreadyCaptured(Map<BlackboardAttribute.Type, BlackboardAttribute> attributeMap, ATTRIBUTE_TYPE attrType, String groupName, Matcher matcher) {
|
||||
BlackboardAttribute.Type type = new BlackboardAttribute.Type(attrType);
|
||||
|
||||
Reference in New Issue
Block a user