1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-20 10:13:31 +00:00

[PM-7162] Cipher Form - Item Details (#9758)

* [PM-7162] Fix weird angular error regarding disabled component bit-select

* [PM-7162] Introduce CipherFormConfigService and related types

* [PM-7162] Introduce CipherFormService

* [PM-7162] Introduce the Item Details section component and the CipherFormContainer interface

* [PM-7162] Introduce the CipherForm component

* [PM-7162] Add strongly typed QueryParams to the add-edit-v2.component

* [PM-7162] Export CipherForm from Vault Lib

* [PM-7162] Use the CipherForm in Browser AddEditV2

* [PM-7162] Introduce CipherForm storybook

* [PM-7162] Remove VaultPopupListFilterService dependency from NewItemDropDownV2 component

* [PM-7162] Add support for content projection of attachment button

* [PM-7162] Fix typo

* [PM-7162] Cipher form service cleanup

* [PM-7162] Move readonly collection notice to bit-hint

* [PM-7162] Refactor CipherFormConfig type to enforce required properties with Typescript

* [PM-7162] Fix storybook after config changes

* [PM-7162] Use new add-edit component for clone route
This commit is contained in:
Shane Melton
2024-07-02 13:22:51 -07:00
committed by GitHub
parent 9294a4c47e
commit 17d37ecaeb
26 changed files with 1737 additions and 40 deletions

View File

@@ -0,0 +1,79 @@
import { inject, Injectable } from "@angular/core";
import { combineLatest, firstValueFrom, map } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums";
import { CipherId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType } from "@bitwarden/common/vault/enums";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import {
CipherFormConfig,
CipherFormConfigService,
CipherFormMode,
} from "../abstractions/cipher-form-config.service";
/**
* Default implementation of the `CipherFormConfigService`. This service should suffice for most use cases, however
* the admin console may need to provide a custom implementation to support admin/custom users who have access to
* collections that are not part of their normal sync data.
*/
@Injectable()
export class DefaultCipherFormConfigService implements CipherFormConfigService {
private policyService: PolicyService = inject(PolicyService);
private organizationService: OrganizationService = inject(OrganizationService);
private cipherService: CipherService = inject(CipherService);
private folderService: FolderService = inject(FolderService);
private collectionService: CollectionService = inject(CollectionService);
async buildConfig(
mode: CipherFormMode,
cipherId?: CipherId,
cipherType?: CipherType,
): Promise<CipherFormConfig> {
const [organizations, collections, allowPersonalOwnership, folders, cipher] =
await firstValueFrom(
combineLatest([
this.organizations$,
this.collectionService.decryptedCollections$,
this.allowPersonalOwnership$,
this.folderService.folderViews$,
this.getCipher(cipherId),
]),
);
return {
mode,
cipherType,
admin: false,
allowPersonalOwnership,
originalCipher: cipher,
collections,
organizations,
folders,
};
}
private organizations$ = this.organizationService.organizations$.pipe(
map((orgs) =>
orgs.filter(
(o) => o.isMember && o.enabled && o.status === OrganizationUserStatusType.Confirmed,
),
),
);
private allowPersonalOwnership$ = this.policyService
.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
.pipe(map((p) => !p));
private getCipher(id?: CipherId): Promise<Cipher | null> {
if (id == null) {
return Promise.resolve(null);
}
return this.cipherService.get(id);
}
}

View File

@@ -0,0 +1,74 @@
import { inject, Injectable } from "@angular/core";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
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);
async decryptCipher(cipher: Cipher): Promise<CipherView> {
return await cipher.decrypt(await this.cipherService.getKeyForCipherKeyDecryption(cipher));
}
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 encryptedCipher = await this.cipherService.encrypt(
cipher,
null,
null,
config.originalCipher ?? null,
);
let savedCipher: Cipher;
// Creating a new cipher
if (cipher.id == null) {
savedCipher = await this.cipherService.createWithServer(encryptedCipher, config.admin);
return await savedCipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(savedCipher),
);
}
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 ?? []);
// If the collectionIds are the same, update the cipher normally
if (isSetEqual(originalCollectionIds, newCollectionIds)) {
savedCipher = await this.cipherService.updateWithServer(encryptedCipher, config.admin);
} else {
// 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);
// Then save the new collection changes separately
encryptedCipher.collectionIds = cipher.collectionIds;
savedCipher = await this.cipherService.saveCollectionsWithServer(encryptedCipher);
}
// 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;
}
return await savedCipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(savedCipher),
);
}
}