mirror of
https://github.com/discourse/discourse.git
synced 2025-10-03 17:21:20 +08:00
SECURITY: Escape names
This commit is contained in:
parent
8b20137317
commit
4edad4dc3c
4 changed files with 105 additions and 74 deletions
|
@ -1,10 +1,11 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { service } from "@ember/service";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import InterpolatedTranslation from "discourse/components/interpolated-translation";
|
||||
import PostQuotedContent from "discourse/components/post/quoted-content";
|
||||
import UserLink from "discourse/components/user-link";
|
||||
import concatClass from "discourse/helpers/concat-class";
|
||||
import { iconHTML } from "discourse/lib/icon-library";
|
||||
import { formatUsername } from "discourse/lib/utilities";
|
||||
import icon from "discourse/helpers/d-icon";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class SolvedAcceptedAnswer extends Component {
|
||||
|
@ -35,53 +36,45 @@ export default class SolvedAcceptedAnswer extends Component {
|
|||
return htmlSafe(this.acceptedAnswer.excerpt);
|
||||
}
|
||||
|
||||
get htmlAccepter() {
|
||||
if (!this.siteSettings.show_who_marked_solved) {
|
||||
return;
|
||||
get showMarkedBy() {
|
||||
return this.siteSettings.show_who_marked_solved;
|
||||
}
|
||||
|
||||
const { accepter_username, accepter_name } = this.acceptedAnswer;
|
||||
const displayName = this.#getDisplayName(accepter_username, accepter_name);
|
||||
|
||||
if (!displayName) {
|
||||
return;
|
||||
get showSolvedBy() {
|
||||
return !(!this.acceptedAnswer.username || !this.acceptedAnswer.post_number);
|
||||
}
|
||||
|
||||
return htmlSafe(
|
||||
i18n("solved.marked_solved_by", {
|
||||
username: displayName,
|
||||
username_lower: accepter_username.toLowerCase(),
|
||||
})
|
||||
);
|
||||
get postNumber() {
|
||||
return i18n("solved.accepted_answer_post_number", {
|
||||
post_number: this.acceptedAnswer.post_number,
|
||||
});
|
||||
}
|
||||
|
||||
get htmlSolvedBy() {
|
||||
const { username, name, post_number: postNumber } = this.acceptedAnswer;
|
||||
if (!username || !postNumber) {
|
||||
return;
|
||||
get solverUsername() {
|
||||
return this.acceptedAnswer.username;
|
||||
}
|
||||
|
||||
const displayedUser = this.#getDisplayName(username, name);
|
||||
const data = {
|
||||
icon: iconHTML("square-check", { class: "accepted" }),
|
||||
username_lower: username.toLowerCase(),
|
||||
username: displayedUser,
|
||||
post_path: `${this.topic.url}/${postNumber}`,
|
||||
post_number: postNumber,
|
||||
user_path: this.store.createRecord("user", { username }).path,
|
||||
};
|
||||
|
||||
return htmlSafe(i18n("solved.accepted_html", data));
|
||||
get accepterUsername() {
|
||||
return this.acceptedAnswer.accepter_username;
|
||||
}
|
||||
|
||||
#getDisplayName(username, name) {
|
||||
if (!username) {
|
||||
return null;
|
||||
get solverDisplayName() {
|
||||
const username = this.acceptedAnswer.username;
|
||||
const name = this.acceptedAnswer.name;
|
||||
|
||||
return this.siteSettings.display_name_on_posts && name ? name : username;
|
||||
}
|
||||
|
||||
return this.siteSettings.display_name_on_posts && name
|
||||
? name
|
||||
: formatUsername(username);
|
||||
get accepterDisplayName() {
|
||||
const username = this.acceptedAnswer.accepter_username;
|
||||
const name = this.acceptedAnswer.accepter_name;
|
||||
|
||||
return this.siteSettings.display_name_on_posts && name ? name : username;
|
||||
}
|
||||
|
||||
get postPath() {
|
||||
const postNumber = this.acceptedAnswer.post_number;
|
||||
return `${this.topic.url}/${postNumber}`;
|
||||
}
|
||||
|
||||
<template>
|
||||
|
@ -103,10 +96,38 @@ export default class SolvedAcceptedAnswer extends Component {
|
|||
<:title>
|
||||
<div class="accepted-answer--solver-accepter">
|
||||
<div class="accepted-answer--solver">
|
||||
{{this.htmlSolvedBy}}
|
||||
{{#if this.showSolvedBy}}
|
||||
{{icon "square-check" class="accepted"}}
|
||||
<InterpolatedTranslation
|
||||
@key="solved.accepted_answer_solver_info"
|
||||
as |Placeholder|
|
||||
>
|
||||
<Placeholder @name="user">
|
||||
<UserLink
|
||||
@username={{this.solverUsername}}
|
||||
>{{this.solverDisplayName}}</UserLink>
|
||||
</Placeholder>
|
||||
<Placeholder @name="post">
|
||||
<a href={{this.postPath}}>{{this.postNumber}}</a>
|
||||
</Placeholder>
|
||||
</InterpolatedTranslation>
|
||||
<br />
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
<div class="accepted-answer--accepter">
|
||||
{{this.htmlAccepter}}
|
||||
{{#if this.showMarkedBy}}
|
||||
<InterpolatedTranslation
|
||||
@key="solved.marked_solved_by"
|
||||
as |Placeholder|
|
||||
>
|
||||
<Placeholder @name="user">
|
||||
<UserLink
|
||||
@username={{this.accepterUsername}}
|
||||
>{{this.accepterDisplayName}}</UserLink>
|
||||
</Placeholder>
|
||||
</InterpolatedTranslation>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</:title>
|
||||
|
|
|
@ -2,13 +2,13 @@ import Component from "@glimmer/component";
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { and } from "truth-helpers";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import InterpolatedTranslation from "discourse/components/interpolated-translation";
|
||||
import UserLink from "discourse/components/user-link";
|
||||
import icon from "discourse/helpers/d-icon";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { formatUsername } from "discourse/lib/utilities";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import DTooltip from "float-kit/components/d-tooltip";
|
||||
|
||||
|
@ -18,25 +18,6 @@ export default class SolvedUnacceptAnswerButton extends Component {
|
|||
|
||||
@tracked saving = false;
|
||||
|
||||
get solvedBy() {
|
||||
if (!this.siteSettings.show_who_marked_solved) {
|
||||
return;
|
||||
}
|
||||
|
||||
const username = this.args.post.topic.accepted_answer.accepter_username;
|
||||
const name = this.args.post.topic.accepted_answer.accepter_name;
|
||||
const displayedName =
|
||||
this.siteSettings.display_name_on_posts && name
|
||||
? name
|
||||
: formatUsername(username);
|
||||
if (this.args.post.topic.accepted_answer.accepter_username) {
|
||||
return i18n("solved.marked_solved_by", {
|
||||
username: displayedName,
|
||||
username_lower: username,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
async unacceptAnswer() {
|
||||
const post = this.args.post;
|
||||
|
@ -58,10 +39,27 @@ export default class SolvedUnacceptAnswerButton extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
get showAcceptedBy() {
|
||||
return !(
|
||||
!this.siteSettings.show_who_marked_solved ||
|
||||
!this.args.post.topic.accepted_answer.accepter_username
|
||||
);
|
||||
}
|
||||
|
||||
get acceptedByUsername() {
|
||||
return this.args.post.topic.accepted_answer.accepter_username;
|
||||
}
|
||||
|
||||
get acceptedByDisplayName() {
|
||||
const username = this.args.post.topic.accepted_answer.accepter_username;
|
||||
const name = this.args.post.topic.accepted_answer.accepter_name;
|
||||
return this.siteSettings.display_name_on_posts && name ? name : username;
|
||||
}
|
||||
|
||||
<template>
|
||||
<span class="extra-buttons">
|
||||
{{#if (and @post.can_accept_answer @post.accepted_answer)}}
|
||||
{{#if this.solvedBy}}
|
||||
{{#if this.showAcceptedBy}}
|
||||
<DTooltip @identifier="post-action-menu__solved-accepted-tooltip">
|
||||
<:trigger>
|
||||
<DButton
|
||||
|
@ -74,7 +72,16 @@ export default class SolvedUnacceptAnswerButton extends Component {
|
|||
/>
|
||||
</:trigger>
|
||||
<:content>
|
||||
{{htmlSafe this.solvedBy}}
|
||||
<InterpolatedTranslation
|
||||
@key="solved.marked_solved_by"
|
||||
as |Placeholder|
|
||||
>
|
||||
<Placeholder @name="user">
|
||||
<UserLink @username={{this.acceptedByUsername}}>
|
||||
{{this.acceptedByDisplayName}}
|
||||
</UserLink>
|
||||
</Placeholder>
|
||||
</InterpolatedTranslation>
|
||||
</:content>
|
||||
</DTooltip>
|
||||
{{else}}
|
||||
|
|
|
@ -24,7 +24,8 @@ en:
|
|||
solution_summary:
|
||||
one: "solution"
|
||||
other: "solutions"
|
||||
accepted_html: "%{icon} Solved <span class='by'>by <a href data-user-card='%{username_lower}'>%{username}</a></span> in <a href='%{post_path}' class='back'>post #%{post_number}</a>"
|
||||
accepted_answer_solver_info: "Solved by %{user} in %{post}"
|
||||
accepted_answer_post_number: "post %{post_number}"
|
||||
accepted_notification: "<p><span>%{username}</span> %{description}</p>"
|
||||
topic_status_filter:
|
||||
all: "all"
|
||||
|
@ -33,7 +34,7 @@ en:
|
|||
no_solved_topics_title: "You haven’t solved any topics yet"
|
||||
no_solved_topics_title_others: "%{username} has not solved any topics yet"
|
||||
no_solved_topics_body: "When you provide a helpful reply to a topic, your reply might be selected as the solution by the topic owner or staff."
|
||||
marked_solved_by: "Marked as solved by <a href data-user-card='%{username_lower}'>%{username}</a></span>"
|
||||
marked_solved_by: "Marked as solved by %{user}"
|
||||
|
||||
no_answer:
|
||||
title: Has your question been answered?
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
describe "Solved", type: :system do
|
||||
fab!(:admin)
|
||||
fab!(:solver) { Fabricate(:user) }
|
||||
fab!(:accepter) { Fabricate(:user) }
|
||||
fab!(:accepter) { Fabricate(:user, name: "<b>DERP<b>") }
|
||||
fab!(:topic) { Fabricate(:post, user: admin).topic }
|
||||
fab!(:solver_post) { Fabricate(:post, topic:, user: solver, cooked: "The answer is 42") }
|
||||
|
||||
|
@ -20,6 +21,7 @@ describe "Solved", type: :system do
|
|||
SiteSetting.allow_solved_on_all_topics = true
|
||||
SiteSetting.accept_all_solutions_allowed_groups = Group::AUTO_GROUPS[:everyone]
|
||||
SiteSetting.show_who_marked_solved = true
|
||||
SiteSetting.display_name_on_posts = true
|
||||
end
|
||||
|
||||
%w[enabled disabled].each do |value|
|
||||
|
@ -91,9 +93,9 @@ describe "Solved", type: :system do
|
|||
end
|
||||
|
||||
def verify_solver_and_accepter_info
|
||||
expect(topic_page.find(SOLVER_INFO_SELECTOR)).to have_content("Solved by #{solver.username}")
|
||||
expect(topic_page.find(SOLVER_INFO_SELECTOR)).to have_content("Solved by #{solver.name}")
|
||||
expect(topic_page.find(ACCEPTER_INFO_SELECTOR)).to have_content(
|
||||
"Marked as solved by #{accepter.username}",
|
||||
"Marked as solved by #{accepter.name}",
|
||||
)
|
||||
end
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue