2020-04-27 22:41:31 +02:00
|
|
|
from unittest import mock
|
|
|
|
|
2023-08-29 09:00:59 +02:00
|
|
|
import orjson
|
2019-11-16 09:26:28 +01:00
|
|
|
import responses
|
2020-10-20 19:25:34 +02:00
|
|
|
from django.core.signing import Signer
|
2020-04-27 22:41:31 +02:00
|
|
|
from django.http import HttpResponseRedirect
|
2023-10-12 19:43:45 +02:00
|
|
|
from typing_extensions import override
|
2020-06-11 00:54:34 +02:00
|
|
|
|
2018-12-28 20:45:54 +01:00
|
|
|
from zerver.lib.test_classes import ZulipTestCase
|
2020-10-20 19:25:34 +02:00
|
|
|
from zerver.lib.url_encoding import append_url_query_string
|
2019-11-16 09:26:28 +01:00
|
|
|
|
2018-12-28 20:45:54 +01:00
|
|
|
|
2020-01-18 08:57:08 +01:00
|
|
|
class TestVideoCall(ZulipTestCase):
|
2023-10-12 19:43:45 +02:00
|
|
|
@override
|
2018-12-28 20:45:54 +01:00
|
|
|
def setUp(self) -> None:
|
2019-10-19 20:47:00 +02:00
|
|
|
super().setUp()
|
2019-11-16 09:26:28 +01:00
|
|
|
self.user = self.example_user("hamlet")
|
|
|
|
self.login_user(self.user)
|
2020-10-20 19:25:34 +02:00
|
|
|
# Signing for bbb
|
|
|
|
self.signer = Signer()
|
|
|
|
self.signed_bbb_a_object = self.signer.sign_object(
|
|
|
|
{
|
|
|
|
"meeting_id": "a",
|
|
|
|
"name": "a",
|
|
|
|
"password": "a",
|
|
|
|
}
|
|
|
|
)
|
2019-11-16 09:26:28 +01:00
|
|
|
|
|
|
|
def test_register_video_request_no_settings(self) -> None:
|
|
|
|
with self.settings(VIDEO_ZOOM_CLIENT_ID=None):
|
|
|
|
response = self.client_get("/calls/zoom/register")
|
|
|
|
self.assert_json_error(
|
2021-02-12 08:19:30 +01:00
|
|
|
response,
|
|
|
|
"Zoom credentials have not been configured",
|
2019-11-16 09:26:28 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
def test_register_video_request(self) -> None:
|
|
|
|
response = self.client_get("/calls/zoom/register")
|
|
|
|
self.assertEqual(response.status_code, 302)
|
2018-12-28 20:45:54 +01:00
|
|
|
|
2019-11-16 09:26:28 +01:00
|
|
|
@responses.activate
|
2023-08-27 02:43:48 +02:00
|
|
|
def test_create_zoom_video_and_audio_links(self) -> None:
|
2019-11-16 09:26:28 +01:00
|
|
|
responses.add(
|
|
|
|
responses.POST,
|
|
|
|
"https://zoom.us/oauth/token",
|
|
|
|
json={"access_token": "oldtoken", "expires_in": -60},
|
|
|
|
)
|
|
|
|
|
|
|
|
response = self.client_get(
|
|
|
|
"/calls/zoom/complete",
|
|
|
|
{"code": "code", "state": '{"realm":"zulip","sid":""}'},
|
|
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
2023-08-27 02:43:48 +02:00
|
|
|
# Test creating a video link
|
2019-11-16 09:26:28 +01:00
|
|
|
responses.replace(
|
|
|
|
responses.POST,
|
|
|
|
"https://zoom.us/oauth/token",
|
|
|
|
json={"access_token": "newtoken", "expires_in": 60},
|
|
|
|
)
|
|
|
|
|
|
|
|
responses.add(
|
|
|
|
responses.POST,
|
|
|
|
"https://api.zoom.us/v2/users/me/meetings",
|
|
|
|
json={"join_url": "example.com"},
|
|
|
|
)
|
|
|
|
|
2023-08-27 02:43:48 +02:00
|
|
|
response = self.client_post("/json/calls/zoom/create", {"is_video_call": "true"})
|
|
|
|
self.assertEqual(
|
|
|
|
responses.calls[-1].request.url,
|
|
|
|
"https://api.zoom.us/v2/users/me/meetings",
|
|
|
|
)
|
2023-11-15 02:21:11 +01:00
|
|
|
assert responses.calls[-1].request.body is not None
|
2023-08-29 09:00:59 +02:00
|
|
|
self.assertEqual(
|
|
|
|
orjson.loads(responses.calls[-1].request.body),
|
|
|
|
{
|
|
|
|
"settings": {
|
|
|
|
"host_video": True,
|
|
|
|
"participant_video": True,
|
|
|
|
},
|
2024-03-12 10:13:08 +01:00
|
|
|
"default_password": True,
|
2023-08-29 09:00:59 +02:00
|
|
|
},
|
|
|
|
)
|
2023-08-27 02:43:48 +02:00
|
|
|
self.assertEqual(
|
|
|
|
responses.calls[-1].request.headers["Authorization"],
|
|
|
|
"Bearer newtoken",
|
|
|
|
)
|
|
|
|
json = self.assert_json_success(response)
|
|
|
|
self.assertEqual(json["url"], "example.com")
|
|
|
|
|
|
|
|
# Test creating an audio link
|
|
|
|
responses.replace(
|
|
|
|
responses.POST,
|
|
|
|
"https://zoom.us/oauth/token",
|
|
|
|
json={"access_token": "newtoken", "expires_in": 60},
|
|
|
|
)
|
|
|
|
|
|
|
|
responses.add(
|
|
|
|
responses.POST,
|
|
|
|
"https://api.zoom.us/v2/users/me/meetings",
|
|
|
|
json={"join_url": "example.com"},
|
|
|
|
)
|
|
|
|
|
|
|
|
response = self.client_post("/json/calls/zoom/create", {"is_video_call": "false"})
|
2019-11-16 09:26:28 +01:00
|
|
|
self.assertEqual(
|
2021-02-12 08:19:30 +01:00
|
|
|
responses.calls[-1].request.url,
|
|
|
|
"https://api.zoom.us/v2/users/me/meetings",
|
2019-11-16 09:26:28 +01:00
|
|
|
)
|
2023-11-15 02:21:11 +01:00
|
|
|
assert responses.calls[-1].request.body is not None
|
2023-08-29 09:00:59 +02:00
|
|
|
self.assertEqual(
|
|
|
|
orjson.loads(responses.calls[-1].request.body),
|
|
|
|
{
|
|
|
|
"settings": {
|
|
|
|
"host_video": False,
|
|
|
|
"participant_video": False,
|
|
|
|
},
|
2024-03-12 10:13:08 +01:00
|
|
|
"default_password": True,
|
2023-08-29 09:00:59 +02:00
|
|
|
},
|
|
|
|
)
|
2019-11-16 09:26:28 +01:00
|
|
|
self.assertEqual(
|
2021-02-12 08:19:30 +01:00
|
|
|
responses.calls[-1].request.headers["Authorization"],
|
|
|
|
"Bearer newtoken",
|
2019-11-16 09:26:28 +01:00
|
|
|
)
|
|
|
|
json = self.assert_json_success(response)
|
|
|
|
self.assertEqual(json["url"], "example.com")
|
|
|
|
|
2023-08-27 02:43:48 +02:00
|
|
|
# Test for authentication error
|
2019-11-16 09:26:28 +01:00
|
|
|
self.logout()
|
|
|
|
self.login_user(self.user)
|
|
|
|
|
|
|
|
response = self.client_post("/json/calls/zoom/create")
|
|
|
|
self.assert_json_error(response, "Invalid Zoom access token")
|
|
|
|
|
|
|
|
def test_create_video_realm_redirect(self) -> None:
|
|
|
|
response = self.client_get(
|
|
|
|
"/calls/zoom/complete",
|
|
|
|
{"code": "code", "state": '{"realm":"zephyr","sid":"somesid"}'},
|
|
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 302)
|
2022-05-29 21:12:13 +02:00
|
|
|
self.assertIn("http://zephyr.testserver/", response["Location"])
|
|
|
|
self.assertIn("somesid", response["Location"])
|
2019-11-16 09:26:28 +01:00
|
|
|
|
|
|
|
def test_create_video_sid_error(self) -> None:
|
|
|
|
response = self.client_get(
|
|
|
|
"/calls/zoom/complete",
|
|
|
|
{"code": "code", "state": '{"realm":"zulip","sid":"bad"}'},
|
|
|
|
)
|
|
|
|
self.assert_json_error(response, "Invalid Zoom session identifier")
|
|
|
|
|
|
|
|
@responses.activate
|
|
|
|
def test_create_video_credential_error(self) -> None:
|
|
|
|
responses.add(responses.POST, "https://zoom.us/oauth/token", status=400)
|
|
|
|
|
|
|
|
response = self.client_get(
|
|
|
|
"/calls/zoom/complete",
|
|
|
|
{"code": "code", "state": '{"realm":"zulip","sid":""}'},
|
|
|
|
)
|
|
|
|
self.assert_json_error(response, "Invalid Zoom credentials")
|
|
|
|
|
|
|
|
@responses.activate
|
|
|
|
def test_create_video_refresh_error(self) -> None:
|
|
|
|
responses.add(
|
|
|
|
responses.POST,
|
|
|
|
"https://zoom.us/oauth/token",
|
|
|
|
json={"access_token": "token", "expires_in": -60},
|
|
|
|
)
|
|
|
|
|
|
|
|
response = self.client_get(
|
|
|
|
"/calls/zoom/complete",
|
|
|
|
{"code": "code", "state": '{"realm":"zulip","sid":""}'},
|
|
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
|
|
responses.replace(responses.POST, "https://zoom.us/oauth/token", status=400)
|
|
|
|
|
|
|
|
response = self.client_post("/json/calls/zoom/create")
|
|
|
|
self.assert_json_error(response, "Invalid Zoom access token")
|
2018-12-28 20:45:54 +01:00
|
|
|
|
2019-11-16 09:26:28 +01:00
|
|
|
@responses.activate
|
|
|
|
def test_create_video_request_error(self) -> None:
|
|
|
|
responses.add(
|
|
|
|
responses.POST,
|
|
|
|
"https://zoom.us/oauth/token",
|
|
|
|
json={"access_token": "token"},
|
|
|
|
)
|
2018-12-28 20:45:54 +01:00
|
|
|
|
2019-11-16 09:26:28 +01:00
|
|
|
responses.add(
|
2021-02-12 08:19:30 +01:00
|
|
|
responses.POST,
|
|
|
|
"https://api.zoom.us/v2/users/me/meetings",
|
|
|
|
status=400,
|
2019-11-16 09:26:28 +01:00
|
|
|
)
|
2019-01-17 00:25:22 +01:00
|
|
|
|
2019-11-16 09:26:28 +01:00
|
|
|
response = self.client_get(
|
|
|
|
"/calls/zoom/complete",
|
|
|
|
{"code": "code", "state": '{"realm":"zulip","sid":""}'},
|
|
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
2019-01-17 00:54:01 +01:00
|
|
|
|
2019-11-16 09:26:28 +01:00
|
|
|
response = self.client_post("/json/calls/zoom/create")
|
|
|
|
self.assert_json_error(response, "Failed to create Zoom call")
|
2019-01-17 00:54:01 +01:00
|
|
|
|
2019-11-16 09:26:28 +01:00
|
|
|
responses.replace(
|
2021-02-12 08:19:30 +01:00
|
|
|
responses.POST,
|
|
|
|
"https://api.zoom.us/v2/users/me/meetings",
|
|
|
|
status=401,
|
2019-11-16 09:26:28 +01:00
|
|
|
)
|
2019-01-17 00:54:01 +01:00
|
|
|
|
2019-11-16 09:26:28 +01:00
|
|
|
response = self.client_post("/json/calls/zoom/create")
|
|
|
|
self.assert_json_error(response, "Invalid Zoom access token")
|
2019-01-17 00:54:01 +01:00
|
|
|
|
2019-11-16 09:26:28 +01:00
|
|
|
@responses.activate
|
|
|
|
def test_deauthorize_zoom_user(self) -> None:
|
|
|
|
response = self.client_post(
|
|
|
|
"/calls/zoom/deauthorize",
|
|
|
|
"""\
|
|
|
|
{
|
|
|
|
"event": "app_deauthorized",
|
|
|
|
"payload": {
|
|
|
|
"user_data_retention": "false",
|
|
|
|
"account_id": "EabCDEFghiLHMA",
|
|
|
|
"user_id": "z9jkdsfsdfjhdkfjQ",
|
|
|
|
"signature": "827edc3452044f0bc86bdd5684afb7d1e6becfa1a767f24df1b287853cf73000",
|
|
|
|
"deauthorization_time": "2019-06-17T13:52:28.632Z",
|
|
|
|
"client_id": "ADZ9k9bTWmGUoUbECUKU_a"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
""",
|
|
|
|
content_type="application/json",
|
|
|
|
)
|
|
|
|
self.assert_json_success(response)
|
2020-04-27 22:41:31 +02:00
|
|
|
|
|
|
|
def test_create_bigbluebutton_link(self) -> None:
|
2024-07-12 02:30:32 +02:00
|
|
|
with (
|
|
|
|
mock.patch("zerver.views.video_calls.random.randint", return_value="1"),
|
|
|
|
mock.patch("secrets.token_bytes", return_value=b"\x00" * 20),
|
2021-02-12 08:19:30 +01:00
|
|
|
):
|
2020-10-20 19:25:34 +02:00
|
|
|
response = self.client_get(
|
|
|
|
"/json/calls/bigbluebutton/create?meeting_name=general > meeting"
|
|
|
|
)
|
2022-06-07 01:37:01 +02:00
|
|
|
response_dict = self.assert_json_success(response)
|
2021-02-12 08:19:30 +01:00
|
|
|
self.assertEqual(
|
2022-06-07 01:37:01 +02:00
|
|
|
response_dict["url"],
|
2020-10-20 19:25:34 +02:00
|
|
|
append_url_query_string(
|
|
|
|
"/calls/bigbluebutton/join",
|
|
|
|
"bigbluebutton="
|
|
|
|
+ self.signer.sign_object(
|
|
|
|
{
|
|
|
|
"meeting_id": "zulip-1",
|
|
|
|
"name": "general > meeting",
|
2022-03-21 00:09:36 +01:00
|
|
|
"password": "A" * 32,
|
2020-10-20 19:25:34 +02:00
|
|
|
}
|
|
|
|
),
|
|
|
|
),
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2020-04-27 22:41:31 +02:00
|
|
|
|
|
|
|
@responses.activate
|
|
|
|
def test_join_bigbluebutton_redirect(self) -> None:
|
2021-02-12 08:19:30 +01:00
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
2020-10-20 19:25:34 +02:00
|
|
|
"https://bbb.example.com/bigbluebutton/api/create?meetingID=a&name=a"
|
|
|
|
"&moderatorPW=a&attendeePW=a&checksum=131bdec35f62fc63d5436e6f791d6d7aed7cf79ef256c03597e51d320d042823",
|
|
|
|
"<response><returncode>SUCCESS</returncode><messageKey/><createTime>0</createTime></response>",
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
|
|
|
response = self.client_get(
|
2020-10-20 19:25:34 +02:00
|
|
|
"/calls/bigbluebutton/join", {"bigbluebutton": self.signed_bbb_a_object}
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2020-04-27 22:41:31 +02:00
|
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
self.assertEqual(isinstance(response, HttpResponseRedirect), True)
|
2021-02-12 08:19:30 +01:00
|
|
|
self.assertEqual(
|
2022-05-29 21:12:13 +02:00
|
|
|
response["Location"],
|
2020-10-20 19:25:34 +02:00
|
|
|
"https://bbb.example.com/bigbluebutton/api/join?meetingID=a&"
|
|
|
|
"password=a&fullName=King%20Hamlet&createTime=0&checksum=47ca959b4ff5c8047a5a56d6e99c07e17eac43dbf792afc0a2a9f6491ec0048b",
|
|
|
|
)
|
|
|
|
|
|
|
|
@responses.activate
|
|
|
|
def test_join_bigbluebutton_invalid_signature(self) -> None:
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
"https://bbb.example.com/bigbluebutton/api/create?meetingID=a&name=a"
|
|
|
|
"&moderatorPW=a&attendeePW=a&checksum=131bdec35f62fc63d5436e6f791d6d7aed7cf79ef256c03597e51d320d042823",
|
|
|
|
"<response><returncode>SUCCESS</returncode><messageKey/><createTime>0</createTime></response>",
|
|
|
|
)
|
|
|
|
response = self.client_get(
|
|
|
|
"/calls/bigbluebutton/join", {"bigbluebutton": self.signed_bbb_a_object + "zoo"}
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2020-10-20 19:25:34 +02:00
|
|
|
self.assert_json_error(response, "Invalid signature.")
|
2020-04-27 22:41:31 +02:00
|
|
|
|
|
|
|
@responses.activate
|
2020-10-20 19:25:34 +02:00
|
|
|
def test_join_bigbluebutton_redirect_wrong_big_blue_button_checksum(self) -> None:
|
2021-02-12 08:19:30 +01:00
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
2020-10-20 19:25:34 +02:00
|
|
|
"https://bbb.example.com/bigbluebutton/api/create?meetingID=a&name=a&moderatorPW=a&attendeePW=a&checksum=131bdec35f62fc63d5436e6f791d6d7aed7cf79ef256c03597e51d320d042823",
|
2021-02-12 08:19:30 +01:00
|
|
|
"<response><returncode>FAILED</returncode><messageKey>checksumError</messageKey>"
|
|
|
|
"<message>You did not pass the checksum security check</message></response>",
|
|
|
|
)
|
|
|
|
response = self.client_get(
|
|
|
|
"/calls/bigbluebutton/join",
|
2020-10-20 19:25:34 +02:00
|
|
|
{"bigbluebutton": self.signed_bbb_a_object},
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2021-07-06 00:23:51 +02:00
|
|
|
self.assert_json_error(response, "Error authenticating to the BigBlueButton server.")
|
2020-04-27 22:41:31 +02:00
|
|
|
|
|
|
|
@responses.activate
|
|
|
|
def test_join_bigbluebutton_redirect_server_error(self) -> None:
|
|
|
|
# Simulate bbb server error
|
2021-02-12 08:19:30 +01:00
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
2020-10-20 19:25:34 +02:00
|
|
|
"https://bbb.example.com/bigbluebutton/api/create?meetingID=a&name=a&moderatorPW=a&attendeePW=a&checksum=131bdec35f62fc63d5436e6f791d6d7aed7cf79ef256c03597e51d320d042823",
|
2021-02-12 08:19:30 +01:00
|
|
|
"",
|
|
|
|
status=500,
|
|
|
|
)
|
2020-04-27 22:41:31 +02:00
|
|
|
response = self.client_get(
|
2021-02-12 08:19:30 +01:00
|
|
|
"/calls/bigbluebutton/join",
|
2020-10-20 19:25:34 +02:00
|
|
|
{"bigbluebutton": self.signed_bbb_a_object},
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2021-07-06 00:23:51 +02:00
|
|
|
self.assert_json_error(response, "Error connecting to the BigBlueButton server.")
|
2020-04-27 22:41:31 +02:00
|
|
|
|
|
|
|
@responses.activate
|
|
|
|
def test_join_bigbluebutton_redirect_error_by_server(self) -> None:
|
|
|
|
# Simulate bbb server error
|
2021-02-12 08:19:30 +01:00
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
2020-10-20 19:25:34 +02:00
|
|
|
"https://bbb.example.com/bigbluebutton/api/create?meetingID=a&name=a&moderatorPW=a&attendeePW=a&checksum=131bdec35f62fc63d5436e6f791d6d7aed7cf79ef256c03597e51d320d042823",
|
2021-02-12 08:19:30 +01:00
|
|
|
"<response><returncode>FAILURE</returncode><messageKey>otherFailure</messageKey></response>",
|
|
|
|
)
|
2020-04-27 22:41:31 +02:00
|
|
|
response = self.client_get(
|
2021-02-12 08:19:30 +01:00
|
|
|
"/calls/bigbluebutton/join",
|
2020-10-20 19:25:34 +02:00
|
|
|
{"bigbluebutton": self.signed_bbb_a_object},
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2021-07-06 00:23:51 +02:00
|
|
|
self.assert_json_error(response, "BigBlueButton server returned an unexpected error.")
|
2020-04-27 22:41:31 +02:00
|
|
|
|
|
|
|
def test_join_bigbluebutton_redirect_not_configured(self) -> None:
|
2021-02-12 08:19:30 +01:00
|
|
|
with self.settings(BIG_BLUE_BUTTON_SECRET=None, BIG_BLUE_BUTTON_URL=None):
|
2020-04-27 22:41:31 +02:00
|
|
|
response = self.client_get(
|
2021-02-12 08:19:30 +01:00
|
|
|
"/calls/bigbluebutton/join",
|
2020-10-20 19:25:34 +02:00
|
|
|
{"bigbluebutton": self.signed_bbb_a_object},
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2021-07-06 00:23:51 +02:00
|
|
|
self.assert_json_error(response, "BigBlueButton is not configured.")
|