Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert papers/contacts to CipherData instead of CipherResponse #184

Merged
merged 6 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions apps/browser/src/cozy/realtime/RealtimeNotifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@ import CozyClient from "cozy-client";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { CipherResponse } from "@bitwarden/common/vault/models/response/cipher.response";

import { convertContactToCipherResponse } from "../../../../../libs/cozy/contact.helper";
import { convertContactToCipherData } from "../../../../../libs/cozy/contact.helper";
import {
convertNoteToCipherResponse,
convertNoteToCipherData,
fetchNoteIllustrationUrl,
isNote,
} from "../../../../../libs/cozy/note.helper";
import { convertPaperToCipherResponse } from "../../../../../libs/cozy/paper.helper";
import { convertPaperToCipherData } from "../../../../../libs/cozy/paper.helper";
import { fetchPaper } from "../../../../../libs/cozy/queries";

export class RealTimeNotifications {
Expand Down Expand Up @@ -69,13 +67,13 @@ export class RealTimeNotifications {
}

async dispatchCreateOrUpdateContact(data: any) {
const cipherResponse = await convertContactToCipherResponse(
const cipherData = await convertContactToCipherData(
this.cipherService,
this.i18nService,
data,
null
);
await this.cipherService.upsert(new CipherData(cipherResponse));
await this.cipherService.upsert(cipherData);
this.messagingService.send("syncedUpsertedCipher", { cipherId: data._id });
this.messagingService.send("syncCompleted", { successfully: true });
}
Expand Down Expand Up @@ -105,11 +103,11 @@ export class RealTimeNotifications {
const itemFromDb = await fetchPaper(this.client, paperId);
const hydratedData = this.client.hydrateDocuments("io.cozy.files", [itemFromDb])[0];

let cipherResponse: CipherResponse;
let cipherData;
if (isNote(itemFromDb)) {
const noteIllustrationUrl = await fetchNoteIllustrationUrl(this.client);

cipherResponse = await convertNoteToCipherResponse(
cipherData = await convertNoteToCipherData(
this.cipherService,
this.i18nService,
hydratedData,
Expand All @@ -120,7 +118,7 @@ export class RealTimeNotifications {
} else {
const baseUrl = this.client.getStackClient().uri;

cipherResponse = await convertPaperToCipherResponse(
cipherData = await convertPaperToCipherData(
this.cipherService,
this.i18nService,
hydratedData,
Expand All @@ -130,7 +128,7 @@ export class RealTimeNotifications {
);
}

await this.cipherService.upsert(new CipherData(cipherResponse));
await this.cipherService.upsert(cipherData);
this.messagingService.send("syncedUpsertedCipher", { cipherId: paperId });
this.messagingService.send("syncCompleted", { successfully: true });
}
Expand Down
6 changes: 6 additions & 0 deletions libs/common/src/vault/models/domain/cipher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,12 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
case CipherType.Identity:
c.identity = this.identity.toIdentityData();
break;
case CipherType.Paper:
c.paper = this.paper.toPaperData();
break;
case CipherType.Contact:
c.contact = this.contact.toContactData();
break;
default:
break;
}
Expand Down
18 changes: 18 additions & 0 deletions libs/common/src/vault/models/domain/contact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,23 @@ export class Contact extends Domain {
encKey
);
}

toContactData(): ContactData {
const c = new ContactData();
c.me = this.me;
this.buildDataModel(
this,
c,
{
displayName: null,
initials: null,
primaryEmail: null,
primaryPhone: null,
},
[]
);

return c;
}
}
// Cozy customization end
5 changes: 5 additions & 0 deletions libs/common/src/vault/models/domain/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ export class Field extends Domain {

toFieldData(): FieldData {
const f = new FieldData();
f.id = this.id;
f.parentId = this.parentId;
f.subtype = this.subtype;
f.expirationData = this.expirationData;
f.label = this.label;
this.buildDataModel(
this,
f,
Expand Down
19 changes: 19 additions & 0 deletions libs/common/src/vault/models/domain/paper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,24 @@ export class Paper extends Domain {
encKey
);
}

toPaperData(): PaperData {
const p = new PaperData();
p.type = this.type;
this.buildDataModel(
this,
p,
{
ownerName: null,
illustrationThumbnailUrl: null,
illustrationUrl: null,
qualificationLabel: null,
noteContent: null,
},
[]
);

return p;
}
}
// Cozy customization end
1 change: 1 addition & 0 deletions libs/common/src/vault/services/cipher.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1277,6 +1277,7 @@ export class CipherService implements CipherServiceAbstraction {
return;
case CipherType.Contact:
cipher.contact = new Contact();
cipher.contact.me = model.contact.me;
await this.encryptObjProperty(
model.contact,
cipher.contact,
Expand Down
19 changes: 16 additions & 3 deletions libs/common/src/vault/services/sync/sync.service.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-console */
import { CozyClientService } from "../../../../../../apps/browser/src/popup/services/cozyClient.service";
import { fetchContactsAndConvertAsCiphers } from "../../../../../../libs/cozy/contactCipher";
import { fetchPapersAndConvertAsCiphers } from "../../../../../../libs/cozy/paperCipher";
Expand Down Expand Up @@ -131,16 +132,28 @@ export class SyncService implements SyncServiceAbstraction {

const [papersPromise, contactsPromise] = await Promise.allSettled(fetchPromises);

/*
Because syncCiphers replace previous ciphers, we need to do syncCiphers :
- after we converted papers and contacts because papers and contacts conversion
can try to reuse previous ciphers
- before we upsert papers and contacts because we want to have
bitwarden ciphers and papers and contacts ciphers
*/
await this.syncCiphers(response.ciphers);

if (papersPromise.status === "fulfilled") {
response.ciphers.push(...papersPromise.value);
console.log(`${papersPromise.value.length} contacts ciphers will be added`);

await this.cipherService.upsert(papersPromise.value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Care, each upsert will trigger the localstorage serialization. By doing this instead of using the in-memory response.ciphers my understanding is that we will add 2 serializations to the sync process (one for papers, one for contacts).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Ideas :

  • I can easily do 1 upsert for both papers and contacts.
  • I can also modify syncCiphers to take in parameters papers and contacts as CipherData (and also of course bitwarden ciphers as CipherReponse). It should be easy but at first I wanted to avoid modifying syncCiphers. What do you think about it?

}

if (contactsPromise.status === "fulfilled") {
response.ciphers.push(...contactsPromise.value);
console.log(`${contactsPromise.value.length} papers ciphers will be added`);

await this.cipherService.upsert(contactsPromise.value);
}
// Cozy customization end

await this.syncCiphers(response.ciphers);
await this.syncSends(response.sends);
await this.syncSettings(response.domains);
await this.syncPolicies(response.policies);
Expand Down
57 changes: 25 additions & 32 deletions libs/cozy/contact.helper.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
import { models } from "cozy-client";
import { IOCozyContact } from "cozy-client/types/types";

import { ContactApi } from "@bitwarden/common/models/api/contact.api";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { CipherResponse } from "@bitwarden/common/vault/models/response/cipher.response";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { ContactView } from "@bitwarden/common/vault/models/view/contact.view";

const { getInitials } = models.contact;

import { buildFieldsFromContact, copyEncryptedFields } from "./fields.helper";
import { buildFieldsFromContact } from "./fields.helper";

const getPrimaryEmail = (contact: any): string | undefined => {
return contact.email?.find((email: any) => email.primary)?.address;
const getPrimaryEmail = (contact: IOCozyContact): string | undefined => {
return contact.email?.find((email) => email.primary)?.address;
};

const getPrimaryPhone = (contact: any): string | undefined => {
return contact.phone?.find((phone: any) => phone.primary)?.number;
const getPrimaryPhone = (contact: IOCozyContact): string | undefined => {
return contact.phone?.find((phone) => phone.primary)?.number;
};

export const convertContactToCipherResponse = async (
cipherService: any,
i18nService: any,
contact: any,
export const convertContactToCipherData = async (
cipherService: CipherService,
i18nService: I18nService,
contact: IOCozyContact,
key?: SymmetricCryptoKey
): Promise<CipherResponse> => {
): Promise<CipherData> => {
// Temporary type fix because contact.cozyMetadata is not properly typed
const cozyMetadata = contact.cozyMetadata as any;

const cipherView = new CipherView();
cipherView.id = contact.id ?? contact._id;
cipherView.name = contact.displayName;
Expand All @@ -34,27 +39,15 @@ export const convertContactToCipherResponse = async (
cipherView.contact.initials = getInitials(contact);
cipherView.contact.primaryEmail = getPrimaryEmail(contact);
cipherView.contact.primaryPhone = getPrimaryPhone(contact);
cipherView.favorite = !!contact.cozyMetadata?.favorite;
cipherView.favorite = !!cozyMetadata?.favorite;
cipherView.fields = buildFieldsFromContact(i18nService, contact);
cipherView.contact.me = contact.me;
cipherView.creationDate = new Date(cozyMetadata?.createdAt);
cipherView.revisionDate = new Date(cozyMetadata?.updatedAt);

const cipherEncrypted = await cipherService.encrypt(cipherView, key);
const cipherViewEncrypted = new CipherView(cipherEncrypted);
const cipherViewResponse = new CipherResponse(cipherViewEncrypted);
cipherViewResponse.id = cipherEncrypted.id;
cipherViewResponse.name = cipherEncrypted.name?.encryptedString ?? "";
cipherViewResponse.contact = new ContactApi();
cipherViewResponse.contact.displayName =
cipherEncrypted.contact.displayName?.encryptedString ?? "";
cipherViewResponse.contact.initials = cipherEncrypted.contact.initials?.encryptedString ?? "";
cipherViewResponse.contact.primaryEmail =
cipherEncrypted.contact.primaryEmail?.encryptedString ?? "";
cipherViewResponse.contact.primaryPhone =
cipherEncrypted.contact.primaryPhone?.encryptedString ?? "";
cipherViewResponse.favorite = cipherEncrypted.favorite;
cipherViewResponse.creationDate = contact.cozyMetadata?.createdAt;
cipherViewResponse.revisionDate = contact.cozyMetadata?.updatedAt;
cipherViewResponse.fields = copyEncryptedFields(cipherEncrypted.fields ?? []);
cipherViewResponse.contact.me = contact.me;

return cipherViewResponse;

const cipherData = cipherEncrypted.toCipherData();

return cipherData;
};
64 changes: 32 additions & 32 deletions libs/cozy/contactCipher.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
/* eslint-disable no-console */
// Cozy customization
import { IOCozyContact } from "cozy-client/types/types";

import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { CipherResponse } from "@bitwarden/common/vault/models/response/cipher.response";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";

import { convertContactToCipherResponse } from "./contact.helper";
import { CozyClientService } from "../../apps/browser/src/popup/services/cozyClient.service";

import { convertContactToCipherData } from "./contact.helper";
import { fetchContacts, fetchContact } from "./queries";

const convertContactsAsCiphers = async (
cipherService: any,
cipherService: CipherService,
cryptoService: CryptoService,
i18nService: any,
contacts: any
): Promise<CipherResponse[]> => {
i18nService: I18nService,
contacts: IOCozyContact[]
): Promise<CipherData[]> => {
const contactsCiphers = [];

const key = await cryptoService.getKeyForUserEncryption();

for (const contact of contacts) {
try {
const cipherResponse = await convertContactToCipherResponse(
cipherService,
i18nService,
contact,
key
);

contactsCiphers.push(cipherResponse);
const cipherData = await convertContactToCipherData(cipherService, i18nService, contact, key);

contactsCiphers.push(cipherData);
} catch (e) {
if (e.message === "No encryption key provided.") {
throw e;
}

console.log(`Error during conversion of contact ${contact.id}`, contact, e);
}
}
Expand All @@ -41,11 +44,11 @@ const convertContactsAsCiphers = async (
};

export const fetchContactsAndConvertAsCiphers = async (
cipherService: any,
cipherService: CipherService,
cryptoService: CryptoService,
cozyClientService: any,
i18nService: any
): Promise<CipherResponse[]> => {
cozyClientService: CozyClientService,
i18nService: I18nService
): Promise<CipherData[]> => {
const client = await cozyClientService.getClientInstance();

try {
Expand All @@ -58,21 +61,24 @@ export const fetchContactsAndConvertAsCiphers = async (
contacts
);

console.log(`${contactsCiphers.length} contacts ciphers will be added`);

return contactsCiphers;
} catch (e) {
console.log("Error while fetching contacts and converting them as ciphers", e);
console.log(
"Error while fetching contacts and converting them as ciphers. Fallbacking to stored contacts.",
e
);

return [];
return (await cipherService.getAll())
.filter((cipher) => cipher.type === CipherType.Contact)
.map((cipher) => cipher.toCipherData());
}
};

export const favoriteContactCipher = async (
cipherService: CipherService,
i18nService: I18nService,
cipher: CipherView,
cozyClientService: any
cozyClientService: CozyClientService
): Promise<boolean> => {
const client = await cozyClientService.getClientInstance();

Expand All @@ -86,15 +92,9 @@ export const favoriteContactCipher = async (
},
});

const cipherResponse = await convertContactToCipherResponse(
cipherService,
i18nService,
updatedContact
);

const cipherData = new CipherData(cipherResponse);
const cipherData = await convertContactToCipherData(cipherService, i18nService, updatedContact);

await cipherService.upsert([cipherData]);
await cipherService.upsert(cipherData);

return true;
};
Expand All @@ -105,7 +105,7 @@ export const deleteContactCipher = async (
platformUtilsService: PlatformUtilsService,
cipher: CipherView,
stateService: StateService,
cozyClientService: any
cozyClientService: CozyClientService
): Promise<boolean> => {
const confirmed = await platformUtilsService.showDialog(
i18nService.t("deleteContactItemConfirmation"),
Expand Down
Loading
Loading