1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-14 23:45:37 +00:00

Merge branch 'main' into ps/extension-refresh

This commit is contained in:
Victoria League
2024-08-23 11:09:15 -04:00
committed by GitHub
356 changed files with 4204 additions and 1530 deletions

View File

@@ -1,7 +1,9 @@
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { firstValueFrom, map } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -36,6 +38,7 @@ export class CollectionsComponent implements OnInit {
protected organizationService: OrganizationService,
private logService: LogService,
private configService: ConfigService,
private accountService: AccountService,
) {}
async ngOnInit() {
@@ -48,8 +51,11 @@ export class CollectionsComponent implements OnInit {
async load() {
this.cipherDomain = await this.loadCipher();
this.collectionIds = this.loadCipherCollections();
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.cipher = await this.cipherDomain.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain),
await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId),
);
this.collections = await this.loadCollections();

View File

@@ -21,6 +21,7 @@ import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/auth/models/domain/kdf-con
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -72,6 +73,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
private ssoLoginService: SsoLoginServiceAbstraction,
dialogService: DialogService,
kdfConfigService: KdfConfigService,
private encryptService: EncryptService,
) {
super(
i18nService,
@@ -160,7 +162,23 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
// Existing JIT provisioned user in a MP encryption org setting first password
// Users in this state will not already have a user asymmetric key pair so must create it for them
// We don't want to re-create the user key pair if the user already has one (TDE user case)
newKeyPair = await this.cryptoService.makeKeyPair(userKey[0]);
// in case we have a local private key, and are not sure whether it has been posted to the server, we post the local private key instead of generating a new one
const existingUserPrivateKey = (await firstValueFrom(
this.cryptoService.userPrivateKey$(this.userId),
)) as Uint8Array;
const existingUserPublicKey = await firstValueFrom(
this.cryptoService.userPublicKey$(this.userId),
);
if (existingUserPrivateKey != null && existingUserPublicKey != null) {
const existingUserPublicKeyB64 = Utils.fromBufferToB64(existingUserPublicKey);
newKeyPair = [
existingUserPublicKeyB64,
await this.encryptService.encrypt(existingUserPrivateKey, userKey[0]),
];
} else {
newKeyPair = await this.cryptoService.makeKeyPair(userKey[0]);
}
keysRequest = new KeysRequest(newKeyPair[0], newKeyPair[1].encryptedString);
}

View File

@@ -4,6 +4,7 @@ import { firstValueFrom, map, Observable, Subject, takeUntil } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -36,6 +37,7 @@ export class ShareComponent implements OnInit, OnDestroy {
protected cipherService: CipherService,
private logService: LogService,
protected organizationService: OrganizationService,
protected accountService: AccountService,
) {}
async ngOnInit() {
@@ -67,8 +69,11 @@ export class ShareComponent implements OnInit, OnDestroy {
});
const cipherDomain = await this.cipherService.get(this.cipherId);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.cipher = await cipherDomain.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain),
await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId),
);
}
@@ -95,8 +100,11 @@ export class ShareComponent implements OnInit, OnDestroy {
}
const cipherDomain = await this.cipherService.get(this.cipherId);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const cipherView = await cipherDomain.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain),
await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId),
);
const orgs = await firstValueFrom(this.organizations$);
const orgName =
@@ -104,7 +112,7 @@ export class ShareComponent implements OnInit, OnDestroy {
try {
this.formPromise = this.cipherService
.shareWithServer(cipherView, this.organizationId, selectedCollectionIds)
.shareWithServer(cipherView, this.organizationId, selectedCollectionIds, activeUserId)
.then(async () => {
this.onSharedCipher.emit();
this.platformUtilsService.showToast(

View File

@@ -8,8 +8,7 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { I18nMockService } from "@bitwarden/components/src";
import { I18nMockService, ToastService } from "@bitwarden/components/src";
import { canAccessFeature } from "./feature-flag.guard";
@@ -22,11 +21,11 @@ describe("canAccessFeature", () => {
const redirectRoute = "redirect";
let mockConfigService: MockProxy<ConfigService>;
let mockPlatformUtilsService: MockProxy<PlatformUtilsService>;
let mockToastService: MockProxy<ToastService>;
const setup = (featureGuard: CanActivateFn, flagValue: any) => {
mockConfigService = mock<ConfigService>();
mockPlatformUtilsService = mock<PlatformUtilsService>();
mockToastService = mock<ToastService>();
// Mock the correct getter based on the type of flagValue; also mock default values if one is not provided
if (typeof flagValue === "boolean") {
@@ -57,7 +56,7 @@ describe("canAccessFeature", () => {
],
providers: [
{ provide: ConfigService, useValue: mockConfigService },
{ provide: PlatformUtilsService, useValue: mockPlatformUtilsService },
{ provide: ToastService, useValue: mockToastService },
{ provide: LogService, useValue: mock<LogService>() },
{
provide: I18nService,
@@ -117,11 +116,11 @@ describe("canAccessFeature", () => {
await router.navigate([featureRoute]);
expect(mockPlatformUtilsService.showToast).toHaveBeenCalledWith(
"error",
null,
"Access Denied!",
);
expect(mockToastService.showToast).toHaveBeenCalledWith({
variant: "error",
title: null,
message: "Access Denied!",
});
});
it("does not show an error toast when the feature flag is enabled", async () => {
@@ -129,7 +128,7 @@ describe("canAccessFeature", () => {
await router.navigate([featureRoute]);
expect(mockPlatformUtilsService.showToast).not.toHaveBeenCalled();
expect(mockToastService.showToast).not.toHaveBeenCalled();
});
it("redirects to the specified redirect url when the feature flag is disabled", async () => {

View File

@@ -5,7 +5,7 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ToastService } from "@bitwarden/components";
// Replace this with a type safe lookup of the feature flag values in PM-2282
type FlagValue = boolean | number | string;
@@ -24,7 +24,7 @@ export const canAccessFeature = (
): CanActivateFn => {
return async () => {
const configService = inject(ConfigService);
const platformUtilsService = inject(PlatformUtilsService);
const toastService = inject(ToastService);
const router = inject(Router);
const i18nService = inject(I18nService);
const logService = inject(LogService);
@@ -36,7 +36,11 @@ export const canAccessFeature = (
return true;
}
platformUtilsService.showToast("error", null, i18nService.t("accessDenied"));
toastService.showToast({
variant: "error",
title: null,
message: i18nService.t("accessDenied"),
});
if (redirectUrlOnDisabled != null) {
return router.createUrlTree([redirectUrlOnDisabled]);

View File

@@ -123,14 +123,12 @@ import {
BillingApiServiceAbstraction,
BraintreeServiceAbstraction,
OrganizationBillingServiceAbstraction,
PaymentMethodWarningsServiceAbstraction,
StripeServiceAbstraction,
} from "@bitwarden/common/billing/abstractions";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
import { BillingApiService } from "@bitwarden/common/billing/services/billing-api.service";
import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service";
import { PaymentMethodWarningsService } from "@bitwarden/common/billing/services/payment-method-warnings.service";
import { BraintreeService } from "@bitwarden/common/billing/services/payment-processors/braintree.service";
import { StripeService } from "@bitwarden/common/billing/services/payment-processors/stripe.service";
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service";
@@ -775,6 +773,7 @@ const safeProviders: SafeProvider[] = [
CollectionServiceAbstraction,
CryptoServiceAbstraction,
PinServiceAbstraction,
AccountServiceAbstraction,
],
}),
safeProvider({
@@ -800,6 +799,7 @@ const safeProviders: SafeProvider[] = [
CryptoFunctionServiceAbstraction,
CollectionServiceAbstraction,
KdfConfigServiceAbstraction,
AccountServiceAbstraction,
],
}),
safeProvider({
@@ -1199,11 +1199,6 @@ const safeProviders: SafeProvider[] = [
useClass: BillingApiService,
deps: [ApiServiceAbstraction, LogService, ToastService],
}),
safeProvider({
provide: PaymentMethodWarningsServiceAbstraction,
useClass: PaymentMethodWarningsService,
deps: [BillingApiServiceAbstraction, StateProvider],
}),
safeProvider({
provide: BillingAccountProfileStateService,
useClass: DefaultBillingAccountProfileStateService,

View File

@@ -0,0 +1,32 @@
import { Type, inject } from "@angular/core";
import { Route, Routes } from "@angular/router";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { componentRouteSwap } from "./component-route-swap";
/**
* Helper function to swap between two components based on the ExtensionRefresh feature flag.
* @param defaultComponent - The current non-refreshed component to render.
* @param refreshedComponent - The new refreshed component to render.
* @param options - The shared route options to apply to the default component, and to the alt component if altOptions is not provided.
* @param altOptions - The alt route options to apply to the alt component.
*/
export function extensionRefreshSwap(
defaultComponent: Type<any>,
refreshedComponent: Type<any>,
options: Route,
altOptions?: Route,
): Routes {
return componentRouteSwap(
defaultComponent,
refreshedComponent,
async () => {
const configService = inject(ConfigService);
return configService.getFeatureFlag(FeatureFlag.ExtensionRefresh);
},
options,
altOptions,
);
}

View File

@@ -22,6 +22,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { UserId } 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";
@@ -250,8 +251,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
if (this.cipher == null) {
if (this.editMode) {
const cipher = await this.loadCipher();
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.cipher = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher),
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
// Adjust Cipher Name if Cloning
@@ -371,7 +375,10 @@ export class AddEditComponent implements OnInit, OnDestroy {
this.cipher.id = null;
}
const cipher = await this.encryptCipher();
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const cipher = await this.encryptCipher(activeUserId);
try {
this.formPromise = this.saveCipher(cipher);
await this.formPromise;
@@ -664,8 +671,8 @@ export class AddEditComponent implements OnInit, OnDestroy {
return this.cipherService.get(this.cipherId);
}
protected encryptCipher() {
return this.cipherService.encrypt(this.cipher);
protected encryptCipher(userId: UserId) {
return this.cipherService.encrypt(this.cipher, userId);
}
protected saveCipher(cipher: Cipher) {

View File

@@ -1,7 +1,8 @@
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { firstValueFrom, map } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@@ -11,6 +12,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
@@ -46,6 +48,7 @@ export class AttachmentsComponent implements OnInit {
protected fileDownloadService: FileDownloadService,
protected dialogService: DialogService,
protected billingAccountProfileStateService: BillingAccountProfileStateService,
protected accountService: AccountService,
) {}
async ngOnInit() {
@@ -75,10 +78,13 @@ export class AttachmentsComponent implements OnInit {
}
try {
this.formPromise = this.saveCipherAttachment(files[0]);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.formPromise = this.saveCipherAttachment(files[0], activeUserId);
this.cipherDomain = await this.formPromise;
this.cipher = await this.cipherDomain.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain),
await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId),
);
this.platformUtilsService.showToast("success", null, this.i18nService.t("attachmentSaved"));
this.onUploadedAttachment.emit();
@@ -185,8 +191,11 @@ export class AttachmentsComponent implements OnInit {
protected async init() {
this.cipherDomain = await this.loadCipher();
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.cipher = await this.cipherDomain.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain),
await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId),
);
const canAccessPremium = await firstValueFrom(
@@ -235,14 +244,18 @@ export class AttachmentsComponent implements OnInit {
? attachment.key
: await this.cryptoService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(encBuf, key);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer(
this.cipherDomain,
attachment.fileName,
decBuf,
activeUserId,
admin,
);
this.cipher = await this.cipherDomain.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain),
await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId),
);
// 3. Delete old
@@ -278,8 +291,8 @@ export class AttachmentsComponent implements OnInit {
return this.cipherService.get(this.cipherId);
}
protected saveCipherAttachment(file: File) {
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file);
protected saveCipherAttachment(file: File, userId: UserId) {
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, userId);
}
protected deleteCipherAttachment(attachmentId: string) {

View File

@@ -1,5 +1,7 @@
import { Directive, OnInit } from "@angular/core";
import { firstValueFrom, map } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@@ -14,6 +16,7 @@ export class PasswordHistoryComponent implements OnInit {
protected cipherService: CipherService,
protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
protected accountService: AccountService,
private win: Window,
) {}
@@ -33,8 +36,11 @@ export class PasswordHistoryComponent implements OnInit {
protected async init() {
const cipher = await this.cipherService.get(this.cipherId);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const decCipher = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher),
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory;
}

View File

@@ -9,11 +9,12 @@ import {
OnInit,
Output,
} from "@angular/core";
import { firstValueFrom } from "rxjs";
import { firstValueFrom, map } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EventType } from "@bitwarden/common/enums";
@@ -100,6 +101,7 @@ export class ViewComponent implements OnDestroy, OnInit {
protected fileDownloadService: FileDownloadService,
protected dialogService: DialogService,
protected datePipe: DatePipe,
protected accountService: AccountService,
private billingAccountProfileStateService: BillingAccountProfileStateService,
) {}
@@ -129,8 +131,11 @@ export class ViewComponent implements OnDestroy, OnInit {
this.cleanUp();
const cipher = await this.cipherService.get(this.cipherId);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.cipher = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher),
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
this.canAccessPremium = await firstValueFrom(
this.billingAccountProfileStateService.hasPremiumFromAnySource$,