64565: Reorganize call log artifact viewer

Interim commit.
This commit is contained in:
Raman Arora 2020-06-15 12:42:04 -04:00
parent 5550a37293
commit 77a0563ac0
7 changed files with 1231 additions and 1 deletions

View File

@ -0,0 +1,76 @@
/*
* 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.contentviewers;
import javax.swing.JButton;
import javax.swing.JLabel;
/**
* A data bag for the persona searching thread. It wraps the account id to
* search for, and the UI label and button to update once the search completes.
*/
class AccountPersonaSearcherData {
// Account identifier to search personas for.
private final String accountIdentifer;
// Persona name label to be updated when the search is complete.
private final JLabel personaNameLabel;
// Persona action button to be updated when the search is complete
private final JButton personaActionButton;
/**
* Constructor.
*
* @param accountIdentifer Account identifier.
* @param personaNameLabel Persona name label.
* @param personaActionButton Persona button.
*/
AccountPersonaSearcherData(String accountIdentifer, JLabel personaNameLabel, JButton personaActionButton) {
this.accountIdentifer = accountIdentifer;
this.personaNameLabel = personaNameLabel;
this.personaActionButton = personaActionButton;
}
/**
* Gets the account identifier.
*
* @return Account identifier.
*/
public String getAccountIdentifer() {
return accountIdentifer;
}
/**
* Gets the persona name label.
*
* @return Persona name label.
*/
public JLabel getPersonaNameLabel() {
return personaNameLabel;
}
/**
* Gets the persona button.
*
* @return Persona button.
*/
public JButton getPersonaActionButton() {
return personaActionButton;
}
}

View File

