fix(TOTP): Translate success status messages on /settings/recovery_codes

view.translate was not being called on the success status messages
before writing to the DOM. This remedies that.

Also change writing status/error messages to the DOM from using
.html to .text to prevent XSS from creeping in.

fixes #6728
This commit is contained in:
Shane Tomlinson 2019-01-11 13:28:34 +00:00
Родитель 2e2ba86a75
Коммит 6984fa8b5f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 09D4F897B87A2D19
5 изменённых файлов: 57 добавлений и 36 удалений

Просмотреть файл

@ -26,7 +26,7 @@ const Mixin = {
// This copies the text to clipboard by creating a tiny transparent
// textArea with the content. Then it executes the browser `copy` command and removes textArea.
$('<textArea id=\"temporary-copy-area\" class=\"temporary-copy-text-area\"></textArea>').appendTo(appendToElement);
this.$('textArea.temporary-copy-text-area').html(text);
this.$('textArea.temporary-copy-text-area').text(text);
if (this.getUserAgent().isIos()) {
// iOS does not allow you to directly use the `document.execCommand('copy')` function.
@ -85,14 +85,14 @@ const Mixin = {
this.$('.error').addClass('hidden');
this.$('.modal-success').removeClass('hidden');
this.$('.modal-success').addClass('success');
this.$('.modal-success').html(msg);
this.$('.modal-success').text(this.translate(msg));
},
_displayError(msg) {
this.$('.error').removeClass('hidden');
this.$('.modal-success').addClass('hidden');
this.$('.modal-success').removeClass('success');
this.$('.error').html(msg);
this.$('.error').text(this.translate(msg));
},
};

Просмотреть файл

@ -2,14 +2,14 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Cocktail = require('cocktail');
const FormView = require('../form');
const ModalSettingsPanelMixin = require('../mixins/modal-settings-panel-mixin');
const Template = require('templates/settings/recovery_codes.mustache');
const RecoveryCodePrintTemplate = require('templates/settings/recovery_codes_print.mustache');
const RecoveryCode = require('../../models/recovery-code');
const SaveOptionsMixin = require('../mixins/save-options-mixin');
const UserAgentMixin = require('../../lib/user-agent-mixin');
import Cocktail from 'cocktail';
import FormView from '../form';
import ModalSettingsPanelMixin from '../mixins/modal-settings-panel-mixin';
import Template from 'templates/settings/recovery_codes.mustache';
import RecoveryCodePrintTemplate from 'templates/settings/recovery_codes_print.mustache';
import RecoveryCode from '../../models/recovery-code';
import SaveOptionsMixin from '../mixins/save-options-mixin';
import UserAgentMixin from '../../lib/user-agent-mixin';
const t = msg => msg;
@ -115,7 +115,9 @@ const View = FormView.extend({
context.set({
isIos: this.getUserAgent().isIos(),
modalSuccessMsg,
// There can be several modalSuccessMsg's, make sure they are translated
// before displaying to the user user. See #6728
modalSuccessMsg: modalSuccessMsg && this.translate(modalSuccessMsg),
recoveryCodes,
showRecoveryCodes: recoveryCodes.length > 0
});

Просмотреть файл

@ -113,7 +113,7 @@ const View = FormView.extend({
const qrImageAltText = t('Use the code %(code)s to set up two-step authentication in supported applications.');
this.$('.qr-image').attr('alt', this.translate(qrImageAltText, {code: result.secret}));
this.$('.code').html(this._getFormattedCode(result.secret));
this.$('.code').text(this._getFormattedCode(result.secret));
this._showQrCode();
this._hideStatus();
});

Просмотреть файл

@ -4,13 +4,18 @@
'use strict';
const {assert} = require('chai');
const BaseView = require('views/base');
const Cocktail = require('cocktail');
const SaveOptionsMixin = require('views/mixins/save-options-mixin');
const sinon = require('sinon');
import { assert } from 'chai';
import BaseView from 'views/base';
import Cocktail from 'cocktail';
import SaveOptionsMixin from 'views/mixins/save-options-mixin';
import sinon from 'sinon';
const View = BaseView.extend({});
const View = BaseView.extend({
template: () => `
<div class="modal-success"></div>
<div class="error"></div>
`
});
Cocktail.mixin(
View,
@ -25,7 +30,10 @@ describe('views/mixins/save-options-mixin', () => {
beforeEach(() => {
view = new View({});
sinon.stub(view, 'translate').callsFake(msg => `translated ${msg}`);
sandbox = sinon.sandbox.create();
return view.render();
});
afterEach(function () {
@ -100,4 +108,15 @@ describe('views/mixins/save-options-mixin', () => {
assert.equal(view._displaySuccess.called, true, 'display success called');
});
});
it('_displaySuccess should translate the text', () => {
view._displaySuccess('success message');
assert.equal(view.$('.modal-success').text(), 'translated success message');
});
it('_displayError should translate the text', () => {
view._displayError('error message');
assert.equal(view.$('.error').text(), 'translated error message');
});
});

Просмотреть файл

@ -2,17 +2,17 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const $ = require('jquery');
const assert = require('chai').assert;
const Broker = require('models/auth_brokers/base');
const Metrics = require('lib/metrics');
const { Model } = require('backbone');
const Notifier = require('lib/channels/notifier');
const sinon = require('sinon');
const TestHelpers = require('../../../lib/helpers');
const User = require('models/user');
const View = require('views/settings/recovery_codes');
const WindowMock = require('../../../mocks/window');
import $ from 'jquery';
import { assert } from 'chai';
import Broker from 'models/auth_brokers/base';
import Metrics from 'lib/metrics';
import { Model } from 'backbone';
import Notifier from 'lib/channels/notifier';
import sinon from 'sinon';
import TestHelpers from '../../../lib/helpers';
import User from 'models/user';
import View from 'views/settings/recovery_codes';
import WindowMock from '../../../mocks/window';
describe('views/settings/recovery_codes', () => {
let account;
@ -43,6 +43,8 @@ describe('views/settings/recovery_codes', () => {
window: windowMock
});
sinon.stub(view, 'translate').callsFake(msg => `translated ${msg}`);
sinon.stub(view, 'getSignedInAccount').callsFake(() => account);
sinon.stub(account, 'replaceRecoveryCodes').callsFake(() => Promise.resolve({
@ -89,12 +91,10 @@ describe('views/settings/recovery_codes', () => {
view = null;
});
describe('should show recovery codes', () => {
it('show codes', () => {
assert.equal(view.$('.recovery-code').length, 8);
assert.equal(view.$('.recovery-code:first').text(), '00001111', 'correct recovery code');
assert.include(view.$('.modal-success').text(), 'authentication enabled');
});
it('should show recovery codes, translated success message', () => {
assert.equal(view.$('.recovery-code').length, 8);
assert.equal(view.$('.recovery-code:first').text(), '00001111', 'correct recovery code');
assert.include(view.$('.modal-success').text(), 'translated Two-step authentication enabled');
});
describe('should print codes', () => {