Skip to content
This repository has been archived by the owner on Feb 6, 2023. It is now read-only.

Commit

Permalink
SECURITY: Ensure htmlSafe content is properly escaped.
Browse files Browse the repository at this point in the history
  • Loading branch information
romanrizzi committed Aug 25, 2022
1 parent 67bd949 commit 6e0cd45
Show file tree
Hide file tree
Showing 27 changed files with 421 additions and 115 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
NEW_TOPIC_SELECTION,
} from "discourse/plugins/discourse-chat/discourse/components/chat-to-topic-selector";
import { CHANNEL_STATUSES } from "discourse/plugins/discourse-chat/discourse/models/chat-channel";
import { htmlSafe } from "@ember/template";
import { escapeExpression } from "discourse/lib/utilities";

export default Component.extend({
chat: service(),
Expand Down Expand Up @@ -101,4 +103,13 @@ export default Component.extend({
);
return labels;
},

@discourseComputed()
instructionsText() {
return htmlSafe(
I18n.t("chat.channel_archive.instructions", {
channelTitle: escapeExpression(this.chatChannel.title),
})
);
},
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Component from "@ember/component";
import { htmlSafe } from "@ember/template";
import I18n from "I18n";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { ajax } from "discourse/lib/ajax";
Expand All @@ -17,11 +18,13 @@ export default Component.extend({
"channel.archive_failed"
)
channelArchiveFailedMessage() {
return I18n.t("chat.channel_status.archive_failed", {
completed: this.channel.archived_messages,
total: this.channel.total_messages,
topic_url: this._getTopicUrl(),
});
return htmlSafe(
I18n.t("chat.channel_status.archive_failed", {
completed: this.channel.archived_messages,
total: this.channel.total_messages,
topic_url: this._getTopicUrl(),
})
);
},

@discourseComputed(
Expand All @@ -31,9 +34,11 @@ export default Component.extend({
"channel.archive_completed"
)
channelArchiveCompletedMessage() {
return I18n.t("chat.channel_status.archive_completed", {
topic_url: this._getTopicUrl(),
});
return htmlSafe(
I18n.t("chat.channel_status.archive_completed", {
topic_url: this._getTopicUrl(),
})
);
},

@action
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { ajax } from "discourse/lib/ajax";
import { inject as service } from "@ember/service";
import { popupAjaxError } from "discourse/lib/ajax-error";
import discourseLater from "discourse-common/lib/later";
import { htmlSafe } from "@ember/template";
import { escapeExpression } from "discourse/lib/utilities";

export default Component.extend({
chat: service(),
Expand All @@ -25,8 +27,8 @@ export default Component.extend({

if (
isEmpty(channelNameConfirmation) ||
channelNameConfirmation.toLowerCase() !==
this.chatChannel.title.toLowerCase()
escapeExpression(channelNameConfirmation).toLowerCase() !==
this.escapedTitle.toLowerCase()
) {
return true;
}
Expand Down Expand Up @@ -55,4 +57,18 @@ export default Component.extend({
.catch(popupAjaxError)
.finally(() => this.set("deleting", false));
},

@discourseComputed()
instructionsText() {
return htmlSafe(
I18n.t("chat.channel_delete.instructions", {
name: this.escapedTitle,
})
);
},

@discourseComputed()
escapedTitle() {
return escapeExpression(this.chatChannel.title);
},
});
6 changes: 6 additions & 0 deletions assets/javascripts/discourse/components/chat-channel-title.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Component from "@ember/component";
import { htmlSafe } from "@ember/template";
import { computed } from "@ember/object";
import { gt, reads } from "@ember/object/computed";

Expand All @@ -14,4 +15,9 @@ export default class ChatChannelTitle extends Component {
get usernames() {
return this.users.mapBy("username").join(", ");
}

@computed("channel.chatable.color")
get channelColorStyle() {
return htmlSafe(`color: #${this.channel.chatable.color}`);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Component from "@ember/component";
import { htmlSafe } from "@ember/template";
import { CHANNEL_STATUSES } from "discourse/plugins/discourse-chat/discourse/models/chat-channel";
import I18n from "I18n";
import { action, computed } from "@ember/object";
Expand All @@ -25,9 +26,9 @@ export default class ChatChannelToggleView extends Component {
@computed("channel.isClosed")
get instructions() {
if (this.channel.isClosed) {
return I18n.t("chat.channel_open.instructions");
return htmlSafe(I18n.t("chat.channel_open.instructions"));
} else {
return I18n.t("chat.channel_close.instructions");
return htmlSafe(I18n.t("chat.channel_close.instructions"));
}
}

Expand Down
26 changes: 17 additions & 9 deletions assets/javascripts/discourse/components/chat-message-collapser.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Component from "@ember/component";
import { computed } from "@ember/object";
import { htmlSafe } from "@ember/template";
import escape from "discourse-common/lib/escape";
import { escapeExpression } from "discourse/lib/utilities";
import domFromString from "discourse-common/lib/dom-from-string";
import I18n from "I18n";

Expand All @@ -24,7 +24,11 @@ export default class ChatMessageCollapser extends Component {
} else {
name = I18n.t("chat.uploaded_files", { count: this.uploads.length });
}
return `<span class="chat-message-collapser-link-small">${name}</span>`;
return htmlSafe(
`<span class="chat-message-collapser-link-small">${escapeExpression(
name
)}</span>`
);
}

@computed("cooked")
Expand Down Expand Up @@ -54,8 +58,8 @@ export default class ChatMessageCollapser extends Component {
return elements.reduce((acc, e) => {
if (youtubePredicate(e)) {
const id = e.dataset.youtubeId;
const link = `https://www.youtube.com/watch?v=${escape(id)}`;
const title = e.dataset.youtubeTitle;
const link = `https://www.youtube.com/watch?v=${escapeExpression(id)}`;
const title = escapeExpression(e.dataset.youtubeTitle);
const header = htmlSafe(
`<a target="_blank" class="chat-message-collapser-link" rel="noopener noreferrer" href="${link}">${title}</a>`
);
Expand All @@ -74,9 +78,11 @@ export default class ChatMessageCollapser extends Component {
imageOneboxCooked(elements) {
return elements.reduce((acc, e) => {
if (imageOneboxPredicate(e)) {
const link = animatedImagePredicate(e)
let link = animatedImagePredicate(e)
? e.firstChild.src
: e.firstElementChild.href;

link = escapeExpression(link);
const header = htmlSafe(
`<a target="_blank" class="chat-message-collapser-link-small" rel="noopener noreferrer" href="${link}">${link}</a>`
);
Expand All @@ -91,8 +97,8 @@ export default class ChatMessageCollapser extends Component {
imageCooked(elements) {
return elements.reduce((acc, e) => {
if (imagePredicate(e)) {
const link = e.firstElementChild.src;
const alt = e.firstElementChild.alt;
const link = escapeExpression(e.firstElementChild.src);
const alt = escapeExpression(e.firstElementChild.alt);
const header = htmlSafe(
`<a target="_blank" class="chat-message-collapser-link-small" rel="noopener noreferrer" href="${link}">${
alt || link
Expand All @@ -109,8 +115,10 @@ export default class ChatMessageCollapser extends Component {
galleryCooked(elements) {
return elements.reduce((acc, e) => {
if (galleryPredicate(e)) {
const link = e.firstElementChild.href;
const title = e.firstElementChild.firstElementChild.textContent;
const link = escapeExpression(e.firstElementChild.href);
const title = escapeExpression(
e.firstElementChild.firstElementChild.textContent
);
e.firstElementChild.removeChild(e.firstElementChild.firstElementChild);
const header = htmlSafe(
`<a target="_blank" class="chat-message-collapser-link-small" rel="noopener noreferrer" href="${link}">${title}</a>`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import Component from "@ember/component";
import I18n from "I18n";
import { reads } from "@ember/object/computed";
import { isBlank } from "@ember/utils";
import { action, computed } from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import { inject as service } from "@ember/service";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { htmlSafe } from "@ember/template";
import { escapeExpression } from "discourse/lib/utilities";

export default class MoveToChannelModalInner extends Component {
@service chat;
Expand Down Expand Up @@ -50,4 +53,14 @@ export default class MoveToChannelModalInner extends Component {
})
.catch(popupAjaxError);
}

@computed()
get instructionsText() {
return htmlSafe(
I18n.t("chat.move_to_channel.instructions", {
channelTitle: escapeExpression(this.sourceChannel.title),
count: this.selectedMessageCount,
})
);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Component from "@ember/component";
import { htmlSafe } from "@ember/template";
import discourseComputed from "discourse-common/utils/decorators";
import { alias, equal } from "@ember/object/computed";

Expand Down Expand Up @@ -28,16 +29,16 @@ export default Component.extend({

@discourseComputed()
newTopicInstruction() {
return this.instructionLabels[NEW_TOPIC_SELECTION];
return htmlSafe(this.instructionLabels[NEW_TOPIC_SELECTION]);
},

@discourseComputed()
existingTopicInstruction() {
return this.instructionLabels[EXISTING_TOPIC_SELECTION];
return htmlSafe(this.instructionLabels[EXISTING_TOPIC_SELECTION]);
},

@discourseComputed()
newMessageInstruction() {
return this.instructionLabels[NEW_MESSAGE_SELECTION];
return htmlSafe(this.instructionLabels[NEW_MESSAGE_SELECTION]);
},
});
31 changes: 16 additions & 15 deletions assets/javascripts/discourse/controllers/create-channel.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import bootbox from "bootbox";
import { escapeExpression } from "discourse/lib/utilities";
import Controller from "@ember/controller";
import ChatApi from "discourse/plugins/discourse-chat/discourse/lib/chat-api";
import ChatChannel from "discourse/plugins/discourse-chat/discourse/models/chat-channel";
import escape from "discourse-common/lib/escape";
import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { ajax } from "discourse/lib/ajax";
import { action, computed } from "@ember/object";
import { gt, notEmpty } from "@ember/object/computed";
import { inject as service } from "@ember/service";
import { isBlank } from "@ember/utils";
import { htmlSafe } from "@ember/template";

const DEFAULT_HINT = I18n.t(
"chat.create_channel.choose_category.default_hint",
{
const DEFAULT_HINT = htmlSafe(
I18n.t("chat.create_channel.choose_category.default_hint", {
link: "/categories",
category: "category",
}
})
);

export default class CreateChannelController extends Controller.extend(
Expand Down Expand Up @@ -98,16 +98,16 @@ export default class CreateChannelController extends Controller.extend(
"autoJoinWarning",
I18n.t(`chat.create_channel.auto_join_users.${warningTranslationKey}`, {
members_count: catPermissions.members_count,
group_1: allowedGroups[0],
group_2: allowedGroups[1],
group_1: escapeExpression(allowedGroups[0]),
group_2: escapeExpression(allowedGroups[1]),
count: allowedGroups.length,
})
);
} else {
this.set(
"autoJoinWarning",
I18n.t(`chat.create_channel.auto_join_users.public_category_warning`, {
category: escape(category.name),
category: escapeExpression(category.name),
})
);
}
Expand All @@ -120,18 +120,19 @@ export default class CreateChannelController extends Controller.extend(
return ChatApi.categoryPermissions(category.id).then((catPermissions) => {
this._updateAutoJoinConfirmWarning(category, catPermissions);
const allowedGroups = catPermissions.allowed_groups;

const translationKey =
allowedGroups.length < 3 ? "hint_groups" : "hint_multiple_groups";

this.set(
"categoryPermissionsHint",
I18n.t(`chat.create_channel.choose_category.${translationKey}`, {
link: `/c/${escape(fullSlug)}/edit/security`,
hint_1: allowedGroups[0],
hint_2: allowedGroups[1],
count: allowedGroups.length,
})
htmlSafe(
I18n.t(`chat.create_channel.choose_category.${translationKey}`, {
link: `/c/${escapeExpression(fullSlug)}/edit/security`,
hint_1: escapeExpression(allowedGroups[0]),
hint_2: escapeExpression(allowedGroups[1]),
count: allowedGroups.length,
})
)
);
});
} else {
Expand Down
Loading

0 comments on commit 6e0cd45

Please sign in to comment.