Share contact upon authorising secondary device

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

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

@ -1113,12 +1113,19 @@
}
}
// Do not set name to allow working with lokiProfile and nicknames
conversation.set({
name: details.name,
// name: details.name,
color: details.color,
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
const { avatar } = details;
if (avatar && avatar.data) {

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

@ -39,6 +39,21 @@
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 StaticArrayBufferProto = new ArrayBuffer().__proto__;
const StaticUint8ArrayProto = new Uint8Array().__proto__;
@ -913,4 +928,5 @@
window.SignalProtocolStore = SignalProtocolStore;
window.SignalProtocolStore.prototype.Direction = Direction;
window.SignalProtocolStore.prototype.VerifiedStatus = VerifiedStatus;
window.SignalProtocolStore.prototype.convertVerifiedStatusToProtoState = convertVerifiedStatusToProtoState;
})();

@ -151,7 +151,7 @@
Whisper.Registration.remove();
// Do not remove all items since they are only set
// at startup.
textsecure.storage.remove('identityKey')
textsecure.storage.remove('identityKey');
textsecure.storage.remove('secondaryDeviceStatus');
window.ConversationController.reset();
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
(function() {
@ -98,7 +98,66 @@
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) {
const pairingAuthorisation = createPairingAuthorisationProtoMessage(
authorisation
@ -116,10 +175,14 @@
const dataMessage = new textsecure.protobuf.DataMessage({
profile,
});
// Attach contact list
const syncMessage = await createContactSyncProtoMessage();
const content = new textsecure.protobuf.Content({
pairingAuthorisation,
dataMessage,
syncMessage,
});
// Send
const options = { messageType: 'pairing-request' };
const p = new Promise((resolve, reject) => {
const outgoingMessage = new textsecure.OutgoingMessage(
@ -149,5 +212,6 @@
broadcastOnlineStatus,
sendPairingAuthorisation,
createPairingAuthorisationProtoMessage,
createContactSyncProtoMessage,
};
})();

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

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

Loading…
Cancel
Save