From 5b480ce501d32a19fa3fb47093e05b18c6c9aa88 Mon Sep 17 00:00:00 2001
From: Sebastian Richner
Date: Mon, 4 Mar 2024 17:38:21 +0100
Subject: [PATCH] Add obfuscation term feature
---
src/electron/electron/ipc/IpcHandler.ts | 9 +-
.../main/services/DataExportService.ts | 137 ++++++++++--------
.../DataExportWindowActivityTracker.vue | 63 ++++++--
src/electron/src/utils/Commands.ts | 3 +-
src/electron/src/views/DataExportView.vue | 110 ++++++++++----
5 files changed, 220 insertions(+), 102 deletions(-)
diff --git a/src/electron/electron/ipc/IpcHandler.ts b/src/electron/electron/ipc/IpcHandler.ts
index e9097f6f..75e2be6e 100644
--- a/src/electron/electron/ipc/IpcHandler.ts
+++ b/src/electron/electron/ipc/IpcHandler.ts
@@ -139,9 +139,14 @@ export class IpcHandler {
private async startDataExport(
windowActivityExportType: DataExportType,
- userInputExportType: DataExportType
+ userInputExportType: DataExportType,
+ obfuscationTerms: string[]
): Promise {
- return this.dataExportService.startDataExport(windowActivityExportType, userInputExportType);
+ return this.dataExportService.startDataExport(
+ windowActivityExportType,
+ userInputExportType,
+ obfuscationTerms
+ );
}
private async revealItemInFolder(path: string): Promise {
diff --git a/src/electron/electron/main/services/DataExportService.ts b/src/electron/electron/main/services/DataExportService.ts
index 6df73cf0..08a2739f 100644
--- a/src/electron/electron/main/services/DataExportService.ts
+++ b/src/electron/electron/main/services/DataExportService.ts
@@ -16,73 +16,96 @@ export class DataExportService {
new WindowActivityTrackerService();
public async startDataExport(
windowActivityExportType: DataExportType,
- userInputExportType: DataExportType
+ userInputExportType: DataExportType,
+ obfuscationTerms: string[]
): Promise {
LOG.info('startDataExport called');
- const dbName = 'database.sqlite';
- let dbPath = dbName;
- if (!(is.dev && process.env['VITE_DEV_SERVER_URL'])) {
- const userDataPath = app.getPath('userData');
- dbPath = path.join(userDataPath, dbName);
- }
+ try {
+ const dbName = 'database.sqlite';
+ let dbPath = dbName;
+ if (!(is.dev && process.env['VITE_DEV_SERVER_URL'])) {
+ const userDataPath = app.getPath('userData');
+ dbPath = path.join(userDataPath, dbName);
+ }
- const settings: Settings = await Settings.findOneBy({ onlyOneEntityShouldExist: 1 });
+ const settings: Settings = await Settings.findOneBy({ onlyOneEntityShouldExist: 1 });
- const userDataPath = app.getPath('userData');
- const exportFolderPath = path.join(userDataPath, 'exports');
- if (!fs.existsSync(exportFolderPath)) {
- fs.mkdirSync(exportFolderPath);
- }
- const now = new Date();
- const nowStr = now.toISOString().replace(/:/g, '-').replace('T', '_').slice(0, 16);
- // Also update the DataExportView if you change the file name here
- const exportDbPath = path.join(
- userDataPath,
- 'exports',
- `PA_${settings.subjectId}_${nowStr}.sqlite`
- );
- fs.copyFileSync(dbPath, exportDbPath);
- LOG.info(`Database copied to ${exportDbPath}`);
- const db = new Database(exportDbPath);
- // see https://github.com/WiseLibs/better-sqlite3/blob/master/docs/performance.md
- db.pragma('journal_mode = WAL');
+ const userDataPath = app.getPath('userData');
+ const exportFolderPath = path.join(userDataPath, 'exports');
+ if (!fs.existsSync(exportFolderPath)) {
+ fs.mkdirSync(exportFolderPath);
+ }
+ const now = new Date();
+ const nowStr = now.toISOString().replace(/:/g, '-').replace('T', '_').slice(0, 16);
+ // Also update the DataExportView if you change the file name here
+ const exportDbPath = path.join(
+ userDataPath,
+ 'exports',
+ `PA_${settings.subjectId}_${nowStr}.sqlite`
+ );
+ fs.copyFileSync(dbPath, exportDbPath);
+ LOG.info(`Database copied to ${exportDbPath}`);
+ const db = new Database(exportDbPath);
+ // see https://github.com/WiseLibs/better-sqlite3/blob/master/docs/performance.md
+ db.pragma('journal_mode = WAL');
- // see https://github.com/m4heshd/better-sqlite3-multiple-ciphers/issues/5#issuecomment-1008330548
- db.pragma(`cipher='sqlcipher'`);
- db.pragma(`legacy=4`);
+ // see https://github.com/m4heshd/better-sqlite3-multiple-ciphers/issues/5#issuecomment-1008330548
+ db.pragma(`cipher='sqlcipher'`);
+ db.pragma(`legacy=4`);
- db.pragma(`rekey='PersonalAnalytics_${settings.subjectId}'`);
+ db.pragma(`rekey='PersonalAnalytics_${settings.subjectId}'`);
- if (windowActivityExportType === DataExportType.Obfuscate) {
- const items: { windowTitle: string; url: string; id: string }[] =
- await WindowActivityEntity.getRepository()
- .createQueryBuilder('window_activity')
- .select('id, windowTitle, url')
- .getRawMany();
- for (const item of items) {
- const randomizeWindowTitle = this.windowActivityTrackerService.randomizeWindowTitle(
- item.windowTitle
- );
- const randomizeUrl = this.windowActivityTrackerService.randomizeUrl(item.url);
- const obfuscateWindowActivities = db.prepare(
- 'UPDATE window_activity SET windowTitle = ?, url = ? WHERE id = ?'
- );
- obfuscateWindowActivities.run(randomizeWindowTitle, randomizeUrl, item.id);
+ if (windowActivityExportType === DataExportType.Obfuscate || obfuscationTerms?.length > 0) {
+ const items: { windowTitle: string; url: string; id: string }[] =
+ await WindowActivityEntity.getRepository()
+ .createQueryBuilder('window_activity')
+ .select('id, windowTitle, url')
+ .getRawMany();
+ for (const item of items) {
+ if (windowActivityExportType === DataExportType.Obfuscate) {
+ const randomizeWindowTitle = this.windowActivityTrackerService.randomizeWindowTitle(
+ item.windowTitle
+ );
+ const randomizeUrl = this.windowActivityTrackerService.randomizeUrl(item.url);
+ const obfuscateWindowActivities = db.prepare(
+ 'UPDATE window_activity SET windowTitle = ?, url = ? WHERE id = ?'
+ );
+ obfuscateWindowActivities.run(randomizeWindowTitle, randomizeUrl, item.id);
+ } else if (obfuscationTerms.length > 0) {
+ const lowerCaseObfuscationTerms: string[] = obfuscationTerms.map((term: string) =>
+ term.toLowerCase()
+ );
+ lowerCaseObfuscationTerms.forEach((term: string) => {
+ if (
+ item.windowTitle?.toLowerCase().includes(term) ||
+ item.url?.toLowerCase().includes(term)
+ ) {
+ const obfuscateWindowActivities = db.prepare(
+ 'UPDATE window_activity SET windowTitle = ?, url = ? WHERE id = ?'
+ );
+ obfuscateWindowActivities.run('[anonymized]', '[anonymized]', item.id);
+ }
+ });
+ }
+ }
+ } else if (windowActivityExportType === DataExportType.None) {
+ // remove all window activities
+ const removeWindowActivities = db.prepare('DROP TABLE IF EXISTS window_activity');
+ removeWindowActivities.run();
}
- } else if (windowActivityExportType === DataExportType.None) {
- // remove all window activities
- const removeWindowActivities = db.prepare('DROP TABLE IF EXISTS window_activity');
- removeWindowActivities.run();
- }
- if (userInputExportType === DataExportType.None) {
- // remove all user input
- const removeUserInput = db.prepare('DROP TABLE IF EXISTS user_input');
- removeUserInput.run();
- }
+ if (userInputExportType === DataExportType.None) {
+ // remove all user input
+ const removeUserInput = db.prepare('DROP TABLE IF EXISTS user_input');
+ removeUserInput.run();
+ }
- db.close();
+ db.close();
- return exportDbPath;
+ return exportDbPath;
+ } catch (error) {
+ LOG.error('Error exporting the data', error);
+ throw error;
+ }
}
}
diff --git a/src/electron/src/components/DataExportWindowActivityTracker.vue b/src/electron/src/components/DataExportWindowActivityTracker.vue
index 6e73896f..4b3f22e5 100644
--- a/src/electron/src/components/DataExportWindowActivityTracker.vue
+++ b/src/electron/src/components/DataExportWindowActivityTracker.vue
@@ -20,12 +20,21 @@ const props = defineProps({
}
});
-const emits = defineEmits(['change']);
+const emits = defineEmits(['optionChanged', 'obfuscationTermsChanged', 'obfuscateSampleData']);
const selectedOption = ref(props.defaultValue);
+const obfuscationTermsInput = ref('');
-const emitChange = async () => {
- emits('change', selectedOption.value);
+const emitOptionChanged = async () => {
+ emits('optionChanged', selectedOption.value);
+};
+
+const emitObfuscationTermsChanged = async () => {
+ emits('obfuscationTermsChanged', obfuscationTermsInput.value);
+};
+
+const emitObfuscateSampleData = async () => {
+ emits('obfuscateSampleData');
};
@@ -37,15 +46,14 @@ const emitChange = async () => {
-
+
Window Title |
URL |
Activity |
Process Name |
+ Process Path |
Process ID |
Timestamp |
@@ -60,26 +68,34 @@ const emitChange = async () => {
{{ windowActivity.activity }} |
{{ windowActivity.processName }} |
+
+ {{ windowActivity.processPath }}
+ |
{{ windowActivity.processId }} |
{{ windowActivity.ts.toLocaleString() }} |
-
-
+
+
+
How do you want to share your data?
@@ -91,7 +107,7 @@ const emitChange = async () => {
type="radio"
:value="DataExportType.Obfuscate"
class="radio checked:bg-blue-500"
- @change="emitChange"
+ @change="emitOptionChanged"
/>
@@ -103,9 +119,32 @@ const emitChange = async () => {
type="radio"
:value="DataExportType.None"
class="radio checked:bg-blue-500"
- @change="emitChange"
+ @change="emitOptionChanged"
+ />
+
+
+
+
+
+
Obfuscate content by terms
+
+
+
+
diff --git a/src/electron/src/utils/Commands.ts b/src/electron/src/utils/Commands.ts
index 633941a9..0197cd08 100644
--- a/src/electron/src/utils/Commands.ts
+++ b/src/electron/src/utils/Commands.ts
@@ -23,7 +23,8 @@ type Commands = {
obfuscateWindowActivityDtosById(ids: string[]): Promise;
startDataExport: (
windowActivityExportType: DataExportType,
- userInputExportType: DataExportType
+ userInputExportType: DataExportType,
+ obfuscationTerms: string[]
) => Promise;
revealItemInFolder: (path: string) => Promise;
startAllTrackers: () => void;
diff --git a/src/electron/src/views/DataExportView.vue b/src/electron/src/views/DataExportView.vue
index 6d7b4cf4..461ec53e 100644
--- a/src/electron/src/views/DataExportView.vue
+++ b/src/electron/src/views/DataExportView.vue
@@ -29,6 +29,8 @@ const exportExperienceSamplesSelectedOption = ref(DataExportType
const exportWindowActivitySelectedOption = ref(DataExportType.None);
const exportUserInputSelectedOption = ref(DataExportType.None);
+const obfuscationTermsInput = ref();
+
const isExporting = ref(false);
const hasExportError = ref(false);
@@ -77,10 +79,50 @@ async function handleWindowActivityExportConfigChanged(newSelectedOption: DataEx
obfuscateWindowActivities.value = true;
} else if (newSelectedOption === DataExportType.All) {
obfuscateWindowActivities.value = false;
+ mostRecentWindowActivities.value = await typedIpcRenderer.invoke(
+ 'getMostRecentWindowActivityDtos',
+ 20
+ );
}
exportWindowActivitySelectedOption.value = newSelectedOption;
}
+async function handleObfuscationTermsChanged(newObfuscationTerms: string) {
+ if (!newObfuscationTerms) {
+ obfuscationTermsInput.value = [];
+ } else {
+ obfuscationTermsInput.value = newObfuscationTerms.split(',').map((s: string) => s.trim());
+ }
+}
+
+async function handleObfuscateSampleData() {
+ if (
+ obfuscationTermsInput.value &&
+ obfuscationTermsInput?.value?.length > 0 &&
+ mostRecentWindowActivities.value
+ ) {
+ mostRecentWindowActivities.value = mostRecentWindowActivities.value?.map((activity) => {
+ let windowTitle = activity.windowTitle;
+ let url = activity.url;
+ obfuscationTermsInput.value?.forEach((term) => {
+ if (
+ windowTitle?.toLowerCase().includes(term.toLowerCase()) ||
+ url?.toLowerCase().includes(term.toLowerCase())
+ ) {
+ windowTitle = '[anonymized]';
+ url = '[anonymized]';
+ }
+ });
+ return { ...activity, windowTitle, url };
+ });
+ } else {
+ mostRecentWindowActivities.value = await typedIpcRenderer.invoke(
+ 'getMostRecentWindowActivityDtos',
+ 20
+ );
+ }
+}
+
function handleExperienceSamplingConfigChanged(newSelectedOption: DataExportType) {
exportExperienceSamplesSelectedOption.value = newSelectedOption;
}
@@ -104,10 +146,12 @@ async function handleNextStep() {
if (currentNamedStep.value === 'create-export') {
isExporting.value = true;
try {
+ const obfuscationTerms = Array.from(obfuscationTermsInput.value || []);
pathToExportedFile.value = await typedIpcRenderer.invoke(
'startDataExport',
exportWindowActivitySelectedOption.value,
- exportUserInputSelectedOption.value
+ exportUserInputSelectedOption.value,
+ Array.from(obfuscationTerms)
);
hasExportError.value = false;
const now = new Date();
@@ -206,34 +250,6 @@ function revealItemInFolder(event: Event) {
-
-
-
-
-
Your Export is Ready
@@ -259,7 +275,11 @@ function revealItemInFolder(event: Event) {
Click here to open the upload
page.
- Upload the file named (data-export.sqlite) using the upload page.
+
+ Upload the file named
+ {{ fileName }} using
+ the upload page.
+
Please contact {{ studyConfig.contactName }} ({{ studyConfig.contactEmail }}) in
@@ -274,6 +294,36 @@ function revealItemInFolder(event: Event) {
+
+
+
+
+