diff --git a/res/css/_components.scss b/res/css/_components.scss index a2c6d4bb77b..69edd301d04 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -144,6 +144,7 @@ @import "./views/settings/tabs/_HelpSettingsTab.scss"; @import "./views/settings/tabs/_PreferencesSettingsTab.scss"; @import "./views/settings/tabs/_RolesRoomSettingsTab.scss"; +@import "./views/settings/tabs/_SecurityRoomSettingsTab.scss"; @import "./views/settings/tabs/_SecuritySettingsTab.scss"; @import "./views/settings/tabs/_SettingsTab.scss"; @import "./views/settings/tabs/_VoiceSettingsTab.scss"; diff --git a/res/css/views/elements/_ToggleSwitch.scss b/res/css/views/elements/_ToggleSwitch.scss index 4076414086f..1bb3a74ab11 100644 --- a/res/css/views/elements/_ToggleSwitch.scss +++ b/res/css/views/elements/_ToggleSwitch.scss @@ -21,10 +21,12 @@ limitations under the License. border-radius: 14px; background-color: $togglesw-off-color; position: relative; + opacity: 0.5; } .mx_ToggleSwitch_enabled { cursor: pointer; + opacity: 1; } .mx_ToggleSwitch.mx_ToggleSwitch_on { diff --git a/res/css/views/settings/tabs/_SecurityRoomSettingsTab.scss b/res/css/views/settings/tabs/_SecurityRoomSettingsTab.scss new file mode 100644 index 00000000000..beab1254de7 --- /dev/null +++ b/res/css/views/settings/tabs/_SecurityRoomSettingsTab.scss @@ -0,0 +1,34 @@ +/* +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_SecurityRoomSettingsTab label { + display: block; +} + +.mx_SecurityRoomSettingsTab_warning { + display: block; + + img { + vertical-align: middle; + margin-right: 5px; + margin-left: 3px; + margin-bottom: 5px; + } +} + +.mx_SecurityRoomSettingsTab_encryptionSection { + margin-bottom: 25px; +} \ No newline at end of file diff --git a/src/components/views/dialogs/RoomSettingsDialog.js b/src/components/views/dialogs/RoomSettingsDialog.js index df35d8fadc1..6be2676d72e 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.js +++ b/src/components/views/dialogs/RoomSettingsDialog.js @@ -23,6 +23,7 @@ import AccessibleButton from "../elements/AccessibleButton"; import dis from '../../../dispatcher'; import RolesRoomSettingsTab from "../settings/tabs/RolesRoomSettingsTab"; import GeneralRoomSettingsTab from "../settings/tabs/GeneralRoomSettingsTab"; +import SecurityRoomSettingsTab from "../settings/tabs/SecurityRoomSettingsTab"; // TODO: Ditch this whole component export class TempTab extends React.Component { @@ -70,7 +71,7 @@ export default class RoomSettingsDialog extends React.Component { tabs.push(new Tab( _td("Security & Privacy"), "mx_RoomSettingsDialog_securityIcon", -
Security Test
, + , )); tabs.push(new Tab( _td("Roles & Permissions"), diff --git a/src/components/views/elements/LabelledToggleSwitch.js b/src/components/views/elements/LabelledToggleSwitch.js index 3d3025ad065..292c978e88e 100644 --- a/src/components/views/elements/LabelledToggleSwitch.js +++ b/src/components/views/elements/LabelledToggleSwitch.js @@ -28,6 +28,9 @@ export default class LabelledToggleSwitch extends React.Component { // The translated label for the switch label: PropTypes.string.isRequired, + + // Whether or not to disable the toggle switch + disabled: PropTypes.bool, }; render() { @@ -35,7 +38,8 @@ export default class LabelledToggleSwitch extends React.Component { return (
{this.props.label} - +
); } diff --git a/src/components/views/settings/tabs/SecurityRoomSettingsTab.js b/src/components/views/settings/tabs/SecurityRoomSettingsTab.js new file mode 100644 index 00000000000..d7645cd0ac0 --- /dev/null +++ b/src/components/views/settings/tabs/SecurityRoomSettingsTab.js @@ -0,0 +1,272 @@ +/* +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. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import {_t} from "../../../../languageHandler"; +import MatrixClientPeg from "../../../../MatrixClientPeg"; +import sdk from "../../../../index"; +import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch"; +import {SettingLevel} from "../../../../settings/SettingsStore"; +import Modal from "../../../../Modal"; + +export default class SecurityRoomSettingsTab extends React.Component { + static propTypes = { + roomId: PropTypes.string.isRequired, + }; + + componentWillMount(): void { + MatrixClientPeg.get().on("RoomState.events", this._onStateEvent); + } + + componentWillUnmount(): void { + MatrixClientPeg.get().removeListener("RoomState.events", this._onStateEvent); + } + + _onStateEvent = (e) => { + const refreshWhenTypes = ['m.room.join_rules', 'm.room.guest_access', 'm.room.history_visibility']; + if (refreshWhenTypes.includes(e.getType())) this.forceUpdate(); + }; + + _onEncryptionChange = (e) => { + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + Modal.createTrackedDialog('E2E Enable Warning', '', QuestionDialog, { + title: _t('Warning!'), + description: ( +
+

{ _t('End-to-end encryption is in beta and may not be reliable') }.

+

{ _t('You should not yet trust it to secure data') }.

+

{ _t('Devices will not yet be able to decrypt history from before they joined the room') }.

+

{ _t('Once encryption is enabled for a room it cannot be turned off again (for now)') }.

+

{ _t('Encrypted messages will not be visible on clients that do not yet implement encryption') }.

+
+ ), + onFinished: (confirm)=>{ + if (confirm) { + return MatrixClientPeg.get().sendStateEvent( + this.props.roomId, "m.room.encryption", + { algorithm: "m.megolm.v1.aes-sha2" }, + ); + } + }, + }); + }; + + _fixGuestAccess = (e) => { + e.preventDefault(); + e.stopPropagation(); + + const client = MatrixClientPeg.get(); + client.sendStateEvent(this.props.roomId, "m.room.join_rules", {join_rule: "invite"}, ""); + client.sendStateEvent(this.props.roomId, "m.room.guest_access", {guest_access: "can_join"}, ""); + }; + + _onRoomAccessRadioToggle = (ev) => { + // join_rule + // INVITE | PUBLIC + // ----------------------+---------------- + // guest CAN_JOIN | inv_only | pub_with_guest + // access ----------------------+---------------- + // FORBIDDEN | inv_only | pub_no_guest + // ----------------------+---------------- + + // we always set guests can_join here as it makes no sense to have + // an invite-only room that guests can't join. If you explicitly + // invite them, you clearly want them to join, whether they're a + // guest or not. In practice, guest_access should probably have + // been implemented as part of the join_rules enum. + let joinRule = "invite"; + let guestAccess = "can_join"; + + switch (ev.target.value) { + case "invite_only": + // no change - use defaults above + break; + case "public_no_guests": + joinRule = "public"; + guestAccess = "forbidden"; + break; + case "public_with_guests": + joinRule = "public"; + guestAccess = "can_join"; + break; + } + + const client = MatrixClientPeg.get(); + client.sendStateEvent(this.props.roomId, "m.room.join_rules", {join_rule: joinRule}, ""); + client.sendStateEvent(this.props.roomId, "m.room.guest_access", {guest_access: guestAccess}, ""); + }; + + _onHistoryRadioToggle = (ev) => { + MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.history_visibility", { + history_visibility: ev.target.value, + }, ""); + }; + + _renderRoomAccess() { + const client = MatrixClientPeg.get(); + const room = client.getRoom(this.props.roomId); + const joinRule = room.currentState.getStateEvents("m.room.join_rules", "").getContent()['join_rule']; + const guestAccess = room.currentState.getStateEvents("m.room.guest_access", "").getContent()['guest_access']; + const aliasEvents = room.currentState.getStateEvents("m.room.aliases") || []; + const hasAliases = aliasEvents.includes((ev) => (ev.getContent().aliases || []).length); + + const canChangeAccess = room.currentState.mayClientSendStateEvent("m.room.join_rules", client) + && room.currentState.mayClientSendStateEvent("m.room.guest_access", client); + + let guestWarning = null; + if (joinRule !== 'public' && guestAccess === 'forbidden') { + guestWarning = ( +
+ + + {_t("Guests cannot join this room even if explicitly invited.")}  + {_t("Click here to fix")} + +
+ ); + } + + let aliasWarning = null; + if (joinRule === 'public' && !hasAliases) { + aliasWarning = ( +
+ + + {_t("To link to this room, please add an alias.")} + +
+ ); + } + + return ( +
+ {guestWarning} + {aliasWarning} + + + +
+ ); + } + + _renderHistory() { + const client = MatrixClientPeg.get(); + const room = client.getRoom(this.props.roomId); + const state = room.currentState; + const history = state.getStateEvents("m.room.history_visibility", "").getContent()['history_visibility']; + const canChangeHistory = state.mayClientSendStateEvent('m.room.history_visibility', client); + + return ( +
+
+ {_t('Changes to who can read history will only apply to future messages in this room. ' + + 'The visibility of existing history will be unchanged.')} +
+ + + + +
+ ); + } + + render() { + const SettingsFlag = sdk.getComponent("elements.SettingsFlag"); + + const client = MatrixClientPeg.get(); + const room = client.getRoom(this.props.roomId); + const isEncrypted = client.isRoomEncrypted(this.props.roomId); + const hasEncryptionPermission = room.currentState.mayClientSendStateEvent("m.room.encryption", client); + const canEnableEncryption = !isEncrypted && hasEncryptionPermission; + + let encryptionSettings = null; + if (isEncrypted) { + encryptionSettings = ; + } + + return ( +
+
{_t("Security & Privacy")}
+ + {_t("Encryption")} +
+
+
+ {_t("Once enabled, encryption cannot be disabled.")} +
+ +
+ {encryptionSettings} +
+ + {_t("Who can access this room?")} +
+ {this._renderRoomAccess()} +
+ + {_t("Who can read history?")} +
+ {this._renderHistory()} +
+
+ ); + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a0b37a61e1d..341b9754a5c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -515,6 +515,28 @@ "To send events of type , you must be a": "To send events of type , you must be a", "Roles & Permissions": "Roles & Permissions", "Permissions": "Permissions", + "End-to-end encryption is in beta and may not be reliable": "End-to-end encryption is in beta and may not be reliable", + "You should not yet trust it to secure data": "You should not yet trust it to secure data", + "Devices will not yet be able to decrypt history from before they joined the room": "Devices will not yet be able to decrypt history from before they joined the room", + "Once encryption is enabled for a room it cannot be turned off again (for now)": "Once encryption is enabled for a room it cannot be turned off again (for now)", + "Encrypted messages will not be visible on clients that do not yet implement encryption": "Encrypted messages will not be visible on clients that do not yet implement encryption", + "Guests cannot join this room even if explicitly invited.": "Guests cannot join this room even if explicitly invited.", + "Click here to fix": "Click here to fix", + "To link to this room, please add an alias.": "To link to this room, please add an alias.", + "Only people who have been invited": "Only people who have been invited", + "Anyone who knows the room's link, apart from guests": "Anyone who knows the room's link, apart from guests", + "Anyone who knows the room's link, including guests": "Anyone who knows the room's link, including guests", + "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.": "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.", + "Anyone": "Anyone", + "Members only (since the point in time of selecting this option)": "Members only (since the point in time of selecting this option)", + "Members only (since they were invited)": "Members only (since they were invited)", + "Members only (since they joined)": "Members only (since they joined)", + "Security & Privacy": "Security & Privacy", + "Encryption": "Encryption", + "Once enabled, encryption cannot be disabled.": "Once enabled, encryption cannot be disabled.", + "Encrypted": "Encrypted", + "Who can access this room?": "Who can access this room?", + "Who can read history?": "Who can read history?", "Unignore": "Unignore", "": "", "Import E2E room keys": "Import E2E room keys", @@ -525,7 +547,6 @@ "Bulk options": "Bulk options", "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites", "Key backup": "Key backup", - "Security & Privacy": "Security & Privacy", "Devices": "Devices", "Riot collects anonymous analytics to allow us to improve the application.": "Riot collects anonymous analytics to allow us to improve the application.", "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.", @@ -724,11 +745,6 @@ "The visibility of existing history will be unchanged": "The visibility of existing history will be unchanged", "unknown error code": "unknown error code", "Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s", - "End-to-end encryption is in beta and may not be reliable": "End-to-end encryption is in beta and may not be reliable", - "You should not yet trust it to secure data": "You should not yet trust it to secure data", - "Devices will not yet be able to decrypt history from before they joined the room": "Devices will not yet be able to decrypt history from before they joined the room", - "Once encryption is enabled for a room it cannot be turned off again (for now)": "Once encryption is enabled for a room it cannot be turned off again (for now)", - "Encrypted messages will not be visible on clients that do not yet implement encryption": "Encrypted messages will not be visible on clients that do not yet implement encryption", "Enable encryption": "Enable encryption", "(warning: cannot be disabled again!)": "(warning: cannot be disabled again!)", "Encryption is enabled in this room": "Encryption is enabled in this room", @@ -736,18 +752,7 @@ "Favourite": "Favourite", "Tagged as: ": "Tagged as: ", "To link to a room it must have an address.": "To link to a room it must have an address.", - "Guests cannot join this room even if explicitly invited.": "Guests cannot join this room even if explicitly invited.", - "Click here to fix": "Click here to fix", - "Who can access this room?": "Who can access this room?", - "Only people who have been invited": "Only people who have been invited", - "Anyone who knows the room's link, apart from guests": "Anyone who knows the room's link, apart from guests", - "Anyone who knows the room's link, including guests": "Anyone who knows the room's link, including guests", "Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?", - "Who can read history?": "Who can read history?", - "Anyone": "Anyone", - "Members only (since the point in time of selecting this option)": "Members only (since the point in time of selecting this option)", - "Members only (since they were invited)": "Members only (since they were invited)", - "Members only (since they joined)": "Members only (since they joined)", "Internal room ID: ": "Internal room ID: ", "Room version number: ": "Room version number: ", "Add a topic": "Add a topic",