Added contact information to cvt tooltips and MessageContentViewer

This commit is contained in:
Kelly Kelly 2020-08-03 14:02:28 -04:00
parent 972f736a9d
commit c151a58974
4 changed files with 227 additions and 15 deletions

View File

@ -61,6 +61,7 @@ DefaultArtifactContentViewer.copyMenuItem.text=Copy
DefaultArtifactContentViewer.selectAllMenuItem.text=Select All DefaultArtifactContentViewer.selectAllMenuItem.text=Select All
MessageAccountPanel_button_create_label=Create MessageAccountPanel_button_create_label=Create
MessageAccountPanel_button_view_label=View MessageAccountPanel_button_view_label=View
MessageAccountPanel_contact_label=Contact:
MessageAccountPanel_no_matches=No matches found. MessageAccountPanel_no_matches=No matches found.
MessageAccountPanel_persona_label=Persona: MessageAccountPanel_persona_label=Persona:
MessageAccountPanel_unknown_label=Unknown MessageAccountPanel_unknown_label=Unknown

View File

@ -16,7 +16,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.sleuthkit.autopsy.contentviewers.artifactviewers; package org.sleuthkit.autopsy.contentviewers.artifactviewers;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
@ -39,6 +38,7 @@ import javax.swing.SwingUtilities;
import javax.swing.SwingWorker; import javax.swing.SwingWorker;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.centralrepository.datamodel.Persona; import org.sleuthkit.autopsy.centralrepository.datamodel.Persona;
import org.sleuthkit.autopsy.centralrepository.datamodel.PersonaAccount; import org.sleuthkit.autopsy.centralrepository.datamodel.PersonaAccount;
import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsDialog; import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsDialog;
@ -46,8 +46,10 @@ import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsDialogCallb
import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsMode; import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsMode;
import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsPanel; import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsPanel;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.guiutils.ContactCache;
import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.CommunicationsManager; import org.sleuthkit.datamodel.CommunicationsManager;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
@ -113,13 +115,24 @@ final class MessageAccountPanel extends JPanel {
return new ArrayList<>(); return new ArrayList<>();
} }
List<BlackboardArtifact> contactList = ContactCache.getContacts(account);
BlackboardArtifact contact = null;
if (contactList != null && !contactList.isEmpty()) {
contact = contactList.get(0);
}
if (CentralRepository.isEnabled()) {
Collection<PersonaAccount> personAccounts = PersonaAccount.getPersonaAccountsForAccount(account); Collection<PersonaAccount> personAccounts = PersonaAccount.getPersonaAccountsForAccount(account);
if (personAccounts != null && !personAccounts.isEmpty()) { if (personAccounts != null && !personAccounts.isEmpty()) {
for (PersonaAccount personaAccount : PersonaAccount.getPersonaAccountsForAccount(account)) { for (PersonaAccount personaAccount : PersonaAccount.getPersonaAccountsForAccount(account)) {
dataList.add(new AccountContainer(account, personaAccount)); dataList.add(new AccountContainer(account, personaAccount, contact));
} }
} else { } else {
dataList.add(new AccountContainer(account, null)); dataList.add(new AccountContainer(account, null, contact));
}
} else {
dataList.add(new AccountContainer(account, null, contact));
} }
} }
@ -127,8 +140,7 @@ final class MessageAccountPanel extends JPanel {
} }
@Messages({ @Messages({
"MessageAccountPanel_no_matches=No matches found.", "MessageAccountPanel_no_matches=No matches found.",})
})
@Override @Override
protected void done() { protected void done() {
try { try {
@ -199,6 +211,7 @@ final class MessageAccountPanel extends JPanel {
for (AccountContainer o : data) { for (AccountContainer o : data) {
group.addGap(5) group.addGap(5)
.addComponent(o.getAccountLabel()) .addComponent(o.getAccountLabel())
.addGroup(o.getContactLineVerticalGroup(layout))
.addGroup(o.getPersonLineVerticalGroup(layout)); .addGroup(o.getPersonLineVerticalGroup(layout));
} }
@ -234,6 +247,7 @@ final class MessageAccountPanel extends JPanel {
group.addGap(10); group.addGap(10);
for (AccountContainer o : data) { for (AccountContainer o : data) {
pgroup.addGroup(o.getPersonaSequentialGroup(layout)); pgroup.addGroup(o.getPersonaSequentialGroup(layout));
pgroup.addGroup(o.getContactSequentialGroup(layout));
} }
group.addGap(10) group.addGap(10)
.addGroup(pgroup) .addGroup(pgroup)
@ -253,10 +267,13 @@ final class MessageAccountPanel extends JPanel {
private final Account account; private final Account account;
private Persona persona = null; private Persona persona = null;
private final String contactName;
private JLabel accountLabel; private JLabel accountLabel;
private JLabel personaHeader; private JLabel personaHeader;
private JLabel personaDisplayName; private JLabel personaDisplayName;
private JLabel contactHeader;
private JLabel contactDisplayName;
private JButton button; private JButton button;
/** /**
@ -265,16 +282,22 @@ final class MessageAccountPanel extends JPanel {
* @param account * @param account
* @param personaAccount * @param personaAccount
*/ */
AccountContainer(Account account, PersonaAccount personaAccount) { AccountContainer(Account account, PersonaAccount personaAccount, BlackboardArtifact contactArtifact) throws TskCoreException {
if (contactArtifact != null && contactArtifact.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID()) {
throw new IllegalArgumentException("Failed to create AccountContainer object, passed in artifact was not a TSK_CONTACT");
}
this.account = account; this.account = account;
this.persona = personaAccount != null ? personaAccount.getPersona() : null; this.persona = personaAccount != null ? personaAccount.getPersona() : null;
this.contactName = getNameFromContactArtifact(contactArtifact);
} }
@Messages({ @Messages({
"MessageAccountPanel_persona_label=Persona:", "MessageAccountPanel_persona_label=Persona:",
"MessageAccountPanel_unknown_label=Unknown", "MessageAccountPanel_unknown_label=Unknown",
"MessageAccountPanel_button_view_label=View", "MessageAccountPanel_button_view_label=View",
"MessageAccountPanel_button_create_label=Create" "MessageAccountPanel_button_create_label=Create",
"MessageAccountPanel_contact_label=Contact:"
}) })
/** /**
* Swing components will not be initialized until this method is called. * Swing components will not be initialized until this method is called.
@ -282,16 +305,29 @@ final class MessageAccountPanel extends JPanel {
private void initalizeSwingControls() { private void initalizeSwingControls() {
accountLabel = new JLabel(); accountLabel = new JLabel();
personaHeader = new JLabel(Bundle.MessageAccountPanel_persona_label()); personaHeader = new JLabel(Bundle.MessageAccountPanel_persona_label());
contactHeader = new JLabel(Bundle.MessageAccountPanel_contact_label());
personaDisplayName = new JLabel(); personaDisplayName = new JLabel();
contactDisplayName = new JLabel();
button = new JButton(); button = new JButton();
button.addActionListener(new PersonaButtonListener(this)); button.addActionListener(new PersonaButtonListener(this));
accountLabel.setText(account.getTypeSpecificID()); accountLabel.setText(account.getTypeSpecificID());
contactDisplayName.setText(contactName);
personaDisplayName.setText(persona != null ? persona.getName() : Bundle.MessageAccountPanel_unknown_label()); personaDisplayName.setText(persona != null ? persona.getName() : Bundle.MessageAccountPanel_unknown_label());
button.setText(persona != null ? Bundle.MessageAccountPanel_button_view_label() : Bundle.MessageAccountPanel_button_create_label()); button.setText(persona != null ? Bundle.MessageAccountPanel_button_view_label() : Bundle.MessageAccountPanel_button_create_label());
} }
private String getNameFromContactArtifact(BlackboardArtifact contactArtifact) throws TskCoreException {
if (contactArtifact != null) {
BlackboardAttribute attribute = contactArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME));
if (attribute != null) {
return attribute.getValueString();
}
}
return Bundle.MessageAccountPanel_unknown_label();
}
/** /**
* Sets a new persona for this object and update the controls. * Sets a new persona for this object and update the controls.
* *
@ -366,6 +402,17 @@ final class MessageAccountPanel extends JPanel {
return group; return group;
} }
private SequentialGroup getContactSequentialGroup(GroupLayout layout) {
SequentialGroup group = layout.createSequentialGroup();
group
.addComponent(contactHeader)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(contactDisplayName);
return group;
}
/** /**
* Generates the vertical layout code for the persona line. * Generates the vertical layout code for the persona line.
* *
@ -379,6 +426,12 @@ final class MessageAccountPanel extends JPanel {
.addComponent(personaDisplayName) .addComponent(personaDisplayName)
.addComponent(button); .addComponent(button);
} }
private ParallelGroup getContactLineVerticalGroup(GroupLayout layout) {
return layout.createParallelGroup(Alignment.BASELINE)
.addComponent(contactHeader)
.addComponent(contactDisplayName);
}
} }
/** /**

View File

@ -447,11 +447,9 @@ public class MessageArtifactViewer extends javax.swing.JPanel implements Artifac
resetComponent(); resetComponent();
} }
msgbodyTabbedPane.setEnabledAt(ACCT_TAB_INDEX, CentralRepository.isEnabled()); msgbodyTabbedPane.setEnabledAt(ACCT_TAB_INDEX, true);
if(CentralRepository.isEnabled()) {
accountsPanel.setArtifact(artifact); accountsPanel.setArtifact(artifact);
} }
}
/** /**
* Get the artifact associated with the given artifact, if there is one. * Get the artifact associated with the given artifact, if there is one.

View File

@ -0,0 +1,160 @@
/*
* Autopsy Forensic Browser
*
* 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.guiutils;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.beans.PropertyChangeListener;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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. The map of account
* unique ids to list of contact artifacts is stored in a LoadingCache which
* expires after 10 of non-use.
*
*/
public final class ContactCache {
private static final Logger logger = Logger.getLogger(ContactCache.class.getName());
private static ContactCache instance;
private final LoadingCache<String, Map<String, List<BlackboardArtifact>>> accountMap;
/**
* 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
*/
public static synchronized List<BlackboardArtifact> getContacts(Account account) throws ExecutionException {
return getInstance().accountMap.get("realMap").get(account.getTypeSpecificID());
}
/**
* Force the cache to invalidate all entries.
*/
static synchronized void invalidateCache() {
getInstance().accountMap.invalidateAll();
}
/**
* Construct a new instance.
*/
private ContactCache() {
accountMap = CacheBuilder.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES).build(
new CacheLoader<String, Map<String, List<BlackboardArtifact>>>() {
@Override
public Map<String, List<BlackboardArtifact>> load(String key) {
try {
return buildMap();
} catch (SQLException | TskCoreException ex) {
logger.log(Level.WARNING, "Failed to build account to contact map", ex);
}
return new HashMap<>(); // Return an empty map if there is an exception to avoid NPE and continual trying.
}
});
PropertyChangeListener 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;
}
/**
* Builds the map of account IDs to contacts that reference them.
*
* @return A map of account IDs to contact artifacts.
*
* @throws TskCoreException
* @throws SQLException
*/
private Map<String, List<BlackboardArtifact>> buildMap() throws TskCoreException, SQLException {
Map<String, List<BlackboardArtifact>> acctMap = new HashMap<>();
List<BlackboardArtifact> contactList = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT);
for(BlackboardArtifact contactArtifact: contactList) {
List<BlackboardAttribute> contactAttributes = contactArtifact.getAttributes();
for(BlackboardAttribute attribute: contactAttributes) {
String typeName = attribute.getAttributeType().getTypeName();
if(typeName.startsWith("TSK_EMAIL")
|| typeName.startsWith("TSK_PHONE")
|| typeName.startsWith("TSK_NAME")
|| typeName.startsWith("TSK_ID")) {
String accountID = attribute.getValueString();
List<BlackboardArtifact> artifactList = acctMap.get(accountID);
if(artifactList == null) {
artifactList = new ArrayList<>();
acctMap.put(accountID, artifactList);
}
if(!artifactList.contains(contactArtifact)) {
artifactList.add(contactArtifact);
}
}
}
}
return acctMap;
}
}