You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-28 05:03:59 +03:00
[CONFLICT CHUNKS] Merge branch 'develop' into travis/sourcemaps-dev
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -33,6 +34,8 @@ import {
|
||||
UnknownDeviceError,
|
||||
} from "./base";
|
||||
|
||||
import {WITHHELD_MESSAGES} from '../OlmDevice';
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @constructor
|
||||
@@ -52,6 +55,7 @@ function OutboundSessionInfo(sessionId) {
|
||||
this.useCount = 0;
|
||||
this.creationTime = new Date().getTime();
|
||||
this.sharedWithDevices = {};
|
||||
this.blockedDevicesNotified = {};
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +93,15 @@ OutboundSessionInfo.prototype.markSharedWithDevice = function(
|
||||
this.sharedWithDevices[userId][deviceId] = chainIndex;
|
||||
};
|
||||
|
||||
OutboundSessionInfo.prototype.markNotifiedBlockedDevice = function(
|
||||
userId, deviceId,
|
||||
) {
|
||||
if (!this.blockedDevicesNotified[userId]) {
|
||||
this.blockedDevicesNotified[userId] = {};
|
||||
}
|
||||
this.blockedDevicesNotified[userId][deviceId] = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if this session has been shared with devices which it shouldn't
|
||||
* have been.
|
||||
@@ -171,11 +184,14 @@ utils.inherits(MegolmEncryption, EncryptionAlgorithm);
|
||||
* @private
|
||||
*
|
||||
* @param {Object} devicesInRoom The devices in this room, indexed by user ID
|
||||
* @param {Object} blocked The devices that are blocked, indexed by user ID
|
||||
*
|
||||
* @return {module:client.Promise} Promise which resolves to the
|
||||
* OutboundSessionInfo when setup is complete.
|
||||
*/
|
||||
MegolmEncryption.prototype._ensureOutboundSession = function(devicesInRoom) {
|
||||
MegolmEncryption.prototype._ensureOutboundSession = async function(
|
||||
devicesInRoom, blocked,
|
||||
) {
|
||||
const self = this;
|
||||
|
||||
let session;
|
||||
@@ -242,9 +258,36 @@ MegolmEncryption.prototype._ensureOutboundSession = function(devicesInRoom) {
|
||||
}
|
||||
}
|
||||
|
||||
return self._shareKeyWithDevices(
|
||||
await self._shareKeyWithDevices(
|
||||
session, shareMap,
|
||||
);
|
||||
|
||||
// are there any new blocked devices that we need to notify?
|
||||
const blockedMap = {};
|
||||
for (const userId in blocked) {
|
||||
if (!blocked.hasOwnProperty(userId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const userBlockedDevices = blocked[userId];
|
||||
|
||||
for (const deviceId in userBlockedDevices) {
|
||||
if (!userBlockedDevices.hasOwnProperty(deviceId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
!session.blockedDevicesNotified[userId] ||
|
||||
session.blockedDevicesNotified[userId][deviceId] === undefined
|
||||
) {
|
||||
blockedMap[userId] = blockedMap[userId] || [];
|
||||
blockedMap[userId].push(userBlockedDevices[deviceId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// notify blocked devices that they're blocked
|
||||
await self._notifyBlockedDevices(session, blockedMap);
|
||||
}
|
||||
|
||||
// helper which returns the session prepared by prepareSession
|
||||
@@ -368,6 +411,42 @@ MegolmEncryption.prototype._splitUserDeviceMap = function(
|
||||
return mapSlices;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @param {object} devicesByUser map from userid to list of devices
|
||||
*
|
||||
* @return {array<array<object>>} the blocked devices, split into chunks
|
||||
*/
|
||||
MegolmEncryption.prototype._splitBlockedDevices = function(devicesByUser) {
|
||||
const maxToDeviceMessagesPerRequest = 20;
|
||||
|
||||
// use an array where the slices of a content map gets stored
|
||||
let currentSlice = [];
|
||||
const mapSlices = [currentSlice];
|
||||
|
||||
for (const userId of Object.keys(devicesByUser)) {
|
||||
const userBlockedDevicesToShareWith = devicesByUser[userId];
|
||||
|
||||
for (const blockedInfo of userBlockedDevicesToShareWith) {
|
||||
if (currentSlice.length > maxToDeviceMessagesPerRequest) {
|
||||
// the current slice is filled up. Start inserting into the next slice
|
||||
currentSlice = [];
|
||||
mapSlices.push(currentSlice);
|
||||
}
|
||||
|
||||
currentSlice.push({
|
||||
userId: userId,
|
||||
blockedInfo: blockedInfo,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (currentSlice.length === 0) {
|
||||
mapSlices.pop();
|
||||
}
|
||||
return mapSlices;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
@@ -432,6 +511,49 @@ MegolmEncryption.prototype._encryptAndSendKeysToDevices = function(
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @param {module:crypto/algorithms/megolm.OutboundSessionInfo} session
|
||||
*
|
||||
* @param {array<object>} userDeviceMap list of blocked devices to notify
|
||||
*
|
||||
* @param {object} payload fields to include in the notification payload
|
||||
*
|
||||
* @return {module:client.Promise} Promise which resolves once the notifications
|
||||
* for the given userDeviceMap is generated and has been sent.
|
||||
*/
|
||||
MegolmEncryption.prototype._sendBlockedNotificationsToDevices = async function(
|
||||
session, userDeviceMap, payload,
|
||||
) {
|
||||
const contentMap = {};
|
||||
|
||||
for (const val of userDeviceMap) {
|
||||
const userId = val.userId;
|
||||
const blockedInfo = val.blockedInfo;
|
||||
const deviceInfo = blockedInfo.deviceInfo;
|
||||
const deviceId = deviceInfo.deviceId;
|
||||
|
||||
const message = Object.assign({}, payload);
|
||||
message.code = blockedInfo.code;
|
||||
message.reason = blockedInfo.reason;
|
||||
|
||||
if (!contentMap[userId]) {
|
||||
contentMap[userId] = {};
|
||||
}
|
||||
contentMap[userId][deviceId] = message;
|
||||
}
|
||||
|
||||
await this._baseApis.sendToDevice("org.matrix.room_key.withheld", contentMap);
|
||||
|
||||
// store that we successfully uploaded the keys of the current slice
|
||||
for (const userId of Object.keys(contentMap)) {
|
||||
for (const deviceId of Object.keys(contentMap[userId])) {
|
||||
session.markNotifiedBlockedDevice(userId, deviceId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Re-shares a megolm session key with devices if the key has already been
|
||||
* sent to them.
|
||||
@@ -566,6 +688,42 @@ MegolmEncryption.prototype._shareKeyWithDevices = async function(session, device
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify blocked devices that they have been blocked.
|
||||
*
|
||||
* @param {module:crypto/algorithms/megolm.OutboundSessionInfo} session
|
||||
*
|
||||
* @param {object<string, object>} devicesByUser
|
||||
* map from userid to device ID to blocked data
|
||||
*/
|
||||
MegolmEncryption.prototype._notifyBlockedDevices = async function(
|
||||
session, devicesByUser,
|
||||
) {
|
||||
const payload = {
|
||||
room_id: this._roomId,
|
||||
session_id: session.sessionId,
|
||||
algorithm: olmlib.MEGOLM_ALGORITHM,
|
||||
sender_key: this._olmDevice.deviceCurve25519Key,
|
||||
};
|
||||
|
||||
const userDeviceMaps = this._splitBlockedDevices(devicesByUser);
|
||||
|
||||
for (let i = 0; i < userDeviceMaps.length; i++) {
|
||||
try {
|
||||
await this._sendBlockedNotificationsToDevices(
|
||||
session, userDeviceMaps[i], payload,
|
||||
);
|
||||
logger.log(`Completed blacklist notification for ${session.sessionId} `
|
||||
+ `in ${this._roomId} (slice ${i + 1}/${userDeviceMaps.length})`);
|
||||
} catch (e) {
|
||||
logger.log(`blacklist notification for ${session.sessionId} in `
|
||||
+ `${this._roomId} (slice ${i + 1}/${userDeviceMaps.length}) failed`);
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
@@ -575,42 +733,41 @@ MegolmEncryption.prototype._shareKeyWithDevices = async function(session, device
|
||||
*
|
||||
* @return {module:client.Promise} Promise which resolves to the new event body
|
||||
*/
|
||||
MegolmEncryption.prototype.encryptMessage = function(room, eventType, content) {
|
||||
MegolmEncryption.prototype.encryptMessage = async function(room, eventType, content) {
|
||||
const self = this;
|
||||
logger.log(`Starting to encrypt event for ${this._roomId}`);
|
||||
|
||||
return this._getDevicesInRoom(room).then(function(devicesInRoom) {
|
||||
// check if any of these devices are not yet known to the user.
|
||||
// if so, warn the user so they can verify or ignore.
|
||||
self._checkForUnknownDevices(devicesInRoom);
|
||||
const [devicesInRoom, blocked] = await this._getDevicesInRoom(room);
|
||||
|
||||
return self._ensureOutboundSession(devicesInRoom);
|
||||
}).then(function(session) {
|
||||
const payloadJson = {
|
||||
room_id: self._roomId,
|
||||
type: eventType,
|
||||
content: content,
|
||||
};
|
||||
// check if any of these devices are not yet known to the user.
|
||||
// if so, warn the user so they can verify or ignore.
|
||||
self._checkForUnknownDevices(devicesInRoom);
|
||||
|
||||
const ciphertext = self._olmDevice.encryptGroupMessage(
|
||||
session.sessionId, JSON.stringify(payloadJson),
|
||||
);
|
||||
const session = await self._ensureOutboundSession(devicesInRoom, blocked);
|
||||
const payloadJson = {
|
||||
room_id: self._roomId,
|
||||
type: eventType,
|
||||
content: content,
|
||||
};
|
||||
|
||||
const encryptedContent = {
|
||||
algorithm: olmlib.MEGOLM_ALGORITHM,
|
||||
sender_key: self._olmDevice.deviceCurve25519Key,
|
||||
ciphertext: ciphertext,
|
||||
session_id: session.sessionId,
|
||||
// Include our device ID so that recipients can send us a
|
||||
// m.new_device message if they don't have our session key.
|
||||
// XXX: Do we still need this now that m.new_device messages
|
||||
// no longer exist since #483?
|
||||
device_id: self._deviceId,
|
||||
};
|
||||
const ciphertext = self._olmDevice.encryptGroupMessage(
|
||||
session.sessionId, JSON.stringify(payloadJson),
|
||||
);
|
||||
|
||||
session.useCount++;
|
||||
return encryptedContent;
|
||||
});
|
||||
const encryptedContent = {
|
||||
algorithm: olmlib.MEGOLM_ALGORITHM,
|
||||
sender_key: self._olmDevice.deviceCurve25519Key,
|
||||
ciphertext: ciphertext,
|
||||
session_id: session.sessionId,
|
||||
// Include our device ID so that recipients can send us a
|
||||
// m.new_device message if they don't have our session key.
|
||||
// XXX: Do we still need this now that m.new_device messages
|
||||
// no longer exist since #483?
|
||||
device_id: self._deviceId,
|
||||
};
|
||||
|
||||
session.useCount++;
|
||||
return encryptedContent;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -659,8 +816,11 @@ MegolmEncryption.prototype._checkForUnknownDevices = function(devicesInRoom) {
|
||||
*
|
||||
* @param {module:models/room} room
|
||||
*
|
||||
* @return {module:client.Promise} Promise which resolves to a map
|
||||
* from userId to deviceId to deviceInfo
|
||||
* @return {module:client.Promise} Promise which resolves to an array whose
|
||||
* first element is a map from userId to deviceId to deviceInfo indicating
|
||||
* the devices that messages should be encrypted to, and whose second
|
||||
* element is a map from userId to deviceId to data indicating the devices
|
||||
* that are in the room but that have been blocked
|
||||
*/
|
||||
MegolmEncryption.prototype._getDevicesInRoom = async function(room) {
|
||||
const members = await room.getEncryptionTargetMembers();
|
||||
@@ -681,6 +841,7 @@ MegolmEncryption.prototype._getDevicesInRoom = async function(room) {
|
||||
// using all the device_lists changes and left fields.
|
||||
// See https://github.com/vector-im/riot-web/issues/2305 for details.
|
||||
const devices = await this._crypto.downloadKeys(roomMembers, false);
|
||||
const blocked = {};
|
||||
// remove any blocked devices
|
||||
for (const userId in devices) {
|
||||
if (!devices.hasOwnProperty(userId)) {
|
||||
@@ -695,13 +856,27 @@ MegolmEncryption.prototype._getDevicesInRoom = async function(room) {
|
||||
|
||||
if (userDevices[deviceId].isBlocked() ||
|
||||
(userDevices[deviceId].isUnverified() && isBlacklisting)
|
||||
) {
|
||||
) {
|
||||
if (!blocked[userId]) {
|
||||
blocked[userId] = {};
|
||||
}
|
||||
const blockedInfo = userDevices[deviceId].isBlocked()
|
||||
? {
|
||||
code: "m.blacklisted",
|
||||
reason: WITHHELD_MESSAGES["m.blacklisted"],
|
||||
}
|
||||
: {
|
||||
code: "m.unverified",
|
||||
reason: WITHHELD_MESSAGES["m.unverified"],
|
||||
};
|
||||
blockedInfo.deviceInfo = userDevices[deviceId];
|
||||
blocked[userId][deviceId] = blockedInfo;
|
||||
delete userDevices[deviceId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return devices;
|
||||
return [devices, blocked];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -761,6 +936,11 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
|
||||
event.getId(), event.getTs(),
|
||||
);
|
||||
} catch (e) {
|
||||
if (e.name === "DecryptionError") {
|
||||
// re-throw decryption errors as-is
|
||||
throw e;
|
||||
}
|
||||
|
||||
let errorCode = "OLM_DECRYPT_GROUP_MESSAGE_ERROR";
|
||||
|
||||
if (e && e.message === 'OLM.UNKNOWN_MESSAGE_INDEX') {
|
||||
@@ -968,6 +1148,20 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @param {module:models/event.MatrixEvent} event key event
|
||||
*/
|
||||
MegolmDecryption.prototype.onRoomKeyWithheldEvent = async function(event) {
|
||||
const content = event.getContent();
|
||||
|
||||
await this._olmDevice.addInboundGroupSessionWithheld(
|
||||
content.room_id, content.sender_key, content.session_id, content.code,
|
||||
content.reason,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user