mirror of
https://github.com/bitwarden/browser
synced 2025-12-20 02:03:39 +00:00
Merge branch 'master' into feature/org-admin-refresh
This commit is contained in:
72
libs/common/spec/importers/chrome-csv-importer.spec.ts
Normal file
72
libs/common/spec/importers/chrome-csv-importer.spec.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { ChromeCsvImporter as Importer } from "@bitwarden/common/importers/chrome-csv-importer";
|
||||
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
|
||||
import { LoginUriView } from "@bitwarden/common/models/view/login-uri.view";
|
||||
import { LoginView } from "@bitwarden/common/models/view/login.view";
|
||||
|
||||
import { data as androidData } from "./testData/chromeCsv/android-data.csv";
|
||||
import { data as simplePasswordData } from "./testData/chromeCsv/simple-password-data.csv";
|
||||
|
||||
const CipherData = [
|
||||
{
|
||||
title: "should parse app name",
|
||||
csv: androidData,
|
||||
expected: Object.assign(new CipherView(), {
|
||||
id: null,
|
||||
organizationId: null,
|
||||
folderId: null,
|
||||
name: "com.xyz.example.app.android",
|
||||
login: Object.assign(new LoginView(), {
|
||||
username: "username@example.com",
|
||||
password: "Qh6W4Wz55YGFNU",
|
||||
uris: [
|
||||
Object.assign(new LoginUriView(), {
|
||||
uri: "android://N2H9MndUUUt3JuQSWAKexOU9oJLJeHR4nyUGac5E1TXKppkY7xtdRl6l8vKo1hQWCqAEy4gsNLUBIbVxpdmhOP==@com.xyz.example.app.android/",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
notes: null,
|
||||
type: 1,
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "should parse password",
|
||||
csv: simplePasswordData,
|
||||
expected: Object.assign(new CipherView(), {
|
||||
id: null,
|
||||
organizationId: null,
|
||||
folderId: null,
|
||||
name: "www.example.com",
|
||||
login: Object.assign(new LoginView(), {
|
||||
username: "username@example.com",
|
||||
password: "wpC9qFvsbWQK5Z",
|
||||
uris: [
|
||||
Object.assign(new LoginUriView(), {
|
||||
uri: "https://www.example.com/",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
notes: null,
|
||||
type: 1,
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
describe("Chrome CSV Importer", () => {
|
||||
CipherData.forEach((data) => {
|
||||
it(data.title, async () => {
|
||||
const importer = new Importer();
|
||||
const result = await importer.parse(data.csv);
|
||||
expect(result != null).toBe(true);
|
||||
expect(result.ciphers.length).toBeGreaterThan(0);
|
||||
|
||||
const cipher = result.ciphers.shift();
|
||||
let property: keyof typeof data.expected;
|
||||
for (property in data.expected) {
|
||||
if (Object.prototype.hasOwnProperty.call(data.expected, property)) {
|
||||
expect(Object.prototype.hasOwnProperty.call(cipher, property)).toBe(true);
|
||||
expect(cipher[property]).toEqual(data.expected[property]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
export const data = `name,url,username,password
|
||||
,android://N2H9MndUUUt3JuQSWAKexOU9oJLJeHR4nyUGac5E1TXKppkY7xtdRl6l8vKo1hQWCqAEy4gsNLUBIbVxpdmhOP==@com.xyz.example.app.android/,username@example.com,Qh6W4Wz55YGFNU`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export const data = `name,url,username,password
|
||||
www.example.com,https://www.example.com/,username@example.com,wpC9qFvsbWQK5Z`;
|
||||
@@ -37,9 +37,11 @@ const kdf = 0;
|
||||
const kdfIterations = 10000;
|
||||
const userId = Utils.newGuid();
|
||||
const masterPasswordHash = "MASTER_PASSWORD_HASH";
|
||||
const name = "NAME";
|
||||
|
||||
const decodedToken = {
|
||||
sub: userId,
|
||||
name: name,
|
||||
email: email,
|
||||
premium: false,
|
||||
};
|
||||
@@ -122,6 +124,7 @@ describe("LogInStrategy", () => {
|
||||
...new AccountProfile(),
|
||||
...{
|
||||
userId: userId,
|
||||
name: name,
|
||||
email: email,
|
||||
hasPremiumPersonally: false,
|
||||
kdfIterations: kdfIterations,
|
||||
|
||||
@@ -14,27 +14,105 @@ describe("Utils Service", () => {
|
||||
expect(Utils.getDomain("data:image/jpeg;base64,AAA")).toBeNull();
|
||||
});
|
||||
|
||||
it("should fail for about urls", () => {
|
||||
expect(Utils.getDomain("about")).toBeNull();
|
||||
expect(Utils.getDomain("about:")).toBeNull();
|
||||
expect(Utils.getDomain("about:blank")).toBeNull();
|
||||
});
|
||||
|
||||
it("should fail for file url", () => {
|
||||
expect(Utils.getDomain("file:///C://somefolder/form.pdf")).toBeNull();
|
||||
});
|
||||
|
||||
it("should handle urls without protocol", () => {
|
||||
expect(Utils.getDomain("bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getDomain("wrong://bitwarden.com")).toBe("bitwarden.com");
|
||||
});
|
||||
|
||||
it("should handle valid urls", () => {
|
||||
expect(Utils.getDomain("https://bitwarden")).toBe("bitwarden");
|
||||
expect(Utils.getDomain("https://bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getDomain("bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getDomain("http://bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getDomain("https://bitwarden.com")).toBe("bitwarden.com");
|
||||
|
||||
expect(Utils.getDomain("www.bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getDomain("http://www.bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getDomain("https://www.bitwarden.com")).toBe("bitwarden.com");
|
||||
|
||||
expect(Utils.getDomain("vault.bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getDomain("http://vault.bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getDomain("https://vault.bitwarden.com")).toBe("bitwarden.com");
|
||||
|
||||
expect(Utils.getDomain("www.vault.bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getDomain("http://www.vault.bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getDomain("https://www.vault.bitwarden.com")).toBe("bitwarden.com");
|
||||
|
||||
expect(
|
||||
Utils.getDomain("user:password@bitwarden.com:8080/password/sites?and&query#hash")
|
||||
).toBe("bitwarden.com");
|
||||
expect(
|
||||
Utils.getDomain("http://user:password@bitwarden.com:8080/password/sites?and&query#hash")
|
||||
).toBe("bitwarden.com");
|
||||
expect(
|
||||
Utils.getDomain("https://user:password@bitwarden.com:8080/password/sites?and&query#hash")
|
||||
).toBe("bitwarden.com");
|
||||
|
||||
expect(Utils.getDomain("bitwarden.unknown")).toBe("bitwarden.unknown");
|
||||
expect(Utils.getDomain("http://bitwarden.unknown")).toBe("bitwarden.unknown");
|
||||
expect(Utils.getDomain("https://bitwarden.unknown")).toBe("bitwarden.unknown");
|
||||
});
|
||||
|
||||
it("should support localhost and IP", () => {
|
||||
it("should handle valid urls with an underscore in subdomain", () => {
|
||||
expect(Utils.getDomain("my_vault.bitwarden.com/")).toBe("bitwarden.com");
|
||||
expect(Utils.getDomain("http://my_vault.bitwarden.com/")).toBe("bitwarden.com");
|
||||
expect(Utils.getDomain("https://my_vault.bitwarden.com/")).toBe("bitwarden.com");
|
||||
});
|
||||
|
||||
it("should support urls containing umlauts", () => {
|
||||
expect(Utils.getDomain("bütwarden.com")).toBe("bütwarden.com");
|
||||
expect(Utils.getDomain("http://bütwarden.com")).toBe("bütwarden.com");
|
||||
expect(Utils.getDomain("https://bütwarden.com")).toBe("bütwarden.com");
|
||||
|
||||
expect(Utils.getDomain("subdomain.bütwarden.com")).toBe("bütwarden.com");
|
||||
expect(Utils.getDomain("http://subdomain.bütwarden.com")).toBe("bütwarden.com");
|
||||
expect(Utils.getDomain("https://subdomain.bütwarden.com")).toBe("bütwarden.com");
|
||||
});
|
||||
|
||||
it("should support punycode urls", () => {
|
||||
expect(Utils.getDomain("xn--btwarden-65a.com")).toBe("xn--btwarden-65a.com");
|
||||
expect(Utils.getDomain("xn--btwarden-65a.com")).toBe("xn--btwarden-65a.com");
|
||||
expect(Utils.getDomain("xn--btwarden-65a.com")).toBe("xn--btwarden-65a.com");
|
||||
|
||||
expect(Utils.getDomain("subdomain.xn--btwarden-65a.com")).toBe("xn--btwarden-65a.com");
|
||||
expect(Utils.getDomain("http://subdomain.xn--btwarden-65a.com")).toBe("xn--btwarden-65a.com");
|
||||
expect(Utils.getDomain("https://subdomain.xn--btwarden-65a.com")).toBe(
|
||||
"xn--btwarden-65a.com"
|
||||
);
|
||||
});
|
||||
|
||||
it("should support localhost", () => {
|
||||
expect(Utils.getDomain("localhost")).toBe("localhost");
|
||||
expect(Utils.getDomain("http://localhost")).toBe("localhost");
|
||||
expect(Utils.getDomain("https://localhost")).toBe("localhost");
|
||||
});
|
||||
|
||||
it("should support localhost with subdomain", () => {
|
||||
expect(Utils.getDomain("subdomain.localhost")).toBe("localhost");
|
||||
expect(Utils.getDomain("http://subdomain.localhost")).toBe("localhost");
|
||||
expect(Utils.getDomain("https://subdomain.localhost")).toBe("localhost");
|
||||
});
|
||||
|
||||
it("should support IPv4", () => {
|
||||
expect(Utils.getDomain("192.168.1.1")).toBe("192.168.1.1");
|
||||
expect(Utils.getDomain("http://192.168.1.1")).toBe("192.168.1.1");
|
||||
expect(Utils.getDomain("https://192.168.1.1")).toBe("192.168.1.1");
|
||||
});
|
||||
|
||||
it("should support IPv6", () => {
|
||||
expect(Utils.getDomain("[2620:fe::fe]")).toBe("2620:fe::fe");
|
||||
expect(Utils.getDomain("http://[2620:fe::fe]")).toBe("2620:fe::fe");
|
||||
expect(Utils.getDomain("https://[2620:fe::fe]")).toBe("2620:fe::fe");
|
||||
});
|
||||
|
||||
it("should reject invalid hostnames", () => {
|
||||
expect(Utils.getDomain("https://mywebsite.com$.mywebsite.com")).toBeNull();
|
||||
expect(Utils.getDomain("https://mywebsite.com!.mywebsite.com")).toBeNull();
|
||||
@@ -47,20 +125,107 @@ describe("Utils Service", () => {
|
||||
expect(Utils.getHostname(undefined)).toBeNull();
|
||||
expect(Utils.getHostname(" ")).toBeNull();
|
||||
expect(Utils.getHostname('https://bit!:"_&ward.com')).toBeNull();
|
||||
expect(Utils.getHostname("bitwarden")).toBeNull();
|
||||
});
|
||||
|
||||
it("should fail for data urls", () => {
|
||||
expect(Utils.getHostname("data:image/jpeg;base64,AAA")).toBeNull();
|
||||
});
|
||||
|
||||
it("should fail for about urls", () => {
|
||||
expect(Utils.getHostname("about")).toBe("about");
|
||||
expect(Utils.getHostname("about:")).toBeNull();
|
||||
expect(Utils.getHostname("about:blank")).toBeNull();
|
||||
});
|
||||
|
||||
it("should fail for file url", () => {
|
||||
expect(Utils.getHostname("file:///C:/somefolder/form.pdf")).toBeNull();
|
||||
});
|
||||
|
||||
it("should handle valid urls", () => {
|
||||
expect(Utils.getHostname("bitwarden")).toBe("bitwarden");
|
||||
expect(Utils.getHostname("http://bitwarden")).toBe("bitwarden");
|
||||
expect(Utils.getHostname("https://bitwarden")).toBe("bitwarden");
|
||||
|
||||
expect(Utils.getHostname("bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getHostname("https://bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getHostname("http://bitwarden.com")).toBe("bitwarden.com");
|
||||
expect(Utils.getHostname("https://bitwarden.com")).toBe("bitwarden.com");
|
||||
|
||||
expect(Utils.getHostname("www.bitwarden.com")).toBe("www.bitwarden.com");
|
||||
expect(Utils.getHostname("http://www.bitwarden.com")).toBe("www.bitwarden.com");
|
||||
expect(Utils.getHostname("https://www.bitwarden.com")).toBe("www.bitwarden.com");
|
||||
|
||||
expect(Utils.getHostname("vault.bitwarden.com")).toBe("vault.bitwarden.com");
|
||||
expect(Utils.getHostname("http://vault.bitwarden.com")).toBe("vault.bitwarden.com");
|
||||
expect(Utils.getHostname("https://vault.bitwarden.com")).toBe("vault.bitwarden.com");
|
||||
|
||||
expect(Utils.getHostname("www.vault.bitwarden.com")).toBe("www.vault.bitwarden.com");
|
||||
expect(Utils.getHostname("http://www.vault.bitwarden.com")).toBe("www.vault.bitwarden.com");
|
||||
expect(Utils.getHostname("https://www.vault.bitwarden.com")).toBe("www.vault.bitwarden.com");
|
||||
|
||||
expect(
|
||||
Utils.getHostname("user:password@bitwarden.com:8080/password/sites?and&query#hash")
|
||||
).toBe("bitwarden.com");
|
||||
expect(
|
||||
Utils.getHostname("https://user:password@bitwarden.com:8080/password/sites?and&query#hash")
|
||||
).toBe("bitwarden.com");
|
||||
expect(Utils.getHostname("https://bitwarden.unknown")).toBe("bitwarden.unknown");
|
||||
});
|
||||
|
||||
it("should support localhost and IP", () => {
|
||||
it("should handle valid urls with an underscore in subdomain", () => {
|
||||
expect(Utils.getHostname("my_vault.bitwarden.com/")).toBe("my_vault.bitwarden.com");
|
||||
expect(Utils.getHostname("http://my_vault.bitwarden.com/")).toBe("my_vault.bitwarden.com");
|
||||
expect(Utils.getHostname("https://my_vault.bitwarden.com/")).toBe("my_vault.bitwarden.com");
|
||||
});
|
||||
|
||||
it("should support urls containing umlauts", () => {
|
||||
expect(Utils.getHostname("bütwarden.com")).toBe("bütwarden.com");
|
||||
expect(Utils.getHostname("http://bütwarden.com")).toBe("bütwarden.com");
|
||||
expect(Utils.getHostname("https://bütwarden.com")).toBe("bütwarden.com");
|
||||
|
||||
expect(Utils.getHostname("subdomain.bütwarden.com")).toBe("subdomain.bütwarden.com");
|
||||
expect(Utils.getHostname("http://subdomain.bütwarden.com")).toBe("subdomain.bütwarden.com");
|
||||
expect(Utils.getHostname("https://subdomain.bütwarden.com")).toBe("subdomain.bütwarden.com");
|
||||
});
|
||||
|
||||
it("should support punycode urls", () => {
|
||||
expect(Utils.getHostname("xn--btwarden-65a.com")).toBe("xn--btwarden-65a.com");
|
||||
expect(Utils.getHostname("xn--btwarden-65a.com")).toBe("xn--btwarden-65a.com");
|
||||
expect(Utils.getHostname("xn--btwarden-65a.com")).toBe("xn--btwarden-65a.com");
|
||||
|
||||
expect(Utils.getHostname("subdomain.xn--btwarden-65a.com")).toBe(
|
||||
"subdomain.xn--btwarden-65a.com"
|
||||
);
|
||||
expect(Utils.getHostname("http://subdomain.xn--btwarden-65a.com")).toBe(
|
||||
"subdomain.xn--btwarden-65a.com"
|
||||
);
|
||||
expect(Utils.getHostname("https://subdomain.xn--btwarden-65a.com")).toBe(
|
||||
"subdomain.xn--btwarden-65a.com"
|
||||
);
|
||||
});
|
||||
|
||||
it("should support localhost", () => {
|
||||
expect(Utils.getHostname("localhost")).toBe("localhost");
|
||||
expect(Utils.getHostname("http://localhost")).toBe("localhost");
|
||||
expect(Utils.getHostname("https://localhost")).toBe("localhost");
|
||||
});
|
||||
|
||||
it("should support localhost with subdomain", () => {
|
||||
expect(Utils.getHostname("subdomain.localhost")).toBe("subdomain.localhost");
|
||||
expect(Utils.getHostname("http://subdomain.localhost")).toBe("subdomain.localhost");
|
||||
expect(Utils.getHostname("https://subdomain.localhost")).toBe("subdomain.localhost");
|
||||
});
|
||||
|
||||
it("should support IPv4", () => {
|
||||
expect(Utils.getHostname("192.168.1.1")).toBe("192.168.1.1");
|
||||
expect(Utils.getHostname("http://192.168.1.1")).toBe("192.168.1.1");
|
||||
expect(Utils.getHostname("https://192.168.1.1")).toBe("192.168.1.1");
|
||||
});
|
||||
|
||||
it("should support IPv6", () => {
|
||||
expect(Utils.getHostname("[2620:fe::fe]")).toBe("2620:fe::fe");
|
||||
expect(Utils.getHostname("http://[2620:fe::fe]")).toBe("2620:fe::fe");
|
||||
expect(Utils.getHostname("https://[2620:fe::fe]")).toBe("2620:fe::fe");
|
||||
});
|
||||
});
|
||||
|
||||
describe("newGuid", () => {
|
||||
|
||||
65
libs/common/spec/view/login-uri-view.spec.ts
Normal file
65
libs/common/spec/view/login-uri-view.spec.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { UriMatchType } from "@bitwarden/common/enums/uriMatchType";
|
||||
import { LoginUriView } from "@bitwarden/common/models/view/login-uri.view";
|
||||
|
||||
const testData = [
|
||||
{
|
||||
match: UriMatchType.Host,
|
||||
uri: "http://example.com/login",
|
||||
expected: "http://example.com/login",
|
||||
},
|
||||
{
|
||||
match: UriMatchType.Host,
|
||||
uri: "bitwarden.com",
|
||||
expected: "http://bitwarden.com",
|
||||
},
|
||||
{
|
||||
match: UriMatchType.Host,
|
||||
uri: "bitwarden.de",
|
||||
expected: "http://bitwarden.de",
|
||||
},
|
||||
{
|
||||
match: UriMatchType.Host,
|
||||
uri: "bitwarden.br",
|
||||
expected: "http://bitwarden.br",
|
||||
},
|
||||
];
|
||||
|
||||
describe("LoginUriView", () => {
|
||||
it("isWebsite() given an invalid domain should return false", async () => {
|
||||
const uri = new LoginUriView();
|
||||
Object.assign(uri, { match: UriMatchType.Host, uri: "bit!:_&ward.com" });
|
||||
expect(uri.isWebsite).toBe(false);
|
||||
});
|
||||
|
||||
testData.forEach((data) => {
|
||||
it(`isWebsite() given ${data.uri} should return true`, async () => {
|
||||
const uri = new LoginUriView();
|
||||
Object.assign(uri, { match: data.match, uri: data.uri });
|
||||
expect(uri.isWebsite).toBe(true);
|
||||
});
|
||||
|
||||
it(`launchUri() given ${data.uri} should return ${data.expected}`, async () => {
|
||||
const uri = new LoginUriView();
|
||||
Object.assign(uri, { match: data.match, uri: data.uri });
|
||||
expect(uri.launchUri).toBe(data.expected);
|
||||
});
|
||||
|
||||
it(`canLaunch() given ${data.uri} should return true`, async () => {
|
||||
const uri = new LoginUriView();
|
||||
Object.assign(uri, { match: data.match, uri: data.uri });
|
||||
expect(uri.canLaunch).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it(`canLaunch should return false when MatchDetection is set to Regex`, async () => {
|
||||
const uri = new LoginUriView();
|
||||
Object.assign(uri, { match: UriMatchType.RegularExpression, uri: "bitwarden.com" });
|
||||
expect(uri.canLaunch).toBe(false);
|
||||
});
|
||||
|
||||
it(`canLaunch() should return false when the given protocol does not match CanLaunchWhiteList`, async () => {
|
||||
const uri = new LoginUriView();
|
||||
Object.assign(uri, { match: UriMatchType.Host, uri: "someprotocol://bitwarden.com" });
|
||||
expect(uri.canLaunch).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -8,6 +8,7 @@ import { CipherBulkRestoreRequest } from "../models/request/cipher-bulk-restore.
|
||||
import { CipherBulkShareRequest } from "../models/request/cipher-bulk-share.request";
|
||||
import { CipherCollectionsRequest } from "../models/request/cipher-collections.request";
|
||||
import { CipherCreateRequest } from "../models/request/cipher-create.request";
|
||||
import { CipherPartialRequest } from "../models/request/cipher-partial.request";
|
||||
import { CipherShareRequest } from "../models/request/cipher-share.request";
|
||||
import { CipherRequest } from "../models/request/cipher.request";
|
||||
import { CollectionRequest } from "../models/request/collection.request";
|
||||
@@ -257,6 +258,7 @@ export abstract class ApiService {
|
||||
postCipherCreate: (request: CipherCreateRequest) => Promise<CipherResponse>;
|
||||
postCipherAdmin: (request: CipherCreateRequest) => Promise<CipherResponse>;
|
||||
putCipher: (id: string, request: CipherRequest) => Promise<CipherResponse>;
|
||||
putPartialCipher: (id: string, request: CipherPartialRequest) => Promise<CipherResponse>;
|
||||
putCipherAdmin: (id: string, request: CipherRequest) => Promise<CipherResponse>;
|
||||
deleteCipher: (id: string) => Promise<any>;
|
||||
deleteCipherAdmin: (id: string) => Promise<any>;
|
||||
|
||||
@@ -33,7 +33,8 @@ export abstract class CipherService {
|
||||
updateLastUsedDate: (id: string) => Promise<void>;
|
||||
updateLastLaunchedDate: (id: string) => Promise<void>;
|
||||
saveNeverDomain: (domain: string) => Promise<void>;
|
||||
saveWithServer: (cipher: Cipher) => Promise<any>;
|
||||
createWithServer: (cipher: Cipher) => Promise<any>;
|
||||
updateWithServer: (cipher: Cipher) => Promise<any>;
|
||||
shareWithServer: (
|
||||
cipher: CipherView,
|
||||
organizationId: string,
|
||||
|
||||
@@ -173,8 +173,6 @@ export abstract class StateService<T extends Account = Account> {
|
||||
) => Promise<void>;
|
||||
getEnableFullWidth: (options?: StorageOptions) => Promise<boolean>;
|
||||
setEnableFullWidth: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getEnableGravitars: (options?: StorageOptions) => Promise<boolean>;
|
||||
setEnableGravitars: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getEnableMinimizeToTray: (options?: StorageOptions) => Promise<boolean>;
|
||||
setEnableMinimizeToTray: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getEnableStartToTray: (options?: StorageOptions) => Promise<boolean>;
|
||||
|
||||
@@ -4,6 +4,8 @@ import { BaseImporter } from "./baseImporter";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class ChromeCsvImporter extends BaseImporter implements Importer {
|
||||
private androidPatternRegex = new RegExp("^android:\\/\\/.*(?<=@)(.*)(?=\\/)");
|
||||
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = this.parseCsv(data, true);
|
||||
@@ -14,7 +16,11 @@ export class ChromeCsvImporter extends BaseImporter implements Importer {
|
||||
|
||||
results.forEach((value) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value.name, "--");
|
||||
let name = value.name;
|
||||
if (!name && this.androidPatternRegex.test(value.url)) {
|
||||
name = value.url.match(this.androidPatternRegex)[1];
|
||||
}
|
||||
cipher.name = this.getValueOrDefault(name, "--");
|
||||
cipher.login.username = this.getValueOrDefault(value.username);
|
||||
cipher.login.password = this.getValueOrDefault(value.password);
|
||||
cipher.login.uris = this.makeUriArray(value.url);
|
||||
@@ -105,6 +105,7 @@ export abstract class LogInStrategy {
|
||||
...new AccountProfile(),
|
||||
...{
|
||||
userId: accountInformation.sub,
|
||||
name: accountInformation.name,
|
||||
email: accountInformation.email,
|
||||
hasPremiumPersonally: accountInformation.premium,
|
||||
kdfIterations: tokenResponse.kdfIterations,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export function getDomain(host: string): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function isValid(host: string): boolean {
|
||||
return true;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-useless-escape */
|
||||
import * as tldjs from "tldjs";
|
||||
import { getHostname, parse } from "tldts";
|
||||
|
||||
import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service";
|
||||
import { CryptoService } from "../abstractions/crypto.service";
|
||||
@@ -24,11 +24,10 @@ export class Utils {
|
||||
static isMobileBrowser = false;
|
||||
static isAppleMobileBrowser = false;
|
||||
static global: typeof global = null;
|
||||
static tldEndingRegex =
|
||||
/.*\.(com|net|org|edu|uk|gov|ca|de|jp|fr|au|ru|ch|io|es|us|co|xyz|info|ly|mil)$/;
|
||||
// Transpiled version of /\p{Emoji_Presentation}/gu using https://mothereff.in/regexpu. Used for compatability in older browsers.
|
||||
static regexpEmojiPresentation =
|
||||
/(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5-\uDED7\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDD78\uDD7A-\uDDCB\uDDCD-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6])/g;
|
||||
static readonly validHosts: string[] = ["localhost"];
|
||||
|
||||
static init() {
|
||||
if (Utils.inited) {
|
||||
@@ -214,12 +213,39 @@ export class Utils {
|
||||
}
|
||||
|
||||
static getHostname(uriString: string): string {
|
||||
const url = Utils.getUrl(uriString);
|
||||
if (Utils.isNullOrWhitespace(uriString)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
uriString = uriString.trim();
|
||||
|
||||
if (uriString.startsWith("data:")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (uriString.startsWith("about:")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (uriString.startsWith("file:")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Does uriString contain invalid characters
|
||||
// TODO Needs to possibly be extended, although '!' is a reserved character
|
||||
if (uriString.indexOf("!") > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return url != null && url.hostname !== "" ? url.hostname : null;
|
||||
const hostname = getHostname(uriString, { validHosts: this.validHosts });
|
||||
if (hostname != null) {
|
||||
return hostname;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static getHost(uriString: string): string {
|
||||
@@ -232,60 +258,35 @@ export class Utils {
|
||||
}
|
||||
|
||||
static getDomain(uriString: string): string {
|
||||
if (uriString == null) {
|
||||
if (Utils.isNullOrWhitespace(uriString)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
uriString = uriString.trim();
|
||||
if (uriString === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (uriString.startsWith("data:")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let httpUrl = uriString.startsWith("http://") || uriString.startsWith("https://");
|
||||
if (
|
||||
!httpUrl &&
|
||||
uriString.indexOf("://") < 0 &&
|
||||
Utils.tldEndingRegex.test(uriString) &&
|
||||
uriString.indexOf("@") < 0
|
||||
) {
|
||||
uriString = "http://" + uriString;
|
||||
httpUrl = true;
|
||||
}
|
||||
|
||||
if (httpUrl) {
|
||||
try {
|
||||
const url = Utils.getUrlObject(uriString);
|
||||
const validHostname = tldjs?.isValid != null ? tldjs.isValid(url.hostname) : true;
|
||||
if (!validHostname) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (url.hostname === "localhost" || Utils.validIpAddress(url.hostname)) {
|
||||
return url.hostname;
|
||||
}
|
||||
|
||||
const urlDomain =
|
||||
tldjs != null && tldjs.getDomain != null ? tldjs.getDomain(url.hostname) : null;
|
||||
return urlDomain != null ? urlDomain : url.hostname;
|
||||
} catch (e) {
|
||||
// Invalid domain, try another approach below.
|
||||
}
|
||||
if (uriString.startsWith("about:")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const domain = tldjs != null && tldjs.getDomain != null ? tldjs.getDomain(uriString) : null;
|
||||
const parseResult = parse(uriString, { validHosts: this.validHosts });
|
||||
if (parseResult != null && parseResult.hostname != null) {
|
||||
if (parseResult.hostname === "localhost" || parseResult.isIp) {
|
||||
return parseResult.hostname;
|
||||
}
|
||||
|
||||
if (domain != null) {
|
||||
return domain;
|
||||
if (parseResult.domain != null) {
|
||||
return parseResult.domain;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -358,14 +359,11 @@ export class Utils {
|
||||
}
|
||||
|
||||
static getUrl(uriString: string): URL {
|
||||
if (uriString == null) {
|
||||
if (this.isNullOrWhitespace(uriString)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
uriString = uriString.trim();
|
||||
if (uriString === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
let url = Utils.getUrlObject(uriString);
|
||||
if (url == null) {
|
||||
@@ -425,12 +423,6 @@ export class Utils {
|
||||
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]?)$/;
|
||||
return ipRegex.test(ipString);
|
||||
}
|
||||
|
||||
private static isMobile(win: Window) {
|
||||
let mobile = false;
|
||||
((a) => {
|
||||
|
||||
@@ -175,6 +175,7 @@ export class AccountProfile {
|
||||
apiKeyClientId?: string;
|
||||
authenticationStatus?: AuthenticationStatus;
|
||||
convertAccountToKeyConnector?: boolean;
|
||||
name?: string;
|
||||
email?: string;
|
||||
emailVerified?: boolean;
|
||||
entityId?: string;
|
||||
@@ -219,7 +220,6 @@ export class AccountSettings {
|
||||
enableAutoFillOnPageLoad?: boolean;
|
||||
enableBiometric?: boolean;
|
||||
enableFullWidth?: boolean;
|
||||
enableGravitars?: boolean;
|
||||
environmentUrls: EnvironmentUrls = new EnvironmentUrls();
|
||||
equivalentDomains?: any;
|
||||
minimizeOnCopyToClipboard?: boolean;
|
||||
|
||||
11
libs/common/src/models/request/cipher-partial.request.ts
Normal file
11
libs/common/src/models/request/cipher-partial.request.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Cipher } from "../domain/cipher";
|
||||
|
||||
export class CipherPartialRequest {
|
||||
folderId: string;
|
||||
favorite: boolean;
|
||||
|
||||
constructor(cipher: Cipher) {
|
||||
this.folderId = cipher.folderId;
|
||||
this.favorite = cipher.favorite;
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ export class LoginUriView implements View {
|
||||
this.uri != null &&
|
||||
(this.uri.indexOf("http://") === 0 ||
|
||||
this.uri.indexOf("https://") === 0 ||
|
||||
(this.uri.indexOf("://") < 0 && Utils.tldEndingRegex.test(this.uri)))
|
||||
(this.uri.indexOf("://") < 0 && !Utils.isNullOrWhitespace(Utils.getDomain(this.uri))))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ export class LoginUriView implements View {
|
||||
}
|
||||
|
||||
get launchUri(): string {
|
||||
return this.uri.indexOf("://") < 0 && Utils.tldEndingRegex.test(this.uri)
|
||||
return this.uri.indexOf("://") < 0 && !Utils.isNullOrWhitespace(Utils.getDomain(this.uri))
|
||||
? "http://" + this.uri
|
||||
: this.uri;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { CipherBulkMoveRequest } from "../models/request/cipher-bulk-move.reques
|
||||
import { CipherBulkShareRequest } from "../models/request/cipher-bulk-share.request";
|
||||
import { CipherCollectionsRequest } from "../models/request/cipher-collections.request";
|
||||
import { CipherCreateRequest } from "../models/request/cipher-create.request";
|
||||
import { CipherPartialRequest } from "../models/request/cipher-partial.request";
|
||||
import { CipherShareRequest } from "../models/request/cipher-share.request";
|
||||
import { CipherRequest } from "../models/request/cipher.request";
|
||||
import { CollectionRequest } from "../models/request/collection.request";
|
||||
@@ -609,6 +610,11 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
return new CipherResponse(r);
|
||||
}
|
||||
|
||||
async putPartialCipher(id: string, request: CipherPartialRequest): Promise<CipherResponse> {
|
||||
const r = await this.send("PUT", "/ciphers/" + id + "/partial", request, true, true);
|
||||
return new CipherResponse(r);
|
||||
}
|
||||
|
||||
async putCipherAdmin(id: string, request: CipherRequest): Promise<CipherResponse> {
|
||||
const r = await this.send("PUT", "/ciphers/" + id + "/admin", request, true, true);
|
||||
return new CipherResponse(r);
|
||||
|
||||
@@ -37,6 +37,7 @@ import { CipherBulkRestoreRequest } from "../models/request/cipher-bulk-restore.
|
||||
import { CipherBulkShareRequest } from "../models/request/cipher-bulk-share.request";
|
||||
import { CipherCollectionsRequest } from "../models/request/cipher-collections.request";
|
||||
import { CipherCreateRequest } from "../models/request/cipher-create.request";
|
||||
import { CipherPartialRequest } from "../models/request/cipher-partial.request";
|
||||
import { CipherShareRequest } from "../models/request/cipher-share.request";
|
||||
import { CipherRequest } from "../models/request/cipher.request";
|
||||
import { CipherResponse } from "../models/response/cipher.response";
|
||||
@@ -157,6 +158,7 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
cipher.collectionIds = model.collectionIds;
|
||||
cipher.revisionDate = model.revisionDate;
|
||||
cipher.reprompt = model.reprompt;
|
||||
cipher.edit = model.edit;
|
||||
|
||||
if (key == null && cipher.organizationId != null) {
|
||||
key = await this.cryptoService.getOrgKey(cipher.organizationId);
|
||||
@@ -594,20 +596,29 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
await this.stateService.setNeverDomains(domains);
|
||||
}
|
||||
|
||||
async saveWithServer(cipher: Cipher): Promise<any> {
|
||||
async createWithServer(cipher: Cipher): Promise<any> {
|
||||
let response: CipherResponse;
|
||||
if (cipher.id == null) {
|
||||
if (cipher.collectionIds != null) {
|
||||
const request = new CipherCreateRequest(cipher);
|
||||
response = await this.apiService.postCipherCreate(request);
|
||||
} else {
|
||||
const request = new CipherRequest(cipher);
|
||||
response = await this.apiService.postCipher(request);
|
||||
}
|
||||
cipher.id = response.id;
|
||||
if (cipher.collectionIds != null) {
|
||||
const request = new CipherCreateRequest(cipher);
|
||||
response = await this.apiService.postCipherCreate(request);
|
||||
} else {
|
||||
const request = new CipherRequest(cipher);
|
||||
response = await this.apiService.postCipher(request);
|
||||
}
|
||||
cipher.id = response.id;
|
||||
|
||||
const data = new CipherData(response, cipher.collectionIds);
|
||||
await this.upsert(data);
|
||||
}
|
||||
|
||||
async updateWithServer(cipher: Cipher): Promise<any> {
|
||||
let response: CipherResponse;
|
||||
if (cipher.edit) {
|
||||
const request = new CipherRequest(cipher);
|
||||
response = await this.apiService.putCipher(cipher.id, request);
|
||||
} else {
|
||||
const request = new CipherPartialRequest(cipher);
|
||||
response = await this.apiService.putPartialCipher(cipher.id, request);
|
||||
}
|
||||
|
||||
const data = new CipherData(response, cipher.collectionIds);
|
||||
|
||||
@@ -22,7 +22,7 @@ import { BitwardenPasswordProtectedImporter } from "../importers/bitwardenPasswo
|
||||
import { BlackBerryCsvImporter } from "../importers/blackBerryCsvImporter";
|
||||
import { BlurCsvImporter } from "../importers/blurCsvImporter";
|
||||
import { ButtercupCsvImporter } from "../importers/buttercupCsvImporter";
|
||||
import { ChromeCsvImporter } from "../importers/chromeCsvImporter";
|
||||
import { ChromeCsvImporter } from "../importers/chrome-csv-importer";
|
||||
import { ClipperzHtmlImporter } from "../importers/clipperzHtmlImporter";
|
||||
import { CodebookCsvImporter } from "../importers/codebookCsvImporter";
|
||||
import { DashlaneCsvImporter } from "../importers/dashlaneImporters/dashlaneCsvImporter";
|
||||
|
||||
@@ -1228,27 +1228,6 @@ export class StateService<
|
||||
);
|
||||
}
|
||||
|
||||
async getEnableGravitars(options?: StorageOptions): Promise<boolean> {
|
||||
return (
|
||||
(
|
||||
await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
|
||||
)
|
||||
)?.settings?.enableGravitars ?? false
|
||||
);
|
||||
}
|
||||
|
||||
async setEnableGravitars(value: boolean, options?: StorageOptions): Promise<void> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
|
||||
);
|
||||
account.settings.enableGravitars = value;
|
||||
await this.saveAccount(
|
||||
account,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
|
||||
);
|
||||
}
|
||||
|
||||
async getEnableMinimizeToTray(options?: StorageOptions): Promise<boolean> {
|
||||
return (
|
||||
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
|
||||
|
||||
@@ -61,7 +61,6 @@ const v1Keys: { [key: string]: string } = {
|
||||
enableBrowserIntegrationFingerprint: "enableBrowserIntegrationFingerprint",
|
||||
enableCloseToTray: "enableCloseToTray",
|
||||
enableFullWidth: "enableFullWidth",
|
||||
enableGravatars: "enableGravatars",
|
||||
enableMinimizeToTray: "enableMinimizeToTray",
|
||||
enableStartToTray: "enableStartToTrayKey",
|
||||
enableTray: "enableTray",
|
||||
@@ -305,9 +304,6 @@ export class StateMigrationService<
|
||||
enableFullWidth:
|
||||
(await this.get<boolean>(v1Keys.enableFullWidth)) ??
|
||||
defaultAccount.settings.enableFullWidth,
|
||||
enableGravitars:
|
||||
(await this.get<boolean>(v1Keys.enableGravatars)) ??
|
||||
defaultAccount.settings.enableGravitars,
|
||||
environmentUrls: globals.environmentUrls ?? defaultAccount.settings.environmentUrls,
|
||||
equivalentDomains:
|
||||
(await this.get<any>(v1Keys.equivalentDomains)) ??
|
||||
|
||||
Reference in New Issue
Block a user