mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 21:33:27 +00:00
* Handle scenario where folders fail to decrypt * Add comment explaining the folderViews$ filter check
113 lines
4.6 KiB
TypeScript
113 lines
4.6 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 { combineLatest, filter, firstValueFrom, map, switchMap } from "rxjs";
|
|
|
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
|
// eslint-disable-next-line no-restricted-imports
|
|
import { CollectionService } from "@bitwarden/admin-console/common";
|
|
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 { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
|
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.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);
|
|
private accountService = inject(AccountService);
|
|
|
|
async buildConfig(
|
|
mode: CipherFormMode,
|
|
cipherId?: CipherId,
|
|
cipherType?: CipherType,
|
|
): Promise<CipherFormConfig> {
|
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
|
|
|
const [organizations, collections, organizationDataOwnershipDisabled, folders, cipher] =
|
|
await firstValueFrom(
|
|
combineLatest([
|
|
this.organizations$(activeUserId),
|
|
this.collectionService.encryptedCollections$(activeUserId).pipe(
|
|
map((collections) => collections ?? []),
|
|
switchMap((c) =>
|
|
this.collectionService.decryptedCollections$(activeUserId).pipe(
|
|
filter((d) => d.length === c.length), // Ensure all collections have been decrypted
|
|
),
|
|
),
|
|
),
|
|
this.organizationDataOwnershipDisabled$,
|
|
this.folderService.folders$(activeUserId).pipe(
|
|
switchMap((f) =>
|
|
this.folderService
|
|
.folderViews$(activeUserId)
|
|
// Ensure the folders have decrypted. f.length === 0 indicates we don't have any
|
|
// folders to wait for, and d.length > 0 indicates that `folderViews` has emitted the
|
|
// array, which includes the "No Folder" default folder.
|
|
.pipe(filter((d) => d.length > 0 || f.length === 0)),
|
|
),
|
|
),
|
|
this.getCipher(activeUserId, cipherId),
|
|
]),
|
|
);
|
|
|
|
return {
|
|
mode,
|
|
cipherType: cipher?.type ?? cipherType ?? CipherType.Login,
|
|
admin: false,
|
|
organizationDataOwnershipDisabled,
|
|
originalCipher: cipher,
|
|
collections,
|
|
organizations,
|
|
folders,
|
|
};
|
|
}
|
|
|
|
organizations$(userId: UserId) {
|
|
return this.organizationService
|
|
.organizations$(userId)
|
|
.pipe(
|
|
map((orgs) =>
|
|
orgs.filter(
|
|
(o) => o.isMember && o.enabled && o.status === OrganizationUserStatusType.Confirmed,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
private organizationDataOwnershipDisabled$ = this.accountService.activeAccount$.pipe(
|
|
getUserId,
|
|
switchMap((userId) =>
|
|
this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId),
|
|
),
|
|
map((p) => !p),
|
|
);
|
|
|
|
private getCipher(userId: UserId, id?: CipherId): Promise<Cipher | null> {
|
|
if (id == null) {
|
|
return Promise.resolve(null);
|
|
}
|
|
return this.cipherService.get(id, userId);
|
|
}
|
|
}
|