From 3ac0deebabd957629ee10a8282a20cd476a75a49 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 13 Dec 2019 13:55:26 +0000 Subject: [PATCH 1/2] Use static modals for verification Verification with cross-signing may requiring asking for your secret storage passphrase, which is displayed in it's own modal on top of flows. For now while verification takes in modals also, mark the verification ones as static so they don't lose state when secret storage appears on top mid-flow. --- src/components/structures/MatrixChat.js | 2 +- src/components/views/dialogs/KeyShareDialog.js | 2 +- src/components/views/elements/DeviceVerifyButtons.js | 2 +- src/components/views/messages/MKeyVerificationRequest.js | 2 +- src/components/views/right_panel/UserInfo.js | 2 +- src/components/views/toasts/VerificationRequestToast.js | 4 +++- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 8105805ab0..82a682f9ab 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1492,7 +1492,7 @@ export default createReactClass({ const IncomingSasDialog = sdk.getComponent("views.dialogs.IncomingSasDialog"); Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, { verifier, - }); + }, null, /* priority = */ false, /* static = */ true); }); } // Fire the tinter right on startup to ensure the default theme is applied diff --git a/src/components/views/dialogs/KeyShareDialog.js b/src/components/views/dialogs/KeyShareDialog.js index 01e3479bb1..ba8918e79c 100644 --- a/src/components/views/dialogs/KeyShareDialog.js +++ b/src/components/views/dialogs/KeyShareDialog.js @@ -99,7 +99,7 @@ export default createReactClass({ this.props.onFinished(true); } }, - }); + }, null, /* priority = */ false, /* static = */ true); }, _onShareClicked: function() { diff --git a/src/components/views/elements/DeviceVerifyButtons.js b/src/components/views/elements/DeviceVerifyButtons.js index 15678b7d7a..bb08f8b234 100644 --- a/src/components/views/elements/DeviceVerifyButtons.js +++ b/src/components/views/elements/DeviceVerifyButtons.js @@ -59,7 +59,7 @@ export default createReactClass({ Modal.createTrackedDialog('Device Verify Dialog', '', DeviceVerifyDialog, { userId: this.props.userId, device: this.state.device, - }); + }, null, /* priority = */ false, /* static = */ true); }, onUnverifyClick: function() { diff --git a/src/components/views/messages/MKeyVerificationRequest.js b/src/components/views/messages/MKeyVerificationRequest.js index b2a1724fc6..4faa1b20aa 100644 --- a/src/components/views/messages/MKeyVerificationRequest.js +++ b/src/components/views/messages/MKeyVerificationRequest.js @@ -52,7 +52,7 @@ export default class MKeyVerificationRequest extends React.Component { const verifier = MatrixClientPeg.get().acceptVerificationDM(this.props.mxEvent, verificationMethods.SAS); Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, { verifier, - }); + }, null, /* priority = */ false, /* static = */ true); }; _onRejectClicked = () => { diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index af8b4616f8..ae29021e08 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -114,7 +114,7 @@ function verifyDevice(userId, device) { Modal.createTrackedDialog('Device Verify Dialog', '', DeviceVerifyDialog, { userId: userId, device: device, - }); + }, null, /* priority = */ false, /* static = */ true); } function DeviceItem({userId, device}) { diff --git a/src/components/views/toasts/VerificationRequestToast.js b/src/components/views/toasts/VerificationRequestToast.js index 89af91c41f..7e043f4d83 100644 --- a/src/components/views/toasts/VerificationRequestToast.js +++ b/src/components/views/toasts/VerificationRequestToast.js @@ -90,7 +90,9 @@ export default class VerificationRequestToast extends React.PureComponent { const verifier = this.props.request.beginKeyVerification(verificationMethods.SAS); const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog'); - Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {verifier}); + Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, { + verifier, + }, null, /* priority = */ false, /* static = */ true); }; render() { From 2df72bfde2570e42d9960fa76dadd3a0d1450157 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 13 Dec 2019 17:57:26 +0000 Subject: [PATCH 2/2] Update room / user decoration for cross-signing --- res/css/views/rooms/_E2EIcon.scss | 16 ++++-- src/components/structures/RoomView.js | 41 ++++++++++++--- src/components/views/right_panel/UserInfo.js | 20 ++++++-- src/components/views/rooms/E2EIcon.js | 54 +++++++++++++++++--- src/i18n/strings/en_EN.json | 7 ++- 5 files changed, 114 insertions(+), 24 deletions(-) diff --git a/res/css/views/rooms/_E2EIcon.scss b/res/css/views/rooms/_E2EIcon.scss index cb99aa63f1..584ea17433 100644 --- a/res/css/views/rooms/_E2EIcon.scss +++ b/res/css/views/rooms/_E2EIcon.scss @@ -22,7 +22,9 @@ limitations under the License. display: block; } -.mx_E2EIcon_verified::after, .mx_E2EIcon_warning::after { +.mx_E2EIcon_warning::after, +.mx_E2EIcon_normal::after, +.mx_E2EIcon_verified::after { content: ""; display: block; position: absolute; @@ -34,10 +36,14 @@ limitations under the License. background-size: contain; } -.mx_E2EIcon_verified::after { - background-image: url('$(res)/img/e2e/verified.svg'); -} - .mx_E2EIcon_warning::after { background-image: url('$(res)/img/e2e/warning.svg'); } + +.mx_E2EIcon_normal::after { + background-image: url('$(res)/img/e2e/normal.svg'); +} + +.mx_E2EIcon_verified::after { + background-image: url('$(res)/img/e2e/verified.svg'); +} diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index d78c9923c2..8c05acf60a 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -792,11 +792,12 @@ module.exports = createReactClass({ this._updateE2EStatus(room); }, - _updateE2EStatus: function(room) { - if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) { + _updateE2EStatus: async function(room) { + const cli = MatrixClientPeg.get(); + if (!cli.isRoomEncrypted(room.roomId)) { return; } - if (!MatrixClientPeg.get().isCryptoEnabled()) { + if (!cli.isCryptoEnabled()) { // If crypto is not currently enabled, we aren't tracking devices at all, // so we don't know what the answer is. Let's error on the safe side and show // a warning for this case. @@ -805,10 +806,38 @@ module.exports = createReactClass({ }); return; } - room.hasUnverifiedDevices().then((hasUnverifiedDevices) => { - this.setState({ - e2eStatus: hasUnverifiedDevices ? "warning" : "verified", + if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) { + room.hasUnverifiedDevices().then((hasUnverifiedDevices) => { + this.setState({ + e2eStatus: hasUnverifiedDevices ? "warning" : "verified", + }); }); + return; + } + const e2eMembers = await room.getEncryptionTargetMembers(); + for (const member of e2eMembers) { + const { userId } = member; + const userVerified = cli.checkUserTrust(userId).isCrossSigningVerified(); + if (!userVerified) { + this.setState({ + e2eStatus: "warning", + }); + return; + } + const devices = await cli.getStoredDevicesForUser(userId); + const allDevicesVerified = devices.every(device => { + const { deviceId } = device; + return cli.checkDeviceTrust(userId, deviceId).isCrossSigningVerified(); + }); + if (!allDevicesVerified) { + this.setState({ + e2eStatus: "warning", + }); + return; + } + } + this.setState({ + e2eStatus: "verified", }); }, diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index ae29021e08..90bb3f3dcb 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -58,9 +58,20 @@ const _disambiguateDevices = (devices) => { } }; -const _getE2EStatus = (devices) => { - const hasUnverifiedDevice = devices.some((device) => device.isUnverified()); - return hasUnverifiedDevice ? "warning" : "verified"; +const _getE2EStatus = (cli, userId, devices) => { + if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) { + const hasUnverifiedDevice = devices.some((device) => device.isUnverified()); + return hasUnverifiedDevice ? "warning" : "verified"; + } + const userVerified = cli.checkUserTrust(userId).isCrossSigningVerified(); + const allDevicesVerified = devices.every(device => { + const { deviceId } = device; + return cli.checkDeviceTrust(userId, deviceId).isCrossSigningVerified(); + }); + if (allDevicesVerified) { + return userVerified ? "verified" : "normal"; + } + return "warning"; }; async function unverifyUser(matrixClient, userId) { @@ -1264,7 +1275,8 @@ const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, room let e2eIcon; if (isRoomEncrypted && devices) { - e2eIcon = ; + const e2eStatus = _getE2EStatus(cli, user.userId, devices); + e2eIcon = ; } return ( diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js index d6baa30c8e..545d1fd7ed 100644 --- a/src/components/views/rooms/E2EIcon.js +++ b/src/components/views/rooms/E2EIcon.js @@ -17,24 +17,62 @@ limitations under the License. import classNames from 'classnames'; import { _t } from '../../../languageHandler'; import AccessibleButton from '../elements/AccessibleButton'; +import SettingsStore from '../../../settings/SettingsStore'; export default function(props) { + const { isUser } = props; + const isNormal = props.status === "normal"; const isWarning = props.status === "warning"; const isVerified = props.status === "verified"; const e2eIconClasses = classNames({ mx_E2EIcon: true, mx_E2EIcon_warning: isWarning, + mx_E2EIcon_normal: isNormal, mx_E2EIcon_verified: isVerified, }, props.className); let e2eTitle; - if (isWarning) { - e2eTitle = props.isUser ? - _t("Some devices for this user are not trusted") : - _t("Some devices in this encrypted room are not trusted"); - } else if (isVerified) { - e2eTitle = props.isUser ? - _t("All devices for this user are trusted") : - _t("All devices in this encrypted room are trusted"); + + const crossSigning = SettingsStore.isFeatureEnabled("feature_cross_signing"); + if (crossSigning && isUser) { + if (isWarning) { + e2eTitle = _t( + "This user has not verified all of their devices.", + ); + } else if (isNormal) { + e2eTitle = _t( + "You have not verified this user. " + + "This user has verified all of their devices.", + ); + } else if (isVerified) { + e2eTitle = _t( + "You have verified this user. " + + "This user has verified all of their devices.", + ); + } + } else if (crossSigning && !isUser) { + if (isWarning) { + e2eTitle = _t( + "Some users in this encrypted room are not verified by you or " + + "they have not verified their own devices.", + ); + } else if (isVerified) { + e2eTitle = _t( + "All users in this encrypted room are verified by you and " + + "they have verified their own devices.", + ); + } + } else if (!crossSigning && isUser) { + if (isWarning) { + e2eTitle = _t("Some devices for this user are not trusted"); + } else if (isVerified) { + e2eTitle = _t("All devices for this user are trusted"); + } + } else if (!crossSigning && !isUser) { + if (isWarning) { + e2eTitle = _t("Some devices in this encrypted room are not trusted"); + } else if (isVerified) { + e2eTitle = _t("All devices in this encrypted room are trusted"); + } } let style = null; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 80604e9090..f801c3a5c5 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -857,9 +857,14 @@ " (unsupported)": " (unsupported)", "Join as voice or video.": "Join as voice or video.", "Ongoing conference call%(supportedText)s.": "Ongoing conference call%(supportedText)s.", + "This user has not verified all of their devices.": "This user has not verified all of their devices.", + "You have not verified this user. This user has verified all of their devices.": "You have not verified this user. This user has verified all of their devices.", + "You have verified this user. This user has verified all of their devices.": "You have verified this user. This user has verified all of their devices.", + "Some users in this encrypted room are not verified by you or they have not verified their own devices.": "Some users in this encrypted room are not verified by you or they have not verified their own devices.", + "All users in this encrypted room are verified by you and they have verified their own devices.": "All users in this encrypted room are verified by you and they have verified their own devices.", "Some devices for this user are not trusted": "Some devices for this user are not trusted", - "Some devices in this encrypted room are not trusted": "Some devices in this encrypted room are not trusted", "All devices for this user are trusted": "All devices for this user are trusted", + "Some devices in this encrypted room are not trusted": "Some devices in this encrypted room are not trusted", "All devices in this encrypted room are trusted": "All devices in this encrypted room are trusted", "Edit message": "Edit message", "This event could not be displayed": "This event could not be displayed",