Commit 020970c9 authored by kelvinp's avatar kelvinp Committed by Commit bot

Hangouts Remote Desktop Part VI - Show confirm dialog before retrieving access code

This dialog explains the implications of accepting remote
assistance and provides a way for the user to decline
assistance.

This ensures that even if the caller (Hangouts) is compromised,
it won't be able to control the user's desktop without user
interaction.

BUG=405139

Review URL: https://codereview.chromium.org/503063004

Cr-Commit-Position: refs/heads/master@{#293200}
parent bb04e105
......@@ -171,6 +171,7 @@
'host/win/version.rc.jinja2',
'resources/play_store_resources.cc',
'webapp/background/background.js',
'webapp/background/it2me_helpee_channel.js',
'webapp/butter_bar.js',
'webapp/client_screen.js',
'webapp/error.js',
......
......@@ -200,6 +200,7 @@
'webapp/host_installer.js',
'webapp/host_session.js',
'webapp/it2me_host_facade.js',
'webapp/l10n.js',
'webapp/plugin_settings.js',
'webapp/typecheck.js',
'webapp/background/app_launcher.js',
......
......@@ -854,6 +854,21 @@ For information about privacy, please see the Google Privacy Policy (http://goo.
<message desc="Label for the Feedback button displayed in the Android Help screen. Pressing this button causes the Feedback screen to be shown." name="IDS_ACTIONBAR_FEEDBACK" formatter_data="android_java">
Feedback
</message>
<message desc="Message displayed in the Hangouts confirm dialog. This message is shown to the Hangouts participant receiving remote assistance before the access code is generated. The dialog informs the user of the implications of accepting remote assistance. It also provides a way for the user to decline the assistance." name="IDS_HANGOUTS_CONFIRM_DIALOG_MESSAGE_1" >
A participant in this hangout has offered to help you by controlling your computer. If you accept:
</message>
<message desc="Message displayed in the Hangouts confirm dialog. This message is shown to the Hangouts participant receiving remote assistance before the access code is generated. The dialog informs the user of the implications of accepting remote assistance. It also provides a way for the user to decline the assistance." name="IDS_HANGOUTS_CONFIRM_DIALOG_MESSAGE_2" >
The person helping you will be able to control your mouse and keyboard.
</message>
<message desc="Message displayed in the Hangouts confirm dialog. This message is shown to the Hangouts participant receiving remote assistance before the access code is generated. The dialog informs the user of the implications of accepting remote assistance. It also provides a way for the user to decline the assistance." name="IDS_HANGOUTS_CONFIRM_DIALOG_MESSAGE_3" >
You can end at any time.
</message>
<message desc="Label for button to accept remote assistance. This button appears in the Hangouts confirm dialog." name="IDS_HANGOUTS_CONFIRM_DIALOG_ACCEPT" >
Accept
</message>
<message desc="Label for button to decline remote assistance. This button appears in the Hangouts confirm dialog." name="IDS_HANGOUTS_CONFIRM_DIALOG_DECLINE" >
Decline
</message>
</messages>
</release>
</grit>
......@@ -275,27 +275,80 @@ remoting.It2MeHelpeeChannel.prototype.handleConnect_ =
* ensures that even if Hangouts is compromised, an attacker cannot start the
* host without explicit user confirmation.
*
* @return {Promise} A promise that resolves when the user clicks accept on the
* dialog.
* @return {Promise} A promise that resolves to a boolean value, indicating
* whether the user accepts the remote assistance or not.
* @private
*/
remoting.It2MeHelpeeChannel.prototype.showConfirmDialog_ = function() {
if (base.isAppsV2()) {
return this.showConfirmDialogV2_();
} else {
return this.showConfirmDialogV1_();
}
};
/**
* @return {Promise} A promise that resolves to a boolean value, indicating
* whether the user accepts the remote assistance or not.
* @private
*/
remoting.It2MeHelpeeChannel.prototype.showConfirmDialogV1_ = function() {
var messageHeader = l10n.getTranslationOrError(
/*i18n-content*/'HANGOUTS_CONFIRM_DIALOG_MESSAGE_1');
var message1 = l10n.getTranslationOrError(
/*i18n-content*/'HANGOUTS_CONFIRM_DIALOG_MESSAGE_2');
var message2 = l10n.getTranslationOrError(
/*i18n-content*/'HANGOUTS_CONFIRM_DIALOG_MESSAGE_3');
var message = base.escapeHTML(messageHeader) + '\n' +
'- ' + base.escapeHTML(message1) + '\n' +
'- ' + base.escapeHTML(message2) + '\n';
if(window.confirm(message)) {
return Promise.resolve();
} else {
return Promise.reject(new Error(remoting.Error.CANCELLED));
}
};
/**
* @return {Promise} A promise that resolves to a boolean value, indicating
* whether the user accepts the remote assistance or not.
* @private
*/
remoting.It2MeHelpeeChannel.prototype.showConfirmDialogV2_ = function() {
var messageHeader = l10n.getTranslationOrError(
/*i18n-content*/'HANGOUTS_CONFIRM_DIALOG_MESSAGE_1');
var message1 = l10n.getTranslationOrError(
/*i18n-content*/'HANGOUTS_CONFIRM_DIALOG_MESSAGE_2');
var message2 = l10n.getTranslationOrError(
/*i18n-content*/'HANGOUTS_CONFIRM_DIALOG_MESSAGE_3');
var message = '<div>' + base.escapeHTML(messageHeader) + '</div>' +
'<ul class="insetList">' +
'<li>' + base.escapeHTML(message1) + '</li>' +
'<li>' + base.escapeHTML(message2) + '</li>' +
'</ul>';
/**
* @param {function(*=):void} resolve
* @param {function(*=):void} reject
*/
return new Promise(function(resolve, reject) {
// TODO(kelvinp): The existing implementation doesn't work in the v2 app as
// window.confirm is blacklisted. Implement the dialog using
// chrome.app.window instead (crbug.com/405139).
if (base.isAppsV2()) {
resolve();
// The unlocalized string will be replaced in (crbug.com/405139).
} else if (window.confirm('Accept help?')) {
resolve();
} else {
reject(new Error(remoting.Error.CANCELLED));
/** @param {number} result */
function confirmDialogCallback(result) {
if (result === 1) {
resolve();
} else {
reject(new Error(remoting.Error.CANCELLED));
}
}
remoting.MessageWindow.showConfirmWindow(
'', // Empty string to use the package name as the dialog title.
message,
l10n.getTranslationOrError(
/*i18n-content*/'HANGOUTS_CONFIRM_DIALOG_ACCEPT'),
l10n.getTranslationOrError(
/*i18n-content*/'HANGOUTS_CONFIRM_DIALOG_DECLINE'),
confirmDialogCallback
);
});
};
......
......@@ -105,9 +105,11 @@ MessageWindowImpl.prototype.onMessage_ = function(event) {
var cancelButton = document.getElementById('button-secondary');
var messageDiv = document.getElementById('message');
var infoboxDiv = document.getElementById('infobox');
document.getElementById('title').innerText = title;
document.querySelector('title').innerText = title;
messageDiv.innerText = message;
messageDiv.innerHTML = message;
if (showSpinner) {
messageDiv.classList.add('waiting');
messageDiv.classList.add('prominent');
......
......@@ -149,6 +149,18 @@ base.urlJoin = function(url, opt_params) {
return url + '?' + queryParameters.join('&');
};
/**
* Convert special characters (e.g. &, < and >) to HTML entities.
*
* @param {string} str
* @return {string}
*/
base.escapeHTML = function(str) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
};
/**
* Promise is a great tool for writing asynchronous code. However, the construct
* var p = new promise(function init(resolve, reject) {
......
......@@ -10,4 +10,8 @@ body {
#infobox {
margin-top: 10px;
margin-bottom: 10px;
}
.insetList {
list-style-position: inside;
}
\ No newline at end of file
......@@ -75,6 +75,12 @@ test('urljoin(url, opt_param) should urlencode |opt_param|',
'&escapist=%3A%2F%3F%23%5B%5D%40%24%26%2B%2C%3B%3D');
});
test('escapeHTML(str) should escape special characters', function() {
QUnit.equal(
base.escapeHTML('<script>alert("hello")</script>'),
'&lt;script&gt;alert("hello")&lt;/script&gt;');
});
QUnit.asyncTest('Promise.sleep(delay) should fulfill the promise after |delay|',
function() {
var isCalled = false;
......
......@@ -82,7 +82,7 @@ QUnit.asyncTest(
QUnit.asyncTest('isHostInstalled() should return true if host is installed',
function() {
sinon.stub(hostInstaller, "isInstalled").returns(Promise.resolve(true));
sinon.stub(hostInstaller, 'isInstalled').returns(Promise.resolve(true));
var MessageTypes = remoting.It2MeHelpeeChannel.HangoutMessageTypes;
hangoutPort.onMessage.mock$fire({
......@@ -100,7 +100,7 @@ QUnit.asyncTest('isHostInstalled() should return true if host is installed',
test('downloadHost() should trigger a host download',
function() {
sinon.stub(hostInstaller, "download").returns(Promise.resolve(true));
sinon.stub(hostInstaller, 'download').returns(Promise.resolve(true));
hangoutPort.onMessage.mock$fire({
method: remoting.It2MeHelpeeChannel.HangoutMessageTypes.DOWNLOAD_HOST
......@@ -125,14 +125,16 @@ test('connect() should return error if email is missing',
QUnit.asyncTest('connect() should return access code',
function() {
// Stubs authentication.
sinon.stub(base, "isAppsV2").returns(true);
sinon.stub(chrome.identity, "getAuthToken")
sinon.stub(base, 'isAppsV2').returns(true);
sinon.stub(remoting.MessageWindow, 'showConfirmWindow')
.callsArgWith(4, 1 /* 1 for OK. */);
sinon.stub(chrome.identity, 'getAuthToken')
.callsArgWith(1, 'token');
// Stubs Host behavior.
sinon.stub(host, "initialized").returns(true);
sinon.stub(host, "connect")
sinon.stub(host, 'initialized').returns(true);
sinon.stub(host, 'connect')
.callsArgWith(2, remoting.HostSession.State.RECEIVED_ACCESS_CODE);
sinon.stub(host, "getAccessCode").returns('accessCode');
sinon.stub(host, 'getAccessCode').returns('accessCode');
var MessageTypes = remoting.It2MeHelpeeChannel.HangoutMessageTypes;
hangoutPort.onMessage.mock$fire({
......@@ -149,12 +151,13 @@ QUnit.asyncTest('connect() should return access code',
chrome.identity.getAuthToken.restore();
base.isAppsV2.restore();
remoting.MessageWindow.showConfirmWindow.restore();
QUnit.start();
});
});
test('should disconnect the session if Hangout crashes', function() {
sinon.spy(host, "disconnect");
sinon.spy(host, 'disconnect');
hangoutPort.onDisconnect.mock$fire();
sinon.assert.called(onDisposedCallback);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment