mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 15:53:27 +00:00
Merge branch 'master' into feature/org-admin-refresh
This commit is contained in:
@@ -8,6 +8,7 @@ import { CollectionData } from "../models/data/collectionData";
|
||||
import { EncryptedOrganizationKeyData } from "../models/data/encryptedOrganizationKeyData";
|
||||
import { EventData } from "../models/data/eventData";
|
||||
import { FolderData } from "../models/data/folderData";
|
||||
import { LocalData } from "../models/data/localData";
|
||||
import { OrganizationData } from "../models/data/organizationData";
|
||||
import { PolicyData } from "../models/data/policyData";
|
||||
import { ProviderData } from "../models/data/providerData";
|
||||
@@ -245,8 +246,11 @@ export abstract class StateService<T extends Account = Account> {
|
||||
setLastActive: (value: number, options?: StorageOptions) => Promise<void>;
|
||||
getLastSync: (options?: StorageOptions) => Promise<string>;
|
||||
setLastSync: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getLocalData: (options?: StorageOptions) => Promise<any>;
|
||||
setLocalData: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getLocalData: (options?: StorageOptions) => Promise<{ [cipherId: string]: LocalData }>;
|
||||
setLocalData: (
|
||||
value: { [cipherId: string]: LocalData },
|
||||
options?: StorageOptions
|
||||
) => Promise<void>;
|
||||
getLocale: (options?: StorageOptions) => Promise<string>;
|
||||
setLocale: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getMainWindowSize: (options?: StorageOptions) => Promise<number>;
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as tldjs from "tldjs";
|
||||
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
|
||||
import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service";
|
||||
import { I18nService } from "../abstractions/i18n.service";
|
||||
|
||||
const nodeURL = typeof window === "undefined" ? require("url") : null;
|
||||
@@ -14,6 +15,7 @@ declare global {
|
||||
|
||||
interface BitwardenContainerService {
|
||||
getCryptoService: () => CryptoService;
|
||||
getEncryptService: () => AbstractEncryptService;
|
||||
}
|
||||
|
||||
export class Utils {
|
||||
@@ -368,6 +370,49 @@ export class Utils {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* There are a few ways to calculate text color for contrast, this one seems to fit accessibility guidelines best.
|
||||
* https://stackoverflow.com/a/3943023/6869691
|
||||
*
|
||||
* @param {string} bgColor
|
||||
* @param {number} [threshold] see stackoverflow link above
|
||||
* @param {boolean} [svgTextFill]
|
||||
* Indicates if this method is performed on an SVG <text> 'fill' attribute (e.g. <text fill="black"></text>).
|
||||
* This check is necessary because the '!important' tag cannot be used in a 'fill' attribute.
|
||||
*/
|
||||
static pickTextColorBasedOnBgColor(bgColor: string, threshold = 186, svgTextFill = false) {
|
||||
const bgColorHexNums = bgColor.charAt(0) === "#" ? bgColor.substring(1, 7) : bgColor;
|
||||
const r = parseInt(bgColorHexNums.substring(0, 2), 16); // hexToR
|
||||
const g = parseInt(bgColorHexNums.substring(2, 4), 16); // hexToG
|
||||
const b = parseInt(bgColorHexNums.substring(4, 6), 16); // hexToB
|
||||
const blackColor = svgTextFill ? "black" : "black !important";
|
||||
const whiteColor = svgTextFill ? "white" : "white !important";
|
||||
return r * 0.299 + g * 0.587 + b * 0.114 > threshold ? blackColor : whiteColor;
|
||||
}
|
||||
|
||||
static stringToColor(str: string): string {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||
}
|
||||
let color = "#";
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const value = (hash >> (i * 8)) & 0xff;
|
||||
color += ("00" + value.toString(16)).substr(-2);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Will throw an error if the ContainerService has not been attached to the window object
|
||||
*/
|
||||
static getContainerService(): BitwardenContainerService {
|
||||
if (this.global.bitwardenContainerService == null) {
|
||||
throw new Error("global bitwardenContainerService not initialized.");
|
||||
}
|
||||
return this.global.bitwardenContainerService;
|
||||
}
|
||||
|
||||
private static validIpAddress(ipString: string): boolean {
|
||||
const ipRegex =
|
||||
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
|
||||
4
libs/common/src/models/data/localData.ts
Normal file
4
libs/common/src/models/data/localData.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export type LocalData = {
|
||||
lastUsedDate?: number;
|
||||
lastLaunched?: number;
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { AttachmentData } from "../data/attachmentData";
|
||||
import { AttachmentView } from "../view/attachmentView";
|
||||
@@ -47,26 +46,33 @@ export class Attachment extends Domain {
|
||||
);
|
||||
|
||||
if (this.key != null) {
|
||||
let cryptoService: CryptoService;
|
||||
const containerService = Utils.global.bitwardenContainerService;
|
||||
if (containerService) {
|
||||
cryptoService = containerService.getCryptoService();
|
||||
} else {
|
||||
throw new Error("global bitwardenContainerService not initialized.");
|
||||
}
|
||||
|
||||
try {
|
||||
const orgKey = await cryptoService.getOrgKey(orgId);
|
||||
const decValue = await cryptoService.decryptToBytes(this.key, orgKey ?? encKey);
|
||||
view.key = new SymmetricCryptoKey(decValue);
|
||||
} catch (e) {
|
||||
// TODO: error?
|
||||
}
|
||||
view.key = await this.decryptAttachmentKey(orgId, encKey);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private async decryptAttachmentKey(orgId: string, encKey?: SymmetricCryptoKey) {
|
||||
try {
|
||||
if (encKey == null) {
|
||||
encKey = await this.getKeyForDecryption(orgId);
|
||||
}
|
||||
|
||||
const encryptService = Utils.getContainerService().getEncryptService();
|
||||
const decValue = await encryptService.decryptToBytes(this.key, encKey);
|
||||
return new SymmetricCryptoKey(decValue);
|
||||
} catch (e) {
|
||||
// TODO: error?
|
||||
}
|
||||
}
|
||||
|
||||
private async getKeyForDecryption(orgId: string) {
|
||||
const cryptoService = Utils.getContainerService().getCryptoService();
|
||||
return orgId != null
|
||||
? await cryptoService.getOrgKey(orgId)
|
||||
: await cryptoService.getKeyForUserEncryption();
|
||||
}
|
||||
|
||||
toAttachmentData(): AttachmentData {
|
||||
const a = new AttachmentData();
|
||||
a.size = this.size;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CipherRepromptType } from "../../enums/cipherRepromptType";
|
||||
import { CipherType } from "../../enums/cipherType";
|
||||
import { CipherData } from "../data/cipherData";
|
||||
import { LocalData } from "../data/localData";
|
||||
import { CipherView } from "../view/cipherView";
|
||||
|
||||
import { Attachment } from "./attachment";
|
||||
@@ -26,7 +27,7 @@ export class Cipher extends Domain {
|
||||
edit: boolean;
|
||||
viewPassword: boolean;
|
||||
revisionDate: Date;
|
||||
localData: any;
|
||||
localData: LocalData;
|
||||
login: Login;
|
||||
identity: Identity;
|
||||
card: Card;
|
||||
@@ -38,7 +39,7 @@ export class Cipher extends Domain {
|
||||
deletedDate: Date;
|
||||
reprompt: CipherRepromptType;
|
||||
|
||||
constructor(obj?: CipherData, localData: any = null) {
|
||||
constructor(obj?: CipherData, localData: LocalData = null) {
|
||||
super();
|
||||
if (obj == null) {
|
||||
return;
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Jsonify } from "type-fest";
|
||||
|
||||
import { IEncrypted } from "@bitwarden/common/interfaces/IEncrypted";
|
||||
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { EncryptionType } from "../../enums/encryptionType";
|
||||
import { Utils } from "../../misc/utils";
|
||||
|
||||
@@ -29,30 +28,6 @@ export class EncString implements IEncrypted {
|
||||
}
|
||||
}
|
||||
|
||||
async decrypt(orgId: string, key: SymmetricCryptoKey = null): Promise<string> {
|
||||
if (this.decryptedValue != null) {
|
||||
return this.decryptedValue;
|
||||
}
|
||||
|
||||
let cryptoService: CryptoService;
|
||||
const containerService = Utils.global.bitwardenContainerService;
|
||||
if (containerService) {
|
||||
cryptoService = containerService.getCryptoService();
|
||||
} else {
|
||||
throw new Error("global bitwardenContainerService not initialized.");
|
||||
}
|
||||
|
||||
try {
|
||||
if (key == null) {
|
||||
key = await cryptoService.getOrgKey(orgId);
|
||||
}
|
||||
this.decryptedValue = await cryptoService.decryptToUtf8(this, key);
|
||||
} catch (e) {
|
||||
this.decryptedValue = "[error: cannot decrypt]";
|
||||
}
|
||||
return this.decryptedValue;
|
||||
}
|
||||
|
||||
get ivBytes(): ArrayBuffer {
|
||||
return this.iv == null ? null : Utils.fromB64ToArray(this.iv).buffer;
|
||||
}
|
||||
@@ -160,4 +135,32 @@ export class EncString implements IEncrypted {
|
||||
encPieces,
|
||||
};
|
||||
}
|
||||
|
||||
async decrypt(orgId: string, key: SymmetricCryptoKey = null): Promise<string> {
|
||||
if (this.decryptedValue != null) {
|
||||
return this.decryptedValue;
|
||||
}
|
||||
|
||||
try {
|
||||
if (key == null) {
|
||||
key = await this.getKeyForDecryption(orgId);
|
||||
}
|
||||
if (key == null) {
|
||||
throw new Error("No key to decrypt EncString with orgId " + orgId);
|
||||
}
|
||||
|
||||
const encryptService = Utils.getContainerService().getEncryptService();
|
||||
this.decryptedValue = await encryptService.decryptToUtf8(this, key);
|
||||
} catch (e) {
|
||||
this.decryptedValue = "[error: cannot decrypt]";
|
||||
}
|
||||
return this.decryptedValue;
|
||||
}
|
||||
|
||||
private async getKeyForDecryption(orgId: string) {
|
||||
const cryptoService = Utils.getContainerService().getCryptoService();
|
||||
return orgId != null
|
||||
? await cryptoService.getOrgKey(orgId)
|
||||
: await cryptoService.getKeyForUserEncryption();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { SendType } from "../../enums/sendType";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { SendData } from "../data/sendData";
|
||||
@@ -71,13 +70,7 @@ export class Send extends Domain {
|
||||
async decrypt(): Promise<SendView> {
|
||||
const model = new SendView(this);
|
||||
|
||||
let cryptoService: CryptoService;
|
||||
const containerService = Utils.global.bitwardenContainerService;
|
||||
if (containerService) {
|
||||
cryptoService = containerService.getCryptoService();
|
||||
} else {
|
||||
throw new Error("global bitwardenContainerService not initialized.");
|
||||
}
|
||||
const cryptoService = Utils.getContainerService().getCryptoService();
|
||||
|
||||
try {
|
||||
model.key = await cryptoService.decryptToBytes(this.key, null);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Jsonify } from "type-fest";
|
||||
import { CipherRepromptType } from "../../enums/cipherRepromptType";
|
||||
import { CipherType } from "../../enums/cipherType";
|
||||
import { LinkedIdType } from "../../enums/linkedIdType";
|
||||
import { LocalData } from "../data/localData";
|
||||
import { Cipher } from "../domain/cipher";
|
||||
|
||||
import { AttachmentView } from "./attachmentView";
|
||||
@@ -25,7 +26,7 @@ export class CipherView implements View {
|
||||
organizationUseTotp = false;
|
||||
edit = false;
|
||||
viewPassword = true;
|
||||
localData: any;
|
||||
localData: LocalData;
|
||||
login = new LoginView();
|
||||
identity = new IdentityView();
|
||||
card = new CardView();
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service";
|
||||
import { CryptoService } from "../abstractions/crypto.service";
|
||||
|
||||
export class ContainerService {
|
||||
constructor(private cryptoService: CryptoService) {}
|
||||
constructor(
|
||||
private cryptoService: CryptoService,
|
||||
private encryptService: AbstractEncryptService
|
||||
) {}
|
||||
|
||||
attachToGlobal(global: any) {
|
||||
if (!global.bitwardenContainerService) {
|
||||
@@ -9,7 +13,23 @@ export class ContainerService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Will throw if CryptoService was not instantiated and provided to the ContainerService constructor
|
||||
*/
|
||||
getCryptoService(): CryptoService {
|
||||
if (this.cryptoService == null) {
|
||||
throw new Error("ContainerService.cryptoService not initialized.");
|
||||
}
|
||||
return this.cryptoService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Will throw if EncryptService was not instantiated and provided to the ContainerService constructor
|
||||
*/
|
||||
getEncryptService(): AbstractEncryptService {
|
||||
if (this.encryptService == null) {
|
||||
throw new Error("ContainerService.encryptService not initialized.");
|
||||
}
|
||||
return this.encryptService;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ export class I18nService implements I18nServiceAbstraction {
|
||||
collator: Intl.Collator;
|
||||
localeNames = new Map<string, string>([
|
||||
["af", "Afrikaans"],
|
||||
["ar", "العربية الفصحى"],
|
||||
["az", "Azərbaycanca"],
|
||||
["be", "Беларуская"],
|
||||
["bg", "български"],
|
||||
|
||||
@@ -16,6 +16,7 @@ import { CollectionData } from "../models/data/collectionData";
|
||||
import { EncryptedOrganizationKeyData } from "../models/data/encryptedOrganizationKeyData";
|
||||
import { EventData } from "../models/data/eventData";
|
||||
import { FolderData } from "../models/data/folderData";
|
||||
import { LocalData } from "../models/data/localData";
|
||||
import { OrganizationData } from "../models/data/organizationData";
|
||||
import { PolicyData } from "../models/data/policyData";
|
||||
import { ProviderData } from "../models/data/providerData";
|
||||
@@ -1747,12 +1748,16 @@ export class StateService<
|
||||
);
|
||||
}
|
||||
|
||||
async getLocalData(options?: StorageOptions): Promise<any> {
|
||||
async getLocalData(options?: StorageOptions): Promise<{ [cipherId: string]: LocalData }> {
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
|
||||
)?.data?.localData;
|
||||
}
|
||||
async setLocalData(value: string, options?: StorageOptions): Promise<void> {
|
||||
|
||||
async setLocalData(
|
||||
value: { [cipherId: string]: LocalData },
|
||||
options?: StorageOptions
|
||||
): Promise<void> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user