[PM-10607] Require userId for getKeyForCipherKeyDecryption (#10509)

* updated cipher service to stop using the deprecated getUserKeyWithLegacySupport and use the version that requires a user id

* Added account service mock

* fixed cipher test

* Fixed test

* removed async from encryptCipher

* updated encryptSharedCipher to pass userId to the encrypt function

* Pass userId to getUserKeyWithLegacySupport on encryptSharedCipher

* pass in userid when setting masterKeyEncryptedUserKey

* Added activer usedId to new web refresh function
This commit is contained in:
SmithThe4th 2024-08-20 12:00:48 -04:00 committed by GitHub
parent ed719f835a
commit dedd7f1b5c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
67 changed files with 534 additions and 118 deletions

View File

@ -2,6 +2,7 @@ import { mock } from "jest-mock-extended";
import { BehaviorSubject, firstValueFrom } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { ExtensionCommand } from "@bitwarden/common/autofill/constants";
@ -12,6 +13,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
@ -55,6 +57,7 @@ describe("NotificationBackground", () => {
const logService = mock<LogService>();
const themeStateService = mock<ThemeStateService>();
const configService = mock<ConfigService>();
const accountService = mock<AccountService>();
beforeEach(() => {
notificationBackground = new NotificationBackground(
@ -69,6 +72,7 @@ describe("NotificationBackground", () => {
logService,
themeStateService,
configService,
accountService,
);
});
@ -691,6 +695,13 @@ describe("NotificationBackground", () => {
});
describe("saveOrUpdateCredentials", () => {
const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({
id: "testId" as UserId,
email: "test@example.com",
emailVerified: true,
name: "Test User",
});
let getDecryptedCipherByIdSpy: jest.SpyInstance;
let getAllDecryptedForUrlSpy: jest.SpyInstance;
let updatePasswordSpy: jest.SpyInstance;
@ -727,6 +738,8 @@ describe("NotificationBackground", () => {
updateWithServerSpy = jest.spyOn(cipherService, "updateWithServer");
folderExistsSpy = jest.spyOn(notificationBackground as any, "folderExists");
cipherEncryptSpy = jest.spyOn(cipherService, "encrypt");
accountService.activeAccount$ = activeAccountSubject;
});
it("skips saving the cipher if the notification queue does not have a tab that is related to the sender", async () => {
@ -981,7 +994,7 @@ describe("NotificationBackground", () => {
queueMessage,
null,
);
expect(cipherEncryptSpy).toHaveBeenCalledWith(cipherView);
expect(cipherEncryptSpy).toHaveBeenCalledWith(cipherView, "testId");
expect(createWithServerSpy).toHaveBeenCalled();
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
command: "saveCipherAttemptCompleted",
@ -1020,7 +1033,7 @@ describe("NotificationBackground", () => {
sendMockExtensionMessage(message, sender);
await flushPromises();
expect(cipherEncryptSpy).toHaveBeenCalledWith(cipherView);
expect(cipherEncryptSpy).toHaveBeenCalledWith(cipherView, "testId");
expect(createWithServerSpy).toThrow(errorMessage);
expect(tabSendMessageSpy).not.toHaveBeenCalledWith(sender.tab, {
command: "addedCipher",

View File

@ -1,7 +1,8 @@
import { firstValueFrom } from "rxjs";
import { firstValueFrom, map } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import {
@ -90,6 +91,7 @@ export default class NotificationBackground {
private logService: LogService,
private themeStateService: ThemeStateService,
private configService: ConfigService,
private accountService: AccountService,
) {}
async init() {
@ -556,7 +558,11 @@ export default class NotificationBackground {
return;
}
const cipher = await this.cipherService.encrypt(newCipher);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const cipher = await this.cipherService.encrypt(newCipher, activeUserId);
try {
await this.cipherService.createWithServer(cipher);
await BrowserApi.tabSendMessage(tab, { command: "saveCipherAttemptCompleted" });
@ -594,7 +600,11 @@ export default class NotificationBackground {
return;
}
const cipher = await this.cipherService.encrypt(cipherView);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const cipher = await this.cipherService.encrypt(cipherView, activeUserId);
try {
// We've only updated the password, no need to broadcast editedCipher message
await this.cipherService.updateWithServer(cipher);
@ -634,7 +644,13 @@ export default class NotificationBackground {
private async getDecryptedCipherById(cipherId: string) {
const cipher = await this.cipherService.get(cipherId);
if (cipher != null && cipher.type === CipherType.Login) {
return await cipher.decrypt(await this.cipherService.getKeyForCipherKeyDecryption(cipher));
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
return await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
}
return null;
}

View File

@ -14,6 +14,7 @@ import {
} from "rxjs";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -80,6 +81,7 @@ export class Fido2Component implements OnInit, OnDestroy {
private browserMessagingApi: ZonedMessageListenerService,
private passwordRepromptService: PasswordRepromptService,
private fido2UserVerificationService: Fido2UserVerificationService,
private accountService: AccountService,
) {}
ngOnInit() {
@ -156,11 +158,15 @@ export class Fido2Component implements OnInit, OnDestroy {
}
case "PickCredentialRequest": {
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.ciphers = await Promise.all(
message.cipherIds.map(async (cipherId) => {
const cipher = await this.cipherService.get(cipherId);
return cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher),
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
}),
);
@ -172,11 +178,15 @@ export class Fido2Component implements OnInit, OnDestroy {
}
case "InformExcludedCredentialRequest": {
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.ciphers = await Promise.all(
message.existingCipherIds.map(async (cipherId) => {
const cipher = await this.cipherService.get(cipherId);
return cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher),
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
}),
);
@ -378,8 +388,12 @@ export class Fido2Component implements OnInit, OnDestroy {
}
private async createNewCipher(name: string, username: string) {
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.buildCipher(name, username);
const cipher = await this.cipherService.encrypt(this.cipher);
const cipher = await this.cipherService.encrypt(this.cipher, activeUserId);
try {
await this.cipherService.createWithServer(cipher);
this.cipher.id = cipher.id;

View File

@ -956,6 +956,7 @@ export default class MainBackground {
this.collectionService,
this.cryptoService,
this.pinService,
this.accountService,
);
this.individualVaultExportService = new IndividualVaultExportService(
@ -975,6 +976,7 @@ export default class MainBackground {
this.cryptoFunctionService,
this.collectionService,
this.kdfConfigService,
this.accountService,
);
this.exportService = new VaultExportService(
@ -1000,6 +1002,7 @@ export default class MainBackground {
this.cipherService,
this.fido2UserInterfaceService,
this.syncService,
this.accountService,
this.logService,
);
const fido2ActiveRequestManager = new Fido2ActiveRequestManager();
@ -1093,6 +1096,7 @@ export default class MainBackground {
this.logService,
this.themeStateService,
this.configService,
this.accountService,
);
this.filelessImporterBackground = new FilelessImporterBackground(

View File

@ -3,9 +3,10 @@ import { Component } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ReactiveFormsModule } from "@angular/forms";
import { ActivatedRoute } from "@angular/router";
import { Observable, combineLatest, first, switchMap } from "rxjs";
import { Observable, combineLatest, first, map, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
@ -52,18 +53,24 @@ export class AssignCollections {
private location: Location,
private collectionService: CollectionService,
private cipherService: CipherService,
private accountService: AccountService,
route: ActivatedRoute,
) {
const $cipher: Observable<CipherView> = route.queryParams.pipe(
const cipher$: Observable<CipherView> = route.queryParams.pipe(
switchMap(({ cipherId }) => this.cipherService.get(cipherId)),
switchMap((cipherDomain) =>
this.cipherService
.getKeyForCipherKeyDecryption(cipherDomain)
.then(cipherDomain.decrypt.bind(cipherDomain)),
this.accountService.activeAccount$.pipe(
map((account) => account?.id),
switchMap((userId) =>
this.cipherService
.getKeyForCipherKeyDecryption(cipherDomain, userId)
.then(cipherDomain.decrypt.bind(cipherDomain)),
),
),
),
);
combineLatest([$cipher, this.collectionService.decryptedCollections$])
combineLatest([cipher$, this.collectionService.decryptedCollections$])
.pipe(takeUntilDestroyed(), first())
.subscribe(([cipherView, collections]) => {
let availableCollections = collections.filter((c) => !c.readOnly);

View File

@ -5,10 +5,14 @@ import { ActivatedRoute, Router } from "@angular/router";
import { mock } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
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 { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { ButtonComponent } from "@bitwarden/components";
import { CipherAttachmentsComponent } from "@bitwarden/vault";
@ -46,6 +50,9 @@ describe("AttachmentsV2Component", () => {
const navigate = jest.fn();
const back = jest.fn().mockResolvedValue(undefined);
const mockUserId = Utils.newGuid() as UserId;
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
beforeEach(async () => {
back.mockClear();
navigate.mockClear();
@ -66,6 +73,10 @@ describe("AttachmentsV2Component", () => {
queryParams,
},
},
{
provide: AccountService,
useValue: accountService,
},
],
})
.overrideComponent(AttachmentsV2Component, {

View File

@ -5,10 +5,13 @@ import { BehaviorSubject } 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 { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherId } from "@bitwarden/common/types/guid";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { CipherId, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@ -51,6 +54,9 @@ describe("OpenAttachmentsComponent", () => {
const getOrganization = jest.fn().mockResolvedValue(org);
const showFilePopoutMessage = jest.fn().mockReturnValue(false);
const mockUserId = Utils.newGuid() as UserId;
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
beforeEach(async () => {
openCurrentPagePopout.mockClear();
getCipher.mockClear();
@ -82,6 +88,10 @@ describe("OpenAttachmentsComponent", () => {
provide: FilePopoutUtilsService,
useValue: { showFilePopoutMessage },
},
{
provide: AccountService,
useValue: accountService,
},
],
}).compileComponents();
});

View File

@ -2,9 +2,11 @@ import { CommonModule } from "@angular/common";
import { Component, Input, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { Router } from "@angular/router";
import { firstValueFrom, map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -48,6 +50,7 @@ export class OpenAttachmentsComponent implements OnInit {
private toastService: ToastService,
private i18nService: I18nService,
private filePopoutUtilsService: FilePopoutUtilsService,
private accountService: AccountService,
) {
this.billingAccountProfileStateService.hasPremiumFromAnySource$
.pipe(takeUntilDestroyed())
@ -64,8 +67,11 @@ export class OpenAttachmentsComponent implements OnInit {
}
const cipherDomain = await this.cipherService.get(this.cipherId);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const cipher = await cipherDomain.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain),
await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId),
);
if (!cipher.organizationId) {

View File

@ -1,8 +1,10 @@
import { CommonModule } from "@angular/common";
import { booleanAttribute, Component, Input } from "@angular/core";
import { Router, RouterModule } from "@angular/router";
import { firstValueFrom, map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums";
@ -50,6 +52,7 @@ export class ItemMoreOptionsComponent {
private router: Router,
private i18nService: I18nService,
private vaultPopupAutofillService: VaultPopupAutofillService,
private accountService: AccountService,
) {}
get canEdit() {
@ -108,7 +111,10 @@ export class ItemMoreOptionsComponent {
*/
async toggleFavorite() {
this.cipher.favorite = !this.cipher.favorite;
const encryptedCipher = await this.cipherService.encrypt(this.cipher);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const encryptedCipher = await this.cipherService.encrypt(this.cipher, activeUserId);
await this.cipherService.updateWithServer(encryptedCipher);
this.toastService.showToast({
variant: "success",

View File

@ -3,10 +3,14 @@ import { ActivatedRoute, Router } from "@angular/router";
import { mock } from "jest-mock-extended";
import { Subject } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
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 { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums";
@ -30,6 +34,9 @@ describe("ViewV2Component", () => {
type: CipherType.Login,
};
const mockUserId = Utils.newGuid() as UserId;
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
const mockCipherService = {
get: jest.fn().mockResolvedValue({ decrypt: jest.fn().mockResolvedValue(mockCipher) }),
getKeyForCipherKeyDecryption: jest.fn().mockResolvedValue({}),
@ -59,6 +66,10 @@ describe("ViewV2Component", () => {
},
},
},
{
provide: AccountService,
useValue: accountService,
},
],
}).compileComponents();

View File

@ -3,10 +3,11 @@ import { Component } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormsModule } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { Observable, switchMap } from "rxjs";
import { firstValueFrom, map, Observable, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
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 { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@ -67,6 +68,7 @@ export class ViewV2Component {
private dialogService: DialogService,
private logService: LogService,
private toastService: ToastService,
private accountService: AccountService,
) {
this.subscribeToParams();
}
@ -103,7 +105,12 @@ export class ViewV2Component {
async getCipherData(id: string) {
const cipher = await this.cipherService.get(id);
return await cipher.decrypt(await this.cipherService.getKeyForCipherKeyDecryption(cipher));
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
return await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
}
async editCipher() {

View File

@ -5,6 +5,7 @@ import { first } from "rxjs/operators";
import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component";
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 { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
@ -36,6 +37,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
fileDownloadService: FileDownloadService,
dialogService: DialogService,
billingAccountProfileStateService: BillingAccountProfileStateService,
accountService: AccountService,
) {
super(
cipherService,
@ -49,6 +51,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
fileDownloadService,
dialogService,
billingAccountProfileStateService,
accountService,
);
}

View File

@ -5,6 +5,7 @@ import { first } from "rxjs/operators";
import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
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";
@ -28,6 +29,7 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
private location: Location,
logService: LogService,
configService: ConfigService,
accountService: AccountService,
) {
super(
collectionService,
@ -37,6 +39,7 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
organizationService,
logService,
configService,
accountService,
);
}

View File

@ -4,6 +4,7 @@ import { ActivatedRoute } from "@angular/router";
import { first } from "rxjs/operators";
import { PasswordHistoryComponent as BasePasswordHistoryComponent } from "@bitwarden/angular/vault/components/password-history.component";
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";
@ -18,10 +19,11 @@ export class PasswordHistoryComponent extends BasePasswordHistoryComponent imple
cipherService: CipherService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
accountService: AccountService,
private location: Location,
private route: ActivatedRoute,
) {
super(cipherService, platformUtilsService, i18nService, window);
super(cipherService, platformUtilsService, i18nService, accountService, window);
}
async ngOnInit() {

View File

@ -4,6 +4,7 @@ import { first } from "rxjs/operators";
import { ShareComponent as BaseShareComponent } from "@bitwarden/angular/components/share.component";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
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";
@ -25,6 +26,7 @@ export class ShareComponent extends BaseShareComponent implements OnInit {
private route: ActivatedRoute,
private router: Router,
organizationService: OrganizationService,
accountService: AccountService,
) {
super(
collectionService,
@ -33,6 +35,7 @@ export class ShareComponent extends BaseShareComponent implements OnInit {
cipherService,
logService,
organizationService,
accountService,
);
}

View File

@ -2,12 +2,13 @@ import { DatePipe, Location } from "@angular/common";
import { ChangeDetectorRef, Component, NgZone, OnInit, OnDestroy } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { Subject, firstValueFrom, takeUntil, Subscription } from "rxjs";
import { first } from "rxjs/operators";
import { first, map } from "rxjs/operators";
import { ViewComponent as BaseViewComponent } from "@bitwarden/angular/vault/components/view.component";
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 { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
@ -97,6 +98,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro
fileDownloadService: FileDownloadService,
dialogService: DialogService,
datePipe: DatePipe,
accountService: AccountService,
billingAccountProfileStateService: BillingAccountProfileStateService,
) {
super(
@ -120,6 +122,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro
fileDownloadService,
dialogService,
datePipe,
accountService,
billingAccountProfileStateService,
);
}
@ -267,7 +270,10 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro
this.cipher.login.uris.push(loginUri);
try {
const cipher: Cipher = await this.cipherService.encrypt(this.cipher);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const cipher: Cipher = await this.cipherService.encrypt(this.cipher, activeUserId);
await this.cipherService.updateWithServer(cipher);
this.platformUtilsService.showToast(
"success",

View File

@ -2,10 +2,13 @@ import { TestBed } from "@angular/core/testing";
import { mock } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.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";
import { subscribeTo } from "@bitwarden/common/spec";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith, subscribeTo } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
@ -40,6 +43,9 @@ describe("VaultPopupAutofillService", () => {
const mockCipherService = mock<CipherService>();
const mockMessagingService = mock<MessagingService>();
const mockUserId = Utils.newGuid() as UserId;
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
beforeEach(() => {
jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(false);
jest.spyOn(BrowserApi, "getTabFromCurrentWindow").mockResolvedValue(mockCurrentTab);
@ -55,6 +61,10 @@ describe("VaultPopupAutofillService", () => {
{ provide: PasswordRepromptService, useValue: mockPasswordRepromptService },
{ provide: CipherService, useValue: mockCipherService },
{ provide: MessagingService, useValue: mockMessagingService },
{
provide: AccountService,
useValue: accountService,
},
],
});
@ -311,7 +321,7 @@ describe("VaultPopupAutofillService", () => {
expect(result).toBe(true);
expect(mockCipher.login.uris).toHaveLength(1);
expect(mockCipher.login.uris[0].uri).toBe(mockCurrentTab.url);
expect(mockCipherService.encrypt).toHaveBeenCalledWith(mockCipher);
expect(mockCipherService.encrypt).toHaveBeenCalledWith(mockCipher, mockUserId);
expect(mockCipherService.updateWithServer).toHaveBeenCalledWith(mockEncryptedCipher);
});

View File

@ -10,6 +10,7 @@ import {
switchMap,
} from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.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 VaultPopupAutofillService {
private passwordRepromptService: PasswordRepromptService,
private cipherService: CipherService,
private messagingService: MessagingService,
private accountService: AccountService,
) {
this._currentPageDetails$.subscribe();
}
@ -221,7 +223,10 @@ export class VaultPopupAutofillService {
cipher.login.uris.push(loginUri);
try {
const encCipher = await this.cipherService.encrypt(cipher);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const encCipher = await this.cipherService.encrypt(cipher, activeUserId);
await this.cipherService.updateWithServer(encCipher);
this.messagingService.send("editedCipher");
return true;

View File

@ -1,3 +1,6 @@
import { firstValueFrom, map } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { Response } from "../../models/response";
@ -5,7 +8,10 @@ import { CliUtils } from "../../utils";
import { CipherResponse } from "../../vault/models/cipher.response";
export class ShareCommand {
constructor(private cipherService: CipherService) {}
constructor(
private cipherService: CipherService,
private accountService: AccountService,
) {}
async run(id: string, organizationId: string, requestJson: string): Promise<Response> {
if (process.env.BW_SERVE !== "true" && (requestJson == null || requestJson === "")) {
@ -45,14 +51,18 @@ export class ShareCommand {
if (cipher.organizationId != null) {
return Response.badRequest("This item already belongs to an organization.");
}
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const cipherView = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher),
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
try {
await this.cipherService.shareWithServer(cipherView, organizationId, req);
await this.cipherService.shareWithServer(cipherView, organizationId, req, activeUserId);
const updatedCipher = await this.cipherService.get(cipher.id);
const decCipher = await updatedCipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher),
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId),
);
const res = new CipherResponse(decCipher);
return Response.success(res);

View File

@ -1,5 +1,8 @@
import { firstValueFrom, map } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { CipherExport } from "@bitwarden/common/models/export/cipher.export";
import { CollectionExport } from "@bitwarden/common/models/export/collection.export";
import { FolderExport } from "@bitwarden/common/models/export/folder.export";
@ -24,6 +27,7 @@ export class EditCommand {
private cryptoService: CryptoService,
private apiService: ApiService,
private folderApiService: FolderApiServiceAbstraction,
private accountService: AccountService,
) {}
async run(
@ -77,18 +81,21 @@ export class EditCommand {
return Response.notFound();
}
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
let cipherView = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher),
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
if (cipherView.isDeleted) {
return Response.badRequest("You may not edit a deleted item. Use the restore command first.");
}
cipherView = CipherExport.toView(req, cipherView);
const encCipher = await this.cipherService.encrypt(cipherView);
const encCipher = await this.cipherService.encrypt(cipherView, activeUserId);
try {
const updatedCipher = await this.cipherService.updateWithServer(encCipher);
const decCipher = await updatedCipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher),
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId),
);
const res = new CipherResponse(decCipher);
return Response.success(res);
@ -110,9 +117,12 @@ export class EditCommand {
cipher.collectionIds = req;
try {
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const updatedCipher = await this.cipherService.saveCollectionsWithServer(cipher);
const decCipher = await updatedCipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher),
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId),
);
const res = new CipherResponse(decCipher);
return Response.success(res);

View File

@ -1,4 +1,4 @@
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";
@ -6,6 +6,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
import { SearchService } from "@bitwarden/common/abstractions/search.service";
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 { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EventType } from "@bitwarden/common/enums";
import { CardExport } from "@bitwarden/common/models/export/card.export";
@ -62,6 +63,7 @@ export class GetCommand extends DownloadCommand {
private organizationService: OrganizationService,
private eventCollectionService: EventCollectionService,
private accountProfileService: BillingAccountProfileStateService,
private accountService: AccountService,
) {
super(cryptoService);
}
@ -110,9 +112,12 @@ export class GetCommand extends DownloadCommand {
let decCipher: CipherView = null;
if (Utils.isGuid(id)) {
const cipher = await this.cipherService.get(id);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
if (cipher != null) {
decCipher = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher),
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
}
} else if (id.trim() !== "") {

View File

@ -63,6 +63,7 @@ export class OssServeConfigurator {
this.serviceContainer.organizationService,
this.serviceContainer.eventCollectionService,
this.serviceContainer.billingAccountProfileStateService,
this.serviceContainer.accountService,
);
this.listCommand = new ListCommand(
this.serviceContainer.cipherService,
@ -82,6 +83,7 @@ export class OssServeConfigurator {
this.serviceContainer.folderApiService,
this.serviceContainer.billingAccountProfileStateService,
this.serviceContainer.organizationService,
this.serviceContainer.accountService,
);
this.editCommand = new EditCommand(
this.serviceContainer.cipherService,
@ -89,6 +91,7 @@ export class OssServeConfigurator {
this.serviceContainer.cryptoService,
this.serviceContainer.apiService,
this.serviceContainer.folderApiService,
this.serviceContainer.accountService,
);
this.generateCommand = new GenerateCommand(
this.serviceContainer.passwordGenerationService,
@ -114,7 +117,10 @@ export class OssServeConfigurator {
this.serviceContainer.organizationUserService,
);
this.restoreCommand = new RestoreCommand(this.serviceContainer.cipherService);
this.shareCommand = new ShareCommand(this.serviceContainer.cipherService);
this.shareCommand = new ShareCommand(
this.serviceContainer.cipherService,
this.serviceContainer.accountService,
);
this.lockCommand = new LockCommand(this.serviceContainer.vaultTimeoutService);
this.unlockCommand = new UnlockCommand(
this.serviceContainer.accountService,

View File

@ -714,6 +714,7 @@ export class ServiceContainer {
this.collectionService,
this.cryptoService,
this.pinService,
this.accountService,
);
this.individualExportService = new IndividualVaultExportService(
@ -733,6 +734,7 @@ export class ServiceContainer {
this.cryptoFunctionService,
this.collectionService,
this.kdfConfigService,
this.accountService,
);
this.exportService = new VaultExportService(

View File

@ -149,6 +149,7 @@ export class SendProgram extends BaseProgram {
this.serviceContainer.organizationService,
this.serviceContainer.eventCollectionService,
this.serviceContainer.billingAccountProfileStateService,
this.serviceContainer.accountService,
);
const response = await cmd.run("template", object, null);
this.processResponse(response);

View File

@ -184,6 +184,7 @@ export class VaultProgram extends BaseProgram {
this.serviceContainer.organizationService,
this.serviceContainer.eventCollectionService,
this.serviceContainer.billingAccountProfileStateService,
this.serviceContainer.accountService,
);
const response = await command.run(object, id, cmd);
this.processResponse(response);
@ -227,6 +228,7 @@ export class VaultProgram extends BaseProgram {
this.serviceContainer.folderApiService,
this.serviceContainer.billingAccountProfileStateService,
this.serviceContainer.organizationService,
this.serviceContainer.accountService,
);
const response = await command.run(object, encodedJson, cmd);
this.processResponse(response);
@ -272,6 +274,7 @@ export class VaultProgram extends BaseProgram {
this.serviceContainer.cryptoService,
this.serviceContainer.apiService,
this.serviceContainer.folderApiService,
this.serviceContainer.accountService,
);
const response = await command.run(object, id, encodedJson, cmd);
this.processResponse(response);
@ -375,7 +378,10 @@ export class VaultProgram extends BaseProgram {
})
.action(async (id, organizationId, encodedJson, cmd) => {
await this.exitIfLocked();
const command = new ShareCommand(this.serviceContainer.cipherService);
const command = new ShareCommand(
this.serviceContainer.cipherService,
this.serviceContainer.accountService,
);
const response = await command.run(id, organizationId, encodedJson);
this.processResponse(response);
});

View File

@ -1,11 +1,12 @@
import * as fs from "fs";
import * as path from "path";
import { firstValueFrom } from "rxjs";
import { firstValueFrom, map } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { CipherExport } from "@bitwarden/common/models/export/cipher.export";
import { CollectionExport } from "@bitwarden/common/models/export/collection.export";
@ -34,6 +35,7 @@ export class CreateCommand {
private folderApiService: FolderApiServiceAbstraction,
private accountProfileService: BillingAccountProfileStateService,
private organizationService: OrganizationService,
private accountService: AccountService,
) {}
async run(
@ -80,11 +82,14 @@ export class CreateCommand {
}
private async createCipher(req: CipherExport) {
const cipher = await this.cipherService.encrypt(CipherExport.toView(req));
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId);
try {
const newCipher = await this.cipherService.createWithServer(cipher);
const decCipher = await newCipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(newCipher),
await this.cipherService.getKeyForCipherKeyDecryption(newCipher, activeUserId),
);
const res = new CipherResponse(decCipher);
return Response.success(res);
@ -143,13 +148,17 @@ export class CreateCommand {
}
try {
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const updatedCipher = await this.cipherService.saveAttachmentRawWithServer(
cipher,
fileName,
new Uint8Array(fileBuf).buffer,
activeUserId,
);
const decCipher = await updatedCipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher),
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId),
);
return Response.success(new CipherResponse(decCipher));
} catch (e) {

View File

@ -164,7 +164,10 @@ export class EncryptedMessageHandlerService {
cipherView.login.uris[0].uri = credentialCreatePayload.uri;
try {
const encrypted = await this.cipherService.encrypt(cipherView);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const encrypted = await this.cipherService.encrypt(cipherView, activeUserId);
await this.cipherService.createWithServer(encrypted);
// Notify other clients of new login
@ -197,14 +200,17 @@ export class EncryptedMessageHandlerService {
if (cipher === null) {
return { status: "failure" };
}
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const cipherView = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher),
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
cipherView.name = credentialUpdatePayload.name;
cipherView.login.password = credentialUpdatePayload.password;
cipherView.login.username = credentialUpdatePayload.userName;
cipherView.login.uris[0].uri = credentialUpdatePayload.uri;
const encrypted = await this.cipherService.encrypt(cipherView);
const encrypted = await this.cipherService.encrypt(cipherView, activeUserId);
await this.cipherService.updateWithServer(encrypted);

View File

@ -2,6 +2,7 @@ import { Component } from "@angular/core";
import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component";
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 { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
@ -28,6 +29,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
fileDownloadService: FileDownloadService,
dialogService: DialogService,
billingAccountProfileStateService: BillingAccountProfileStateService,
accountService: AccountService,
) {
super(
cipherService,
@ -41,6 +43,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
fileDownloadService,
dialogService,
billingAccountProfileStateService,
accountService,
);
}
}

View File

@ -2,6 +2,7 @@ import { Component } from "@angular/core";
import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
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";
@ -22,6 +23,7 @@ export class CollectionsComponent extends BaseCollectionsComponent {
organizationService: OrganizationService,
logService: LogService,
configService: ConfigService,
accountService: AccountService,
) {
super(
collectionService,
@ -31,6 +33,7 @@ export class CollectionsComponent extends BaseCollectionsComponent {
organizationService,
logService,
configService,
accountService,
);
}
}

View File

@ -1,6 +1,7 @@
import { Component } from "@angular/core";
import { PasswordHistoryComponent as BasePasswordHistoryComponent } from "@bitwarden/angular/vault/components/password-history.component";
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,7 +15,8 @@ export class PasswordHistoryComponent extends BasePasswordHistoryComponent {
cipherService: CipherService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
accountService: AccountService,
) {
super(cipherService, platformUtilsService, i18nService, window);
super(cipherService, platformUtilsService, i18nService, accountService, window);
}
}

View File

@ -3,6 +3,7 @@ import { Component } from "@angular/core";
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
import { ShareComponent as BaseShareComponent } from "@bitwarden/angular/components/share.component";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
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";
@ -21,6 +22,7 @@ export class ShareComponent extends BaseShareComponent {
platformUtilsService: PlatformUtilsService,
logService: LogService,
organizationService: OrganizationService,
accountService: AccountService,
private modalRef: ModalRef,
) {
super(
@ -30,6 +32,7 @@ export class ShareComponent extends BaseShareComponent {
cipherService,
logService,
organizationService,
accountService,
);
}

View File

@ -14,6 +14,7 @@ import { ViewComponent as BaseViewComponent } from "@bitwarden/angular/vault/com
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 { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
@ -62,6 +63,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro
dialogService: DialogService,
datePipe: DatePipe,
billingAccountProfileStateService: BillingAccountProfileStateService,
accountService: AccountService,
) {
super(
cipherService,
@ -84,6 +86,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro
fileDownloadService,
dialogService,
datePipe,
accountService,
billingAccountProfileStateService,
);
}

View File

@ -2,6 +2,7 @@ import { Component } from "@angular/core";
import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component";
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 { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
@ -32,6 +33,7 @@ export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponen
fileDownloadService: FileDownloadService,
dialogService: DialogService,
billingAccountProfileStateService: BillingAccountProfileStateService,
accountService: AccountService,
) {
super(
cipherService,
@ -45,6 +47,7 @@ export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponen
fileDownloadService,
dialogService,
billingAccountProfileStateService,
accountService,
);
}

View File

@ -2,6 +2,7 @@ import { Component } from "@angular/core";
import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component";
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 { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
@ -31,6 +32,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
fileDownloadService: FileDownloadService,
dialogService: DialogService,
billingAccountProfileStateService: BillingAccountProfileStateService,
accountService: AccountService,
) {
super(
cipherService,
@ -44,6 +46,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
fileDownloadService,
dialogService,
billingAccountProfileStateService,
accountService,
);
}

View File

@ -1,8 +1,10 @@
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject, OnDestroy, OnInit } 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 { 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";
@ -61,6 +63,7 @@ export class BulkShareDialogComponent implements OnInit, OnDestroy {
private collectionService: CollectionService,
private organizationService: OrganizationService,
private logService: LogService,
private accountService: AccountService,
) {
this.ciphers = params.ciphers ?? [];
this.organizationId = params.organizationId;
@ -98,10 +101,14 @@ export class BulkShareDialogComponent implements OnInit, OnDestroy {
submit = async () => {
const checkedCollectionIds = this.collections.filter(isChecked).map((c) => c.id);
try {
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
await this.cipherService.shareManyWithServer(
this.shareableCiphers,
this.organizationId,
checkedCollectionIds,
activeUserId,
);
const orgName =
this.organizations.find((o) => o.id === this.organizationId)?.name ??

View File

@ -3,6 +3,7 @@ import { Component, Inject, OnDestroy } from "@angular/core";
import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
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";
@ -25,6 +26,7 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
organizationSerivce: OrganizationService,
logService: LogService,
configService: ConfigService,
accountService: AccountService,
protected dialogRef: DialogRef,
@Inject(DIALOG_DATA) params: CollectionsDialogParams,
) {
@ -36,6 +38,7 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
organizationSerivce,
logService,
configService,
accountService,
);
this.cipherId = params?.cipherId;
}

View File

@ -2,6 +2,7 @@ import { Component, OnDestroy } from "@angular/core";
import { ShareComponent as BaseShareComponent } from "@bitwarden/angular/components/share.component";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
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";
@ -21,6 +22,7 @@ export class ShareComponent extends BaseShareComponent implements OnDestroy {
cipherService: CipherService,
organizationService: OrganizationService,
logService: LogService,
accountService: AccountService,
) {
super(
collectionService,
@ -29,6 +31,7 @@ export class ShareComponent extends BaseShareComponent implements OnDestroy {
cipherService,
logService,
organizationService,
accountService,
);
}

View File

@ -35,6 +35,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
import { SearchService } from "@bitwarden/common/abstractions/search.service";
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 { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EventType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
@ -198,6 +199,7 @@ export class VaultComponent implements OnInit, OnDestroy {
private apiService: ApiService,
private billingAccountProfileStateService: BillingAccountProfileStateService,
private toastService: ToastService,
private accountService: AccountService,
) {}
async ngOnInit() {
@ -699,9 +701,12 @@ export class VaultComponent implements OnInit, OnDestroy {
return;
}
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
// Decrypt the cipher.
const cipherView = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher),
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
// Open the dialog.

View File

@ -14,6 +14,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
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";
@ -107,11 +108,12 @@ export class AddEditComponent extends BaseAddEditComponent {
return cipher;
}
protected encryptCipher() {
protected encryptCipher(userId: UserId) {
if (!this.organization.canEditAllCiphers(this.restrictProviderAccess)) {
return super.encryptCipher();
return super.encryptCipher(userId);
}
return this.cipherService.encrypt(this.cipher, null, null, this.originalCipher);
return this.cipherService.encrypt(this.cipher, userId, null, null, this.originalCipher);
}
protected async deleteCipher() {

View File

@ -3,6 +3,7 @@ import { firstValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@ -12,6 +13,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
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 { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
@ -41,6 +43,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
fileDownloadService: FileDownloadService,
dialogService: DialogService,
billingAccountProfileStateService: BillingAccountProfileStateService,
accountService: AccountService,
private configService: ConfigService,
) {
super(
@ -54,6 +57,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
fileDownloadService,
dialogService,
billingAccountProfileStateService,
accountService,
);
}
@ -81,10 +85,11 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
return new Cipher(new CipherData(response));
}
protected saveCipherAttachment(file: File) {
protected saveCipherAttachment(file: File, userId: UserId) {
return this.cipherService.saveAttachmentWithServer(
this.cipherDomain,
file,
userId,
this.organization.canEditAllCiphers(this.restrictProviderAccess),
);
}

View File

@ -4,6 +4,7 @@ import { Component, Inject } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
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 { 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";
@ -37,6 +38,7 @@ export class CollectionsComponent extends BaseCollectionsComponent {
private apiService: ApiService,
logService: LogService,
configService: ConfigService,
accountService: AccountService,
protected dialogRef: DialogRef,
@Inject(DIALOG_DATA) params: OrgVaultCollectionsDialogParams,
) {
@ -48,6 +50,7 @@ export class CollectionsComponent extends BaseCollectionsComponent {
organizationService,
logService,
configService,
accountService,
dialogRef,
params,
);

View File

@ -39,6 +39,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { EventType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
@ -216,6 +217,7 @@ export class VaultComponent implements OnInit, OnDestroy {
private organizationUserService: OrganizationUserService,
protected configService: ConfigService,
private toastService: ToastService,
private accountService: AccountService,
) {}
async ngOnInit() {
@ -893,9 +895,12 @@ export class VaultComponent implements OnInit, OnDestroy {
return;
}
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
// Decrypt the cipher.
const cipherView = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher),
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
// Open the dialog.

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

@ -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

@ -775,6 +775,7 @@ const safeProviders: SafeProvider[] = [
CollectionServiceAbstraction,
CryptoServiceAbstraction,
PinServiceAbstraction,
AccountServiceAbstraction,
],
}),
safeProvider({
@ -800,6 +801,7 @@ const safeProviders: SafeProvider[] = [
CryptoFunctionServiceAbstraction,
CollectionServiceAbstraction,
KdfConfigServiceAbstraction,
AccountServiceAbstraction,
],
}),
safeProvider({

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$,

View File

@ -1,7 +1,10 @@
import { TextEncoder } from "util";
import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
import { AccountInfo, AccountService } from "../../../auth/abstractions/account.service";
import { UserId } from "../../../types/guid";
import { CipherService } from "../../../vault/abstractions/cipher.service";
import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction";
import { CipherRepromptType } from "../../../vault/enums/cipher-reprompt-type";
@ -30,10 +33,18 @@ import { guidToRawFormat } from "./guid-utils";
const RpId = "bitwarden.com";
describe("FidoAuthenticatorService", () => {
const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({
id: "testId" as UserId,
email: "test@example.com",
emailVerified: true,
name: "Test User",
});
let cipherService!: MockProxy<CipherService>;
let userInterface!: MockProxy<Fido2UserInterfaceService>;
let userInterfaceSession!: MockProxy<Fido2UserInterfaceSession>;
let syncService!: MockProxy<SyncService>;
let accountService!: MockProxy<AccountService>;
let authenticator!: Fido2AuthenticatorService;
let tab!: chrome.tabs.Tab;
@ -43,8 +54,15 @@ describe("FidoAuthenticatorService", () => {
userInterfaceSession = mock<Fido2UserInterfaceSession>();
userInterface.newSession.mockResolvedValue(userInterfaceSession);
syncService = mock<SyncService>();
authenticator = new Fido2AuthenticatorService(cipherService, userInterface, syncService);
accountService = mock<AccountService>();
authenticator = new Fido2AuthenticatorService(
cipherService,
userInterface,
syncService,
accountService,
);
tab = { id: 123, windowId: 456 } as chrome.tabs.Tab;
accountService.activeAccount$ = activeAccountSubject;
});
describe("makeCredential", () => {
@ -677,6 +695,7 @@ describe("FidoAuthenticatorService", () => {
],
}),
}),
"testId",
);
});

View File

@ -1,3 +1,6 @@
import { firstValueFrom, map } from "rxjs";
import { AccountService } from "../../../auth/abstractions/account.service";
import { CipherService } from "../../../vault/abstractions/cipher.service";
import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction";
import { CipherRepromptType } from "../../../vault/enums/cipher-reprompt-type";
@ -42,6 +45,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
private cipherService: CipherService,
private userInterface: Fido2UserInterfaceService,
private syncService: SyncService,
private accountService: AccountService,
private logService?: LogService,
) {}
@ -130,8 +134,12 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
keyPair = await createKeyPair();
pubKeyDer = await crypto.subtle.exportKey("spki", keyPair.publicKey);
const encrypted = await this.cipherService.get(cipherId);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
cipher = await encrypted.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(encrypted),
await this.cipherService.getKeyForCipherKeyDecryption(encrypted, activeUserId),
);
if (
@ -150,7 +158,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
if (Utils.isNullOrEmpty(cipher.login.username)) {
cipher.login.username = fido2Credential.userName;
}
const reencrypted = await this.cipherService.encrypt(cipher);
const reencrypted = await this.cipherService.encrypt(cipher, activeUserId);
await this.cipherService.updateWithServer(reencrypted);
credentialId = fido2Credential.credentialId;
} catch (error) {
@ -277,7 +285,10 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr
};
if (selectedFido2Credential.counter > 0) {
const encrypted = await this.cipherService.encrypt(selectedCipher);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const encrypted = await this.cipherService.encrypt(selectedCipher, activeUserId);
await this.cipherService.updateWithServer(encrypted);
}

View File

@ -175,7 +175,7 @@ export class DefaultSyncService extends CoreSyncService {
throw new Error("Stamp has changed");
}
await this.cryptoService.setMasterKeyEncryptedUserKey(response.key);
await this.cryptoService.setMasterKeyEncryptedUserKey(response.key, response.id);
await this.cryptoService.setPrivateKey(response.privateKey, response.id);
await this.cryptoService.setProviderKeys(response.providers, response.id);
await this.cryptoService.setOrgKeys(

View File

@ -27,6 +27,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
clearCache: (userId?: string) => Promise<void>;
encrypt: (
model: CipherView,
userId: UserId,
keyForEncryption?: SymmetricCryptoKey,
keyForCipherKeyDecryption?: SymmetricCryptoKey,
originalCipher?: Cipher,
@ -83,21 +84,25 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
cipher: CipherView,
organizationId: string,
collectionIds: string[],
userId: UserId,
) => Promise<any>;
shareManyWithServer: (
ciphers: CipherView[],
organizationId: string,
collectionIds: string[],
userId: UserId,
) => Promise<any>;
saveAttachmentWithServer: (
cipher: Cipher,
unencryptedFile: any,
userId: UserId,
admin?: boolean,
) => Promise<Cipher>;
saveAttachmentRawWithServer: (
cipher: Cipher,
filename: string,
data: ArrayBuffer,
userId: UserId,
admin?: boolean,
) => Promise<Cipher>;
/**
@ -147,7 +152,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
) => Promise<any>;
restoreWithServer: (id: string, asAdmin?: boolean) => Promise<any>;
restoreManyWithServer: (ids: string[], orgId?: string) => Promise<void>;
getKeyForCipherKeyDecryption: (cipher: Cipher) => Promise<any>;
getKeyForCipherKeyDecryption: (cipher: Cipher, userId: UserId) => Promise<any>;
setAddEditCipherInfo: (value: AddEditCipherInfo) => Promise<void>;
/**
* Returns user ciphers re-encrypted with the new user key.

View File

@ -1,6 +1,8 @@
import { mock } from "jest-mock-extended";
import { Jsonify } from "type-fest";
import { UserId } from "@bitwarden/common/types/guid";
import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec/utils";
import { UriMatchStrategy } from "../../../models/domain/domain-service";
import { CryptoService } from "../../../platform/abstractions/crypto.service";
@ -247,7 +249,7 @@ describe("Cipher DTO", () => {
);
const cipherView = await cipher.decrypt(
await cipherService.getKeyForCipherKeyDecryption(cipher),
await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId),
);
expect(cipherView).toMatchObject({
@ -367,7 +369,7 @@ describe("Cipher DTO", () => {
);
const cipherView = await cipher.decrypt(
await cipherService.getKeyForCipherKeyDecryption(cipher),
await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId),
);
expect(cipherView).toMatchObject({
@ -505,7 +507,7 @@ describe("Cipher DTO", () => {
);
const cipherView = await cipher.decrypt(
await cipherService.getKeyForCipherKeyDecryption(cipher),
await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId),
);
expect(cipherView).toMatchObject({
@ -667,7 +669,7 @@ describe("Cipher DTO", () => {
);
const cipherView = await cipher.decrypt(
await cipherService.getKeyForCipherKeyDecryption(cipher),
await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId),
);
expect(cipherView).toMatchObject({
@ -754,3 +756,5 @@ describe("Cipher DTO", () => {
});
});
});
const mockUserId = "TestUserId" as UserId;

View File

@ -121,6 +121,8 @@ describe("Cipher Service", () => {
accountService = mockAccountServiceWith(mockUserId);
const stateProvider = new FakeStateProvider(accountService);
const userId = "TestUserId" as UserId;
let cipherService: CipherService;
let cipherObj: Cipher;
@ -168,7 +170,7 @@ describe("Cipher Service", () => {
const spy = jest.spyOn(cipherFileUploadService, "upload");
await cipherService.saveAttachmentRawWithServer(new Cipher(), fileName, fileData);
await cipherService.saveAttachmentRawWithServer(new Cipher(), fileName, fileData, userId);
expect(spy).toHaveBeenCalled();
});
@ -283,7 +285,7 @@ describe("Cipher Service", () => {
{ uri: "uri", match: UriMatchStrategy.RegularExpression } as LoginUriView,
];
const domain = await cipherService.encrypt(cipherView);
const domain = await cipherService.encrypt(cipherView, userId);
expect(domain.login.uris).toEqual([
{
@ -299,7 +301,7 @@ describe("Cipher Service", () => {
it("is null when enableCipherKeyEncryption flag is false", async () => {
setEncryptionKeyFlag(false);
const cipher = await cipherService.encrypt(cipherView);
const cipher = await cipherService.encrypt(cipherView, userId);
expect(cipher.key).toBeNull();
});
@ -307,7 +309,7 @@ describe("Cipher Service", () => {
it("is defined when enableCipherKeyEncryption flag is true", async () => {
setEncryptionKeyFlag(true);
const cipher = await cipherService.encrypt(cipherView);
const cipher = await cipherService.encrypt(cipherView, userId);
expect(cipher.key).toBeDefined();
});
@ -321,7 +323,7 @@ describe("Cipher Service", () => {
it("is not called when enableCipherKeyEncryption is false", async () => {
setEncryptionKeyFlag(false);
await cipherService.encrypt(cipherView);
await cipherService.encrypt(cipherView, userId);
expect(cipherService["encryptCipherWithCipherKey"]).not.toHaveBeenCalled();
});
@ -329,7 +331,7 @@ describe("Cipher Service", () => {
it("is called when enableCipherKeyEncryption is true", async () => {
setEncryptionKeyFlag(true);
await cipherService.encrypt(cipherView);
await cipherService.encrypt(cipherView, userId);
expect(cipherService["encryptCipherWithCipherKey"]).toHaveBeenCalled();
});

View File

@ -165,6 +165,7 @@ export class CipherService implements CipherServiceAbstraction {
async encrypt(
model: CipherView,
userId: UserId,
keyForEncryption?: SymmetricCryptoKey,
keyForCipherKeyDecryption?: SymmetricCryptoKey,
originalCipher: Cipher = null,
@ -174,7 +175,7 @@ export class CipherService implements CipherServiceAbstraction {
originalCipher = await this.get(model.id);
}
if (originalCipher != null) {
await this.updateModelfromExistingCipher(model, originalCipher);
await this.updateModelfromExistingCipher(model, originalCipher, userId);
}
this.adjustPasswordHistoryLength(model);
}
@ -192,7 +193,7 @@ export class CipherService implements CipherServiceAbstraction {
if (await this.getCipherKeyEncryptionEnabled()) {
cipher.key = originalCipher?.key ?? null;
const userOrOrgKey = await this.getKeyForCipherKeyDecryption(cipher);
const userOrOrgKey = await this.getKeyForCipherKeyDecryption(cipher, userId);
// The keyForEncryption is only used for encrypting the cipher key, not the cipher itself, since cipher key encryption is enabled.
// If the caller has provided a key for cipher key encryption, use it. Otherwise, use the user or org key.
keyForEncryption ||= userOrOrgKey;
@ -718,6 +719,7 @@ export class CipherService implements CipherServiceAbstraction {
cipher: CipherView,
organizationId: string,
collectionIds: string[],
userId: UserId,
): Promise<any> {
const attachmentPromises: Promise<any>[] = [];
if (cipher.attachments != null) {
@ -733,7 +735,7 @@ export class CipherService implements CipherServiceAbstraction {
cipher.organizationId = organizationId;
cipher.collectionIds = collectionIds;
const encCipher = await this.encryptSharedCipher(cipher);
const encCipher = await this.encryptSharedCipher(cipher, userId);
const request = new CipherShareRequest(encCipher);
const response = await this.apiService.putShareCipher(cipher.id, request);
const data = new CipherData(response, collectionIds);
@ -744,6 +746,7 @@ export class CipherService implements CipherServiceAbstraction {
ciphers: CipherView[],
organizationId: string,
collectionIds: string[],
userId: UserId,
): Promise<any> {
const promises: Promise<any>[] = [];
const encCiphers: Cipher[] = [];
@ -751,7 +754,7 @@ export class CipherService implements CipherServiceAbstraction {
cipher.organizationId = organizationId;
cipher.collectionIds = collectionIds;
promises.push(
this.encryptSharedCipher(cipher).then((c) => {
this.encryptSharedCipher(cipher, userId).then((c) => {
encCiphers.push(c);
}),
);
@ -770,7 +773,12 @@ export class CipherService implements CipherServiceAbstraction {
await this.upsert(encCiphers.map((c) => c.toCipherData()));
}
saveAttachmentWithServer(cipher: Cipher, unencryptedFile: any, admin = false): Promise<Cipher> {
saveAttachmentWithServer(
cipher: Cipher,
unencryptedFile: any,
userId: UserId,
admin = false,
): Promise<Cipher> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsArrayBuffer(unencryptedFile);
@ -780,6 +788,7 @@ export class CipherService implements CipherServiceAbstraction {
cipher,
unencryptedFile.name,
evt.target.result,
userId,
admin,
);
resolve(cData);
@ -797,9 +806,10 @@ export class CipherService implements CipherServiceAbstraction {
cipher: Cipher,
filename: string,
data: Uint8Array,
userId: UserId,
admin = false,
): Promise<Cipher> {
const encKey = await this.getKeyForCipherKeyDecryption(cipher);
const encKey = await this.getKeyForCipherKeyDecryption(cipher, userId);
const cipherKeyEncryptionEnabled = await this.getCipherKeyEncryptionEnabled();
const cipherEncKey =
@ -813,8 +823,8 @@ export class CipherService implements CipherServiceAbstraction {
//then we rollback to using the user key as the main key of encryption of the item
//in order to keep item and it's attachments with the same encryption level
if (cipher.key != null && !cipherKeyEncryptionEnabled) {
const model = await cipher.decrypt(await this.getKeyForCipherKeyDecryption(cipher));
cipher = await this.encrypt(model);
const model = await cipher.decrypt(await this.getKeyForCipherKeyDecryption(cipher, userId));
cipher = await this.encrypt(model, userId);
await this.updateWithServer(cipher);
}
@ -1209,10 +1219,10 @@ export class CipherService implements CipherServiceAbstraction {
await this.restore(restores);
}
async getKeyForCipherKeyDecryption(cipher: Cipher): Promise<UserKey | OrgKey> {
async getKeyForCipherKeyDecryption(cipher: Cipher, userId: UserId): Promise<UserKey | OrgKey> {
return (
(await this.cryptoService.getOrgKey(cipher.organizationId)) ||
((await this.cryptoService.getUserKeyWithLegacySupport()) as UserKey)
((await this.cryptoService.getUserKeyWithLegacySupport(userId)) as UserKey)
);
}
@ -1247,7 +1257,7 @@ export class CipherService implements CipherServiceAbstraction {
}
encryptedCiphers = await Promise.all(
userCiphers.map(async (cipher) => {
const encryptedCipher = await this.encrypt(cipher, newUserKey, originalUserKey);
const encryptedCipher = await this.encrypt(cipher, userId, newUserKey, originalUserKey);
return new CipherWithIdRequest(encryptedCipher);
}),
);
@ -1259,17 +1269,18 @@ export class CipherService implements CipherServiceAbstraction {
// In the case of a cipher that is being shared with an organization, we want to decrypt the
// cipher key with the user's key and then re-encrypt it with the organization's key.
private async encryptSharedCipher(model: CipherView): Promise<Cipher> {
const keyForCipherKeyDecryption = await this.cryptoService.getUserKeyWithLegacySupport();
return await this.encrypt(model, null, keyForCipherKeyDecryption);
private async encryptSharedCipher(model: CipherView, userId: UserId): Promise<Cipher> {
const keyForCipherKeyDecryption = await this.cryptoService.getUserKeyWithLegacySupport(userId);
return await this.encrypt(model, userId, null, keyForCipherKeyDecryption);
}
private async updateModelfromExistingCipher(
model: CipherView,
originalCipher: Cipher,
userId: UserId,
): Promise<void> {
const existingCipher = await originalCipher.decrypt(
await this.getKeyForCipherKeyDecryption(originalCipher),
await this.getKeyForCipherKeyDecryption(originalCipher, userId),
);
model.passwordHistory = existingCipher.passwordHistory || [];
if (model.type === CipherType.Login && existingCipher.type === CipherType.Login) {

View File

@ -1,6 +1,7 @@
import { mock, MockProxy } from "jest-mock-extended";
import { PinServiceAbstraction } from "@bitwarden/auth/common";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { KdfType } from "@bitwarden/common/platform/enums";
@ -21,6 +22,7 @@ describe("BitwardenPasswordProtectedImporter", () => {
let i18nService: MockProxy<I18nService>;
let cipherService: MockProxy<CipherService>;
let pinService: MockProxy<PinServiceAbstraction>;
let accountService: MockProxy<AccountService>;
const password = Utils.newGuid();
const promptForPassword_callback = async () => {
return password;
@ -31,12 +33,14 @@ describe("BitwardenPasswordProtectedImporter", () => {
i18nService = mock<I18nService>();
cipherService = mock<CipherService>();
pinService = mock<PinServiceAbstraction>();
accountService = mock<AccountService>();
importer = new BitwardenPasswordProtectedImporter(
cryptoService,
i18nService,
cipherService,
pinService,
accountService,
promptForPassword_callback,
);
});

View File

@ -27,6 +27,7 @@ import {
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } 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 { ClientType } from "@bitwarden/common/enums";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -89,6 +90,7 @@ const safeProviders: SafeProvider[] = [
CollectionService,
CryptoService,
PinServiceAbstraction,
AccountService,
],
}),
];

View File

@ -1,6 +1,7 @@
import { firstValueFrom } from "rxjs";
import { firstValueFrom, map } from "rxjs";
import { PinServiceAbstraction } from "@bitwarden/auth/common";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import {
CipherWithIdExport,
CollectionWithIdExport,
@ -33,6 +34,7 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
protected i18nService: I18nService,
protected cipherService: CipherService,
protected pinService: PinServiceAbstraction,
protected accountService: AccountService,
) {
super();
}
@ -103,8 +105,11 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
});
}
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const view = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher),
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
this.cleanupCipher(view);
this.result.ciphers.push(view);

View File

@ -1,4 +1,5 @@
import { PinServiceAbstraction } from "@bitwarden/auth/common";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import {
Argon2KdfConfig,
KdfConfig,
@ -25,9 +26,10 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im
i18nService: I18nService,
cipherService: CipherService,
pinService: PinServiceAbstraction,
accountService: AccountService,
private promptForPassword_callback: () => Promise<string>,
) {
super(cryptoService, i18nService, cipherService, pinService);
super(cryptoService, i18nService, cipherService, pinService, accountService);
}
async parse(data: string): Promise<ImportResult> {

View File

@ -1,6 +1,7 @@
import { mock, MockProxy } from "jest-mock-extended";
import { PinServiceAbstraction } from "@bitwarden/auth/common";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@ -27,6 +28,7 @@ describe("ImportService", () => {
let collectionService: MockProxy<CollectionService>;
let cryptoService: MockProxy<CryptoService>;
let pinService: MockProxy<PinServiceAbstraction>;
let accountService: MockProxy<AccountService>;
beforeEach(() => {
cipherService = mock<CipherService>();
@ -45,6 +47,7 @@ describe("ImportService", () => {
collectionService,
cryptoService,
pinService,
accountService,
);
});

View File

@ -1,4 +1,7 @@
import { firstValueFrom, map } from "rxjs";
import { PinServiceAbstraction } from "@bitwarden/auth/common";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ImportCiphersRequest } from "@bitwarden/common/models/request/import-ciphers.request";
import { ImportOrganizationCiphersRequest } from "@bitwarden/common/models/request/import-organization-ciphers.request";
import { KvpRequest } from "@bitwarden/common/models/request/kvp.request";
@ -102,6 +105,7 @@ export class ImportService implements ImportServiceAbstraction {
private collectionService: CollectionService,
private cryptoService: CryptoService,
private pinService: PinServiceAbstraction,
private accountService: AccountService,
) {}
getImportOptions(): ImportOption[] {
@ -206,6 +210,7 @@ export class ImportService implements ImportServiceAbstraction {
this.i18nService,
this.cipherService,
this.pinService,
this.accountService,
promptForPassword_callback,
);
case "lastpasscsv":
@ -332,8 +337,11 @@ export class ImportService implements ImportServiceAbstraction {
private async handleIndividualImport(importResult: ImportResult) {
const request = new ImportCiphersRequest();
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
for (let i = 0; i < importResult.ciphers.length; i++) {
const c = await this.cipherService.encrypt(importResult.ciphers[i]);
const c = await this.cipherService.encrypt(importResult.ciphers[i], activeUserId);
request.ciphers.push(new CipherRequest(c));
}
if (importResult.folders != null) {
@ -352,9 +360,12 @@ export class ImportService implements ImportServiceAbstraction {
private async handleOrganizationalImport(importResult: ImportResult, organizationId: string) {
const request = new ImportOrganizationCiphersRequest();
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
for (let i = 0; i < importResult.ciphers.length; i++) {
importResult.ciphers[i].organizationId = organizationId;
const c = await this.cipherService.encrypt(importResult.ciphers[i]);
const c = await this.cipherService.encrypt(importResult.ciphers[i], activeUserId);
request.ciphers.push(new CipherRequest(c));
}
if (importResult.collections != null) {

View File

@ -1,8 +1,9 @@
import * as papa from "papaparse";
import { firstValueFrom } from "rxjs";
import { firstValueFrom, map } from "rxjs";
import { PinServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { CipherWithIdExport, CollectionWithIdExport } from "@bitwarden/common/models/export";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
@ -42,6 +43,7 @@ export class OrganizationVaultExportService
cryptoFunctionService: CryptoFunctionService,
private collectionService: CollectionService,
kdfConfigService: KdfConfigService,
private accountService: AccountService,
) {
super(pinService, cryptoService, cryptoFunctionService, kdfConfigService);
}
@ -87,6 +89,9 @@ export class OrganizationVaultExportService
const decCollections: CollectionView[] = [];
const decCiphers: CipherView[] = [];
const promises = [];
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
promises.push(
this.apiService.getOrganizationExport(organizationId).then((exportData) => {
@ -111,7 +116,7 @@ export class OrganizationVaultExportService
const cipher = new Cipher(new CipherData(c));
exportPromises.push(
this.cipherService
.getKeyForCipherKeyDecryption(cipher)
.getKeyForCipherKeyDecryption(cipher, activeUserId)
.then((key) => cipher.decrypt(key))
.then((decCipher) => {
decCiphers.push(decCipher);

View File

@ -3,11 +3,13 @@ import { ComponentFixture, TestBed } from "@angular/core/testing";
import { By } from "@angular/platform-browser";
import { mock } from "jest-mock-extended";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
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 { CipherId } from "@bitwarden/common/types/guid";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { CipherId, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
@ -15,6 +17,8 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { ButtonComponent, ToastService } from "@bitwarden/components";
import { DownloadAttachmentComponent } from "@bitwarden/vault";
import { FakeAccountService, mockAccountServiceWith } from "../../../../../common/spec";
import { CipherAttachmentsComponent } from "./cipher-attachments.component";
import { DeleteAttachmentComponent } from "./delete-attachment/delete-attachment.component";
@ -49,6 +53,9 @@ describe("CipherAttachmentsComponent", () => {
const cipherServiceGet = jest.fn().mockResolvedValue(cipherDomain);
const saveAttachmentWithServer = jest.fn().mockResolvedValue(cipherDomain);
const mockUserId = Utils.newGuid() as UserId;
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
beforeEach(async () => {
cipherServiceGet.mockClear();
showToast.mockClear();
@ -75,6 +82,10 @@ describe("CipherAttachmentsComponent", () => {
{ provide: LogService, useValue: mock<LogService>() },
{ provide: ConfigService, useValue: mock<ConfigService>() },
{ provide: PlatformUtilsService, useValue: mock<PlatformUtilsService>() },
{
provide: AccountService,
useValue: accountService,
},
],
})
.overrideComponent(CipherAttachmentsComponent, {
@ -219,7 +230,7 @@ describe("CipherAttachmentsComponent", () => {
it("calls `saveAttachmentWithServer`", async () => {
await component.submit();
expect(saveAttachmentWithServer).toHaveBeenCalledWith(cipherDomain, file);
expect(saveAttachmentWithServer).toHaveBeenCalledWith(cipherDomain, file, mockUserId);
});
it("resets form and input values", async () => {

View File

@ -19,11 +19,13 @@ import {
ReactiveFormsModule,
Validators,
} from "@angular/forms";
import { firstValueFrom, map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
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 { CipherId } from "@bitwarden/common/types/guid";
import { CipherId, 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";
@ -90,6 +92,7 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit {
});
private cipherDomain: Cipher;
private activeUserId: UserId;
private destroy$ = inject(DestroyRef);
constructor(
@ -98,6 +101,7 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit {
private formBuilder: FormBuilder,
private logService: LogService,
private toastService: ToastService,
private accountService: AccountService,
) {
this.attachmentForm.statusChanges.pipe(takeUntilDestroyed()).subscribe((status) => {
if (!this.submitBtn) {
@ -110,8 +114,11 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit {
async ngOnInit(): Promise<void> {
this.cipherDomain = await this.cipherService.get(this.cipherId);
this.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, this.activeUserId),
);
// Update the initial state of the submit button
@ -178,11 +185,12 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit {
this.cipherDomain = await this.cipherService.saveAttachmentWithServer(
this.cipherDomain,
file,
this.activeUserId,
);
// re-decrypt the cipher to update the attachments
this.cipher = await this.cipherDomain.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain),
await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, this.activeUserId),
);
// Reset reactive form and input element

View File

@ -1,5 +1,7 @@
import { inject, Injectable } from "@angular/core";
import { firstValueFrom, map } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
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";
@ -14,15 +16,25 @@ function isSetEqual(a: Set<string>, b: Set<string>) {
@Injectable()
export class DefaultCipherFormService implements CipherFormService {
private cipherService: CipherService = inject(CipherService);
private accountService: AccountService = inject(AccountService);
async decryptCipher(cipher: Cipher): Promise<CipherView> {
return await cipher.decrypt(await this.cipherService.getKeyForCipherKeyDecryption(cipher));
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
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 activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const encryptedCipher = await this.cipherService.encrypt(
cipher,
activeUserId,
null,
null,
config.originalCipher ?? null,
@ -34,7 +46,7 @@ export class DefaultCipherFormService implements CipherFormService {
if (cipher.id == null) {
savedCipher = await this.cipherService.createWithServer(encryptedCipher, config.admin);
return await savedCipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(savedCipher),
await this.cipherService.getKeyForCipherKeyDecryption(savedCipher, activeUserId),
);
}
@ -68,7 +80,7 @@ export class DefaultCipherFormService implements CipherFormService {
}
return await savedCipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(savedCipher),
await this.cipherService.getKeyForCipherKeyDecryption(savedCipher, activeUserId),
);
}
}

View File

@ -14,6 +14,7 @@ import {
Observable,
Subject,
combineLatest,
firstValueFrom,
map,
shareReplay,
switchMap,
@ -25,10 +26,11 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
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 { 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 { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
import { CipherId, CollectionId, OrganizationId, 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 { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@ -162,6 +164,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
private get selectedOrgId(): OrganizationId {
return this.formGroup.getRawValue().selectedOrg || this.params.organizationId;
}
private activeUserId: UserId;
private destroy$ = new Subject<void>();
constructor(
@ -172,6 +175,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
private collectionService: CollectionService,
private formBuilder: FormBuilder,
private toastService: ToastService,
private accountService: AccountService,
) {}
async ngOnInit() {
@ -179,6 +183,10 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
FeatureFlag.RestrictProviderAccess,
);
this.activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const onlyPersonalItems = this.params.ciphers.every((c) => c.organizationId == null);
if (this.selectedOrgId === MY_VAULT_ID || onlyPersonalItems) {
@ -420,6 +428,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
shareableCiphers,
organizationId,
selectedCollectionIds,
this.activeUserId,
);
this.toastService.showToast({
@ -460,7 +469,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
private async updateAssignedCollections(cipherView: CipherView) {
const { collections } = this.formGroup.getRawValue();
cipherView.collectionIds = collections.map((i) => i.id as CollectionId);
const cipher = await this.cipherService.encrypt(cipherView);
const cipher = await this.cipherService.encrypt(cipherView, this.activeUserId);
await this.cipherService.saveCollectionsWithServer(cipher);
}
}