diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java index a6af74ad3c..9b4f610721 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java @@ -53,7 +53,7 @@ public interface CentralRepository { case SQLITE: return SqliteCentralRepo.getInstance(); default: - return null; + throw new CentralRepoException("Failed to get CentralRepository instance, Central Repositiory is not enabled."); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Persona.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Persona.java index 502e4ddfbc..c4830fe70a 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Persona.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Persona.java @@ -329,7 +329,7 @@ public class Persona { String deleteSQL = "UPDATE personas SET status_id = " + PersonaStatus.DELETED.status_id + " WHERE id = " + this.id; CentralRepository cr = CentralRepository.getInstance(); if (cr != null) { - cr.executeDeleteSQL(deleteSQL); + cr.executeUpdateSQL(deleteSQL); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java index efd8b76152..e0dcf11361 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java @@ -121,22 +121,18 @@ public class PersonaAccount { /** * Creates an account for the specified Persona. * - * @param persona Persona for which the account is being added. - * @param account Account. + * @param persona Persona for which the account is being added. + * @param account Account. * @param justification Reason for assigning the alias, may be null. - * @param confidence Confidence level. + * @param confidence Confidence level. * * @return PersonaAccount + * * @throws CentralRepoException If there is an error in creating the - * account. + * account. */ static PersonaAccount addPersonaAccount(Persona persona, CentralRepoAccount account, String justification, Persona.Confidence confidence) throws CentralRepoException { CentralRepository cr = CentralRepository.getInstance(); - - if(cr == null) { - throw new CentralRepoException("Failed to add Persona, Central Repository is not enable"); - } - CentralRepoExaminer currentExaminer = cr.getOrInsertExaminer(System.getProperty("user.name")); Instant instant = Instant.now(); @@ -151,7 +147,7 @@ public class PersonaAccount { + currentExaminer.getId() + ")"; - CentralRepository.getInstance().executeInsertSQL(insertClause); + cr.executeInsertSQL(insertClause); String queryClause = PERSONA_ACCOUNTS_QUERY_CLAUSE + "WHERE persona_id = " + persona.getId() @@ -249,19 +245,13 @@ public class PersonaAccount { * persona_account. */ static Collection getPersonaAccountsForPersona(long personaId) throws CentralRepoException { - CentralRepository cr = CentralRepository.getInstance(); + String queryClause = PERSONA_ACCOUNTS_QUERY_CLAUSE + + " WHERE persona_accounts.persona_id = " + personaId; - if (cr != null) { - String queryClause = PERSONA_ACCOUNTS_QUERY_CLAUSE - + " WHERE persona_accounts.persona_id = " + personaId; + PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback(); + CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback); - PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback(); - cr.executeSelectSQL(queryClause, queryCallback); - - return queryCallback.getPersonaAccountsList(); - } - - return new ArrayList<>(); + return queryCallback.getPersonaAccountsList(); } /** @@ -279,16 +269,10 @@ public class PersonaAccount { + " WHERE persona_accounts.account_id = " + accountId + " AND personas.status_id != " + Persona.PersonaStatus.DELETED.getStatusId(); - CentralRepository cr = CentralRepository.getInstance(); + PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback(); + CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback); - if (cr != null) { - PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback(); - cr.executeSelectSQL(queryClause, queryCallback); - - return queryCallback.getPersonaAccountsList(); - } - - return new ArrayList<>(); + return queryCallback.getPersonaAccountsList(); } /** @@ -308,15 +292,10 @@ public class PersonaAccount { + " WHERE LOWER(accounts.account_unique_identifier) LIKE LOWER('%" + accountIdentifierSubstring + "%')" + " AND personas.status_id != " + Persona.PersonaStatus.DELETED.getStatusId(); - CentralRepository cr = CentralRepository.getInstance(); - if (cr != null) { - PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback(); - cr.executeSelectSQL(queryClause, queryCallback); + PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback(); + CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback); - return queryCallback.getPersonaAccountsList(); - } - - return new ArrayList<>(); + return queryCallback.getPersonaAccountsList(); } /** @@ -335,14 +314,10 @@ public class PersonaAccount { + " AND type_name = '" + account.getAccountType().getTypeName() + "' " + " AND personas.status_id != " + Persona.PersonaStatus.DELETED.getStatusId(); - CentralRepository cr = CentralRepository.getInstance(); - if (cr != null) { - PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback(); - cr.executeSelectSQL(queryClause, queryCallback); - return queryCallback.getPersonaAccountsList(); - } + PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback(); + CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback); + return queryCallback.getPersonaAccountsList(); - return new ArrayList<>(); } /** @@ -351,36 +326,24 @@ public class PersonaAccount { * @param id row id for the account to be removed * * @throws CentralRepoException If there is an error in removing the - * account. + * account. */ static void removePersonaAccount(long id) throws CentralRepoException { - CentralRepository cr = CentralRepository.getInstance(); - - if(cr == null) { - throw new CentralRepoException("Failed to remove persona account, Central Repo is not enabled"); - } - String deleteClause = " DELETE FROM persona_accounts WHERE id = " + id; - cr.executeDeleteSQL(deleteClause); + CentralRepository.getInstance().executeDeleteSQL(deleteClause); } - + /** * Modifies the PersonaAccount row by the given id * * @param id row id for the account to be removed * * @throws CentralRepoException If there is an error in removing the - * account. + * account. */ static void modifyPersonaAccount(long id, Persona.Confidence confidence, String justification) throws CentralRepoException { - CentralRepository cr = CentralRepository.getInstance(); - - if (cr == null) { - throw new CentralRepoException("Failed to modify persona account, Central Repo is not enabled"); - } - String updateClause = "UPDATE persona_accounts SET confidence_id = " + confidence.getLevelId() + ", justification = \"" + justification + "\" WHERE id = " + id; - cr.executeUpdateSQL(updateClause); + CentralRepository.getInstance().executeUpdateSQL(updateClause); } /** @@ -418,28 +381,25 @@ public class PersonaAccount { * @param personaId Id of the persona to look for. * * @return Collection of all accounts associated with the given persona, may - * be empty. + * be empty. + * * @throws CentralRepoException If there is an error in getting the - * accounts. + * accounts. */ static Collection getAccountsForPersona(long personaId) throws CentralRepoException { - CentralRepository cr = CentralRepository.getInstance(); - if (cr != null) { - String queryClause = "SELECT account_id, " - + " accounts.account_type_id as account_type_id, accounts.account_unique_identifier as account_unique_identifier," - + " account_types.type_name as type_name " - + " FROM persona_accounts " - + " JOIN accounts as accounts on persona_accounts.account_id = accounts.id " - + " JOIN account_types as account_types on accounts.account_type_id = account_types.id " - + " WHERE persona_accounts.persona_id = " + personaId; + String queryClause = "SELECT account_id, " + + " accounts.account_type_id as account_type_id, accounts.account_unique_identifier as account_unique_identifier," + + " account_types.type_name as type_name " + + " FROM persona_accounts " + + " JOIN accounts as accounts on persona_accounts.account_id = accounts.id " + + " JOIN account_types as account_types on accounts.account_type_id = account_types.id " + + " WHERE persona_accounts.persona_id = " + personaId; - AccountsForPersonaQueryCallback queryCallback = new AccountsForPersonaQueryCallback(); - cr.executeSelectSQL(queryClause, queryCallback); + AccountsForPersonaQueryCallback queryCallback = new AccountsForPersonaQueryCallback(); + CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback); - return queryCallback.getAccountsList(); - } + return queryCallback.getAccountsList(); - return new ArrayList<>(); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties index 3535f3cd52..db3e61bef5 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties @@ -1,15 +1,6 @@ -CTL_OpenPersonaManager=Persona Manager -CTL_PersonaManagerTopComponentAction=Persona Manager +CTL_OpenPersonas=Personas +CTL_PersonasTopComponentAction=Personas CTL_PersonaDetailsTopComponent=Persona Details -PersonaManagerTopComponent.createBtn.text=New Persona -PersonaManagerTopComponent.searchBtn.text=Search -PersonaManagerTopComponent.resultsTable.columnModel.title1=Name -PersonaManagerTopComponent.resultsTable.columnModel.title0=ID -PersonaManagerTopComponent.resultsTable.toolTipText= -PersonaManagerTopComponent.searchAccountRadio.text=Account -PersonaManagerTopComponent.searchNameRadio.text=Name -PersonaManagerTopComponent.searchField.text= -PersonaManagerTopComponent.editBtn.text=Edit Persona PersonaDetailsDialog.cancelBtn.text=Cancel PersonaDetailsDialog.okBtn.text=OK PersonaDetailsPanel.casesLbl.text=Cases found in: @@ -27,7 +18,6 @@ PersonaDetailsPanel.nameLbl.text=Name: AddAliasDialog.accountsLbl.text=Account: AddAliasDialog.okBtn.text=OK AddAliasDialog.cancelBtn.text=Cancel -PersonaManagerTopComponent.deleteBtn.text=Delete Persona PersonaDetailsPanel.casesLbl.text=Cases found in: PersonaDetailsPanel.deleteAliasBtn.text=Delete PersonaDetailsPanel.addAliasBtn.text=Add @@ -73,3 +63,19 @@ PersonaMetadataDialog.cancelBtn.text=Cancel PersonaDetailsPanel.editAccountBtn.text=Edit PersonaDetailsPanel.editMetadataBtn.text=Edit PersonaDetailsPanel.editAliasBtn.text=Edit +PersonasTopComponent.searchAccountRadio.text=Account +PersonasTopComponent.searchNameRadio.text=Name +PersonasTopComponent.searchField.text= +PersonasTopComponent.deleteBtn.text=Delete Persona +PersonasTopComponent.editBtn.text=Edit Persona +PersonasTopComponent.createBtn.text=New Persona +PersonasTopComponent.createAccountBtn.text=Create Account +PersonasTopComponent.searchBtn.text=Search +PersonasTopComponent.resultsTable.columnModel.title1=Name +PersonasTopComponent.resultsTable.columnModel.title0=ID +PersonasTopComponent.resultsTable.toolTipText= +CreatePersonaAccountDialog.cancelBtn.text=Cancel +CreatePersonaAccountDialog.typeLbl.text=Type: +CreatePersonaAccountDialog.identifierTextField.text= +CreatePersonaAccountDialog.identiferLbl.text=Identifier: +CreatePersonaAccountDialog.okBtn.text=OK diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED index ed71818774..ae2e6926d8 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED @@ -3,10 +3,13 @@ AddMetadataDialog_dup_msg=A metadata entry with this name has already been added AddMetadataDialog_dup_Title=Metadata add failure AddMetadataDialog_empty_name_msg=A metadata entry cannot have an empty name or value. AddMetadataDialog_empty_name_Title=Missing field(s) -CTL_OpenPersonaManager=Persona Manager -CTL_PersonaManagerTopComponentAction=Persona Manager +CreatePersonaAccountDialog.title.text=Create Account +CreatePersonaAccountDialog_dup_msg=An account with this identifier and type already exists. +CreatePersonaAccountDialog_dup_Title=Account creation failure +CTL_OpenPersonas=Personas +CTL_PersonasTopComponentAction=Personas CTL_PersonaDetailsTopComponent=Persona Details -OpenPersonasAction.displayName=Persona Manager +OpenPersonasAction.displayName=Personas PersonaAccountDialog.title.text=Add Account PersonaAccountDialog_dup_msg=This account is already added to the persona. PersonaAccountDialog_dup_Title=Account add failure @@ -23,32 +26,11 @@ PersonaAliasDialog_dup_msg=This alias has already been added to this persona. PersonaAliasDialog_dup_Title=Alias add failure PersonaAliasDialog_empty_msg=An alias cannot be empty. PersonaAliasDialog_empty_Title=Empty alias +PersonaDetailsDialog.cancelBtn.text=Cancel +PersonaDetailsDialog.okBtn.text=OK PersonaDetailsDialogCreateTitle=Create Persona PersonaDetailsDialogEditTitle=Edit Persona PersonaDetailsDialogViewTitle=View Persona -PersonaDetailsPanel_CentralRepoErr_msg=Failure to write to Central Repository. -PersonaDetailsPanel_CentralRepoErr_Title=Central Repository failure -PersonaDetailsPanel_empty_justification_msg=The justification field cannot be empty -PersonaDetailsPanel_empty_justification_Title=Empty justification -PersonaDetailsPanel_EmptyComment_msg=Persona comment cannot be empty. -PersonaDetailsPanel_EmptyComment_Title=Empty persona comment -PersonaDetailsPanel_EmptyName_msg=Persona name cannot be empty. -PersonaDetailsPanel_EmptyName_Title=Empty persona name -PersonaDetailsPanel_load_exception_msg=Failed to load persona. -PersonaDetailsPanel_load_exception_Title=Initialization failure -PersonaDetailsPanel_NotEnoughAccounts_msg=A persona needs at least one account. -PersonaDetailsPanel_NotEnoughAccounts_Title=Missing account -PersonaManagerTopComponent.createBtn.text=New Persona -PersonaManagerTopComponent.searchBtn.text=Search -PersonaManagerTopComponent.resultsTable.columnModel.title1=Name -PersonaManagerTopComponent.resultsTable.columnModel.title0=ID -PersonaManagerTopComponent.resultsTable.toolTipText= -PersonaManagerTopComponent.searchAccountRadio.text=Account -PersonaManagerTopComponent.searchNameRadio.text=Name -PersonaManagerTopComponent.searchField.text= -PersonaManagerTopComponent.editBtn.text=Edit Persona -PersonaDetailsDialog.cancelBtn.text=Cancel -PersonaDetailsDialog.okBtn.text=OK PersonaDetailsPanel.casesLbl.text=Cases found in: PersonaDetailsPanel.deleteAliasBtn.text=Delete PersonaDetailsPanel.addAliasBtn.text=Add @@ -64,7 +46,6 @@ PersonaDetailsPanel.nameLbl.text=Name: AddAliasDialog.accountsLbl.text=Account: AddAliasDialog.okBtn.text=OK AddAliasDialog.cancelBtn.text=Cancel -PersonaManagerTopComponent.deleteBtn.text=Delete Persona PersonaDetailsPanel.casesLbl.text=Cases found in: PersonaDetailsPanel.deleteAliasBtn.text=Delete PersonaDetailsPanel.addAliasBtn.text=Add @@ -98,6 +79,18 @@ PersonaAliasDialog.justificationLbl.text=Justification: PersonaAliasDialog.aliasTextField.text= PersonaAliasDialog.aliasLbl.text=Alias: PersonaAliasDialog.okBtn.text_1=OK +PersonaDetailsPanel_CentralRepoErr_msg=Failure to write to Central Repository. +PersonaDetailsPanel_CentralRepoErr_Title=Central Repository failure +PersonaDetailsPanel_empty_justification_msg=The justification field cannot be empty +PersonaDetailsPanel_empty_justification_Title=Empty justification +PersonaDetailsPanel_EmptyComment_msg=Persona comment cannot be empty. +PersonaDetailsPanel_EmptyComment_Title=Empty persona comment +PersonaDetailsPanel_EmptyName_msg=Persona name cannot be empty. +PersonaDetailsPanel_EmptyName_Title=Empty persona name +PersonaDetailsPanel_load_exception_msg=Failed to load persona. +PersonaDetailsPanel_load_exception_Title=Initialization failure +PersonaDetailsPanel_NotEnoughAccounts_msg=A persona needs at least one account. +PersonaDetailsPanel_NotEnoughAccounts_Title=Missing account PersonaMetadataDialog.confidenceLbl.text=Confidence: PersonaMetadataDialog.justificationTextField.text= PersonaMetadataDialog.justificationLbl.text=Justification: @@ -110,10 +103,26 @@ PersonaMetadataDialog.cancelBtn.text=Cancel PersonaDetailsPanel.editAccountBtn.text=Edit PersonaDetailsPanel.editMetadataBtn.text=Edit PersonaDetailsPanel.editAliasBtn.text=Edit -PMTopComponent_delete_confirmation_msg=Are you sure you want to delete this persona? -PMTopComponent_delete_confirmation_Title=Are you sure? -PMTopComponent_delete_exception_msg=Failed to delete persona. -PMTopComponent_delete_exception_Title=Delete failure -PMTopComponent_Name=Persona Manager -PMTopComponent_search_exception_msg=Failed to search personas. -PMTopComponent_search_exception_Title=Search failure +PersonasTopComponent.searchAccountRadio.text=Account +PersonasTopComponent.searchNameRadio.text=Name +PersonasTopComponent.searchField.text= +PersonasTopComponent.deleteBtn.text=Delete Persona +PersonasTopComponent.editBtn.text=Edit Persona +PersonasTopComponent.createBtn.text=New Persona +PersonasTopComponent.createAccountBtn.text=Create Account +PersonasTopComponent.searchBtn.text=Search +PersonasTopComponent.resultsTable.columnModel.title1=Name +PersonasTopComponent.resultsTable.columnModel.title0=ID +PersonasTopComponent.resultsTable.toolTipText= +CreatePersonaAccountDialog.cancelBtn.text=Cancel +CreatePersonaAccountDialog.typeLbl.text=Type: +CreatePersonaAccountDialog.identifierTextField.text= +CreatePersonaAccountDialog.identiferLbl.text=Identifier: +CreatePersonaAccountDialog.okBtn.text=OK +PersonasTopComponent_delete_confirmation_msg=Are you sure you want to delete this persona? +PersonasTopComponent_delete_confirmation_Title=Are you sure? +PersonasTopComponent_delete_exception_msg=Failed to delete persona. +PersonasTopComponent_delete_exception_Title=Delete failure +PersonasTopComponent_Name=Personas +PersonasTopComponent_search_exception_msg=Failed to search personas. +PersonasTopComponent_search_exception_Title=Search failure diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle_ja.properties index f757a8f7ef..bfdd721a69 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle_ja.properties @@ -8,3 +8,5 @@ PersonaAliasDialog.cancelBtn.text_1=\u53d6\u308a\u6d88\u3057 PersonaAliasDialog.okBtn.text_1=OK PersonaMetadataDialog.okBtn.text=OK PersonaMetadataDialog.cancelBtn.text=\u53d6\u308a\u6d88\u3057 +CreatePersonaAccountDialog.okBtn.text=OK +CreatePersonaAccountDialog.cancelBtn.text=\u53d6\u308a\u6d88\u3057 diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/CreatePersonaAccountDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/CreatePersonaAccountDialog.form new file mode 100644 index 0000000000..fd74cca117 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/CreatePersonaAccountDialog.form @@ -0,0 +1,168 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/CreatePersonaAccountDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/CreatePersonaAccountDialog.java new file mode 100644 index 0000000000..1ac9abf3b7 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/CreatePersonaAccountDialog.java @@ -0,0 +1,270 @@ +/* + * Central Repository + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.centralrepository.persona; + +import java.awt.Component; +import java.io.Serializable; +import java.util.Collection; +import java.util.logging.Level; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.ListCellRenderer; +import javax.swing.SwingUtilities; +import org.openide.util.NbBundle.Messages; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount.CentralRepoAccountType; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * Configuration dialog for creating an account. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +public class CreatePersonaAccountDialog extends JDialog { + + private static final Logger logger = Logger.getLogger(CreatePersonaAccountDialog.class.getName()); + + private static final long serialVersionUID = 1L; + + private final TypeChoiceRenderer TYPE_CHOICE_RENDERER = new TypeChoiceRenderer(); + + /** + * Creates new create account dialog. + */ + @Messages({"CreatePersonaAccountDialog.title.text=Create Account",}) + public CreatePersonaAccountDialog(PersonaDetailsPanel pdp) { + super(SwingUtilities.windowForComponent(pdp), + Bundle.PersonaAccountDialog_title_text(), + ModalityType.APPLICATION_MODAL); + + initComponents(); + typeComboBox.setRenderer(TYPE_CHOICE_RENDERER); + display(); + } + + /** + * This class handles displaying and rendering drop down menu for account + * choices. + */ + private class TypeChoiceRenderer extends JLabel implements ListCellRenderer, Serializable { + + private static final long serialVersionUID = 1L; + + @Override + public Component getListCellRendererComponent( + JList list, CentralRepoAccountType value, + int index, boolean isSelected, boolean cellHasFocus) { + setText(value.getAcctType().getDisplayName()); + return this; + } + } + + private CentralRepoAccountType[] getAllAccountTypes() { + Collection allAccountTypes; + try { + allAccountTypes = CentralRepository.getInstance().getAllAccountTypes(); + } catch (CentralRepoException e) { + logger.log(Level.SEVERE, "Failed to access central repository", e); + JOptionPane.showMessageDialog(this, + Bundle.PersonaAccountDialog_get_types_exception_Title(), + Bundle.PersonaAccountDialog_get_types_exception_msg(), + JOptionPane.ERROR_MESSAGE); + return new CentralRepoAccountType[0]; + } + return allAccountTypes.toArray(new CentralRepoAccountType[0]); + } + + /** + * 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") + // //GEN-BEGIN:initComponents + private void initComponents() { + + settingsPanel = new javax.swing.JPanel(); + identiferLbl = new javax.swing.JLabel(); + identifierTextField = new javax.swing.JTextField(); + typeLbl = new javax.swing.JLabel(); + typeComboBox = new javax.swing.JComboBox<>(); + cancelBtn = new javax.swing.JButton(); + okBtn = new javax.swing.JButton(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setResizable(false); + + settingsPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + + org.openide.awt.Mnemonics.setLocalizedText(identiferLbl, org.openide.util.NbBundle.getMessage(CreatePersonaAccountDialog.class, "CreatePersonaAccountDialog.identiferLbl.text")); // NOI18N + + identifierTextField.setText(org.openide.util.NbBundle.getMessage(CreatePersonaAccountDialog.class, "CreatePersonaAccountDialog.identifierTextField.text")); // NOI18N + identifierTextField.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + identifierTextFieldActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(typeLbl, org.openide.util.NbBundle.getMessage(CreatePersonaAccountDialog.class, "CreatePersonaAccountDialog.typeLbl.text")); // NOI18N + + typeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(getAllAccountTypes())); + + javax.swing.GroupLayout settingsPanelLayout = new javax.swing.GroupLayout(settingsPanel); + settingsPanel.setLayout(settingsPanelLayout); + settingsPanelLayout.setHorizontalGroup( + settingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(settingsPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(settingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(settingsPanelLayout.createSequentialGroup() + .addComponent(typeLbl) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(typeComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(settingsPanelLayout.createSequentialGroup() + .addComponent(identiferLbl) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(identifierTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 281, Short.MAX_VALUE))) + .addContainerGap()) + ); + settingsPanelLayout.setVerticalGroup( + settingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(settingsPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(settingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(identiferLbl) + .addComponent(identifierTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(settingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(typeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(typeLbl, javax.swing.GroupLayout.PREFERRED_SIZE, 9, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + org.openide.awt.Mnemonics.setLocalizedText(cancelBtn, org.openide.util.NbBundle.getMessage(CreatePersonaAccountDialog.class, "CreatePersonaAccountDialog.cancelBtn.text")); // NOI18N + cancelBtn.setMaximumSize(new java.awt.Dimension(79, 23)); + cancelBtn.setMinimumSize(new java.awt.Dimension(79, 23)); + cancelBtn.setPreferredSize(new java.awt.Dimension(79, 23)); + cancelBtn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cancelBtnActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(okBtn, org.openide.util.NbBundle.getMessage(CreatePersonaAccountDialog.class, "CreatePersonaAccountDialog.okBtn.text")); // NOI18N + okBtn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + okBtnActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap(194, Short.MAX_VALUE) + .addComponent(okBtn) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cancelBtn, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + .addComponent(settingsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {cancelBtn, okBtn}); + + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(settingsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(okBtn) + .addComponent(cancelBtn, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + pack(); + }// //GEN-END:initComponents + + private void display() { + this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); + setVisible(true); + } + + private CentralRepoAccount createAccount(CentralRepoAccount.CentralRepoAccountType type, String identifier) { + CentralRepoAccount ret = null; + try { + CentralRepository cr = CentralRepository.getInstance(); + if (cr != null) { + ret = cr.getOrCreateAccount(type, identifier); + } + } catch (CentralRepoException e) { + logger.log(Level.SEVERE, "Failed to access central repository", e); + JOptionPane.showMessageDialog(this, + Bundle.PersonaAccountDialog_get_types_exception_Title(), + Bundle.PersonaAccountDialog_get_types_exception_msg(), + JOptionPane.ERROR_MESSAGE); + } + return ret; + } + + @Messages({ + "CreatePersonaAccountDialog_dup_Title=Account creation failure", + "CreatePersonaAccountDialog_dup_msg=An account with this identifier and type already exists.",}) + private void okBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okBtnActionPerformed + if (identifierTextField.getText().isEmpty()) { + JOptionPane.showMessageDialog(this, + Bundle.PersonaAccountDialog_identifier_empty_msg(), + Bundle.PersonaAccountDialog_identifier_empty_Title(), + JOptionPane.ERROR_MESSAGE); + return; + } + + CentralRepoAccount.CentralRepoAccountType type = + (CentralRepoAccount.CentralRepoAccountType) typeComboBox.getSelectedItem(); + String identifier = identifierTextField.getText(); + + if (createAccount(type, identifier) != null) { + dispose(); + } + }//GEN-LAST:event_okBtnActionPerformed + + private void cancelBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelBtnActionPerformed + dispose(); + }//GEN-LAST:event_cancelBtnActionPerformed + + private void identifierTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_identifierTextFieldActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_identifierTextFieldActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton cancelBtn; + private javax.swing.JLabel identiferLbl; + private javax.swing.JTextField identifierTextField; + private javax.swing.JButton okBtn; + private javax.swing.JPanel settingsPanel; + private javax.swing.JComboBox typeComboBox; + private javax.swing.JLabel typeLbl; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/OpenPersonaManagerAction.java b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/OpenPersonasAction.java similarity index 88% rename from Core/src/org/sleuthkit/autopsy/centralrepository/persona/OpenPersonaManagerAction.java rename to Core/src/org/sleuthkit/autopsy/centralrepository/persona/OpenPersonasAction.java index 9d40eeb357..5407abc981 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/OpenPersonaManagerAction.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/OpenPersonasAction.java @@ -35,18 +35,18 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; * An Action that opens the Persona Search window. */ -@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.centralrepository.persona.OpenPersonaManagerAction") -@ActionRegistration(displayName = "#CTL_OpenPersonaManager", lazy = false) +@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.centralrepository.persona.OpenPersonasAction") +@ActionRegistration(displayName = "#CTL_OpenPersonas", lazy = false) @ActionReferences(value = { @ActionReference(path = "Menu/Tools", position = 105) }) -public final class OpenPersonaManagerAction extends CallableSystemAction { +public final class OpenPersonasAction extends CallableSystemAction { private static final long serialVersionUID = 1L; private final JMenuItem menuItem; - public OpenPersonaManagerAction() { + public OpenPersonasAction() { menuItem = super.getMenuPresenter(); this.setEnabled(CentralRepository.isEnabled()); } @@ -54,7 +54,7 @@ public final class OpenPersonaManagerAction extends CallableSystemAction { @Override @ThreadConfined(type = ThreadConfined.ThreadType.AWT) public void performAction() { - final TopComponent topComponent = WindowManager.getDefault().findTopComponent("PersonaManagerTopComponent"); + final TopComponent topComponent = WindowManager.getDefault().findTopComponent("PersonasTopComponent"); if (topComponent != null) { if (topComponent.isOpened() == false) { topComponent.open(); @@ -65,7 +65,7 @@ public final class OpenPersonaManagerAction extends CallableSystemAction { } @Override - @NbBundle.Messages("OpenPersonasAction.displayName=Persona Manager") + @NbBundle.Messages("OpenPersonasAction.displayName=Personas") public String getName() { return Bundle.OpenPersonasAction_displayName(); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaManagerTopComponent.form b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.form similarity index 78% rename from Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaManagerTopComponent.form rename to Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.form index 4c2a65a276..e731a133b8 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaManagerTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.form @@ -27,9 +27,7 @@ - - - + @@ -46,7 +44,7 @@ - + @@ -70,9 +68,10 @@ - + + @@ -89,6 +88,10 @@ + + + + @@ -106,7 +109,7 @@ - + @@ -114,6 +117,10 @@ + + + + @@ -122,7 +129,7 @@ - + @@ -133,7 +140,7 @@ - + @@ -143,7 +150,7 @@ - + @@ -157,20 +164,20 @@ - + - <ResourceString bundle="org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties" key="PersonaManagerTopComponent.resultsTable.columnModel.title0" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties" key="PersonasTopComponent.resultsTable.columnModel.title0" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> - <ResourceString bundle="org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties" key="PersonaManagerTopComponent.resultsTable.columnModel.title1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties" key="PersonasTopComponent.resultsTable.columnModel.title1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> @@ -187,36 +194,48 @@ - + - + - - - - - - - - - + - + - + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaManagerTopComponent.java b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.java similarity index 76% rename from Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaManagerTopComponent.java rename to Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.java index 93dc22e4e0..d4d1a7b340 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaManagerTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.java @@ -44,27 +44,29 @@ import org.sleuthkit.autopsy.coreutils.Logger; * Top component for the Personas tool * */ -@TopComponent.Description(preferredID = "PersonaManagerTopComponent", persistenceType = TopComponent.PERSISTENCE_NEVER) -@TopComponent.Registration(mode = "personamanager", openAtStartup = false) -@RetainLocation("personamanager") +@TopComponent.Description(preferredID = "PersonasTopComponent", persistenceType = TopComponent.PERSISTENCE_NEVER) +@TopComponent.Registration(mode = "personas", openAtStartup = false) +@RetainLocation("personas") @SuppressWarnings("PMD.SingularField") -public final class PersonaManagerTopComponent extends TopComponent { +public final class PersonasTopComponent extends TopComponent { + + private static final long serialVersionUID = 1L; - private static final Logger logger = Logger.getLogger(PersonaManagerTopComponent.class.getName()); + private static final Logger logger = Logger.getLogger(PersonasTopComponent.class.getName()); private List currentResults = null; private Persona selectedPersona = null; @Messages({ - "PMTopComponent_Name=Persona Manager", - "PMTopComponent_delete_exception_Title=Delete failure", - "PMTopComponent_delete_exception_msg=Failed to delete persona.", - "PMTopComponent_delete_confirmation_Title=Are you sure?", - "PMTopComponent_delete_confirmation_msg=Are you sure you want to delete this persona?", + "PersonasTopComponent_Name=Personas", + "PersonasTopComponent_delete_exception_Title=Delete failure", + "PersonasTopComponent_delete_exception_msg=Failed to delete persona.", + "PersonasTopComponent_delete_confirmation_Title=Are you sure?", + "PersonasTopComponent_delete_confirmation_msg=Are you sure you want to delete this persona?", }) - public PersonaManagerTopComponent() { + public PersonasTopComponent() { initComponents(); - setName(Bundle.PMTopComponent_Name()); + setName(Bundle.PersonasTopComponent_Name()); executeSearch(); searchBtn.addActionListener(new ActionListener() { @@ -77,7 +79,7 @@ public final class PersonaManagerTopComponent extends TopComponent { editBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - new PersonaDetailsDialog(PersonaManagerTopComponent.this, + new PersonaDetailsDialog(PersonasTopComponent.this, PersonaDetailsMode.EDIT, selectedPersona, new CreateEditCallbackImpl()); } }); @@ -85,7 +87,7 @@ public final class PersonaManagerTopComponent extends TopComponent { createBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - new PersonaDetailsDialog(PersonaManagerTopComponent.this, + new PersonaDetailsDialog(PersonasTopComponent.this, PersonaDetailsMode.CREATE, selectedPersona, new CreateEditCallbackImpl()); } }); @@ -94,8 +96,8 @@ public final class PersonaManagerTopComponent extends TopComponent { @Override public void actionPerformed(ActionEvent e) { NotifyDescriptor confirm = new NotifyDescriptor.Confirmation( - Bundle.PMTopComponent_delete_confirmation_msg(), - Bundle.PMTopComponent_delete_confirmation_Title(), + Bundle.PersonasTopComponent_delete_confirmation_msg(), + Bundle.PersonasTopComponent_delete_confirmation_Title(), NotifyDescriptor.YES_NO_OPTION); DialogDisplayer.getDefault().notify(confirm); if (confirm.getValue().equals(NotifyDescriptor.YES_OPTION)) { @@ -105,9 +107,9 @@ public final class PersonaManagerTopComponent extends TopComponent { } } catch (CentralRepoException ex) { logger.log(Level.SEVERE, "Failed to delete persona: " + selectedPersona.getName(), ex); - JOptionPane.showMessageDialog(PersonaManagerTopComponent.this, - Bundle.PMTopComponent_delete_exception_msg(), - Bundle.PMTopComponent_delete_exception_Title(), + JOptionPane.showMessageDialog(PersonasTopComponent.this, + Bundle.PersonasTopComponent_delete_exception_msg(), + Bundle.PersonasTopComponent_delete_exception_Title(), JOptionPane.ERROR_MESSAGE); return; } @@ -132,6 +134,13 @@ public final class PersonaManagerTopComponent extends TopComponent { searchAccountRadio.addActionListener((ActionEvent e) -> { searchField.setText(""); }); + + createAccountBtn.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + new CreatePersonaAccountDialog(detailsPanel); + } + }); } /** @@ -214,8 +223,8 @@ public final class PersonaManagerTopComponent extends TopComponent { } @Messages({ - "PMTopComponent_search_exception_Title=Search failure", - "PMTopComponent_search_exception_msg=Failed to search personas.",}) + "PersonasTopComponent_search_exception_Title=Search failure", + "PersonasTopComponent_search_exception_msg=Failed to search personas.",}) private void executeSearch() { Collection results; try { @@ -227,8 +236,8 @@ public final class PersonaManagerTopComponent extends TopComponent { } catch (CentralRepoException ex) { logger.log(Level.SEVERE, "Failed to search personas", ex); JOptionPane.showMessageDialog(this, - Bundle.PMTopComponent_search_exception_Title(), - Bundle.PMTopComponent_search_exception_msg(), + Bundle.PersonasTopComponent_search_exception_Title(), + Bundle.PersonasTopComponent_search_exception_msg(), JOptionPane.ERROR_MESSAGE); return; } @@ -262,39 +271,43 @@ public final class PersonaManagerTopComponent extends TopComponent { resultsPane = new javax.swing.JScrollPane(); resultsTable = new javax.swing.JTable(); searchBtn = new javax.swing.JButton(); - editBtn = new javax.swing.JButton(); + createAccountBtn = new javax.swing.JButton(); createBtn = new javax.swing.JButton(); + editBtn = new javax.swing.JButton(); deleteBtn = new javax.swing.JButton(); + jSeparator1 = new javax.swing.JSeparator(); detailsPanel = new org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsPanel(); - setMinimumSize(new java.awt.Dimension(400, 400)); + setName(""); // NOI18N - searchField.setText(org.openide.util.NbBundle.getMessage(PersonaManagerTopComponent.class, "PersonaManagerTopComponent.searchField.text")); // NOI18N + searchField.setText(org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.searchField.text")); // NOI18N searchButtonGroup.add(searchNameRadio); searchNameRadio.setSelected(true); - org.openide.awt.Mnemonics.setLocalizedText(searchNameRadio, org.openide.util.NbBundle.getMessage(PersonaManagerTopComponent.class, "PersonaManagerTopComponent.searchNameRadio.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(searchNameRadio, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.searchNameRadio.text")); // NOI18N searchButtonGroup.add(searchAccountRadio); - org.openide.awt.Mnemonics.setLocalizedText(searchAccountRadio, org.openide.util.NbBundle.getMessage(PersonaManagerTopComponent.class, "PersonaManagerTopComponent.searchAccountRadio.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(searchAccountRadio, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.searchAccountRadio.text")); // NOI18N - resultsTable.setToolTipText(org.openide.util.NbBundle.getMessage(PersonaManagerTopComponent.class, "PersonaManagerTopComponent.resultsTable.toolTipText")); // NOI18N + resultsTable.setToolTipText(org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.resultsTable.toolTipText")); // NOI18N resultsTable.getTableHeader().setReorderingAllowed(false); resultsPane.setViewportView(resultsTable); if (resultsTable.getColumnModel().getColumnCount() > 0) { resultsTable.getColumnModel().getColumn(0).setMaxWidth(25); - resultsTable.getColumnModel().getColumn(0).setHeaderValue(org.openide.util.NbBundle.getMessage(PersonaManagerTopComponent.class, "PersonaManagerTopComponent.resultsTable.columnModel.title0")); // NOI18N - resultsTable.getColumnModel().getColumn(1).setHeaderValue(org.openide.util.NbBundle.getMessage(PersonaManagerTopComponent.class, "PersonaManagerTopComponent.resultsTable.columnModel.title1")); // NOI18N + resultsTable.getColumnModel().getColumn(0).setHeaderValue(org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.resultsTable.columnModel.title0")); // NOI18N + resultsTable.getColumnModel().getColumn(1).setHeaderValue(org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.resultsTable.columnModel.title1")); // NOI18N } - org.openide.awt.Mnemonics.setLocalizedText(searchBtn, org.openide.util.NbBundle.getMessage(PersonaManagerTopComponent.class, "PersonaManagerTopComponent.searchBtn.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(searchBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.searchBtn.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(editBtn, org.openide.util.NbBundle.getMessage(PersonaManagerTopComponent.class, "PersonaManagerTopComponent.editBtn.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(createAccountBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.createAccountBtn.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(createBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.createBtn.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(editBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.editBtn.text")); // NOI18N editBtn.setEnabled(false); - org.openide.awt.Mnemonics.setLocalizedText(createBtn, org.openide.util.NbBundle.getMessage(PersonaManagerTopComponent.class, "PersonaManagerTopComponent.createBtn.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(deleteBtn, org.openide.util.NbBundle.getMessage(PersonaManagerTopComponent.class, "PersonaManagerTopComponent.deleteBtn.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(deleteBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.deleteBtn.text")); // NOI18N deleteBtn.setEnabled(false); javax.swing.GroupLayout searchPanelLayout = new javax.swing.GroupLayout(searchPanel); @@ -304,6 +317,7 @@ public final class PersonaManagerTopComponent extends TopComponent { .addGroup(searchPanelLayout.createSequentialGroup() .addContainerGap() .addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jSeparator1) .addGroup(searchPanelLayout.createSequentialGroup() .addComponent(createBtn) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -317,7 +331,10 @@ public final class PersonaManagerTopComponent extends TopComponent { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(searchAccountRadio) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(searchBtn))) + .addComponent(searchBtn)) + .addGroup(searchPanelLayout.createSequentialGroup() + .addComponent(createAccountBtn) + .addGap(0, 0, Short.MAX_VALUE))) .addContainerGap()) ); searchPanelLayout.setVerticalGroup( @@ -331,12 +348,16 @@ public final class PersonaManagerTopComponent extends TopComponent { .addComponent(searchAccountRadio) .addComponent(searchBtn)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(resultsPane, javax.swing.GroupLayout.DEFAULT_SIZE, 528, Short.MAX_VALUE) + .addComponent(resultsPane, javax.swing.GroupLayout.DEFAULT_SIZE, 457, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(editBtn) .addComponent(createBtn) .addComponent(deleteBtn)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 4, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(createAccountBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) ); @@ -347,7 +368,7 @@ public final class PersonaManagerTopComponent extends TopComponent { this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jSplitPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 692, Short.MAX_VALUE) + .addComponent(jSplitPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 794, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -356,10 +377,12 @@ public final class PersonaManagerTopComponent extends TopComponent { }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton createAccountBtn; private javax.swing.JButton createBtn; private javax.swing.JButton deleteBtn; private org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsPanel detailsPanel; private javax.swing.JButton editBtn; + private javax.swing.JSeparator jSeparator1; private javax.swing.JSplitPane jSplitPane1; private javax.swing.JScrollPane resultsPane; private javax.swing.JTable resultsTable; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index 5bac74e842..6e75857c26 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -981,4 +981,3 @@ CallLogArtifactViewer.localAccountPersonaNameLabel.text=jLabel1 CallLogArtifactViewer.localAccountPersonaButton.text=jButton1 ContactArtifactViewer.personasLabel.text=Personas MessageArtifactViewer.accountsTab.TabConstraints.tabTitle=Accounts -ContactArtifactViewer.contactImage.text= diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index 0aa57ce92b..2d299b1f0e 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -35,15 +35,23 @@ AnnotationsContentViewer.title=Annotations AnnotationsContentViewer.toolTip=Displays tags and comments associated with the selected content. ApplicationContentViewer.title=Application ApplicationContentViewer.toolTip=Displays file contents. -CallLogArtifactViewer_crdisbaled_persona_button_text=Create -CallLogArtifactViewer_crdisbaled_persona_label=Unknown -CallLogArtifactViewer_number_from=From -CallLogArtifactViewer_number_to=To -CallLogArtifactViewer_persona_button_new=Create -CallLogArtifactViewer_persona_button_view=View -CallLogArtifactViewer_persona_label=\ Persona -CallLogArtifactViewer_persona_searching=Searching... -CallLogArtifactViewer_persona_text_none=None Found +CallLogArtifactViewer_heading_metadata=Metadata +CallLogArtifactViewer_heading_parties=Parties +CallLogArtifactViewer_heading_Source=Source +CallLogArtifactViewer_label_datasource=Data Source +CallLogArtifactViewer_label_date=Date +CallLogArtifactViewer_label_direction=Direction +CallLogArtifactViewer_label_duration=Duration +CallLogArtifactViewer_label_from=From +CallLogArtifactViewer_label_to=To +CallLogArtifactViewer_suffix_local=(Local) +CallLogArtifactViewer_value_unknown=Unknown +CommunicationArtifactViewerHelper_menuitem_copy=Copy +CommunicationArtifactViewerHelper_persona_button_create=Create +CommunicationArtifactViewerHelper_persona_button_view=View +CommunicationArtifactViewerHelper_persona_label=Persona: +CommunicationArtifactViewerHelper_persona_searching=Searching... +CommunicationArtifactViewerHelper_persona_unknown=Unknown ContactArtifactViewer_missing_account_label=Missing Account: ContactArtifactViewer_persona_account_justification=Account found in Contact artifact ContactArtifactViewer_persona_button_new=Create diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/CallLogArtifactViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/CallLogArtifactViewer.java index 99b662f52a..8bb83660a1 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/CallLogArtifactViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/CallLogArtifactViewer.java @@ -35,6 +35,7 @@ import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -66,8 +67,8 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac private GridBagLayout m_gridBagLayout = new GridBagLayout(); private GridBagConstraints m_constraints = new GridBagConstraints(); - - private final List personaSearchtasks = new ArrayList<>(); + + private PersonaAccountFetcher currentAccountFetcher = null; /** * Creates new form CallLogArtifactViewer. @@ -92,6 +93,10 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac public void setArtifact(BlackboardArtifact artifact) { resetComponent(); + if (artifact == null) { + return; + } + CallLogViewData callLogViewData = null; try { callLogViewData = getCallLogViewData(artifact); @@ -101,11 +106,16 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac // update the view with the call log data if (callLogViewData != null) { - updateView(callLogViewData); + List personaSearchDataList = updateView(callLogViewData); + if(!personaSearchDataList.isEmpty()) { + currentAccountFetcher = new PersonaAccountFetcher(artifact, personaSearchDataList, this); + currentAccountFetcher.execute(); + } else { + currentAccountFetcher = null; + } } // repaint this.revalidate(); - } /** @@ -281,6 +291,8 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac * Update the viewer with the call log data. * * @param callLogViewData Call log data to update the view with. + * + * @return List of AccountPersonaSearcherData objects. */ @NbBundle.Messages({ "CallLogArtifactViewer_heading_parties=Parties", @@ -288,10 +300,11 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac "CallLogArtifactViewer_label_from=From", "CallLogArtifactViewer_label_to=To" }) - private void updateView(CallLogViewData callLogViewData) { + private List updateView(CallLogViewData callLogViewData) { CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_heading_parties()); + List dataList = new ArrayList<>(); // Display From address CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_label_from()); @@ -301,10 +314,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, accountDisplayString); // show persona - Optional task = CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, callLogViewData.getFromAccount()); - if (task.isPresent()) { - personaSearchtasks.add(task.get()); - } + dataList.addAll( CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, callLogViewData.getFromAccount())); } else { CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_value_unknown()); } @@ -315,10 +325,8 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac String accountDisplayString = getAccountDisplayString(callLogViewData.getToAccount(), callLogViewData); CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, accountDisplayString); - Optional task = CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, callLogViewData.getToAccount()); - if (task.isPresent()) { - personaSearchtasks.add(task.get()); - } + dataList.addAll( CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, callLogViewData.getToAccount())); + } else { CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_value_unknown()); } @@ -328,20 +336,26 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_label_to()); CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, otherParty); - Optional task = CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, otherParty); - if (task.isPresent()) { - personaSearchtasks.add(task.get()); - } + dataList.addAll( CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, otherParty)); } updateMetadataView(callLogViewData); + + updateOtherAttributesView(callLogViewData); + updateSourceView(callLogViewData); + if (CentralRepository.isEnabled() == false) { + showCRDisabledMessage(); + } + CommunicationArtifactViewerHelper.addPageEndGlue(this, m_gridBagLayout, this.m_constraints); this.setLayout(m_gridBagLayout); this.revalidate(); this.repaint(); + + return dataList; } /** @@ -392,6 +406,37 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, callLogViewData.getDataSourceName()); } + /** + * Update the other attributes section. + * + * @param callLogViewData Call log data. + */ + @NbBundle.Messages({ + "CallLogArtifactViewer_heading_others=Other Attributes" + }) + private void updateOtherAttributesView(CallLogViewData callLogViewData) { + + if (callLogViewData.getOtherAttributes().isEmpty()) { + return; + } + CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_heading_others()); + + for (Map.Entry entry : callLogViewData.getOtherAttributes().entrySet()) { + CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, entry.getKey()); + CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, entry.getValue()); + } + } + + @NbBundle.Messages({ + "CalllogArtifactViewer_cr_disabled_message=Enable Central Repository to view, create and edit personas." + }) + private void showCRDisabledMessage() { + CommunicationArtifactViewerHelper.addBlankLine(this, m_gridBagLayout, m_constraints); + m_constraints.gridy++; + CommunicationArtifactViewerHelper.addMessageRow(this, m_gridBagLayout, m_constraints, Bundle.ContactArtifactViewer_cr_disabled_message()); + m_constraints.gridy++; + } + /** * 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 @@ -419,7 +464,9 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac @Override public boolean isSupported(BlackboardArtifact artifact) { - return artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID(); + + return (artifact != null) + && (artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID()); } /** @@ -428,8 +475,10 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac private void resetComponent() { // cancel any outstanding persona searching threads. - personaSearchtasks.forEach(task -> task.cancel(Boolean.TRUE)); - personaSearchtasks.clear(); + if(currentAccountFetcher != null && !currentAccountFetcher.isDone()) { + currentAccountFetcher.cancel(true); + currentAccountFetcher = null; + } // clear the panel this.removeAll(); @@ -441,9 +490,9 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac 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.weighty = 0.0; + m_constraints.weightx = 0.0; // keep components fixed horizontally. + m_constraints.insets = new java.awt.Insets(0, CommunicationArtifactViewerHelper.LEFT_INSET, 0, 0); m_constraints.fill = GridBagConstraints.NONE; } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/CommunicationArtifactViewerHelper.java b/Core/src/org/sleuthkit/autopsy/contentviewers/CommunicationArtifactViewerHelper.java index bb37885786..2c6a013929 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/CommunicationArtifactViewerHelper.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/CommunicationArtifactViewerHelper.java @@ -27,12 +27,14 @@ import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; import javax.swing.JLabel; import javax.swing.JMenuItem; +import javax.swing.JComponent; 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; @@ -41,20 +43,20 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; * A class to help display a communication artifact in a panel using a * gridbaglayout. */ -public final class CommunicationArtifactViewerHelper { +final class CommunicationArtifactViewerHelper { // Number of columns in the gridbag layout. private final static int MAX_COLS = 4; - private final static int LEFT_INDENT = 12; - + final static int LEFT_INSET = 12; + /** * Empty private constructor */ private CommunicationArtifactViewerHelper() { } - + /** * Adds a new heading to the panel. * @@ -62,8 +64,15 @@ public final class CommunicationArtifactViewerHelper { * @param gridbagLayout Layout to use. * @param constraints Constrains to use. * @param headerString Heading string to display. + * + * @return JLabel Heading label added. */ - static void addHeader(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String headerString) { + static JLabel addHeader(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String headerString) { + + Insets savedInsets = constraints.insets; + + // create label for heading + javax.swing.JLabel headingLabel = new javax.swing.JLabel(); // add a blank line before the start of new section, unless it's // the first section @@ -75,9 +84,9 @@ public final class CommunicationArtifactViewerHelper { // let the header span all of the row constraints.gridwidth = MAX_COLS; + constraints.insets = new Insets(0, 0, 0, 0); // No inset for header - // create label for heading - javax.swing.JLabel headingLabel = new javax.swing.JLabel(); + // set text headingLabel.setText(headerString); // make it large and bold @@ -92,6 +101,28 @@ public final class CommunicationArtifactViewerHelper { // add line end glue addLineEndGlue(panel, gridbagLayout, constraints); + + //restore insets + constraints.insets = savedInsets; + + return headingLabel; + } + + /** + * Adds the given component to the panel. + * + * Caller must know what it's doing and set up all the constraints properly. + * + * @param panel Panel to update. + * @param gridbagLayout Layout to use. + * @param constraints Constrains to use. + * @param component Component to add. + */ + static void addComponent(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, JComponent component) { + + // add to panel + gridbagLayout.setConstraints(component, constraints); + panel.add(component); } /** @@ -102,7 +133,7 @@ public final class CommunicationArtifactViewerHelper { * @param gridbagLayout Layout to use. * @param constraints Constrains to use. */ - private static void addLineEndGlue(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) { + static void addLineEndGlue(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) { // Place the filler just past the last column. constraints.gridx = MAX_COLS; @@ -155,7 +186,7 @@ public final class CommunicationArtifactViewerHelper { * @param gridbagLayout Layout to use. * @param constraints Constrains to use. */ - private static void addBlankLine(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) { + static void addBlankLine(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints) { constraints.gridy++; constraints.gridx = 0; @@ -167,54 +198,85 @@ public final class CommunicationArtifactViewerHelper { } /** - * Adds a label/key to the panel. + * Adds a label/key to the panel at col 0. * * @param panel Panel to update. * @param gridbagLayout Layout to use. * @param constraints Constrains to use. * @param keyString Key name to display. + * + * @return Label added. */ - static void addKey(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String keyString) { + static JLabel addKey(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String keyString) { + return addKeyAtCol(panel, gridbagLayout, constraints, keyString, 0); + } + + /** + * Adds a label/key to the panel at specified column. + * + * @param panel Panel to update. + * @param gridbagLayout Layout to use. + * @param constraints Constrains to use. + * @param keyString Key name to display. + * @param gridx column index, must be less than MAX_COLS - 1. + * + * @return Label added. + */ + static JLabel addKeyAtCol(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String keyString, int gridx) { + + // create label + javax.swing.JLabel keyLabel = new javax.swing.JLabel(); constraints.gridy++; - constraints.gridx = 0; + constraints.gridx = gridx < MAX_COLS - 1 ? gridx : MAX_COLS - 2; - 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(); + // set text keyLabel.setText(keyString + ": "); // add to panel gridbagLayout.setConstraints(keyLabel, constraints); panel.add(keyLabel); - // restore inset - constraints.insets = savedInsets; + return keyLabel; } /** - * Adds a value string to the panel. + * Adds a value string to the panel at col 1. * * @param panel Panel to update. * @param gridbagLayout Layout to use. * @param constraints Constrains to use. * @param keyString Value string to display. + * + * @return Label added. */ - static void addValue(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String valueString) { + static JLabel addValue(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String valueString) { + return addValueAtCol(panel, gridbagLayout, constraints, valueString, 1); + } - constraints.gridx = 1; + /** + * Adds a value string to the panel at specified column. + * + * @param panel Panel to update. + * @param gridbagLayout Layout to use. + * @param constraints Constrains to use. + * @param keyString Value string to display. + * @param gridx Column index, must be less than MAX_COLS; + * + * @return Label added. + */ + static JLabel addValueAtCol(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String valueString, int gridx) { + // create label, + javax.swing.JLabel valueField = new javax.swing.JLabel(); + + constraints.gridx = gridx < MAX_COLS ? gridx : MAX_COLS - 1; int savedGridwidth = constraints.gridwidth; // let the value span 2 cols constraints.gridwidth = 2; - // create label, - javax.swing.JLabel valueField = new javax.swing.JLabel(); + // set text valueField.setText(valueString); // attach a right click menu with Copy option @@ -234,6 +296,63 @@ public final class CommunicationArtifactViewerHelper { // end the line addLineEndGlue(panel, gridbagLayout, constraints); + + return valueField; + } + + /** + * Displays a message string, starting at column 0, and spanning the entire + * row. + * + * @param panel Panel to show. + * @param gridbagLayout Layout to use. + * @param constraints Constraints to use. + * + * @param messageString Message to display. + * + * @return Label for message added. + */ + static JLabel addMessageRow(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String messageString) { + return addMessageRow(panel, gridbagLayout, constraints, messageString, 0); + } + + /** + * Displays a message string, starting at specified column, and spanning the + * entire row. + * + * @param panel Panel to show. + * @param gridbagLayout Layout to use. + * @param constraints Constraints to use. + * + * @param messageString Message to display. + * + * @return Label for message added. + */ + static JLabel addMessageRow(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String messageString, int gridx) { + + // create label + javax.swing.JLabel messageLabel = new javax.swing.JLabel(); + + constraints.gridy++; + constraints.gridx = gridx < MAX_COLS - 1 ? gridx : MAX_COLS - 2; + + int savedGridwidth = constraints.gridwidth; + + constraints.gridwidth = 3; + + // set text + messageLabel.setText(messageString); + + // add to panel + gridbagLayout.setConstraints(messageLabel, constraints); + panel.add(messageLabel); + + addLineEndGlue(panel, gridbagLayout, constraints); + + // restore constraints + constraints.gridwidth = savedGridwidth; + + return messageLabel; } /** @@ -250,8 +369,7 @@ public final class CommunicationArtifactViewerHelper { * @param constraints Constrains to use. * @param accountIdentifier Account identifier to search the persona. * - * @return Optional PersonaSearchAndDisplayTask started to search for - * persona. + * @return List of AccountPersonaSearcherData objects. */ @NbBundle.Messages({ "CommunicationArtifactViewerHelper_persona_label=Persona: ", @@ -260,17 +378,17 @@ public final class CommunicationArtifactViewerHelper { "CommunicationArtifactViewerHelper_persona_button_view=View", "CommunicationArtifactViewerHelper_persona_button_create=Create" }) - static Optional addPersonaRow(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String accountIdentifier) { - - PersonaSearchAndDisplayTask personaTask = null; + + static List addPersonaRow(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String accountIdentifier) { + List dataList = new ArrayList<>(); constraints.gridy++; constraints.gridx = 1; Insets savedInsets = constraints.insets; - // Indent in - constraints.insets = new java.awt.Insets(0, LEFT_INDENT, 0, 0); + // extra Indent in + constraints.insets = new java.awt.Insets(0, 2 * LEFT_INSET, 0, 0); // create label javax.swing.JLabel personaLabel = new javax.swing.JLabel(); @@ -293,23 +411,22 @@ public final class CommunicationArtifactViewerHelper { // 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.setMargin(new Insets(0, 5, 0, 5)); 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(); + dataList.add(new AccountPersonaSearcherData(accountIdentifier, personaLabel, personaButton)); } else { personaLabel.setEnabled(false); } addLineEndGlue(panel, gridbagLayout, constraints); - return Optional.ofNullable(personaTask); + return dataList; } /** diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/ContactArtifactViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/ContactArtifactViewer.form index 467d3ae72d..806a0e3de5 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/ContactArtifactViewer.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/ContactArtifactViewer.form @@ -1,6 +1,9 @@ -
+ + + + @@ -11,217 +14,8 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/ContactArtifactViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/ContactArtifactViewer.java index 2d11fcf853..72997e9ede 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/ContactArtifactViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/ContactArtifactViewer.java @@ -19,9 +19,9 @@ package org.sleuthkit.autopsy.contentviewers; import java.awt.Component; -import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; +import java.awt.Insets; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.io.File; @@ -40,11 +40,11 @@ import javax.imageio.ImageIO; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; -import javax.swing.JPanel; import javax.swing.JScrollPane; -import org.openide.util.lookup.ServiceProvider; import javax.swing.SwingWorker; +import org.apache.commons.lang.StringUtils; import org.openide.util.NbBundle; +import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; @@ -70,11 +70,23 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac private final static Logger logger = Logger.getLogger(ContactArtifactViewer.class.getName()); private static final long serialVersionUID = 1L; - private static final int TOP_INSET = 4; - private static final int LEFT_INSET = 12; + private GridBagLayout m_gridBagLayout = new GridBagLayout(); + private GridBagConstraints m_constraints = new GridBagConstraints(); - // contact name, if available. + private JLabel personaSearchStatusLabel; + + private BlackboardArtifact contactArtifact; private String contactName; + private String datasourceName; + + private List phoneNumList = new ArrayList<>(); + private List emailList = new ArrayList<>(); + private List nameList = new ArrayList<>(); + private List otherList = new ArrayList<>(); + private List accountAttributesList = new ArrayList<>(); + + private final static String DEFAULT_IMAGE_PATH = "/org/sleuthkit/autopsy/images/defaultContact.png"; + private final ImageIcon defaultImage; // A list of unique accounts matching the attributes of the contact artifact. private final List contactUniqueAccountsList = new ArrayList<>(); @@ -83,11 +95,10 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac // account identifier attributes of the Contact artifact. private final Map> contactUniquePersonasMap = new HashMap<>(); - private final static String DEFAULT_IMAGE_PATH = "/org/sleuthkit/autopsy/images/defaultContact.png"; - private final ImageIcon defaultImage; + private ContactPersonaSearcherTask personaSearchTask; /** - * Creates new form for ContactArtifactViewer + * Creates new form ContactArtifactViewer */ public ContactArtifactViewer() { initComponents(); @@ -103,164 +114,13 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac @SuppressWarnings("unchecked") // //GEN-BEGIN:initComponents private void initComponents() { - java.awt.GridBagConstraints gridBagConstraints; - - namePanel = new javax.swing.JPanel(); - contactNameLabel = new javax.swing.JLabel(); - phonesLabel = new javax.swing.JLabel(); - phoneNumbersPanel = new javax.swing.JPanel(); - emailsLabel = new javax.swing.JLabel(); - emailsPanel = new javax.swing.JPanel(); - othersLabel = new javax.swing.JLabel(); - otherAttrsPanel = new javax.swing.JPanel(); - javax.swing.Box.Filler interPanelfiller = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767)); - personasLabel = new javax.swing.JLabel(); - personasPanel = new javax.swing.JPanel(); - javax.swing.Box.Filler bottomFiller = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767)); - javax.swing.Box.Filler rightFiller = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0)); - contactImage = new javax.swing.JLabel(); + setToolTipText(""); // NOI18N setLayout(new java.awt.GridBagLayout()); - - namePanel.setLayout(new java.awt.GridBagLayout()); - - contactNameLabel.setFont(contactNameLabel.getFont().deriveFont((contactNameLabel.getFont().getStyle() | java.awt.Font.ITALIC) | java.awt.Font.BOLD, contactNameLabel.getFont().getSize()+6)); - org.openide.awt.Mnemonics.setLocalizedText(contactNameLabel, org.openide.util.NbBundle.getMessage(ContactArtifactViewer.class, "ContactArtifactViewer.contactNameLabel.text")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 0; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.ipadx = 111; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.weightx = 1.0; - namePanel.add(contactNameLabel, gridBagConstraints); - - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 1; - gridBagConstraints.gridwidth = 5; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.weightx = 1.0; - gridBagConstraints.insets = new java.awt.Insets(6, 19, 0, 0); - add(namePanel, gridBagConstraints); - - phonesLabel.setFont(phonesLabel.getFont().deriveFont(phonesLabel.getFont().getStyle() | java.awt.Font.BOLD, phonesLabel.getFont().getSize()+2)); - org.openide.awt.Mnemonics.setLocalizedText(phonesLabel, org.openide.util.NbBundle.getMessage(ContactArtifactViewer.class, "ContactArtifactViewer.phonesLabel.text")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 2; - gridBagConstraints.gridwidth = 3; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(6, 19, 0, 0); - add(phonesLabel, gridBagConstraints); - - phoneNumbersPanel.setLayout(new java.awt.GridBagLayout()); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 3; - gridBagConstraints.gridwidth = 4; - gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(6, 19, 0, 0); - add(phoneNumbersPanel, gridBagConstraints); - - emailsLabel.setFont(emailsLabel.getFont().deriveFont(emailsLabel.getFont().getStyle() | java.awt.Font.BOLD, emailsLabel.getFont().getSize()+2)); - org.openide.awt.Mnemonics.setLocalizedText(emailsLabel, org.openide.util.NbBundle.getMessage(ContactArtifactViewer.class, "ContactArtifactViewer.emailsLabel.text")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 4; - gridBagConstraints.gridwidth = 2; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(6, 19, 0, 0); - add(emailsLabel, gridBagConstraints); - - emailsPanel.setLayout(new java.awt.GridBagLayout()); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 5; - gridBagConstraints.gridwidth = 4; - gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(6, 19, 0, 0); - add(emailsPanel, gridBagConstraints); - - othersLabel.setFont(othersLabel.getFont().deriveFont(othersLabel.getFont().getStyle() | java.awt.Font.BOLD, othersLabel.getFont().getSize()+2)); - org.openide.awt.Mnemonics.setLocalizedText(othersLabel, org.openide.util.NbBundle.getMessage(ContactArtifactViewer.class, "ContactArtifactViewer.othersLabel.text")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 6; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(6, 19, 0, 0); - add(othersLabel, gridBagConstraints); - - otherAttrsPanel.setLayout(new java.awt.GridBagLayout()); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 7; - gridBagConstraints.gridwidth = 4; - gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(6, 19, 0, 0); - add(otherAttrsPanel, gridBagConstraints); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 8; - gridBagConstraints.gridheight = 2; - gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL; - gridBagConstraints.weighty = 0.1; - add(interPanelfiller, gridBagConstraints); - - personasLabel.setFont(personasLabel.getFont().deriveFont(personasLabel.getFont().getStyle() | java.awt.Font.BOLD, personasLabel.getFont().getSize()+2)); - org.openide.awt.Mnemonics.setLocalizedText(personasLabel, org.openide.util.NbBundle.getMessage(ContactArtifactViewer.class, "ContactArtifactViewer.personasLabel.text")); // NOI18N - personasLabel.setMaximumSize(new java.awt.Dimension(90, 19)); - personasLabel.setMinimumSize(new java.awt.Dimension(90, 19)); - personasLabel.setPreferredSize(new java.awt.Dimension(90, 19)); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 9; - gridBagConstraints.gridwidth = 3; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(6, 19, 0, 0); - add(personasLabel, gridBagConstraints); - - personasPanel.setLayout(new java.awt.GridBagLayout()); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 10; - gridBagConstraints.gridwidth = 4; - gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(6, 19, 0, 0); - add(personasPanel, gridBagConstraints); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 11; - gridBagConstraints.gridwidth = 4; - gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; - gridBagConstraints.weighty = 1.0; - add(bottomFiller, gridBagConstraints); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 4; - gridBagConstraints.gridy = 3; - gridBagConstraints.gridheight = 8; - gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; - gridBagConstraints.ipadx = 2; - gridBagConstraints.weightx = 1.0; - add(rightFiller, gridBagConstraints); - - contactImage.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/defaultContact.png"))); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(contactImage, org.openide.util.NbBundle.getMessage(ContactArtifactViewer.class, "ContactArtifactViewer.contactImage.text")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 0; - gridBagConstraints.insets = new java.awt.Insets(6, 19, 0, 0); - add(contactImage, gridBagConstraints); }// //GEN-END:initComponents @Override public void setArtifact(BlackboardArtifact artifact) { - // Reset the panel. resetComponent(); @@ -268,50 +128,16 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac return; } - List phoneNumList = new ArrayList<>(); - List emailList = new ArrayList<>(); - List nameList = new ArrayList<>(); - List otherList = new ArrayList<>(); - List accountAttributesList = new ArrayList<>(); - try { - // Get all the attributes and group them by the section panels they go in - for (BlackboardAttribute bba : artifact.getAttributes()) { - if (bba.getAttributeType().getTypeName().startsWith("TSK_PHONE")) { - phoneNumList.add(bba); - accountAttributesList.add(bba); - } else if (bba.getAttributeType().getTypeName().startsWith("TSK_EMAIL")) { - emailList.add(bba); - accountAttributesList.add(bba); - } else if (bba.getAttributeType().getTypeName().startsWith("TSK_NAME")) { - nameList.add(bba); - } else { - otherList.add(bba); - if (bba.getAttributeType().getTypeName().equalsIgnoreCase("TSK_ID")) { - accountAttributesList.add(bba); - } - } - } + extractArtifactData(artifact); } catch (TskCoreException ex) { logger.log(Level.SEVERE, String.format("Error getting attributes for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex); - } - // update name section - updateNamePanel(nameList); - - // update contact attributes sections - updateSection(phoneNumList, this.phonesLabel, this.phoneNumbersPanel); - updateSection(emailList, this.emailsLabel, this.emailsPanel); - updateSection(otherList, this.othersLabel, this.otherAttrsPanel); - - try { - initiatePersonasSearch(accountAttributesList); - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, String.format("Error getting Personas for Contact artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex); + return; } - contactImage.setIcon(getImageFromArtifact(artifact)); + updateView(); - // repaint + this.setLayout(this.m_gridBagLayout); this.revalidate(); this.repaint(); } @@ -322,204 +148,254 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac return new JScrollPane(this, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); } - /** - * Checks if the given artifact is supported by this viewer. This viewer - * supports TSK_CONTACT artifacts. - * - * @param artifact artifact to check. - * - * @return True if the artifact is supported, false otherwise. - */ @Override public boolean isSupported(BlackboardArtifact artifact) { - return artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID(); + return (artifact != null) + && (artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID()); } /** - * Clears all artifact specific state. + * Extracts data from the artifact to be displayed in the panel. + * + * @param artifact Artifact to show. + * @throws TskCoreException */ - private void resetComponent() { - contactNameLabel.setVisible(false); - emailsLabel.setVisible(false); - emailsPanel.removeAll(); - //namePanel.removeAll(); // this is not dynamically populated, do not remove. - otherAttrsPanel.removeAll(); - othersLabel.setVisible(false); - personasLabel.setVisible(false); - personasPanel.removeAll(); - phoneNumbersPanel.removeAll(); - phonesLabel.setVisible(false); + private void extractArtifactData(BlackboardArtifact artifact) throws TskCoreException { - contactName = null; - contactUniqueAccountsList.clear(); - contactUniquePersonasMap.clear(); - contactImage.setIcon(defaultImage); + this.contactArtifact = artifact; + + phoneNumList = new ArrayList<>(); + emailList = new ArrayList<>(); + nameList = new ArrayList<>(); + otherList = new ArrayList<>(); + accountAttributesList = new ArrayList<>(); + + // Get all the attributes and group them by the section panels they go in + for (BlackboardAttribute bba : contactArtifact.getAttributes()) { + if (bba.getAttributeType().getTypeName().startsWith("TSK_PHONE")) { + phoneNumList.add(bba); + accountAttributesList.add(bba); + } else if (bba.getAttributeType().getTypeName().startsWith("TSK_EMAIL")) { + emailList.add(bba); + accountAttributesList.add(bba); + } else if (bba.getAttributeType().getTypeName().startsWith("TSK_NAME")) { + nameList.add(bba); + } else { + otherList.add(bba); + if (bba.getAttributeType().getTypeName().equalsIgnoreCase("TSK_ID")) { + accountAttributesList.add(bba); + } + } + } + + datasourceName = contactArtifact.getDataSource().getName(); + } + + /** + * Updates the view with the data extracted from the artifact. + */ + private void updateView() { + + // Update contact name, image, phone numbers + updateContactDetails(); + + // update artifact source panel + updateSource(); + + // show a empty Personas panel and kick off a serch for personas + initiatePersonasSearch(); + + } + + /** + * Updates the view with contact's details. + */ + @NbBundle.Messages({ + "ContactArtifactViewer_phones_header=Phone", + "ContactArtifactViewer_emails_header=Email", + "ContactArtifactViewer_others_header=Other",}) + private void updateContactDetails() { + + // update image and name. + updateContactImage(m_gridBagLayout, m_constraints); + updateContactName(m_gridBagLayout, m_constraints); + + // update contact attributes sections + updateContactMethodSection(phoneNumList, Bundle.ContactArtifactViewer_phones_header(), m_gridBagLayout, m_constraints); + updateContactMethodSection(emailList, Bundle.ContactArtifactViewer_emails_header(), m_gridBagLayout, m_constraints); + updateContactMethodSection(otherList, Bundle.ContactArtifactViewer_others_header(), m_gridBagLayout, m_constraints); + } + + /** + * Updates the contact image in the view. + * + * @param contactPanelLayout Panel layout. + * @param contactPanelConstraints Layout constraints. + * + */ + @NbBundle.Messages({ + "ContactArtifactViewer.contactImage.text=",}) + private void updateContactImage(GridBagLayout contactPanelLayout, GridBagConstraints contactPanelConstraints) { + // place the image on the top right corner + Insets savedInsets = contactPanelConstraints.insets; + contactPanelConstraints.gridy = 0; + contactPanelConstraints.gridx = 0; + contactPanelConstraints.insets = new Insets(0, 0, 0, 0); + + javax.swing.JLabel contactImage = new javax.swing.JLabel(); + contactImage.setIcon(getImageFromArtifact(contactArtifact)); + contactImage.setText(Bundle.ContactArtifactViewer_contactImage_text()); + + // add image to top left corner of the page. + CommunicationArtifactViewerHelper.addComponent(this, contactPanelLayout, contactPanelConstraints, contactImage); + CommunicationArtifactViewerHelper.addLineEndGlue(this, contactPanelLayout, contactPanelConstraints); + contactPanelConstraints.gridy++; + + contactPanelConstraints.insets = savedInsets; } /** * Updates the contact name in the view. * - * @param attributesList + * @param contactPanelLayout Panel layout. + * @param contactPanelConstraints Layout constraints. + * */ - private void updateNamePanel(List attributesList) { - for (BlackboardAttribute bba : attributesList) { - if (bba.getAttributeType().getTypeName().startsWith("TSK_NAME")) { + @NbBundle.Messages({ + "ContactArtifactViewer_contactname_unknown=Unknown",}) + private void updateContactName(GridBagLayout contactPanelLayout, GridBagConstraints contactPanelConstraints) { + + boolean foundName = false; + for (BlackboardAttribute bba : this.nameList) { + if (StringUtils.isEmpty(bba.getValueString()) == false) { contactName = bba.getDisplayString(); - contactNameLabel.setText(contactName); - contactNameLabel.setVisible(true); + + CommunicationArtifactViewerHelper.addHeader(this, contactPanelLayout, contactPanelConstraints, contactName); + foundName = true; break; } } - - contactNameLabel.revalidate(); + if (foundName == false) { + CommunicationArtifactViewerHelper.addHeader(this, contactPanelLayout, contactPanelConstraints, Bundle.ContactArtifactViewer_contactname_unknown()); + } } /** * Updates the view by displaying the given list of attributes in the given * section panel. * - * @param sectionAttributesList list of attributes to display. - * @param sectionLabel section name label. - * @param sectionPanel section panel to display the attributes in. + * @param sectionAttributesList List of attributes to display. + * @param sectionLabel Section name label. + * @param contactPanelLayout Panel layout. + * @param contactPanelConstraints Layout constraints. + * */ - private void updateSection(List sectionAttributesList, JLabel sectionLabel, JPanel sectionPanel) { + @NbBundle.Messages({ + "ContactArtifactViewer_plural_suffix=s",}) + private void updateContactMethodSection(List sectionAttributesList, String sectionHeader, GridBagLayout contactPanelLayout, GridBagConstraints contactPanelConstraints) { - // If there are no attributes for tis section, hide the section panel and the section label + // If there are no attributes for this section, do nothing if (sectionAttributesList.isEmpty()) { - sectionLabel.setVisible(false); - sectionPanel.setVisible(false); return; } - // create a gridbag layout to show each attribute on one line - GridBagLayout gridBagLayout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.anchor = GridBagConstraints.FIRST_LINE_START; - constraints.gridy = 0; - constraints.insets = new java.awt.Insets(TOP_INSET, LEFT_INSET, 0, 0); - for (BlackboardAttribute bba : sectionAttributesList) { - constraints.fill = GridBagConstraints.NONE; - constraints.weightx = 0; - - constraints.gridx = 0; - - // Add a label for attribute type - javax.swing.JLabel attrTypeLabel = new javax.swing.JLabel(); - String attrLabel = bba.getAttributeType().getDisplayName(); - attrTypeLabel.setText(attrLabel); - - // make type label bold - uncomment if needed. - //attrTypeLabel.setFont(attrTypeLabel.getFont().deriveFont(Font.BOLD, attrTypeLabel.getFont().getSize() )); - gridBagLayout.setConstraints(attrTypeLabel, constraints); - sectionPanel.add(attrTypeLabel); - - // Add the attribute value - constraints.gridx++; - javax.swing.JLabel attrValueLabel = new javax.swing.JLabel(); - attrValueLabel.setText(bba.getValueString()); - gridBagLayout.setConstraints(attrValueLabel, constraints); - sectionPanel.add(attrValueLabel); - - // add a filler to take up rest of the space - constraints.gridx++; - constraints.weightx = 1.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - sectionPanel.add(new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0))); - - constraints.gridy++; + String sectionHeaderString = sectionHeader; + if (sectionAttributesList.size() > 1) { + sectionHeaderString = sectionHeaderString.concat(Bundle.ContactArtifactViewer_plural_suffix()); + } + CommunicationArtifactViewerHelper.addHeader(this, contactPanelLayout, contactPanelConstraints, sectionHeaderString); + for (BlackboardAttribute bba : sectionAttributesList) { + CommunicationArtifactViewerHelper.addKey(this, contactPanelLayout, contactPanelConstraints, bba.getAttributeType().getDisplayName()); + CommunicationArtifactViewerHelper.addValue(this, contactPanelLayout, contactPanelConstraints, bba.getDisplayString()); } - - sectionLabel.setVisible(true); - sectionPanel.setVisible(true); - - sectionPanel.setLayout(gridBagLayout); - sectionPanel.revalidate(); - sectionPanel.repaint(); } /** - * Kicks off a search for personas, based in the list of attributes. + * Updates the source section. + */ + @NbBundle.Messages({ + "ContactArtifactViewer_heading_Source=Source", + "ContactArtifactViewer_label_datasource=Data Source",}) + private void updateSource() { + CommunicationArtifactViewerHelper.addHeader(this, this.m_gridBagLayout, m_constraints, Bundle.ContactArtifactViewer_heading_Source()); + CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, m_constraints, Bundle.ContactArtifactViewer_label_datasource()); + CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, m_constraints, datasourceName); + } + + /** + * Kicks off a search for personas, based in the given list of attributes. * * @param accountAttributesList a list of account identifying attributes. * * @throws CentralRepoException */ @NbBundle.Messages({ - "ContactArtifactViewer_persona_searching= Searching...", + "ContactArtifactViewer_persona_header=Persona", + "ContactArtifactViewer_persona_searching=Searching...", + "ContactArtifactViewer_cr_disabled_message=Enable Central Repository to view, create and edit personas.", "ContactArtifactViewer_persona_unknown=Unknown" }) - private void initiatePersonasSearch(List accountAttributesList) throws CentralRepoException { - personasLabel.setVisible(true); + /** + * Initiates a search for Personas for the accounts associated with the + * Contact. + * + */ + private void initiatePersonasSearch() { - String personaStatusLabelText = CentralRepository.isEnabled() - ? Bundle.ContactArtifactViewer_persona_searching() + // add a section header + JLabel personaHeader = CommunicationArtifactViewerHelper.addHeader(this, m_gridBagLayout, m_constraints, Bundle.ContactArtifactViewer_persona_header()); + + m_constraints.gridy++; + + // add a status label + String personaStatusLabelText = CentralRepository.isEnabled() + ? Bundle.ContactArtifactViewer_persona_searching() : Bundle.ContactArtifactViewer_persona_unknown(); - - // create a gridbag layout to show each participant on one line - GridBagLayout gridBagLayout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.anchor = GridBagConstraints.FIRST_LINE_START; - constraints.gridx = 0; - constraints.gridy = 0; - constraints.insets = new java.awt.Insets(TOP_INSET, LEFT_INSET, 0, 0); - // Add a Persona Name label - constraints.fill = GridBagConstraints.NONE; - constraints.weightx = 0; + this.personaSearchStatusLabel = new javax.swing.JLabel(); + personaSearchStatusLabel.setText(personaStatusLabelText); - //javax.swing.Box.Filler filler1 = this.createFiller(5, 0); - //personasPanel.add(filler1, constraints); - javax.swing.JLabel personaLabel = new javax.swing.JLabel(); - personaLabel.setText(Bundle.ContactArtifactViewer_persona_label()); - personaLabel.setFont(personaLabel.getFont().deriveFont(Font.BOLD, personaLabel.getFont().getSize())); - gridBagLayout.setConstraints(personaLabel, constraints); - personasPanel.add(personaLabel); + m_constraints.gridx = 0; - constraints.gridy++; - javax.swing.JLabel personaStatusLabel = new javax.swing.JLabel(); - personaStatusLabel.setText(personaStatusLabelText); - gridBagLayout.setConstraints(personaStatusLabel, constraints); - personasPanel.add(personaStatusLabel); + CommunicationArtifactViewerHelper.addComponent(this, m_gridBagLayout, m_constraints, personaSearchStatusLabel); - - if (CentralRepository.isEnabled() ) { - personasLabel.setEnabled(true); - + if (CentralRepository.isEnabled()) { // Kick off a background task to serach for personas for the contact - ContactPersonaSearcherTask personaSearchTask = new ContactPersonaSearcherTask(accountAttributesList); + personaSearchTask = new ContactPersonaSearcherTask(accountAttributesList); personaSearchTask.execute(); } else { - personasLabel.setEnabled(false); - personaLabel.setEnabled(false); - personaStatusLabel.setEnabled(false); + personaHeader.setEnabled(false); + personaSearchStatusLabel.setEnabled(false); + + CommunicationArtifactViewerHelper.addBlankLine(this, m_gridBagLayout, m_constraints); + m_constraints.gridy++; + CommunicationArtifactViewerHelper.addMessageRow(this, m_gridBagLayout, m_constraints, Bundle.ContactArtifactViewer_cr_disabled_message()); + m_constraints.gridy++; + + CommunicationArtifactViewerHelper.addPageEndGlue(this, m_gridBagLayout, this.m_constraints); } - personasPanel.setLayout(gridBagLayout); - personasPanel.revalidate(); - personasPanel.repaint(); } /** * Updates the Persona panel with the gathered persona information. */ - private void updatePersonasPanel() { - // Clear out the panel - personasPanel.removeAll(); + private void updatePersonas() { - GridBagLayout gridBagLayout = new GridBagLayout(); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.anchor = GridBagConstraints.FIRST_LINE_START; - constraints.gridx = 0; - constraints.gridy = 0; - constraints.insets = new java.awt.Insets(TOP_INSET, LEFT_INSET, 0, 0); + // Remove the "Searching....." label + this.remove(personaSearchStatusLabel); + m_constraints.gridx = 0; if (contactUniquePersonasMap.isEmpty()) { - showPersona(null, Collections.emptyList(), gridBagLayout, constraints); + // No persona found - show a button to create one. + showPersona(null, 0, Collections.emptyList(), this.m_gridBagLayout, this.m_constraints); } else { + int matchCounter = 0; for (Map.Entry> entry : contactUniquePersonasMap.entrySet()) { List missingAccounts = new ArrayList<>(); ArrayList personaAccounts = entry.getValue(); + matchCounter++; // create a list of accounts missing from this persona for (CentralRepoAccount account : contactUniqueAccountsList) { @@ -528,52 +404,51 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac } } - showPersona(entry.getKey(), missingAccounts, gridBagLayout, constraints); - - constraints.gridy += 2; + showPersona(entry.getKey(), matchCounter, missingAccounts, m_gridBagLayout, m_constraints); + m_constraints.gridy += 2; } } - personasPanel.setLayout(gridBagLayout); - personasPanel.setSize(personasPanel.getPreferredSize()); - personasPanel.revalidate(); - personasPanel.repaint(); - } + // add veritcal glue at the end + CommunicationArtifactViewerHelper.addPageEndGlue(this, m_gridBagLayout, this.m_constraints); - @NbBundle.Messages({ - "ContactArtifactViewer_persona_label=Persona ", - "ContactArtifactViewer_persona_text_none=None found", - "ContactArtifactViewer_persona_button_view=View", - "ContactArtifactViewer_persona_button_new=Create", - "ContactArtifactViewer_missing_account_label=Missing Account: " - }) + // redraw the panel + this.setLayout(this.m_gridBagLayout); + this.revalidate(); + this.repaint(); + } /** * Displays the given persona in the persona panel. * - * @param persona Persona to display. - * @param missingAccountsList List of accounts this persona may be missing. - * @param gridBagLayout Layout to use. - * @param constraints layout constraints. + * @param persona Persona to display. + * @param missingAccountsList List of contact accounts this persona may be + * missing. + * @param gridBagLayout Layout to use. + * @param constraints layout constraints. * * @throws CentralRepoException */ - private void showPersona(Persona persona, List missingAccountsList, GridBagLayout gridBagLayout, GridBagConstraints constraints) { + @NbBundle.Messages({ + "ContactArtifactViewer_persona_label=Persona ", + "ContactArtifactViewer_persona_no_match=No matches found", + "ContactArtifactViewer_persona_button_view=View", + "ContactArtifactViewer_persona_button_new=Create", + "ContactArtifactViewer_persona_match_num=Match ", + "ContactArtifactViewer_missing_account_label=Missing contact account", + "ContactArtifactViewer_found_all_accounts_label=All accounts found." + }) + private void showPersona(Persona persona, int matchNumber, List missingAccountsList, GridBagLayout gridBagLayout, GridBagConstraints constraints) { - constraints.fill = GridBagConstraints.NONE; - constraints.weightx = 0; + // save the original insets + Insets savedInsets = constraints.insets; + + // some label are indented 2x to appear indented w.r.t column above + Insets extraIndentInsets = new java.awt.Insets(0, 2 * CommunicationArtifactViewerHelper.LEFT_INSET, 0, 0); + + // Add a Match X label in col 0. constraints.gridx = 0; - - //javax.swing.Box.Filler filler1 = createFiller(5, 0); - // gridBagLayout.setConstraints(filler1, constraints); - //personasPanel.add(filler1); - // Add a "Persona" label - //constraints.gridx++; - javax.swing.JLabel personaLabel = new javax.swing.JLabel(); - personaLabel.setText(Bundle.ContactArtifactViewer_persona_label()); - personaLabel.setFont(personaLabel.getFont().deriveFont(Font.BOLD, personaLabel.getFont().getSize())); - gridBagLayout.setConstraints(personaLabel, constraints); - personasPanel.add(personaLabel); + javax.swing.JLabel matchNumberLabel = CommunicationArtifactViewerHelper.addKey(this, gridBagLayout, constraints, String.format("%s %d", Bundle.ContactArtifactViewer_persona_match_num(), matchNumber)); javax.swing.JLabel personaNameLabel = new javax.swing.JLabel(); javax.swing.JButton personaButton = new javax.swing.JButton(); @@ -581,59 +456,110 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac String personaName; String personaButtonText; ActionListener personaButtonListener; - if (persona != null) { personaName = persona.getName(); personaButtonText = Bundle.ContactArtifactViewer_persona_button_view(); - personaButtonListener = new ViewPersonaButtonListener(persona); + personaButtonListener = new ViewPersonaButtonListener(this, persona); } else { - personaName = Bundle.ContactArtifactViewer_persona_text_none(); + matchNumberLabel.setVisible(false); + personaName = Bundle.ContactArtifactViewer_persona_no_match(); personaButtonText = Bundle.ContactArtifactViewer_persona_button_new(); - personaButtonListener = new CreatePersonaButtonListener(new PersonaUIComponents(personaNameLabel, personaButton)); + personaButtonListener = new CreatePersonaButtonListener(this, new PersonaUIComponents(personaNameLabel, personaButton)); } - // Add the label for persona name, - constraints.gridy++; - constraints.gridx = 0; + //constraints.gridwidth = 1; // TBD: this may not be needed if we use single panel + constraints.gridx++; personaNameLabel.setText(personaName); gridBagLayout.setConstraints(personaNameLabel, constraints); - personasPanel.add(personaNameLabel); + CommunicationArtifactViewerHelper.addComponent(this, gridBagLayout, constraints, personaNameLabel); + //personasPanel.add(personaNameLabel); - //constraints.gridx++; - //personasPanel.add(createFiller(5, 0), constraints); // Add a Persona action button constraints.gridx++; + //constraints.gridwidth = 1; personaButton.setText(personaButtonText); personaButton.addActionListener(personaButtonListener); - // no top inset of the button, in order to center align with the labels. - constraints.insets = new java.awt.Insets(0, LEFT_INSET, 0, 0); + // Shirnk the button height. + personaButton.setMargin(new Insets(0, 5, 0, 5)); gridBagLayout.setConstraints(personaButton, constraints); - personasPanel.add(personaButton); + CommunicationArtifactViewerHelper.addComponent(this, gridBagLayout, constraints, personaButton); + CommunicationArtifactViewerHelper.addLineEndGlue(this, gridBagLayout, constraints); - // restore normal inset - constraints.insets = new java.awt.Insets(TOP_INSET, LEFT_INSET, 0, 0); + constraints.insets = savedInsets; - // show missing accounts. - for (CentralRepoAccount missingAccount : missingAccountsList) { - constraints.weightx = 0; - constraints.gridx = 0; - constraints.gridy++; + // if we have a persona, indicate if any of the contact's accounts are missing from it. + if (persona != null) { + if (missingAccountsList.isEmpty()) { + constraints.gridy++; + constraints.gridx = 1; + //constraints.insets = labelInsets; - // Add a "Missing Account: " label - constraints.gridx++; // Ident - javax.swing.JLabel missingAccountLabel = new javax.swing.JLabel(); - missingAccountLabel.setText(Bundle.ContactArtifactViewer_missing_account_label()); - gridBagLayout.setConstraints(missingAccountLabel, constraints); - personasPanel.add(missingAccountLabel); + javax.swing.JLabel accountsStatus = new javax.swing.JLabel(Bundle.ContactArtifactViewer_found_all_accounts_label()); + constraints.insets = extraIndentInsets; + CommunicationArtifactViewerHelper.addComponent(this, gridBagLayout, constraints, accountsStatus); + constraints.insets = savedInsets; - // Add the label for account id, - constraints.gridx++; - javax.swing.JLabel missingAccountIdentifierLabel = new javax.swing.JLabel(); - missingAccountIdentifierLabel.setText(missingAccount.getIdentifier()); - gridBagLayout.setConstraints(missingAccountIdentifierLabel, constraints); - personasPanel.add(missingAccountIdentifierLabel); + CommunicationArtifactViewerHelper.addLineEndGlue(this, gridBagLayout, constraints); + } else { + // show missing accounts. + for (CentralRepoAccount missingAccount : missingAccountsList) { + //constraints.weightx = 0; + constraints.gridx = 0; + constraints.gridy++; + + // this needs an extra indent + constraints.insets = extraIndentInsets; + CommunicationArtifactViewerHelper.addKeyAtCol(this, gridBagLayout, constraints, Bundle.ContactArtifactViewer_missing_account_label(), 1); + constraints.insets = savedInsets; + + CommunicationArtifactViewerHelper.addValueAtCol(this, gridBagLayout, constraints, missingAccount.getIdentifier(), 2); + } + } } + + // restore insets + constraints.insets = savedInsets; + } + + /** + * Resets all artifact specific state. + */ + private void resetComponent() { + + contactArtifact = null; + contactName = null; + datasourceName = null; + + contactUniqueAccountsList.clear(); + contactUniquePersonasMap.clear(); + + phoneNumList.clear(); + emailList.clear(); + nameList.clear(); + otherList.clear(); + accountAttributesList.clear(); + + if (personaSearchTask != null) { + personaSearchTask.cancel(Boolean.TRUE); + personaSearchTask = null; + } + + // 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.0; + m_constraints.weightx = 0.0; // keep components fixed horizontally. + m_constraints.insets = new java.awt.Insets(0, CommunicationArtifactViewerHelper.LEFT_INSET, 0, 0); + m_constraints.fill = GridBagConstraints.NONE; + } /** @@ -642,7 +568,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac * @param artifact * * @return Image from a TSK_CONTACT artifact or default image if none was - * found or the artifact is not a TSK_CONTACT + * found or the artifact is not a TSK_CONTACT */ private ImageIcon getImageFromArtifact(BlackboardArtifact artifact) { ImageIcon imageIcon = defaultImage; @@ -666,7 +592,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac imageIcon = new ImageIcon(image); break; } catch (IOException ex) { - // ImageIO.read will through an IOException if file is not an image + // ImageIO.read will throw an IOException if file is not an image // therefore we don't need to report this exception just try // the next file. } @@ -692,7 +618,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac * Creates a persona searcher task. * * @param accountAttributesList List of attributes that may map to - * accounts. + * accounts. */ ContactPersonaSearcherTask(List accountAttributesList) { this.accountAttributesList = accountAttributesList; @@ -703,6 +629,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac Map> uniquePersonas = new HashMap<>(); + // TBD: this search needs to change to use the new method CommunicationsManager.getAccountsRelatedToArtifact for (BlackboardAttribute bba : accountAttributesList) { // Get account, add to accounts list @@ -736,7 +663,6 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac } } } - } return uniquePersonas; @@ -758,7 +684,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac contactUniqueAccountsList.clear(); contactUniqueAccountsList.addAll(uniqueAccountsList); - updatePersonasPanel(); + updatePersonas(); } catch (CancellationException ex) { logger.log(Level.INFO, "Persona searching was canceled."); //NON-NLS @@ -783,7 +709,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac /** * Constructor. * - * @param personaNameLabel Persona name label. + * @param personaNameLabel Persona name label. * @param personaActionButton Persona action button. */ PersonaUIComponents(JLabel personaNameLabel, JButton personaActionButton) { @@ -815,6 +741,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac */ private class CreatePersonaButtonListener implements ActionListener { + private final Component parentComponent; private final PersonaUIComponents personaUIComponents; /** @@ -822,8 +749,9 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac * * @param personaUIComponents UI components. */ - CreatePersonaButtonListener(PersonaUIComponents personaUIComponents) { + CreatePersonaButtonListener(Component parentComponent, PersonaUIComponents personaUIComponents) { this.personaUIComponents = personaUIComponents; + this.parentComponent = parentComponent; } @NbBundle.Messages({ @@ -833,8 +761,8 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac @Override public void actionPerformed(java.awt.event.ActionEvent evt) { // Launch the Persona Create dialog - do not display immediately - PersonaDetailsDialog createPersonaDialog = new PersonaDetailsDialog(ContactArtifactViewer.this, - PersonaDetailsMode.CREATE, null, new PersonaCreateCallbackImpl(personaUIComponents), false); + PersonaDetailsDialog createPersonaDialog = new PersonaDetailsDialog(parentComponent, + PersonaDetailsMode.CREATE, null, new PersonaCreateCallbackImpl(parentComponent, personaUIComponents), false); // Pre populate the persona name and accounts if we have them. PersonaDetailsPanel personaPanel = createPersonaDialog.getDetailsPanel(); @@ -859,19 +787,21 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac private class ViewPersonaButtonListener implements ActionListener { private final Persona persona; + private final Component parentComponent; /** * Creates listener for View persona button. * * @param persona */ - ViewPersonaButtonListener(Persona persona) { + ViewPersonaButtonListener(Component parentComponent, Persona persona) { this.persona = persona; + this.parentComponent = parentComponent; } @Override public void actionPerformed(java.awt.event.ActionEvent evt) { - new PersonaDetailsDialog(ContactArtifactViewer.this, + new PersonaDetailsDialog(parentComponent, PersonaDetailsMode.VIEW, persona, new PersonaViewCallbackImpl()); } } @@ -881,6 +811,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac */ class PersonaCreateCallbackImpl implements PersonaDetailsDialogCallback { + private final Component parentComponent; private final PersonaUIComponents personaUIComponents; /** @@ -888,7 +819,8 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac * * @param personaUIComponents UI Components. */ - PersonaCreateCallbackImpl(PersonaUIComponents personaUIComponents) { + PersonaCreateCallbackImpl(Component parentComponent, PersonaUIComponents personaUIComponents) { + this.parentComponent = parentComponent; this.personaUIComponents = personaUIComponents; } @@ -905,7 +837,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac for (ActionListener act : personaButton.getActionListeners()) { personaButton.removeActionListener(act); } - personaButton.addActionListener(new ViewPersonaButtonListener(persona)); + personaButton.addActionListener(new ViewPersonaButtonListener(parentComponent, persona)); } @@ -925,18 +857,6 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac } } - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JLabel contactImage; - private javax.swing.JLabel contactNameLabel; - private javax.swing.JLabel emailsLabel; - private javax.swing.JPanel emailsPanel; - private javax.swing.JPanel namePanel; - private javax.swing.JPanel otherAttrsPanel; - private javax.swing.JLabel othersLabel; - private javax.swing.JLabel personasLabel; - private javax.swing.JPanel personasPanel; - private javax.swing.JPanel phoneNumbersPanel; - private javax.swing.JLabel phonesLabel; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/PersonaSearchAndDisplayTask.java b/Core/src/org/sleuthkit/autopsy/contentviewers/PersonaAccountFetcher.java old mode 100644 new mode 100755 similarity index 50% rename from Core/src/org/sleuthkit/autopsy/contentviewers/PersonaSearchAndDisplayTask.java rename to Core/src/org/sleuthkit/autopsy/contentviewers/PersonaAccountFetcher.java index f42fea1002..3450c91b9e --- a/Core/src/org/sleuthkit/autopsy/contentviewers/PersonaSearchAndDisplayTask.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/PersonaAccountFetcher.java @@ -1,4 +1,4 @@ -/** +/* * Autopsy Forensic Browser * * Copyright 2020 Basis Technology Corp. @@ -22,116 +22,138 @@ import java.awt.Component; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; 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.casemodule.Case; 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; +import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.CommunicationsManager; /** - * 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. - * - */ + * SwingWorker for fetching and updating Persona controls. + */ +class PersonaAccountFetcher extends SwingWorker>, Void> { - @NbBundle.Messages({ - "# {0} - Persona count", - "PersonaDisplayTask_persona_count_suffix=(1 of {0})" - }) -class PersonaSearchAndDisplayTask extends SwingWorker, Void> { + private final static Logger logger = Logger.getLogger(PersonaAccountFetcher.class.getName()); - private final static Logger logger = Logger.getLogger(PersonaSearchAndDisplayTask.class.getName()); - - private final Component parentComponent; - private final AccountPersonaSearcherData personaSearcherData; + private final BlackboardArtifact artifact; + private final List personaSearchDataList; + private final Component parentComponent; - PersonaSearchAndDisplayTask(Component parentComponent, AccountPersonaSearcherData personaSearcherData) { - this.parentComponent = parentComponent; - this.personaSearcherData = personaSearcherData; - } + /** + * Construct the SwingWorker. + * + * @param artifact The artifact to search account for. + * @param personaSearchDataList List of PersonaSerarcherData objects. + * @param parentComponent The parent panel. + */ + PersonaAccountFetcher(BlackboardArtifact artifact, List personaSearchDataList, Component parentComponent) { + this.artifact = artifact; + this.personaSearchDataList = personaSearchDataList; + this.parentComponent = parentComponent; + } - @Override - protected Collection doInBackground() throws Exception { + @Override + protected Map> doInBackground() throws Exception { + Map> accountMap = new HashMap<>(); - Collection personas = new ArrayList<>(); + CommunicationsManager commManager = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager(); - if (CentralRepository.isEnabled()) { - Collection accountCandidates - = CentralRepoAccount.getAccountsWithIdentifier(personaSearcherData.getAccountIdentifer()); + List relatedAccountList = commManager.getAccountsRelatedToArtifact(artifact); - if (accountCandidates.isEmpty() == false) { - CentralRepoAccount account = accountCandidates.iterator().next(); + for (Account account : relatedAccountList) { - // get personas for the account - Collection personaAccountsList = PersonaAccount.getPersonaAccountsForAccount(account.getId()); - personas = personaAccountsList.stream().map(PersonaAccount::getPersona) - .collect(Collectors.toList()); - } - } - return personas; - } - - @Override - protected void done() { - Collection 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 + if (isCancelled()) { + return new HashMap<>(); } + Collection personaAccountList = PersonaAccount.getPersonaAccountsForAccount(account); + Collection personaList = new ArrayList<>(); + for (PersonaAccount pAccount : personaAccountList) { + personaList.add(pAccount.getPersona()); + } + + accountMap.put(account.getTypeSpecificID(), personaList); } - + + return accountMap; + } + + @Override + protected void done() { + if (isCancelled()) { + return; + } + + try { + Map> accountMap = get(); + + for (AccountPersonaSearcherData searcherData : personaSearchDataList) { + Collection persona = accountMap.get(searcherData.getAccountIdentifer()); + updatePersonaControls(searcherData, persona); + } + + } 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 + } + + parentComponent.repaint(); + } + + /** + * Update the Persona gui controls. + * + * @param personaSearcherData The data objects with persona controls + * @param personas Collection of persona objects + */ + private void updatePersonaControls(AccountPersonaSearcherData personaSearcherData, Collection personas) { + //Update the Persona label and button based on the search result + String personaLabelText = Bundle.CommunicationArtifactViewerHelper_persona_label(); + String personaButtonText; + ActionListener buttonActionListener; + + if (personas == null || 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); + } + /** * Action listener for Create persona button. */ @@ -177,7 +199,7 @@ class PersonaSearchAndDisplayTask extends SwingWorker, Void> * Callback method for the create mode of the PersonaDetailsDialog */ class PersonaCreateCallbackImpl implements PersonaDetailsDialogCallback { - + private final Component parentComponent; private final AccountPersonaSearcherData personaSearcherData; @@ -217,4 +239,5 @@ class PersonaSearchAndDisplayTask extends SwingWorker, Void> // nothing to do } } + } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentUtils.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentUtils.java index c44289491e..34ebfac8a6 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentUtils.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentUtils.java @@ -290,6 +290,62 @@ public final class ContentUtils { return totalRead; } + /** + * Reads all the data from any content object and writes (extracts) it to a + * file, using a cancellation check instead of a Future object method. + * + * @param content Any content object. + * @param outputFile Will be created if it doesn't exist, and overwritten + * if it does + * @param cancelCheck A function used to check if the file write process + * should be terminated. + * @param startingOffset the starting offset to start reading the file + * @param endingOffset the ending offset to read of the file to write + * + * @return number of bytes extracted + * + * @throws IOException if file could not be written + */ + public static long writeToFile(Content content, java.io.File outputFile, + Supplier cancelCheck, long startingOffset, long endingOffset) throws IOException { + + InputStream in = new ReadContentInputStream(content); + long totalRead = 0; + try (FileOutputStream out = new FileOutputStream(outputFile, false)) { + long offsetSkipped = in.skip(startingOffset); + if (offsetSkipped != startingOffset) { + in.close(); + throw new IOException(String.format("Skipping file to starting offset {0} was not successful only skipped to offset {1}.", startingOffset, offsetSkipped)); + } + byte[] buffer = new byte[TO_FILE_BUFFER_SIZE]; + int len = in.read(buffer); + long writeFileLength = endingOffset - startingOffset; + writeFileLength = writeFileLength - TO_FILE_BUFFER_SIZE; + while (len != -1 && writeFileLength != 0) { + out.write(buffer, 0, len); + totalRead += len; + if (cancelCheck.get()) { + break; + } + if (writeFileLength > TO_FILE_BUFFER_SIZE) { + len = in.read(buffer); + writeFileLength = writeFileLength - TO_FILE_BUFFER_SIZE; + } else { + int writeLength = (int)writeFileLength; + byte[] lastBuffer = new byte[writeLength]; + len = in.read(lastBuffer); + out.write(lastBuffer, 0, len); + totalRead += len; + writeFileLength = 0; + } + } + + } finally { + in.close(); + } + return totalRead; + } + /** * Helper to ignore the '.' and '..' directories * diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index 6b41d849d8..9bc02f491c 100755 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -777,10 +777,6 @@ public class TimeLineController { case CONTENT_TAG_DELETED: future = executor.submit(() -> filteredEvents.handleContentTagDeleted((ContentTagDeletedEvent) evt)); break; - case CURRENT_CASE: - //close timeline on case changes. - SwingUtilities.invokeLater(TimeLineController.this::shutDownTimeLine); - break; case DATA_SOURCE_ADDED: future = executor.submit(() -> { filteredEvents.handleDataSourceAdded(); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineModule.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineModule.java index 96cb9845c4..a2261b07b0 100755 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineModule.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineModule.java @@ -86,19 +86,13 @@ public class TimeLineModule { @Override public void propertyChange(PropertyChangeEvent evt) { - try { - getController().handleCaseEvent(evt); - } catch (NoCurrentCaseException ex) { - // ignore - return; - } catch (TskCoreException ex) { - MessageNotifyUtil.Message.error("Error creating timeline controller."); - logger.log(Level.SEVERE, "Error creating timeline controller", ex); - } - if (Case.Events.valueOf(evt.getPropertyName()).equals(CURRENT_CASE)) { - // we care only about case closing here if (evt.getNewValue() == null) { + /* + * Current case is closing, shut down the timeline top + * component and set the pre case singleton controller + * reference to null. + */ synchronized (controllerLock) { if (controller != null) { SwingUtilities.invokeLater(controller::shutDownTimeLine); @@ -106,6 +100,13 @@ public class TimeLineModule { controller = null; } } + } else { + try { + getController().handleCaseEvent(evt); + } catch (NoCurrentCaseException ignored) { + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error handling application event", ex); + } } } } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED index 26823e1150..5fcd412743 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED @@ -55,6 +55,7 @@ ExtractOs.windowsVolume.label=OS Drive (Windows) ExtractOs.yellowDogLinuxOs.label=Linux (Yellow Dog) ExtractOs.yellowDogLinuxVolume.label=OS Drive (Linux Yellow Dog) ExtractOS_progressMessage=Checking for OS +ExtractPrefetch_module_name=Windows Prefetch Extractor ExtractRecycleBin_module_name=Recycle Bin ExtractSafari_Error_Getting_History=An error occurred while processing Safari history files. ExtractSafari_Error_Parsing_Bookmark=An error occured while processing Safari Bookmark files diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java new file mode 100644 index 0000000000..1e6fe08d02 --- /dev/null +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java @@ -0,0 +1,388 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * + * + * 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.recentactivity; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.logging.Level; +import org.openide.modules.InstalledFileLocator; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.services.FileManager; +import org.sleuthkit.autopsy.coreutils.ExecUtil; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.coreutils.SQLiteDBConnect; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProcessTerminator; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Blackboard; +import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT; +import org.sleuthkit.datamodel.BlackboardAttribute; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Extract the Prefetch Files and process them thru an External program. The data will then be added to the + * TSK_PROG_RUN artifact. Associated artifacts will be created if possible. + */ +final class ExtractPrefetch extends Extract { + + private static final Logger logger = Logger.getLogger(ExtractPrefetch.class.getName()); + + private IngestJobContext context; + + + private static final String MODULE_NAME = "extractPREFETCH"; //NON-NLS + + private static final String PREFETCH_TSK_COMMENT = "Prefetch File"; + private static final String PREFETCH_FILE_LOCATION = "/Windows/Prefetch"; + private static final String PREFETCH_TOOL_FOLDER = "markmckinnon"; //NON-NLS + private static final String PREFETCH_TOOL_NAME_WINDOWS_64 = "parse_prefetch_x64.exe"; //NON-NLS + private static final String PREFETCH_TOOL_NAME_WINDOWS_32 = "parse_prefetch_x32.exe"; //NON-NLS + private static final String PREFETCH_TOOL_NAME_MACOS = "parse_prefetch_macos"; //NON-NLS + private static final String PREFETCH_TOOL_NAME_LINUX = "parse_prefetch_linux"; //NON-NLS + private static final String PREFETCH_OUTPUT_FILE_NAME = "Output.txt"; //NON-NLS + private static final String PREFETCH_ERROR_FILE_NAME = "Error.txt"; //NON-NLS + private static final String PREFETCH_PARSER_DB_FILE = "Autopsy_PF_DB.db3"; //NON-NLS + private static final String PREFETCH_DIR_NAME = "prefetch"; //NON-NLS + + @Messages({ + "ExtractPrefetch_module_name=Windows Prefetch Extractor" + }) + ExtractPrefetch() { + this.moduleName = Bundle.ExtractPrefetch_module_name(); + } + + @Override + void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) { + + this.context = context; + + String modOutPath = Case.getCurrentCase().getModuleDirectory() + File.separator + PREFETCH_DIR_NAME; + File dir = new File(modOutPath); + if (dir.exists() == false) { + boolean dirMade = dir.mkdirs(); + if (!dirMade) { + logger.log(Level.SEVERE, "Error creating directory to store prefetch output database"); //NON-NLS + return; //If we cannot create the directory then we need to exit + + } + } + + extractPrefetchFiles(dataSource); + + final String prefetchDumper = getPathForPrefetchDumper(); + if (prefetchDumper == null) { + logger.log(Level.SEVERE, "Error finding parse_prefetch program"); //NON-NLS + return; //If we cannot find the parse_prefetch program we cannot proceed + } + + if (context.dataSourceIngestIsCancelled()) { + return; + } + + String modOutFile = modOutPath + File.separator + PREFETCH_PARSER_DB_FILE; + try { + String tempDirPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), PREFETCH_DIR_NAME ); + parsePrefetchFiles(prefetchDumper, tempDirPath, modOutFile, modOutPath); + createAppExecArtifacts(modOutFile, dataSource); + } catch (IOException ex) { + logger.log(Level.WARNING, "Error runing parse_prefetch or creating artifacts.", ex); //NON-NLS + } + } + + /** + * Extract prefetch file to temp directory to process. Checks to make sure that the prefetch files only + * come from the /Windows/Prefetch directory + * + * @param dataSource - datasource to search for prefetch files + * + */ + + void extractPrefetchFiles(Content dataSource) { + List pFiles; + + FileManager fileManager = Case.getCurrentCase().getServices().getFileManager(); + + try { + pFiles = fileManager.findFiles(dataSource, "%.pf"); //NON-NLS + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to find prefetch files.", ex); //NON-NLS + return; // No need to continue + } + + for (AbstractFile pFile : pFiles) { + + if (context.dataSourceIngestIsCancelled()) { + return; + } + + String prefetchFile = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), PREFETCH_DIR_NAME) + File.separator + pFile.getName(); + if (pFile.getParentPath().contains(PREFETCH_FILE_LOCATION)) { + try { + ContentUtils.writeToFile(pFile, new File(prefetchFile)); + } catch (IOException ex) { + logger.log(Level.WARNING, String.format("Unable to write %s to temp directory. File name: %s", pFile.getName(), prefetchFile), ex); //NON-NLS + } + } + } + + } + + /** + * Run the export parse_prefetch program against the prefetch files + * + * @param prefetchExePath - Path to the Executable to run + * @param prefetchDir - Directory where the prefetch files reside to be processed. + * @param tempOutFile - Output database file name and path. + * @param tempOutPath - Directory to store the output and error files. + * + * @throws FileNotFoundException + * @throws IOException + */ + void parsePrefetchFiles(String prefetchExePath, String prefetchDir, String tempOutFile, String tempOutPath) throws FileNotFoundException, IOException { + final Path outputFilePath = Paths.get(tempOutPath, PREFETCH_OUTPUT_FILE_NAME); + final Path errFilePath = Paths.get(tempOutPath, PREFETCH_ERROR_FILE_NAME); + + List commandLine = new ArrayList<>(); + commandLine.add(prefetchExePath); + commandLine.add(prefetchDir); //NON-NLS + commandLine.add(tempOutFile); + + ProcessBuilder processBuilder = new ProcessBuilder(commandLine); + processBuilder.redirectOutput(outputFilePath.toFile()); + processBuilder.redirectError(errFilePath.toFile()); + + ExecUtil.execute(processBuilder, new DataSourceIngestModuleProcessTerminator(context)); + } + + /** + * Get the path and executable for the parse_prefetch program. Checks for specific version of OS to + * get proper executable. + * + * @return - path and executable to run. + * + */ + + private String getPathForPrefetchDumper() { + Path path = null; + if (PlatformUtil.isWindowsOS()) { + if (PlatformUtil.is64BitOS()) { + path = Paths.get(PREFETCH_TOOL_FOLDER, PREFETCH_TOOL_NAME_WINDOWS_64); + } else { + path = Paths.get(PREFETCH_TOOL_FOLDER, PREFETCH_TOOL_NAME_WINDOWS_32); + } + } else { + if ("Linux".equals(PlatformUtil.getOSName())) { + path = Paths.get(PREFETCH_TOOL_FOLDER, PREFETCH_TOOL_NAME_LINUX); + } else { + path = Paths.get(PREFETCH_TOOL_FOLDER, PREFETCH_TOOL_NAME_MACOS); + } + } + File prefetchToolFile = InstalledFileLocator.getDefault().locate(path.toString(), + ExtractPrefetch.class.getPackage().getName(), false); + if (prefetchToolFile != null) { + return prefetchToolFile.getAbsolutePath(); + } + + return null; + + } + + /** + * Create the artifacts from external run of the parse_prefetch program + * + * @param prefetchDb - Database file to read from running the parse_prefetch program. + * @param dataSource - The datasource to search in + * + */ + + private void createAppExecArtifacts(String prefetchDb, Content dataSource) { + List blkBrdArtList = new ArrayList<>(); + + String sqlStatement = "SELECT prefetch_File_Name, actual_File_Name, file_path, Number_time_file_run, Embeded_date_Time_Unix_1, " + + " Embeded_date_Time_Unix_2, Embeded_date_Time_Unix_3, Embeded_date_Time_Unix_4, Embeded_date_Time_Unix_5," + + " Embeded_date_Time_Unix_6, Embeded_date_Time_Unix_7, Embeded_date_Time_Unix_8 " + + " FROM prefetch_file_info;"; //NON-NLS + + try (SQLiteDBConnect tempdbconnect = new SQLiteDBConnect("org.sqlite.JDBC", "jdbc:sqlite:" + prefetchDb); //NON-NLS + ResultSet resultSet = tempdbconnect.executeQry(sqlStatement)) { + + while (resultSet.next()) { + + if (context.dataSourceIngestIsCancelled()) { + logger.log(Level.INFO, "Cancelled Prefetch Artifact Creation."); //NON-NLS + return; + } + + String prefetchFileName = resultSet.getString("prefetch_File_Name"); + String applicationName = resultSet.getString("actual_File_Name"); //NON-NLS + List executionTimes = new ArrayList<>(); + executionTimes.add(Long.valueOf(resultSet.getInt("Embeded_date_Time_Unix_1"))); + executionTimes.add(Long.valueOf(resultSet.getInt("Embeded_date_Time_Unix_2"))); + executionTimes.add(Long.valueOf(resultSet.getInt("Embeded_date_Time_Unix_3"))); + executionTimes.add(Long.valueOf(resultSet.getInt("Embeded_date_Time_Unix_4"))); + executionTimes.add(Long.valueOf(resultSet.getInt("Embeded_date_Time_Unix_5"))); + executionTimes.add(Long.valueOf(resultSet.getInt("Embeded_date_Time_Unix_6"))); + executionTimes.add(Long.valueOf(resultSet.getInt("Embeded_date_Time_Unix_7"))); + executionTimes.add(Long.valueOf(resultSet.getInt("Embeded_date_Time_Unix_8"))); + String timesProgramRun = resultSet.getString("Number_time_file_run"); + String filePath = resultSet.getString("file_path"); + + AbstractFile pfAbstractFile = getAbstractFile(prefetchFileName, PREFETCH_FILE_LOCATION, dataSource); + + List prefetchExecutionTimes = findNonZeroExecutionTimes(executionTimes); + + if (pfAbstractFile != null) { + for (Long executionTime : prefetchExecutionTimes) { + + // only add prefetch file entries that have an actual date associated with them + Collection blkBrdAttributes = Arrays.asList( + new BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, getName(), + applicationName),//NON-NLS + new BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, getName(), + executionTime), + new BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COUNT, getName(), Integer.valueOf(timesProgramRun)), + new BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, getName(), PREFETCH_TSK_COMMENT)); + + try { + BlackboardArtifact blkBrdArt = pfAbstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN); + blkBrdArt.addAttributes(blkBrdAttributes); + blkBrdArtList.add(blkBrdArt); + BlackboardArtifact associatedBbArtifact = createAssociatedArtifact(applicationName.toLowerCase(), filePath, blkBrdArt, dataSource); + if (associatedBbArtifact != null) { + blkBrdArtList.add(associatedBbArtifact); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Exception Adding Artifact.", ex);//NON-NLS + } + } + } else { + logger.log(Level.SEVERE, "File has a null value " + prefetchFileName);//NON-NLS + } + + } + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error while trying to read into a sqlite db.", ex);//NON-NLS + } + + if (!blkBrdArtList.isEmpty()) { + try { + blackboard.postArtifacts(blkBrdArtList, MODULE_NAME); + } catch (Blackboard.BlackboardException ex) { + logger.log(Level.SEVERE, "Error Posting Artifact.", ex);//NON-NLS + } + } + } + + /** + * Cycle thru the execution times list and only return a new list of times that are greater than zero. + * + * @param executionTimes - list of prefetch execution times 8 possible timestamps + * + * @return List of timestamps that are greater than zero + */ + + private List findNonZeroExecutionTimes(List executionTimes) { + List prefetchExecutionTimes = new ArrayList<>(); + for (Long executionTime : executionTimes) { // only add prefetch file entries that have an actual date associated with them + if (executionTime > 0) { + prefetchExecutionTimes.add(executionTime); + } + } + return prefetchExecutionTimes; + } + /** + * Create associated artifacts using file path name and the artifact it associates with + * + * @param fileName the filename to search for + * @param filePathName file and path of object being associated with + * @param bba blackboard artifact to associate with + * @param dataSource - The datasource to search in + * + * @returnv BlackboardArtifact or a null value + */ + private BlackboardArtifact createAssociatedArtifact(String fileName, String filePathName, BlackboardArtifact bba, Content dataSource) { + AbstractFile sourceFile = getAbstractFile(fileName, filePathName, dataSource); + if (sourceFile != null) { + Collection bbattributes2 = new ArrayList<>(); + bbattributes2.addAll(Arrays.asList( + new BlackboardAttribute(TSK_ASSOCIATED_ARTIFACT, this.getName(), + bba.getArtifactID()))); + + BlackboardArtifact associatedObjectBba = createArtifactWithAttributes(TSK_ASSOCIATED_OBJECT, sourceFile, bbattributes2); + if (associatedObjectBba != null) { + return associatedObjectBba; + } + } + + return null; + } + + /** + * Get the abstract file for the prefetch file. + * + * @param fileName - File name of the prefetch file to find. + * @param filePath - Path where the prefetch file is located. + * @param dataSource - The datasource to search in + * + * @return Abstract file of the prefetch file. + * + */ + + AbstractFile getAbstractFile(String fileName, String filePath, Content dataSource) { + List files; + + FileManager fileManager = Case.getCurrentCase().getServices().getFileManager(); + + try { + files = fileManager.findFiles(dataSource, fileName, filePath); //NON-NLS + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to find prefetch files.", ex); //NON-NLS + return null; // No need to continue + } + + if (!files.isEmpty()) { + return files.get(0); + } else { + return null; + } + + } + +} diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java index c5cb62a779..e9519abf13 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java @@ -79,6 +79,7 @@ public final class RAImageIngestModule implements DataSourceIngestModule { Extract zoneInfo = new ExtractZoneIdentifier(); Extract recycleBin = new ExtractRecycleBin(); Extract sru = new ExtractSru(); + Extract prefetch = new ExtractPrefetch(); extractors.add(chrome); extractors.add(firefox); @@ -93,6 +94,7 @@ public final class RAImageIngestModule implements DataSourceIngestModule { extractors.add(zoneInfo); // this needs to run after the web browser modules extractors.add(recycleBin); // this needs to run after ExtractRegistry and ExtractOS extractors.add(sru); + extractors.add(prefetch); browserExtractors.add(chrome); browserExtractors.add(firefox); diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index 5f60aed634..f4d5248a8b 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Fri, 12 Jun 2020 14:50:38 -0400 +#Fri, 19 Jun 2020 10:14:47 -0400 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=314 SPLASH_WIDTH=538 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index 6160c1ec95..52d17e0e96 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,4 +1,4 @@ #Updated by build script -#Fri, 12 Jun 2020 14:50:38 -0400 +#Fri, 19 Jun 2020 10:14:47 -0400 CTL_MainWindow_Title=Autopsy 4.15.0 CTL_MainWindow_Title_No_Project=Autopsy 4.15.0 diff --git a/thirdparty/markmckinnon/parse_prefetch_linux b/thirdparty/markmckinnon/parse_prefetch_linux new file mode 100644 index 0000000000..183674e9cf Binary files /dev/null and b/thirdparty/markmckinnon/parse_prefetch_linux differ diff --git a/thirdparty/markmckinnon/parse_prefetch_macos b/thirdparty/markmckinnon/parse_prefetch_macos new file mode 100644 index 0000000000..f36fa57d09 Binary files /dev/null and b/thirdparty/markmckinnon/parse_prefetch_macos differ diff --git a/thirdparty/markmckinnon/parse_prefetch_x64.exe b/thirdparty/markmckinnon/parse_prefetch_x64.exe new file mode 100644 index 0000000000..3e0b7ae674 Binary files /dev/null and b/thirdparty/markmckinnon/parse_prefetch_x64.exe differ diff --git a/thirdparty/markmckinnon/parse_prefetch_x86.exe b/thirdparty/markmckinnon/parse_prefetch_x86.exe new file mode 100644 index 0000000000..fb58178c3a Binary files /dev/null and b/thirdparty/markmckinnon/parse_prefetch_x86.exe differ diff --git a/thunderbirdparser/nbproject/project.xml b/thunderbirdparser/nbproject/project.xml index 29782066ac..5f285136a0 100644 --- a/thunderbirdparser/nbproject/project.xml +++ b/thunderbirdparser/nbproject/project.xml @@ -6,6 +6,15 @@ org.sleuthkit.autopsy.thunderbirdparser + + org.netbeans.api.progress + + + + 1 + 1.47.1 + + org.openide.util diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java index e9db7287e8..0f9759819f 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java @@ -54,6 +54,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.DerivedFile; +import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.Relationship; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -76,6 +77,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { private Blackboard blackboard; private CommunicationArtifactsHelper communicationArtifactsHelper; + private static final int MBOX_SIZE_TO_SPLIT = 1048576000; private Case currentCase; /** @@ -309,12 +311,75 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return ProcessResult.OK; } - try { - ContentUtils.writeToFile(abstractFile, file, context::fileIngestIsCancelled); - } catch (IOException ex) { - logger.log(Level.WARNING, "Failed writing mbox file to disk.", ex); //NON-NLS - return ProcessResult.OK; + if (abstractFile.getSize() < MBOX_SIZE_TO_SPLIT) { + + try { + ContentUtils.writeToFile(abstractFile, file, context::fileIngestIsCancelled); + } catch (IOException ex) { + logger.log(Level.WARNING, "Failed writing mbox file to disk.", ex); //NON-NLS + return ProcessResult.OK; + } + + processMboxFile(file, abstractFile, emailFolder); + + if (file.delete() == false) { + logger.log(Level.INFO, "Failed to delete temp file: {0}", file.getName()); //NON-NLS + } + } else { + + List mboxSplitOffsets = new ArrayList<>(); + try{ + mboxSplitOffsets = findMboxSplitOffset(abstractFile, file); + } catch (IOException ex) { + logger.log(Level.WARNING, String.format("Failed finding split offsets for mbox file {0}.", fileName), ex); //NON-NLS + return ProcessResult.OK; + } + + long startingOffset = 0; + for (Long mboxSplitOffset : mboxSplitOffsets) { + File splitFile = new File(fileName + "-" + mboxSplitOffset); + try { + ContentUtils.writeToFile(abstractFile, splitFile, context::fileIngestIsCancelled, startingOffset, mboxSplitOffset); + } catch (IOException ex) { + logger.log(Level.WARNING, "Failed writing split mbox file to disk.", ex); //NON-NLS + return ProcessResult.OK; + } + processMboxFile(splitFile, abstractFile, emailFolder); + startingOffset = mboxSplitOffset; + if (splitFile.delete() == false) { + logger.log(Level.INFO, "Failed to delete temp file: {0}", splitFile); //NON-NLS + } + + } + } + + return ProcessResult.OK; + } + + private List findMboxSplitOffset(AbstractFile abstractFile, File file) throws IOException { + + List mboxSplitOffset = new ArrayList<>(); + + byte[] buffer = new byte[7]; + ReadContentInputStream in = new ReadContentInputStream(abstractFile); + in.skip(MBOX_SIZE_TO_SPLIT); + int len = in.read(buffer); + while (len != -1) { + len = in.read(buffer); + if (buffer[0] == 13 && buffer[1] == 10 && buffer[2] == 70 && buffer[3] == 114 && + buffer[4] == 111 && buffer[5] == 109 && buffer[6] == 32) { + mboxSplitOffset.add(in.getCurPosition() - 5 ); + in.skip(MBOX_SIZE_TO_SPLIT); + } } + + return mboxSplitOffset; + + } + + + private void processMboxFile(File file, AbstractFile abstractFile, String emailFolder) { + MboxParser emailIterator = MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId()); List emails = new ArrayList<>(); @@ -325,7 +390,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { emails.add(emailMessage); } } - + String errors = emailIterator.getErrors(); if (!errors.isEmpty()) { postErrorMessage( @@ -335,11 +400,6 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } processEmails(emails, MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId()), abstractFile); - if (file.delete() == false) { - logger.log(Level.INFO, "Failed to delete temp file: {0}", file.getName()); //NON-NLS - } - - return ProcessResult.OK; } /** @@ -755,4 +815,5 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { public void shutDown() { // nothing to shut down } + }