From ba10d0704212f2bc8fabf0d3d6ebb552fd183401 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 17 May 2018 10:52:06 -0400 Subject: [PATCH] export service --- src/abstractions/export.service.ts | 4 + src/angular/components/export.component.ts | 123 +-------------------- src/services/export.service.ts | 112 +++++++++++++++++++ 3 files changed, 120 insertions(+), 119 deletions(-) create mode 100644 src/abstractions/export.service.ts create mode 100644 src/services/export.service.ts diff --git a/src/abstractions/export.service.ts b/src/abstractions/export.service.ts new file mode 100644 index 0000000000..e03ce20644 --- /dev/null +++ b/src/abstractions/export.service.ts @@ -0,0 +1,4 @@ +export abstract class ExportService { + getCsv: () => Promise; + getFileName: () => string; +} diff --git a/src/angular/components/export.component.ts b/src/angular/components/export.component.ts index ac5a6a87a0..964d8f61b7 100644 --- a/src/angular/components/export.component.ts +++ b/src/angular/components/export.component.ts @@ -1,5 +1,3 @@ -import * as papa from 'papaparse'; - import { ToasterService } from 'angular2-toaster'; import { Angulartics2 } from 'angulartics2'; @@ -8,14 +6,8 @@ import { Output, } from '@angular/core'; -import { CipherType } from '../../enums/cipherType'; - -import { CipherView } from '../../models/view/cipherView'; -import { FolderView } from '../../models/view/folderView'; - -import { CipherService } from '../../abstractions/cipher.service'; import { CryptoService } from '../../abstractions/crypto.service'; -import { FolderService } from '../../abstractions/folder.service'; +import { ExportService } from '../../abstractions/export.service'; import { I18nService } from '../../abstractions/i18n.service'; import { PlatformUtilsService } from '../../abstractions/platformUtils.service'; import { UserService } from '../../abstractions/user.service'; @@ -27,10 +19,9 @@ export class ExportComponent { showPassword = false; constructor(protected analytics: Angulartics2, protected toasterService: ToasterService, - protected cipherService: CipherService, protected folderService: FolderService, protected cryptoService: CryptoService, protected userService: UserService, protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, - protected win: Window) { } + protected exportService: ExportService, protected win: Window) { } async submit() { if (this.masterPassword == null || this.masterPassword === '') { @@ -45,7 +36,7 @@ export class ExportComponent { const storedKeyHash = await this.cryptoService.getKeyHash(); if (storedKeyHash != null && keyHash != null && storedKeyHash === keyHash) { - const csv = await this.getCsv(); + const csv = await this.exportService.getCsv(); this.analytics.eventTrack.next({ action: 'Exported Data' }); this.downloadFile(csv); this.saved(); @@ -65,114 +56,8 @@ export class ExportComponent { this.onSaved.emit(); } - private async checkPassword() { - const email = await this.userService.getEmail(); - const key = await this.cryptoService.makeKey(this.masterPassword, email); - const keyHash = await this.cryptoService.hashPassword(this.masterPassword, key); - const storedKeyHash = await this.cryptoService.getKeyHash(); - if (storedKeyHash == null || keyHash == null || storedKeyHash !== keyHash) { - throw new Error('Invalid password.'); - } - } - - private async getCsv(): Promise { - let decFolders: FolderView[] = []; - let decCiphers: CipherView[] = []; - const promises = []; - - promises.push(this.folderService.getAllDecrypted().then((folders) => { - decFolders = folders; - })); - - promises.push(this.cipherService.getAllDecrypted().then((ciphers) => { - decCiphers = ciphers; - })); - - await Promise.all(promises); - - const foldersMap = new Map(); - decFolders.forEach((f) => { - foldersMap.set(f.id, f); - }); - - const exportCiphers: any[] = []; - decCiphers.forEach((c) => { - // only export logins and secure notes - if (c.type !== CipherType.Login && c.type !== CipherType.SecureNote) { - return; - } - - const cipher: any = { - folder: c.folderId && foldersMap.has(c.folderId) ? foldersMap.get(c.folderId).name : null, - favorite: c.favorite ? 1 : null, - type: null, - name: c.name, - notes: c.notes, - fields: null, - // Login props - login_uri: null, - login_username: null, - login_password: null, - login_totp: null, - }; - - if (c.fields) { - c.fields.forEach((f: any) => { - if (!cipher.fields) { - cipher.fields = ''; - } else { - cipher.fields += '\n'; - } - - cipher.fields += ((f.name || '') + ': ' + f.value); - }); - } - - switch (c.type) { - case CipherType.Login: - cipher.type = 'login'; - cipher.login_username = c.login.username; - cipher.login_password = c.login.password; - cipher.login_totp = c.login.totp; - - if (c.login.uris) { - cipher.login_uri = []; - c.login.uris.forEach((u) => { - cipher.login_uri.push(u.uri); - }); - } - break; - case CipherType.SecureNote: - cipher.type = 'note'; - break; - default: - return; - } - - exportCiphers.push(cipher); - }); - - return papa.unparse(exportCiphers); - } - private downloadFile(csv: string): void { - const fileName = this.makeFileName(); + const fileName = this.exportService.getFileName(); this.platformUtilsService.saveFile(this.win, csv, { type: 'text/plain' }, fileName); } - - private makeFileName(): string { - const now = new Date(); - const dateString = - now.getFullYear() + '' + this.padNumber(now.getMonth() + 1, 2) + '' + this.padNumber(now.getDate(), 2) + - this.padNumber(now.getHours(), 2) + '' + this.padNumber(now.getMinutes(), 2) + - this.padNumber(now.getSeconds(), 2); - - return 'bitwarden_export_' + dateString + '.csv'; - } - - private padNumber(num: number, width: number, padCharacter: string = '0'): string { - const numString = num.toString(); - return numString.length >= width ? numString : - new Array(width - numString.length + 1).join(padCharacter) + numString; - } } diff --git a/src/services/export.service.ts b/src/services/export.service.ts new file mode 100644 index 0000000000..6f2a1ae3ee --- /dev/null +++ b/src/services/export.service.ts @@ -0,0 +1,112 @@ +import * as papa from 'papaparse'; + +import { CipherType } from '../enums/cipherType'; + +import { CipherService } from '../abstractions/cipher.service'; +import { ExportService as ExportServiceAbstraction } from '../abstractions/export.service'; +import { FolderService } from '../abstractions/folder.service'; + +import { CipherView } from '../models/view/cipherView'; +import { FolderView } from '../models/view/folderView'; + +import { Utils } from '../misc/utils'; + +export class ExportService implements ExportServiceAbstraction { + constructor(private folderService: FolderService, private cipherService: CipherService) { } + + async getCsv(): Promise { + let decFolders: FolderView[] = []; + let decCiphers: CipherView[] = []; + const promises = []; + + promises.push(this.folderService.getAllDecrypted().then((folders) => { + decFolders = folders; + })); + + promises.push(this.cipherService.getAllDecrypted().then((ciphers) => { + decCiphers = ciphers; + })); + + await Promise.all(promises); + + const foldersMap = new Map(); + decFolders.forEach((f) => { + foldersMap.set(f.id, f); + }); + + const exportCiphers: any[] = []; + decCiphers.forEach((c) => { + // only export logins and secure notes + if (c.type !== CipherType.Login && c.type !== CipherType.SecureNote) { + return; + } + + const cipher: any = { + folder: c.folderId && foldersMap.has(c.folderId) ? foldersMap.get(c.folderId).name : null, + favorite: c.favorite ? 1 : null, + type: null, + name: c.name, + notes: c.notes, + fields: null, + // Login props + login_uri: null, + login_username: null, + login_password: null, + login_totp: null, + }; + + if (c.fields) { + c.fields.forEach((f: any) => { + if (!cipher.fields) { + cipher.fields = ''; + } else { + cipher.fields += '\n'; + } + + cipher.fields += ((f.name || '') + ': ' + f.value); + }); + } + + switch (c.type) { + case CipherType.Login: + cipher.type = 'login'; + cipher.login_username = c.login.username; + cipher.login_password = c.login.password; + cipher.login_totp = c.login.totp; + + if (c.login.uris) { + cipher.login_uri = []; + c.login.uris.forEach((u) => { + cipher.login_uri.push(u.uri); + }); + } + break; + case CipherType.SecureNote: + cipher.type = 'note'; + break; + default: + return; + } + + exportCiphers.push(cipher); + }); + + return papa.unparse(exportCiphers); + } + + getFileName(): string { + const now = new Date(); + const dateString = + now.getFullYear() + '' + this.padNumber(now.getMonth() + 1, 2) + '' + this.padNumber(now.getDate(), 2) + + this.padNumber(now.getHours(), 2) + '' + this.padNumber(now.getMinutes(), 2) + + this.padNumber(now.getSeconds(), 2); + + return 'bitwarden_export_' + dateString + '.csv'; + } + + private padNumber(num: number, width: number, padCharacter: string = '0'): string { + const numString = num.toString(); + return numString.length >= width ? numString : + new Array(width - numString.length + 1).join(padCharacter) + numString; + } +}