Merge branches 'develop' and 't3chguy/e2eedefault' of github.com:matrix-org/matrix-react-sdk into t3chguy/e2eedefault

 Conflicts:
	src/components/views/dialogs/CreateRoomDialog.js
	src/components/views/dialogs/InviteDialog.js
	src/components/views/right_panel/UserInfo.js
	src/createRoom.js
This commit is contained in:
Michael Telatynski 2020-06-03 10:51:17 +01:00
commit eb536ff2f7
41 changed files with 242 additions and 2951 deletions

View file

@ -9,7 +9,6 @@ src/components/structures/UploadBar.js
src/components/views/avatars/MemberAvatar.js src/components/views/avatars/MemberAvatar.js
src/components/views/create_room/RoomAlias.js src/components/views/create_room/RoomAlias.js
src/components/views/dialogs/SetPasswordDialog.js src/components/views/dialogs/SetPasswordDialog.js
src/components/views/dialogs/UnknownDeviceDialog.js
src/components/views/elements/AddressSelector.js src/components/views/elements/AddressSelector.js
src/components/views/elements/DirectorySearchBox.js src/components/views/elements/DirectorySearchBox.js
src/components/views/elements/MemberEventListSummary.js src/components/views/elements/MemberEventListSummary.js
@ -21,7 +20,6 @@ src/components/views/room_settings/ColorSettings.js
src/components/views/rooms/Autocomplete.js src/components/views/rooms/Autocomplete.js
src/components/views/rooms/AuxPanel.js src/components/views/rooms/AuxPanel.js
src/components/views/rooms/LinkPreviewWidget.js src/components/views/rooms/LinkPreviewWidget.js
src/components/views/rooms/MemberDeviceInfo.js
src/components/views/rooms/MemberInfo.js src/components/views/rooms/MemberInfo.js
src/components/views/rooms/MemberList.js src/components/views/rooms/MemberList.js
src/components/views/rooms/RoomList.js src/components/views/rooms/RoomList.js

View file

@ -61,7 +61,6 @@
@import "./views/dialogs/_CreateGroupDialog.scss"; @import "./views/dialogs/_CreateGroupDialog.scss";
@import "./views/dialogs/_CreateRoomDialog.scss"; @import "./views/dialogs/_CreateRoomDialog.scss";
@import "./views/dialogs/_DeactivateAccountDialog.scss"; @import "./views/dialogs/_DeactivateAccountDialog.scss";
@import "./views/dialogs/_DeviceVerifyDialog.scss";
@import "./views/dialogs/_DevtoolsDialog.scss"; @import "./views/dialogs/_DevtoolsDialog.scss";
@import "./views/dialogs/_GroupAddressPicker.scss"; @import "./views/dialogs/_GroupAddressPicker.scss";
@import "./views/dialogs/_IncomingSasDialog.scss"; @import "./views/dialogs/_IncomingSasDialog.scss";
@ -81,7 +80,6 @@
@import "./views/dialogs/_SlashCommandHelpDialog.scss"; @import "./views/dialogs/_SlashCommandHelpDialog.scss";
@import "./views/dialogs/_TabbedIntegrationManagerDialog.scss"; @import "./views/dialogs/_TabbedIntegrationManagerDialog.scss";
@import "./views/dialogs/_TermsDialog.scss"; @import "./views/dialogs/_TermsDialog.scss";
@import "./views/dialogs/_UnknownDeviceDialog.scss";
@import "./views/dialogs/_UploadConfirmDialog.scss"; @import "./views/dialogs/_UploadConfirmDialog.scss";
@import "./views/dialogs/_UserSettingsDialog.scss"; @import "./views/dialogs/_UserSettingsDialog.scss";
@import "./views/dialogs/_WidgetOpenIDPermissionsDialog.scss"; @import "./views/dialogs/_WidgetOpenIDPermissionsDialog.scss";
@ -167,7 +165,6 @@
@import "./views/rooms/_InviteOnlyIcon.scss"; @import "./views/rooms/_InviteOnlyIcon.scss";
@import "./views/rooms/_JumpToBottomButton.scss"; @import "./views/rooms/_JumpToBottomButton.scss";
@import "./views/rooms/_LinkPreviewWidget.scss"; @import "./views/rooms/_LinkPreviewWidget.scss";
@import "./views/rooms/_MemberDeviceInfo.scss";
@import "./views/rooms/_MemberInfo.scss"; @import "./views/rooms/_MemberInfo.scss";
@import "./views/rooms/_MemberList.scss"; @import "./views/rooms/_MemberList.scss";
@import "./views/rooms/_MessageComposer.scss"; @import "./views/rooms/_MessageComposer.scss";

View file

@ -1,29 +0,0 @@
/*
Copyright 2019 New Vector Ltd.
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.
*/
.mx_DeviceVerifyDialog_cryptoSection ul {
display: table;
}
.mx_DeviceVerifyDialog_cryptoSection li {
display: table-row;
}
.mx_DeviceVerifyDialog_cryptoSection label,
.mx_DeviceVerifyDialog_cryptoSection span {
display: table-cell;
padding-right: 1em;
}

View file

@ -1,48 +0,0 @@
/*
Copyright 2016 OpenMarket Ltd
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.
*/
.mx_UnknownDeviceDialog {
height: 100%;
display: flex;
flex-direction: column;
}
.mx_UnknownDeviceDialog ul {
list-style: none;
padding: 0;
}
// userid
.mx_UnknownDeviceDialog p {
font-weight: bold;
font-size: $font-16px;
}
.mx_UnknownDeviceDialog .mx_DeviceVerifyButtons {
flex-direction: row !important;
}
.mx_UnknownDeviceDialog .mx_Dialog_content {
margin-bottom: 24px;
overflow-y: scroll;
}
.mx_UnknownDeviceDialog_deviceList > li {
padding: 4px;
}
.mx_UnknownDeviceDialog_deviceList > li > * {
padding-bottom: 0;
}

View file

@ -1,95 +0,0 @@
/*
Copyright 2016 OpenMarket Ltd
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.
*/
.mx_MemberDeviceInfo {
display: flex;
padding-bottom: 10px;
align-items: flex-start;
}
.mx_MemberDeviceInfo_icon {
margin-top: 4px;
width: 12px;
height: 12px;
mask-repeat: no-repeat;
mask-size: 100%;
}
.mx_MemberDeviceInfo_icon_blacklisted {
mask-image: url('$(res)/img/e2e/blacklisted.svg');
background-color: $warning-color;
}
.mx_MemberDeviceInfo_icon_verified {
mask-image: url('$(res)/img/e2e/verified.svg');
background-color: $accent-color;
}
.mx_MemberDeviceInfo_icon_unverified {
mask-image: url('$(res)/img/e2e/warning.svg');
background-color: $warning-color;
}
.mx_MemberDeviceInfo > .mx_DeviceVerifyButtons {
display: flex;
flex-direction: column;
flex: 0 1 auto;
align-items: stretch;
}
.mx_MemberDeviceInfo_textButton {
@mixin mx_DialogButton_small;
margin: 2px;
flex: 1;
}
.mx_MemberDeviceInfo_textButton:hover {
@mixin mx_DialogButton_hover;
}
.mx_MemberDeviceInfo_deviceId {
word-break: break-word;
font-size: $font-13px;
}
.mx_MemberDeviceInfo_deviceInfo {
margin: 0 5px 5px 8px;
flex: 1;
}
/* "Unblacklist" is too long for a regular button: make it wider and
reduce the padding. */
.mx_EncryptedEventDialog .mx_MemberDeviceInfo_blacklist,
.mx_EncryptedEventDialog .mx_MemberDeviceInfo_unblacklist {
padding-left: 1em;
padding-right: 1em;
}
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_verified,
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_unverified,
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_blacklisted {
float: right;
padding-left: 1em;
}
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_verified {
color: $e2e-verified-color;
}
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_unverified {
color: $e2e-unverified-color;
}
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_blacklisted {
color: $e2e-warning-color;
}

View file

