diff --git a/src/Resend.js b/src/Resend.js index 59a8bd4192..ad0f58eb9b 100644 --- a/src/Resend.js +++ b/src/Resend.js @@ -32,7 +32,8 @@ module.exports = { if (err.name === "UnknownDeviceError") { var UnknownDeviceDialog = sdk.getComponent("dialogs.UnknownDeviceDialog"); Modal.createDialog(UnknownDeviceDialog, { - devices: err.devices + devices: err.devices, + room: MatrixClientPeg.get().getRoom(event.getRoomId()), }, "mx_Dialog_unknownDevice"); } diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index e5dba62ee7..d7d3e7bc7a 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -149,6 +149,23 @@ module.exports = { return MatrixClientPeg.get().setAccountData("im.vector.web.settings", settings); }, + getLocalSettings: function() { + var localSettingsString = localStorage.getItem('mx_local_settings') || '{}'; + return JSON.parse(localSettingsString); + }, + + getLocalSetting: function(type, defaultValue = null) { + var settings = this.getLocalSettings(); + return settings.hasOwnProperty(type) ? settings[type] : null; + }, + + setLocalSetting: function(type, value) { + var settings = this.getLocalSettings(); + settings[type] = value; + // FIXME: handle errors + localStorage.setItem('mx_local_settings', JSON.stringify(settings)); + }, + isFeatureEnabled: function(feature: string): boolean { // Disable labs for guests. if (MatrixClientPeg.get().isGuest()) return false; diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 3d330e3649..ff19e7c239 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -59,6 +59,18 @@ const SETTINGS_LABELS = [ */ ]; +const CRYPTO_SETTINGS_LABELS = [ + { + id: 'blacklistUnverifiedDevices', + label: 'Never send encrypted messages to unverified devices from this device', + }, + // XXX: this is here for documentation; the actual setting is managed via RoomSettings + // { + // id: 'blacklistUnverifiedDevicesPerRoom' + // label: 'Never send encrypted messages to unverified devices in this room', + // } +]; + // Enumerate the available themes, with a nice human text label. // 'id' gives the key name in the im.vector.web.settings account data event // 'value' is the value for that key in the event @@ -151,6 +163,8 @@ module.exports = React.createClass({ syncedSettings.theme = 'light'; } this._syncedSettings = syncedSettings; + + this._localSettings = UserSettingsStore.getLocalSettings(); }, componentDidMount: function() { @@ -566,10 +580,34 @@ module.exports = React.createClass({ {exportButton} {importButton} +
+ { CRYPTO_SETTINGS_LABELS.map( this._renderLocalSetting ) } +
); }, + _renderLocalSetting: function(setting) { + const client = MatrixClientPeg.get(); + return
+ { + UserSettingsStore.setLocalSetting(setting.id, e.target.checked) + if (setting.id === 'blacklistUnverifiedDevices') { // XXX: this is a bit ugly + client.setGlobalBlacklistUnverifiedDevices(e.target.checked); + } + } + } + /> + +
; + }, + _renderDevicesPanel: function() { var DevicesPanel = sdk.getComponent('settings.DevicesPanel'); return ( diff --git a/src/components/views/dialogs/UnknownDeviceDialog.js b/src/components/views/dialogs/UnknownDeviceDialog.js index be1138fe43..409852a2cc 100644 --- a/src/components/views/dialogs/UnknownDeviceDialog.js +++ b/src/components/views/dialogs/UnknownDeviceDialog.js @@ -19,19 +19,47 @@ import sdk from '../../../index'; import MatrixClientPeg from '../../../MatrixClientPeg'; import GeminiScrollbar from 'react-gemini-scrollbar'; +function DeviceListEntry(props) { + const {userId, device} = props; + + const DeviceVerifyButtons = sdk.getComponent('elements.DeviceVerifyButtons'); + + return ( +
  • + + { device.deviceId } +
    + { device.getDisplayName() } +
  • + ); +} + +DeviceListEntry.propTypes = { + userId: React.PropTypes.string.isRequired, + + // deviceinfo + device: React.PropTypes.object.isRequired, +}; + + function UserUnknownDeviceList(props) { - const {userDevices} = props; + const {userId, userDevices} = props; const deviceListEntries = Object.keys(userDevices).map((deviceId) => -
  • - { deviceId } ( { userDevices[deviceId].getDisplayName() } ) -
  • , + , ); - return ; + return ( + + ); } UserUnknownDeviceList.propTypes = { + userId: React.PropTypes.string.isRequired, + // map from deviceid -> deviceinfo userDevices: React.PropTypes.object.isRequired, }; @@ -43,7 +71,7 @@ function UnknownDeviceList(props) { const userListEntries = Object.keys(devices).map((userId) =>
  • { userId }:

    - +
  • , ); @@ -60,6 +88,8 @@ export default React.createClass({ displayName: 'UnknownEventDialog', propTypes: { + room: React.PropTypes.object.isRequired, + // map from userid -> deviceid -> deviceinfo devices: React.PropTypes.object.isRequired, onFinished: React.PropTypes.func.isRequired, @@ -76,6 +106,34 @@ export default React.createClass({ }, render: function() { + const client = MatrixClientPeg.get(); + const blacklistUnverified = client.getGlobalBlacklistUnverifiedDevices() || + this.props.room.getBlacklistUnverifiedDevices(); + + let warning; + if (blacklistUnverified) { + warning = ( +

    + You are currently blacklisting unverified devices; to send + messages to these devices you must verify them. +

    + ); + } else { + warning = ( +
    +

    + This means there is no guarantee that the devices + belong to the users they claim to. +

    +

    + We recommend you go through the verification process + for each device before continuing, but you can resend + the message without verifying if you prefer. +

    +
    + ); + } + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return ( -

    This room contains devices which have not been - verified.

    -

    - This means there is no guarantee that the devices belong - to a rightful user of the room. -

    - We recommend you go through the verification process - for each device before continuing, but you can resend - the message without verifying if you prefer. -

    -

    Unknown devices:

    +

    + This room contains unknown devices which have not been + verified. +

    + { warning } + Unknown devices: +
    @@ -104,5 +158,7 @@ export default React.createClass({
    ); + // 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... }, }); diff --git a/src/components/views/elements/DeviceVerifyButtons.js b/src/components/views/elements/DeviceVerifyButtons.js index da3975e4db..7e209232b6 100644 --- a/src/components/views/elements/DeviceVerifyButtons.js +++ b/src/components/views/elements/DeviceVerifyButtons.js @@ -27,6 +27,28 @@ export default React.createClass({ device: React.PropTypes.object.isRequired, }, + getInitialState: function() { + return { + device: this.props.device + }; + }, + + componentWillMount: function() { + const cli = MatrixClientPeg.get(); + cli.on("deviceVerificationChanged", this.onDeviceVerificationChanged); + }, + + componentWillUnmount: function() { + const cli = MatrixClientPeg.get(); + cli.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); + }, + + onDeviceVerificationChanged: function(userId, deviceId) { + if (userId === this.props.userId && deviceId === this.props.device.deviceId) { + this.setState({ device: MatrixClientPeg.get().getStoredDevice(userId, deviceId) }); + } + }, + onVerifyClick: function() { var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); Modal.createDialog(QuestionDialog, { @@ -41,9 +63,9 @@ export default React.createClass({

    @@ -60,7 +82,7 @@ export default React.createClass({ onFinished: confirm=>{ if (confirm) { MatrixClientPeg.get().setDeviceVerified( - this.props.userId, this.props.device.deviceId, true + this.props.userId, this.state.device.deviceId, true ); } }, @@ -69,26 +91,26 @@ export default React.createClass({ onUnverifyClick: function() { MatrixClientPeg.get().setDeviceVerified( - this.props.userId, this.props.device.deviceId, false + this.props.userId, this.state.device.deviceId, false ); }, onBlacklistClick: function() { MatrixClientPeg.get().setDeviceBlocked( - this.props.userId, this.props.device.deviceId, true + this.props.userId, this.state.device.deviceId, true ); }, onUnblacklistClick: function() { MatrixClientPeg.get().setDeviceBlocked( - this.props.userId, this.props.device.deviceId, false + this.props.userId, this.state.device.deviceId, false ); }, render: function() { var blacklistButton = null, verifyButton = null; - if (this.props.device.isBlocked()) { + if (this.state.device.isBlocked()) { blacklistButton = (