mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-26 12:25:20 +01:00
Fix 1password importer (#222)
* Change cipher type based on csv type header * Test identity and credit card import * Do not use node 'fs' module Karma is being used for automated tests so node modules are not available * WIP: mac and windows 1password importer split Need to improve windows field identification to limit secret data exposure and improve user experience * Hide fields with likely secret values Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
This commit is contained in:
parent
2d62e10d98
commit
72bf18f369
@ -1,5 +1,5 @@
|
||||
import { FieldType } from '../../../src/enums/fieldType';
|
||||
import { OnePassword1PifImporter as Importer } from '../../../src/importers/onepassword1PifImporter';
|
||||
import { OnePassword1PifImporter as Importer } from '../../../src/importers/onepasswordImporters/onepassword1PifImporter';
|
||||
|
||||
import { Utils } from '../../../src/misc/utils';
|
||||
|
||||
|
@ -1,50 +0,0 @@
|
||||
import { OnePasswordWinCsvImporter as Importer } from '../../../src/importers/onepasswordWinCsvImporter';
|
||||
|
||||
import { CipherType } from '../../../src/enums';
|
||||
|
||||
import { data as creditCardData } from './testData/onePasswordCsv/creditCard.csv'
|
||||
import { data as identityData } from './testData/onePasswordCsv/identity.csv'
|
||||
|
||||
describe('1Password CSV Importer', () => {
|
||||
it('should parse identity imports', async () => {
|
||||
const importer = new Importer();
|
||||
const result = await importer.parse(identityData);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.ciphers.length).toBe(1);
|
||||
const cipher = result.ciphers[0];
|
||||
expect(cipher.type).toBe(CipherType.Identity)
|
||||
|
||||
expect(cipher.identity).toEqual(jasmine.objectContaining({
|
||||
firstName: 'first name',
|
||||
middleName: 'mi',
|
||||
lastName: 'last name',
|
||||
username: 'userNam3',
|
||||
company: 'bitwarden',
|
||||
phone: '8005555555',
|
||||
email: 'email@bitwarden.com'
|
||||
}));
|
||||
|
||||
expect(cipher.notes).toContain('address\ncity state zip\nUnited States');
|
||||
});
|
||||
|
||||
it('should parse credit card imports', async () => {
|
||||
const importer = new Importer();
|
||||
const result = await importer.parse(creditCardData);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.ciphers.length).toBe(1);
|
||||
const cipher = result.ciphers[0];
|
||||
expect(cipher.type).toBe(CipherType.Card);
|
||||
|
||||
expect(cipher.card).toEqual(jasmine.objectContaining({
|
||||
number: '4111111111111111',
|
||||
code: '111',
|
||||
cardholderName: 'test',
|
||||
expMonth: '1',
|
||||
expYear: '2030',
|
||||
}));
|
||||
});
|
||||
});
|
71
spec/common/importers/onepasswordMacCsvImporter.spec.ts
Normal file
71
spec/common/importers/onepasswordMacCsvImporter.spec.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { OnePasswordMacCsvImporter as Importer } from '../../../src/importers/onepasswordImporters/onepasswordMacCsvImporter';
|
||||
|
||||
import { CipherType } from '../../../src/enums';
|
||||
import { CipherView } from '../../../src/models/view/cipherView';
|
||||
|
||||
import { data as creditCardData } from './testData/onePasswordCsv/creditCard.mac.csv';
|
||||
import { data as identityData } from './testData/onePasswordCsv/identity.mac.csv';
|
||||
import { data as multiTypeData } from './testData/onePasswordCsv/multipleItems.mac.csv';
|
||||
|
||||
function expectIdentity(cipher: CipherView) {
|
||||
expect(cipher.type).toBe(CipherType.Identity);
|
||||
|
||||
expect(cipher.identity).toEqual(jasmine.objectContaining({
|
||||
firstName: 'first name',
|
||||
middleName: 'mi',
|
||||
lastName: 'last name',
|
||||
username: 'userNam3',
|
||||
company: 'bitwarden',
|
||||
phone: '8005555555',
|
||||
email: 'email@bitwarden.com'
|
||||
}));
|
||||
|
||||
expect(cipher.notes).toContain('address\ncity state zip\nUnited States');
|
||||
}
|
||||
|
||||
function expectCreditCard(cipher: CipherView) {
|
||||
expect(cipher.type).toBe(CipherType.Card);
|
||||
|
||||
expect(cipher.card).toEqual(jasmine.objectContaining({
|
||||
number: '4111111111111111',
|
||||
code: '111',
|
||||
cardholderName: 'test',
|
||||
expMonth: '1',
|
||||
expYear: '2030',
|
||||
}));
|
||||
}
|
||||
|
||||
describe('1Password mac CSV Importer', () => {
|
||||
it('should parse identity records', async () => {
|
||||
const importer = new Importer();
|
||||
const result = await importer.parse(identityData);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.ciphers.length).toBe(1);
|
||||
const cipher = result.ciphers[0];
|
||||
expectIdentity(cipher);
|
||||
});
|
||||
|
||||
it('should parse credit card records', async () => {
|
||||
const importer = new Importer();
|
||||
const result = await importer.parse(creditCardData);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.ciphers.length).toBe(1);
|
||||
const cipher = result.ciphers[0];
|
||||
expectCreditCard(cipher);
|
||||
});
|
||||
|
||||
it('should parse csv\'s with multiple record type', async () => {
|
||||
const importer = new Importer();
|
||||
const result = await importer.parse(multiTypeData);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.ciphers.length).toBe(4);
|
||||
expectIdentity(result.ciphers[1]);
|
||||
expectCreditCard(result.ciphers[2]);
|
||||
});
|
||||
});
|
81
spec/common/importers/onepasswordWinCsvImporter.spec.ts
Normal file
81
spec/common/importers/onepasswordWinCsvImporter.spec.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { OnePasswordWinCsvImporter as Importer } from '../../../src/importers/onepasswordImporters/onepasswordWinCsvImporter';
|
||||
|
||||
import { CipherType, FieldType } from '../../../src/enums';
|
||||
import { CipherView } from '../../../src/models/view/cipherView';
|
||||
import { FieldView } from '../../../src/models/view/fieldView';
|
||||
|
||||
import { data as creditCardData } from './testData/onePasswordCsv/creditCard.windows.csv';
|
||||
import { data as identityData } from './testData/onePasswordCsv/identity.windows.csv';
|
||||
import { data as multiTypeData } from './testData/onePasswordCsv/multipleItems.windows.csv';
|
||||
|
||||
function expectIdentity(cipher: CipherView) {
|
||||
expect(cipher.type).toBe(CipherType.Identity);
|
||||
|
||||
expect(cipher.identity).toEqual(jasmine.objectContaining({
|
||||
firstName: 'first name',
|
||||
middleName: 'mi',
|
||||
lastName: 'last name',
|
||||
username: 'userNam3',
|
||||
company: 'bitwarden',
|
||||
phone: '8005555555',
|
||||
email: 'email@bitwarden.com'
|
||||
}));
|
||||
|
||||
expect(cipher.fields).toEqual(jasmine.arrayContaining([
|
||||
Object.assign(new FieldView(), {
|
||||
type: FieldType.Text,
|
||||
name: 'address',
|
||||
value: 'address city state zip us'
|
||||
})
|
||||
]));
|
||||
}
|
||||
|
||||
function expectCreditCard(cipher: CipherView) {
|
||||
expect(cipher.type).toBe(CipherType.Card);
|
||||
|
||||
expect(cipher.card).toEqual(jasmine.objectContaining({
|
||||
number: '4111111111111111',
|
||||
code: '111',
|
||||
cardholderName: 'test',
|
||||
expMonth: '1',
|
||||
expYear: '1970',
|
||||
}));
|
||||
}
|
||||
|
||||
describe('1Password windows CSV Importer', () => {
|
||||
let importer: Importer;
|
||||
beforeEach(() => {
|
||||
importer = new Importer();
|
||||
});
|
||||
|
||||
it('should parse identity records', async () => {
|
||||
const result = await importer.parse(identityData);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.ciphers.length).toBe(1);
|
||||
const cipher = result.ciphers[0];
|
||||
expectIdentity(cipher);
|
||||
});
|
||||
|
||||
it('should parse credit card records', async () => {
|
||||
const result = await importer.parse(creditCardData);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.ciphers.length).toBe(1);
|
||||
const cipher = result.ciphers[0];
|
||||
expectCreditCard(cipher);
|
||||
});
|
||||
|
||||
it('should parse csv\'s with multiple record types', async () => {
|
||||
const result = await importer.parse(multiTypeData);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.ciphers.length).toBe(4);
|
||||
|
||||
expectIdentity(result.ciphers[1]);
|
||||
expectCreditCard(result.ciphers[2]);
|
||||
});
|
||||
});
|
@ -1,3 +1,3 @@
|
||||
export const data = `"account number(accountNo)","address(address)","address(branchAddress)","admin console URL(admin_console_url)","admin console username(admin_console_username)","AirPort ID(airport_id)","alias(alias)","AOL/AIM(aim)","approved wildlife(game)","attached storage password(disk_password)","auth method(pop_authentication)","auth method(smtp_authentication)","bank name(bankName)","base station name(name)","base station password(password)","birth date(birthdate)","business(busphone)","cardholder name(cardholder)","cash withdrawal limit(cashLimit)","cell(cellphone)","company name(company_name)","company(company)","conditions / restrictions(conditions)","connection options(options)","console password(admin_console_password)","country(country)","Created Date","credit limit(creditLimit)","customer service phone(customer_service_phone)","database(database)","date of birth(birthdate)","default phone(defphone)","department(department)","download page(download_link)","email(email)","expires(expires)","expiry date(expiry_date)","expiry date(expiry)","first name(firstname)","forum signature(forumsig)","full name(fullname)","full name(name)","group(org_name)","height(height)","home(homephone)","IBAN(iban)","ICQ(icq)","initial(initial)","interest rate(interest)","issue number(issuenumber)","issued on(issue_date)","issuing authority(issuing_authority)","issuing bank(bank)","issuing country(issuing_country)","job title(jobtitle)","last name(lastname)","license class(class)","license key(reg_code)","licensed to(reg_name)","maximum quota(quota)","member ID (additional)(additional_no)","member ID(membership_no)","member name(member_name)","member since(member_since)","Modified Date","MSN(msn)","name on account(owner)","name(name)","nationality(nationality)","network name(network_name)","Notes","number(ccnum)","number(number)","occupation(occupation)","order number(order_number)","order total(order_total)","Password","password(password)","password(pop_password)","password(smtp_password)","phone (intl)(phoneIntl)","phone (local)(phone_local)","phone (local)(phoneLocal)","phone (toll free)(phone_tollfree)","phone (toll free)(phoneTollFree)","phone for reservations(reservations_phone)","phone(branchPhone)","PIN(pin)","PIN(telephonePin)","place of birth(birthplace)","port number(pop_port)","port number(smtp_port)","port(port)","provider's website(provider_website)","provider(provider)","publisher(publisher_name)","purchase date(order_date)","registered email(reg_email)","reminder answer(remindera)","reminder question(reminderq)","retail price(retail_price)","routing number(routingNo)","Scope","security(pop_security)","security(smtp_security)","server / IP address(server)","server(hostname)","server(pop_server)","sex(sex)","SID(sid)","skype(skype)","SMTP server(smtp_server)","state(state)","support email(support_email)","support phone(support_contact_phone)","support URL(support_contact_url)","SWIFT(swift)","Tags","telephone(phone)","Title","Type","type(accountType)","type(database_type)","type(pop_type)","type(type)","URL","URL(url)","Username","username(pop_username)","username(smtp_username)","username(username)","valid from(valid_from)","valid from(validFrom)","verification number(cvv)","version(product_version)","website(publisher_website)","website(website)","wireless network password(wireless_password)","wireless security(wireless_security)","Yahoo(yahoo)",
|
||||
,,,,,,,,,,,,,,,,,"test",,,,,,,,,"1606923869",,,,,,,,,,,"01/2030",,,,,,,,,,,,,,,,,,,,,,,,,,,"1606924056",,,,,,"","4111111111111111",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"{(
|
||||
)}",,"test card","Credit Card",,,,"laser",,,,,,,,,"111",,,,,,,`
|
||||
)}",,"test card","Credit Card",,,,"laser",,,,,,,,,"111",,,,,,,`;
|
@ -0,0 +1,2 @@
|
||||
export const data = `"UUID","TITLE","SCOPE","AUTOSUBMIT","1: CARDHOLDER NAME","2: NUMBER","3: VERIFICATION NUMBER","4: EXPIRY DATE","SECTION 2: SECTION_PZET7LEKRQXZUINIEGH5ABA2UY","SECTION_PZET7LEKRQXZUINIEGH5ABA2UY 1: LABEL"
|
||||
"sd26pt226etnsijbl3kqzi5bmm","test card","Default","Default","test","4111111111111111","111","1/3/1970 12:23 AM","section","field (phone)"`;
|
@ -3,4 +3,4 @@ export const data = `"account number(accountNo)","address(address)","address(bra
|
||||
city state zip
|
||||
United States",,,,,,"",,,,,,,,"12/2/20","",,,"",,"bitwarden",,,,,"1606923754",,,,"12/2/20","8005555555","department",,"email@bitwarden.com",,,,"first name","",,,,,"",,"","mi",,,,,,,"job title","last name",,,,,,,,,"1607020883","",,,,,"It’s you! 🖐 Select Edit to fill in more details, like your address and contact information.",,,"occupation",,,,,,,,,,,,,,,,,,,,,,,,,"","",,,,,,,,,"",,"",,,,,,,"{(
|
||||
\\"Starter Kit\\"
|
||||
)}",,"Identity Item","Identity",,,,,,,"userNam3",,,"userNam3",,,,,,"",,,"",`
|
||||
)}",,"Identity Item","Identity",,,,,,,"userNam3",,,"userNam3",,,,,,"",,,"",`;
|
@ -0,0 +1,2 @@
|
||||
export const data = `"UUID","TITLE","SCOPE","AUTOSUBMIT","TAGS","NOTES","SECTION 1: NAME","NAME 1: FIRST NAME","NAME 2: INITIAL","NAME 3: LAST NAME","NAME 4: BIRTH DATE","NAME 5: OCCUPATION","NAME 6: COMPANY","NAME 7: DEPARTMENT","NAME 8: JOB TITLE","SECTION 2: ADDRESS","ADDRESS 1: ADDRESS","ADDRESS 2: DEFAULT PHONE","SECTION 3: INTERNET","INTERNET 1: USERNAME","INTERNET 2: EMAIL","SECTION 4: MFJQKMWEOYDZDFH4YMR7WLJKIY","MFJQKMWEOYDZDFH4YMR7WLJKIY 1: SECTION FIELD","MFJQKMWEOYDZDFH4YMR7WLJKIY 2: SECTION FIELD"
|
||||
"6v56y5z4tejwg37jsettta7d7m","Identity Item","Default","Default","Starter Kit","It’s you! 🖐 Select Edit to fill in more details, like your address and contact information.","Identification","first name","mi","last name","12/2/2020 4:01 AM","occupation","bitwarden","department","job title","Address","address city state zip us","8005555555","Internet Details","userNam3","email@bitwarden.com","💡 Did you know?","1Password can fill names and addresses into webpages:","https://support.1password.com/credit-card-address-filling/"`;
|
@ -0,0 +1,14 @@
|
||||
export const data = `"account number(accountNo)","address(address)","address(branchAddress)","admin console URL(admin_console_url)","admin console username(admin_console_username)","AirPort ID(airport_id)","alias(alias)","AOL/AIM(aim)","approved wildlife(game)","attached storage password(disk_password)","auth method(pop_authentication)","auth method(smtp_authentication)","bank name(bankName)","base station name(name)","base station password(password)","birth date(birthdate)","business(busphone)","cardholder name(cardholder)","cash withdrawal limit(cashLimit)","cell(cellphone)","company name(company_name)","company(company)","conditions / restrictions(conditions)","connection options(options)","console password(admin_console_password)","country(country)","Created Date","credit limit(creditLimit)","customer service phone(customer_service_phone)","database(database)","date of birth(birthdate)","default phone(defphone)","department(department)","download page(download_link)","email(email)","expires(expires)","expiry date(expiry_date)","expiry date(expiry)","first name(firstname)","forum signature(forumsig)","full name(fullname)","full name(name)","group(org_name)","height(height)","home(homephone)","IBAN(iban)","ICQ(icq)","initial(initial)","interest rate(interest)","issue number(issuenumber)","issued on(issue_date)","issuing authority(issuing_authority)","issuing bank(bank)","issuing country(issuing_country)","job title(jobtitle)","last name(lastname)","license class(class)","license key(reg_code)","licensed to(reg_name)","maximum quota(quota)","member ID (additional)(additional_no)","member ID(membership_no)","member name(member_name)","member since(member_since)","Modified Date","MSN(msn)","name on account(owner)","name(name)","nationality(nationality)","network name(network_name)","Notes","number(ccnum)","number(number)","occupation(occupation)","order number(order_number)","order total(order_total)","Password","password(password)","password(pop_password)","password(smtp_password)","phone (intl)(phoneIntl)","phone (local)(phone_local)","phone (local)(phoneLocal)","phone (toll free)(phone_tollfree)","phone (toll free)(phoneTollFree)","phone for reservations(reservations_phone)","phone(branchPhone)","PIN(pin)","PIN(telephonePin)","place of birth(birthplace)","port number(pop_port)","port number(smtp_port)","port(port)","provider's website(provider_website)","provider(provider)","publisher(publisher_name)","purchase date(order_date)","registered email(reg_email)","reminder answer(remindera)","reminder question(reminderq)","retail price(retail_price)","routing number(routingNo)","Scope","security(pop_security)","security(smtp_security)","server / IP address(server)","server(hostname)","server(pop_server)","sex(sex)","SID(sid)","skype(skype)","SMTP server(smtp_server)","state(state)","support email(support_email)","support phone(support_contact_phone)","support URL(support_contact_url)","SWIFT(swift)","Tags","telephone(phone)","Title","Type","type(accountType)","type(database_type)","type(pop_type)","type(type)","URL","URL(url)","Username","username(pop_username)","username(smtp_username)","username(username)","valid from(valid_from)","valid from(validFrom)","verification number(cvv)","version(product_version)","website(publisher_website)","website(website)","wireless network password(wireless_password)","wireless security(wireless_security)","Yahoo(yahoo)",
|
||||
,,,,,,,,,,,,,,,,,,,,,,,,,,"1606923754",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"1606923754",,,,,,"Follow these steps to get started.",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"{(
|
||||
\\"Starter Kit\\"
|
||||
)}",,"🎉 Welcome to 1Password!","Secure Note",,,,,,,,,,,,,,,,,,,,
|
||||
,"address
|
||||
city state zip
|
||||
United States",,,,,,,,,,,,,,"12/2/20",,,,,,"bitwarden",,,,,"1606923754",,,,"12/2/20","8005555555","department",,"email@bitwarden.com",,,,"first name",,,,,,,,,"mi",,,,,,,"job title","last name",,,,,,,,,"1607390191",,,,,,"It’s you! 🖐 Select Edit to fill in more details, like your address and contact information.",,,"occupation",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"{(
|
||||
\\"Starter Kit\\"
|
||||
)}",,"Identity Item","Identity",,,,,,,"userNam3",,,"userNam3",,,,,,,,,,
|
||||
,,,,,,,,,,,,,,,,,"test",,,,,,,,,"1606923869",,,,,,,,,,,"01/2030",,,,,,,,,,,,,,,,,,,,,,,,,,,"1607355631",,,,,,"","4111111111111111",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"{(
|
||||
)}",,"test card","Credit Card",,,,"laser",,,,,,,,,"111",,,,,,,
|
||||
,,,,,,,,,,,,,,,,,,,,,,,,,,"1606923754",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"1607020972",,,,,,"You can use this login to sign in to your account on 1password.com.",,,,,,"the account's password",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"{(
|
||||
\\"Starter Kit\\"
|
||||
)}",,"1Password Account","Login",,,,,"https://my.1password.com",,"email@bitwarden.com",,,,,,,,,,,,,`;
|
@ -0,0 +1,5 @@
|
||||
export const data = `"UUID","TITLE","USERNAME","PASSWORD","URL","URLS","EMAIL","MASTER-PASSWORD","ACCOUNT-KEY","SCOPE","AUTOSUBMIT","TAGS","NOTES","SECTION 1: WXHDKEQREE3TH6QRFCPFPSD3AE","WXHDKEQREE3TH6QRFCPFPSD3AE 1: SECRET KEY","SECTION 1: NAME","NAME 1: FIRST NAME","NAME 2: INITIAL","NAME 3: LAST NAME","NAME 4: BIRTH DATE","NAME 5: OCCUPATION","NAME 6: COMPANY","NAME 7: DEPARTMENT","NAME 8: JOB TITLE","SECTION 2: ADDRESS","ADDRESS 1: ADDRESS","ADDRESS 2: DEFAULT PHONE","SECTION 3: INTERNET","INTERNET 1: USERNAME","INTERNET 2: EMAIL","SECTION 4: MFJQKMWEOYDZDFH4YMR7WLJKIY","MFJQKMWEOYDZDFH4YMR7WLJKIY 1: SECTION FIELD","MFJQKMWEOYDZDFH4YMR7WLJKIY 2: SECTION FIELD","1: CARDHOLDER NAME","2: NUMBER","3: VERIFICATION NUMBER","4: EXPIRY DATE","SECTION 2: SECTION_PZET7LEKRQXZUINIEGH5ABA2UY","SECTION_PZET7LEKRQXZUINIEGH5ABA2UY 1: LABEL","SECTION 1: 4PQVXPR4BMOPGC3DBMTP5U4OFY","4PQVXPR4BMOPGC3DBMTP5U4OFY 1: SECTION FIELD","4PQVXPR4BMOPGC3DBMTP5U4OFY 2: SECTION FIELD","SECTION 2: M2NTUZZBFOFTPAYXVXE6EMZ5JU","M2NTUZZBFOFTPAYXVXE6EMZ5JU 1: SECTION FIELD","M2NTUZZBFOFTPAYXVXE6EMZ5JU 2: SECTION FIELD","SECTION 3: WC3KPAWH6ZAEQB2ARJB6WYZ3DQ","WC3KPAWH6ZAEQB2ARJB6WYZ3DQ 1: SECTION FIELD","WC3KPAWH6ZAEQB2ARJB6WYZ3DQ 2: SECTION FIELD","WC3KPAWH6ZAEQB2ARJB6WYZ3DQ 3: SECTION FIELD","SECTION 4: TOHUYJEJEMGMI6GEQAZ2LJODFE","TOHUYJEJEMGMI6GEQAZ2LJODFE 1: SECTION FIELD","TOHUYJEJEMGMI6GEQAZ2LJODFE 2: SECTION FIELD","SECTION 5: O26UWJJTXRAANG3ONYYOUUJHDM","O26UWJJTXRAANG3ONYYOUUJHDM 1: SECTION FIELD","O26UWJJTXRAANG3ONYYOUUJHDM 2: WATCH VIDEOS","O26UWJJTXRAANG3ONYYOUUJHDM 3: GET SUPPORT","O26UWJJTXRAANG3ONYYOUUJHDM 4: READ THE BLOG","O26UWJJTXRAANG3ONYYOUUJHDM 5: CONTACT US"
|
||||
"xjq32axcswefpcxu2mtxxqnufa","1Password Account","email@bitwarden.com","the account's password","https://my.1password.com","https://my.1password.com","email@bitwarden.com","the account's password","A3-76TR2N-NJG3TZ-9NXFX-WT8GF-6YQC9-R2659","Default","Default","Starter Kit","You can use this login to sign in to your account on 1password.com.","🔑 Secret Key","the account's secret key","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""
|
||||
"6v56y5z4tejwg37jsettta7d7m","Identity Item","","","","","","","","Default","Default","Starter Kit","It’s you! 🖐 Select Edit to fill in more details, like your address and contact information.","","","Identification","first name","mi","last name","12/2/2020 4:01 AM","occupation","bitwarden","department","job title","Address","address city state zip us","8005555555","Internet Details","userNam3","email@bitwarden.com","💡 Did you know?","1Password can fill names and addresses into webpages:","https://support.1password.com/credit-card-address-filling/","","","","","","","","","","","","","","","","","","","","","","","","",""
|
||||
"sd26pt226etnsijbl3kqzi5bmm","test card","","","","","","","","Default","Default","","","","","","","","","","","","","","","","","","","","","","","test","4111111111111111","111","1/3/1970 12:23 AM","section","field (phone)","","","","","","","","","","","","","","","","","","",""
|
||||
"oml2sgit3yk7737kxdis65o4xq","🎉 Welcome to 1Password!","","","","","","","","Default","Default","Starter Kit","Follow these steps to get started.","","","","","","","","","","","","","","","","","","","","","","","","","","","1️⃣ Get the apps","https://1password.com/downloads","Install 1Password everywhere you need your passwords.","2️⃣ Get 1Password in your browser","https://1password.com/downloads/#browsers","Install 1Password in your browser to save and fill passwords.","3️⃣ Save your first password","1. Sign in to your favorite website.","2. 1Password will ask to save your username and password.","3. Click Save Login.","4️⃣ Fill passwords and more","https://support.1password.com/explore/extension/","Save and fill passwords, credit cards, and addresses.","📚 Learn 1Password","Check out our videos and articles:","https://youtube.com/1PasswordVideos","https://support.1password.com/","https://blog.1password.com/","https://support.1password.com/contact-us/"`;
|
@ -66,7 +66,7 @@ describe('NodeCrypto Function Service', () => {
|
||||
const prk16Byte = 'criAmKtfzxanbgea5/kelQ==';
|
||||
const prk32Byte = 'F5h4KdYQnIVH4rKH0P9CZb1GrR4n16/sJrS0PsQEn0Y=';
|
||||
const prk64Byte = 'ssBK0mRG17VHdtsgt8yo4v25CRNpauH+0r2fwY/E9rLyaFBAOMbIeTry+' +
|
||||
'gUJ28p8y+hFh3EI9pcrEWaNvFYonQ=='
|
||||
'gUJ28p8y+hFh3EI9pcrEWaNvFYonQ==';
|
||||
|
||||
testHkdfExpand('sha256', prk32Byte, 32, 'BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD8=');
|
||||
testHkdfExpand('sha256', prk32Byte, 64, 'BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD9BV+' +
|
||||
|
@ -70,7 +70,7 @@ describe('WebCrypto Function Service', () => {
|
||||
const prk16Byte = 'criAmKtfzxanbgea5/kelQ==';
|
||||
const prk32Byte = 'F5h4KdYQnIVH4rKH0P9CZb1GrR4n16/sJrS0PsQEn0Y=';
|
||||
const prk64Byte = 'ssBK0mRG17VHdtsgt8yo4v25CRNpauH+0r2fwY/E9rLyaFBAOMbIeTry+' +
|
||||
'gUJ28p8y+hFh3EI9pcrEWaNvFYonQ=='
|
||||
'gUJ28p8y+hFh3EI9pcrEWaNvFYonQ==';
|
||||
|
||||
testHkdfExpand('sha256', prk32Byte, 32, 'BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD8=');
|
||||
testHkdfExpand('sha256', prk32Byte, 64, 'BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD9BV+' +
|
||||
|
@ -5,7 +5,7 @@ export abstract class CryptoFunctionService {
|
||||
pbkdf2: (password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512',
|
||||
iterations: number) => Promise<ArrayBuffer>;
|
||||
hkdf: (ikm: ArrayBuffer, salt: string | ArrayBuffer, info: string | ArrayBuffer,
|
||||
outputByteSize: number, algorithm: 'sha256' | 'sha512') => Promise<ArrayBuffer>
|
||||
outputByteSize: number, algorithm: 'sha256' | 'sha512') => Promise<ArrayBuffer>;
|
||||
hkdfExpand: (prk: ArrayBuffer, info: string | ArrayBuffer, outputByteSize: number,
|
||||
algorithm: 'sha256' | 'sha512') => Promise<ArrayBuffer>;
|
||||
hash: (value: string | ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512' | 'md5') => Promise<ArrayBuffer>;
|
||||
|
@ -246,7 +246,7 @@ export class TwoFactorComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
get authing(): boolean {
|
||||
return this.authService.authingWithPassword() || this.authService.authingWithSso() || this.authService.authingWithApiKey()
|
||||
return this.authService.authingWithPassword() || this.authService.authingWithSso() || this.authService.authingWithApiKey();
|
||||
}
|
||||
|
||||
get needsLock(): boolean {
|
||||
|
@ -68,7 +68,7 @@ export abstract class BaseImporter {
|
||||
protected parseCsvOptions = {
|
||||
encoding: 'UTF-8',
|
||||
skipEmptyLines: false,
|
||||
}
|
||||
};
|
||||
|
||||
protected organization() {
|
||||
return this.organizationId != null;
|
||||
|
@ -0,0 +1,8 @@
|
||||
import { CipherView } from '../../models/view';
|
||||
|
||||
export class CipherImportContext {
|
||||
lowerProperty: string;
|
||||
constructor(public importRecord: any, public property: string, public cipher: CipherView) {
|
||||
this.lowerProperty = property.toLowerCase();
|
||||
}
|
||||
}
|
@ -1,17 +1,17 @@
|
||||
import { BaseImporter } from './baseImporter';
|
||||
import { Importer } from './importer';
|
||||
import { BaseImporter } from '../baseImporter';
|
||||
import { Importer } from '../importer';
|
||||
|
||||
import { ImportResult } from '../models/domain/importResult';
|
||||
import { ImportResult } from '../../models/domain/importResult';
|
||||
|
||||
import { CardView } from '../models/view/cardView';
|
||||
import { CipherView } from '../models/view/cipherView';
|
||||
import { IdentityView } from '../models/view/identityView';
|
||||
import { PasswordHistoryView } from '../models/view/passwordHistoryView';
|
||||
import { SecureNoteView } from '../models/view/secureNoteView';
|
||||
import { CardView } from '../../models/view/cardView';
|
||||
import { CipherView } from '../../models/view/cipherView';
|
||||
import { IdentityView } from '../../models/view/identityView';
|
||||
import { PasswordHistoryView } from '../../models/view/passwordHistoryView';
|
||||
import { SecureNoteView } from '../../models/view/secureNoteView';
|
||||
|
||||
import { CipherType } from '../enums/cipherType';
|
||||
import { FieldType } from '../enums/fieldType';
|
||||
import { SecureNoteType } from '../enums/secureNoteType';
|
||||
import { CipherType } from '../../enums/cipherType';
|
||||
import { FieldType } from '../../enums/fieldType';
|
||||
import { SecureNoteType } from '../../enums/secureNoteType';
|
||||
|
||||
export class OnePassword1PifImporter extends BaseImporter implements Importer {
|
||||
result = new ImportResult();
|
288
src/importers/onepasswordImporters/onepasswordCsvImporter.ts
Normal file
288
src/importers/onepasswordImporters/onepasswordCsvImporter.ts
Normal file
@ -0,0 +1,288 @@
|
||||
import { ImportResult } from '../../models/domain/importResult';
|
||||
import { BaseImporter } from '../baseImporter';
|
||||
import { Importer } from '../importer';
|
||||
|
||||
import { CipherType } from '../../enums/cipherType';
|
||||
import { FieldType } from '../../enums/fieldType';
|
||||
import { CipherView } from '../../models/view';
|
||||
import { CipherImportContext } from './cipherImportContext';
|
||||
|
||||
export const IgnoredProperties = ['ainfo', 'autosubmit', 'notesplain', 'ps', 'scope', 'tags', 'title', 'uuid', 'notes'];
|
||||
|
||||
export abstract class OnePasswordCsvImporter extends BaseImporter implements Importer {
|
||||
protected loginPropertyParsers = [this.setLoginUsername, this.setLoginPassword, this.setLoginUris];
|
||||
protected creditCardPropertyParsers = [this.setCreditCardNumber, this.setCreditCardVerification, this.setCreditCardCardholderName, this.setCreditCardExpiry];
|
||||
protected identityPropertyParsers = [this.setIdentityFirstName, this.setIdentityInitial, this.setIdentityLastName, this.setIdentityUserName, this.setIdentityEmail, this.setIdentityPhone, this.setIdentityCompany];
|
||||
|
||||
abstract setCipherType(value: any, cipher: CipherView): void;
|
||||
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = this.parseCsv(data, true, {
|
||||
quoteChar: '"',
|
||||
escapeChar: '\\',
|
||||
});
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
results.forEach((value) => {
|
||||
if (this.isNullOrWhitespace(this.getProp(value, 'title'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(this.getProp(value, 'title'), '--');
|
||||
|
||||
this.setNotes(value, cipher);
|
||||
|
||||
this.setCipherType(value, cipher);
|
||||
|
||||
let altUsername: string = null;
|
||||
for (const property in value) {
|
||||
if (!value.hasOwnProperty(property) || this.isNullOrWhitespace(value[property])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const context = new CipherImportContext(value, property, cipher);
|
||||
if (cipher.type === CipherType.Login && this.setKnownLoginValue(context)) {
|
||||
continue;
|
||||
} else if (cipher.type === CipherType.Card && this.setKnownCreditCardValue(context)) {
|
||||
continue;
|
||||
} else if (cipher.type === CipherType.Identity && this.setKnownIdentityValue(context)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
altUsername = this.setUnknownValue(context, altUsername);
|
||||
}
|
||||
|
||||
if (cipher.type === CipherType.Login && !this.isNullOrWhitespace(altUsername) &&
|
||||
this.isNullOrWhitespace(cipher.login.username) && altUsername.indexOf('://') === -1) {
|
||||
cipher.login.username = altUsername;
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
protected getProp(obj: any, name: string): any {
|
||||
const lowerObj = Object.entries(obj).reduce((agg: any, entry: [string, any]) => {
|
||||
agg[entry[0].toLowerCase()] = entry[1];
|
||||
return agg;
|
||||
}, {});
|
||||
return lowerObj[name.toLowerCase()];
|
||||
}
|
||||
|
||||
protected getPropByRegexp(obj: any, regexp: RegExp): any {
|
||||
const matchingKeys = Object.keys(obj).reduce((agg: string[], key: string) => {
|
||||
if (key.match(regexp)) {
|
||||
agg.push(key);
|
||||
}
|
||||
return agg;
|
||||
}, []);
|
||||
if (matchingKeys.length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
return obj[matchingKeys[0]];
|
||||
}
|
||||
}
|
||||
|
||||
protected getPropIncluding(obj: any, name: string): any {
|
||||
const includesMap = Object.keys(obj).reduce((agg: string[], entry: string) => {
|
||||
if (entry.toLowerCase().includes(name.toLowerCase())) {
|
||||
agg.push(entry);
|
||||
}
|
||||
return agg;
|
||||
}, []);
|
||||
if (includesMap.length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
return obj[includesMap[0]];
|
||||
}
|
||||
}
|
||||
|
||||
protected setNotes(importRecord: any, cipher: CipherView) {
|
||||
cipher.notes = this.getValueOrDefault(this.getProp(importRecord, 'notesPlain'), '') + '\n' +
|
||||
this.getValueOrDefault(this.getProp(importRecord, 'notes'), '') + '\n';
|
||||
cipher.notes.trim();
|
||||
|
||||
}
|
||||
|
||||
protected setKnownLoginValue(context: CipherImportContext): boolean {
|
||||
return this.loginPropertyParsers.reduce((agg: boolean, func) => {
|
||||
if (!agg) {
|
||||
agg = func.bind(this)(context);
|
||||
}
|
||||
return agg;
|
||||
}, false);
|
||||
}
|
||||
|
||||
protected setKnownCreditCardValue(context: CipherImportContext): boolean {
|
||||
return this.creditCardPropertyParsers.reduce((agg: boolean, func) => {
|
||||
if (!agg) {
|
||||
agg = func.bind(this)(context);
|
||||
}
|
||||
return agg;
|
||||
}, false);
|
||||
}
|
||||
|
||||
protected setKnownIdentityValue(context: CipherImportContext): boolean {
|
||||
return this.identityPropertyParsers.reduce((agg: boolean, func) => {
|
||||
if (!agg) {
|
||||
agg = func.bind(this)(context);
|
||||
}
|
||||
return agg;
|
||||
}, false);
|
||||
}
|
||||
|
||||
protected setUnknownValue(context: CipherImportContext, altUsername: string): string {
|
||||
if (IgnoredProperties.indexOf(context.lowerProperty) === -1 && !context.lowerProperty.startsWith('section:') &&
|
||||
!context.lowerProperty.startsWith('section ')) {
|
||||
if (altUsername == null && context.lowerProperty === 'email') {
|
||||
return context.importRecord[context.property];
|
||||
}
|
||||
else if (context.lowerProperty === 'created date' || context.lowerProperty === 'modified date') {
|
||||
const readableDate = new Date(parseInt(context.importRecord[context.property], 10) * 1000).toUTCString();
|
||||
this.processKvp(context.cipher, '1Password ' + context.property, readableDate);
|
||||
return null;
|
||||
}
|
||||
if (context.lowerProperty.includes('password') || context.lowerProperty.includes('key') || context.lowerProperty.includes('secret')) {
|
||||
this.processKvp(context.cipher, context.property, context.importRecord[context.property], FieldType.Hidden);
|
||||
} else {
|
||||
this.processKvp(context.cipher, context.property, context.importRecord[context.property]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected setIdentityFirstName(context: CipherImportContext) {
|
||||
if (this.isNullOrWhitespace(context.cipher.identity.firstName) && context.lowerProperty.includes('first name')) {
|
||||
context.cipher.identity.firstName = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setIdentityInitial(context: CipherImportContext) {
|
||||
if (this.isNullOrWhitespace(context.cipher.identity.middleName) && context.lowerProperty.includes('initial')) {
|
||||
context.cipher.identity.middleName = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setIdentityLastName(context: CipherImportContext) {
|
||||
if (this.isNullOrWhitespace(context.cipher.identity.lastName) && context.lowerProperty.includes('last name')) {
|
||||
context.cipher.identity.lastName = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setIdentityUserName(context: CipherImportContext) {
|
||||
if (this.isNullOrWhitespace(context.cipher.identity.username) && context.lowerProperty.includes('username')) {
|
||||
context.cipher.identity.username = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setIdentityCompany(context: CipherImportContext) {
|
||||
if (this.isNullOrWhitespace(context.cipher.identity.company) && context.lowerProperty.includes('company')) {
|
||||
context.cipher.identity.company = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setIdentityPhone(context: CipherImportContext) {
|
||||
if (this.isNullOrWhitespace(context.cipher.identity.phone) && context.lowerProperty.includes('default phone')) {
|
||||
context.cipher.identity.phone = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setIdentityEmail(context: CipherImportContext) {
|
||||
if (this.isNullOrWhitespace(context.cipher.identity.email) && context.lowerProperty.includes('email')) {
|
||||
context.cipher.identity.email = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setCreditCardNumber(context: CipherImportContext): boolean {
|
||||
if (this.isNullOrWhitespace(context.cipher.card.number) && context.lowerProperty.includes('number')) {
|
||||
context.cipher.card.number = context.importRecord[context.property];
|
||||
context.cipher.card.brand = this.getCardBrand(context.cipher.card.number);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setCreditCardVerification(context: CipherImportContext) {
|
||||
if (this.isNullOrWhitespace(context.cipher.card.code) && context.lowerProperty.includes('verification number')) {
|
||||
context.cipher.card.code = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setCreditCardCardholderName(context: CipherImportContext) {
|
||||
if (this.isNullOrWhitespace(context.cipher.card.cardholderName) && context.lowerProperty.includes('cardholder name')) {
|
||||
context.cipher.card.cardholderName = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setCreditCardExpiry(context: CipherImportContext) {
|
||||
if (this.isNullOrWhitespace(context.cipher.card.expiration) && context.lowerProperty.includes('expiry date') &&
|
||||
context.importRecord[context.property].length === 7) {
|
||||
context.cipher.card.expMonth = (context.importRecord[context.property] as string).substr(0, 2);
|
||||
if (context.cipher.card.expMonth[0] === '0') {
|
||||
context.cipher.card.expMonth = context.cipher.card.expMonth.substr(1, 1);
|
||||
}
|
||||
context.cipher.card.expYear = (context.importRecord[context.property] as string).substr(3, 4);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setLoginPassword(context: CipherImportContext) {
|
||||
if (this.isNullOrWhitespace(context.cipher.login.password) && context.lowerProperty === 'password') {
|
||||
context.cipher.login.password = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setLoginUsername(context: CipherImportContext) {
|
||||
if (this.isNullOrWhitespace(context.cipher.login.username) && context.lowerProperty === 'username') {
|
||||
context.cipher.login.username = context.importRecord[context.property];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected setLoginUris(context: CipherImportContext) {
|
||||
if ((context.cipher.login.uris == null || context.cipher.login.uris.length === 0) && context.lowerProperty === 'urls') {
|
||||
const urls = context.importRecord[context.property].split(this.newLineRegex);
|
||||
context.cipher.login.uris = this.makeUriArray(urls);
|
||||
return true;
|
||||
} else if ((context.lowerProperty === 'url')) {
|
||||
if (context.cipher.login.uris == null) {
|
||||
context.cipher.login.uris = [];
|
||||
}
|
||||
context.cipher.login.uris.concat(this.makeUriArray(context.importRecord[context.property]));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import { Importer } from '../importer';
|
||||
import { IgnoredProperties, OnePasswordCsvImporter } from './onepasswordCsvImporter';
|
||||
|
||||
import { CipherType } from '../../enums/cipherType';
|
||||
import { CardView, CipherView, IdentityView } from '../../models/view';
|
||||
|
||||
export class OnePasswordMacCsvImporter extends OnePasswordCsvImporter implements Importer {
|
||||
setCipherType(value: any, cipher: CipherView) {
|
||||
const onePassType = this.getValueOrDefault(this.getProp(value, 'type'), 'Login');
|
||||
switch (onePassType) {
|
||||
case 'Credit Card':
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
IgnoredProperties.push('type');
|
||||
break;
|
||||
case 'Identity':
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.identity = new IdentityView();
|
||||
IgnoredProperties.push('type');
|
||||
break;
|
||||
case 'Login':
|
||||
case 'Secure Note':
|
||||
IgnoredProperties.push('type');
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
import { Importer } from '../importer';
|
||||
import { CipherImportContext } from './cipherImportContext';
|
||||
import { OnePasswordCsvImporter } from './onepasswordCsvImporter';
|
||||
|
||||
import { CipherType } from '../../enums/cipherType';
|
||||
import { CardView, CipherView, IdentityView, LoginView } from '../../models/view';
|
||||
|
||||
export class OnePasswordWinCsvImporter extends OnePasswordCsvImporter implements Importer {
|
||||
constructor() {
|
||||
super();
|
||||
this.identityPropertyParsers.push(this.setIdentityAddress);
|
||||
}
|
||||
|
||||
setCipherType(value: any, cipher: CipherView) {
|
||||
cipher.type = CipherType.Login;
|
||||
cipher.login = new LoginView();
|
||||
|
||||
if (!this.isNullOrWhitespace(this.getPropByRegexp(value, /\d+: number/i)) &&
|
||||
!this.isNullOrWhitespace(this.getPropByRegexp(value, /\d+: expiry date/i))) {
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
}
|
||||
|
||||
if (!this.isNullOrWhitespace(this.getPropByRegexp(value, /name \d+: first name/i)) ||
|
||||
!this.isNullOrWhitespace(this.getPropByRegexp(value, /name \d+: initial/i)) ||
|
||||
!this.isNullOrWhitespace(this.getPropByRegexp(value, /name \d+: last name/i)) ||
|
||||
!this.isNullOrWhitespace(this.getPropByRegexp(value, /internet \d+: email/i))) {
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.identity = new IdentityView();
|
||||
}
|
||||
}
|
||||
|
||||
setIdentityAddress(context: CipherImportContext) {
|
||||
if (context.lowerProperty.match(/address \d+: address/i)) {
|
||||
this.processKvp(context.cipher, 'address', context.importRecord[context.property]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setCreditCardExpiry(context: CipherImportContext) {
|
||||
if (this.isNullOrWhitespace(context.cipher.card.expiration) && context.lowerProperty.includes('expiry date')) {
|
||||
const expSplit = (context.importRecord[context.property] as string).split('/');
|
||||
context.cipher.card.expMonth = expSplit[0];
|
||||
if (context.cipher.card.expMonth[0] === '0' && context.cipher.card.expMonth.length === 2) {
|
||||
context.cipher.card.expMonth = context.cipher.card.expMonth.substr(1, 1);
|
||||
}
|
||||
context.cipher.card.expYear = expSplit[2].length > 4 ? expSplit[2].substr(0, 4) : expSplit[2];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,168 +0,0 @@
|
||||
import { BaseImporter } from './baseImporter';
|
||||
import { Importer } from './importer';
|
||||
|
||||
import { ImportResult } from '../models/domain/importResult';
|
||||
|
||||
import { CipherType } from '../enums/cipherType';
|
||||
import { CardView, IdentityView } from '../models/view';
|
||||
|
||||
const IgnoredProperties = ['ainfo', 'autosubmit', 'notesplain', 'ps', 'scope', 'tags', 'title', 'uuid', 'notes'];
|
||||
|
||||
export class OnePasswordWinCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = this.parseCsv(data, true, {
|
||||
quoteChar: '"',
|
||||
escapeChar: '\\',
|
||||
});
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
results.forEach((value) => {
|
||||
if (this.isNullOrWhitespace(this.getProp(value, 'title'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(this.getProp(value, 'title'), '--');
|
||||
|
||||
cipher.notes = this.getValueOrDefault(this.getProp(value, 'notesPlain'), '') + '\n' +
|
||||
this.getValueOrDefault(this.getProp(value, 'notes'), '') + '\n';
|
||||
cipher.notes.trim();
|
||||
|
||||
const onePassType = this.getValueOrDefault(this.getProp(value, 'type'), 'Login')
|
||||
switch (onePassType) {
|
||||
case 'Credit Card':
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
IgnoredProperties.push('type');
|
||||
break;
|
||||
case 'Identity':
|
||||
cipher.type = CipherType.Identity;
|
||||
cipher.identity = new IdentityView();
|
||||
IgnoredProperties.push('type');
|
||||
break;
|
||||
case 'Login':
|
||||
case 'Secure Note':
|
||||
IgnoredProperties.push('type');
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!this.isNullOrWhitespace(this.getProp(value, 'number')) &&
|
||||
!this.isNullOrWhitespace(this.getProp(value, 'expiry date'))) {
|
||||
cipher.type = CipherType.Card;
|
||||
cipher.card = new CardView();
|
||||
}
|
||||
|
||||
let altUsername: string = null;
|
||||
for (const property in value) {
|
||||
if (!value.hasOwnProperty(property) || this.isNullOrWhitespace(value[property])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const lowerProp = property.toLowerCase();
|
||||
if (cipher.type === CipherType.Login) {
|
||||
if (this.isNullOrWhitespace(cipher.login.password) && lowerProp === 'password') {
|
||||
cipher.login.password = value[property];
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.login.username) && lowerProp === 'username') {
|
||||
cipher.login.username = value[property];
|
||||
continue;
|
||||
} else if ((cipher.login.uris == null || cipher.login.uri.length === 0) && lowerProp === 'urls') {
|
||||
const urls = value[property].split(this.newLineRegex);
|
||||
cipher.login.uris = this.makeUriArray(urls);
|
||||
continue;
|
||||
} else if ((lowerProp === 'url')) {
|
||||
if (cipher.login.uris == null) {
|
||||
cipher.login.uris = [];
|
||||
}
|
||||
cipher.login.uris.concat(this.makeUriArray(value[property]));
|
||||
continue;
|
||||
}
|
||||
} else if (cipher.type === CipherType.Card) {
|
||||
if (this.isNullOrWhitespace(cipher.card.number) && lowerProp.includes('number')) {
|
||||
cipher.card.number = value[property];
|
||||
cipher.card.brand = this.getCardBrand(this.getProp(value, 'number'));
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.card.code) && lowerProp.includes('verification number')) {
|
||||
cipher.card.code = value[property];
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.card.cardholderName) && lowerProp.includes('cardholder name')) {
|
||||
cipher.card.cardholderName = value[property];
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.card.expiration) && lowerProp.includes('expiry date') &&
|
||||
value[property].length === 7) {
|
||||
cipher.card.expMonth = (value[property] as string).substr(0, 2);
|
||||
if (cipher.card.expMonth[0] === '0') {
|
||||
cipher.card.expMonth = cipher.card.expMonth.substr(1, 1);
|
||||
}
|
||||
cipher.card.expYear = (value[property] as string).substr(3, 4);
|
||||
continue;
|
||||
} else if (lowerProp === 'type' || lowerProp === 'type(type)') {
|
||||
// Skip since brand was determined from number above
|
||||
continue;
|
||||
}
|
||||
} else if (cipher.type === CipherType.Identity) {
|
||||
if (this.isNullOrWhitespace(cipher.identity.firstName) && lowerProp.includes('first name')) {
|
||||
cipher.identity.firstName = value[property];
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.identity.middleName) && lowerProp.includes('initial')) {
|
||||
cipher.identity.middleName = value[property];
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.identity.lastName) && lowerProp.includes('last name')) {
|
||||
cipher.identity.lastName = value[property];
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.identity.username) && lowerProp.includes('username')) {
|
||||
cipher.identity.username = value[property];
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.identity.company) && lowerProp.includes('company')) {
|
||||
cipher.identity.company = value[property];
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.identity.phone) && lowerProp.includes('default phone')) {
|
||||
cipher.identity.phone = value[property];
|
||||
continue;
|
||||
} else if (this.isNullOrWhitespace(cipher.identity.email) && lowerProp.includes('email')) {
|
||||
cipher.identity.email = value[property];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (IgnoredProperties.indexOf(lowerProp) === -1 && !lowerProp.startsWith('section:') &&
|
||||
!lowerProp.startsWith('section ')) {
|
||||
if (altUsername == null && lowerProp === 'email') {
|
||||
altUsername = value[property];
|
||||
}
|
||||
else if (lowerProp === 'created date' || lowerProp === 'modified date') {
|
||||
const readableDate = new Date(parseInt(value[property], 10) * 1000).toUTCString();
|
||||
this.processKvp(cipher, '1Password ' + property, readableDate);
|
||||
continue;
|
||||
}
|
||||
this.processKvp(cipher, property, value[property]);
|
||||
}
|
||||
}
|
||||
|
||||
if (cipher.type === CipherType.Login && !this.isNullOrWhitespace(altUsername) &&
|
||||
this.isNullOrWhitespace(cipher.login.username) && altUsername.indexOf('://') === -1) {
|
||||
cipher.login.username = altUsername;
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private getProp(obj: any, name: string): any {
|
||||
const lowerObj = Object.entries(obj).reduce((agg: any, entry: [string, any]) => {
|
||||
agg[entry[0].toLowerCase()] = entry[1];
|
||||
return agg;
|
||||
}, {});
|
||||
return lowerObj[name.toLowerCase()];
|
||||
}
|
||||
}
|
@ -94,7 +94,7 @@ export class Utils {
|
||||
}
|
||||
|
||||
static fromBufferToUrlB64(buffer: ArrayBuffer): string {
|
||||
return Utils.fromB64toUrlB64(Utils.fromBufferToB64(buffer))
|
||||
return Utils.fromB64toUrlB64(Utils.fromBufferToB64(buffer));
|
||||
}
|
||||
|
||||
static fromB64toUrlB64(b64Str: string) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { SendType } from '../../enums/sendType';
|
||||
|
||||
import { SendFileApi } from '../api/sendFileApi'
|
||||
import { SendFileApi } from '../api/sendFileApi';
|
||||
import { SendTextApi } from '../api/sendTextApi';
|
||||
|
||||
import { Send } from '../domain/send';
|
||||
|
@ -25,8 +25,8 @@ export class TokenRequest {
|
||||
this.codeVerifier = codes[1];
|
||||
this.redirectUri = codes[2];
|
||||
} else if (clientIdClientSecret != null && clientIdClientSecret.length > 1) {
|
||||
this.clientId = clientIdClientSecret[0]
|
||||
this.clientSecret = clientIdClientSecret[1]
|
||||
this.clientId = clientIdClientSecret[0];
|
||||
this.clientSecret = clientIdClientSecret[1];
|
||||
}
|
||||
this.token = token;
|
||||
this.provider = provider;
|
||||
|
@ -283,7 +283,7 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
codeCodeVerifier = null;
|
||||
}
|
||||
if (clientId != null && clientSecret != null) {
|
||||
clientIdClientSecret = [clientId, clientSecret]
|
||||
clientIdClientSecret = [clientId, clientSecret];
|
||||
} else {
|
||||
clientIdClientSecret = null;
|
||||
}
|
||||
|
@ -52,8 +52,9 @@ import { LogMeOnceCsvImporter } from '../importers/logMeOnceCsvImporter';
|
||||
import { MeldiumCsvImporter } from '../importers/meldiumCsvImporter';
|
||||
import { MSecureCsvImporter } from '../importers/msecureCsvImporter';
|
||||
import { MykiCsvImporter } from '../importers/mykiCsvImporter';
|
||||
import { OnePassword1PifImporter } from '../importers/onepassword1PifImporter';
|
||||
import { OnePasswordWinCsvImporter } from '../importers/onepasswordWinCsvImporter';
|
||||
import { OnePassword1PifImporter } from '../importers/onepasswordImporters/onepassword1PifImporter';
|
||||
import { OnePasswordMacCsvImporter } from '../importers/onepasswordImporters/onepasswordMacCsvImporter';
|
||||
import { OnePasswordWinCsvImporter } from '../importers/onepasswordImporters/onepasswordWinCsvImporter';
|
||||
import { PadlockCsvImporter } from '../importers/padlockCsvImporter';
|
||||
import { PassKeepCsvImporter } from '../importers/passkeepCsvImporter';
|
||||
import { PassmanJsonImporter } from '../importers/passmanJsonImporter';
|
||||
@ -90,6 +91,7 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
regularImportOptions: ImportOption[] = [
|
||||
{ id: 'keepassxcsv', name: 'KeePassX (csv)' },
|
||||
{ id: '1passwordwincsv', name: '1Password 6 and 7 Windows (csv)' },
|
||||
{ id: '1passwordmaccsv', name: '1Password 6 and 7 Mac (csv)' },
|
||||
{ id: 'roboformcsv', name: 'RoboForm (csv)' },
|
||||
{ id: 'keepercsv', name: 'Keeper (csv)' },
|
||||
{ id: 'enpasscsv', name: 'Enpass (csv)' },
|
||||
@ -215,6 +217,8 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
return new OnePassword1PifImporter();
|
||||
case '1passwordwincsv':
|
||||
return new OnePasswordWinCsvImporter();
|
||||
case '1passwordmaccsv':
|
||||
return new OnePasswordMacCsvImporter();
|
||||
case 'keepercsv':
|
||||
return new KeeperCsvImporter();
|
||||
case 'passworddragonxml':
|
||||
|
@ -51,6 +51,10 @@
|
||||
"check-separator",
|
||||
"check-type"
|
||||
],
|
||||
"max-classes-per-file": false
|
||||
"max-classes-per-file": false,
|
||||
"semicolon": [
|
||||
true,
|
||||
"always"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user