From 2b432b0d82346915de6d5928399ff0b13c701337 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 27 May 2020 10:28:25 +0100 Subject: [PATCH] Remove feature_cross_signing Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/DeviceListener.ts | 6 +- src/KeyRequestHandler.js | 158 --- .../keybackup/CreateKeyBackupDialog.js | 6 +- src/components/structures/MatrixChat.tsx | 35 +- src/components/structures/RightPanel.js | 107 +- src/components/structures/RoomView.js | 9 - .../views/dialogs/CreateRoomDialog.js | 5 +- .../views/dialogs/DeviceVerifyDialog.js | 4 +- src/components/views/dialogs/InviteDialog.js | 18 +- .../keybackup/RestoreKeyBackupDialog.js | 16 +- .../views/groups/GroupMemberInfo.js | 208 --- src/components/views/right_panel/UserInfo.js | 35 +- src/components/views/rooms/E2EIcon.js | 19 +- src/components/views/rooms/EventTile.js | 9 - src/components/views/rooms/MemberInfo.js | 1165 ----------------- src/components/views/rooms/MemberTile.js | 28 +- src/components/views/rooms/MessageComposer.js | 30 +- src/components/views/rooms/RoomHeader.js | 6 +- src/components/views/rooms/RoomTile.js | 9 +- .../views/settings/CrossSigningPanel.js | 2 + .../views/settings/KeyBackupPanel.js | 9 +- .../tabs/user/SecurityUserSettingsTab.js | 5 +- src/createRoom.js | 6 +- src/settings/Settings.js | 7 - src/verification.js | 3 +- 25 files changed, 106 insertions(+), 1799 deletions(-) delete mode 100644 src/KeyRequestHandler.js delete mode 100644 src/components/views/groups/GroupMemberInfo.js delete mode 100644 src/components/views/rooms/MemberInfo.js diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index ca51b5ac1c..a5175b3220 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -15,7 +15,6 @@ limitations under the License. */ import {MatrixClientPeg} from './MatrixClientPeg'; -import SettingsStore from './settings/SettingsStore'; import { hideToast as hideBulkUnverifiedSessionsToast, showToast as showBulkUnverifiedSessionsToast @@ -173,10 +172,7 @@ export default class DeviceListener { async _recheck() { const cli = MatrixClientPeg.get(); - if ( - !SettingsStore.getValue("feature_cross_signing") || - !await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing") - ) return; + if (!await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) return; if (!cli.isCryptoEnabled()) return; // don't recheck until the initial sync is complete: lots of account data events will fire diff --git a/src/KeyRequestHandler.js b/src/KeyRequestHandler.js deleted file mode 100644 index ceaff0c54d..0000000000 --- a/src/KeyRequestHandler.js +++ /dev/null @@ -1,158 +0,0 @@ -/* -Copyright 2017 Vector Creations 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. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import * as sdk from './index'; -import Modal from './Modal'; -import SettingsStore from './settings/SettingsStore'; - -// TODO: We can remove this once cross-signing is the only way. -// https://github.com/vector-im/riot-web/issues/11908 -export default class KeyRequestHandler { - constructor(matrixClient) { - this._matrixClient = matrixClient; - - // the user/device for which we currently have a dialog open - this._currentUser = null; - this._currentDevice = null; - - // userId -> deviceId -> [keyRequest] - this._pendingKeyRequests = Object.create(null); - } - - handleKeyRequest(keyRequest) { - // Ignore own device key requests if cross-signing lab enabled - if (SettingsStore.getValue("feature_cross_signing")) { - return; - } - - const userId = keyRequest.userId; - const deviceId = keyRequest.deviceId; - const requestId = keyRequest.requestId; - - if (!this._pendingKeyRequests[userId]) { - this._pendingKeyRequests[userId] = Object.create(null); - } - if (!this._pendingKeyRequests[userId][deviceId]) { - this._pendingKeyRequests[userId][deviceId] = []; - } - - // check if we already have this request - const requests = this._pendingKeyRequests[userId][deviceId]; - if (requests.find((r) => r.requestId === requestId)) { - console.log("Already have this key request, ignoring"); - return; - } - - requests.push(keyRequest); - - if (this._currentUser) { - // ignore for now - console.log("Key request, but we already have a dialog open"); - return; - } - - this._processNextRequest(); - } - - handleKeyRequestCancellation(cancellation) { - // Ignore own device key requests if cross-signing lab enabled - if (SettingsStore.getValue("feature_cross_signing")) { - return; - } - - // see if we can find the request in the queue - const userId = cancellation.userId; - const deviceId = cancellation.deviceId; - const requestId = cancellation.requestId; - - if (userId === this._currentUser && deviceId === this._currentDevice) { - console.log( - "room key request cancellation for the user we currently have a" - + " dialog open for", - ); - // TODO: update the dialog. For now, we just ignore the - // cancellation. - return; - } - - if (!this._pendingKeyRequests[userId]) { - return; - } - const requests = this._pendingKeyRequests[userId][deviceId]; - if (!requests) { - return; - } - const idx = requests.findIndex((r) => r.requestId === requestId); - if (idx < 0) { - return; - } - console.log("Forgetting room key request"); - requests.splice(idx, 1); - if (requests.length === 0) { - delete this._pendingKeyRequests[userId][deviceId]; - if (Object.keys(this._pendingKeyRequests[userId]).length === 0) { - delete this._pendingKeyRequests[userId]; - } - } - } - - _processNextRequest() { - const userId = Object.keys(this._pendingKeyRequests)[0]; - if (!userId) { - return; - } - const deviceId = Object.keys(this._pendingKeyRequests[userId])[0]; - if (!deviceId) { - return; - } - console.log(`Starting KeyShareDialog for ${userId}:${deviceId}`); - - const finished = (r) => { - this._currentUser = null; - this._currentDevice = null; - - if (!this._pendingKeyRequests[userId] || !this._pendingKeyRequests[userId][deviceId]) { - // request was removed in the time the dialog was displayed - this._processNextRequest(); - return; - } - - if (r) { - for (const req of this._pendingKeyRequests[userId][deviceId]) { - req.share(); - } - } - delete this._pendingKeyRequests[userId][deviceId]; - if (Object.keys(this._pendingKeyRequests[userId]).length === 0) { - delete this._pendingKeyRequests[userId]; - } - - this._processNextRequest(); - }; - - const KeyShareDialog = sdk.getComponent("dialogs.KeyShareDialog"); - Modal.appendTrackedDialog('Key Share', 'Process Next Request', KeyShareDialog, { - matrixClient: this._matrixClient, - userId: userId, - deviceId: deviceId, - onFinished: finished, - }); - this._currentUser = userId; - this._currentDevice = deviceId; - } -} - diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js index 7c5170fab6..79fbb98c7b 100644 --- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -22,7 +22,6 @@ import {MatrixClientPeg} from '../../../../MatrixClientPeg'; import PropTypes from 'prop-types'; import {_t, _td} from '../../../../languageHandler'; import { accessSecretStorage } from '../../../../CrossSigningManager'; -import SettingsStore from '../../../../settings/SettingsStore'; import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; import {copyNode} from "../../../../utils/strings"; import PassphraseField from "../../../../components/views/auth/PassphraseField"; @@ -67,10 +66,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { async componentDidMount() { const cli = MatrixClientPeg.get(); - const secureSecretStorage = ( - SettingsStore.getValue("feature_cross_signing") && - await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing") - ); + const secureSecretStorage = await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing"); this.setState({ secureSecretStorage }); // If we're using secret storage, skip ahead to the backing up step, as diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index f1992bbfcf..2bebdd1e79 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -49,7 +49,6 @@ import PageTypes from '../../PageTypes'; import { getHomePageUrl } from '../../utils/pages'; import createRoom from "../../createRoom"; -import KeyRequestHandler from '../../KeyRequestHandler'; import { _t, getCurrentLanguage } from '../../languageHandler'; import SettingsStore, { SettingLevel } from "../../settings/SettingsStore"; import ThemeController from "../../settings/controllers/ThemeController"; @@ -1471,16 +1470,6 @@ export default class MatrixChat extends React.PureComponent { cli.on("Session.logged_out", () => dft.stop()); cli.on("Event.decrypted", (e, err) => dft.eventDecrypted(e, err)); - // TODO: We can remove this once cross-signing is the only way. - // https://github.com/vector-im/riot-web/issues/11908 - const krh = new KeyRequestHandler(cli); - cli.on("crypto.roomKeyRequest", (req) => { - krh.handleKeyRequest(req); - }); - cli.on("crypto.roomKeyRequestCancellation", (req) => { - krh.handleKeyRequestCancellation(req); - }); - cli.on("Room", (room) => { if (MatrixClientPeg.get().isCryptoEnabled()) { const blacklistEnabled = SettingsStore.getValueAt( @@ -1551,13 +1540,6 @@ export default class MatrixChat extends React.PureComponent { }); cli.on("crypto.verification.request", request => { - const isFlagOn = SettingsStore.getValue("feature_cross_signing"); - - if (!isFlagOn && !request.channel.deviceId) { - request.cancel({code: "m.invalid_message", reason: "This client has cross-signing disabled"}); - return; - } - if (request.verifier) { const IncomingSasDialog = sdk.getComponent("views.dialogs.IncomingSasDialog"); Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, { @@ -1600,9 +1582,7 @@ export default class MatrixChat extends React.PureComponent { // be aware of will be signalled through the room shield // changing colour. More advanced behaviour will come once // we implement more settings. - cli.setGlobalErrorOnUnknownDevices( - !SettingsStore.getValue("feature_cross_signing"), - ); + cli.setGlobalErrorOnUnknownDevices(false); } } @@ -1956,18 +1936,7 @@ export default class MatrixChat extends React.PureComponent { return setLoggedInPromise; } - // Test for the master cross-signing key in SSSS as a quick proxy for - // whether cross-signing has been set up on the account. - const masterKeyInStorage = !!cli.getAccountData("m.cross_signing.master"); - if (masterKeyInStorage) { - // Auto-enable cross-signing for the new session when key found in - // secret storage. - SettingsStore.setValue("feature_cross_signing", null, SettingLevel.DEVICE, true); - this.setStateForNewView({ view: Views.COMPLETE_SECURITY }); - } else if ( - SettingsStore.getValue("feature_cross_signing") && - await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing") - ) { + if (await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) { // This will only work if the feature is set to 'enable' in the config, // since it's too early in the lifecycle for users to have turned the // labs flag on. diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index 56cc92a8f8..811feb8614 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -189,16 +189,45 @@ export default class RightPanel extends React.Component { } } + onCloseRoomMemberInfo = () => { + // XXX: There are three different ways of 'closing' this panel depending on what state + // things are in... this knows far more than it should do about the state of the rest + // of the app and is generally a bit silly. + if (this.props.user) { + // If we have a user prop then we're displaying a user from the 'user' page type + // in LoggedInView, so need to change the page type to close the panel (we switch + // to the home page which is not obviously the correct thing to do, but I'm not sure + // anything else is - we could hide the close button altogether?) + dis.dispatch({ + action: "view_home_page", + }); + } else { + // Otherwise we have got our user from RoomViewStore which means we're being shown + // within a room, so go back to the member panel if we were in the encryption panel, + // or the member list if we were in the member panel... phew. + dis.dispatch({ + action: Action.ViewUser, + member: this.state.phase === RIGHT_PANEL_PHASES.EncryptionPanel ? + this.state.member : null, + }); + } + }; + + onCloseGroupMemberInfo = () => { + dis.dispatch({ + action: Action.ViewUser, + member: null, + }); + }; + render() { const MemberList = sdk.getComponent('rooms.MemberList'); - const MemberInfo = sdk.getComponent('rooms.MemberInfo'); const UserInfo = sdk.getComponent('right_panel.UserInfo'); const ThirdPartyMemberInfo = sdk.getComponent('rooms.ThirdPartyMemberInfo'); const NotificationPanel = sdk.getComponent('structures.NotificationPanel'); const FilePanel = sdk.getComponent('structures.FilePanel'); const GroupMemberList = sdk.getComponent('groups.GroupMemberList'); - const GroupMemberInfo = sdk.getComponent('groups.GroupMemberInfo'); const GroupRoomList = sdk.getComponent('groups.GroupRoomList'); const GroupRoomInfo = sdk.getComponent('groups.GroupRoomInfo'); @@ -220,71 +249,25 @@ export default class RightPanel extends React.Component { break; case RIGHT_PANEL_PHASES.RoomMemberInfo: case RIGHT_PANEL_PHASES.EncryptionPanel: - if (SettingsStore.getValue("feature_cross_signing")) { - const onClose = () => { - // XXX: There are three different ways of 'closing' this panel depending on what state - // things are in... this knows far more than it should do about the state of the rest - // of the app and is generally a bit silly. - if (this.props.user) { - // If we have a user prop then we're displaying a user from the 'user' page type - // in LoggedInView, so need to change the page type to close the panel (we switch - // to the home page which is not obviously the correct thing to do, but I'm not sure - // anything else is - we could hide the close button altogether?) - dis.dispatch({ - action: "view_home_page", - }); - } else { - // Otherwise we have got our user from RoomViewStore which means we're being shown - // within a room, so go back to the member panel if we were in the encryption panel, - // or the member list if we were in the member panel... phew. - dis.dispatch({ - action: Action.ViewUser, - member: this.state.phase === RIGHT_PANEL_PHASES.EncryptionPanel ? - this.state.member : null, - }); - } - }; - panel = ; - } else { - panel = ; - } + panel = ; break; case RIGHT_PANEL_PHASES.Room3pidMemberInfo: panel = ; break; case RIGHT_PANEL_PHASES.GroupMemberInfo: - if (SettingsStore.getValue("feature_cross_signing")) { - const onClose = () => { - dis.dispatch({ - action: Action.ViewUser, - member: null, - }); - }; - panel = ; - } else { - panel = ( - - ); - } + panel = ; break; case RIGHT_PANEL_PHASES.GroupRoomInfo: panel = { - this.setState({ - e2eStatus: hasUnverifiedDevices ? "warning" : "verified", - }); - }); - debuglog("e2e check is warning/verified only as cross-signing is off"); - return; - } /* At this point, the user has encryption on and cross-signing on */ this.setState({ diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index cc308a834b..87fbf3de02 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -24,7 +24,6 @@ import withValidation from '../elements/Validation'; import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {Key} from "../../../Keyboard"; -import SettingsStore from "../../../settings/SettingsStore"; export default createReactClass({ displayName: 'CreateRoomDialog', @@ -66,7 +65,7 @@ export default createReactClass({ createOpts.creation_content = {'m.federate': false}; } - if (!this.state.isPublic && SettingsStore.getValue("feature_cross_signing")) { + if (!this.state.isPublic) { opts.encryption = this.state.isEncrypted; } @@ -193,7 +192,7 @@ export default createReactClass({ } let e2eeSection; - if (!this.state.isPublic && SettingsStore.getValue("feature_cross_signing")) { + if (!this.state.isPublic) { e2eeSection = t instanceof ThreepidMember); - if (!has3PidMembers) { - const client = MatrixClientPeg.get(); - const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds); - if (allHaveDeviceKeys) { - createRoomOptions.encryption = true; - } + // Check whether all users have uploaded device keys before. + // If so, enable encryption in the new room. + const has3PidMembers = targets.some(t => t instanceof ThreepidMember); + if (!has3PidMembers) { + const client = MatrixClientPeg.get(); + const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds); + if (allHaveDeviceKeys) { + createRoomOptions.encryption = true; } } diff --git a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js index a16202ed93..4944c4b5ee 100644 --- a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js +++ b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js @@ -90,21 +90,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { _onResetRecoveryClick = () => { this.props.onFinished(false); - - if (SettingsStore.getValue("feature_cross_signing")) { - // If cross-signing is enabled, we reset the SSSS recovery passphrase (and cross-signing keys) - this.props.onFinished(false); - accessSecretStorage(() => {}, /* forceReset = */ true); - } else { - Modal.createTrackedDialogAsync('Key Backup', 'Key Backup', - import('../../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'), - { - onFinished: () => { - this._loadBackupStatus(); - }, - }, null, /* priority = */ false, /* static = */ true, - ); - } + accessSecretStorage(() => {}, /* forceReset = */ true); } _onRecoveryKeyChange = (e) => { diff --git a/src/components/views/groups/GroupMemberInfo.js b/src/components/views/groups/GroupMemberInfo.js deleted file mode 100644 index 2582cab573..0000000000 --- a/src/components/views/groups/GroupMemberInfo.js +++ /dev/null @@ -1,208 +0,0 @@ -/* -Copyright 2017 Vector Creations Ltd -Copyright 2017 New Vector Ltd -Copyright 2019 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. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; -import dis from '../../../dispatcher/dispatcher'; -import Modal from '../../../Modal'; -import * as sdk from '../../../index'; -import { _t } from '../../../languageHandler'; -import { GroupMemberType } from '../../../groups'; -import GroupStore from '../../../stores/GroupStore'; -import AccessibleButton from '../elements/AccessibleButton'; -import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; -import {Action} from "../../../dispatcher/actions"; - -export default createReactClass({ - displayName: 'GroupMemberInfo', - - statics: { - contextType: MatrixClientContext, - }, - - propTypes: { - groupId: PropTypes.string, - groupMember: GroupMemberType, - isInvited: PropTypes.bool, - }, - - getInitialState: function() { - return { - removingUser: false, - isUserPrivilegedInGroup: null, - }; - }, - - componentDidMount: function() { - this._unmounted = false; - this._initGroupStore(this.props.groupId); - }, - - // TODO: [REACT-WARNING] Replace with appropriate lifecycle event - UNSAFE_componentWillReceiveProps(newProps) { - if (newProps.groupId !== this.props.groupId) { - this._unregisterGroupStore(this.props.groupId); - this._initGroupStore(newProps.groupId); - } - }, - - componentWillUnmount() { - this._unmounted = true; - this._unregisterGroupStore(this.props.groupId); - }, - - _initGroupStore(groupId) { - GroupStore.registerListener(groupId, this.onGroupStoreUpdated); - }, - - _unregisterGroupStore(groupId) { - GroupStore.unregisterListener(this.onGroupStoreUpdated); - }, - - onGroupStoreUpdated: function() { - if (this._unmounted) return; - this.setState({ - isUserInvited: GroupStore.getGroupInvitedMembers(this.props.groupId).some( - (m) => m.userId === this.props.groupMember.userId, - ), - isUserPrivilegedInGroup: GroupStore.isUserPrivileged(this.props.groupId), - }); - }, - - _onKick: function() { - const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); - Modal.createDialog(ConfirmUserActionDialog, { - matrixClient: this.context, - groupMember: this.props.groupMember, - action: this.state.isUserInvited ? _t('Disinvite') : _t('Remove from community'), - title: this.state.isUserInvited ? _t('Disinvite this user from community?') - : _t('Remove this user from community?'), - danger: true, - onFinished: (proceed) => { - if (!proceed) return; - - this.setState({removingUser: true}); - this.context.removeUserFromGroup( - this.props.groupId, this.props.groupMember.userId, - ).then(() => { - // return to the user list - dis.dispatch({ - action: Action.ViewUser, - member: null, - }); - }).catch((e) => { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to remove user from group', '', ErrorDialog, { - title: _t('Error'), - description: this.state.isUserInvited ? - _t('Failed to withdraw invitation') : - _t('Failed to remove user from community'), - }); - }).finally(() => { - this.setState({removingUser: false}); - }); - }, - }); - }, - - _onCancel: function(e) { - // Go back to the user list - dis.dispatch({ - action: Action.ViewUser, - member: null, - }); - }, - - onRoomTileClick(roomId) { - dis.dispatch({ - action: 'view_room', - room_id: roomId, - }); - }, - - render: function() { - if (this.state.removingUser) { - const Spinner = sdk.getComponent("elements.Spinner"); - return
- -
; - } - - let adminTools; - if (this.state.isUserPrivilegedInGroup) { - const kickButton = ( - - { this.state.isUserInvited ? _t('Disinvite') : _t('Remove from community') } - - ); - - // No make/revoke admin API yet - /*const opLabel = this.state.isTargetMod ? _t("Revoke Moderator") : _t("Make Moderator"); - giveModButton = - {giveOpLabel} - ;*/ - - if (kickButton) { - adminTools = -
-

{ _t("Admin Tools") }

-
- { kickButton } -
-
; - } - } - - - const avatarUrl = this.props.groupMember.avatarUrl; - let avatarElement; - if (avatarUrl) { - const httpUrl = this.context.mxcUrlToHttp(avatarUrl, 800, 800); - avatarElement = (
- -
); - } - - const groupMemberName = ( - this.props.groupMember.displayname || this.props.groupMember.userId - ); - - return ( -
- - - - - { avatarElement } -

{ groupMemberName }

- -
-
- { this.props.groupMember.userId } -
-
- - { adminTools } -
-
- ); - }, -}); diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 0392746c94..836e35ba22 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -64,10 +64,6 @@ const _disambiguateDevices = (devices) => { }; export const getE2EStatus = (cli, userId, devices) => { - if (!SettingsStore.getValue("feature_cross_signing")) { - const hasUnverifiedDevice = devices.some((device) => device.isUnverified()); - return hasUnverifiedDevice ? "warning" : "verified"; - } const isMe = userId === cli.getUserId(); const userTrust = cli.checkUserTrust(userId); if (!userTrust.isCrossSigningVerified()) { @@ -112,17 +108,15 @@ async function openDMForUser(matrixClient, userId) { dmUserId: userId, }; - if (SettingsStore.getValue("feature_cross_signing")) { - // Check whether all users have uploaded device keys before. - // If so, enable encryption in the new room. - const usersToDevicesMap = await matrixClient.downloadKeys([userId]); - const allHaveDeviceKeys = Object.values(usersToDevicesMap).every(devices => { - // `devices` is an object of the form { deviceId: deviceInfo, ... }. - return Object.keys(devices).length > 0; - }); - if (allHaveDeviceKeys) { - createRoomOptions.encryption = true; - } + // Check whether all users have uploaded device keys before. + // If so, enable encryption in the new room. + const usersToDevicesMap = await matrixClient.downloadKeys([userId]); + const allHaveDeviceKeys = Object.values(usersToDevicesMap).every(devices => { + // `devices` is an object of the form { deviceId: deviceInfo, ... }. + return Object.keys(devices).length > 0; + }); + if (allHaveDeviceKeys) { + createRoomOptions.encryption = true; } createRoom(createRoomOptions); @@ -167,9 +161,7 @@ function DeviceItem({userId, device}) { // cross-signing so that other users can then safely trust you. // For other people's devices, the more general verified check that // includes locally verified devices can be used. - const isVerified = (isMe && SettingsStore.getValue("feature_cross_signing")) ? - deviceTrust.isCrossSigningVerified() : - deviceTrust.isVerified(); + const isVerified = isMe ? deviceTrust.isCrossSigningVerified() : deviceTrust.isVerified(); const classes = classNames("mx_UserInfo_device", { mx_UserInfo_device_verified: isVerified, @@ -248,9 +240,7 @@ function DevicesSection({devices, userId, loading}) { // cross-signing so that other users can then safely trust you. // For other people's devices, the more general verified check that // includes locally verified devices can be used. - const isVerified = (isMe && SettingsStore.getValue("feature_cross_signing")) ? - deviceTrust.isCrossSigningVerified() : - deviceTrust.isVerified(); + const isVerified = isMe ? deviceTrust.isCrossSigningVerified() : deviceTrust.isVerified(); if (isVerified) { expandSectionDevices.push(device); @@ -1309,8 +1299,7 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { const userTrust = cli.checkUserTrust(member.userId); const userVerified = userTrust.isCrossSigningVerified(); const isMe = member.userId === cli.getUserId(); - const canVerify = SettingsStore.getValue("feature_cross_signing") && - homeserverSupportsCrossSigning && !userVerified && !isMe; + const canVerify = homeserverSupportsCrossSigning && !userVerified && !isMe; const setUpdating = (updating) => { setPendingUpdateCount(count => count + (updating ? 1 : -1)); diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js index 5e74656920..bf65c7fb7c 100644 --- a/src/components/views/rooms/E2EIcon.js +++ b/src/components/views/rooms/E2EIcon.js @@ -20,7 +20,6 @@ import PropTypes from "prop-types"; import classNames from 'classnames'; import {_t, _td} from '../../../languageHandler'; -import {useSettingValue} from "../../../hooks/useSettings"; import AccessibleButton from "../elements/AccessibleButton"; import Tooltip from "../elements/Tooltip"; @@ -42,15 +41,6 @@ const crossSigningRoomTitles = { [E2E_STATE.VERIFIED]: _td("Everyone in this room is verified"), }; -const legacyUserTitles = { - [E2E_STATE.WARNING]: _td("Some sessions for this user are not trusted"), - [E2E_STATE.VERIFIED]: _td("All sessions for this user are trusted"), -}; -const legacyRoomTitles = { - [E2E_STATE.WARNING]: _td("Some sessions in this encrypted room are not trusted"), - [E2E_STATE.VERIFIED]: _td("All sessions in this encrypted room are trusted"), -}; - const E2EIcon = ({isUser, status, className, size, onClick, hideTooltip}) => { const [hover, setHover] = useState(false); @@ -62,15 +52,10 @@ const E2EIcon = ({isUser, status, className, size, onClick, hideTooltip}) => { }, className); let e2eTitle; - const crossSigning = useSettingValue("feature_cross_signing"); - if (crossSigning && isUser) { + if (isUser) { e2eTitle = crossSigningUserTitles[status]; - } else if (crossSigning && !isUser) { + } else { e2eTitle = crossSigningRoomTitles[status]; - } else if (!crossSigning && isUser) { - e2eTitle = legacyUserTitles[status]; - } else if (!crossSigning && !isUser) { - e2eTitle = legacyRoomTitles[status]; } let style; diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index ee0b40c0de..3d8a2a7c35 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -325,15 +325,6 @@ export default createReactClass({ return; } - // If cross-signing is off, the old behaviour is to scream at the user - // as if they've done something wrong, which they haven't - if (!SettingsStore.getValue("feature_cross_signing")) { - this.setState({ - verified: E2E_STATE.WARNING, - }, this.props.onHeightChanged); - return; - } - if (!this.context.checkUserTrust(mxEvent.getSender()).isCrossSigningVerified()) { this.setState({ verified: E2E_STATE.NORMAL, diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js deleted file mode 100644 index ed6c4ad748..0000000000 --- a/src/components/views/rooms/MemberInfo.js +++ /dev/null @@ -1,1165 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017, 2018 Vector Creations Ltd -Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/* - * State vars: - * 'can': { - * kick: boolean, - * ban: boolean, - * mute: boolean, - * modifyLevel: boolean - * }, - * 'muted': boolean, - * 'isTargetMod': boolean - */ -import React from 'react'; -import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; -import classNames from 'classnames'; -import dis from '../../../dispatcher/dispatcher'; -import Modal from '../../../Modal'; -import * as sdk from '../../../index'; -import { _t } from '../../../languageHandler'; -import createRoom from '../../../createRoom'; -import DMRoomMap from '../../../utils/DMRoomMap'; -import * as Unread from '../../../Unread'; -import { findReadReceiptFromUserId } from '../../../utils/Receipt'; -import AccessibleButton from '../elements/AccessibleButton'; -import RoomViewStore from '../../../stores/RoomViewStore'; -import SdkConfig from '../../../SdkConfig'; -import MultiInviter from "../../../utils/MultiInviter"; -import SettingsStore from "../../../settings/SettingsStore"; -import E2EIcon from "./E2EIcon"; -import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; -import {MatrixClientPeg} from "../../../MatrixClientPeg"; -import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import {Action} from "../../../dispatcher/actions"; - -export default createReactClass({ - displayName: 'MemberInfo', - - propTypes: { - member: PropTypes.object.isRequired, - }, - - getInitialState: function() { - return { - can: { - kick: false, - ban: false, - mute: false, - modifyLevel: false, - synapseDeactivate: false, - redactMessages: false, - }, - muted: false, - isTargetMod: false, - updating: 0, - devicesLoading: true, - devices: null, - isIgnoring: false, - }; - }, - - statics: { - contextType: MatrixClientContext, - }, - - // TODO: [REACT-WARNING] Move this to constructor - UNSAFE_componentWillMount: function() { - this._cancelDeviceList = null; - const cli = this.context; - - // only display the devices list if our client supports E2E - this._enableDevices = cli.isCryptoEnabled(); - - cli.on("deviceVerificationChanged", this.onDeviceVerificationChanged); - cli.on("Room", this.onRoom); - cli.on("deleteRoom", this.onDeleteRoom); - cli.on("Room.timeline", this.onRoomTimeline); - cli.on("Room.name", this.onRoomName); - cli.on("Room.receipt", this.onRoomReceipt); - cli.on("RoomState.events", this.onRoomStateEvents); - cli.on("RoomMember.name", this.onRoomMemberName); - cli.on("RoomMember.membership", this.onRoomMemberMembership); - cli.on("accountData", this.onAccountData); - - this._checkIgnoreState(); - - this._updateStateForNewMember(this.props.member); - }, - - // TODO: [REACT-WARNING] Replace with appropriate lifecycle event - UNSAFE_componentWillReceiveProps: function(newProps) { - if (this.props.member.userId !== newProps.member.userId) { - this._updateStateForNewMember(newProps.member); - } - }, - - componentWillUnmount: function() { - const client = this.context; - if (client) { - client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); - client.removeListener("Room", this.onRoom); - client.removeListener("deleteRoom", this.onDeleteRoom); - client.removeListener("Room.timeline", this.onRoomTimeline); - client.removeListener("Room.name", this.onRoomName); - client.removeListener("Room.receipt", this.onRoomReceipt); - client.removeListener("RoomState.events", this.onRoomStateEvents); - client.removeListener("RoomMember.name", this.onRoomMemberName); - client.removeListener("RoomMember.membership", this.onRoomMemberMembership); - client.removeListener("accountData", this.onAccountData); - } - if (this._cancelDeviceList) { - this._cancelDeviceList(); - } - }, - - _checkIgnoreState: function() { - const isIgnoring = this.context.isUserIgnored(this.props.member.userId); - this.setState({isIgnoring: isIgnoring}); - }, - - _disambiguateDevices: function(devices) { - const names = Object.create(null); - for (let i = 0; i < devices.length; i++) { - const name = devices[i].getDisplayName(); - const indexList = names[name] || []; - indexList.push(i); - names[name] = indexList; - } - for (const name in names) { - if (names[name].length > 1) { - names[name].forEach((j)=>{ - devices[j].ambiguous = true; - }); - } - } - }, - - onDeviceVerificationChanged: function(userId, device) { - if (!this._enableDevices) { - return; - } - - if (userId === this.props.member.userId) { - // no need to re-download the whole thing; just update our copy of - // the list. - - const devices = this.context.getStoredDevicesForUser(userId); - this.setState({ - devices: devices, - e2eStatus: this._getE2EStatus(devices), - }); - } - }, - - _getE2EStatus: function(devices) { - const hasUnverifiedDevice = devices.some((device) => device.isUnverified()); - return hasUnverifiedDevice ? "warning" : "verified"; - }, - - onRoom: function(room) { - this.forceUpdate(); - }, - - onDeleteRoom: function(roomId) { - this.forceUpdate(); - }, - - onRoomTimeline: function(ev, room, toStartOfTimeline) { - if (toStartOfTimeline) return; - this.forceUpdate(); - }, - - onRoomName: function(room) { - this.forceUpdate(); - }, - - onRoomReceipt: function(receiptEvent, room) { - // because if we read a notification, it will affect notification count - // only bother updating if there's a receipt from us - if (findReadReceiptFromUserId(receiptEvent, this.context.credentials.userId)) { - this.forceUpdate(); - } - }, - - onRoomStateEvents: function(ev, state) { - this.forceUpdate(); - }, - - onRoomMemberName: function(ev, member) { - this.forceUpdate(); - }, - - onRoomMemberMembership: function(ev, member) { - if (this.props.member.userId === member.userId) this.forceUpdate(); - }, - - onAccountData: function(ev) { - if (ev.getType() === 'm.direct') { - this.forceUpdate(); - } - }, - - _updateStateForNewMember: async function(member) { - const newState = await this._calculateOpsPermissions(member); - newState.devicesLoading = true; - newState.devices = null; - this.setState(newState); - - if (this._cancelDeviceList) { - this._cancelDeviceList(); - this._cancelDeviceList = null; - } - - this._downloadDeviceList(member); - }, - - _downloadDeviceList: function(member) { - if (!this._enableDevices) { - return; - } - - let cancelled = false; - this._cancelDeviceList = function() { cancelled = true; }; - - const client = this.context; - const self = this; - client.downloadKeys([member.userId], true).then(() => { - return client.getStoredDevicesForUser(member.userId); - }).finally(function() { - self._cancelDeviceList = null; - }).then(function(devices) { - if (cancelled) { - // we got cancelled - presumably a different user now - return; - } - - self._disambiguateDevices(devices); - self.setState({ - devicesLoading: false, - devices: devices, - e2eStatus: self._getE2EStatus(devices), - }); - }, function(err) { - console.log("Error downloading sessions", err); - self.setState({devicesLoading: false}); - }); - }, - - onIgnoreToggle: function() { - const ignoredUsers = this.context.getIgnoredUsers(); - if (this.state.isIgnoring) { - const index = ignoredUsers.indexOf(this.props.member.userId); - if (index !== -1) ignoredUsers.splice(index, 1); - } else { - ignoredUsers.push(this.props.member.userId); - } - - this.context.setIgnoredUsers(ignoredUsers).then(() => { - return this.setState({isIgnoring: !this.state.isIgnoring}); - }); - }, - - onKick: function() { - const membership = this.props.member.membership; - const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); - Modal.createTrackedDialog('Confirm User Action Dialog', 'onKick', ConfirmUserActionDialog, { - member: this.props.member, - action: membership === "invite" ? _t("Disinvite") : _t("Kick"), - title: membership === "invite" ? _t("Disinvite this user?") : _t("Kick this user?"), - askReason: membership === "join", - danger: true, - onFinished: (proceed, reason) => { - if (!proceed) return; - - this.setState({ updating: this.state.updating + 1 }); - this.context.kick( - this.props.member.roomId, this.props.member.userId, - reason || undefined, - ).then(function() { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Kick success"); - }, function(err) { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - console.error("Kick error: " + err); - Modal.createTrackedDialog('Failed to kick', '', ErrorDialog, { - title: _t("Failed to kick"), - description: ((err && err.message) ? err.message : "Operation failed"), - }); - }, - ).finally(()=>{ - this.setState({ updating: this.state.updating - 1 }); - }); - }, - }); - }, - - onBanOrUnban: function() { - const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); - Modal.createTrackedDialog('Confirm User Action Dialog', 'onBanOrUnban', ConfirmUserActionDialog, { - member: this.props.member, - action: this.props.member.membership === 'ban' ? _t("Unban") : _t("Ban"), - title: this.props.member.membership === 'ban' ? _t("Unban this user?") : _t("Ban this user?"), - askReason: this.props.member.membership !== 'ban', - danger: this.props.member.membership !== 'ban', - onFinished: (proceed, reason) => { - if (!proceed) return; - - this.setState({ updating: this.state.updating + 1 }); - let promise; - if (this.props.member.membership === 'ban') { - promise = this.context.unban( - this.props.member.roomId, this.props.member.userId, - ); - } else { - promise = this.context.ban( - this.props.member.roomId, this.props.member.userId, - reason || undefined, - ); - } - promise.then( - function() { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Ban success"); - }, function(err) { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - console.error("Ban error: " + err); - Modal.createTrackedDialog('Failed to ban user', '', ErrorDialog, { - title: _t("Error"), - description: _t("Failed to ban user"), - }); - }, - ).finally(()=>{ - this.setState({ updating: this.state.updating - 1 }); - }); - }, - }); - }, - - onRedactAllMessages: async function() { - const {roomId, userId} = this.props.member; - const room = this.context.getRoom(roomId); - if (!room) { - return; - } - const timelineSet = room.getUnfilteredTimelineSet(); - let eventsToRedact = []; - for (const timeline of timelineSet.getTimelines()) { - eventsToRedact = timeline.getEvents().reduce((events, event) => { - if (event.getSender() === userId && !event.isRedacted() && !event.isRedaction()) { - return events.concat(event); - } else { - return events; - } - }, eventsToRedact); - } - - const count = eventsToRedact.length; - const user = this.props.member.name; - - if (count === 0) { - const InfoDialog = sdk.getComponent("dialogs.InfoDialog"); - Modal.createTrackedDialog('No user messages found to remove', '', InfoDialog, { - title: _t("No recent messages by %(user)s found", {user}), - description: -
-

{ _t("Try scrolling up in the timeline to see if there are any earlier ones.") }

-
, - }); - } else { - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - const confirmed = await new Promise((resolve) => { - Modal.createTrackedDialog('Remove recent messages by user', '', QuestionDialog, { - title: _t("Remove recent messages by %(user)s", {user}), - description: -
-

{ _t("You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?", {count, user}) }

-

{ _t("For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.") }

-
, - button: _t("Remove %(count)s messages", {count}), - onFinished: resolve, - }); - }); - - if (!confirmed) { - return; - } - - // Submitting a large number of redactions freezes the UI, - // so first yield to allow to rerender after closing the dialog. - await Promise.resolve(); - - console.info(`Started redacting recent ${count} messages for ${user} in ${roomId}`); - await Promise.all(eventsToRedact.map(async event => { - try { - await this.context.redactEvent(roomId, event.getId()); - } catch (err) { - // log and swallow errors - console.error("Could not redact", event.getId()); - console.error(err); - } - })); - console.info(`Finished redacting recent ${count} messages for ${user} in ${roomId}`); - } - }, - - _warnSelfDemote: function() { - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - return new Promise((resolve) => { - Modal.createTrackedDialog('Demoting Self', '', QuestionDialog, { - title: _t("Demote yourself?"), - description: -
- { _t("You will not be able to undo this change as you are demoting yourself, " + - "if you are the last privileged user in the room it will be impossible " + - "to regain privileges.") } -
, - button: _t("Demote"), - onFinished: resolve, - }); - }); - }, - - onMuteToggle: async function() { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - const roomId = this.props.member.roomId; - const target = this.props.member.userId; - const room = this.context.getRoom(roomId); - if (!room) return; - - // if muting self, warn as it may be irreversible - if (target === this.context.getUserId()) { - try { - if (!(await this._warnSelfDemote())) return; - } catch (e) { - console.error("Failed to warn about self demotion: ", e); - return; - } - } - - const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); - if (!powerLevelEvent) return; - - const isMuted = this.state.muted; - const powerLevels = powerLevelEvent.getContent(); - const levelToSend = ( - (powerLevels.events ? powerLevels.events["m.room.message"] : null) || - powerLevels.events_default - ); - let level; - if (isMuted) { // unmute - level = levelToSend; - } else { // mute - level = levelToSend - 1; - } - level = parseInt(level); - - if (!isNaN(level)) { - this.setState({ updating: this.state.updating + 1 }); - this.context.setPowerLevel(roomId, target, level, powerLevelEvent).then( - function() { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Mute toggle success"); - }, function(err) { - console.error("Mute error: " + err); - Modal.createTrackedDialog('Failed to mute user', '', ErrorDialog, { - title: _t("Error"), - description: _t("Failed to mute user"), - }); - }, - ).finally(()=>{ - this.setState({ updating: this.state.updating - 1 }); - }); - } - }, - - onModToggle: function() { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - const roomId = this.props.member.roomId; - const target = this.props.member.userId; - const room = this.context.getRoom(roomId); - if (!room) return; - - const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); - if (!powerLevelEvent) return; - - const me = room.getMember(this.context.credentials.userId); - if (!me) return; - - const defaultLevel = powerLevelEvent.getContent().users_default; - let modLevel = me.powerLevel - 1; - if (modLevel > 50 && defaultLevel < 50) modLevel = 50; // try to stick with the vector level defaults - // toggle the level - const newLevel = this.state.isTargetMod ? defaultLevel : modLevel; - this.setState({ updating: this.state.updating + 1 }); - this.context.setPowerLevel(roomId, target, parseInt(newLevel), powerLevelEvent).then( - function() { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Mod toggle success"); - }, function(err) { - if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') { - dis.dispatch({action: 'require_registration'}); - } else { - console.error("Toggle moderator error:" + err); - Modal.createTrackedDialog('Failed to toggle moderator status', '', ErrorDialog, { - title: _t("Error"), - description: _t("Failed to toggle moderator status"), - }); - } - }, - ).finally(()=>{ - this.setState({ updating: this.state.updating - 1 }); - }); - }, - - onSynapseDeactivate: function() { - const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog'); - Modal.createTrackedDialog('Synapse User Deactivation', '', QuestionDialog, { - title: _t("Deactivate user?"), - description: -
{ _t( - "Deactivating this user will log them out and prevent them from logging back in. Additionally, " + - "they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to " + - "deactivate this user?" - ) }
, - button: _t("Deactivate user"), - danger: true, - onFinished: (accepted) => { - if (!accepted) return; - this.context.deactivateSynapseUser(this.props.member.userId).catch(e => { - console.error("Failed to deactivate user"); - console.error(e); - - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to deactivate Synapse user', '', ErrorDialog, { - title: _t('Failed to deactivate user'), - description: ((e && e.message) ? e.message : _t("Operation failed")), - }); - }); - }, - }); - }, - - _applyPowerChange: function(roomId, target, powerLevel, powerLevelEvent) { - this.setState({ updating: this.state.updating + 1 }); - this.context.setPowerLevel(roomId, target, parseInt(powerLevel), powerLevelEvent).then( - function() { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Power change success"); - }, function(err) { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - console.error("Failed to change power level " + err); - Modal.createTrackedDialog('Failed to change power level', '', ErrorDialog, { - title: _t("Error"), - description: _t("Failed to change power level"), - }); - }, - ).finally(()=>{ - this.setState({ updating: this.state.updating - 1 }); - }); - }, - - onPowerChange: async function(powerLevel) { - const roomId = this.props.member.roomId; - const target = this.props.member.userId; - const room = this.context.getRoom(roomId); - if (!room) return; - - const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); - if (!powerLevelEvent) return; - - if (!powerLevelEvent.getContent().users) { - this._applyPowerChange(roomId, target, powerLevel, powerLevelEvent); - return; - } - - const myUserId = this.context.getUserId(); - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - - // If we are changing our own PL it can only ever be decreasing, which we cannot reverse. - if (myUserId === target) { - try { - if (!(await this._warnSelfDemote())) return; - this._applyPowerChange(roomId, target, powerLevel, powerLevelEvent); - } catch (e) { - console.error("Failed to warn about self demotion: ", e); - } - return; - } - - const myPower = powerLevelEvent.getContent().users[myUserId]; - if (parseInt(myPower) === parseInt(powerLevel)) { - Modal.createTrackedDialog('Promote to PL100 Warning', '', QuestionDialog, { - title: _t("Warning!"), - description: -
- { _t("You will not be able to undo this change as you are promoting the user " + - "to have the same power level as yourself.") }
- { _t("Are you sure?") } -
, - button: _t("Continue"), - onFinished: (confirmed) => { - if (confirmed) { - this._applyPowerChange(roomId, target, powerLevel, powerLevelEvent); - } - }, - }); - return; - } - this._applyPowerChange(roomId, target, powerLevel, powerLevelEvent); - }, - - onNewDMClick: function() { - this.setState({ updating: this.state.updating + 1 }); - createRoom({dmUserId: this.props.member.userId}).finally(() => { - this.setState({ updating: this.state.updating - 1 }); - }); - }, - - onLeaveClick: function() { - dis.dispatch({ - action: 'leave_room', - room_id: this.props.member.roomId, - }); - }, - - _calculateOpsPermissions: async function(member) { - let canDeactivate = false; - if (this.context) { - try { - canDeactivate = await this.context.isSynapseAdministrator(); - } catch (e) { - console.error(e); - } - } - - const defaultPerms = { - can: { - // Calculate permissions for Synapse before doing the PL checks - synapseDeactivate: canDeactivate, - }, - muted: false, - }; - const room = this.context.getRoom(member.roomId); - if (!room) return defaultPerms; - - const powerLevels = room.currentState.getStateEvents("m.room.power_levels", ""); - if (!powerLevels) return defaultPerms; - - const me = room.getMember(this.context.credentials.userId); - if (!me) return defaultPerms; - - const them = member; - return { - can: { - ...defaultPerms.can, - ...await this._calculateCanPermissions(me, them, powerLevels.getContent()), - }, - muted: this._isMuted(them, powerLevels.getContent()), - isTargetMod: them.powerLevel > powerLevels.getContent().users_default, - }; - }, - - _calculateCanPermissions: function(me, them, powerLevels) { - const isMe = me.userId === them.userId; - const can = { - kick: false, - ban: false, - mute: false, - modifyLevel: false, - modifyLevelMax: 0, - redactMessages: me.powerLevel >= powerLevels.redact, - }; - - const canAffectUser = them.powerLevel < me.powerLevel || isMe; - if (!canAffectUser) { - //console.info("Cannot affect user: %s >= %s", them.powerLevel, me.powerLevel); - return can; - } - const editPowerLevel = ( - (powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) || - powerLevels.state_default - ); - - can.kick = me.powerLevel >= powerLevels.kick; - can.ban = me.powerLevel >= powerLevels.ban; - can.invite = me.powerLevel >= powerLevels.invite; - can.mute = me.powerLevel >= editPowerLevel; - can.modifyLevel = me.powerLevel >= editPowerLevel && (isMe || me.powerLevel > them.powerLevel); - can.modifyLevelMax = me.powerLevel; - - return can; - }, - - _isMuted: function(member, powerLevelContent) { - if (!powerLevelContent || !member) return false; - - const levelToSend = ( - (powerLevelContent.events ? powerLevelContent.events["m.room.message"] : null) || - powerLevelContent.events_default - ); - return member.powerLevel < levelToSend; - }, - - onCancel: function(e) { - dis.dispatch({ - action: Action.ViewUser, - member: null, - }); - }, - - onMemberAvatarClick: function() { - const member = this.props.member; - const avatarUrl = member.getMxcAvatarUrl(); - if (!avatarUrl) return; - - const httpUrl = this.context.mxcUrlToHttp(avatarUrl); - const ImageView = sdk.getComponent("elements.ImageView"); - const params = { - src: httpUrl, - name: member.name, - }; - - Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); - }, - - onRoomTileClick(roomId) { - dis.dispatch({ - action: 'view_room', - room_id: roomId, - }); - }, - - _renderDevices: function() { - if (!this._enableDevices) return null; - - const devices = this.state.devices; - const MemberDeviceInfo = sdk.getComponent('rooms.MemberDeviceInfo'); - const Spinner = sdk.getComponent("elements.Spinner"); - - let devComponents; - if (this.state.devicesLoading) { - // still loading - devComponents = ; - } else if (devices === null) { - devComponents = _t("Unable to load session list"); - } else if (devices.length === 0) { - devComponents = _t("No sessions with registered encryption keys"); - } else { - devComponents = []; - for (let i = 0; i < devices.length; i++) { - devComponents.push(); - } - } - - return ( -
-

{ _t("Sessions") }

-
- { devComponents } -
-
- ); - }, - - onShareUserClick: function() { - const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); - Modal.createTrackedDialog('share room member dialog', '', ShareDialog, { - target: this.props.member, - }); - }, - - _renderUserOptions: function() { - const cli = this.context; - const member = this.props.member; - - let ignoreButton = null; - let insertPillButton = null; - let inviteUserButton = null; - let readReceiptButton = null; - - // Only allow the user to ignore the user if its not ourselves - // same goes for jumping to read receipt - if (member.userId !== cli.getUserId()) { - ignoreButton = ( - - { this.state.isIgnoring ? _t("Unignore") : _t("Ignore") } - - ); - - if (member.roomId) { - const room = cli.getRoom(member.roomId); - const eventId = room.getEventReadUpTo(member.userId); - - const onReadReceiptButton = function() { - dis.dispatch({ - action: 'view_room', - highlighted: true, - event_id: eventId, - room_id: member.roomId, - }); - }; - - const onInsertPillButton = function() { - dis.dispatch({ - action: 'insert_mention', - user_id: member.userId, - }); - }; - - readReceiptButton = ( - - { _t('Jump to read receipt') } - - ); - - insertPillButton = ( - - { _t('Mention') } - - ); - } - - if (this.state.can.invite && (!member || !member.membership || member.membership === 'leave')) { - const roomId = member && member.roomId ? member.roomId : RoomViewStore.getRoomId(); - const onInviteUserButton = async () => { - try { - // We use a MultiInviter to re-use the invite logic, even though - // we're only inviting one user. - const inviter = new MultiInviter(roomId); - await inviter.invite([member.userId]).then(() => { - if (inviter.getCompletionState(member.userId) !== "invited") - throw new Error(inviter.getErrorText(member.userId)); - }); - } catch (err) { - const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); - Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, { - title: _t('Failed to invite'), - description: ((err && err.message) ? err.message : _t("Operation failed")), - }); - } - }; - - inviteUserButton = ( - - { _t('Invite') } - - ); - } - } - - const shareUserButton = ( - - { _t('Share Link to User') } - - ); - - return ( -
-

{ _t("User Options") }

-
- { readReceiptButton } - { shareUserButton } - { insertPillButton } - { ignoreButton } - { inviteUserButton } -
-
- ); - }, - - render: function() { - let startChat; - let kickButton; - let banButton; - let muteButton; - let giveModButton; - let redactButton; - let synapseDeactivateButton; - let spinner; - - if (this.props.member.userId !== this.context.credentials.userId) { - // TODO: Immutable DMs replaces a lot of this - const dmRoomMap = new DMRoomMap(this.context); - // dmRooms will not include dmRooms that we have been invited into but did not join. - // Because DMRoomMap runs off account_data[m.direct] which is only set on join of dm room. - // XXX: we potentially want DMs we have been invited to, to also show up here :L - // especially as logic below concerns specially if we haven't joined but have been invited - const dmRooms = dmRoomMap.getDMRoomsForUserId(this.props.member.userId); - - const RoomTile = sdk.getComponent("rooms.RoomTile"); - - const tiles = []; - for (const roomId of dmRooms) { - const room = this.context.getRoom(roomId); - if (room) { - const myMembership = room.getMyMembership(); - // not a DM room if we have are not joined - if (myMembership !== 'join') continue; - - const them = this.props.member; - // not a DM room if they are not joined - if (!them.membership || them.membership !== 'join') continue; - - const highlight = room.getUnreadNotificationCount('highlight') > 0; - - tiles.push( - , - ); - } - } - - const labelClasses = classNames({ - mx_MemberInfo_createRoom_label: true, - mx_RoomTile_name: true, - }); - let startNewChat = -
- -
-
{ _t("Start a chat") }
-
; - - if (tiles.length > 0) startNewChat = null; // Don't offer a button for a new chat if we have one. - - startChat =
-

{ _t("Direct chats") }

- { tiles } - { startNewChat } -
; - } - - if (this.state.updating) { - const Loader = sdk.getComponent("elements.Spinner"); - spinner = ; - } - - if (this.state.can.kick) { - const membership = this.props.member.membership; - const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick"); - kickButton = ( - - { kickLabel } - - ); - } - - if (this.state.can.redactMessages) { - redactButton = ( - - { _t("Remove recent messages") } - - ); - } - - if (this.state.can.ban) { - let label = _t("Ban"); - if (this.props.member.membership === 'ban') { - label = _t("Unban"); - } - banButton = ( - - { label } - - ); - } - if (this.state.can.mute) { - const muteLabel = this.state.muted ? _t("Unmute") : _t("Mute"); - muteButton = ( - - { muteLabel } - - ); - } - if (this.state.can.toggleMod) { - const giveOpLabel = this.state.isTargetMod ? _t("Revoke Moderator") : _t("Make Moderator"); - giveModButton = - { giveOpLabel } - ; - } - - // We don't need a perfect check here, just something to pass as "probably not our homeserver". If - // someone does figure out how to bypass this check the worst that happens is an error. - const sameHomeserver = this.props.member.userId.endsWith(`:${MatrixClientPeg.getHomeserverName()}`); - if (this.state.can.synapseDeactivate && sameHomeserver) { - synapseDeactivateButton = ( - - {_t("Deactivate user")} - - ); - } - - let adminTools; - if (kickButton || banButton || muteButton || giveModButton || synapseDeactivateButton || redactButton) { - adminTools = -
-

{ _t("Admin Tools") }

- -
- { muteButton } - { kickButton } - { banButton } - { redactButton } - { giveModButton } - { synapseDeactivateButton } -
-
; - } - - const memberName = this.props.member.name; - - let presenceState; - let presenceLastActiveAgo; - let presenceCurrentlyActive; - let statusMessage; - - if (this.props.member.user) { - presenceState = this.props.member.user.presence; - presenceLastActiveAgo = this.props.member.user.lastActiveAgo; - presenceCurrentlyActive = this.props.member.user.currentlyActive; - - if (SettingsStore.isFeatureEnabled("feature_custom_status")) { - statusMessage = this.props.member.user._unstable_statusMessage; - } - } - - const room = this.context.getRoom(this.props.member.roomId); - const powerLevelEvent = room ? room.currentState.getStateEvents("m.room.power_levels", "") : null; - const powerLevelUsersDefault = powerLevelEvent ? powerLevelEvent.getContent().users_default : 0; - - const enablePresenceByHsUrl = SdkConfig.get()["enable_presence_by_hs_url"]; - const hsUrl = this.context.baseUrl; - let showPresence = true; - if (enablePresenceByHsUrl && enablePresenceByHsUrl[hsUrl] !== undefined) { - showPresence = enablePresenceByHsUrl[hsUrl]; - } - - let presenceLabel = null; - if (showPresence) { - const PresenceLabel = sdk.getComponent('rooms.PresenceLabel'); - presenceLabel = ; - } - - let statusLabel = null; - if (statusMessage) { - statusLabel = { statusMessage }; - } - - let roomMemberDetails = null; - let e2eIconElement; - - if (this.props.member.roomId) { // is in room - const PowerSelector = sdk.getComponent('elements.PowerSelector'); - roomMemberDetails =
-
- -
-
- {presenceLabel} - {statusLabel} -
-
; - - const isEncrypted = this.context.isRoomEncrypted(this.props.member.roomId); - if (this.state.e2eStatus && isEncrypted) { - e2eIconElement = (); - } - } - - const {member} = this.props; - const avatarUrl = member.avatarUrl || (member.getMxcAvatarUrl && member.getMxcAvatarUrl()); - let avatarElement; - if (avatarUrl) { - const httpUrl = this.context.mxcUrlToHttp(avatarUrl, 800, 800); - avatarElement =
- -
; - } - - let backButton; - if (this.props.member.roomId) { - backButton = (); - } - - return ( -
-
- { backButton } - { e2eIconElement } -

{ memberName }

-
- { avatarElement } -
- -
-
- { this.props.member.userId } -
- { roomMemberDetails } -
-
- -
- { this._renderUserOptions() } - - { adminTools } - - { startChat } - - { this._renderDevices() } - - { spinner } -
-
-
- ); - }, -}); diff --git a/src/components/views/rooms/MemberTile.js b/src/components/views/rooms/MemberTile.js index 2d290564c3..3be378b341 100644 --- a/src/components/views/rooms/MemberTile.js +++ b/src/components/views/rooms/MemberTile.js @@ -57,21 +57,19 @@ export default createReactClass({ } } - if (SettingsStore.getValue("feature_cross_signing")) { - const { roomId } = this.props.member; - if (roomId) { - const isRoomEncrypted = cli.isRoomEncrypted(roomId); - this.setState({ - isRoomEncrypted, - }); - if (isRoomEncrypted) { - cli.on("userTrustStatusChanged", this.onUserTrustStatusChanged); - cli.on("deviceVerificationChanged", this.onDeviceVerificationChanged); - this.updateE2EStatus(); - } else { - // Listen for room to become encrypted - cli.on("RoomState.events", this.onRoomStateEvents); - } + const { roomId } = this.props.member; + if (roomId) { + const isRoomEncrypted = cli.isRoomEncrypted(roomId); + this.setState({ + isRoomEncrypted, + }); + if (isRoomEncrypted) { + cli.on("userTrustStatusChanged", this.onUserTrustStatusChanged); + cli.on("deviceVerificationChanged", this.onDeviceVerificationChanged); + this.updateE2EStatus(); + } else { + // Listen for room to become encrypted + cli.on("RoomState.events", this.onRoomStateEvents); } } }, diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 732df3dbbf..14caed0183 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -281,33 +281,17 @@ export default class MessageComposer extends React.Component { } renderPlaceholderText() { - if (SettingsStore.getValue("feature_cross_signing")) { - if (this.state.isQuoting) { - if (this.props.e2eStatus) { - return _t('Send an encrypted reply…'); - } else { - return _t('Send a reply…'); - } + if (this.state.isQuoting) { + if (this.props.e2eStatus) { + return _t('Send an encrypted reply…'); } else { - if (this.props.e2eStatus) { - return _t('Send an encrypted message…'); - } else { - return _t('Send a message…'); - } + return _t('Send a reply…'); } } else { - if (this.state.isQuoting) { - if (this.props.e2eStatus) { - return _t('Send an encrypted reply…'); - } else { - return _t('Send a reply (unencrypted)…'); - } + if (this.props.e2eStatus) { + return _t('Send an encrypted message…'); } else { - if (this.props.e2eStatus) { - return _t('Send an encrypted message…'); - } else { - return _t('Send a message (unencrypted)…'); - } + return _t('Send a message…'); } } } diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 17495e6299..4820d0c8ff 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -168,10 +168,8 @@ export default createReactClass({ const joinRule = joinRules && joinRules.getContent().join_rule; let privateIcon; // Don't show an invite-only icon for DMs. Users know they're invite-only. - if (!dmUserId && SettingsStore.getValue("feature_cross_signing")) { - if (joinRule == "invite") { - privateIcon = ; - } + if (!dmUserId && joinRule === "invite") { + privateIcon = ; } if (this.props.onCancelClick) { diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 44e5ae7643..5917f2ae77 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -155,9 +155,6 @@ export default createReactClass({ if (!cli.isRoomEncrypted(this.props.room.roomId)) { return; } - if (!SettingsStore.getValue("feature_cross_signing")) { - return; - } /* At this point, the user has encryption on and cross-signing on */ this.setState({ @@ -515,10 +512,8 @@ export default createReactClass({ } let privateIcon = null; - if (SettingsStore.getValue("feature_cross_signing")) { - if (this.state.joinRule == "invite" && !dmUserId) { - privateIcon = ; - } + if (this.state.joinRule === "invite" && !dmUserId) { + privateIcon = ; } let e2eIcon = null; diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index b1642e260d..7eb239cbca 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -194,6 +194,8 @@ export default class CrossSigningPanel extends React.PureComponent { ); } + + // TODO: determine how better to expose this to users in addition to prompts at login/toast let bootstrapButton; if ( (!enabledForAccount || !crossSigningPublicKeysOnDevice) && diff --git a/src/components/views/settings/KeyBackupPanel.js b/src/components/views/settings/KeyBackupPanel.js index fa3fa03c74..d09870d5e3 100644 --- a/src/components/views/settings/KeyBackupPanel.js +++ b/src/components/views/settings/KeyBackupPanel.js @@ -316,7 +316,7 @@ export default class KeyBackupPanel extends React.PureComponent { trustedLocally = _t("This backup is trusted because it has been restored on this session"); } - let buttonRow = ( + const buttonRow = (
{restoreButtonCaption} @@ -326,13 +326,6 @@ export default class KeyBackupPanel extends React.PureComponent {
); - if (this.state.backupKeyStored && !SettingsStore.getValue("feature_cross_signing")) { - buttonRow =

⚠️ {_t( - "Backup key stored in secret storage, but this feature is not " + - "enabled on this session. Please enable cross-signing in Labs to " + - "modify key backup state.", - )}

; - } return
{clientBackupStatus}
diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index bed057f03d..a410617631 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -306,9 +306,7 @@ export default class SecurityUserSettingsTab extends React.Component { // in having advanced details here once all flows are implemented, we // can remove this. const CrossSigningPanel = sdk.getComponent('views.settings.CrossSigningPanel'); - let crossSigning; - if (SettingsStore.getValue("feature_cross_signing")) { - crossSigning = ( + const crossSigning = (
{_t("Cross-signing")}
@@ -316,7 +314,6 @@ export default class SecurityUserSettingsTab extends React.Component {
); - } const E2eAdvancedPanel = sdk.getComponent('views.settings.E2eAdvancedPanel'); diff --git a/src/createRoom.js b/src/createRoom.js index 18fc787e1c..b5761e91c5 100644 --- a/src/createRoom.js +++ b/src/createRoom.js @@ -23,7 +23,6 @@ import dis from "./dispatcher/dispatcher"; import * as Rooms from "./Rooms"; import DMRoomMap from "./utils/DMRoomMap"; import {getAddressType} from "./UserAddress"; -import SettingsStore from "./settings/SettingsStore"; /** * Create a new room, and switch to it. @@ -226,10 +225,7 @@ export async function ensureDMExists(client, userId) { if (existingDMRoom) { roomId = existingDMRoom.roomId; } else { - let encryption; - if (SettingsStore.getValue("feature_cross_signing")) { - encryption = canEncryptToAllUsers(client, [userId]); - } + const encryption = canEncryptToAllUsers(client, [userId]); roomId = await createRoom({encryption, dmUserId: userId, spinner: false, andView: false}); await _waitForMember(client, roomId, userId); } diff --git a/src/settings/Settings.js b/src/settings/Settings.js index e6aa112c5f..ea10a027cf 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -164,13 +164,6 @@ export const SETTINGS = { supportedLevels: ['account'], default: null, }, - "feature_cross_signing": { - // XXX: We shouldn't be using the feature prefix for non-feature settings. There is an exception - // for this case though as we're converting a feature to a setting for a temporary safety net. - displayName: _td("Enable cross-signing to verify per-user instead of per-session"), - supportedLevels: ['device', 'config'], // we shouldn't use LEVELS_FEATURE for non-features, so copy it here. - default: true, - }, "feature_bridge_state": { isFeature: true, supportedLevels: LEVELS_FEATURE, diff --git a/src/verification.js b/src/verification.js index 289ac9544b..1dccb7dc28 100644 --- a/src/verification.js +++ b/src/verification.js @@ -22,12 +22,11 @@ import { _t } from './languageHandler'; import {RIGHT_PANEL_PHASES} from "./stores/RightPanelStorePhases"; import {findDMForUser} from './createRoom'; import {accessSecretStorage} from './CrossSigningManager'; -import SettingsStore from './settings/SettingsStore'; import {verificationMethods} from 'matrix-js-sdk/src/crypto'; async function enable4SIfNeeded() { const cli = MatrixClientPeg.get(); - if (!cli.isCryptoEnabled() || !SettingsStore.getValue("feature_cross_signing")) { + if (!cli.isCryptoEnabled()) { return false; } const usk = cli.getCrossSigningId("user_signing");