mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-14 17:06:16 +00:00
Merge branch 'develop' of https://github.com/sleuthkit/autopsy into 6305-UpdateDiscoveryUI
This commit is contained in:
commit
6b66ef36d5
@ -98,6 +98,8 @@
|
||||
tofile="${ext.dir}/sleuthkit-${TSK_VERSION}.jar"/>
|
||||
<copy file="${env.TSK_HOME}/bindings/java/lib/sqlite-jdbc-3.25.2.jar"
|
||||
tofile="${ext.dir}/sqlite-jdbc-3.25.2.jar"/>
|
||||
<copy file="${env.TSK_HOME}/bindings/java/lib/postgresql-9.4.1211.jre7.jar"
|
||||
tofile="${ext.dir}/postgresql-9.4.1211.jre7.jar"/>
|
||||
<copy file="${env.TSK_HOME}/bindings/java/lib/mchange-commons-java-0.2.9.jar"
|
||||
tofile="${ext.dir}/mchange-commons-java-0.2.9.jar"/>
|
||||
<copy file="${env.TSK_HOME}/bindings/java/lib/c3p0-0.9.5.jar"
|
||||
|
@ -1563,11 +1563,12 @@ public class Case {
|
||||
*
|
||||
* This should not be called from the event dispatch thread (EDT)
|
||||
*
|
||||
* @param newTag new ContentTag added
|
||||
* @param deletedTag Removed ContentTag
|
||||
* @param newTag The added ContentTag.
|
||||
* @param deletedTagList List of ContentTags that were removed as a result
|
||||
* of the addition of newTag.
|
||||
*/
|
||||
public void notifyContentTagAdded(ContentTag newTag, ContentTag deletedTag) {
|
||||
eventPublisher.publish(new ContentTagAddedEvent(newTag, deletedTag));
|
||||
public void notifyContentTagAdded(ContentTag newTag, List<ContentTag> deletedTagList) {
|
||||
eventPublisher.publish(new ContentTagAddedEvent(newTag, deletedTagList));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1627,11 +1628,12 @@ public class Case {
|
||||
*
|
||||
* This should not be called from the event dispatch thread (EDT)
|
||||
*
|
||||
* @param newTag new BlackboardArtifactTag added
|
||||
* @param removedTag The BlackboardArtifactTag that was removed.
|
||||
* @param newTag The added ContentTag.
|
||||
* @param removedTagList List of ContentTags that were removed as a result
|
||||
* of the addition of newTag.
|
||||
*/
|
||||
public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, BlackboardArtifactTag removedTag) {
|
||||
eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag, removedTag));
|
||||
public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List<BlackboardArtifactTag> removedTagList) {
|
||||
eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag, removedTagList));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,6 +19,8 @@
|
||||
package org.sleuthkit.autopsy.casemodule.events;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
@ -30,7 +32,7 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
* Event sent when a black board artifact tag is added.
|
||||
*/
|
||||
@Immutable
|
||||
public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent<BlackboardArtifactTag> implements Serializable {
|
||||
public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent<BlackboardArtifactTag, DeletedBlackboardArtifactTagInfo> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ -38,8 +40,8 @@ public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent<BlackboardArt
|
||||
super(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString(), newTag);
|
||||
}
|
||||
|
||||
public BlackBoardArtifactTagAddedEvent(BlackboardArtifactTag newTag, BlackboardArtifactTag removedTag) {
|
||||
super(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString(), newTag, (removedTag != null ? new DeletedBlackboardArtifactTagInfo(removedTag) : null));
|
||||
public BlackBoardArtifactTagAddedEvent(BlackboardArtifactTag newTag, List<BlackboardArtifactTag> removedTagList) {
|
||||
super(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString(), newTag, (removedTagList != null ? getDeletedInfo(removedTagList) : null));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,4 +56,24 @@ public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent<BlackboardArt
|
||||
BlackboardArtifactTag getTagByID() throws NoCurrentCaseException, TskCoreException {
|
||||
return Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagByTagID(getTagID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a list of DeletedContentTagInfo objects from a list of
|
||||
* BlackboardArtifactTags.
|
||||
*
|
||||
* @param deletedTagList List of deleted ContentTags.
|
||||
*
|
||||
* @return List of DeletedContentTagInfo objects or empty list if
|
||||
* deletedTagList was empty or null.
|
||||
*/
|
||||
private static List<DeletedBlackboardArtifactTagInfo> getDeletedInfo(List<BlackboardArtifactTag> deletedTagList) {
|
||||
List<DeletedBlackboardArtifactTagInfo> deletedInfoList = new ArrayList<>();
|
||||
if (deletedTagList != null) {
|
||||
for (BlackboardArtifactTag tag : deletedTagList) {
|
||||
deletedInfoList.add(new DeletedBlackboardArtifactTagInfo(tag));
|
||||
}
|
||||
}
|
||||
|
||||
return deletedInfoList;
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@
|
||||
package org.sleuthkit.autopsy.casemodule.events;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
@ -30,7 +32,7 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
* An event that is fired when a ContentTag is added.
|
||||
*/
|
||||
@Immutable
|
||||
public class ContentTagAddedEvent extends TagAddedEvent<ContentTag> implements Serializable {
|
||||
public class ContentTagAddedEvent extends TagAddedEvent<ContentTag, DeletedContentTagInfo> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ -38,8 +40,8 @@ public class ContentTagAddedEvent extends TagAddedEvent<ContentTag> implements S
|
||||
super(Case.Events.CONTENT_TAG_ADDED.toString(), newTag);
|
||||
}
|
||||
|
||||
public ContentTagAddedEvent(ContentTag newTag, ContentTag deletedTag) {
|
||||
super(Case.Events.CONTENT_TAG_ADDED.toString(), newTag, (deletedTag != null ? new DeletedContentTagInfo(deletedTag) : null));
|
||||
public ContentTagAddedEvent(ContentTag newTag, List<ContentTag> deletedTagList) {
|
||||
super(Case.Events.CONTENT_TAG_ADDED.toString(), newTag, getDeletedInfo(deletedTagList));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,7 +52,26 @@ public class ContentTagAddedEvent extends TagAddedEvent<ContentTag> implements S
|
||||
* @throws NoCurrentCaseException
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
@Override
|
||||
ContentTag getTagByID() throws NoCurrentCaseException, TskCoreException {
|
||||
return Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagByTagID(getTagID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a list of DeletedContentTagInfo objects from a list of ContentTags.
|
||||
*
|
||||
* @param deletedTagList List of deleted ContentTags.
|
||||
*
|
||||
* @return List of DeletedContentTagInfo objects or empty list if deletedTagList was empty or null.
|
||||
*/
|
||||
private static List<DeletedContentTagInfo> getDeletedInfo(List<ContentTag> deletedTagList) {
|
||||
List<DeletedContentTagInfo> deletedInfoList = new ArrayList<>();
|
||||
if (deletedTagList != null) {
|
||||
for (ContentTag tag : deletedTagList) {
|
||||
deletedInfoList.add(new DeletedContentTagInfo(tag));
|
||||
}
|
||||
}
|
||||
|
||||
return deletedInfoList;
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@
|
||||
package org.sleuthkit.autopsy.casemodule.events;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.casemodule.events.TagDeletedEvent.DeletedTagInfo;
|
||||
@ -30,7 +32,7 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
/**
|
||||
* Base Class for events that are fired when a Tag is added
|
||||
*/
|
||||
abstract class TagAddedEvent<T extends Tag> extends AutopsyEvent implements Serializable {
|
||||
abstract class TagAddedEvent<T extends Tag, V extends DeletedTagInfo<T>> extends AutopsyEvent implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ -39,6 +41,8 @@ abstract class TagAddedEvent<T extends Tag> extends AutopsyEvent implements Seri
|
||||
* re-loaded from the database in getNewValue()
|
||||
*/
|
||||
private transient T tag;
|
||||
|
||||
private List<V> deletedTagInfoList;
|
||||
|
||||
/**
|
||||
* The id of the tag that was added. This will be used to re-load the
|
||||
@ -50,10 +54,19 @@ abstract class TagAddedEvent<T extends Tag> extends AutopsyEvent implements Seri
|
||||
this(propertyName, addedTag, null);
|
||||
}
|
||||
|
||||
TagAddedEvent(String propertyName, T addedTag, DeletedTagInfo<T> deletedTagInfo) {
|
||||
super(propertyName, deletedTagInfo, null);
|
||||
/**
|
||||
* Construct a TagAddedEvent.
|
||||
*
|
||||
* @param propertyName Name of property changing
|
||||
* @param addedTag Instance of added tag.
|
||||
* @param deletedTagInfoList List of tags deleted as a result of the
|
||||
* addition of addedTag.
|
||||
*/
|
||||
TagAddedEvent(String propertyName, T addedTag, List<V> deletedTagInfoList) {
|
||||
super(propertyName, deletedTagInfoList, null);
|
||||
tag = addedTag;
|
||||
tagID = addedTag.getId();
|
||||
this.deletedTagInfoList = deletedTagInfoList;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,7 +86,7 @@ abstract class TagAddedEvent<T extends Tag> extends AutopsyEvent implements Seri
|
||||
public T getAddedTag() {
|
||||
return getNewValue();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public T getNewValue() {
|
||||
/**
|
||||
@ -95,6 +108,21 @@ abstract class TagAddedEvent<T extends Tag> extends AutopsyEvent implements Seri
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of tags that were removed as a result of the addition
|
||||
* of the T.
|
||||
*
|
||||
* @return A list of removed tags or null if no tags were removed.
|
||||
*/
|
||||
public List<V> getDeletedTags() {
|
||||
return deletedTagInfoList != null ? Collections.unmodifiableList(deletedTagInfoList) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getOldValue() {
|
||||
return getDeletedTags();
|
||||
}
|
||||
|
||||
/**
|
||||
* implementors should override this to lookup the appropriate kind of tag
|
||||
|
@ -80,7 +80,7 @@ final class TagNameDefinition implements Comparable<TagNameDefinition> {
|
||||
PROJECT_VIC_TAG_DEFINITIONS.put(CATEGORY_TWO_NAME, new TagNameDefinition(CATEGORY_TWO_NAME, "", TagName.HTML_COLOR.LIME, TskData.FileKnown.BAD));
|
||||
PROJECT_VIC_TAG_DEFINITIONS.put(CATEGORY_THREE_NAME, new TagNameDefinition(CATEGORY_THREE_NAME, "", TagName.HTML_COLOR.YELLOW, TskData.FileKnown.BAD));
|
||||
PROJECT_VIC_TAG_DEFINITIONS.put(CATEGORY_FOUR_NAME, new TagNameDefinition(CATEGORY_FOUR_NAME, "", TagName.HTML_COLOR.PURPLE, TskData.FileKnown.UNKNOWN));
|
||||
PROJECT_VIC_TAG_DEFINITIONS.put(CATEGORY_FIVE_NAME, new TagNameDefinition(CATEGORY_FIVE_NAME, "", TagName.HTML_COLOR.SILVER, TskData.FileKnown.UNKNOWN));
|
||||
PROJECT_VIC_TAG_DEFINITIONS.put(CATEGORY_FIVE_NAME, new TagNameDefinition(CATEGORY_FIVE_NAME, "", TagName.HTML_COLOR.FUCHSIA, TskData.FileKnown.UNKNOWN));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -481,7 +481,7 @@ public class TagsManager implements Closeable {
|
||||
try {
|
||||
Case currentCase = Case.getCurrentCaseThrows();
|
||||
|
||||
currentCase.notifyContentTagAdded(tagChange.getAddedTag(), tagChange.getRemovedTags().isEmpty() ? null : tagChange.getRemovedTags().get(0));
|
||||
currentCase.notifyContentTagAdded(tagChange.getAddedTag(), tagChange.getRemovedTags().isEmpty() ? null : tagChange.getRemovedTags());
|
||||
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
throw new TskCoreException("Added a tag to a closed case", ex);
|
||||
@ -701,7 +701,7 @@ public class TagsManager implements Closeable {
|
||||
TaggingManager.BlackboardArtifactTagChange tagChange = caseDb.getTaggingManager().addArtifactTag(artifact, tagName, comment);
|
||||
try {
|
||||
Case currentCase = Case.getCurrentCaseThrows();
|
||||
currentCase.notifyBlackBoardArtifactTagAdded(tagChange.getAddedTag(), tagChange.getRemovedTags().isEmpty() ? null : tagChange.getRemovedTags().get(0));
|
||||
currentCase.notifyBlackBoardArtifactTagAdded(tagChange.getAddedTag(), tagChange.getRemovedTags().isEmpty() ? null : tagChange.getRemovedTags());
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
throw new TskCoreException("Added a tag to a closed case", ex);
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Central Repository
|
||||
*
|
||||
* Copyright 2020 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> 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;
|
||||
|
||||
/**
|
||||
* Encapsulates the concept of an examiner.
|
||||
*/
|
||||
final public class CentralRepoExaminer {
|
||||
|
||||
private final long id; // Row id in the examiners table in central repo database.
|
||||
private final String loginName;
|
||||
|
||||
public CentralRepoExaminer(long id, String loginName) {
|
||||
this.id = id;
|
||||
this.loginName = loginName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id.
|
||||
*
|
||||
* @return id
|
||||
*/
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the login name of examiner.
|
||||
*
|
||||
* @return login name
|
||||
*/
|
||||
public String getLoginName() {
|
||||
return this.loginName;
|
||||
}
|
||||
|
||||
}
|
@ -19,6 +19,7 @@
|
||||
package org.sleuthkit.autopsy.centralrepository.datamodel;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
@ -159,6 +160,17 @@ public interface CentralRepository {
|
||||
* @param eamCase The case to update
|
||||
*/
|
||||
void updateCase(CorrelationCase eamCase) throws CentralRepoException;
|
||||
|
||||
/**
|
||||
* Queries the examiner table for the given user name.
|
||||
* Adds a row if the 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.
|
||||
*/
|
||||
CentralRepoExaminer getOrInsertExaminer(String examinerLoginName) throws CentralRepoException;
|
||||
|
||||
/**
|
||||
* Retrieves Central Repo case based on an Autopsy Case
|
||||
@ -806,6 +818,24 @@ public interface CentralRepository {
|
||||
public void processSelectClause(String selectClause, InstanceTableCallback instanceTableCallback) throws CentralRepoException;
|
||||
|
||||
|
||||
/**
|
||||
* Executes an INSERT sql statement on the central repository database.
|
||||
* @param sql INSERT sql to execute.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error.
|
||||
*/
|
||||
void executeInsertSQL(String sql) throws CentralRepoException;
|
||||
|
||||
/**
|
||||
* Executes a SELECT sql statement on the central repository database.
|
||||
*
|
||||
* @param sql SELECT sql to execute.
|
||||
* @param queryCallback Query callback to handle the result of the query.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error.
|
||||
*/
|
||||
void executeSelectSQL(String sql, CentralRepositoryDbQueryCallback queryCallback) throws CentralRepoException;
|
||||
|
||||
/**
|
||||
* Get account type by type name.
|
||||
*
|
||||
@ -815,6 +845,15 @@ public interface CentralRepository {
|
||||
*/
|
||||
CentralRepoAccountType getAccountTypeByName(String accountTypeName) throws CentralRepoException;
|
||||
|
||||
/**
|
||||
* Gets all account types.
|
||||
*
|
||||
* @return Collection of all CR account types in the database.
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
Collection<CentralRepoAccountType> getAllAccountTypes() throws CentralRepoException;
|
||||
|
||||
/**
|
||||
* Get an account from the accounts table matching the given type/ID.
|
||||
* Inserts a row if one doesn't exists.
|
||||
@ -826,6 +865,5 @@ public interface CentralRepository {
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
CentralRepoAccount getOrCreateAccount(CentralRepoAccount.CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Central Repository
|
||||
*
|
||||
* Copyright 2020 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> 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.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* An interface to process the resultset from a Central Repository DB query.
|
||||
* This enables clients of Central Repository to run custom queries and process
|
||||
* the results themselves.
|
||||
*
|
||||
*/
|
||||
interface CentralRepositoryDbQueryCallback {
|
||||
|
||||
/**
|
||||
* Process the resultset from a query.
|
||||
* @param rs ResultSet.
|
||||
*
|
||||
* @throws CentralRepoException In case of an error processing the result set.
|
||||
* @throws SQLException In case of a SQL error in processing the result set.
|
||||
*/
|
||||
void process(ResultSet rs) throws CentralRepoException, SQLException;
|
||||
}
|
@ -77,7 +77,7 @@ public class CorrelationAttributeInstance implements Serializable {
|
||||
) throws CentralRepoException, CorrelationAttributeNormalizationException {
|
||||
this(type, value, -1, eamCase, eamDataSource, filePath, comment, knownStatus, fileObjectId, (long)-1);
|
||||
}
|
||||
CorrelationAttributeInstance(
|
||||
public CorrelationAttributeInstance(
|
||||
Type type,
|
||||
String value,
|
||||
int instanceId,
|
||||
|
@ -18,13 +18,24 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.centralrepository.datamodel;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
|
||||
/**
|
||||
* This class abstracts a persona.
|
||||
*
|
||||
* An examiner may create a persona from an account.
|
||||
*
|
||||
*/
|
||||
class Persona {
|
||||
public class Persona {
|
||||
|
||||
/**
|
||||
* Defines level of confidence in assigning a persona to an account.
|
||||
@ -50,9 +61,19 @@ class Persona {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
public int getLevelId() {
|
||||
return this.level_id;
|
||||
}
|
||||
|
||||
static Confidence fromId(int value) {
|
||||
for (Confidence confidence : Confidence.values()) {
|
||||
if (confidence.getLevelId() == value) {
|
||||
return confidence;
|
||||
}
|
||||
}
|
||||
return Confidence.UNKNOWN;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,9 +100,559 @@ class Persona {
|
||||
return description;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
public int getStatusId() {
|
||||
return this.status_id;
|
||||
}
|
||||
|
||||
static PersonaStatus fromId(int value) {
|
||||
for (PersonaStatus status : PersonaStatus.values()) {
|
||||
if (status.getStatusId() == value) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return PersonaStatus.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
// Persona name to use if no name is specified.
|
||||
private static final String DEFAULT_PERSONA_NAME = "Unknown";
|
||||
|
||||
// primary key in the Personas table in CR database
|
||||
private final long id;
|
||||
private final String uuidStr;
|
||||
private final String name;
|
||||
private final String comment;
|
||||
private final long createdDate;
|
||||
private final long modifiedDate;
|
||||
private final PersonaStatus status;
|
||||
private final CentralRepoExaminer examiner;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getUuidStr() {
|
||||
return uuidStr;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public long getCreatedDate() {
|
||||
return createdDate;
|
||||
}
|
||||
|
||||
public long getModifiedDate() {
|
||||
return modifiedDate;
|
||||
}
|
||||
|
||||
public PersonaStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public CentralRepoExaminer getExaminer() {
|
||||
return examiner;
|
||||
}
|
||||
|
||||
Persona(long id, String uuidStr, String name, String comment, long created_date, long modified_date, PersonaStatus status, CentralRepoExaminer examiner) {
|
||||
this.id = id;
|
||||
this.uuidStr = uuidStr;
|
||||
this.name = name;
|
||||
this.comment = comment;
|
||||
this.createdDate = created_date;
|
||||
this.modifiedDate = modified_date;
|
||||
this.status = status;
|
||||
this.examiner = examiner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Persona and associates the specified account with it.
|
||||
*
|
||||
* @param personaName Persona name.
|
||||
* @param comment Comment to associate with persona, may be null.
|
||||
* @param status Persona status
|
||||
* @param account Account for which the persona is being created.
|
||||
* @param justification Justification for why this account belongs to this
|
||||
* persona, may be null.
|
||||
* @param confidence Confidence level for this association of Persona &
|
||||
* account.
|
||||
*
|
||||
* @return PersonaAccount
|
||||
* @throws CentralRepoException If there is an error creating the Persona.
|
||||
*/
|
||||
public static PersonaAccount createPersonaForAccount(String personaName, String comment, PersonaStatus status, CentralRepoAccount account, String justification, Persona.Confidence confidence) throws CentralRepoException {
|
||||
Persona persona = createPersona(personaName, comment, status);
|
||||
return persona.addAccountToPersona(account, justification, confidence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a row in the Persona tables.
|
||||
*
|
||||
* @param name Persona name, may be null - default name is used in that
|
||||
* case.
|
||||
* @param comment Comment to associate with persona, may be null.
|
||||
* @param status Persona status.
|
||||
*
|
||||
* @return Persona corresponding to the row inserted in the personas table.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in adding a row to
|
||||
* personas table.
|
||||
*/
|
||||
private static Persona createPersona(String name, String comment, PersonaStatus status) throws CentralRepoException {
|
||||
// generate a UUID for the persona
|
||||
String uuidStr = UUID.randomUUID().toString();
|
||||
CentralRepoExaminer examiner = CentralRepository.getInstance().getOrInsertExaminer(System.getProperty("user.name"));
|
||||
|
||||
Instant instant = Instant.now();
|
||||
Long timeStampMillis = instant.toEpochMilli();
|
||||
String insertClause = " INTO personas (uuid, comment, name, created_date, modified_date, status_id, examiner_id ) "
|
||||
+ "VALUES ( '" + uuidStr + "', "
|
||||
+ "'" + ((StringUtils.isBlank(comment) ? "" : SleuthkitCase.escapeSingleQuotes(comment))) + "',"
|
||||
+ "'" + ((StringUtils.isBlank(name) ? DEFAULT_PERSONA_NAME : SleuthkitCase.escapeSingleQuotes(name))) + "',"
|
||||
+ timeStampMillis.toString() + ","
|
||||
+ timeStampMillis.toString() + ","
|
||||
+ status.getStatusId() + ","
|
||||
+ examiner.getId()
|
||||
+ ")";
|
||||
|
||||
CentralRepository.getInstance().executeInsertSQL(insertClause);
|
||||
return getPersonaByUUID(uuidStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates an account with a persona by creating a row in the
|
||||
* PersonaAccounts table.
|
||||
*
|
||||
* @param persona Persona to add the account to.
|
||||
* @param account Account to add to persona.
|
||||
* @param justification Reason for adding the account to persona, may be
|
||||
* null.
|
||||
* @param confidence Confidence level.
|
||||
*
|
||||
* @return PersonaAccount
|
||||
* @throws CentralRepoException If there is an error.
|
||||
*/
|
||||
public PersonaAccount addAccountToPersona(CentralRepoAccount account, String justification, Persona.Confidence confidence) throws CentralRepoException {
|
||||
|
||||
CentralRepoExaminer currentExaminer = CentralRepository.getInstance().getOrInsertExaminer(System.getProperty("user.name"));
|
||||
|
||||
Instant instant = Instant.now();
|
||||
Long timeStampMillis = instant.toEpochMilli();
|
||||
String insertClause = " INTO persona_accounts (persona_id, account_id, justification, confidence_id, date_added, examiner_id ) "
|
||||
+ "VALUES ( "
|
||||
+ this.getId() + ", "
|
||||
+ account.getAccountId() + ", "
|
||||
+ "'" + ((StringUtils.isBlank(justification) ? "" : SleuthkitCase.escapeSingleQuotes(justification))) + "', "
|
||||
+ confidence.getLevelId() + ", "
|
||||
+ timeStampMillis.toString() + ", "
|
||||
+ currentExaminer.getId()
|
||||
+ ")";
|
||||
|
||||
CentralRepository.getInstance().executeInsertSQL(insertClause);
|
||||
return new PersonaAccount(this, account, justification, confidence, timeStampMillis, currentExaminer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to process a Persona query from the persona table.
|
||||
*/
|
||||
private static class PersonaQueryCallback implements CentralRepositoryDbQueryCallback {
|
||||
|
||||
private final Collection<Persona> personaList = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void process(ResultSet rs) throws SQLException {
|
||||
|
||||
while (rs.next()) {
|
||||
CentralRepoExaminer examiner = new CentralRepoExaminer(
|
||||
rs.getInt("examiner_id"),
|
||||
rs.getString("login_name"));
|
||||
|
||||
PersonaStatus status = PersonaStatus.fromId(rs.getInt("status_id"));
|
||||
Persona persona = new Persona(
|
||||
rs.getInt("id"),
|
||||
rs.getString("uuid"),
|
||||
rs.getString("name"),
|
||||
rs.getString("comment"),
|
||||
Long.parseLong(rs.getString("created_date")),
|
||||
Long.parseLong(rs.getString("modified_date")),
|
||||
status,
|
||||
examiner
|
||||
);
|
||||
|
||||
personaList.add(persona);
|
||||
}
|
||||
}
|
||||
|
||||
Collection<Persona> getPersonas() {
|
||||
return Collections.unmodifiableCollection(personaList);
|
||||
}
|
||||
};
|
||||
|
||||
// Partial query string to select from personas table,
|
||||
// just supply the where clause.
|
||||
private static final String PERSONA_QUERY =
|
||||
"SELECT p.id, p.uuid, p.name, p.comment, p.created_date, p.modified_date, p.status_id, p.examiner_id, e.login_name, e.display_name "
|
||||
+ "FROM personas as p "
|
||||
+ "INNER JOIN examiners as e ON e.id = p.examiner_id ";
|
||||
|
||||
|
||||
/**
|
||||
* Gets the row from the Personas table with the given UUID, creates and
|
||||
* returns the Persona from that data.
|
||||
*
|
||||
* @param uuid Persona UUID to match.
|
||||
* @return Persona matching the given UUID, may be null if no match is
|
||||
* found.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in querying the
|
||||
* Personas table.
|
||||
*/
|
||||
private static Persona getPersonaByUUID(String uuid) throws CentralRepoException {
|
||||
|
||||
String queryClause =
|
||||
PERSONA_QUERY
|
||||
+ "WHERE p.uuid = '" + uuid + "'";
|
||||
|
||||
PersonaQueryCallback queryCallback = new PersonaQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
|
||||
Collection<Persona> personas = queryCallback.getPersonas();
|
||||
|
||||
return personas.isEmpty() ? null : personas.iterator().next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rows from the Personas table with matching name.
|
||||
*
|
||||
* @param partialName Name substring to match.
|
||||
* @return Collection of personas matching the given name substring, may be
|
||||
* empty if no match is found.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in querying the
|
||||
* Personas table.
|
||||
*/
|
||||
public static Collection<Persona> getPersonaByName(String partialName) throws CentralRepoException {
|
||||
|
||||
String queryClause = PERSONA_QUERY
|
||||
+ "WHERE LOWER(p.name) LIKE " + "LOWER('%" + partialName + "%')" ;
|
||||
|
||||
PersonaQueryCallback queryCallback = new PersonaQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
|
||||
return queryCallback.getPersonas();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an alias for the Persona.
|
||||
*
|
||||
* @param alias Alias name.
|
||||
* @param justification Reason for assigning the alias, may be null.
|
||||
* @param confidence Confidence level.
|
||||
*
|
||||
* @return PersonaAlias
|
||||
* @throws CentralRepoException If there is an error in creating the alias.
|
||||
*/
|
||||
public PersonaAlias addAlias(String alias, String justification, Persona.Confidence confidence) throws CentralRepoException {
|
||||
return PersonaAlias.addPersonaAlias(this, alias, justification, confidence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all aliases for the persona.
|
||||
*
|
||||
* @return A collection of aliases, may be empty.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in retrieving aliases.
|
||||
*/
|
||||
public Collection<PersonaAlias> getAliases() throws CentralRepoException {
|
||||
return PersonaAlias.getPersonaAliases(this.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds specified metadata to the persona.
|
||||
*
|
||||
* @param name Metadata name.
|
||||
* @param value Metadata value.
|
||||
* @param justification Reason for adding the metadata, may be null.
|
||||
* @param confidence Confidence level.
|
||||
*
|
||||
* @return PersonaMetadata
|
||||
* @throws CentralRepoException If there is an error in adding metadata.
|
||||
*/
|
||||
public PersonaMetadata addMetadata(String name, String value, String justification, Persona.Confidence confidence) throws CentralRepoException {
|
||||
return PersonaMetadata.addPersonaMetadata(this.getId(), name, value, justification, confidence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all metadata for the persona.
|
||||
*
|
||||
* @return A collection of metadata, may be empty.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in retrieving aliases.
|
||||
*/
|
||||
public Collection<PersonaMetadata> getMetadata() throws CentralRepoException {
|
||||
return PersonaMetadata.getPersonaMetadata(this.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the Accounts for the Persona.
|
||||
*
|
||||
* @return Collection of PersonaAccounts, may be empty.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in getting the
|
||||
* persona_account.
|
||||
*/
|
||||
public Collection<PersonaAccount> getPersonaAccounts() throws CentralRepoException {
|
||||
return PersonaAccount.getPersonaAccountsForPersona(this.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to process a query that gets cases for account instances of an
|
||||
* account
|
||||
*/
|
||||
private static class CaseForAccountInstanceQueryCallback implements CentralRepositoryDbQueryCallback {
|
||||
|
||||
Collection<CorrelationCase> correlationCases = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void process(ResultSet resultSet) throws CentralRepoException, SQLException {
|
||||
|
||||
while (resultSet.next()) {
|
||||
// get Case for case_id
|
||||
CorrelationCase correlationCase = CentralRepository.getInstance().getCaseById(resultSet.getInt("case_id"));
|
||||
correlationCases.add(correlationCase);
|
||||
}
|
||||
}
|
||||
|
||||
Collection<CorrelationCase> getCases() {
|
||||
return Collections.unmodifiableCollection(correlationCases);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a list of cases that the persona appears in.
|
||||
*
|
||||
* @return Collection of cases that the persona appears in, may be empty.
|
||||
* @throws CentralRepoException If there is an error in getting the cases
|
||||
* from the database.
|
||||
*/
|
||||
public Collection<CorrelationCase> getCases() throws CentralRepoException {
|
||||
|
||||
Collection<CorrelationCase> casesForPersona = new ArrayList<>();
|
||||
|
||||
// get all accounts for this persona
|
||||
Collection<CentralRepoAccount> accounts = PersonaAccount.getAccountsForPersona(this.getId());
|
||||
for (CentralRepoAccount account : accounts) {
|
||||
int corrTypeId = account.getAccountType().getCorrelationTypeId();
|
||||
CorrelationAttributeInstance.Type correlationType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId);
|
||||
|
||||
String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(correlationType);
|
||||
String querySql = "SELECT DISTINCT case_id FROM " + tableName
|
||||
+ " WHERE account_id = " + account.getAccountId();
|
||||
|
||||
CaseForAccountInstanceQueryCallback queryCallback = new CaseForAccountInstanceQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(querySql, queryCallback);
|
||||
|
||||
// Add any cases that aren't already on the list.
|
||||
for (CorrelationCase corrCase : queryCallback.getCases()) {
|
||||
if (!casesForPersona.stream().anyMatch(p -> p.getCaseUUID().equalsIgnoreCase(corrCase.getCaseUUID()))) {
|
||||
casesForPersona.add(corrCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return casesForPersona;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to process a query that gets data source for account instances
|
||||
* of an account
|
||||
*/
|
||||
private static class DatasourceForAccountInstanceQueryCallback implements CentralRepositoryDbQueryCallback {
|
||||
|
||||
Collection<CorrelationDataSource> correlationDataSources = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void process(ResultSet resultSet) throws CentralRepoException, SQLException {
|
||||
|
||||
while (resultSet.next()) {
|
||||
// get Case for case_id
|
||||
|
||||
CorrelationCase correlationCase = CentralRepository.getInstance().getCaseById(resultSet.getInt("case_id"));
|
||||
CorrelationDataSource correlationDatasource = CentralRepository.getInstance().getDataSourceById(correlationCase, resultSet.getInt("data_source_id"));
|
||||
|
||||
// Add data source to list if not already on it.
|
||||
if (!correlationDataSources.stream().anyMatch(p -> Objects.equals(p.getDataSourceObjectID(), correlationDatasource.getDataSourceObjectID()))) {
|
||||
correlationDataSources.add(correlationDatasource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collection<CorrelationDataSource> getDataSources() {
|
||||
return Collections.unmodifiableCollection(correlationDataSources);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets all data sources that the persona appears in.
|
||||
*
|
||||
* @return Collection of data sources that the persona appears in, may be
|
||||
* empty.
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
public Collection<CorrelationDataSource> getDataSources() throws CentralRepoException {
|
||||
Collection<CorrelationDataSource> correlationDataSources = new ArrayList<>();
|
||||
|
||||
Collection<CentralRepoAccount> accounts = PersonaAccount.getAccountsForPersona(this.getId());
|
||||
for (CentralRepoAccount account : accounts) {
|
||||
int corrTypeId = account.getAccountType().getCorrelationTypeId();
|
||||
CorrelationAttributeInstance.Type correlationType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId);
|
||||
|
||||
String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(correlationType);
|
||||
String querySql = "SELECT case_id, data_source_id FROM " + tableName
|
||||
+ " WHERE account_id = " + account.getAccountId();
|
||||
|
||||
DatasourceForAccountInstanceQueryCallback queryCallback = new DatasourceForAccountInstanceQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(querySql, queryCallback);
|
||||
|
||||
// Add any data sources that aren't already on the list.
|
||||
for (CorrelationDataSource correlationDatasource : queryCallback.getDataSources()) {
|
||||
if (!correlationDataSources.stream().anyMatch(p -> Objects.equals(p.getDataSourceObjectID(), correlationDatasource.getDataSourceObjectID()))) {
|
||||
correlationDataSources.add(correlationDatasource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return correlationDataSources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to process a query that gets Personas for a case/datasource.
|
||||
*/
|
||||
private static class PersonaFromAccountInstanceQueryCallback implements CentralRepositoryDbQueryCallback {
|
||||
|
||||
Collection<Persona> personasList = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void process(ResultSet resultSet) throws CentralRepoException, SQLException {
|
||||
|
||||
while (resultSet.next()) {
|
||||
|
||||
// examiner that created the persona
|
||||
CentralRepoExaminer personaExaminer = new CentralRepoExaminer(
|
||||
resultSet.getInt("persona_examiner_id"),
|
||||
resultSet.getString("persona_examiner_login_name"));
|
||||
|
||||
// create persona
|
||||
PersonaStatus status = PersonaStatus.fromId(resultSet.getInt("status_id"));
|
||||
Persona persona = new Persona(
|
||||
resultSet.getInt("persona_id"),
|
||||
resultSet.getString("uuid"),
|
||||
resultSet.getString("name"),
|
||||
resultSet.getString("comment"),
|
||||
Long.parseLong(resultSet.getString("created_date")),
|
||||
Long.parseLong(resultSet.getString("modified_date")),
|
||||
status,
|
||||
personaExaminer
|
||||
);
|
||||
|
||||
personasList.add(persona);
|
||||
}
|
||||
}
|
||||
|
||||
Collection<Persona> getPersonasList() {
|
||||
return Collections.unmodifiableCollection(personasList);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a query string for selecting personas for a case/datasource from
|
||||
* the X_instance table for the given account type.
|
||||
*
|
||||
* @param crAccountType Account type to generate the query string for.
|
||||
* @return Query substring.
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
private static String getPersonaFromInstanceTableQueryTemplate(CentralRepoAccount.CentralRepoAccountType crAccountType) throws CentralRepoException {
|
||||
|
||||
int corrTypeId = crAccountType.getCorrelationTypeId();
|
||||
CorrelationAttributeInstance.Type correlationType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId);
|
||||
|
||||
String instanceTableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(correlationType);
|
||||
return "SELECT " + instanceTableName + ".account_id, case_id, data_source_id, "
|
||||
+ " personas.id as persona_id, personas.uuid, personas.name, personas.comment, personas.created_date, personas.modified_date, personas.status_id, "
|
||||
+ " personas.examiner_id as persona_examiner_id, persona_examiner.login_name as persona_examiner_login_name, persona_examiner.display_name as persona_examiner_display_name "
|
||||
+ " FROM " + instanceTableName
|
||||
+ " JOIN persona_accounts as pa on pa.account_id = " + instanceTableName + ".account_id"
|
||||
+ " JOIN personas as personas on personas.id = pa.persona_id"
|
||||
+ " JOIN examiners as persona_examiner ON persona_examiner.id = personas.examiner_id ";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the persona for a given case.
|
||||
*
|
||||
* @param correlationCase Case to look the persona in.
|
||||
*
|
||||
* @return Collection of personas, may be empty.
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
public static Collection<Persona> getPersonasForCase(CorrelationCase correlationCase) throws CentralRepoException {
|
||||
Collection<Persona> personaList = new ArrayList<>();
|
||||
|
||||
Collection<CentralRepoAccount.CentralRepoAccountType> accountTypes = CentralRepository.getInstance().getAllAccountTypes();
|
||||
for (CentralRepoAccount.CentralRepoAccountType crAccountType : accountTypes) {
|
||||
|
||||
String querySql = getPersonaFromInstanceTableQueryTemplate(crAccountType)
|
||||
+ " WHERE case_id = " + correlationCase.getID();
|
||||
|
||||
PersonaFromAccountInstanceQueryCallback queryCallback = new PersonaFromAccountInstanceQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(querySql, queryCallback);
|
||||
|
||||
// Add persona that aren't already on the list.
|
||||
for (Persona persona : queryCallback.getPersonasList()) {
|
||||
if (!personaList.stream().anyMatch(p -> Objects.equals(p.getUuidStr(), persona.getUuidStr()))) {
|
||||
personaList.add(persona);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return personaList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the persona for a given data source.
|
||||
*
|
||||
* @param CorrelationDataSource Data source to look the persona in.
|
||||
*
|
||||
* @return Collection of personas, may be empty.
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
public static Collection<Persona> getPersonasForDataSource(CorrelationDataSource dataSource) throws CentralRepoException {
|
||||
Collection<Persona> personaList = new ArrayList<>();
|
||||
|
||||
Collection<CentralRepoAccount.CentralRepoAccountType> accountTypes = CentralRepository.getInstance().getAllAccountTypes();
|
||||
for (CentralRepoAccount.CentralRepoAccountType crAccountType : accountTypes) {
|
||||
|
||||
String querySql = getPersonaFromInstanceTableQueryTemplate(crAccountType)
|
||||
+ " WHERE data_source_id = " + dataSource.getID();
|
||||
|
||||
PersonaFromAccountInstanceQueryCallback queryCallback = new PersonaFromAccountInstanceQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(querySql, queryCallback);
|
||||
|
||||
// Add persona that aren't already on the list.
|
||||
for (Persona persona : queryCallback.getPersonasList()) {
|
||||
if (!personaList.stream().anyMatch(p -> Objects.equals(p.getUuidStr(), persona.getUuidStr()))) {
|
||||
personaList.add(persona);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return personaList;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Central Repository
|
||||
*
|
||||
* Copyright 2020 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> 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.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* This class represents an association between a Persona and an Account.
|
||||
*
|
||||
* A Persona has at least one, possibly more, accounts associated with it.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class PersonaAccount {
|
||||
|
||||
private final Persona persona;
|
||||
private final CentralRepoAccount account;
|
||||
private final String justification;
|
||||
private final Persona.Confidence confidence;
|
||||
private final long dateAdded;
|
||||
private final CentralRepoExaminer examiner;
|
||||
|
||||
public PersonaAccount(Persona persona, CentralRepoAccount account, String justification, Persona.Confidence confidence, long dateAdded, CentralRepoExaminer examiner) {
|
||||
this.persona = persona;
|
||||
this.account = account;
|
||||
this.justification = justification;
|
||||
this.confidence = confidence;
|
||||
this.dateAdded = dateAdded;
|
||||
this.examiner = examiner;
|
||||
}
|
||||
|
||||
public Persona getPersona() {
|
||||
return persona;
|
||||
}
|
||||
|
||||
public CentralRepoAccount getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public String getJustification() {
|
||||
return justification;
|
||||
}
|
||||
|
||||
public Persona.Confidence getConfidence() {
|
||||
return confidence;
|
||||
}
|
||||
|
||||
public long getDateAdded() {
|
||||
return dateAdded;
|
||||
}
|
||||
|
||||
public CentralRepoExaminer getExaminer() {
|
||||
return examiner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to process a Persona Accounts query.
|
||||
*/
|
||||
private static class PersonaAccountsQueryCallback implements CentralRepositoryDbQueryCallback {
|
||||
|
||||
Collection<PersonaAccount> personaAccountsList = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void process(ResultSet rs) throws CentralRepoException, SQLException {
|
||||
|
||||
while (rs.next()) {
|
||||
// examiner that created the persona/account association
|
||||
CentralRepoExaminer paExaminer = new CentralRepoExaminer(
|
||||
rs.getInt("pa_examiner_id"),
|
||||
rs.getString("pa_examiner_login_name"));
|
||||
|
||||
// examiner that created the persona
|
||||
CentralRepoExaminer personaExaminer = new CentralRepoExaminer(
|
||||
rs.getInt("persona_examiner_id"),
|
||||
rs.getString("persona_examiner_login_name"));
|
||||
|
||||
// create persona
|
||||
Persona.PersonaStatus status = Persona.PersonaStatus.fromId(rs.getInt("status_id"));
|
||||
Persona persona = new Persona(
|
||||
rs.getInt("persona_id"),
|
||||
rs.getString("uuid"),
|
||||
rs.getString("name"),
|
||||
rs.getString("comment"),
|
||||
Long.parseLong(rs.getString("created_date")),
|
||||
Long.parseLong(rs.getString("modified_date")),
|
||||
status,
|
||||
personaExaminer
|
||||
);
|
||||
|
||||
// create account
|
||||
CentralRepoAccount.CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(rs.getString("type_name"));
|
||||
CentralRepoAccount account = new CentralRepoAccount(
|
||||
rs.getInt("account_id"),
|
||||
crAccountType,
|
||||
rs.getString("account_unique_identifier"));
|
||||
|
||||
// create persona account
|
||||
PersonaAccount personaAccount = new PersonaAccount(persona, account,
|
||||
rs.getString("justification"),
|
||||
Persona.Confidence.fromId(rs.getInt("confidence_id")),
|
||||
Long.parseLong(rs.getString("date_added")),
|
||||
paExaminer);
|
||||
|
||||
personaAccountsList.add(personaAccount);
|
||||
}
|
||||
}
|
||||
|
||||
Collection<PersonaAccount> getPersonaAccountsList() {
|
||||
return Collections.unmodifiableCollection(personaAccountsList);
|
||||
}
|
||||
};
|
||||
|
||||
// Query clause to select from persona_accounts table to create PersonaAccount(s)
|
||||
private static final String PERSONA_ACCOUNTS_QUERY_CALUSE = "SELECT justification, confidence_id, date_added, persona_accounts.examiner_id as pa_examiner_id, pa_examiner.login_name as pa_examiner_login_name, pa_examiner.display_name as pa_examiner_display_name,"
|
||||
+ " personas.id as persona_id, personas.uuid, personas.name, personas.comment, personas.created_date, personas.modified_date, personas.status_id, "
|
||||
+ " personas.examiner_id as persona_examiner_id, persona_examiner.login_name as persona_examiner_login_name, persona_examiner.display_name as persona_examiner_display_name, "
|
||||
+ " accounts.id as account_id, account_type_id, account_unique_identifier,"
|
||||
+ " account_types.type_name as type_name "
|
||||
+ " FROM persona_accounts as persona_accounts "
|
||||
+ " JOIN personas as personas on persona_accounts.persona_id = personas.id "
|
||||
+ " JOIN accounts as accounts on persona_accounts.account_id = accounts.id "
|
||||
+ " JOIN account_types as account_types on accounts.account_type_id = account_types.id "
|
||||
+ " JOIN examiners as pa_examiner ON pa_examiner.id = persona_accounts.examiner_id "
|
||||
+ " JOIN examiners as persona_examiner ON persona_examiner.id = personas.examiner_id ";
|
||||
|
||||
|
||||
/**
|
||||
* Gets all the Accounts for the specified Persona.
|
||||
*
|
||||
* @param personaId Id of persona for which to get the accounts for.
|
||||
* @return Collection of PersonaAccounts, may be empty.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in getting the
|
||||
* persona_account.
|
||||
*/
|
||||
static Collection<PersonaAccount> getPersonaAccountsForPersona(long personaId) throws CentralRepoException {
|
||||
String queryClause = PERSONA_ACCOUNTS_QUERY_CALUSE
|
||||
+ " WHERE persona_accounts.persona_id = " + personaId;
|
||||
|
||||
PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
|
||||
return queryCallback.getPersonaAccountsList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the Persona for the specified Account.
|
||||
*
|
||||
* @param accountId Id of account for which to get the Personas for.
|
||||
* @return Collection of PersonaAccounts. may be empty.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in getting the
|
||||
* persona_account.
|
||||
*/
|
||||
public static Collection<PersonaAccount> getPersonaAccountsForAccount(long accountId) throws CentralRepoException {
|
||||
String queryClause = PERSONA_ACCOUNTS_QUERY_CALUSE
|
||||
+ " WHERE persona_accounts.account_id = " + accountId;
|
||||
|
||||
PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
|
||||
return queryCallback.getPersonaAccountsList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the Persona associated with all the accounts matching the given
|
||||
* account identifier substring.
|
||||
*
|
||||
* @param accountIdentifierSubstring Account identifier substring to search
|
||||
* for.
|
||||
* @return Collection of PersonaAccounts. may be empty.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in getting the
|
||||
* persona_account.
|
||||
*/
|
||||
public static Collection<PersonaAccount> getPersonaAccountsForAccountIdentifier(String accountIdentifierSubstring) throws CentralRepoException {
|
||||
String queryClause = PERSONA_ACCOUNTS_QUERY_CALUSE
|
||||
+ " WHERE LOWER(accounts.account_unique_identifier) LIKE LOWER('%" + accountIdentifierSubstring + "%')";
|
||||
|
||||
PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
|
||||
return queryCallback.getPersonaAccountsList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to process a query that gets all accounts belonging to a
|
||||
* persona.
|
||||
*/
|
||||
private static class AccountsForPersonaQueryCallback implements CentralRepositoryDbQueryCallback {
|
||||
|
||||
Collection<CentralRepoAccount> accountsList = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void process(ResultSet rs) throws CentralRepoException, SQLException {
|
||||
|
||||
while (rs.next()) {
|
||||
|
||||
// create account
|
||||
CentralRepoAccount.CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(rs.getString("type_name"));
|
||||
CentralRepoAccount account = new CentralRepoAccount(
|
||||
rs.getInt("account_id"),
|
||||
crAccountType,
|
||||
rs.getString("account_unique_identifier"));
|
||||
|
||||
accountsList.add(account);
|
||||
}
|
||||
}
|
||||
|
||||
Collection<CentralRepoAccount> getAccountsList() {
|
||||
return Collections.unmodifiableCollection(accountsList);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all accounts associated with a persona.
|
||||
*
|
||||
* @param personaId Id of the persona to look for.
|
||||
*
|
||||
* @return Collection of all accounts associated with the given persona, may
|
||||
* be empty.
|
||||
* @throws CentralRepoException If there is an error in getting the accounts.
|
||||
*/
|
||||
static Collection<CentralRepoAccount> getAccountsForPersona(long personaId) throws CentralRepoException {
|
||||
String queryClause = "SELECT account_id, "
|
||||
+ " accounts.account_type_id as account_type_id, accounts.account_unique_identifier as account_unique_identifier,"
|
||||
+ " account_types.type_name as type_name "
|
||||
+ " FROM persona_accounts "
|
||||
+ " JOIN accounts as accounts on persona_accounts.account_id = accounts.id "
|
||||
+ " JOIN account_types as account_types on accounts.account_type_id = account_types.id "
|
||||
+ " WHERE persona_accounts.persona_id = " + personaId;
|
||||
|
||||
AccountsForPersonaQueryCallback queryCallback = new AccountsForPersonaQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
|
||||
return queryCallback.getAccountsList();
|
||||
}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Central Repository
|
||||
*
|
||||
* Copyright 2020 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> 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.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
|
||||
/**
|
||||
* This class abstracts an alias assigned to a Persona.
|
||||
* A Persona may have multiple aliases.
|
||||
*
|
||||
*/
|
||||
public class PersonaAlias {
|
||||
|
||||
private final long personaId;
|
||||
private final String alias;
|
||||
private final String justification;
|
||||
private final Persona.Confidence confidence;
|
||||
private final long dateAdded;
|
||||
private final CentralRepoExaminer examiner;
|
||||
|
||||
public long getPersonaId() {
|
||||
return personaId;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public String getJustification() {
|
||||
return justification;
|
||||
}
|
||||
|
||||
public Persona.Confidence getConfidence() {
|
||||
return confidence;
|
||||
}
|
||||
|
||||
public long getDateAadded() {
|
||||
return dateAdded;
|
||||
}
|
||||
|
||||
public CentralRepoExaminer getExaminer() {
|
||||
return examiner;
|
||||
}
|
||||
|
||||
public PersonaAlias(long personaId, String alias, String justification, Persona.Confidence confidence, long dateAdded, CentralRepoExaminer examiner) {
|
||||
this.personaId = personaId;
|
||||
this.alias = alias;
|
||||
this.justification = justification;
|
||||
this.confidence = confidence;
|
||||
this.dateAdded = dateAdded;
|
||||
this.examiner = examiner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an alias for the specified Persona.
|
||||
*
|
||||
* @param persona Persona for which the alias is being added.
|
||||
* @param alias Alias name.
|
||||
* @param justification Reason for assigning the alias, may be null.
|
||||
* @param confidence Confidence level.
|
||||
*
|
||||
* @return PersonaAlias
|
||||
* @throws CentralRepoException If there is an error in creating the alias.
|
||||
*/
|
||||
static PersonaAlias addPersonaAlias(Persona persona, String alias, String justification, Persona.Confidence confidence) throws CentralRepoException {
|
||||
|
||||
CentralRepoExaminer examiner = CentralRepository.getInstance().getOrInsertExaminer(System.getProperty("user.name"));
|
||||
|
||||
Instant instant = Instant.now();
|
||||
Long timeStampMillis = instant.toEpochMilli();
|
||||
|
||||
String insertClause = " INTO persona_alias (persona_id, alias, justification, confidence_id, date_added, examiner_id ) "
|
||||
+ "VALUES ( "
|
||||
+ persona.getId() + ", "
|
||||
+ "'" + alias + "', "
|
||||
+ "'" + ((StringUtils.isBlank(justification) ? "" : SleuthkitCase.escapeSingleQuotes(justification))) + "', "
|
||||
+ confidence.getLevelId() + ", "
|
||||
+ timeStampMillis.toString() + ", "
|
||||
+ examiner.getId()
|
||||
+ ")";
|
||||
|
||||
CentralRepository.getInstance().executeInsertSQL(insertClause);
|
||||
return new PersonaAlias(persona.getId(), alias, justification, confidence, timeStampMillis, examiner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to process a Persona aliases query.
|
||||
*/
|
||||
static class PersonaAliasesQueryCallback implements CentralRepositoryDbQueryCallback {
|
||||
|
||||
private final Collection<PersonaAlias> personaAliases = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void process(ResultSet rs) throws SQLException {
|
||||
|
||||
while (rs.next()) {
|
||||
CentralRepoExaminer examiner = new CentralRepoExaminer(
|
||||
rs.getInt("examiner_id"),
|
||||
rs.getString("login_name"));
|
||||
|
||||
PersonaAlias alias = new PersonaAlias(
|
||||
rs.getLong("persona_id"),
|
||||
rs.getString("alias"),
|
||||
rs.getString("justification"),
|
||||
Persona.Confidence.fromId(rs.getInt("confidence_id")),
|
||||
Long.parseLong(rs.getString("date_added")),
|
||||
examiner);
|
||||
|
||||
personaAliases.add(alias);
|
||||
}
|
||||
}
|
||||
|
||||
Collection<PersonaAlias> getAliases() {
|
||||
return Collections.unmodifiableCollection(personaAliases);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets all aliases for the persona with specified id.
|
||||
*
|
||||
* @param personaId Id of the persona for which to get the aliases.
|
||||
* @return A collection of aliases, may be empty.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in retrieving aliases.
|
||||
*/
|
||||
public static Collection<PersonaAlias> getPersonaAliases(long personaId) throws CentralRepoException {
|
||||
String queryClause = "SELECT pa.id, pa.persona_id, pa.alias, pa.justification, pa.confidence_id, pa.date_added, pa.examiner_id, e.login_name, e.display_name "
|
||||
+ "FROM persona_alias as pa "
|
||||
+ "INNER JOIN examiners as e ON e.id = pa.examiner_id ";
|
||||
|
||||
PersonaAliasesQueryCallback queryCallback = new PersonaAliasesQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
|
||||
return queryCallback.getAliases();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Central Repository
|
||||
*
|
||||
* Copyright 2020 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> 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.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
|
||||
/**
|
||||
* This class abstracts metadata associated with a Persona.
|
||||
* Metadata is in the form of a name/value pair.
|
||||
*
|
||||
* A Persona may have zero or more metadata.
|
||||
*
|
||||
*/
|
||||
public class PersonaMetadata {
|
||||
|
||||
private final long personaId;
|
||||
private final String name;
|
||||
private final String value;
|
||||
private final String justification;
|
||||
private final Persona.Confidence confidence;
|
||||
private final long dateAdded;
|
||||
private final CentralRepoExaminer examiner;
|
||||
|
||||
public long getPersonaId() {
|
||||
return personaId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getJustification() {
|
||||
return justification;
|
||||
}
|
||||
|
||||
public Persona.Confidence getConfidence() {
|
||||
return confidence;
|
||||
}
|
||||
|
||||
public long getDateAdded() {
|
||||
return dateAdded;
|
||||
}
|
||||
|
||||
public CentralRepoExaminer getExaminer() {
|
||||
return examiner;
|
||||
}
|
||||
|
||||
public PersonaMetadata(long personaId, String name, String value, String justification, Persona.Confidence confidence, long dateAdded, CentralRepoExaminer examiner) {
|
||||
this.personaId = personaId;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.justification = justification;
|
||||
this.confidence = confidence;
|
||||
this.dateAdded = dateAdded;
|
||||
this.examiner = examiner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds specified metadata to the given persona.
|
||||
*
|
||||
* @param personaId Id of persona to add metadata for.
|
||||
* @param name Metadata name.
|
||||
* @param value Metadata value.
|
||||
* @param justification Reason for adding the metadata, may be null.
|
||||
* @param confidence Confidence level.
|
||||
*
|
||||
* @return PersonaMetadata
|
||||
* @throws CentralRepoException If there is an error in adding metadata.
|
||||
*/
|
||||
static PersonaMetadata addPersonaMetadata(long personaId, String name, String value, String justification, Persona.Confidence confidence) throws CentralRepoException {
|
||||
|
||||
CentralRepoExaminer examiner = CentralRepository.getInstance().getOrInsertExaminer(System.getProperty("user.name"));
|
||||
|
||||
Instant instant = Instant.now();
|
||||
Long timeStampMillis = instant.toEpochMilli();
|
||||
|
||||
String insertClause = " INTO persona_metadata (persona_id, name, value, justification, confidence_id, date_added, examiner_id ) "
|
||||
+ "VALUES ( "
|
||||
+ personaId + ", "
|
||||
+ "'" + name + "', "
|
||||
+ "'" + value + "', "
|
||||
+ "'" + ((StringUtils.isBlank(justification) ? "" : SleuthkitCase.escapeSingleQuotes(justification))) + "', "
|
||||
+ confidence.getLevelId() + ", "
|
||||
+ timeStampMillis.toString() + ", "
|
||||
+ examiner.getId()
|
||||
+ ")";
|
||||
|
||||
CentralRepository.getInstance().executeInsertSQL(insertClause);
|
||||
return new PersonaMetadata(personaId, name, value, justification, confidence, timeStampMillis, examiner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to process a Persona metadata query.
|
||||
*/
|
||||
private static class PersonaMetadataQueryCallback implements CentralRepositoryDbQueryCallback {
|
||||
|
||||
Collection<PersonaMetadata> personaMetadataList = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void process(ResultSet rs) throws SQLException {
|
||||
|
||||
while (rs.next()) {
|
||||
CentralRepoExaminer examiner = new CentralRepoExaminer(
|
||||
rs.getInt("examiner_id"),
|
||||
rs.getString("login_name"));
|
||||
|
||||
PersonaMetadata metaData = new PersonaMetadata(
|
||||
rs.getLong("persona_id"),
|
||||
rs.getString("name"),
|
||||
rs.getString("value"),
|
||||
rs.getString("justification"),
|
||||
Persona.Confidence.fromId(rs.getInt("confidence_id")),
|
||||
Long.parseLong(rs.getString("date_added")),
|
||||
examiner);
|
||||
|
||||
personaMetadataList.add(metaData);
|
||||
}
|
||||
}
|
||||
|
||||
Collection<PersonaMetadata> getMetadataList() {
|
||||
return Collections.unmodifiableCollection(personaMetadataList);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets all metadata for the persona with specified id.
|
||||
*
|
||||
* @param personaId Id of the persona for which to get the metadata.
|
||||
* @return A collection of metadata, may be empty.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in retrieving aliases.
|
||||
*/
|
||||
static Collection<PersonaMetadata> getPersonaMetadata(long personaId) throws CentralRepoException {
|
||||
String queryClause = "SELECT pmd.id, pmd.persona_id, pmd.name, pmd.value, pmd.justification, pmd.confidence_id, pmd.date_added, pmd.examiner_id, e.login_name, e.display_name "
|
||||
+ "FROM persona_metadata as pmd "
|
||||
+ "INNER JOIN examiners as e ON e.id = pmd.examiner_id ";
|
||||
|
||||
PersonaMetadataQueryCallback queryCallback = new PersonaMetadataQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
|
||||
return queryCallback.getMetadataList();
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -51,6 +51,7 @@ import org.sleuthkit.autopsy.healthmonitor.HealthMonitor;
|
||||
import org.sleuthkit.autopsy.healthmonitor.TimingMetric;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
/**
|
||||
@ -101,6 +102,9 @@ abstract class RdbmsCentralRepo implements CentralRepository {
|
||||
// Update Test code if this changes. It's hard coded there.
|
||||
static final int DEFAULT_BULK_THRESHHOLD = 1000;
|
||||
|
||||
private static final int QUERY_STR_MAX_LEN = 1000;
|
||||
|
||||
|
||||
/**
|
||||
* Connect to the DB and initialize it.
|
||||
*
|
||||
@ -216,7 +220,7 @@ abstract class RdbmsCentralRepo implements CentralRepository {
|
||||
/**
|
||||
* Reset the contents of the caches associated with EamDb results.
|
||||
*/
|
||||
protected final void clearCaches() {
|
||||
public final void clearCaches() {
|
||||
synchronized(typeCache) {
|
||||
typeCache.invalidateAll();
|
||||
isCRTypeCacheInitialized = false;
|
||||
@ -225,6 +229,7 @@ abstract class RdbmsCentralRepo implements CentralRepository {
|
||||
caseCacheById.invalidateAll();
|
||||
dataSourceCacheByDsObjectId.invalidateAll();
|
||||
dataSourceCacheById.invalidateAll();
|
||||
accountsCache.invalidateAll();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1111,6 +1116,31 @@ abstract class RdbmsCentralRepo implements CentralRepository {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<CentralRepoAccountType> getAllAccountTypes() throws CentralRepoException {
|
||||
|
||||
Collection<CentralRepoAccountType> accountTypes = new ArrayList<>();
|
||||
|
||||
String sql = "SELECT * FROM account_types";
|
||||
try ( Connection conn = connect();
|
||||
PreparedStatement preparedStatement = conn.prepareStatement(sql);) {
|
||||
|
||||
|
||||
try (ResultSet resultSet = preparedStatement.executeQuery();) {
|
||||
while (resultSet.next()) {
|
||||
Account.Type acctType = new Account.Type(resultSet.getString("type_name"), resultSet.getString("display_name"));
|
||||
CentralRepoAccountType crAccountType = new CentralRepoAccountType(resultSet.getInt("id"), acctType, resultSet.getInt("correlation_type_id"));
|
||||
|
||||
accountTypes.add(crAccountType);
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
throw new CentralRepoException("Error getting account types from central repository.", ex); // NON-NLS
|
||||
}
|
||||
return accountTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CR account type for the specified type name.
|
||||
*
|
||||
@ -2486,6 +2516,48 @@ abstract class RdbmsCentralRepo implements CentralRepository {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeInsertSQL(String insertClause) throws CentralRepoException {
|
||||
|
||||
if (insertClause == null) {
|
||||
throw new CentralRepoException("Insert SQL is null");
|
||||
}
|
||||
|
||||
String sql = getPlatformSpecificInsertSQL(insertClause);
|
||||
try (Connection conn = connect();
|
||||
PreparedStatement preparedStatement = conn.prepareStatement(sql);) {
|
||||
preparedStatement.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
throw new CentralRepoException(String.format("Error running SQL %s, exception = %s", sql, ex.getMessage()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeSelectSQL(String selectSQL, CentralRepositoryDbQueryCallback queryCallback) throws CentralRepoException {
|
||||
if (queryCallback == null) {
|
||||
throw new CentralRepoException("Query callback is null");
|
||||
}
|
||||
|
||||
if (selectSQL == null) {
|
||||
throw new CentralRepoException("Select SQL is null");
|
||||
}
|
||||
|
||||
StringBuilder sqlSb = new StringBuilder(QUERY_STR_MAX_LEN);
|
||||
if (selectSQL.trim().toUpperCase().startsWith("SELECT") == false) {
|
||||
sqlSb.append("SELECT ");
|
||||
}
|
||||
|
||||
sqlSb.append(selectSQL);
|
||||
|
||||
try (Connection conn = connect();
|
||||
PreparedStatement preparedStatement = conn.prepareStatement(sqlSb.toString());
|
||||
ResultSet resultSet = preparedStatement.executeQuery();) {
|
||||
queryCallback.process(resultSet);
|
||||
} catch (SQLException ex) {
|
||||
throw new CentralRepoException(String.format("Error running SQL %s, exception = %s", selectSQL, ex.getMessage()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CentralRepoOrganization newOrganization(CentralRepoOrganization eamOrg) throws CentralRepoException {
|
||||
if (eamOrg == null) {
|
||||
@ -2624,6 +2696,61 @@ abstract class RdbmsCentralRepo implements CentralRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the examiner table for the given user name.
|
||||
* Adds a row if the 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.
|
||||
*/
|
||||
|
||||
@Override
|
||||
public CentralRepoExaminer getOrInsertExaminer(String examinerLoginName) throws CentralRepoException {
|
||||
|
||||
String querySQL = "SELECT * FROM examiners WHERE login_name = '" + SleuthkitCase.escapeSingleQuotes(examinerLoginName) + "'";
|
||||
try (Connection connection = connect();
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery(querySQL);) {
|
||||
|
||||
if (resultSet.next()) {
|
||||
return new CentralRepoExaminer(resultSet.getLong("id"), resultSet.getString("login_name"));
|
||||
} else {
|
||||
// Could not find this user in the Examiner table, add a row for it.
|
||||
try {
|
||||
String insertSQL;
|
||||
switch (CentralRepoDbManager.getSavedDbChoice().getDbPlatform()) {
|
||||
case POSTGRESQL:
|
||||
insertSQL = "INSERT INTO examiners (login_name) VALUES ('" + SleuthkitCase.escapeSingleQuotes(examinerLoginName) + "')" + getConflictClause(); //NON-NLS
|
||||
break;
|
||||
case SQLITE:
|
||||
insertSQL = "INSERT OR IGNORE INTO examiners (login_name) VALUES ('" + SleuthkitCase.escapeSingleQuotes(examinerLoginName) + "')"; //NON-NLS
|
||||
break;
|
||||
default:
|
||||
throw new CentralRepoException(String.format("Cannot add examiner to currently selected CR database platform %s", CentralRepoDbManager.getSavedDbChoice().getDbPlatform())); //NON-NLS
|
||||
}
|
||||
statement.execute(insertSQL);
|
||||
|
||||
// Query the table again to get the row for the user
|
||||
try (ResultSet resultSet2 = statement.executeQuery(querySQL)) {
|
||||
if (resultSet2.next()) {
|
||||
return new CentralRepoExaminer(resultSet2.getLong("id"), resultSet2.getString("login_name"));
|
||||
} else {
|
||||
throw new CentralRepoException("Error getting examiner for name = " + examinerLoginName);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (SQLException ex) {
|
||||
throw new CentralRepoException("Error inserting row in examiners", ex);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (SQLException ex) {
|
||||
throw new CentralRepoException("Error getting examiner for name = " + examinerLoginName, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing organization.
|
||||
*
|
||||
@ -3489,6 +3616,19 @@ abstract class RdbmsCentralRepo implements CentralRepository {
|
||||
);
|
||||
}
|
||||
|
||||
private String getPlatformSpecificInsertSQL(String sql) throws CentralRepoException {
|
||||
|
||||
switch (CentralRepoDbManager.getSavedDbChoice().getDbPlatform()) {
|
||||
case POSTGRESQL:
|
||||
return "INSERT " + sql + " ON CONFLICT DO NOTHING"; //NON-NLS
|
||||
case SQLITE:
|
||||
return "INSERT OR IGNORE " + sql;
|
||||
|
||||
default:
|
||||
throw new CentralRepoException("Unknown Central Repo DB platform" + CentralRepoDbManager.getSavedDbChoice().getDbPlatform());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a specific column already exists in a specific table
|
||||
*
|
||||
|
@ -811,14 +811,14 @@ public class RdbmsCentralRepoFactory {
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
// populate the confidence table
|
||||
for (Confidence confidence : Persona.Confidence.values()) {
|
||||
String sqlString = "INSERT INTO confidence (confidence_id, description) VALUES ( " + confidence.getLevel() + ", '" + confidence.toString() + "')" //NON-NLS
|
||||
String sqlString = "INSERT INTO confidence (confidence_id, description) VALUES ( " + confidence.getLevelId() + ", '" + confidence.toString() + "')" //NON-NLS
|
||||
+ getOnConflictDoNothingClause(selectedPlatform);
|
||||
stmt.execute(sqlString);
|
||||
}
|
||||
|
||||
// populate the persona_status table
|
||||
for (PersonaStatus status : Persona.PersonaStatus.values()) {
|
||||
String sqlString = "INSERT INTO persona_status (status_id, status) VALUES ( " + status.getStatus() + ", '" + status.toString() + "')" //NON-NLS
|
||||
String sqlString = "INSERT INTO persona_status (status_id, status) VALUES ( " + status.getStatusId() + ", '" + status.toString() + "')" //NON-NLS
|
||||
+ getOnConflictDoNothingClause(selectedPlatform);
|
||||
stmt.execute(sqlString);
|
||||
}
|
||||
|
@ -833,6 +833,27 @@ final class SqliteCentralRepo extends RdbmsCentralRepo {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeInsertSQL(String insertSQL) throws CentralRepoException {
|
||||
try {
|
||||
acquireSharedLock();
|
||||
super.executeInsertSQL(insertSQL);
|
||||
} finally {
|
||||
releaseSharedLock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeSelectSQL(String selectSQL, CentralRepositoryDbQueryCallback queryCallback) throws CentralRepoException {
|
||||
try {
|
||||
acquireSharedLock();
|
||||
super.executeSelectSQL(selectSQL, queryCallback);
|
||||
} finally {
|
||||
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
|
||||
|
@ -37,6 +37,7 @@ 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;
|
||||
@ -62,6 +63,11 @@ 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
|
||||
@ -337,6 +343,94 @@ public class IngestEventsListener {
|
||||
event = evt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically creates personas from all the TSK_CONTACT artifacts
|
||||
* found in a data source.
|
||||
*
|
||||
* @param dataSource Data source that was just analyzed.
|
||||
* @throws TskCoreException If there is any error getting contact
|
||||
* artifacts from case database.
|
||||
* @throws CentralRepoException If there is an error in creating
|
||||
* personas in the Central Repo.
|
||||
*/
|
||||
private void autoGenerateContactPersonas(Content dataSource) throws TskCoreException, CentralRepoException {
|
||||
|
||||
Blackboard blackboard;
|
||||
try {
|
||||
blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
// get all TSK_CONTACT artifacts in this data source.
|
||||
List<BlackboardArtifact> contactArtifacts = blackboard.getArtifacts(TSK_CONTACT.getTypeID(), dataSource.getId());
|
||||
for (BlackboardArtifact artifact : contactArtifacts) {
|
||||
|
||||
BlackboardAttribute nameAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME));
|
||||
String personaName = (nameAttr != null) ? nameAttr.getValueString() : null;
|
||||
|
||||
// Get phone number and email attributes.
|
||||
BlackboardAttribute phoneAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER));
|
||||
BlackboardAttribute homePhoneAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_HOME));
|
||||
BlackboardAttribute mobilePhoneAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_MOBILE));
|
||||
BlackboardAttribute emailAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL));
|
||||
|
||||
Persona persona = personaFromContactAttribute(null, Account.Type.PHONE, phoneAttr, personaName);
|
||||
persona = personaFromContactAttribute(persona, Account.Type.PHONE, homePhoneAttr, personaName);
|
||||
persona = personaFromContactAttribute(persona, Account.Type.PHONE, mobilePhoneAttr, personaName);
|
||||
personaFromContactAttribute(persona, Account.Type.EMAIL, emailAttr, personaName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets central repo account for the given attribute for a TSK_CONTACT
|
||||
* artifact. Associates the given persona with that account. Creates a
|
||||
* Persona, if one isn't provided.
|
||||
*
|
||||
* @param persona Persona to associate with the account. May be null, in
|
||||
* which case a persona is created first.
|
||||
* @param accountType Account type of account to be associated.
|
||||
* @param attribute Attribute form which get the account id.
|
||||
* @param personaName Persona name, if a persona needs to be created.
|
||||
* @return Persona created or associated with the account.
|
||||
*
|
||||
* @throws TskCoreException If there is an error in normalizing the
|
||||
* account id.
|
||||
* @throws CentralRepoException If there is an erorr is getting the
|
||||
* account or associating the persona with it.
|
||||
*/
|
||||
private Persona personaFromContactAttribute(Persona persona, Account.Type accountType, BlackboardAttribute attribute, String personaName) throws CentralRepoException, TskCoreException {
|
||||
|
||||
Persona personaToReturn = persona;
|
||||
if (attribute != null) {
|
||||
|
||||
String accountId = attribute.getValueString();
|
||||
if (CommunicationsUtils.isValidAccountId(accountType, accountId)) {
|
||||
if (accountType == Account.Type.PHONE) {
|
||||
accountId = CommunicationsUtils.normalizePhoneNum(accountId);
|
||||
} else if (accountType == Account.Type.EMAIL) {
|
||||
accountId = CommunicationsUtils.normalizeEmailAddress(accountId);
|
||||
}
|
||||
|
||||
CentralRepoAccount.CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(accountType.getTypeName());
|
||||
CentralRepoAccount crAccount = CentralRepository.getInstance().getOrCreateAccount(crAccountType, accountId);
|
||||
|
||||
PersonaAccount personaAccount;
|
||||
// If persona doesnt exist, create one
|
||||
if (persona == null) {
|
||||
personaAccount = Persona.createPersonaForAccount(personaName, "Auto generated contact persona", Persona.PersonaStatus.UNKNOWN, crAccount, "Found in contact book entry", Persona.Confidence.DERIVED);
|
||||
personaToReturn = personaAccount.getPersona();
|
||||
} else {
|
||||
persona.addAccountToPersona(crAccount, "Found in contact book entry", Persona.Confidence.DERIVED);
|
||||
}
|
||||
}
|
||||
}
|
||||
return personaToReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// clear the tracker to reduce memory usage
|
||||
@ -411,6 +505,8 @@ public class IngestEventsListener {
|
||||
correlationDataSource.setSha256(imageSha256Hash);
|
||||
}
|
||||
}
|
||||
// automatically generate persona from contact artifacts.
|
||||
autoGenerateContactPersonas(dataSource);
|
||||
}
|
||||
} catch (CentralRepoException ex) {
|
||||
LOGGER.log(Level.SEVERE, String.format(
|
||||
|
@ -17,7 +17,10 @@ ContextViewer.messageFrom=From
|
||||
ContextViewer.messageOn=On
|
||||
ContextViewer.messageTo=To
|
||||
ContextViewer.on=Opened at
|
||||
ContextViewer.programExecution=Program Execution:
|
||||
ContextViewer.recentDocs=Recent Documents:
|
||||
ContextViewer.runOn=Program Run On
|
||||
ContextViewer.runUnknown=\ Program Run at unknown time
|
||||
ContextViewer.title=Context
|
||||
ContextViewer.toolTip=Displays context for selected file.
|
||||
ContextViewer.unknown=Opened at unknown time
|
||||
|
@ -334,7 +334,8 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
|
||||
@NbBundle.Messages({
|
||||
"ContextViewer.attachmentSource=Attached to: ",
|
||||
"ContextViewer.downloadSource=Downloaded from: ",
|
||||
"ContextViewer.recentDocs=Recent Documents: "
|
||||
"ContextViewer.recentDocs=Recent Documents: ",
|
||||
"ContextViewer.programExecution=Program Execution: "
|
||||
})
|
||||
private void setSourceFields(BlackboardArtifact associatedArtifact) throws TskCoreException {
|
||||
if (BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() == associatedArtifact.getArtifactTypeID()
|
||||
@ -357,6 +358,11 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
|
||||
javax.swing.JPanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact);
|
||||
contextUsagePanels.add(usagePanel);
|
||||
|
||||
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID() == associatedArtifact.getArtifactTypeID()) {
|
||||
String sourceName = Bundle.ContextViewer_programExecution();
|
||||
String sourceText = programExecArtifactToString(associatedArtifact);
|
||||
javax.swing.JPanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact);
|
||||
contextUsagePanels.add(usagePanel);
|
||||
}
|
||||
}
|
||||
|
||||
@ -416,6 +422,36 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a display string with Program Execution
|
||||
* artifact.
|
||||
*
|
||||
* @param artifact artifact to get doc from.
|
||||
*
|
||||
* @return Display string with download URL and date/time.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"ContextViewer.runOn=Program Run On",
|
||||
"ContextViewer.runUnknown= Program Run at unknown time"
|
||||
})
|
||||
private String programExecArtifactToString(BlackboardArtifact artifact) throws TskCoreException {
|
||||
StringBuilder sb = new StringBuilder(ARTIFACT_STR_MAX_LEN);
|
||||
Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributesMap = getAttributesMap(artifact);
|
||||
|
||||
BlackboardAttribute attribute = attributesMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
|
||||
|
||||
if (BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID() == artifact.getArtifactTypeID()) {
|
||||
if (attribute != null && attribute.getValueLong() > 0) {
|
||||
appendAttributeString(sb, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, attributesMap, Bundle.ContextViewer_runOn());
|
||||
} else {
|
||||
sb.append(Bundle.ContextViewer_runUnknown());
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a abbreviated display string for a message artifact.
|
||||
*
|
||||
|
@ -42,9 +42,9 @@ public final class AutopsyEventPublisher {
|
||||
private static final Logger logger = Logger.getLogger(AutopsyEventPublisher.class.getName());
|
||||
private static final int MAX_REMOTE_EVENT_PUBLISH_TRIES = 1;
|
||||
private final LocalEventPublisher localPublisher; // LocalEventPublisher is thread-safe
|
||||
@GuardedBy("this)")
|
||||
@GuardedBy("this")
|
||||
private RemoteEventPublisher remotePublisher;
|
||||
@GuardedBy("this)")
|
||||
@GuardedBy("this")
|
||||
private String currentChannelName;
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,904 @@
|
||||
/*
|
||||
* Central Repository
|
||||
*
|
||||
* Copyright 2020 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> 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.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import junit.framework.Assert;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import junit.framework.TestCase;
|
||||
import junit.framework.Test;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import org.netbeans.junit.NbModuleSuite;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Tests the Persona API in CentralRepository.
|
||||
*/
|
||||
public class CentralRepoPersonasTest extends TestCase {
|
||||
|
||||
private final Path testDirectory = Paths.get(System.getProperty("java.io.tmpdir"), "CentralRepoDatamodelTest");
|
||||
|
||||
|
||||
private static final String CASE_1_UUID = "case1_uuid";
|
||||
private static final String CASE_2_UUID = "case2_uuid";
|
||||
private static final String CASE_3_UUID = "case3_uuid";
|
||||
private static final String CASE_4_UUID = "case4_uuid";
|
||||
|
||||
private static final String DS1_DEVICEID = "dataSource1_deviceID";
|
||||
private static final String DS2_DEVICEID = "dataSource2_deviceID";
|
||||
private static final String DS3_DEVICEID = "dataSource3_deviceID";
|
||||
private static final String DS4_DEVICEID = "dataSource4_deviceID";
|
||||
private static final String DS5_DEVICEID = "dataSource5_deviceID";
|
||||
private static final String DS6_DEVICEID = "dataSource6_deviceID";
|
||||
|
||||
|
||||
private static final long CASE_1_DATA_SOURCE_1_ID = 11;
|
||||
private static final long CASE_1_DATA_SOURCE_2_ID = 12;
|
||||
private static final long CASE_2_DATA_SOURCE_1_ID = 21;
|
||||
|
||||
private static final long CASE_3_DATA_SOURCE_1_ID = 31;
|
||||
private static final long CASE_3_DATA_SOURCE_2_ID = 32;
|
||||
|
||||
private static final long CASE_4_DATA_SOURCE_1_ID = 41;
|
||||
|
||||
|
||||
private static final String PHONE_NUM_1 = "+1 441-231-2552";
|
||||
|
||||
|
||||
private static final String FACEBOOK_ID_CATDOG = "BalooSherkhan";
|
||||
|
||||
private static final String DOG_EMAIL_ID = "superpupper@junglebook.com";
|
||||
private static final String CAT_WHATSAPP_ID = "111 222 3333";
|
||||
private static final String EMAIL_ID_1 = "rkipling@jungle.book";
|
||||
|
||||
private static final String HOLMES_SKYPE_ID = "live:holmes@221baker.com";
|
||||
|
||||
|
||||
private static final String DOG_PERSONA_NAME = "Baloo McDog";
|
||||
private static final String CAT_PERSONA_NAME = "SherKhan";
|
||||
private static final String HOLMES_PERSONA_NAME = "Sherlock Holmes";
|
||||
|
||||
|
||||
private CorrelationCase case1;
|
||||
private CorrelationCase case2;
|
||||
private CorrelationCase case3;
|
||||
private CorrelationCase case4;
|
||||
|
||||
private CorrelationDataSource dataSource1fromCase1;
|
||||
private CorrelationDataSource dataSource2fromCase1;
|
||||
private CorrelationDataSource dataSource1fromCase2;
|
||||
|
||||
private CorrelationDataSource dataSource1fromCase3;
|
||||
private CorrelationDataSource dataSource2fromCase3;
|
||||
private CorrelationDataSource dataSource1fromCase4;
|
||||
|
||||
private CentralRepoOrganization org1;
|
||||
private CentralRepoOrganization org2;
|
||||
|
||||
private CentralRepoAccount.CentralRepoAccountType phoneAccountType;
|
||||
private CentralRepoAccount.CentralRepoAccountType emailAccountType;
|
||||
private CentralRepoAccount.CentralRepoAccountType facebookAccountType;
|
||||
private CentralRepoAccount.CentralRepoAccountType textnowAccountType;
|
||||
private CentralRepoAccount.CentralRepoAccountType whatsAppAccountType;
|
||||
private CentralRepoAccount.CentralRepoAccountType skypeAccountType;
|
||||
|
||||
|
||||
private CorrelationAttributeInstance.Type phoneInstanceType;
|
||||
private CorrelationAttributeInstance.Type emailInstanceType;
|
||||
private CorrelationAttributeInstance.Type facebookInstanceType;
|
||||
private CorrelationAttributeInstance.Type textnowInstanceType;
|
||||
private CorrelationAttributeInstance.Type whatsAppInstanceType;
|
||||
private CorrelationAttributeInstance.Type skypeInstanceType;
|
||||
|
||||
|
||||
// NbModuleSuite requires these tests use Junit 3.8
|
||||
// Extension of the TestCase class is how tests were defined and used
|
||||
// in Junit 3.8
|
||||
public static Test suite() {
|
||||
NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(CentralRepoPersonasTest.class).
|
||||
clusters(".*").
|
||||
enableModules(".*");
|
||||
return conf.suite();
|
||||
}
|
||||
|
||||
// This function is run before every test, NOT before the entire collection of
|
||||
// tests defined in this class are run.
|
||||
@Override
|
||||
public void setUp() throws CentralRepoException, IOException {
|
||||
// Tear down the previous run, if need be.
|
||||
if (Files.exists(testDirectory)) {
|
||||
tearDown();
|
||||
}
|
||||
|
||||
// Create the test directory
|
||||
Files.createDirectory(testDirectory);
|
||||
|
||||
final String CR_DB_NAME = "testcentralrepo.db";
|
||||
|
||||
|
||||
|
||||
SqliteCentralRepoSettings sqliteSettings = new SqliteCentralRepoSettings();
|
||||
sqliteSettings.setDbName(CR_DB_NAME);
|
||||
sqliteSettings.setDbDirectory(testDirectory.toString());
|
||||
|
||||
if (!sqliteSettings.dbDirectoryExists() && !sqliteSettings.createDbDirectory()) {
|
||||
Assert.fail("Failed to create central repo directory.");
|
||||
}
|
||||
|
||||
RdbmsCentralRepoFactory factory = new RdbmsCentralRepoFactory(CentralRepoPlatforms.SQLITE, sqliteSettings);
|
||||
if (!factory.initializeDatabaseSchema() || !factory.insertDefaultDatabaseContent()) {
|
||||
Assert.fail("Failed to initialize central repo database");
|
||||
}
|
||||
|
||||
sqliteSettings.saveSettings();
|
||||
CentralRepoDbUtil.setUseCentralRepo(true);
|
||||
CentralRepoDbManager.saveDbChoice(CentralRepoDbChoice.SQLITE);
|
||||
|
||||
Path crDbFilePath = Paths.get(testDirectory.toString(), CR_DB_NAME);
|
||||
if (!Files.exists(crDbFilePath)) {
|
||||
Assert.fail("Failed to create central repo database, should be located at + " + crDbFilePath);
|
||||
}
|
||||
|
||||
|
||||
// Set up some default objects to be used by the tests
|
||||
try {
|
||||
case1 = new CorrelationCase(CASE_1_UUID, "case1");
|
||||
case1 = CentralRepository.getInstance().newCase(case1);
|
||||
assertTrue("Failed to create test object case1", case1 != null);
|
||||
|
||||
case2 = new CorrelationCase(CASE_2_UUID, "case2");
|
||||
case2 = CentralRepository.getInstance().newCase(case2);
|
||||
assertTrue("Failed to create test object case2", case2 != null);
|
||||
|
||||
case3 = new CorrelationCase(CASE_3_UUID, "case3");
|
||||
case3 = CentralRepository.getInstance().newCase(case3);
|
||||
assertTrue("Failed to create test object case3", case3 != null);
|
||||
|
||||
case4 = new CorrelationCase(CASE_4_UUID, "case4");
|
||||
case4 = CentralRepository.getInstance().newCase(case4);
|
||||
assertTrue("Failed to create test object case4", case4 != null);
|
||||
|
||||
dataSource1fromCase1 = new CorrelationDataSource(case1, DS1_DEVICEID, "dataSource1", CASE_1_DATA_SOURCE_1_ID, null, null, null);
|
||||
CentralRepository.getInstance().newDataSource(dataSource1fromCase1);
|
||||
dataSource1fromCase1 = CentralRepository.getInstance().getDataSource(case1, dataSource1fromCase1.getDataSourceObjectID());
|
||||
assertTrue("Failed to create test object dataSource1fromCase1", dataSource1fromCase1 != null);
|
||||
|
||||
dataSource2fromCase1 = new CorrelationDataSource(case1, DS2_DEVICEID, "dataSource2", CASE_1_DATA_SOURCE_2_ID, null, null, null);
|
||||
CentralRepository.getInstance().newDataSource(dataSource2fromCase1);
|
||||
dataSource2fromCase1 = CentralRepository.getInstance().getDataSource(case1, dataSource2fromCase1.getDataSourceObjectID());
|
||||
assertTrue("Failed to create test object dataSource2fromCase1", dataSource2fromCase1 != null);
|
||||
|
||||
dataSource1fromCase2 = new CorrelationDataSource(case2, DS3_DEVICEID, "dataSource3", CASE_2_DATA_SOURCE_1_ID, null, null, null);
|
||||
CentralRepository.getInstance().newDataSource(dataSource1fromCase2);
|
||||
dataSource1fromCase2 = CentralRepository.getInstance().getDataSource(case2, dataSource1fromCase2.getDataSourceObjectID());
|
||||
assertTrue("Failed to create test object dataSource1fromCase2", dataSource1fromCase2 != null);
|
||||
|
||||
dataSource1fromCase3 = new CorrelationDataSource(case3, DS4_DEVICEID, "dataSource4", CASE_3_DATA_SOURCE_1_ID, null, null, null);
|
||||
CentralRepository.getInstance().newDataSource(dataSource1fromCase3);
|
||||
dataSource1fromCase3 = CentralRepository.getInstance().getDataSource(case3, dataSource1fromCase3.getDataSourceObjectID());
|
||||
assertTrue("Failed to create test object dataSource1fromCase3", dataSource1fromCase3 != null);
|
||||
|
||||
dataSource2fromCase3 = new CorrelationDataSource(case3, DS5_DEVICEID, "dataSource5", CASE_3_DATA_SOURCE_2_ID, null, null, null);
|
||||
CentralRepository.getInstance().newDataSource(dataSource2fromCase3);
|
||||
dataSource2fromCase3 = CentralRepository.getInstance().getDataSource(case3, dataSource2fromCase3.getDataSourceObjectID());
|
||||
assertTrue("Failed to create test object dataSource2fromCase3", dataSource2fromCase3 != null);
|
||||
|
||||
dataSource1fromCase4 = new CorrelationDataSource(case4, DS6_DEVICEID, "dataSource6", CASE_4_DATA_SOURCE_1_ID, null, null, null);
|
||||
CentralRepository.getInstance().newDataSource(dataSource1fromCase4);
|
||||
dataSource1fromCase4 = CentralRepository.getInstance().getDataSource(case4, dataSource1fromCase4.getDataSourceObjectID());
|
||||
assertTrue("Failed to create test object dataSource1fromCase4", dataSource1fromCase4 != null);
|
||||
|
||||
org1 = new CentralRepoOrganization("org1");
|
||||
org1 = CentralRepository.getInstance().newOrganization(org1);
|
||||
|
||||
org2 = new CentralRepoOrganization("org2");
|
||||
org2 = CentralRepository.getInstance().newOrganization(org2);
|
||||
|
||||
// get some correltion types for different account types, for later use
|
||||
phoneAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.PHONE.getTypeName());
|
||||
phoneInstanceType = CentralRepository.getInstance().getCorrelationTypeById(phoneAccountType.getCorrelationTypeId());
|
||||
assertTrue("getCorrelationTypeById(PHONE) returned null", phoneInstanceType != null);
|
||||
|
||||
emailAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.EMAIL.getTypeName());
|
||||
emailInstanceType = CentralRepository.getInstance().getCorrelationTypeById(emailAccountType.getCorrelationTypeId());
|
||||
assertTrue("getCorrelationTypeById(EMAIL) returned null", emailInstanceType != null);
|
||||
|
||||
facebookAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.FACEBOOK.getTypeName());
|
||||
facebookInstanceType = CentralRepository.getInstance().getCorrelationTypeById(facebookAccountType.getCorrelationTypeId());
|
||||
assertTrue("getCorrelationTypeById(FACEBOOK) returned null", facebookInstanceType != null);
|
||||
|
||||
textnowAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.TEXTNOW.getTypeName());
|
||||
textnowInstanceType = CentralRepository.getInstance().getCorrelationTypeById(textnowAccountType.getCorrelationTypeId());
|
||||
assertTrue("getCorrelationTypeById(TEXTNOW) returned null", textnowInstanceType != null);
|
||||
|
||||
whatsAppAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.WHATSAPP.getTypeName());
|
||||
whatsAppInstanceType = CentralRepository.getInstance().getCorrelationTypeById(whatsAppAccountType.getCorrelationTypeId());
|
||||
assertTrue("getCorrelationTypeById(WHATSAPP) returned null", whatsAppInstanceType != null);
|
||||
|
||||
skypeAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.SKYPE.getTypeName());
|
||||
skypeInstanceType = CentralRepository.getInstance().getCorrelationTypeById(skypeAccountType.getCorrelationTypeId());
|
||||
assertTrue("getCorrelationTypeById(SKYPE) returned null", skypeInstanceType != null);
|
||||
|
||||
|
||||
} catch (CentralRepoException ex) {
|
||||
Exceptions.printStackTrace(ex);
|
||||
Assert.fail(ex.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// This function is run after every test, NOT after the entire collection of
|
||||
// tests defined in the class are run.
|
||||
@Override
|
||||
public void tearDown() throws CentralRepoException, IOException {
|
||||
// Close and delete the test case and central repo db
|
||||
if (CentralRepository.isEnabled()) {
|
||||
CentralRepository.getInstance().shutdownConnections();
|
||||
}
|
||||
FileUtils.deleteDirectory(testDirectory.toFile());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Basic tests for:
|
||||
* - Persona creation,
|
||||
* - adding aliases and metadata
|
||||
* - add additional accounts to Persona
|
||||
* - Get Persona(s) by account
|
||||
* - get Account(s) by Persona
|
||||
*
|
||||
*/
|
||||
public void testBasicPersonaCreation() {
|
||||
|
||||
//final String DATE_FORMAT_STRING = "yyyy/MM/dd HH:mm:ss"; //NON-NLS
|
||||
//final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US);
|
||||
|
||||
try {
|
||||
|
||||
// Step 1: Create an account
|
||||
CentralRepoAccount phoneAccount1 = CentralRepository.getInstance()
|
||||
.getOrCreateAccount(phoneAccountType, PHONE_NUM_1);
|
||||
|
||||
|
||||
// Step 2: Create a Persona for the Account
|
||||
|
||||
String comment = "The best dog ever";
|
||||
Persona.PersonaStatus status = Persona.PersonaStatus.ACTIVE;
|
||||
PersonaAccount pa1 = Persona.createPersonaForAccount(DOG_PERSONA_NAME, comment , status, phoneAccount1, "Because I said so", Persona.Confidence.LOW );
|
||||
|
||||
|
||||
Persona dogPersona = pa1.getPersona();
|
||||
|
||||
// Verify Persona name, status etc.
|
||||
Assert.assertEquals(DOG_PERSONA_NAME, pa1.getPersona().getName());
|
||||
Assert.assertEquals(status.name(), dogPersona.getStatus().name());
|
||||
Assert.assertTrue(dogPersona.getExaminer().getLoginName().equalsIgnoreCase(pa1.getExaminer().getLoginName()));
|
||||
|
||||
// Assert that the persona was created by the currently logged in user
|
||||
Assert.assertTrue(dogPersona.getExaminer().getLoginName().equalsIgnoreCase(System.getProperty("user.name")));
|
||||
|
||||
// Assert that Persona was created within the last 10 mins
|
||||
Assert.assertTrue(Instant.now().toEpochMilli() - pa1.getDateAdded() < 600 * 1000);
|
||||
Assert.assertEquals(pa1.getConfidence(), Persona.Confidence.LOW);
|
||||
|
||||
// Step 3. Add Persona Aliases
|
||||
PersonaAlias alias1 = dogPersona.addAlias("Good Boy", "Coz he's is the best dog ever", Persona.Confidence.MEDIUM);
|
||||
PersonaAlias alias2 = dogPersona.addAlias("WoofWoof", "How many dumb comments can I come up with?", Persona.Confidence.LOW);
|
||||
|
||||
Assert.assertNotNull(alias1);
|
||||
Assert.assertNotNull(alias2);
|
||||
|
||||
// get all aliases for persona
|
||||
Collection<PersonaAlias> aliases = dogPersona.getAliases();
|
||||
Assert.assertEquals(2, aliases.size());
|
||||
for (PersonaAlias alias: aliases) {
|
||||
//System.out.println("Alias: "+ alias.getAlias()) ;
|
||||
Assert.assertFalse(alias.getAlias().isEmpty());
|
||||
}
|
||||
|
||||
|
||||
//Step 4: Add Persona metadata
|
||||
PersonaMetadata metadata1 = dogPersona.addMetadata("Color", "Black", "He's got thick black hair.", Persona.Confidence.MEDIUM);
|
||||
PersonaMetadata metadata2 = dogPersona.addMetadata("Gender", "Male", "Because...", Persona.Confidence.LOW);
|
||||
|
||||
Assert.assertNotNull(metadata1);
|
||||
Assert.assertNotNull(metadata2);
|
||||
|
||||
// get all metadata for persona
|
||||
Collection<PersonaMetadata> metadataList = dogPersona.getMetadata();
|
||||
Assert.assertEquals(2, metadataList.size());
|
||||
for (PersonaMetadata md: metadataList) {
|
||||
//System.out.println(String.format("Metadata: %s : %s", md.getName(), md.getValue())) ;
|
||||
Assert.assertFalse(md.getName().isEmpty());
|
||||
Assert.assertFalse(md.getValue().isEmpty());
|
||||
}
|
||||
|
||||
|
||||
// Step 5: associate another account with same persona
|
||||
CentralRepoAccount catdogFBAccount = CentralRepository.getInstance()
|
||||
.getOrCreateAccount(facebookAccountType, FACEBOOK_ID_CATDOG);
|
||||
|
||||
// Add an account to persona
|
||||
dogPersona.addAccountToPersona(catdogFBAccount, "Looks like dog, barks like a dog...", Persona.Confidence.MEDIUM);
|
||||
|
||||
// Get all acounts for the persona...
|
||||
Collection<PersonaAccount> personaAccounts = dogPersona.getPersonaAccounts();
|
||||
|
||||
Assert.assertEquals(2, personaAccounts.size());
|
||||
|
||||
for (PersonaAccount pa: personaAccounts) {
|
||||
//System.out.println(String.format("PersonaAccount: Justification = %s : Date Added = %s", pa.getJustification(), DATE_FORMAT.format(new Date(pa.getDateAdded())))) ;
|
||||
Assert.assertFalse(pa.getJustification().isEmpty());
|
||||
Assert.assertFalse(pa.getAccount().getTypeSpecificId().isEmpty());
|
||||
Assert.assertTrue(pa.getDateAdded() > 0);
|
||||
Assert.assertTrue(pa.getPersona().getCreatedDate()> 0);
|
||||
}
|
||||
|
||||
// Step 6: Create a Second Persona, that shares a common account with another persona
|
||||
|
||||
String comment2 = "The fiercest cat alive.";
|
||||
PersonaAccount pa2 = Persona.createPersonaForAccount(CAT_PERSONA_NAME, comment2 , Persona.PersonaStatus.ACTIVE, catdogFBAccount, "Smells like a cat.", Persona.Confidence.LOW );
|
||||
Assert.assertNotNull(pa2);
|
||||
Assert.assertTrue(pa2.getPersona().getName().equalsIgnoreCase(CAT_PERSONA_NAME));
|
||||
|
||||
|
||||
// Get ALL personas for an account
|
||||
Collection<PersonaAccount> personaAccounts2 = PersonaAccount.getPersonaAccountsForAccount(catdogFBAccount.getAccountId());
|
||||
|
||||
Assert.assertEquals(2, personaAccounts2.size());
|
||||
for (PersonaAccount pa: personaAccounts2) {
|
||||
//System.out.println(String.format("PersonaAccount: Justification = %s : Date Added = %s", pa.getJustification(), DATE_FORMAT.format(new Date(pa.getDateAdded())))) ;
|
||||
Assert.assertFalse(pa.getJustification().isEmpty());
|
||||
Assert.assertFalse(pa.getAccount().getTypeSpecificId().isEmpty());
|
||||
Assert.assertTrue(pa.getDateAdded() > 0);
|
||||
Assert.assertTrue(pa.getPersona().getCreatedDate()> 0);
|
||||
Assert.assertFalse(pa.getPersona().getName().isEmpty());
|
||||
}
|
||||
|
||||
|
||||
} catch (CentralRepoException ex) {
|
||||
Assert.fail("Didn't expect an exception here. Exception: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Personas & X_Accounts and X_instances in the context of Case/data source.
|
||||
* There are 4 Cases.
|
||||
* - Case1 has 2 data sources, case2 has 1.
|
||||
* - Case3 has 2 data sources, case4 has 1.
|
||||
* There are 3 personas - A Cat, a Dog, and Sherlock Holmes.
|
||||
* Cat & Dog share a FB account - with 3 instances split over the 2 cases - Case1 & Case2.
|
||||
* Dog has his his own email account - with one instance in Case1
|
||||
* Cat has his own WhatsApp account. - with 2 instances - in Case1 & Case2
|
||||
* Sherlock has a Skype account - with 1 instance in Case 3.
|
||||
* Case 4 has no personas or accounts
|
||||
*/
|
||||
public void testPersonaWithCases() {
|
||||
|
||||
try {
|
||||
// Create an account - Cat and Dog have a shared FB account
|
||||
CentralRepoAccount catdogFBAccount = CentralRepository.getInstance()
|
||||
.getOrCreateAccount(facebookAccountType, FACEBOOK_ID_CATDOG);
|
||||
|
||||
|
||||
// Create account instance attribute for that account, on Case 1, DS 1
|
||||
CorrelationAttributeInstance fbAcctInstance1 = new CorrelationAttributeInstance(facebookInstanceType, FACEBOOK_ID_CATDOG,
|
||||
-1,
|
||||
case1,
|
||||
dataSource1fromCase1,
|
||||
"path1",
|
||||
"",
|
||||
TskData.FileKnown.UNKNOWN,
|
||||
1001L,
|
||||
catdogFBAccount.getAccountId());
|
||||
CentralRepository.getInstance().addArtifactInstance(fbAcctInstance1);
|
||||
|
||||
|
||||
// Create account instance attribute for that account, on Case 1, DS 2
|
||||
CorrelationAttributeInstance fbAcctInstance2 = new CorrelationAttributeInstance(facebookInstanceType, FACEBOOK_ID_CATDOG,
|
||||
-1,
|
||||
case1,
|
||||
dataSource2fromCase1,
|
||||
"path2",
|
||||
"",
|
||||
TskData.FileKnown.UNKNOWN,
|
||||
1002L, catdogFBAccount.getAccountId());
|
||||
|
||||
CentralRepository.getInstance().addArtifactInstance(fbAcctInstance2);
|
||||
|
||||
|
||||
// Create account instance attribute for that account, on Case 1, DS 2
|
||||
CorrelationAttributeInstance fbAcctInstance3 = new CorrelationAttributeInstance(facebookInstanceType, FACEBOOK_ID_CATDOG,
|
||||
-1,
|
||||
case2,
|
||||
dataSource1fromCase2,
|
||||
"path3",
|
||||
"",
|
||||
TskData.FileKnown.UNKNOWN,
|
||||
1003L, catdogFBAccount.getAccountId());
|
||||
CentralRepository.getInstance().addArtifactInstance(fbAcctInstance3);
|
||||
|
||||
|
||||
// Create Persona for the Dog, using the shared FB account
|
||||
String comment = "The best dog ever";
|
||||
Persona.PersonaStatus status = Persona.PersonaStatus.ACTIVE;
|
||||
PersonaAccount pa1 = Persona.createPersonaForAccount(DOG_PERSONA_NAME,
|
||||
comment ,
|
||||
status, catdogFBAccount, "Because I said so", Persona.Confidence.LOW );
|
||||
Persona dogPersona = pa1.getPersona();
|
||||
|
||||
|
||||
|
||||
// create a second persona for the same account - Cat has the same FB account as dog
|
||||
String comment2 = "The fiercest cat alive.";
|
||||
PersonaAccount pa2 = Persona.createPersonaForAccount(CAT_PERSONA_NAME,
|
||||
comment2 , Persona.PersonaStatus.ACTIVE,
|
||||
catdogFBAccount, "Smells like a cat.", Persona.Confidence.LOW );
|
||||
|
||||
Persona catPersona = pa2.getPersona();
|
||||
Assert.assertNotNull(pa2);
|
||||
Assert.assertTrue(pa2.getPersona().getName().equalsIgnoreCase(CAT_PERSONA_NAME));
|
||||
|
||||
|
||||
|
||||
// Add a 2nd account to the Dog - dog has his own email
|
||||
CentralRepoAccount dogEmailAccount = CentralRepository.getInstance()
|
||||
.getOrCreateAccount(emailAccountType, DOG_EMAIL_ID);
|
||||
|
||||
// Add an instance of dog email
|
||||
CorrelationAttributeInstance dogEmailAcctInstance = new CorrelationAttributeInstance(emailInstanceType, DOG_EMAIL_ID,
|
||||
-1,
|
||||
case1,
|
||||
dataSource2fromCase1,
|
||||
"path3",
|
||||
"",
|
||||
TskData.FileKnown.UNKNOWN,
|
||||
1002L,
|
||||
dogEmailAccount.getAccountId());
|
||||
|
||||
CentralRepository.getInstance().addArtifactInstance(dogEmailAcctInstance);
|
||||
|
||||
PersonaAccount pa3 = dogPersona.addAccountToPersona(dogEmailAccount, "Thats definitely a dog email account", Persona.Confidence.MEDIUM);
|
||||
Assert.assertNotNull(pa3);
|
||||
Assert.assertTrue(pa3.getPersona().getName().equalsIgnoreCase(DOG_PERSONA_NAME));
|
||||
|
||||
|
||||
// create a WhatsApp account for cat, add 2 instances, and then add that to Cat persona
|
||||
CentralRepoAccount catWhatsAppAccount = CentralRepository.getInstance()
|
||||
.getOrCreateAccount(whatsAppAccountType, CAT_WHATSAPP_ID);
|
||||
|
||||
// Add 2 instances of cat whatsApp
|
||||
CorrelationAttributeInstance catWhatsAppAccountInstance1 = new CorrelationAttributeInstance(whatsAppInstanceType, CAT_WHATSAPP_ID,
|
||||
-1,
|
||||
case1,
|
||||
dataSource1fromCase1,
|
||||
"path4",
|
||||
"",
|
||||
TskData.FileKnown.UNKNOWN,
|
||||
1005L,
|
||||
catWhatsAppAccount.getAccountId());
|
||||
CentralRepository.getInstance().addArtifactInstance(catWhatsAppAccountInstance1);
|
||||
|
||||
CorrelationAttributeInstance catWhatsAppAccountInstance2 = new CorrelationAttributeInstance(whatsAppInstanceType, CAT_WHATSAPP_ID,
|
||||
-1,
|
||||
case2,
|
||||
dataSource1fromCase2,
|
||||
"path5",
|
||||
"",
|
||||
TskData.FileKnown.UNKNOWN,
|
||||
1006L,
|
||||
catWhatsAppAccount.getAccountId());
|
||||
CentralRepository.getInstance().addArtifactInstance(catWhatsAppAccountInstance2);
|
||||
|
||||
|
||||
PersonaAccount pa4 = catPersona.addAccountToPersona(catWhatsAppAccount, "The cat has a WhatsApp account", Persona.Confidence.MEDIUM);
|
||||
Assert.assertNotNull(pa4);
|
||||
Assert.assertTrue(pa4.getPersona().getName().equalsIgnoreCase(CAT_PERSONA_NAME));
|
||||
|
||||
|
||||
|
||||
Collection<PersonaAccount> dogPersonaAccounts = dogPersona.getPersonaAccounts();
|
||||
Assert.assertEquals(2, dogPersonaAccounts.size()); // Dog has 2 accounts.
|
||||
for (PersonaAccount pa : dogPersonaAccounts) {
|
||||
Assert.assertTrue(pa.getAccount().getTypeSpecificId().equalsIgnoreCase(FACEBOOK_ID_CATDOG)
|
||||
|| pa.getAccount().getTypeSpecificId().equalsIgnoreCase(DOG_EMAIL_ID));
|
||||
// System.out.println("Dog Account id : " + acct.getTypeSpecificId());
|
||||
}
|
||||
|
||||
|
||||
Collection<PersonaAccount> catPersonaAccounts = catPersona.getPersonaAccounts();
|
||||
Assert.assertEquals(2, catPersonaAccounts.size()); // cat has 2 accounts.
|
||||
for (PersonaAccount pa:catPersonaAccounts) {
|
||||
//System.out.println("Cat Account id : " + acct.getTypeSpecificId());
|
||||
Assert.assertTrue(pa.getAccount().getTypeSpecificId().equalsIgnoreCase(FACEBOOK_ID_CATDOG)
|
||||
|| pa.getAccount().getTypeSpecificId().equalsIgnoreCase(CAT_WHATSAPP_ID));
|
||||
}
|
||||
|
||||
// create account and Persona for Sherlock Holmes.
|
||||
// Create a Skype Account
|
||||
CentralRepoAccount holmesSkypeAccount = CentralRepository.getInstance()
|
||||
.getOrCreateAccount(skypeAccountType, HOLMES_SKYPE_ID);
|
||||
|
||||
// Add an instance of Skype account to Case3/DS1
|
||||
CorrelationAttributeInstance skypeAcctInstance = new CorrelationAttributeInstance(skypeInstanceType, HOLMES_SKYPE_ID,
|
||||
-1,
|
||||
case3,
|
||||
dataSource1fromCase3,
|
||||
"path8",
|
||||
"",
|
||||
TskData.FileKnown.UNKNOWN,
|
||||
1011L,
|
||||
holmesSkypeAccount.getAccountId());
|
||||
CentralRepository.getInstance().addArtifactInstance(skypeAcctInstance);
|
||||
|
||||
|
||||
// Create a person for the Skype account
|
||||
PersonaAccount pa5 = Persona.createPersonaForAccount(HOLMES_PERSONA_NAME,
|
||||
"Has a Pipe in his mouth." , Persona.PersonaStatus.ACTIVE,
|
||||
holmesSkypeAccount, "The name says it all.", Persona.Confidence.LOW );
|
||||
|
||||
Persona holmesPersona = pa5.getPersona();
|
||||
Assert.assertNotNull(pa5);
|
||||
Assert.assertTrue(pa5.getPersona().getName().equalsIgnoreCase(HOLMES_PERSONA_NAME));
|
||||
|
||||
|
||||
|
||||
// Test that getting cases for Persona
|
||||
Collection<CorrelationCase> dogCases = dogPersona.getCases();
|
||||
Assert.assertEquals(2, dogCases.size()); // dog appears in 2 cases.
|
||||
for (CorrelationCase dc: dogCases) {
|
||||
Assert.assertTrue(dc.getCaseUUID().equalsIgnoreCase(CASE_1_UUID)
|
||||
|| dc.getCaseUUID().equalsIgnoreCase(CASE_2_UUID));
|
||||
//System.out.println("Dog Case UUID : " + dc.getCaseUUID());
|
||||
}
|
||||
|
||||
Collection<CorrelationCase> catCases = catPersona.getCases();
|
||||
Assert.assertEquals(2, catCases.size()); // cat appears in 2 cases.
|
||||
for (CorrelationCase cc: catCases) {
|
||||
Assert.assertTrue(cc.getCaseUUID().equalsIgnoreCase(CASE_1_UUID)
|
||||
|| cc.getCaseUUID().equalsIgnoreCase(CASE_2_UUID));
|
||||
//System.out.println("Cat Case UUID : " + cc.getCaseUUID());
|
||||
}
|
||||
|
||||
Collection<CorrelationCase> holmesCases = holmesPersona.getCases();
|
||||
Assert.assertEquals(1, holmesCases.size()); // Holmes appears in 1 case.
|
||||
for (CorrelationCase hc: holmesCases) {
|
||||
Assert.assertTrue(hc.getCaseUUID().equalsIgnoreCase(CASE_3_UUID));
|
||||
//System.out.println("Holmes Case UUID : " + hc.getCaseUUID());
|
||||
}
|
||||
|
||||
|
||||
// Test that getting data sources for the Persona -
|
||||
Collection<CorrelationDataSource> dogDatasources = dogPersona.getDataSources();
|
||||
Assert.assertEquals(3, dogDatasources.size()); // dog appears in 2 cases in 3 data sources.
|
||||
for (CorrelationDataSource dds: dogDatasources) {
|
||||
Assert.assertTrue(dds.getDeviceID().equalsIgnoreCase(DS1_DEVICEID)
|
||||
|| dds.getDeviceID().equalsIgnoreCase(DS2_DEVICEID)
|
||||
|| dds.getDeviceID().equalsIgnoreCase(DS3_DEVICEID));
|
||||
//System.out.println("Dog DS DeviceID : " + dds.getDeviceID());
|
||||
}
|
||||
|
||||
Collection<CorrelationDataSource> catDatasources = catPersona.getDataSources();
|
||||
Assert.assertEquals(3, catDatasources.size()); // cat appears in 2 cases in 3 data sources.
|
||||
for (CorrelationDataSource cds: catDatasources) {
|
||||
Assert.assertTrue(cds.getDeviceID().equalsIgnoreCase(DS1_DEVICEID)
|
||||
|| cds.getDeviceID().equalsIgnoreCase(DS2_DEVICEID)
|
||||
|| cds.getDeviceID().equalsIgnoreCase(DS3_DEVICEID));
|
||||
//System.out.println("Cat DS DeviceID : " + cds.getDeviceID());
|
||||
}
|
||||
|
||||
Collection<CorrelationDataSource> holmesDatasources = holmesPersona.getDataSources();
|
||||
Assert.assertEquals(1, holmesDatasources.size()); // Holmes appears in 1 cases in 1 data source.
|
||||
for (CorrelationDataSource hds: holmesDatasources) {
|
||||
Assert.assertTrue(hds.getDeviceID().equalsIgnoreCase(DS4_DEVICEID));
|
||||
//System.out.println("Holmes DS DeviceID : " + hds.getDeviceID());
|
||||
}
|
||||
|
||||
// Test getting peronas by case.
|
||||
|
||||
// Test that getting all Personas for Case 1 - Case1 has 2 persona - Cat & Dog
|
||||
Collection<Persona> case1Persona = Persona.getPersonasForCase(case1);
|
||||
Assert.assertEquals(2, case1Persona.size()); //
|
||||
|
||||
// Test that getting all Personas for Case 2 - Case2 has 2 persona - Cat & Dog
|
||||
Collection<Persona> case2Persona = Persona.getPersonasForCase(case2);
|
||||
Assert.assertEquals(2, case2Persona.size()); //
|
||||
|
||||
// Test that getting all Personas for Case 3 - Case3 has 1 persona - Holmes
|
||||
Collection<Persona> case3Persona = Persona.getPersonasForCase(case3);
|
||||
Assert.assertEquals(1, case3Persona.size()); //
|
||||
|
||||
// Test that getting all Personas for Case 4 - Case4 has no persona
|
||||
Collection<Persona> case4Persona = Persona.getPersonasForCase(case4);
|
||||
Assert.assertEquals(0, case4Persona.size()); //
|
||||
|
||||
|
||||
// Test getting peronas by data source.
|
||||
|
||||
// Test that getting all Personas for DS 1
|
||||
Collection<Persona> ds1Persona = Persona.getPersonasForDataSource(dataSource1fromCase1);
|
||||
Assert.assertEquals(2, ds1Persona.size()); //
|
||||
|
||||
Collection<Persona> ds2Persona = Persona.getPersonasForDataSource(dataSource2fromCase1);
|
||||
Assert.assertEquals(2, ds2Persona.size()); //
|
||||
|
||||
Collection<Persona> ds3Persona = Persona.getPersonasForDataSource(dataSource1fromCase2);
|
||||
Assert.assertEquals(2, ds3Persona.size()); //
|
||||
|
||||
Collection<Persona> ds4Persona = Persona.getPersonasForDataSource(dataSource1fromCase3);
|
||||
Assert.assertEquals(1, ds4Persona.size()); //
|
||||
|
||||
Collection<Persona> ds5Persona = Persona.getPersonasForDataSource(dataSource2fromCase3);
|
||||
Assert.assertEquals(0, ds5Persona.size()); //
|
||||
|
||||
Collection<Persona> ds6Persona = Persona.getPersonasForDataSource(dataSource1fromCase4);
|
||||
Assert.assertEquals(0, ds6Persona.size()); //
|
||||
|
||||
|
||||
}
|
||||
catch (CentralRepoException | CorrelationAttributeNormalizationException ex) {
|
||||
Exceptions.printStackTrace(ex);
|
||||
Assert.fail(ex.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Tests edge cases, error cases
|
||||
*/
|
||||
public void testPersonaCreationEdgeCases() {
|
||||
|
||||
// Test1: create Persona without specifying a name
|
||||
{
|
||||
try {
|
||||
// Create an email account
|
||||
CentralRepoAccount emailAccount1 = CentralRepository.getInstance()
|
||||
.getOrCreateAccount(emailAccountType, EMAIL_ID_1);
|
||||
|
||||
// Create a Persona with no name
|
||||
PersonaAccount pa1 = Persona.createPersonaForAccount(null, "A persona with no name",
|
||||
Persona.PersonaStatus.ACTIVE, emailAccount1, "The person lost his name", Persona.Confidence.LOW);
|
||||
|
||||
// Verify Persona has a default name
|
||||
Assert.assertEquals("Unknown", pa1.getPersona().getName());
|
||||
|
||||
} catch (CentralRepoException ex) {
|
||||
Assert.fail("No name persona test failed. Exception: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests searching of Persona by persona name.
|
||||
*/
|
||||
public void testPersonaSearchByName() {
|
||||
|
||||
// Test1: create Personas with similar names.
|
||||
{
|
||||
try {
|
||||
// Create an email account
|
||||
CentralRepoAccount emailAccount1 = CentralRepository.getInstance()
|
||||
.getOrCreateAccount(emailAccountType, EMAIL_ID_1);
|
||||
|
||||
// Create all personas with same comment.
|
||||
final String personaComment = "Creator of Jungle Book.";
|
||||
|
||||
// Create a Persona with name "Rudyard Kipling"
|
||||
Persona.createPersonaForAccount("Rudyard Kipling", personaComment,
|
||||
Persona.PersonaStatus.ACTIVE, emailAccount1, "", Persona.Confidence.LOW);
|
||||
|
||||
// Create a Persona with name "Rudy"
|
||||
Persona.createPersonaForAccount("Rudy", personaComment,
|
||||
Persona.PersonaStatus.ACTIVE, emailAccount1, "", Persona.Confidence.LOW);
|
||||
|
||||
|
||||
// Create a Persona with name "Kipling Senior"
|
||||
Persona.createPersonaForAccount("Kipling Senior", personaComment,
|
||||
Persona.PersonaStatus.ACTIVE, emailAccount1, "", Persona.Confidence.LOW);
|
||||
|
||||
// Create a Persona with name "Senor Kipling"
|
||||
Persona.createPersonaForAccount("Senor Kipling", personaComment,
|
||||
Persona.PersonaStatus.ACTIVE, emailAccount1, "", Persona.Confidence.LOW);
|
||||
|
||||
|
||||
// Test 1 Search "kipling" - expect 3 matches
|
||||
Collection<Persona> personaSearchResult = Persona.getPersonaByName("kipling");
|
||||
Assert.assertEquals(3, personaSearchResult.size());
|
||||
for (Persona p: personaSearchResult) {
|
||||
Assert.assertTrue(p.getComment().equalsIgnoreCase(personaComment));
|
||||
}
|
||||
|
||||
// Search 'Rudy' - expect 2 matches
|
||||
personaSearchResult = Persona.getPersonaByName("Rudy");
|
||||
Assert.assertEquals(2, personaSearchResult.size());
|
||||
|
||||
|
||||
// Search 'Sen' - expect 2 matches
|
||||
personaSearchResult = Persona.getPersonaByName("Sen");
|
||||
Assert.assertEquals(2, personaSearchResult.size());
|
||||
|
||||
|
||||
// Search 'IPL' - expect 3 matches
|
||||
personaSearchResult = Persona.getPersonaByName("IPL");
|
||||
Assert.assertEquals(3, personaSearchResult.size());
|
||||
|
||||
|
||||
// Serach "Rudyard Kipling" - expect 1 match
|
||||
personaSearchResult = Persona.getPersonaByName("Rudyard Kipling");
|
||||
Assert.assertEquals(1, personaSearchResult.size());
|
||||
Assert.assertTrue(personaSearchResult.iterator().next().getName().equalsIgnoreCase("Rudyard Kipling"));
|
||||
|
||||
// Search '' - expect ALL (4) to match
|
||||
personaSearchResult = Persona.getPersonaByName("");
|
||||
Assert.assertEquals(4, personaSearchResult.size());
|
||||
|
||||
|
||||
} catch (CentralRepoException ex) {
|
||||
Assert.fail("No name persona test failed. Exception: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests searching of Persona by account identifier substrings.
|
||||
*/
|
||||
public void testPersonaSearchByAccountIdentifier() {
|
||||
|
||||
// Test1: create Personas with similar names.
|
||||
{
|
||||
try {
|
||||
// Create an email account1
|
||||
CentralRepoAccount emailAccount1 = CentralRepository.getInstance()
|
||||
.getOrCreateAccount(emailAccountType, "joeexotic555@yahoo.com");
|
||||
|
||||
// Create all personas with same comment.
|
||||
final String personaComment = "Comment used to create a persona";
|
||||
|
||||
// Create a Persona with name "Joe Exotic" associated with the email address
|
||||
Persona.createPersonaForAccount("Joe Exotic", personaComment,
|
||||
Persona.PersonaStatus.ACTIVE, emailAccount1, "", Persona.Confidence.LOW);
|
||||
|
||||
// Create a Persona with name "Tiger King" associated with the email address
|
||||
Persona.createPersonaForAccount("Tiger King", personaComment,
|
||||
Persona.PersonaStatus.ACTIVE, emailAccount1, "", Persona.Confidence.LOW);
|
||||
|
||||
|
||||
|
||||
// Create an phone account with number "+1 999 555 3366"
|
||||
CentralRepoAccount phoneAccount1 = CentralRepository.getInstance()
|
||||
.getOrCreateAccount(phoneAccountType, "+1 999 555 3366");
|
||||
|
||||
|
||||
// Create a Persona with name "Carol Baskin" associated
|
||||
Persona.createPersonaForAccount("Carol Baskin", personaComment,
|
||||
Persona.PersonaStatus.ACTIVE, phoneAccount1, "", Persona.Confidence.LOW);
|
||||
|
||||
// Create a Persona with no name assoctaed with
|
||||
Persona.createPersonaForAccount(null, personaComment,
|
||||
Persona.PersonaStatus.ACTIVE, phoneAccount1, "", Persona.Confidence.LOW);
|
||||
|
||||
|
||||
|
||||
// Create another email account1
|
||||
CentralRepoAccount emailAccount2 = CentralRepository.getInstance()
|
||||
.getOrCreateAccount(emailAccountType, "jodoe@mail.com");
|
||||
|
||||
|
||||
|
||||
// Create a Persona with name "John Doe" associated with the email address
|
||||
Persona.createPersonaForAccount("John Doe", personaComment,
|
||||
Persona.PersonaStatus.ACTIVE, emailAccount2, "", Persona.Confidence.LOW);
|
||||
|
||||
Persona.createPersonaForAccount("Joanne Doe", personaComment,
|
||||
Persona.PersonaStatus.ACTIVE, emailAccount2, "", Persona.Confidence.LOW);
|
||||
|
||||
|
||||
|
||||
// Test1 Search on 'joe' - should get 2
|
||||
Collection<PersonaAccount> personaSearchResult = PersonaAccount.getPersonaAccountsForAccountIdentifier("joe");
|
||||
Assert.assertEquals(2, personaSearchResult.size());
|
||||
for (PersonaAccount pa: personaSearchResult) {
|
||||
Assert.assertTrue(pa.getAccount().getTypeSpecificId().contains("joe"));
|
||||
}
|
||||
|
||||
// Search on 'exotic' - should get 2
|
||||
personaSearchResult = PersonaAccount.getPersonaAccountsForAccountIdentifier("exotic");
|
||||
Assert.assertEquals(2, personaSearchResult.size());
|
||||
for (PersonaAccount pa: personaSearchResult) {
|
||||
Assert.assertTrue(pa.getAccount().getTypeSpecificId().contains("exotic"));
|
||||
}
|
||||
|
||||
// Test1 Search on '999' - should get 2
|
||||
personaSearchResult = PersonaAccount.getPersonaAccountsForAccountIdentifier("999");
|
||||
Assert.assertEquals(2, personaSearchResult.size());
|
||||
for (PersonaAccount pa: personaSearchResult) {
|
||||
Assert.assertTrue(pa.getAccount().getTypeSpecificId().contains("999"));
|
||||
}
|
||||
|
||||
// Test1 Search on '555' - should get 4
|
||||
personaSearchResult = PersonaAccount.getPersonaAccountsForAccountIdentifier("555");
|
||||
Assert.assertEquals(4, personaSearchResult.size());
|
||||
for (PersonaAccount pa: personaSearchResult) {
|
||||
Assert.assertTrue(pa.getAccount().getTypeSpecificId().contains("555"));
|
||||
}
|
||||
|
||||
// Test1 Search on 'doe' - should get 2
|
||||
personaSearchResult = PersonaAccount.getPersonaAccountsForAccountIdentifier("doe");
|
||||
Assert.assertEquals(2, personaSearchResult.size());
|
||||
for (PersonaAccount pa: personaSearchResult) {
|
||||
Assert.assertTrue(pa.getAccount().getTypeSpecificId().contains("doe"));
|
||||
}
|
||||
|
||||
// Test1 Search on '@' - should get 4
|
||||
personaSearchResult = PersonaAccount.getPersonaAccountsForAccountIdentifier("@");
|
||||
Assert.assertEquals(4, personaSearchResult.size());
|
||||
for (PersonaAccount pa: personaSearchResult) {
|
||||
Assert.assertTrue(pa.getAccount().getTypeSpecificId().contains("@"));
|
||||
}
|
||||
|
||||
// Test1 Search on '' - should get ALL (6)
|
||||
personaSearchResult = PersonaAccount.getPersonaAccountsForAccountIdentifier("");
|
||||
Assert.assertEquals(6, personaSearchResult.size());
|
||||
|
||||
|
||||
} catch (CentralRepoException ex) {
|
||||
Assert.fail("No name persona test failed. Exception: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getOrInsertExaminer() api.
|
||||
*/
|
||||
public void testExaminers() {
|
||||
|
||||
try {
|
||||
String examinerName = "abcdefg";
|
||||
CentralRepoExaminer examiner = CentralRepository.getInstance().getOrInsertExaminer(examinerName);
|
||||
Assert.assertTrue(examiner.getLoginName().equalsIgnoreCase(examinerName));
|
||||
|
||||
examinerName = "";
|
||||
examiner = CentralRepository.getInstance().getOrInsertExaminer(examinerName);
|
||||
Assert.assertTrue(examiner.getLoginName().equalsIgnoreCase(examinerName));
|
||||
|
||||
examinerName = "D'Aboville";
|
||||
examiner = CentralRepository.getInstance().getOrInsertExaminer(examinerName);
|
||||
Assert.assertTrue(examiner.getLoginName().equalsIgnoreCase(examinerName));
|
||||
|
||||
} catch (CentralRepoException ex) {
|
||||
Assert.fail("Examiner tests failed. Exception: " + ex);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -79,6 +79,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TagSet;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
@ -107,6 +108,8 @@ public final class ImageGalleryController {
|
||||
Case.Events.CONTENT_TAG_DELETED,
|
||||
Case.Events.DATA_SOURCE_DELETED
|
||||
);
|
||||
|
||||
private static final String CATEGORY_TAG_SET_PREFIX = "Project VIC";
|
||||
|
||||
/*
|
||||
* There is an image gallery controller per case. It is created during the
|
||||
@ -228,14 +231,16 @@ public final class ImageGalleryController {
|
||||
void startUp() throws TskCoreException {
|
||||
selectionModel = new FileIDSelectionModel(this);
|
||||
thumbnailCache = new ThumbnailCache(this);
|
||||
|
||||
TagSet categoryTagSet = getCategoryTagSet();
|
||||
/*
|
||||
* TODO (JIRA-5212): The next two lines need to be executed in this
|
||||
* order. Why? This suggests there is some inappropriate coupling
|
||||
* between the DrawableDB and GroupManager classes.
|
||||
*/
|
||||
groupManager = new GroupManager(this);
|
||||
drawableDB = DrawableDB.getDrawableDB(this);
|
||||
categoryManager = new CategoryManager(this);
|
||||
drawableDB = DrawableDB.getDrawableDB(this, categoryTagSet);
|
||||
categoryManager = new CategoryManager(this, categoryTagSet);
|
||||
tagsManager = new DrawableTagsManager(this);
|
||||
tagsManager.registerListener(groupManager);
|
||||
tagsManager.registerListener(categoryManager);
|
||||
@ -720,6 +725,28 @@ public final class ImageGalleryController {
|
||||
private static boolean isDrawableAndNotKnown(AbstractFile abstractFile) throws FileTypeDetector.FileTypeDetectorInitException {
|
||||
return (abstractFile.getKnown() != TskData.FileKnown.KNOWN) && FileTypeUtils.isDrawable(abstractFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TagSet with the image gallery categories.
|
||||
*
|
||||
* @return Category TagSet.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
private TagSet getCategoryTagSet() throws TskCoreException {
|
||||
List<TagSet> tagSetList = getCaseDatabase().getTaggingManager().getTagSets();
|
||||
if (tagSetList != null && !tagSetList.isEmpty()) {
|
||||
for (TagSet set : tagSetList) {
|
||||
if (set.getName().startsWith(CATEGORY_TAG_SET_PREFIX)) {
|
||||
return set;
|
||||
}
|
||||
}
|
||||
// If we get to here the Project VIC Test set wasn't found;
|
||||
throw new TskCoreException("Error loading Project VIC tag set: Tag set not found.");
|
||||
} else {
|
||||
throw new TskCoreException("Error loading Project VIC tag set: Tag set not found.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener for ingest module application events.
|
||||
|
@ -27,7 +27,7 @@ import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* A task that updates one drawable file in the drawables database.
|
||||
* A task that updates one drawable file in the drawable database.
|
||||
*/
|
||||
class UpdateDrawableFileTask extends DrawableDbTask {
|
||||
|
||||
@ -60,5 +60,5 @@ class UpdateDrawableFileTask extends DrawableDbTask {
|
||||
Logger.getLogger(UpdateDrawableFileTask.class.getName()).log(Level.SEVERE, "Error in update file task", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -19,6 +19,9 @@
|
||||
package org.sleuthkit.autopsy.imagegallery.actions;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -27,9 +30,10 @@ import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.collections.ObservableSet;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javax.annotation.Nonnull;
|
||||
@ -41,9 +45,7 @@ import org.openide.util.NbBundle;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.DrawableDbTask;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
|
||||
@ -51,6 +53,7 @@ import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.Tag;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import javafx.scene.image.ImageView;
|
||||
|
||||
/**
|
||||
* An action that associates a drawable file with a Project Vic category.
|
||||
@ -62,24 +65,24 @@ public class CategorizeAction extends Action {
|
||||
|
||||
private final ImageGalleryController controller;
|
||||
private final UndoRedoManager undoManager;
|
||||
private final DhsImageCategory cat;
|
||||
private final Set<Long> selectedFileIDs;
|
||||
private final Boolean createUndo;
|
||||
private final TagName tagName;
|
||||
|
||||
public CategorizeAction(ImageGalleryController controller, DhsImageCategory cat, Set<Long> selectedFileIDs) {
|
||||
this(controller, cat, selectedFileIDs, true);
|
||||
public CategorizeAction(ImageGalleryController controller, TagName tagName, Set<Long> selectedFileIDs) {
|
||||
this(controller, tagName, selectedFileIDs, true);
|
||||
}
|
||||
|
||||
private CategorizeAction(ImageGalleryController controller, DhsImageCategory cat, Set<Long> selectedFileIDs, Boolean createUndo) {
|
||||
super(cat.getDisplayName());
|
||||
private CategorizeAction(ImageGalleryController controller, TagName tagName, Set<Long> selectedFileIDs, Boolean createUndo) {
|
||||
super(tagName.getDisplayName());
|
||||
this.controller = controller;
|
||||
this.undoManager = controller.getUndoManager();
|
||||
this.cat = cat;
|
||||
this.selectedFileIDs = selectedFileIDs;
|
||||
this.createUndo = createUndo;
|
||||
setGraphic(cat.getGraphic());
|
||||
this.tagName = tagName;
|
||||
setGraphic(getGraphic(tagName));
|
||||
setEventHandler(actionEvent -> addCatToFiles(selectedFileIDs));
|
||||
setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(Integer.toString(cat.getCategoryNumber()))));
|
||||
setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(getCategoryNumberFromTagName(tagName))));
|
||||
}
|
||||
|
||||
static public Menu getCategoriesMenu(ImageGalleryController controller) {
|
||||
@ -87,8 +90,18 @@ public class CategorizeAction extends Action {
|
||||
}
|
||||
|
||||
final void addCatToFiles(Set<Long> ids) {
|
||||
Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{ids.toString(), cat.getDisplayName()}); //NON-NLS
|
||||
controller.queueDBTask(new CategorizeDrawableFileTask(ids, cat, createUndo));
|
||||
Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{ids.toString(), tagName.getDisplayName()}); //NON-NLS
|
||||
controller.queueDBTask(new CategorizeDrawableFileTask(ids, tagName, createUndo));
|
||||
}
|
||||
|
||||
private String getCategoryNumberFromTagName(TagName tagName) {
|
||||
String displayName = tagName.getDisplayName();
|
||||
if (displayName.contains("CAT")) {
|
||||
String[] split = displayName.split(":");
|
||||
split = split[0].split("-");
|
||||
return split[1];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -104,8 +117,8 @@ public class CategorizeAction extends Action {
|
||||
|
||||
// Each category get an item in the sub-menu. Selecting one of these menu items adds
|
||||
// a tag with the associated category.
|
||||
for (final DhsImageCategory cat : DhsImageCategory.values()) {
|
||||
MenuItem categoryItem = ActionUtils.createMenuItem(new CategorizeAction(controller, cat, selected));
|
||||
for (TagName tagName : controller.getCategoryManager().getCategories()) {
|
||||
MenuItem categoryItem = ActionUtils.createMenuItem(new CategorizeAction(controller, tagName, selected));
|
||||
getItems().add(categoryItem);
|
||||
}
|
||||
}
|
||||
@ -124,54 +137,39 @@ public class CategorizeAction extends Action {
|
||||
final Set<Long> fileIDs;
|
||||
|
||||
final boolean createUndo;
|
||||
final DhsImageCategory cat;
|
||||
final TagName catTagName;
|
||||
|
||||
CategorizeDrawableFileTask(Set<Long> fileIDs, @Nonnull DhsImageCategory cat, boolean createUndo) {
|
||||
CategorizeDrawableFileTask(Set<Long> fileIDs, @Nonnull TagName catTagName, boolean createUndo) {
|
||||
super();
|
||||
this.fileIDs = fileIDs;
|
||||
java.util.Objects.requireNonNull(cat);
|
||||
this.cat = cat;
|
||||
java.util.Objects.requireNonNull(catTagName);
|
||||
this.catTagName = catTagName;
|
||||
this.createUndo = createUndo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final DrawableTagsManager tagsManager = controller.getTagsManager();
|
||||
final CategoryManager categoryManager = controller.getCategoryManager();
|
||||
Map<Long, DhsImageCategory> oldCats = new HashMap<>();
|
||||
TagName tagName = categoryManager.getTagName(cat);
|
||||
Map<Long, TagName> oldCats = new HashMap<>();
|
||||
for (long fileID : fileIDs) {
|
||||
try {
|
||||
DrawableFile file = controller.getFileFromID(fileID); //drawable db access
|
||||
if (createUndo) {
|
||||
DhsImageCategory oldCat = file.getCategory(); //drawable db access
|
||||
TagName oldCatTagName = categoryManager.getTagName(oldCat);
|
||||
if (false == tagName.equals(oldCatTagName)) {
|
||||
oldCats.put(fileID, oldCat);
|
||||
TagName oldCatTagName = file.getCategory(); //drawable db access
|
||||
if (false == catTagName.equals(oldCatTagName)) {
|
||||
oldCats.put(fileID, oldCatTagName);
|
||||
}
|
||||
}
|
||||
|
||||
final List<ContentTag> fileTags = tagsManager.getContentTags(file);
|
||||
if (tagName == categoryManager.getTagName(DhsImageCategory.ZERO)) {
|
||||
// delete all cat tags for cat-0
|
||||
fileTags.stream()
|
||||
.filter(tag -> CategoryManager.isCategoryTagName(tag.getName()))
|
||||
.forEach((ct) -> {
|
||||
try {
|
||||
tagsManager.deleteContentTag(ct);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error removing old categories result", ex); //NON-NLS
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//add cat tag if no existing cat tag for that cat
|
||||
if (fileTags.stream()
|
||||
.map(Tag::getName)
|
||||
.filter(tagName::equals)
|
||||
.collect(Collectors.toList()).isEmpty()) {
|
||||
tagsManager.addContentTag(file, tagName, "");
|
||||
}
|
||||
|
||||
if (fileTags.stream()
|
||||
.map(Tag::getName)
|
||||
.filter(tagName::equals)
|
||||
.collect(Collectors.toList()).isEmpty()) {
|
||||
tagsManager.addContentTag(file, tagName, "");
|
||||
}
|
||||
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error categorizing result", ex); //NON-NLS
|
||||
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
|
||||
@ -183,7 +181,7 @@ public class CategorizeAction extends Action {
|
||||
}
|
||||
|
||||
if (createUndo && oldCats.isEmpty() == false) {
|
||||
undoManager.addToUndo(new CategorizationChange(controller, cat, oldCats));
|
||||
undoManager.addToUndo(new CategorizationChange(controller, catTagName, oldCats));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -194,14 +192,14 @@ public class CategorizeAction extends Action {
|
||||
@Immutable
|
||||
private final class CategorizationChange implements UndoRedoManager.UndoableCommand {
|
||||
|
||||
private final DhsImageCategory newCategory;
|
||||
private final ImmutableMap<Long, DhsImageCategory> oldCategories;
|
||||
private final TagName newTagNameCategory;
|
||||
private final ImmutableMap<Long, TagName> oldTagNameCategories;
|
||||
private final ImageGalleryController controller;
|
||||
|
||||
CategorizationChange(ImageGalleryController controller, DhsImageCategory newCategory, Map<Long, DhsImageCategory> oldCategories) {
|
||||
CategorizationChange(ImageGalleryController controller, TagName newTagNameCategory, Map<Long, TagName> oldTagNameCategories) {
|
||||
this.controller = controller;
|
||||
this.newCategory = newCategory;
|
||||
this.oldCategories = ImmutableMap.copyOf(oldCategories);
|
||||
this.newTagNameCategory = newTagNameCategory;
|
||||
this.oldTagNameCategories = ImmutableMap.copyOf(oldTagNameCategories);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -210,7 +208,7 @@ public class CategorizeAction extends Action {
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
new CategorizeAction(controller, newCategory, this.oldCategories.keySet(), false)
|
||||
new CategorizeAction(controller, newTagNameCategory, this.oldTagNameCategories.keySet(), false)
|
||||
.handle(null);
|
||||
}
|
||||
|
||||
@ -221,10 +219,42 @@ public class CategorizeAction extends Action {
|
||||
@Override
|
||||
public void undo() {
|
||||
|
||||
for (Map.Entry<Long, DhsImageCategory> entry : oldCategories.entrySet()) {
|
||||
for (Map.Entry<Long, TagName> entry : oldTagNameCategories.entrySet()) {
|
||||
new CategorizeAction(controller, entry.getValue(), Collections.singleton(entry.getKey()), false)
|
||||
.handle(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an BufferedImage to use as the icon for the given TagName.
|
||||
*
|
||||
* @param tagName The category TagName.
|
||||
*
|
||||
* @return TagName Icon BufferedImage.
|
||||
*/
|
||||
private BufferedImage getImageForTagName(TagName tagName) {
|
||||
BufferedImage off_image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2 = off_image.createGraphics();
|
||||
|
||||
g2.setColor(java.awt.Color.decode(tagName.getColor().getRgbValue()));
|
||||
g2.fillRect(0, 0, 16, 16);
|
||||
|
||||
g2.setColor(Color.BLACK);
|
||||
g2.drawRect(0, 0, 16, 16);
|
||||
return off_image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Node which is a ImageView of the icon for the given TagName.
|
||||
*
|
||||
* @param tagname
|
||||
*
|
||||
* @return Node for use as the TagName menu item graphic.
|
||||
*/
|
||||
private Node getGraphic(TagName tagname) {
|
||||
BufferedImage buff_image = getImageForTagName(tagname);
|
||||
return new ImageView(SwingFXUtils.toFXImage(buff_image, null));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import java.util.logging.Level;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonBar;
|
||||
import javafx.scene.control.ButtonType;
|
||||
@ -37,9 +38,9 @@ import javafx.scene.layout.VBox;
|
||||
import static org.apache.commons.lang.ObjectUtils.notEqual;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryPreferences;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
@ -50,7 +51,7 @@ public class CategorizeGroupAction extends CategorizeAction {
|
||||
|
||||
private final static Logger LOGGER = Logger.getLogger(CategorizeGroupAction.class.getName());
|
||||
|
||||
public CategorizeGroupAction(DhsImageCategory newCat, ImageGalleryController controller) {
|
||||
public CategorizeGroupAction(TagName newCat, ImageGalleryController controller) {
|
||||
super(controller, newCat, null);
|
||||
setEventHandler(actionEvent -> {
|
||||
controller.getViewState().getGroup().ifPresent(group -> {
|
||||
@ -60,12 +61,12 @@ public class CategorizeGroupAction extends CategorizeAction {
|
||||
//if they have preveiously disabled the warning, just go ahead and apply categories.
|
||||
addCatToFiles(ImmutableSet.copyOf(fileIDs));
|
||||
} else {
|
||||
final Map<DhsImageCategory, Long> catCountMap = new HashMap<>();
|
||||
final Map<TagName, Long> catCountMap = new HashMap<>();
|
||||
|
||||
for (Long fileID : fileIDs) {
|
||||
try {
|
||||
DhsImageCategory category = controller.getFileFromID(fileID).getCategory();
|
||||
if (false == DhsImageCategory.ZERO.equals(category) && newCat.equals(category) == false) {
|
||||
TagName category = controller.getFileFromID(fileID).getCategory();
|
||||
if (category != null && newCat.equals(category) == false) {
|
||||
catCountMap.merge(category, 1L, Long::sum);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
@ -90,18 +91,19 @@ public class CategorizeGroupAction extends CategorizeAction {
|
||||
"CategorizeGroupAction.fileCountMessage={0} with {1}",
|
||||
"CategorizeGroupAction.dontShowAgain=Don't show this message again",
|
||||
"CategorizeGroupAction.fileCountHeader=Files in the following categories will have their categories overwritten: "})
|
||||
private void showConfirmationDialog(final Map<DhsImageCategory, Long> catCountMap, DhsImageCategory newCat, ObservableList<Long> fileIDs) {
|
||||
private void showConfirmationDialog(final Map<TagName, Long> catCountMap, TagName newCat, ObservableList<Long> fileIDs) {
|
||||
|
||||
ButtonType categorizeButtonType
|
||||
= new ButtonType(Bundle.CategorizeGroupAction_OverwriteButton_text(), ButtonBar.ButtonData.APPLY);
|
||||
|
||||
VBox textFlow = new VBox();
|
||||
|
||||
for (Map.Entry<DhsImageCategory, Long> entry : catCountMap.entrySet()) {
|
||||
if (entry.getValue() > 0
|
||||
&& notEqual(entry.getKey(), newCat)) {
|
||||
for (Map.Entry<TagName, Long> entry : catCountMap.entrySet()) {
|
||||
|
||||
if (entry != null && entry.getValue() > 0
|
||||
&& notEqual(entry.getKey(), newCat)) {
|
||||
Label label = new Label(Bundle.CategorizeGroupAction_fileCountMessage(entry.getValue(), entry.getKey().getDisplayName()),
|
||||
entry.getKey().getGraphic());
|
||||
getGraphic(entry.getKey()));
|
||||
label.setContentDisplay(ContentDisplay.RIGHT);
|
||||
textFlow.getChildren().add(label);
|
||||
}
|
||||
@ -127,4 +129,8 @@ public class CategorizeGroupAction extends CategorizeAction {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Node getGraphic(TagName tagName) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -19,14 +19,14 @@
|
||||
package org.sleuthkit.autopsy.imagegallery.actions;
|
||||
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class CategorizeSelectedFilesAction extends CategorizeAction {
|
||||
|
||||
public CategorizeSelectedFilesAction(DhsImageCategory cat, ImageGalleryController controller) {
|
||||
public CategorizeSelectedFilesAction(TagName cat, ImageGalleryController controller) {
|
||||
super(controller, cat, null);
|
||||
setEventHandler(actionEvent ->
|
||||
addCatToFiles(controller.getSelectionModel().getSelected())
|
||||
|
@ -24,8 +24,11 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.eventbus.AsyncEventBus;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
import java.util.logging.Level;
|
||||
@ -33,11 +36,13 @@ import javax.annotation.concurrent.Immutable;
|
||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent.DeletedContentTagInfo;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TagSet;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
@ -56,8 +61,6 @@ public class CategoryManager {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(CategoryManager.class.getName());
|
||||
|
||||
private final ImageGalleryController controller;
|
||||
|
||||
/**
|
||||
* the DrawableDB that backs the category counts cache. The counts are
|
||||
* initialized from this, and the counting of CAT-0 is always delegated to
|
||||
@ -65,6 +68,8 @@ public class CategoryManager {
|
||||
*/
|
||||
private final DrawableDB drawableDb;
|
||||
|
||||
private final TagSet categoryTagSet;
|
||||
|
||||
/**
|
||||
* Used to distribute CategoryChangeEvents
|
||||
*/
|
||||
@ -79,32 +84,20 @@ public class CategoryManager {
|
||||
* the count related methods go through this cache, which loads initial
|
||||
* values from the database if needed.
|
||||
*/
|
||||
private final LoadingCache<DhsImageCategory, LongAdder> categoryCounts
|
||||
private final LoadingCache<TagName, LongAdder> categoryCounts
|
||||
= CacheBuilder.newBuilder().build(CacheLoader.from(this::getCategoryCountHelper));
|
||||
/**
|
||||
* cached TagNames corresponding to Categories, looked up from
|
||||
* autopsyTagManager at initial request or if invalidated by case change.
|
||||
*/
|
||||
private final LoadingCache<DhsImageCategory, TagName> catTagNameMap
|
||||
= CacheBuilder.newBuilder().build(new CacheLoader<DhsImageCategory, TagName>() {
|
||||
@Override
|
||||
public TagName load(DhsImageCategory cat) throws TskCoreException {
|
||||
return getController().getTagsManager().getTagName(cat);
|
||||
}
|
||||
});
|
||||
|
||||
public CategoryManager(ImageGalleryController controller) {
|
||||
this.controller = controller;
|
||||
public CategoryManager(ImageGalleryController controller, TagSet categoryTagSet) throws TskCoreException {
|
||||
this.drawableDb = controller.getDrawablesDatabase();
|
||||
this.categoryTagSet = categoryTagSet;
|
||||
}
|
||||
|
||||
private ImageGalleryController getController() {
|
||||
return controller;
|
||||
public List<TagName> getCategories() {
|
||||
return Collections.unmodifiableList(getSortedTagNames(categoryTagSet.getTagNames()));
|
||||
}
|
||||
|
||||
synchronized public void invalidateCaches() {
|
||||
categoryCounts.invalidateAll();
|
||||
catTagNameMap.invalidateAll();
|
||||
fireChange(Collections.emptyList(), null);
|
||||
}
|
||||
|
||||
@ -115,16 +108,8 @@ public class CategoryManager {
|
||||
*
|
||||
* @return the number of files with the given Category
|
||||
*/
|
||||
synchronized public long getCategoryCount(DhsImageCategory cat) {
|
||||
if (cat == DhsImageCategory.ZERO) {
|
||||
// Keeping track of the uncategorized files is a bit tricky while ingest
|
||||
// is going on, so always use the list of file IDs we already have along with the
|
||||
// other category counts instead of trying to track it separately.
|
||||
long allOtherCatCount = getCategoryCount(DhsImageCategory.ONE) + getCategoryCount(DhsImageCategory.TWO) + getCategoryCount(DhsImageCategory.THREE) + getCategoryCount(DhsImageCategory.FOUR) + getCategoryCount(DhsImageCategory.FIVE);
|
||||
return drawableDb.getNumberOfImageFilesInList() - allOtherCatCount;
|
||||
} else {
|
||||
return categoryCounts.getUnchecked(cat).sum();
|
||||
}
|
||||
synchronized public long getCategoryCount(TagName tagName) {
|
||||
return categoryCounts.getUnchecked(tagName).sum();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,10 +118,8 @@ public class CategoryManager {
|
||||
*
|
||||
* @param cat the Category to increment
|
||||
*/
|
||||
synchronized public void incrementCategoryCount(DhsImageCategory cat) {
|
||||
if (cat != DhsImageCategory.ZERO) {
|
||||
categoryCounts.getUnchecked(cat).increment();
|
||||
}
|
||||
synchronized public void incrementCategoryCount(TagName tagName) {
|
||||
categoryCounts.getUnchecked(tagName).increment();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -145,10 +128,8 @@ public class CategoryManager {
|
||||
*
|
||||
* @param cat the Category to decrement
|
||||
*/
|
||||
synchronized public void decrementCategoryCount(DhsImageCategory cat) {
|
||||
if (cat != DhsImageCategory.ZERO) {
|
||||
categoryCounts.getUnchecked(cat).decrement();
|
||||
}
|
||||
synchronized public void decrementCategoryCount(TagName tagName) {
|
||||
categoryCounts.getUnchecked(tagName).decrement();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -161,14 +142,14 @@ public class CategoryManager {
|
||||
* @return a LongAdder whose value is set to the number of file with the
|
||||
* given Category
|
||||
*/
|
||||
synchronized private LongAdder getCategoryCountHelper(DhsImageCategory cat) {
|
||||
synchronized private LongAdder getCategoryCountHelper(TagName cat) {
|
||||
LongAdder longAdder = new LongAdder();
|
||||
longAdder.decrement();
|
||||
try {
|
||||
longAdder.add(drawableDb.getCategoryCount(cat));
|
||||
longAdder.increment();
|
||||
} catch (IllegalStateException ex) {
|
||||
LOGGER.log(Level.WARNING, "Case closed while getting files"); //NON-NLS
|
||||
LOGGER.log(Level.WARNING, "Case closed while getting files", ex); //NON-NLS
|
||||
}
|
||||
return longAdder;
|
||||
}
|
||||
@ -178,8 +159,8 @@ public class CategoryManager {
|
||||
*
|
||||
* @param fileIDs
|
||||
*/
|
||||
public void fireChange(Collection<Long> fileIDs, DhsImageCategory newCategory) {
|
||||
categoryEventBus.post(new CategoryChangeEvent(fileIDs, newCategory));
|
||||
public void fireChange(Collection<Long> fileIDs, TagName tagName) {
|
||||
categoryEventBus.post(new CategoryChangeEvent(fileIDs, tagName));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -216,68 +197,66 @@ public class CategoryManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* get the TagName used to store this Category in the main autopsy db.
|
||||
* Returns true if the given TagName is a category tag.
|
||||
*
|
||||
* @return the TagName used for this Category
|
||||
* @param tName TagName
|
||||
*
|
||||
* @return True if tName is a category tag.
|
||||
*/
|
||||
synchronized public TagName getTagName(DhsImageCategory cat) {
|
||||
return catTagNameMap.getUnchecked(cat);
|
||||
public boolean isCategoryTagName(TagName tName) {
|
||||
return categoryTagSet.getTagNames().contains(tName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given TagName is not a category tag.
|
||||
*
|
||||
* Keep for use in location were a reference to this function is passed.
|
||||
*
|
||||
* @param tName TagName
|
||||
*
|
||||
* @return True if the given tName is not a category tag.
|
||||
*/
|
||||
public boolean isNotCategoryTagName(TagName tName) {
|
||||
return !isCategoryTagName(tName);
|
||||
|
||||
}
|
||||
|
||||
public static DhsImageCategory categoryFromTagName(TagName tagName) {
|
||||
return DhsImageCategory.fromDisplayName(tagName.getDisplayName());
|
||||
}
|
||||
|
||||
public static boolean isCategoryTagName(TagName tName) {
|
||||
return DhsImageCategory.isCategoryName(tName.getDisplayName());
|
||||
}
|
||||
|
||||
public static boolean isNotCategoryTagName(TagName tName) {
|
||||
return DhsImageCategory.isNotCategoryName(tName.getDisplayName());
|
||||
|
||||
/**
|
||||
* Returns the category tag set.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
TagSet getCategorySet() {
|
||||
return categoryTagSet;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void handleTagAdded(ContentTagAddedEvent event) {
|
||||
final ContentTag addedTag = event.getAddedTag();
|
||||
if (isCategoryTagName(addedTag.getName())) {
|
||||
final DrawableTagsManager tagsManager = controller.getTagsManager();
|
||||
try {
|
||||
//remove old category tag(s) if necessary
|
||||
for (ContentTag ct : tagsManager.getContentTags(addedTag.getContent())) {
|
||||
if (ct.getId() != addedTag.getId()
|
||||
&& CategoryManager.isCategoryTagName(ct.getName())) {
|
||||
try {
|
||||
tagsManager.deleteContentTag(ct);
|
||||
} catch (TskCoreException tskException) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to delete content tag. Unable to maintain categories in a consistent state.", tskException); //NON-NLS
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (TskCoreException tskException) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to get content tags for content. Unable to maintain category in a consistent state.", tskException); //NON-NLS
|
||||
}
|
||||
DhsImageCategory newCat = CategoryManager.categoryFromTagName(addedTag.getName());
|
||||
if (newCat != DhsImageCategory.ZERO) {
|
||||
incrementCategoryCount(newCat);
|
||||
}
|
||||
|
||||
fireChange(Collections.singleton(addedTag.getContent().getId()), newCat);
|
||||
List<DeletedContentTagInfo> removedTags = event.getDeletedTags();
|
||||
if (removedTags != null) {
|
||||
for (DeletedContentTagInfo tagInfo : removedTags) {
|
||||
handleDeletedInfo(tagInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (isCategoryTagName(addedTag.getName())) {
|
||||
incrementCategoryCount(addedTag.getName());
|
||||
fireChange(Collections.singleton(addedTag.getContent().getId()), addedTag.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void handleTagDeleted(ContentTagDeletedEvent event) {
|
||||
final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = event.getDeletedTagInfo();
|
||||
handleDeletedInfo(deletedTagInfo);
|
||||
}
|
||||
|
||||
private void handleDeletedInfo(DeletedContentTagInfo deletedTagInfo) {
|
||||
TagName tagName = deletedTagInfo.getName();
|
||||
if (isCategoryTagName(tagName)) {
|
||||
|
||||
DhsImageCategory deletedCat = CategoryManager.categoryFromTagName(tagName);
|
||||
if (deletedCat != DhsImageCategory.ZERO) {
|
||||
decrementCategoryCount(deletedCat);
|
||||
}
|
||||
decrementCategoryCount(tagName);
|
||||
fireChange(Collections.singleton(deletedTagInfo.getContentID()), null);
|
||||
}
|
||||
}
|
||||
@ -290,16 +269,16 @@ public class CategoryManager {
|
||||
public static class CategoryChangeEvent {
|
||||
|
||||
private final ImmutableSet<Long> fileIDs;
|
||||
private final DhsImageCategory newCategory;
|
||||
private final TagName tagName;
|
||||
|
||||
public CategoryChangeEvent(Collection<Long> fileIDs, DhsImageCategory newCategory) {
|
||||
public CategoryChangeEvent(Collection<Long> fileIDs, TagName tagName) {
|
||||
super();
|
||||
this.fileIDs = ImmutableSet.copyOf(fileIDs);
|
||||
this.newCategory = newCategory;
|
||||
this.tagName = tagName;
|
||||
}
|
||||
|
||||
public DhsImageCategory getNewCategory() {
|
||||
return newCategory;
|
||||
public TagName getNewCategory() {
|
||||
return tagName;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -309,4 +288,18 @@ public class CategoryManager {
|
||||
return fileIDs;
|
||||
}
|
||||
}
|
||||
|
||||
private List<TagName> getSortedTagNames(List<TagName> tagNames) {
|
||||
Comparator<TagName> compareByDisplayName = new Comparator<TagName>() {
|
||||
@Override
|
||||
public int compare(TagName tagName1, TagName tagName2) {
|
||||
return tagName1.getDisplayName().compareTo(tagName2.getDisplayName());
|
||||
}
|
||||
};
|
||||
|
||||
List<TagName> sortedTagNames = new ArrayList<>(tagNames);
|
||||
sortedTagNames.sort(compareByDisplayName);
|
||||
|
||||
return sortedTagNames;
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery.datamodel;
|
||||
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@ -89,15 +88,17 @@ public class DrawableAttribute<T extends Comparable<T>> {
|
||||
* //TODO: this has lead to awkward hard to maintain code, and little
|
||||
* advantage. move categories into DrawableDB?
|
||||
*/
|
||||
public final static DrawableAttribute<DhsImageCategory> CATEGORY
|
||||
= new DrawableAttribute<DhsImageCategory>(AttributeName.CATEGORY, Bundle.DrawableAttribute_category(),
|
||||
public final static DrawableAttribute<TagName> CATEGORY
|
||||
= new DrawableAttribute<TagName>(AttributeName.CATEGORY, Bundle.DrawableAttribute_category(),
|
||||
false,
|
||||
"category-icon.png", //NON-NLS
|
||||
f -> Collections.singleton(f.getCategory())) {
|
||||
|
||||
@Override
|
||||
public Node getGraphicForValue(DhsImageCategory val) {
|
||||
return val.getGraphic();
|
||||
public Node getGraphicForValue(TagName val) {
|
||||
|
||||
return null;
|
||||
//return val.getGraphic();
|
||||
}
|
||||
};
|
||||
|
||||
@ -235,9 +236,13 @@ public class DrawableAttribute<T extends Comparable<T>> {
|
||||
.filter(value -> (value != null && value.toString().isEmpty() == false))
|
||||
.collect(Collectors.toSet());
|
||||
} catch (Exception ex) {
|
||||
/* There is a catch-all here because the code in the try block executes third-party
|
||||
library calls that throw unchecked exceptions. See JIRA-5144, where an IllegalStateException
|
||||
was thrown because a file's MIME type was incorrectly identified as a picture type. */
|
||||
/*
|
||||
* There is a catch-all here because the code in the try block
|
||||
* executes third-party library calls that throw unchecked
|
||||
* exceptions. See JIRA-5144, where an IllegalStateException was
|
||||
* thrown because a file's MIME type was incorrectly identified as a
|
||||
* picture type.
|
||||
*/
|
||||
logger.log(Level.WARNING, "Exception while getting image attributes", ex); //NON-NLS
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
@ -52,11 +52,9 @@ import java.util.logging.Level;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.swing.SortOrder;
|
||||
import static org.apache.commons.lang3.ObjectUtils.notEqual;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.FileTypeUtils;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule;
|
||||
@ -80,6 +78,7 @@ import org.sleuthkit.datamodel.TskDataException;
|
||||
import org.sleuthkit.datamodel.VersionNumber;
|
||||
import org.sqlite.SQLiteJDBCLoader;
|
||||
import java.util.stream.Collectors;
|
||||
import org.sleuthkit.datamodel.TagSet;
|
||||
|
||||
/**
|
||||
* Provides access to the image gallery database and selected tables in the case
|
||||
@ -250,7 +249,7 @@ public final class DrawableDB {
|
||||
* could not be correctly initialized for Image
|
||||
* Gallery use.
|
||||
*/
|
||||
private DrawableDB(Path dbPath, ImageGalleryController controller) throws IOException, SQLException, TskCoreException {
|
||||
private DrawableDB(Path dbPath, ImageGalleryController controller, TagSet standardCategories) throws IOException, SQLException, TskCoreException {
|
||||
this.dbPath = dbPath;
|
||||
this.controller = controller;
|
||||
caseDb = this.controller.getCaseDatabase();
|
||||
@ -259,7 +258,7 @@ public final class DrawableDB {
|
||||
dbWriteLock();
|
||||
try {
|
||||
con = DriverManager.getConnection("jdbc:sqlite:" + dbPath.toString()); //NON-NLS
|
||||
if (!initializeDBSchema() || !upgradeDBSchema() || !prepareStatements() || !initializeStandardGroups() || !removeDeletedDataSources() || !initializeImageList()) {
|
||||
if (!initializeDBSchema() || !upgradeDBSchema() || !prepareStatements() || !initializeStandardGroups(standardCategories) || !removeDeletedDataSources() || !initializeImageList()) {
|
||||
close();
|
||||
throw new TskCoreException("Failed to initialize drawables database for Image Gallery use"); //NON-NLS
|
||||
}
|
||||
@ -297,12 +296,13 @@ public final class DrawableDB {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean initializeStandardGroups() {
|
||||
private boolean initializeStandardGroups(TagSet standardCategories) {
|
||||
CaseDbTransaction caseDbTransaction = null;
|
||||
try {
|
||||
caseDbTransaction = caseDb.beginTransaction();
|
||||
for (DhsImageCategory cat : DhsImageCategory.values()) {
|
||||
insertGroup(cat.getDisplayName(), DrawableAttribute.CATEGORY, caseDbTransaction);
|
||||
|
||||
for(TagName tagName: standardCategories.getTagNames()) {
|
||||
insertGroup(tagName.getDisplayName(), DrawableAttribute.CATEGORY, caseDbTransaction);
|
||||
}
|
||||
caseDbTransaction.commit();
|
||||
return true;
|
||||
@ -466,7 +466,7 @@ public final class DrawableDB {
|
||||
*
|
||||
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||
*/
|
||||
public static DrawableDB getDrawableDB(ImageGalleryController controller) throws TskCoreException {
|
||||
public static DrawableDB getDrawableDB(ImageGalleryController controller, TagSet standardCategories) throws TskCoreException {
|
||||
Path dbPath = ImageGalleryModule.getModuleOutputDir(controller.getCase()).resolve("drawable.db");
|
||||
try {
|
||||
deleteDatabaseIfOlderVersion(dbPath);
|
||||
@ -477,14 +477,14 @@ public final class DrawableDB {
|
||||
}
|
||||
|
||||
try {
|
||||
return new DrawableDB(dbPath, controller);
|
||||
return new DrawableDB(dbPath, controller, standardCategories);
|
||||
} catch (IOException ex) {
|
||||
throw new TskCoreException("Failed to create drawables database directory", ex); //NON-NLS
|
||||
} catch (SQLException ex) {
|
||||
throw new TskCoreException("Failed to create/open the drawables database", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the specified table exists in Drawable DB
|
||||
*
|
||||
@ -2068,7 +2068,7 @@ public final class DrawableDB {
|
||||
case MIME_TYPE:
|
||||
return groupManager.getFileIDsWithMimeType((String) groupKey.getValue());
|
||||
case CATEGORY:
|
||||
return groupManager.getFileIDsWithCategory((DhsImageCategory) groupKey.getValue());
|
||||
return groupManager.getFileIDsWithCategory((TagName) groupKey.getValue());
|
||||
case TAGS:
|
||||
return groupManager.getFileIDsWithTag((TagName) groupKey.getValue());
|
||||
}
|
||||
@ -2269,9 +2269,8 @@ public final class DrawableDB {
|
||||
*
|
||||
* @return the number of the with the given category
|
||||
*/
|
||||
public long getCategoryCount(DhsImageCategory cat) {
|
||||
public long getCategoryCount(TagName tagName) {
|
||||
try {
|
||||
TagName tagName = controller.getTagsManager().getTagName(cat);
|
||||
if (nonNull(tagName)) {
|
||||
return caseDb.getContentTagsByTagName(tagName).stream()
|
||||
.map(ContentTag::getContent)
|
||||
@ -2280,7 +2279,7 @@ public final class DrawableDB {
|
||||
.count();
|
||||
}
|
||||
} catch (IllegalStateException ex) {
|
||||
logger.log(Level.WARNING, "Case closed while getting files"); //NON-NLS
|
||||
logger.log(Level.WARNING, "Case closed while getting files", ex); //NON-NLS
|
||||
} catch (TskCoreException ex1) {
|
||||
logger.log(Level.SEVERE, "Failed to get content tags by tag name.", ex1); //NON-NLS
|
||||
}
|
||||
@ -2314,7 +2313,6 @@ public final class DrawableDB {
|
||||
DrawableTagsManager tagsManager = controller.getTagsManager();
|
||||
|
||||
String catTagNameIDs = tagsManager.getCategoryTagNames().stream()
|
||||
.filter(tagName -> notEqual(tagName.getDisplayName(), DhsImageCategory.ZERO.getDisplayName()))
|
||||
.map(TagName::getId)
|
||||
.map(Object::toString)
|
||||
.collect(Collectors.joining(",", "(", ")"));
|
||||
|
@ -40,8 +40,8 @@ import org.apache.commons.lang3.text.WordUtils;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.FileTypeUtils;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
@ -94,15 +94,19 @@ public abstract class DrawableFile {
|
||||
|
||||
private final SimpleBooleanProperty analyzed;
|
||||
|
||||
private final SimpleObjectProperty<DhsImageCategory> category = new SimpleObjectProperty<>(null);
|
||||
private final SimpleObjectProperty<TagName> categoryTagName = new SimpleObjectProperty<>(null);
|
||||
|
||||
private String make;
|
||||
|
||||
private String model;
|
||||
|
||||
private final CategoryManager categoryManager;
|
||||
|
||||
protected DrawableFile(AbstractFile file, Boolean analyzed) {
|
||||
this.analyzed = new SimpleBooleanProperty(analyzed);
|
||||
this.file = file;
|
||||
|
||||
categoryManager = ImageGalleryController.getController(Case.getCurrentCase()).getCategoryManager();
|
||||
}
|
||||
|
||||
public abstract boolean isVideo();
|
||||
@ -229,32 +233,30 @@ public abstract class DrawableFile {
|
||||
return "";
|
||||
}
|
||||
|
||||
public void setCategory(DhsImageCategory category) {
|
||||
categoryProperty().set(category);
|
||||
|
||||
}
|
||||
|
||||
public DhsImageCategory getCategory() {
|
||||
public TagName getCategory() {
|
||||
updateCategory();
|
||||
return category.get();
|
||||
return categoryTagName.get();
|
||||
}
|
||||
|
||||
public SimpleObjectProperty<DhsImageCategory> categoryProperty() {
|
||||
return category;
|
||||
public SimpleObjectProperty<TagName> categoryProperty() {
|
||||
return categoryTagName;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the category property to the most severe one found
|
||||
* Update the category property.
|
||||
*/
|
||||
private void updateCategory() {
|
||||
try {
|
||||
category.set(getContentTags().stream()
|
||||
.map(Tag::getName).filter(CategoryManager::isCategoryTagName)
|
||||
.map(TagName::getDisplayName)
|
||||
.map(DhsImageCategory::fromDisplayName)
|
||||
.sorted().findFirst() //sort by severity and take the first
|
||||
.orElse(DhsImageCategory.ZERO)
|
||||
);
|
||||
List<ContentTag> contentTags = getContentTags();
|
||||
TagName tag = null;
|
||||
for (ContentTag ct : contentTags) {
|
||||
TagName tagName = ct.getName();
|
||||
if (categoryManager.isCategoryTagName(tagName)) {
|
||||
tag = tagName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
categoryTagName.set(tag);
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.WARNING, "problem looking up category for " + this.getContentPathSafe(), ex); //NON-NLS
|
||||
} catch (IllegalStateException ex) {
|
||||
|
@ -20,10 +20,11 @@ package org.sleuthkit.autopsy.imagegallery.datamodel;
|
||||
|
||||
import com.google.common.eventbus.AsyncEventBus;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
@ -33,7 +34,6 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
@ -54,10 +54,16 @@ public final class DrawableTagsManager {
|
||||
private static final Image BOOKMARK_IMAGE = new Image("/org/sleuthkit/autopsy/images/star-bookmark-icon-16.png");
|
||||
|
||||
private final TagsManager autopsyTagsManager;
|
||||
/** The tag name corresponding to the "built-in" tag "Follow Up" */
|
||||
/**
|
||||
* The tag name corresponding to the "built-in" tag "Follow Up"
|
||||
*/
|
||||
private final TagName followUpTagName;
|
||||
private final TagName bookmarkTagName;
|
||||
|
||||
private final ImageGalleryController controller;
|
||||
|
||||
private final Comparator<TagName> compareByDisplayName;
|
||||
|
||||
/**
|
||||
* Used to distribute TagsChangeEvents
|
||||
*/
|
||||
@ -74,6 +80,14 @@ public final class DrawableTagsManager {
|
||||
this.autopsyTagsManager = controller.getCase().getServices().getTagsManager();
|
||||
followUpTagName = getTagName(Bundle.DrawableTagsManager_followUp());
|
||||
bookmarkTagName = getTagName(Bundle.DrawableTagsManager_bookMark());
|
||||
this.controller = controller;
|
||||
|
||||
compareByDisplayName = new Comparator<TagName>() {
|
||||
@Override
|
||||
public int compare(TagName tagName1, TagName tagName2) {
|
||||
return tagName1.getDisplayName().compareTo(tagName2.getDisplayName());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,25 +143,26 @@ public final class DrawableTagsManager {
|
||||
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||
*/
|
||||
public List<TagName> getNonCategoryTagNames() throws TskCoreException {
|
||||
return autopsyTagsManager.getAllTagNames().stream()
|
||||
.filter(CategoryManager::isNotCategoryTagName)
|
||||
.distinct().sorted()
|
||||
.collect(Collectors.toList());
|
||||
List<TagName> nonCategoryTagNames = new ArrayList<>();
|
||||
List<TagName> allTags = autopsyTagsManager.getAllTagNames();
|
||||
for (TagName tag : allTags) {
|
||||
if (controller.getCategoryManager().isNotCategoryTagName(tag)) {
|
||||
nonCategoryTagNames.add(tag);
|
||||
}
|
||||
}
|
||||
nonCategoryTagNames.sort(compareByDisplayName);
|
||||
return nonCategoryTagNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the TagNames that are categories
|
||||
*
|
||||
* @return All the TagNames that are categories, in alphabetical order by
|
||||
* displayName.
|
||||
* @return All the TagNames that are categories.
|
||||
*
|
||||
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||
*/
|
||||
public List<TagName> getCategoryTagNames() throws TskCoreException {
|
||||
return autopsyTagsManager.getAllTagNames().stream()
|
||||
.filter(CategoryManager::isCategoryTagName)
|
||||
.distinct().sorted()
|
||||
.collect(Collectors.toList());
|
||||
return controller.getCategoryManager().getCategorySet().getTagNames();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -190,15 +205,11 @@ public final class DrawableTagsManager {
|
||||
returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName);
|
||||
if (returnTagName != null) {
|
||||
return returnTagName;
|
||||
}
|
||||
}
|
||||
throw new TskCoreException("Tag name exists but an error occured in retrieving it", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public TagName getTagName(DhsImageCategory cat) throws TskCoreException {
|
||||
return getTagName(cat.getDisplayName());
|
||||
}
|
||||
|
||||
public ContentTag addContentTag(DrawableFile file, TagName tagName, String comment) throws TskCoreException {
|
||||
return autopsyTagsManager.addContentTag(file.getAbstractFile(), tagName, comment);
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ public class VideoFile extends DrawableFile {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the genereric video thumbnail.
|
||||
* Get the generic video thumbnail.
|
||||
*
|
||||
* @return The thumbnail.
|
||||
*/
|
||||
|
@ -59,6 +59,7 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
|
||||
|
||||
public String getValueDisplayName() {
|
||||
return Objects.equals(attr, DrawableAttribute.TAGS)
|
||||
|| Objects.equals(attr, DrawableAttribute.CATEGORY)
|
||||
? ((TagName) getValue()).getDisplayName()
|
||||
: Objects.toString(getValue(), "unknown");
|
||||
}
|
||||
@ -74,8 +75,9 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
|
||||
|
||||
hash = 79 * hash + Objects.hashCode(this.val);
|
||||
hash = 79 * hash + Objects.hashCode(this.attr);
|
||||
if (this.dataSource != null)
|
||||
hash = 79 * hash + (int)this.dataSource.getId();
|
||||
if (this.dataSource != null) {
|
||||
hash = 79 * hash + (int) this.dataSource.getId();
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
@ -99,20 +101,20 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
|
||||
if (!Objects.equals(this.attr, other.attr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Data source is significant only for PATH based groups.
|
||||
if (this.attr == DrawableAttribute.PATH) {
|
||||
if (this.dataSource != null && other.dataSource != null) {
|
||||
return this.dataSource.getId() == other.dataSource.getId();
|
||||
return this.dataSource.getId() == other.dataSource.getId();
|
||||
} else if (this.dataSource == null && other.dataSource == null) {
|
||||
// neither group has a datasource
|
||||
return true;
|
||||
} else {
|
||||
// one group has a datasource, other doesn't
|
||||
// one group has a datasource, other doesn't
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,6 @@ import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.swing.SortOrder;
|
||||
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
|
||||
import static org.apache.commons.lang3.ObjectUtils.notEqual;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
@ -70,9 +69,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
||||
@ -106,23 +103,23 @@ public class GroupManager {
|
||||
private final ImageGalleryController controller;
|
||||
|
||||
/**
|
||||
* Keeps track of the current path group
|
||||
* - a change in path indicates the current path group is analyzed
|
||||
* Keeps track of the current path group - a change in path indicates the
|
||||
* current path group is analyzed
|
||||
*/
|
||||
@GuardedBy("this") //NOPMD
|
||||
private GroupKey<?> currentPathGroup = null;
|
||||
|
||||
|
||||
/**
|
||||
* list of all analyzed groups - i.e. groups that are ready to be shown to user.
|
||||
* These are groups under the selected groupBy attribute.
|
||||
* list of all analyzed groups - i.e. groups that are ready to be shown to
|
||||
* user. These are groups under the selected groupBy attribute.
|
||||
*/
|
||||
@GuardedBy("this") //NOPMD
|
||||
private final ObservableList<DrawableGroup> analyzedGroups = FXCollections.observableArrayList();
|
||||
private final ObservableList<DrawableGroup> unmodifiableAnalyzedGroups = FXCollections.unmodifiableObservableList(analyzedGroups);
|
||||
|
||||
/**
|
||||
* list of unseen groups
|
||||
* These are groups under the selected groupBy attribute.
|
||||
* list of unseen groups These are groups under the selected groupBy
|
||||
* attribute.
|
||||
*/
|
||||
@GuardedBy("this") //NOPMD
|
||||
private final ObservableList<DrawableGroup> unSeenGroups = FXCollections.observableArrayList();
|
||||
@ -186,15 +183,15 @@ public class GroupManager {
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
synchronized public Set<GroupKey<?>> getAllGroupKeysForFile(DrawableFile file) throws TskCoreException, TskDataException {
|
||||
Set<GroupKey<?>> resultSet = new HashSet<>();
|
||||
|
||||
for (DrawableAttribute<?> attr: DrawableAttribute.getGroupableAttrs()) {
|
||||
|
||||
for (DrawableAttribute<?> attr : DrawableAttribute.getGroupableAttrs()) {
|
||||
for (Comparable<?> val : attr.getValue(file)) {
|
||||
|
||||
if (attr == DrawableAttribute.PATH) {
|
||||
resultSet.add(new GroupKey(attr, val, file.getDataSource()));
|
||||
} else if (attr == DrawableAttribute.TAGS) {
|
||||
//don't show groups for the categories when grouped by tags.
|
||||
if (CategoryManager.isNotCategoryTagName((TagName) val)) {
|
||||
if (controller.getCategoryManager().isNotCategoryTagName((TagName) val)) {
|
||||
resultSet.add(new GroupKey(attr, val, null));
|
||||
}
|
||||
} else {
|
||||
@ -204,9 +201,8 @@ public class GroupManager {
|
||||
}
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
*
|
||||
* Returns GroupKeys for all the Groups the given file is a part of.
|
||||
*
|
||||
@ -225,7 +221,7 @@ public class GroupManager {
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param groupKey
|
||||
*
|
||||
@ -244,7 +240,7 @@ public class GroupManager {
|
||||
setGroupBy(DrawableAttribute.PATH);
|
||||
setSortOrder(SortOrder.ASCENDING);
|
||||
setDataSource(null);
|
||||
|
||||
|
||||
unSeenGroups.forEach(controller.getCategoryManager()::unregisterListener);
|
||||
unSeenGroups.clear();
|
||||
analyzedGroups.forEach(controller.getCategoryManager()::unregisterListener);
|
||||
@ -300,12 +296,12 @@ public class GroupManager {
|
||||
public ListenableFuture<?> markGroupUnseen(DrawableGroup group) {
|
||||
return exec.submit(() -> {
|
||||
try {
|
||||
|
||||
|
||||
getDrawableDB().markGroupUnseen(group.getGroupKey());
|
||||
// only update and reshuffle if its new results
|
||||
if (group.isSeen() == true) {
|
||||
group.setSeen(false);
|
||||
}
|
||||
}
|
||||
// The group may already be in 'unseen' state, e.g. when new files are added,
|
||||
// but not be on the unseenGroupsList yet.
|
||||
updateUnSeenGroups(group);
|
||||
@ -314,7 +310,7 @@ public class GroupManager {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update unseenGroups list accordingly based on the current status of
|
||||
* 'group'. Removes it if it is seen or adds it if it is unseen.
|
||||
@ -322,13 +318,13 @@ public class GroupManager {
|
||||
* @param group
|
||||
*/
|
||||
synchronized private void updateUnSeenGroups(DrawableGroup group) {
|
||||
if (group.isSeen()) {
|
||||
unSeenGroups.removeAll(group);
|
||||
} else if (unSeenGroups.contains(group) == false &&
|
||||
getGroupBy() == group.getGroupKey().getAttribute()) {
|
||||
unSeenGroups.add(group);
|
||||
}
|
||||
sortUnseenGroups();
|
||||
if (group.isSeen()) {
|
||||
unSeenGroups.removeAll(group);
|
||||
} else if (unSeenGroups.contains(group) == false
|
||||
&& getGroupBy() == group.getGroupKey().getAttribute()) {
|
||||
unSeenGroups.add(group);
|
||||
}
|
||||
sortUnseenGroups();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -390,7 +386,7 @@ public class GroupManager {
|
||||
switch (groupKey.getAttribute().attrName) {
|
||||
//these cases get special treatment
|
||||
case CATEGORY:
|
||||
return getFileIDsWithCategory((DhsImageCategory) groupKey.getValue());
|
||||
return getFileIDsWithCategory((TagName) groupKey.getValue());
|
||||
case TAGS:
|
||||
return getFileIDsWithTag((TagName) groupKey.getValue());
|
||||
case MIME_TYPE:
|
||||
@ -405,33 +401,18 @@ public class GroupManager {
|
||||
|
||||
// @@@ This was kind of slow in the profiler. Maybe we should cache it.
|
||||
// Unless the list of file IDs is necessary, use countFilesWithCategory() to get the counts.
|
||||
synchronized public Set<Long> getFileIDsWithCategory(DhsImageCategory category) throws TskCoreException {
|
||||
synchronized public Set<Long> getFileIDsWithCategory(TagName category) throws TskCoreException {
|
||||
Set<Long> fileIDsToReturn = Collections.emptySet();
|
||||
|
||||
try {
|
||||
final DrawableTagsManager tagsManager = controller.getTagsManager();
|
||||
if (category == DhsImageCategory.ZERO) {
|
||||
Set<Long> fileIDs = new HashSet<>();
|
||||
for (TagName catTagName : tagsManager.getCategoryTagNames()) {
|
||||
if (notEqual(catTagName.getDisplayName(), DhsImageCategory.ZERO.getDisplayName())) {
|
||||
tagsManager.getContentTagsByTagName(catTagName).stream()
|
||||
.filter(ct -> ct.getContent() instanceof AbstractFile)
|
||||
.map(ct -> ct.getContent().getId())
|
||||
.filter(getDrawableDB()::isInDB)
|
||||
.forEach(fileIDs::add);
|
||||
}
|
||||
}
|
||||
|
||||
fileIDsToReturn = getDrawableDB().findAllFileIdsWhere("obj_id NOT IN (" + StringUtils.join(fileIDs, ',') + ")"); //NON-NLS
|
||||
} else {
|
||||
|
||||
List<ContentTag> contentTags = tagsManager.getContentTagsByTagName(tagsManager.getTagName(category));
|
||||
fileIDsToReturn = contentTags.stream()
|
||||
.filter(ct -> ct.getContent() instanceof AbstractFile)
|
||||
.filter(ct -> getDrawableDB().isInDB(ct.getContent().getId()))
|
||||
.map(ct -> ct.getContent().getId())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
List<ContentTag> contentTags = tagsManager.getContentTagsByTagName(category);
|
||||
fileIDsToReturn = contentTags.stream()
|
||||
.filter(ct -> ct.getContent() instanceof AbstractFile)
|
||||
.filter(ct -> getDrawableDB().isInDB(ct.getContent().getId()))
|
||||
.map(ct -> ct.getContent().getId())
|
||||
.collect(Collectors.toSet());
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "TSK error getting files in Category:" + category.getDisplayName(), ex); //NON-NLS
|
||||
throw ex;
|
||||
@ -552,14 +533,14 @@ public class GroupManager {
|
||||
synchronized public void handleTagAdded(ContentTagAddedEvent evt) {
|
||||
GroupKey<?> newGroupKey = null;
|
||||
final long fileID = evt.getAddedTag().getContent().getId();
|
||||
if (getGroupBy() == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(evt.getAddedTag().getName())) {
|
||||
newGroupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(evt.getAddedTag().getName()), getDataSource());
|
||||
if (getGroupBy() == DrawableAttribute.CATEGORY && controller.getCategoryManager().isCategoryTagName(evt.getAddedTag().getName())) {
|
||||
newGroupKey = new GroupKey<>(DrawableAttribute.CATEGORY, evt.getAddedTag().getName(), getDataSource());
|
||||
for (GroupKey<?> oldGroupKey : groupMap.keySet()) {
|
||||
if (oldGroupKey.equals(newGroupKey) == false) {
|
||||
removeFromGroup(oldGroupKey, fileID);
|
||||
}
|
||||
}
|
||||
} else if (getGroupBy() == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(evt.getAddedTag().getName())) {
|
||||
} else if (getGroupBy() == DrawableAttribute.TAGS && controller.getCategoryManager().isNotCategoryTagName(evt.getAddedTag().getName())) {
|
||||
newGroupKey = new GroupKey<>(DrawableAttribute.TAGS, evt.getAddedTag().getName(), getDataSource());
|
||||
}
|
||||
if (newGroupKey != null) {
|
||||
@ -569,7 +550,8 @@ public class GroupManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an analyzed file to the in-memory group data structures. Marks the group as unseen.
|
||||
* Adds an analyzed file to the in-memory group data structures. Marks the
|
||||
* group as unseen.
|
||||
*
|
||||
* @param group Group being added to (will be null if a group has not yet
|
||||
* been created)
|
||||
@ -584,16 +566,20 @@ public class GroupManager {
|
||||
//if there wasn't already a DrawableGroup, then check if this group is now
|
||||
// in an appropriate state to get one made.
|
||||
// Path group, for example, only gets a DrawableGroup created when all files are analyzed
|
||||
/* NOTE: With the current (Jan 2019) behavior of how we detect a PATH group as being analyzed, the group
|
||||
* is not marked as analyzed until we add a file for another folder. So, when the last picture in a folder
|
||||
* is added to the group, the call to 'populateIfAnalyzed' will still not return a group and therefore this
|
||||
* method will never mark the group as unseen. */
|
||||
/*
|
||||
* NOTE: With the current (Jan 2019) behavior of how we detect a
|
||||
* PATH group as being analyzed, the group is not marked as analyzed
|
||||
* until we add a file for another folder. So, when the last picture
|
||||
* in a folder is added to the group, the call to
|
||||
* 'populateIfAnalyzed' will still not return a group and therefore
|
||||
* this method will never mark the group as unseen.
|
||||
*/
|
||||
group = popuplateIfAnalyzed(groupKey, null);
|
||||
} else {
|
||||
//if there is aleady a group that was previously deemed fully analyzed, then add this newly analyzed file to it.
|
||||
group.addFile(fileID);
|
||||
}
|
||||
|
||||
|
||||
// reset the seen status for the group (if it is currently considered analyzed)
|
||||
if (group != null) {
|
||||
markGroupUnseen(group);
|
||||
@ -605,18 +591,14 @@ public class GroupManager {
|
||||
GroupKey<?> groupKey = null;
|
||||
final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo();
|
||||
final TagName deletedTagName = deletedTagInfo.getName();
|
||||
if (getGroupBy() == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(deletedTagName)) {
|
||||
groupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(deletedTagName), null);
|
||||
} else if (getGroupBy() == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(deletedTagName)) {
|
||||
if (getGroupBy() == DrawableAttribute.CATEGORY && controller.getCategoryManager().isCategoryTagName(deletedTagName)) {
|
||||
groupKey = new GroupKey<>(DrawableAttribute.CATEGORY, deletedTagName, null);
|
||||
} else if (getGroupBy() == DrawableAttribute.TAGS && controller.getCategoryManager().isNotCategoryTagName(deletedTagName)) {
|
||||
groupKey = new GroupKey<>(DrawableAttribute.TAGS, deletedTagName, null);
|
||||
}
|
||||
if (groupKey != null) {
|
||||
final long fileID = deletedTagInfo.getContentID();
|
||||
DrawableGroup g = removeFromGroup(groupKey, fileID);
|
||||
|
||||
if (controller.getCategoryManager().getTagName(DhsImageCategory.ZERO).equals(deletedTagName) == false) {
|
||||
addFileToGroup(null, new GroupKey<>(DrawableAttribute.CATEGORY, DhsImageCategory.ZERO, null), fileID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -653,13 +635,13 @@ public class GroupManager {
|
||||
try {
|
||||
DrawableFile file = getDrawableDB().getFileFromID(fileId);
|
||||
String pathVal = file.getDrawablePath();
|
||||
GroupKey<?> pathGroupKey = new GroupKey<>(DrawableAttribute.PATH,pathVal, file.getDataSource());
|
||||
|
||||
GroupKey<?> pathGroupKey = new GroupKey<>(DrawableAttribute.PATH, pathVal, file.getDataSource());
|
||||
|
||||
updateCurrentPathGroup(pathGroupKey);
|
||||
} catch (TskCoreException | TskDataException ex) {
|
||||
logger.log(Level.WARNING, "Error getting drawabledb for fileId " + fileId, ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Update all the groups that this file belongs to
|
||||
Set<GroupKey<?>> groupsForFile = getAllGroupKeysForFile(fileId);
|
||||
for (GroupKey<?> gk : groupsForFile) {
|
||||
@ -672,45 +654,45 @@ public class GroupManager {
|
||||
//we fire this event for all files so that the category counts get updated during initial db population
|
||||
controller.getCategoryManager().fireChange(updatedFileIDs, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the given path is different from the current path group.
|
||||
* If so, updates the current path group as analyzed, and sets current path
|
||||
* group to the given path.
|
||||
*
|
||||
* The idea is that when the path of the files being processed changes,
|
||||
* we have moved from one folder to the next, and the group for the
|
||||
* previous PATH can be considered as analyzed and can be displayed.
|
||||
*
|
||||
* NOTE: this a close approximation for when all files in a folder have been processed,
|
||||
* but there's some room for error - files may go down the ingest pipleline
|
||||
* out of order or the events may not always arrive in the same order
|
||||
*
|
||||
* @param groupKey
|
||||
* Checks if the given path is different from the current path group. If so,
|
||||
* updates the current path group as analyzed, and sets current path group
|
||||
* to the given path.
|
||||
*
|
||||
* The idea is that when the path of the files being processed changes, we
|
||||
* have moved from one folder to the next, and the group for the previous
|
||||
* PATH can be considered as analyzed and can be displayed.
|
||||
*
|
||||
* NOTE: this a close approximation for when all files in a folder have been
|
||||
* processed, but there's some room for error - files may go down the ingest
|
||||
* pipleline out of order or the events may not always arrive in the same
|
||||
* order
|
||||
*
|
||||
* @param groupKey
|
||||
*/
|
||||
synchronized private void updateCurrentPathGroup(GroupKey<?> groupKey) {
|
||||
try {
|
||||
if (groupKey.getAttribute() == DrawableAttribute.PATH) {
|
||||
|
||||
|
||||
if (this.currentPathGroup == null) {
|
||||
currentPathGroup = groupKey;
|
||||
}
|
||||
else if (groupKey.getValue().toString().equalsIgnoreCase(this.currentPathGroup.getValue().toString()) == false) {
|
||||
} else if (groupKey.getValue().toString().equalsIgnoreCase(this.currentPathGroup.getValue().toString()) == false) {
|
||||
// mark the last path group as analyzed
|
||||
getDrawableDB().markGroupAnalyzed(currentPathGroup);
|
||||
popuplateIfAnalyzed(currentPathGroup, null);
|
||||
|
||||
|
||||
currentPathGroup = groupKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (TskCoreException ex) {
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error setting is_analyzed status for group: %s", groupKey.getValue().toString()), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets current path group, after marking the current path group as analyzed.
|
||||
* Resets current path group, after marking the current path group as
|
||||
* analyzed.
|
||||
*/
|
||||
synchronized public void resetCurrentPathGroup() {
|
||||
try {
|
||||
@ -719,11 +701,11 @@ public class GroupManager {
|
||||
popuplateIfAnalyzed(currentPathGroup, null);
|
||||
currentPathGroup = null;
|
||||
}
|
||||
}
|
||||
catch (TskCoreException ex) {
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error resetting last path group: %s", currentPathGroup.getValue().toString()), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the group is analyzed (or other criteria based on grouping) and should
|
||||
* be shown to the user, then add it to the appropriate data structures so
|
||||
@ -768,12 +750,12 @@ public class GroupManager {
|
||||
controller.getCategoryManager().registerListener(group);
|
||||
groupMap.put(groupKey, group);
|
||||
}
|
||||
|
||||
|
||||
// Add to analyzedGroups only if it's the a group with the selected groupBy attribute
|
||||
if ((analyzedGroups.contains(group) == false) &&
|
||||
(getGroupBy() == group.getGroupKey().getAttribute())) {
|
||||
analyzedGroups.add(group);
|
||||
sortAnalyzedGroups();
|
||||
if ((analyzedGroups.contains(group) == false)
|
||||
&& (getGroupBy() == group.getGroupKey().getAttribute())) {
|
||||
analyzedGroups.add(group);
|
||||
sortAnalyzedGroups();
|
||||
}
|
||||
updateUnSeenGroups(group);
|
||||
|
||||
@ -944,11 +926,11 @@ public class GroupManager {
|
||||
switch (groupBy.attrName) {
|
||||
//these cases get special treatment
|
||||
case CATEGORY:
|
||||
results.putAll(null, Arrays.asList(DhsImageCategory.values()));
|
||||
results.putAll(null, controller.getCategoryManager().getCategories());
|
||||
break;
|
||||
case TAGS:
|
||||
results.putAll(null, controller.getTagsManager().getTagNamesInUse().stream()
|
||||
.filter(CategoryManager::isNotCategoryTagName)
|
||||
.filter(controller.getCategoryManager()::isNotCategoryTagName)
|
||||
.collect(Collectors.toList()));
|
||||
break;
|
||||
|
||||
|
@ -63,9 +63,7 @@ public class GroupSortBy implements Comparator<DrawableGroup> {
|
||||
*/
|
||||
public final static GroupSortBy PRIORITY
|
||||
= new GroupSortBy(Bundle.GroupSortBy_priority(), "hashset_hits.png",
|
||||
Comparator.comparing(DrawableGroup::getHashHitDensity)
|
||||
.thenComparing(Comparator.comparing(DrawableGroup::getUncategorizedCount))
|
||||
.reversed());
|
||||
Comparator.comparing(DrawableGroup::getHashHitDensity).reversed());
|
||||
|
||||
private final static ObservableList<GroupSortBy> values = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(PRIORITY, NONE, GROUP_BY_VALUE, FILE_COUNT));
|
||||
|
||||
|
@ -56,7 +56,7 @@ public final class GuiUtils {
|
||||
|
||||
/**
|
||||
* Create a MenuItem that performs the given action and also set the Action
|
||||
* as the action for the given Button. Usefull to have a SplitMenuButton
|
||||
* as the action for the given Button. Useful to have a SplitMenuButton
|
||||
* remember the last chosen menu item as its action.
|
||||
*
|
||||
* @param button
|
||||
|
@ -34,10 +34,10 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.util.Pair;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager.CategoryChangeEvent;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
|
||||
/**
|
||||
* Displays summary statistics (counts) for each group
|
||||
@ -45,13 +45,13 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager.CategoryChan
|
||||
public class SummaryTablePane extends AnchorPane {
|
||||
|
||||
@FXML
|
||||
private TableColumn<Pair<DhsImageCategory, Long>, String> catColumn;
|
||||
private TableColumn<Pair<TagName, Long>, String> catColumn;
|
||||
|
||||
@FXML
|
||||
private TableColumn<Pair<DhsImageCategory, Long>, Long> countColumn;
|
||||
private TableColumn<Pair<TagName, Long>, Long> countColumn;
|
||||
|
||||
@FXML
|
||||
private TableView<Pair<DhsImageCategory, Long>> tableView;
|
||||
private TableView<Pair<TagName, Long>> tableView;
|
||||
|
||||
private final ImageGalleryController controller;
|
||||
|
||||
@ -97,9 +97,9 @@ public class SummaryTablePane extends AnchorPane {
|
||||
*/
|
||||
@Subscribe
|
||||
public void handleCategoryChanged(CategoryChangeEvent evt) {
|
||||
final ObservableList<Pair<DhsImageCategory, Long>> data = FXCollections.observableArrayList();
|
||||
final ObservableList<Pair<TagName, Long>> data = FXCollections.observableArrayList();
|
||||
if (Case.isCaseOpen()) {
|
||||
for (DhsImageCategory cat : DhsImageCategory.values()) {
|
||||
for (TagName cat : controller.getCategoryManager().getCategories()) {
|
||||
data.add(new Pair<>(cat, controller.getCategoryManager().getCategoryCount(cat)));
|
||||
}
|
||||
}
|
||||
|
@ -20,12 +20,9 @@ package org.sleuthkit.autopsy.imagegallery.gui;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -64,7 +61,6 @@ import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED;
|
||||
import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_DELETED;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeGroupAction;
|
||||
@ -220,13 +216,13 @@ public class Toolbar extends ToolBar {
|
||||
});
|
||||
initTagMenuButton();
|
||||
|
||||
CategorizeGroupAction cat5GroupAction = new CategorizeGroupAction(DhsImageCategory.FIVE, controller);
|
||||
CategorizeGroupAction cat5GroupAction = new CategorizeGroupAction(controller.getCategoryManager().getCategories().get(0), controller);
|
||||
catGroupMenuButton.setOnAction(cat5GroupAction);
|
||||
catGroupMenuButton.setText(cat5GroupAction.getText());
|
||||
catGroupMenuButton.setGraphic(cat5GroupAction.getGraphic());
|
||||
catGroupMenuButton.showingProperty().addListener(showing -> {
|
||||
if (catGroupMenuButton.isShowing()) {
|
||||
List<MenuItem> categoryMenues = Lists.transform(Arrays.asList(DhsImageCategory.values()),
|
||||
List<MenuItem> categoryMenues = Lists.transform(controller.getCategoryManager().getCategories(),
|
||||
cat -> GuiUtils.createAutoAssigningMenuItem(catGroupMenuButton, new CategorizeGroupAction(cat, controller)));
|
||||
catGroupMenuButton.getItems().setAll(categoryMenues);
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
@ -34,10 +36,11 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TagName.HTML_COLOR;
|
||||
|
||||
/**
|
||||
* Interface for classes that are views of a single DrawableFile. Implementation
|
||||
@ -54,19 +57,9 @@ public interface DrawableView {
|
||||
|
||||
static final CornerRadii CAT_CORNER_RADII = new CornerRadii(3);
|
||||
|
||||
static final Border HASH_BORDER = new Border(new BorderStroke(Color.PURPLE, BorderStrokeStyle.DASHED, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
|
||||
Border HASH_BORDER = new Border(new BorderStroke(Color.CYAN, BorderStrokeStyle.DASHED, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
|
||||
|
||||
static final Border CAT1_BORDER = new Border(new BorderStroke(DhsImageCategory.ONE.getColor(), BorderStrokeStyle.SOLID, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
|
||||
|
||||
static final Border CAT2_BORDER = new Border(new BorderStroke(DhsImageCategory.TWO.getColor(), BorderStrokeStyle.SOLID, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
|
||||
|
||||
static final Border CAT3_BORDER = new Border(new BorderStroke(DhsImageCategory.THREE.getColor(), BorderStrokeStyle.SOLID, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
|
||||
|
||||
static final Border CAT4_BORDER = new Border(new BorderStroke(DhsImageCategory.FOUR.getColor(), BorderStrokeStyle.SOLID, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
|
||||
|
||||
static final Border CAT5_BORDER = new Border(new BorderStroke(DhsImageCategory.FIVE.getColor(), BorderStrokeStyle.SOLID, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
|
||||
|
||||
static final Border CAT0_BORDER = new Border(new BorderStroke(DhsImageCategory.ZERO.getColor(), BorderStrokeStyle.SOLID, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
|
||||
Map<String, Border> BORDER_MAP = new HashMap<>();
|
||||
|
||||
Region getCategoryBorderRegion();
|
||||
|
||||
@ -115,38 +108,38 @@ public interface DrawableView {
|
||||
|
||||
}
|
||||
|
||||
static Border getCategoryBorder(DhsImageCategory category) {
|
||||
if (category != null) {
|
||||
switch (category) {
|
||||
case ONE:
|
||||
return CAT1_BORDER;
|
||||
case TWO:
|
||||
return CAT2_BORDER;
|
||||
case THREE:
|
||||
return CAT3_BORDER;
|
||||
case FOUR:
|
||||
return CAT4_BORDER;
|
||||
case FIVE:
|
||||
return CAT5_BORDER;
|
||||
case ZERO:
|
||||
default:
|
||||
return CAT0_BORDER;
|
||||
/**
|
||||
* Get the boarder for the given category.
|
||||
*
|
||||
* Static instances of the boarders will lazily constructed and stored in
|
||||
* the BORDER_MAP.
|
||||
*
|
||||
* @param category
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static Border getCategoryBorder(TagName category) {
|
||||
Border border = null;
|
||||
if (category != null && category.getColor() != HTML_COLOR.NONE) {
|
||||
border = BORDER_MAP.get(category.getDisplayName());
|
||||
|
||||
if (border == null) {
|
||||
border = new Border(new BorderStroke(Color.web(category.getColor().getRgbValue()), BorderStrokeStyle.SOLID, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
|
||||
BORDER_MAP.put(category.getDisplayName(), border);
|
||||
}
|
||||
} else {
|
||||
return CAT0_BORDER;
|
||||
}
|
||||
return border;
|
||||
}
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
|
||||
default DhsImageCategory updateCategory() {
|
||||
default TagName updateCategory() {
|
||||
if (getFile().isPresent()) {
|
||||
final DhsImageCategory category = getFile().map(DrawableFile::getCategory).orElse(DhsImageCategory.ZERO);
|
||||
final Border border = hasHashHit() && (category == DhsImageCategory.ZERO) ? HASH_BORDER : getCategoryBorder(category);
|
||||
final TagName tagNameCat = getFile().map(DrawableFile::getCategory).orElse(null);
|
||||
final Border border = hasHashHit() ? HASH_BORDER : getCategoryBorder(tagNameCat);
|
||||
Platform.runLater(() -> getCategoryBorderRegion().setBorder(border));
|
||||
return category;
|
||||
return tagNameCat;
|
||||
} else {
|
||||
return DhsImageCategory.ZERO;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -182,35 +182,6 @@
|
||||
</Button>
|
||||
</children>
|
||||
</HBox>
|
||||
<HBox fx:id="catSegmentedContainer" alignment="CENTER" maxWidth="-Infinity" spacing="5.0">
|
||||
<children>
|
||||
<Label fx:id="catHeadingLabel" text="Category:">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../../images/category-icon.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Label>
|
||||
<SegmentedButton fx:id="catSegmentedButton">
|
||||
<buttons>
|
||||
<RadioButton fx:id="cat0Toggle" mnemonicParsing="false" text="0" HBox.hgrow="ALWAYS">
|
||||
<toggleGroup>
|
||||
<ToggleGroup fx:id="cat" />
|
||||
</toggleGroup></RadioButton>
|
||||
<RadioButton fx:id="cat1Toggle" mnemonicParsing="false" style="" styleClass="button" text="1" toggleGroup="$cat" HBox.hgrow="ALWAYS" />
|
||||
<RadioButton id="Cat2Toggle" fx:id="cat2Toggle" mnemonicParsing="false" styleClass="button" text="2" toggleGroup="$cat" HBox.hgrow="ALWAYS" />
|
||||
<RadioButton fx:id="cat3Toggle" mnemonicParsing="false" styleClass="button" text="3" toggleGroup="$cat" HBox.hgrow="ALWAYS" />
|
||||
<RadioButton fx:id="cat4Toggle" mnemonicParsing="false" styleClass="button" text="4" toggleGroup="$cat" HBox.hgrow="ALWAYS" />
|
||||
<RadioButton fx:id="cat5Toggle" mnemonicParsing="false" text="5" toggleGroup="$cat" HBox.hgrow="ALWAYS" />
|
||||
</buttons>
|
||||
</SegmentedButton>
|
||||
</children>
|
||||
<padding>
|
||||
<Insets left="5.0" />
|
||||
</padding>
|
||||
</HBox>
|
||||
</items>
|
||||
</ToolBar>
|
||||
</children>
|
||||
|
@ -19,12 +19,10 @@
|
||||
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import static com.google.common.collect.Lists.transform;
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import static java.util.Arrays.asList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@ -50,7 +48,6 @@ import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.ObservableSet;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
@ -86,10 +83,7 @@ import static javafx.scene.input.KeyCode.UP;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.Border;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.BorderStroke;
|
||||
import javafx.scene.layout.BorderStrokeStyle;
|
||||
import javafx.scene.layout.BorderWidths;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.HBox;
|
||||
@ -111,7 +105,6 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.ContextMenuActionsProvider;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.directorytree.ExtractAction;
|
||||
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
|
||||
@ -134,6 +127,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
||||
import static org.sleuthkit.autopsy.imagegallery.gui.GuiUtils.createAutoAssigningMenuItem;
|
||||
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
|
||||
import static org.sleuthkit.autopsy.imagegallery.utils.TaskUtils.addFXCallback;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
@ -176,19 +170,6 @@ public class GroupPane extends BorderPane {
|
||||
private SplitMenuButton tagSelectedSplitMenu;
|
||||
@FXML
|
||||
private ToolBar headerToolBar;
|
||||
@FXML
|
||||
private ToggleButton cat0Toggle;
|
||||
@FXML
|
||||
private ToggleButton cat1Toggle;
|
||||
@FXML
|
||||
private ToggleButton cat2Toggle;
|
||||
@FXML
|
||||
private ToggleButton cat3Toggle;
|
||||
@FXML
|
||||
private ToggleButton cat4Toggle;
|
||||
@FXML
|
||||
private ToggleButton cat5Toggle;
|
||||
|
||||
@FXML
|
||||
private SegmentedButton segButton;
|
||||
|
||||
@ -220,11 +201,6 @@ public class GroupPane extends BorderPane {
|
||||
@FXML
|
||||
private Label catContainerLabel;
|
||||
@FXML
|
||||
private Label catHeadingLabel;
|
||||
|
||||
@FXML
|
||||
private HBox catSegmentedContainer;
|
||||
@FXML
|
||||
private HBox catSplitMenuContainer;
|
||||
|
||||
private final ListeningExecutorService exec = TaskUtils.getExecutorForClass(GroupPane.class);
|
||||
@ -244,12 +220,18 @@ public class GroupPane extends BorderPane {
|
||||
|
||||
private ContextMenu contextMenu;
|
||||
|
||||
/** the current GroupViewMode of this GroupPane */
|
||||
/**
|
||||
* the current GroupViewMode of this GroupPane
|
||||
*/
|
||||
private final SimpleObjectProperty<GroupViewMode> groupViewMode = new SimpleObjectProperty<>(GroupViewMode.TILE);
|
||||
|
||||
/** the grouping this pane is currently the view for */
|
||||
/**
|
||||
* the grouping this pane is currently the view for
|
||||
*/
|
||||
private final ReadOnlyObjectWrapper<DrawableGroup> grouping = new ReadOnlyObjectWrapper<>();
|
||||
|
||||
private final Map<String, ToggleButton> toggleButtonMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Map from fileIDs to their assigned cells in the tile view. This is used
|
||||
* to determine whether fileIDs are visible or are offscreen. No entry
|
||||
@ -278,7 +260,7 @@ public class GroupPane extends BorderPane {
|
||||
undoAction = new UndoAction(controller);
|
||||
redoAction = new RedoAction(controller);
|
||||
|
||||
FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS
|
||||
FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS
|
||||
}
|
||||
|
||||
GroupViewMode getGroupViewMode() {
|
||||
@ -307,7 +289,35 @@ public class GroupPane extends BorderPane {
|
||||
}
|
||||
|
||||
void syncCatToggle(DrawableFile file) {
|
||||
getToggleForCategory(file.getCategory()).setSelected(true);
|
||||
TagName tagName = file.getCategory();
|
||||
if (tagName != null) {
|
||||
getToggleForCategory(tagName).setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a toggle button for the given TagName.
|
||||
*
|
||||
* @param tagName TagName to create a button for.
|
||||
*
|
||||
* @return A new instance of a ToggleButton.
|
||||
*/
|
||||
private ToggleButton getToggleForCategory(TagName tagName) {
|
||||
|
||||
ToggleButton button = toggleButtonMap.get(tagName.getDisplayName());
|
||||
|
||||
if (button == null) {
|
||||
String[] split = tagName.getDisplayName().split(":");
|
||||
split = split[0].split("-");
|
||||
|
||||
int category = Integer.parseInt(split[1]);
|
||||
|
||||
button = new ToggleButton();
|
||||
button.setText(Integer.toString(category));
|
||||
|
||||
toggleButtonMap.put(tagName.getDisplayName(), button);
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
public void activateTileViewer() {
|
||||
@ -353,25 +363,6 @@ public class GroupPane extends BorderPane {
|
||||
return grouping.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
private ToggleButton getToggleForCategory(DhsImageCategory category) {
|
||||
switch (category) {
|
||||
case ZERO:
|
||||
return cat0Toggle;
|
||||
case ONE:
|
||||
return cat1Toggle;
|
||||
case TWO:
|
||||
return cat2Toggle;
|
||||
case THREE:
|
||||
return cat3Toggle;
|
||||
case FOUR:
|
||||
return cat4Toggle;
|
||||
case FIVE:
|
||||
return cat5Toggle;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown category: " + category.name());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* called automatically during constructor by FXMLConstructor.
|
||||
*
|
||||
@ -384,12 +375,6 @@ public class GroupPane extends BorderPane {
|
||||
"GroupPane.catContainerLabel.displayText=Categorize Selected File:",
|
||||
"GroupPane.catHeadingLabel.displayText=Category:"})
|
||||
void initialize() {
|
||||
assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert cat2Toggle != null : "fx:id=\"cat2Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert cat3Toggle != null : "fx:id=\"cat3Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert cat4Toggle != null : "fx:id=\"cat4Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert cat5Toggle != null : "fx:id=\"cat5Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert gridView != null : "fx:id=\"tilePane\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert catSelectedSplitMenu != null : "fx:id=\"grpCatSplitMenu\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert tagSelectedSplitMenu != null : "fx:id=\"grpTagSplitMenu\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
@ -399,21 +384,6 @@ public class GroupPane extends BorderPane {
|
||||
assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert seenByOtherExaminersCheckBox != null : "fx:id=\"seenByOtherExaminersCheckBox\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
|
||||
for (DhsImageCategory cat : DhsImageCategory.values()) {
|
||||
ToggleButton toggleForCategory = getToggleForCategory(cat);
|
||||
toggleForCategory.setBorder(new Border(new BorderStroke(cat.getColor(), BorderStrokeStyle.SOLID, CORNER_RADII_2, BORDER_WIDTHS_2)));
|
||||
toggleForCategory.getStyleClass().remove("radio-button");
|
||||
toggleForCategory.getStyleClass().add("toggle-button");
|
||||
toggleForCategory.selectedProperty().addListener((ov, wasSelected, toggleSelected) -> {
|
||||
if (toggleSelected && slideShowPane != null) {
|
||||
slideShowPane.getFileID().ifPresent(fileID -> {
|
||||
selectionModel.clearAndSelect(fileID);
|
||||
new CategorizeAction(controller, cat, ImmutableSet.of(fileID)).handle(null);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//configure flashing glow animation on next unseen group button
|
||||
flashAnimation.setCycleCount(Timeline.INDEFINITE);
|
||||
flashAnimation.setAutoReverse(true);
|
||||
@ -447,14 +417,14 @@ public class GroupPane extends BorderPane {
|
||||
},
|
||||
throwable -> logger.log(Level.SEVERE, "Error getting tag names.", throwable)//NON-NLS
|
||||
);
|
||||
CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(DhsImageCategory.FIVE, controller);
|
||||
CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(controller.getCategoryManager().getCategories().get(0), controller);
|
||||
|
||||
catSelectedSplitMenu.setOnAction(cat5SelectedAction);
|
||||
|
||||
catSelectedSplitMenu.setText(cat5SelectedAction.getText());
|
||||
catSelectedSplitMenu.setGraphic(cat5SelectedAction.getGraphic());
|
||||
|
||||
List<MenuItem> categoryMenues = transform(asList(DhsImageCategory.values()),
|
||||
List<MenuItem> categoryMenues = transform(controller.getCategoryManager().getCategories(),
|
||||
cat -> createAutoAssigningMenuItem(catSelectedSplitMenu, new CategorizeSelectedFilesAction(cat, controller)));
|
||||
catSelectedSplitMenu.getItems().setAll(categoryMenues);
|
||||
|
||||
@ -466,16 +436,21 @@ public class GroupPane extends BorderPane {
|
||||
bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText());
|
||||
headerLabel.setText(Bundle.GroupPane_hederLabel_displayText());
|
||||
catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText());
|
||||
catHeadingLabel.setText(Bundle.GroupPane_catHeadingLabel_displayText());
|
||||
//show categorization controls depending on group view mode
|
||||
headerToolBar.getItems().remove(catSegmentedContainer);
|
||||
|
||||
// This seems to be the only way to make sure the when the user switches
|
||||
// to SLIDE_SHOW the first time that the undo\redo buttons are removed.
|
||||
headerToolBar.getItems().remove(undoButton);
|
||||
headerToolBar.getItems().remove(redoButton);
|
||||
headerToolBar.getItems().add(undoButton);
|
||||
headerToolBar.getItems().add(redoButton);
|
||||
|
||||
groupViewMode.addListener((ObservableValue<? extends GroupViewMode> observable, GroupViewMode oldValue, GroupViewMode newValue) -> {
|
||||
if (newValue == GroupViewMode.SLIDE_SHOW) {
|
||||
headerToolBar.getItems().remove(catSplitMenuContainer);
|
||||
headerToolBar.getItems().add(catSegmentedContainer);
|
||||
headerToolBar.getItems().remove(undoButton);
|
||||
headerToolBar.getItems().remove(redoButton);
|
||||
} else {
|
||||
headerToolBar.getItems().remove(catSegmentedContainer);
|
||||
headerToolBar.getItems().add(catSplitMenuContainer);
|
||||
headerToolBar.getItems().add(undoButton);
|
||||
headerToolBar.getItems().add(redoButton);
|
||||
}
|
||||
});
|
||||
|
||||
@ -527,7 +502,7 @@ public class GroupPane extends BorderPane {
|
||||
//listen to tile selection and make sure it is visible in scroll area
|
||||
selectionModel.lastSelectedProperty().addListener((observable, oldFileID, newFileId) -> {
|
||||
if (groupViewMode.get() == GroupViewMode.SLIDE_SHOW
|
||||
&& slideShowPane != null) {
|
||||
&& slideShowPane != null) {
|
||||
slideShowPane.setFile(newFileId);
|
||||
} else {
|
||||
scrollToFileID(newFileId);
|
||||
@ -775,42 +750,9 @@ public class GroupPane extends BorderPane {
|
||||
selectAllFiles();
|
||||
t.consume();
|
||||
}
|
||||
ObservableSet<Long> selected = selectionModel.getSelected();
|
||||
if (selected.isEmpty() == false) {
|
||||
DhsImageCategory cat = keyCodeToCat(t.getCode());
|
||||
if (cat != null) {
|
||||
new CategorizeAction(controller, cat, selected).handle(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DhsImageCategory keyCodeToCat(KeyCode t) {
|
||||
if (t != null) {
|
||||
switch (t) {
|
||||
case NUMPAD0:
|
||||
case DIGIT0:
|
||||
return DhsImageCategory.ZERO;
|
||||
case NUMPAD1:
|
||||
case DIGIT1:
|
||||
return DhsImageCategory.ONE;
|
||||
case NUMPAD2:
|
||||
case DIGIT2:
|
||||
return DhsImageCategory.TWO;
|
||||
case NUMPAD3:
|
||||
case DIGIT3:
|
||||
return DhsImageCategory.THREE;
|
||||
case NUMPAD4:
|
||||
case DIGIT4:
|
||||
return DhsImageCategory.FOUR;
|
||||
case NUMPAD5:
|
||||
case DIGIT5:
|
||||
return DhsImageCategory.FIVE;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void handleArrows(KeyEvent t) {
|
||||
Long lastSelectFileId = selectionModel.lastSelectedProperty().get();
|
||||
|
||||
|
@ -19,9 +19,9 @@
|
||||
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@ -56,7 +56,6 @@ import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
||||
@ -165,20 +164,50 @@ public class MetaDataPane extends DrawableUIBase {
|
||||
titledPane.setText(Bundle.MetaDataPane_titledPane_displayName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display string for the given pair.
|
||||
*
|
||||
* @param p A DrawableAttribute and its collection.
|
||||
*
|
||||
* @return The string to display.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static private String getValueDisplayString(Pair<DrawableAttribute<?>, Collection<?>> p) {
|
||||
if (p.getKey() == DrawableAttribute.TAGS) {
|
||||
return ((Collection<TagName>) p.getValue()).stream()
|
||||
.map(TagName::getDisplayName)
|
||||
.filter(DhsImageCategory::isNotCategoryName)
|
||||
.collect(Collectors.joining(" ; "));
|
||||
private String getValueDisplayString(Pair<DrawableAttribute<?>, Collection<?>> p) {
|
||||
if (p.getKey() == DrawableAttribute.TAGS || p.getKey() == DrawableAttribute.CATEGORY) {
|
||||
return getTagDisplayNames((Collection<TagName>) p.getValue(), p.getKey());
|
||||
} else {
|
||||
return p.getValue().stream()
|
||||
.map(value -> Objects.toString(value, ""))
|
||||
.collect(Collectors.joining(" ; "));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the list of TagName displayNames for either Tags or Categories.
|
||||
*
|
||||
* @param tagNameList List of TagName values
|
||||
* @param attribute A DrawableAttribute value either CATEGORY or TAGS
|
||||
*
|
||||
* @return A list of TagNames separated by ; or an empty string.
|
||||
*/
|
||||
private String getTagDisplayNames(Collection<TagName> tagNameList, DrawableAttribute<?> attribute) {
|
||||
String displayStr = "";
|
||||
CategoryManager controller = getController().getCategoryManager();
|
||||
List<String> nameList = new ArrayList<>();
|
||||
if (tagNameList != null && !tagNameList.isEmpty()) {
|
||||
for (TagName tagName : tagNameList) {
|
||||
if ((attribute == DrawableAttribute.CATEGORY && controller.isCategoryTagName(tagName))
|
||||
|| (attribute == DrawableAttribute.TAGS && !controller.isCategoryTagName(tagName))) {
|
||||
nameList.add(tagName.getDisplayName());
|
||||
}
|
||||
}
|
||||
displayStr = String.join(";", nameList);
|
||||
}
|
||||
|
||||
return displayStr;
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized protected void setFileHelper(Long newFileID) {
|
||||
setFileIDOpt(Optional.ofNullable(newFileID));
|
||||
|
@ -50,12 +50,12 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
|
||||
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile;
|
||||
import org.sleuthkit.autopsy.imagegallery.gui.VideoPlayer;
|
||||
import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableUIBase.exec;
|
||||
import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableView.CAT_BORDER_WIDTH;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
|
||||
/**
|
||||
* Displays the files of a group one at a time. Designed to be embedded in a
|
||||
@ -297,14 +297,14 @@ public class SlideShowView extends DrawableTileBase {
|
||||
|
||||
@Override
|
||||
@ThreadConfined(type = ThreadType.ANY)
|
||||
public DhsImageCategory updateCategory() {
|
||||
public TagName updateCategory() {
|
||||
Optional<DrawableFile> file = getFile();
|
||||
if (file.isPresent()) {
|
||||
DhsImageCategory updateCategory = super.updateCategory();
|
||||
TagName updateCategory = super.updateCategory();
|
||||
Platform.runLater(() -> getGroupPane().syncCatToggle(file.get()));
|
||||
return updateCategory;
|
||||
} else {
|
||||
return DhsImageCategory.ZERO;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,6 +117,7 @@ class GroupCellFactory {
|
||||
final Node graphic = (group.getGroupByAttribute() == DrawableAttribute.TAGS)
|
||||
? controller.getTagsManager().getGraphic((TagName) group.getGroupByValue())
|
||||
: group.getGroupKey().getGraphic();
|
||||
|
||||
final String text = getCellText(cell);
|
||||
final String style = getSeenStyleClass(cell);
|
||||
|
||||
@ -157,10 +158,10 @@ class GroupCellFactory {
|
||||
*/
|
||||
private String getCountsText(GroupCell<?> cell) {
|
||||
return cell.getGroup()
|
||||
.map(group ->
|
||||
" (" + (sortOrder.get() == GroupComparators.ALPHABETICAL
|
||||
? group.getSize()
|
||||
: sortOrder.get().getFormattedValueOfGroup(group)) + ")"
|
||||
.map(group
|
||||
-> " (" + (sortOrder.get() == GroupComparators.ALPHABETICAL
|
||||
? group.getSize()
|
||||
: sortOrder.get().getFormattedValueOfGroup(group)) + ")"
|
||||
).orElse(""); //if item is null or group is null
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,9 @@
|
||||
<copy todir="${basedir}/release/ESEDatabaseView" >
|
||||
<fileset dir="${thirdparty.dir}/ESEDatabaseView/" />
|
||||
</copy>
|
||||
<copy todir="${basedir}/release/markmckinnon/" >
|
||||
<fileset dir="${thirdparty.dir}/markmckinnon/" />
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
|
||||
|
@ -55,6 +55,13 @@ ExtractSafari_Error_Getting_History=An error occurred while processing Safari hi
|
||||
ExtractSafari_Error_Parsing_Bookmark=An error occured while processing Safari Bookmark files
|
||||
ExtractSafari_Error_Parsing_Cookies=An error occured while processing Safari Cookies files
|
||||
ExtractSafari_Module_Name=Safari
|
||||
ExtractSru_error_finding_export_srudb_program=Error finding export_srudb program
|
||||
ExtractSru_module_name=System Resource Usage Extractor
|
||||
ExtractSru_process_error_executing_export_srudb_program=Error running export_srudb program
|
||||
ExtractSru_process_errormsg_find_software_hive=Unable to find SOFTWARE HIVE file
|
||||
ExtractSru_process_errormsg_find_srudb_dat=Unable to find srudb.dat file
|
||||
ExtractSru_process_errormsg_write_software_hive=Unable to write SOFTWARE HIVE file
|
||||
ExtractSru_process_errormsg_write_srudb_dat=Unable to write srudb.dat file
|
||||
ExtractZone_Internet=Internet Zone
|
||||
ExtractZone_Local_Intranet=Local Intranet Zone
|
||||
ExtractZone_Local_Machine=Local Machine Zone
|
||||
|
@ -0,0 +1,484 @@
|
||||
/*
|
||||
*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2020 Basis Technology Corp.
|
||||
*
|
||||
* 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.recentactivity;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.openide.modules.InstalledFileLocator;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||
import org.sleuthkit.autopsy.coreutils.ExecUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.SQLiteDBConnect;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProcessTerminator;
|
||||
import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
|
||||
import org.sleuthkit.autopsy.ingest.IngestJobContext;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Blackboard;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
|
||||
/**
|
||||
* Extract the System Resource Usage database to a temp directory so it can be parsed into a SQLite db
|
||||
* and then brought into extracted content
|
||||
*/
|
||||
final class ExtractSru extends Extract {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ExtractSru.class.getName());
|
||||
|
||||
private IngestJobContext context;
|
||||
|
||||
private static final String APPLICATION_USAGE_SOURCE_NAME = "System Resource Usage - Application Usage"; //NON-NLS
|
||||
private static final String NETWORK_USAGE_SOURCE_NAME = "System Resource Usage - Network Usage";
|
||||
|
||||
// private static final String ARTIFACT_ATTRIBUTE_NAME = "TSK_ARTIFACT_NAME"; //NON-NLS
|
||||
|
||||
private static final String MODULE_NAME = "extractSRU"; //NON-NLS
|
||||
|
||||
private static final String SRU_TOOL_FOLDER = "markmckinnon"; //NON-NLS
|
||||
private static final String SRU_TOOL_NAME_WINDOWS_32 = "Export_Srudb_32.exe"; //NON-NLS
|
||||
private static final String SRU_TOOL_NAME_WINDOWS_64 = "Export_Srudb_64.exe"; //NON-NLS
|
||||
private static final String SRU_TOOL_NAME_LINUX = "Export_Srudb_Linux.exe"; //NON-NLS
|
||||
private static final String SRU_TOOL_NAME_MAC = "Export_srudb_macos"; //NON-NLS
|
||||
private static final String SRU_OUTPUT_FILE_NAME = "Output.txt"; //NON-NLS
|
||||
private static final String SRU_ERROR_FILE_NAME = "Error.txt"; //NON-NLS
|
||||
|
||||
private static final Map<String, AbstractFile> applicationFilesFound = new HashMap<>();
|
||||
|
||||
@Messages({
|
||||
"ExtractSru_module_name=System Resource Usage Extractor"
|
||||
})
|
||||
ExtractSru() {
|
||||
this.moduleName = Bundle.ExtractSru_module_name();
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"ExtractSru_error_finding_export_srudb_program=Error finding export_srudb program",
|
||||
"ExtractSru_process_error_executing_export_srudb_program=Error running export_srudb program"
|
||||
})
|
||||
|
||||
@Override
|
||||
void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
|
||||
|
||||
this.context = context;
|
||||
|
||||
String modOutPath = Case.getCurrentCase().getModuleDirectory() + File.separator + "sru";
|
||||
File dir = new File(modOutPath);
|
||||
if (dir.exists() == false) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
String tempDirPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), "sru"); //NON-NLS
|
||||
String softwareHiveFileName = getSoftwareHiveFile(dataSource, tempDirPath);
|
||||
|
||||
if (softwareHiveFileName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
AbstractFile sruAbstractFile = getSruFile(dataSource, tempDirPath);
|
||||
|
||||
String sruFileName = tempDirPath + File.separator + sruAbstractFile.getId() + "_" + sruAbstractFile.getName();
|
||||
|
||||
if (sruFileName == null) {
|
||||
this.addErrorMessage(Bundle.ExtractSru_process_errormsg_find_srudb_dat());
|
||||
logger.log(Level.SEVERE, "SRUDB.dat file not found"); //NON-NLS
|
||||
return; //If we cannot find the srudb.dat file we cannot proceed
|
||||
}
|
||||
|
||||
final String sruDumper = getPathForSruDumper();
|
||||
if (sruDumper == null) {
|
||||
this.addErrorMessage(Bundle.ExtractSru_error_finding_export_srudb_program());
|
||||
logger.log(Level.SEVERE, "Error finding export_srudb program"); //NON-NLS
|
||||
return; //If we cannot find the export_srudb program we cannot proceed
|
||||
}
|
||||
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String modOutFile = modOutPath + File.separator + sruAbstractFile.getId() + "_srudb.db3";
|
||||
|
||||
extractSruFiles(sruDumper, sruFileName, modOutFile, tempDirPath, softwareHiveFileName);
|
||||
|
||||
findSruExecutedFiles(modOutFile, dataSource);
|
||||
|
||||
createNetUsageArtifacts(modOutFile, sruAbstractFile);
|
||||
createAppUsageArtifacts(modOutFile, sruAbstractFile);
|
||||
} catch (IOException ex) {
|
||||
this.addErrorMessage(Bundle.ExtractSru_process_error_executing_export_srudb_program());
|
||||
logger.log(Level.SEVERE, "SRUDB.dat file not found"); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"ExtractSru_process_errormsg_find_software_hive=Unable to find SOFTWARE HIVE file",
|
||||
"ExtractSru_process_errormsg_write_software_hive=Unable to write SOFTWARE HIVE file"
|
||||
})
|
||||
|
||||
/**
|
||||
* Extract the SOFTWARE hive file to the temp directory
|
||||
*
|
||||
* @param dataSource datasource where software hiive is
|
||||
* @param tempDirPath temp directory to write file to
|
||||
*
|
||||
* @return Software hive file location
|
||||
*/
|
||||
String getSoftwareHiveFile(Content dataSource, String tempDirPath) {
|
||||
FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
|
||||
|
||||
List<AbstractFile> softwareHiveFiles;
|
||||
|
||||
try {
|
||||
softwareHiveFiles = fileManager.findFiles(dataSource, "SOFTWARE"); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
this.addErrorMessage(Bundle.ExtractSru_process_errormsg_find_software_hive());
|
||||
logger.log(Level.WARNING, "Unable to find SOFTWARE HIVE file.", ex); //NON-NLS
|
||||
return null; // No need to continue
|
||||
}
|
||||
|
||||
String softwareHiveFileName = null;
|
||||
|
||||
for (AbstractFile softwareFile : softwareHiveFiles) {
|
||||
|
||||
if (softwareFile.getParentPath().endsWith("/config/")) {
|
||||
softwareHiveFileName = tempDirPath + File.separator + softwareFile.getId() + "_" + softwareFile.getName();
|
||||
|
||||
try {
|
||||
ContentUtils.writeToFile(softwareFile, new File(softwareHiveFileName));
|
||||
} catch (IOException ex) {
|
||||
this.addErrorMessage(Bundle.ExtractSru_process_errormsg_find_software_hive());
|
||||
logger.log(Level.WARNING, String.format("Unable to write %s to temp directory. File name: %s", softwareFile.getName(), softwareFile), ex); //NON-NLS
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return softwareHiveFileName;
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"ExtractSru_process_errormsg_find_srudb_dat=Unable to find srudb.dat file",
|
||||
"ExtractSru_process_errormsg_write_srudb_dat=Unable to write srudb.dat file"
|
||||
})
|
||||
/**
|
||||
* Extract the SOFTWARE hive file to the temp directory
|
||||
*
|
||||
* @param dataSource datasource where software hiive is
|
||||
* @param tempDirPath temp directory to write file to
|
||||
*
|
||||
* @return Software hive file location
|
||||
*/
|
||||
AbstractFile getSruFile(Content dataSource, String tempDirPath) {
|
||||
FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
|
||||
|
||||
List<AbstractFile> sruFiles;
|
||||
|
||||
try {
|
||||
sruFiles = fileManager.findFiles(dataSource, "SRUDB.DAT"); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
this.addErrorMessage(Bundle.ExtractSru_process_errormsg_find_srudb_dat());
|
||||
logger.log(Level.WARNING, "Unable to find SRUDB.DAT file.", ex); //NON-NLS
|
||||
return null; // No need to continue
|
||||
}
|
||||
|
||||
AbstractFile sruAbstractFile = null;
|
||||
|
||||
for (AbstractFile sruFile : sruFiles) {
|
||||
|
||||
String sruFileName = tempDirPath + File.separator + sruFile.getId() + "_" + sruFile.getName();
|
||||
sruAbstractFile = sruFile;
|
||||
|
||||
try {
|
||||
ContentUtils.writeToFile(sruFile, new File(sruFileName));
|
||||
} catch (IOException ex) {
|
||||
this.addErrorMessage(Bundle.ExtractSru_process_errormsg_write_srudb_dat());
|
||||
logger.log(Level.WARNING, String.format("Unable to write %s to temp directory. File name: %s", sruFile.getName(), sruFile), ex); //NON-NLS
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
return sruAbstractFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the export srudb program against the srudb.dat file
|
||||
*
|
||||
* @param sruExePath
|
||||
* @param tempDirPath
|
||||
* @param tempOutPath
|
||||
*
|
||||
* @throws FileNotFoundException
|
||||
* @throws IOException
|
||||
*/
|
||||
void extractSruFiles(String sruExePath, String sruFile, String tempOutFile, String tempOutPath, String softwareHiveFile) throws IOException {
|
||||
final Path outputFilePath = Paths.get(tempOutPath, SRU_OUTPUT_FILE_NAME);
|
||||
final Path errFilePath = Paths.get(tempOutPath, SRU_ERROR_FILE_NAME);
|
||||
|
||||
List<String> commandLine = new ArrayList<>();
|
||||
commandLine.add(sruExePath);
|
||||
commandLine.add(sruFile); //NON-NLS
|
||||
commandLine.add(softwareHiveFile);
|
||||
commandLine.add(tempOutFile);
|
||||
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(commandLine);
|
||||
processBuilder.redirectOutput(outputFilePath.toFile());
|
||||
processBuilder.redirectError(errFilePath.toFile());
|
||||
|
||||
ExecUtil.execute(processBuilder, new DataSourceIngestModuleProcessTerminator(context));
|
||||
}
|
||||
|
||||
private String getPathForSruDumper() {
|
||||
Path path = null;
|
||||
if (PlatformUtil.isWindowsOS()) {
|
||||
if (PlatformUtil.is64BitOS()) {
|
||||
path = Paths.get(SRU_TOOL_FOLDER, SRU_TOOL_NAME_WINDOWS_64);
|
||||
} else {
|
||||
path = Paths.get(SRU_TOOL_FOLDER, SRU_TOOL_NAME_WINDOWS_32);
|
||||
}
|
||||
} else {
|
||||
if ("Linux".equals(PlatformUtil.getOSName())) {
|
||||
path = Paths.get(SRU_TOOL_FOLDER, SRU_TOOL_NAME_LINUX);
|
||||
} else {
|
||||
path = Paths.get(SRU_TOOL_FOLDER, SRU_TOOL_NAME_MAC);
|
||||
}
|
||||
}
|
||||
File sruToolFile = InstalledFileLocator.getDefault().locate(path.toString(),
|
||||
ExtractSru.class.getPackage().getName(), false);
|
||||
if (sruToolFile != null) {
|
||||
return sruToolFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void findSruExecutedFiles(String sruDb, Content dataSource) {
|
||||
|
||||
org.sleuthkit.autopsy.casemodule.services.FileManager fileManager = currentCase.getServices().getFileManager();
|
||||
|
||||
String sqlStatement = "SELECT DISTINCT SUBSTR(LTRIM(IdBlob, '\\Device\\HarddiskVolume'), INSTR(LTRIM(IdBlob, '\\Device\\HarddiskVolume'), '\\')) "
|
||||
+ " application_name, idBlob source_name FROM SruDbIdMapTable WHERE idType = 0 AND idBlob NOT LIKE '!!%'"; //NON-NLS
|
||||
|
||||
try (SQLiteDBConnect tempdbconnect = new SQLiteDBConnect("org.sqlite.JDBC", "jdbc:sqlite:" + sruDb); //NON-NLS
|
||||
ResultSet resultSet = tempdbconnect.executeQry(sqlStatement)) {
|
||||
|
||||
while (resultSet.next()) {
|
||||
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
logger.log(Level.INFO, "Cancelled SRU Artifact Creation."); //NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
String applicationName = resultSet.getString("application_name"); //NON-NLS
|
||||
String sourceName = resultSet.getString("source_name"); //NON-NLS
|
||||
|
||||
String normalizePathName = FilenameUtils.normalize(applicationName, true);
|
||||
String fileName = FilenameUtils.getName(normalizePathName);
|
||||
String filePath = FilenameUtils.getPath(normalizePathName);
|
||||
if (fileName.contains(" [")) {
|
||||
fileName = fileName.substring(0, fileName.indexOf(" ["));
|
||||
}
|
||||
List<AbstractFile> sourceFiles;
|
||||
try {
|
||||
sourceFiles = fileManager.findFiles(dataSource, fileName, filePath); //NON-NLS
|
||||
for (AbstractFile sourceFile : sourceFiles) {
|
||||
if (sourceFile.getParentPath().endsWith(filePath)) {
|
||||
applicationFilesFound.put(sourceName.toLowerCase(), sourceFile);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, String.format("Error finding actual file %s. file may not exist", normalizePathName)); //NON-NLS
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.WARNING, "Error while trying to read into a sqlite db.", ex);//NON-NLS
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void createNetUsageArtifacts(String sruDb, AbstractFile sruAbstractFile) {
|
||||
List<BlackboardArtifact> bba = new ArrayList<>();
|
||||
|
||||
String sqlStatement = "SELECT STRFTIME('%s', timestamp) ExecutionTime, Application_Name, User_Name, "
|
||||
+ " bytesSent, BytesRecvd FROM network_Usage , SruDbIdMapTable "
|
||||
+ " where appId = IdIndex and IdType = 0 order by ExecutionTime;"; //NON-NLS
|
||||
|
||||
try (SQLiteDBConnect tempdbconnect = new SQLiteDBConnect("org.sqlite.JDBC", "jdbc:sqlite:" + sruDb); //NON-NLS
|
||||
ResultSet resultSet = tempdbconnect.executeQry(sqlStatement)) {
|
||||
|
||||
while (resultSet.next()) {
|
||||
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
logger.log(Level.INFO, "Cancelled SRU Net Usage Artifact Creation."); //NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
String applicationName = resultSet.getString("Application_Name"); //NON-NLS
|
||||
Long executionTime = Long.valueOf(resultSet.getInt("ExecutionTime")); //NON-NLS
|
||||
Long bytesSent = Long.valueOf(resultSet.getInt("bytesSent")); //NON-NLS
|
||||
Long bytesRecvd = Long.valueOf(resultSet.getInt("BytesRecvd")); //NON-NLS
|
||||
String userName = resultSet.getString("User_Name"); //NON-NLS
|
||||
|
||||
Collection<BlackboardAttribute> bbattributes = Arrays.asList(
|
||||
new BlackboardAttribute(
|
||||
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, getName(),
|
||||
applicationName),//NON-NLS
|
||||
new BlackboardAttribute(
|
||||
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_NAME, getName(),
|
||||
userName),
|
||||
new BlackboardAttribute(
|
||||
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, getName(),
|
||||
executionTime),
|
||||
new BlackboardAttribute(
|
||||
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_BYTES_SENT, getName(), bytesSent),
|
||||
new BlackboardAttribute(
|
||||
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_BYTES_RECEIVED, getName(), bytesRecvd),
|
||||
new BlackboardAttribute(
|
||||
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, getName(), NETWORK_USAGE_SOURCE_NAME));
|
||||
|
||||
try {
|
||||
BlackboardArtifact bbart = sruAbstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN);
|
||||
bbart.addAttributes(bbattributes);
|
||||
bba.add(bbart);
|
||||
BlackboardArtifact associateBbArtifact = createAssociatedArtifact(applicationName.toLowerCase(), bbart);
|
||||
if (associateBbArtifact != null) {
|
||||
bba.add(associateBbArtifact);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Exception Adding Artifact.", ex);//NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, "Error while trying to read into a sqlite db.", ex);//NON-NLS
|
||||
}
|
||||
|
||||
try {
|
||||
blackboard.postArtifacts(bba, MODULE_NAME);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, "Error Posting Artifact.", ex);//NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
private void createAppUsageArtifacts(String sruDb, AbstractFile sruAbstractFile) {
|
||||
List<BlackboardArtifact> bba = new ArrayList<>();
|
||||
|
||||
String sqlStatement = "SELECT STRFTIME('%s', timestamp) ExecutionTime, Application_Name, User_Name "
|
||||
+ " FROM Application_Resource_Usage, SruDbIdMapTable WHERE "
|
||||
+ " idType = 0 and idIndex = appId order by ExecutionTime;"; //NON-NLS
|
||||
|
||||
try (SQLiteDBConnect tempdbconnect = new SQLiteDBConnect("org.sqlite.JDBC", "jdbc:sqlite:" + sruDb); //NON-NLS
|
||||
ResultSet resultSet = tempdbconnect.executeQry(sqlStatement)) {
|
||||
|
||||
while (resultSet.next()) {
|
||||
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
logger.log(Level.INFO, "Cancelled SRU Net Usage Artifact Creation."); //NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
String applicationName = resultSet.getString("Application_Name"); //NON-NLS
|
||||
Long executionTime = Long.valueOf(resultSet.getInt("ExecutionTime")); //NON-NLS
|
||||
String userName = resultSet.getString("User_Name");
|
||||
|
||||
Collection<BlackboardAttribute> bbattributes = Arrays.asList(
|
||||
new BlackboardAttribute(
|
||||
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, getName(),
|
||||
applicationName),//NON-NLS
|
||||
new BlackboardAttribute(
|
||||
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_NAME, getName(),
|
||||
userName),
|
||||
new BlackboardAttribute(
|
||||
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, getName(),
|
||||
executionTime),
|
||||
new BlackboardAttribute(
|
||||
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, getName(), APPLICATION_USAGE_SOURCE_NAME));
|
||||
|
||||
try {
|
||||
BlackboardArtifact bbart = sruAbstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN);
|
||||
bbart.addAttributes(bbattributes);
|
||||
bba.add(bbart);
|
||||
BlackboardArtifact associateBbArtifact = createAssociatedArtifact(applicationName.toLowerCase(), bbart);
|
||||
if (associateBbArtifact != null) {
|
||||
bba.add(associateBbArtifact);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Exception Adding Artifact.", ex);//NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.SEVERE, "Error while trying to read into a sqlite db.", ex);//NON-NLS
|
||||
}
|
||||
|
||||
try {
|
||||
blackboard.postArtifacts(bba, MODULE_NAME);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, "Error Posting Artifact.", ex);//NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create associated artifacts using file path name and the artifact it associates with
|
||||
*
|
||||
* @param filePathName file and path of object being associated with
|
||||
*
|
||||
* @param bba blackboard artifact to associate with
|
||||
*
|
||||
* @returnv BlackboardArtifact or a null value
|
||||
*/
|
||||
private BlackboardArtifact createAssociatedArtifact(String filePathName, BlackboardArtifact bba) {
|
||||
if (applicationFilesFound.containsKey(filePathName)) {
|
||||
AbstractFile sourceFile = applicationFilesFound.get(filePathName);
|
||||
Collection<BlackboardAttribute> bbattributes2 = new ArrayList<>();
|
||||
bbattributes2.addAll(Arrays.asList(
|
||||
new BlackboardAttribute(TSK_ASSOCIATED_ARTIFACT, this.getName(),
|
||||
bba.getArtifactID())));
|
||||
|
||||
BlackboardArtifact associatedObjectBba = createArtifactWithAttributes(TSK_ASSOCIATED_OBJECT, sourceFile, bbattributes2);
|
||||
if (associatedObjectBba != null) {
|
||||
return associatedObjectBba;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -78,6 +78,7 @@ public final class RAImageIngestModule implements DataSourceIngestModule {
|
||||
Extract safari = new ExtractSafari();
|
||||
Extract zoneInfo = new ExtractZoneIdentifier();
|
||||
Extract recycleBin = new ExtractRecycleBin();
|
||||
Extract sru = new ExtractSru();
|
||||
|
||||
extractors.add(chrome);
|
||||
extractors.add(firefox);
|
||||
@ -91,7 +92,8 @@ public final class RAImageIngestModule implements DataSourceIngestModule {
|
||||
extractors.add(dataSourceAnalyzer); //this needs to run after ExtractRegistry and ExtractOs
|
||||
extractors.add(zoneInfo); // this needs to run after the web browser modules
|
||||
extractors.add(recycleBin); // this needs to run after ExtractRegistry and ExtractOS
|
||||
|
||||
extractors.add(sru);
|
||||
|
||||
browserExtractors.add(chrome);
|
||||
browserExtractors.add(firefox);
|
||||
browserExtractors.add(iexplore);
|
||||
|
@ -19,7 +19,6 @@ environment:
|
||||
LIBVHDI_HOME: "C:\\libvhdi_64bit"
|
||||
LIBVMDK_HOME: "C:\\libvmdk_64bit\\libvmdk"
|
||||
LIBEWF_HOME: "C:\\libewf_64bit"
|
||||
POSTGRESQL_HOME_64: "C:\\Program Files\\PostgreSQL\\9.5"
|
||||
JDK_HOME: C:\Program Files\Java\jdk1.8.0
|
||||
PYTHON: "C:\\Python36-x64"
|
||||
|
||||
@ -41,7 +40,7 @@ build_script:
|
||||
- python win32\updateAndBuildAll.py -m
|
||||
- ps: pushd bindings/java
|
||||
- ps: ant -version
|
||||
- cmd: ant dist-PostgreSQL
|
||||
- cmd: ant dist
|
||||
- ps: popd
|
||||
- cd %APPVEYOR_BUILD_FOLDER%
|
||||
- cmd: ant -q build
|
||||
|
BIN
thirdparty/markmckinnon/Export_Srudb_Linux
vendored
Normal file
BIN
thirdparty/markmckinnon/Export_Srudb_Linux
vendored
Normal file
Binary file not shown.
BIN
thirdparty/markmckinnon/Export_srudb_macos
vendored
Normal file
BIN
thirdparty/markmckinnon/Export_srudb_macos
vendored
Normal file
Binary file not shown.
202
thirdparty/markmckinnon/LICENSE-2.0.txt
vendored
Normal file
202
thirdparty/markmckinnon/LICENSE-2.0.txt
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
BIN
thirdparty/markmckinnon/export_srudb_32.exe
vendored
Normal file
BIN
thirdparty/markmckinnon/export_srudb_32.exe
vendored
Normal file
Binary file not shown.
BIN
thirdparty/markmckinnon/export_srudb_64.exe
vendored
Normal file
BIN
thirdparty/markmckinnon/export_srudb_64.exe
vendored
Normal file
Binary file not shown.
@ -4,7 +4,7 @@ set -e
|
||||
echo "Building TSK..."
|
||||
cd sleuthkit/sleuthkit
|
||||
./bootstrap && ./configure --prefix=/usr && make
|
||||
pushd bindings/java && ant -q dist-PostgreSQL && popd
|
||||
pushd bindings/java && ant -q dist && popd
|
||||
|
||||
echo "Building Autopsy..." && echo -en 'travis_fold:start:script.build\\r'
|
||||
cd $TRAVIS_BUILD_DIR/
|
||||
|
@ -57,13 +57,13 @@ fi
|
||||
|
||||
ext_jar_filepath=$PWD/autopsy/modules/ext/sleuthkit-$TSK_VERSION.jar;
|
||||
echo -n "Copying sleuthkit-$TSK_VERSION.jar into the Autopsy directory..."
|
||||
rm -f $ext_jar_filepath;
|
||||
rm -f "$ext_jar_filepath";
|
||||
if [ "$?" -gt 0 ]; then #checking if remove operation failed
|
||||
echo "ERROR: Deleting $ext_jar_filepath failed."
|
||||
echo "Please check your permissions."
|
||||
exit 1
|
||||
else
|
||||
cp $sleuthkit_jar_filepath $ext_jar_filepath
|
||||
cp $sleuthkit_jar_filepath "$ext_jar_filepath"
|
||||
if [ "$?" -ne 0 ]; then # checking copy operation was successful
|
||||
echo "ERROR: Copying $sleuthkit_jar_filepath to $ext_jar_filepath failed."
|
||||
echo "Please check your permissions."
|
||||
|
Loading…
x
Reference in New Issue
Block a user