Share contact upon authorising secondary device

pull/487/head
sachaaaaa 6 years ago
parent cf3ddf0b03
commit b10835ffc7

@ -101,7 +101,7 @@ module.exports = {
updateConversation, updateConversation,
removeConversation, removeConversation,
getAllConversations, getAllConversations,
getPubKeysWithFriendStatus, getConversationsWithFriendStatus,
getAllConversationIds, getAllConversationIds,
getAllPrivateConversations, getAllPrivateConversations,
getAllGroupsInvolvingId, getAllGroupsInvolvingId,
@ -1566,7 +1566,9 @@ async function updateConversation(data) {
async function removeConversation(id) { async function removeConversation(id) {
if (!Array.isArray(id)) { if (!Array.isArray(id)) {
await db.run(`DELETE FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`, { $id: id }); await db.run(`DELETE FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`, {
$id: id,
});
return; return;
} }
@ -1584,9 +1586,12 @@ async function removeConversation(id) {
} }
async function getConversationById(id) { async function getConversationById(id) {
const row = await db.get(`SELECT * FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`, { const row = await db.get(
$id: id, `SELECT * FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`,
}); {
$id: id,
}
);
if (!row) { if (!row) {
return null; return null;
@ -1596,24 +1601,29 @@ async function getConversationById(id) {
} }
async function getAllConversations() { async function getAllConversations() {
const rows = await db.all(`SELECT json FROM ${CONVERSATIONS_TABLE} ORDER BY id ASC;`); const rows = await db.all(
`SELECT json FROM ${CONVERSATIONS_TABLE} ORDER BY id ASC;`
);
return map(rows, row => jsonToObject(row.json)); return map(rows, row => jsonToObject(row.json));
} }
async function getPubKeysWithFriendStatus(status) { async function getConversationsWithFriendStatus(status) {
const rows = await db.all( const rows = await db.all(
`SELECT id FROM ${CONVERSATIONS_TABLE} WHERE `SELECT * FROM ${CONVERSATIONS_TABLE} WHERE
friendRequestStatus = $status friendRequestStatus = $status
AND type = 'private'
ORDER BY id ASC;`, ORDER BY id ASC;`,
{ {
$status: status, $status: status,
} }
); );
return map(rows, row => row.id); return map(rows, row => jsonToObject(row.json));
} }
async function getAllConversationIds() { async function getAllConversationIds() {
const rows = await db.all(`SELECT id FROM ${CONVERSATIONS_TABLE} ORDER BY id ASC;`); const rows = await db.all(
`SELECT id FROM ${CONVERSATIONS_TABLE} ORDER BY id ASC;`
);
return map(rows, row => row.id); return map(rows, row => row.id);
} }

@ -1113,12 +1113,19 @@
} }
} }
// Do not set name to allow working with lokiProfile and nicknames
conversation.set({ conversation.set({
name: details.name, // name: details.name,
color: details.color, color: details.color,
active_at: activeAt, active_at: activeAt,
}); });
await conversation.setLokiProfile({ displayName: details.name });
if (details.nickname) {
await conversation.setNickname(details.nickname);
}
// Update the conversation avatar only if new avatar exists and hash differs // Update the conversation avatar only if new avatar exists and hash differs
const { avatar } = details; const { avatar } = details;
if (avatar && avatar.data) { if (avatar && avatar.data) {

@ -122,6 +122,7 @@ module.exports = {
getAllConversations, getAllConversations,
getPubKeysWithFriendStatus, getPubKeysWithFriendStatus,
getConversationsWithFriendStatus,
getAllConversationIds, getAllConversationIds,
getAllPrivateConversations, getAllPrivateConversations,
getAllGroupsInvolvingId, getAllGroupsInvolvingId,
@ -782,8 +783,20 @@ async function _removeConversations(ids) {
await channels.removeConversation(ids); await channels.removeConversation(ids);
} }
async function getConversationsWithFriendStatus(
status,
{ ConversationCollection }
) {
const conversations = await channels.getConversationsWithFriendStatus(status);
const collection = new ConversationCollection();
collection.add(conversations);
return collection;
}
async function getPubKeysWithFriendStatus(status) { async function getPubKeysWithFriendStatus(status) {
return channels.getPubKeysWithFriendStatus(status); const conversations = await getConversationsWithFriendStatus(status);
return conversations.map(row => row.id);
} }
async function getAllConversations({ ConversationCollection }) { async function getAllConversations({ ConversationCollection }) {

@ -39,6 +39,21 @@
return false; return false;
} }
function convertVerifiedStatusToProtoState(status) {
switch (status) {
case VerifiedStatus.VERIFIED:
return textsecure.protobuf.Verified.State.VERIFIED;
case VerifiedStatus.UNVERIFIED:
return textsecure.protobuf.Verified.State.VERIFIED;
case VerifiedStatus.DEFAULT:
// intentional fallthrough
default:
return textsecure.protobuf.Verified.State.DEFAULT;
}
}
const StaticByteBufferProto = new dcodeIO.ByteBuffer().__proto__; const StaticByteBufferProto = new dcodeIO.ByteBuffer().__proto__;
const StaticArrayBufferProto = new ArrayBuffer().__proto__; const StaticArrayBufferProto = new ArrayBuffer().__proto__;
const StaticUint8ArrayProto = new Uint8Array().__proto__; const StaticUint8ArrayProto = new Uint8Array().__proto__;
@ -913,4 +928,5 @@
window.SignalProtocolStore = SignalProtocolStore; window.SignalProtocolStore = SignalProtocolStore;
window.SignalProtocolStore.prototype.Direction = Direction; window.SignalProtocolStore.prototype.Direction = Direction;
window.SignalProtocolStore.prototype.VerifiedStatus = VerifiedStatus; window.SignalProtocolStore.prototype.VerifiedStatus = VerifiedStatus;
window.SignalProtocolStore.prototype.convertVerifiedStatusToProtoState = convertVerifiedStatusToProtoState;
})(); })();

