diff --git a/res/css/views/rooms/_E2EIcon.scss b/res/css/views/rooms/_E2EIcon.scss index cb99aa63f18..584ea174336 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/MatrixChat.js b/src/components/structures/MatrixChat.js index 8105805ab04..82a682f9ab2 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/structures/RoomView.js b/src/components/structures/RoomView.js index d78c9923c25..8c05acf60a6 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/dialogs/KeyShareDialog.js b/src/components/views/dialogs/KeyShareDialog.js index 01e3479bb1c..ba8918e79c5 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 15678b7d7af..bb08f8b2349 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 b2a1724fc66..4faa1b20aaa 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 af8b4616f86..90bb3f3dcbb 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) { @@ -114,7 +125,7 @@ function verifyDevice(userId, device) { Modal.createTrackedDialog('Device Verify Dialog', '', DeviceVerifyDialog, { userId: userId, device: device, - }); + }, null, /* priority = */ false, /* static = */ true); } function DeviceItem({userId, device}) { @@ -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 d6baa30c8ea..545d1fd7edf 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/components/views/toasts/VerificationRequestToast.js b/src/components/views/toasts/VerificationRequestToast.js index 89af91c41f2..7e043f4d833 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() { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 80604e90907..f801c3a5c59 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",