@ -68,7 +68,7 @@ import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsMode;
* *
* *
*/ */
@ServiceProvider(service = ArtifactContentViewer.class) //@ServiceProvider(service = ArtifactContentViewer.class)
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
public class CallLogArtifactViewer extends javax.swing.JPanel implements ArtifactContentViewer { public class CallLogArtifactViewer extends javax.swing.JPanel implements ArtifactContentViewer {

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.4" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
</Form>

View File

@ -0,0 +1,435 @@
/*
* 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.contentviewers;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
import javax.swing.JScrollPane;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Artifact viewer for Call log artifacts.
*
* Displays the To/From and other parties, and metadata for a call.
*/
@ServiceProvider(service = ArtifactContentViewer.class)
public class CallLogArtifactViewerNew extends javax.swing.JPanel implements ArtifactContentViewer {
private final static Logger logger = Logger.getLogger(CallLogArtifactViewerNew.class.getName());
private static final long serialVersionUID = 1L;
private static final Set<Integer> HANDLED_ATTRIBUTE_TYPES = new HashSet<Integer>(Arrays.asList(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END.getTypeID()
));
private GridBagLayout m_gridBagLayout = new GridBagLayout();
private GridBagConstraints m_constraints = new GridBagConstraints();
private final List<PersonaSearchAndDisplayTask> personaSearchtasks = new ArrayList<>();
/**
* Creates new form CallLogArtifactViewerNew
*/
public CallLogArtifactViewerNew() {
initComponents();
}
/**
* 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")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
setLayout(new java.awt.GridBagLayout());
}// </editor-fold>//GEN-END:initComponents
@Override
public void setArtifact(BlackboardArtifact artifact) {
resetComponent();
CallLogViewDataNew callLogViewData = null;
try {
callLogViewData = getCallLogViewData(artifact);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Error getting attributes for Calllog artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex);
}
// update the view with the call log data
if (callLogViewData != null) {
updateView(callLogViewData);
}
// repaint
this.revalidate();
}
/**
* Extracts data from the call log artifact for display in the view.
*
* @param artifact Artifact to extract data from.
*
* @return CallLogViewData Extracted data to be displayed.
*
* @throws TskCoreException
*/
private CallLogViewDataNew getCallLogViewData(BlackboardArtifact artifact) throws TskCoreException {
if (artifact == null) {
return null;
}
BlackboardAttribute directionAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION));
BlackboardAttribute toAccountAttr = null;
BlackboardAttribute fromAccountAttr = null;
BlackboardAttribute localAccountAttr = null;
CallLogViewDataNew callLogViewData = null;
String direction = null;
String fromAccountIdentifier = null;
String toAccountIdentifier = null;
List<String> otherParties = null;
Content dataSource = artifact.getDataSource();
String deviceId = ((DataSource) dataSource).getDeviceId();
if (directionAttr != null) {
direction = directionAttr.getValueString();
if (direction.equalsIgnoreCase("Incoming")) {
fromAccountAttr = ObjectUtils.firstNonNull(
artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM)),
artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)),
artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID))
);
toAccountAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO));
localAccountAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO));
} else if (direction.equalsIgnoreCase("Outgoing")) {
toAccountAttr = ObjectUtils.firstNonNull(
artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO)),
artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)),
artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID))
);
fromAccountAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM));
localAccountAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM));
}
}
// if direction isn't known, check all the usual attributes that may have the number/address
// in the absence of sufficent data, any number availabel will be displayed as a From address.
if (fromAccountAttr == null) {
fromAccountAttr = ObjectUtils.firstNonNull(
artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM)),
artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO)),
artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)),
artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID))
);
}
if (fromAccountAttr != null) {
String fromAccountAttrValue = fromAccountAttr.getValueString();
if (fromAccountAttrValue.equalsIgnoreCase(deviceId) == false) {
fromAccountIdentifier = fromAccountAttrValue;
}
}
if (toAccountAttr != null) {
// TO may be a list of comma separated values.
String[] numbers = toAccountAttr.getValueString().split(",");
String toAccountAttrValue = StringUtils.trim(numbers[0]);
if (toAccountAttrValue.equalsIgnoreCase(deviceId) == false) {
toAccountIdentifier = toAccountAttrValue;
}
// if more than one To address, then stcik the rest of them in the
// "Other parties" list.
if (numbers.length > 1) {
otherParties = new ArrayList<>();
for (int i = 1; i < numbers.length; i++) {
otherParties.add(StringUtils.trim(numbers[i]));
}
}
}
// if we have at least one address attribute
if (null != fromAccountAttr || null != toAccountAttr) {
callLogViewData = new CallLogViewDataNew(fromAccountIdentifier, toAccountIdentifier);
callLogViewData.setDirection(direction);
callLogViewData.setOtherParties(otherParties);
extractTimeAndDuration(artifact, callLogViewData);
callLogViewData.setDataSourceName(dataSource.getName());
// set local account, if it can be deduced.
if (localAccountAttr != null) {
String attrValue = localAccountAttr.getValueString();
// value must be a singular address and not a deviceId to be the local account id
if (attrValue.equalsIgnoreCase(deviceId) == false && attrValue.contains(",") == false) {
callLogViewData.setLocalAccountId(attrValue);
}
}
callLogViewData.setOtherAttributes(extractOtherAttributes(artifact));
}
return callLogViewData;
}
/**
* Extract the call time and duration from the artifact and saves in the
* CallLogViewData.
*
* @param artifact Call log artifact.
* @param callLogViewData CallLogViewData object to save the time & duration
* in.
*
* @throws TskCoreException
*/
private void extractTimeAndDuration(BlackboardArtifact artifact, CallLogViewDataNew callLogViewData) throws TskCoreException {
BlackboardAttribute startTimeAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START));
if (startTimeAttr == null) {
startTimeAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME));
}
if (startTimeAttr != null) {
long startTime = startTimeAttr.getValueLong();
callLogViewData.setDateTimeStr(startTimeAttr.getDisplayString());
BlackboardAttribute endTimeAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END));
if (endTimeAttr != null) {
long endTime = endTimeAttr.getValueLong();
if (endTime > 0 && (endTime - startTime) > 0) {
callLogViewData.setDuration(String.format("%d seconds", (endTime - startTime)));
}
}
}
}
/**
* Returns the attributes from the given artifact that are not already
* displayed by the artifact viewer.
*
* @param artifact Call log artifact.
*
* @return Attribute names/values.
*
* @throws TskCoreException
*/
private Map<String, String> extractOtherAttributes(BlackboardArtifact artifact) throws TskCoreException {
List<BlackboardAttribute> attributes = artifact.getAttributes();
Map<String, String> otherAttributes = new HashMap<>();
for (BlackboardAttribute attr : attributes) {
if (HANDLED_ATTRIBUTE_TYPES.contains(attr.getAttributeType().getTypeID()) == false) {
otherAttributes.put(attr.getAttributeType().getDisplayName(), attr.getDisplayString());
}
}
return otherAttributes;
}
/**
* Update the viewer with the call log data.
*
* @param callLogViewData Call log data to update the view with.
*/
private void updateView(CallLogViewDataNew callLogViewData) {
CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, "Parties");
// Display From address
CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, "From");
if (callLogViewData.getFromAccount() != null) {
// check if this is local account
String accountDisplayString = getAccountDisplayString(callLogViewData.getFromAccount(), callLogViewData);
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, accountDisplayString);
// show persona
Optional<PersonaSearchAndDisplayTask> task = CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, callLogViewData.getFromAccount());
if (task.isPresent()) {
personaSearchtasks.add(task.get());
}
} else {
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, "Unknown");
}
// Display To:
CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, "To");
if (callLogViewData.getToAccount() != null) {
String accountDisplayString = getAccountDisplayString(callLogViewData.getToAccount(), callLogViewData);
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, accountDisplayString);
Optional<PersonaSearchAndDisplayTask> task = CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, callLogViewData.getToAccount());
if (task.isPresent()) {
personaSearchtasks.add(task.get());
}
} else {
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, "Unknown");
}
// Display other parties
for (String otherParty : callLogViewData.getOtherParties()) {
CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, "To");
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, otherParty);
Optional<PersonaSearchAndDisplayTask> task = CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, otherParty);
if (task.isPresent()) {
personaSearchtasks.add(task.get());
}
}
updateMetadataView(callLogViewData);
updateSourceView(callLogViewData);
CommunicationArtifactViewerHelper.addPageEndGlue(this, m_gridBagLayout, this.m_constraints);
this.setLayout(m_gridBagLayout);
this.revalidate();
this.repaint();
}
/**
* Update the call log meta data section.
*
* @param callLogViewData Call log data.
*/
private void updateMetadataView(CallLogViewDataNew callLogViewData) {
CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, "Metadata");
CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, "Direction");
if (callLogViewData.getDirection() != null) {
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, callLogViewData.getDirection());
} else {
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, "Unknown");
}
if (callLogViewData.getDateTimeStr() != null) {
CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, "Date");
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, callLogViewData.getDateTimeStr());
}
if (callLogViewData.getDuration() != null) {
CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, "Duration");
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, callLogViewData.getDuration());
}
}
/**
* Update the call log source section.
*
* @param callLogViewData
*/
private void updateSourceView(CallLogViewDataNew callLogViewData) {
CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, "Source");
CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, "Data Source");
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, callLogViewData.getDataSourceName());
}
/**
* Returns display string for a account.
* Checks if the given account is the local account, if it is known.
* If it is, it appends a "(Local)" suffix to account display string.
*
* @param accountIdentifier Account identifier to check.
* @param callLogViewDataNew Call log data which may have the lock account.
*
* @return Account string to display.
*/
private String getAccountDisplayString(String accountIdentifier, CallLogViewDataNew callLogViewDataNew) {
String accountDisplayValue = accountIdentifier;
if (callLogViewDataNew.getLocalAccountId() != null && callLogViewDataNew.getLocalAccountId().equalsIgnoreCase(accountIdentifier)) {
accountDisplayValue += " (Local)";
}
return accountDisplayValue;
}
@Override
public Component getComponent() {
return new JScrollPane(this, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
}
@Override
public boolean isSupported(BlackboardArtifact artifact) {
return artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID();
}
/**
* Resets all artifact specific state.
*/
private void resetComponent() {
// cancel any outstanding persona searching threads.
personaSearchtasks.forEach(task -> task.cancel(Boolean.TRUE));
personaSearchtasks.clear();
// clear the panel
this.removeAll();
this.setLayout(null);
m_gridBagLayout = new GridBagLayout();
m_constraints = new GridBagConstraints();
m_constraints.anchor = GridBagConstraints.FIRST_LINE_START;
m_constraints.gridy = 0;
m_constraints.gridx = 0;
m_constraints.weighty = 0.05;
m_constraints.weightx = 0.05;
m_constraints.insets = new java.awt.Insets(0, 0, 0, 0);
m_constraints.fill = GridBagConstraints.NONE;
}
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
}

