mirror of
https://github.com/discourse/discourse.git
synced 2025-05-29 01:31:35 +08:00
FEATURE: Update topic/comment embedding parameters (#20181)
This commit implements many changes to topic and comments embedding. It deprecates the class_name field from EmbeddableHost and suggests using the className parameter. discourse_username parameter has been deprecated and it will fetch it from embedded site from the author or discourse-username meta. See the updated code sample from Admin > Customize > Embedding page. * FEATURE: Add className parameter for Discourse embed * DEV: Hide class_name from EmbeddableHost * DEV: Deprecate class_name field of EmbeddableHost * FEATURE: Use either author or discourse-username meta tag * DEV: Deprecate discourse_username parameter * DEV: Improve embed code sample
This commit is contained in:
@ -5,32 +5,13 @@ RSpec.describe EmbedController do
|
||||
let(:embed_url_secure) { "https://eviltrout.com/2013/02/10/why-discourse-uses-emberjs.html" }
|
||||
let(:discourse_username) { "eviltrout" }
|
||||
|
||||
it "is 404 without an embed_url" do
|
||||
get "/embed/comments"
|
||||
expect(response.body).to match(I18n.t("embed.error"))
|
||||
end
|
||||
|
||||
it "raises an error with a missing host" do
|
||||
get "/embed/comments", params: { embed_url: embed_url }
|
||||
expect(response.body).to match(I18n.t("embed.error"))
|
||||
end
|
||||
|
||||
describe "by topic id" do
|
||||
let(:headers) { { "REFERER" => "http://eviltrout.com/some-page" } }
|
||||
|
||||
before { Fabricate(:embeddable_host) }
|
||||
|
||||
it "allows a topic to be embedded by id" do
|
||||
topic = Fabricate(:topic)
|
||||
get "/embed/comments", params: { topic_id: topic.id }, headers: headers
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
end
|
||||
fab!(:topic) { Fabricate(:topic) }
|
||||
|
||||
describe "#info" do
|
||||
context "without api key" do
|
||||
it "fails" do
|
||||
get "/embed/info.json"
|
||||
|
||||
expect(response.body).to match(I18n.t("embed.error"))
|
||||
end
|
||||
end
|
||||
@ -51,10 +32,9 @@ RSpec.describe EmbedController do
|
||||
HTTP_API_USERNAME: "system",
|
||||
}
|
||||
|
||||
json = response.parsed_body
|
||||
expect(json["topic_id"]).to eq(topic_embed.topic.id)
|
||||
expect(json["post_id"]).to eq(topic_embed.post.id)
|
||||
expect(json["topic_slug"]).to eq(topic_embed.topic.slug)
|
||||
expect(response.parsed_body["topic_id"]).to eq(topic_embed.topic.id)
|
||||
expect(response.parsed_body["post_id"]).to eq(topic_embed.post.id)
|
||||
expect(response.parsed_body["topic_slug"]).to eq(topic_embed.topic.slug)
|
||||
end
|
||||
end
|
||||
|
||||
@ -79,6 +59,7 @@ RSpec.describe EmbedController do
|
||||
describe "#topics" do
|
||||
it "raises an error when not enabled" do
|
||||
get "/embed/topics?embed_id=de-1234"
|
||||
|
||||
expect(response.status).to eq(400)
|
||||
end
|
||||
|
||||
@ -87,15 +68,16 @@ RSpec.describe EmbedController do
|
||||
|
||||
it "raises an error with a weird id" do
|
||||
get "/embed/topics?discourse_embed_id=../asdf/-1234", headers: headers
|
||||
|
||||
expect(response.status).to eq(400)
|
||||
end
|
||||
|
||||
it "returns a list of topics" do
|
||||
topic = Fabricate(:topic)
|
||||
get "/embed/topics?discourse_embed_id=de-1234",
|
||||
headers: {
|
||||
"REFERER" => "https://example.com/evil-trout",
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.headers["X-Frame-Options"]).to be_nil
|
||||
expect(response.body).to match("data-embed-id=\"de-1234\"")
|
||||
@ -104,7 +86,6 @@ RSpec.describe EmbedController do
|
||||
end
|
||||
|
||||
it "returns a list of top topics" do
|
||||
bad_topic = Fabricate(:topic)
|
||||
good_topic = Fabricate(:topic, like_count: 1000, posts_count: 100)
|
||||
TopTopic.refresh!
|
||||
|
||||
@ -116,13 +97,11 @@ RSpec.describe EmbedController do
|
||||
expect(response.headers["X-Frame-Options"]).to be_nil
|
||||
expect(response.body).to match("data-embed-id=\"de-1234\"")
|
||||
expect(response.body).to match("data-topic-id=\"#{good_topic.id}\"")
|
||||
expect(response.body).not_to match("data-topic-id=\"#{bad_topic.id}\"")
|
||||
expect(response.body).not_to match("data-topic-id=\"#{topic.id}\"")
|
||||
expect(response.body).to match("data-referer=\"https://example.com/evil-trout\"")
|
||||
end
|
||||
|
||||
it "returns a list of topics if the top_period is not valid" do
|
||||
topic1 = Fabricate(:topic)
|
||||
topic2 = Fabricate(:topic)
|
||||
good_topic = Fabricate(:topic, like_count: 1000, posts_count: 100)
|
||||
TopTopic.refresh!
|
||||
TopicQuery.any_instance.expects(:list_top_for).never
|
||||
@ -131,21 +110,21 @@ RSpec.describe EmbedController do
|
||||
headers: {
|
||||
"REFERER" => "https://example.com/evil-trout",
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.headers["X-Frame-Options"]).to be_nil
|
||||
expect(response.body).to match("data-embed-id=\"de-1234\"")
|
||||
expect(response.body).to match("data-topic-id=\"#{good_topic.id}\"")
|
||||
expect(response.body).to match("data-topic-id=\"#{topic1.id}\"")
|
||||
expect(response.body).to match("data-topic-id=\"#{topic2.id}\"")
|
||||
expect(response.body).to match("data-topic-id=\"#{topic.id}\"")
|
||||
expect(response.body).to match("data-referer=\"https://example.com/evil-trout\"")
|
||||
end
|
||||
|
||||
it "wraps the list in a custom class" do
|
||||
topic = Fabricate(:topic)
|
||||
get "/embed/topics?discourse_embed_id=de-1234&embed_class=my-special-class",
|
||||
headers: {
|
||||
"REFERER" => "https://example.com/evil-trout",
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.headers["X-Frame-Options"]).to be_nil
|
||||
expect(response.body).to match("class='topics-list my-special-class'")
|
||||
@ -153,222 +132,293 @@ RSpec.describe EmbedController do
|
||||
|
||||
it "returns no referer if not supplied" do
|
||||
get "/embed/topics?discourse_embed_id=de-1234"
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.body).to match("data-referer=\"\"")
|
||||
end
|
||||
|
||||
it "returns * for the referer if `embed_any_origin` is set" do
|
||||
SiteSetting.embed_any_origin = true
|
||||
|
||||
get "/embed/topics?discourse_embed_id=de-1234"
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.body).to match("data-referer=\"\\*\"")
|
||||
end
|
||||
|
||||
it "disallows indexing the embed topic list" do
|
||||
topic = Fabricate(:topic)
|
||||
get "/embed/topics?discourse_embed_id=de-1234",
|
||||
headers: {
|
||||
"REFERER" => "https://example.com/evil-trout",
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.headers["X-Robots-Tag"]).to match(/noindex/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with a host" do
|
||||
let!(:embeddable_host) { Fabricate(:embeddable_host) }
|
||||
let(:headers) { { "REFERER" => embed_url } }
|
||||
describe "#comments" do
|
||||
it "is 404 without an embed_url" do
|
||||
get "/embed/comments"
|
||||
|
||||
before { Jobs.run_immediately! }
|
||||
expect(response.body).to match(I18n.t("embed.error"))
|
||||
end
|
||||
|
||||
it "doesn't raises an error with no referer" do
|
||||
it "raises an error with a missing host" do
|
||||
get "/embed/comments", params: { embed_url: embed_url }
|
||||
expect(response.body).not_to match(I18n.t("embed.error"))
|
||||
|
||||
expect(response.body).to match(I18n.t("embed.error"))
|
||||
end
|
||||
|
||||
it "includes CSS from embedded_scss field" do
|
||||
theme = Fabricate(:theme)
|
||||
theme.set_default!
|
||||
describe "by topic id" do
|
||||
fab!(:embeddable_host) { Fabricate(:embeddable_host) }
|
||||
|
||||
ThemeField.create!(
|
||||
theme_id: theme.id,
|
||||
name: "embedded_scss",
|
||||
target_id: 0,
|
||||
type_id: 1,
|
||||
value: ".test-osama-15 {\n" + " color: red;\n" + "}\n",
|
||||
)
|
||||
it "allows a topic to be embedded by id" do
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
topic_id: topic.id,
|
||||
},
|
||||
headers: {
|
||||
"REFERER" => "http://eviltrout.com/some-page",
|
||||
}
|
||||
|
||||
topic_embed = Fabricate(:topic_embed, embed_url: embed_url)
|
||||
post = Fabricate(:post, topic: topic_embed.topic)
|
||||
|
||||
get "/embed/comments", params: { embed_url: embed_url }, headers: headers
|
||||
|
||||
html = Nokogiri::HTML5.fragment(response.body)
|
||||
css_link = html.at("link[data-target=embedded_theme]").attribute("href").value
|
||||
|
||||
get css_link
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.body).to include(".test-osama-15")
|
||||
end
|
||||
|
||||
it "includes HTML from embedded_header field" do
|
||||
theme = Fabricate(:theme)
|
||||
theme.set_default!
|
||||
|
||||
ThemeField.create!(
|
||||
theme_id: theme.id,
|
||||
name: "embedded_header",
|
||||
target_id: 0,
|
||||
type_id: 0,
|
||||
value: "<strong class='custom-text'>hey there!</strong>\n",
|
||||
)
|
||||
|
||||
topic_embed = Fabricate(:topic_embed, embed_url: embed_url)
|
||||
post = Fabricate(:post, topic: topic_embed.topic)
|
||||
|
||||
get "/embed/comments", params: { embed_url: embed_url }, headers: headers
|
||||
|
||||
html = Nokogiri::HTML5.fragment(response.body)
|
||||
custom_header = html.at(".custom-text")
|
||||
|
||||
expect(custom_header.name).to eq("strong")
|
||||
expect(custom_header.text).to eq("hey there!")
|
||||
end
|
||||
|
||||
context "with success" do
|
||||
after do
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.headers["X-Frame-Options"]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "with a host" do
|
||||
fab!(:embeddable_host) { Fabricate(:embeddable_host) }
|
||||
|
||||
before { Jobs.run_immediately! }
|
||||
|
||||
it "doesn't raise an error with no referer" do
|
||||
get "/embed/comments", params: { embed_url: embed_url }
|
||||
|
||||
expect(response.body).not_to match(I18n.t("embed.error"))
|
||||
end
|
||||
|
||||
it "tells the topic retriever to work when no previous embed is found" do
|
||||
TopicEmbed.expects(:topic_id_for_embed).returns(nil)
|
||||
retriever = mock
|
||||
TopicRetriever.expects(:new).returns(retriever)
|
||||
retriever.expects(:retrieve)
|
||||
get "/embed/comments", params: { embed_url: embed_url }, headers: headers
|
||||
end
|
||||
it "includes CSS from embedded_scss field" do
|
||||
theme = Fabricate(:theme)
|
||||
theme.set_default!
|
||||
|
||||
ThemeField.create!(
|
||||
theme_id: theme.id,
|
||||
name: "embedded_scss",
|
||||
target_id: 0,
|
||||
type_id: 1,
|
||||
value: ".test-osama-15 { color: red }",
|
||||
)
|
||||
|
||||
it "displays the right view" do
|
||||
topic_embed = Fabricate(:topic_embed, embed_url: embed_url)
|
||||
post = Fabricate(:post, topic: topic_embed.topic)
|
||||
|
||||
get "/embed/comments", params: { embed_url: embed_url_secure }, headers: headers
|
||||
get "/embed/comments", params: { embed_url: embed_url }, headers: { "REFERER" => embed_url }
|
||||
|
||||
expect(response.body).to match(I18n.t("embed.start_discussion"))
|
||||
html = Nokogiri::HTML5.fragment(response.body)
|
||||
get html.at("link[data-target=embedded_theme]").attribute("href").value
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.body).to include(".test-osama-15")
|
||||
end
|
||||
|
||||
it "creates a topic view when a topic_id is found" do
|
||||
it "includes HTML from embedded_header field" do
|
||||
theme = Fabricate(:theme)
|
||||
theme.set_default!
|
||||
|
||||
ThemeField.create!(
|
||||
theme_id: theme.id,
|
||||
name: "embedded_header",
|
||||
target_id: 0,
|
||||
type_id: 0,
|
||||
value: "<strong class='custom-text'>hey there!</strong>\n",
|
||||
)
|
||||
|
||||
topic_embed = Fabricate(:topic_embed, embed_url: embed_url)
|
||||
post = Fabricate(:post, topic: topic_embed.topic)
|
||||
|
||||
get "/embed/comments", params: { embed_url: embed_url }, headers: headers
|
||||
|
||||
expect(response.body).to match(I18n.t("embed.continue"))
|
||||
expect(response.body).to match(post.cooked)
|
||||
expect(response.body).to match("<span class='replies'>1 reply</span>")
|
||||
html = Nokogiri::HTML5.fragment(response.body)
|
||||
custom_header = html.at(".custom-text")
|
||||
|
||||
small_action = Fabricate(:small_action, topic: topic_embed.topic)
|
||||
|
||||
get "/embed/comments", params: { embed_url: embed_url }, headers: headers
|
||||
|
||||
expect(response.body).not_to match("post-#{small_action.id}")
|
||||
expect(response.body).to match("<span class='replies'>1 reply</span>")
|
||||
expect(custom_header.name).to eq("strong")
|
||||
expect(custom_header.text).to eq("hey there!")
|
||||
end
|
||||
|
||||
it "provides the topic retriever with the discourse username when provided" do
|
||||
retriever = mock
|
||||
retriever.expects(:retrieve).returns(nil)
|
||||
TopicRetriever
|
||||
.expects(:new)
|
||||
.with(embed_url, has_entry(author_username: discourse_username))
|
||||
.returns(retriever)
|
||||
context "with success" do
|
||||
it "tells the topic retriever to work when no previous embed is found" do
|
||||
TopicRetriever.any_instance.expects(:retrieve)
|
||||
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
embed_url: embed_url,
|
||||
discourse_username: discourse_username,
|
||||
},
|
||||
headers: headers
|
||||
end
|
||||
end
|
||||
end
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
embed_url: embed_url,
|
||||
},
|
||||
headers: {
|
||||
"REFERER" => embed_url,
|
||||
}
|
||||
|
||||
context "with multiple hosts" do
|
||||
before do
|
||||
Fabricate(:embeddable_host)
|
||||
Fabricate(:embeddable_host, host: "http://discourse.org")
|
||||
Fabricate(:embeddable_host, host: "https://example.com/1234", class_name: "example")
|
||||
end
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.headers["X-Frame-Options"]).to be_nil
|
||||
end
|
||||
|
||||
context "with success" do
|
||||
it "works with the first host" do
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
embed_url: embed_url,
|
||||
},
|
||||
headers: {
|
||||
"REFERER" => "http://eviltrout.com/wat/1-2-3.html",
|
||||
}
|
||||
it "displays the right view" do
|
||||
topic_embed = Fabricate(:topic_embed, embed_url: embed_url)
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
embed_url: embed_url_secure,
|
||||
},
|
||||
headers: {
|
||||
"REFERER" => embed_url,
|
||||
}
|
||||
|
||||
it "works with the second host" do
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
embed_url: embed_url,
|
||||
},
|
||||
headers: {
|
||||
"REFERER" => "http://eviltrout.com/wat/1-2-3.html",
|
||||
}
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.headers["X-Frame-Options"]).to be_nil
|
||||
expect(response.body).to match(I18n.t("embed.start_discussion"))
|
||||
end
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
it "creates a topic view when a topic_id is found" do
|
||||
topic_embed = Fabricate(:topic_embed, embed_url: embed_url)
|
||||
|
||||
it "works with a host with a path" do
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
embed_url: embed_url,
|
||||
},
|
||||
headers: {
|
||||
"REFERER" => "https://example.com/some-other-path",
|
||||
}
|
||||
post = Fabricate(:post, topic: topic_embed.topic)
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
embed_url: embed_url,
|
||||
},
|
||||
headers: {
|
||||
"REFERER" => embed_url,
|
||||
}
|
||||
|
||||
it "contains custom class name" do
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
embed_url: embed_url,
|
||||
},
|
||||
headers: {
|
||||
"REFERER" => "https://example.com/some-other-path",
|
||||
}
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.headers["X-Frame-Options"]).to be_nil
|
||||
expect(response.body).to match(I18n.t("embed.continue"))
|
||||
expect(response.body).to match(post.cooked)
|
||||
expect(response.body).to match("<span class='replies'>1 reply</span>")
|
||||
|
||||
expect(response.body).to match('class="example"')
|
||||
small_action = Fabricate(:small_action, topic: topic_embed.topic)
|
||||
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
embed_url: embed_url,
|
||||
},
|
||||
headers: {
|
||||
"REFERER" => embed_url,
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.headers["X-Frame-Options"]).to be_nil
|
||||
expect(response.body).not_to match("post-#{small_action.id}")
|
||||
expect(response.body).to match("<span class='replies'>1 reply</span>")
|
||||
end
|
||||
|
||||
it "provides the topic retriever with the discourse username when provided" do
|
||||
TopicRetriever.any_instance.expects(:retrieve).returns(nil)
|
||||
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
embed_url: embed_url,
|
||||
discourse_username: discourse_username,
|
||||
},
|
||||
headers: {
|
||||
"REFERER" => embed_url,
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.headers["X-Frame-Options"]).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with CSP frame-ancestors enabled" do
|
||||
before { SiteSetting.content_security_policy_frame_ancestors = true }
|
||||
context "with multiple hosts" do
|
||||
fab!(:embeddable_host_1) { Fabricate(:embeddable_host) }
|
||||
fab!(:embeddable_host_2) { Fabricate(:embeddable_host, host: "http://discourse.org") }
|
||||
fab!(:embeddable_host_3) do
|
||||
Fabricate(:embeddable_host, host: "https://example.com/1234", class_name: "example")
|
||||
end
|
||||
|
||||
it "includes all the hosts" do
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
embed_url: embed_url,
|
||||
},
|
||||
headers: {
|
||||
"REFERER" => "http://eviltrout.com/wat/1-2-3.html",
|
||||
}
|
||||
context "with success" do
|
||||
it "works with the first host" do
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
embed_url: embed_url,
|
||||
},
|
||||
headers: {
|
||||
"REFERER" => "http://eviltrout.com/wat/1-2-3.html",
|
||||
}
|
||||
|
||||
expect(response.headers["Content-Security-Policy"]).to match(
|
||||
%r{frame-ancestors.*https://discourse\.org},
|
||||
)
|
||||
expect(response.headers["Content-Security-Policy"]).to match(
|
||||
%r{frame-ancestors.*https://example\.com},
|
||||
)
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
|
||||
it "works with the second host" do
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
embed_url: embed_url,
|
||||
},
|
||||
headers: {
|
||||
"REFERER" => "http://eviltrout.com/wat/1-2-3.html",
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
|
||||
it "works with a host with a path" do
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
embed_url: embed_url,
|
||||
},
|
||||
headers: {
|
||||
"REFERER" => "https://example.com/some-other-path",
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
|
||||
it "contains custom class name" do
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
embed_url: embed_url,
|
||||
},
|
||||
headers: {
|
||||
"REFERER" => "https://example.com/some-other-path",
|
||||
}
|
||||
|
||||
expect(response.body).to match('class="example"')
|
||||
end
|
||||
|
||||
it "contains custom class name from params" do
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
embed_url: embed_url,
|
||||
class_name: "param-class-name",
|
||||
},
|
||||
headers: {
|
||||
"REFERER" => "https://example.com/some-other-path",
|
||||
}
|
||||
|
||||
expect(response.body).to match('class="param-class-name"')
|
||||
end
|
||||
end
|
||||
|
||||
context "with CSP frame-ancestors enabled" do
|
||||
before { SiteSetting.content_security_policy_frame_ancestors = true }
|
||||
|
||||
it "includes all the hosts" do
|
||||
get "/embed/comments",
|
||||
params: {
|
||||
embed_url: embed_url,
|
||||
},
|
||||
headers: {
|
||||
"REFERER" => "http://eviltrout.com/wat/1-2-3.html",
|
||||
}
|
||||
|
||||
expect(response.headers["Content-Security-Policy"]).to match(
|
||||
%r{frame-ancestors.*https://discourse\.org},
|
||||
)
|
||||
expect(response.headers["Content-Security-Policy"]).to match(
|
||||
%r{frame-ancestors.*https://example\.com},
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Reference in New Issue
Block a user