1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-19 09:43:23 +00:00

[PM-328] Move Send to Tools (#5104)

* Move send in libs/common

* Move send in libs/angular

* Move send in browser

* Move send in cli

* Move send in desktop

* Move send in web
This commit is contained in:
Daniel James Smith
2023-03-29 16:23:37 +02:00
committed by GitHub
parent e645688f8a
commit e238ea20a9
105 changed files with 328 additions and 321 deletions

View File

@@ -5,7 +5,7 @@ import { LogService } from "../abstractions/log.service";
import { SearchService as SearchServiceAbstraction } from "../abstractions/search.service";
import { FieldType } from "../enums/fieldType";
import { UriMatchType } from "../enums/uriMatchType";
import { SendView } from "../models/view/send.view";
import { SendView } from "../tools/send/models/view/send.view";
import { CipherService } from "../vault/abstractions/cipher.service";
import { CipherType } from "../vault/enums/cipher-type";
import { CipherView } from "../vault/models/view/cipher.view";

View File

@@ -1,251 +0,0 @@
import { SendType } from "../../../../common/src/enums/sendType";
import { Utils } from "../../../../common/src/misc/utils";
import { ErrorResponse } from "../../../../common/src/models/response/error.response";
import { ApiService } from "../../abstractions/api.service";
import {
FileUploadApiMethods,
FileUploadService,
} from "../../abstractions/file-upload/file-upload.service";
import { SendApiService as SendApiServiceAbstraction } from "../../abstractions/send/send-api.service.abstraction";
import { InternalSendService } from "../../abstractions/send/send.service.abstraction";
import { SendData } from "../../models/data/send.data";
import { EncArrayBuffer } from "../../models/domain/enc-array-buffer";
import { Send } from "../../models/domain/send";
import { SendAccessRequest } from "../../models/request/send-access.request";
import { SendRequest } from "../../models/request/send.request";
import { ListResponse } from "../../models/response/list.response";
import { SendAccessResponse } from "../../models/response/send-access.response";
import { SendFileDownloadDataResponse } from "../../models/response/send-file-download-data.response";
import { SendFileUploadDataResponse } from "../../models/response/send-file-upload-data.response";
import { SendResponse } from "../../models/response/send.response";
import { SendAccessView } from "../../models/view/send-access.view";
export class SendApiService implements SendApiServiceAbstraction {
constructor(
private apiService: ApiService,
private fileUploadService: FileUploadService,
private sendService: InternalSendService
) {}
async getSend(id: string): Promise<SendResponse> {
const r = await this.apiService.send("GET", "/sends/" + id, null, true, true);
return new SendResponse(r);
}
async postSendAccess(
id: string,
request: SendAccessRequest,
apiUrl?: string
): Promise<SendAccessResponse> {
const addSendIdHeader = (headers: Headers) => {
headers.set("Send-Id", id);
};
const r = await this.apiService.send(
"POST",
"/sends/access/" + id,
request,
false,
true,
apiUrl,
addSendIdHeader
);
return new SendAccessResponse(r);
}
async getSendFileDownloadData(
send: SendAccessView,
request: SendAccessRequest,
apiUrl?: string
): Promise<SendFileDownloadDataResponse> {
const addSendIdHeader = (headers: Headers) => {
headers.set("Send-Id", send.id);
};
const r = await this.apiService.send(
"POST",
"/sends/" + send.id + "/access/file/" + send.file.id,
request,
false,
true,
apiUrl,
addSendIdHeader
);
return new SendFileDownloadDataResponse(r);
}
async getSends(): Promise<ListResponse<SendResponse>> {
const r = await this.apiService.send("GET", "/sends", null, true, true);
return new ListResponse(r, SendResponse);
}
async postSend(request: SendRequest): Promise<SendResponse> {
const r = await this.apiService.send("POST", "/sends", request, true, true);
return new SendResponse(r);
}
async postFileTypeSend(request: SendRequest): Promise<SendFileUploadDataResponse> {
const r = await this.apiService.send("POST", "/sends/file/v2", request, true, true);
return new SendFileUploadDataResponse(r);
}
async renewSendFileUploadUrl(
sendId: string,
fileId: string
): Promise<SendFileUploadDataResponse> {
const r = await this.apiService.send(
"GET",
"/sends/" + sendId + "/file/" + fileId,
null,
true,
true
);
return new SendFileUploadDataResponse(r);
}
postSendFile(sendId: string, fileId: string, data: FormData): Promise<any> {
return this.apiService.send("POST", "/sends/" + sendId + "/file/" + fileId, data, true, false);
}
/**
* @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads.
* This method still exists for backward compatibility with old server versions.
*/
async postSendFileLegacy(data: FormData): Promise<SendResponse> {
const r = await this.apiService.send("POST", "/sends/file", data, true, true);
return new SendResponse(r);
}
async putSend(id: string, request: SendRequest): Promise<SendResponse> {
const r = await this.apiService.send("PUT", "/sends/" + id, request, true, true);
return new SendResponse(r);
}
async putSendRemovePassword(id: string): Promise<SendResponse> {
const r = await this.apiService.send(
"PUT",
"/sends/" + id + "/remove-password",
null,
true,
true
);
return new SendResponse(r);
}
deleteSend(id: string): Promise<any> {
return this.apiService.send("DELETE", "/sends/" + id, null, true, false);
}
async save(sendData: [Send, EncArrayBuffer]): Promise<any> {
const response = await this.upload(sendData);
const data = new SendData(response);
await this.sendService.upsert(data);
}
async delete(id: string): Promise<any> {
await this.deleteSend(id);
await this.sendService.delete(id);
}
async removePassword(id: string): Promise<any> {
const response = await this.putSendRemovePassword(id);
const data = new SendData(response);
await this.sendService.upsert(data);
}
// Send File Upload methoids
private async upload(sendData: [Send, EncArrayBuffer]): Promise<SendResponse> {
const request = new SendRequest(sendData[0], sendData[1]?.buffer.byteLength);
let response: SendResponse;
if (sendData[0].id == null) {
if (sendData[0].type === SendType.Text) {
response = await this.postSend(request);
} else {
try {
const uploadDataResponse = await this.postFileTypeSend(request);
response = uploadDataResponse.sendResponse;
await this.fileUploadService.upload(
uploadDataResponse,
sendData[0].file.fileName,
sendData[1],
this.generateMethods(uploadDataResponse, response)
);
} catch (e) {
if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) {
response = await this.legacyServerSendFileUpload(sendData, request);
} else if (e instanceof ErrorResponse) {
throw new Error((e as ErrorResponse).getSingleMessage());
} else {
throw e;
}
}
}
sendData[0].id = response.id;
sendData[0].accessId = response.accessId;
} else {
response = await this.putSend(sendData[0].id, request);
}
return response;
}
private generateMethods(
uploadData: SendFileUploadDataResponse,
response: SendResponse
): FileUploadApiMethods {
return {
postDirect: this.generatePostDirectCallback(response),
renewFileUploadUrl: this.generateRenewFileUploadUrlCallback(response.id, response.file.id),
rollback: this.generateRollbackCallback(response.id),
};
}
private generatePostDirectCallback(sendResponse: SendResponse) {
return (data: FormData) => {
return this.postSendFile(sendResponse.id, sendResponse.file.id, data);
};
}
private generateRenewFileUploadUrlCallback(sendId: string, fileId: string) {
return async () => {
const renewResponse = await this.renewSendFileUploadUrl(sendId, fileId);
return renewResponse?.url;
};
}
private generateRollbackCallback(sendId: string) {
return () => {
return this.deleteSend(sendId);
};
}
/**
* @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads.
* This method still exists for backward compatibility with old server versions.
*/
async legacyServerSendFileUpload(
sendData: [Send, EncArrayBuffer],
request: SendRequest
): Promise<SendResponse> {
const fd = new FormData();
try {
const blob = new Blob([sendData[1].buffer], { type: "application/octet-stream" });
fd.append("model", JSON.stringify(request));
fd.append("data", blob, sendData[0].file.fileName.encryptedString);
} catch (e) {
if (Utils.isNode && !Utils.isBrowser) {
fd.append("model", JSON.stringify(request));
fd.append(
"data",
Buffer.from(sendData[1].buffer) as any,
{
filepath: sendData[0].file.fileName.encryptedString,
contentType: "application/octet-stream",
} as any
);
} else {
throw e;
}
}
return await this.postSendFileLegacy(fd);
}
}