View File

@ -0,0 +1,147 @@
/*
* 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.contentviewers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Encapsulates the information to be displayed about a call log artifact.
*/
final class CallLogViewDataNew {
private String fromAccount = null;
private String toAccount = null;
// account identifier of the device owner, if known.
// will be one of the to or from account.
private String localAccountId = null;
private String direction;
private String dateTimeStr = null;
private String duration = null;
// Account identifers of other parties in the call.
private Collection<String> otherParties = new ArrayList<>();
private Map<String, String> otherAttributes = new HashMap<>();
private String dataSourceName = null;
/**
* Constructor.
* @param fromAccount From account identifier, may be null;
* @param toAccount To account identifier, may be null;
*/
CallLogViewDataNew(String fromAccount, String toAccount) {
this(fromAccount, toAccount, null);
}
/**
* Constructor.
* @param fromAccount From account identifier, may be null;
* @param toAccount To account identifier, may be null;
* @param direction Direction, may be null.
*/
CallLogViewDataNew(String fromAccount, String toAccount, String direction) {
this.fromAccount = fromAccount;
this.toAccount = toAccount;
this.direction = direction;
}
String getFromAccount() {
return fromAccount;
}
void setFromAccount(String fromAccount) {
this.fromAccount = fromAccount;
}
String getToAccount() {
return toAccount;
}
void setToAccount(String toAccount) {
this.toAccount = toAccount;
}
String getDirection() {
return direction;
}
void setDirection(String direction) {
this.direction = direction;
}
String getDataSourceName() {
return dataSourceName;
}
void setDataSourceName(String dataSourceName) {
this.dataSourceName = dataSourceName;
}
String getDateTimeStr() {
return dateTimeStr;
}
void setDateTimeStr(String dateTimeStr) {
this.dateTimeStr = dateTimeStr;
}
String getDuration() {
return duration;
}
void setDuration(String duration) {
this.duration = duration;
}
Collection<String> getOtherParties() {
return Collections.unmodifiableCollection(otherParties);
}
void setOtherParties(Collection<String> otherParticipants) {
if (otherParticipants != null) {
this.otherParties = new ArrayList<>(otherParticipants);
}
}
public Map<String, String> getOtherAttributes() {
return Collections.unmodifiableMap(otherAttributes);
}
public void setOtherAttributes(Map<String, String> otherAttributes) {
if (otherAttributes != null) {
this.otherAttributes = new HashMap<>(otherAttributes);
}
}
public String getLocalAccountId() {
return localAccountId;
}
public void setLocalAccountId(String localAccountId) {
this.localAccountId = localAccountId;
}
}

