mirror of
https://github.com/bitwarden/browser
synced 2025-12-13 23:03:32 +00:00
[PM-12047] Remove usage of ActiveUserState from cipher.service (#12814)
* Cipher service web changes * Updated browser client to pass user id to cipher service observable changes * Cli changes * desktop changes * Fixed test * Libs changes * Fixed merge conflicts * Fixed merge conflicts * removed duplicate reference fixed conflict * Fixed test * Fixed test * Fixed test * Fixed desturcturing issue on failed to decrypt ciphers cipher service * Updated abstraction to use method syntax * Fixed conflicts * Fixed test on add edit v2 Passed active userId to delete function * Used getUserId utility function * made vault changes * made suggestion changes * made suggestion changes * made suggestion changes * Replace getUserId function calls with pipe operator syntax for better consistency * fixed merge conflicts * revert mistake made of usinf account activity during merge conflict fix * fixed conflicts * fixed tests
This commit is contained in:
@@ -825,6 +825,7 @@ describe("NotificationBackground", () => {
|
|||||||
queueMessage.newPassword,
|
queueMessage.newPassword,
|
||||||
message.edit,
|
message.edit,
|
||||||
sender.tab,
|
sender.tab,
|
||||||
|
"testId",
|
||||||
);
|
);
|
||||||
expect(updateWithServerSpy).toHaveBeenCalled();
|
expect(updateWithServerSpy).toHaveBeenCalled();
|
||||||
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
|
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
|
||||||
@@ -862,6 +863,7 @@ describe("NotificationBackground", () => {
|
|||||||
queueMessage.password,
|
queueMessage.password,
|
||||||
message.edit,
|
message.edit,
|
||||||
sender.tab,
|
sender.tab,
|
||||||
|
"testId",
|
||||||
);
|
);
|
||||||
expect(editItemSpy).not.toHaveBeenCalled();
|
expect(editItemSpy).not.toHaveBeenCalled();
|
||||||
expect(createWithServerSpy).not.toHaveBeenCalled();
|
expect(createWithServerSpy).not.toHaveBeenCalled();
|
||||||
@@ -895,6 +897,7 @@ describe("NotificationBackground", () => {
|
|||||||
queueMessage.newPassword,
|
queueMessage.newPassword,
|
||||||
message.edit,
|
message.edit,
|
||||||
sender.tab,
|
sender.tab,
|
||||||
|
"testId",
|
||||||
);
|
);
|
||||||
expect(editItemSpy).toHaveBeenCalled();
|
expect(editItemSpy).toHaveBeenCalled();
|
||||||
expect(updateWithServerSpy).not.toHaveBeenCalled();
|
expect(updateWithServerSpy).not.toHaveBeenCalled();
|
||||||
@@ -904,10 +907,13 @@ describe("NotificationBackground", () => {
|
|||||||
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
|
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
|
||||||
command: "editedCipher",
|
command: "editedCipher",
|
||||||
});
|
});
|
||||||
expect(setAddEditCipherInfoSpy).toHaveBeenCalledWith({
|
expect(setAddEditCipherInfoSpy).toHaveBeenCalledWith(
|
||||||
cipher: cipherView,
|
{
|
||||||
collectionIds: cipherView.collectionIds,
|
cipher: cipherView,
|
||||||
});
|
collectionIds: cipherView.collectionIds,
|
||||||
|
},
|
||||||
|
"testId",
|
||||||
|
);
|
||||||
expect(openAddEditVaultItemPopoutSpy).toHaveBeenCalledWith(sender.tab, {
|
expect(openAddEditVaultItemPopoutSpy).toHaveBeenCalledWith(sender.tab, {
|
||||||
cipherId: cipherView.id,
|
cipherId: cipherView.id,
|
||||||
});
|
});
|
||||||
@@ -945,7 +951,7 @@ describe("NotificationBackground", () => {
|
|||||||
queueMessage,
|
queueMessage,
|
||||||
message.folder,
|
message.folder,
|
||||||
);
|
);
|
||||||
expect(editItemSpy).toHaveBeenCalledWith(cipherView, sender.tab);
|
expect(editItemSpy).toHaveBeenCalledWith(cipherView, "testId", sender.tab);
|
||||||
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
|
expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, {
|
||||||
command: "closeNotificationBar",
|
command: "closeNotificationBar",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import {
|
import {
|
||||||
ExtensionCommand,
|
ExtensionCommand,
|
||||||
ExtensionCommandType,
|
ExtensionCommandType,
|
||||||
@@ -22,6 +23,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
|
|||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
@@ -87,8 +89,6 @@ export default class NotificationBackground {
|
|||||||
bgGetDecryptedCiphers: () => this.getNotificationCipherData(),
|
bgGetDecryptedCiphers: () => this.getNotificationCipherData(),
|
||||||
};
|
};
|
||||||
|
|
||||||
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private autofillService: AutofillService,
|
private autofillService: AutofillService,
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
@@ -151,7 +151,13 @@ export default class NotificationBackground {
|
|||||||
firstValueFrom(this.environmentService.environment$),
|
firstValueFrom(this.environmentService.environment$),
|
||||||
]);
|
]);
|
||||||
const iconsServerUrl = env.getIconsUrl();
|
const iconsServerUrl = env.getIconsUrl();
|
||||||
const decryptedCiphers = await this.cipherService.getAllDecryptedForUrl(currentTab.url);
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||||
|
);
|
||||||
|
const decryptedCiphers = await this.cipherService.getAllDecryptedForUrl(
|
||||||
|
currentTab.url,
|
||||||
|
activeUserId,
|
||||||
|
);
|
||||||
|
|
||||||
return decryptedCiphers.map((view) => {
|
return decryptedCiphers.map((view) => {
|
||||||
const { id, name, reprompt, favorite, login } = view;
|
const { id, name, reprompt, favorite, login } = view;
|
||||||
@@ -304,7 +310,14 @@ export default class NotificationBackground {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url);
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||||
|
);
|
||||||
|
if (activeUserId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url, activeUserId);
|
||||||
const usernameMatches = ciphers.filter(
|
const usernameMatches = ciphers.filter(
|
||||||
(c) => c.login.username != null && c.login.username.toLowerCase() === normalizedUsername,
|
(c) => c.login.username != null && c.login.username.toLowerCase() === normalizedUsername,
|
||||||
);
|
);
|
||||||
@@ -382,7 +395,14 @@ export default class NotificationBackground {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let id: string = null;
|
let id: string = null;
|
||||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url);
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||||
|
);
|
||||||
|
if (activeUserId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url, activeUserId);
|
||||||
if (changeData.currentPassword != null) {
|
if (changeData.currentPassword != null) {
|
||||||
const passwordMatches = ciphers.filter(
|
const passwordMatches = ciphers.filter(
|
||||||
(c) => c.login.password === changeData.currentPassword,
|
(c) => c.login.password === changeData.currentPassword,
|
||||||
@@ -535,37 +555,42 @@ export default class NotificationBackground {
|
|||||||
|
|
||||||
this.notificationQueue.splice(i, 1);
|
this.notificationQueue.splice(i, 1);
|
||||||
|
|
||||||
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||||
|
);
|
||||||
|
|
||||||
if (queueMessage.type === NotificationQueueMessageType.ChangePassword) {
|
if (queueMessage.type === NotificationQueueMessageType.ChangePassword) {
|
||||||
const cipherView = await this.getDecryptedCipherById(queueMessage.cipherId);
|
const cipherView = await this.getDecryptedCipherById(queueMessage.cipherId, activeUserId);
|
||||||
await this.updatePassword(cipherView, queueMessage.newPassword, edit, tab);
|
await this.updatePassword(cipherView, queueMessage.newPassword, edit, tab, activeUserId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the vault was locked, check if a cipher needs updating instead of creating a new one
|
// If the vault was locked, check if a cipher needs updating instead of creating a new one
|
||||||
if (queueMessage.wasVaultLocked) {
|
if (queueMessage.wasVaultLocked) {
|
||||||
const allCiphers = await this.cipherService.getAllDecryptedForUrl(queueMessage.uri);
|
const allCiphers = await this.cipherService.getAllDecryptedForUrl(
|
||||||
|
queueMessage.uri,
|
||||||
|
activeUserId,
|
||||||
|
);
|
||||||
const existingCipher = allCiphers.find(
|
const existingCipher = allCiphers.find(
|
||||||
(c) =>
|
(c) =>
|
||||||
c.login.username != null && c.login.username.toLowerCase() === queueMessage.username,
|
c.login.username != null && c.login.username.toLowerCase() === queueMessage.username,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (existingCipher != null) {
|
if (existingCipher != null) {
|
||||||
await this.updatePassword(existingCipher, queueMessage.password, edit, tab);
|
await this.updatePassword(existingCipher, queueMessage.password, edit, tab, activeUserId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
folderId = (await this.folderExists(folderId)) ? folderId : null;
|
folderId = (await this.folderExists(folderId, activeUserId)) ? folderId : null;
|
||||||
const newCipher = this.convertAddLoginQueueMessageToCipherView(queueMessage, folderId);
|
const newCipher = this.convertAddLoginQueueMessageToCipherView(queueMessage, folderId);
|
||||||
|
|
||||||
if (edit) {
|
if (edit) {
|
||||||
await this.editItem(newCipher, tab);
|
await this.editItem(newCipher, activeUserId, tab);
|
||||||
await BrowserApi.tabSendMessage(tab, { command: "closeNotificationBar" });
|
await BrowserApi.tabSendMessage(tab, { command: "closeNotificationBar" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
|
||||||
|
|
||||||
const cipher = await this.cipherService.encrypt(newCipher, activeUserId);
|
const cipher = await this.cipherService.encrypt(newCipher, activeUserId);
|
||||||
try {
|
try {
|
||||||
await this.cipherService.createWithServer(cipher);
|
await this.cipherService.createWithServer(cipher);
|
||||||
@@ -588,24 +613,25 @@ export default class NotificationBackground {
|
|||||||
* @param newPassword - The new password to update the cipher with
|
* @param newPassword - The new password to update the cipher with
|
||||||
* @param edit - Identifies if the cipher should be edited or simply updated
|
* @param edit - Identifies if the cipher should be edited or simply updated
|
||||||
* @param tab - The tab that the message was sent from
|
* @param tab - The tab that the message was sent from
|
||||||
|
* @param userId - The active account user ID
|
||||||
*/
|
*/
|
||||||
private async updatePassword(
|
private async updatePassword(
|
||||||
cipherView: CipherView,
|
cipherView: CipherView,
|
||||||
newPassword: string,
|
newPassword: string,
|
||||||
edit: boolean,
|
edit: boolean,
|
||||||
tab: chrome.tabs.Tab,
|
tab: chrome.tabs.Tab,
|
||||||
|
userId: UserId,
|
||||||
) {
|
) {
|
||||||
cipherView.login.password = newPassword;
|
cipherView.login.password = newPassword;
|
||||||
|
|
||||||
if (edit) {
|
if (edit) {
|
||||||
await this.editItem(cipherView, tab);
|
await this.editItem(cipherView, userId, tab);
|
||||||
await BrowserApi.tabSendMessage(tab, { command: "closeNotificationBar" });
|
await BrowserApi.tabSendMessage(tab, { command: "closeNotificationBar" });
|
||||||
await BrowserApi.tabSendMessage(tab, { command: "editedCipher" });
|
await BrowserApi.tabSendMessage(tab, { command: "editedCipher" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
const cipher = await this.cipherService.encrypt(cipherView, userId);
|
||||||
const cipher = await this.cipherService.encrypt(cipherView, activeUserId);
|
|
||||||
try {
|
try {
|
||||||
// We've only updated the password, no need to broadcast editedCipher message
|
// We've only updated the password, no need to broadcast editedCipher message
|
||||||
await this.cipherService.updateWithServer(cipher);
|
await this.cipherService.updateWithServer(cipher);
|
||||||
@@ -622,33 +648,34 @@ export default class NotificationBackground {
|
|||||||
* and opens the add/edit vault item popout.
|
* and opens the add/edit vault item popout.
|
||||||
*
|
*
|
||||||
* @param cipherView - The cipher to edit
|
* @param cipherView - The cipher to edit
|
||||||
|
* @param userId - The active account user ID
|
||||||
* @param senderTab - The tab that the message was sent from
|
* @param senderTab - The tab that the message was sent from
|
||||||
*/
|
*/
|
||||||
private async editItem(cipherView: CipherView, senderTab: chrome.tabs.Tab) {
|
private async editItem(cipherView: CipherView, userId: UserId, senderTab: chrome.tabs.Tab) {
|
||||||
await this.cipherService.setAddEditCipherInfo({
|
await this.cipherService.setAddEditCipherInfo(
|
||||||
cipher: cipherView,
|
{
|
||||||
collectionIds: cipherView.collectionIds,
|
cipher: cipherView,
|
||||||
});
|
collectionIds: cipherView.collectionIds,
|
||||||
|
},
|
||||||
|
userId,
|
||||||
|
);
|
||||||
|
|
||||||
await this.openAddEditVaultItemPopout(senderTab, { cipherId: cipherView.id });
|
await this.openAddEditVaultItemPopout(senderTab, { cipherId: cipherView.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async folderExists(folderId: string) {
|
private async folderExists(folderId: string, userId: UserId) {
|
||||||
if (Utils.isNullOrWhitespace(folderId) || folderId === "null") {
|
if (Utils.isNullOrWhitespace(folderId) || folderId === "null") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
const folders = await firstValueFrom(this.folderService.folderViews$(userId));
|
||||||
const folders = await firstValueFrom(this.folderService.folderViews$(activeUserId));
|
|
||||||
return folders.some((x) => x.id === folderId);
|
return folders.some((x) => x.id === folderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getDecryptedCipherById(cipherId: string) {
|
private async getDecryptedCipherById(cipherId: string, userId: UserId) {
|
||||||
const cipher = await this.cipherService.get(cipherId);
|
const cipher = await this.cipherService.get(cipherId, userId);
|
||||||
if (cipher != null && cipher.type === CipherType.Login) {
|
if (cipher != null && cipher.type === CipherType.Login) {
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
|
||||||
|
|
||||||
return await cipher.decrypt(
|
return await cipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -685,7 +712,9 @@ export default class NotificationBackground {
|
|||||||
* Returns the first value found from the folder service's folderViews$ observable.
|
* Returns the first value found from the folder service's folderViews$ observable.
|
||||||
*/
|
*/
|
||||||
private async getFolderData() {
|
private async getFolderData() {
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||||
|
);
|
||||||
return await firstValueFrom(this.folderService.folderViews$(activeUserId));
|
return await firstValueFrom(this.folderService.folderViews$(activeUserId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ describe("OverlayBackground", () => {
|
|||||||
inlineMenuFieldQualificationService,
|
inlineMenuFieldQualificationService,
|
||||||
themeStateService,
|
themeStateService,
|
||||||
totpService,
|
totpService,
|
||||||
|
accountService,
|
||||||
generatedPasswordCallbackMock,
|
generatedPasswordCallbackMock,
|
||||||
addPasswordCallbackMock,
|
addPasswordCallbackMock,
|
||||||
);
|
);
|
||||||
@@ -849,7 +850,7 @@ describe("OverlayBackground", () => {
|
|||||||
await flushPromises();
|
await flushPromises();
|
||||||
|
|
||||||
expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled();
|
expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled();
|
||||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, [
|
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, mockUserId, [
|
||||||
CipherType.Card,
|
CipherType.Card,
|
||||||
CipherType.Identity,
|
CipherType.Identity,
|
||||||
]);
|
]);
|
||||||
@@ -872,7 +873,7 @@ describe("OverlayBackground", () => {
|
|||||||
await flushPromises();
|
await flushPromises();
|
||||||
|
|
||||||
expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled();
|
expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled();
|
||||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url);
|
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, mockUserId);
|
||||||
expect(cipherService.sortCiphersByLastUsedThenName).toHaveBeenCalled();
|
expect(cipherService.sortCiphersByLastUsedThenName).toHaveBeenCalled();
|
||||||
expect(overlayBackground["inlineMenuCiphers"]).toStrictEqual(
|
expect(overlayBackground["inlineMenuCiphers"]).toStrictEqual(
|
||||||
new Map([
|
new Map([
|
||||||
@@ -891,7 +892,7 @@ describe("OverlayBackground", () => {
|
|||||||
await flushPromises();
|
await flushPromises();
|
||||||
|
|
||||||
expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled();
|
expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled();
|
||||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, [
|
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, mockUserId, [
|
||||||
CipherType.Card,
|
CipherType.Card,
|
||||||
CipherType.Identity,
|
CipherType.Identity,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ import {
|
|||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
import { parse } from "tldts";
|
import { parse } from "tldts";
|
||||||
|
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { getOptionalUserId, getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import {
|
import {
|
||||||
AutofillOverlayVisibility,
|
AutofillOverlayVisibility,
|
||||||
SHOW_AUTOFILL_BUTTON,
|
SHOW_AUTOFILL_BUTTON,
|
||||||
@@ -34,6 +36,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
|||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||||
@@ -225,6 +228,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||||||
private inlineMenuFieldQualificationService: InlineMenuFieldQualificationService,
|
private inlineMenuFieldQualificationService: InlineMenuFieldQualificationService,
|
||||||
private themeStateService: ThemeStateService,
|
private themeStateService: ThemeStateService,
|
||||||
private totpService: TotpService,
|
private totpService: TotpService,
|
||||||
|
private accountService: AccountService,
|
||||||
private generatePasswordCallback: () => Promise<string>,
|
private generatePasswordCallback: () => Promise<string>,
|
||||||
private addPasswordCallback: (password: string) => Promise<void>,
|
private addPasswordCallback: (password: string) => Promise<void>,
|
||||||
) {
|
) {
|
||||||
@@ -405,13 +409,20 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||||||
currentTab: chrome.tabs.Tab,
|
currentTab: chrome.tabs.Tab,
|
||||||
updateAllCipherTypes: boolean,
|
updateAllCipherTypes: boolean,
|
||||||
): Promise<CipherView[]> {
|
): Promise<CipherView[]> {
|
||||||
if (updateAllCipherTypes || !this.cardAndIdentityCiphers) {
|
const activeUserId = await firstValueFrom(
|
||||||
return this.getAllCipherTypeViews(currentTab);
|
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||||
|
);
|
||||||
|
if (!activeUserId) {
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const cipherViews = (await this.cipherService.getAllDecryptedForUrl(currentTab.url || "")).sort(
|
if (updateAllCipherTypes || !this.cardAndIdentityCiphers) {
|
||||||
(a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b),
|
return this.getAllCipherTypeViews(currentTab, activeUserId);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
const cipherViews = (
|
||||||
|
await this.cipherService.getAllDecryptedForUrl(currentTab.url || "", activeUserId)
|
||||||
|
).sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b));
|
||||||
|
|
||||||
return this.cardAndIdentityCiphers
|
return this.cardAndIdentityCiphers
|
||||||
? cipherViews.concat(...this.cardAndIdentityCiphers)
|
? cipherViews.concat(...this.cardAndIdentityCiphers)
|
||||||
@@ -422,15 +433,19 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||||||
* Queries all cipher types from the user's vault returns them sorted by last used.
|
* Queries all cipher types from the user's vault returns them sorted by last used.
|
||||||
*
|
*
|
||||||
* @param currentTab - The current tab
|
* @param currentTab - The current tab
|
||||||
|
* @param userId - The active user id
|
||||||
*/
|
*/
|
||||||
private async getAllCipherTypeViews(currentTab: chrome.tabs.Tab): Promise<CipherView[]> {
|
private async getAllCipherTypeViews(
|
||||||
|
currentTab: chrome.tabs.Tab,
|
||||||
|
userId: UserId,
|
||||||
|
): Promise<CipherView[]> {
|
||||||
if (!this.cardAndIdentityCiphers) {
|
if (!this.cardAndIdentityCiphers) {
|
||||||
this.cardAndIdentityCiphers = new Set([]);
|
this.cardAndIdentityCiphers = new Set([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cardAndIdentityCiphers.clear();
|
this.cardAndIdentityCiphers.clear();
|
||||||
const cipherViews = (
|
const cipherViews = (
|
||||||
await this.cipherService.getAllDecryptedForUrl(currentTab.url || "", [
|
await this.cipherService.getAllDecryptedForUrl(currentTab.url || "", userId, [
|
||||||
CipherType.Card,
|
CipherType.Card,
|
||||||
CipherType.Identity,
|
CipherType.Identity,
|
||||||
])
|
])
|
||||||
@@ -2399,10 +2414,14 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
this.closeInlineMenu(sender);
|
this.closeInlineMenu(sender);
|
||||||
await this.cipherService.setAddEditCipherInfo({
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
cipher: cipherView,
|
await this.cipherService.setAddEditCipherInfo(
|
||||||
collectionIds: cipherView.collectionIds,
|
{
|
||||||
});
|
cipher: cipherView,
|
||||||
|
collectionIds: cipherView.collectionIds,
|
||||||
|
},
|
||||||
|
activeUserId,
|
||||||
|
);
|
||||||
|
|
||||||
await this.openAddEditVaultItemPopout(sender.tab, {
|
await this.openAddEditVaultItemPopout(sender.tab, {
|
||||||
cipherId: cipherView.id,
|
cipherId: cipherView.id,
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
|
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
@@ -14,6 +18,7 @@ export default class WebRequestBackground {
|
|||||||
platformUtilsService: PlatformUtilsService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
|
private accountService: AccountService,
|
||||||
private readonly webRequest: typeof chrome.webRequest,
|
private readonly webRequest: typeof chrome.webRequest,
|
||||||
) {
|
) {
|
||||||
this.isFirefox = platformUtilsService.isFirefox();
|
this.isFirefox = platformUtilsService.isFirefox();
|
||||||
@@ -55,7 +60,16 @@ export default class WebRequestBackground {
|
|||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
private async resolveAuthCredentials(domain: string, success: Function, error: Function) {
|
private async resolveAuthCredentials(domain: string, success: Function, error: Function) {
|
||||||
if ((await this.authService.getAuthStatus()) < AuthenticationStatus.Unlocked) {
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||||
|
);
|
||||||
|
if (activeUserId == null) {
|
||||||
|
error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authStatus = await firstValueFrom(this.authService.authStatusFor$(activeUserId));
|
||||||
|
if (authStatus < AuthenticationStatus.Unlocked) {
|
||||||
error();
|
error();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -63,6 +77,7 @@ export default class WebRequestBackground {
|
|||||||
try {
|
try {
|
||||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(
|
const ciphers = await this.cipherService.getAllDecryptedForUrl(
|
||||||
domain,
|
domain,
|
||||||
|
activeUserId,
|
||||||
null,
|
null,
|
||||||
UriMatchStrategy.Host,
|
UriMatchStrategy.Host,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { mock, MockProxy } from "jest-mock-extended";
|
|||||||
|
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||||
@@ -14,6 +16,9 @@ describe("CipherContextMenuHandler", () => {
|
|||||||
let authService: MockProxy<AuthService>;
|
let authService: MockProxy<AuthService>;
|
||||||
let cipherService: MockProxy<CipherService>;
|
let cipherService: MockProxy<CipherService>;
|
||||||
|
|
||||||
|
const mockUserId = "UserId" as UserId;
|
||||||
|
const accountService = mockAccountServiceWith(mockUserId);
|
||||||
|
|
||||||
let sut: CipherContextMenuHandler;
|
let sut: CipherContextMenuHandler;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -24,7 +29,12 @@ describe("CipherContextMenuHandler", () => {
|
|||||||
|
|
||||||
jest.spyOn(MainContextMenuHandler, "removeAll").mockResolvedValue();
|
jest.spyOn(MainContextMenuHandler, "removeAll").mockResolvedValue();
|
||||||
|
|
||||||
sut = new CipherContextMenuHandler(mainContextMenuHandler, authService, cipherService);
|
sut = new CipherContextMenuHandler(
|
||||||
|
mainContextMenuHandler,
|
||||||
|
authService,
|
||||||
|
cipherService,
|
||||||
|
accountService,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => jest.resetAllMocks());
|
afterEach(() => jest.resetAllMocks());
|
||||||
@@ -119,10 +129,11 @@ describe("CipherContextMenuHandler", () => {
|
|||||||
|
|
||||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1);
|
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com", [
|
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(
|
||||||
CipherType.Card,
|
"https://test.com",
|
||||||
CipherType.Identity,
|
mockUserId,
|
||||||
]);
|
[CipherType.Card, CipherType.Identity],
|
||||||
|
);
|
||||||
|
|
||||||
expect(mainContextMenuHandler.loadOptions).toHaveBeenCalledTimes(3);
|
expect(mainContextMenuHandler.loadOptions).toHaveBeenCalledTimes(3);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
@@ -14,6 +18,7 @@ export class CipherContextMenuHandler {
|
|||||||
private mainContextMenuHandler: MainContextMenuHandler,
|
private mainContextMenuHandler: MainContextMenuHandler,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
|
private accountService: AccountService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async update(url: string) {
|
async update(url: string) {
|
||||||
@@ -35,7 +40,14 @@ export class CipherContextMenuHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(url, [
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||||
|
);
|
||||||
|
if (activeUserId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciphers = await this.cipherService.getAllDecryptedForUrl(url, activeUserId, [
|
||||||
CipherType.Card,
|
CipherType.Card,
|
||||||
CipherType.Identity,
|
CipherType.Identity,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ describe("ContextMenuClickedHandler", () => {
|
|||||||
return cipherView;
|
return cipherView;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockUserId = "UserId" as UserId;
|
||||||
|
|
||||||
let copyToClipboard: CopyToClipboardAction;
|
let copyToClipboard: CopyToClipboardAction;
|
||||||
let generatePasswordToClipboard: GeneratePasswordToClipboardAction;
|
let generatePasswordToClipboard: GeneratePasswordToClipboardAction;
|
||||||
let autofill: AutofillAction;
|
let autofill: AutofillAction;
|
||||||
@@ -79,7 +81,7 @@ describe("ContextMenuClickedHandler", () => {
|
|||||||
autofill = jest.fn<Promise<void>, [tab: chrome.tabs.Tab, cipher: CipherView]>();
|
autofill = jest.fn<Promise<void>, [tab: chrome.tabs.Tab, cipher: CipherView]>();
|
||||||
authService = mock();
|
authService = mock();
|
||||||
cipherService = mock();
|
cipherService = mock();
|
||||||
accountService = mockAccountServiceWith("userId" as UserId);
|
accountService = mockAccountServiceWith(mockUserId as UserId);
|
||||||
totpService = mock();
|
totpService = mock();
|
||||||
eventCollectionService = mock();
|
eventCollectionService = mock();
|
||||||
|
|
||||||
@@ -191,7 +193,11 @@ describe("ContextMenuClickedHandler", () => {
|
|||||||
|
|
||||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1);
|
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com", []);
|
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(
|
||||||
|
"https://test.com",
|
||||||
|
mockUserId,
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
expect(copyToClipboard).toHaveBeenCalledTimes(1);
|
expect(copyToClipboard).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
@@ -215,7 +221,11 @@ describe("ContextMenuClickedHandler", () => {
|
|||||||
|
|
||||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1);
|
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com", []);
|
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(
|
||||||
|
"https://test.com",
|
||||||
|
mockUserId,
|
||||||
|
[],
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import {
|
import {
|
||||||
AUTOFILL_CARD_ID,
|
AUTOFILL_CARD_ID,
|
||||||
AUTOFILL_ID,
|
AUTOFILL_ID,
|
||||||
@@ -105,6 +106,13 @@ export class ContextMenuClickedHandler {
|
|||||||
menuItemId as string,
|
menuItemId as string,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||||
|
);
|
||||||
|
if (activeUserId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isCreateCipherAction) {
|
if (isCreateCipherAction) {
|
||||||
// pass; defer to logic below
|
// pass; defer to logic below
|
||||||
} else if (menuItemId === NOOP_COMMAND_SUFFIX) {
|
} else if (menuItemId === NOOP_COMMAND_SUFFIX) {
|
||||||
@@ -120,12 +128,13 @@ export class ContextMenuClickedHandler {
|
|||||||
// in scenarios like unlock on autofill
|
// in scenarios like unlock on autofill
|
||||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(
|
const ciphers = await this.cipherService.getAllDecryptedForUrl(
|
||||||
tab.url,
|
tab.url,
|
||||||
|
activeUserId,
|
||||||
additionalCiphersToGet,
|
additionalCiphersToGet,
|
||||||
);
|
);
|
||||||
|
|
||||||
cipher = ciphers[0];
|
cipher = ciphers[0];
|
||||||
} else {
|
} else {
|
||||||
const ciphers = await this.cipherService.getAllDecrypted();
|
const ciphers = await this.cipherService.getAllDecrypted(activeUserId);
|
||||||
cipher = ciphers.find(({ id }) => id === menuItemId);
|
cipher = ciphers.find(({ id }) => id === menuItemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,9 +142,6 @@ export class ContextMenuClickedHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeUserId = await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
await this.accountService.setAccountActivity(activeUserId, new Date());
|
await this.accountService.setAccountActivity(activeUserId, new Date());
|
||||||
switch (info.parentMenuItemId) {
|
switch (info.parentMenuItemId) {
|
||||||
case AUTOFILL_ID:
|
case AUTOFILL_ID:
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ describe("OverlayBackground", () => {
|
|||||||
i18nService,
|
i18nService,
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
themeStateService,
|
themeStateService,
|
||||||
|
accountService,
|
||||||
);
|
);
|
||||||
|
|
||||||
jest
|
jest
|
||||||
@@ -205,7 +206,7 @@ describe("OverlayBackground", () => {
|
|||||||
await overlayBackground.updateOverlayCiphers();
|
await overlayBackground.updateOverlayCiphers();
|
||||||
|
|
||||||
expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled();
|
expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled();
|
||||||
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url);
|
expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, mockUserId);
|
||||||
expect(overlayBackground["cipherService"].sortCiphersByLastUsedThenName).toHaveBeenCalled();
|
expect(overlayBackground["cipherService"].sortCiphersByLastUsedThenName).toHaveBeenCalled();
|
||||||
expect(overlayBackground["overlayLoginCiphers"]).toStrictEqual(
|
expect(overlayBackground["overlayLoginCiphers"]).toStrictEqual(
|
||||||
new Map([
|
new Map([
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom, map } from "rxjs";
|
||||||
|
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
import { SHOW_AUTOFILL_BUTTON } from "@bitwarden/common/autofill/constants";
|
import { SHOW_AUTOFILL_BUTTON } from "@bitwarden/common/autofill/constants";
|
||||||
@@ -106,6 +107,7 @@ class LegacyOverlayBackground implements OverlayBackgroundInterface {
|
|||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private themeStateService: ThemeStateService,
|
private themeStateService: ThemeStateService,
|
||||||
|
private accountService: AccountService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,9 +154,13 @@ class LegacyOverlayBackground implements OverlayBackgroundInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.overlayLoginCiphers = new Map();
|
this.overlayLoginCiphers = new Map();
|
||||||
const ciphersViews = (await this.cipherService.getAllDecryptedForUrl(currentTab.url)).sort(
|
|
||||||
(a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b),
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||||
);
|
);
|
||||||
|
const ciphersViews = (
|
||||||
|
await this.cipherService.getAllDecryptedForUrl(currentTab.url, activeUserId)
|
||||||
|
).sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b));
|
||||||
for (let cipherIndex = 0; cipherIndex < ciphersViews.length; cipherIndex++) {
|
for (let cipherIndex = 0; cipherIndex < ciphersViews.length; cipherIndex++) {
|
||||||
this.overlayLoginCiphers.set(`overlay-cipher-${cipherIndex}`, ciphersViews[cipherIndex]);
|
this.overlayLoginCiphers.set(`overlay-cipher-${cipherIndex}`, ciphersViews[cipherIndex]);
|
||||||
}
|
}
|
||||||
@@ -660,10 +666,16 @@ class LegacyOverlayBackground implements OverlayBackgroundInterface {
|
|||||||
cipherView.type = CipherType.Login;
|
cipherView.type = CipherType.Login;
|
||||||
cipherView.login = loginView;
|
cipherView.login = loginView;
|
||||||
|
|
||||||
await this.cipherService.setAddEditCipherInfo({
|
const activeUserId = await firstValueFrom(
|
||||||
cipher: cipherView,
|
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||||
collectionIds: cipherView.collectionIds,
|
);
|
||||||
});
|
await this.cipherService.setAddEditCipherInfo(
|
||||||
|
{
|
||||||
|
cipher: cipherView,
|
||||||
|
collectionIds: cipherView.collectionIds,
|
||||||
|
},
|
||||||
|
activeUserId,
|
||||||
|
);
|
||||||
|
|
||||||
await this.openAddEditVaultItemPopout(sender.tab, { cipherId: cipherView.id });
|
await this.openAddEditVaultItemPopout(sender.tab, { cipherId: cipherView.id });
|
||||||
await BrowserApi.sendMessage("inlineAutofillMenuRefreshAddEditCipher");
|
await BrowserApi.sendMessage("inlineAutofillMenuRefreshAddEditCipher");
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
@@ -186,7 +187,10 @@ export class Fido2Component implements OnInit, OnDestroy {
|
|||||||
this.domainSettingsService.getUrlEquivalentDomains(this.url),
|
this.domainSettingsService.getUrlEquivalentDomains(this.url),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.ciphers = (await this.cipherService.getAllDecrypted()).filter(
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(getUserId),
|
||||||
|
);
|
||||||
|
this.ciphers = (await this.cipherService.getAllDecrypted(activeUserId)).filter(
|
||||||
(cipher) => cipher.type === CipherType.Login && !cipher.isDeleted,
|
(cipher) => cipher.type === CipherType.Login && !cipher.isDeleted,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -211,7 +215,7 @@ export class Fido2Component implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.ciphers = await Promise.all(
|
this.ciphers = await Promise.all(
|
||||||
message.cipherIds.map(async (cipherId) => {
|
message.cipherIds.map(async (cipherId) => {
|
||||||
const cipher = await this.cipherService.get(cipherId);
|
const cipher = await this.cipherService.get(cipherId, activeUserId);
|
||||||
return cipher.decrypt(
|
return cipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||||
);
|
);
|
||||||
@@ -232,7 +236,7 @@ export class Fido2Component implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.ciphers = await Promise.all(
|
this.ciphers = await Promise.all(
|
||||||
message.existingCipherIds.map(async (cipherId) => {
|
message.existingCipherIds.map(async (cipherId) => {
|
||||||
const cipher = await this.cipherService.get(cipherId);
|
const cipher = await this.cipherService.get(cipherId, activeUserId);
|
||||||
return cipher.decrypt(
|
return cipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -769,7 +769,10 @@ describe("AutofillService", () => {
|
|||||||
);
|
);
|
||||||
expect(autofillService["generateLoginFillScript"]).toHaveBeenCalled();
|
expect(autofillService["generateLoginFillScript"]).toHaveBeenCalled();
|
||||||
expect(logService.info).not.toHaveBeenCalled();
|
expect(logService.info).not.toHaveBeenCalled();
|
||||||
expect(cipherService.updateLastUsedDate).toHaveBeenCalledWith(autofillOptions.cipher.id);
|
expect(cipherService.updateLastUsedDate).toHaveBeenCalledWith(
|
||||||
|
autofillOptions.cipher.id,
|
||||||
|
mockUserId,
|
||||||
|
);
|
||||||
expect(chrome.tabs.sendMessage).toHaveBeenCalledWith(
|
expect(chrome.tabs.sendMessage).toHaveBeenCalledWith(
|
||||||
autofillOptions.pageDetails[0].tab.id,
|
autofillOptions.pageDetails[0].tab.id,
|
||||||
{
|
{
|
||||||
@@ -1030,8 +1033,8 @@ describe("AutofillService", () => {
|
|||||||
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, false);
|
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, false);
|
||||||
|
|
||||||
expect(cipherService.getNextCipherForUrl).not.toHaveBeenCalled();
|
expect(cipherService.getNextCipherForUrl).not.toHaveBeenCalled();
|
||||||
expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, true);
|
expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true);
|
||||||
expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, true);
|
expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true);
|
||||||
expect(autofillService.doAutoFill).not.toHaveBeenCalled();
|
expect(autofillService.doAutoFill).not.toHaveBeenCalled();
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
});
|
});
|
||||||
@@ -1044,7 +1047,7 @@ describe("AutofillService", () => {
|
|||||||
|
|
||||||
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true);
|
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true);
|
||||||
|
|
||||||
expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url);
|
expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId);
|
||||||
expect(cipherService.getLastLaunchedForUrl).not.toHaveBeenCalled();
|
expect(cipherService.getLastLaunchedForUrl).not.toHaveBeenCalled();
|
||||||
expect(cipherService.getLastUsedForUrl).not.toHaveBeenCalled();
|
expect(cipherService.getLastUsedForUrl).not.toHaveBeenCalled();
|
||||||
expect(autofillService.doAutoFill).not.toHaveBeenCalled();
|
expect(autofillService.doAutoFill).not.toHaveBeenCalled();
|
||||||
@@ -1074,7 +1077,7 @@ describe("AutofillService", () => {
|
|||||||
|
|
||||||
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand);
|
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand);
|
||||||
|
|
||||||
expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, true);
|
expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true);
|
||||||
expect(cipherService.getLastUsedForUrl).not.toHaveBeenCalled();
|
expect(cipherService.getLastUsedForUrl).not.toHaveBeenCalled();
|
||||||
expect(cipherService.updateLastUsedIndexForUrl).not.toHaveBeenCalled();
|
expect(cipherService.updateLastUsedIndexForUrl).not.toHaveBeenCalled();
|
||||||
expect(autofillService.doAutoFill).toHaveBeenCalledWith({
|
expect(autofillService.doAutoFill).toHaveBeenCalledWith({
|
||||||
@@ -1104,8 +1107,8 @@ describe("AutofillService", () => {
|
|||||||
|
|
||||||
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand);
|
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand);
|
||||||
|
|
||||||
expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, true);
|
expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true);
|
||||||
expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, true);
|
expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true);
|
||||||
expect(cipherService.updateLastUsedIndexForUrl).not.toHaveBeenCalled();
|
expect(cipherService.updateLastUsedIndexForUrl).not.toHaveBeenCalled();
|
||||||
expect(autofillService.doAutoFill).toHaveBeenCalledWith({
|
expect(autofillService.doAutoFill).toHaveBeenCalledWith({
|
||||||
tab: tab,
|
tab: tab,
|
||||||
@@ -1132,7 +1135,7 @@ describe("AutofillService", () => {
|
|||||||
|
|
||||||
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand);
|
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand);
|
||||||
|
|
||||||
expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url);
|
expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId);
|
||||||
expect(cipherService.updateLastUsedIndexForUrl).toHaveBeenCalledWith(tab.url);
|
expect(cipherService.updateLastUsedIndexForUrl).toHaveBeenCalledWith(tab.url);
|
||||||
expect(autofillService.doAutoFill).toHaveBeenCalledWith({
|
expect(autofillService.doAutoFill).toHaveBeenCalledWith({
|
||||||
tab: tab,
|
tab: tab,
|
||||||
@@ -1163,7 +1166,7 @@ describe("AutofillService", () => {
|
|||||||
|
|
||||||
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true);
|
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true);
|
||||||
|
|
||||||
expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url);
|
expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId);
|
||||||
expect(userVerificationService.hasMasterPasswordAndMasterKeyHash).toHaveBeenCalled();
|
expect(userVerificationService.hasMasterPasswordAndMasterKeyHash).toHaveBeenCalled();
|
||||||
expect(autofillService["openVaultItemPasswordRepromptPopout"]).toHaveBeenCalledWith(tab, {
|
expect(autofillService["openVaultItemPasswordRepromptPopout"]).toHaveBeenCalledWith(tab, {
|
||||||
cipherId: cipher.id,
|
cipherId: cipher.id,
|
||||||
@@ -1189,7 +1192,7 @@ describe("AutofillService", () => {
|
|||||||
|
|
||||||
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true);
|
const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true);
|
||||||
|
|
||||||
expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url);
|
expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId);
|
||||||
expect(autofillService["openVaultItemPasswordRepromptPopout"]).not.toHaveBeenCalled();
|
expect(autofillService["openVaultItemPasswordRepromptPopout"]).not.toHaveBeenCalled();
|
||||||
expect(autofillService.doAutoFill).not.toHaveBeenCalled();
|
expect(autofillService.doAutoFill).not.toHaveBeenCalled();
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions
|
|||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import {
|
import {
|
||||||
AutofillOverlayVisibility,
|
AutofillOverlayVisibility,
|
||||||
CardExpiryDateDelimiters,
|
CardExpiryDateDelimiters,
|
||||||
@@ -464,7 +465,7 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
|
|
||||||
didAutofill = true;
|
didAutofill = true;
|
||||||
if (!options.skipLastUsed) {
|
if (!options.skipLastUsed) {
|
||||||
await this.cipherService.updateLastUsedDate(options.cipher.id);
|
await this.cipherService.updateLastUsedDate(options.cipher.id, activeAccount.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||||
@@ -527,17 +528,29 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
autoSubmitLogin = false,
|
autoSubmitLogin = false,
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
let cipher: CipherView;
|
let cipher: CipherView;
|
||||||
|
|
||||||
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||||
|
);
|
||||||
|
if (activeUserId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (fromCommand) {
|
if (fromCommand) {
|
||||||
cipher = await this.cipherService.getNextCipherForUrl(tab.url);
|
cipher = await this.cipherService.getNextCipherForUrl(tab.url, activeUserId);
|
||||||
} else {
|
} else {
|
||||||
const lastLaunchedCipher = await this.cipherService.getLastLaunchedForUrl(tab.url, true);
|
const lastLaunchedCipher = await this.cipherService.getLastLaunchedForUrl(
|
||||||
|
tab.url,
|
||||||
|
activeUserId,
|
||||||
|
true,
|
||||||
|
);
|
||||||
if (
|
if (
|
||||||
lastLaunchedCipher &&
|
lastLaunchedCipher &&
|
||||||
Date.now().valueOf() - lastLaunchedCipher.localData?.lastLaunched?.valueOf() < 30000
|
Date.now().valueOf() - lastLaunchedCipher.localData?.lastLaunched?.valueOf() < 30000
|
||||||
) {
|
) {
|
||||||
cipher = lastLaunchedCipher;
|
cipher = lastLaunchedCipher;
|
||||||
} else {
|
} else {
|
||||||
cipher = await this.cipherService.getLastUsedForUrl(tab.url, true);
|
cipher = await this.cipherService.getLastUsedForUrl(tab.url, activeUserId, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -626,12 +639,19 @@ export default class AutofillService implements AutofillServiceInterface {
|
|||||||
let cipher: CipherView;
|
let cipher: CipherView;
|
||||||
let cacheKey = "";
|
let cacheKey = "";
|
||||||
|
|
||||||
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||||
|
);
|
||||||
|
if (activeUserId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (cipherType === CipherType.Card) {
|
if (cipherType === CipherType.Card) {
|
||||||
cacheKey = "cardCiphers";
|
cacheKey = "cardCiphers";
|
||||||
cipher = await this.cipherService.getNextCardCipher();
|
cipher = await this.cipherService.getNextCardCipher(activeUserId);
|
||||||
} else {
|
} else {
|
||||||
cacheKey = "identityCiphers";
|
cacheKey = "identityCiphers";
|
||||||
cipher = await this.cipherService.getNextIdentityCipher();
|
cipher = await this.cipherService.getNextIdentityCipher(activeUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cipher || !cacheKey || (cipher.reprompt === CipherRepromptType.Password && !fromCommand)) {
|
if (!cipher || !cacheKey || (cipher.reprompt === CipherRepromptType.Password && !fromCommand)) {
|
||||||
|
|||||||
@@ -1258,6 +1258,7 @@ export default class MainBackground {
|
|||||||
this.mainContextMenuHandler,
|
this.mainContextMenuHandler,
|
||||||
this.authService,
|
this.authService,
|
||||||
this.cipherService,
|
this.cipherService,
|
||||||
|
this.accountService,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (chrome.webRequest != null && chrome.webRequest.onAuthRequired != null) {
|
if (chrome.webRequest != null && chrome.webRequest.onAuthRequired != null) {
|
||||||
@@ -1265,6 +1266,7 @@ export default class MainBackground {
|
|||||||
this.platformUtilsService,
|
this.platformUtilsService,
|
||||||
this.cipherService,
|
this.cipherService,
|
||||||
this.authService,
|
this.authService,
|
||||||
|
this.accountService,
|
||||||
chrome.webRequest,
|
chrome.webRequest,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1636,6 +1638,7 @@ export default class MainBackground {
|
|||||||
this.i18nService,
|
this.i18nService,
|
||||||
this.platformUtilsService,
|
this.platformUtilsService,
|
||||||
this.themeStateService,
|
this.themeStateService,
|
||||||
|
this.accountService,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.overlayBackground = new OverlayBackground(
|
this.overlayBackground = new OverlayBackground(
|
||||||
@@ -1653,6 +1656,7 @@ export default class MainBackground {
|
|||||||
this.inlineMenuFieldQualificationService,
|
this.inlineMenuFieldQualificationService,
|
||||||
this.themeStateService,
|
this.themeStateService,
|
||||||
this.totpService,
|
this.totpService,
|
||||||
|
this.accountService,
|
||||||
() => this.generatePassword(),
|
() => this.generatePassword(),
|
||||||
(password) => this.addPasswordToHistory(password),
|
(password) => this.addPasswordToHistory(password),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
|
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
@@ -22,6 +24,7 @@ export class UpdateBadge {
|
|||||||
private authService: AuthService;
|
private authService: AuthService;
|
||||||
private badgeSettingsService: BadgeSettingsServiceAbstraction;
|
private badgeSettingsService: BadgeSettingsServiceAbstraction;
|
||||||
private cipherService: CipherService;
|
private cipherService: CipherService;
|
||||||
|
private accountService: AccountService;
|
||||||
private badgeAction: typeof chrome.action | typeof chrome.browserAction;
|
private badgeAction: typeof chrome.action | typeof chrome.browserAction;
|
||||||
private sidebarAction: OperaSidebarAction | FirefoxSidebarAction;
|
private sidebarAction: OperaSidebarAction | FirefoxSidebarAction;
|
||||||
private win: Window & typeof globalThis;
|
private win: Window & typeof globalThis;
|
||||||
@@ -34,6 +37,7 @@ export class UpdateBadge {
|
|||||||
this.badgeSettingsService = services.badgeSettingsService;
|
this.badgeSettingsService = services.badgeSettingsService;
|
||||||
this.authService = services.authService;
|
this.authService = services.authService;
|
||||||
this.cipherService = services.cipherService;
|
this.cipherService = services.cipherService;
|
||||||
|
this.accountService = services.accountService;
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(opts?: { tabId?: number; windowId?: number }): Promise<void> {
|
async run(opts?: { tabId?: number; windowId?: number }): Promise<void> {
|
||||||
@@ -87,7 +91,14 @@ export class UpdateBadge {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(opts?.tab?.url);
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||||
|
);
|
||||||
|
if (!activeUserId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciphers = await this.cipherService.getAllDecryptedForUrl(opts?.tab?.url, activeUserId);
|
||||||
let countText = ciphers.length == 0 ? "" : ciphers.length.toString();
|
let countText = ciphers.length == 0 ? "" : ciphers.length.toString();
|
||||||
if (ciphers.length > 9) {
|
if (ciphers.length > 9) {
|
||||||
countText = "9+";
|
countText = "9+";
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
await this.clearComponentStates();
|
await this.clearComponentStates();
|
||||||
}
|
}
|
||||||
if (url.startsWith("/tabs/")) {
|
if (url.startsWith("/tabs/")) {
|
||||||
await this.cipherService.setAddEditCipherInfo(null);
|
await this.cipherService.setAddEditCipherInfo(null, this.activeUserId);
|
||||||
}
|
}
|
||||||
(window as any).previousPopupUrl = url;
|
(window as any).previousPopupUrl = url;
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing";
|
import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
import { BehaviorSubject, Observable } from "rxjs";
|
import { BehaviorSubject } from "rxjs";
|
||||||
|
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { EventType } from "@bitwarden/common/enums";
|
import { EventType } from "@bitwarden/common/enums";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
import { mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||||
@@ -58,9 +61,9 @@ describe("AddEditV2Component", () => {
|
|||||||
collect.mockClear();
|
collect.mockClear();
|
||||||
|
|
||||||
addEditCipherInfo$ = new BehaviorSubject<AddEditCipherInfo | null>(null);
|
addEditCipherInfo$ = new BehaviorSubject<AddEditCipherInfo | null>(null);
|
||||||
cipherServiceMock = mock<CipherService>();
|
cipherServiceMock = mock<CipherService>({
|
||||||
cipherServiceMock.addEditCipherInfo$ =
|
addEditCipherInfo$: jest.fn().mockReturnValue(addEditCipherInfo$),
|
||||||
addEditCipherInfo$.asObservable() as Observable<AddEditCipherInfo>;
|
});
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [AddEditV2Component],
|
imports: [AddEditV2Component],
|
||||||
@@ -81,6 +84,7 @@ describe("AddEditV2Component", () => {
|
|||||||
canDeleteCipher$: jest.fn().mockReturnValue(true),
|
canDeleteCipher$: jest.fn().mockReturnValue(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{ provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
.overrideProvider(CipherFormConfigService, {
|
.overrideProvider(CipherFormConfigService, {
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import { firstValueFrom, map, Observable, switchMap } from "rxjs";
|
|||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { EventType } from "@bitwarden/common/enums";
|
import { EventType } from "@bitwarden/common/enums";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.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 { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
@@ -180,6 +182,7 @@ export class AddEditV2Component implements OnInit {
|
|||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
protected cipherAuthorizationService: CipherAuthorizationService,
|
protected cipherAuthorizationService: CipherAuthorizationService,
|
||||||
|
private accountService: AccountService,
|
||||||
) {
|
) {
|
||||||
this.subscribeToParams();
|
this.subscribeToParams();
|
||||||
}
|
}
|
||||||
@@ -281,9 +284,15 @@ export class AddEditV2Component implements OnInit {
|
|||||||
|
|
||||||
config.initialValues = this.setInitialValuesFromParams(params);
|
config.initialValues = this.setInitialValuesFromParams(params);
|
||||||
|
|
||||||
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(getUserId),
|
||||||
|
);
|
||||||
|
|
||||||
// The browser notification bar and overlay use addEditCipherInfo$ to pass modified cipher details to the form
|
// The browser notification bar and overlay use addEditCipherInfo$ to pass modified cipher details to the form
|
||||||
// Attempt to fetch them here and overwrite the initialValues if present
|
// Attempt to fetch them here and overwrite the initialValues if present
|
||||||
const cachedCipherInfo = await firstValueFrom(this.cipherService.addEditCipherInfo$);
|
const cachedCipherInfo = await firstValueFrom(
|
||||||
|
this.cipherService.addEditCipherInfo$(activeUserId),
|
||||||
|
);
|
||||||
|
|
||||||
if (cachedCipherInfo != null) {
|
if (cachedCipherInfo != null) {
|
||||||
// Cached cipher info has priority over queryParams
|
// Cached cipher info has priority over queryParams
|
||||||
@@ -292,7 +301,7 @@ export class AddEditV2Component implements OnInit {
|
|||||||
...mapAddEditCipherInfoToInitialValues(cachedCipherInfo),
|
...mapAddEditCipherInfoToInitialValues(cachedCipherInfo),
|
||||||
};
|
};
|
||||||
// Be sure to clear the "cached" cipher info, so it doesn't get used again
|
// Be sure to clear the "cached" cipher info, so it doesn't get used again
|
||||||
await this.cipherService.setAddEditCipherInfo(null);
|
await this.cipherService.setAddEditCipherInfo(null, activeUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (["edit", "partial-edit"].includes(config.mode) && config.originalCipher?.id) {
|
if (["edit", "partial-edit"].includes(config.mode) && config.originalCipher?.id) {
|
||||||
@@ -371,7 +380,8 @@ export class AddEditV2Component implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.deleteCipher();
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
await this.deleteCipher(activeUserId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logService.error(e);
|
this.logService.error(e);
|
||||||
return false;
|
return false;
|
||||||
@@ -388,10 +398,10 @@ export class AddEditV2Component implements OnInit {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
protected deleteCipher() {
|
protected deleteCipher(userId: UserId) {
|
||||||
return this.config.originalCipher.deletedDate
|
return this.config.originalCipher.deletedDate
|
||||||
? this.cipherService.deleteWithServer(this.config.originalCipher.id)
|
? this.cipherService.deleteWithServer(this.config.originalCipher.id, userId)
|
||||||
: this.cipherService.softDeleteWithServer(this.config.originalCipher.id);
|
: this.cipherService.softDeleteWithServer(this.config.originalCipher.id, userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ import { Component } from "@angular/core";
|
|||||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
import { ReactiveFormsModule } from "@angular/forms";
|
import { ReactiveFormsModule } from "@angular/forms";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { Observable, combineLatest, first, map, switchMap } from "rxjs";
|
import { Observable, combineLatest, filter, first, map, switchMap } from "rxjs";
|
||||||
|
|
||||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||||
|
import { OrgKey, UserKey } from "@bitwarden/common/types/key";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import {
|
import {
|
||||||
@@ -58,16 +59,19 @@ export class AssignCollections {
|
|||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
route: ActivatedRoute,
|
route: ActivatedRoute,
|
||||||
) {
|
) {
|
||||||
const cipher$: Observable<CipherView> = route.queryParams.pipe(
|
const cipher$: Observable<CipherView> = this.accountService.activeAccount$.pipe(
|
||||||
switchMap(({ cipherId }) => this.cipherService.get(cipherId)),
|
map((account) => account?.id),
|
||||||
switchMap((cipherDomain) =>
|
filter((userId) => userId != null),
|
||||||
this.accountService.activeAccount$.pipe(
|
switchMap((userId) =>
|
||||||
map((account) => account?.id),
|
route.queryParams.pipe(
|
||||||
switchMap((userId) =>
|
switchMap(async ({ cipherId }) => {
|
||||||
this.cipherService
|
const cipherDomain = await this.cipherService.get(cipherId, userId);
|
||||||
.getKeyForCipherKeyDecryption(cipherDomain, userId)
|
const key: UserKey | OrgKey = await this.cipherService.getKeyForCipherKeyDecryption(
|
||||||
.then(cipherDomain.decrypt.bind(cipherDomain)),
|
cipherDomain,
|
||||||
),
|
userId,
|
||||||
|
);
|
||||||
|
return cipherDomain.decrypt(key);
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -77,10 +77,10 @@ export class OpenAttachmentsComponent implements OnInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cipherDomain = await this.cipherService.get(this.cipherId);
|
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||||
);
|
);
|
||||||
|
const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId);
|
||||||
const cipher = await cipherDomain.decrypt(
|
const cipher = await cipherDomain.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -59,7 +59,9 @@ describe("VaultHeaderV2Component", () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: CipherService,
|
provide: CipherService,
|
||||||
useValue: mock<CipherService>({ cipherViews$: new BehaviorSubject([]) }),
|
useValue: mock<CipherService>({
|
||||||
|
cipherViews$: jest.fn().mockReturnValue(new BehaviorSubject([])),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
{ provide: VaultSettingsService, useValue: mock<VaultSettingsService>() },
|
{ provide: VaultSettingsService, useValue: mock<VaultSettingsService>() },
|
||||||
{ provide: FolderService, useValue: mock<FolderService>() },
|
{ provide: FolderService, useValue: mock<FolderService>() },
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import { Router } from "@angular/router";
|
|||||||
import { firstValueFrom, Observable, map } from "rxjs";
|
import { firstValueFrom, Observable, map } from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { CipherId } from "@bitwarden/common/types/guid";
|
import { CipherId } from "@bitwarden/common/types/guid";
|
||||||
@@ -265,6 +267,7 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
|
private accountService: AccountService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@@ -311,7 +314,8 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
|
|||||||
this.viewCipherTimeout = null;
|
this.viewCipherTimeout = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.cipherService.updateLastLaunchedDate(cipher.id);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
await this.cipherService.updateLastLaunchedDate(cipher.id, activeUserId);
|
||||||
|
|
||||||
await BrowserApi.createNewTab(cipher.login.launchUri);
|
await BrowserApi.createNewTab(cipher.login.launchUri);
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing";
|
import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { mock } from "jest-mock-extended";
|
import { mock } from "jest-mock-extended";
|
||||||
import { BehaviorSubject, Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
||||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
import { mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
@@ -19,6 +21,7 @@ import { PasswordHistoryV2Component } from "./vault-password-history-v2.componen
|
|||||||
describe("PasswordHistoryV2Component", () => {
|
describe("PasswordHistoryV2Component", () => {
|
||||||
let fixture: ComponentFixture<PasswordHistoryV2Component>;
|
let fixture: ComponentFixture<PasswordHistoryV2Component>;
|
||||||
const params$ = new Subject();
|
const params$ = new Subject();
|
||||||
|
const mockUserId = "acct-1" as UserId;
|
||||||
|
|
||||||
const mockCipherView = {
|
const mockCipherView = {
|
||||||
id: "111-222-333",
|
id: "111-222-333",
|
||||||
@@ -45,9 +48,7 @@ describe("PasswordHistoryV2Component", () => {
|
|||||||
{ provide: CipherService, useValue: mock<CipherService>({ get: getCipher }) },
|
{ provide: CipherService, useValue: mock<CipherService>({ get: getCipher }) },
|
||||||
{
|
{
|
||||||
provide: AccountService,
|
provide: AccountService,
|
||||||
useValue: mock<AccountService>({
|
useValue: mockAccountServiceWith(mockUserId),
|
||||||
activeAccount$: new BehaviorSubject({ id: "acct-1" } as Account),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
{ provide: PopupRouterCacheService, useValue: { back } },
|
{ provide: PopupRouterCacheService, useValue: { back } },
|
||||||
{ provide: ActivatedRoute, useValue: { queryParams: params$ } },
|
{ provide: ActivatedRoute, useValue: { queryParams: params$ } },
|
||||||
@@ -64,7 +65,7 @@ describe("PasswordHistoryV2Component", () => {
|
|||||||
|
|
||||||
tick(100);
|
tick(100);
|
||||||
|
|
||||||
expect(getCipher).toHaveBeenCalledWith(mockCipherView.id);
|
expect(getCipher).toHaveBeenCalledWith(mockCipherView.id, mockUserId);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it("navigates back when a cipherId is not in the params", () => {
|
it("navigates back when a cipherId is not in the params", () => {
|
||||||
|
|||||||
@@ -58,8 +58,6 @@ export class PasswordHistoryV2Component implements OnInit {
|
|||||||
|
|
||||||
/** Load the cipher based on the given Id */
|
/** Load the cipher based on the given Id */
|
||||||
private async loadCipher(cipherId: string) {
|
private async loadCipher(cipherId: string) {
|
||||||
const cipher = await this.cipherService.get(cipherId);
|
|
||||||
|
|
||||||
const activeAccount = await firstValueFrom(
|
const activeAccount = await firstValueFrom(
|
||||||
this.accountService.activeAccount$.pipe(map((a: { id: string | undefined }) => a)),
|
this.accountService.activeAccount$.pipe(map((a: { id: string | undefined }) => a)),
|
||||||
);
|
);
|
||||||
@@ -69,6 +67,8 @@ export class PasswordHistoryV2Component implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const activeUserId = activeAccount.id as UserId;
|
const activeUserId = activeAccount.id as UserId;
|
||||||
|
|
||||||
|
const cipher = await this.cipherService.get(cipherId, activeUserId);
|
||||||
this.cipher = await cipher.decrypt(
|
this.cipher = await cipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,9 +3,20 @@ import { CommonModule } from "@angular/common";
|
|||||||
import { AfterViewInit, Component, DestroyRef, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
import { AfterViewInit, Component, DestroyRef, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
import { RouterLink } from "@angular/router";
|
import { RouterLink } from "@angular/router";
|
||||||
import { combineLatest, filter, map, Observable, shareReplay, switchMap, take } from "rxjs";
|
import {
|
||||||
|
combineLatest,
|
||||||
|
filter,
|
||||||
|
map,
|
||||||
|
firstValueFrom,
|
||||||
|
Observable,
|
||||||
|
shareReplay,
|
||||||
|
switchMap,
|
||||||
|
take,
|
||||||
|
} from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
@@ -96,6 +107,7 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
private vaultPopupItemsService: VaultPopupItemsService,
|
private vaultPopupItemsService: VaultPopupItemsService,
|
||||||
private vaultPopupListFiltersService: VaultPopupListFiltersService,
|
private vaultPopupListFiltersService: VaultPopupListFiltersService,
|
||||||
private vaultScrollPositionService: VaultPopupScrollPositionService,
|
private vaultScrollPositionService: VaultPopupScrollPositionService,
|
||||||
|
private accountService: AccountService,
|
||||||
private destroyRef: DestroyRef,
|
private destroyRef: DestroyRef,
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
@@ -136,7 +148,10 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.cipherService.failedToDecryptCiphers$
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|
||||||
|
this.cipherService
|
||||||
|
.failedToDecryptCiphers$(activeUserId)
|
||||||
.pipe(
|
.pipe(
|
||||||
map((ciphers) => ciphers.filter((c) => !c.isDeleted)),
|
map((ciphers) => ciphers.filter((c) => !c.isDeleted)),
|
||||||
filter((ciphers) => ciphers.length > 0),
|
filter((ciphers) => ciphers.length > 0),
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ describe("ViewV2Component", () => {
|
|||||||
|
|
||||||
flush(); // Resolve all promises
|
flush(); // Resolve all promises
|
||||||
|
|
||||||
expect(mockCipherService.get).toHaveBeenCalledWith("122-333-444");
|
expect(mockCipherService.get).toHaveBeenCalledWith("122-333-444", mockUserId);
|
||||||
expect(component.cipher).toEqual(mockCipher);
|
expect(component.cipher).toEqual(mockCipher);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ import { Component } from "@angular/core";
|
|||||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
import { FormsModule } from "@angular/forms";
|
import { FormsModule } from "@angular/forms";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { firstValueFrom, map, Observable, switchMap } from "rxjs";
|
import { firstValueFrom, Observable, switchMap } from "rxjs";
|
||||||
|
|
||||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import {
|
import {
|
||||||
AUTOFILL_ID,
|
AUTOFILL_ID,
|
||||||
COPY_PASSWORD_ID,
|
COPY_PASSWORD_ID,
|
||||||
@@ -22,6 +23,7 @@ import {
|
|||||||
import { EventType } from "@bitwarden/common/enums";
|
import { EventType } from "@bitwarden/common/enums";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
|
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
|
||||||
import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
|
import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
|
||||||
@@ -87,6 +89,8 @@ type LoadAction =
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ViewV2Component {
|
export class ViewV2Component {
|
||||||
|
private activeUserId: UserId;
|
||||||
|
|
||||||
headerText: string;
|
headerText: string;
|
||||||
cipher: CipherView;
|
cipher: CipherView;
|
||||||
organization$: Observable<Organization>;
|
organization$: Observable<Organization>;
|
||||||
@@ -117,14 +121,20 @@ export class ViewV2Component {
|
|||||||
subscribeToParams(): void {
|
subscribeToParams(): void {
|
||||||
this.route.queryParams
|
this.route.queryParams
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(async (params): Promise<CipherView> => {
|
switchMap(async (params) => {
|
||||||
this.loadAction = params.action;
|
this.loadAction = params.action;
|
||||||
this.senderTabId = params.senderTabId ? parseInt(params.senderTabId, 10) : undefined;
|
this.senderTabId = params.senderTabId ? parseInt(params.senderTabId, 10) : undefined;
|
||||||
return await this.getCipherData(params.cipherId);
|
|
||||||
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(getUserId),
|
||||||
|
);
|
||||||
|
const cipher = await this.getCipherData(params.cipherId, activeUserId);
|
||||||
|
return { activeUserId, cipher };
|
||||||
}),
|
}),
|
||||||
switchMap(async (cipher) => {
|
switchMap(async ({ activeUserId, cipher }) => {
|
||||||
this.cipher = cipher;
|
this.cipher = cipher;
|
||||||
this.headerText = this.setHeader(cipher.type);
|
this.headerText = this.setHeader(cipher.type);
|
||||||
|
this.activeUserId = activeUserId;
|
||||||
|
|
||||||
if (this.loadAction) {
|
if (this.loadAction) {
|
||||||
await this._handleLoadAction(this.loadAction, this.senderTabId);
|
await this._handleLoadAction(this.loadAction, this.senderTabId);
|
||||||
@@ -159,13 +169,10 @@ export class ViewV2Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCipherData(id: string) {
|
async getCipherData(id: string, userId: UserId) {
|
||||||
const cipher = await this.cipherService.get(id);
|
const cipher = await this.cipherService.get(id, userId);
|
||||||
const activeUserId = await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
return await cipher.decrypt(
|
return await cipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +220,7 @@ export class ViewV2Component {
|
|||||||
|
|
||||||
restore = async (): Promise<void> => {
|
restore = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
await this.cipherService.restoreWithServer(this.cipher.id);
|
await this.cipherService.restoreWithServer(this.cipher.id, this.activeUserId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logService.error(e);
|
this.logService.error(e);
|
||||||
}
|
}
|
||||||
@@ -228,8 +235,8 @@ export class ViewV2Component {
|
|||||||
|
|
||||||
protected deleteCipher() {
|
protected deleteCipher() {
|
||||||
return this.cipher.isDeleted
|
return this.cipher.isDeleted
|
||||||
? this.cipherService.deleteWithServer(this.cipher.id)
|
? this.cipherService.deleteWithServer(this.cipher.id, this.activeUserId)
|
||||||
: this.cipherService.softDeleteWithServer(this.cipher.id);
|
: this.cipherService.softDeleteWithServer(this.cipher.id, this.activeUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected showFooter(): boolean {
|
protected showFooter(): boolean {
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
|||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
|
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
|
||||||
|
import { LocalData } from "@bitwarden/common/vault/models/data/local.data";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
|
||||||
import { InlineMenuFieldQualificationService } from "../../../autofill/services/inline-menu-field-qualification.service";
|
import { InlineMenuFieldQualificationService } from "../../../autofill/services/inline-menu-field-qualification.service";
|
||||||
@@ -34,6 +36,10 @@ describe("VaultPopupItemsService", () => {
|
|||||||
let mockCollections: CollectionView[];
|
let mockCollections: CollectionView[];
|
||||||
let activeUserLastSync$: BehaviorSubject<Date>;
|
let activeUserLastSync$: BehaviorSubject<Date>;
|
||||||
|
|
||||||
|
let ciphersSubject: BehaviorSubject<Record<CipherId, CipherData>>;
|
||||||
|
let localDataSubject: BehaviorSubject<Record<CipherId, LocalData>>;
|
||||||
|
let failedToDecryptCiphersSubject: BehaviorSubject<CipherView[]>;
|
||||||
|
|
||||||
const cipherServiceMock = mock<CipherService>();
|
const cipherServiceMock = mock<CipherService>();
|
||||||
const vaultSettingsServiceMock = mock<VaultSettingsService>();
|
const vaultSettingsServiceMock = mock<VaultSettingsService>();
|
||||||
const organizationServiceMock = mock<OrganizationService>();
|
const organizationServiceMock = mock<OrganizationService>();
|
||||||
@@ -60,9 +66,21 @@ describe("VaultPopupItemsService", () => {
|
|||||||
cipherList[3].favorite = true;
|
cipherList[3].favorite = true;
|
||||||
|
|
||||||
cipherServiceMock.getAllDecrypted.mockResolvedValue(cipherList);
|
cipherServiceMock.getAllDecrypted.mockResolvedValue(cipherList);
|
||||||
cipherServiceMock.ciphers$ = new BehaviorSubject(null);
|
|
||||||
cipherServiceMock.localData$ = new BehaviorSubject(null);
|
ciphersSubject = new BehaviorSubject<Record<CipherId, CipherData>>({});
|
||||||
cipherServiceMock.failedToDecryptCiphers$ = new BehaviorSubject([]);
|
localDataSubject = new BehaviorSubject<Record<CipherId, LocalData>>({});
|
||||||
|
failedToDecryptCiphersSubject = new BehaviorSubject<CipherView[]>([]);
|
||||||
|
|
||||||
|
cipherServiceMock.ciphers$.mockImplementation((userId: UserId) =>
|
||||||
|
ciphersSubject.asObservable(),
|
||||||
|
);
|
||||||
|
cipherServiceMock.localData$.mockImplementation((userId: UserId) =>
|
||||||
|
localDataSubject.asObservable(),
|
||||||
|
);
|
||||||
|
cipherServiceMock.failedToDecryptCiphers$.mockImplementation((userId: UserId) =>
|
||||||
|
failedToDecryptCiphersSubject.asObservable(),
|
||||||
|
);
|
||||||
|
|
||||||
searchService.searchCiphers.mockImplementation(async (_, __, ciphers) => ciphers);
|
searchService.searchCiphers.mockImplementation(async (_, __, ciphers) => ciphers);
|
||||||
cipherServiceMock.filterCiphersForUrl.mockImplementation(async (ciphers) =>
|
cipherServiceMock.filterCiphersForUrl.mockImplementation(async (ciphers) =>
|
||||||
ciphers.filter((c) => ["0", "1"].includes(c.id)),
|
ciphers.filter((c) => ["0", "1"].includes(c.id)),
|
||||||
@@ -118,6 +136,7 @@ describe("VaultPopupItemsService", () => {
|
|||||||
{ provide: CollectionService, useValue: collectionService },
|
{ provide: CollectionService, useValue: collectionService },
|
||||||
{ provide: VaultPopupAutofillService, useValue: vaultAutofillServiceMock },
|
{ provide: VaultPopupAutofillService, useValue: vaultAutofillServiceMock },
|
||||||
{ provide: SyncService, useValue: syncServiceMock },
|
{ provide: SyncService, useValue: syncServiceMock },
|
||||||
|
{ provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) },
|
||||||
{
|
{
|
||||||
provide: InlineMenuFieldQualificationService,
|
provide: InlineMenuFieldQualificationService,
|
||||||
useValue: inlineMenuFieldQualificationServiceMock,
|
useValue: inlineMenuFieldQualificationServiceMock,
|
||||||
@@ -155,7 +174,7 @@ describe("VaultPopupItemsService", () => {
|
|||||||
|
|
||||||
await tracker.expectEmission();
|
await tracker.expectEmission();
|
||||||
|
|
||||||
(cipherServiceMock.ciphers$ as BehaviorSubject<any>).next(null);
|
ciphersSubject.next({});
|
||||||
|
|
||||||
await tracker.expectEmission();
|
await tracker.expectEmission();
|
||||||
|
|
||||||
@@ -169,7 +188,7 @@ describe("VaultPopupItemsService", () => {
|
|||||||
|
|
||||||
await tracker.expectEmission();
|
await tracker.expectEmission();
|
||||||
|
|
||||||
(cipherServiceMock.localData$ as BehaviorSubject<any>).next(null);
|
localDataSubject.next({});
|
||||||
|
|
||||||
await tracker.expectEmission();
|
await tracker.expectEmission();
|
||||||
|
|
||||||
@@ -373,7 +392,7 @@ describe("VaultPopupItemsService", () => {
|
|||||||
|
|
||||||
cipherServiceMock.getAllDecrypted.mockResolvedValue(ciphers);
|
cipherServiceMock.getAllDecrypted.mockResolvedValue(ciphers);
|
||||||
|
|
||||||
(cipherServiceMock.ciphers$ as BehaviorSubject<any>).next(null);
|
ciphersSubject.next({});
|
||||||
|
|
||||||
const deletedCiphers = await firstValueFrom(service.deletedCiphers$);
|
const deletedCiphers = await firstValueFrom(service.deletedCiphers$);
|
||||||
expect(deletedCiphers.length).toBe(1);
|
expect(deletedCiphers.length).toBe(1);
|
||||||
@@ -422,7 +441,7 @@ describe("VaultPopupItemsService", () => {
|
|||||||
it("should cycle when cipherService.ciphers$ emits", async () => {
|
it("should cycle when cipherService.ciphers$ emits", async () => {
|
||||||
// Restart tracking
|
// Restart tracking
|
||||||
tracked = new ObservableTracker(service.loading$);
|
tracked = new ObservableTracker(service.loading$);
|
||||||
(cipherServiceMock.ciphers$ as BehaviorSubject<any>).next(null);
|
ciphersSubject.next({});
|
||||||
|
|
||||||
await trackedCiphers.pauseUntilReceived(2);
|
await trackedCiphers.pauseUntilReceived(2);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { inject, Injectable, NgZone } from "@angular/core";
|
import { Injectable, NgZone } from "@angular/core";
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
combineLatest,
|
combineLatest,
|
||||||
@@ -86,16 +86,19 @@ export class VaultPopupItemsService {
|
|||||||
* Observable that contains the list of all decrypted ciphers.
|
* Observable that contains the list of all decrypted ciphers.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private _allDecryptedCiphers$: Observable<CipherView[]> = merge(
|
private _allDecryptedCiphers$: Observable<CipherView[]> = this.accountService.activeAccount$.pipe(
|
||||||
this.cipherService.ciphers$,
|
map((a) => a?.id),
|
||||||
this.cipherService.localData$,
|
filter((userId) => userId != null),
|
||||||
).pipe(
|
switchMap((userId) =>
|
||||||
runInsideAngular(inject(NgZone)), // Workaround to ensure cipher$ state provider emissions are run inside Angular
|
merge(this.cipherService.ciphers$(userId), this.cipherService.localData$(userId)).pipe(
|
||||||
tap(() => this._ciphersLoading$.next()),
|
runInsideAngular(this.ngZone),
|
||||||
waitUntilSync(this.syncService),
|
tap(() => this._ciphersLoading$.next()),
|
||||||
switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted())),
|
waitUntilSync(this.syncService),
|
||||||
withLatestFrom(this.cipherService.failedToDecryptCiphers$),
|
switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted(userId))),
|
||||||
map(([ciphers, failedToDecryptCiphers]) => [...failedToDecryptCiphers, ...ciphers]),
|
withLatestFrom(this.cipherService.failedToDecryptCiphers$(userId)),
|
||||||
|
map(([ciphers, failedToDecryptCiphers]) => [...failedToDecryptCiphers, ...ciphers]),
|
||||||
|
),
|
||||||
|
),
|
||||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -281,6 +284,7 @@ export class VaultPopupItemsService {
|
|||||||
private vaultPopupAutofillService: VaultPopupAutofillService,
|
private vaultPopupAutofillService: VaultPopupAutofillService,
|
||||||
private syncService: SyncService,
|
private syncService: SyncService,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
|
private ngZone: NgZone,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
applyFilter(newSearchText: string) {
|
applyFilter(newSearchText: string) {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ describe("VaultPopupListFiltersService", () => {
|
|||||||
} as unknown as FolderService;
|
} as unknown as FolderService;
|
||||||
|
|
||||||
const cipherService = {
|
const cipherService = {
|
||||||
cipherViews$,
|
cipherViews$: () => cipherViews$,
|
||||||
} as unknown as CipherService;
|
} as unknown as CipherService;
|
||||||
|
|
||||||
const organizationService = {
|
const organizationService = {
|
||||||
|
|||||||
@@ -93,16 +93,6 @@ export class VaultPopupListFiltersService {
|
|||||||
*/
|
*/
|
||||||
private cipherViews: CipherView[] = [];
|
private cipherViews: CipherView[] = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* Observable of cipher views
|
|
||||||
*/
|
|
||||||
private cipherViews$: Observable<CipherView[]> = this.cipherService.cipherViews$.pipe(
|
|
||||||
tap((cipherViews) => {
|
|
||||||
this.cipherViews = Object.values(cipherViews);
|
|
||||||
}),
|
|
||||||
map((ciphers) => Object.values(ciphers)),
|
|
||||||
);
|
|
||||||
|
|
||||||
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
|
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -271,8 +261,16 @@ export class VaultPopupListFiltersService {
|
|||||||
* Folder array structured to be directly passed to `ChipSelectComponent`
|
* Folder array structured to be directly passed to `ChipSelectComponent`
|
||||||
*/
|
*/
|
||||||
folders$: Observable<ChipSelectOption<FolderView>[]> = this.activeUserId$.pipe(
|
folders$: Observable<ChipSelectOption<FolderView>[]> = this.activeUserId$.pipe(
|
||||||
switchMap((userId) =>
|
switchMap((userId) => {
|
||||||
combineLatest([
|
// Observable of cipher views
|
||||||
|
const cipherViews$ = this.cipherService.cipherViews$(userId).pipe(
|
||||||
|
tap((cipherViews) => {
|
||||||
|
this.cipherViews = Object.values(cipherViews);
|
||||||
|
}),
|
||||||
|
map((ciphers) => Object.values(ciphers)),
|
||||||
|
);
|
||||||
|
|
||||||
|
return combineLatest([
|
||||||
this.filters$.pipe(
|
this.filters$.pipe(
|
||||||
distinctUntilChanged(
|
distinctUntilChanged(
|
||||||
(previousFilter, currentFilter) =>
|
(previousFilter, currentFilter) =>
|
||||||
@@ -281,7 +279,7 @@ export class VaultPopupListFiltersService {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
this.folderService.folderViews$(userId),
|
this.folderService.folderViews$(userId),
|
||||||
this.cipherViews$,
|
cipherViews$,
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map(([filters, folders, cipherViews]): [PopupListFilter, FolderView[], CipherView[]] => {
|
map(([filters, folders, cipherViews]): [PopupListFilter, FolderView[], CipherView[]] => {
|
||||||
if (folders.length === 1 && folders[0].id === null) {
|
if (folders.length === 1 && folders[0].id === null) {
|
||||||
@@ -330,8 +328,8 @@ export class VaultPopupListFiltersService {
|
|||||||
map((folders) =>
|
map((folders) =>
|
||||||
folders.nestedList.map((f) => this.convertToChipSelectOption(f, "bwi-folder")),
|
folders.nestedList.map((f) => this.convertToChipSelectOption(f, "bwi-folder")),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,8 +3,11 @@
|
|||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { ChangeDetectionStrategy, Component, Input } from "@angular/core";
|
import { ChangeDetectionStrategy, Component, Input } from "@angular/core";
|
||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { CipherId } from "@bitwarden/common/types/guid";
|
import { CipherId } from "@bitwarden/common/types/guid";
|
||||||
@@ -65,6 +68,7 @@ export class TrashListItemsContainerComponent {
|
|||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private passwordRepromptService: PasswordRepromptService,
|
private passwordRepromptService: PasswordRepromptService,
|
||||||
|
private accountService: AccountService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -81,7 +85,8 @@ export class TrashListItemsContainerComponent {
|
|||||||
|
|
||||||
async restore(cipher: CipherView) {
|
async restore(cipher: CipherView) {
|
||||||
try {
|
try {
|
||||||
await this.cipherService.restoreWithServer(cipher.id);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
await this.cipherService.restoreWithServer(cipher.id, activeUserId);
|
||||||
|
|
||||||
await this.router.navigate(["/trash"]);
|
await this.router.navigate(["/trash"]);
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
@@ -112,7 +117,8 @@ export class TrashListItemsContainerComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.cipherService.deleteWithServer(cipher.id);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
await this.cipherService.deleteWithServer(cipher.id, activeUserId);
|
||||||
|
|
||||||
await this.router.navigate(["/trash"]);
|
await this.router.navigate(["/trash"]);
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
|
||||||
import { Response } from "../../models/response";
|
import { Response } from "../../models/response";
|
||||||
@@ -48,7 +49,9 @@ export class ShareCommand {
|
|||||||
organizationId = organizationId.toLowerCase();
|
organizationId = organizationId.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
const cipher = await this.cipherService.get(id);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|
||||||
|
const cipher = await this.cipherService.get(id, activeUserId);
|
||||||
if (cipher == null) {
|
if (cipher == null) {
|
||||||
return Response.notFound();
|
return Response.notFound();
|
||||||
}
|
}
|
||||||
@@ -56,15 +59,12 @@ export class ShareCommand {
|
|||||||
return Response.badRequest("This item already belongs to an organization.");
|
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(
|
const cipherView = await cipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
await this.cipherService.shareWithServer(cipherView, organizationId, req, activeUserId);
|
await this.cipherService.shareWithServer(cipherView, organizationId, req, activeUserId);
|
||||||
const updatedCipher = await this.cipherService.get(cipher.id);
|
const updatedCipher = await this.cipherService.get(cipher.id, activeUserId);
|
||||||
const decCipher = await updatedCipher.decrypt(
|
const decCipher = await updatedCipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { CollectionRequest } from "@bitwarden/admin-console/common";
|
import { CollectionRequest } from "@bitwarden/admin-console/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request";
|
import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { CipherExport } from "@bitwarden/common/models/export/cipher.export";
|
import { CipherExport } from "@bitwarden/common/models/export/cipher.export";
|
||||||
import { CollectionExport } from "@bitwarden/common/models/export/collection.export";
|
import { CollectionExport } from "@bitwarden/common/models/export/collection.export";
|
||||||
@@ -25,8 +26,6 @@ import { CipherResponse } from "../vault/models/cipher.response";
|
|||||||
import { FolderResponse } from "../vault/models/folder.response";
|
import { FolderResponse } from "../vault/models/folder.response";
|
||||||
|
|
||||||
export class EditCommand {
|
export class EditCommand {
|
||||||
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private folderService: FolderService,
|
private folderService: FolderService,
|
||||||
@@ -85,14 +84,12 @@ export class EditCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async editCipher(id: string, req: CipherExport) {
|
private async editCipher(id: string, req: CipherExport) {
|
||||||
const cipher = await this.cipherService.get(id);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
const cipher = await this.cipherService.get(id, activeUserId);
|
||||||
if (cipher == null) {
|
if (cipher == null) {
|
||||||
return Response.notFound();
|
return Response.notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeUserId = await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
let cipherView = await cipher.decrypt(
|
let cipherView = await cipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||||
);
|
);
|
||||||
@@ -114,7 +111,9 @@ export class EditCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async editCipherCollections(id: string, req: string[]) {
|
private async editCipherCollections(id: string, req: string[]) {
|
||||||
const cipher = await this.cipherService.get(id);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|
||||||
|
const cipher = await this.cipherService.get(id, activeUserId);
|
||||||
if (cipher == null) {
|
if (cipher == null) {
|
||||||
return Response.notFound();
|
return Response.notFound();
|
||||||
}
|
}
|
||||||
@@ -129,11 +128,14 @@ export class EditCommand {
|
|||||||
|
|
||||||
cipher.collectionIds = req;
|
cipher.collectionIds = req;
|
||||||
try {
|
try {
|
||||||
const updatedCipher = await this.cipherService.saveCollectionsWithServer(cipher);
|
const updatedCipher = await this.cipherService.saveCollectionsWithServer(
|
||||||
|
cipher,
|
||||||
|
activeUserId,
|
||||||
|
);
|
||||||
const decCipher = await updatedCipher.decrypt(
|
const decCipher = await updatedCipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(
|
await this.cipherService.getKeyForCipherKeyDecryption(
|
||||||
updatedCipher,
|
updatedCipher,
|
||||||
await firstValueFrom(this.activeUserId$),
|
await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const res = new CipherResponse(decCipher);
|
const res = new CipherResponse(decCipher);
|
||||||
@@ -144,7 +146,7 @@ export class EditCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async editFolder(id: string, req: FolderExport) {
|
private async editFolder(id: string, req: FolderExport) {
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
const folder = await this.folderService.getFromState(id, activeUserId);
|
const folder = await this.folderService.getFromState(id, activeUserId);
|
||||||
if (folder == null) {
|
if (folder == null) {
|
||||||
return Response.notFound();
|
return Response.notFound();
|
||||||
|
|||||||
@@ -52,8 +52,6 @@ import { FolderResponse } from "../vault/models/folder.response";
|
|||||||
import { DownloadCommand } from "./download.command";
|
import { DownloadCommand } from "./download.command";
|
||||||
|
|
||||||
export class GetCommand extends DownloadCommand {
|
export class GetCommand extends DownloadCommand {
|
||||||
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private folderService: FolderService,
|
private folderService: FolderService,
|
||||||
@@ -114,16 +112,16 @@ export class GetCommand extends DownloadCommand {
|
|||||||
|
|
||||||
private async getCipherView(id: string): Promise<CipherView | CipherView[]> {
|
private async getCipherView(id: string): Promise<CipherView | CipherView[]> {
|
||||||
let decCipher: CipherView = null;
|
let decCipher: CipherView = null;
|
||||||
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
if (Utils.isGuid(id)) {
|
if (Utils.isGuid(id)) {
|
||||||
const cipher = await this.cipherService.get(id);
|
const cipher = await this.cipherService.get(id, activeUserId);
|
||||||
if (cipher != null) {
|
if (cipher != null) {
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
|
||||||
decCipher = await cipher.decrypt(
|
decCipher = await cipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (id.trim() !== "") {
|
} else if (id.trim() !== "") {
|
||||||
let ciphers = await this.cipherService.getAllDecrypted();
|
let ciphers = await this.cipherService.getAllDecrypted(activeUserId);
|
||||||
ciphers = this.searchService.searchCiphersBasic(ciphers, id);
|
ciphers = this.searchService.searchCiphersBasic(ciphers, id);
|
||||||
if (ciphers.length > 1) {
|
if (ciphers.length > 1) {
|
||||||
return ciphers;
|
return ciphers;
|
||||||
@@ -265,8 +263,10 @@ export class GetCommand extends DownloadCommand {
|
|||||||
const canAccessPremium = await firstValueFrom(
|
const canAccessPremium = await firstValueFrom(
|
||||||
this.accountProfileService.hasPremiumFromAnySource$(account.id),
|
this.accountProfileService.hasPremiumFromAnySource$(account.id),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!canAccessPremium) {
|
if (!canAccessPremium) {
|
||||||
const originalCipher = await this.cipherService.get(cipher.id);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
const originalCipher = await this.cipherService.get(cipher.id, activeUserId);
|
||||||
if (
|
if (
|
||||||
originalCipher == null ||
|
originalCipher == null ||
|
||||||
originalCipher.organizationId == null ||
|
originalCipher.organizationId == null ||
|
||||||
@@ -352,7 +352,8 @@ export class GetCommand extends DownloadCommand {
|
|||||||
this.accountProfileService.hasPremiumFromAnySource$(account.id),
|
this.accountProfileService.hasPremiumFromAnySource$(account.id),
|
||||||
);
|
);
|
||||||
if (!canAccessPremium) {
|
if (!canAccessPremium) {
|
||||||
const originalCipher = await this.cipherService.get(cipher.id);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
const originalCipher = await this.cipherService.get(cipher.id, activeUserId);
|
||||||
if (originalCipher == null || originalCipher.organizationId == null) {
|
if (originalCipher == null || originalCipher.organizationId == null) {
|
||||||
return Response.error("Premium status is required to use this feature.");
|
return Response.error("Premium status is required to use this feature.");
|
||||||
}
|
}
|
||||||
@@ -384,7 +385,7 @@ export class GetCommand extends DownloadCommand {
|
|||||||
|
|
||||||
private async getFolder(id: string) {
|
private async getFolder(id: string) {
|
||||||
let decFolder: FolderView = null;
|
let decFolder: FolderView = null;
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
if (Utils.isGuid(id)) {
|
if (Utils.isGuid(id)) {
|
||||||
const folder = await this.folderService.getFromState(id, activeUserId);
|
const folder = await this.folderService.getFromState(id, activeUserId);
|
||||||
if (folder != null) {
|
if (folder != null) {
|
||||||
@@ -561,7 +562,7 @@ export class GetCommand extends DownloadCommand {
|
|||||||
private async getFingerprint(id: string) {
|
private async getFingerprint(id: string) {
|
||||||
let fingerprint: string[] = null;
|
let fingerprint: string[] = null;
|
||||||
if (id === "me") {
|
if (id === "me") {
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
const publicKey = await firstValueFrom(this.keyService.userPublicKey$(activeUserId));
|
const publicKey = await firstValueFrom(this.keyService.userPublicKey$(activeUserId));
|
||||||
fingerprint = await this.keyService.getFingerprint(activeUserId, publicKey);
|
fingerprint = await this.keyService.getFingerprint(activeUserId, publicKey);
|
||||||
} else if (Utils.isGuid(id)) {
|
} else if (Utils.isGuid(id)) {
|
||||||
|
|||||||
@@ -65,11 +65,14 @@ export class ListCommand {
|
|||||||
|
|
||||||
private async listCiphers(options: Options) {
|
private async listCiphers(options: Options) {
|
||||||
let ciphers: CipherView[];
|
let ciphers: CipherView[];
|
||||||
|
|
||||||
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|
||||||
options.trash = options.trash || false;
|
options.trash = options.trash || false;
|
||||||
if (options.url != null && options.url.trim() !== "") {
|
if (options.url != null && options.url.trim() !== "") {
|
||||||
ciphers = await this.cipherService.getAllDecryptedForUrl(options.url);
|
ciphers = await this.cipherService.getAllDecryptedForUrl(options.url, activeUserId);
|
||||||
} else {
|
} else {
|
||||||
ciphers = await this.cipherService.getAllDecrypted();
|
ciphers = await this.cipherService.getAllDecrypted(activeUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -138,9 +141,8 @@ export class ListCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async listFolders(options: Options) {
|
private async listFolders(options: Options) {
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
let folders = await this.folderService.getAllDecryptedFromState(activeUserId);
|
let folders = await this.folderService.getAllDecryptedFromState(activeUserId);
|
||||||
|
|
||||||
if (options.search != null && options.search.trim() !== "") {
|
if (options.search != null && options.search.trim() !== "") {
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
|
||||||
import { Response } from "../models/response";
|
import { Response } from "../models/response";
|
||||||
|
|
||||||
export class RestoreCommand {
|
export class RestoreCommand {
|
||||||
constructor(private cipherService: CipherService) {}
|
constructor(
|
||||||
|
private cipherService: CipherService,
|
||||||
|
private accountService: AccountService,
|
||||||
|
) {}
|
||||||
|
|
||||||
async run(object: string, id: string): Promise<Response> {
|
async run(object: string, id: string): Promise<Response> {
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
@@ -19,7 +26,9 @@ export class RestoreCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async restoreCipher(id: string) {
|
private async restoreCipher(id: string) {
|
||||||
const cipher = await this.cipherService.get(id);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|
||||||
|
const cipher = await this.cipherService.get(id, activeUserId);
|
||||||
if (cipher == null) {
|
if (cipher == null) {
|
||||||
return Response.notFound();
|
return Response.notFound();
|
||||||
}
|
}
|
||||||
@@ -28,7 +37,7 @@ export class RestoreCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.cipherService.restoreWithServer(id);
|
await this.cipherService.restoreWithServer(id, activeUserId);
|
||||||
return Response.success();
|
return Response.success();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Response.error(e);
|
return Response.error(e);
|
||||||
|
|||||||
@@ -124,7 +124,10 @@ export class OssServeConfigurator {
|
|||||||
this.serviceContainer.encryptService,
|
this.serviceContainer.encryptService,
|
||||||
this.serviceContainer.organizationUserApiService,
|
this.serviceContainer.organizationUserApiService,
|
||||||
);
|
);
|
||||||
this.restoreCommand = new RestoreCommand(this.serviceContainer.cipherService);
|
this.restoreCommand = new RestoreCommand(
|
||||||
|
this.serviceContainer.cipherService,
|
||||||
|
this.serviceContainer.accountService,
|
||||||
|
);
|
||||||
this.shareCommand = new ShareCommand(
|
this.shareCommand = new ShareCommand(
|
||||||
this.serviceContainer.cipherService,
|
this.serviceContainer.cipherService,
|
||||||
this.serviceContainer.accountService,
|
this.serviceContainer.accountService,
|
||||||
|
|||||||
@@ -347,7 +347,10 @@ export class VaultProgram extends BaseProgram {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.exitIfLocked();
|
await this.exitIfLocked();
|
||||||
const command = new RestoreCommand(this.serviceContainer.cipherService);
|
const command = new RestoreCommand(
|
||||||
|
this.serviceContainer.cipherService,
|
||||||
|
this.serviceContainer.accountService,
|
||||||
|
);
|
||||||
const response = await command.run(object, id);
|
const response = await command.run(object, id);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
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 { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { CipherExport } from "@bitwarden/common/models/export/cipher.export";
|
import { CipherExport } from "@bitwarden/common/models/export/cipher.export";
|
||||||
@@ -30,8 +31,6 @@ import { CipherResponse } from "./models/cipher.response";
|
|||||||
import { FolderResponse } from "./models/folder.response";
|
import { FolderResponse } from "./models/folder.response";
|
||||||
|
|
||||||
export class CreateCommand {
|
export class CreateCommand {
|
||||||
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private folderService: FolderService,
|
private folderService: FolderService,
|
||||||
@@ -90,7 +89,7 @@ export class CreateCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async createCipher(req: CipherExport) {
|
private async createCipher(req: CipherExport) {
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId);
|
const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId);
|
||||||
try {
|
try {
|
||||||
const newCipher = await this.cipherService.createWithServer(cipher);
|
const newCipher = await this.cipherService.createWithServer(cipher);
|
||||||
@@ -132,14 +131,14 @@ export class CreateCommand {
|
|||||||
return Response.badRequest("File name not provided.");
|
return Response.badRequest("File name not provided.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|
||||||
const itemId = options.itemId.toLowerCase();
|
const itemId = options.itemId.toLowerCase();
|
||||||
const cipher = await this.cipherService.get(itemId);
|
const cipher = await this.cipherService.get(itemId, activeUserId);
|
||||||
if (cipher == null) {
|
if (cipher == null) {
|
||||||
return Response.notFound();
|
return Response.notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
|
||||||
|
|
||||||
const canAccessPremium = await firstValueFrom(
|
const canAccessPremium = await firstValueFrom(
|
||||||
this.accountProfileService.hasPremiumFromAnySource$(activeUserId),
|
this.accountProfileService.hasPremiumFromAnySource$(activeUserId),
|
||||||
);
|
);
|
||||||
@@ -173,7 +172,7 @@ export class CreateCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async createFolder(req: FolderExport) {
|
private async createFolder(req: FolderExport) {
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId);
|
const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId);
|
||||||
const folder = await this.folderService.encrypt(FolderExport.toView(req), userKey);
|
const folder = await this.folderService.encrypt(FolderExport.toView(req), userKey);
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
@@ -44,7 +45,9 @@ export class DeleteCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async deleteCipher(id: string, options: Options) {
|
private async deleteCipher(id: string, options: Options) {
|
||||||
const cipher = await this.cipherService.get(id);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|
||||||
|
const cipher = await this.cipherService.get(id, activeUserId);
|
||||||
if (cipher == null) {
|
if (cipher == null) {
|
||||||
return Response.notFound();
|
return Response.notFound();
|
||||||
}
|
}
|
||||||
@@ -59,9 +62,9 @@ export class DeleteCommand {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (options.permanent) {
|
if (options.permanent) {
|
||||||
await this.cipherService.deleteWithServer(id);
|
await this.cipherService.deleteWithServer(id, activeUserId);
|
||||||
} else {
|
} else {
|
||||||
await this.cipherService.softDeleteWithServer(id);
|
await this.cipherService.softDeleteWithServer(id, activeUserId);
|
||||||
}
|
}
|
||||||
return Response.success();
|
return Response.success();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -74,8 +77,10 @@ export class DeleteCommand {
|
|||||||
return Response.badRequest("`itemid` option is required.");
|
return Response.badRequest("`itemid` option is required.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|
||||||
const itemId = options.itemId.toLowerCase();
|
const itemId = options.itemId.toLowerCase();
|
||||||
const cipher = await this.cipherService.get(itemId);
|
const cipher = await this.cipherService.get(itemId, activeUserId);
|
||||||
if (cipher == null) {
|
if (cipher == null) {
|
||||||
return Response.notFound();
|
return Response.notFound();
|
||||||
}
|
}
|
||||||
@@ -89,16 +94,19 @@ export class DeleteCommand {
|
|||||||
return Response.error("Attachment `" + id + "` was not found.");
|
return Response.error("Attachment `" + id + "` was not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const account = await firstValueFrom(this.accountService.activeAccount$);
|
|
||||||
const canAccessPremium = await firstValueFrom(
|
const canAccessPremium = await firstValueFrom(
|
||||||
this.accountProfileService.hasPremiumFromAnySource$(account.id),
|
this.accountProfileService.hasPremiumFromAnySource$(activeUserId),
|
||||||
);
|
);
|
||||||
if (cipher.organizationId == null && !canAccessPremium) {
|
if (cipher.organizationId == null && !canAccessPremium) {
|
||||||
return Response.error("Premium status is required to use this feature.");
|
return Response.error("Premium status is required to use this feature.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.cipherService.deleteAttachmentWithServer(cipher.id, attachments[0].id);
|
await this.cipherService.deleteAttachmentWithServer(
|
||||||
|
cipher.id,
|
||||||
|
attachments[0].id,
|
||||||
|
activeUserId,
|
||||||
|
);
|
||||||
return Response.success();
|
return Response.success();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Response.error(e);
|
return Response.error(e);
|
||||||
@@ -106,9 +114,7 @@ export class DeleteCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async deleteFolder(id: string) {
|
private async deleteFolder(id: string) {
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
const folder = await this.folderService.getFromState(id, activeUserId);
|
const folder = await this.folderService.getFromState(id, activeUserId);
|
||||||
if (folder == null) {
|
if (folder == null) {
|
||||||
return Response.notFound();
|
return Response.notFound();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
EMPTY,
|
EMPTY,
|
||||||
Subject,
|
Subject,
|
||||||
distinctUntilChanged,
|
distinctUntilChanged,
|
||||||
|
filter,
|
||||||
firstValueFrom,
|
firstValueFrom,
|
||||||
map,
|
map,
|
||||||
mergeMap,
|
mergeMap,
|
||||||
@@ -12,6 +13,7 @@ import {
|
|||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
|
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
|
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
@@ -27,6 +29,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
|
|||||||
import { getCredentialsForAutofill } from "@bitwarden/common/platform/services/fido2/fido2-autofill-utils";
|
import { getCredentialsForAutofill } from "@bitwarden/common/platform/services/fido2/fido2-autofill-utils";
|
||||||
import { Fido2Utils } from "@bitwarden/common/platform/services/fido2/fido2-utils";
|
import { Fido2Utils } from "@bitwarden/common/platform/services/fido2/fido2-utils";
|
||||||
import { guidToRawFormat } from "@bitwarden/common/platform/services/fido2/guid-utils";
|
import { guidToRawFormat } from "@bitwarden/common/platform/services/fido2/guid-utils";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
@@ -60,7 +63,11 @@ export class DesktopAutofillService implements OnDestroy {
|
|||||||
return EMPTY;
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.cipherService.cipherViews$;
|
return this.accountService.activeAccount$.pipe(
|
||||||
|
map((account) => account?.id),
|
||||||
|
filter((userId): userId is UserId => userId != null),
|
||||||
|
switchMap((userId) => this.cipherService.cipherViews$(userId)),
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
// TODO: This will unset all the autofill credentials on the OS
|
// TODO: This will unset all the autofill credentials on the OS
|
||||||
// when the account locks. We should instead explicilty clear the credentials
|
// when the account locks. We should instead explicilty clear the credentials
|
||||||
@@ -164,17 +171,22 @@ export class DesktopAutofillService implements OnDestroy {
|
|||||||
// TODO: For some reason the credentialId is passed as an empty array in the request, so we need to
|
// TODO: For some reason the credentialId is passed as an empty array in the request, so we need to
|
||||||
// get it from the cipher. For that we use the recordIdentifier, which is the cipherId.
|
// get it from the cipher. For that we use the recordIdentifier, which is the cipherId.
|
||||||
if (request.recordIdentifier && request.credentialId.length === 0) {
|
if (request.recordIdentifier && request.credentialId.length === 0) {
|
||||||
const cipher = await this.cipherService.get(request.recordIdentifier);
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||||
|
);
|
||||||
|
if (!activeUserId) {
|
||||||
|
this.logService.error("listenPasskeyAssertion error", "Active user not found");
|
||||||
|
callback(new Error("Active user not found"), null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cipher = await this.cipherService.get(request.recordIdentifier, activeUserId);
|
||||||
if (!cipher) {
|
if (!cipher) {
|
||||||
this.logService.error("listenPasskeyAssertion error", "Cipher not found");
|
this.logService.error("listenPasskeyAssertion error", "Cipher not found");
|
||||||
callback(new Error("Cipher not found"), null);
|
callback(new Error("Cipher not found"), null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeUserId = await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
const decrypted = await cipher.decrypt(
|
const decrypted = await cipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
|
|||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { CommandDefinition, MessageListener } from "@bitwarden/common/platform/messaging";
|
import { CommandDefinition, MessageListener } from "@bitwarden/common/platform/messaging";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { DialogService, ToastService } from "@bitwarden/components";
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
@@ -92,14 +93,14 @@ export class SshAgentService implements OnDestroy {
|
|||||||
}),
|
}),
|
||||||
filter(({ enabled }) => enabled),
|
filter(({ enabled }) => enabled),
|
||||||
map(({ message }) => message),
|
map(({ message }) => message),
|
||||||
withLatestFrom(this.authService.activeAccountStatus$),
|
withLatestFrom(this.authService.activeAccountStatus$, this.accountService.activeAccount$),
|
||||||
// This switchMap handles unlocking the vault if it is locked:
|
// This switchMap handles unlocking the vault if it is locked:
|
||||||
// - If the vault is locked, we will wait for it to be unlocked.
|
// - If the vault is locked, we will wait for it to be unlocked.
|
||||||
// - If the vault is not unlocked within the timeout, we will abort the flow.
|
// - If the vault is not unlocked within the timeout, we will abort the flow.
|
||||||
// - If the vault is unlocked, we will continue with the flow.
|
// - If the vault is unlocked, we will continue with the flow.
|
||||||
// switchMap is used here to prevent multiple requests from being processed at the same time,
|
// switchMap is used here to prevent multiple requests from being processed at the same time,
|
||||||
// and will cancel the previous request if a new one is received.
|
// and will cancel the previous request if a new one is received.
|
||||||
switchMap(([message, status]) => {
|
switchMap(([message, status, account]) => {
|
||||||
if (status !== AuthenticationStatus.Unlocked) {
|
if (status !== AuthenticationStatus.Unlocked) {
|
||||||
ipc.platform.focusWindow();
|
ipc.platform.focusWindow();
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
@@ -133,11 +134,11 @@ export class SshAgentService implements OnDestroy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return of(message);
|
return of([message, account.id]);
|
||||||
}),
|
}),
|
||||||
// This switchMap handles fetching the ciphers from the vault.
|
// This switchMap handles fetching the ciphers from the vault.
|
||||||
switchMap((message) =>
|
switchMap(([message, userId]: [Record<string, unknown>, UserId]) =>
|
||||||
from(this.cipherService.getAllDecrypted()).pipe(
|
from(this.cipherService.getAllDecrypted(userId)).pipe(
|
||||||
map((ciphers) => [message, ciphers] as const),
|
map((ciphers) => [message, ciphers] as const),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -245,7 +246,7 @@ export class SshAgentService implements OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ciphers = await this.cipherService.getAllDecrypted();
|
const ciphers = await this.cipherService.getAllDecrypted(activeAccount.id);
|
||||||
if (ciphers == null) {
|
if (ciphers == null) {
|
||||||
await ipc.platform.sshAgent.lock();
|
await ipc.platform.sshAgent.lock();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
@@ -65,9 +66,7 @@ export class EncryptedMessageHandlerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async checkUserStatus(userId: string): Promise<string> {
|
private async checkUserStatus(userId: string): Promise<string> {
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (userId !== activeUserId) {
|
if (userId !== activeUserId) {
|
||||||
return "not-active-user";
|
return "not-active-user";
|
||||||
@@ -83,9 +82,7 @@ export class EncryptedMessageHandlerService {
|
|||||||
|
|
||||||
private async statusCommandHandler(): Promise<AccountStatusResponse[]> {
|
private async statusCommandHandler(): Promise<AccountStatusResponse[]> {
|
||||||
const accounts = await firstValueFrom(this.accountService.accounts$);
|
const accounts = await firstValueFrom(this.accountService.accounts$);
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!accounts || !Object.keys(accounts)) {
|
if (!accounts || !Object.keys(accounts)) {
|
||||||
return [];
|
return [];
|
||||||
@@ -114,16 +111,14 @@ export class EncryptedMessageHandlerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ciphersResponse: CipherResponse[] = [];
|
const ciphersResponse: CipherResponse[] = [];
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
const authStatus = await this.authService.getAuthStatus(activeUserId);
|
const authStatus = await this.authService.getAuthStatus(activeUserId);
|
||||||
|
|
||||||
if (authStatus !== AuthenticationStatus.Unlocked) {
|
if (authStatus !== AuthenticationStatus.Unlocked) {
|
||||||
return { error: "locked" };
|
return { error: "locked" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(payload.uri);
|
const ciphers = await this.cipherService.getAllDecryptedForUrl(payload.uri, activeUserId);
|
||||||
ciphers.sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b));
|
ciphers.sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b));
|
||||||
|
|
||||||
ciphers.forEach((c) => {
|
ciphers.forEach((c) => {
|
||||||
@@ -166,9 +161,7 @@ export class EncryptedMessageHandlerService {
|
|||||||
cipherView.login.uris[0].uri = credentialCreatePayload.uri;
|
cipherView.login.uris[0].uri = credentialCreatePayload.uri;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
const encrypted = await this.cipherService.encrypt(cipherView, activeUserId);
|
const encrypted = await this.cipherService.encrypt(cipherView, activeUserId);
|
||||||
await this.cipherService.createWithServer(encrypted);
|
await this.cipherService.createWithServer(encrypted);
|
||||||
|
|
||||||
@@ -200,13 +193,16 @@ export class EncryptedMessageHandlerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cipher = await this.cipherService.get(credentialUpdatePayload.credentialId);
|
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
|
|
||||||
|
const cipher = await this.cipherService.get(
|
||||||
|
credentialUpdatePayload.credentialId,
|
||||||
|
activeUserId,
|
||||||
|
);
|
||||||
if (cipher === null) {
|
if (cipher === null) {
|
||||||
return { status: "failure" };
|
return { status: "failure" };
|
||||||
}
|
}
|
||||||
const activeUserId = await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
const cipherView = await cipher.decrypt(
|
const cipherView = await cipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { distinctUntilChanged } from "rxjs";
|
|||||||
|
|
||||||
import { VaultItemsComponent as BaseVaultItemsComponent } from "@bitwarden/angular/vault/components/vault-items.component";
|
import { VaultItemsComponent as BaseVaultItemsComponent } from "@bitwarden/angular/vault/components/vault-items.component";
|
||||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
|
||||||
@@ -20,8 +21,9 @@ export class VaultItemsComponent extends BaseVaultItemsComponent {
|
|||||||
searchService: SearchService,
|
searchService: SearchService,
|
||||||
searchBarService: SearchBarService,
|
searchBarService: SearchBarService,
|
||||||
cipherService: CipherService,
|
cipherService: CipherService,
|
||||||
|
accountService: AccountService,
|
||||||
) {
|
) {
|
||||||
super(searchService, cipherService);
|
super(searchService, cipherService, accountService);
|
||||||
|
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||||
searchBarService.searchText$.pipe(distinctUntilChanged()).subscribe((searchText) => {
|
searchBarService.searchText$.pipe(distinctUntilChanged()).subscribe((searchText) => {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
ViewContainerRef,
|
ViewContainerRef,
|
||||||
} from "@angular/core";
|
} from "@angular/core";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { combineLatest, firstValueFrom, Subject, takeUntil, switchMap } from "rxjs";
|
import { firstValueFrom, Subject, takeUntil, switchMap } from "rxjs";
|
||||||
import { filter, first, map, take } from "rxjs/operators";
|
import { filter, first, map, take } from "rxjs/operators";
|
||||||
|
|
||||||
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
||||||
@@ -19,6 +19,7 @@ import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-
|
|||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { EventType } from "@bitwarden/common/enums";
|
import { EventType } from "@bitwarden/common/enums";
|
||||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||||
@@ -236,15 +237,12 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store a reference to the current active account during page init
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
|
||||||
|
|
||||||
// Combine with the activeAccount$ to ensure we only show the dialog for the current account from ngOnInit.
|
this.cipherService
|
||||||
// The account switching process updates the cipherService before Vault is destroyed and would cause duplicate emissions
|
.failedToDecryptCiphers$(activeUserId)
|
||||||
combineLatest([this.accountService.activeAccount$, this.cipherService.failedToDecryptCiphers$])
|
|
||||||
.pipe(
|
.pipe(
|
||||||
filter(([account]) => account.id === activeAccount.id),
|
map((ciphers) => ciphers.filter((c) => !c.isDeleted)),
|
||||||
map(([_, ciphers]) => ciphers.filter((c) => !c.isDeleted)),
|
|
||||||
filter((ciphers) => ciphers.length > 0),
|
filter((ciphers) => ciphers.length > 0),
|
||||||
take(1),
|
take(1),
|
||||||
takeUntil(this.componentIsDestroyed$),
|
takeUntil(this.componentIsDestroyed$),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
OrganizationService,
|
OrganizationService,
|
||||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||||
@@ -59,15 +60,13 @@ export class ExposedPasswordsReportComponent
|
|||||||
this.isAdminConsoleActive = true;
|
this.isAdminConsoleActive = true;
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||||
this.route.parent.parent.params.subscribe(async (params) => {
|
this.route.parent.parent.params.subscribe(async (params) => {
|
||||||
const userId = await firstValueFrom(
|
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
this.organization = await firstValueFrom(
|
this.organization = await firstValueFrom(
|
||||||
this.organizationService
|
this.organizationService
|
||||||
.organizations$(userId)
|
.organizations$(userId)
|
||||||
.pipe(getOrganizationById(params.organizationId)),
|
.pipe(getOrganizationById(params.organizationId)),
|
||||||
);
|
);
|
||||||
this.manageableCiphers = await this.cipherService.getAll();
|
this.manageableCiphers = await this.cipherService.getAll(userId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import {
|
import {
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
OrganizationService,
|
OrganizationService,
|
||||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||||
@@ -56,15 +57,13 @@ export class ReusedPasswordsReportComponent
|
|||||||
this.isAdminConsoleActive = true;
|
this.isAdminConsoleActive = true;
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||||
this.route.parent.parent.params.subscribe(async (params) => {
|
this.route.parent.parent.params.subscribe(async (params) => {
|
||||||
const userId = await firstValueFrom(
|
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
this.organization = await firstValueFrom(
|
this.organization = await firstValueFrom(
|
||||||
this.organizationService
|
this.organizationService
|
||||||
.organizations$(userId)
|
.organizations$(userId)
|
||||||
.pipe(getOrganizationById(params.organizationId)),
|
.pipe(getOrganizationById(params.organizationId)),
|
||||||
);
|
);
|
||||||
this.manageableCiphers = await this.cipherService.getAll();
|
this.manageableCiphers = await this.cipherService.getAll(userId);
|
||||||
await super.ngOnInit();
|
await super.ngOnInit();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import {
|
import {
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
OrganizationService,
|
OrganizationService,
|
||||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
@@ -59,15 +60,14 @@ export class WeakPasswordsReportComponent
|
|||||||
this.isAdminConsoleActive = true;
|
this.isAdminConsoleActive = true;
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||||
this.route.parent.parent.params.subscribe(async (params) => {
|
this.route.parent.parent.params.subscribe(async (params) => {
|
||||||
const userId = await firstValueFrom(
|
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
this.organization = await firstValueFrom(
|
this.organization = await firstValueFrom(
|
||||||
this.organizationService
|
this.organizationService
|
||||||
.organizations$(userId)
|
.organizations$(userId)
|
||||||
.pipe(getOrganizationById(params.organizationId)),
|
.pipe(getOrganizationById(params.organizationId)),
|
||||||
);
|
);
|
||||||
this.manageableCiphers = await this.cipherService.getAll();
|
this.manageableCiphers = await this.cipherService.getAll(userId);
|
||||||
await super.ngOnInit();
|
await super.ngOnInit();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,9 @@ export class ChangePasswordComponent
|
|||||||
|
|
||||||
async rotateUserKeyClicked() {
|
async rotateUserKeyClicked() {
|
||||||
if (this.rotateUserKey) {
|
if (this.rotateUserKey) {
|
||||||
const ciphers = await this.cipherService.getAllDecrypted();
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|
||||||
|
const ciphers = await this.cipherService.getAllDecrypted(activeUserId);
|
||||||
let hasOldAttachments = false;
|
let hasOldAttachments = false;
|
||||||
if (ciphers != null) {
|
if (ciphers != null) {
|
||||||
for (let i = 0; i < ciphers.length; i++) {
|
for (let i = 0; i < ciphers.length; i++) {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Directive, ViewChild, ViewContainerRef, OnDestroy } from "@angular/core";
|
import { Directive, ViewChild, ViewContainerRef, OnDestroy } from "@angular/core";
|
||||||
import { BehaviorSubject, Observable, Subject, switchMap, takeUntil } from "rxjs";
|
import { BehaviorSubject, Observable, Subject, firstValueFrom, switchMap, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
@@ -51,8 +52,10 @@ export class CipherReportComponent implements OnDestroy {
|
|||||||
private syncService: SyncService,
|
private syncService: SyncService,
|
||||||
) {
|
) {
|
||||||
this.organizations$ = this.accountService.activeAccount$.pipe(
|
this.organizations$ = this.accountService.activeAccount$.pipe(
|
||||||
switchMap((account) => this.organizationService.organizations$(account?.id)),
|
getUserId,
|
||||||
|
switchMap((userId) => this.organizationService.organizations$(userId)),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.organizations$.pipe(takeUntil(this.destroyed$)).subscribe((orgs) => {
|
this.organizations$.pipe(takeUntil(this.destroyed$)).subscribe((orgs) => {
|
||||||
this.organizations = orgs;
|
this.organizations = orgs;
|
||||||
});
|
});
|
||||||
@@ -182,7 +185,8 @@ export class CipherReportComponent implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async getAllCiphers(): Promise<CipherView[]> {
|
protected async getAllCiphers(): Promise<CipherView[]> {
|
||||||
return await this.cipherService.getAllDecrypted();
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
return await this.cipherService.getAllDecrypted(activeUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected filterCiphersByOrg(ciphersList: CipherView[]) {
|
protected filterCiphersByOrg(ciphersList: CipherView[]) {
|
||||||
|
|||||||
@@ -25,15 +25,14 @@ describe("ExposedPasswordsReportComponent", () => {
|
|||||||
let auditService: MockProxy<AuditService>;
|
let auditService: MockProxy<AuditService>;
|
||||||
let organizationService: MockProxy<OrganizationService>;
|
let organizationService: MockProxy<OrganizationService>;
|
||||||
let syncServiceMock: MockProxy<SyncService>;
|
let syncServiceMock: MockProxy<SyncService>;
|
||||||
let accountService: FakeAccountService;
|
|
||||||
const userId = Utils.newGuid() as UserId;
|
const userId = Utils.newGuid() as UserId;
|
||||||
|
const accountService: FakeAccountService = mockAccountServiceWith(userId);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
syncServiceMock = mock<SyncService>();
|
syncServiceMock = mock<SyncService>();
|
||||||
auditService = mock<AuditService>();
|
auditService = mock<AuditService>();
|
||||||
organizationService = mock<OrganizationService>();
|
organizationService = mock<OrganizationService>();
|
||||||
organizationService.organizations$.mockReturnValue(of([]));
|
organizationService.organizations$.mockReturnValue(of([]));
|
||||||
accountService = mockAccountServiceWith(userId);
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
|
|||||||
@@ -24,14 +24,13 @@ describe("InactiveTwoFactorReportComponent", () => {
|
|||||||
let fixture: ComponentFixture<InactiveTwoFactorReportComponent>;
|
let fixture: ComponentFixture<InactiveTwoFactorReportComponent>;
|
||||||
let organizationService: MockProxy<OrganizationService>;
|
let organizationService: MockProxy<OrganizationService>;
|
||||||
let syncServiceMock: MockProxy<SyncService>;
|
let syncServiceMock: MockProxy<SyncService>;
|
||||||
let accountService: FakeAccountService;
|
|
||||||
const userId = Utils.newGuid() as UserId;
|
const userId = Utils.newGuid() as UserId;
|
||||||
|
const accountService: FakeAccountService = mockAccountServiceWith(userId);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
organizationService = mock<OrganizationService>();
|
organizationService = mock<OrganizationService>();
|
||||||
organizationService.organizations$.mockReturnValue(of([]));
|
organizationService.organizations$.mockReturnValue(of([]));
|
||||||
syncServiceMock = mock<SyncService>();
|
syncServiceMock = mock<SyncService>();
|
||||||
accountService = mockAccountServiceWith(userId);
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
|
|||||||
@@ -23,15 +23,13 @@ describe("ReusedPasswordsReportComponent", () => {
|
|||||||
let fixture: ComponentFixture<ReusedPasswordsReportComponent>;
|
let fixture: ComponentFixture<ReusedPasswordsReportComponent>;
|
||||||
let organizationService: MockProxy<OrganizationService>;
|
let organizationService: MockProxy<OrganizationService>;
|
||||||
let syncServiceMock: MockProxy<SyncService>;
|
let syncServiceMock: MockProxy<SyncService>;
|
||||||
let accountService: FakeAccountService;
|
|
||||||
const userId = Utils.newGuid() as UserId;
|
const userId = Utils.newGuid() as UserId;
|
||||||
|
const accountService: FakeAccountService = mockAccountServiceWith(userId);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
organizationService = mock<OrganizationService>();
|
organizationService = mock<OrganizationService>();
|
||||||
organizationService.organizations$.mockReturnValue(of([]));
|
organizationService.organizations$.mockReturnValue(of([]));
|
||||||
syncServiceMock = mock<SyncService>();
|
syncServiceMock = mock<SyncService>();
|
||||||
accountService = mockAccountServiceWith(userId);
|
|
||||||
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
|
|||||||
@@ -25,15 +25,14 @@ describe("UnsecuredWebsitesReportComponent", () => {
|
|||||||
let organizationService: MockProxy<OrganizationService>;
|
let organizationService: MockProxy<OrganizationService>;
|
||||||
let syncServiceMock: MockProxy<SyncService>;
|
let syncServiceMock: MockProxy<SyncService>;
|
||||||
let collectionService: MockProxy<CollectionService>;
|
let collectionService: MockProxy<CollectionService>;
|
||||||
let accountService: FakeAccountService;
|
|
||||||
const userId = Utils.newGuid() as UserId;
|
const userId = Utils.newGuid() as UserId;
|
||||||
|
const accountService: FakeAccountService = mockAccountServiceWith(userId);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
organizationService = mock<OrganizationService>();
|
organizationService = mock<OrganizationService>();
|
||||||
organizationService.organizations$.mockReturnValue(of([]));
|
organizationService.organizations$.mockReturnValue(of([]));
|
||||||
syncServiceMock = mock<SyncService>();
|
syncServiceMock = mock<SyncService>();
|
||||||
collectionService = mock<CollectionService>();
|
collectionService = mock<CollectionService>();
|
||||||
accountService = mockAccountServiceWith(userId);
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
|
|||||||
@@ -25,15 +25,14 @@ describe("WeakPasswordsReportComponent", () => {
|
|||||||
let passwordStrengthService: MockProxy<PasswordStrengthServiceAbstraction>;
|
let passwordStrengthService: MockProxy<PasswordStrengthServiceAbstraction>;
|
||||||
let organizationService: MockProxy<OrganizationService>;
|
let organizationService: MockProxy<OrganizationService>;
|
||||||
let syncServiceMock: MockProxy<SyncService>;
|
let syncServiceMock: MockProxy<SyncService>;
|
||||||
let accountService: FakeAccountService;
|
|
||||||
const userId = Utils.newGuid() as UserId;
|
const userId = Utils.newGuid() as UserId;
|
||||||
|
const accountService: FakeAccountService = mockAccountServiceWith(userId);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
syncServiceMock = mock<SyncService>();
|
syncServiceMock = mock<SyncService>();
|
||||||
passwordStrengthService = mock<PasswordStrengthServiceAbstraction>();
|
passwordStrengthService = mock<PasswordStrengthServiceAbstraction>();
|
||||||
organizationService = mock<OrganizationService>();
|
organizationService = mock<OrganizationService>();
|
||||||
organizationService.organizations$.mockReturnValue(of([]));
|
organizationService.organizations$.mockReturnValue(of([]));
|
||||||
accountService = mockAccountServiceWith(userId);
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||||
import { EventType } from "@bitwarden/common/enums";
|
import { EventType } from "@bitwarden/common/enums";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
@@ -355,8 +356,8 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
|
|||||||
this.formConfig.mode = "edit";
|
this.formConfig.mode = "edit";
|
||||||
this.formConfig.initialValues = null;
|
this.formConfig.initialValues = null;
|
||||||
}
|
}
|
||||||
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
let cipher = await this.cipherService.get(cipherView.id);
|
let cipher = await this.cipherService.get(cipherView.id, activeUserId);
|
||||||
|
|
||||||
// When the form config is used within the Admin Console, retrieve the cipher from the admin endpoint (if not found in local state)
|
// When the form config is used within the Admin Console, retrieve the cipher from the admin endpoint (if not found in local state)
|
||||||
if (this.formConfig.isAdminConsole && (cipher == null || this.formConfig.admin)) {
|
if (this.formConfig.isAdminConsole && (cipher == null || this.formConfig.admin)) {
|
||||||
@@ -448,10 +449,13 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
|
|||||||
result.action === AttachmentDialogResult.Removed ||
|
result.action === AttachmentDialogResult.Removed ||
|
||||||
result.action === AttachmentDialogResult.Uploaded
|
result.action === AttachmentDialogResult.Uploaded
|
||||||
) {
|
) {
|
||||||
const updatedCipher = await this.cipherService.get(this.formConfig.originalCipher?.id);
|
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||||
);
|
);
|
||||||
|
const updatedCipher = await this.cipherService.get(
|
||||||
|
this.formConfig.originalCipher?.id,
|
||||||
|
activeUserId,
|
||||||
|
);
|
||||||
|
|
||||||
const updatedCipherView = await updatedCipher.decrypt(
|
const updatedCipherView = await updatedCipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId),
|
||||||
@@ -490,9 +494,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
|
|||||||
if (config.originalCipher == null) {
|
if (config.originalCipher == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
return await config.originalCipher.decrypt(
|
return await config.originalCipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(config.originalCipher, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(config.originalCipher, activeUserId),
|
||||||
);
|
);
|
||||||
@@ -574,10 +576,14 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
|
|||||||
// - The cipher is unassigned
|
// - The cipher is unassigned
|
||||||
const asAdmin = this.organization?.canEditAllCiphers || cipherIsUnassigned;
|
const asAdmin = this.organization?.canEditAllCiphers || cipherIsUnassigned;
|
||||||
|
|
||||||
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
||||||
|
);
|
||||||
|
|
||||||
if (this.cipher.isDeleted) {
|
if (this.cipher.isDeleted) {
|
||||||
await this.cipherService.deleteWithServer(this.cipher.id, asAdmin);
|
await this.cipherService.deleteWithServer(this.cipher.id, activeUserId, asAdmin);
|
||||||
} else {
|
} else {
|
||||||
await this.cipherService.softDeleteWithServer(this.cipher.id, asAdmin);
|
await this.cipherService.softDeleteWithServer(this.cipher.id, activeUserId, asAdmin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,13 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||||
import { Component, Inject } from "@angular/core";
|
import { Component, Inject } from "@angular/core";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
|
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
@@ -61,6 +64,7 @@ export class BulkDeleteDialogComponent {
|
|||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private collectionService: CollectionService,
|
private collectionService: CollectionService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
|
private accountService: AccountService,
|
||||||
) {
|
) {
|
||||||
this.cipherIds = params.cipherIds ?? [];
|
this.cipherIds = params.cipherIds ?? [];
|
||||||
this.permanent = params.permanent;
|
this.permanent = params.permanent;
|
||||||
@@ -115,10 +119,12 @@ export class BulkDeleteDialogComponent {
|
|||||||
|
|
||||||
private async deleteCiphers(): Promise<any> {
|
private async deleteCiphers(): Promise<any> {
|
||||||
const asAdmin = this.organization?.canEditAllCiphers;
|
const asAdmin = this.organization?.canEditAllCiphers;
|
||||||
|
|
||||||
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
if (this.permanent) {
|
if (this.permanent) {
|
||||||
await this.cipherService.deleteManyWithServer(this.cipherIds, asAdmin);
|
await this.cipherService.deleteManyWithServer(this.cipherIds, activeUserId, asAdmin);
|
||||||
} else {
|
} else {
|
||||||
await this.cipherService.softDeleteManyWithServer(this.cipherIds, asAdmin);
|
await this.cipherService.softDeleteManyWithServer(this.cipherIds, activeUserId, asAdmin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,10 @@
|
|||||||
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||||
import { Component, Inject, OnInit } from "@angular/core";
|
import { Component, Inject, OnInit } from "@angular/core";
|
||||||
import { FormBuilder, Validators } from "@angular/forms";
|
import { FormBuilder, Validators } from "@angular/forms";
|
||||||
import { firstValueFrom, map, Observable } from "rxjs";
|
import { firstValueFrom, Observable } from "rxjs";
|
||||||
|
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
@@ -48,8 +49,6 @@ export class BulkMoveDialogComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
folders$: Observable<FolderView[]>;
|
folders$: Observable<FolderView[]>;
|
||||||
|
|
||||||
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DIALOG_DATA) params: BulkMoveDialogParams,
|
@Inject(DIALOG_DATA) params: BulkMoveDialogParams,
|
||||||
private dialogRef: DialogRef<BulkMoveDialogResult>,
|
private dialogRef: DialogRef<BulkMoveDialogResult>,
|
||||||
@@ -65,7 +64,7 @@ export class BulkMoveDialogComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
this.folders$ = this.folderService.folderViews$(activeUserId);
|
this.folders$ = this.folderService.folderViews$(activeUserId);
|
||||||
this.formGroup.patchValue({
|
this.formGroup.patchValue({
|
||||||
folderId: (await firstValueFrom(this.folders$))[0].id,
|
folderId: (await firstValueFrom(this.folders$))[0].id,
|
||||||
@@ -81,7 +80,12 @@ export class BulkMoveDialogComponent implements OnInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.cipherService.moveManyWithServer(this.cipherIds, this.formGroup.value.folderId);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
await this.cipherService.moveManyWithServer(
|
||||||
|
this.cipherIds,
|
||||||
|
this.formGroup.value.folderId,
|
||||||
|
activeUserId,
|
||||||
|
);
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
title: null,
|
title: null,
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ describe("vault filter service", () => {
|
|||||||
policyService.policyAppliesToActiveUser$
|
policyService.policyAppliesToActiveUser$
|
||||||
.calledWith(PolicyType.SingleOrg)
|
.calledWith(PolicyType.SingleOrg)
|
||||||
.mockReturnValue(singleOrgPolicy);
|
.mockReturnValue(singleOrgPolicy);
|
||||||
cipherService.cipherViews$ = cipherViews;
|
cipherService.cipherViews$.mockReturnValue(cipherViews);
|
||||||
|
|
||||||
vaultFilterService = new VaultFilterService(
|
vaultFilterService = new VaultFilterService(
|
||||||
organizationService,
|
organizationService,
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
|
|||||||
switchMap((userId) =>
|
switchMap((userId) =>
|
||||||
combineLatest([
|
combineLatest([
|
||||||
this.folderService.folderViews$(userId),
|
this.folderService.folderViews$(userId),
|
||||||
this.cipherService.cipherViews$,
|
this.cipherService.cipherViews$(userId),
|
||||||
this._organizationFilter,
|
this._organizationFilter,
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import {
|
|||||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions";
|
import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||||
@@ -166,7 +167,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
protected selectedCollection: TreeNode<CollectionView> | undefined;
|
protected selectedCollection: TreeNode<CollectionView> | undefined;
|
||||||
protected canCreateCollections = false;
|
protected canCreateCollections = false;
|
||||||
protected currentSearchText$: Observable<string>;
|
protected currentSearchText$: Observable<string>;
|
||||||
private activeUserId: UserId;
|
|
||||||
private searchText$ = new Subject<string>();
|
private searchText$ = new Subject<string>();
|
||||||
private refresh$ = new BehaviorSubject<void>(null);
|
private refresh$ = new BehaviorSubject<void>(null);
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
@@ -271,9 +271,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
: "trashCleanupWarning",
|
: "trashCleanupWarning",
|
||||||
);
|
);
|
||||||
|
|
||||||
this.activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
const firstSetup$ = this.route.queryParams.pipe(
|
const firstSetup$ = this.route.queryParams.pipe(
|
||||||
first(),
|
first(),
|
||||||
@@ -337,13 +335,15 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search));
|
this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search));
|
||||||
|
|
||||||
const ciphers$ = combineLatest([
|
const ciphers$ = combineLatest([
|
||||||
this.cipherService.cipherViews$.pipe(filter((c) => c !== null)),
|
this.cipherService.cipherViews$(activeUserId).pipe(filter((c) => c !== null)),
|
||||||
filter$,
|
filter$,
|
||||||
this.currentSearchText$,
|
this.currentSearchText$,
|
||||||
]).pipe(
|
]).pipe(
|
||||||
filter(([ciphers, filter]) => ciphers != undefined && filter != undefined),
|
filter(([ciphers, filter]) => ciphers != undefined && filter != undefined),
|
||||||
concatMap(async ([ciphers, filter, searchText]) => {
|
concatMap(async ([ciphers, filter, searchText]) => {
|
||||||
const failedCiphers = await firstValueFrom(this.cipherService.failedToDecryptCiphers$);
|
const failedCiphers = await firstValueFrom(
|
||||||
|
this.cipherService.failedToDecryptCiphers$(activeUserId),
|
||||||
|
);
|
||||||
const filterFunction = createFilterFunction(filter);
|
const filterFunction = createFilterFunction(filter);
|
||||||
// Append any failed to decrypt ciphers to the top of the cipher list
|
// Append any failed to decrypt ciphers to the top of the cipher list
|
||||||
const allCiphers = [...failedCiphers, ...ciphers];
|
const allCiphers = [...failedCiphers, ...ciphers];
|
||||||
@@ -416,7 +416,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
switchMap(async (params) => {
|
switchMap(async (params) => {
|
||||||
const cipherId = getCipherIdFromParams(params);
|
const cipherId = getCipherIdFromParams(params);
|
||||||
if (cipherId) {
|
if (cipherId) {
|
||||||
if (await this.cipherService.get(cipherId)) {
|
if (await this.cipherService.get(cipherId, activeUserId)) {
|
||||||
let action = params.action;
|
let action = params.action;
|
||||||
// Default to "view"
|
// Default to "view"
|
||||||
if (action == null) {
|
if (action == null) {
|
||||||
@@ -459,7 +459,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
firstSetup$
|
firstSetup$
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(() => this.cipherService.failedToDecryptCiphers$),
|
switchMap(() => this.cipherService.failedToDecryptCiphers$(activeUserId)),
|
||||||
map((ciphers) => ciphers.filter((c) => !c.isDeleted)),
|
map((ciphers) => ciphers.filter((c) => !c.isDeleted)),
|
||||||
filter((ciphers) => ciphers.length > 0),
|
filter((ciphers) => ciphers.length > 0),
|
||||||
take(1),
|
take(1),
|
||||||
@@ -480,7 +480,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
switchMap(() =>
|
switchMap(() =>
|
||||||
combineLatest([
|
combineLatest([
|
||||||
filter$,
|
filter$,
|
||||||
this.billingAccountProfileStateService.hasPremiumFromAnySource$(this.activeUserId),
|
this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId),
|
||||||
allCollections$,
|
allCollections$,
|
||||||
this.organizations$,
|
this.organizations$,
|
||||||
ciphers$,
|
ciphers$,
|
||||||
@@ -732,7 +732,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async editCipherId(id: string, cloneMode?: boolean) {
|
async editCipherId(id: string, cloneMode?: boolean) {
|
||||||
const cipher = await this.cipherService.get(id);
|
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
|
const cipher = await this.cipherService.get(id, activeUserId);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
cipher &&
|
cipher &&
|
||||||
@@ -768,7 +769,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
* @returns Promise<void>
|
* @returns Promise<void>
|
||||||
*/
|
*/
|
||||||
async viewCipherById(id: string) {
|
async viewCipherById(id: string) {
|
||||||
const cipher = await this.cipherService.get(id);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
const cipher = await this.cipherService.get(id, activeUserId);
|
||||||
// If cipher exists (cipher is null when new) and MP reprompt
|
// If cipher exists (cipher is null when new) and MP reprompt
|
||||||
// is on for this cipher, then show password reprompt.
|
// is on for this cipher, then show password reprompt.
|
||||||
if (
|
if (
|
||||||
@@ -959,7 +961,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.cipherService.restoreWithServer(c.id);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
await this.cipherService.restoreWithServer(c.id, activeUserId);
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
title: null,
|
title: null,
|
||||||
@@ -1041,7 +1044,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.deleteCipherWithServer(c.id, permanent);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
await this.deleteCipherWithServer(c.id, activeUserId, permanent);
|
||||||
|
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
@@ -1176,10 +1180,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected deleteCipherWithServer(id: string, permanent: boolean) {
|
protected deleteCipherWithServer(id: string, userId: UserId, permanent: boolean) {
|
||||||
return permanent
|
return permanent
|
||||||
? this.cipherService.deleteWithServer(id)
|
? this.cipherService.deleteWithServer(id, userId)
|
||||||
: this.cipherService.softDeleteWithServer(id);
|
: this.cipherService.softDeleteWithServer(id, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async repromptCipher(ciphers: CipherView[]) {
|
protected async repromptCipher(ciphers: CipherView[]) {
|
||||||
|
|||||||
@@ -165,10 +165,11 @@ export class ViewComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
protected async deleteCipher(): Promise<void> {
|
protected async deleteCipher(): Promise<void> {
|
||||||
const asAdmin = this.organization?.canEditAllCiphers;
|
const asAdmin = this.organization?.canEditAllCiphers;
|
||||||
|
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
if (this.cipher.isDeleted) {
|
if (this.cipher.isDeleted) {
|
||||||
await this.cipherService.deleteWithServer(this.cipher.id, asAdmin);
|
await this.cipherService.deleteWithServer(this.cipher.id, userId, asAdmin);
|
||||||
} else {
|
} else {
|
||||||
await this.cipherService.softDeleteWithServer(this.cipher.id, asAdmin);
|
await this.cipherService.softDeleteWithServer(this.cipher.id, userId, asAdmin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { DatePipe } from "@angular/common";
|
import { DatePipe } from "@angular/common";
|
||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
@@ -10,6 +11,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
|
|||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
@@ -98,8 +100,9 @@ export class AddEditComponent extends BaseAddEditComponent {
|
|||||||
|
|
||||||
protected async loadCipher() {
|
protected async loadCipher() {
|
||||||
this.isAdminConsoleAction = true;
|
this.isAdminConsoleAction = true;
|
||||||
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
// Calling loadCipher first to assess if the cipher is unassigned. If null use apiService getCipherAdmin
|
// Calling loadCipher first to assess if the cipher is unassigned. If null use apiService getCipherAdmin
|
||||||
const firstCipherCheck = await super.loadCipher();
|
const firstCipherCheck = await super.loadCipher(activeUserId);
|
||||||
|
|
||||||
if (!this.organization.canEditAllCiphers && firstCipherCheck != null) {
|
if (!this.organization.canEditAllCiphers && firstCipherCheck != null) {
|
||||||
return firstCipherCheck;
|
return firstCipherCheck;
|
||||||
@@ -123,7 +126,8 @@ export class AddEditComponent extends BaseAddEditComponent {
|
|||||||
|
|
||||||
protected async deleteCipher() {
|
protected async deleteCipher() {
|
||||||
if (!this.organization.canEditAllCiphers) {
|
if (!this.organization.canEditAllCiphers) {
|
||||||
return super.deleteCipher();
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
return super.deleteCipher(activeUserId);
|
||||||
}
|
}
|
||||||
return this.cipher.isDeleted
|
return this.cipher.isDeleted
|
||||||
? this.apiService.deleteCipherAdmin(this.cipherId)
|
? this.apiService.deleteCipherAdmin(this.cipherId)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||||
@@ -74,7 +76,8 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
|
|||||||
|
|
||||||
protected async loadCipher() {
|
protected async loadCipher() {
|
||||||
if (!this.organization.canEditAllCiphers) {
|
if (!this.organization.canEditAllCiphers) {
|
||||||
return await super.loadCipher();
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
return await super.loadCipher(activeUserId);
|
||||||
}
|
}
|
||||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||||
return new Cipher(new CipherData(response));
|
return new Cipher(new CipherData(response));
|
||||||
@@ -89,9 +92,9 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected deleteCipherAttachment(attachmentId: string) {
|
protected deleteCipherAttachment(attachmentId: string, userId: UserId) {
|
||||||
if (!this.organization.canEditAllCiphers) {
|
if (!this.organization.canEditAllCiphers) {
|
||||||
return super.deleteCipherAttachment(attachmentId);
|
return super.deleteCipherAttachment(attachmentId, userId);
|
||||||
}
|
}
|
||||||
return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId);
|
return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
|
|||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
|
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||||
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ describe("AdminConsoleCipherFormConfigService", () => {
|
|||||||
isMember: true,
|
isMember: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
status: OrganizationUserStatusType.Confirmed,
|
status: OrganizationUserStatusType.Confirmed,
|
||||||
|
userId: "UserId",
|
||||||
};
|
};
|
||||||
const testOrg2 = {
|
const testOrg2 = {
|
||||||
id: "333-999-888",
|
id: "333-999-888",
|
||||||
@@ -34,6 +36,7 @@ describe("AdminConsoleCipherFormConfigService", () => {
|
|||||||
isMember: true,
|
isMember: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
status: OrganizationUserStatusType.Confirmed,
|
status: OrganizationUserStatusType.Confirmed,
|
||||||
|
userId: "UserId",
|
||||||
};
|
};
|
||||||
const policyAppliesToActiveUser$ = new BehaviorSubject<boolean>(true);
|
const policyAppliesToActiveUser$ = new BehaviorSubject<boolean>(true);
|
||||||
const collection = {
|
const collection = {
|
||||||
@@ -80,17 +83,7 @@ describe("AdminConsoleCipherFormConfigService", () => {
|
|||||||
},
|
},
|
||||||
{ provide: ApiService, useValue: { getCipherAdmin } },
|
{ provide: ApiService, useValue: { getCipherAdmin } },
|
||||||
{ provide: CipherService, useValue: { get: getCipher } },
|
{ provide: CipherService, useValue: { get: getCipher } },
|
||||||
{
|
{ provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) },
|
||||||
provide: AccountService,
|
|
||||||
useValue: {
|
|
||||||
activeAccount$: new BehaviorSubject<Account>({
|
|
||||||
id: "123-456-789" as UserId,
|
|
||||||
email: "test@email.com",
|
|
||||||
emailVerified: true,
|
|
||||||
name: "Test User",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService);
|
adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService);
|
||||||
@@ -207,7 +200,7 @@ describe("AdminConsoleCipherFormConfigService", () => {
|
|||||||
await adminConsoleConfigService.buildConfig("edit", cipherId);
|
await adminConsoleConfigService.buildConfig("edit", cipherId);
|
||||||
|
|
||||||
expect(getCipherAdmin).not.toHaveBeenCalled();
|
expect(getCipherAdmin).not.toHaveBeenCalled();
|
||||||
expect(getCipher).toHaveBeenCalledWith(cipherId);
|
expect(getCipher).toHaveBeenCalledWith(cipherId, "UserId");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
|||||||
import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums";
|
import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.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 { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
|
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
|
||||||
@@ -100,7 +100,7 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const localCipher = await this.cipherService.get(id);
|
const localCipher = await this.cipherService.get(id, organization.userId as UserId);
|
||||||
|
|
||||||
// Fetch from the API because we don't need the permissions in local state OR the cipher was not found (e.g. unassigned)
|
// Fetch from the API because we don't need the permissions in local state OR the cipher was not found (e.g. unassigned)
|
||||||
if (organization.canEditAllCiphers || localCipher == null) {
|
if (organization.canEditAllCiphers || localCipher == null) {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
|
|||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions";
|
import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions";
|
||||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||||
import { EventType } from "@bitwarden/common/enums";
|
import { EventType } from "@bitwarden/common/enums";
|
||||||
@@ -52,7 +53,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
|||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||||
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 { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
@@ -952,8 +953,9 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
// Allow restore of an Unassigned Item
|
// Allow restore of an Unassigned Item
|
||||||
try {
|
try {
|
||||||
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
const asAdmin = this.organization?.canEditAnyCollection || c.isUnassigned;
|
const asAdmin = this.organization?.canEditAnyCollection || c.isUnassigned;
|
||||||
await this.cipherService.restoreWithServer(c.id, asAdmin);
|
await this.cipherService.restoreWithServer(c.id, activeUserId, asAdmin);
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
title: null,
|
title: null,
|
||||||
@@ -1044,7 +1046,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.deleteCipherWithServer(c.id, permanent, c.isUnassigned);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
await this.deleteCipherWithServer(c.id, activeUserId, permanent, c.isUnassigned);
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
title: null,
|
title: null,
|
||||||
@@ -1332,11 +1335,16 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected deleteCipherWithServer(id: string, permanent: boolean, isUnassigned: boolean) {
|
protected deleteCipherWithServer(
|
||||||
|
id: string,
|
||||||
|
userId: UserId,
|
||||||
|
permanent: boolean,
|
||||||
|
isUnassigned: boolean,
|
||||||
|
) {
|
||||||
const asAdmin = this.organization?.canEditAllCiphers || isUnassigned;
|
const asAdmin = this.organization?.canEditAllCiphers || isUnassigned;
|
||||||
return permanent
|
return permanent
|
||||||
? this.cipherService.deleteWithServer(id, asAdmin)
|
? this.cipherService.deleteWithServer(id, userId, asAdmin)
|
||||||
: this.cipherService.softDeleteWithServer(id, asAdmin);
|
: this.cipherService.softDeleteWithServer(id, userId, asAdmin);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async repromptCipher(ciphers: CipherView[]) {
|
protected async repromptCipher(ciphers: CipherView[]) {
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import { CollectionService, CollectionView } from "@bitwarden/admin-console/comm
|
|||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
@@ -45,11 +47,9 @@ export class CollectionsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
this.cipherDomain = await this.loadCipher();
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
this.cipherDomain = await this.loadCipher(activeUserId);
|
||||||
this.collectionIds = this.loadCipherCollections();
|
this.collectionIds = this.loadCipherCollections();
|
||||||
const activeUserId = await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
this.cipher = await this.cipherDomain.decrypt(
|
this.cipher = await this.cipherDomain.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId),
|
||||||
);
|
);
|
||||||
@@ -95,7 +95,8 @@ export class CollectionsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
this.cipherDomain.collectionIds = selectedCollectionIds;
|
this.cipherDomain.collectionIds = selectedCollectionIds;
|
||||||
try {
|
try {
|
||||||
this.formPromise = this.saveCollections();
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
this.formPromise = this.saveCollections(activeUserId);
|
||||||
await this.formPromise;
|
await this.formPromise;
|
||||||
this.onSavedCollections.emit();
|
this.onSavedCollections.emit();
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
@@ -114,8 +115,8 @@ export class CollectionsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected loadCipher() {
|
protected loadCipher(userId: UserId) {
|
||||||
return this.cipherService.get(this.cipherId);
|
return this.cipherService.get(this.cipherId, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected loadCipherCollections() {
|
protected loadCipherCollections() {
|
||||||
@@ -129,7 +130,7 @@ export class CollectionsComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected saveCollections() {
|
protected saveCollections(userId: UserId) {
|
||||||
return this.cipherService.saveCollectionsWithServer(this.cipherDomain);
|
return this.cipherService.saveCollectionsWithServer(this.cipherDomain, userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
|
|||||||
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
|
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
@@ -73,10 +74,8 @@ export class ShareComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const cipherDomain = await this.cipherService.get(this.cipherId);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
const activeUserId = await firstValueFrom(
|
const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId);
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
this.cipher = await cipherDomain.decrypt(
|
this.cipher = await cipherDomain.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId),
|
||||||
);
|
);
|
||||||
@@ -104,10 +103,8 @@ export class ShareComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cipherDomain = await this.cipherService.get(this.cipherId);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
const activeUserId = await firstValueFrom(
|
const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId);
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
const cipherView = await cipherDomain.decrypt(
|
const cipherView = await cipherDomain.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
|||||||
import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums";
|
import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils";
|
import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils";
|
||||||
import { EventType } from "@bitwarden/common/enums";
|
import { EventType } from "@bitwarden/common/enums";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
@@ -101,8 +102,6 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
|||||||
private personalOwnershipPolicyAppliesToActiveUser: boolean;
|
private personalOwnershipPolicyAppliesToActiveUser: boolean;
|
||||||
private previousCipherId: string;
|
private previousCipherId: string;
|
||||||
|
|
||||||
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
|
|
||||||
|
|
||||||
get fido2CredentialCreationDateValue(): string {
|
get fido2CredentialCreationDateValue(): string {
|
||||||
const dateCreated = this.i18nService.t("dateCreated");
|
const dateCreated = this.i18nService.t("dateCreated");
|
||||||
const creationDate = this.datePipe.transform(
|
const creationDate = this.datePipe.transform(
|
||||||
@@ -263,12 +262,13 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
|||||||
this.title = this.i18nService.t("addItem");
|
this.title = this.i18nService.t("addItem");
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadedAddEditCipherInfo = await this.loadAddEditCipherInfo();
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|
||||||
|
const loadedAddEditCipherInfo = await this.loadAddEditCipherInfo(activeUserId);
|
||||||
|
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
|
||||||
if (this.cipher == null) {
|
if (this.cipher == null) {
|
||||||
if (this.editMode) {
|
if (this.editMode) {
|
||||||
const cipher = await this.loadCipher();
|
const cipher = await this.loadCipher(activeUserId);
|
||||||
this.cipher = await cipher.decrypt(
|
this.cipher = await cipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||||
);
|
);
|
||||||
@@ -420,9 +420,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
|||||||
this.cipher.id = null;
|
this.cipher.id = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
const cipher = await this.encryptCipher(activeUserId);
|
const cipher = await this.encryptCipher(activeUserId);
|
||||||
try {
|
try {
|
||||||
this.formPromise = this.saveCipher(cipher);
|
this.formPromise = this.saveCipher(cipher);
|
||||||
@@ -516,7 +514,8 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.deletePromise = this.deleteCipher();
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
this.deletePromise = this.deleteCipher(activeUserId);
|
||||||
await this.deletePromise;
|
await this.deletePromise;
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
@@ -542,7 +541,8 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.restorePromise = this.restoreCipher();
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
this.restorePromise = this.restoreCipher(activeUserId);
|
||||||
await this.restorePromise;
|
await this.restorePromise;
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
@@ -725,8 +725,8 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
|||||||
return allCollections.filter((c) => !c.readOnly);
|
return allCollections.filter((c) => !c.readOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected loadCipher() {
|
protected loadCipher(userId: UserId) {
|
||||||
return this.cipherService.get(this.cipherId);
|
return this.cipherService.get(this.cipherId, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected encryptCipher(userId: UserId) {
|
protected encryptCipher(userId: UserId) {
|
||||||
@@ -746,14 +746,14 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
|||||||
: this.cipherService.updateWithServer(cipher, orgAdmin);
|
: this.cipherService.updateWithServer(cipher, orgAdmin);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected deleteCipher() {
|
protected deleteCipher(userId: UserId) {
|
||||||
return this.cipher.isDeleted
|
return this.cipher.isDeleted
|
||||||
? this.cipherService.deleteWithServer(this.cipher.id, this.asAdmin)
|
? this.cipherService.deleteWithServer(this.cipher.id, userId, this.asAdmin)
|
||||||
: this.cipherService.softDeleteWithServer(this.cipher.id, this.asAdmin);
|
: this.cipherService.softDeleteWithServer(this.cipher.id, userId, this.asAdmin);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected restoreCipher() {
|
protected restoreCipher(userId: UserId) {
|
||||||
return this.cipherService.restoreWithServer(this.cipher.id, this.asAdmin);
|
return this.cipherService.restoreWithServer(this.cipher.id, userId, this.asAdmin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -773,8 +773,10 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
|||||||
return this.ownershipOptions[0].value;
|
return this.ownershipOptions[0].value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadAddEditCipherInfo(): Promise<boolean> {
|
async loadAddEditCipherInfo(userId: UserId): Promise<boolean> {
|
||||||
const addEditCipherInfo: any = await firstValueFrom(this.cipherService.addEditCipherInfo$);
|
const addEditCipherInfo: any = await firstValueFrom(
|
||||||
|
this.cipherService.addEditCipherInfo$(userId),
|
||||||
|
);
|
||||||
const loadedSavedInfo = addEditCipherInfo != null;
|
const loadedSavedInfo = addEditCipherInfo != null;
|
||||||
|
|
||||||
if (loadedSavedInfo) {
|
if (loadedSavedInfo) {
|
||||||
@@ -787,7 +789,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.cipherService.setAddEditCipherInfo(null);
|
await this.cipherService.setAddEditCipherInfo(null, userId);
|
||||||
|
|
||||||
return loadedSavedInfo;
|
return loadedSavedInfo;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||||
import { firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||||
@@ -84,9 +85,7 @@ export class AttachmentsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
this.formPromise = this.saveCipherAttachment(files[0], activeUserId);
|
this.formPromise = this.saveCipherAttachment(files[0], activeUserId);
|
||||||
this.cipherDomain = await this.formPromise;
|
this.cipherDomain = await this.formPromise;
|
||||||
this.cipher = await this.cipherDomain.decrypt(
|
this.cipher = await this.cipherDomain.decrypt(
|
||||||
@@ -125,12 +124,11 @@ export class AttachmentsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id);
|
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
|
|
||||||
|
this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id, activeUserId);
|
||||||
const updatedCipher = await this.deletePromises[attachment.id];
|
const updatedCipher = await this.deletePromises[attachment.id];
|
||||||
|
|
||||||
const activeUserId = await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
const cipher = new Cipher(updatedCipher);
|
const cipher = new Cipher(updatedCipher);
|
||||||
this.cipher = await cipher.decrypt(
|
this.cipher = await cipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||||
@@ -228,10 +226,8 @@ export class AttachmentsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async init() {
|
protected async init() {
|
||||||
this.cipherDomain = await this.loadCipher();
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
const activeUserId = await firstValueFrom(
|
this.cipherDomain = await this.loadCipher(activeUserId);
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
this.cipher = await this.cipherDomain.decrypt(
|
this.cipher = await this.cipherDomain.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId),
|
||||||
);
|
);
|
||||||
@@ -287,7 +283,7 @@ export class AttachmentsComponent implements OnInit {
|
|||||||
: await this.keyService.getOrgKey(this.cipher.organizationId);
|
: await this.keyService.getOrgKey(this.cipher.organizationId);
|
||||||
const decBuf = await this.encryptService.decryptToBytes(encBuf, key);
|
const decBuf = await this.encryptService.decryptToBytes(encBuf, key);
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
this.accountService.activeAccount$.pipe(getUserId),
|
||||||
);
|
);
|
||||||
this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer(
|
this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer(
|
||||||
this.cipherDomain,
|
this.cipherDomain,
|
||||||
@@ -301,7 +297,10 @@ export class AttachmentsComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 3. Delete old
|
// 3. Delete old
|
||||||
this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id);
|
this.deletePromises[attachment.id] = this.deleteCipherAttachment(
|
||||||
|
attachment.id,
|
||||||
|
activeUserId,
|
||||||
|
);
|
||||||
await this.deletePromises[attachment.id];
|
await this.deletePromises[attachment.id];
|
||||||
const foundAttachment = this.cipher.attachments.filter((a2) => a2.id === attachment.id);
|
const foundAttachment = this.cipher.attachments.filter((a2) => a2.id === attachment.id);
|
||||||
if (foundAttachment.length > 0) {
|
if (foundAttachment.length > 0) {
|
||||||
@@ -335,16 +334,16 @@ export class AttachmentsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected loadCipher() {
|
protected loadCipher(userId: UserId) {
|
||||||
return this.cipherService.get(this.cipherId);
|
return this.cipherService.get(this.cipherId, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected saveCipherAttachment(file: File, userId: UserId) {
|
protected saveCipherAttachment(file: File, userId: UserId) {
|
||||||
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, userId);
|
return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected deleteCipherAttachment(attachmentId: string) {
|
protected deleteCipherAttachment(attachmentId: string, userId: UserId) {
|
||||||
return this.cipherService.deleteAttachmentWithServer(this.cipher.id, attachmentId);
|
return this.cipherService.deleteAttachmentWithServer(this.cipher.id, attachmentId, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async reupload(attachment: AttachmentView) {
|
protected async reupload(attachment: AttachmentView) {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Directive, OnInit } from "@angular/core";
|
import { Directive, OnInit } from "@angular/core";
|
||||||
import { firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
@@ -39,10 +40,8 @@ export class PasswordHistoryComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async init() {
|
protected async init() {
|
||||||
const cipher = await this.cipherService.get(this.cipherId);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
const activeUserId = await firstValueFrom(
|
const cipher = await this.cipherService.get(this.cipherId, activeUserId);
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
const decCipher = await cipher.decrypt(
|
const decCipher = await cipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,10 +2,13 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
import { BehaviorSubject, Subject, firstValueFrom, from, switchMap, takeUntil } from "rxjs";
|
import { BehaviorSubject, Subject, firstValueFrom, from, map, switchMap, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
|
||||||
@@ -41,11 +44,20 @@ export class VaultItemsComponent implements OnInit, OnDestroy {
|
|||||||
constructor(
|
constructor(
|
||||||
protected searchService: SearchService,
|
protected searchService: SearchService,
|
||||||
protected cipherService: CipherService,
|
protected cipherService: CipherService,
|
||||||
|
protected accountService: AccountService,
|
||||||
) {
|
) {
|
||||||
this.cipherService.cipherViews$.pipe(takeUntilDestroyed()).subscribe((ciphers) => {
|
this.accountService.activeAccount$
|
||||||
void this.doSearch(ciphers);
|
.pipe(
|
||||||
this.loaded = true;
|
getUserId,
|
||||||
});
|
switchMap((userId) =>
|
||||||
|
this.cipherService.cipherViews$(userId).pipe(map((ciphers) => ({ userId, ciphers }))),
|
||||||
|
),
|
||||||
|
takeUntilDestroyed(),
|
||||||
|
)
|
||||||
|
.subscribe(({ userId, ciphers }) => {
|
||||||
|
void this.doSearch(ciphers, userId);
|
||||||
|
this.loaded = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@@ -122,10 +134,16 @@ export class VaultItemsComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
protected deletedFilter: (cipher: CipherView) => boolean = (c) => c.isDeleted === this.deleted;
|
protected deletedFilter: (cipher: CipherView) => boolean = (c) => c.isDeleted === this.deleted;
|
||||||
|
|
||||||
protected async doSearch(indexedCiphers?: CipherView[]) {
|
protected async doSearch(indexedCiphers?: CipherView[], userId?: UserId) {
|
||||||
indexedCiphers = indexedCiphers ?? (await firstValueFrom(this.cipherService.cipherViews$));
|
// Get userId from activeAccount if not provided from parent stream
|
||||||
|
if (!userId) {
|
||||||
|
userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
|
}
|
||||||
|
|
||||||
const failedCiphers = await firstValueFrom(this.cipherService.failedToDecryptCiphers$);
|
indexedCiphers =
|
||||||
|
indexedCiphers ?? (await firstValueFrom(this.cipherService.cipherViews$(userId)));
|
||||||
|
|
||||||
|
const failedCiphers = await firstValueFrom(this.cipherService.failedToDecryptCiphers$(userId));
|
||||||
if (failedCiphers != null && failedCiphers.length > 0) {
|
if (failedCiphers != null && failedCiphers.length > 0) {
|
||||||
indexedCiphers = [...failedCiphers, ...indexedCiphers];
|
indexedCiphers = [...failedCiphers, ...indexedCiphers];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
|||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { EventType } from "@bitwarden/common/enums";
|
import { EventType } from "@bitwarden/common/enums";
|
||||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
@@ -29,7 +30,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
|||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
|
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
|
||||||
import { CollectionId } from "@bitwarden/common/types/guid";
|
import { CollectionId, UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||||
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||||
@@ -79,7 +80,6 @@ export class ViewComponent implements OnDestroy, OnInit {
|
|||||||
private previousCipherId: string;
|
private previousCipherId: string;
|
||||||
private passwordReprompted = false;
|
private passwordReprompted = false;
|
||||||
|
|
||||||
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
|
|
||||||
private destroyed$ = new Subject<void>();
|
private destroyed$ = new Subject<void>();
|
||||||
|
|
||||||
get fido2CredentialCreationDateValue(): string {
|
get fido2CredentialCreationDateValue(): string {
|
||||||
@@ -144,9 +144,10 @@ export class ViewComponent implements OnDestroy, OnInit {
|
|||||||
async load() {
|
async load() {
|
||||||
this.cleanUp();
|
this.cleanUp();
|
||||||
|
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
// Grab individual cipher from `cipherViews$` for the most up-to-date information
|
// Grab individual cipher from `cipherViews$` for the most up-to-date information
|
||||||
this.cipherService.cipherViews$
|
this.cipherService
|
||||||
|
.cipherViews$(activeUserId)
|
||||||
.pipe(
|
.pipe(
|
||||||
map((ciphers) => ciphers?.find((c) => c.id === this.cipherId)),
|
map((ciphers) => ciphers?.find((c) => c.id === this.cipherId)),
|
||||||
filter((cipher) => !!cipher),
|
filter((cipher) => !!cipher),
|
||||||
@@ -250,7 +251,8 @@ export class ViewComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.deleteCipher();
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
await this.deleteCipher(activeUserId);
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
title: null,
|
title: null,
|
||||||
@@ -272,7 +274,8 @@ export class ViewComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.restoreCipher();
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
await this.restoreCipher(activeUserId);
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
title: null,
|
title: null,
|
||||||
@@ -380,7 +383,8 @@ export class ViewComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cipherId) {
|
if (cipherId) {
|
||||||
await this.cipherService.updateLastLaunchedDate(cipherId);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
await this.cipherService.updateLastLaunchedDate(cipherId, activeUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.platformUtilsService.launchUri(uri.launchUri);
|
this.platformUtilsService.launchUri(uri.launchUri);
|
||||||
@@ -498,14 +502,14 @@ export class ViewComponent implements OnDestroy, OnInit {
|
|||||||
a.downloading = false;
|
a.downloading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected deleteCipher() {
|
protected deleteCipher(userId: UserId) {
|
||||||
return this.cipher.isDeleted
|
return this.cipher.isDeleted
|
||||||
? this.cipherService.deleteWithServer(this.cipher.id)
|
? this.cipherService.deleteWithServer(this.cipher.id, userId)
|
||||||
: this.cipherService.softDeleteWithServer(this.cipher.id);
|
: this.cipherService.softDeleteWithServer(this.cipher.id, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected restoreCipher() {
|
protected restoreCipher(userId: UserId) {
|
||||||
return this.cipherService.restoreWithServer(this.cipher.id);
|
return this.cipherService.restoreWithServer(this.cipher.id, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async promptPassword() {
|
protected async promptPassword() {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
|
|||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state";
|
import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||||
@@ -30,8 +31,6 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
|
|||||||
private readonly collapsedGroupings$: Observable<Set<string>> =
|
private readonly collapsedGroupings$: Observable<Set<string>> =
|
||||||
this.collapsedGroupingsState.state$.pipe(map((c) => new Set(c)));
|
this.collapsedGroupingsState.state$.pipe(map((c) => new Set(c)));
|
||||||
|
|
||||||
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected organizationService: OrganizationService,
|
protected organizationService: OrganizationService,
|
||||||
protected folderService: FolderService,
|
protected folderService: FolderService,
|
||||||
@@ -63,7 +62,7 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildNestedFolders(organizationId?: string): Observable<DynamicTreeNode<FolderView>> {
|
buildNestedFolders(organizationId?: string): Observable<DynamicTreeNode<FolderView>> {
|
||||||
const transformation = async (storedFolders: FolderView[]) => {
|
const transformation = async (storedFolders: FolderView[], userId: UserId) => {
|
||||||
let folders: FolderView[];
|
let folders: FolderView[];
|
||||||
|
|
||||||
// If no org or "My Vault" is selected, show all folders
|
// If no org or "My Vault" is selected, show all folders
|
||||||
@@ -71,7 +70,7 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
|
|||||||
folders = storedFolders;
|
folders = storedFolders;
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, show only folders that have ciphers from the selected org and the "no folder" folder
|
// Otherwise, show only folders that have ciphers from the selected org and the "no folder" folder
|
||||||
const ciphers = await this.cipherService.getAllDecrypted();
|
const ciphers = await this.cipherService.getAllDecrypted(userId);
|
||||||
const orgCiphers = ciphers.filter((c) => c.organizationId == organizationId);
|
const orgCiphers = ciphers.filter((c) => c.organizationId == organizationId);
|
||||||
folders = storedFolders.filter(
|
folders = storedFolders.filter(
|
||||||
(f) => orgCiphers.some((oc) => oc.folderId == f.id) || f.id == null,
|
(f) => orgCiphers.some((oc) => oc.folderId == f.id) || f.id == null,
|
||||||
@@ -85,9 +84,13 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.activeUserId$.pipe(
|
return this.accountService.activeAccount$.pipe(
|
||||||
switchMap((userId) => this.folderService.folderViews$(userId)),
|
getUserId,
|
||||||
mergeMap((folders) => from(transformation(folders))),
|
switchMap((userId) =>
|
||||||
|
this.folderService
|
||||||
|
.folderViews$(userId)
|
||||||
|
.pipe(mergeMap((folders) => from(transformation(folders, userId)))),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +134,7 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getFolderNested(id: string): Promise<TreeNode<FolderView>> {
|
async getFolderNested(id: string): Promise<TreeNode<FolderView>> {
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
const folders = await this.getAllFoldersNested(
|
const folders = await this.getAllFoldersNested(
|
||||||
await firstValueFrom(this.folderService.folderViews$(activeUserId)),
|
await firstValueFrom(this.folderService.folderViews$(activeUserId)),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -151,11 +151,15 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract
|
|||||||
await this.syncService.syncUpsertCipher(
|
await this.syncService.syncUpsertCipher(
|
||||||
notification.payload as SyncCipherNotification,
|
notification.payload as SyncCipherNotification,
|
||||||
notification.type === NotificationType.SyncCipherUpdate,
|
notification.type === NotificationType.SyncCipherUpdate,
|
||||||
|
payloadUserId,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case NotificationType.SyncCipherDelete:
|
case NotificationType.SyncCipherDelete:
|
||||||
case NotificationType.SyncLoginDelete:
|
case NotificationType.SyncLoginDelete:
|
||||||
await this.syncService.syncDeleteCipher(notification.payload as SyncCipherNotification);
|
await this.syncService.syncDeleteCipher(
|
||||||
|
notification.payload as SyncCipherNotification,
|
||||||
|
payloadUserId,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case NotificationType.SyncFolderCreate:
|
case NotificationType.SyncFolderCreate:
|
||||||
case NotificationType.SyncFolderUpdate:
|
case NotificationType.SyncFolderUpdate:
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { TextEncoder } from "util";
|
|||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
import { BehaviorSubject, of } from "rxjs";
|
import { BehaviorSubject, of } from "rxjs";
|
||||||
|
|
||||||
import { Account, AccountService } from "../../../auth/abstractions/account.service";
|
import { mockAccountServiceWith } from "../../../../spec";
|
||||||
|
import { Account } from "../../../auth/abstractions/account.service";
|
||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
import { CipherService } from "../../../vault/abstractions/cipher.service";
|
import { CipherService } from "../../../vault/abstractions/cipher.service";
|
||||||
import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction";
|
import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction";
|
||||||
@@ -46,7 +47,6 @@ describe("FidoAuthenticatorService", () => {
|
|||||||
let userInterface!: MockProxy<Fido2UserInterfaceService<ParentWindowReference>>;
|
let userInterface!: MockProxy<Fido2UserInterfaceService<ParentWindowReference>>;
|
||||||
let userInterfaceSession!: MockProxy<Fido2UserInterfaceSession>;
|
let userInterfaceSession!: MockProxy<Fido2UserInterfaceSession>;
|
||||||
let syncService!: MockProxy<SyncService>;
|
let syncService!: MockProxy<SyncService>;
|
||||||
let accountService!: MockProxy<AccountService>;
|
|
||||||
let authenticator!: Fido2AuthenticatorService<ParentWindowReference>;
|
let authenticator!: Fido2AuthenticatorService<ParentWindowReference>;
|
||||||
let windowReference!: ParentWindowReference;
|
let windowReference!: ParentWindowReference;
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ describe("FidoAuthenticatorService", () => {
|
|||||||
syncService = mock<SyncService>({
|
syncService = mock<SyncService>({
|
||||||
activeUserLastSync$: () => of(new Date()),
|
activeUserLastSync$: () => of(new Date()),
|
||||||
});
|
});
|
||||||
accountService = mock<AccountService>();
|
const accountService = mockAccountServiceWith("testId" as UserId);
|
||||||
authenticator = new Fido2AuthenticatorService(
|
authenticator = new Fido2AuthenticatorService(
|
||||||
cipherService,
|
cipherService,
|
||||||
userInterface,
|
userInterface,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { AccountService } from "../../../auth/abstractions/account.service";
|
import { AccountService } from "../../../auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "../../../auth/services/account.service";
|
||||||
import { CipherService } from "../../../vault/abstractions/cipher.service";
|
import { CipherService } from "../../../vault/abstractions/cipher.service";
|
||||||
import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction";
|
import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction";
|
||||||
import { CipherRepromptType } from "../../../vault/enums/cipher-reprompt-type";
|
import { CipherRepromptType } from "../../../vault/enums/cipher-reprompt-type";
|
||||||
@@ -145,10 +146,10 @@ export class Fido2AuthenticatorService<ParentWindowReference>
|
|||||||
try {
|
try {
|
||||||
keyPair = await createKeyPair();
|
keyPair = await createKeyPair();
|
||||||
pubKeyDer = await crypto.subtle.exportKey("spki", keyPair.publicKey);
|
pubKeyDer = await crypto.subtle.exportKey("spki", keyPair.publicKey);
|
||||||
const encrypted = await this.cipherService.get(cipherId);
|
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
this.accountService.activeAccount$.pipe(getUserId),
|
||||||
);
|
);
|
||||||
|
const encrypted = await this.cipherService.get(cipherId, activeUserId);
|
||||||
|
|
||||||
cipher = await encrypted.decrypt(
|
cipher = await encrypted.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(encrypted, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(encrypted, activeUserId),
|
||||||
@@ -309,7 +310,7 @@ export class Fido2AuthenticatorService<ParentWindowReference>
|
|||||||
|
|
||||||
if (selectedFido2Credential.counter > 0) {
|
if (selectedFido2Credential.counter > 0) {
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
this.accountService.activeAccount$.pipe(getUserId),
|
||||||
);
|
);
|
||||||
const encrypted = await this.cipherService.encrypt(selectedCipher, activeUserId);
|
const encrypted = await this.cipherService.encrypt(selectedCipher, activeUserId);
|
||||||
await this.cipherService.updateWithServer(encrypted);
|
await this.cipherService.updateWithServer(encrypted);
|
||||||
@@ -400,7 +401,8 @@ export class Fido2AuthenticatorService<ParentWindowReference>
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const ciphers = await this.cipherService.getAllDecrypted();
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
const ciphers = await this.cipherService.getAllDecrypted(activeUserId);
|
||||||
return ciphers
|
return ciphers
|
||||||
.filter(
|
.filter(
|
||||||
(cipher) =>
|
(cipher) =>
|
||||||
@@ -421,7 +423,8 @@ export class Fido2AuthenticatorService<ParentWindowReference>
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const ciphers = await this.cipherService.getAllDecrypted();
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
const ciphers = await this.cipherService.getAllDecrypted(activeUserId);
|
||||||
return ciphers.filter(
|
return ciphers.filter(
|
||||||
(cipher) =>
|
(cipher) =>
|
||||||
!cipher.isDeleted &&
|
!cipher.isDeleted &&
|
||||||
@@ -438,7 +441,8 @@ export class Fido2AuthenticatorService<ParentWindowReference>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async findCredentialsByRp(rpId: string): Promise<CipherView[]> {
|
private async findCredentialsByRp(rpId: string): Promise<CipherView[]> {
|
||||||
const ciphers = await this.cipherService.getAllDecrypted();
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
const ciphers = await this.cipherService.getAllDecrypted(activeUserId);
|
||||||
return ciphers.filter(
|
return ciphers.filter(
|
||||||
(cipher) =>
|
(cipher) =>
|
||||||
!cipher.isDeleted &&
|
!cipher.isDeleted &&
|
||||||
|
|||||||
@@ -129,12 +129,18 @@ export abstract class CoreSyncService implements SyncService {
|
|||||||
return this.syncCompleted(false);
|
return this.syncCompleted(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async syncUpsertCipher(notification: SyncCipherNotification, isEdit: boolean): Promise<boolean> {
|
async syncUpsertCipher(
|
||||||
|
notification: SyncCipherNotification,
|
||||||
|
isEdit: boolean,
|
||||||
|
userId: UserId,
|
||||||
|
): Promise<boolean> {
|
||||||
this.syncStarted();
|
this.syncStarted();
|
||||||
if (await this.stateService.getIsAuthenticated()) {
|
|
||||||
|
const authStatus = await firstValueFrom(this.authService.authStatusFor$(userId));
|
||||||
|
if (authStatus >= AuthenticationStatus.Locked) {
|
||||||
try {
|
try {
|
||||||
let shouldUpdate = true;
|
let shouldUpdate = true;
|
||||||
const localCipher = await this.cipherService.get(notification.id);
|
const localCipher = await this.cipherService.get(notification.id, userId);
|
||||||
if (localCipher != null && localCipher.revisionDate >= notification.revisionDate) {
|
if (localCipher != null && localCipher.revisionDate >= notification.revisionDate) {
|
||||||
shouldUpdate = false;
|
shouldUpdate = false;
|
||||||
}
|
}
|
||||||
@@ -182,7 +188,7 @@ export abstract class CoreSyncService implements SyncService {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e != null && e.statusCode === 404 && isEdit) {
|
if (e != null && e.statusCode === 404 && isEdit) {
|
||||||
await this.cipherService.delete(notification.id);
|
await this.cipherService.delete(notification.id, userId);
|
||||||
this.messageSender.send("syncedDeletedCipher", { cipherId: notification.id });
|
this.messageSender.send("syncedDeletedCipher", { cipherId: notification.id });
|
||||||
return this.syncCompleted(true);
|
return this.syncCompleted(true);
|
||||||
}
|
}
|
||||||
@@ -191,10 +197,12 @@ export abstract class CoreSyncService implements SyncService {
|
|||||||
return this.syncCompleted(false);
|
return this.syncCompleted(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async syncDeleteCipher(notification: SyncCipherNotification): Promise<boolean> {
|
async syncDeleteCipher(notification: SyncCipherNotification, userId: UserId): Promise<boolean> {
|
||||||
this.syncStarted();
|
this.syncStarted();
|
||||||
if (await this.stateService.getIsAuthenticated()) {
|
|
||||||
await this.cipherService.delete(notification.id);
|
const authStatus = await firstValueFrom(this.authService.authStatusFor$(userId));
|
||||||
|
if (authStatus >= AuthenticationStatus.Locked) {
|
||||||
|
await this.cipherService.delete(notification.id, userId);
|
||||||
this.messageSender.send("syncedDeletedCipher", { cipherId: notification.id });
|
this.messageSender.send("syncedDeletedCipher", { cipherId: notification.id });
|
||||||
return this.syncCompleted(true);
|
return this.syncCompleted(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,8 +62,9 @@ export abstract class SyncService {
|
|||||||
abstract syncUpsertCipher(
|
abstract syncUpsertCipher(
|
||||||
notification: SyncCipherNotification,
|
notification: SyncCipherNotification,
|
||||||
isEdit: boolean,
|
isEdit: boolean,
|
||||||
|
userId: UserId,
|
||||||
): Promise<boolean>;
|
): Promise<boolean>;
|
||||||
abstract syncDeleteCipher(notification: SyncFolderNotification): Promise<boolean>;
|
abstract syncDeleteCipher(notification: SyncFolderNotification, userId: UserId): Promise<boolean>;
|
||||||
abstract syncUpsertSend(notification: SyncSendNotification, isEdit: boolean): Promise<boolean>;
|
abstract syncUpsertSend(notification: SyncSendNotification, isEdit: boolean): Promise<boolean>;
|
||||||
abstract syncDeleteSend(notification: SyncSendNotification): Promise<boolean>;
|
abstract syncDeleteSend(notification: SyncSendNotification): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
|
|||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
|
||||||
import { EventCollectionService as EventCollectionServiceAbstraction } from "../../abstractions/event/event-collection.service";
|
import { EventCollectionService as EventCollectionServiceAbstraction } from "../../abstractions/event/event-collection.service";
|
||||||
import { EventUploadService } from "../../abstractions/event/event-upload.service";
|
import { EventUploadService } from "../../abstractions/event/event-upload.service";
|
||||||
@@ -46,7 +47,7 @@ export class EventCollectionService implements EventCollectionServiceAbstraction
|
|||||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
const eventStore = this.stateProvider.getUser(userId, EVENT_COLLECTION);
|
const eventStore = this.stateProvider.getUser(userId, EVENT_COLLECTION);
|
||||||
|
|
||||||
if (!(await this.shouldUpdate(null, eventType, ciphers))) {
|
if (!(await this.shouldUpdate(userId, null, eventType, ciphers))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +92,7 @@ export class EventCollectionService implements EventCollectionServiceAbstraction
|
|||||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
const eventStore = this.stateProvider.getUser(userId, EVENT_COLLECTION);
|
const eventStore = this.stateProvider.getUser(userId, EVENT_COLLECTION);
|
||||||
|
|
||||||
if (!(await this.shouldUpdate(organizationId, eventType, undefined, cipherId))) {
|
if (!(await this.shouldUpdate(userId, organizationId, eventType, undefined, cipherId))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,18 +114,18 @@ export class EventCollectionService implements EventCollectionServiceAbstraction
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Verifies if the event collection should be updated for the provided information
|
/** Verifies if the event collection should be updated for the provided information
|
||||||
|
* @param userId the active user's id
|
||||||
* @param cipherId the cipher for the event
|
* @param cipherId the cipher for the event
|
||||||
* @param organizationId the organization for the event
|
* @param organizationId the organization for the event
|
||||||
*/
|
*/
|
||||||
private async shouldUpdate(
|
private async shouldUpdate(
|
||||||
|
userId: UserId,
|
||||||
organizationId: string = null,
|
organizationId: string = null,
|
||||||
eventType: EventType = null,
|
eventType: EventType = null,
|
||||||
ciphers: CipherView[] = [],
|
ciphers: CipherView[] = [],
|
||||||
cipherId?: string,
|
cipherId?: string,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const cipher$ = from(this.cipherService.get(cipherId));
|
const cipher$ = from(this.cipherService.get(cipherId, userId));
|
||||||
|
|
||||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
|
||||||
|
|
||||||
const orgIds$ = this.organizationService
|
const orgIds$ = this.organizationService
|
||||||
.organizations$(userId)
|
.organizations$(userId)
|
||||||
|
|||||||
@@ -19,57 +19,70 @@ import { FieldView } from "../models/view/field.view";
|
|||||||
import { AddEditCipherInfo } from "../types/add-edit-cipher-info";
|
import { AddEditCipherInfo } from "../types/add-edit-cipher-info";
|
||||||
|
|
||||||
export abstract class CipherService implements UserKeyRotationDataProvider<CipherWithIdRequest> {
|
export abstract class CipherService implements UserKeyRotationDataProvider<CipherWithIdRequest> {
|
||||||
cipherViews$: Observable<CipherView[]>;
|
abstract cipherViews$(userId: UserId): Observable<CipherView[]>;
|
||||||
ciphers$: Observable<Record<CipherId, CipherData>>;
|
abstract ciphers$(userId: UserId): Observable<Record<CipherId, CipherData>>;
|
||||||
localData$: Observable<Record<CipherId, LocalData>>;
|
abstract localData$(userId: UserId): Observable<Record<CipherId, LocalData>>;
|
||||||
/**
|
/**
|
||||||
* An observable monitoring the add/edit cipher info saved to memory.
|
* An observable monitoring the add/edit cipher info saved to memory.
|
||||||
*/
|
*/
|
||||||
addEditCipherInfo$: Observable<AddEditCipherInfo>;
|
abstract addEditCipherInfo$(userId: UserId): Observable<AddEditCipherInfo>;
|
||||||
/**
|
/**
|
||||||
* Observable that emits an array of cipherViews that failed to decrypt. Does not emit until decryption has completed.
|
* Observable that emits an array of cipherViews that failed to decrypt. Does not emit until decryption has completed.
|
||||||
*
|
*
|
||||||
* An empty array indicates that all ciphers were successfully decrypted.
|
* An empty array indicates that all ciphers were successfully decrypted.
|
||||||
*/
|
*/
|
||||||
failedToDecryptCiphers$: Observable<CipherView[]>;
|
abstract failedToDecryptCiphers$(userId: UserId): Observable<CipherView[]>;
|
||||||
clearCache: (userId?: string) => Promise<void>;
|
abstract clearCache(userId: UserId): Promise<void>;
|
||||||
encrypt: (
|
abstract encrypt(
|
||||||
model: CipherView,
|
model: CipherView,
|
||||||
userId: UserId,
|
userId: UserId,
|
||||||
keyForEncryption?: SymmetricCryptoKey,
|
keyForEncryption?: SymmetricCryptoKey,
|
||||||
keyForCipherKeyDecryption?: SymmetricCryptoKey,
|
keyForCipherKeyDecryption?: SymmetricCryptoKey,
|
||||||
originalCipher?: Cipher,
|
originalCipher?: Cipher,
|
||||||
) => Promise<Cipher>;
|
): Promise<Cipher>;
|
||||||
encryptFields: (fieldsModel: FieldView[], key: SymmetricCryptoKey) => Promise<Field[]>;
|
abstract encryptFields(fieldsModel: FieldView[], key: SymmetricCryptoKey): Promise<Field[]>;
|
||||||
encryptField: (fieldModel: FieldView, key: SymmetricCryptoKey) => Promise<Field>;
|
abstract encryptField(fieldModel: FieldView, key: SymmetricCryptoKey): Promise<Field>;
|
||||||
get: (id: string) => Promise<Cipher>;
|
abstract get(id: string, userId: UserId): Promise<Cipher>;
|
||||||
getAll: () => Promise<Cipher[]>;
|
abstract getAll(userId: UserId): Promise<Cipher[]>;
|
||||||
getAllDecrypted: () => Promise<CipherView[]>;
|
abstract getAllDecrypted(userId: UserId): Promise<CipherView[]>;
|
||||||
getAllDecryptedForGrouping: (groupingId: string, folder?: boolean) => Promise<CipherView[]>;
|
abstract getAllDecryptedForGrouping(
|
||||||
getAllDecryptedForUrl: (
|
groupingId: string,
|
||||||
|
userId: UserId,
|
||||||
|
folder?: boolean,
|
||||||
|
): Promise<CipherView[]>;
|
||||||
|
abstract getAllDecryptedForUrl(
|
||||||
url: string,
|
url: string,
|
||||||
|
userId: UserId,
|
||||||
includeOtherTypes?: CipherType[],
|
includeOtherTypes?: CipherType[],
|
||||||
defaultMatch?: UriMatchStrategySetting,
|
defaultMatch?: UriMatchStrategySetting,
|
||||||
) => Promise<CipherView[]>;
|
): Promise<CipherView[]>;
|
||||||
filterCiphersForUrl: (
|
abstract filterCiphersForUrl(
|
||||||
ciphers: CipherView[],
|
ciphers: CipherView[],
|
||||||
url: string,
|
url: string,
|
||||||
includeOtherTypes?: CipherType[],
|
includeOtherTypes?: CipherType[],
|
||||||
defaultMatch?: UriMatchStrategySetting,
|
defaultMatch?: UriMatchStrategySetting,
|
||||||
) => Promise<CipherView[]>;
|
): Promise<CipherView[]>;
|
||||||
getAllFromApiForOrganization: (organizationId: string) => Promise<CipherView[]>;
|
abstract getAllFromApiForOrganization(organizationId: string): Promise<CipherView[]>;
|
||||||
/**
|
/**
|
||||||
* Gets ciphers belonging to the specified organization that the user has explicit collection level access to.
|
* Gets ciphers belonging to the specified organization that the user has explicit collection level access to.
|
||||||
* Ciphers that are not assigned to any collections are only included for users with admin access.
|
* Ciphers that are not assigned to any collections are only included for users with admin access.
|
||||||
*/
|
*/
|
||||||
getManyFromApiForOrganization: (organizationId: string) => Promise<CipherView[]>;
|
abstract getManyFromApiForOrganization(organizationId: string): Promise<CipherView[]>;
|
||||||
getLastUsedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise<CipherView>;
|
abstract getLastUsedForUrl(
|
||||||
getLastLaunchedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise<CipherView>;
|
url: string,
|
||||||
getNextCipherForUrl: (url: string) => Promise<CipherView>;
|
userId: UserId,
|
||||||
updateLastUsedIndexForUrl: (url: string) => void;
|
autofillOnPageLoad: boolean,
|
||||||
updateLastUsedDate: (id: string) => Promise<void>;
|
): Promise<CipherView>;
|
||||||
updateLastLaunchedDate: (id: string) => Promise<void>;
|
abstract getLastLaunchedForUrl(
|
||||||
saveNeverDomain: (domain: string) => Promise<void>;
|
url: string,
|
||||||
|
userId: UserId,
|
||||||
|
autofillOnPageLoad: boolean,
|
||||||
|
): Promise<CipherView>;
|
||||||
|
abstract getNextCipherForUrl(url: string, userId: UserId): Promise<CipherView>;
|
||||||
|
abstract updateLastUsedIndexForUrl(url: string): void;
|
||||||
|
abstract updateLastUsedDate(id: string, userId: UserId): Promise<void>;
|
||||||
|
abstract updateLastLaunchedDate(id: string, userId: UserId): Promise<void>;
|
||||||
|
abstract saveNeverDomain(domain: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Create a cipher with the server
|
* Create a cipher with the server
|
||||||
*
|
*
|
||||||
@@ -78,7 +91,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
|
|||||||
*
|
*
|
||||||
* @returns A promise that resolves to the created cipher
|
* @returns A promise that resolves to the created cipher
|
||||||
*/
|
*/
|
||||||
createWithServer: (cipher: Cipher, orgAdmin?: boolean) => Promise<Cipher>;
|
abstract createWithServer(cipher: Cipher, orgAdmin?: boolean): Promise<Cipher>;
|
||||||
/**
|
/**
|
||||||
* Update a cipher with the server
|
* Update a cipher with the server
|
||||||
* @param cipher The cipher to update
|
* @param cipher The cipher to update
|
||||||
@@ -87,88 +100,105 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
|
|||||||
*
|
*
|
||||||
* @returns A promise that resolves to the updated cipher
|
* @returns A promise that resolves to the updated cipher
|
||||||
*/
|
*/
|
||||||
updateWithServer: (cipher: Cipher, orgAdmin?: boolean, isNotClone?: boolean) => Promise<Cipher>;
|
abstract updateWithServer(
|
||||||
shareWithServer: (
|
cipher: Cipher,
|
||||||
|
orgAdmin?: boolean,
|
||||||
|
isNotClone?: boolean,
|
||||||
|
): Promise<Cipher>;
|
||||||
|
abstract shareWithServer(
|
||||||
cipher: CipherView,
|
cipher: CipherView,
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
collectionIds: string[],
|
collectionIds: string[],
|
||||||
userId: UserId,
|
userId: UserId,
|
||||||
) => Promise<Cipher>;
|
): Promise<Cipher>;
|
||||||
shareManyWithServer: (
|
abstract shareManyWithServer(
|
||||||
ciphers: CipherView[],
|
ciphers: CipherView[],
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
collectionIds: string[],
|
collectionIds: string[],
|
||||||
userId: UserId,
|
userId: UserId,
|
||||||
) => Promise<any>;
|
): Promise<any>;
|
||||||
saveAttachmentWithServer: (
|
abstract saveAttachmentWithServer(
|
||||||
cipher: Cipher,
|
cipher: Cipher,
|
||||||
unencryptedFile: any,
|
unencryptedFile: any,
|
||||||
userId: UserId,
|
userId: UserId,
|
||||||
admin?: boolean,
|
admin?: boolean,
|
||||||
) => Promise<Cipher>;
|
): Promise<Cipher>;
|
||||||
saveAttachmentRawWithServer: (
|
abstract saveAttachmentRawWithServer(
|
||||||
cipher: Cipher,
|
cipher: Cipher,
|
||||||
filename: string,
|
filename: string,
|
||||||
data: ArrayBuffer,
|
data: ArrayBuffer,
|
||||||
userId: UserId,
|
userId: UserId,
|
||||||
admin?: boolean,
|
admin?: boolean,
|
||||||
) => Promise<Cipher>;
|
): Promise<Cipher>;
|
||||||
/**
|
/**
|
||||||
* Save the collections for a cipher with the server
|
* Save the collections for a cipher with the server
|
||||||
*
|
*
|
||||||
* @param cipher The cipher to save collections for
|
* @param cipher The cipher to save collections for
|
||||||
|
* @param userId The user ID
|
||||||
*
|
*
|
||||||
* @returns A promise that resolves when the collections have been saved
|
* @returns A promise that resolves when the collections have been saved
|
||||||
*/
|
*/
|
||||||
saveCollectionsWithServer: (cipher: Cipher) => Promise<Cipher>;
|
abstract saveCollectionsWithServer(cipher: Cipher, userId: UserId): Promise<Cipher>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save the collections for a cipher with the server as an admin.
|
* Save the collections for a cipher with the server as an admin.
|
||||||
* Used for Unassigned ciphers or when the user only has admin access to the cipher (not assigned normally).
|
* Used for Unassigned ciphers or when the user only has admin access to the cipher (not assigned normally).
|
||||||
* @param cipher
|
* @param cipher
|
||||||
*/
|
*/
|
||||||
saveCollectionsWithServerAdmin: (cipher: Cipher) => Promise<Cipher>;
|
abstract saveCollectionsWithServerAdmin(cipher: Cipher): Promise<Cipher>;
|
||||||
/**
|
/**
|
||||||
* Bulk update collections for many ciphers with the server
|
* Bulk update collections for many ciphers with the server
|
||||||
* @param orgId
|
* @param orgId
|
||||||
|
* @param userId
|
||||||
* @param cipherIds
|
* @param cipherIds
|
||||||
* @param collectionIds
|
* @param collectionIds
|
||||||
* @param removeCollections - If true, the collections will be removed from the ciphers, otherwise they will be added
|
* @param removeCollections - If true, the collections will be removed from the ciphers, otherwise they will be added
|
||||||
*/
|
*/
|
||||||
bulkUpdateCollectionsWithServer: (
|
abstract bulkUpdateCollectionsWithServer(
|
||||||
orgId: OrganizationId,
|
orgId: OrganizationId,
|
||||||
|
userId: UserId,
|
||||||
cipherIds: CipherId[],
|
cipherIds: CipherId[],
|
||||||
collectionIds: CollectionId[],
|
collectionIds: CollectionId[],
|
||||||
removeCollections: boolean,
|
removeCollections: boolean,
|
||||||
) => Promise<void>;
|
): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Update the local store of CipherData with the provided data. Values are upserted into the existing store.
|
* Update the local store of CipherData with the provided data. Values are upserted into the existing store.
|
||||||
*
|
*
|
||||||
* @param cipher The cipher data to upsert. Can be a single CipherData object or an array of CipherData objects.
|
* @param cipher The cipher data to upsert. Can be a single CipherData object or an array of CipherData objects.
|
||||||
* @returns A promise that resolves to a record of updated cipher store, keyed by their cipher ID. Returns all ciphers, not just those updated
|
* @returns A promise that resolves to a record of updated cipher store, keyed by their cipher ID. Returns all ciphers, not just those updated
|
||||||
*/
|
*/
|
||||||
upsert: (cipher: CipherData | CipherData[]) => Promise<Record<CipherId, CipherData>>;
|
abstract upsert(cipher: CipherData | CipherData[]): Promise<Record<CipherId, CipherData>>;
|
||||||
replace: (ciphers: { [id: string]: CipherData }, userId: UserId) => Promise<any>;
|
abstract replace(ciphers: { [id: string]: CipherData }, userId: UserId): Promise<any>;
|
||||||
clear: (userId?: string) => Promise<void>;
|
abstract clear(userId?: string): Promise<void>;
|
||||||
moveManyWithServer: (ids: string[], folderId: string) => Promise<any>;
|
abstract moveManyWithServer(ids: string[], folderId: string, userId: UserId): Promise<any>;
|
||||||
delete: (id: string | string[]) => Promise<any>;
|
abstract delete(id: string | string[], userId: UserId): Promise<any>;
|
||||||
deleteWithServer: (id: string, asAdmin?: boolean) => Promise<any>;
|
abstract deleteWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise<any>;
|
||||||
deleteManyWithServer: (ids: string[], asAdmin?: boolean) => Promise<any>;
|
abstract deleteManyWithServer(ids: string[], userId: UserId, asAdmin?: boolean): Promise<any>;
|
||||||
deleteAttachment: (id: string, revisionDate: string, attachmentId: string) => Promise<CipherData>;
|
abstract deleteAttachment(
|
||||||
deleteAttachmentWithServer: (id: string, attachmentId: string) => Promise<CipherData>;
|
id: string,
|
||||||
sortCiphersByLastUsed: (a: CipherView, b: CipherView) => number;
|
revisionDate: string,
|
||||||
sortCiphersByLastUsedThenName: (a: CipherView, b: CipherView) => number;
|
attachmentId: string,
|
||||||
getLocaleSortingFunction: () => (a: CipherView, b: CipherView) => number;
|
userId: UserId,
|
||||||
softDelete: (id: string | string[]) => Promise<any>;
|
): Promise<CipherData>;
|
||||||
softDeleteWithServer: (id: string, asAdmin?: boolean) => Promise<any>;
|
abstract deleteAttachmentWithServer(
|
||||||
softDeleteManyWithServer: (ids: string[], asAdmin?: boolean) => Promise<any>;
|
id: string,
|
||||||
restore: (
|
attachmentId: string,
|
||||||
|
userId: UserId,
|
||||||
|
): Promise<CipherData>;
|
||||||
|
abstract sortCiphersByLastUsed(a: CipherView, b: CipherView): number;
|
||||||
|
abstract sortCiphersByLastUsedThenName(a: CipherView, b: CipherView): number;
|
||||||
|
abstract getLocaleSortingFunction(): (a: CipherView, b: CipherView) => number;
|
||||||
|
abstract softDelete(id: string | string[], userId: UserId): Promise<any>;
|
||||||
|
abstract softDeleteWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise<any>;
|
||||||
|
abstract softDeleteManyWithServer(ids: string[], userId: UserId, asAdmin?: boolean): Promise<any>;
|
||||||
|
abstract restore(
|
||||||
cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[],
|
cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[],
|
||||||
) => Promise<any>;
|
userId: UserId,
|
||||||
restoreWithServer: (id: string, asAdmin?: boolean) => Promise<any>;
|
): Promise<any>;
|
||||||
restoreManyWithServer: (ids: string[], orgId?: string) => Promise<void>;
|
abstract restoreWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise<any>;
|
||||||
getKeyForCipherKeyDecryption: (cipher: Cipher, userId: UserId) => Promise<any>;
|
abstract restoreManyWithServer(ids: string[], orgId?: string): Promise<void>;
|
||||||
setAddEditCipherInfo: (value: AddEditCipherInfo) => Promise<void>;
|
abstract getKeyForCipherKeyDecryption(cipher: Cipher, userId: UserId): Promise<any>;
|
||||||
|
abstract setAddEditCipherInfo(value: AddEditCipherInfo, userId: UserId): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Returns user ciphers re-encrypted with the new user key.
|
* Returns user ciphers re-encrypted with the new user key.
|
||||||
* @param originalUserKey the original user key
|
* @param originalUserKey the original user key
|
||||||
@@ -177,11 +207,11 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
|
|||||||
* @throws Error if new user key is null
|
* @throws Error if new user key is null
|
||||||
* @returns a list of user ciphers that have been re-encrypted with the new user key
|
* @returns a list of user ciphers that have been re-encrypted with the new user key
|
||||||
*/
|
*/
|
||||||
getRotatedData: (
|
abstract getRotatedData(
|
||||||
originalUserKey: UserKey,
|
originalUserKey: UserKey,
|
||||||
newUserKey: UserKey,
|
newUserKey: UserKey,
|
||||||
userId: UserId,
|
userId: UserId,
|
||||||
) => Promise<CipherWithIdRequest[]>;
|
): Promise<CipherWithIdRequest[]>;
|
||||||
getNextCardCipher: () => Promise<CipherView>;
|
abstract getNextCardCipher(userId: UserId): Promise<CipherView>;
|
||||||
getNextIdentityCipher: () => Promise<CipherView>;
|
abstract getNextIdentityCipher(userId: UserId): Promise<CipherView>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -382,8 +382,16 @@ describe("Cipher Service", () => {
|
|||||||
Cipher1: cipher1,
|
Cipher1: cipher1,
|
||||||
Cipher2: cipher2,
|
Cipher2: cipher2,
|
||||||
});
|
});
|
||||||
cipherService.cipherViews$ = decryptedCiphers.pipe(map((ciphers) => Object.values(ciphers)));
|
jest
|
||||||
cipherService.failedToDecryptCiphers$ = failedCiphers = new BehaviorSubject<CipherView[]>([]);
|
.spyOn(cipherService, "cipherViews$")
|
||||||
|
.mockImplementation((userId: UserId) =>
|
||||||
|
decryptedCiphers.pipe(map((ciphers) => Object.values(ciphers))),
|
||||||
|
);
|
||||||
|
|
||||||
|
failedCiphers = new BehaviorSubject<CipherView[]>([]);
|
||||||
|
jest
|
||||||
|
.spyOn(cipherService, "failedToDecryptCiphers$")
|
||||||
|
.mockImplementation((userId: UserId) => failedCiphers);
|
||||||
|
|
||||||
encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(32));
|
encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(32));
|
||||||
encryptedKey = new EncString("Re-encrypted Cipher Key");
|
encryptedKey = new EncString("Re-encrypted Cipher Key");
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import Domain from "../../platform/models/domain/domain-base";
|
|||||||
import { EncArrayBuffer } from "../../platform/models/domain/enc-array-buffer";
|
import { EncArrayBuffer } from "../../platform/models/domain/enc-array-buffer";
|
||||||
import { EncString } from "../../platform/models/domain/enc-string";
|
import { EncString } from "../../platform/models/domain/enc-string";
|
||||||
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
|
||||||
import { ActiveUserState, StateProvider } from "../../platform/state";
|
import { StateProvider } from "../../platform/state";
|
||||||
import { CipherId, CollectionId, OrganizationId, UserId } from "../../types/guid";
|
import { CipherId, CollectionId, OrganizationId, UserId } from "../../types/guid";
|
||||||
import { OrgKey, UserKey } from "../../types/key";
|
import { OrgKey, UserKey } from "../../types/key";
|
||||||
import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service";
|
import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service";
|
||||||
@@ -97,33 +97,6 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
*/
|
*/
|
||||||
private forceCipherViews$: Subject<CipherView[]> = new Subject<CipherView[]>();
|
private forceCipherViews$: Subject<CipherView[]> = new Subject<CipherView[]>();
|
||||||
|
|
||||||
localData$: Observable<Record<CipherId, LocalData>>;
|
|
||||||
ciphers$: Observable<Record<CipherId, CipherData>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observable that emits an array of decrypted ciphers for the active user.
|
|
||||||
* This observable will not emit until the encrypted ciphers have either been loaded from state or after sync.
|
|
||||||
*
|
|
||||||
* A `null` value indicates that the latest encrypted ciphers have not been decrypted yet and that
|
|
||||||
* decryption is in progress. The latest decrypted ciphers will be emitted once decryption is complete.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
cipherViews$: Observable<CipherView[] | null>;
|
|
||||||
addEditCipherInfo$: Observable<AddEditCipherInfo>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observable that emits an array of cipherViews that failed to decrypt. Does not emit until decryption has completed.
|
|
||||||
*
|
|
||||||
* An empty array indicates that all ciphers were successfully decrypted.
|
|
||||||
*/
|
|
||||||
failedToDecryptCiphers$: Observable<CipherView[]>;
|
|
||||||
|
|
||||||
private localDataState: ActiveUserState<Record<CipherId, LocalData>>;
|
|
||||||
private encryptedCiphersState: ActiveUserState<Record<CipherId, CipherData>>;
|
|
||||||
private decryptedCiphersState: ActiveUserState<Record<CipherId, CipherView>>;
|
|
||||||
private failedToDecryptCiphersState: ActiveUserState<CipherView[]>;
|
|
||||||
private addEditCipherInfoState: ActiveUserState<AddEditCipherInfo>;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private keyService: KeyService,
|
private keyService: KeyService,
|
||||||
private domainSettingsService: DomainSettingsService,
|
private domainSettingsService: DomainSettingsService,
|
||||||
@@ -138,30 +111,49 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
private stateProvider: StateProvider,
|
private stateProvider: StateProvider,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
) {
|
) {}
|
||||||
this.localDataState = this.stateProvider.getActive(LOCAL_DATA_KEY);
|
|
||||||
this.encryptedCiphersState = this.stateProvider.getActive(ENCRYPTED_CIPHERS);
|
|
||||||
this.decryptedCiphersState = this.stateProvider.getActive(DECRYPTED_CIPHERS);
|
|
||||||
this.failedToDecryptCiphersState = this.stateProvider.getActive(FAILED_DECRYPTED_CIPHERS);
|
|
||||||
this.addEditCipherInfoState = this.stateProvider.getActive(ADD_EDIT_CIPHER_INFO_KEY);
|
|
||||||
|
|
||||||
this.localData$ = this.localDataState.state$.pipe(map((data) => data ?? {}));
|
localData$(userId: UserId): Observable<Record<CipherId, LocalData>> {
|
||||||
this.ciphers$ = this.encryptedCiphersState.state$.pipe(map((ciphers) => ciphers ?? {}));
|
return this.localDataState(userId).state$.pipe(map((data) => data ?? {}));
|
||||||
|
}
|
||||||
|
|
||||||
// Decrypted ciphers depend on both ciphers and local data and need to be updated when either changes
|
/**
|
||||||
this.cipherViews$ = combineLatest([this.encryptedCiphersState.state$, this.localData$]).pipe(
|
* Observable that emits an object of encrypted ciphers for the active user.
|
||||||
|
*/
|
||||||
|
ciphers$(userId: UserId): Observable<Record<CipherId, CipherData>> {
|
||||||
|
return this.encryptedCiphersState(userId).state$.pipe(map((ciphers) => ciphers ?? {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable that emits an array of decrypted ciphers for the active user.
|
||||||
|
* This observable will not emit until the encrypted ciphers have either been loaded from state or after sync.
|
||||||
|
*
|
||||||
|
* A `null` value indicates that the latest encrypted ciphers have not been decrypted yet and that
|
||||||
|
* decryption is in progress. The latest decrypted ciphers will be emitted once decryption is complete.
|
||||||
|
*/
|
||||||
|
cipherViews$(userId: UserId): Observable<CipherView[] | null> {
|
||||||
|
return combineLatest([this.encryptedCiphersState(userId).state$, this.localData$(userId)]).pipe(
|
||||||
filter(([ciphers]) => ciphers != null), // Skip if ciphers haven't been loaded yor synced yet
|
filter(([ciphers]) => ciphers != null), // Skip if ciphers haven't been loaded yor synced yet
|
||||||
switchMap(() => merge(this.forceCipherViews$, this.getAllDecrypted())),
|
switchMap(() => merge(this.forceCipherViews$, this.getAllDecrypted(userId))),
|
||||||
shareReplay({ bufferSize: 1, refCount: true }),
|
shareReplay({ bufferSize: 1, refCount: true }),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.failedToDecryptCiphers$ = this.failedToDecryptCiphersState.state$.pipe(
|
addEditCipherInfo$(userId: UserId): Observable<AddEditCipherInfo> {
|
||||||
|
return this.addEditCipherInfoState(userId).state$;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable that emits an array of cipherViews that failed to decrypt. Does not emit until decryption has completed.
|
||||||
|
*
|
||||||
|
* An empty array indicates that all ciphers were successfully decrypted.
|
||||||
|
*/
|
||||||
|
failedToDecryptCiphers$(userId: UserId): Observable<CipherView[]> {
|
||||||
|
return this.failedToDecryptCiphersState(userId).state$.pipe(
|
||||||
filter((ciphers) => ciphers != null),
|
filter((ciphers) => ciphers != null),
|
||||||
switchMap((ciphers) => merge(this.forceCipherViews$, of(ciphers))),
|
switchMap((ciphers) => merge(this.forceCipherViews$, of(ciphers))),
|
||||||
shareReplay({ bufferSize: 1, refCount: true }),
|
shareReplay({ bufferSize: 1, refCount: true }),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.addEditCipherInfo$ = this.addEditCipherInfoState.state$;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async setDecryptedCipherCache(value: CipherView[], userId: UserId) {
|
async setDecryptedCipherCache(value: CipherView[], userId: UserId) {
|
||||||
@@ -212,7 +204,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
): Promise<Cipher> {
|
): Promise<Cipher> {
|
||||||
if (model.id != null) {
|
if (model.id != null) {
|
||||||
if (originalCipher == null) {
|
if (originalCipher == null) {
|
||||||
originalCipher = await this.get(model.id);
|
originalCipher = await this.get(model.id, userId);
|
||||||
}
|
}
|
||||||
if (originalCipher != null) {
|
if (originalCipher != null) {
|
||||||
await this.updateModelfromExistingCipher(model, originalCipher, userId);
|
await this.updateModelfromExistingCipher(model, originalCipher, userId);
|
||||||
@@ -366,22 +358,22 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
return ph;
|
return ph;
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(id: string): Promise<Cipher> {
|
async get(id: string, userId: UserId): Promise<Cipher> {
|
||||||
const ciphers = await firstValueFrom(this.ciphers$);
|
const ciphers = await firstValueFrom(this.ciphers$(userId));
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
if (ciphers == null || !ciphers.hasOwnProperty(id)) {
|
if (ciphers == null || !ciphers.hasOwnProperty(id)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const localData = await firstValueFrom(this.localData$);
|
const localData = await firstValueFrom(this.localData$(userId));
|
||||||
const cipherId = id as CipherId;
|
const cipherId = id as CipherId;
|
||||||
|
|
||||||
return new Cipher(ciphers[cipherId], localData ? localData[cipherId] : null);
|
return new Cipher(ciphers[cipherId], localData ? localData[cipherId] : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAll(): Promise<Cipher[]> {
|
async getAll(userId: UserId): Promise<Cipher[]> {
|
||||||
const localData = await firstValueFrom(this.localData$);
|
const localData = await firstValueFrom(this.localData$(userId));
|
||||||
const ciphers = await firstValueFrom(this.ciphers$);
|
const ciphers = await firstValueFrom(this.ciphers$(userId));
|
||||||
const response: Cipher[] = [];
|
const response: Cipher[] = [];
|
||||||
for (const id in ciphers) {
|
for (const id in ciphers) {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
@@ -399,33 +391,27 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
* @deprecated Use `cipherViews$` observable instead
|
* @deprecated Use `cipherViews$` observable instead
|
||||||
*/
|
*/
|
||||||
@sequentialize(() => "getAllDecrypted")
|
@sequentialize(() => "getAllDecrypted")
|
||||||
async getAllDecrypted(): Promise<CipherView[]> {
|
async getAllDecrypted(userId: UserId): Promise<CipherView[]> {
|
||||||
const decCiphers = await this.getDecryptedCiphers();
|
const decCiphers = await this.getDecryptedCiphers(userId);
|
||||||
if (decCiphers != null && decCiphers.length !== 0) {
|
if (decCiphers != null && decCiphers.length !== 0) {
|
||||||
await this.reindexCiphers();
|
await this.reindexCiphers(userId);
|
||||||
return await this.getDecryptedCiphers();
|
return await this.getDecryptedCiphers(userId);
|
||||||
}
|
|
||||||
|
|
||||||
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
|
|
||||||
|
|
||||||
if (activeUserId == null) {
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [newDecCiphers, failedCiphers] = await this.decryptCiphers(
|
const [newDecCiphers, failedCiphers] = await this.decryptCiphers(
|
||||||
await this.getAll(),
|
await this.getAll(userId),
|
||||||
activeUserId,
|
userId,
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.setDecryptedCipherCache(newDecCiphers, activeUserId);
|
await this.setDecryptedCipherCache(newDecCiphers, userId);
|
||||||
await this.setFailedDecryptedCiphers(failedCiphers, activeUserId);
|
await this.setFailedDecryptedCiphers(failedCiphers, userId);
|
||||||
|
|
||||||
return newDecCiphers;
|
return newDecCiphers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getDecryptedCiphers() {
|
private async getDecryptedCiphers(userId: UserId) {
|
||||||
return Object.values(
|
return Object.values(
|
||||||
await firstValueFrom(this.decryptedCiphersState.state$.pipe(map((c) => c ?? {}))),
|
await firstValueFrom(this.decryptedCiphersState(userId).state$.pipe(map((c) => c ?? {}))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,18 +477,21 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async reindexCiphers() {
|
private async reindexCiphers(userId: UserId) {
|
||||||
const userId = await this.stateService.getUserId();
|
|
||||||
const reindexRequired =
|
const reindexRequired =
|
||||||
this.searchService != null &&
|
this.searchService != null &&
|
||||||
((await firstValueFrom(this.searchService.indexedEntityId$)) ?? userId) !== userId;
|
((await firstValueFrom(this.searchService.indexedEntityId$)) ?? userId) !== userId;
|
||||||
if (reindexRequired) {
|
if (reindexRequired) {
|
||||||
await this.searchService.indexCiphers(await this.getDecryptedCiphers(), userId);
|
await this.searchService.indexCiphers(await this.getDecryptedCiphers(userId), userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllDecryptedForGrouping(groupingId: string, folder = true): Promise<CipherView[]> {
|
async getAllDecryptedForGrouping(
|
||||||
const ciphers = await this.getAllDecrypted();
|
groupingId: string,
|
||||||
|
userId: UserId,
|
||||||
|
folder = true,
|
||||||
|
): Promise<CipherView[]> {
|
||||||
|
const ciphers = await this.getAllDecrypted(userId);
|
||||||
|
|
||||||
return ciphers.filter((cipher) => {
|
return ciphers.filter((cipher) => {
|
||||||
if (cipher.isDeleted) {
|
if (cipher.isDeleted) {
|
||||||
@@ -524,10 +513,11 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
|
|
||||||
async getAllDecryptedForUrl(
|
async getAllDecryptedForUrl(
|
||||||
url: string,
|
url: string,
|
||||||
|
userId: UserId,
|
||||||
includeOtherTypes?: CipherType[],
|
includeOtherTypes?: CipherType[],
|
||||||
defaultMatch: UriMatchStrategySetting = null,
|
defaultMatch: UriMatchStrategySetting = null,
|
||||||
): Promise<CipherView[]> {
|
): Promise<CipherView[]> {
|
||||||
const ciphers = await this.getAllDecrypted();
|
const ciphers = await this.getAllDecrypted(userId);
|
||||||
return await this.filterCiphersForUrl(ciphers, url, includeOtherTypes, defaultMatch);
|
return await this.filterCiphersForUrl(ciphers, url, includeOtherTypes, defaultMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,8 +559,11 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getAllDecryptedCiphersOfType(type: CipherType[]): Promise<CipherView[]> {
|
private async getAllDecryptedCiphersOfType(
|
||||||
const ciphers = await this.getAllDecrypted();
|
type: CipherType[],
|
||||||
|
userId: UserId,
|
||||||
|
): Promise<CipherView[]> {
|
||||||
|
const ciphers = await this.getAllDecrypted(userId);
|
||||||
return ciphers
|
return ciphers
|
||||||
.filter((cipher) => cipher.deletedDate == null && type.includes(cipher.type))
|
.filter((cipher) => cipher.deletedDate == null && type.includes(cipher.type))
|
||||||
.sort((a, b) => this.sortCiphersByLastUsedThenName(a, b));
|
.sort((a, b) => this.sortCiphersByLastUsedThenName(a, b));
|
||||||
@@ -613,23 +606,31 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
return decCiphers;
|
return decCiphers;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLastUsedForUrl(url: string, autofillOnPageLoad = false): Promise<CipherView> {
|
async getLastUsedForUrl(
|
||||||
return this.getCipherForUrl(url, true, false, autofillOnPageLoad);
|
url: string,
|
||||||
|
userId: UserId,
|
||||||
|
autofillOnPageLoad = false,
|
||||||
|
): Promise<CipherView> {
|
||||||
|
return this.getCipherForUrl(url, userId, true, false, autofillOnPageLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLastLaunchedForUrl(url: string, autofillOnPageLoad = false): Promise<CipherView> {
|
async getLastLaunchedForUrl(
|
||||||
return this.getCipherForUrl(url, false, true, autofillOnPageLoad);
|
url: string,
|
||||||
|
userId: UserId,
|
||||||
|
autofillOnPageLoad = false,
|
||||||
|
): Promise<CipherView> {
|
||||||
|
return this.getCipherForUrl(url, userId, false, true, autofillOnPageLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNextCipherForUrl(url: string): Promise<CipherView> {
|
async getNextCipherForUrl(url: string, userId: UserId): Promise<CipherView> {
|
||||||
return this.getCipherForUrl(url, false, false, false);
|
return this.getCipherForUrl(url, userId, false, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNextCardCipher(): Promise<CipherView> {
|
async getNextCardCipher(userId: UserId): Promise<CipherView> {
|
||||||
const cacheKey = "cardCiphers";
|
const cacheKey = "cardCiphers";
|
||||||
|
|
||||||
if (!this.sortedCiphersCache.isCached(cacheKey)) {
|
if (!this.sortedCiphersCache.isCached(cacheKey)) {
|
||||||
const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Card]);
|
const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Card], userId);
|
||||||
if (!ciphers?.length) {
|
if (!ciphers?.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -640,11 +641,11 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
return this.sortedCiphersCache.getNext(cacheKey);
|
return this.sortedCiphersCache.getNext(cacheKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNextIdentityCipher(): Promise<CipherView> {
|
async getNextIdentityCipher(userId: UserId): Promise<CipherView> {
|
||||||
const cacheKey = "identityCiphers";
|
const cacheKey = "identityCiphers";
|
||||||
|
|
||||||
if (!this.sortedCiphersCache.isCached(cacheKey)) {
|
if (!this.sortedCiphersCache.isCached(cacheKey)) {
|
||||||
const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Identity]);
|
const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Identity], userId);
|
||||||
if (!ciphers?.length) {
|
if (!ciphers?.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -659,9 +660,8 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
this.sortedCiphersCache.updateLastUsedIndex(url);
|
this.sortedCiphersCache.updateLastUsedIndex(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateLastUsedDate(id: string): Promise<void> {
|
async updateLastUsedDate(id: string, userId: UserId): Promise<void> {
|
||||||
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
|
let ciphersLocalData = await firstValueFrom(this.localData$(userId));
|
||||||
let ciphersLocalData = await firstValueFrom(this.localData$);
|
|
||||||
|
|
||||||
if (!ciphersLocalData) {
|
if (!ciphersLocalData) {
|
||||||
ciphersLocalData = {};
|
ciphersLocalData = {};
|
||||||
@@ -676,9 +676,9 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.localDataState.update(() => ciphersLocalData);
|
await this.localDataState(userId).update(() => ciphersLocalData);
|
||||||
|
|
||||||
const decryptedCipherCache = await this.getDecryptedCiphers();
|
const decryptedCipherCache = await this.getDecryptedCiphers(userId);
|
||||||
if (!decryptedCipherCache) {
|
if (!decryptedCipherCache) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -693,9 +693,8 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
await this.setDecryptedCiphers(decryptedCipherCache, userId);
|
await this.setDecryptedCiphers(decryptedCipherCache, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateLastLaunchedDate(id: string): Promise<void> {
|
async updateLastLaunchedDate(id: string, userId: UserId): Promise<void> {
|
||||||
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
|
let ciphersLocalData = await firstValueFrom(this.localData$(userId));
|
||||||
let ciphersLocalData = await firstValueFrom(this.localData$);
|
|
||||||
|
|
||||||
if (!ciphersLocalData) {
|
if (!ciphersLocalData) {
|
||||||
ciphersLocalData = {};
|
ciphersLocalData = {};
|
||||||
@@ -707,9 +706,9 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
lastUsedDate: currentTime,
|
lastUsedDate: currentTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.localDataState.update(() => ciphersLocalData);
|
await this.localDataState(userId).update(() => ciphersLocalData);
|
||||||
|
|
||||||
const decryptedCipherCache = await this.getDecryptedCiphers();
|
const decryptedCipherCache = await this.getDecryptedCiphers(userId);
|
||||||
if (!decryptedCipherCache) {
|
if (!decryptedCipherCache) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -914,13 +913,13 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
return new Cipher(cData);
|
return new Cipher(cData);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveCollectionsWithServer(cipher: Cipher): Promise<Cipher> {
|
async saveCollectionsWithServer(cipher: Cipher, userId: UserId): Promise<Cipher> {
|
||||||
const request = new CipherCollectionsRequest(cipher.collectionIds);
|
const request = new CipherCollectionsRequest(cipher.collectionIds);
|
||||||
const response = await this.apiService.putCipherCollections(cipher.id, request);
|
const response = await this.apiService.putCipherCollections(cipher.id, request);
|
||||||
// The response will now check for an unavailable value. This value determines whether
|
// The response will now check for an unavailable value. This value determines whether
|
||||||
// the user still has Can Manage access to the item after updating.
|
// the user still has Can Manage access to the item after updating.
|
||||||
if (response.unavailable) {
|
if (response.unavailable) {
|
||||||
await this.delete(cipher.id);
|
await this.delete(cipher.id, userId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = new CipherData(response.cipher);
|
const data = new CipherData(response.cipher);
|
||||||
@@ -944,6 +943,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
*/
|
*/
|
||||||
async bulkUpdateCollectionsWithServer(
|
async bulkUpdateCollectionsWithServer(
|
||||||
orgId: OrganizationId,
|
orgId: OrganizationId,
|
||||||
|
userId: UserId,
|
||||||
cipherIds: CipherId[],
|
cipherIds: CipherId[],
|
||||||
collectionIds: CollectionId[],
|
collectionIds: CollectionId[],
|
||||||
removeCollections: boolean = false,
|
removeCollections: boolean = false,
|
||||||
@@ -958,7 +958,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
await this.apiService.send("POST", "/ciphers/bulk-collections", request, true, false);
|
await this.apiService.send("POST", "/ciphers/bulk-collections", request, true, false);
|
||||||
|
|
||||||
// Update the local state
|
// Update the local state
|
||||||
const ciphers = await firstValueFrom(this.ciphers$);
|
const ciphers = await firstValueFrom(this.ciphers$(userId));
|
||||||
|
|
||||||
for (const id of cipherIds) {
|
for (const id of cipherIds) {
|
||||||
const cipher = ciphers[id];
|
const cipher = ciphers[id];
|
||||||
@@ -975,7 +975,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.clearCache();
|
await this.clearCache();
|
||||||
await this.encryptedCiphersState.update(() => ciphers);
|
await this.encryptedCiphersState(userId).update(() => ciphers);
|
||||||
}
|
}
|
||||||
|
|
||||||
async upsert(cipher: CipherData | CipherData[]): Promise<Record<CipherId, CipherData>> {
|
async upsert(cipher: CipherData | CipherData[]): Promise<Record<CipherId, CipherData>> {
|
||||||
@@ -1016,10 +1016,10 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
await this.clearCache(userId);
|
await this.clearCache(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async moveManyWithServer(ids: string[], folderId: string): Promise<any> {
|
async moveManyWithServer(ids: string[], folderId: string, userId: UserId): Promise<any> {
|
||||||
await this.apiService.putMoveCiphers(new CipherBulkMoveRequest(ids, folderId));
|
await this.apiService.putMoveCiphers(new CipherBulkMoveRequest(ids, folderId));
|
||||||
|
|
||||||
let ciphers = await firstValueFrom(this.ciphers$);
|
let ciphers = await firstValueFrom(this.ciphers$(userId));
|
||||||
if (ciphers == null) {
|
if (ciphers == null) {
|
||||||
ciphers = {};
|
ciphers = {};
|
||||||
}
|
}
|
||||||
@@ -1032,11 +1032,11 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await this.clearCache();
|
await this.clearCache();
|
||||||
await this.encryptedCiphersState.update(() => ciphers);
|
await this.encryptedCiphersState(userId).update(() => ciphers);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(id: string | string[]): Promise<any> {
|
async delete(id: string | string[], userId: UserId): Promise<any> {
|
||||||
const ciphers = await firstValueFrom(this.ciphers$);
|
const ciphers = await firstValueFrom(this.ciphers$(userId));
|
||||||
if (ciphers == null) {
|
if (ciphers == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1054,35 +1054,36 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.clearCache();
|
await this.clearCache();
|
||||||
await this.encryptedCiphersState.update(() => ciphers);
|
await this.encryptedCiphersState(userId).update(() => ciphers);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteWithServer(id: string, asAdmin = false): Promise<any> {
|
async deleteWithServer(id: string, userId: UserId, asAdmin = false): Promise<any> {
|
||||||
if (asAdmin) {
|
if (asAdmin) {
|
||||||
await this.apiService.deleteCipherAdmin(id);
|
await this.apiService.deleteCipherAdmin(id);
|
||||||
} else {
|
} else {
|
||||||
await this.apiService.deleteCipher(id);
|
await this.apiService.deleteCipher(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.delete(id);
|
await this.delete(id, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteManyWithServer(ids: string[], asAdmin = false): Promise<any> {
|
async deleteManyWithServer(ids: string[], userId: UserId, asAdmin = false): Promise<any> {
|
||||||
const request = new CipherBulkDeleteRequest(ids);
|
const request = new CipherBulkDeleteRequest(ids);
|
||||||
if (asAdmin) {
|
if (asAdmin) {
|
||||||
await this.apiService.deleteManyCiphersAdmin(request);
|
await this.apiService.deleteManyCiphersAdmin(request);
|
||||||
} else {
|
} else {
|
||||||
await this.apiService.deleteManyCiphers(request);
|
await this.apiService.deleteManyCiphers(request);
|
||||||
}
|
}
|
||||||
await this.delete(ids);
|
await this.delete(ids, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteAttachment(
|
async deleteAttachment(
|
||||||
id: string,
|
id: string,
|
||||||
revisionDate: string,
|
revisionDate: string,
|
||||||
attachmentId: string,
|
attachmentId: string,
|
||||||
|
userId: UserId,
|
||||||
): Promise<CipherData> {
|
): Promise<CipherData> {
|
||||||
let ciphers = await firstValueFrom(this.ciphers$);
|
let ciphers = await firstValueFrom(this.ciphers$(userId));
|
||||||
const cipherId = id as CipherId;
|
const cipherId = id as CipherId;
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
if (ciphers == null || !ciphers.hasOwnProperty(id) || ciphers[cipherId].attachments == null) {
|
if (ciphers == null || !ciphers.hasOwnProperty(id) || ciphers[cipherId].attachments == null) {
|
||||||
@@ -1100,7 +1101,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
ciphers[cipherId].revisionDate = revisionDate;
|
ciphers[cipherId].revisionDate = revisionDate;
|
||||||
|
|
||||||
await this.clearCache();
|
await this.clearCache();
|
||||||
await this.encryptedCiphersState.update(() => {
|
await this.encryptedCiphersState(userId).update(() => {
|
||||||
if (ciphers == null) {
|
if (ciphers == null) {
|
||||||
ciphers = {};
|
ciphers = {};
|
||||||
}
|
}
|
||||||
@@ -1110,7 +1111,11 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
return ciphers[cipherId];
|
return ciphers[cipherId];
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteAttachmentWithServer(id: string, attachmentId: string): Promise<CipherData> {
|
async deleteAttachmentWithServer(
|
||||||
|
id: string,
|
||||||
|
attachmentId: string,
|
||||||
|
userId: UserId,
|
||||||
|
): Promise<CipherData> {
|
||||||
let cipherResponse = null;
|
let cipherResponse = null;
|
||||||
try {
|
try {
|
||||||
cipherResponse = await this.apiService.deleteCipherAttachment(id, attachmentId);
|
cipherResponse = await this.apiService.deleteCipherAttachment(id, attachmentId);
|
||||||
@@ -1119,7 +1124,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
}
|
}
|
||||||
const cipherData = CipherData.fromJSON(cipherResponse?.cipher);
|
const cipherData = CipherData.fromJSON(cipherResponse?.cipher);
|
||||||
|
|
||||||
return await this.deleteAttachment(id, cipherData.revisionDate, attachmentId);
|
return await this.deleteAttachment(id, cipherData.revisionDate, attachmentId, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
sortCiphersByLastUsed(a: CipherView, b: CipherView): number {
|
sortCiphersByLastUsed(a: CipherView, b: CipherView): number {
|
||||||
@@ -1192,8 +1197,8 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async softDelete(id: string | string[]): Promise<any> {
|
async softDelete(id: string | string[], userId: UserId): Promise<any> {
|
||||||
let ciphers = await firstValueFrom(this.ciphers$);
|
let ciphers = await firstValueFrom(this.ciphers$(userId));
|
||||||
if (ciphers == null) {
|
if (ciphers == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1212,7 +1217,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.clearCache();
|
await this.clearCache();
|
||||||
await this.encryptedCiphersState.update(() => {
|
await this.encryptedCiphersState(userId).update(() => {
|
||||||
if (ciphers == null) {
|
if (ciphers == null) {
|
||||||
ciphers = {};
|
ciphers = {};
|
||||||
}
|
}
|
||||||
@@ -1220,17 +1225,17 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async softDeleteWithServer(id: string, asAdmin = false): Promise<any> {
|
async softDeleteWithServer(id: string, userId: UserId, asAdmin = false): Promise<any> {
|
||||||
if (asAdmin) {
|
if (asAdmin) {
|
||||||
await this.apiService.putDeleteCipherAdmin(id);
|
await this.apiService.putDeleteCipherAdmin(id);
|
||||||
} else {
|
} else {
|
||||||
await this.apiService.putDeleteCipher(id);
|
await this.apiService.putDeleteCipher(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.softDelete(id);
|
await this.softDelete(id, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async softDeleteManyWithServer(ids: string[], asAdmin = false): Promise<any> {
|
async softDeleteManyWithServer(ids: string[], userId: UserId, asAdmin = false): Promise<any> {
|
||||||
const request = new CipherBulkDeleteRequest(ids);
|
const request = new CipherBulkDeleteRequest(ids);
|
||||||
if (asAdmin) {
|
if (asAdmin) {
|
||||||
await this.apiService.putDeleteManyCiphersAdmin(request);
|
await this.apiService.putDeleteManyCiphersAdmin(request);
|
||||||
@@ -1238,13 +1243,14 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
await this.apiService.putDeleteManyCiphers(request);
|
await this.apiService.putDeleteManyCiphers(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.softDelete(ids);
|
await this.softDelete(ids, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async restore(
|
async restore(
|
||||||
cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[],
|
cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[],
|
||||||
|
userId: UserId,
|
||||||
) {
|
) {
|
||||||
let ciphers = await firstValueFrom(this.ciphers$);
|
let ciphers = await firstValueFrom(this.ciphers$(userId));
|
||||||
if (ciphers == null) {
|
if (ciphers == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1265,7 +1271,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.clearCache();
|
await this.clearCache();
|
||||||
await this.encryptedCiphersState.update(() => {
|
await this.encryptedCiphersState(userId).update(() => {
|
||||||
if (ciphers == null) {
|
if (ciphers == null) {
|
||||||
ciphers = {};
|
ciphers = {};
|
||||||
}
|
}
|
||||||
@@ -1273,7 +1279,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async restoreWithServer(id: string, asAdmin = false): Promise<any> {
|
async restoreWithServer(id: string, userId: UserId, asAdmin = false): Promise<any> {
|
||||||
let response;
|
let response;
|
||||||
if (asAdmin) {
|
if (asAdmin) {
|
||||||
response = await this.apiService.putRestoreCipherAdmin(id);
|
response = await this.apiService.putRestoreCipherAdmin(id);
|
||||||
@@ -1281,14 +1287,14 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
response = await this.apiService.putRestoreCipher(id);
|
response = await this.apiService.putRestoreCipher(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.restore({ id: id, revisionDate: response.revisionDate });
|
await this.restore({ id: id, revisionDate: response.revisionDate }, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No longer using an asAdmin Param. Org Vault bulkRestore will assess if an item is unassigned or editable
|
* No longer using an asAdmin Param. Org Vault bulkRestore will assess if an item is unassigned or editable
|
||||||
* The Org Vault will pass those ids an array as well as the orgId when calling bulkRestore
|
* The Org Vault will pass those ids an array as well as the orgId when calling bulkRestore
|
||||||
*/
|
*/
|
||||||
async restoreManyWithServer(ids: string[], orgId: string = null): Promise<void> {
|
async restoreManyWithServer(ids: string[], userId: UserId, orgId: string = null): Promise<void> {
|
||||||
let response;
|
let response;
|
||||||
|
|
||||||
if (orgId) {
|
if (orgId) {
|
||||||
@@ -1303,7 +1309,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
for (const cipher of response.data) {
|
for (const cipher of response.data) {
|
||||||
restores.push({ id: cipher.id, revisionDate: cipher.revisionDate });
|
restores.push({ id: cipher.id, revisionDate: cipher.revisionDate });
|
||||||
}
|
}
|
||||||
await this.restore(restores);
|
await this.restore(restores, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getKeyForCipherKeyDecryption(cipher: Cipher, userId: UserId): Promise<UserKey | OrgKey> {
|
async getKeyForCipherKeyDecryption(cipher: Cipher, userId: UserId): Promise<UserKey | OrgKey> {
|
||||||
@@ -1313,8 +1319,8 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setAddEditCipherInfo(value: AddEditCipherInfo) {
|
async setAddEditCipherInfo(value: AddEditCipherInfo, userId: UserId) {
|
||||||
await this.addEditCipherInfoState.update(() => value, {
|
await this.addEditCipherInfoState(userId).update(() => value, {
|
||||||
shouldUpdate: (current) => !(current == null && value == null),
|
shouldUpdate: (current) => !(current == null && value == null),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1333,8 +1339,8 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
|
|
||||||
let encryptedCiphers: CipherWithIdRequest[] = [];
|
let encryptedCiphers: CipherWithIdRequest[] = [];
|
||||||
|
|
||||||
const ciphers = await firstValueFrom(this.cipherViews$);
|
const ciphers = await firstValueFrom(this.cipherViews$(userId));
|
||||||
const failedCiphers = await firstValueFrom(this.failedToDecryptCiphers$);
|
const failedCiphers = await firstValueFrom(this.failedToDecryptCiphers$(userId));
|
||||||
if (!ciphers) {
|
if (!ciphers) {
|
||||||
return encryptedCiphers;
|
return encryptedCiphers;
|
||||||
}
|
}
|
||||||
@@ -1357,6 +1363,41 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
return encryptedCiphers;
|
return encryptedCiphers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns a SingleUserState
|
||||||
|
*/
|
||||||
|
private localDataState(userId: UserId) {
|
||||||
|
return this.stateProvider.getUser(userId, LOCAL_DATA_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns a SingleUserState for the encrypted ciphers
|
||||||
|
*/
|
||||||
|
private encryptedCiphersState(userId: UserId) {
|
||||||
|
return this.stateProvider.getUser(userId, ENCRYPTED_CIPHERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns a SingleUserState for the decrypted ciphers
|
||||||
|
*/
|
||||||
|
private decryptedCiphersState(userId: UserId) {
|
||||||
|
return this.stateProvider.getUser(userId, DECRYPTED_CIPHERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns a SingleUserState for the add/edit cipher info
|
||||||
|
*/
|
||||||
|
private addEditCipherInfoState(userId: UserId) {
|
||||||
|
return this.stateProvider.getUser(userId, ADD_EDIT_CIPHER_INFO_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns a SingleUserState for the failed to decrypt ciphers
|
||||||
|
*/
|
||||||
|
private failedToDecryptCiphersState(userId: UserId) {
|
||||||
|
return this.stateProvider.getUser(userId, FAILED_DECRYPTED_CIPHERS);
|
||||||
|
}
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
// In the case of a cipher that is being shared with an organization, we want to decrypt the
|
// In the case of a cipher that is being shared with an organization, we want to decrypt the
|
||||||
@@ -1660,6 +1701,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
|
|
||||||
private async getCipherForUrl(
|
private async getCipherForUrl(
|
||||||
url: string,
|
url: string,
|
||||||
|
userId: UserId,
|
||||||
lastUsed: boolean,
|
lastUsed: boolean,
|
||||||
lastLaunched: boolean,
|
lastLaunched: boolean,
|
||||||
autofillOnPageLoad: boolean,
|
autofillOnPageLoad: boolean,
|
||||||
@@ -1667,7 +1709,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
const cacheKey = autofillOnPageLoad ? "autofillOnPageLoad-" + url : url;
|
const cacheKey = autofillOnPageLoad ? "autofillOnPageLoad-" + url : url;
|
||||||
|
|
||||||
if (!this.sortedCiphersCache.isCached(cacheKey)) {
|
if (!this.sortedCiphersCache.isCached(cacheKey)) {
|
||||||
let ciphers = await this.getAllDecryptedForUrl(url);
|
let ciphers = await this.getAllDecryptedForUrl(url, userId);
|
||||||
if (!ciphers) {
|
if (!ciphers) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ export class FolderService implements InternalFolderServiceAbstraction {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Items in a deleted folder are re-assigned to "No Folder"
|
// Items in a deleted folder are re-assigned to "No Folder"
|
||||||
const ciphers = await this.cipherService.getAll();
|
const ciphers = await this.cipherService.getAll(userId);
|
||||||
if (ciphers != null) {
|
if (ciphers != null) {
|
||||||
const updates: Cipher[] = [];
|
const updates: Cipher[] = [];
|
||||||
for (const cId in ciphers) {
|
for (const cId in ciphers) {
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export class DefaultUserAsymmetricKeysRegenerationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The private isn't decryptable, check to see if we can decrypt something with the userKey.
|
// The private isn't decryptable, check to see if we can decrypt something with the userKey.
|
||||||
const userKeyCanDecrypt = await this.userKeyCanDecrypt(userKey);
|
const userKeyCanDecrypt = await this.userKeyCanDecrypt(userKey, userId);
|
||||||
if (userKeyCanDecrypt) {
|
if (userKeyCanDecrypt) {
|
||||||
this.logService.info(
|
this.logService.info(
|
||||||
"[UserAsymmetricKeyRegeneration] User Asymmetric Key decryption failure detected, attempting regeneration.",
|
"[UserAsymmetricKeyRegeneration] User Asymmetric Key decryption failure detected, attempting regeneration.",
|
||||||
@@ -155,8 +155,8 @@ export class DefaultUserAsymmetricKeysRegenerationService
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async userKeyCanDecrypt(userKey: UserKey): Promise<boolean> {
|
private async userKeyCanDecrypt(userKey: UserKey, userId: UserId): Promise<boolean> {
|
||||||
const ciphers = await this.cipherService.getAll();
|
const ciphers = await this.cipherService.getAll(userId);
|
||||||
const cipher = ciphers.find((cipher) => cipher.organizationId == null);
|
const cipher = ciphers.find((cipher) => cipher.organizationId == null);
|
||||||
|
|
||||||
if (cipher != null) {
|
if (cipher != null) {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import * as papa from "papaparse";
|
import * as papa from "papaparse";
|
||||||
import { firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { CipherWithIdExport, FolderWithIdExport } from "@bitwarden/common/models/export";
|
import { CipherWithIdExport, FolderWithIdExport } from "@bitwarden/common/models/export";
|
||||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||||
@@ -32,8 +33,6 @@ export class IndividualVaultExportService
|
|||||||
extends BaseVaultExportService
|
extends BaseVaultExportService
|
||||||
implements IndividualVaultExportServiceAbstraction
|
implements IndividualVaultExportServiceAbstraction
|
||||||
{
|
{
|
||||||
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private folderService: FolderService,
|
private folderService: FolderService,
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
@@ -63,7 +62,7 @@ export class IndividualVaultExportService
|
|||||||
let decFolders: FolderView[] = [];
|
let decFolders: FolderView[] = [];
|
||||||
let decCiphers: CipherView[] = [];
|
let decCiphers: CipherView[] = [];
|
||||||
const promises = [];
|
const promises = [];
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
firstValueFrom(this.folderService.folderViews$(activeUserId)).then((folders) => {
|
firstValueFrom(this.folderService.folderViews$(activeUserId)).then((folders) => {
|
||||||
@@ -72,7 +71,7 @@ export class IndividualVaultExportService
|
|||||||
);
|
);
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
this.cipherService.getAllDecrypted().then((ciphers) => {
|
this.cipherService.getAllDecrypted(activeUserId).then((ciphers) => {
|
||||||
decCiphers = ciphers.filter((f) => f.deletedDate == null);
|
decCiphers = ciphers.filter((f) => f.deletedDate == null);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -90,7 +89,7 @@ export class IndividualVaultExportService
|
|||||||
let folders: Folder[] = [];
|
let folders: Folder[] = [];
|
||||||
let ciphers: Cipher[] = [];
|
let ciphers: Cipher[] = [];
|
||||||
const promises = [];
|
const promises = [];
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
firstValueFrom(this.folderService.folders$(activeUserId)).then((f) => {
|
firstValueFrom(this.folderService.folders$(activeUserId)).then((f) => {
|
||||||
@@ -99,7 +98,7 @@ export class IndividualVaultExportService
|
|||||||
);
|
);
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
this.cipherService.getAll().then((c) => {
|
this.cipherService.getAll(activeUserId).then((c) => {
|
||||||
ciphers = c.filter((f) => f.deletedDate == null);
|
ciphers = c.filter((f) => f.deletedDate == null);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -107,7 +106,7 @@ export class IndividualVaultExportService
|
|||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
||||||
const userKey = await this.keyService.getUserKeyWithLegacySupport(
|
const userKey = await this.keyService.getUserKeyWithLegacySupport(
|
||||||
await firstValueFrom(this.activeUserId$),
|
await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)),
|
||||||
);
|
);
|
||||||
const encKeyValidation = await this.encryptService.encrypt(Utils.newGuid(), userKey);
|
const encKeyValidation = await this.encryptService.encrypt(Utils.newGuid(), userKey);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import * as papa from "papaparse";
|
import * as papa from "papaparse";
|
||||||
import { firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CollectionService,
|
CollectionService,
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { CipherWithIdExport, CollectionWithIdExport } from "@bitwarden/common/models/export";
|
import { CipherWithIdExport, CollectionWithIdExport } from "@bitwarden/common/models/export";
|
||||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||||
@@ -94,9 +95,7 @@ export class OrganizationVaultExportService
|
|||||||
const decCollections: CollectionView[] = [];
|
const decCollections: CollectionView[] = [];
|
||||||
const decCiphers: CipherView[] = [];
|
const decCiphers: CipherView[] = [];
|
||||||
const promises = [];
|
const promises = [];
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
this.apiService.getOrganizationExport(organizationId).then((exportData) => {
|
this.apiService.getOrganizationExport(organizationId).then((exportData) => {
|
||||||
@@ -184,6 +183,7 @@ export class OrganizationVaultExportService
|
|||||||
let allDecCiphers: CipherView[] = [];
|
let allDecCiphers: CipherView[] = [];
|
||||||
let decCollections: CollectionView[] = [];
|
let decCollections: CollectionView[] = [];
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
this.collectionService.getAllDecrypted().then(async (collections) => {
|
this.collectionService.getAllDecrypted().then(async (collections) => {
|
||||||
@@ -192,7 +192,7 @@ export class OrganizationVaultExportService
|
|||||||
);
|
);
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
this.cipherService.getAllDecrypted().then((ciphers) => {
|
this.cipherService.getAllDecrypted(activeUserId).then((ciphers) => {
|
||||||
allDecCiphers = ciphers;
|
allDecCiphers = ciphers;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -216,6 +216,7 @@ export class OrganizationVaultExportService
|
|||||||
let allCiphers: Cipher[] = [];
|
let allCiphers: Cipher[] = [];
|
||||||
let encCollections: Collection[] = [];
|
let encCollections: Collection[] = [];
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
this.collectionService.getAll().then((collections) => {
|
this.collectionService.getAll().then((collections) => {
|
||||||
@@ -224,7 +225,7 @@ export class OrganizationVaultExportService
|
|||||||
);
|
);
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
this.cipherService.getAll().then((ciphers) => {
|
this.cipherService.getAll(activeUserId).then((ciphers) => {
|
||||||
allCiphers = ciphers;
|
allCiphers = ciphers;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ describe("CipherAttachmentsComponent", () => {
|
|||||||
it("fetches cipherView using `cipherId`", async () => {
|
it("fetches cipherView using `cipherId`", async () => {
|
||||||
await component.ngOnInit();
|
await component.ngOnInit();
|
||||||
|
|
||||||
expect(cipherServiceGet).toHaveBeenCalledWith("5555-444-3333");
|
expect(cipherServiceGet).toHaveBeenCalledWith("5555-444-3333", mockUserId);
|
||||||
expect(component.cipher).toEqual(cipherView);
|
expect(component.cipher).toEqual(cipherView);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,11 @@ import {
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
Validators,
|
Validators,
|
||||||
} from "@angular/forms";
|
} from "@angular/forms";
|
||||||
import { firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
||||||
@@ -118,10 +119,8 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this.cipherDomain = await this.cipherService.get(this.cipherId);
|
this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
this.activeUserId = await firstValueFrom(
|
this.cipherDomain = await this.cipherService.get(this.cipherId, this.activeUserId);
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
this.cipher = await this.cipherDomain.decrypt(
|
this.cipher = await this.cipherDomain.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, this.activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, this.activeUserId),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,12 +2,16 @@ import { ComponentFixture, TestBed } from "@angular/core/testing";
|
|||||||
import { By } from "@angular/platform-browser";
|
import { By } from "@angular/platform-browser";
|
||||||
import { mock } from "jest-mock-extended";
|
import { mock } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
|
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
|
||||||
import { DialogService, ToastService } from "@bitwarden/components";
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { mockAccountServiceWith } from "../../../../../../common/spec";
|
||||||
|
|
||||||
import { DeleteAttachmentComponent } from "./delete-attachment.component";
|
import { DeleteAttachmentComponent } from "./delete-attachment.component";
|
||||||
|
|
||||||
describe("DeleteAttachmentComponent", () => {
|
describe("DeleteAttachmentComponent", () => {
|
||||||
@@ -42,6 +46,7 @@ describe("DeleteAttachmentComponent", () => {
|
|||||||
},
|
},
|
||||||
{ provide: I18nService, useValue: { t: (key: string) => key } },
|
{ provide: I18nService, useValue: { t: (key: string) => key } },
|
||||||
{ provide: LogService, useValue: mock<LogService>() },
|
{ provide: LogService, useValue: mock<LogService>() },
|
||||||
|
{ provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
.overrideProvider(DialogService, {
|
.overrideProvider(DialogService, {
|
||||||
@@ -90,7 +95,11 @@ describe("DeleteAttachmentComponent", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Called with cipher id and attachment id
|
// Called with cipher id and attachment id
|
||||||
expect(deleteAttachmentWithServer).toHaveBeenCalledWith("5555-444-3333", "222-3333-4444");
|
expect(deleteAttachmentWithServer).toHaveBeenCalledWith(
|
||||||
|
"5555-444-3333",
|
||||||
|
"222-3333-4444",
|
||||||
|
"UserId",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows toast message on successful deletion", async () => {
|
it("shows toast message on successful deletion", async () => {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
@@ -36,6 +39,7 @@ export class DeleteAttachmentComponent {
|
|||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
|
private accountService: AccountService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
delete = async () => {
|
delete = async () => {
|
||||||
@@ -50,7 +54,19 @@ export class DeleteAttachmentComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.cipherService.deleteAttachmentWithServer(this.cipherId, this.attachment.id);
|
const activeUserId = await firstValueFrom(
|
||||||
|
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (activeUserId == null) {
|
||||||
|
throw new Error("An active user is expected while deleting an attachment.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.cipherService.deleteAttachmentWithServer(
|
||||||
|
this.cipherId,
|
||||||
|
this.attachment.id,
|
||||||
|
activeUserId,
|
||||||
|
);
|
||||||
|
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
|
|||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums";
|
import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
import { CipherId, UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||||
@@ -34,14 +35,12 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService {
|
|||||||
private collectionService: CollectionService = inject(CollectionService);
|
private collectionService: CollectionService = inject(CollectionService);
|
||||||
private accountService = inject(AccountService);
|
private accountService = inject(AccountService);
|
||||||
|
|
||||||
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
|
|
||||||
|
|
||||||
async buildConfig(
|
async buildConfig(
|
||||||
mode: CipherFormMode,
|
mode: CipherFormMode,
|
||||||
cipherId?: CipherId,
|
cipherId?: CipherId,
|
||||||
cipherType?: CipherType,
|
cipherType?: CipherType,
|
||||||
): Promise<CipherFormConfig> {
|
): Promise<CipherFormConfig> {
|
||||||
const activeUserId = await firstValueFrom(this.activeUserId$);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|
||||||
const [organizations, collections, allowPersonalOwnership, folders, cipher] =
|
const [organizations, collections, allowPersonalOwnership, folders, cipher] =
|
||||||
await firstValueFrom(
|
await firstValueFrom(
|
||||||
@@ -62,7 +61,7 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
this.getCipher(cipherId),
|
this.getCipher(activeUserId, cipherId),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -94,10 +93,10 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService {
|
|||||||
.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
|
.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
|
||||||
.pipe(map((p) => !p));
|
.pipe(map((p) => !p));
|
||||||
|
|
||||||
private getCipher(id?: CipherId): Promise<Cipher | null> {
|
private getCipher(userId: UserId, id?: CipherId): Promise<Cipher | null> {
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
return this.cipherService.get(id);
|
return this.cipherService.get(id, userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { inject, Injectable } from "@angular/core";
|
import { inject, Injectable } from "@angular/core";
|
||||||
import { firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
@@ -23,9 +24,7 @@ export class DefaultCipherFormService implements CipherFormService {
|
|||||||
private apiService: ApiService = inject(ApiService);
|
private apiService: ApiService = inject(ApiService);
|
||||||
|
|
||||||
async decryptCipher(cipher: Cipher): Promise<CipherView> {
|
async decryptCipher(cipher: Cipher): Promise<CipherView> {
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
return await cipher.decrypt(
|
return await cipher.decrypt(
|
||||||
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
|
||||||
);
|
);
|
||||||
@@ -33,9 +32,7 @@ export class DefaultCipherFormService implements CipherFormService {
|
|||||||
|
|
||||||
async saveCipher(cipher: CipherView, config: CipherFormConfig): Promise<CipherView> {
|
async saveCipher(cipher: CipherView, config: CipherFormConfig): Promise<CipherView> {
|
||||||
// Passing the original cipher is important here as it is responsible for appending to password history
|
// Passing the original cipher is important here as it is responsible for appending to password history
|
||||||
const activeUserId = await firstValueFrom(
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
const encryptedCipher = await this.cipherService.encrypt(
|
const encryptedCipher = await this.cipherService.encrypt(
|
||||||
cipher,
|
cipher,
|
||||||
activeUserId,
|
activeUserId,
|
||||||
@@ -90,7 +87,10 @@ export class DefaultCipherFormService implements CipherFormService {
|
|||||||
// When using an admin config or the cipher was unassigned, update collections as an admin
|
// When using an admin config or the cipher was unassigned, update collections as an admin
|
||||||
savedCipher = await this.cipherService.saveCollectionsWithServerAdmin(encryptedCipher);
|
savedCipher = await this.cipherService.saveCollectionsWithServerAdmin(encryptedCipher);
|
||||||
} else {
|
} else {
|
||||||
savedCipher = await this.cipherService.saveCollectionsWithServer(encryptedCipher);
|
savedCipher = await this.cipherService.saveCollectionsWithServer(
|
||||||
|
encryptedCipher,
|
||||||
|
activeUserId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { Component, Input } from "@angular/core";
|
import { Component, Input } from "@angular/core";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
|
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
|
||||||
@@ -38,10 +41,12 @@ export class AutofillOptionsViewComponent {
|
|||||||
constructor(
|
constructor(
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
|
private accountService: AccountService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async openWebsite(selectedUri: string) {
|
async openWebsite(selectedUri: string) {
|
||||||
await this.cipherService.updateLastLaunchedDate(this.cipherId);
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
await this.cipherService.updateLastLaunchedDate(this.cipherId, activeUserId);
|
||||||
this.platformUtilsService.launchUri(selectedUri);
|
this.platformUtilsService.launchUri(selectedUri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,7 +179,6 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
|
|||||||
private get selectedOrgId(): OrganizationId {
|
private get selectedOrgId(): OrganizationId {
|
||||||
return this.formGroup.getRawValue().selectedOrg || this.params.organizationId;
|
return this.formGroup.getRawValue().selectedOrg || this.params.organizationId;
|
||||||
}
|
}
|
||||||
private activeUserId: UserId;
|
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -193,10 +192,6 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.activeUserId = await firstValueFrom(
|
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
|
|
||||||
);
|
|
||||||
|
|
||||||
const onlyPersonalItems = this.params.ciphers.every((c) => c.organizationId == null);
|
const onlyPersonalItems = this.params.ciphers.every((c) => c.organizationId == null);
|
||||||
|
|
||||||
if (this.selectedOrgId === MY_VAULT_ID || onlyPersonalItems) {
|
if (this.selectedOrgId === MY_VAULT_ID || onlyPersonalItems) {
|
||||||
@@ -253,12 +248,15 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
|
|||||||
.filter((i) => i.organizationId)
|
.filter((i) => i.organizationId)
|
||||||
.map((i) => i.id as CipherId);
|
.map((i) => i.id as CipherId);
|
||||||
|
|
||||||
|
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|
||||||
// Move personal items to the organization
|
// Move personal items to the organization
|
||||||
if (this.personalItemsCount > 0) {
|
if (this.personalItemsCount > 0) {
|
||||||
await this.moveToOrganization(
|
await this.moveToOrganization(
|
||||||
this.selectedOrgId,
|
this.selectedOrgId,
|
||||||
this.params.ciphers.filter((c) => c.organizationId == null),
|
this.params.ciphers.filter((c) => c.organizationId == null),
|
||||||
this.formGroup.controls.collections.value.map((i) => i.id as CollectionId),
|
this.formGroup.controls.collections.value.map((i) => i.id as CollectionId),
|
||||||
|
activeUserId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,8 +265,8 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
|
|||||||
|
|
||||||
// Update assigned collections for single org cipher or bulk update collections for multiple org ciphers
|
// Update assigned collections for single org cipher or bulk update collections for multiple org ciphers
|
||||||
await (isSingleOrgCipher
|
await (isSingleOrgCipher
|
||||||
? this.updateAssignedCollections(this.editableItems[0])
|
? this.updateAssignedCollections(this.editableItems[0], activeUserId)
|
||||||
: this.bulkUpdateCollections(cipherIds));
|
: this.bulkUpdateCollections(cipherIds, activeUserId));
|
||||||
|
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
variant: "success",
|
variant: "success",
|
||||||
@@ -447,12 +445,13 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
|
|||||||
organizationId: OrganizationId,
|
organizationId: OrganizationId,
|
||||||
shareableCiphers: CipherView[],
|
shareableCiphers: CipherView[],
|
||||||
selectedCollectionIds: CollectionId[],
|
selectedCollectionIds: CollectionId[],
|
||||||
|
userId: UserId,
|
||||||
) {
|
) {
|
||||||
await this.cipherService.shareManyWithServer(
|
await this.cipherService.shareManyWithServer(
|
||||||
shareableCiphers,
|
shareableCiphers,
|
||||||
organizationId,
|
organizationId,
|
||||||
selectedCollectionIds,
|
selectedCollectionIds,
|
||||||
this.activeUserId,
|
userId,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
@@ -465,10 +464,11 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async bulkUpdateCollections(cipherIds: CipherId[]) {
|
private async bulkUpdateCollections(cipherIds: CipherId[], userId: UserId) {
|
||||||
if (this.formGroup.controls.collections.value.length > 0) {
|
if (this.formGroup.controls.collections.value.length > 0) {
|
||||||
await this.cipherService.bulkUpdateCollectionsWithServer(
|
await this.cipherService.bulkUpdateCollectionsWithServer(
|
||||||
this.selectedOrgId,
|
this.selectedOrgId,
|
||||||
|
userId,
|
||||||
cipherIds,
|
cipherIds,
|
||||||
this.formGroup.controls.collections.value.map((i) => i.id as CollectionId),
|
this.formGroup.controls.collections.value.map((i) => i.id as CollectionId),
|
||||||
false,
|
false,
|
||||||
@@ -483,6 +483,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
|
|||||||
) {
|
) {
|
||||||
await this.cipherService.bulkUpdateCollectionsWithServer(
|
await this.cipherService.bulkUpdateCollectionsWithServer(
|
||||||
this.selectedOrgId,
|
this.selectedOrgId,
|
||||||
|
userId,
|
||||||
cipherIds,
|
cipherIds,
|
||||||
[this.params.activeCollection.id as CollectionId],
|
[this.params.activeCollection.id as CollectionId],
|
||||||
true,
|
true,
|
||||||
@@ -490,14 +491,14 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateAssignedCollections(cipherView: CipherView) {
|
private async updateAssignedCollections(cipherView: CipherView, userId: UserId) {
|
||||||
const { collections } = this.formGroup.getRawValue();
|
const { collections } = this.formGroup.getRawValue();
|
||||||
cipherView.collectionIds = collections.map((i) => i.id as CollectionId);
|
cipherView.collectionIds = collections.map((i) => i.id as CollectionId);
|
||||||
const cipher = await this.cipherService.encrypt(cipherView, this.activeUserId);
|
const cipher = await this.cipherService.encrypt(cipherView, userId);
|
||||||
if (this.params.isSingleCipherAdmin) {
|
if (this.params.isSingleCipherAdmin) {
|
||||||
await this.cipherService.saveCollectionsWithServerAdmin(cipher);
|
await this.cipherService.saveCollectionsWithServerAdmin(cipher);
|
||||||
} else {
|
} else {
|
||||||
await this.cipherService.saveCollectionsWithServer(cipher);
|
await this.cipherService.saveCollectionsWithServer(cipher, userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user