mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 16:06:15 +00:00
64565: Reorganize call log artifact viewer
Interim commit.
This commit is contained in:
parent
5550a37293
commit
77a0563ac0
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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 {
|
||||||
|
|
||||||
|
@ -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>
|
@ -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
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user