summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkelvinp <kelvinp@chromium.org>2014-09-03 14:33:27 -0700
committerCommit bot <commit-bot@chromium.org>2014-09-03 21:37:45 +0000
commit020970c9991637b98a034c4a23ec0fcbff35f01b (patch)
treea73999e133e0f6f89ec1bda071ea9b1d781a6dbf
parentbb04e1054366543cb554531c728b33525a5dae8c (diff)
downloadchromium_src-020970c9991637b98a034c4a23ec0fcbff35f01b.zip
chromium_src-020970c9991637b98a034c4a23ec0fcbff35f01b.tar.gz
chromium_src-020970c9991637b98a034c4a23ec0fcbff35f01b.tar.bz2
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}
-rw-r--r--remoting/remoting.gyp1
-rw-r--r--remoting/remoting_webapp_files.gypi1
-rw-r--r--remoting/resources/remoting_strings.grd15
-rw-r--r--remoting/webapp/background/it2me_helpee_channel.js77
-rw-r--r--remoting/webapp/background/message_window.js4
-rw-r--r--remoting/webapp/base.js12
-rw-r--r--remoting/webapp/message_window.css4
-rw-r--r--remoting/webapp/unittests/base_unittest.js6
-rw-r--r--remoting/webapp/unittests/it2me_helpee_channel_unittest.js19
9 files changed, 118 insertions, 21 deletions
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp
index a1e1f41..335c544 100644
--- a/remoting/remoting.gyp
+++ b/remoting/remoting.gyp
@@ -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',
diff --git a/remoting/remoting_webapp_files.gypi b/remoting/remoting_webapp_files.gypi
index 0dd2855..c403dcf 100644
--- a/remoting/remoting_webapp_files.gypi
+++ b/remoting/remoting_webapp_files.gypi
@@ -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',
diff --git a/remoting/resources/remoting_strings.grd b/remoting/resources/remoting_strings.grd
index c89a11b..604db9a 100644
--- a/remoting/resources/remoting_strings.grd
+++ b/remoting/resources/remoting_strings.grd
@@ -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>
diff --git a/remoting/webapp/background/it2me_helpee_channel.js b/remoting/webapp/background/it2me_helpee_channel.js
index 94001883..b53f798 100644
--- a/remoting/webapp/background/it2me_helpee_channel.js
+++ b/remoting/webapp/background/it2me_helpee_channel.js
@@ -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
+ );
});
};
diff --git a/remoting/webapp/background/message_window.js b/remoting/webapp/background/message_window.js
index fee4c9b0..a57684a 100644
--- a/remoting/webapp/background/message_window.js
+++ b/remoting/webapp/background/message_window.js
@@ -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');
diff --git a/remoting/webapp/base.js b/remoting/webapp/base.js
index 17cfc29..ee909be 100644
--- a/remoting/webapp/base.js
+++ b/remoting/webapp/base.js
@@ -150,6 +150,18 @@ base.urlJoin = function(url, opt_params) {
};
/**
+ * 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) {
* ... // code that fulfills the Promise.
diff --git a/remoting/webapp/message_window.css b/remoting/webapp/message_window.css
index c5a51ee..a053df3 100644
--- a/remoting/webapp/message_window.css
+++ b/remoting/webapp/message_window.css
@@ -10,4 +10,8 @@ body {
#infobox {
margin-top: 10px;
margin-bottom: 10px;
+}
+
+.insetList {
+ list-style-position: inside;
} \ No newline at end of file
diff --git a/remoting/webapp/unittests/base_unittest.js b/remoting/webapp/unittests/base_unittest.js
index a002bdf..a37715c3 100644
--- a/remoting/webapp/unittests/base_unittest.js
+++ b/remoting/webapp/unittests/base_unittest.js
@@ -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;
diff --git a/remoting/webapp/unittests/it2me_helpee_channel_unittest.js b/remoting/webapp/unittests/it2me_helpee_channel_unittest.js
index 46619c3..504affc 100644
--- a/remoting/webapp/unittests/it2me_helpee_channel_unittest.js
+++ b/remoting/webapp/unittests/it2me_helpee_channel_unittest.js
@@ -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);