mirror of
https://github.com/discourse/discourse.git
synced 2026-08-08 14:34:02 +08:00
## Summary EmbeddableHost allowed_paths values are stored as regular expression source and compiled during request handling without validation or error handling. Now new or updated allowlists are validated on save, and legacy invalid or timed-out patterns are safely skipped as non-matches rather than causing HTTP 500 errors on public embed endpoints. This prevents an administrator from accidentally breaking embed functionality across their site. ## Source - Patch Triage: `patch-triage/1556` Co-authored-by: discourse-patch-triage <272280883+discourse-patch-triage[bot]@users.noreply.github.com>
690 lines
22 KiB
Ruby
Vendored
690 lines
22 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe EmbedController do
|
|
let(:embed_url) { "http://eviltrout.com/2013/02/10/why-discourse-uses-emberjs.html" }
|
|
let(:embed_url_secure) { "https://eviltrout.com/2013/02/10/why-discourse-uses-emberjs.html" }
|
|
|
|
fab!(: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
|
|
|
|
context "with api key" do
|
|
let(:api_key) { Fabricate(:api_key) }
|
|
|
|
context "with valid embed url" do
|
|
let(:topic_embed) { Fabricate(:topic_embed, embed_url: embed_url) }
|
|
|
|
it "returns information about the topic" do
|
|
get "/embed/info.json",
|
|
params: {
|
|
embed_url: topic_embed.embed_url,
|
|
},
|
|
headers: {
|
|
HTTP_API_KEY: api_key.key,
|
|
HTTP_API_USERNAME: "system",
|
|
}
|
|
|
|
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
|
|
|
|
it "returns not found for topics the API user cannot see" do
|
|
user = Fabricate(:user)
|
|
api_key = Fabricate(:api_key, user: user)
|
|
restricted_category = Fabricate(:category)
|
|
restricted_category.set_permissions(staff: :full)
|
|
restricted_category.save!
|
|
restricted_topic = Fabricate(:topic, category: restricted_category, posts_count: 5)
|
|
restricted_topic_embed =
|
|
Fabricate(
|
|
:topic_embed,
|
|
post: Fabricate(:post, topic: restricted_topic),
|
|
topic: restricted_topic,
|
|
embed_url: "http://eviltrout.com/private-article",
|
|
)
|
|
|
|
get "/embed/info.json",
|
|
params: {
|
|
embed_url: restricted_topic_embed.embed_url,
|
|
},
|
|
headers: {
|
|
HTTP_API_KEY: api_key.key,
|
|
HTTP_API_USERNAME: user.username,
|
|
}
|
|
|
|
expect(response.status).to eq(404)
|
|
expect(response.parsed_body["error_type"]).to eq("not_found")
|
|
expect(response.body).not_to include(restricted_topic.slug)
|
|
end
|
|
end
|
|
|
|
context "without invalid embed url" do
|
|
it "returns error response" do
|
|
get "/embed/info.json",
|
|
params: {
|
|
embed_url: "http://nope.com",
|
|
},
|
|
headers: {
|
|
HTTP_API_KEY: api_key.key,
|
|
HTTP_API_USERNAME: "system",
|
|
}
|
|
|
|
json = response.parsed_body
|
|
expect(json["error_type"]).to eq("not_found")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
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
|
|
|
|
context "when enabled" do
|
|
before { SiteSetting.embed_topics_list = true }
|
|
|
|
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
|
|
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\"")
|
|
expect(response.body).to match("data-topic-id=\"#{topic.id}\"")
|
|
expect(response.body).to match("data-referer=\"https://example.com/evil-trout\"")
|
|
end
|
|
|
|
it "ignores invalid path allowlists from legacy hosts" do
|
|
embeddable_host = Fabricate(:embeddable_host, allowed_paths: "/articles/.*")
|
|
embeddable_host.update_column(:allowed_paths, "[invalid")
|
|
|
|
get "/embed/topics?discourse_embed_id=de-1234",
|
|
headers: {
|
|
"REFERER" => "https://#{embeddable_host.host}/articles/test",
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(response.body).to match("data-embed-id=\"de-1234\"")
|
|
end
|
|
|
|
it "returns a list of top topics" do
|
|
good_topic = Fabricate(:topic, like_count: 1000, posts_count: 100)
|
|
TopTopic.refresh!
|
|
|
|
get "/embed/topics?discourse_embed_id=de-1234&top_period=yearly",
|
|
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).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
|
|
good_topic = Fabricate(:topic, like_count: 1000, posts_count: 100)
|
|
TopTopic.refresh!
|
|
TopicQuery.any_instance.expects(:list_top_for).never
|
|
|
|
get "/embed/topics?discourse_embed_id=de-1234&top_period=decadely",
|
|
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=\"#{topic.id}\"")
|
|
expect(response.body).to match("data-referer=\"https://example.com/evil-trout\"")
|
|
end
|
|
|
|
it "wraps the list in a custom class" do
|
|
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'")
|
|
end
|
|
|
|
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
|
|
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
|
|
|
|
describe "#comments" do
|
|
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
|
|
fab!(:embeddable_host)
|
|
|
|
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",
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
end
|
|
|
|
it "does not share an anonymous cached response between referers" do
|
|
global_setting :anon_cache_store_threshold, 1
|
|
Middleware::AnonymousCache.enable_anon_cache
|
|
Middleware::AnonymousCache.clear_all_cache!
|
|
|
|
attacker_referer = "https://origin-a.example/page"
|
|
victim_referer = "https://origin-b.example/page"
|
|
|
|
get "/embed/comments",
|
|
params: {
|
|
topic_id: topic.id,
|
|
},
|
|
headers: {
|
|
"REFERER" => attacker_referer,
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(response.headers["X-Discourse-Cached"]).to eq("store")
|
|
expect(response.body).to include("data-referer=\"#{attacker_referer}\"")
|
|
|
|
get "/embed/comments",
|
|
params: {
|
|
topic_id: topic.id,
|
|
},
|
|
headers: {
|
|
"REFERER" => victim_referer,
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(response.headers["X-Discourse-Cached"]).to eq("store")
|
|
expect(response.body).to include("data-referer=\"#{victim_referer}\"")
|
|
end
|
|
|
|
fab!(:attacker, :trust_level_1)
|
|
fab!(:existing_post) { Fabricate(:post, topic: topic) }
|
|
|
|
it "does not reinterpret an allowlisted non-Vimeo iframe as Vimeo content" do
|
|
iframe_source = "https://www.instagram.com/?x=player.vimeo.com/<img src=x onerror=alert(1)>"
|
|
sign_in(attacker)
|
|
|
|
post "/posts.json",
|
|
params: {
|
|
raw: %(<iframe src="#{iframe_source}"></iframe>),
|
|
topic_id: topic.id,
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
created_post = Post.find(response.parsed_body["id"])
|
|
cooked_iframe = Nokogiri::HTML5.fragment(created_post.cooked).at_css("iframe")
|
|
|
|
aggregate_failures do
|
|
expect(created_post).to have_attributes(hidden: false, deleted_at: nil)
|
|
expect(cooked_iframe["src"]).to eq(iframe_source)
|
|
end
|
|
|
|
sign_out
|
|
get "/embed/comments", params: { topic_id: topic.id }
|
|
|
|
document = Nokogiri.HTML5(response.body)
|
|
rendered_iframe = document.at_css("article#post-#{created_post.id} .cooked > iframe")
|
|
|
|
aggregate_failures do
|
|
expect(response.status).to eq(200)
|
|
expect(rendered_iframe).to be_present
|
|
expect(rendered_iframe&.[]("src")).to eq(iframe_source)
|
|
expect(document.css("img[onerror]")).to be_empty
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "full_app redirect" do
|
|
fab!(:embeddable_host)
|
|
|
|
before { SiteSetting.embed_full_app = true }
|
|
|
|
it "redirects to topic URL with embed_mode when full_app is present" do
|
|
topic_embed = Fabricate(:topic_embed, embed_url: embed_url)
|
|
|
|
get "/embed/comments",
|
|
params: {
|
|
embed_url: embed_url,
|
|
full_app: "true",
|
|
},
|
|
headers: {
|
|
"REFERER" => embed_url,
|
|
}
|
|
|
|
expect(response).to redirect_to("#{topic_embed.topic.url}?embed_mode=true")
|
|
end
|
|
|
|
it "redirects to topic URL with embed_mode when using topic_id" do
|
|
get "/embed/comments",
|
|
params: {
|
|
topic_id: topic.id,
|
|
full_app: "true",
|
|
},
|
|
headers: {
|
|
"REFERER" => "http://eviltrout.com/some-page",
|
|
}
|
|
|
|
expect(response).to redirect_to("#{topic.url}?embed_mode=true")
|
|
end
|
|
|
|
it "forwards class_name to the topic URL" do
|
|
get "/embed/comments",
|
|
params: {
|
|
topic_id: topic.id,
|
|
full_app: "true",
|
|
class_name: "lee-af",
|
|
},
|
|
headers: {
|
|
"REFERER" => "http://eviltrout.com/some-page",
|
|
}
|
|
|
|
expect(response).to redirect_to("#{topic.url}?class_name=lee-af&embed_mode=true")
|
|
end
|
|
|
|
it "redirects blank-slug topics to a slugless URL" do
|
|
topic_embed = Fabricate(:topic_embed, embed_url: embed_url)
|
|
topic_embed.topic.update_columns(title: "", slug: nil)
|
|
topic_embed.topic.reload
|
|
|
|
get "/embed/comments",
|
|
params: {
|
|
embed_url: embed_url,
|
|
full_app: "true",
|
|
},
|
|
headers: {
|
|
"REFERER" => embed_url,
|
|
}
|
|
|
|
expect(response).to redirect_to("#{topic_embed.topic.url}?embed_mode=true")
|
|
end
|
|
|
|
it "does not redirect when embed_full_app is disabled" do
|
|
SiteSetting.embed_full_app = false
|
|
topic_embed = Fabricate(:topic_embed, embed_url: embed_url)
|
|
|
|
get "/embed/comments",
|
|
params: {
|
|
embed_url: embed_url,
|
|
full_app: "true",
|
|
},
|
|
headers: {
|
|
"REFERER" => embed_url,
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
end
|
|
end
|
|
|
|
context "with a host" do
|
|
fab!(:embeddable_host)
|
|
|
|
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 "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 }",
|
|
)
|
|
|
|
topic_embed = Fabricate(:topic_embed, embed_url: embed_url)
|
|
Fabricate(:post, topic: topic_embed.topic)
|
|
|
|
get "/embed/comments", params: { embed_url: embed_url }, headers: { "REFERER" => embed_url }
|
|
|
|
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 "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
|
|
before { Jobs.run_immediately! }
|
|
|
|
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,
|
|
},
|
|
headers: {
|
|
"REFERER" => embed_url,
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(response.headers["X-Frame-Options"]).to be_nil
|
|
end
|
|
|
|
it "displays the right view" do
|
|
topic_embed = Fabricate(:topic_embed, embed_url: embed_url)
|
|
|
|
get "/embed/comments",
|
|
params: {
|
|
embed_url: embed_url_secure,
|
|
},
|
|
headers: {
|
|
"REFERER" => embed_url,
|
|
}
|
|
|
|
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
|
|
|
|
it "creates a topic view when a topic_id is found" 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,
|
|
},
|
|
headers: {
|
|
"REFERER" => embed_url,
|
|
}
|
|
|
|
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>")
|
|
|
|
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 "does not forward user-supplied discourse_username to TopicRetriever" do
|
|
captured_opts = nil
|
|
original_new = TopicRetriever.method(:new)
|
|
TopicRetriever
|
|
.stubs(:new)
|
|
.with do |url, opts|
|
|
captured_opts = opts
|
|
true
|
|
end
|
|
.returns(stub(retrieve: nil))
|
|
|
|
get "/embed/comments",
|
|
params: {
|
|
embed_url: embed_url,
|
|
discourse_username: "admin",
|
|
},
|
|
headers: {
|
|
"REFERER" => embed_url,
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(captured_opts).to be_present
|
|
expect(captured_opts[:author_username]).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with multiple hosts" do
|
|
fab!(:embeddable_host_1, :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
|
|
|
|
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.status).to eq(200)
|
|
end
|
|
|
|
it "works with the second host" do
|
|
get "/embed/comments",
|
|
params: {
|
|
embed_url: embed_url,
|
|
},
|
|
headers: {
|
|
"REFERER" => "https://discourse.org/blog-entry-1",
|
|
}
|
|
|
|
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
|
|
|
|
describe "#count" do
|
|
fab!(:embeddable_host)
|
|
|
|
it "returns counts for public topics" do
|
|
topic_embed = Fabricate(:topic_embed, embed_url: "http://eviltrout.com/public-article")
|
|
|
|
get "/embed/count.json", params: { embed_url: ["http://eviltrout.com/public-article"] }
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(response.parsed_body["counts"]).to have_key("http://eviltrout.com/public-article")
|
|
end
|
|
|
|
it "does not return counts for topics in restricted categories" do
|
|
restricted_category = Fabricate(:category)
|
|
restricted_category.set_permissions(staff: :full)
|
|
restricted_category.save!
|
|
|
|
restricted_topic = Fabricate(:topic, category: restricted_category, posts_count: 5)
|
|
Fabricate(
|
|
:topic_embed,
|
|
post: Fabricate(:post, topic: restricted_topic),
|
|
topic: restricted_topic,
|
|
embed_url: "http://eviltrout.com/private-article",
|
|
)
|
|
|
|
get "/embed/count.json", params: { embed_url: ["http://eviltrout.com/private-article"] }
|
|
|
|
expect(response.status).to eq(200)
|
|
expect(response.parsed_body["counts"]).not_to have_key("http://eviltrout.com/private-article")
|
|
end
|
|
|
|
it "returns counts only for visible topics when both public and restricted are requested" do
|
|
public_topic = Fabricate(:topic, posts_count: 3)
|
|
Fabricate(
|
|
:topic_embed,
|
|
post: Fabricate(:post, topic: public_topic),
|
|
topic: public_topic,
|
|
embed_url: "http://eviltrout.com/public-post",
|
|
)
|
|
|
|
restricted_category = Fabricate(:category)
|
|
restricted_category.set_permissions(staff: :full)
|
|
restricted_category.save!
|
|
|
|
restricted_topic = Fabricate(:topic, category: restricted_category, posts_count: 5)
|
|
Fabricate(
|
|
:topic_embed,
|
|
post: Fabricate(:post, topic: restricted_topic),
|
|
topic: restricted_topic,
|
|
embed_url: "http://eviltrout.com/secret-post",
|
|
)
|
|
|
|
get "/embed/count.json",
|
|
params: {
|
|
embed_url: %w[http://eviltrout.com/public-post http://eviltrout.com/secret-post],
|
|
}
|
|
|
|
expect(response.status).to eq(200)
|
|
counts = response.parsed_body["counts"]
|
|
expect(counts).to have_key("http://eviltrout.com/public-post")
|
|
expect(counts).not_to have_key("http://eviltrout.com/secret-post")
|
|
end
|
|
end
|
|
end
|