View File

@@ -1,263 +0,0 @@
import { BehaviorSubject, concatMap } from "rxjs";
import { CryptoService } from "../../abstractions/crypto.service";
import { CryptoFunctionService } from "../../abstractions/cryptoFunction.service";
import { I18nService } from "../../abstractions/i18n.service";
import { InternalSendService as InternalSendServiceAbstraction } from "../../abstractions/send/send.service.abstraction";
import { StateService } from "../../abstractions/state.service";
import { SEND_KDF_ITERATIONS } from "../../enums/kdfType";
import { SendType } from "../../enums/sendType";
import { Utils } from "../../misc/utils";
import { SendData } from "../../models/data/send.data";
import { EncArrayBuffer } from "../../models/domain/enc-array-buffer";
import { EncString } from "../../models/domain/enc-string";
import { Send } from "../../models/domain/send";
import { SendFile } from "../../models/domain/send-file";
import { SendText } from "../../models/domain/send-text";
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
import { SendView } from "../../models/view/send.view";
export class SendService implements InternalSendServiceAbstraction {
protected _sends: BehaviorSubject<Send[]> = new BehaviorSubject([]);
protected _sendViews: BehaviorSubject<SendView[]> = new BehaviorSubject([]);
sends$ = this._sends.asObservable();
sendViews$ = this._sendViews.asObservable();
constructor(
private cryptoService: CryptoService,
private i18nService: I18nService,
private cryptoFunctionService: CryptoFunctionService,
private stateService: StateService
) {
this.stateService.activeAccountUnlocked$
.pipe(
concatMap(async (unlocked) => {
if (Utils.global.bitwardenContainerService == null) {
return;
}
if (!unlocked) {
this._sends.next([]);
this._sendViews.next([]);
return;
}
const data = await this.stateService.getEncryptedSends();
await this.updateObservables(data);
})
)
.subscribe();
}
async clearCache(): Promise<void> {
await this._sendViews.next([]);
}
async encrypt(
model: SendView,
file: File | ArrayBuffer,
password: string,
key?: SymmetricCryptoKey
): Promise<[Send, EncArrayBuffer]> {
let fileData: EncArrayBuffer = null;
const send = new Send();
send.id = model.id;
send.type = model.type;
send.disabled = model.disabled;
send.hideEmail = model.hideEmail;
send.maxAccessCount = model.maxAccessCount;
if (model.key == null) {
model.key = await this.cryptoFunctionService.randomBytes(16);
model.cryptoKey = await this.cryptoService.makeSendKey(model.key);
}
if (password != null) {
const passwordHash = await this.cryptoFunctionService.pbkdf2(
password,
model.key,
"sha256",
SEND_KDF_ITERATIONS
);
send.password = Utils.fromBufferToB64(passwordHash);
}
send.key = await this.cryptoService.encrypt(model.key, key);
send.name = await this.cryptoService.encrypt(model.name, model.cryptoKey);
send.notes = await this.cryptoService.encrypt(model.notes, model.cryptoKey);
if (send.type === SendType.Text) {
send.text = new SendText();
send.text.text = await this.cryptoService.encrypt(model.text.text, model.cryptoKey);
send.text.hidden = model.text.hidden;
} else if (send.type === SendType.File) {
send.file = new SendFile();
if (file != null) {
if (file instanceof ArrayBuffer) {
const [name, data] = await this.encryptFileData(
model.file.fileName,
file,
model.cryptoKey
);
send.file.fileName = name;
fileData = data;
} else {
fileData = await this.parseFile(send, file, model.cryptoKey);
}
}
}
return [send, fileData];
}
get(id: string): Send {
const sends = this._sends.getValue();
return sends.find((send) => send.id === id);
}
async getFromState(id: string): Promise<Send> {
const sends = await this.stateService.getEncryptedSends();
// eslint-disable-next-line
if (sends == null || !sends.hasOwnProperty(id)) {
return null;
}
return new Send(sends[id]);
}
async getAll(): Promise<Send[]> {
const sends = await this.stateService.getEncryptedSends();
const response: Send[] = [];
for (const id in sends) {
// eslint-disable-next-line
if (sends.hasOwnProperty(id)) {
response.push(new Send(sends[id]));
}
}
return response;
}
async getAllDecryptedFromState(): Promise<SendView[]> {
let decSends = await this.stateService.getDecryptedSends();
if (decSends != null) {
return decSends;
}
decSends = [];
const hasKey = await this.cryptoService.hasKey();
if (!hasKey) {
throw new Error("No key.");
}
const promises: Promise<any>[] = [];
const sends = await this.getAll();
sends.forEach((send) => {
promises.push(send.decrypt().then((f) => decSends.push(f)));
});
await Promise.all(promises);
decSends.sort(Utils.getSortFunction(this.i18nService, "name"));
await this.stateService.setDecryptedSends(decSends);
return decSends;
}
async upsert(send: SendData | SendData[]): Promise<any> {
let sends = await this.stateService.getEncryptedSends();
if (sends == null) {
sends = {};
}
if (send instanceof SendData) {
const s = send as SendData;
sends[s.id] = s;
} else {
(send as SendData[]).forEach((s) => {
sends[s.id] = s;
});
}
await this.replace(sends);
}
async clear(userId?: string): Promise<any> {
if (userId == null || userId == (await this.stateService.getUserId())) {
this._sends.next([]);
this._sendViews.next([]);
}
await this.stateService.setDecryptedSends(null, { userId: userId });
await this.stateService.setEncryptedSends(null, { userId: userId });
}
async delete(id: string | string[]): Promise<any> {
const sends = await this.stateService.getEncryptedSends();
if (sends == null) {
return;
}
if (typeof id === "string") {
if (sends[id] == null) {
return;
}
delete sends[id];
} else {
(id as string[]).forEach((i) => {
delete sends[i];
});
}
await this.replace(sends);
}
async replace(sends: { [id: string]: SendData }): Promise<any> {
await this.updateObservables(sends);
await this.stateService.setEncryptedSends(sends);
}
private parseFile(send: Send, file: File, key: SymmetricCryptoKey): Promise<EncArrayBuffer> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = async (evt) => {
try {
const [name, data] = await this.encryptFileData(
file.name,
evt.target.result as ArrayBuffer,
key
);
send.file.fileName = name;
resolve(data);
} catch (e) {
reject(e);
}
};
reader.onerror = () => {
reject("Error reading file.");
};
});
}
private async encryptFileData(
fileName: string,
data: ArrayBuffer,
key: SymmetricCryptoKey
): Promise<[EncString, EncArrayBuffer]> {
const encFileName = await this.cryptoService.encrypt(fileName, key);
const encFileData = await this.cryptoService.encryptToBytes(data, key);
return [encFileName, encFileData];
}
private async updateObservables(sendsMap: { [id: string]: SendData }) {
const sends = Object.values(sendsMap || {}).map((f) => new Send(f));
this._sends.next(sends);
if (await this.cryptoService.hasKey()) {
this._sendViews.next(await this.decryptSends(sends));
}
}
private async decryptSends(sends: Send[]) {
const decryptSendPromises = sends.map((s) => s.decrypt());
const decryptedSends = await Promise.all(decryptSendPromises);
decryptedSends.sort(Utils.getSortFunction(this.i18nService, "name"));
return decryptedSends;
}
}

