Autopsy  4.16.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
UserActivitySummary.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2020 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.datasourcesummary.datamodel;
20 
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.Comparator;
27 import java.util.Date;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Set;
33 import java.util.logging.Level;
34 import java.util.stream.Collectors;
35 import java.util.stream.Stream;
36 import org.apache.commons.lang3.StringUtils;
37 import org.apache.commons.lang3.tuple.Pair;
38 import org.openide.util.NbBundle.Messages;
40 import org.sleuthkit.datamodel.BlackboardArtifact;
41 import org.sleuthkit.datamodel.BlackboardAttribute;
42 import org.sleuthkit.datamodel.DataSource;
43 import org.sleuthkit.datamodel.TskCoreException;
47 import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
48 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
49 
56 
57  private static final BlackboardArtifact.Type TYPE_DEVICE_ATTACHED = new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_DEVICE_ATTACHED);
58  private static final BlackboardArtifact.Type TYPE_WEB_HISTORY = new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WEB_HISTORY);
59 
60  private static final BlackboardAttribute.Type TYPE_DATETIME = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME);
61  private static final BlackboardAttribute.Type TYPE_DATETIME_ACCESSED = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED);
62  private static final BlackboardAttribute.Type TYPE_DEVICE_ID = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_ID);
63  private static final BlackboardAttribute.Type TYPE_DEVICE_MAKE = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_MAKE);
64  private static final BlackboardAttribute.Type TYPE_DEVICE_MODEL = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL);
65  private static final BlackboardAttribute.Type TYPE_MESSAGE_TYPE = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE);
66  private static final BlackboardAttribute.Type TYPE_TEXT = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_TEXT);
67  private static final BlackboardAttribute.Type TYPE_DATETIME_RCVD = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_RCVD);
68  private static final BlackboardAttribute.Type TYPE_DATETIME_SENT = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_SENT);
69  private static final BlackboardAttribute.Type TYPE_DATETIME_START = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_START);
70  private static final BlackboardAttribute.Type TYPE_DATETIME_END = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_END);
71  private static final BlackboardAttribute.Type TYPE_DOMAIN = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DOMAIN);
72 
73  private static final Comparator<TopAccountResult> TOP_ACCOUNT_RESULT_DATE_COMPARE = (a, b) -> a.getLastAccess().compareTo(b.getLastAccess());
74  private static final Comparator<TopWebSearchResult> TOP_WEBSEARCH_RESULT_DATE_COMPARE = (a, b) -> a.getDateAccessed().compareTo(b.getDateAccessed());
75 
76  private static final Set<Integer> ARTIFACT_UPDATE_TYPE_IDS = new HashSet<>(Arrays.asList(
77  ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID(),
78  ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(),
79  ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(),
80  ARTIFACT_TYPE.TSK_CALLLOG.getTypeID(),
81  ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID(),
82  ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID()
83  ));
84 
85  private static final Set<String> DEVICE_EXCLUDE_LIST = new HashSet<>(Arrays.asList("ROOT_HUB", "ROOT_HUB20"));
86  private static final Set<String> DOMAIN_EXCLUDE_LIST = new HashSet<>(Arrays.asList("127.0.0.1", "LOCALHOST"));
87 
88  private static final long MS_PER_DAY = 1000 * 60 * 60 * 24;
89  private static final long DOMAIN_WINDOW_DAYS = 30;
90  private static final long DOMAIN_WINDOW_MS = DOMAIN_WINDOW_DAYS * MS_PER_DAY;
91 
94  private final java.util.logging.Logger logger;
95 
102  }
103 
114  SleuthkitCaseProvider provider,
115  TextTranslationService translationService,
116  java.util.logging.Logger logger) {
117 
118  this.caseProvider = provider;
119  this.translationService = translationService;
120  this.logger = logger;
121  }
122 
123  @Override
124  public Set<Integer> getArtifactTypeIdsForRefresh() {
126  }
127 
133  private void assertValidCount(int count) {
134  if (count <= 0) {
135  throw new IllegalArgumentException("Count must be greater than 0");
136  }
137  }
138 
149  public List<TopDomainsResult> getRecentDomains(DataSource dataSource, int count) throws TskCoreException, SleuthkitCaseProviderException {
150  assertValidCount(count);
151 
152  if (dataSource == null) {
153  return Collections.emptyList();
154  }
155 
156  Pair<Long, Map<String, List<Long>>> mostRecentAndGroups = getDomainGroupsAndMostRecent(dataSource);
157  // if no recent domains, return accordingly
158  if (mostRecentAndGroups.getKey() == null || mostRecentAndGroups.getValue().size() == 0) {
159  return Collections.emptyList();
160  }
161 
162  final long mostRecentMs = mostRecentAndGroups.getLeft();
163  Map<String, List<Long>> groups = mostRecentAndGroups.getRight();
164 
165  return groups.entrySet().stream()
166  .map(entry -> getDomainsResult(entry.getKey(), entry.getValue(), mostRecentMs))
167  .filter(result -> result != null)
168  // sort by number of visit times in those 30 days (max to min)
169  .sorted((a, b) -> -Long.compare(a.getVisitTimes(), b.getVisitTimes()))
170  // limit the result number to the parameter provided
171  .limit(count)
172  .collect(Collectors.toList());
173  }
174 
186  private TopDomainsResult getDomainsResult(String domain, List<Long> visits, long mostRecentMs) {
187  long visitCount = 0;
188  Long thisMostRecentMs = null;
189 
190  for (Long visitMs : visits) {
191  // make sure that visit is within window of mostRecentMS; otherwise skip it.
192  if (visitMs + DOMAIN_WINDOW_MS < mostRecentMs) {
193  continue;
194  }
195 
196  // if visit is within window, increment the count and get most recent
197  visitCount++;
198  thisMostRecentMs = getMax(thisMostRecentMs, visitMs);
199  }
200 
201  // if there are no visits within the window, return null
202  if (visitCount <= 0 || thisMostRecentMs == null) {
203  return null;
204  } else {
205  // create a top domain result with the domain, count, and most recent visit date
206  return new TopDomainsResult(domain, visitCount, new Date(thisMostRecentMs));
207  }
208  }
209 
224  private Pair<Long, Map<String, List<Long>>> getDomainGroupsAndMostRecent(DataSource dataSource) throws TskCoreException, SleuthkitCaseProviderException {
225  List<BlackboardArtifact> artifacts = DataSourceInfoUtilities.getArtifacts(caseProvider.get(), TYPE_WEB_HISTORY,
226  dataSource, TYPE_DATETIME_ACCESSED, DataSourceInfoUtilities.SortOrder.DESCENDING, 0);
227 
228  Long mostRecentMs = null;
229  Map<String, List<Long>> domainVisits = new HashMap<>();
230 
231  for (BlackboardArtifact art : artifacts) {
232  Long artifactDateSecs = DataSourceInfoUtilities.getLongOrNull(art, TYPE_DATETIME_ACCESSED);
233  String domain = DataSourceInfoUtilities.getStringOrNull(art, TYPE_DOMAIN);
234 
235  // if there isn't a last access date or domain for this artifact, it can be ignored.
236  // Also, ignore the loopback address.
237  if (artifactDateSecs == null || StringUtils.isBlank(domain) || DOMAIN_EXCLUDE_LIST.contains(domain.toUpperCase().trim())) {
238  continue;
239  }
240 
241  Long artifactDateMs = artifactDateSecs * 1000;
242 
243  // update the most recent visit date overall
244  mostRecentMs = getMax(mostRecentMs, artifactDateMs);
245 
246  //Normalize the domain to lower case.
247  domain = domain.toLowerCase().trim();
248 
249  // add this visit date to the list of dates for the domain
250  List<Long> domainVisitList = domainVisits.get(domain);
251  if (domainVisitList == null) {
252  domainVisitList = new ArrayList<>();
253  domainVisits.put(domain, domainVisitList);
254  }
255 
256  domainVisitList.add(artifactDateMs);
257  }
258 
259  return Pair.of(mostRecentMs, domainVisits);
260  }
261 
270  private static Long getMax(Long num1, Long num2) {
271  if (num1 == null) {
272  return num2;
273  } else if (num2 == null) {
274  return num1;
275  } else {
276  return num2 > num1 ? num2 : num1;
277  }
278  }
279 
288  private static TopWebSearchResult getWebSearchResult(BlackboardArtifact artifact) {
289  String searchString = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_TEXT);
290  Date dateAccessed = DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME_ACCESSED);
291  return (StringUtils.isNotBlank(searchString) && dateAccessed != null)
292  ? new TopWebSearchResult(searchString, dateAccessed)
293  : null;
294  }
295 
311  public List<TopWebSearchResult> getMostRecentWebSearches(DataSource dataSource, int count) throws SleuthkitCaseProviderException, TskCoreException {
312  assertValidCount(count);
313 
314  if (dataSource == null) {
315  return Collections.emptyList();
316  }
317 
318  // get the artifacts
319  List<BlackboardArtifact> webSearchArtifacts = caseProvider.get().getBlackboard()
320  .getArtifacts(ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID(), dataSource.getId());
321 
322  // group by search string (case insensitive)
323  Collection<List<TopWebSearchResult>> resultGroups = webSearchArtifacts
324  .stream()
325  // get items where search string and date is not null
327  // remove null records
328  .filter(result -> result != null)
329  // get these messages grouped by search to string
330  .collect(Collectors.groupingBy((result) -> result.getSearchString().toUpperCase()))
331  .values();
332 
333  // get the most recent date for each search term
334  List<TopWebSearchResult> results = resultGroups
335  .stream()
336  // get the most recent access per search type
337  .map((list) -> list.stream().max(TOP_WEBSEARCH_RESULT_DATE_COMPARE).get())
338  // get most recent searches first
339  .sorted(TOP_WEBSEARCH_RESULT_DATE_COMPARE.reversed())
340  .limit(count)
341  // get as list
342  .collect(Collectors.toList());
343 
344  // get translation if possible
345  if (translationService.hasProvider()) {
346  for (TopWebSearchResult result : results) {
347  result.setTranslatedResult(getTranslationOrNull(result.getSearchString()));
348  }
349  }
350 
351  return results;
352  }
353 
363  private String getTranslationOrNull(String original) {
364  if (!translationService.hasProvider() || StringUtils.isBlank(original)) {
365  return null;
366  }
367 
368  String translated = null;
369  try {
370  translated = translationService.translate(original);
372  logger.log(Level.WARNING, String.format("There was an error translating text: '%s'", original), ex);
373  }
374 
375  // if there is no translation or the translation is the same as the original, return null.
376  if (StringUtils.isBlank(translated)
377  || translated.toUpperCase().trim().equals(original.toUpperCase().trim())) {
378 
379  return null;
380  }
381 
382  return translated;
383  }
384 
399  public List<TopDeviceAttachedResult> getRecentDevices(DataSource dataSource, int count) throws SleuthkitCaseProviderException, TskCoreException {
400  assertValidCount(count);
401 
402  if (dataSource == null) {
403  return Collections.emptyList();
404  }
405 
406  return DataSourceInfoUtilities.getArtifacts(caseProvider.get(), TYPE_DEVICE_ATTACHED,
407  dataSource, TYPE_DATETIME, DataSourceInfoUtilities.SortOrder.DESCENDING, 0)
408  .stream()
409  .map(artifact -> {
410  return new TopDeviceAttachedResult(
411  DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_ID),
412  DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME),
413  DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MAKE),
414  DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MODEL)
415  );
416  })
417  // remove Root Hub identifier
418  .filter(result -> {
419  return result.getDeviceModel() == null
420  || !DEVICE_EXCLUDE_LIST.contains(result.getDeviceModel().trim().toUpperCase());
421  })
422  .limit(count)
423  .collect(Collectors.toList());
424  }
425 
434  private static TopAccountResult getMessageAccountResult(BlackboardArtifact artifact) {
435  String type = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_MESSAGE_TYPE);
436  Date date = DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME);
437  return (StringUtils.isNotBlank(type) && date != null)
438  ? new TopAccountResult(type, date)
439  : null;
440  }
441 
453  private static TopAccountResult getAccountResult(BlackboardArtifact artifact, String messageType, BlackboardAttribute.Type... dateAttrs) {
454  String type = messageType;
455 
456  Date latestDate = null;
457  if (dateAttrs != null) {
458  latestDate = Stream.of(dateAttrs)
459  .map((attr) -> DataSourceInfoUtilities.getDateOrNull(artifact, attr))
460  .filter((date) -> date != null)
461  .max((a, b) -> a.compareTo(b))
462  .orElse(null);
463  }
464 
465  return (StringUtils.isNotBlank(type) && latestDate != null)
466  ? new TopAccountResult(type, latestDate)
467  : null;
468  }
469 
485  @Messages({
486  "DataSourceUserActivitySummary_getRecentAccounts_emailMessage=Email Message",
487  "DataSourceUserActivitySummary_getRecentAccounts_calllogMessage=Call Log",})
488  public List<TopAccountResult> getRecentAccounts(DataSource dataSource, int count) throws SleuthkitCaseProviderException, TskCoreException {
489  assertValidCount(count);
490 
491  if (dataSource == null) {
492  return Collections.emptyList();
493  }
494 
495  Stream<TopAccountResult> messageResults = caseProvider.get().getBlackboard().getArtifacts(ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(), dataSource.getId())
496  .stream()
497  .map((art) -> getMessageAccountResult(art));
498 
499  Stream<TopAccountResult> emailResults = caseProvider.get().getBlackboard().getArtifacts(ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(), dataSource.getId())
500  .stream()
501  .map((art) -> {
502  return getAccountResult(
503  art,
504  Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(),
507  });
508 
509  Stream<TopAccountResult> calllogResults = caseProvider.get().getBlackboard().getArtifacts(ARTIFACT_TYPE.TSK_CALLLOG.getTypeID(), dataSource.getId())
510  .stream()
511  .map((art) -> {
512  return getAccountResult(
513  art,
514  Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(),
517  });
518 
519  Stream<TopAccountResult> allResults = Stream.concat(messageResults, Stream.concat(emailResults, calllogResults));
520 
521  // get them grouped by account type
522  Collection<List<TopAccountResult>> groupedResults = allResults
523  // remove null records
524  .filter(result -> result != null)
525  // get these messages grouped by account type
526  .collect(Collectors.groupingBy(TopAccountResult::getAccountType))
527  .values();
528 
529  // get account type sorted by most recent date
530  return groupedResults
531  .stream()
532  // get the most recent access per account type
533  .map((accountGroup) -> accountGroup.stream().max(TOP_ACCOUNT_RESULT_DATE_COMPARE).get())
534  // get most recent accounts accessed
535  .sorted(TOP_ACCOUNT_RESULT_DATE_COMPARE.reversed())
536  // limit to count
537  .limit(count)
538  // get as list
539  .collect(Collectors.toList());
540  }
541 
545  public static class TopWebSearchResult {
546 
547  private final String searchString;
548  private final Date dateAccessed;
549  private String translatedResult;
550 
557  public TopWebSearchResult(String searchString, Date dateAccessed) {
558  this.searchString = searchString;
559  this.dateAccessed = dateAccessed;
560  }
561 
565  public String getTranslatedResult() {
566  return translatedResult;
567  }
568 
574  public void setTranslatedResult(String translatedResult) {
575  this.translatedResult = translatedResult;
576  }
577 
581  public String getSearchString() {
582  return searchString;
583  }
584 
588  public Date getDateAccessed() {
589  return dateAccessed;
590  }
591  }
592 
596  public static class TopDeviceAttachedResult {
597 
598  private final String deviceId;
599  private final Date dateAccessed;
600  private final String deviceMake;
601  private final String deviceModel;
602 
611  public TopDeviceAttachedResult(String deviceId, Date dateAccessed, String deviceMake, String deviceModel) {
612  this.deviceId = deviceId;
613  this.dateAccessed = dateAccessed;
614  this.deviceMake = deviceMake;
615  this.deviceModel = deviceModel;
616  }
617 
621  public String getDeviceId() {
622  return deviceId;
623  }
624 
628  public Date getDateAccessed() {
629  return dateAccessed;
630  }
631 
635  public String getDeviceMake() {
636  return deviceMake;
637  }
638 
642  public String getDeviceModel() {
643  return deviceModel;
644  }
645  }
646 
651  public static class TopAccountResult {
652 
653  private final String accountType;
654  private final Date lastAccess;
655 
662  public TopAccountResult(String accountType, Date lastAccess) {
663  this.accountType = accountType;
664  this.lastAccess = lastAccess;
665  }
666 
670  public String getAccountType() {
671  return accountType;
672  }
673 
677  public Date getLastAccess() {
678  return lastAccess;
679  }
680  }
681 
685  public static class TopDomainsResult {
686 
687  private final String domain;
688  private final Long visitTimes;
689  private final Date lastVisit;
690 
699  public TopDomainsResult(String domain, Long visitTimes, Date lastVisit) {
700  this.domain = domain;
701  this.visitTimes = visitTimes;
702  this.lastVisit = lastVisit;
703  }
704 
708  public String getDomain() {
709  return domain;
710  }
711 
715  public Long getVisitTimes() {
716  return visitTimes;
717  }
718 
722  public Date getLastVisit() {
723  return lastVisit;
724  }
725  }
726 }
TopDeviceAttachedResult(String deviceId, Date dateAccessed, String deviceMake, String deviceModel)
static final Comparator< TopWebSearchResult > TOP_WEBSEARCH_RESULT_DATE_COMPARE
static TopWebSearchResult getWebSearchResult(BlackboardArtifact artifact)
List< TopAccountResult > getRecentAccounts(DataSource dataSource, int count)
Pair< Long, Map< String, List< Long > > > getDomainGroupsAndMostRecent(DataSource dataSource)
UserActivitySummary(SleuthkitCaseProvider provider, TextTranslationService translationService, java.util.logging.Logger logger)
List< TopWebSearchResult > getMostRecentWebSearches(DataSource dataSource, int count)
static TopAccountResult getMessageAccountResult(BlackboardArtifact artifact)
List< TopDeviceAttachedResult > getRecentDevices(DataSource dataSource, int count)
TopDomainsResult getDomainsResult(String domain, List< Long > visits, long mostRecentMs)
static final Comparator< TopAccountResult > TOP_ACCOUNT_RESULT_DATE_COMPARE
List< TopDomainsResult > getRecentDomains(DataSource dataSource, int count)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static TopAccountResult getAccountResult(BlackboardArtifact artifact, String messageType, BlackboardAttribute.Type...dateAttrs)

Copyright © 2012-2020 Basis Technology. Generated on: Tue Sep 22 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.