1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-18 18:33:50 +00:00
Files
browser/libs/vault/src/cipher-form/services/default-cipher-form.service.ts
2025-03-20 10:51:35 -04:00

131 lines
5.4 KiB
TypeScript

// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { inject, Injectable } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { UserEventLogProvider } from "@bitwarden/common/tools/log/logger";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CipherFormConfig } from "../abstractions/cipher-form-config.service";
import { CipherFormService } from "../abstractions/cipher-form.service";
function isSetEqual(a: Set<string>, b: Set<string>) {
return a.size === b.size && [...a].every((value) => b.has(value));
}
@Injectable()
export class DefaultCipherFormService implements CipherFormService {
private cipherService: CipherService = inject(CipherService);
private accountService: AccountService = inject(AccountService);
private apiService: ApiService = inject(ApiService);
private system = inject(UserEventLogProvider);
async decryptCipher(cipher: Cipher): Promise<CipherView> {
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
return await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
}
async saveCipher(cipher: CipherView, config: CipherFormConfig): Promise<CipherView> {
// Passing the original cipher is important here as it is responsible for appending to password history
const activeUser = await firstValueFrom(this.accountService.activeAccount$);
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const encryptedCipher = await this.cipherService.encrypt(
cipher,
activeUserId,
null,
null,
config.originalCipher ?? null,
);
const event = this.system.create(activeUser);
const labels = {
"vault-item-type": CipherType[cipher.type],
"vault-item-uri-quantity": cipher.type === CipherType.Login ? cipher.login.uris.length : null,
};
const tags = [
cipher.attachments.length > 0 ? "with-attachment" : null,
cipher.folderId ? "with-folder" : null,
];
let savedCipher: Cipher;
// Creating a new cipher
if (cipher.id == null) {
savedCipher = await this.cipherService.createWithServer(encryptedCipher, config.admin);
event.creation({ action: "vault-item-added", labels, tags });
return await savedCipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(savedCipher, activeUserId),
);
}
if (config.originalCipher == null) {
throw new Error("Original cipher is required for updating an existing cipher");
}
// Updating an existing cipher
const originalCollectionIds = new Set(config.originalCipher.collectionIds ?? []);
const newCollectionIds = new Set(cipher.collectionIds ?? []);
// Call shareWithServer if the owner is changing from a user to an organization
if (config.originalCipher.organizationId === null && cipher.organizationId != null) {
savedCipher = await this.cipherService.shareWithServer(
cipher,
cipher.organizationId,
cipher.collectionIds,
activeUserId,
);
event.info({ action: "vault-item-shared-with-organization", labels, tags });
// If the collectionIds are the same, update the cipher normally
} else if (isSetEqual(originalCollectionIds, newCollectionIds)) {
savedCipher = await this.cipherService.updateWithServer(encryptedCipher, config.admin);
event.info({ action: "vault-item-updated", labels, tags });
} else {
tags.push("collection");
// Updating a cipher with collection changes is not supported with a single request currently
// First update the cipher with the original collectionIds
encryptedCipher.collectionIds = config.originalCipher.collectionIds;
await this.cipherService.updateWithServer(
encryptedCipher,
config.admin || originalCollectionIds.size === 0,
);
// Then save the new collection changes separately
encryptedCipher.collectionIds = cipher.collectionIds;
if (config.admin || originalCollectionIds.size === 0) {
// When using an admin config or the cipher was unassigned, update collections as an admin
savedCipher = await this.cipherService.saveCollectionsWithServerAdmin(encryptedCipher);
} else {
savedCipher = await this.cipherService.saveCollectionsWithServer(
encryptedCipher,
activeUserId,
);
}
event.deletion({ action: "vault-item-moved", labels, tags });
}
// Its possible the cipher was made no longer available due to collection assignment changes
// e.g. The cipher was moved to a collection that the user no longer has access to
if (savedCipher == null) {
return null;
} else {
event.creation({ action: "vault-item-moved", labels, tags });
}
return await savedCipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(savedCipher, activeUserId),
);
}
}