2023-02-06 22:53:37 +01:00
|
|
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
|
|
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
2023-06-06 22:34:53 +02:00
|
|
|
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
|
|
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
|
|
|
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
2023-01-31 22:08:37 +01:00
|
|
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
|
|
|
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
|
|
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
2023-01-07 01:31:32 +01:00
|
|
|
|
|
|
|
import {
|
|
|
|
authServiceFactory,
|
|
|
|
AuthServiceInitOptions,
|
2023-02-06 22:53:37 +01:00
|
|
|
} from "../../auth/background/service-factories/auth-service.factory";
|
2023-01-31 22:08:37 +01:00
|
|
|
import { Account } from "../../models/account";
|
2023-06-06 22:34:53 +02:00
|
|
|
import { CachedServices } from "../../platform/background/service-factories/factory-options";
|
|
|
|
import { BrowserApi } from "../../platform/browser/browser-api";
|
2023-01-07 01:31:32 +01:00
|
|
|
import {
|
|
|
|
cipherServiceFactory,
|
|
|
|
CipherServiceInitOptions,
|
2023-01-31 22:08:37 +01:00
|
|
|
} from "../../vault/background/service_factories/cipher-service.factory";
|
2023-09-29 23:20:41 +02:00
|
|
|
import { AutofillCipherTypeId } from "../types";
|
2023-01-07 01:31:32 +01:00
|
|
|
|
|
|
|
import { MainContextMenuHandler } from "./main-context-menu-handler";
|
|
|
|
|
|
|
|
const NOT_IMPLEMENTED = (..._args: unknown[]) => Promise.resolve();
|
|
|
|
|
|
|
|
const LISTENED_TO_COMMANDS = [
|
|
|
|
"loggedIn",
|
|
|
|
"unlocked",
|
|
|
|
"syncCompleted",
|
|
|
|
"bgUpdateContextMenu",
|
|
|
|
"editedCipher",
|
|
|
|
"addedCipher",
|
|
|
|
"deletedCipher",
|
|
|
|
];
|
|
|
|
|
|
|
|
export class CipherContextMenuHandler {
|
|
|
|
constructor(
|
|
|
|
private mainContextMenuHandler: MainContextMenuHandler,
|
|
|
|
private authService: AuthService,
|
2023-08-30 17:18:20 +02:00
|
|
|
private cipherService: CipherService
|
2023-01-07 01:31:32 +01:00
|
|
|
) {}
|
|
|
|
|
|
|
|
static async create(cachedServices: CachedServices) {
|
|
|
|
const stateFactory = new StateFactory(GlobalState, Account);
|
|
|
|
const serviceOptions: AuthServiceInitOptions & CipherServiceInitOptions = {
|
|
|
|
apiServiceOptions: {
|
|
|
|
logoutCallback: NOT_IMPLEMENTED,
|
|
|
|
},
|
|
|
|
cryptoFunctionServiceOptions: {
|
|
|
|
win: self,
|
|
|
|
},
|
|
|
|
encryptServiceOptions: {
|
|
|
|
logMacFailures: false,
|
|
|
|
},
|
|
|
|
i18nServiceOptions: {
|
|
|
|
systemLanguage: chrome.i18n.getUILanguage(),
|
|
|
|
},
|
|
|
|
keyConnectorServiceOptions: {
|
|
|
|
logoutCallback: NOT_IMPLEMENTED,
|
|
|
|
},
|
|
|
|
logServiceOptions: {
|
|
|
|
isDev: false,
|
|
|
|
},
|
|
|
|
platformUtilsServiceOptions: {
|
|
|
|
biometricCallback: () => Promise.resolve(false),
|
|
|
|
clipboardWriteCallback: NOT_IMPLEMENTED,
|
|
|
|
win: self,
|
|
|
|
},
|
|
|
|
stateServiceOptions: {
|
|
|
|
stateFactory: stateFactory,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
return new CipherContextMenuHandler(
|
|
|
|
await MainContextMenuHandler.mv3Create(cachedServices),
|
|
|
|
await authServiceFactory(cachedServices, serviceOptions),
|
2023-08-30 17:18:20 +02:00
|
|
|
await cipherServiceFactory(cachedServices, serviceOptions)
|
2023-01-07 01:31:32 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-08-08 22:56:42 +02:00
|
|
|
static async windowsOnFocusChangedListener(windowId: number, serviceCache: CachedServices) {
|
|
|
|
const cipherContextMenuHandler = await CipherContextMenuHandler.create(serviceCache);
|
|
|
|
const tab = await BrowserApi.getTabFromCurrentWindow();
|
|
|
|
await cipherContextMenuHandler.update(tab?.url);
|
|
|
|
}
|
|
|
|
|
2023-01-07 01:31:32 +01:00
|
|
|
static async tabsOnActivatedListener(
|
|
|
|
activeInfo: chrome.tabs.TabActiveInfo,
|
|
|
|
serviceCache: CachedServices
|
|
|
|
) {
|
|
|
|
const cipherContextMenuHandler = await CipherContextMenuHandler.create(serviceCache);
|
|
|
|
const tab = await BrowserApi.getTab(activeInfo.tabId);
|
|
|
|
await cipherContextMenuHandler.update(tab.url);
|
|
|
|
}
|
|
|
|
|
|
|
|
static async tabsOnReplacedListener(
|
|
|
|
addedTabId: number,
|
|
|
|
removedTabId: number,
|
|
|
|
serviceCache: CachedServices
|
|
|
|
) {
|
|
|
|
const cipherContextMenuHandler = await CipherContextMenuHandler.create(serviceCache);
|
|
|
|
const tab = await BrowserApi.getTab(addedTabId);
|
|
|
|
await cipherContextMenuHandler.update(tab.url);
|
|
|
|
}
|
|
|
|
|
|
|
|
static async tabsOnUpdatedListener(
|
|
|
|
tabId: number,
|
|
|
|
changeInfo: chrome.tabs.TabChangeInfo,
|
|
|
|
tab: chrome.tabs.Tab,
|
|
|
|
serviceCache: CachedServices
|
|
|
|
) {
|
|
|
|
if (changeInfo.status !== "complete") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const cipherContextMenuHandler = await CipherContextMenuHandler.create(serviceCache);
|
|
|
|
await cipherContextMenuHandler.update(tab.url);
|
|
|
|
}
|
|
|
|
|
2023-01-20 18:33:41 +01:00
|
|
|
static async messageListener(
|
|
|
|
message: { command: string },
|
|
|
|
sender: chrome.runtime.MessageSender,
|
|
|
|
cachedServices: CachedServices
|
|
|
|
) {
|
|
|
|
if (!CipherContextMenuHandler.shouldListen(message)) {
|
|
|
|
return;
|
|
|
|
}
|
2023-01-07 01:31:32 +01:00
|
|
|
const cipherContextMenuHandler = await CipherContextMenuHandler.create(cachedServices);
|
|
|
|
await cipherContextMenuHandler.messageListener(message);
|
|
|
|
}
|
|
|
|
|
2023-01-20 18:33:41 +01:00
|
|
|
private static shouldListen(message: { command: string }) {
|
|
|
|
return LISTENED_TO_COMMANDS.includes(message.command);
|
|
|
|
}
|
|
|
|
|
|
|
|
async messageListener(message: { command: string }, sender?: chrome.runtime.MessageSender) {
|
|
|
|
if (!CipherContextMenuHandler.shouldListen(message)) {
|
2023-01-07 01:31:32 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const activeTabs = await BrowserApi.getActiveTabs();
|
|
|
|
if (!activeTabs || activeTabs.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
await this.update(activeTabs[0].url);
|
|
|
|
}
|
|
|
|
|
|
|
|
async update(url: string) {
|
|
|
|
const authStatus = await this.authService.getAuthStatus();
|
|
|
|
await MainContextMenuHandler.removeAll();
|
|
|
|
if (authStatus !== AuthenticationStatus.Unlocked) {
|
2023-04-26 12:16:07 +02:00
|
|
|
// Should I pass in the auth status or even have two separate methods for this
|
2023-01-07 01:31:32 +01:00
|
|
|
// on MainContextMenuHandler
|
|
|
|
await this.mainContextMenuHandler.noAccess();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const menuEnabled = await this.mainContextMenuHandler.init();
|
|
|
|
if (!menuEnabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-09-29 23:20:41 +02:00
|
|
|
const ciphers = await this.cipherService.getAllDecryptedForUrl(url, [
|
|
|
|
CipherType.Card,
|
|
|
|
CipherType.Identity,
|
|
|
|
]);
|
2023-01-07 01:31:32 +01:00
|
|
|
ciphers.sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b));
|
|
|
|
|
2023-09-29 23:20:41 +02:00
|
|
|
const groupedCiphers: Record<AutofillCipherTypeId, CipherView[]> = ciphers.reduce(
|
|
|
|
(ciphersByType, cipher) => {
|
|
|
|
if (!cipher?.type) {
|
|
|
|
return ciphersByType;
|
|
|
|
}
|
|
|
|
|
|
|
|
const existingCiphersOfType = ciphersByType[cipher.type as AutofillCipherTypeId] || [];
|
|
|
|
|
|
|
|
return {
|
|
|
|
...ciphersByType,
|
|
|
|
[cipher.type]: [...existingCiphersOfType, cipher],
|
|
|
|
};
|
|
|
|
},
|
|
|
|
{
|
|
|
|
[CipherType.Login]: [],
|
|
|
|
[CipherType.Card]: [],
|
|
|
|
[CipherType.Identity]: [],
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if (groupedCiphers[CipherType.Login].length === 0) {
|
|
|
|
await this.mainContextMenuHandler.noLogins();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (groupedCiphers[CipherType.Identity].length === 0) {
|
|
|
|
await this.mainContextMenuHandler.noIdentities();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (groupedCiphers[CipherType.Card].length === 0) {
|
|
|
|
await this.mainContextMenuHandler.noCards();
|
2023-01-07 01:31:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for (const cipher of ciphers) {
|
2023-09-29 23:20:41 +02:00
|
|
|
await this.updateForCipher(cipher);
|
2023-01-07 01:31:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-29 23:20:41 +02:00
|
|
|
private async updateForCipher(cipher: CipherView) {
|
|
|
|
if (
|
|
|
|
cipher == null ||
|
|
|
|
!new Set([CipherType.Login, CipherType.Card, CipherType.Identity]).has(cipher.type)
|
|
|
|
) {
|
2023-01-07 01:31:32 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let title = cipher.name;
|
2023-09-29 23:20:41 +02:00
|
|
|
|
|
|
|
if (cipher.type === CipherType.Login && !Utils.isNullOrEmpty(title) && cipher.login?.username) {
|
2023-01-07 01:31:32 +01:00
|
|
|
title += ` (${cipher.login.username})`;
|
|
|
|
}
|
|
|
|
|
2023-09-29 23:20:41 +02:00
|
|
|
if (cipher.type === CipherType.Card && cipher.card?.subTitle) {
|
|
|
|
title += ` ${cipher.card.subTitle}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
await this.mainContextMenuHandler.loadOptions(title, cipher.id, cipher);
|
2023-01-07 01:31:32 +01:00
|
|
|
}
|
|
|
|
}
|