@ -151,7 +151,7 @@
Whisper.Registration.remove(); Whisper.Registration.remove();
// Do not remove all items since they are only set // Do not remove all items since they are only set
// at startup. // at startup.
textsecure.storage.remove('identityKey') textsecure.storage.remove('identityKey');
textsecure.storage.remove('secondaryDeviceStatus'); textsecure.storage.remove('secondaryDeviceStatus');
window.ConversationController.reset(); window.ConversationController.reset();
await window.ConversationController.load(); await window.ConversationController.load();

@ -1,4 +1,4 @@
/* global window, textsecure, log */ /* global window, textsecure, log, Whisper, dcodeIO, StringView */
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
(function() { (function() {
@ -98,7 +98,66 @@
type, type,
}); });
} }
// Serialise as <Element0.length><Element0><Element1.length><Element1>...
// This is an implementation of the reciprocal of contacts_parser.js
function serialiseByteBuffers(buffers) {
const result = new dcodeIO.ByteBuffer();
buffers.forEach(buffer => {
// bytebuffer container expands and increments
// offset automatically
result.writeVarint32(buffer.limit);
result.append(buffer);
});
result.limit = result.offset;
result.reset();
return result;
}
async function createContactSyncProtoMessage() {
const conversations = await window.Signal.Data.getConversationsWithFriendStatus(
window.friends.friendRequestStatusEnum.friends,
{ ConversationCollection: Whisper.ConversationCollection }
);
// Extract required contacts information out of conversations
const rawContacts = conversations.map(conversation => {
const profile = conversation.getLokiProfile();
const number = conversation.getNumber();
const name = profile
? profile.displayName
: conversation.getProfileName();
const status = conversation.safeGetVerified();
const protoState = textsecure.storage.protocol.convertVerifiedStatusToProtoState(
status
);
const verified = new textsecure.protobuf.Verified({
state: protoState,
destination: number,
identityKey: StringView.hexToArrayBuffer(number),
});
return {
name,
verified,
number,
nickname: conversation.getNickname(),
blocked: conversation.isBlocked(),
expireTimer: conversation.get('expireTimer'),
};
});
// Convert raw contacts to an array of buffers
const contactDetails = rawContacts
.filter(x => x.number !== textsecure.storage.user.getNumber())
.map(x => new textsecure.protobuf.ContactDetails(x))
.map(x => x.encode());
// Serialise array of byteBuffers into 1 byteBuffer
const byteBuffer = serialiseByteBuffers(contactDetails);
const data = new Uint8Array(byteBuffer.toArrayBuffer());
const contacts = new textsecure.protobuf.SyncMessage.Contacts({
data,
});
const syncMessage = new textsecure.protobuf.SyncMessage({
contacts,
});
return syncMessage;
}
async function sendPairingAuthorisation(authorisation, recipientPubKey) { async function sendPairingAuthorisation(authorisation, recipientPubKey) {
const pairingAuthorisation = createPairingAuthorisationProtoMessage( const pairingAuthorisation = createPairingAuthorisationProtoMessage(
authorisation authorisation
@ -116,10 +175,14 @@
const dataMessage = new textsecure.protobuf.DataMessage({ const dataMessage = new textsecure.protobuf.DataMessage({
profile, profile,
}); });
// Attach contact list
const syncMessage = await createContactSyncProtoMessage();
const content = new textsecure.protobuf.Content({ const content = new textsecure.protobuf.Content({
pairingAuthorisation, pairingAuthorisation,
dataMessage, dataMessage,
syncMessage,
}); });
// Send
const options = { messageType: 'pairing-request' }; const options = { messageType: 'pairing-request' };
const p = new Promise((resolve, reject) => { const p = new Promise((resolve, reject) => {
const outgoingMessage = new textsecure.OutgoingMessage( const outgoingMessage = new textsecure.OutgoingMessage(
@ -149,5 +212,6 @@
broadcastOnlineStatus, broadcastOnlineStatus,
sendPairingAuthorisation, sendPairingAuthorisation,
createPairingAuthorisationProtoMessage, createPairingAuthorisationProtoMessage,
createContactSyncProtoMessage,
}; };
})(); })();

@ -1093,82 +1093,103 @@ MessageReceiver.prototype.extend({
} }
return true; return true;
}, },
async handlePairingRequest(pairingRequest) { async handlePairingRequest(envelope, pairingRequest) {
const valid = await this.validateAuthorisation(pairingRequest); const valid = await this.validateAuthorisation(pairingRequest);
if (!valid) { if (valid) {
return; await window.libloki.storage.savePairingAuthorisation(pairingRequest);
Whisper.events.trigger(
'devicePairingRequestReceived',
pairingRequest.secondaryDevicePubKey
);
} }
await window.libloki.storage.savePairingAuthorisation(pairingRequest); return this.removeFromCache(envelope);
Whisper.events.trigger(
'devicePairingRequestReceived',
pairingRequest.secondaryDevicePubKey
);
}, },
async handleAuthorisationForSelf(pairingAuthorisation, dataMessage) { async handleAuthorisationForSelf(
envelope,
pairingAuthorisation,
{ dataMessage, syncMessage }
) {
const valid = await this.validateAuthorisation(pairingAuthorisation); const valid = await this.validateAuthorisation(pairingAuthorisation);
if (!valid) { const alreadySecondaryDevice = !!window.storage.get('isSecondaryDevice');
return; let removedFromCache = false;
} if (alreadySecondaryDevice) {
const { type, primaryDevicePubKey } = pairingAuthorisation; window.log.warn(
if (type === textsecure.protobuf.PairingAuthorisationMessage.Type.GRANT) { 'Received an unexpected pairing authorisation (device is already paired as secondary device). Ignoring.'
// Authorisation received to become a secondary device
window.log.info(
`Received pairing authorisation from ${primaryDevicePubKey}`
); );
const alreadySecondaryDevice = !!window.storage.get('isSecondaryDevice'); } else if (!valid) {
if (alreadySecondaryDevice) { window.log.warn(
window.log.warn( 'Received invalid pairing authorisation for self. Could not verify signature. Ignoring.'
'Received an unexpected pairing authorisation (device is already paired as secondary device). Ignoring.' );
} else {
const { type, primaryDevicePubKey } = pairingAuthorisation;
if (type === textsecure.protobuf.PairingAuthorisationMessage.Type.GRANT) {
// Authorisation received to become a secondary device
window.log.info(
`Received pairing authorisation from ${primaryDevicePubKey}`
); );
return; await libloki.storage.savePairingAuthorisation(pairingAuthorisation);
} // Set current device as secondary.
await libloki.storage.savePairingAuthorisation(pairingAuthorisation); // This will ensure the authorisation is sent
// Set current device as secondary. // along with each friend request.
// This will ensure the authorisation is sent window.storage.remove('secondaryDeviceStatus');
// along with each friend request. window.storage.put('isSecondaryDevice', true);
window.storage.remove('secondaryDeviceStatus'); Whisper.events.trigger('secondaryDeviceRegistration');
window.storage.put('isSecondaryDevice', true); // Update profile name
Whisper.events.trigger('secondaryDeviceRegistration'); if (dataMessage && dataMessage.profile) {
// Update profile name const ourNumber = textsecure.storage.user.getNumber();
if (dataMessage && dataMessage.profile) { const me = window.ConversationController.get(ourNumber);
const ourNumber = textsecure.storage.user.getNumber(); if (me) {
const me = window.ConversationController.get(ourNumber); me.setLokiProfile(dataMessage.profile);
if (me) { }
me.setLokiProfile(dataMessage.profile); }
// Update contact list
if (syncMessage && syncMessage.contacts) {
// This call already removes the envelope from the cache
await this.handleContacts(envelope, syncMessage.contacts);
removedFromCache = true;
} }
} else {
window.log.warn('Unimplemented pairing authorisation message type');
} }
} else { }
window.log.warn('Unimplemented pairing authorisation message type'); if (!removedFromCache) {
await this.removeFromCache(envelope);
} }
}, },
async handleAuthorisationForContact(pairingAuthorisation) { async handleAuthorisationForContact(envelope, pairingAuthorisation) {
const valid = await this.validateAuthorisation(pairingAuthorisation); const valid = await this.validateAuthorisation(pairingAuthorisation);
if (!valid) { if (!valid) {
return; window.log.warn(
} 'Received invalid pairing authorisation for self. Could not verify signature. Ignoring.'
const { primaryDevicePubKey, secondaryDevicePubKey } = pairingAuthorisation; );
// ensure the primary device is a friend } else {
const c = window.ConversationController.get(primaryDevicePubKey); const {
if (!c || !c.isFriend()) { primaryDevicePubKey,
return; secondaryDevicePubKey,
} = pairingAuthorisation;
// ensure the primary device is a friend
const c = window.ConversationController.get(primaryDevicePubKey);
if (c && c.isFriend()) {
await libloki.storage.savePairingAuthorisation(pairingAuthorisation);
// send friend accept?
window.libloki.api.sendBackgroundMessage(secondaryDevicePubKey);
}
} }
await libloki.storage.savePairingAuthorisation(pairingAuthorisation); return this.removeFromCache(envelope);
// send friend accept?
window.libloki.api.sendBackgroundMessage(secondaryDevicePubKey);
}, },
async handlePairingAuthorisationMessage( async handlePairingAuthorisationMessage(envelope, content) {
envelope, const { pairingAuthorisation } = content;
{ pairingAuthorisation, dataMessage }
) {
const { type, secondaryDevicePubKey } = pairingAuthorisation; const { type, secondaryDevicePubKey } = pairingAuthorisation;
if (type === textsecure.protobuf.PairingAuthorisationMessage.Type.REQUEST) { if (type === textsecure.protobuf.PairingAuthorisationMessage.Type.REQUEST) {
await this.handlePairingRequest(pairingAuthorisation); return this.handlePairingRequest(envelope, pairingAuthorisation);
} else if (secondaryDevicePubKey === textsecure.storage.user.getNumber()) { } else if (secondaryDevicePubKey === textsecure.storage.user.getNumber()) {
await this.handleAuthorisationForSelf(pairingAuthorisation, dataMessage); return this.handleAuthorisationForSelf(
} else { envelope,
await this.handleAuthorisationForContact(pairingAuthorisation); pairingAuthorisation,
content
);
} }
return this.removeFromCache(envelope); return this.handleAuthorisationForContact(envelope, pairingAuthorisation);
}, },
handleDataMessage(envelope, msg) { handleDataMessage(envelope, msg) {
if (!envelope.isP2p) { if (!envelope.isP2p) {
@ -1455,11 +1476,11 @@ MessageReceiver.prototype.extend({
}, },
handleContacts(envelope, contacts) { handleContacts(envelope, contacts) {
window.log.info('contact sync'); window.log.info('contact sync');
const { blob } = contacts; // const { blob } = contacts;
// Note: we do not return here because we don't want to block the next message on // Note: we do not return here because we don't want to block the next message on
// this attachment download and a lot of processing of that attachment. // this attachment download and a lot of processing of that attachment.
this.handleAttachment(blob).then(attachmentPointer => { this.handleAttachment(contacts).then(attachmentPointer => {
const results = []; const results = [];
const contactBuffer = new ContactBuffer(attachmentPointer.data); const contactBuffer = new ContactBuffer(attachmentPointer.data);
let contactDetails = contactBuffer.next(); let contactDetails = contactBuffer.next();
@ -1562,8 +1583,8 @@ MessageReceiver.prototype.extend({
}; };
}, },
async downloadAttachment(attachment) { async downloadAttachment(attachment) {
window.log.info('Not downloading attachments.'); // window.log.info('Not downloading attachments.');
return Promise.reject(); // return Promise.reject();
const encrypted = await this.server.getAttachment(attachment.id); const encrypted = await this.server.getAttachment(attachment.id);
const { key, digest, size } = attachment; const { key, digest, size } = attachment;
@ -1588,8 +1609,11 @@ MessageReceiver.prototype.extend({
}; };
}, },
handleAttachment(attachment) { handleAttachment(attachment) {
window.log.info('Not handling attachments.'); // window.log.info('Not handling attachments.');
return Promise.reject(); return Promise.resolve({
...attachment,
data: dcodeIO.ByteBuffer.wrap(attachment.data).toArrayBuffer(), // ByteBuffer to ArrayBuffer
});
const cleaned = this.cleanAttachment(attachment); const cleaned = this.cleanAttachment(attachment);
return this.downloadAttachment(cleaned); return this.downloadAttachment(cleaned);

@ -272,6 +272,7 @@ message SyncMessage {
message Contacts { message Contacts {
optional AttachmentPointer blob = 1; optional AttachmentPointer blob = 1;
optional bool complete = 2 [default = false]; optional bool complete = 2 [default = false];
optional bytes data = 101;
} }
message Groups { message Groups {
@ -365,6 +366,7 @@ message ContactDetails {
optional bytes profileKey = 6; optional bytes profileKey = 6;
optional bool blocked = 7; optional bool blocked = 7;
optional uint32 expireTimer = 8; optional uint32 expireTimer = 8;
optional string nickname = 101;
} }
message GroupDetails { message GroupDetails {

Loading…
Cancel
Save