mirror of
https://github.com/zulip/zulip.git
synced 2025-11-09 16:37:23 +00:00
compose: Add support for using Zoom as the video chat provider.
This adds Zoom call properties to the `Realm` model, creates endpoints for creating calls, adds a frontend and tests. Fixes #10979.
This commit is contained in:
committed by
Tim Abbott
parent
1aab1594e2
commit
9ddadd39f4
@@ -18,6 +18,7 @@ from LDAP/active directory.
|
|||||||
|
|
||||||
**Full feature changelog:**
|
**Full feature changelog:**
|
||||||
- Added API documentation for user groups and custom emoji.
|
- Added API documentation for user groups and custom emoji.
|
||||||
|
- Added support for using Zoom as the video chat provider.
|
||||||
- Added display of a user's role (administrator, guest, etc.) in
|
- Added display of a user's role (administrator, guest, etc.) in
|
||||||
various relevant places.
|
various relevant places.
|
||||||
- Added support for sending "topic" rather than the legacy "subject"
|
- Added support for sending "topic" rather than the legacy "subject"
|
||||||
|
|||||||
@@ -1427,6 +1427,20 @@ run_test('on_events', () => {
|
|||||||
video_link_regex = /\[Click to join video call\]\(https:\/\/hangouts.google.com\/hangouts\/\_\/zulip\/\d{15}\)/;
|
video_link_regex = /\[Click to join video call\]\(https:\/\/hangouts.google.com\/hangouts\/\_\/zulip\/\d{15}\)/;
|
||||||
assert(video_link_regex.test(syntax_to_insert));
|
assert(video_link_regex.test(syntax_to_insert));
|
||||||
|
|
||||||
|
page_params.realm_video_chat_provider = 'Zoom';
|
||||||
|
page_params.realm_zoom_user_id = 'example@example.com';
|
||||||
|
page_params.realm_zoom_api_key = 'abc';
|
||||||
|
page_params.realm_zoom_api_secret = 'abc';
|
||||||
|
|
||||||
|
channel.get = function (options) {
|
||||||
|
assert(options.url === '/json/calls/create');
|
||||||
|
options.success({ zoom_url: 'example.zoom.com' });
|
||||||
|
};
|
||||||
|
|
||||||
|
handler(event);
|
||||||
|
video_link_regex = /\[Click to join video call\]\(example\.zoom\.com\)/;
|
||||||
|
assert(video_link_regex.test(syntax_to_insert));
|
||||||
|
|
||||||
page_params.jitsi_server_url = null;
|
page_params.jitsi_server_url = null;
|
||||||
called = false;
|
called = false;
|
||||||
|
|
||||||
|
|||||||
@@ -688,6 +688,10 @@ exports.needs_subscribe_warning = function (email) {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function insert_video_call_url(url) {
|
||||||
|
var video_call_link_text = '[' + _('Click to join video call') + '](' + url + ')';
|
||||||
|
compose_ui.insert_syntax_and_focus(video_call_link_text);
|
||||||
|
}
|
||||||
|
|
||||||
exports.initialize = function () {
|
exports.initialize = function () {
|
||||||
$('#stream_message_recipient_stream,#stream_message_recipient_topic,#private_message_recipient').on('keyup', update_fade);
|
$('#stream_message_recipient_stream,#stream_message_recipient_topic,#private_message_recipient').on('keyup', update_fade);
|
||||||
@@ -940,11 +944,18 @@ exports.initialize = function () {
|
|||||||
var video_call_id = util.random_int(100000000000000, 999999999999999);
|
var video_call_id = util.random_int(100000000000000, 999999999999999);
|
||||||
if (page_params.realm_video_chat_provider === "Google Hangouts") {
|
if (page_params.realm_video_chat_provider === "Google Hangouts") {
|
||||||
video_call_link = "https://hangouts.google.com/hangouts/_/" + page_params.realm_google_hangouts_domain + "/" + video_call_id;
|
video_call_link = "https://hangouts.google.com/hangouts/_/" + page_params.realm_google_hangouts_domain + "/" + video_call_id;
|
||||||
|
insert_video_call_url(video_call_link);
|
||||||
|
} else if (page_params.realm_video_chat_provider === "Zoom") {
|
||||||
|
channel.get({
|
||||||
|
url: '/json/calls/create',
|
||||||
|
success: function (response) {
|
||||||
|
insert_video_call_url(response.zoom_url);
|
||||||
|
},
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
video_call_link = page_params.jitsi_server_url + "/" + video_call_id;
|
video_call_link = page_params.jitsi_server_url + "/" + video_call_id;
|
||||||
|
insert_video_call_url(video_call_link);
|
||||||
}
|
}
|
||||||
var video_call_link_text = '[' + _('Click to join video call') + '](' + video_call_link + ')';
|
|
||||||
compose_ui.insert_syntax_and_focus(video_call_link_text);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#compose").on("click", "#markdown_preview", function (e) {
|
$("#compose").on("click", "#markdown_preview", function (e) {
|
||||||
|
|||||||
@@ -46,6 +46,15 @@ var org_settings = {
|
|||||||
google_hangouts_domain: {
|
google_hangouts_domain: {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
|
zoom_user_id: {
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
zoom_api_key: {
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
zoom_api_secret: {
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
user_defaults: {
|
user_defaults: {
|
||||||
default_language: {
|
default_language: {
|
||||||
@@ -243,9 +252,17 @@ function set_video_chat_provider_dropdown() {
|
|||||||
$("#id_realm_video_chat_provider").val(chat_provider);
|
$("#id_realm_video_chat_provider").val(chat_provider);
|
||||||
if (chat_provider === "Google Hangouts") {
|
if (chat_provider === "Google Hangouts") {
|
||||||
$("#google_hangouts_domain").show();
|
$("#google_hangouts_domain").show();
|
||||||
|
$(".zoom_credentials").hide();
|
||||||
$("#id_realm_google_hangouts_domain").val(page_params.realm_google_hangouts_domain);
|
$("#id_realm_google_hangouts_domain").val(page_params.realm_google_hangouts_domain);
|
||||||
|
} else if (chat_provider === "Zoom") {
|
||||||
|
$("#google_hangouts_domain").hide();
|
||||||
|
$(".zoom_credentials").show();
|
||||||
|
$("#id_realm_zoom_user_id").val(page_params.realm_zoom_user_id);
|
||||||
|
$("#id_realm_zoom_api_key").val(page_params.realm_zoom_api_key);
|
||||||
|
$("#id_realm_zoom_api_secret").val(page_params.realm_zoom_api_secret);
|
||||||
} else {
|
} else {
|
||||||
$("#google_hangouts_domain").hide();
|
$("#google_hangouts_domain").hide();
|
||||||
|
$(".zoom_credentials").hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,7 +467,8 @@ function update_dependent_subsettings(property_name) {
|
|||||||
if (property_name === 'realm_create_stream_permission' || property_name === 'realm_waiting_period_threshold') {
|
if (property_name === 'realm_create_stream_permission' || property_name === 'realm_waiting_period_threshold') {
|
||||||
set_create_stream_permission_dropdown();
|
set_create_stream_permission_dropdown();
|
||||||
} else if (property_name === 'realm_video_chat_provider' ||
|
} else if (property_name === 'realm_video_chat_provider' ||
|
||||||
property_name === 'realm_google_hangouts_domain') {
|
property_name === 'realm_google_hangouts_domain' ||
|
||||||
|
property_name.startsWith('realm_zoom')) {
|
||||||
set_video_chat_provider_dropdown();
|
set_video_chat_provider_dropdown();
|
||||||
} else if (property_name === 'realm_msg_edit_limit_setting' ||
|
} else if (property_name === 'realm_msg_edit_limit_setting' ||
|
||||||
property_name === 'realm_message_content_edit_limit_minutes') {
|
property_name === 'realm_message_content_edit_limit_minutes') {
|
||||||
@@ -831,11 +849,15 @@ exports.build_page = function () {
|
|||||||
|
|
||||||
$("#id_realm_video_chat_provider").change(function (e) {
|
$("#id_realm_video_chat_provider").change(function (e) {
|
||||||
var video_chat_provider = e.target.value;
|
var video_chat_provider = e.target.value;
|
||||||
var node = $("#google_hangouts_domain");
|
|
||||||
if (video_chat_provider === "Google Hangouts") {
|
if (video_chat_provider === "Google Hangouts") {
|
||||||
node.show();
|
$("#google_hangouts_domain").show();
|
||||||
|
$(".zoom_credentials").hide();
|
||||||
|
} else if (video_chat_provider === "Zoom") {
|
||||||
|
$("#google_hangouts_domain").hide();
|
||||||
|
$(".zoom_credentials").show();
|
||||||
} else {
|
} else {
|
||||||
node.hide();
|
$("#google_hangouts_domain").hide();
|
||||||
|
$(".zoom_credentials").hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,24 @@
|
|||||||
name="realm_google_hangouts_domain"
|
name="realm_google_hangouts_domain"
|
||||||
class="admin-realm-google-hangouts-domain"/>
|
class="admin-realm-google-hangouts-domain"/>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="zoom_user_id" class="zoom_credentials">
|
||||||
|
<label>{{t 'Zoom user ID (required)' }}:</label>
|
||||||
|
<input type="text" id="id_realm_zoom_user_id"
|
||||||
|
name="realm_zoom_user_id"
|
||||||
|
class="admin-realm-zoom-field"/>
|
||||||
|
</div>
|
||||||
|
<div id="zoom_api_key" class="zoom_credentials">
|
||||||
|
<label>{{t 'Zoom API key (required)' }}:</label>
|
||||||
|
<input type="text" id="id_realm_zoom_api_key"
|
||||||
|
name="realm_zoom_api_key"
|
||||||
|
class="admin-realm-zoom-field"/>
|
||||||
|
</div>
|
||||||
|
<div id="zoom_api_secret" class="zoom_credentials">
|
||||||
|
<label>{{t 'Zoom API secret (required)' }}:</label>
|
||||||
|
<input type="text" id="id_realm_zoom_api_secret"
|
||||||
|
name="realm_zoom_api_secret"
|
||||||
|
class="admin-realm-zoom-field"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -238,7 +238,7 @@
|
|||||||
<h3>VIDEO CALLS</h3>
|
<h3>VIDEO CALLS</h3>
|
||||||
<p>
|
<p>
|
||||||
Create and join video calls with a single click. Powered
|
Create and join video calls with a single click. Powered
|
||||||
by your choice of Jitsi or Google Hangouts.
|
by your choice of Zoom, Jitsi, or Google Hangouts.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-block">
|
<div class="feature-block">
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ IGNORED_PHRASES = [
|
|||||||
r"WordPress",
|
r"WordPress",
|
||||||
r"XML",
|
r"XML",
|
||||||
r"Zephyr",
|
r"Zephyr",
|
||||||
|
r"Zoom",
|
||||||
r"Zulip",
|
r"Zulip",
|
||||||
r"Zulip Account Security",
|
r"Zulip Account Security",
|
||||||
r"Zulip Security",
|
r"Zulip Security",
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ from zerver.lib.exceptions import JsonableError, ErrorCode, BugdownRenderingExce
|
|||||||
from zerver.lib.sessions import delete_user_sessions
|
from zerver.lib.sessions import delete_user_sessions
|
||||||
from zerver.lib.upload import attachment_url_re, attachment_url_to_path_id, \
|
from zerver.lib.upload import attachment_url_re, attachment_url_to_path_id, \
|
||||||
claim_attachment, delete_message_image, upload_emoji_image, delete_avatar_image
|
claim_attachment, delete_message_image, upload_emoji_image, delete_avatar_image
|
||||||
|
from zerver.lib.video_calls import request_zoom_video_call_url
|
||||||
from zerver.tornado.event_queue import request_event_queue, send_event
|
from zerver.tornado.event_queue import request_event_queue, send_event
|
||||||
from zerver.lib.types import ProfileFieldData
|
from zerver.lib.types import ProfileFieldData
|
||||||
|
|
||||||
@@ -5287,3 +5288,15 @@ def do_send_realm_reactivation_email(realm: Realm) -> None:
|
|||||||
'zerver/emails/realm_reactivation', realm,
|
'zerver/emails/realm_reactivation', realm,
|
||||||
from_address=FromAddress.tokenized_no_reply_address(),
|
from_address=FromAddress.tokenized_no_reply_address(),
|
||||||
from_name="Zulip Account Security", context=context)
|
from_name="Zulip Account Security", context=context)
|
||||||
|
|
||||||
|
def get_zoom_video_call_url(realm: Realm) -> str:
|
||||||
|
response = request_zoom_video_call_url(
|
||||||
|
realm.zoom_user_id,
|
||||||
|
realm.zoom_api_key,
|
||||||
|
realm.zoom_api_secret
|
||||||
|
)
|
||||||
|
|
||||||
|
if response is None:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
return response['join_url']
|
||||||
|
|||||||
24
zerver/lib/video_calls.py
Normal file
24
zerver/lib/video_calls.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import requests
|
||||||
|
import jwt
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
import time
|
||||||
|
|
||||||
|
def request_zoom_video_call_url(user_id: str, api_key: str, api_secret: str) -> Optional[Dict[str, Any]]:
|
||||||
|
encodedToken = jwt.encode({
|
||||||
|
'iss': api_key,
|
||||||
|
'exp': int(round(time.time() * 1000)) + 5000
|
||||||
|
}, api_secret, algorithm='HS256').decode('utf-8')
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
'https://api.zoom.us/v2/users/' + user_id + '/meetings',
|
||||||
|
headers = {
|
||||||
|
'Authorization': 'Bearer ' + encodedToken,
|
||||||
|
'content-type': 'application/json'
|
||||||
|
},
|
||||||
|
json = {}
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return response.json()
|
||||||
30
zerver/migrations/0201_zoom_video_chat.py
Normal file
30
zerver/migrations/0201_zoom_video_chat.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.16 on 2018-12-28 18:00
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('zerver', '0200_remove_preregistrationuser_invited_as_admin'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='realm',
|
||||||
|
name='zoom_api_key',
|
||||||
|
field=models.TextField(default=''),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='realm',
|
||||||
|
name='zoom_api_secret',
|
||||||
|
field=models.TextField(default=''),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='realm',
|
||||||
|
name='zoom_user_id',
|
||||||
|
field=models.TextField(default=''),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -144,7 +144,7 @@ class Realm(models.Model):
|
|||||||
MAX_GOOGLE_HANGOUTS_DOMAIN_LENGTH = 255 # This is just the maximum domain length by RFC
|
MAX_GOOGLE_HANGOUTS_DOMAIN_LENGTH = 255 # This is just the maximum domain length by RFC
|
||||||
INVITES_STANDARD_REALM_DAILY_MAX = 3000
|
INVITES_STANDARD_REALM_DAILY_MAX = 3000
|
||||||
MESSAGE_VISIBILITY_LIMITED = 10000
|
MESSAGE_VISIBILITY_LIMITED = 10000
|
||||||
VIDEO_CHAT_PROVIDERS = [u"Jitsi", u"Google Hangouts"]
|
VIDEO_CHAT_PROVIDERS = [u"Jitsi", u"Google Hangouts", u"Zoom"]
|
||||||
AUTHENTICATION_FLAGS = [u'Google', u'Email', u'GitHub', u'LDAP', u'Dev', u'RemoteUser', u'AzureAD']
|
AUTHENTICATION_FLAGS = [u'Google', u'Email', u'GitHub', u'LDAP', u'Dev', u'RemoteUser', u'AzureAD']
|
||||||
SUBDOMAIN_FOR_ROOT_DOMAIN = ''
|
SUBDOMAIN_FOR_ROOT_DOMAIN = ''
|
||||||
|
|
||||||
@@ -262,6 +262,9 @@ class Realm(models.Model):
|
|||||||
|
|
||||||
video_chat_provider = models.CharField(default=u"Jitsi", max_length=MAX_VIDEO_CHAT_PROVIDER_LENGTH)
|
video_chat_provider = models.CharField(default=u"Jitsi", max_length=MAX_VIDEO_CHAT_PROVIDER_LENGTH)
|
||||||
google_hangouts_domain = models.TextField(default="")
|
google_hangouts_domain = models.TextField(default="")
|
||||||
|
zoom_user_id = models.TextField(default="")
|
||||||
|
zoom_api_key = models.TextField(default="")
|
||||||
|
zoom_api_secret = models.TextField(default="")
|
||||||
|
|
||||||
# Define the types of the various automatically managed properties
|
# Define the types of the various automatically managed properties
|
||||||
property_types = dict(
|
property_types = dict(
|
||||||
@@ -277,6 +280,9 @@ class Realm(models.Model):
|
|||||||
email_address_visibility=int,
|
email_address_visibility=int,
|
||||||
email_changes_disabled=bool,
|
email_changes_disabled=bool,
|
||||||
google_hangouts_domain=str,
|
google_hangouts_domain=str,
|
||||||
|
zoom_user_id=str,
|
||||||
|
zoom_api_key=str,
|
||||||
|
zoom_api_secret=str,
|
||||||
invite_required=bool,
|
invite_required=bool,
|
||||||
invite_by_admins_only=bool,
|
invite_by_admins_only=bool,
|
||||||
inline_image_preview=bool,
|
inline_image_preview=bool,
|
||||||
|
|||||||
41
zerver/tests/test_create_video_call.py
Normal file
41
zerver/tests/test_create_video_call.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import mock
|
||||||
|
from zerver.lib.test_classes import ZulipTestCase
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
class TestFeedbackBot(ZulipTestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
user_profile = self.example_user('hamlet')
|
||||||
|
self.login(user_profile.email, realm=user_profile.realm)
|
||||||
|
|
||||||
|
def test_create_video_call_success(self) -> None:
|
||||||
|
with mock.patch('zerver.lib.actions.request_zoom_video_call_url', return_value={'join_url': 'example.com'}):
|
||||||
|
result = self.client_get("/json/calls/create")
|
||||||
|
self.assert_json_success(result)
|
||||||
|
self.assertEqual(200, result.status_code)
|
||||||
|
content = result.json()
|
||||||
|
self.assertEqual(content['zoom_url'], 'example.com')
|
||||||
|
|
||||||
|
def test_create_video_call_failure(self) -> None:
|
||||||
|
with mock.patch('zerver.lib.actions.request_zoom_video_call_url', return_value=None):
|
||||||
|
result = self.client_get("/json/calls/create")
|
||||||
|
self.assert_json_success(result)
|
||||||
|
self.assertEqual(200, result.status_code)
|
||||||
|
content = result.json()
|
||||||
|
self.assertEqual(content['zoom_url'], '')
|
||||||
|
|
||||||
|
def test_create_video_request_success(self) -> None:
|
||||||
|
class MockResponse:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.status_code = 200
|
||||||
|
|
||||||
|
def json(self) -> Dict[str, str]:
|
||||||
|
return {"join_url": "example.com"}
|
||||||
|
|
||||||
|
with mock.patch('requests.post', return_value=MockResponse()):
|
||||||
|
result = self.client_get("/json/calls/create")
|
||||||
|
self.assert_json_success(result)
|
||||||
|
|
||||||
|
def test_create_video_request(self) -> None:
|
||||||
|
with mock.patch('requests.post'):
|
||||||
|
result = self.client_get("/json/calls/create")
|
||||||
|
self.assert_json_success(result)
|
||||||
@@ -1463,6 +1463,9 @@ class EventsRegisterTest(ZulipTestCase):
|
|||||||
bot_creation_policy=[Realm.BOT_CREATION_EVERYONE],
|
bot_creation_policy=[Realm.BOT_CREATION_EVERYONE],
|
||||||
video_chat_provider=[u'Google Hangouts', u'Jitsi'],
|
video_chat_provider=[u'Google Hangouts', u'Jitsi'],
|
||||||
google_hangouts_domain=[u"zulip.com", u"zulip.org"],
|
google_hangouts_domain=[u"zulip.com", u"zulip.org"],
|
||||||
|
zoom_api_secret=[u"abc", u"xyz"],
|
||||||
|
zoom_api_key=[u"abc", u"xyz"],
|
||||||
|
zoom_user_id=[u"example@example.com", u"example@example.org"]
|
||||||
) # type: Dict[str, Any]
|
) # type: Dict[str, Any]
|
||||||
|
|
||||||
vals = test_values.get(name)
|
vals = test_values.get(name)
|
||||||
|
|||||||
@@ -171,6 +171,9 @@ class HomeTest(ZulipTestCase):
|
|||||||
"realm_users",
|
"realm_users",
|
||||||
"realm_video_chat_provider",
|
"realm_video_chat_provider",
|
||||||
"realm_waiting_period_threshold",
|
"realm_waiting_period_threshold",
|
||||||
|
"realm_zoom_api_key",
|
||||||
|
"realm_zoom_api_secret",
|
||||||
|
"realm_zoom_user_id",
|
||||||
"root_domain_uri",
|
"root_domain_uri",
|
||||||
"save_stacktraces",
|
"save_stacktraces",
|
||||||
"search_pills_enabled",
|
"search_pills_enabled",
|
||||||
|
|||||||
@@ -411,6 +411,45 @@ class RealmTest(ZulipTestCase):
|
|||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
self.assertEqual(get_realm('zulip').video_chat_provider, "Jitsi")
|
self.assertEqual(get_realm('zulip').video_chat_provider, "Jitsi")
|
||||||
|
|
||||||
|
req = {"video_chat_provider": ujson.dumps("Zoom")}
|
||||||
|
result = self.client_patch('/json/realm', req)
|
||||||
|
self.assert_json_error(result, "Invalid user ID: user ID cannot be empty")
|
||||||
|
|
||||||
|
req = {
|
||||||
|
"video_chat_provider": ujson.dumps("Zoom"),
|
||||||
|
"zoom_user_id": ujson.dumps("example@example.com")
|
||||||
|
}
|
||||||
|
result = self.client_patch('/json/realm', req)
|
||||||
|
self.assert_json_error(result, "Invalid API key: API key cannot be empty")
|
||||||
|
|
||||||
|
req = {
|
||||||
|
"video_chat_provider": ujson.dumps("Zoom"),
|
||||||
|
"zoom_user_id": ujson.dumps("example@example.com"),
|
||||||
|
"zoom_api_key": ujson.dumps("abc")
|
||||||
|
}
|
||||||
|
result = self.client_patch('/json/realm', req)
|
||||||
|
self.assert_json_error(result, "Invalid API secret: API secret cannot be empty")
|
||||||
|
|
||||||
|
with mock.patch("zerver.views.realm.request_zoom_video_call_url", return_value=None):
|
||||||
|
req = {
|
||||||
|
"video_chat_provider": ujson.dumps("Zoom"),
|
||||||
|
"zoom_user_id": ujson.dumps("example@example.com"),
|
||||||
|
"zoom_api_key": ujson.dumps("abc"),
|
||||||
|
"zoom_api_secret": ujson.dumps("abc"),
|
||||||
|
}
|
||||||
|
result = self.client_patch('/json/realm', req)
|
||||||
|
self.assert_json_error(result, "Invalid credentials for the Zoom API.")
|
||||||
|
|
||||||
|
with mock.patch("zerver.views.realm.request_zoom_video_call_url", return_value={'join_url': 'example.com'}):
|
||||||
|
req = {
|
||||||
|
"video_chat_provider": ujson.dumps("Zoom"),
|
||||||
|
"zoom_user_id": ujson.dumps("example@example.com"),
|
||||||
|
"zoom_api_key": ujson.dumps("abc"),
|
||||||
|
"zoom_api_secret": ujson.dumps("abc"),
|
||||||
|
}
|
||||||
|
result = self.client_patch('/json/realm', req)
|
||||||
|
self.assert_json_success(result)
|
||||||
|
|
||||||
def test_initial_plan_type(self) -> None:
|
def test_initial_plan_type(self) -> None:
|
||||||
with self.settings(BILLING_ENABLED=True):
|
with self.settings(BILLING_ENABLED=True):
|
||||||
self.assertEqual(do_create_realm('hosted', 'hosted').plan_type, Realm.LIMITED)
|
self.assertEqual(do_create_realm('hosted', 'hosted').plan_type, Realm.LIMITED)
|
||||||
@@ -481,6 +520,9 @@ class RealmAPITest(ZulipTestCase):
|
|||||||
Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS],
|
Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS],
|
||||||
video_chat_provider=[u'Jitsi', u'Hangouts'],
|
video_chat_provider=[u'Jitsi', u'Hangouts'],
|
||||||
google_hangouts_domain=[u'zulip.com', u'zulip.org'],
|
google_hangouts_domain=[u'zulip.com', u'zulip.org'],
|
||||||
|
zoom_api_secret=[u"abc", u"xyz"],
|
||||||
|
zoom_api_key=[u"abc", u"xyz"],
|
||||||
|
zoom_user_id=[u"example@example.com", u"example@example.org"]
|
||||||
) # type: Dict[str, Any]
|
) # type: Dict[str, Any]
|
||||||
vals = test_values.get(name)
|
vals = test_values.get(name)
|
||||||
if Realm.property_types[name] is bool:
|
if Realm.property_types[name] is bool:
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ from zerver.lib.response import json_success, json_error
|
|||||||
from zerver.lib.validator import check_string, check_dict, check_bool, check_int
|
from zerver.lib.validator import check_string, check_dict, check_bool, check_int
|
||||||
from zerver.lib.streams import access_stream_by_id
|
from zerver.lib.streams import access_stream_by_id
|
||||||
from zerver.lib.domains import validate_domain
|
from zerver.lib.domains import validate_domain
|
||||||
|
from zerver.lib.video_calls import request_zoom_video_call_url
|
||||||
from zerver.models import Realm, UserProfile
|
from zerver.models import Realm, UserProfile
|
||||||
from zerver.forms import check_subdomain_available as check_subdomain
|
from zerver.forms import check_subdomain_available as check_subdomain
|
||||||
from confirmation.models import get_object_from_key, Confirmation, ConfirmationKeyException
|
from confirmation.models import get_object_from_key, Confirmation, ConfirmationKeyException
|
||||||
@@ -63,6 +64,9 @@ def update_realm(
|
|||||||
default_twenty_four_hour_time: Optional[bool]=REQ(validator=check_bool, default=None),
|
default_twenty_four_hour_time: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
video_chat_provider: Optional[str]=REQ(validator=check_string, default=None),
|
video_chat_provider: Optional[str]=REQ(validator=check_string, default=None),
|
||||||
google_hangouts_domain: Optional[str]=REQ(validator=check_string, default=None),
|
google_hangouts_domain: Optional[str]=REQ(validator=check_string, default=None),
|
||||||
|
zoom_user_id: Optional[str]=REQ(validator=check_string, default=None),
|
||||||
|
zoom_api_key: Optional[str]=REQ(validator=check_string, default=None),
|
||||||
|
zoom_api_secret: Optional[str]=REQ(validator=check_string, default=None),
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
realm = user_profile.realm
|
realm = user_profile.realm
|
||||||
|
|
||||||
@@ -81,6 +85,21 @@ def update_realm(
|
|||||||
validate_domain(google_hangouts_domain)
|
validate_domain(google_hangouts_domain)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
return json_error(_('Invalid domain: {}').format(e.messages[0]))
|
return json_error(_('Invalid domain: {}').format(e.messages[0]))
|
||||||
|
if video_chat_provider == "Zoom":
|
||||||
|
if not zoom_user_id:
|
||||||
|
return json_error(_('Invalid user ID: user ID cannot be empty'))
|
||||||
|
if not zoom_api_key:
|
||||||
|
return json_error(_('Invalid API key: API key cannot be empty'))
|
||||||
|
if not zoom_api_secret:
|
||||||
|
return json_error(_('Invalid API secret: API secret cannot be empty'))
|
||||||
|
# Technically, we could call some other API endpoint that
|
||||||
|
# doesn't create a video call link, but this is a nicer
|
||||||
|
# end-to-end test, since it verifies that the Zoom API user's
|
||||||
|
# scopes includes the ability to create video calls, which is
|
||||||
|
# the only capabiility we use.
|
||||||
|
if not request_zoom_video_call_url(zoom_user_id, zoom_api_key, zoom_api_secret):
|
||||||
|
return json_error(_('Invalid credentials for the %(third_party_service)s API.') % dict(
|
||||||
|
third_party_service="Zoom"))
|
||||||
|
|
||||||
# Additional validation of enum-style values
|
# Additional validation of enum-style values
|
||||||
if bot_creation_policy is not None and bot_creation_policy not in Realm.BOT_CREATION_POLICY_TYPES:
|
if bot_creation_policy is not None and bot_creation_policy not in Realm.BOT_CREATION_POLICY_TYPES:
|
||||||
|
|||||||
12
zerver/views/video_calls.py
Normal file
12
zerver/views/video_calls.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from django.http import HttpResponse, HttpRequest
|
||||||
|
|
||||||
|
from zerver.decorator import has_request_variables
|
||||||
|
from zerver.lib.response import json_success
|
||||||
|
from zerver.lib.actions import get_zoom_video_call_url
|
||||||
|
from zerver.models import UserProfile
|
||||||
|
|
||||||
|
@has_request_variables
|
||||||
|
def get_zoom_url(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
||||||
|
return json_success({'zoom_url': get_zoom_video_call_url(
|
||||||
|
user_profile.realm
|
||||||
|
)})
|
||||||
@@ -368,6 +368,10 @@ v1_api_and_json_patterns = [
|
|||||||
{'POST': 'zerver.views.report.report_narrow_times'}),
|
{'POST': 'zerver.views.report.report_narrow_times'}),
|
||||||
url(r'^report/unnarrow_times$', rest_dispatch,
|
url(r'^report/unnarrow_times$', rest_dispatch,
|
||||||
{'POST': 'zerver.views.report.report_unnarrow_times'}),
|
{'POST': 'zerver.views.report.report_unnarrow_times'}),
|
||||||
|
|
||||||
|
# Used to generate a Zoom video call URL
|
||||||
|
url(r'^calls/create$', rest_dispatch,
|
||||||
|
{'GET': 'zerver.views.video_calls.get_zoom_url'})
|
||||||
]
|
]
|
||||||
|
|
||||||
# These views serve pages (HTML). As such, their internationalization
|
# These views serve pages (HTML). As such, their internationalization
|
||||||
|
|||||||
Reference in New Issue
Block a user