diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 5f1653bf7a..6ae521de1e 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -38,6 +38,7 @@ import java.util.Collections; import java.util.EnumSet; import java.util.Iterator; import java.util.List; +import java.util.Objects; import static java.util.Objects.nonNull; import java.util.SortedSet; import java.util.TreeSet; @@ -58,7 +59,6 @@ import javax.imageio.ImageReadParam; import javax.imageio.ImageReader; import javax.imageio.event.IIOReadProgressListener; import javax.imageio.stream.ImageInputStream; -import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.openide.util.NbBundle; @@ -962,7 +962,7 @@ public class ImageUtils { LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT, ImageUtils.getContentPathSafe(file)); } else if (fxImage.isError()) { //if there was somekind of error, log it - LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + ": " + ObjectUtils.toString(fxImage.getException()), ImageUtils.getContentPathSafe(file)); + LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + ": " + Objects.toString(fxImage.getException()), ImageUtils.getContentPathSafe(file)); } } catch (InterruptedException | ExecutionException ex) { failed(); @@ -972,7 +972,7 @@ public class ImageUtils { @Override protected void failed() { super.failed(); - LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + ": " + ObjectUtils.toString(getException()), ImageUtils.getContentPathSafe(file)); + LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT + ": " + Objects.toString(getException()), ImageUtils.getContentPathSafe(file)); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/PastCasesSummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/PastCasesSummary.java new file mode 100644 index 0000000000..4b1154878c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/PastCasesSummary.java @@ -0,0 +1,438 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datasourcesummary.datamodel; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.sleuthkit.autopsy.centralrepository.ingestmodule.CentralRepoIngestModuleFactory; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultArtifactUpdateGovernor; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.IngestJobInfo; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Provides information about how a datasource relates to a previous case. NOTE: + * This code is fragile and has certain expectations about how the central + * repository handles creating artifacts. So, if the central repository changes + * ingest process, this code could break. This code expects that the central + * repository ingest module: + * + * a) Creates a TSK_INTERESTING_FILE_HIT artifact for a file whose hash is in + * the central repository as a notable file. + * + * b) Creates a TSK_INTERESTING_ARTIFACT_HIT artifact for a matching id in the + * central repository. + * + * c) The created artifact will have a TSK_COMMENT attribute attached where one + * of the sources for the attribute matches + * CentralRepoIngestModuleFactory.getModuleName(). The module display name at + * time of ingest will match CentralRepoIngestModuleFactory.getModuleName() as + * well. + * + * d) The content of that TSK_COMMENT attribute will be of the form "Previous + * Case: case1,case2...caseN" + */ +public class PastCasesSummary implements DefaultArtifactUpdateGovernor { + + /** + * Exception that is thrown in the event that a data source has not been + * ingested with a particular ingest module. + */ + public static class NotIngestedWithModuleException extends Exception { + private static final long serialVersionUID = 1L; + + private final String moduleDisplayName; + + /** + * Constructor. + * + * @param moduleName The module name. + * @param message The message for the exception. + */ + public NotIngestedWithModuleException(String moduleName, String message) { + super(message); + this.moduleDisplayName = moduleName; + } + + /** + * Constructor. + * + * @param moduleName The module name. + * @param message The message for the exception. + * @param thrwbl Inner exception if applicable. + */ + public NotIngestedWithModuleException(String moduleName, String message, Throwable thrwbl) { + super(message, thrwbl); + this.moduleDisplayName = moduleName; + } + + /** + * @return The module display name. + */ + public String getModuleDisplayName() { + return moduleDisplayName; + } + } + + /** + * Return type for results items in the past cases tab. + */ + public static class PastCasesResult { + + private final List> sameIdsResults; + private final List> taggedNotable; + + /** + * Main constructor. + * + * @param sameIdsResults Data for the cases with same id table. + * @param taggedNotable Data for the tagged notable table. + */ + public PastCasesResult(List> sameIdsResults, List> taggedNotable) { + this.sameIdsResults = sameIdsResults; + this.taggedNotable = taggedNotable; + } + + /** + * @return Data for the cases with same id table. + */ + public List> getSameIdsResults() { + return sameIdsResults; + } + + /** + * @return Data for the tagged notable table. + */ + public List> getTaggedNotable() { + return taggedNotable; + } + } + + private static final Set ARTIFACT_UPDATE_TYPE_IDS = new HashSet<>(Arrays.asList( + ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID(), + ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() + )); + + + private static final String CENTRAL_REPO_INGEST_NAME = CentralRepoIngestModuleFactory.getModuleName().toUpperCase().trim(); + private static final BlackboardAttribute.Type TYPE_COMMENT = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_COMMENT); + private static final BlackboardAttribute.Type TYPE_ASSOCIATED_ARTIFACT = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT); + + private static final Set CR_DEVICE_TYPE_IDS = new HashSet<>(Arrays.asList( + ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID(), + ARTIFACT_TYPE.TSK_DEVICE_INFO.getTypeID(), + ARTIFACT_TYPE.TSK_SIM_ATTACHED.getTypeID(), + ARTIFACT_TYPE.TSK_WIFI_NETWORK_ADAPTER.getTypeID() + )); + + private static final String CASE_SEPARATOR = ","; + private static final String PREFIX_END = ":"; + + private final SleuthkitCaseProvider caseProvider; + private final java.util.logging.Logger logger; + + /** + * Main constructor. + */ + public PastCasesSummary() { + this( + SleuthkitCaseProvider.DEFAULT, + org.sleuthkit.autopsy.coreutils.Logger.getLogger(PastCasesSummary.class.getName()) + ); + + } + + /** + * Main constructor with external dependencies specified. This constructor + * is designed with unit testing in mind since mocked dependencies can be + * utilized. + * + * @param provider The object providing the current SleuthkitCase. + * @param logger The logger to use. + */ + public PastCasesSummary( + SleuthkitCaseProvider provider, + java.util.logging.Logger logger) { + + this.caseProvider = provider; + this.logger = logger; + } + + + @Override + public Set getArtifactTypeIdsForRefresh() { + return ARTIFACT_UPDATE_TYPE_IDS; + } + + /** + * Given the provided sources for an attribute, aims to determine if one of + * those sources is the Central Repository Ingest Module. + * + * @param sources The list of sources found on an attribute. + * + * @return Whether or not this attribute (and subsequently the parent + * artifact) is created by the Central Repository Ingest Module. + */ + private static boolean isCentralRepoGenerated(List sources) { + if (sources == null) { + return false; + } + + return sources.stream().anyMatch((str) -> { + return str != null && CENTRAL_REPO_INGEST_NAME.equalsIgnoreCase(str.trim()); + }); + } + + /** + * Gets a list of cases from the TSK_COMMENT of an artifact. The cases + * string is expected to be of a form of "Previous Case: + * case1,case2...caseN". + * + * @param artifact The artifact. + * + * @return The list of cases if found or empty list if not. + */ + private static List getCasesFromArtifact(BlackboardArtifact artifact) { + if (artifact == null) { + return Collections.emptyList(); + } + + BlackboardAttribute commentAttr = null; + try { + commentAttr = artifact.getAttribute(TYPE_COMMENT); + } catch (TskCoreException ignored) { + // ignore if no attribute can be found + } + + if (commentAttr == null) { + return Collections.emptyList(); + } + + if (!isCentralRepoGenerated(commentAttr.getSources())) { + return Collections.emptyList(); + } + + String commentStr = commentAttr.getValueString(); + + int prefixCharIdx = commentStr.indexOf(PREFIX_END); + if (prefixCharIdx < 0 || prefixCharIdx >= commentStr.length() - 1) { + return Collections.emptyList(); + } + + String justCasesStr = commentStr.substring(prefixCharIdx + 1).trim(); + return Stream.of(justCasesStr.split(CASE_SEPARATOR)) + .map(String::trim) + .collect(Collectors.toList()); + + } + + /** + * Given a stream of case ids, groups the strings in a case-insensitive + * manner, and then provides a list of cases and the occurrence count sorted + * from max to min. + * + * @param cases A stream of cases. + * + * @return The list of unique cases and their occurrences sorted from max to min. + */ + private List> getCaseCounts(Stream cases) { + Collection> groupedCases = cases + // group by case insensitive compare of cases + .collect(Collectors.groupingBy((caseStr) -> caseStr.toUpperCase().trim())) + .values(); + + return groupedCases + .stream() + // get any cases where an actual case is found + .filter((lst) -> lst != null && lst.size() > 0) + // get non-normalized (i.e. not all caps) case name and number of items found + .map((lst) -> Pair.of(lst.get(0), (long) lst.size())) + // sorted descending + .sorted((a, b) -> -Long.compare(a.getValue(), b.getValue())) + .collect(Collectors.toList()); + } + + /** + * Given an artifact with a TYPE_ASSOCIATED_ARTIFACT attribute, retrieves the related artifact. + * @param skCase The sleuthkit case. + * @param artifact The artifact with the TYPE_ASSOCIATED_ARTIFACT attribute. + * @return The artifact if found or null if not. + * @throws SleuthkitCaseProviderException + */ + private BlackboardArtifact getParentArtifact(BlackboardArtifact artifact) throws SleuthkitCaseProviderException { + Long parentId = DataSourceInfoUtilities.getLongOrNull(artifact, TYPE_ASSOCIATED_ARTIFACT); + if (parentId == null) { + return null; + } + + SleuthkitCase skCase = caseProvider.get(); + try { + return skCase.getArtifactByArtifactId(parentId); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, + String.format("There was an error fetching the parent artifact of a TSK_INTERESTING_ARTIFACT_HIT (parent id: %d)", parentId), + ex); + return null; + } + } + + /** + * Returns true if the artifact has an associated artifact of a device type. + * @param artifact The artifact. + * @return True if there is a device associated artifact. + * @throws SleuthkitCaseProviderException + */ + private boolean hasDeviceAssociatedArtifact(BlackboardArtifact artifact) throws SleuthkitCaseProviderException { + BlackboardArtifact parent = getParentArtifact(artifact); + if (parent == null) { + return false; + } + + return CR_DEVICE_TYPE_IDS.contains(parent.getArtifactTypeID()); + } + + + /** + * Returns the past cases data to be shown in the past cases tab. + * @param dataSource The data source. + * @return The retrieved data. + * @throws SleuthkitCaseProviderException + * @throws TskCoreException + * @throws NotIngestedWithModuleException + */ + public PastCasesResult getPastCasesData(DataSource dataSource) + throws SleuthkitCaseProvider.SleuthkitCaseProviderException, TskCoreException, NotIngestedWithModuleException { + + throwOnNotCentralRepoIngested(dataSource); + + SleuthkitCase skCase = caseProvider.get(); + + List deviceArtifactCases = new ArrayList<>(); + List nonDeviceArtifactCases = new ArrayList<>(); + + for (BlackboardArtifact artifact : skCase.getBlackboard().getArtifacts(ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID(), dataSource.getId())) { + List cases = getCasesFromArtifact(artifact); + if (cases == null || cases.isEmpty()) { + continue; + } + + if (hasDeviceAssociatedArtifact(artifact)) { + deviceArtifactCases.addAll(cases); + } else { + nonDeviceArtifactCases.addAll(cases); + } + } + + Stream filesCases = skCase.getBlackboard().getArtifacts(ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID(), dataSource.getId()).stream() + .flatMap((art) -> getCasesFromArtifact(art).stream()); + + return new PastCasesResult( + getCaseCounts(deviceArtifactCases.stream()), + getCaseCounts(Stream.concat(filesCases, nonDeviceArtifactCases.stream())) + ); + } + + + /** + * Returns true if the ingest job info contains an ingest module that + * matches the Central Repo Module ingest display name. + * + * @param info The info. + * + * @return True if there is a central repo ingest match. + */ + private boolean hasCentralRepoIngest(IngestJobInfo info) { + if (info == null || info.getIngestModuleInfo() == null) { + return false; + } + + return info.getIngestModuleInfo().stream() + .anyMatch((moduleInfo) -> { + return StringUtils.isNotBlank(moduleInfo.getDisplayName()) + && moduleInfo.getDisplayName().trim().equalsIgnoreCase(CENTRAL_REPO_INGEST_NAME); + }); + } + + /** + * Returns true if the central repository ingest module has been run on the + * datasource. + * + * @param dataSource The data source. + * + * @return True if there is an ingest job pertaining to the data source + * where an ingest module matches the central repo ingest module + * display name. + * + * @throws SleuthkitCaseProviderException + * @throws TskCoreException + */ + public boolean isCentralRepoIngested(DataSource dataSource) + throws SleuthkitCaseProvider.SleuthkitCaseProviderException, TskCoreException { + if (dataSource == null) { + return false; + } + + long dataSourceId = dataSource.getId(); + + return this.caseProvider.get().getIngestJobs().stream() + .anyMatch((ingestJob) -> { + return ingestJob != null + && ingestJob.getObjectId() == dataSourceId + && hasCentralRepoIngest(ingestJob); + }); + + } + + /** + * Throws an exception if the current data source has not been ingested with + * the Central Repository Ingest Module. + * + * @param dataSource The data source to check if it has been ingested with + * the Central Repository Ingest Module. + * + * @throws SleuthkitCaseProviderException + * @throws TskCoreException + * @throws NotIngestedWithModuleException + */ + private void throwOnNotCentralRepoIngested(DataSource dataSource) + throws SleuthkitCaseProvider.SleuthkitCaseProviderException, TskCoreException, NotIngestedWithModuleException { + + if (!isCentralRepoIngested(dataSource)) { + String objectId = (dataSource == null) ? "" : String.valueOf(dataSource.getId()); + String message = String.format("Data source: %s has not been ingested with the Central Repository Ingest Module.", objectId); + throw new NotIngestedWithModuleException(CENTRAL_REPO_INGEST_NAME, message); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummary.java index b778b43a90..0f048a6ed0 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummary.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummary.java @@ -19,18 +19,22 @@ package org.sleuthkit.autopsy.datasourcesummary.datamodel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultArtifactUpdateGovernor; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; -import java.util.stream.IntStream; import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -51,6 +55,7 @@ import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; public class UserActivitySummary implements DefaultArtifactUpdateGovernor { private static final BlackboardArtifact.Type TYPE_DEVICE_ATTACHED = new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_DEVICE_ATTACHED); + private static final BlackboardArtifact.Type TYPE_WEB_HISTORY = new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WEB_HISTORY); private static final BlackboardAttribute.Type TYPE_DATETIME = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME); private static final BlackboardAttribute.Type TYPE_DATETIME_ACCESSED = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED); @@ -59,15 +64,14 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { private static final BlackboardAttribute.Type TYPE_DEVICE_MODEL = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL); private static final BlackboardAttribute.Type TYPE_MESSAGE_TYPE = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE); private static final BlackboardAttribute.Type TYPE_TEXT = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_TEXT); - private static final BlackboardAttribute.Type TYPE_DATETIME_RCVD = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_RCVD); private static final BlackboardAttribute.Type TYPE_DATETIME_SENT = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_SENT); private static final BlackboardAttribute.Type TYPE_DATETIME_START = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_START); private static final BlackboardAttribute.Type TYPE_DATETIME_END = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_END); + private static final BlackboardAttribute.Type TYPE_DOMAIN = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DOMAIN); private static final Comparator TOP_ACCOUNT_RESULT_DATE_COMPARE = (a, b) -> a.getLastAccess().compareTo(b.getLastAccess()); private static final Comparator TOP_WEBSEARCH_RESULT_DATE_COMPARE = (a, b) -> a.getDateAccessed().compareTo(b.getDateAccessed()); - private static final String ROOT_HUB_IDENTIFIER = "ROOT_HUB"; private static final Set ARTIFACT_UPDATE_TYPE_IDS = new HashSet<>(Arrays.asList( ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID(), @@ -78,39 +82,12 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID() )); - private static final long SLEEP_TIME = 5000; + private static final Set DEVICE_EXCLUDE_LIST = new HashSet<>(Arrays.asList("ROOT_HUB", "ROOT_HUB20")); + private static final Set DOMAIN_EXCLUDE_LIST = new HashSet<>(Arrays.asList("127.0.0.1", "LOCALHOST")); - /** - * A function to calculate a result from 2 parameters. - */ - interface Function2 { - - O apply(A1 a1, A2 a2); - } - - /** - * Gets a list of recent domains based on the datasource. - * - * @param dataSource The datasource to query for recent domains. - * @param count The max count of items to return. - * - * @return The list of items retrieved from the database. - * - * @throws InterruptedException - */ - public List getRecentDomains(DataSource dataSource, int count) throws InterruptedException { - Thread.sleep(SLEEP_TIME); - final String dId = Long.toString(dataSource.getId()); - final Function2 getId = (s, idx) -> String.format("d:%s, f:%s, i:%d", dId, s, idx); - return IntStream.range(0, count) - .mapToObj(num -> new TopDomainsResult( - getId.apply("domain", num), - getId.apply("url", num), - (long) num, - new Date(((long) num) * 1000 * 60 * 60 * 24) - )) - .collect(Collectors.toList()); - } + private static final long MS_PER_DAY = 1000 * 60 * 60 * 24; + private static final long DOMAIN_WINDOW_DAYS = 30; + private static final long DOMAIN_WINDOW_MS = DOMAIN_WINDOW_DAYS * MS_PER_DAY; private final SleuthkitCaseProvider caseProvider; private final TextTranslationService translationService; @@ -159,6 +136,143 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { } } + /** + * Gets a list of recent domains based on the datasource. + * + * @param dataSource The datasource to query for recent domains. + * @param count The max count of items to return. + * + * @return The list of items retrieved from the database. + * + * @throws InterruptedException + */ + public List getRecentDomains(DataSource dataSource, int count) throws TskCoreException, SleuthkitCaseProviderException { + assertValidCount(count); + + Pair>> mostRecentAndGroups = getDomainGroupsAndMostRecent(dataSource); + // if no recent domains, return accordingly + if (mostRecentAndGroups.getKey() == null || mostRecentAndGroups.getValue().size() == 0) { + return Collections.emptyList(); + } + + final long mostRecentMs = mostRecentAndGroups.getLeft(); + Map> groups = mostRecentAndGroups.getRight(); + + return groups.entrySet().stream() + .map(entry -> getDomainsResult(entry.getKey(), entry.getValue(), mostRecentMs)) + .filter(result -> result != null) + // sort by number of visit times in those 30 days (max to min) + .sorted((a, b) -> -Long.compare(a.getVisitTimes(), b.getVisitTimes())) + // limit the result number to the parameter provided + .limit(count) + .collect(Collectors.toList()); + } + + /** + * Creates a TopDomainsResult from data or null if no visit date exists + * within DOMAIN_WINDOW_MS of mostRecentMs. + * + * @param domain The domain. + * @param visits The number of visits. + * @param mostRecentMs The most recent visit of any domain. + * + * @return The TopDomainsResult or null if no visits to this domain within + * 30 days of mostRecentMs. + */ + private TopDomainsResult getDomainsResult(String domain, List visits, long mostRecentMs) { + long visitCount = 0; + Long thisMostRecentMs = null; + + for (Long visitMs : visits) { + // make sure that visit is within window of mostRecentMS; otherwise skip it. + if (visitMs + DOMAIN_WINDOW_MS < mostRecentMs) { + continue; + } + + // if visit is within window, increment the count and get most recent + visitCount++; + thisMostRecentMs = getMax(thisMostRecentMs, visitMs); + } + + // if there are no visits within the window, return null + if (visitCount <= 0 || thisMostRecentMs == null) { + return null; + } else { + // create a top domain result with the domain, count, and most recent visit date + return new TopDomainsResult(domain, visitCount, new Date(thisMostRecentMs)); + } + } + + /** + * Queries TSK_WEB_HISTORY artifacts and returning the latest web history + * date accessed and a mapping of domains to all of their visits. + * + * @param dataSource The datasource. + * + * @return A tuple where the first value is the latest web history accessed + * date in milliseconds and the second value maps normalized + * (lowercase; trimmed) domain names to when those domains were + * visited. + * + * @throws TskCoreException + * @throws SleuthkitCaseProviderException + */ + private Pair>> getDomainGroupsAndMostRecent(DataSource dataSource) throws TskCoreException, SleuthkitCaseProviderException { + List artifacts = DataSourceInfoUtilities.getArtifacts(caseProvider.get(), TYPE_WEB_HISTORY, + dataSource, TYPE_DATETIME_ACCESSED, DataSourceInfoUtilities.SortOrder.DESCENDING, 0); + + Long mostRecentMs = null; + Map> domainVisits = new HashMap<>(); + + for (BlackboardArtifact art : artifacts) { + Long artifactDateSecs = DataSourceInfoUtilities.getLongOrNull(art, TYPE_DATETIME_ACCESSED); + String domain = DataSourceInfoUtilities.getStringOrNull(art, TYPE_DOMAIN); + + // if there isn't a last access date or domain for this artifact, it can be ignored. + // Also, ignore the loopback address. + if (artifactDateSecs == null || StringUtils.isBlank(domain) || DOMAIN_EXCLUDE_LIST.contains(domain.toUpperCase().trim())) { + continue; + } + + Long artifactDateMs = artifactDateSecs * 1000; + + // update the most recent visit date overall + mostRecentMs = getMax(mostRecentMs, artifactDateMs); + + //Normalize the domain to lower case. + domain = domain.toLowerCase().trim(); + + // add this visit date to the list of dates for the domain + List domainVisitList = domainVisits.get(domain); + if (domainVisitList == null) { + domainVisitList = new ArrayList<>(); + domainVisits.put(domain, domainVisitList); + } + + domainVisitList.add(artifactDateMs); + } + + return Pair.of(mostRecentMs, domainVisits); + } + + /** + * Returns the maximum value given two longs handling possible null values. + * + * @param num1 The first number. + * @param num2 The second number. + * + * @return The maximum non-null number or null if both numbers are null. + */ + private static Long getMax(Long num1, Long num2) { + if (num1 == null) { + return num2; + } else if (num2 == null) { + return num1; + } else { + return num2 > num1 ? num2 : num1; + } + } + /** * Attempts to obtain a web search result record from a blackboard artifact. * @@ -291,7 +405,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { // remove Root Hub identifier .filter(result -> { return result.getDeviceModel() == null - || !result.getDeviceModel().trim().toUpperCase().equals(ROOT_HUB_IDENTIFIER); + || !DEVICE_EXCLUDE_LIST.contains(result.getDeviceModel().trim().toUpperCase()); }) .limit(count) .collect(Collectors.toList()); @@ -555,7 +669,6 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { public static class TopDomainsResult { private final String domain; - private final String url; private final Long visitTimes; private final Date lastVisit; @@ -567,9 +680,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { * @param visitTimes The number of times it was visited. * @param lastVisit The date of the last visit. */ - public TopDomainsResult(String domain, String url, Long visitTimes, Date lastVisit) { + public TopDomainsResult(String domain, Long visitTimes, Date lastVisit) { this.domain = domain; - this.url = url; this.visitTimes = visitTimes; this.lastVisit = lastVisit; } @@ -581,13 +693,6 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { return domain; } - /** - * @return The url for the result. - */ - public String getUrl() { - return url; - } - /** * @return The number of times this site is visited. */ @@ -601,6 +706,5 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { public Date getLastVisit() { return lastVisit; } - } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java index c522ac0e48..6d076c3d13 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java @@ -35,7 +35,7 @@ import org.sleuthkit.datamodel.DataSource; /** * Base class from which other tabs in data source summary derive. */ -abstract class BaseDataSourceSummaryPanel extends JPanel implements AutoCloseable { +abstract class BaseDataSourceSummaryPanel extends JPanel { private static final long serialVersionUID = 1L; @@ -57,11 +57,8 @@ abstract class BaseDataSourceSummaryPanel extends JPanel implements AutoCloseabl /** * Closes listeners and resources. - * - * @throws Exception */ - @Override - public void close() throws Exception { + public void close() { executor.cancelRunning(); updateHandler.unregister(); } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties index c040c4f379..689533fdfe 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties @@ -29,7 +29,7 @@ ContainerPanel.acquisitionDetailsLabel.text=Acquisition Details: ContainerPanel.unallocatedSizeLabel.text=Unallocated Space: ContainerPanel.unallocatedSizeValue.text= UserActivityPanel.programsRunLabel.text=Recent Programs -UserActivityPanel.recentAccountsLabel.text=Recent Accounts +UserActivityPanel.recentAccountsLabel.text=Recent Account Types Used UserActivityPanel.topWebSearchLabel.text=Recent Web Searches UserActivityPanel.topDevicesAttachedLabel.text=Recent Devices Attached UserActivityPanel.recentDomainsLabel.text=Recent Domains @@ -39,3 +39,5 @@ AnalysisPanel.interestingItemLabel.text=Interesting Item Hits RecentFilesPanel.openDocsLabel.text=Recently Opened Documents RecentFilesPanel.downloadLabel.text=Recent Downloads RecentFilesPanel.attachmentLabel.text=Recent Attachments +PastCasesPanel.notableFileLabel.text=Cases with Common Items That Were Tagged as Notable +PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED index 51fceefbf9..c994603559 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED @@ -44,9 +44,13 @@ DataSourceSummaryNode.viewDataSourceAction.text=Go to Data Source DataSourceSummaryTabbedPane_analysisTab_title=Analysis DataSourceSummaryTabbedPane_detailsTab_title=Container DataSourceSummaryTabbedPane_ingestHistoryTab_title=Ingest History +DataSourceSummaryTabbedPane_pastCasesTab_title=Past Cases DataSourceSummaryTabbedPane_recentFileTab_title=Recent Files DataSourceSummaryTabbedPane_typesTab_title=Types DataSourceSummaryTabbedPane_userActivityTab_title=User Activity +PastCasesPanel_caseColumn_title=Case +PastCasesPanel_countColumn_title=Count +PastCasesPanel_onNoCrIngest_message=No results will be shown because the Central Repository module was not run. RecentFilePanel_col_header_domain=Domain RecentFilePanel_col_header_path=Path RecentFilePanel_col_header_sender=Sender @@ -76,7 +80,7 @@ TypesPanel_osLabel_title=OS TypesPanel_sizeLabel_title=Size TypesPanel_usageLabel_title=Usage UserActivityPanel.programsRunLabel.text=Recent Programs -UserActivityPanel.recentAccountsLabel.text=Recent Accounts +UserActivityPanel.recentAccountsLabel.text=Recent Account Types Used UserActivityPanel.topWebSearchLabel.text=Recent Web Searches UserActivityPanel.topDevicesAttachedLabel.text=Recent Devices Attached UserActivityPanel.recentDomainsLabel.text=Recent Domains @@ -86,6 +90,8 @@ AnalysisPanel.interestingItemLabel.text=Interesting Item Hits RecentFilesPanel.openDocsLabel.text=Recently Opened Documents RecentFilesPanel.downloadLabel.text=Recent Downloads RecentFilesPanel.attachmentLabel.text=Recent Attachments +PastCasesPanel.notableFileLabel.text=Cases with Common Items That Were Tagged as Notable +PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs UserActivityPanel_noDataExists=No communication data exists UserActivityPanel_tab_title=User Activity UserActivityPanel_TopAccountTableModel_accountType_header=Account Type @@ -93,9 +99,9 @@ UserActivityPanel_TopAccountTableModel_lastAccess_header=Last Accessed UserActivityPanel_TopDeviceAttachedTableModel_dateAccessed_header=Last Accessed UserActivityPanel_TopDeviceAttachedTableModel_deviceId_header=Device Id UserActivityPanel_TopDeviceAttachedTableModel_makeModel_header=Make and Model +UserActivityPanel_TopDomainsTableModel_count_header=Visits UserActivityPanel_TopDomainsTableModel_domain_header=Domain -UserActivityPanel_TopDomainsTableModel_lastAccess_header=Last Access -UserActivityPanel_TopDomainsTableModel_url_header=URL +UserActivityPanel_TopDomainsTableModel_lastAccess_header=Last Accessed UserActivityPanel_TopProgramsTableModel_count_header=Run Times UserActivityPanel_TopProgramsTableModel_folder_header=Folder UserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryDialog.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryDialog.java index 113821faea..72ce9dc8f2 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryDialog.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryDialog.java @@ -97,12 +97,7 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser @Override public void dispose() { IngestManager.getInstance().removeIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, ingestEventListener); - try { - this.dataSourceSummaryTabbedPane.close(); - } catch (Exception ex) { - logger.log(Level.WARNING, "There was an error disposing of resources in the tabbed pange for data source summary.", ex); - } - + this.dataSourceSummaryTabbedPane.close(); super.dispose(); } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java index 345cd82e7d..cee151b68a 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java @@ -37,9 +37,10 @@ import org.sleuthkit.datamodel.DataSource; "DataSourceSummaryTabbedPane_userActivityTab_title=User Activity", "DataSourceSummaryTabbedPane_ingestHistoryTab_title=Ingest History", "DataSourceSummaryTabbedPane_recentFileTab_title=Recent Files", + "DataSourceSummaryTabbedPane_pastCasesTab_title=Past Cases", "DataSourceSummaryTabbedPane_analysisTab_title=Analysis" }) -public class DataSourceSummaryTabbedPane extends JTabbedPane implements AutoCloseable { +public class DataSourceSummaryTabbedPane extends JTabbedPane { /** * Records of tab information (i.e. title, component, function to call on @@ -50,7 +51,7 @@ public class DataSourceSummaryTabbedPane extends JTabbedPane implements AutoClos private final String tabTitle; private final Component component; private final Consumer onDataSource; - private final AutoCloseable onClose; + private final Runnable onClose; /** * Main constructor. @@ -60,7 +61,7 @@ public class DataSourceSummaryTabbedPane extends JTabbedPane implements AutoClos * @param onDataSource The function to be called on a new data source. * @param onClose Called to cleanup resources when closing tabs. */ - DataSourceTab(String tabTitle, Component component, Consumer onDataSource, AutoCloseable onClose) { + DataSourceTab(String tabTitle, Component component, Consumer onDataSource, Runnable onClose) { this.tabTitle = tabTitle; this.component = component; this.onDataSource = onDataSource; @@ -77,7 +78,7 @@ public class DataSourceSummaryTabbedPane extends JTabbedPane implements AutoClos this.tabTitle = tabTitle; this.component = panel; this.onDataSource = panel::setDataSource; - this.onClose = panel; + this.onClose = panel::close; } /** @@ -102,9 +103,9 @@ public class DataSourceSummaryTabbedPane extends JTabbedPane implements AutoClos } /** - * @return The object for closing when closing the tab. + * @return The action for closing resources in the tab. */ - public AutoCloseable getOnClose() { + public Runnable getOnClose() { return onClose; } } @@ -116,8 +117,9 @@ public class DataSourceSummaryTabbedPane extends JTabbedPane implements AutoClos private final List tabs = Arrays.asList( new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_typesTab_title(), new TypesPanel()), new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_userActivityTab_title(), new UserActivityPanel()), - new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_recentFileTab_title(), new RecentFilesPanel()), new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_analysisTab_title(), new AnalysisPanel()), + new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_recentFileTab_title(), new RecentFilesPanel()), + new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_pastCasesTab_title(), new PastCasesPanel()), // do nothing on closing new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_ingestHistoryTab_title(), ingestHistoryPanel, ingestHistoryPanel::setDataSource, () -> { }), @@ -163,13 +165,10 @@ public class DataSourceSummaryTabbedPane extends JTabbedPane implements AutoClos /** * Handle close events on each tab. - * - * @throws Exception */ - @Override - public void close() throws Exception { + public void close() { for (DataSourceTab tab : tabs) { - tab.getOnClose().close(); + tab.getOnClose().run(); } } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.form b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.form new file mode 100644 index 0000000000..13ddb081bd --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.form @@ -0,0 +1,194 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java new file mode 100644 index 0000000000..a530757276 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java @@ -0,0 +1,209 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datasourcesummary.ui; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.apache.commons.lang3.tuple.Pair; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary.NotIngestedWithModuleException; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary.PastCasesResult; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.ColumnModel; +import org.sleuthkit.datamodel.DataSource; + +/** + * A tab shown in data source summary displaying information about a datasource + * and how it pertains to other cases. + */ +@Messages({ + "PastCasesPanel_caseColumn_title=Case", + "PastCasesPanel_countColumn_title=Count", + "PastCasesPanel_onNoCrIngest_message=No results will be shown because the Central Repository module was not run." + +}) +public class PastCasesPanel extends BaseDataSourceSummaryPanel { + + private static final long serialVersionUID = 1L; + + private static final ColumnModel> CASE_COL = new ColumnModel<>( + Bundle.PastCasesPanel_caseColumn_title(), + (pair) -> new DefaultCellModel(pair.getKey()), + 300 + ); + + private static final ColumnModel> COUNT_COL = new ColumnModel<>( + Bundle.PastCasesPanel_countColumn_title(), + (pair) -> new DefaultCellModel(String.valueOf(pair.getValue())), + 100 + ); + + private static final List>> DEFAULT_COLUMNS = Arrays.asList(CASE_COL, COUNT_COL); + + private final JTablePanel> notableFileTable = JTablePanel.getJTablePanel(DEFAULT_COLUMNS); + + private final JTablePanel> sameIdTable = JTablePanel.getJTablePanel(DEFAULT_COLUMNS); + + private final List> tables = Arrays.asList( + notableFileTable, + sameIdTable + ); + + private final List> dataFetchComponents; + + public PastCasesPanel() { + this(new PastCasesSummary()); + } + + /** + * Creates new form PastCasesPanel + */ + public PastCasesPanel(PastCasesSummary pastCaseData) { + // set up data acquisition methods + dataFetchComponents = Arrays.asList( + new DataFetchWorker.DataFetchComponents<>( + (dataSource) -> pastCaseData.getPastCasesData(dataSource), + (result) -> handleResult(result)) + ); + + initComponents(); + } + + /** + * handles displaying the result for the table. If a + * NotCentralRepoIngestedException is thrown, then an appropriate message is + * shown. Otherwise, this method uses the tables default showDataFetchResult + * method. + * + * @param result The result. + */ + private void handleResult(DataFetchResult result) { + if (result.getResultType() == ResultType.ERROR && result.getException() instanceof NotIngestedWithModuleException) { + notableFileTable.showMessage(Bundle.PastCasesPanel_onNoCrIngest_message()); + sameIdTable.showMessage(Bundle.PastCasesPanel_onNoCrIngest_message()); + } else { + notableFileTable.showDataFetchResult(getSubResult(result, (res) -> (res == null) ? null : res.getTaggedNotable())); + sameIdTable.showDataFetchResult(getSubResult(result, (res) -> (res == null) ? null : res.getSameIdsResults())); + } + } + + /** + * Given an input data fetch result, creates an error result if the original + * is an error. Otherwise, uses the getSubResult function on the underlying + * data to create a new DataFetchResult. + * + * @param inputResult The input result. + * @param getSubComponent The means of getting the data given the original + * data. + * + * @return The new result with the error of the original or the processed + * data. + */ + private DataFetchResult getSubResult(DataFetchResult inputResult, Function getSubResult) { + if (inputResult.getResultType() == ResultType.SUCCESS) { + return DataFetchResult.getSuccessResult(getSubResult.apply(inputResult.getData())); + } else { + return DataFetchResult.getErrorResult(inputResult.getException()); + } + } + + @Override + protected void fetchInformation(DataSource dataSource) { + fetchInformation(dataFetchComponents, dataSource); + } + + @Override + protected void onNewDataSource(DataSource dataSource) { + onNewDataSource(dataFetchComponents, tables, dataSource); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + javax.swing.JScrollPane mainScrollPane = new javax.swing.JScrollPane(); + javax.swing.JPanel mainContentPanel = new javax.swing.JPanel(); + javax.swing.JLabel notableFileLabel = new javax.swing.JLabel(); + javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(32767, 2)); + javax.swing.JPanel notableFilePanel = notableFileTable; + javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(32767, 20)); + javax.swing.JLabel sameIdLabel = new javax.swing.JLabel(); + javax.swing.Box.Filler filler3 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(32767, 2)); + javax.swing.JPanel sameIdPanel = sameIdTable; + javax.swing.Box.Filler filler5 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767)); + + mainContentPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10)); + mainContentPanel.setLayout(new javax.swing.BoxLayout(mainContentPanel, javax.swing.BoxLayout.PAGE_AXIS)); + + org.openide.awt.Mnemonics.setLocalizedText(notableFileLabel, org.openide.util.NbBundle.getMessage(PastCasesPanel.class, "PastCasesPanel.notableFileLabel.text")); // NOI18N + mainContentPanel.add(notableFileLabel); + notableFileLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(PastCasesPanel.class, "PastCasesPanel.notableFileLabel.text")); // NOI18N + + mainContentPanel.add(filler1); + + notableFilePanel.setAlignmentX(0.0F); + notableFilePanel.setMaximumSize(new java.awt.Dimension(32767, 106)); + notableFilePanel.setMinimumSize(new java.awt.Dimension(100, 106)); + notableFilePanel.setPreferredSize(new java.awt.Dimension(100, 106)); + mainContentPanel.add(notableFilePanel); + mainContentPanel.add(filler2); + + org.openide.awt.Mnemonics.setLocalizedText(sameIdLabel, org.openide.util.NbBundle.getMessage(PastCasesPanel.class, "PastCasesPanel.sameIdLabel.text")); // NOI18N + mainContentPanel.add(sameIdLabel); + mainContentPanel.add(filler3); + + sameIdPanel.setAlignmentX(0.0F); + sameIdPanel.setMaximumSize(new java.awt.Dimension(32767, 106)); + sameIdPanel.setMinimumSize(new java.awt.Dimension(100, 106)); + sameIdPanel.setPreferredSize(new java.awt.Dimension(100, 106)); + mainContentPanel.add(sameIdPanel); + mainContentPanel.add(filler5); + + mainScrollPane.setViewportView(mainContentPanel); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(mainScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(mainScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/UserActivityPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/UserActivityPanel.java index a984bbf38a..6437812296 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/UserActivityPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/UserActivityPanel.java @@ -50,9 +50,8 @@ import org.sleuthkit.datamodel.DataSource; "UserActivityPanel_TopProgramsTableModel_count_header=Run Times", "UserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run", "UserActivityPanel_TopDomainsTableModel_domain_header=Domain", - "UserActivityPanel_TopDomainsTableModel_url_header=URL", - "UserActivityPanel_TopDomainsTableModel_lastAccess_header=Last Access", - "UserActivityPanel_noDataExists=No communication data exists", + "UserActivityPanel_TopDomainsTableModel_count_header=Visits", + "UserActivityPanel_TopDomainsTableModel_lastAccess_header=Last Accessed", "UserActivityPanel_TopWebSearchTableModel_searchString_header=Search String", "UserActivityPanel_TopWebSearchTableModel_dateAccessed_header=Date Accessed", "UserActivityPanel_TopWebSearchTableModel_translatedResult_header=Translated", @@ -60,7 +59,8 @@ import org.sleuthkit.datamodel.DataSource; "UserActivityPanel_TopDeviceAttachedTableModel_makeModel_header=Make and Model", "UserActivityPanel_TopDeviceAttachedTableModel_dateAccessed_header=Last Accessed", "UserActivityPanel_TopAccountTableModel_accountType_header=Account Type", - "UserActivityPanel_TopAccountTableModel_lastAccess_header=Last Accessed",}) + "UserActivityPanel_TopAccountTableModel_lastAccess_header=Last Accessed", + "UserActivityPanel_noDataExists=No communication data exists"}) public class UserActivityPanel extends BaseDataSourceSummaryPanel { private static final long serialVersionUID = 1L; @@ -126,11 +126,14 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel { Bundle.UserActivityPanel_TopDomainsTableModel_domain_header(), (recentDomain) -> new DefaultCellModel(recentDomain.getDomain()), 250), - // url column + // count column new ColumnModel<>( - Bundle.UserActivityPanel_TopDomainsTableModel_url_header(), - (recentDomain) -> new DefaultCellModel(recentDomain.getUrl()), - 250), + Bundle.UserActivityPanel_TopDomainsTableModel_count_header(), + (recentDomain) -> { + String visitTimes = recentDomain.getVisitTimes() == null ? "" : Long.toString(recentDomain.getVisitTimes()); + return new DefaultCellModel(visitTimes); + }, + 100), // last accessed column new ColumnModel<>( Bundle.UserActivityPanel_TopDomainsTableModel_lastAccess_header(), diff --git a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties-MERGED index 8638a2c121..b4f350f478 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties-MERGED @@ -12,8 +12,8 @@ ILeappAnalyzerIngestModule.report.name=iLeapp Html Report ILeappAnalyzerIngestModule.requires.windows=iLeapp module requires windows. ILeappAnalyzerIngestModule.running.iLeapp=Running iLeapp ILeappAnalyzerIngestModule.starting.iLeapp=Starting iLeapp -ILeappAnalyzerModuleFactory_moduleDesc=Runs iLeapp against files. -ILeappAnalyzerModuleFactory_moduleName=ILeapp Analyzer +ILeappAnalyzerModuleFactory_moduleDesc=Uses iLEAPP to analyze logical acquisitions of iOS devices. +ILeappAnalyzerModuleFactory_moduleName=iOS Analyzer (iLEAPP) ILeappFileProcessor.cannot.load.artifact.xml=Cannor load xml artifact file. ILeappFileProcessor.cannotBuildXmlParser=Cannot buld an XML parser. ILeappFileProcessor.completed=iLeapp Processing Completed diff --git a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappAnalyzerModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappAnalyzerModuleFactory.java index 5da165392f..166c455d55 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappAnalyzerModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappAnalyzerModuleFactory.java @@ -33,7 +33,7 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; @ServiceProvider(service = IngestModuleFactory.class) public class ILeappAnalyzerModuleFactory extends IngestModuleFactoryAdapter { - @NbBundle.Messages({"ILeappAnalyzerModuleFactory_moduleName=ILeapp Analyzer"}) + @NbBundle.Messages({"ILeappAnalyzerModuleFactory_moduleName=iOS Analyzer (iLEAPP)"}) static String getModuleName() { return Bundle.ILeappAnalyzerModuleFactory_moduleName(); } @@ -43,7 +43,7 @@ public class ILeappAnalyzerModuleFactory extends IngestModuleFactoryAdapter { return getModuleName(); } - @NbBundle.Messages({"ILeappAnalyzerModuleFactory_moduleDesc=Runs iLeapp against files."}) + @NbBundle.Messages({"ILeappAnalyzerModuleFactory_moduleDesc=Uses iLEAPP to analyze logical acquisitions of iOS devices."}) @Override public String getModuleDescription() { return Bundle.ILeappAnalyzerModuleFactory_moduleDesc(); diff --git a/Core/src/org/sleuthkit/autopsy/resultviewers/summary/DataSourceSummaryResultViewer.java b/Core/src/org/sleuthkit/autopsy/resultviewers/summary/DataSourceSummaryResultViewer.java index ce9a78d4b5..12dfe741d4 100644 --- a/Core/src/org/sleuthkit/autopsy/resultviewers/summary/DataSourceSummaryResultViewer.java +++ b/Core/src/org/sleuthkit/autopsy/resultviewers/summary/DataSourceSummaryResultViewer.java @@ -137,16 +137,8 @@ public class DataSourceSummaryResultViewer extends AbstractDataResultViewer { @Override public void clearComponent() { - // clear resources for summary panel - if (summaryPanel != null) { - try { - summaryPanel.close(); - } catch (Exception e) { - LOGGER.log(Level.WARNING, "There was an error closing the datasource summary tabbed panel.", e); - } - - summaryPanel = null; - } + summaryPanel.close(); + summaryPanel = null; } private DataSourceSummaryTabbedPane summaryPanel;