Skip to content

Commit

Permalink
Add obfuscation term feature
Browse files Browse the repository at this point in the history
  • Loading branch information
SRichner committed Mar 4, 2024
1 parent 64b27aa commit 5b480ce
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 102 deletions.
9 changes: 7 additions & 2 deletions src/electron/electron/ipc/IpcHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,14 @@ export class IpcHandler {

private async startDataExport(
windowActivityExportType: DataExportType,
userInputExportType: DataExportType
userInputExportType: DataExportType,
obfuscationTerms: string[]
): Promise<string> {
return this.dataExportService.startDataExport(windowActivityExportType, userInputExportType);
return this.dataExportService.startDataExport(
windowActivityExportType,
userInputExportType,
obfuscationTerms
);
}

private async revealItemInFolder(path: string): Promise<void> {
Expand Down
137 changes: 80 additions & 57 deletions src/electron/electron/main/services/DataExportService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,73 +16,96 @@ export class DataExportService {
new WindowActivityTrackerService();
public async startDataExport(
windowActivityExportType: DataExportType,
userInputExportType: DataExportType
userInputExportType: DataExportType,
obfuscationTerms: string[]
): Promise<string> {
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;
}
}
}
63 changes: 51 additions & 12 deletions src/electron/src/components/DataExportWindowActivityTracker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,21 @@ const props = defineProps({
}
});
const emits = defineEmits(['change']);
const emits = defineEmits(['optionChanged', 'obfuscationTermsChanged', 'obfuscateSampleData']);
const selectedOption = ref<string>(props.defaultValue);
const obfuscationTermsInput = ref<string>('');
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');
};
</script>
<template>
Expand All @@ -37,15 +46,14 @@ const emitChange = async () => {
</p>
</div>
<div v-if="selectedOption != DataExportType.None" class="max-h-48 overflow-auto">
<table
class="table table-zebra table-pin-rows w-full overflow-auto text-xs"
>
<table class="table table-zebra table-pin-rows w-full overflow-auto text-xs">
<thead class="border-b">
<tr>
<th>Window Title</th>
<th>URL</th>
<th>Activity</th>
<th>Process Name</th>
<th>Process Path</th>
<th>Process ID</th>
<th>Timestamp</th>
</tr>
Expand All @@ -60,26 +68,34 @@ const emitChange = async () => {
</td>
<td>{{ windowActivity.activity }}</td>
<td>{{ windowActivity.processName }}</td>
<td>
<div class="max-w-56 truncate">{{ windowActivity.processPath }}</div>
</td>
<td>{{ windowActivity.processId }}</td>
<td>{{ windowActivity.ts.toLocaleString() }}</td>
</tr>
</tbody>
</table>
</div>
<div class="mt-4 flex">
<div class="mr-4">

<div class="mt-8 flex">
<div class="mr-12">
<div class="prose">
<h2>How do you want to share your data?</h2>
</div>
<div class="form-control">
<label class="label cursor-pointer">
<span class="label-text">Share data as-is</span>
<span class="label-text">{{
obfuscationTermsInput.length > 0
? 'Share partially obfuscated data'
: 'Share data as-is'
}}</span>
<input
v-model="selectedOption"
type="radio"
:value="DataExportType.All"
class="radio checked:bg-blue-500"
@change="emitChange"
@change="emitOptionChanged"
/>
</label>
</div>
Expand All @@ -91,7 +107,7 @@ const emitChange = async () => {
type="radio"
:value="DataExportType.Obfuscate"
class="radio checked:bg-blue-500"
@change="emitChange"
@change="emitOptionChanged"
/>
</label>
</div>
Expand All @@ -103,9 +119,32 @@ const emitChange = async () => {
type="radio"
:value="DataExportType.None"
class="radio checked:bg-blue-500"
@change="emitChange"
@change="emitOptionChanged"
/>
</label>
</div>
</div>
<div>
<div class="prose">
<h3>Obfuscate content by terms</h3>
</div>
<div class="flex flex-col">
<label class="form-control w-full max-w-xs">
<div class="label">
<span class="label-text">You can enter multiple terms separated by commas</span>
</div>
<input
v-model="obfuscationTermsInput"
type="text"
placeholder="Enter, terms, here, ..."
class="input input-bordered w-full max-w-xs text-sm"
@input="emitObfuscationTermsChanged"
@keyup.enter="emitObfuscateSampleData"
/>
</label>
<button class="btn btn-neutral ml-auto mt-2" @click="emitObfuscateSampleData">
Obfuscate sample data
</button>
</div>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/electron/src/utils/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ type Commands = {
obfuscateWindowActivityDtosById(ids: string[]): Promise<WindowActivityDto[]>;
startDataExport: (
windowActivityExportType: DataExportType,
userInputExportType: DataExportType
userInputExportType: DataExportType,
obfuscationTerms: string[]
) => Promise<string>;
revealItemInFolder: (path: string) => Promise<void>;
startAllTrackers: () => void;
Expand Down
Loading

0 comments on commit 5b480ce

Please sign in to comment.