View File

@ -0,0 +1,334 @@
/*
* 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.contentviewers;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import java.util.Optional;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
/**
*
* A class to help display a communication artifact in a panel using a
* gridbaglayout.
*/
public class CommunicationArtifactViewerHelper {
// Number of columns in the gridbag layout.
private final static int MAX_COLS = 4;
private final static int LEFT_INDENT = 12;
/**
* Adds a new heading to the panel.
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
* @param constraints Constrains to use.
* @param headerString Heading string to display.
*/
static void addHeader(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String headerString) {
// add a blank line before the start of new section, unless it's
// the first section
if (constraints.gridy != 0) {
addBlankLine(panel, gridbagLayout, constraints);
}
constraints.gridy++;
constraints.gridx = 0;
// let the header span all of the row
constraints.gridwidth = MAX_COLS;
// create label for heading
javax.swing.JLabel headingLabel = new javax.swing.JLabel();
headingLabel.setText(headerString);
// make it large and bold
headingLabel.setFont(headingLabel.getFont().deriveFont(Font.BOLD, headingLabel.getFont().getSize() + 2));
// add to panel
gridbagLayout.setConstraints(headingLabel, constraints);
panel.add(headingLabel);
// reset constraints to normal
constraints.gridwidth = 1;
// add line end glue
addLineEndGlue(panel, gridbagLayout, constraints);
}
/**
* Adds a filler/glue at the end of the line to keep the other columns
* aligned, in case the panel is resized.
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
* @param constraints Constrains to use.
*/
private static void addLineEndGlue(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) {
// Place the filler just past the last column.
constraints.gridx = MAX_COLS;
double savedWeightX = constraints.weightx;
int savedFill = constraints.fill;
constraints.weightx = 1.0; // take up all the horizontal space
constraints.fill = GridBagConstraints.BOTH;
javax.swing.Box.Filler horizontalFiller = new javax.swing.Box.Filler(new Dimension(0, 0), new Dimension(0, 0), new Dimension(32767, 0));
gridbagLayout.setConstraints(horizontalFiller, constraints);
panel.add(horizontalFiller);
// restore fill & weight
constraints.fill = savedFill;
constraints.weightx = savedWeightX;
}
/**
* Adds a filler/glue at the bottom of the panel to keep the data rows
* aligned, in case the panel is resized.
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
* @param constraints Constrains to use.
*/
static void addPageEndGlue(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) {
constraints.gridx = 0;
double savedWeighty = constraints.weighty;
int savedFill = constraints.fill;
constraints.weighty = 1.0; // take up all the vertical space
constraints.fill = GridBagConstraints.VERTICAL;
javax.swing.Box.Filler vertFiller = new javax.swing.Box.Filler(new Dimension(0, 0), new Dimension(0, 0), new Dimension(0, 32767));
gridbagLayout.setConstraints(vertFiller, constraints);
panel.add(vertFiller, constraints);
//Resore weight & fill
constraints.weighty = savedWeighty;
constraints.fill = savedFill;
}
/**
* Adds a blank line to the panel.
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
* @param constraints Constrains to use.
*/
private static void addBlankLine(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) {
constraints.gridy++;
constraints.gridx = 0;
javax.swing.JLabel filler = new javax.swing.JLabel(" ");
gridbagLayout.setConstraints(filler, constraints);
panel.add(filler);
addLineEndGlue(panel, gridbagLayout, constraints);
}
/**
* Adds a label/key to the panel.
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
* @param constraints Constrains to use.
* @param keyString Key name to display.
*/
static void addKey(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String keyString) {
constraints.gridy++;
constraints.gridx = 0;
Insets savedInsets = constraints.insets;
// Set inset to indent in
constraints.insets = new java.awt.Insets(0, LEFT_INDENT, 0, 0);
// create label,
javax.swing.JLabel keyLabel = new javax.swing.JLabel();
keyLabel.setText(keyString + ": ");
// add to panel
gridbagLayout.setConstraints(keyLabel, constraints);
panel.add(keyLabel);
// restore inset
constraints.insets = savedInsets;
}
/**
* Adds a value string to the panel.
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
* @param constraints Constrains to use.
* @param keyString Value string to display.
*/
static void addValue(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String valueString) {
constraints.gridx = 1;
int savedGridwidth = constraints.gridwidth;
// let the value span 2 cols
constraints.gridwidth = 2;
// create label,
javax.swing.JLabel valueField = new javax.swing.JLabel();
valueField.setText(valueString);
// attach a right click menu with Copy option
valueField.addMouseListener(new java.awt.event.MouseAdapter() {
@Override
public void mouseClicked(java.awt.event.MouseEvent evt) {
valueLabelMouseClicked(evt, valueField);
}
});
// add label to panel
gridbagLayout.setConstraints(valueField, constraints);
panel.add(valueField);
// restore constraints
constraints.gridwidth = savedGridwidth;
// end the line
addLineEndGlue(panel, gridbagLayout, constraints);
}
/**
* Adds a Persona row to the panel.
*
* Adds a persona name label and a button to the panel. Kicks off a
* background task to search for persona for the given account. Updates the
* persona name and button when the task is done.
*
* If CentralRepostory is disabled, just displays 'Unknown' persona name.
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
* @param constraints Constrains to use.
* @param accountIdentifier Account identifier to search the persona.
*
* @return Optional PersonaSearchAndDisplayTask started to search for
* persona.
*/
@NbBundle.Messages({
"CommunicationArtifactViewerHelper_persona_label=Persona: ",
"CommunicationArtifactViewerHelper_persona_searching=Searching...",
"CommunicationArtifactViewerHelper_persona_unknown=Unknown",
"CommunicationArtifactViewerHelper_persona_button_view=View",
"CommunicationArtifactViewerHelper_persona_button_create=Create"
})
static Optional<PersonaSearchAndDisplayTask> addPersonaRow(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String accountIdentifier) {
PersonaSearchAndDisplayTask personaTask = null;
constraints.gridy++;
constraints.gridx = 1;
Insets savedInsets = constraints.insets;
// Indent in
constraints.insets = new java.awt.Insets(0, LEFT_INDENT, 0, 0);
// create label
javax.swing.JLabel personaLabel = new javax.swing.JLabel();
String personaLabelText = Bundle.CommunicationArtifactViewerHelper_persona_label();
personaLabelText = personaLabelText.concat(CentralRepository.isEnabled()
? Bundle.CommunicationArtifactViewerHelper_persona_searching()
: Bundle.CommunicationArtifactViewerHelper_persona_unknown());
personaLabel.setText(personaLabelText);
// add to panel
gridbagLayout.setConstraints(personaLabel, constraints);
panel.add(personaLabel);
// restore constraint
constraints.insets = savedInsets;
constraints.gridx++;
// Place a button as place holder. It will be enabled when persona is available.
javax.swing.JButton personaButton = new javax.swing.JButton();
personaButton.setText(Bundle.CommunicationArtifactViewerHelper_persona_button_view());
personaButton.setEnabled(false);
gridbagLayout.setConstraints(personaButton, constraints);
panel.add(personaButton);
if (CentralRepository.isEnabled()) {
// kick off a task to find the persona for this account
personaTask = new PersonaSearchAndDisplayTask(panel, new AccountPersonaSearcherData(accountIdentifier, personaLabel, personaButton));
personaTask.execute();
} else {
personaLabel.setEnabled(false);
}
addLineEndGlue(panel, gridbagLayout, constraints);
return Optional.ofNullable(personaTask);
}
/**
* Event handler for mouse click event. Attaches a 'Copy' menu item to right
* click.
*
* @param evt Event to check.
* @param valueLabel Label to attach the menu item to.
*/
@NbBundle.Messages({
"CommunicationArtifactViewerHelper_menuitem_copy=Copy"
})
private static void valueLabelMouseClicked(java.awt.event.MouseEvent evt, JLabel valueLabel) {
if (SwingUtilities.isRightMouseButton(evt)) {
JPopupMenu popup = new JPopupMenu();
JMenuItem copyMenu = new JMenuItem(Bundle.CommunicationArtifactViewerHelper_menuitem_copy()); // NON-NLS
copyMenu.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(valueLabel.getText()), null);
}
});
popup.add(copyMenu);
popup.show(valueLabel, evt.getX(), evt.getY());
}
}
}

