diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties index 833a778637..3050671087 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties @@ -1,4 +1,4 @@ -OptionsCategory_Name_TagNamesOptions=Tags +OptionsCategory_Name_TagNamesOptions=Custom Tags OptionsCategory_TagNames=TagNames TagNameDialog.title.text=New Tag TagNameDialog.JOptionPane.tagNameIllegalCharacters.message=Tag name may not contain any of the following symbols: \\ : * ? " < > | , ; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties-MERGED index 6f6c2374fb..c74380f5a2 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties-MERGED @@ -1,4 +1,4 @@ -OptionsCategory_Name_TagNamesOptions=Tags +OptionsCategory_Name_TagNamesOptions=Custom Tags OptionsCategory_TagNames=TagNames TagNameDefinition.predefTagNames.bookmark.text=Bookmark TagNameDefinition.predefTagNames.followUp.text=Follow Up diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Persona.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Persona.java index 29a8e394b6..af84f336dc 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Persona.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Persona.java @@ -291,6 +291,14 @@ public class Persona { public void removeAccount(PersonaAccount account) throws CentralRepoException { PersonaAccount.removePersonaAccount(account.getId()); } + + /** + * Marks this persona as deleted + */ + public void delete() throws CentralRepoException { + String deleteSQL = "UPDATE personas SET status_id = " + PersonaStatus.DELETED.status_id + " WHERE id = " + this.id; + CentralRepository.getInstance().executeUpdateSQL(deleteSQL); + } /** * Callback to process a Persona query from the persona table. @@ -362,7 +370,8 @@ public class Persona { } /** - * Gets the rows from the Personas table with matching name. + * Gets the rows from the Personas table with matching name. + * Persona marked as DELETED are not returned. * * @param partialName Name substring to match. * @return Collection of personas matching the given name substring, may be @@ -374,7 +383,8 @@ public class Persona { public static Collection getPersonaByName(String partialName) throws CentralRepoException { String queryClause = PERSONA_QUERY - + "WHERE LOWER(p.name) LIKE " + "LOWER('%" + partialName + "%')" ; + + "WHERE p.status_id != " + PersonaStatus.DELETED.status_id + + " AND LOWER(p.name) LIKE " + "LOWER('%" + partialName + "%')" ; PersonaQueryCallback queryCallback = new PersonaQueryCallback(); CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback); @@ -668,7 +678,8 @@ public class Persona { for (CentralRepoAccount.CentralRepoAccountType crAccountType : accountTypes) { String querySql = getPersonaFromInstanceTableQueryTemplate(crAccountType) - + " WHERE case_id = " + correlationCase.getID(); + + " WHERE case_id = " + correlationCase.getID() + + "AND personas.status_id != " + Persona.PersonaStatus.DELETED.getStatusId(); PersonaFromAccountInstanceQueryCallback queryCallback = new PersonaFromAccountInstanceQueryCallback(); CentralRepository.getInstance().executeSelectSQL(querySql, queryCallback); @@ -699,7 +710,8 @@ public class Persona { for (CentralRepoAccount.CentralRepoAccountType crAccountType : accountTypes) { String querySql = getPersonaFromInstanceTableQueryTemplate(crAccountType) - + " WHERE data_source_id = " + dataSource.getID(); + + " WHERE data_source_id = " + dataSource.getID() + + "AND personas.status_id != " + Persona.PersonaStatus.DELETED.getStatusId(); PersonaFromAccountInstanceQueryCallback queryCallback = new PersonaFromAccountInstanceQueryCallback(); CentralRepository.getInstance().executeSelectSQL(querySql, queryCallback); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java index 7aa3b81372..d0322ddbca 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java @@ -27,6 +27,7 @@ import java.util.Collections; import java.util.Objects; import org.apache.commons.lang3.StringUtils; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.Account; /** * This class represents an association between a Persona and an Account. @@ -130,7 +131,13 @@ public class PersonaAccount { * account. */ static PersonaAccount addPersonaAccount(Persona persona, CentralRepoAccount account, String justification, Persona.Confidence confidence) throws CentralRepoException { - CentralRepoExaminer currentExaminer = CentralRepository.getInstance().getOrInsertExaminer(System.getProperty("user.name")); + 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(); Long timeStampMillis = instant.toEpochMilli(); @@ -235,38 +242,53 @@ public class PersonaAccount { * Gets all the Accounts for the specified Persona. * * @param personaId Id of persona for which to get the accounts for. + * * @return Collection of PersonaAccounts, may be empty. * * @throws CentralRepoException If there is an error in getting the - * persona_account. + * persona_account. */ static Collection getPersonaAccountsForPersona(long personaId) throws CentralRepoException { - String queryClause = PERSONA_ACCOUNTS_QUERY_CALUSE - + " WHERE persona_accounts.persona_id = " + personaId; + CentralRepository cr = CentralRepository.getInstance(); - PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback(); - CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback); + if (cr != null) { + String queryClause = PERSONA_ACCOUNTS_QUERY_CALUSE + + " WHERE persona_accounts.persona_id = " + personaId; - return queryCallback.getPersonaAccountsList(); + PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback(); + cr.executeSelectSQL(queryClause, queryCallback); + + return queryCallback.getPersonaAccountsList(); + } + + return new ArrayList<>(); } /** * Gets all the Persona for the specified Account. * * @param accountId Id of account for which to get the Personas for. + * * @return Collection of PersonaAccounts. may be empty. * * @throws CentralRepoException If there is an error in getting the - * persona_account. + * persona_account. */ public static Collection getPersonaAccountsForAccount(long accountId) throws CentralRepoException { String queryClause = PERSONA_ACCOUNTS_QUERY_CALUSE - + " WHERE persona_accounts.account_id = " + accountId; + + " WHERE persona_accounts.account_id = " + accountId + + "AND p.status_id != " + Persona.PersonaStatus.DELETED.getStatusId(); - PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback(); - CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback); + CentralRepository cr = CentralRepository.getInstance(); - return queryCallback.getPersonaAccountsList(); + if (cr != null) { + PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback(); + cr.executeSelectSQL(queryClause, queryCallback); + + return queryCallback.getPersonaAccountsList(); + } + + return new ArrayList<>(); } /** @@ -274,20 +296,51 @@ public class PersonaAccount { * account identifier substring. * * @param accountIdentifierSubstring Account identifier substring to search - * for. + * for. + * * @return Collection of PersonaAccounts. may be empty. * * @throws CentralRepoException If there is an error in getting the - * persona_account. + * persona_account. */ public static Collection getPersonaAccountsForIdentifierLike(String accountIdentifierSubstring) throws CentralRepoException { String queryClause = PERSONA_ACCOUNTS_QUERY_CALUSE - + " WHERE LOWER(accounts.account_unique_identifier) LIKE LOWER('%" + accountIdentifierSubstring + "%')"; + + " WHERE LOWER(accounts.account_unique_identifier) LIKE LOWER('%" + accountIdentifierSubstring + "%')" + + "AND p.status_id != " + Persona.PersonaStatus.DELETED.getStatusId(); - PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback(); - CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback); + CentralRepository cr = CentralRepository.getInstance(); + if (cr != null) { + PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback(); + cr.executeSelectSQL(queryClause, queryCallback); - return queryCallback.getPersonaAccountsList(); + return queryCallback.getPersonaAccountsList(); + } + + return new ArrayList<>(); + } + + /** + * Gets all the Persona associated with the given account. + * + * @param account Account to search for. + * + * @return Collection of PersonaAccounts, maybe empty if none were found or + * CR is not enabled. + * + * @throws CentralRepoException + */ + public static Collection getPersonaAccountsForAccount(Account account) throws CentralRepoException { + String queryClause = PERSONA_ACCOUNTS_QUERY_CALUSE + + " WHERE LOWER(accounts.account_unique_identifier) LIKE LOWER('%" + account.getTypeSpecificID() + "%') AND type_name = '" + account.getAccountType().getTypeName() + "' "; + + CentralRepository cr = CentralRepository.getInstance(); + if (cr != null) { + PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback(); + cr.executeSelectSQL(queryClause, queryCallback); + return queryCallback.getPersonaAccountsList(); + } + + return new ArrayList<>(); } /** @@ -299,8 +352,14 @@ public class PersonaAccount { * 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; - CentralRepository.getInstance().executeDeleteSQL(deleteClause); + cr.executeDeleteSQL(deleteClause); } /** @@ -343,17 +402,23 @@ public class PersonaAccount { * accounts. */ static Collection getAccountsForPersona(long personaId) throws CentralRepoException { - 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; + CentralRepository cr = CentralRepository.getInstance(); - AccountsForPersonaQueryCallback queryCallback = new AccountsForPersonaQueryCallback(); - CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback); + 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; - return queryCallback.getAccountsList(); + AccountsForPersonaQueryCallback queryCallback = new AccountsForPersonaQueryCallback(); + cr.executeSelectSQL(queryClause, queryCallback); + + return queryCallback.getAccountsList(); + } + + return new ArrayList<>(); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java index b554a1071c..88ba957865 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java @@ -343,93 +343,6 @@ public class IngestEventsListener { event = evt; } - /** - * Automatically creates personas from all the TSK_CONTACT artifacts - * found in a data source. - * - * @param dataSource Data source that was just analyzed. - * @throws TskCoreException If there is any error getting contact - * artifacts from case database. - * @throws CentralRepoException If there is an error in creating - * personas in the Central Repo. - */ - private void autoGenerateContactPersonas(Content dataSource) throws TskCoreException, CentralRepoException { - - Blackboard blackboard; - try { - blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); - } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); - return; - } - - // get all TSK_CONTACT artifacts in this data source. - List contactArtifacts = blackboard.getArtifacts(TSK_CONTACT.getTypeID(), dataSource.getId()); - for (BlackboardArtifact artifact : contactArtifacts) { - - BlackboardAttribute nameAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME)); - String personaName = (nameAttr != null) ? nameAttr.getValueString() : null; - - // Get phone number and email attributes. - BlackboardAttribute phoneAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)); - BlackboardAttribute homePhoneAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_HOME)); - BlackboardAttribute mobilePhoneAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_MOBILE)); - BlackboardAttribute emailAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL)); - - Persona persona = personaFromContactAttribute(null, Account.Type.PHONE, phoneAttr, personaName); - persona = personaFromContactAttribute(persona, Account.Type.PHONE, homePhoneAttr, personaName); - persona = personaFromContactAttribute(persona, Account.Type.PHONE, mobilePhoneAttr, personaName); - personaFromContactAttribute(persona, Account.Type.EMAIL, emailAttr, personaName); - } - } - - - - /** - * Gets central repo account for the given attribute for a TSK_CONTACT - * artifact. Associates the given persona with that account. Creates a - * Persona, if one isn't provided. - * - * @param persona Persona to associate with the account. May be null, in - * which case a persona is created first. - * @param accountType Account type of account to be associated. - * @param attribute Attribute form which get the account id. - * @param personaName Persona name, if a persona needs to be created. - * @return Persona created or associated with the account. - * - * @throws TskCoreException If there is an error in normalizing the - * account id. - * @throws CentralRepoException If there is an erorr is getting the - * account or associating the persona with it. - */ - private Persona personaFromContactAttribute(Persona persona, Account.Type accountType, BlackboardAttribute attribute, String personaName) throws CentralRepoException, TskCoreException { - - Persona personaToReturn = persona; - if (attribute != null) { - - String accountId = attribute.getValueString(); - if (CommunicationsUtils.isValidAccountId(accountType, accountId)) { - if (accountType == Account.Type.PHONE) { - accountId = CommunicationsUtils.normalizePhoneNum(accountId); - } else if (accountType == Account.Type.EMAIL) { - accountId = CommunicationsUtils.normalizeEmailAddress(accountId); - } - - CentralRepoAccount.CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(accountType.getTypeName()); - CentralRepoAccount crAccount = CentralRepository.getInstance().getOrCreateAccount(crAccountType, accountId); - - PersonaAccount personaAccount; - // If persona doesnt exist, create one - if (persona == null) { - personaToReturn = Persona.createPersonaForAccount(personaName, "Auto generated contact persona", Persona.PersonaStatus.UNKNOWN, crAccount, "Found in contact book entry", Persona.Confidence.HIGH); - } else { - persona.addAccount(crAccount, "Found in contact book entry", Persona.Confidence.HIGH); - } - } - } - return personaToReturn; - } - @Override public void run() { // clear the tracker to reduce memory usage @@ -504,8 +417,6 @@ public class IngestEventsListener { correlationDataSource.setSha256(imageSha256Hash); } } - // automatically generate persona from contact artifacts. - autoGenerateContactPersonas(dataSource); } } catch (CentralRepoException ex) { LOGGER.log(Level.SEVERE, String.format( diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties index f40bf2c7ca..24fdf1c0ff 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties @@ -53,3 +53,4 @@ AddAccountDialog.confidenceLbl.text=Confidence: AddAccountDialog.typeLbl.text=Type: AddAccountDialog.identiferLbl.text=Identifier: AddAccountDialog.identifierTextField.text= +PersonaManagerTopComponent.deleteBtn.text=Delete Persona 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 2258c7df21..3c7765f09c 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED @@ -1,18 +1,38 @@ AddAccountDialog.title.text=Add Account +AddAccountDialog_dup_msg=This account is already added to the persona +AddAccountDialog_dup_Title=Account add failure +AddAccountDialog_empty_msg=The identifier field cannot be empty +AddAccountDialog_empty_Title=Empty identifier AddAccountDialog_get_types_exception_msg=Failed to access central repository AddAccountDialog_get_types_exception_Title=Central Repository failure -AddAccountDialog_validate_id_failure=Account ID must not be empty -AddAccountDialog_validate_id_failure_title=Account ID issue +AddAccountDialog_search_empty_msg=Account not found for given identifier and type +AddAccountDialog_search_empty_Title=Account not found +AddAccountDialog_search_failure_msg=Central Repository account search failed +AddAccountDialog_search_failure_Title=Account add failure +AddAliasDialog.title.text=Add Alias +AddAliasDialog_dup_msg=This alias has already been added to this persona +AddAliasDialog_dup_Title=Alias add failure +AddMetadataDialog.title.text=Add Metadata +AddMetadataDialog_dup_msg=A metadata entry with this name has already been added to this persona +AddMetadataDialog_dup_Title=Metadata add failure CTL_OpenPersonaManager=Persona Manager CTL_PersonaManagerTopComponentAction=Persona Manager CTL_PersonaDetailsTopComponent=Persona Details OpenPersonasAction.displayName=Persona Manager +PersonaDetailsDialogCreateTitle=Create Persona +PersonaDetailsDialogEditTitle=Edit Persona +PersonaDetailsPanel_CentralRepoErr_msg=Failure to write to Central Repository +PersonaDetailsPanel_CentralRepoErr_Title=Central Repository failure +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_NameCreate=Create Persona PersonaDetailsPanel_NameEdit=Edit Persona PersonaDetailsPanel_NameView=View Persona -PersonaManagerTopComponent.createBtn.text=Create New +PersonaDetailsPanel_NotEnoughAccounts_msg=Two or more accounts are necessary to create a persona +PersonaDetailsPanel_NotEnoughAccounts_Title=Not enough accounts +PersonaManagerTopComponent.createBtn.text=New Persona PersonaManagerTopComponent.searchBtn.text=Search PersonaManagerTopComponent.resultsTable.columnModel.title1=Name PersonaManagerTopComponent.resultsTable.columnModel.title0=ID @@ -20,6 +40,17 @@ PersonaManagerTopComponent.resultsTable.toolTipText= PersonaManagerTopComponent.searchAccountRadio.text=Account PersonaManagerTopComponent.searchNameRadio.text=Name PersonaManagerTopComponent.searchField.text= +AddAccountDialog.cancelBtn.text=Cancel +AddAccountDialog.okBtn.text=OK +PersonaManagerTopComponent.editBtn.text=Edit Persona +PersonaDetailsDialog.cancelBtn.text=Cancel +PersonaDetailsDialog.okBtn.text=OK +PersonaDetailsPanel.deleteCaseBtn.text=Delete +PersonaDetailsPanel.addCaseBtn.text=Add +PersonaDetailsPanel.casesLbl.text=Cases found in: +PersonaDetailsPanel.deleteAliasBtn.text=Delete +PersonaDetailsPanel.addAliasBtn.text=Add +PersonaDetailsPanel.aliasesLabel.text=Aliases: PersonaDetailsPanel.deleteMetadataBtn.text=Delete PersonaDetailsPanel.addMetadataBtn.text=Add PersonaDetailsPanel.metadataLabel.text=Metadata: @@ -27,18 +58,32 @@ PersonaDetailsPanel.deleteAccountBtn.text=Delete PersonaDetailsPanel.addAccountBtn.text=Add PersonaDetailsPanel.accountsLbl.text=Accounts: PersonaDetailsPanel.nameField.text= -PersonaDetailsPanel.saveBtn.toolTipText= -PersonaDetailsPanel.saveBtn.text=Save Changes PersonaDetailsPanel.nameLbl.text=Name: -PersonaDetailsPanel.deleteCaseBtn.text=Delete -PersonaDetailsPanel.addCaseBtn.text=Add -PersonaDetailsPanel.casesLbl.text=Cases found in: -PersonaDetailsPanel.deleteAliasBtn.text=Delete -PersonaDetailsPanel.addAliasBtn.text=Add -PersonaDetailsPanel.aliasesLabel.text=Aliases: -AddAccountDialog.cancelBtn.text=Cancel -AddAccountDialog.okBtn.text=OK -PersonaManagerTopComponent.editBtn.text=Edit +AddAliasDialog.accountsLbl.text=Account: +AddAliasDialog.okBtn.text=OK +AddAliasDialog.cancelBtn.text=Cancel +AddMetadataDialog.cancelBtn.text=Cancel +AddMetadataDialog.okBtn.text=OK +AddMetadataDialog.nameLbl.text=Name: +AddMetadataDialog.nameTextField.text= +AddMetadataDialog.valueLbl.text=Value: +AddMetadataDialog.valueTextField.text= +AddMetadataDialog.justificationLbl.text=Justification: +AddMetadataDialog.justificationTextField.text= +AddMetadataDialog.confidenceLbl.text=Confidence: +AddAliasDialog.justificationLbl.text=Justification: +AddAliasDialog.okBtn.text_1=OK +AddAliasDialog.cancelBtn.text_1=Cancel +AddAliasDialog.confidenceLbl.text=Confidence: +AddAliasDialog.justificationTextField.text= +AddAliasDialog.aliasLbl.text=Alias: +AddAliasDialog.aliasTextField.text= +AddAccountDialog.justificationTextField.text= +AddAccountDialog.justificationLbl.text=Justification: +AddAccountDialog.confidenceLbl.text=Confidence: +AddAccountDialog.typeLbl.text=Type: +AddAccountDialog.identiferLbl.text=Identifier: +AddAccountDialog.identifierTextField.text= PMTopComponent_Name=Persona Manager PMTopComponent_search_exception_msg=Failed to search personas PMTopComponent_search_exception_Title=Search failure diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsPanel.java index 2c5c7b07f6..a7c76b7ad4 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaDetailsPanel.java @@ -182,7 +182,7 @@ public final class PersonaDetailsPanel extends javax.swing.JPanel { } } return false; - } + } boolean addAccount(CentralRepoAccount account, String justification, Persona.Confidence confidence) { if (!accountExists(account)) { @@ -560,7 +560,7 @@ public final class PersonaDetailsPanel extends javax.swing.JPanel { void clear() { currentPersona = null; - nameField.setText(Persona.getDefaultName()); + nameField.setText(mode == PersonaDetailsMode.CREATE ? Persona.getDefaultName() : ""); currentAccounts = new ArrayList<>(); currentMetadata = new ArrayList<>(); currentAliases = new ArrayList<>(); @@ -714,8 +714,8 @@ public final class PersonaDetailsPanel extends javax.swing.JPanel { } @Messages({ - "PersonaDetailsPanel_NotEnoughAccounts_msg=A persona needs two or more accounts", - "PersonaDetailsPanel_NotEnoughAccounts_Title=Not enough accounts", + "PersonaDetailsPanel_NotEnoughAccounts_msg=A persona needs at least one account", + "PersonaDetailsPanel_NotEnoughAccounts_Title=Missing account", "PersonaDetailsPanel_CentralRepoErr_msg=Failure to write to Central Repository", "PersonaDetailsPanel_CentralRepoErr_Title=Central Repository failure", "PersonaDetailsPanel_EmptyName_msg=Persona name cannot be empty", diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaManagerTopComponent.form b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaManagerTopComponent.form index f478739167..918e5e4302 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaManagerTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaManagerTopComponent.form @@ -77,7 +77,8 @@ - + + @@ -110,6 +111,7 @@ + @@ -208,6 +210,14 @@ + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaManagerTopComponent.java b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaManagerTopComponent.java index 7137dcb6c2..c2c7476496 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaManagerTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaManagerTopComponent.java @@ -54,7 +54,9 @@ public final class PersonaManagerTopComponent extends TopComponent { private Persona selectedPersona = null; @Messages({ - "PMTopComponent_Name=Persona Manager" + "PMTopComponent_Name=Persona Manager", + "PMTopComponent_delete_exception_Title=Delete failure", + "PMTopComponent_delete_exception_msg=Failed to delete persona", }) public PersonaManagerTopComponent() { initComponents(); @@ -83,6 +85,25 @@ public final class PersonaManagerTopComponent extends TopComponent { PersonaDetailsMode.CREATE, selectedPersona, new CreateEditCallbackImpl()); } }); + + deleteBtn.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + try { + if (selectedPersona != null) { + selectedPersona.delete(); + } + } 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.ERROR_MESSAGE); + return; + } + executeSearch(); + } + }); // Results table resultsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); @@ -116,6 +137,7 @@ public final class PersonaManagerTopComponent extends TopComponent { Persona persona = currentResults.get(index); selectedPersona = persona; editBtn.setEnabled(true); + deleteBtn.setEnabled(true); } /** @@ -147,6 +169,8 @@ public final class PersonaManagerTopComponent extends TopComponent { if (selectedRow != -1) { setPersona(resultsTable.getSelectedRow()); detailsPanel.setMode(this, PersonaDetailsMode.VIEW, selectedPersona); + } else { + detailsPanel.clear(); } } @@ -189,6 +213,7 @@ public final class PersonaManagerTopComponent extends TopComponent { resultsTable.clearSelection(); updateResultsTable(results); editBtn.setEnabled(false); + deleteBtn.setEnabled(false); } @Override @@ -216,6 +241,7 @@ public final class PersonaManagerTopComponent extends TopComponent { searchBtn = new javax.swing.JButton(); editBtn = new javax.swing.JButton(); createBtn = new javax.swing.JButton(); + deleteBtn = new javax.swing.JButton(); detailsPanel = new org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsPanel(); setMinimumSize(new java.awt.Dimension(400, 400)); @@ -246,6 +272,9 @@ public final class PersonaManagerTopComponent extends TopComponent { 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 + deleteBtn.setEnabled(false); + javax.swing.GroupLayout searchPanelLayout = new javax.swing.GroupLayout(searchPanel); searchPanel.setLayout(searchPanelLayout); searchPanelLayout.setHorizontalGroup( @@ -257,7 +286,8 @@ public final class PersonaManagerTopComponent extends TopComponent { .addComponent(createBtn) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(editBtn) - .addGap(0, 0, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(deleteBtn)) .addComponent(resultsPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) .addComponent(searchField) .addGroup(searchPanelLayout.createSequentialGroup() @@ -283,7 +313,8 @@ public final class PersonaManagerTopComponent extends TopComponent { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(editBtn) - .addComponent(createBtn)) + .addComponent(createBtn) + .addComponent(deleteBtn)) .addContainerGap()) ); @@ -304,6 +335,7 @@ public final class PersonaManagerTopComponent extends TopComponent { // Variables declaration - do not modify//GEN-BEGIN:variables 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.JSplitPane jSplitPane1; diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java index 8bf29e70ac..4a50969ef3 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java @@ -20,12 +20,18 @@ package org.sleuthkit.autopsy.communications; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; import javax.swing.Action; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.centralrepository.datamodel.PersonaAccount; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.AccountDeviceInstance; @@ -37,6 +43,8 @@ import org.sleuthkit.datamodel.CommunicationsManager; */ final class AccountDeviceInstanceNode extends AbstractNode { + private static final Logger logger = Logger.getLogger(AccountDeviceInstanceNode.class.getName()); + private final AccountDeviceInstanceKey accountDeviceInstanceKey; private final CommunicationsManager commsManager; private final Account account; @@ -103,4 +111,34 @@ final class AccountDeviceInstanceNode extends AbstractNode { actions.add(ResetAndPinAccountsAction.getInstance()); return actions.toArray(new Action[actions.size()]); } + + @Messages({ + "# {0} - Contact Name", + "# {1} - Persona Name", + "AccountInstanceNode_Tooltip_Template=Contact: {0} - Persona: {1}", + "# {0} - PersonaAccount count", + "AccountInstanceNode_Tooltip_suffix=(1 of {0})" + }) + @Override + public String getShortDescription() { + List personaList; + try { + personaList = CVTPersonaCache.getPersonaAccounts(account); + } catch (ExecutionException ex) { + logger.log(Level.WARNING, "Failed to retrieve Persona details for node.", ex); + return getDisplayName(); + } + + String personaName; + if (!personaList.isEmpty()) { + personaName = personaList.get(0).getPersona().getName(); + if (personaList.size() > 1) { + personaName += Bundle.AccountInstanceNode_Tooltip_suffix(Integer.toString(personaList.size())); + } + } else { + personaName = "None"; + } + + return Bundle.AccountInstanceNode_Tooltip_Template(getDisplayName(), personaName); + } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTPersonaCache.java b/Core/src/org/sleuthkit/autopsy/communications/CVTPersonaCache.java new file mode 100755 index 0000000000..92cebad8a3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTPersonaCache.java @@ -0,0 +1,96 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.communications; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.centralrepository.datamodel.PersonaAccount; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Account; + +/** + * A singleton cache of the PersonaAccount information. The list of + * PersonaAccounts for a given Account typeSpecificID retrieved on first access + * and evicted from the cache after 5 minutes. + */ +final class CVTPersonaCache { + + private static final Logger logger = Logger.getLogger(CVTPersonaCache.class.getName()); + private final LoadingCache> accountMap; + + private static CVTPersonaCache instance; + + /** + * Cache constructor. + */ + private CVTPersonaCache() { + accountMap = CacheBuilder.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build( + new CacheLoader>() { + @Override + public List load(Account key) { + List accountList = new ArrayList<>(); + try { + if (CentralRepository.isEnabled()) { + Collection accounts = PersonaAccount.getPersonaAccountsForAccount(key); + accountList.addAll(accounts); + } + } catch (CentralRepoException ex) { + logger.log(Level.WARNING, String.format("Unable to load Persona information for account: %s", key), ex); + } + return accountList; + } + } + ); + } + + /** + * Returns the singleton instance of the cache. + * + * @return CVTPersonaCache instance. + */ + private static synchronized CVTPersonaCache getInstance() { + if (instance == null) { + instance = new CVTPersonaCache(); + } + + return instance; + } + + /** + * Returns the list of PersonaAccounts for the given Account typeSpecificId. + * + * @param typeSpecificID Account typeSpecificId. + * + * @return List of PersonaAccounts for id or empty list if none were found. + * + * @throws ExecutionException + */ + static synchronized List getPersonaAccounts(Account account) throws ExecutionException { + return getInstance().accountMap.get(account); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties index ee9b6fb4b6..40abf528ad 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties @@ -1,7 +1,7 @@ -AbstractAbstractFileNode.accessTimeColLbl=\u30a2\u30af\u30bb\u30b9\u6642\u523b +AbstractAbstractFileNode.accessTimeColLbl=\u30a2\u30af\u30bb\u30b9\u65e5\u6642 AbstractAbstractFileNode.attrAddrColLbl=\u5c5e\u6027\u30a2\u30c9\u30ec\u30b9 -AbstractAbstractFileNode.changeTimeColLbl=\u6642\u523b\u5909\u66f4 -AbstractAbstractFileNode.createdTimeColLbl=\u4f5c\u6210\u3057\u305f\u6642\u523b +AbstractAbstractFileNode.changeTimeColLbl=\u30a8\u30f3\u30c8\u30ea\u66f4\u65b0\u65e5\u6642 +AbstractAbstractFileNode.createdTimeColLbl=\u4f5c\u6210\u65e5\u6642 AbstractAbstractFileNode.createSheet.comment.displayName=C AbstractAbstractFileNode.createSheet.comment.name=C # {0} - occurrenceCount @@ -26,7 +26,7 @@ AbstractAbstractFileNode.md5HashColLbl=MD5\u30cf\u30c3\u30b7\u30e5 AbstractAbstractFileNode.metaAddrColLbl=\u30e1\u30bf\u30a2\u30c9\u30ec\u30b9 AbstractAbstractFileNode.mimeType=MIME\u30bf\u30a4\u30d7 AbstractAbstractFileNode.modeColLbl=\u30e2\u30fc\u30c9 -AbstractAbstractFileNode.modifiedTimeColLbl=MFT\u5909\u66f4\u6642\u523b +AbstractAbstractFileNode.modifiedTimeColLbl=\u66f4\u65b0\u65e5\u6642 AbstractAbstractFileNode.nameColLbl=\u540d\u524d AbstractAbstractFileNode.objectId=\u30aa\u30d6\u30b8\u30a7\u30af\u30c8ID AbstractAbstractFileNode.originalName=\u30aa\u30ea\u30b8\u30ca\u30eb\u540d @@ -136,14 +136,14 @@ ImageNode.createSheet.type.name=\u30bf\u30a4\u30d7 ImageNode.createSheet.type.text=\u30a4\u30e1\u30fc\u30b8 ImageNode.getActions.openFileSearchByAttr.text=\u5c5e\u6027\u5225\u306b\u30d5\u30a1\u30a4\u30eb\u691c\u7d22\u3092\u958b\u304f KeyValueNode.menuItemText.viewFileInDir=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30fc\u5185\u306e\u30bd\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb\u3092\u8868\u793a -KeywordHits.createNodeForKey.accessTime.desc=\u30a2\u30af\u30bb\u30b9\u6642\u523b -KeywordHits.createNodeForKey.accessTime.displayName=\u30a2\u30af\u30bb\u30b9\u6642\u523b +KeywordHits.createNodeForKey.accessTime.desc=\u30a2\u30af\u30bb\u30b9\u65e5\u6642 +KeywordHits.createNodeForKey.accessTime.displayName=\u30a2\u30af\u30bb\u30b9\u65e5\u6642 KeywordHits.createNodeForKey.accessTime.name=AccessTime -KeywordHits.createNodeForKey.chgTime.desc=\u6642\u523b\u5909\u66f4 -KeywordHits.createNodeForKey.chgTime.displayName=\u5909\u66f4\u6642\u523b +KeywordHits.createNodeForKey.chgTime.desc=\u30a8\u30f3\u30c8\u30ea\u66f4\u65b0\u65e5\u6642 +KeywordHits.createNodeForKey.chgTime.displayName=\u30a8\u30f3\u30c8\u30ea\u66f4\u65b0\u65e5\u6642 KeywordHits.createNodeForKey.chgTime.name=ChangeTime -KeywordHits.createNodeForKey.modTime.desc=MFT\u5909\u66f4\u6642\u523b -KeywordHits.createNodeForKey.modTime.displayName=MFT\u5909\u66f4\u6642\u523b +KeywordHits.createNodeForKey.modTime.desc=\u66f4\u65b0\u65e5\u6642 +KeywordHits.createNodeForKey.modTime.displayName=\u66f4\u65b0\u65e5\u6642 KeywordHits.createNodeForKey.modTime.name=ModifiedTime KeywordHits.createSheet.filesWithHits.desc=\u8aac\u660e\u306a\u3057 KeywordHits.createSheet.filesWithHits.displayName=\u30d2\u30c3\u30c8\u306e\u3042\u308b\u30d5\u30a1\u30a4\u30eb @@ -199,14 +199,14 @@ ContentTagNode.createSheet.filePath.name=\u30d5\u30a1\u30a4\u30eb\u30d1\u30b9 ContentTagNode.createSheet.filePath.displayName=\u30d5\u30a1\u30a4\u30eb\u30d1\u30b9 ContentTagNode.createSheet.comment.name=\u30b3\u30e1\u30f3\u30c8 ContentTagNode.createSheet.comment.displayName=\u30b3\u30e1\u30f3\u30c8 -ContentTagNode.createSheet.fileModifiedTime.nam=MFT\u5909\u66f4\u6642\u523b -ContentTagNode.createSheet.fileModifiedTime.displayName=MFT\u5909\u66f4\u6642\u523b -ContentTagNode.createSheet.fileChangedTime.name=\u5909\u66f4\u3055\u308c\u305f\u523b\u523b -ContentTagNode.createSheet.fileChangedTime.displayName=\u5909\u66f4\u3055\u308c\u305f\u523b\u523b -ContentTagNode.createSheet.fileAccessedTime.name=\u30a2\u30af\u30bb\u30b9\u3055\u308c\u305f\u6642\u523b -ContentTagNode.createSheet.fileAccessedTime.displayName=\u30a2\u30af\u30bb\u30b9\u3055\u308c\u305f\u6642\u523b -ContentTagNode.createSheet.fileCreatedTime.name=\u4f5c\u6210\u3057\u305f\u6642\u523b -ContentTagNode.createSheet.fileCreatedTime.displayName=\u4f5c\u6210\u3057\u305f\u6642\u523b +ContentTagNode.createSheet.fileModifiedTime.nam=\u66f4\u65b0\u65e5\u6642 +ContentTagNode.createSheet.fileModifiedTime.displayName=\u66f4\u65b0\u65e5\u6642 +ContentTagNode.createSheet.fileChangedTime.name=\u30a8\u30f3\u30c8\u30ea\u66f4\u65b0\u65e5\u6642 +ContentTagNode.createSheet.fileChangedTime.displayName=\u30a8\u30f3\u30c8\u30ea\u66f4\u65b0\u65e5\u6642 +ContentTagNode.createSheet.fileAccessedTime.name=\u30a2\u30af\u30bb\u30b9\u65e5\u6642 +ContentTagNode.createSheet.fileAccessedTime.displayName=\u30a2\u30af\u30bb\u30b9\u65e5\u6642 +ContentTagNode.createSheet.fileCreatedTime.name=\u4f5c\u6210\u65e5\u6642 +ContentTagNode.createSheet.fileCreatedTime.displayName=\u4f5c\u6210\u65e5\u6642 ContentTagNode.createSheet.fileSize.name=\u30b5\u30a4\u30ba ContentTagNode.createSheet.fileSize.displayName=\u30b5\u30a4\u30ba ContentTagTypeNode.displayName.text=\u30d5\u30a1\u30a4\u30eb\u30bf\u30b0 diff --git a/Core/src/org/sleuthkit/autopsy/report/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/report/Bundle_ja.properties index 092b975370..8dc01dddb4 100644 --- a/Core/src/org/sleuthkit/autopsy/report/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/report/Bundle_ja.properties @@ -141,10 +141,10 @@ ReportGenerator.progress.createdThumb.text=\u30b5\u30e0\u30cd\u30a4\u30eb\u306e\ ReportGenerator.htmlOutput.header.file=\u30d5\u30a1\u30a4\u30eb ReportGenerator.htmlOutput.header.tag=\u30bf\u30b0 ReportGenerator.htmlOutput.header.comment=\u30b3\u30e1\u30f3\u30c8 -ReportGenerator.htmlOutput.header.timeModified=MFT\u5909\u66f4\u6642\u523b -ReportGenerator.htmlOutput.header.timeChanged=\u5909\u66f4\u3055\u308c\u305f\u523b\u523b -ReportGenerator.htmlOutput.header.timeAccessed=\u30a2\u30af\u30bb\u30b9\u3055\u308c\u305f\u6642\u523b -ReportGenerator.htmlOutput.header.timeCreated=\u4f5c\u6210\u3055\u308c\u305f\u6642\u523b +ReportGenerator.htmlOutput.header.timeModified=\u66f4\u65b0\u65e5\u6642 +ReportGenerator.htmlOutput.header.timeChanged=\u30a8\u30f3\u30c8\u30ea\u66f4\u65b0\u65e5\u6642 +ReportGenerator.htmlOutput.header.timeAccessed=\u30a2\u30af\u30bb\u30b9\u65e5\u6642 +ReportGenerator.htmlOutput.header.timeCreated=\u4f5c\u6210\u65e5\u6642 ReportGenerator.htmlOutput.header.size=\u30b5\u30a4\u30ba(\u30d0\u30a4\u30c8) ReportGenerator.htmlOutput.header.hash=\u30cf\u30c3\u30b7\u30e5 ReportGenerator.thumbnailTable.name=\u30bf\u30b0\u4ed8\u304d\u30a4\u30e1\u30fc\u30b8 diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/EventTypeUtils.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/EventTypeUtils.java index 85b900a5e6..6bff833e99 100755 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/EventTypeUtils.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/EventTypeUtils.java @@ -95,6 +95,12 @@ final public class EventTypeUtils { imageFileName = "artifact-icon.png"; } else if (typeID == TimelineEventType.WEB_FORM_ADDRESSES.getTypeID()) { imageFileName = "artifact-icon.png"; + } else if (typeID == TimelineEventType.METADATA_CREATED.getTypeID()) { + imageFileName = "blue-document-attribute-b.png"; + } else if (typeID == TimelineEventType.METADATA_LAST_SAVED.getTypeID()) { + imageFileName = "blue-document-attribute-m.png"; + } else if (typeID == TimelineEventType.METADATA_LAST_PRINTED.getTypeID()) { + imageFileName = "blue-document.png"; }else { imageFileName = "timeline_marker.png"; } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java index 72a0df796f..a403b32e7b 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java @@ -19,11 +19,18 @@ package org.sleuthkit.autopsy.keywordsearch; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.io.CharSource; import java.io.IOException; import java.io.Reader; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; import java.util.HashMap; import java.util.List; +import static java.util.Locale.US; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; @@ -54,6 +61,10 @@ import org.sleuthkit.autopsy.textextractors.TextFileExtractor; import org.sleuthkit.autopsy.textextractors.configs.ImageConfig; import org.sleuthkit.autopsy.textextractors.configs.StringsConfig; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Blackboard; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData.FileKnown; @@ -114,6 +125,26 @@ public final class KeywordSearchIngestModule implements FileIngestModule { "application/x-z", //NON-NLS "application/x-compress"); //NON-NLS + private static final List METADATA_DATE_TYPES + = ImmutableList.of( + "Last-Save-Date", //NON-NLS + "Last-Printed", //NON-NLS + "Creation-Date"); //NON-NLS + + private static final Map METADATA_TYPES_MAP = ImmutableMap.builder() + .put("Last-Save-Date", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_MODIFIED) + .put("Last-Author", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_ID) + .put("Creation-Date", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED) + .put("Company", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ORGANIZATION) + .put("Author", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_OWNER) + .put("Application-Name", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME) + .put("Last-Printed", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LAST_PRINTED_DATETIME) + .put("Producer", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME) + .put("Title", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION) + .put("pdf:PDFVersion", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_VERSION) + .build(); + + /** * Options for this extractor */ @@ -493,6 +524,9 @@ public final class KeywordSearchIngestModule implements FileIngestModule { Reader finalReader; try { Map metadata = extractor.getMetadata(); + if (!metadata.isEmpty()) { + createMetadataArtifact(aFile, metadata); + } CharSource formattedMetadata = getMetaDataCharSource(metadata); //Append the metadata to end of the file text finalReader = CharSource.concat(new CharSource() { @@ -515,7 +549,70 @@ public final class KeywordSearchIngestModule implements FileIngestModule { return false; } } + + private void createMetadataArtifact(AbstractFile aFile, Map metadata) { + + String moduleName = KeywordSearchIngestModule.class.getName(); + + Collection attributes = new ArrayList<>(); + Collection bbartifacts = new ArrayList<>(); + for (Map.Entry entry : metadata.entrySet()) { + if (METADATA_TYPES_MAP.containsKey(entry.getKey())) { + BlackboardAttribute bba = checkAttribute(entry.getKey(), entry.getValue()); + if (bba != null) { + attributes.add(bba); + } + } + } + if (!attributes.isEmpty()) { + try { + BlackboardArtifact bbart = aFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA); + bbart.addAttributes(attributes); + bbartifacts.add(bbart); + } catch (TskCoreException ex) { + // Log error and return to continue processing + logger.log(Level.WARNING, String.format("Error creating or adding metadata artifact for file %s.", aFile.getParentPath() + aFile.getName()), ex); //NON-NLS + return; + } + if (!bbartifacts.isEmpty()) { + try{ + Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard().postArtifacts(bbartifacts, moduleName); + } catch (NoCurrentCaseException | Blackboard.BlackboardException ex) { + // Log error and return to continue processing + logger.log(Level.WARNING, String.format("Unable to post blackboard artifacts for file $s.", aFile.getParentPath() + aFile.getName()) , ex); //NON-NLS + return; + } + } + } + } + + private BlackboardAttribute checkAttribute(String key, String value) { + String moduleName = KeywordSearchIngestModule.class.getName(); + if (!value.isEmpty() && value.charAt(0) != ' ') { + if (METADATA_DATE_TYPES.contains(key)) { + SimpleDateFormat metadataDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", US); + Long metadataDateTime = Long.valueOf(0); + try { + String metadataDate = value.replaceAll("T"," ").replaceAll("Z", ""); + Date usedDate = metadataDateFormat.parse(metadataDate); + metadataDateTime = usedDate.getTime()/1000; + return new BlackboardAttribute(METADATA_TYPES_MAP.get(key), moduleName, metadataDateTime); + } catch (ParseException ex) { + // catching error and displaying date that could not be parsed then will continue on. + logger.log(Level.WARNING, String.format("Failed to parse date/time %s for metadata attribute %s.", value, key), ex); //NON-NLS + return null; + } + } else { + return new BlackboardAttribute(METADATA_TYPES_MAP.get(key), moduleName, value); + } + } + + return null; + + } + + /** * Pretty print the text extractor metadata. *