Merge pull request #300 from matrix-org/rav/device_verification

Support for marking devices as verified
This commit is contained in:
Richard van der Hoff 2016-06-09 10:44:42 +01:00
commit ef764c112e
5 changed files with 218 additions and 23 deletions

View file

@ -79,6 +79,7 @@ module.exports.components['views.rooms.EntityTile'] = require('./components/view
module.exports.components['views.rooms.EventTile'] = require('./components/views/rooms/EventTile'); module.exports.components['views.rooms.EventTile'] = require('./components/views/rooms/EventTile');
module.exports.components['views.rooms.InviteMemberList'] = require('./components/views/rooms/InviteMemberList'); module.exports.components['views.rooms.InviteMemberList'] = require('./components/views/rooms/InviteMemberList');
module.exports.components['views.rooms.LinkPreviewWidget'] = require('./components/views/rooms/LinkPreviewWidget'); module.exports.components['views.rooms.LinkPreviewWidget'] = require('./components/views/rooms/LinkPreviewWidget');
module.exports.components['views.rooms.MemberDeviceInfo'] = require('./components/views/rooms/MemberDeviceInfo');
module.exports.components['views.rooms.MemberInfo'] = require('./components/views/rooms/MemberInfo'); module.exports.components['views.rooms.MemberInfo'] = require('./components/views/rooms/MemberInfo');
module.exports.components['views.rooms.MemberList'] = require('./components/views/rooms/MemberList'); module.exports.components['views.rooms.MemberList'] = require('./components/views/rooms/MemberList');
module.exports.components['views.rooms.MemberTile'] = require('./components/views/rooms/MemberTile'); module.exports.components['views.rooms.MemberTile'] = require('./components/views/rooms/MemberTile');

View file

@ -246,6 +246,23 @@ module.exports = React.createClass({
}); });
}, },
_renderDeviceInfo: function() {
var client = MatrixClientPeg.get();
var deviceId = client.deviceId;
var olmKey = client.getDeviceEd25519Key() || "<not supported>";
return (
<div>
<h3>Cryptography</h3>
<div className="mx_UserSettings_section">
<ul>
<li>Device ID: {deviceId}</li>
<li>Device key: {olmKey}</li>
</ul>
</div>
</div>
);
},
render: function() { render: function() {
var self = this; var self = this;
var Loader = sdk.getComponent("elements.Spinner"); var Loader = sdk.getComponent("elements.Spinner");
@ -392,6 +409,8 @@ module.exports = React.createClass({
{notification_area} {notification_area}
{this._renderDeviceInfo()}
<h3>Advanced</h3> <h3>Advanced</h3>
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section">

View file

@ -128,16 +128,24 @@ module.exports = React.createClass({
}, },
getInitialState: function() { getInitialState: function() {
return {menu: false, allReadAvatars: false}; return {menu: false, allReadAvatars: false, verified: null};
}, },
componentWillMount: function() { componentWillMount: function() {
// don't do RR animations until we are mounted // don't do RR animations until we are mounted
this._suppressReadReceiptAnimation = true; this._suppressReadReceiptAnimation = true;
this._verifyEvent(this.props.mxEvent);
}, },
componentDidMount: function() { componentDidMount: function() {
this._suppressReadReceiptAnimation = false; this._suppressReadReceiptAnimation = false;
MatrixClientPeg.get().on("deviceVerified", this.onDeviceVerified);
},
componentWillReceiveProps: function (nextProps) {
if (nextProps.mxEvent !== this.props.mxEvent) {
this._verifyEvent(nextProps.mxEvent);
}
}, },
shouldComponentUpdate: function (nextProps, nextState) { shouldComponentUpdate: function (nextProps, nextState) {
@ -152,6 +160,31 @@ module.exports = React.createClass({
return false; return false;
}, },
componentWillUnmount: function() {
var client = MatrixClientPeg.get();
if (client) {
client.removeListener("deviceVerified", this.onDeviceVerified);
}
},
onDeviceVerified: function(userId, device) {
if (userId == this.props.mxEvent.getSender()) {
this._verifyEvent(this.props.mxEvent);
}
},
_verifyEvent: function(mxEvent) {
var verified = null;
if (mxEvent.isEncrypted()) {
verified = MatrixClientPeg.get().isEventSenderVerified(mxEvent);
}
this.setState({
verified: verified
});
},
_propsEqual: function(objA, objB) { _propsEqual: function(objA, objB) {
var keysA = Object.keys(objA); var keysA = Object.keys(objA);
var keysB = Object.keys(objB); var keysB = Object.keys(objB);
@ -346,6 +379,8 @@ module.exports = React.createClass({
mx_EventTile_last: this.props.last, mx_EventTile_last: this.props.last,
mx_EventTile_contextual: this.props.contextual, mx_EventTile_contextual: this.props.contextual,
menu: this.state.menu, menu: this.state.menu,
mx_EventTile_verified: this.state.verified == true,
mx_EventTile_unverified: this.state.verified == false,
}); });
var timestamp = <a href={ "#/room/" + this.props.mxEvent.getRoomId() +"/"+ this.props.mxEvent.getId() }> var timestamp = <a href={ "#/room/" + this.props.mxEvent.getRoomId() +"/"+ this.props.mxEvent.getId() }>
<MessageTimestamp ts={this.props.mxEvent.getTs()} /> <MessageTimestamp ts={this.props.mxEvent.getTs()} />

View file

@ -0,0 +1,55 @@
/*
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.
*/
var React = require('react');
var MatrixClientPeg = require("../../../MatrixClientPeg");
module.exports = React.createClass({
displayName: 'MemberDeviceInfo',
propTypes: {
userId: React.PropTypes.string.isRequired,
device: React.PropTypes.object.isRequired,
},
onVerifyClick: function() {
MatrixClientPeg.get().setDeviceVerified(this.props.userId,
this.props.device.id);
},
render: function() {
var indicator = null, button = null;
if (this.props.device.verified) {
indicator = (
<div className="mx_MemberDeviceInfo_verified">&#x2714;</div>
);
} else {
button = (
<div className="mx_MemberDeviceInfo_textButton"
onClick={this.onVerifyClick}>
Verify
</div>
);
}
return (
<div className="mx_MemberDeviceInfo">
<div className="mx_MemberDeviceInfo_deviceId">{this.props.device.id}</div>
<div className="mx_MemberDeviceInfo_deviceKey">{this.props.device.key}</div>
{indicator}
{button}
</div>
);
},
});

View file

@ -34,23 +34,95 @@ var sdk = require('../../../index');
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'MemberInfo', displayName: 'MemberInfo',
propTypes: {
member: React.PropTypes.object.isRequired,
onFinished: React.PropTypes.func,
},
getDefaultProps: function() { getDefaultProps: function() {
return { return {
onFinished: function() {} onFinished: function() {}
}; };
}, },
componentDidMount: function() { getInitialState: function() {
// work out the current state return {
if (this.props.member) { can: {
var memberState = this._calculateOpsPermissions(this.props.member); kick: false,
this.setState(memberState); ban: false,
mute: false,
modifyLevel: false
},
muted: false,
isTargetMod: false,
updating: 0,
devices: null, // null means device list is loading
} }
}, },
componentWillMount: function() {
this._cancelDeviceList = null;
},
componentDidMount: function() {
this._updateStateForNewMember(this.props.member);
MatrixClientPeg.get().on("deviceVerified", this.onDeviceVerified);
},
componentWillReceiveProps: function(newProps) { componentWillReceiveProps: function(newProps) {
var memberState = this._calculateOpsPermissions(newProps.member); if (this.props.member.userId != newProps.member.userId) {
this.setState(memberState); this._updateStateForNewMember(newProps.member);
}
},
componentWillUnmount: function() {
var client = MatrixClientPeg.get();
if (client) {
client.removeListener("deviceVerified", this.onDeviceVerified);
}
if (this._cancelDeviceList) {
this._cancelDeviceList();
}
},
onDeviceVerified: function(userId, device) {
if (userId == this.props.member.userId) {
// no need to re-download the whole thing; just update our copy of
// the list.
var devices = MatrixClientPeg.get().listDeviceKeys(userId);
this.setState({devices: devices});
}
},
_updateStateForNewMember: function(member) {
var newState = this._calculateOpsPermissions(member);
newState.devices = null;
this.setState(newState);
if (this._cancelDeviceList) {
this._cancelDeviceList();
this._cancelDeviceList = null;
}
this._downloadDeviceList(member);
},
_downloadDeviceList: function(member) {
var cancelled = false;
this._cancelDeviceList = function() { cancelled = true; }
var client = MatrixClientPeg.get();
var self = this;
client.downloadKeys([member.userId], true).done(function() {
if (cancelled) {
// we got cancelled - presumably a different user now
return;
}
self._cancelDeviceList = null;
var devices = client.listDeviceKeys(member.userId);
self.setState({devices: devices});
});
}, },
onKick: function() { onKick: function() {
@ -371,20 +443,6 @@ module.exports = React.createClass({
this.props.onFinished(); this.props.onFinished();
}, },
getInitialState: function() {
return {
can: {
kick: false,
ban: false,
mute: false,
modifyLevel: false
},
muted: false,
isTargetMod: false,
updating: 0,
}
},
_calculateOpsPermissions: function(member) { _calculateOpsPermissions: function(member) {
var defaultPerms = { var defaultPerms = {
can: {}, can: {},
@ -476,6 +534,32 @@ module.exports = React.createClass({
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
}, },
_renderDevices: function() {
var devices = this.state.devices;
var MemberDeviceInfo = sdk.getComponent('rooms.MemberDeviceInfo');
var Spinner = sdk.getComponent("elements.Spinner");
var devComponents;
if (devices === null) {
// still loading
devComponents = <Spinner />;
} else {
devComponents = [];
for (var i = 0; i < devices.length; i++) {
devComponents.push(<MemberDeviceInfo key={i}
userId={this.props.member.userId}
device={devices[i]}/>);
}
}
return (
<div>
<h3>Devices</h3>
{devComponents}
</div>
);
},
render: function() { render: function() {
var startChat, kickButton, banButton, muteButton, giveModButton, spinner; var startChat, kickButton, banButton, muteButton, giveModButton, spinner;
if (this.props.member.userId !== MatrixClientPeg.get().credentials.userId) { if (this.props.member.userId !== MatrixClientPeg.get().credentials.userId) {
@ -552,6 +636,8 @@ module.exports = React.createClass({
{ startChat } { startChat }
{ this._renderDevices() }
{ adminTools } { adminTools }
{ spinner } { spinner }
@ -559,4 +645,3 @@ module.exports = React.createClass({
); );
} }
}); });