View File

@ -0,0 +1,220 @@
/**
* 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.contentviewers;
import java.awt.Component;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.swing.JButton;
import javax.swing.SwingWorker;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.centralrepository.datamodel.Persona;
import org.sleuthkit.autopsy.centralrepository.datamodel.PersonaAccount;
import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsDialog;
import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsDialogCallback;
import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsMode;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
* Background task to search for a persona for a given account.
*
* When the search is complete, it updates the UI components
* for the persona appropriately.
*
*/
@NbBundle.Messages({
"# {0} - Persona count",
"PersonaDisplayTask_persona_count_suffix=(1 of {0})"
})
class PersonaSearchAndDisplayTask extends SwingWorker<Collection<Persona>, Void> {
private final static Logger logger = Logger.getLogger(PersonaSearchAndDisplayTask.class.getName());
private final Component parentComponent;
private final AccountPersonaSearcherData personaSearcherData;
PersonaSearchAndDisplayTask(Component parentComponent, AccountPersonaSearcherData personaSearcherData) {
this.parentComponent = parentComponent;
this.personaSearcherData = personaSearcherData;
}
@Override
protected Collection<Persona> doInBackground() throws Exception {
Collection<Persona> personas = new ArrayList<>();
if (CentralRepository.isEnabled()) {
Collection<CentralRepoAccount> accountCandidates
= CentralRepoAccount.getAccountsWithIdentifier(personaSearcherData.getAccountIdentifer());
if (accountCandidates.isEmpty() == false) {
CentralRepoAccount account = accountCandidates.iterator().next();
// get personas for the account
Collection<PersonaAccount> personaAccountsList = PersonaAccount.getPersonaAccountsForAccount(account.getId());
personas = personaAccountsList.stream().map(PersonaAccount::getPersona)
.collect(Collectors.toList());
}
}
return personas;
}
@Override
protected void done() {
Collection<Persona> personas;
try {
personas = super.get();
if (this.isCancelled()) {
return;
}
//Update the Persona label and button based on the search result
String personaLabelText = Bundle.CommunicationArtifactViewerHelper_persona_label();
String personaButtonText;
ActionListener buttonActionListener;
if (personas.isEmpty()) {
// No persona found
personaLabelText += Bundle.CommunicationArtifactViewerHelper_persona_unknown();
// show a 'Create' button
personaButtonText = Bundle.CommunicationArtifactViewerHelper_persona_button_create();
buttonActionListener = new CreatePersonaButtonListener(parentComponent, personaSearcherData);
} else {
Persona persona = personas.iterator().next();
personaLabelText += persona.getName();
if (personas.size() > 1) {
personaLabelText += Bundle.PersonaDisplayTask_persona_count_suffix(Integer.toString(personas.size()));
}
// Show a 'View' button
personaButtonText = Bundle.CommunicationArtifactViewerHelper_persona_button_view();
buttonActionListener = new ViewPersonaButtonListener(parentComponent, persona);
}
personaSearcherData.getPersonaNameLabel().setText(personaLabelText);
personaSearcherData.getPersonaActionButton().setText(personaButtonText);
personaSearcherData.getPersonaActionButton().setEnabled(true);
// set button action
personaSearcherData.getPersonaActionButton().addActionListener(buttonActionListener);
} catch (CancellationException ex) {
logger.log(Level.INFO, "Persona searching was canceled."); //NON-NLS
} catch (InterruptedException ex) {
logger.log(Level.INFO, "Persona searching was interrupted."); //NON-NLS
} catch (ExecutionException ex) {
logger.log(Level.SEVERE, "Fatal error during Persona search.", ex); //NON-NLS
}
}
/**
* Action listener for Create persona button.
*/
private class CreatePersonaButtonListener implements ActionListener {
private final Component parentComponent;
private final AccountPersonaSearcherData personaSearcherData;
CreatePersonaButtonListener(Component parentComponent, AccountPersonaSearcherData personaSearcherData) {
this.parentComponent = parentComponent;
this.personaSearcherData = personaSearcherData;
}
@Override
public void actionPerformed(java.awt.event.ActionEvent evt) {
// Launch the Persona Create dialog
new PersonaDetailsDialog(parentComponent,
PersonaDetailsMode.CREATE, null, new PersonaCreateCallbackImpl(parentComponent, personaSearcherData));
}
}
/**
* Action listener for View persona button.
*/
private class ViewPersonaButtonListener implements ActionListener {
private final Component parentComponent;
private final Persona persona;
ViewPersonaButtonListener(Component parentComponent, Persona persona) {
this.parentComponent = parentComponent;
this.persona = persona;
}
@Override
public void actionPerformed(java.awt.event.ActionEvent evt) {
new PersonaDetailsDialog(parentComponent,
PersonaDetailsMode.VIEW, persona, new PersonaViewCallbackImpl());
}
}
/**
* Callback method for the create mode of the PersonaDetailsDialog
*/
class PersonaCreateCallbackImpl implements PersonaDetailsDialogCallback {
private final Component parentComponent;
private final AccountPersonaSearcherData personaSearcherData;
PersonaCreateCallbackImpl(Component parentComponent, AccountPersonaSearcherData personaSearcherData) {
this.parentComponent = parentComponent;
this.personaSearcherData = personaSearcherData;
}
@Override
public void callback(Persona persona) {
JButton personaButton = personaSearcherData.getPersonaActionButton();
if (persona != null) {
// update the persona name label with newly created persona,
// and change the button to a "View" button
personaSearcherData.getPersonaNameLabel().setText(Bundle.CommunicationArtifactViewerHelper_persona_label() + persona.getName());
personaSearcherData.getPersonaActionButton().setText(Bundle.CallLogArtifactViewer_persona_button_view());
// replace action listener with a View button listener
for (ActionListener act : personaButton.getActionListeners()) {
personaButton.removeActionListener(act);
}
personaButton.addActionListener(new ViewPersonaButtonListener(parentComponent, persona));
}
personaButton.getParent().revalidate();
}
}
/**
* Callback method for the view mode of the PersonaDetailsDialog
*/
class PersonaViewCallbackImpl implements PersonaDetailsDialogCallback {
@Override
public void callback(Persona persona) {
// nothing to do
}
}
}