0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-09 21:45:25 +08:00
discourse/plugins/discourse-calendar/spec/requests/livestream_controller_spec.rb
Kris 0cf23c2854
FEATURE: Add a livestream_allowed_hosts site setting for livestreams (#42374)
Currently the hosts that can be used for a livestream event are
hardcoded in the composer's JavaScript, and the server accepts any https
location, so the list isn't really enforced... and there's no way to use
hosts that would otherwise work, including custom or self-hosted
sources.

This adds a `livestream_allowed_hosts` site setting (defaulting to the
existing hardcoded list) and validates against it both in the composer
and on the server. Whether a livestream embeds a player still depends on
the site's onebox settings.

---------

Co-authored-by: Martin Brennan <martin@discourse.org>
2026-08-06 14:49:38 -04:00

183 lines
6.5 KiB
Ruby
Vendored

# frozen_string_literal: true
module DiscourseCalendar
describe LivestreamController do
fab!(:current_user, :user)
fab!(:tag) { Fabricate(:tag, name: "livestream") }
fab!(:topic) { Fabricate(:topic, user: current_user, tags: [tag]) }
fab!(:post) { Fabricate(:post, user: current_user, topic: topic, post_number: 1) }
fab!(:event) do
Fabricate(
:event,
post: post,
location: "https://us06web.zoom.us/j/123456789?pwd=secret",
livestream: true,
original_starts_at: 5.minutes.ago.iso8601,
original_ends_at: 1.hour.from_now.iso8601,
)
end
before do
SiteSetting.calendar_enabled = true
SiteSetting.discourse_post_event_enabled = true
SiteSetting.livestream_zoom_enabled = true
SiteSetting.livestream_zoom_sdk_key = "sdk-key"
SiteSetting.livestream_zoom_sdk_secret = "sdk-secret"
end
describe "#prepare_zoom_signature" do
it "rejects anonymous users" do
get "/discourse-calendar/livestream/zoom/signature.json", params: { topic_id: topic.id }
expect(response.status).to eq(403)
end
context "when signed in" do
before { sign_in(current_user) }
it "returns the Zoom join payload" do
get "/discourse-calendar/livestream/zoom/signature.json", params: { topic_id: topic.id }
expect(response.status).to eq(200)
expect(response.parsed_body["sdk_key"]).to eq("sdk-key")
expect(response.parsed_body["meeting_number"]).to eq("123456789")
expect(response.parsed_body["password"]).to eq("secret")
expect(response.parsed_body["user_name"]).to eq(current_user.display_name)
expect(response.parsed_body["user_email"]).to eq(current_user.email)
expect(response.parsed_body["leave_url"]).to eq(topic.relative_url)
expect(response.parsed_body["signature"]).to be_present
end
it "returns a signature the Zoom SDK can verify" do
get "/discourse-calendar/livestream/zoom/signature.json", params: { topic_id: topic.id }
claims =
JWT.decode(response.parsed_body["signature"], "sdk-secret", true, algorithm: "HS256")[0]
expect(claims["sdkKey"]).to eq("sdk-key")
expect(claims["mn"]).to eq("123456789")
expect(claims["role"]).to eq(0)
end
it "rejects invalid params" do
get "/discourse-calendar/livestream/zoom/signature.json"
expect(response.status).to eq(400)
end
it "returns not found when livestreams are unavailable" do
SiteSetting.discourse_post_event_enabled = false
get "/discourse-calendar/livestream/zoom/signature.json", params: { topic_id: topic.id }
expect(response.status).to eq(404)
end
it "returns not found when Zoom embedding is unavailable" do
SiteSetting.livestream_zoom_enabled = false
get "/discourse-calendar/livestream/zoom/signature.json", params: { topic_id: topic.id }
expect(response.status).to eq(404)
end
it "returns not found when the topic does not exist" do
get "/discourse-calendar/livestream/zoom/signature.json", params: { topic_id: -1 }
expect(response.status).to eq(404)
end
it "returns not found when the topic cannot be seen" do
group = Fabricate(:group)
private_category = Fabricate(:private_category, group: group)
private_topic = Fabricate(:topic, category: private_category, tags: [tag])
private_post = Fabricate(:post, topic: private_topic, post_number: 1)
Fabricate(
:event,
post: private_post,
location: "https://us06web.zoom.us/j/123456789?pwd=secret",
livestream: true,
)
get "/discourse-calendar/livestream/zoom/signature.json",
params: {
topic_id: private_topic.id,
}
expect(response.status).to eq(404)
end
it "returns not found when there is no first-post event" do
topic_without_event = Fabricate(:topic, user: current_user, tags: [tag])
Fabricate(:post, user: current_user, topic: topic_without_event, post_number: 1)
get "/discourse-calendar/livestream/zoom/signature.json",
params: {
topic_id: topic_without_event.id,
}
expect(response.status).to eq(404)
end
it "returns not found when the event is not a livestream" do
event.update!(livestream: false)
get "/discourse-calendar/livestream/zoom/signature.json", params: { topic_id: topic.id }
expect(response.status).to eq(404)
end
it "returns not found when the livestream has no location or URL" do
event.update_columns(location: nil, url: nil)
get "/discourse-calendar/livestream/zoom/signature.json", params: { topic_id: topic.id }
expect(response.status).to eq(404)
end
it "returns not found when the livestream URL is not a supported Zoom URL" do
event.update!(location: "https://www.youtube.com/live/abc123")
get "/discourse-calendar/livestream/zoom/signature.json", params: { topic_id: topic.id }
expect(response.status).to eq(404)
end
it "returns not found when the event is outside its timeframe" do
event.update!(
original_starts_at: 2.hours.from_now.iso8601,
original_ends_at: 3.hours.from_now.iso8601,
)
get "/discourse-calendar/livestream/zoom/signature.json", params: { topic_id: topic.id }
expect(response.status).to eq(404)
end
it "returns not found when the livestream URL is not served over HTTPS" do
event.update_columns(location: "http://zoom.us/j/123456789")
get "/discourse-calendar/livestream/zoom/signature.json", params: { topic_id: topic.id }
expect(response.status).to eq(404)
end
end
end
# The full-page mobile Zoom view is a client-side route rendered by the topic
# controller, so the server just needs to serve the topic at that path.
describe "the full-page Zoom route" do
it "serves the topic" do
get "/t/#{topic.slug}/#{topic.id}/zoom"
expect(response.status).to eq(200)
end
it "returns not found for an unknown topic" do
get "/t/#{topic.slug}/-1/zoom"
expect(response.status).to eq(404)
end
end
end
end