mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-06 21:00:22 +00:00
Merge pull request #7419 from eugene7646/com_accounts_8122
Communications accounts DAO (8122)
This commit is contained in:
commit
ab3540df2f
207
Core/src/org/sleuthkit/autopsy/mainui/datamodel/CommAccountsDAO.java
Executable file
207
Core/src/org/sleuthkit/autopsy/mainui/datamodel/CommAccountsDAO.java
Executable file
@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2021 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.mainui.datamodel;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
import org.sleuthkit.datamodel.Blackboard;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Provides information to populate the results viewer for data in the
|
||||
* Communication Accounts section.
|
||||
*/
|
||||
@Messages({"CommAccountsDAO.fileColumns.noDescription=No Description"})
|
||||
public class CommAccountsDAO {
|
||||
|
||||
private static final int CACHE_SIZE = Account.Type.PREDEFINED_ACCOUNT_TYPES.size(); // number of cached SearchParams sub-types
|
||||
private static final long CACHE_DURATION = 2;
|
||||
private static final TimeUnit CACHE_DURATION_UNITS = TimeUnit.MINUTES;
|
||||
private final Cache<SearchParams<?>, SearchResultsDTO> searchParamsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build();
|
||||
|
||||
private static CommAccountsDAO instance = null;
|
||||
|
||||
synchronized static CommAccountsDAO getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new CommAccountsDAO();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public SearchResultsDTO getCommAcounts(CommAccountsSearchParams key, long startItem, Long maxCount, boolean hardRefresh) throws ExecutionException, IllegalArgumentException {
|
||||
if (key.getType() == null) {
|
||||
throw new IllegalArgumentException("Must have non-null type");
|
||||
} else if (key.getDataSourceId() != null && key.getDataSourceId() <= 0) {
|
||||
throw new IllegalArgumentException("Data source id must be greater than 0 or null");
|
||||
}
|
||||
|
||||
SearchParams<CommAccountsSearchParams> searchParams = new SearchParams<>(key, startItem, maxCount);
|
||||
if (hardRefresh) {
|
||||
this.searchParamsCache.invalidate(searchParams);
|
||||
}
|
||||
|
||||
return searchParamsCache.get(searchParams, () -> fetchCommAccountsDTOs(searchParams));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of paged artifacts.
|
||||
*
|
||||
* @param arts The artifacts.
|
||||
* @param searchParams The search parameters including the paging.
|
||||
*
|
||||
* @return The list of paged artifacts.
|
||||
*/
|
||||
List<BlackboardArtifact> getPaged(List<? extends BlackboardArtifact> arts, SearchParams<?> searchParams) {
|
||||
Stream<? extends BlackboardArtifact> pagedArtsStream = arts.stream()
|
||||
.sorted(Comparator.comparing((art) -> art.getId()))
|
||||
.skip(searchParams.getStartItem());
|
||||
|
||||
if (searchParams.getMaxResultsCount() != null) {
|
||||
pagedArtsStream = pagedArtsStream.limit(searchParams.getMaxResultsCount());
|
||||
}
|
||||
|
||||
return pagedArtsStream.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
long getTotalResultsCount(SearchParams<CommAccountsSearchParams> cacheKey, long currentPageSize) throws TskCoreException, NoCurrentCaseException {
|
||||
Blackboard blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
|
||||
Long dataSourceId = cacheKey.getParamData().getDataSourceId();
|
||||
BlackboardArtifact.Type artType = BlackboardArtifact.Type.TSK_ACCOUNT;
|
||||
|
||||
if ( (cacheKey.getStartItem() == 0) // offset is zero AND
|
||||
&& ( (cacheKey.getMaxResultsCount() != null && currentPageSize < cacheKey.getMaxResultsCount()) // number of results is less than max
|
||||
|| (cacheKey.getMaxResultsCount() == null)) ) { // OR max number of results was not specified
|
||||
return currentPageSize;
|
||||
} else {
|
||||
if (dataSourceId != null) {
|
||||
return blackboard.getArtifactsCount(artType.getTypeID(), dataSourceId);
|
||||
} else {
|
||||
return blackboard.getArtifactsCount(artType.getTypeID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"CommAccounts.name.text=Communication Accounts"})
|
||||
private SearchResultsDTO fetchCommAccountsDTOs(SearchParams<CommAccountsSearchParams> cacheKey) throws NoCurrentCaseException, TskCoreException, SQLException {
|
||||
|
||||
// get current page of communication accounts results
|
||||
SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
|
||||
Blackboard blackboard = skCase.getBlackboard();
|
||||
Account.Type type = cacheKey.getParamData().getType();
|
||||
Long dataSourceId = cacheKey.getParamData().getDataSourceId();
|
||||
List<BlackboardArtifact> allArtifacts = blackboard.getArtifacts(BlackboardArtifact.Type.TSK_ACCOUNT,
|
||||
BlackboardAttribute.Type.TSK_ACCOUNT_TYPE, type.getTypeName(), dataSourceId,
|
||||
false); // GVDTODO handle approved/rejected account actions
|
||||
|
||||
// get current page of artifacts
|
||||
List<BlackboardArtifact> pagedArtifacts = getPaged(allArtifacts, cacheKey);
|
||||
|
||||
// Populate the attributes for paged artifacts in the list. This is done using one database call as an efficient way to
|
||||
// load many artifacts/attributes at once.
|
||||
blackboard.loadBlackboardAttributes(pagedArtifacts);
|
||||
|
||||
DataArtifactDAO dataArtDAO = MainDAO.getInstance().getDataArtifactsDAO();
|
||||
BlackboardArtifactDAO.TableData tableData = dataArtDAO.createTableData(BlackboardArtifact.Type.TSK_ACCOUNT, pagedArtifacts);
|
||||
return new DataArtifactTableSearchResultsDTO(BlackboardArtifact.Type.TSK_ACCOUNT, tableData.columnKeys, tableData.rows, cacheKey.getStartItem(), allArtifacts.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles fetching and paging of data for communication accounts.
|
||||
*/
|
||||
public static class CommAccountFetcher extends DAOFetcher<CommAccountsSearchParams> {
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param params Parameters to handle fetching of data.
|
||||
*/
|
||||
public CommAccountFetcher(CommAccountsSearchParams params) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResultsDTO getSearchResults(int pageSize, int pageIdx, boolean hardRefresh) throws ExecutionException {
|
||||
return MainDAO.getInstance().getCommAccountsDAO().getCommAcounts(this.getParameters(), pageIdx * pageSize, (long) pageSize, hardRefresh);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRefreshRequired(PropertyChangeEvent evt) {
|
||||
CommAccountsSearchParams params = this.getParameters();
|
||||
String eventType = evt.getPropertyName();
|
||||
|
||||
if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
|
||||
/**
|
||||
* Checking for a current case is a stop gap measure until a
|
||||
* different way of handling the closing of cases is worked out.
|
||||
* Currently, remote events may be received for a case that is
|
||||
* already closed.
|
||||
*/
|
||||
try {
|
||||
Case.getCurrentCaseThrows();
|
||||
/**
|
||||
* Even with the check above, it is still possible that the
|
||||
* case will be closed in a different thread before this
|
||||
* code executes. If that happens, it is possible for the
|
||||
* event to have a null oldValue.
|
||||
*/
|
||||
ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
|
||||
if (null != eventData
|
||||
&& eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID()) {
|
||||
|
||||
// check that the update is for the same account type
|
||||
for (BlackboardArtifact artifact : eventData.getArtifacts()) {
|
||||
for (BlackboardAttribute atribute : artifact.getAttributes()) {
|
||||
if (atribute.getAttributeType() == BlackboardAttribute.Type.TSK_ACCOUNT_TYPE) {
|
||||
if (atribute.getValueString().equals(params.getType().toString())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (NoCurrentCaseException notUsed) {
|
||||
// Case is closed, do nothing.
|
||||
} catch (TskCoreException ex) {
|
||||
// There is nothing we can do with the exception.
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2021 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.mainui.datamodel;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
|
||||
/**
|
||||
* Key for accessing data about communication accounts from the DAO.
|
||||
*/
|
||||
public class CommAccountsSearchParams {
|
||||
|
||||
private final Account.Type type;
|
||||
private final Long dataSourceId;
|
||||
|
||||
public CommAccountsSearchParams(Account.Type type, Long dataSourceId) {
|
||||
this.type = type;
|
||||
this.dataSourceId = dataSourceId;
|
||||
}
|
||||
|
||||
public Account.Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Long getDataSourceId() {
|
||||
return dataSourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 7;
|
||||
hash = 23 * hash + Objects.hashCode(this.type);
|
||||
hash = 23 * hash + Objects.hashCode(this.dataSourceId);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final CommAccountsSearchParams other = (CommAccountsSearchParams) obj;
|
||||
if (!Objects.equals(this.dataSourceId, other.dataSourceId)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.type, other.type)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -31,7 +31,6 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.DirectoryRowDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.ImageRowDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.VolumeRowDTO;
|
||||
|
@ -39,7 +39,8 @@ public class MainDAO {
|
||||
private final ViewsDAO viewsDAO = ViewsDAO.getInstance();
|
||||
private final FileSystemDAO fileSystemDAO = FileSystemDAO.getInstance();
|
||||
private final TagsDAO tagsDAO = TagsDAO.getInstance();
|
||||
private final OsAccountsDAO accountsDAO = OsAccountsDAO.getInstance();
|
||||
private final OsAccountsDAO osAccountsDAO = OsAccountsDAO.getInstance();
|
||||
private final CommAccountsDAO commAccountsDAO = CommAccountsDAO.getInstance();
|
||||
|
||||
public DataArtifactDAO getDataArtifactsDAO() {
|
||||
return dataArtifactDAO;
|
||||
@ -62,6 +63,10 @@ public class MainDAO {
|
||||
}
|
||||
|
||||
public OsAccountsDAO getOsAccountsDAO() {
|
||||
return accountsDAO;
|
||||
return osAccountsDAO;
|
||||
}
|
||||
|
||||
public CommAccountsDAO getCommAccountsDAO() {
|
||||
return commAccountsDAO;
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ import org.sleuthkit.autopsy.casemodule.services.TagsManager;
|
||||
import org.sleuthkit.autopsy.testutils.CaseUtils;
|
||||
import org.sleuthkit.autopsy.testutils.TestUtilsException;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
import org.sleuthkit.datamodel.AccountFileInstance;
|
||||
import org.sleuthkit.datamodel.AnalysisResult;
|
||||
import org.sleuthkit.datamodel.Blackboard;
|
||||
import org.sleuthkit.datamodel.Blackboard.BlackboardException;
|
||||
@ -126,8 +128,17 @@ public class TableSearchTest extends NbTestCase {
|
||||
|
||||
// OS Accounts test
|
||||
private static final String REALM_NAME_COLUMN = "Realm Name";
|
||||
private static final String HOST_COLUMN = "Host";
|
||||
private static final String HOST_COLUMN = "Host";
|
||||
|
||||
// Communications accounts test
|
||||
private static final String ACCOUNT_TYPE_COLUMN = "Account Type";
|
||||
private static final String ID_COLUMN = "ID";
|
||||
private static final String EMAIL_A = "aaa@yahoo.com";
|
||||
private static final String EMAIL_B = "bbb@gmail.com";
|
||||
private static final String EMAIL_C = "ccc@funmail.com";
|
||||
private static final String PHONENUM_1 = "1117771111";
|
||||
private static final String PHONENUM_2 = "2223337777";
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// Data to be used across the test methods.
|
||||
// These are initialized in setUpCaseDatabase().
|
||||
@ -212,6 +223,7 @@ public class TableSearchTest extends NbTestCase {
|
||||
fileSystemTest();
|
||||
tagsTest();
|
||||
OsAccountsTest();
|
||||
commAccountsSearchTest();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -494,6 +506,15 @@ public class TableSearchTest extends NbTestCase {
|
||||
osAccount1 = accountMgr.newWindowsOsAccount("S-1-5-21-647283-46237-100", null, null, host2, OsAccountRealm.RealmScope.DOMAIN);
|
||||
accountMgr.newOsAccountInstance(osAccount1, dataSource2, OsAccountInstance.OsAccountInstanceType.LAUNCHED);
|
||||
|
||||
// Add communication accounts
|
||||
openCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, EMAIL_A, "Test Module", fileA1);
|
||||
openCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, EMAIL_B, "Test Module", fileA2);
|
||||
openCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.DEVICE, "devId1", "Test Module", fileA2);
|
||||
openCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.PHONE, PHONENUM_1, "Test Module", fileA2);
|
||||
|
||||
openCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, EMAIL_C, "Test Module", customFile);
|
||||
openCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.PHONE, PHONENUM_2, "Test Module", customFile);
|
||||
|
||||
} catch (TestUtilsException | TskCoreException | BlackboardException | TagsManager.TagNameAlreadyExistsException | OsAccountManager.NotUserSIDException ex) {
|
||||
if (trans != null) {
|
||||
try {
|
||||
@ -608,6 +629,72 @@ public class TableSearchTest extends NbTestCase {
|
||||
Assert.fail(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void commAccountsSearchTest() {
|
||||
// Quick test that everything is initialized
|
||||
assertTrue(db != null);
|
||||
|
||||
try {
|
||||
CommAccountsDAO commAccountsDAO = MainDAO.getInstance().getCommAccountsDAO();
|
||||
|
||||
// Get emails from all data sources
|
||||
CommAccountsSearchParams param = new CommAccountsSearchParams(Account.Type.EMAIL, null);
|
||||
SearchResultsDTO results = commAccountsDAO.getCommAcounts(param, 0, null, false);
|
||||
assertEquals(3, results.getTotalResultsCount());
|
||||
assertEquals(3, results.getItems().size());
|
||||
|
||||
// Get device accounts from data source 1
|
||||
param = new CommAccountsSearchParams(Account.Type.DEVICE, dataSource1.getId());
|
||||
results = commAccountsDAO.getCommAcounts(param, 0, null, false);
|
||||
assertEquals(1, results.getTotalResultsCount());
|
||||
assertEquals(1, results.getItems().size());
|
||||
|
||||
// Get email accounts from data source 2
|
||||
param = new CommAccountsSearchParams(Account.Type.EMAIL, dataSource2.getId());
|
||||
results = commAccountsDAO.getCommAcounts(param, 0, null, false);
|
||||
assertEquals(1, results.getTotalResultsCount());
|
||||
assertEquals(1, results.getItems().size());
|
||||
|
||||
// Check that a few of the expected column names are present
|
||||
List<String> columnDisplayNames = results.getColumns().stream().map(p -> p.getDisplayName()).collect(Collectors.toList());
|
||||
assertTrue(columnDisplayNames.contains(ACCOUNT_TYPE_COLUMN));
|
||||
assertTrue(columnDisplayNames.contains(ID_COLUMN));
|
||||
|
||||
// Get the row
|
||||
RowDTO rowDTO = results.getItems().get(0);
|
||||
assertTrue(rowDTO instanceof DataArtifactRowDTO);
|
||||
DataArtifactRowDTO accountResultRowDTO = (DataArtifactRowDTO) rowDTO;
|
||||
|
||||
// Check that some of the expected result column values are present
|
||||
assertTrue(accountResultRowDTO.getCellValues().contains(EMAIL_C));
|
||||
assertTrue(accountResultRowDTO.getCellValues().contains(customFile.getName()));
|
||||
|
||||
// Get phone accounts from all data sources
|
||||
param = new CommAccountsSearchParams(Account.Type.PHONE, null);
|
||||
results = commAccountsDAO.getCommAcounts(param, 0, null, false);
|
||||
assertEquals(2, results.getTotalResultsCount());
|
||||
assertEquals(2, results.getItems().size());
|
||||
|
||||
// Get phone accounts from data source 2
|
||||
param = new CommAccountsSearchParams(Account.Type.PHONE, dataSource2.getId());
|
||||
results = commAccountsDAO.getCommAcounts(param, 0, null, false);
|
||||
assertEquals(1, results.getTotalResultsCount());
|
||||
assertEquals(1, results.getItems().size());
|
||||
|
||||
// Get the row
|
||||
rowDTO = results.getItems().get(0);
|
||||
assertTrue(rowDTO instanceof DataArtifactRowDTO);
|
||||
accountResultRowDTO = (DataArtifactRowDTO) rowDTO;
|
||||
|
||||
// Check that some of the expected result column values are present
|
||||
assertTrue(accountResultRowDTO.getCellValues().contains(PHONENUM_2));
|
||||
assertTrue(accountResultRowDTO.getCellValues().contains(customFile.getName()));
|
||||
|
||||
} catch (ExecutionException ex) {
|
||||
Exceptions.printStackTrace(ex);
|
||||
Assert.fail(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void mimeSearchTest() {
|
||||
// Quick test that everything is initialized
|
||||
|
Loading…
x
Reference in New Issue
Block a user