From f171de25c6d4785ca5634cfd18c9e284c093a6fe Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Thu, 23 Jul 2020 16:36:01 -0400 Subject: [PATCH] Added ContactCache --- .../autopsy/communications/ContactCache.java | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100755 Core/src/org/sleuthkit/autopsy/communications/ContactCache.java diff --git a/Core/src/org/sleuthkit/autopsy/communications/ContactCache.java b/Core/src/org/sleuthkit/autopsy/communications/ContactCache.java new file mode 100755 index 0000000000..f711634d4e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/ContactCache.java @@ -0,0 +1,182 @@ +/* + * 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.communications; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; +import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A singleton cache of the Contact artifacts for accounts. This list of + * TSK_CONTACT artifacts for a given Account retrieved on first access and + * evicted from the ache after 10 minutes. + * + */ +final class ContactCache { + + private static final Logger logger = Logger.getLogger(ContactCache.class.getName()); + + private final LoadingCache> contactCache; + + private static ContactCache instance; + + private final PropertyChangeListener ingestListener; + + /** + * Returns the list of Contacts for the given Account. + * + * @param account Account instance. + * + * @return List of TSK_CONTACT artifacts that references the given Account. + * An empty list is returned if no contacts are found. + * + * @throws ExecutionException + */ + static synchronized List getContacts(Account account) throws ExecutionException { + return getInstance().contactCache.get(account); + } + + /** + * Force the cache to invalidate all entries. + */ + static synchronized void invalidateCache() { + getInstance().contactCache.invalidateAll(); + } + + /** + * Construct a new instance. + */ + private ContactCache() { + contactCache = CacheBuilder.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES).build( + new CacheLoader>() { + @Override + public List load(Account key) { + try { + List contactList = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT); + return findContactForAccount(contactList, key); + + } catch (TskCoreException ex) { + logger.log(Level.WARNING, String.format("Failed to load contacts for account %d", key.getAccountID()), ex); + } + return new ArrayList<>(); + } + }); + + this.ingestListener = pce -> { + String eventType = pce.getPropertyName(); + if (eventType.equals(DATA_ADDED.toString())) { + ModuleDataEvent eventData = (ModuleDataEvent) pce.getOldValue(); + if (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID()) { + invalidateCache(); + } + } + }; + + IngestManager.getInstance().addIngestModuleEventListener(EnumSet.of(DATA_ADDED), ingestListener); + } + + /** + * Returns the singleton instance of the cache object. + * + * @return AccountCache instance. + */ + private static synchronized ContactCache getInstance() { + if (instance == null) { + instance = new ContactCache(); + } + + return instance; + } + + /** + * Returns a list of TSK_CONTACT artifacts that reference the given account. + * + * @param allContactList List of existing TSK_CONTACT artifacts. + * @param account Account reference. + * + * @return A list of TSK_CONTACT artifact that reference the given account + * or empty list of none were found. + * + * @throws TskCoreException + */ + private List findContactForAccount(List allContactList, Account account) throws TskCoreException { + List accountContacts = new ArrayList<>(); + + for (BlackboardArtifact contact : allContactList) { + if (isAccountInAttributeList(contact.getAttributes(), account)) { + accountContacts.add(contact); + } + } + + return accountContacts; + } + + /** + * Determine if there is an attribute in the given list that references the + * given account. + * + * @param contactAttributes List of attributes. + * @param account Account object. + * + * @return True if one of the attributes in the list reference the account. + */ + private boolean isAccountInAttributeList(List contactAttributes, Account account) { + for (BlackboardAttribute attribute : contactAttributes) { + if (isAccountInAttribute(attribute, account)) { + return true; + } + } + return false; + } + + /** + * Checks the given attribute to see if it references the given account. + * + * @param attribute BlackboardAttribute to check. + * @param account References account. + * + * @return True if the attribute references the account. + */ + private boolean isAccountInAttribute(BlackboardAttribute attribute, Account account) { + + String typeName = attribute.getAttributeType().getTypeName(); + return (typeName.startsWith("TSK_EMAIL") + || typeName.startsWith("TSK_PHONE") + || typeName.startsWith("TSK_NAME") + || typeName.startsWith("TSK_ID")) + && attribute.getValueString().equals(account.getTypeSpecificID()); + } + +}