@ -60,7 +60,6 @@ import * as sdk from './index';
import { _t } from './languageHandler'; import { _t } from './languageHandler';
import Matrix from 'matrix-js-sdk'; import Matrix from 'matrix-js-sdk';
import dis from './dispatcher/dispatcher'; import dis from './dispatcher/dispatcher';
import { showUnknownDeviceDialogForCalls } from './cryptodevices';
import WidgetUtils from './utils/WidgetUtils'; import WidgetUtils from './utils/WidgetUtils';
import WidgetEchoStore from './stores/WidgetEchoStore'; import WidgetEchoStore from './stores/WidgetEchoStore';
import SettingsStore, { SettingLevel } from './settings/SettingsStore'; import SettingsStore, { SettingLevel } from './settings/SettingsStore';
@ -119,62 +118,22 @@ function pause(audioId) {
} }
} }
function _reAttemptCall(call) {
if (call.direction === 'outbound') {
dis.dispatch({
action: 'place_call',
room_id: call.roomId,
type: call.type,
});
} else {
call.answer();
}
}
function _setCallListeners(call) { function _setCallListeners(call) {
call.on("error", function(err) { call.on("error", function(err) {
console.error("Call error:", err); console.error("Call error:", err);
if (err.code === 'unknown_devices') { if (
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); MatrixClientPeg.get().getTurnServers().length === 0 &&
SettingsStore.getValue("fallbackICEServerAllowed") === null
Modal.createTrackedDialog('Call Failed', '', QuestionDialog, { ) {
title: _t('Call Failed'), _showICEFallbackPrompt();
description: _t( return;
"There are unknown sessions in this room: "+
"if you proceed without verifying them, it will be "+
"possible for someone to eavesdrop on your call.",
),
button: _t('Review Sessions'),
onFinished: function(confirmed) {
if (confirmed) {
const room = MatrixClientPeg.get().getRoom(call.roomId);
showUnknownDeviceDialogForCalls(
MatrixClientPeg.get(),
room,
() => {
_reAttemptCall(call);
},
call.direction === 'outbound' ? _t("Call Anyway") : _t("Answer Anyway"),
call.direction === 'outbound' ? _t("Call") : _t("Answer"),
);
}
},
});
} else {
if (
MatrixClientPeg.get().getTurnServers().length === 0 &&
SettingsStore.getValue("fallbackICEServerAllowed") === null
) {
_showICEFallbackPrompt();
return;
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Call Failed', '', ErrorDialog, {
title: _t('Call Failed'),
description: err.message,
});
} }
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Call Failed', '', ErrorDialog, {
title: _t('Call Failed'),
description: err.message,
});
}); });
call.on("hangup", function() { call.on("hangup", function() {
_setCallState(undefined, call.roomId, "ended"); _setCallState(undefined, call.roomId, "ended");

View file

@ -15,7 +15,6 @@ limitations under the License.
*/ */
import {MatrixClientPeg} from './MatrixClientPeg'; import {MatrixClientPeg} from './MatrixClientPeg';
import SettingsStore from './settings/SettingsStore';
import { import {
hideToast as hideBulkUnverifiedSessionsToast, hideToast as hideBulkUnverifiedSessionsToast,
showToast as showBulkUnverifiedSessionsToast showToast as showBulkUnverifiedSessionsToast
@ -181,10 +180,7 @@ export default class DeviceListener {
async _recheck() { async _recheck() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
if ( if (!await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) return;
!SettingsStore.getValue("feature_cross_signing") ||
!await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")
) return;
if (!cli.isCryptoEnabled()) return; if (!cli.isCryptoEnabled()) return;
// don't recheck until the initial sync is complete: lots of account data events will fire // don't recheck until the initial sync is complete: lots of account data events will fire

View file

@ -22,7 +22,6 @@ import {MatrixClientPeg} from '../../../../MatrixClientPeg';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {_t, _td} from '../../../../languageHandler'; import {_t, _td} from '../../../../languageHandler';
import { accessSecretStorage } from '../../../../CrossSigningManager'; import { accessSecretStorage } from '../../../../CrossSigningManager';
import SettingsStore from '../../../../settings/SettingsStore';
import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
import {copyNode} from "../../../../utils/strings"; import {copyNode} from "../../../../utils/strings";
import PassphraseField from "../../../../components/views/auth/PassphraseField"; import PassphraseField from "../../../../components/views/auth/PassphraseField";
@ -67,10 +66,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
async componentDidMount() { async componentDidMount() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const secureSecretStorage = ( const secureSecretStorage = await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing");
SettingsStore.getValue("feature_cross_signing") &&
await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")
);
this.setState({ secureSecretStorage }); this.setState({ secureSecretStorage });
// If we're using secret storage, skip ahead to the backing up step, as // If we're using secret storage, skip ahead to the backing up step, as

View file

@ -90,11 +90,12 @@ export default class CommunityProvider extends AutocompleteProvider {
type: "community", type: "community",
href: makeGroupPermalink(groupId), href: makeGroupPermalink(groupId),
component: ( component: (
<PillCompletion initialComponent={ <PillCompletion title={name} description={groupId}>
<BaseAvatar name={name || groupId} <BaseAvatar name={name || groupId}
width={24} height={24} width={24}
height={24}
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 24, 24) : null} /> url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 24, 24) : null} />
} title={name} description={groupId} /> </PillCompletion>
), ),
range, range,
})) }))

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import React, {forwardRef} from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
/* These were earlier stateless functional components but had to be converted /* These were earlier stateless functional components but had to be converted
@ -30,50 +30,37 @@ interface ITextualCompletionProps {
className?: string; className?: string;
} }
export class TextualCompletion extends React.PureComponent<ITextualCompletionProps> { export const TextualCompletion = forwardRef<ITextualCompletionProps, any>((props, ref) => {
render() { const {title, subtitle, description, className, ...restProps} = props;
const { return (
title, <div {...restProps}
subtitle, className={classNames('mx_Autocomplete_Completion_block', className)}
description, role="option"
className, ref={ref}
...restProps >
} = this.props; <span className="mx_Autocomplete_Completion_title">{ title }</span>
return ( <span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span>
<div className={classNames('mx_Autocomplete_Completion_block', className)} role="option" {...restProps}> <span className="mx_Autocomplete_Completion_description">{ description }</span>
<span className="mx_Autocomplete_Completion_title">{ title }</span> </div>
<span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span> );
<span className="mx_Autocomplete_Completion_description">{ description }</span> });
</div>
); interface IPillCompletionProps extends ITextualCompletionProps {
} children?: React.ReactNode,
} }
interface IPillCompletionProps { export const PillCompletion = forwardRef<IPillCompletionProps, any>((props, ref) => {
title?: string; const {title, subtitle, description, className, children, ...restProps} = props;
subtitle?: string; return (
description?: string; <div {...restProps}
initialComponent?: React.ReactNode, className={classNames('mx_Autocomplete_Completion_pill', className)}
className?: string; role="option"
} ref={ref}
>
export class PillCompletion extends React.PureComponent<IPillCompletionProps> { { children }
render() { <span className="mx_Autocomplete_Completion_title">{ title }</span>
const { <span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span>
title, <span className="mx_Autocomplete_Completion_description">{ description }</span>
subtitle, </div>
description, );
initialComponent, });
className,
...restProps
} = this.props;
return (
<div className={classNames('mx_Autocomplete_Completion_pill', className)} role="option" {...restProps}>
{ initialComponent }
<span className="mx_Autocomplete_Completion_title">{ title }</span>
<span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span>
<span className="mx_Autocomplete_Completion_description">{ description }</span>
</div>
);
}
}

View file

@ -121,9 +121,9 @@ export default class EmojiProvider extends AutocompleteProvider {
return { return {
completion: unicode, completion: unicode,
component: ( component: (
<PillCompletion title={shortname} aria-label={unicode} initialComponent={ <PillCompletion title={shortname} aria-label={unicode}>
<span style={{maxWidth: '1em'}}>{ unicode }</span> <span style={{maxWidth: '1em'}}>{ unicode }</span>
} /> </PillCompletion>
), ),
range, range,
}; };

View file

@ -48,7 +48,9 @@ export default class NotifProvider extends AutocompleteProvider {
type: "at-room", type: "at-room",
suffix: ' ', suffix: ' ',
component: ( component: (
<PillCompletion initialComponent={<RoomAvatar width={24} height={24} room={this.room} />} title="@room" description={_t("Notify the whole room")} /> <PillCompletion title="@room" description={_t("Notify the whole room")}>
<RoomAvatar width={24} height={24} room={this.room} />
</PillCompletion>
), ),
range, range,
}]; }];

View file

@ -103,7 +103,9 @@ export default class RoomProvider extends AutocompleteProvider {
suffix: ' ', suffix: ' ',
href: makeRoomPermalink(room.displayedAlias), href: makeRoomPermalink(room.displayedAlias),
component: ( component: (
<PillCompletion initialComponent={<RoomAvatar width={24} height={24} room={room.room} />} title={room.room.name} description={room.displayedAlias} /> <PillCompletion title={room.room.name} description={room.displayedAlias}>
<RoomAvatar width={24} height={24} room={room.room} />
</PillCompletion>
), ),
range, range,
}; };

View file

@ -125,10 +125,9 @@ export default class UserProvider extends AutocompleteProvider {
suffix: (selection.beginning && range.start === 0) ? ': ' : ' ', suffix: (selection.beginning && range.start === 0) ? ': ' : ' ',
href: makeUserPermalink(user.userId), href: makeUserPermalink(user.userId),
component: ( component: (
<PillCompletion <PillCompletion title={displayName} description={user.userId}>
initialComponent={<MemberAvatar member={user} width={24} height={24} />} <MemberAvatar member={user} width={24} height={24} />
title={displayName} </PillCompletion>
description={user.userId} />
), ),
range, range,
}; };

View file

@ -1514,13 +1514,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}); });
cli.on("crypto.verification.request", request => { 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) { if (request.verifier) {
const IncomingSasDialog = sdk.getComponent("views.dialogs.IncomingSasDialog"); const IncomingSasDialog = sdk.getComponent("views.dialogs.IncomingSasDialog");
Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, { Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {
@ -1563,9 +1556,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// be aware of will be signalled through the room shield // be aware of will be signalled through the room shield
// changing colour. More advanced behaviour will come once // changing colour. More advanced behaviour will come once
// we implement more settings. // we implement more settings.
cli.setGlobalErrorOnUnknownDevices( cli.setGlobalErrorOnUnknownDevices(false);
!SettingsStore.getValue("feature_cross_signing"),
);
} }
} }
@ -1909,21 +1900,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
return setLoggedInPromise; return setLoggedInPromise;
} }
// Test for the master cross-signing key in SSSS as a quick proxy for if (await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) {
// 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")
) {
// 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.
this.setStateForNewView({ view: Views.E2E_SETUP }); this.setStateForNewView({ view: Views.E2E_SETUP });
} else { } else {
this.onLoggedIn(); this.onLoggedIn();

View file

@ -26,7 +26,6 @@ import dis from '../../dispatcher/dispatcher';
import RateLimitedFunc from '../../ratelimitedfunc'; import RateLimitedFunc from '../../ratelimitedfunc';
import { showGroupInviteDialog, showGroupAddRoomDialog } from '../../GroupAddressPicker'; import { showGroupInviteDialog, showGroupAddRoomDialog } from '../../GroupAddressPicker';
import GroupStore from '../../stores/GroupStore'; import GroupStore from '../../stores/GroupStore';
import SettingsStore from "../../settings/SettingsStore";
import {RIGHT_PANEL_PHASES, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPanelStorePhases"; import {RIGHT_PANEL_PHASES, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPanelStorePhases";
import RightPanelStore from "../../stores/RightPanelStore"; import RightPanelStore from "../../stores/RightPanelStore";
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
@ -189,16 +188,37 @@ export default class RightPanel extends React.Component {
} }
} }
onCloseUserInfo = () => {
// 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/group, 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,
});
}
};
render() { render() {
const MemberList = sdk.getComponent('rooms.MemberList'); const MemberList = sdk.getComponent('rooms.MemberList');
const MemberInfo = sdk.getComponent('rooms.MemberInfo');
const UserInfo = sdk.getComponent('right_panel.UserInfo'); const UserInfo = sdk.getComponent('right_panel.UserInfo');
const ThirdPartyMemberInfo = sdk.getComponent('rooms.ThirdPartyMemberInfo'); const ThirdPartyMemberInfo = sdk.getComponent('rooms.ThirdPartyMemberInfo');
const NotificationPanel = sdk.getComponent('structures.NotificationPanel'); const NotificationPanel = sdk.getComponent('structures.NotificationPanel');
const FilePanel = sdk.getComponent('structures.FilePanel'); const FilePanel = sdk.getComponent('structures.FilePanel');
const GroupMemberList = sdk.getComponent('groups.GroupMemberList'); const GroupMemberList = sdk.getComponent('groups.GroupMemberList');
const GroupMemberInfo = sdk.getComponent('groups.GroupMemberInfo');
const GroupRoomList = sdk.getComponent('groups.GroupRoomList'); const GroupRoomList = sdk.getComponent('groups.GroupRoomList');
const GroupRoomInfo = sdk.getComponent('groups.GroupRoomInfo'); const GroupRoomInfo = sdk.getComponent('groups.GroupRoomInfo');
@ -220,71 +240,25 @@ export default class RightPanel extends React.Component {
break; break;
case RIGHT_PANEL_PHASES.RoomMemberInfo: case RIGHT_PANEL_PHASES.RoomMemberInfo:
case RIGHT_PANEL_PHASES.EncryptionPanel: case RIGHT_PANEL_PHASES.EncryptionPanel:
if (SettingsStore.getValue("feature_cross_signing")) { panel = <UserInfo
const onClose = () => { user={this.state.member}
// XXX: There are three different ways of 'closing' this panel depending on what state roomId={this.props.roomId}
// things are in... this knows far more than it should do about the state of the rest key={this.props.roomId || this.state.member.userId}
// of the app and is generally a bit silly. onClose={this.onCloseUserInfo}
if (this.props.user) { phase={this.state.phase}
// If we have a user prop then we're displaying a user from the 'user' page type verificationRequest={this.state.verificationRequest}
// in LoggedInView, so need to change the page type to close the panel (we switch verificationRequestPromise={this.state.verificationRequestPromise}
// 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 = <UserInfo
user={this.state.member}
roomId={this.props.roomId}
key={this.props.roomId || this.state.member.userId}
onClose={onClose}
phase={this.state.phase}
verificationRequest={this.state.verificationRequest}
verificationRequestPromise={this.state.verificationRequestPromise}
/>;
} else {
panel = <MemberInfo
member={this.state.member}
key={this.props.roomId || this.state.member.userId}
/>;
}
break; break;
case RIGHT_PANEL_PHASES.Room3pidMemberInfo: case RIGHT_PANEL_PHASES.Room3pidMemberInfo:
panel = <ThirdPartyMemberInfo event={this.state.event} key={this.props.roomId} />; panel = <ThirdPartyMemberInfo event={this.state.event} key={this.props.roomId} />;
break; break;
case RIGHT_PANEL_PHASES.GroupMemberInfo: case RIGHT_PANEL_PHASES.GroupMemberInfo:
if (SettingsStore.getValue("feature_cross_signing")) { panel = <UserInfo
const onClose = () => { user={this.state.member}
dis.dispatch({ groupId={this.props.groupId}
action: Action.ViewUser, key={this.state.member.userId}
member: null, onClose={this.onCloseUserInfo} />;
});
};
panel = <UserInfo
user={this.state.member}
groupId={this.props.groupId}
key={this.state.member.userId}
onClose={onClose} />;
} else {
panel = (
<GroupMemberInfo
groupMember={this.state.member}
groupId={this.props.groupId}
key={this.state.member.user_id}
/>
);
}
break; break;
case RIGHT_PANEL_PHASES.GroupRoomInfo: case RIGHT_PANEL_PHASES.GroupRoomInfo:
panel = <GroupRoomInfo panel = <GroupRoomInfo

View file

@ -24,7 +24,6 @@ import { _t, _td } from '../../languageHandler';
import * as sdk from '../../index'; import * as sdk from '../../index';
import {MatrixClientPeg} from '../../MatrixClientPeg'; import {MatrixClientPeg} from '../../MatrixClientPeg';
import Resend from '../../Resend'; import Resend from '../../Resend';
import * as cryptodevices from '../../cryptodevices';
import dis from '../../dispatcher/dispatcher'; import dis from '../../dispatcher/dispatcher';
import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils'; import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils';
@ -126,13 +125,6 @@ export default createReactClass({
}); });
}, },
_onSendWithoutVerifyingClick: function() {
cryptodevices.getUnknownDevicesForRoom(MatrixClientPeg.get(), this.props.room).then((devices) => {
cryptodevices.markAllDevicesKnown(MatrixClientPeg.get(), devices);
Resend.resendUnsentEvents(this.props.room);
});
},
_onResendAllClick: function() { _onResendAllClick: function() {
Resend.resendUnsentEvents(this.props.room); Resend.resendUnsentEvents(this.props.room);
dis.dispatch({action: 'focus_composer'}); dis.dispatch({action: 'focus_composer'});
@ -143,10 +135,6 @@ export default createReactClass({
dis.dispatch({action: 'focus_composer'}); dis.dispatch({action: 'focus_composer'});
}, },
_onShowDevicesClick: function() {
cryptodevices.showUnknownDeviceDialogForMessages(MatrixClientPeg.get(), this.props.room);
},
_onRoomLocalEchoUpdated: function(event, room, oldEventId, oldStatus) { _onRoomLocalEchoUpdated: function(event, room, oldEventId, oldStatus) {
if (room.roomId !== this.props.room.roomId) return; if (room.roomId !== this.props.room.roomId) return;
@ -213,82 +201,65 @@ export default createReactClass({
if (!unsentMessages.length) return null; if (!unsentMessages.length) return null;
let title; let title;
let content;
const hasUDE = unsentMessages.some((m) => { let consentError = null;
return m.error && m.error.name === "UnknownDeviceError"; let resourceLimitError = null;
}); for (const m of unsentMessages) {
if (m.error && m.error.errcode === 'M_CONSENT_NOT_GIVEN') {
if (hasUDE) { consentError = m.error;
title = _t("Message not sent due to unknown sessions being present"); break;
content = _t( } else if (m.error && m.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') {
"<showSessionsText>Show sessions</showSessionsText>, <sendAnywayText>send anyway</sendAnywayText> or <cancelText>cancel</cancelText>.", resourceLimitError = m.error;
break;
}
}
if (consentError) {
title = _t(
"You can't send any messages until you review and agree to " +
"<consentLink>our terms and conditions</consentLink>.",
{}, {},
{ {
'showSessionsText': (sub) => <a className="mx_RoomStatusBar_resend_link" key="resend" onClick={this._onShowDevicesClick}>{ sub }</a>, 'consentLink': (sub) =>
'sendAnywayText': (sub) => <a className="mx_RoomStatusBar_resend_link" key="sendAnyway" onClick={this._onSendWithoutVerifyingClick}>{ sub }</a>, <a href={consentError.data && consentError.data.consent_uri} target="_blank">
'cancelText': (sub) => <a className="mx_RoomStatusBar_resend_link" key="cancel" onClick={this._onCancelAllClick}>{ sub }</a>, { sub }
</a>,
}, },
); );
} else if (resourceLimitError) {
title = messageForResourceLimitError(
resourceLimitError.data.limit_type,
resourceLimitError.data.admin_contact, {
'monthly_active_user': _td(
"Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. " +
"Please <a>contact your service administrator</a> to continue using the service.",
),
'': _td(
"Your message wasn't sent because this homeserver has exceeded a resource limit. " +
"Please <a>contact your service administrator</a> to continue using the service.",
),
});
} else if (
unsentMessages.length === 1 &&
unsentMessages[0].error &&
unsentMessages[0].error.data &&
unsentMessages[0].error.data.error
) {
title = messageForSendError(unsentMessages[0].error.data) || unsentMessages[0].error.data.error;
} else { } else {
let consentError = null; title = _t('%(count)s of your messages have not been sent.', { count: unsentMessages.length });
let resourceLimitError = null;
for (const m of unsentMessages) {
if (m.error && m.error.errcode === 'M_CONSENT_NOT_GIVEN') {
consentError = m.error;
break;
} else if (m.error && m.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') {
resourceLimitError = m.error;
break;
}
}
if (consentError) {
title = _t(
"You can't send any messages until you review and agree to " +
"<consentLink>our terms and conditions</consentLink>.",
{},
{
'consentLink': (sub) =>
<a href={consentError.data && consentError.data.consent_uri} target="_blank">
{ sub }
</a>,
},
);
} else if (resourceLimitError) {
title = messageForResourceLimitError(
resourceLimitError.data.limit_type,
resourceLimitError.data.admin_contact, {
'monthly_active_user': _td(
"Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. " +
"Please <a>contact your service administrator</a> to continue using the service.",
),
'': _td(
"Your message wasn't sent because this homeserver has exceeded a resource limit. " +
"Please <a>contact your service administrator</a> to continue using the service.",
),
});
} else if (
unsentMessages.length === 1 &&
unsentMessages[0].error &&
unsentMessages[0].error.data &&
unsentMessages[0].error.data.error
) {
title = messageForSendError(unsentMessages[0].error.data) || unsentMessages[0].error.data.error;
} else {
title = _t('%(count)s of your messages have not been sent.', { count: unsentMessages.length });
}
content = _t("%(count)s <resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. " +
"You can also select individual messages to resend or cancel.",
{ count: unsentMessages.length },
{
'resendText': (sub) =>
<a className="mx_RoomStatusBar_resend_link" key="resend" onClick={this._onResendAllClick}>{ sub }</a>,
'cancelText': (sub) =>
<a className="mx_RoomStatusBar_resend_link" key="cancel" onClick={this._onCancelAllClick}>{ sub }</a>,
},
);
} }
const content = _t("%(count)s <resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> " +
"now. You can also select individual messages to resend or cancel.",
{ count: unsentMessages.length },
{
'resendText': (sub) =>
<a className="mx_RoomStatusBar_resend_link" key="resend" onClick={this._onResendAllClick}>{ sub }</a>,
'cancelText': (sub) =>
<a className="mx_RoomStatusBar_resend_link" key="cancel" onClick={this._onCancelAllClick}>{ sub }</a>,
},
);
return <div className="mx_RoomStatusBar_connectionLostBar"> return <div className="mx_RoomStatusBar_connectionLostBar">
<img src={require("../../../res/img/feather-customised/warning-triangle.svg")} width="24" height="24" title={_t("Warning")} alt="" /> <img src={require("../../../res/img/feather-customised/warning-triangle.svg")} width="24" height="24" title={_t("Warning")} alt="" />
<div> <div>

View file

@ -879,15 +879,6 @@ export default createReactClass({
}); });
return; return;
} }
if (!SettingsStore.getValue("feature_cross_signing")) {
room.hasUnverifiedDevices().then((hasUnverifiedDevices) => {
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 */ /* At this point, the user has encryption on and cross-signing on */
this.setState({ this.setState({

View file

@ -24,7 +24,6 @@ import withValidation from '../elements/Validation';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import {Key} from "../../../Keyboard"; import {Key} from "../../../Keyboard";
import SettingsStore from "../../../settings/SettingsStore";
import {privateShouldBeEncrypted} from "../../../createRoom"; import {privateShouldBeEncrypted} from "../../../createRoom";
export default createReactClass({ export default createReactClass({
@ -67,7 +66,7 @@ export default createReactClass({
createOpts.creation_content = {'m.federate': false}; createOpts.creation_content = {'m.federate': false};
} }
if (!this.state.isPublic && SettingsStore.getValue("feature_cross_signing")) { if (!this.state.isPublic) {
opts.encryption = this.state.isEncrypted; opts.encryption = this.state.isEncrypted;
} }
@ -194,7 +193,7 @@ export default createReactClass({
} }
let e2eeSection; let e2eeSection;
if (!this.state.isPublic && SettingsStore.getValue("feature_cross_signing")) { if (!this.state.isPublic) {
let microcopy; let microcopy;
if (privateShouldBeEncrypted()) { if (privateShouldBeEncrypted()) {
microcopy = _t("You cant disable this later. Bridges & most bots wont work yet."); microcopy = _t("You cant disable this later. Bridges & most bots wont work yet.");
@ -202,7 +201,6 @@ export default createReactClass({
microcopy = _t("Your server admin has disabled end-to-end encryption by default " + microcopy = _t("Your server admin has disabled end-to-end encryption by default " +
"in private rooms & Direct Messages."); "in private rooms & Direct Messages.");
} }
e2eeSection = <React.Fragment> e2eeSection = <React.Fragment>
<LabelledToggleSwitch <LabelledToggleSwitch
label={ _t("Enable end-to-end encryption")} label={ _t("Enable end-to-end encryption")}

View file

@ -1,377 +0,0 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2019 New Vector 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.
*/
import React from 'react';
import PropTypes from 'prop-types';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import * as sdk from '../../../index';
import * as FormattingUtils from '../../../utils/FormattingUtils';
import { _t } from '../../../languageHandler';
import {verificationMethods} from 'matrix-js-sdk/src/crypto';
import {ensureDMExists} from "../../../createRoom";
import dis from "../../../dispatcher/dispatcher";
import SettingsStore from '../../../settings/SettingsStore';
import {SHOW_QR_CODE_METHOD} from "matrix-js-sdk/src/crypto/verification/QRCode";
import VerificationQREmojiOptions from "../verification/VerificationQREmojiOptions";
const MODE_LEGACY = 'legacy';
const MODE_SAS = 'sas';
const PHASE_START = 0;
const PHASE_WAIT_FOR_PARTNER_TO_ACCEPT = 1;
const PHASE_PICK_VERIFICATION_OPTION = 2;
const PHASE_SHOW_SAS = 3;
const PHASE_WAIT_FOR_PARTNER_TO_CONFIRM = 4;
const PHASE_VERIFIED = 5;
const PHASE_CANCELLED = 6;
export default class DeviceVerifyDialog extends React.Component {
static propTypes = {
userId: PropTypes.string.isRequired,
device: PropTypes.object.isRequired,
onFinished: PropTypes.func.isRequired,
};
constructor() {
super();
this._verifier = null;
this._showSasEvent = null;
this._request = null;
this.state = {
phase: PHASE_START,
mode: MODE_SAS,
sasVerified: false,
};
}
componentWillUnmount() {
if (this._verifier) {
this._verifier.removeListener('show_sas', this._onVerifierShowSas);
this._verifier.cancel('User cancel');
}
}
_onSwitchToLegacyClick = () => {
if (this._verifier) {
this._verifier.removeListener('show_sas', this._onVerifierShowSas);
this._verifier.cancel('User cancel');
this._verifier = null;
}
this.setState({mode: MODE_LEGACY});
}
_onSwitchToSasClick = () => {
this.setState({mode: MODE_SAS});
}
_onCancelClick = () => {
this.props.onFinished(false);
}
_onUseSasClick = async () => {
try {
this._verifier = this._request.beginKeyVerification(verificationMethods.SAS);
this._verifier.on('show_sas', this._onVerifierShowSas);
// throws upon cancellation
await this._verifier.verify();
this.setState({phase: PHASE_VERIFIED});
this._verifier.removeListener('show_sas', this._onVerifierShowSas);
this._verifier = null;
} catch (e) {
console.log("Verification failed", e);
this.setState({
phase: PHASE_CANCELLED,
});
this._verifier = null;
this._request = null;
}
};
_onLegacyFinished = (confirm) => {
if (confirm) {
MatrixClientPeg.get().setDeviceVerified(
this.props.userId, this.props.device.deviceId, true,
);
}
this.props.onFinished(confirm);
}
_onSasRequestClick = async () => {
this.setState({
phase: PHASE_WAIT_FOR_PARTNER_TO_ACCEPT,
});
const client = MatrixClientPeg.get();
const verifyingOwnDevice = this.props.userId === client.getUserId();
try {
if (!verifyingOwnDevice && SettingsStore.getValue("feature_cross_signing")) {
const roomId = await ensureDMExistsAndOpen(this.props.userId);
// throws upon cancellation before having started
const request = await client.requestVerificationDM(
this.props.userId, roomId,
);
await request.waitFor(r => r.ready || r.started);
if (request.ready) {
this._verifier = request.beginKeyVerification(verificationMethods.SAS);
} else {
this._verifier = request.verifier;
}
} else if (verifyingOwnDevice && SettingsStore.getValue("feature_cross_signing")) {
this._request = await client.requestVerification(this.props.userId, [
verificationMethods.SAS,
SHOW_QR_CODE_METHOD,
verificationMethods.RECIPROCATE_QR_CODE,
]);
await this._request.waitFor(r => r.ready || r.started);
this.setState({phase: PHASE_PICK_VERIFICATION_OPTION});
} else {
this._verifier = client.beginKeyVerification(
verificationMethods.SAS, this.props.userId, this.props.device.deviceId,
);
}
if (!this._verifier) return;
this._verifier.on('show_sas', this._onVerifierShowSas);
// throws upon cancellation
await this._verifier.verify();
this.setState({phase: PHASE_VERIFIED});
this._verifier.removeListener('show_sas', this._onVerifierShowSas);
this._verifier = null;
} catch (e) {
console.log("Verification failed", e);
this.setState({
phase: PHASE_CANCELLED,
});
this._verifier = null;
}
}
_onSasMatchesClick = () => {
this._showSasEvent.confirm();
this.setState({
phase: PHASE_WAIT_FOR_PARTNER_TO_CONFIRM,
});
}
_onVerifiedDoneClick = () => {
this.props.onFinished(true);
}
_onVerifierShowSas = (e) => {
this._showSasEvent = e;
this.setState({
phase: PHASE_SHOW_SAS,
});
}
_renderSasVerification() {
let body;
switch (this.state.phase) {
case PHASE_START:
body = this._renderVerificationPhaseStart();
break;
case PHASE_WAIT_FOR_PARTNER_TO_ACCEPT:
body = this._renderVerificationPhaseWaitAccept();
break;
case PHASE_PICK_VERIFICATION_OPTION:
body = this._renderVerificationPhasePick();
break;
case PHASE_SHOW_SAS:
body = this._renderSasVerificationPhaseShowSas();
break;
case PHASE_WAIT_FOR_PARTNER_TO_CONFIRM:
body = this._renderSasVerificationPhaseWaitForPartnerToConfirm();
break;
case PHASE_VERIFIED:
body = this._renderVerificationPhaseVerified();
break;
case PHASE_CANCELLED:
body = this._renderVerificationPhaseCancelled();
break;
}
const BaseDialog = sdk.getComponent("dialogs.BaseDialog");
return (
<BaseDialog
title={_t("Verify session")}
onFinished={this._onCancelClick}
>
{body}
</BaseDialog>
);
}
_renderVerificationPhaseStart() {
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return (
<div>
<AccessibleButton
element="span" className="mx_linkButton" onClick={this._onSwitchToLegacyClick}
>
{_t("Use Legacy Verification (for older clients)")}
</AccessibleButton>
<p>
{ _t("Verify by comparing a short text string.") }
</p>
<p>
{_t("To be secure, do this in person or use a trusted way to communicate.")}
</p>
<DialogButtons
primaryButton={_t('Begin Verifying')}
hasCancel={true}
onPrimaryButtonClick={this._onSasRequestClick}
onCancel={this._onCancelClick}
/>
</div>
);
}
_renderVerificationPhaseWaitAccept() {
const Spinner = sdk.getComponent("views.elements.Spinner");
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
return (
<div>
<Spinner />
<p>{_t("Waiting for partner to accept...")}</p>
<p>{_t(
"Nothing appearing? Not all clients support interactive verification yet. " +
"<button>Use legacy verification</button>.",
{}, {button: sub => <AccessibleButton element='span' className="mx_linkButton"
onClick={this._onSwitchToLegacyClick}
>
{sub}
</AccessibleButton>},
)}</p>
</div>
);
}
_renderVerificationPhasePick() {
return <VerificationQREmojiOptions
request={this._request}
onCancel={this._onCancelClick}
onStartEmoji={this._onUseSasClick}
/>;
}
_renderSasVerificationPhaseShowSas() {
const VerificationShowSas = sdk.getComponent('views.verification.VerificationShowSas');
return <VerificationShowSas
sas={this._showSasEvent.sas}
onCancel={this._onCancelClick}
onDone={this._onSasMatchesClick}
isSelf={MatrixClientPeg.get().getUserId() === this.props.userId}
onStartEmoji={this._onUseSasClick}
inDialog={true}
/>;
}
_renderSasVerificationPhaseWaitForPartnerToConfirm() {
const Spinner = sdk.getComponent('views.elements.Spinner');
return <div>
<Spinner />
<p>{_t(
"Waiting for %(userId)s to confirm...", {userId: this.props.userId},
)}</p>
</div>;
}
_renderVerificationPhaseVerified() {
const VerificationComplete = sdk.getComponent('views.verification.VerificationComplete');
return <VerificationComplete onDone={this._onVerifiedDoneClick} />;
}
_renderVerificationPhaseCancelled() {
const VerificationCancelled = sdk.getComponent('views.verification.VerificationCancelled');
return <VerificationCancelled onDone={this._onCancelClick} />;
}
_renderLegacyVerification() {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
let text;
if (MatrixClientPeg.get().getUserId() === this.props.userId) {
text = _t("To verify that this session can be trusted, please check that the key you see " +
"in User Settings on that device matches the key below:");
} else {
text = _t("To verify that this session can be trusted, please contact its owner using some other " +
"means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings " +
"for this session matches the key below:");
}
const key = FormattingUtils.formatCryptoKey(this.props.device.getFingerprint());
const body = (
<div>
<AccessibleButton
element="span" className="mx_linkButton" onClick={this._onSwitchToSasClick}
>
{_t("Use two-way text verification")}
</AccessibleButton>
<p>
{ text }
</p>
<div className="mx_DeviceVerifyDialog_cryptoSection">
<ul>
<li><label>{ _t("Session name") }:</label> <span>{ this.props.device.getDisplayName() }</span></li>
<li><label>{ _t("Session ID") }:</label> <span><code>{ this.props.device.deviceId }</code></span></li>
<li><label>{ _t("Session key") }:</label> <span><code><b>{ key }</b></code></span></li>
</ul>
</div>
<p>
{ _t("If it matches, press the verify button below. " +
"If it doesn't, then someone else is intercepting this session " +
"and you probably want to press the blacklist button instead.") }
</p>
</div>
);
return (
<QuestionDialog
title={_t("Verify session")}
description={body}
button={_t("I verify that the keys match")}
onFinished={this._onLegacyFinished}
/>
);
}
render() {
if (this.state.mode === MODE_LEGACY) {
return this._renderLegacyVerification();
} else {
return <div>
{this._renderSasVerification()}
</div>;
}
}
}
async function ensureDMExistsAndOpen(userId) {
const roomId = await ensureDMExists(MatrixClientPeg.get(), userId);
// don't use andView and spinner in createRoom, together, they cause this dialog to close and reopen,
// we causes us to loose the verifier and restart, and we end up having two verification requests
dis.dispatch({
action: 'view_room',
room_id: roomId,
should_peek: false,
});
return roomId;
}

View file

@ -1,187 +0,0 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2017 New Vector Ltd
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 createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
import { markAllDevicesKnown } from '../../../cryptodevices';
function UserUnknownDeviceList(props) {
const MemberDeviceInfo = sdk.getComponent('rooms.MemberDeviceInfo');
const {userId, userDevices} = props;
const deviceListEntries = Object.keys(userDevices).map((deviceId) =>
<li key={deviceId}><MemberDeviceInfo device={userDevices[deviceId]} userId={userId} showDeviceId={true} /></li>,
);
return (
<ul className="mx_UnknownDeviceDialog_deviceList">
{ deviceListEntries }
</ul>
);
}
UserUnknownDeviceList.propTypes = {
userId: PropTypes.string.isRequired,
// map from deviceid -> deviceinfo
userDevices: PropTypes.object.isRequired,
};
function UnknownDeviceList(props) {
const {devices} = props;
const userListEntries = Object.keys(devices).map((userId) =>
<li key={userId}>
<p>{ userId }:</p>
<UserUnknownDeviceList userId={userId} userDevices={devices[userId]} />
</li>,
);
return <ul>{ userListEntries }</ul>;
}
UnknownDeviceList.propTypes = {
// map from userid -> deviceid -> deviceinfo
devices: PropTypes.object.isRequired,
};
export default createReactClass({
displayName: 'UnknownDeviceDialog',
propTypes: {
room: PropTypes.object.isRequired,
// map from userid -> deviceid -> deviceinfo or null if devices are not yet loaded
devices: PropTypes.object,
onFinished: PropTypes.func.isRequired,
// Label for the button that marks all devices known and tries the send again
sendAnywayLabel: PropTypes.string.isRequired,
// Label for the button that to send the event if you've verified all devices
sendLabel: PropTypes.string.isRequired,
// function to retry the request once all devices are verified / known
onSend: PropTypes.func.isRequired,
},
componentDidMount: function() {
MatrixClientPeg.get().on("deviceVerificationChanged", this._onDeviceVerificationChanged);
},
componentWillUnmount: function() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("deviceVerificationChanged", this._onDeviceVerificationChanged);
}
},
_onDeviceVerificationChanged: function(userId, deviceId, deviceInfo) {
if (this.props.devices[userId] && this.props.devices[userId][deviceId]) {
// XXX: Mutating props :/
this.props.devices[userId][deviceId] = deviceInfo;
this.forceUpdate();
}
},
_onDismissClicked: function() {
this.props.onFinished();
},
_onSendAnywayClicked: function() {
markAllDevicesKnown(MatrixClientPeg.get(), this.props.devices);
this.props.onFinished();
this.props.onSend();
},
_onSendClicked: function() {
this.props.onFinished();
this.props.onSend();
},
render: function() {
if (this.props.devices === null) {
const Spinner = sdk.getComponent("elements.Spinner");
return <Spinner />;
}
let warning;
if (SettingsStore.getValue("blacklistUnverifiedDevices", this.props.room.roomId)) {
warning = (
<h4>
{ _t("You are currently blacklisting unverified sessions; to send " +
"messages to these sessions you must verify them.") }
</h4>
);
} else {
warning = (
<div>
<p>
{ _t("We recommend you go through the verification process " +
"for each session to confirm they belong to their legitimate owner, " +
"but you can resend the message without verifying if you prefer.") }
</p>
</div>
);
}
let haveUnknownDevices = false;
Object.keys(this.props.devices).forEach((userId) => {
Object.keys(this.props.devices[userId]).map((deviceId) => {
const device = this.props.devices[userId][deviceId];
if (device.isUnverified() && !device.isKnown()) {
haveUnknownDevices = true;
}
});
});
const sendButtonOnClick = haveUnknownDevices ? this._onSendAnywayClicked : this._onSendClicked;
const sendButtonLabel = haveUnknownDevices ? this.props.sendAnywayLabel : this.props.sendAnywayLabel;
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return (
<BaseDialog className='mx_UnknownDeviceDialog'
onFinished={this.props.onFinished}
title={_t('Room contains unknown sessions')}
contentId='mx_Dialog_content'
>
<div className="mx_Dialog_content" id='mx_Dialog_content'>
<h4>
{ _t('"%(RoomName)s" contains sessions that you haven\'t seen before.', {RoomName: this.props.room.name}) }
</h4>
{ warning }
{ _t("Unknown sessions") }:
<UnknownDeviceList devices={this.props.devices} />
</div>
<DialogButtons primaryButton={sendButtonLabel}
onPrimaryButtonClick={sendButtonOnClick}
onCancel={this._onDismissClicked} />
</BaseDialog>
);
// XXX: do we want to give the user the option to enable blacklistUnverifiedDevices for this room (or globally) at this point?
// It feels like confused users will likely turn it on and then disappear in a cloud of UISIs...
},
});

View file

@ -20,10 +20,8 @@ import PropTypes from 'prop-types';
import * as sdk from '../../../../index'; import * as sdk from '../../../../index';
import {MatrixClientPeg} from '../../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../../MatrixClientPeg';
import { MatrixClient } from 'matrix-js-sdk'; import { MatrixClient } from 'matrix-js-sdk';
import Modal from '../../../../Modal';
import { _t } from '../../../../languageHandler'; import { _t } from '../../../../languageHandler';
import { accessSecretStorage } from '../../../../CrossSigningManager'; import { accessSecretStorage } from '../../../../CrossSigningManager';
import SettingsStore from "../../../../settings/SettingsStore";
const RESTORE_TYPE_PASSPHRASE = 0; const RESTORE_TYPE_PASSPHRASE = 0;
const RESTORE_TYPE_RECOVERYKEY = 1; const RESTORE_TYPE_RECOVERYKEY = 1;
@ -90,21 +88,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
_onResetRecoveryClick = () => { _onResetRecoveryClick = () => {
this.props.onFinished(false); this.props.onFinished(false);
accessSecretStorage(() => {}, /* forceReset = */ true);
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,
);
}
} }
_onRecoveryKeyChange = (e) => { _onRecoveryKeyChange = (e) => {

View file

@ -1,127 +0,0 @@
/*
Copyright 2016 OpenMarket Ltd
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 {MatrixClientPeg} from '../../../MatrixClientPeg';
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
// XXX: This component is *not* cross-signing aware. Once everything is
// cross-signing, this component should just go away.
export default createReactClass({
displayName: 'DeviceVerifyButtons',
propTypes: {
userId: PropTypes.string.isRequired,
device: PropTypes.object.isRequired,
},
getInitialState: function() {
return {
device: this.props.device,
};
},
componentDidMount: function() {
const cli = MatrixClientPeg.get();
cli.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
},
componentWillUnmount: function() {
const cli = MatrixClientPeg.get();
if (cli) {
cli.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
}
},
onDeviceVerificationChanged: function(userId, deviceId, deviceInfo) {
if (userId === this.props.userId && deviceId === this.props.device.deviceId) {
this.setState({ device: deviceInfo });
}
},
onVerifyClick: function() {
const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog');
Modal.createTrackedDialog('Device Verify Dialog', '', DeviceVerifyDialog, {
userId: this.props.userId,
device: this.state.device,
}, null, /* priority = */ false, /* static = */ true);
},
onUnverifyClick: function() {
MatrixClientPeg.get().setDeviceVerified(
this.props.userId, this.state.device.deviceId, false,
);
},
onBlacklistClick: function() {
MatrixClientPeg.get().setDeviceBlocked(
this.props.userId, this.state.device.deviceId, true,
);
},
onUnblacklistClick: function() {
MatrixClientPeg.get().setDeviceBlocked(
this.props.userId, this.state.device.deviceId, false,
);
},
render: function() {
let blacklistButton = null; let verifyButton = null;
if (this.state.device.isBlocked()) {
blacklistButton = (
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unblacklist"
onClick={this.onUnblacklistClick}>
{ _t("Unblacklist") }
</button>
);
} else {
blacklistButton = (
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_blacklist"
onClick={this.onBlacklistClick}>
{ _t("Blacklist") }
</button>
);
}
if (this.state.device.isVerified()) {
verifyButton = (
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unverify"
onClick={this.onUnverifyClick}>
{ _t("Unverify") }
</button>
);
} else {
verifyButton = (
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_verify"
onClick={this.onVerifyClick}>
{ _t("Verify...") }
</button>
);
}
return (
<div className="mx_DeviceVerifyButtons" >
{ verifyButton }
{ blacklistButton }
</div>
);
},
});

View file

@ -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 <div className="mx_MemberInfo">
<Spinner />
</div>;
}
let adminTools;
if (this.state.isUserPrivilegedInGroup) {
const kickButton = (
<AccessibleButton className="mx_MemberInfo_field"
onClick={this._onKick}>
{ this.state.isUserInvited ? _t('Disinvite') : _t('Remove from community') }
</AccessibleButton>
);
// No make/revoke admin API yet
/*const opLabel = this.state.isTargetMod ? _t("Revoke Moderator") : _t("Make Moderator");
giveModButton = <AccessibleButton className="mx_MemberInfo_field" onClick={this.onModToggle}>
{giveOpLabel}
</AccessibleButton>;*/
if (kickButton) {
adminTools =
<div className="mx_MemberInfo_adminTools">
<h3>{ _t("Admin Tools") }</h3>
<div className="mx_MemberInfo_buttons">
{ kickButton }
</div>
</div>;
}
}
const avatarUrl = this.props.groupMember.avatarUrl;
let avatarElement;
if (avatarUrl) {
const httpUrl = this.context.mxcUrlToHttp(avatarUrl, 800, 800);
avatarElement = (<div className="mx_MemberInfo_avatar">
<img src={httpUrl} />
</div>);
}
const groupMemberName = (
this.props.groupMember.displayname || this.props.groupMember.userId
);
return (
<div className="mx_MemberInfo" role="tabpanel">
<AutoHideScrollbar>
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this._onCancel}>
<img src={require("../../../../res/img/cancel.svg")} width="18" height="18" className="mx_filterFlipColor" />
</AccessibleButton>
{ avatarElement }
<h2>{ groupMemberName }</h2>
<div className="mx_MemberInfo_profile">
<div className="mx_MemberInfo_profileField">
{ this.props.groupMember.userId }
</div>
</div>
{ adminTools }
</AutoHideScrollbar>
</div>
);
},
});

View file

@ -64,10 +64,6 @@ const _disambiguateDevices = (devices) => {
}; };
export const getE2EStatus = (cli, userId, 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 isMe = userId === cli.getUserId();
const userTrust = cli.checkUserTrust(userId); const userTrust = cli.checkUserTrust(userId);
if (!userTrust.isCrossSigningVerified()) { if (!userTrust.isCrossSigningVerified()) {
@ -167,9 +163,7 @@ function DeviceItem({userId, device}) {
// cross-signing so that other users can then safely trust you. // cross-signing so that other users can then safely trust you.
// For other people's devices, the more general verified check that // For other people's devices, the more general verified check that
// includes locally verified devices can be used. // includes locally verified devices can be used.
const isVerified = (isMe && SettingsStore.getValue("feature_cross_signing")) ? const isVerified = isMe ? deviceTrust.isCrossSigningVerified() : deviceTrust.isVerified();
deviceTrust.isCrossSigningVerified() :
deviceTrust.isVerified();
const classes = classNames("mx_UserInfo_device", { const classes = classNames("mx_UserInfo_device", {
mx_UserInfo_device_verified: isVerified, mx_UserInfo_device_verified: isVerified,
@ -248,9 +242,7 @@ function DevicesSection({devices, userId, loading}) {
// cross-signing so that other users can then safely trust you. // cross-signing so that other users can then safely trust you.
// For other people's devices, the more general verified check that // For other people's devices, the more general verified check that
// includes locally verified devices can be used. // includes locally verified devices can be used.
const isVerified = (isMe && SettingsStore.getValue("feature_cross_signing")) ? const isVerified = isMe ? deviceTrust.isCrossSigningVerified() : deviceTrust.isVerified();
deviceTrust.isCrossSigningVerified() :
deviceTrust.isVerified();
if (isVerified) { if (isVerified) {
expandSectionDevices.push(device); expandSectionDevices.push(device);
@ -1309,8 +1301,7 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => {
const userTrust = cli.checkUserTrust(member.userId); const userTrust = cli.checkUserTrust(member.userId);
const userVerified = userTrust.isCrossSigningVerified(); const userVerified = userTrust.isCrossSigningVerified();
const isMe = member.userId === cli.getUserId(); const isMe = member.userId === cli.getUserId();
const canVerify = SettingsStore.getValue("feature_cross_signing") && const canVerify = homeserverSupportsCrossSigning && !userVerified && !isMe;
homeserverSupportsCrossSigning && !userVerified && !isMe;
const setUpdating = (updating) => { const setUpdating = (updating) => {
setPendingUpdateCount(count => count + (updating ? 1 : -1)); setPendingUpdateCount(count => count + (updating ? 1 : -1));

View file

@ -15,8 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import React, {createRef} from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames'; import classNames from 'classnames';
import flatMap from 'lodash/flatMap'; import flatMap from 'lodash/flatMap';
import {ICompletion, ISelectionRange, IProviderCompletions} from '../../../autocomplete/Autocompleter'; import {ICompletion, ISelectionRange, IProviderCompletions} from '../../../autocomplete/Autocompleter';
@ -54,7 +53,7 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
autocompleter: Autocompleter; autocompleter: Autocompleter;
queryRequested: string; queryRequested: string;
debounceCompletionsRequest: NodeJS.Timeout; debounceCompletionsRequest: NodeJS.Timeout;
containerRef: React.RefObject<HTMLDivElement>; private containerRef = createRef<HTMLDivElement>();
constructor(props) { constructor(props) {
super(props); super(props);
@ -78,8 +77,6 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
forceComplete: false, forceComplete: false,
}; };
this.containerRef = React.createRef();
} }
componentDidMount() { componentDidMount() {
@ -256,14 +253,15 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
componentDidUpdate(prevProps: IProps) { componentDidUpdate(prevProps: IProps) {
this.applyNewProps(prevProps.query, prevProps.room); this.applyNewProps(prevProps.query, prevProps.room);
// this is the selected completion, so scroll it into view if needed // this is the selected completion, so scroll it into view if needed
const selectedCompletion = this.refs[`completion${this.state.selectionOffset}`]; const selectedCompletion = this.refs[`completion${this.state.selectionOffset}`] as HTMLElement;
if (selectedCompletion && this.containerRef.current) {
const domNode = ReactDOM.findDOMNode(selectedCompletion); if (selectedCompletion) {
const offsetTop = domNode && (domNode as HTMLElement).offsetTop; selectedCompletion.scrollIntoView({
if (offsetTop > this.containerRef.current.scrollTop + this.containerRef.current.offsetHeight || behavior: "auto",
offsetTop < this.containerRef.current.scrollTop) { block: "nearest",
this.containerRef.current.scrollTop = offsetTop - this.containerRef.current.offsetTop; });
} } else {
this.containerRef.current.scrollTo({ top: 0 });
} }
} }

View file

@ -20,7 +20,6 @@ import PropTypes from "prop-types";
import classNames from 'classnames'; import classNames from 'classnames';
import {_t, _td} from '../../../languageHandler'; import {_t, _td} from '../../../languageHandler';
import {useSettingValue} from "../../../hooks/useSettings";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import Tooltip from "../elements/Tooltip"; import Tooltip from "../elements/Tooltip";
@ -42,15 +41,6 @@ const crossSigningRoomTitles = {
[E2E_STATE.VERIFIED]: _td("Everyone in this room is verified"), [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 E2EIcon = ({isUser, status, className, size, onClick, hideTooltip}) => {
const [hover, setHover] = useState(false); const [hover, setHover] = useState(false);
@ -62,15 +52,10 @@ const E2EIcon = ({isUser, status, className, size, onClick, hideTooltip}) => {
}, className); }, className);
let e2eTitle; let e2eTitle;
const crossSigning = useSettingValue("feature_cross_signing"); if (isUser) {
if (crossSigning && isUser) {
e2eTitle = crossSigningUserTitles[status]; e2eTitle = crossSigningUserTitles[status];
} else if (crossSigning && !isUser) { } else {
e2eTitle = crossSigningRoomTitles[status]; e2eTitle = crossSigningRoomTitles[status];
} else if (!crossSigning && isUser) {
e2eTitle = legacyUserTitles[status];
} else if (!crossSigning && !isUser) {
e2eTitle = legacyRoomTitles[status];
} }
let style; let style;

View file

@ -325,15 +325,6 @@ export default createReactClass({
return; 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()) { if (!this.context.checkUserTrust(mxEvent.getSender()).isCrossSigningVerified()) {
this.setState({ this.setState({
verified: E2E_STATE.NORMAL, verified: E2E_STATE.NORMAL,

View file

@ -1,59 +0,0 @@
/*
Copyright 2016 OpenMarket Ltd
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 * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import classNames from 'classnames';
export default class MemberDeviceInfo extends React.Component {
render() {
const DeviceVerifyButtons = sdk.getComponent('elements.DeviceVerifyButtons');
// XXX: These checks are not cross-signing aware but this component is only used
// from the old, pre-cross-signing memberinfopanel
const iconClasses = classNames({
mx_MemberDeviceInfo_icon: true,
mx_MemberDeviceInfo_icon_blacklisted: this.props.device.isBlocked(),
mx_MemberDeviceInfo_icon_verified: this.props.device.isVerified(),
mx_MemberDeviceInfo_icon_unverified: this.props.device.isUnverified(),
});
const indicator = (<div className={iconClasses} />);
const deviceName = (this.props.device.ambiguous || this.props.showDeviceId) ?
(this.props.device.getDisplayName() ? this.props.device.getDisplayName() : "") + " (" + this.props.device.deviceId + ")" :
this.props.device.getDisplayName();
// add the deviceId as a titletext to help with debugging
return (
<div className="mx_MemberDeviceInfo"
title={_t("device id: ") + this.props.device.deviceId} >
{ indicator }
<div className="mx_MemberDeviceInfo_deviceInfo">
<div className="mx_MemberDeviceInfo_deviceId">
{ deviceName }
</div>
</div>
<DeviceVerifyButtons userId={this.props.userId} device={this.props.device} />
</div>
);
}
}
MemberDeviceInfo.displayName = 'MemberDeviceInfo';
MemberDeviceInfo.propTypes = {
userId: PropTypes.string.isRequired,
device: PropTypes.object.isRequired,
};

File diff suppressed because it is too large Load diff

View file

@ -57,21 +57,19 @@ export default createReactClass({
} }
} }
if (SettingsStore.getValue("feature_cross_signing")) { const { roomId } = this.props.member;
const { roomId } = this.props.member; if (roomId) {
if (roomId) { const isRoomEncrypted = cli.isRoomEncrypted(roomId);
const isRoomEncrypted = cli.isRoomEncrypted(roomId); this.setState({
this.setState({ isRoomEncrypted,
isRoomEncrypted, });
}); if (isRoomEncrypted) {
if (isRoomEncrypted) { cli.on("userTrustStatusChanged", this.onUserTrustStatusChanged);
cli.on("userTrustStatusChanged", this.onUserTrustStatusChanged); cli.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
cli.on("deviceVerificationChanged", this.onDeviceVerificationChanged); this.updateE2EStatus();
this.updateE2EStatus(); } else {
} else { // Listen for room to become encrypted
// Listen for room to become encrypted cli.on("RoomState.events", this.onRoomStateEvents);
cli.on("RoomState.events", this.onRoomStateEvents);
}
} }
} }
}, },

View file

@ -308,33 +308,17 @@ export default class MessageComposer extends React.Component {
} }
renderPlaceholderText() { renderPlaceholderText() {
if (SettingsStore.getValue("feature_cross_signing")) { if (this.state.isQuoting) {
if (this.state.isQuoting) { if (this.props.e2eStatus) {
if (this.props.e2eStatus) { return _t('Send an encrypted reply…');
return _t('Send an encrypted reply…');
} else {
return _t('Send a reply…');
}
} else { } else {
if (this.props.e2eStatus) { return _t('Send a reply…');
return _t('Send an encrypted message…');
} else {
return _t('Send a message…');
}
} }
} else { } else {
if (this.state.isQuoting) { if (this.props.e2eStatus) {
if (this.props.e2eStatus) { return _t('Send an encrypted message…');
return _t('Send an encrypted reply…');
} else {
return _t('Send a reply (unencrypted)…');
}
} else { } else {
if (this.props.e2eStatus) { return _t('Send a message…');
return _t('Send an encrypted message…');
} else {
return _t('Send a message (unencrypted)…');
}
} }
} }
} }

View file

@ -168,10 +168,8 @@ export default createReactClass({
const joinRule = joinRules && joinRules.getContent().join_rule; const joinRule = joinRules && joinRules.getContent().join_rule;
let privateIcon; let privateIcon;
// Don't show an invite-only icon for DMs. Users know they're invite-only. // Don't show an invite-only icon for DMs. Users know they're invite-only.
if (!dmUserId && SettingsStore.getValue("feature_cross_signing")) { if (!dmUserId && joinRule === "invite") {
if (joinRule == "invite") { privateIcon = <InviteOnlyIcon />;
privateIcon = <InviteOnlyIcon />;
}
} }
if (this.props.onCancelClick) { if (this.props.onCancelClick) {

View file

@ -155,9 +155,6 @@ export default createReactClass({
if (!cli.isRoomEncrypted(this.props.room.roomId)) { if (!cli.isRoomEncrypted(this.props.room.roomId)) {
return; return;
} }
if (!SettingsStore.getValue("feature_cross_signing")) {
return;
}
/* At this point, the user has encryption on and cross-signing on */ /* At this point, the user has encryption on and cross-signing on */
this.setState({ this.setState({
@ -515,10 +512,8 @@ export default createReactClass({
} }
let privateIcon = null; let privateIcon = null;
if (SettingsStore.getValue("feature_cross_signing")) { if (this.state.joinRule === "invite" && !dmUserId) {
if (this.state.joinRule == "invite" && !dmUserId) { privateIcon = <InviteOnlyIcon collapsedPanel={this.props.collapsed} />;
privateIcon = <InviteOnlyIcon collapsedPanel={this.props.collapsed} />;
}
} }
let e2eIcon = null; let e2eIcon = null;

View file

@ -194,6 +194,8 @@ export default class CrossSigningPanel extends React.PureComponent {
</div> </div>
); );
} }
// TODO: determine how better to expose this to users in addition to prompts at login/toast
let bootstrapButton; let bootstrapButton;
if ( if (
(!enabledForAccount || !crossSigningPublicKeysOnDevice) && (!enabledForAccount || !crossSigningPublicKeysOnDevice) &&

View file

@ -21,7 +21,6 @@ import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import SettingsStore from '../../../settings/SettingsStore';
export default class KeyBackupPanel extends React.PureComponent { export default class KeyBackupPanel extends React.PureComponent {
constructor(props) { constructor(props) {
@ -316,7 +315,7 @@ export default class KeyBackupPanel extends React.PureComponent {
trustedLocally = _t("This backup is trusted because it has been restored on this session"); trustedLocally = _t("This backup is trusted because it has been restored on this session");
} }
let buttonRow = ( const buttonRow = (
<div className="mx_KeyBackupPanel_buttonRow"> <div className="mx_KeyBackupPanel_buttonRow">
<AccessibleButton kind="primary" onClick={this._restoreBackup}> <AccessibleButton kind="primary" onClick={this._restoreBackup}>
{restoreButtonCaption} {restoreButtonCaption}
@ -326,13 +325,6 @@ export default class KeyBackupPanel extends React.PureComponent {
</AccessibleButton> </AccessibleButton>
</div> </div>
); );
if (this.state.backupKeyStored && !SettingsStore.getValue("feature_cross_signing")) {
buttonRow = <p> {_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.",
)}</p>;
}
return <div> return <div>
<div>{clientBackupStatus}</div> <div>{clientBackupStatus}</div>

View file

@ -17,7 +17,7 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {_t} from "../../../../../languageHandler"; import {_t} from "../../../../../languageHandler";
import SettingsStore, {SettingLevel} from "../../../../../settings/SettingsStore"; import {SettingLevel} from "../../../../../settings/SettingsStore";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as FormattingUtils from "../../../../../utils/FormattingUtils"; import * as FormattingUtils from "../../../../../utils/FormattingUtils";
import AccessibleButton from "../../../elements/AccessibleButton"; import AccessibleButton from "../../../elements/AccessibleButton";
@ -307,9 +307,7 @@ export default class SecurityUserSettingsTab extends React.Component {
// in having advanced details here once all flows are implemented, we // in having advanced details here once all flows are implemented, we
// can remove this. // can remove this.
const CrossSigningPanel = sdk.getComponent('views.settings.CrossSigningPanel'); const CrossSigningPanel = sdk.getComponent('views.settings.CrossSigningPanel');
let crossSigning; const crossSigning = (
if (SettingsStore.getValue("feature_cross_signing")) {
crossSigning = (
<div className='mx_SettingsTab_section'> <div className='mx_SettingsTab_section'>
<span className="mx_SettingsTab_subheading">{_t("Cross-signing")}</span> <span className="mx_SettingsTab_subheading">{_t("Cross-signing")}</span>
<div className='mx_SettingsTab_subsectionText'> <div className='mx_SettingsTab_subsectionText'>
@ -317,7 +315,6 @@ export default class SecurityUserSettingsTab extends React.Component {
</div> </div>
</div> </div>
); );
}
const E2eAdvancedPanel = sdk.getComponent('views.settings.E2eAdvancedPanel'); const E2eAdvancedPanel = sdk.getComponent('views.settings.E2eAdvancedPanel');

View file

@ -1,123 +0,0 @@
/*
Copyright 2017 New Vector Ltd
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 Resend from './Resend';
import * as sdk from './index';
import dis from './dispatcher/dispatcher';
import Modal from './Modal';
import { _t } from './languageHandler';
/**
* Mark all given devices as 'known'
*
* @param {MatrixClient} matrixClient A MatrixClient
* @param {Object} devices Map from userid -> deviceid -> deviceinfo
*/
export function markAllDevicesKnown(matrixClient, devices) {
Object.keys(devices).forEach((userId) => {
Object.keys(devices[userId]).map((deviceId) => {
matrixClient.setDeviceKnown(userId, deviceId, true);
});
});
}
/**
* Gets all crypto devices in a room that are marked neither known
* nor verified.
*
* @param {MatrixClient} matrixClient A MatrixClient
* @param {Room} room js-sdk room object representing the room
* @return {Promise} A promise which resolves to a map userId->deviceId->{@link
* module:crypto~DeviceInfo|DeviceInfo}.
*/
export async function getUnknownDevicesForRoom(matrixClient, room) {
const roomMembers = (await room.getEncryptionTargetMembers()).map((m) => {
return m.userId;
});
const devices = await matrixClient.downloadKeys(roomMembers, false);
const unknownDevices = {};
// This is all devices in this room, so find the unknown ones.
Object.keys(devices).forEach((userId) => {
Object.keys(devices[userId]).map((deviceId) => {
const device = devices[userId][deviceId];
if (device.isUnverified() && !device.isKnown()) {
if (unknownDevices[userId] === undefined) {
unknownDevices[userId] = {};
}
unknownDevices[userId][deviceId] = device;
}
});
});
return unknownDevices;
}
function focusComposer() {
dis.dispatch({action: 'focus_composer'});
}
/**
* Show the UnknownDeviceDialog for a given room. The dialog will inform the user
* that messages they sent to this room have not been sent due to unknown devices
* being present.
*
* @param {MatrixClient} matrixClient A MatrixClient
* @param {Room} room js-sdk room object representing the room
*/
export function showUnknownDeviceDialogForMessages(matrixClient, room) {
getUnknownDevicesForRoom(matrixClient, room).then((unknownDevices) => {
const onSendClicked = () => {
Resend.resendUnsentEvents(room);
};
const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog');
Modal.createTrackedDialog('Unknown Device Dialog', '', UnknownDeviceDialog, {
room: room,
devices: unknownDevices,
sendAnywayLabel: _t("Send anyway"),
sendLabel: _t("Send"),
onSend: onSendClicked,
onFinished: focusComposer,
}, 'mx_Dialog_unknownDevice');
});
}
/**
* Show the UnknownDeviceDialog for a given room. The dialog will inform the user
* that a call they tried to place or answer in the room couldn't be placed or
* answered due to unknown devices being present.
*
* @param {MatrixClient} matrixClient A MatrixClient
* @param {Room} room js-sdk room object representing the room
* @param {func} sendAnyway Function called when the 'call anyway' or 'call'
* button is pressed. This should attempt to place or answer the call again.
* @param {string} sendAnywayLabel Label for the button displayed to retry the call
* when unknown devices are still present (eg. "Call Anyway")
* @param {string} sendLabel Label for the button displayed to retry the call
* after all devices have been verified (eg. "Call")
*/
export function showUnknownDeviceDialogForCalls(matrixClient, room, sendAnyway, sendAnywayLabel, sendLabel) {
getUnknownDevicesForRoom(matrixClient, room).then((unknownDevices) => {
const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog');
Modal.createTrackedDialog('Unknown Device Dialog', '', UnknownDeviceDialog, {
room: room,
devices: unknownDevices,
sendAnywayLabel: sendAnywayLabel,
sendLabel: sendLabel,
onSend: sendAnyway,
}, 'mx_Dialog_unknownDevice');
});
}

View file

@ -35,12 +35,6 @@
"Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.", "Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.",
"Dismiss": "Dismiss", "Dismiss": "Dismiss",
"Call Failed": "Call Failed", "Call Failed": "Call Failed",
"There are unknown sessions in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "There are unknown sessions in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.",
"Review Sessions": "Review Sessions",
"Call Anyway": "Call Anyway",
"Answer Anyway": "Answer Anyway",
"Call": "Call",
"Answer": "Answer",
"Call Timeout": "Call Timeout", "Call Timeout": "Call Timeout",
"The remote side failed to pick up": "The remote side failed to pick up", "The remote side failed to pick up": "The remote side failed to pick up",
"Call failed due to misconfigured server": "Call failed due to misconfigured server", "Call failed due to misconfigured server": "Call failed due to misconfigured server",
@ -75,8 +69,6 @@
"Enter passphrase": "Enter passphrase", "Enter passphrase": "Enter passphrase",
"Cancel": "Cancel", "Cancel": "Cancel",
"Setting up keys": "Setting up keys", "Setting up keys": "Setting up keys",
"Send anyway": "Send anyway",
"Send": "Send",
"Sun": "Sun", "Sun": "Sun",
"Mon": "Mon", "Mon": "Mon",
"Tue": "Tue", "Tue": "Tue",
@ -441,7 +433,6 @@
"Use the improved room list (in development - refresh to apply changes)": "Use the improved room list (in development - refresh to apply changes)", "Use the improved room list (in development - refresh to apply changes)": "Use the improved room list (in development - refresh to apply changes)",
"Support adding custom themes": "Support adding custom themes", "Support adding custom themes": "Support adding custom themes",
"Use IRC layout": "Use IRC layout", "Use IRC layout": "Use IRC layout",
"Enable cross-signing to verify per-user instead of per-session": "Enable cross-signing to verify per-user instead of per-session",
"Show info about bridges in room settings": "Show info about bridges in room settings", "Show info about bridges in room settings": "Show info about bridges in room settings",
"Font size": "Font size", "Font size": "Font size",
"Custom font size": "Custom font size", "Custom font size": "Custom font size",
@ -700,7 +691,6 @@
"Backup has an <validity>invalid</validity> signature from <verify>unverified</verify> session <device></device>": "Backup has an <validity>invalid</validity> signature from <verify>unverified</verify> session <device></device>", "Backup has an <validity>invalid</validity> signature from <verify>unverified</verify> session <device></device>": "Backup has an <validity>invalid</validity> signature from <verify>unverified</verify> session <device></device>",
"Backup is not signed by any of your sessions": "Backup is not signed by any of your sessions", "Backup is not signed by any of your sessions": "Backup is not signed by any of your sessions",
"This backup is trusted because it has been restored on this session": "This backup is trusted because it has been restored on this session", "This backup is trusted because it has been restored on this session": "This backup is trusted because it has been restored on this session",
"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.": "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.",
"Backup version: ": "Backup version: ", "Backup version: ": "Backup version: ",
"Algorithm: ": "Algorithm: ", "Algorithm: ": "Algorithm: ",
"Backup key stored: ": "Backup key stored: ", "Backup key stored: ": "Backup key stored: ",
@ -1015,10 +1005,6 @@
"Someone is using an unknown session": "Someone is using an unknown session", "Someone is using an unknown session": "Someone is using an unknown session",
"This room is end-to-end encrypted": "This room is end-to-end encrypted", "This room is end-to-end encrypted": "This room is end-to-end encrypted",
"Everyone in this room is verified": "Everyone in this room is verified", "Everyone in this room is verified": "Everyone in this room is verified",
"Some sessions for this user are not trusted": "Some sessions for this user are not trusted",
"All sessions for this user are trusted": "All sessions for this user are trusted",
"Some sessions in this encrypted room are not trusted": "Some sessions in this encrypted room are not trusted",
"All sessions in this encrypted room are trusted": "All sessions in this encrypted room are trusted",
"Edit message": "Edit message", "Edit message": "Edit message",
"Mod": "Mod", "Mod": "Mod",
"This event could not be displayed": "This event could not be displayed", "This event could not be displayed": "This event could not be displayed",
@ -1038,51 +1024,6 @@
"Invite only": "Invite only", "Invite only": "Invite only",
"Scroll to most recent messages": "Scroll to most recent messages", "Scroll to most recent messages": "Scroll to most recent messages",
"Close preview": "Close preview", "Close preview": "Close preview",
"device id: ": "device id: ",
"Disinvite": "Disinvite",
"Kick": "Kick",
"Disinvite this user?": "Disinvite this user?",
"Kick this user?": "Kick this user?",
"Failed to kick": "Failed to kick",
"Ban": "Ban",
"Unban this user?": "Unban this user?",
"Ban this user?": "Ban this user?",
"Failed to ban user": "Failed to ban user",
"No recent messages by %(user)s found": "No recent messages by %(user)s found",
"Try scrolling up in the timeline to see if there are any earlier ones.": "Try scrolling up in the timeline to see if there are any earlier ones.",
"Remove recent messages by %(user)s": "Remove recent messages by %(user)s",
"You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?",
"You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|one": "You are about to remove 1 message by %(user)s. This cannot be undone. Do you wish to continue?",
"For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.",
"Remove %(count)s messages|other": "Remove %(count)s messages",
"Remove %(count)s messages|one": "Remove 1 message",
"Demote yourself?": "Demote yourself?",
"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.": "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.",
"Demote": "Demote",
"Failed to mute user": "Failed to mute user",
"Failed to toggle moderator status": "Failed to toggle moderator status",
"Deactivate user?": "Deactivate user?",
"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?": "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?",
"Deactivate user": "Deactivate user",
"Failed to deactivate user": "Failed to deactivate user",
"Failed to change power level": "Failed to change power level",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.",
"Are you sure?": "Are you sure?",
"No sessions with registered encryption keys": "No sessions with registered encryption keys",
"Sessions": "Sessions",
"Jump to read receipt": "Jump to read receipt",
"Mention": "Mention",
"Invite": "Invite",
"Share Link to User": "Share Link to User",
"User Options": "User Options",
"Start a chat": "Start a chat",
"Direct chats": "Direct chats",
"Remove recent messages": "Remove recent messages",
"Unmute": "Unmute",
"Mute": "Mute",
"Revoke Moderator": "Revoke Moderator",
"Make Moderator": "Make Moderator",
"Admin Tools": "Admin Tools",
"and %(count)s others...|other": "and %(count)s others...", "and %(count)s others...|other": "and %(count)s others...",
"and %(count)s others...|one": "and one other...", "and %(count)s others...|one": "and one other...",
"Invite to this room": "Invite to this room", "Invite to this room": "Invite to this room",
@ -1098,8 +1039,6 @@
"Send a reply…": "Send a reply…", "Send a reply…": "Send a reply…",
"Send an encrypted message…": "Send an encrypted message…", "Send an encrypted message…": "Send an encrypted message…",
"Send a message…": "Send a message…", "Send a message…": "Send a message…",
"Send a reply (unencrypted)…": "Send a reply (unencrypted)…",
"Send a message (unencrypted)…": "Send a message (unencrypted)…",
"The conversation continues here.": "The conversation continues here.", "The conversation continues here.": "The conversation continues here.",
"This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.", "This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.",
"You do not have permission to post to this room": "You do not have permission to post to this room", "You do not have permission to post to this room": "You do not have permission to post to this room",
@ -1228,6 +1167,7 @@
"Show Stickers": "Show Stickers", "Show Stickers": "Show Stickers",
"Failed to revoke invite": "Failed to revoke invite", "Failed to revoke invite": "Failed to revoke invite",
"Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.", "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.",
"Admin Tools": "Admin Tools",
"Revoke invite": "Revoke invite", "Revoke invite": "Revoke invite",
"Invited by %(sender)s": "Invited by %(sender)s", "Invited by %(sender)s": "Invited by %(sender)s",
"Jump to first unread message.": "Jump to first unread message.", "Jump to first unread message.": "Jump to first unread message.",
@ -1294,13 +1234,48 @@
"%(count)s sessions|other": "%(count)s sessions", "%(count)s sessions|other": "%(count)s sessions",
"%(count)s sessions|one": "%(count)s session", "%(count)s sessions|one": "%(count)s session",
"Hide sessions": "Hide sessions", "Hide sessions": "Hide sessions",
"Jump to read receipt": "Jump to read receipt",
"Mention": "Mention",
"Invite": "Invite",
"Share Link to User": "Share Link to User",
"Direct message": "Direct message", "Direct message": "Direct message",
"Demote yourself?": "Demote yourself?",
"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.": "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.",
"Demote": "Demote",
"Disinvite": "Disinvite",
"Kick": "Kick",
"Disinvite this user?": "Disinvite this user?",
"Kick this user?": "Kick this user?",
"Failed to kick": "Failed to kick",
"No recent messages by %(user)s found": "No recent messages by %(user)s found",
"Try scrolling up in the timeline to see if there are any earlier ones.": "Try scrolling up in the timeline to see if there are any earlier ones.",
"Remove recent messages by %(user)s": "Remove recent messages by %(user)s",
"You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?",
"You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|one": "You are about to remove 1 message by %(user)s. This cannot be undone. Do you wish to continue?",
"For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.",
"Remove %(count)s messages|other": "Remove %(count)s messages",
"Remove %(count)s messages|one": "Remove 1 message",
"Remove recent messages": "Remove recent messages",
"Ban": "Ban",
"Unban this user?": "Unban this user?",
"Ban this user?": "Ban this user?",
"Failed to ban user": "Failed to ban user",
"Failed to mute user": "Failed to mute user",
"Unmute": "Unmute",
"Mute": "Mute",
"Remove from community": "Remove from community", "Remove from community": "Remove from community",
"Disinvite this user from community?": "Disinvite this user from community?", "Disinvite this user from community?": "Disinvite this user from community?",
"Remove this user from community?": "Remove this user from community?", "Remove this user from community?": "Remove this user from community?",
"Failed to withdraw invitation": "Failed to withdraw invitation", "Failed to withdraw invitation": "Failed to withdraw invitation",
"Failed to remove user from community": "Failed to remove user from community", "Failed to remove user from community": "Failed to remove user from community",
"<strong>%(role)s</strong> in %(roomName)s": "<strong>%(role)s</strong> in %(roomName)s", "<strong>%(role)s</strong> in %(roomName)s": "<strong>%(role)s</strong> in %(roomName)s",
"Failed to change power level": "Failed to change power level",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.",
"Are you sure?": "Are you sure?",
"Deactivate user?": "Deactivate user?",
"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?": "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?",
"Deactivate user": "Deactivate user",
"Failed to deactivate user": "Failed to deactivate user",
"This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.", "This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.",
"Security": "Security", "Security": "Security",
"The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what Riot supports. Try with a different client.": "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what Riot supports. Try with a different client.", "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what Riot supports. Try with a different client.": "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what Riot supports. Try with a different client.",
@ -1440,10 +1415,6 @@
"Popout widget": "Popout widget", "Popout widget": "Popout widget",
"More options": "More options", "More options": "More options",
"Create new room": "Create new room", "Create new room": "Create new room",
"Unblacklist": "Unblacklist",
"Blacklist": "Blacklist",
"Unverify": "Unverify",
"Verify...": "Verify...",
"Join": "Join", "Join": "Join",
"No results": "No results", "No results": "No results",
"Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.": "Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.", "Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.": "Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.",
@ -1616,22 +1587,8 @@
"Deactivating your account <b>does not by default cause us to forget messages you have sent.</b> If you would like us to forget your messages, please tick the box below.": "Deactivating your account <b>does not by default cause us to forget messages you have sent.</b> If you would like us to forget your messages, please tick the box below.", "Deactivating your account <b>does not by default cause us to forget messages you have sent.</b> If you would like us to forget your messages, please tick the box below.": "Deactivating your account <b>does not by default cause us to forget messages you have sent.</b> If you would like us to forget your messages, please tick the box below.",
"Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.", "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.",
"Please forget all messages I have sent when my account is deactivated (<b>Warning:</b> this will cause future users to see an incomplete view of conversations)": "Please forget all messages I have sent when my account is deactivated (<b>Warning:</b> this will cause future users to see an incomplete view of conversations)", "Please forget all messages I have sent when my account is deactivated (<b>Warning:</b> this will cause future users to see an incomplete view of conversations)": "Please forget all messages I have sent when my account is deactivated (<b>Warning:</b> this will cause future users to see an incomplete view of conversations)",
"Verify session": "Verify session",
"Use Legacy Verification (for older clients)": "Use Legacy Verification (for older clients)",
"Verify by comparing a short text string.": "Verify by comparing a short text string.",
"Begin Verifying": "Begin Verifying",
"Waiting for partner to accept...": "Waiting for partner to accept...",
"Nothing appearing? Not all clients support interactive verification yet. <button>Use legacy verification</button>.": "Nothing appearing? Not all clients support interactive verification yet. <button>Use legacy verification</button>.",
"Waiting for %(userId)s to confirm...": "Waiting for %(userId)s to confirm...",
"To verify that this session can be trusted, please check that the key you see in User Settings on that device matches the key below:": "To verify that this session can be trusted, please check that the key you see in User Settings on that device matches the key below:",
"To verify that this session can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this session matches the key below:": "To verify that this session can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this session matches the key below:",
"Use two-way text verification": "Use two-way text verification",
"Session name": "Session name",
"Session ID": "Session ID",
"Session key": "Session key",
"If it matches, press the verify button below. If it doesn't, then someone else is intercepting this session and you probably want to press the blacklist button instead.": "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this session and you probably want to press the blacklist button instead.",
"I verify that the keys match": "I verify that the keys match",
"Back": "Back", "Back": "Back",
"Send": "Send",
"Send Custom Event": "Send Custom Event", "Send Custom Event": "Send Custom Event",
"You must specify an event type!": "You must specify an event type!", "You must specify an event type!": "You must specify an event type!",
"Event sent!": "Event sent!", "Event sent!": "Event sent!",
@ -1695,7 +1652,11 @@
"Are you sure you want to sign out?": "Are you sure you want to sign out?", "Are you sure you want to sign out?": "Are you sure you want to sign out?",
"Confirm by comparing the following with the User Settings in your other session:": "Confirm by comparing the following with the User Settings in your other session:", "Confirm by comparing the following with the User Settings in your other session:": "Confirm by comparing the following with the User Settings in your other session:",
"Confirm this user's session by comparing the following with their User Settings:": "Confirm this user's session by comparing the following with their User Settings:", "Confirm this user's session by comparing the following with their User Settings:": "Confirm this user's session by comparing the following with their User Settings:",
"Session name": "Session name",
"Session ID": "Session ID",
"Session key": "Session key",
"If they don't match, the security of your communication may be compromised.": "If they don't match, the security of your communication may be compromised.", "If they don't match, the security of your communication may be compromised.": "If they don't match, the security of your communication may be compromised.",
"Verify session": "Verify session",
"Your homeserver doesn't seem to support this feature.": "Your homeserver doesn't seem to support this feature.", "Your homeserver doesn't seem to support this feature.": "Your homeserver doesn't seem to support this feature.",
"Message edits": "Message edits", "Message edits": "Message edits",
"Your account is not secure": "Your account is not secure", "Your account is not secure": "Your account is not secure",
@ -1781,11 +1742,6 @@
"Summary": "Summary", "Summary": "Summary",
"Document": "Document", "Document": "Document",
"Next": "Next", "Next": "Next",
"You are currently blacklisting unverified sessions; to send messages to these sessions you must verify them.": "You are currently blacklisting unverified sessions; to send messages to these sessions you must verify them.",
"We recommend you go through the verification process for each session to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "We recommend you go through the verification process for each session to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.",
"Room contains unknown sessions": "Room contains unknown sessions",
"\"%(RoomName)s\" contains sessions that you haven't seen before.": "\"%(RoomName)s\" contains sessions that you haven't seen before.",
"Unknown sessions": "Unknown sessions",
"Upload files (%(current)s of %(total)s)": "Upload files (%(current)s of %(total)s)", "Upload files (%(current)s of %(total)s)": "Upload files (%(current)s of %(total)s)",
"Upload files": "Upload files", "Upload files": "Upload files",
"Upload all": "Upload all", "Upload all": "Upload all",
@ -2049,8 +2005,6 @@
"Find a room… (e.g. %(exampleRoom)s)": "Find a room… (e.g. %(exampleRoom)s)", "Find a room… (e.g. %(exampleRoom)s)": "Find a room… (e.g. %(exampleRoom)s)",
"If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.": "If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.", "If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.": "If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.",
"Explore rooms": "Explore rooms", "Explore rooms": "Explore rooms",
"Message not sent due to unknown sessions being present": "Message not sent due to unknown sessions being present",
"<showSessionsText>Show sessions</showSessionsText>, <sendAnywayText>send anyway</sendAnywayText> or <cancelText>cancel</cancelText>.": "<showSessionsText>Show sessions</showSessionsText>, <sendAnywayText>send anyway</sendAnywayText> or <cancelText>cancel</cancelText>.",
"You can't send any messages until you review and agree to <consentLink>our terms and conditions</consentLink>.": "You can't send any messages until you review and agree to <consentLink>our terms and conditions</consentLink>.", "You can't send any messages until you review and agree to <consentLink>our terms and conditions</consentLink>.": "You can't send any messages until you review and agree to <consentLink>our terms and conditions</consentLink>.",
"Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please <a>contact your service administrator</a> to continue using the service.": "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please <a>contact your service administrator</a> to continue using the service.", "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please <a>contact your service administrator</a> to continue using the service.": "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please <a>contact your service administrator</a> to continue using the service.",
"Your message wasn't sent because this homeserver has exceeded a resource limit. Please <a>contact your service administrator</a> to continue using the service.": "Your message wasn't sent because this homeserver has exceeded a resource limit. Please <a>contact your service administrator</a> to continue using the service.", "Your message wasn't sent because this homeserver has exceeded a resource limit. Please <a>contact your service administrator</a> to continue using the service.": "Your message wasn't sent because this homeserver has exceeded a resource limit. Please <a>contact your service administrator</a> to continue using the service.",

View file

@ -164,13 +164,6 @@ export const SETTINGS = {
supportedLevels: ['account'], supportedLevels: ['account'],
default: null, 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": { "feature_bridge_state": {
isFeature: true, isFeature: true,
supportedLevels: LEVELS_FEATURE, supportedLevels: LEVELS_FEATURE,

View file

@ -22,12 +22,11 @@ import { _t } from './languageHandler';
import {RIGHT_PANEL_PHASES} from "./stores/RightPanelStorePhases"; import {RIGHT_PANEL_PHASES} from "./stores/RightPanelStorePhases";
import {findDMForUser} from './createRoom'; import {findDMForUser} from './createRoom';
import {accessSecretStorage} from './CrossSigningManager'; import {accessSecretStorage} from './CrossSigningManager';
import SettingsStore from './settings/SettingsStore';
import {verificationMethods} from 'matrix-js-sdk/src/crypto'; import {verificationMethods} from 'matrix-js-sdk/src/crypto';
async function enable4SIfNeeded() { async function enable4SIfNeeded() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
if (!cli.isCryptoEnabled() || !SettingsStore.getValue("feature_cross_signing")) { if (!cli.isCryptoEnabled()) {
return false; return false;
} }
const usk = cli.getCrossSigningId("user_signing"); const usk = cli.getCrossSigningId("user_signing");