View File

@@ -25,7 +25,6 @@ import { UriMatchType } from "../enums/uriMatchType";
import { StateFactory } from "../factories/stateFactory";
import { Utils } from "../misc/utils";
import { EventData } from "../models/data/event.data";
import { SendData } from "../models/data/send.data";
import { ServerConfigData } from "../models/data/server-config.data";
import {
Account,
@@ -39,8 +38,9 @@ import { State } from "../models/domain/state";
import { StorageOptions } from "../models/domain/storage-options";
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
import { WindowState } from "../models/domain/window-state";
import { SendView } from "../models/view/send.view";
import { GeneratedPasswordHistory } from "../tools/generator/password";
import { SendData } from "../tools/send/models/data/send.data";
import { SendView } from "../tools/send/models/view/send.view";
import { CipherData } from "../vault/models/data/cipher.data";
import { FolderData } from "../vault/models/data/folder.data";
import { LocalData } from "../vault/models/data/local.data";

View File

@@ -11,7 +11,6 @@ import { StateVersion } from "../enums/stateVersion";
import { ThemeType } from "../enums/themeType";
import { StateFactory } from "../factories/stateFactory";
import { EventData } from "../models/data/event.data";
import { SendData } from "../models/data/send.data";
import {
Account,
AccountSettings,
@@ -22,6 +21,7 @@ import { EncString } from "../models/domain/enc-string";
import { GlobalState } from "../models/domain/global-state";
import { StorageOptions } from "../models/domain/storage-options";
import { GeneratedPasswordHistory } from "../tools/generator/password";
import { SendData } from "../tools/send/models/data/send.data";
import { CipherData } from "../vault/models/data/cipher.data";
import { FolderData } from "../vault/models/data/folder.data";