mirror of https://github.com/zulip/zulip.git
org_settings: Add backend for `realm_jitsi_server_url` setting.
This commit adds a `jitsi_server_url` field to the Realm model, which will be used to save the URL of the custom Jitsi Meet server. In the database, `None` will encode the server-level default. We can't readily use `None` in the API, as it could be confused with "field not sent". Therefore, we will use the string "default" for this purpose. We have also introduced `server_jitsi_server_url` in the `/register` API. This will be used to display the server's default Jitsi server URL in the settings UI. The existing `jitsi_server_url` will now be calculated as `realm_jitsi_server_url || server_jitsi_server_url`. Fixes a part of #17914. Co-authored-by: Gaurav Pandey <gauravguitarrocks@gmail.com>
This commit is contained in:
parent
cb0aaa5197
commit
be653dd5b4
|
@ -20,6 +20,17 @@ format used by the Zulip server that they are interacting with.
|
||||||
|
|
||||||
## Changes in Zulip 8.0
|
## Changes in Zulip 8.0
|
||||||
|
|
||||||
|
**Feature level 212**
|
||||||
|
|
||||||
|
* [`GET /events`](/api/get-events), [`POST /register`](/api/register-queue),
|
||||||
|
`PATCH /realm`: Added the `jitsi_server_url` field to the `realm` object,
|
||||||
|
allowing organizations to set a custom Jitsi Meet server. Previously, this
|
||||||
|
was only available as a server-level configuration.
|
||||||
|
|
||||||
|
* [`POST /register`](/api/register-queue): Added `server_jitsi_server_url`
|
||||||
|
fields to the `realm` object. The existing `jitsi_server_url` will now be
|
||||||
|
calculated as `realm_jitsi_server_url || server_jitsi_server_url`.
|
||||||
|
|
||||||
**Feature level 211**
|
**Feature level 211**
|
||||||
|
|
||||||
* [`POST /streams/{stream_id}/delete_topic`](/api/delete-topic),
|
* [`POST /streams/{stream_id}/delete_topic`](/api/delete-topic),
|
||||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
||||||
# Changes should be accompanied by documentation explaining what the
|
# Changes should be accompanied by documentation explaining what the
|
||||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||||
API_FEATURE_LEVEL = 211
|
API_FEATURE_LEVEL = 212
|
||||||
|
|
||||||
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
||||||
# only when going from an old version of the code to a newer version. Bump
|
# only when going from an old version of the code to a newer version. Bump
|
||||||
|
|
|
@ -866,6 +866,8 @@ def check_realm_update(
|
||||||
assert isinstance(value, property_type)
|
assert isinstance(value, property_type)
|
||||||
elif property_type == (int, type(None)):
|
elif property_type == (int, type(None)):
|
||||||
assert isinstance(value, int)
|
assert isinstance(value, int)
|
||||||
|
elif property_type == (str, type(None)):
|
||||||
|
assert isinstance(value, str)
|
||||||
else:
|
else:
|
||||||
raise AssertionError(f"Unexpected property type {property_type}")
|
raise AssertionError(f"Unexpected property type {property_type}")
|
||||||
|
|
||||||
|
|
|
@ -337,10 +337,15 @@ def fetch_initial_state_data(
|
||||||
state["realm_push_notifications_enabled"] = push_notifications_enabled()
|
state["realm_push_notifications_enabled"] = push_notifications_enabled()
|
||||||
state["realm_default_external_accounts"] = get_default_external_accounts()
|
state["realm_default_external_accounts"] = get_default_external_accounts()
|
||||||
|
|
||||||
if settings.JITSI_SERVER_URL is not None:
|
server_default_jitsi_server_url = (
|
||||||
state["jitsi_server_url"] = settings.JITSI_SERVER_URL.rstrip("/")
|
settings.JITSI_SERVER_URL.rstrip("/") if settings.JITSI_SERVER_URL is not None else None
|
||||||
else: # nocoverage
|
)
|
||||||
state["jitsi_server_url"] = None
|
state["server_jitsi_server_url"] = server_default_jitsi_server_url
|
||||||
|
state["jitsi_server_url"] = (
|
||||||
|
realm.jitsi_server_url
|
||||||
|
if realm.jitsi_server_url is not None
|
||||||
|
else server_default_jitsi_server_url
|
||||||
|
)
|
||||||
|
|
||||||
if realm.notifications_stream and not realm.notifications_stream.deactivated:
|
if realm.notifications_stream and not realm.notifications_stream.deactivated:
|
||||||
notifications_stream = realm.notifications_stream
|
notifications_stream = realm.notifications_stream
|
||||||
|
@ -1068,6 +1073,13 @@ def apply_event(
|
||||||
state["zulip_plan_is_not_limited"] = event["value"] != Realm.PLAN_TYPE_LIMITED
|
state["zulip_plan_is_not_limited"] = event["value"] != Realm.PLAN_TYPE_LIMITED
|
||||||
state["realm_upload_quota_mib"] = event["extra_data"]["upload_quota"]
|
state["realm_upload_quota_mib"] = event["extra_data"]["upload_quota"]
|
||||||
|
|
||||||
|
if field == "realm_jitsi_server_url":
|
||||||
|
state["jitsi_server_url"] = (
|
||||||
|
state["realm_jitsi_server_url"]
|
||||||
|
if state["realm_jitsi_server_url"] is not None
|
||||||
|
else state["server_jitsi_server_url"]
|
||||||
|
)
|
||||||
|
|
||||||
policy_permission_dict = {
|
policy_permission_dict = {
|
||||||
"create_public_stream_policy": "can_create_public_streams",
|
"create_public_stream_policy": "can_create_public_streams",
|
||||||
"create_private_stream_policy": "can_create_private_streams",
|
"create_private_stream_policy": "can_create_private_streams",
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 4.2.5 on 2023-09-19 17:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0474_realmuserdefault_web_stream_unreads_count_display_policy_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="realm",
|
||||||
|
name="jitsi_server_url",
|
||||||
|
field=models.URLField(default=None, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -671,6 +671,9 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
|
||||||
default=VIDEO_CHAT_PROVIDERS["jitsi_meet"]["id"]
|
default=VIDEO_CHAT_PROVIDERS["jitsi_meet"]["id"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
JITSI_SERVER_SPECIAL_VALUES_MAP = {"default": None}
|
||||||
|
jitsi_server_url = models.URLField(null=True, default=None)
|
||||||
|
|
||||||
# Please access this via get_giphy_rating_options.
|
# Please access this via get_giphy_rating_options.
|
||||||
GIPHY_RATING_OPTIONS = {
|
GIPHY_RATING_OPTIONS = {
|
||||||
"disabled": {
|
"disabled": {
|
||||||
|
@ -740,6 +743,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
|
||||||
invite_required=bool,
|
invite_required=bool,
|
||||||
invite_to_realm_policy=int,
|
invite_to_realm_policy=int,
|
||||||
invite_to_stream_policy=int,
|
invite_to_stream_policy=int,
|
||||||
|
jitsi_server_url=(str, type(None)),
|
||||||
mandatory_topics=bool,
|
mandatory_topics=bool,
|
||||||
message_content_allowed_in_email_notifications=bool,
|
message_content_allowed_in_email_notifications=bool,
|
||||||
message_content_edit_limit_seconds=(int, type(None)),
|
message_content_edit_limit_seconds=(int, type(None)),
|
||||||
|
|
|
@ -4365,6 +4365,19 @@ paths:
|
||||||
|
|
||||||
**Changes**: None added as an option in Zulip 3.0 (feature level 1)
|
**Changes**: None added as an option in Zulip 3.0 (feature level 1)
|
||||||
to disable video call UI.
|
to disable video call UI.
|
||||||
|
jitsi_server_url:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: |
|
||||||
|
The URL of the custom Jitsi Meet server configured in this organization's
|
||||||
|
settings.
|
||||||
|
|
||||||
|
`null`, the default, means that the organization is using the should use the
|
||||||
|
server-level configuration, `server_jitsi_server_url`.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 8.0 (feature level 212). Previously, this was only
|
||||||
|
available as a server-level configuration, and required a server restart to
|
||||||
|
change.
|
||||||
waiting_period_threshold:
|
waiting_period_threshold:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
|
@ -13553,6 +13566,21 @@ paths:
|
||||||
|
|
||||||
**Changes**: None added as an option in Zulip 3.0 (feature level 1)
|
**Changes**: None added as an option in Zulip 3.0 (feature level 1)
|
||||||
to disable video call UI.
|
to disable video call UI.
|
||||||
|
realm_jitsi_server_url:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: |
|
||||||
|
The URL of the custom Jitsi Meet server configured in this organization's
|
||||||
|
settings.
|
||||||
|
|
||||||
|
`null`, the default, means that the organization is using the should use the
|
||||||
|
server-level configuration, `server_jitsi_server_url`. A correct client
|
||||||
|
supporting only the modern API should use `realm_jitsi_server_url ||
|
||||||
|
server_jitsi_server_url` to create calls.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 8.0 (feature level 212). Previously, this was only
|
||||||
|
available as a server-level configuration, which was available via the
|
||||||
|
`jitsi_server_url` field.
|
||||||
realm_giphy_rating:
|
realm_giphy_rating:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
|
@ -14009,10 +14037,21 @@ paths:
|
||||||
on the external site.
|
on the external site.
|
||||||
jitsi_server_url:
|
jitsi_server_url:
|
||||||
type: string
|
type: string
|
||||||
|
deprecated: true
|
||||||
description: |
|
description: |
|
||||||
Present if `realm` is present in `fetch_event_types`.
|
Present if `realm` is present in `fetch_event_types`.
|
||||||
|
|
||||||
The base URL the organization uses to create Jitsi video calls.
|
The base URL to be used to create Jitsi video calls. Equals
|
||||||
|
`realm_jitsi_server_url || server_jitsi_server_url`.
|
||||||
|
|
||||||
|
**Changes**: Deprecated in Zulip 8.0 (feature level 212) and will
|
||||||
|
eventually be removed. Previously, the Jitsi server to use was not
|
||||||
|
configurable on a per-realm basis, and this field contained the server's
|
||||||
|
configured Jitsi server. (Which is now provided as
|
||||||
|
`server_jitsi_server_url`). Clients supporting older versions should fall
|
||||||
|
back to this field when creating calls: using `realm_jitsi_server_url ||
|
||||||
|
server_jitsi_server_url` with newer servers and using `jitsi_server_url`
|
||||||
|
with servers below feature level 212.
|
||||||
development_environment:
|
development_environment:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: |
|
description: |
|
||||||
|
@ -14164,6 +14203,16 @@ paths:
|
||||||
since the last request.
|
since the last request.
|
||||||
|
|
||||||
**Changes**: New in Zulip 6.0 (feature level 140).
|
**Changes**: New in Zulip 6.0 (feature level 140).
|
||||||
|
server_jitsi_server_url:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: |
|
||||||
|
The URL of the Jitsi server that the Zulip server is configured to use by
|
||||||
|
default; the organization-level setting `realm_jitsi_server_url` takes
|
||||||
|
precedence over this setting when both are set.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 8.0 (feature level 212). Previously, this value
|
||||||
|
was available as the now-deprecated `jitsi_server_url`.
|
||||||
event_queue_longpoll_timeout_seconds:
|
event_queue_longpoll_timeout_seconds:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
|
|
|
@ -2933,6 +2933,7 @@ class RealmPropertyActionTest(BaseAction):
|
||||||
video_chat_provider=[
|
video_chat_provider=[
|
||||||
Realm.VIDEO_CHAT_PROVIDERS["jitsi_meet"]["id"],
|
Realm.VIDEO_CHAT_PROVIDERS["jitsi_meet"]["id"],
|
||||||
],
|
],
|
||||||
|
jitsi_server_url=["https://jitsi1.example.com", "https://jitsi2.example.com"],
|
||||||
giphy_rating=[
|
giphy_rating=[
|
||||||
Realm.GIPHY_RATING_OPTIONS["disabled"]["id"],
|
Realm.GIPHY_RATING_OPTIONS["disabled"]["id"],
|
||||||
],
|
],
|
||||||
|
|
|
@ -150,6 +150,7 @@ class HomeTest(ZulipTestCase):
|
||||||
"realm_invite_to_realm_policy",
|
"realm_invite_to_realm_policy",
|
||||||
"realm_invite_to_stream_policy",
|
"realm_invite_to_stream_policy",
|
||||||
"realm_is_zephyr_mirror_realm",
|
"realm_is_zephyr_mirror_realm",
|
||||||
|
"realm_jitsi_server_url",
|
||||||
"realm_linkifiers",
|
"realm_linkifiers",
|
||||||
"realm_logo_source",
|
"realm_logo_source",
|
||||||
"realm_logo_url",
|
"realm_logo_url",
|
||||||
|
@ -194,6 +195,7 @@ class HomeTest(ZulipTestCase):
|
||||||
"server_generation",
|
"server_generation",
|
||||||
"server_inline_image_preview",
|
"server_inline_image_preview",
|
||||||
"server_inline_url_embed_preview",
|
"server_inline_url_embed_preview",
|
||||||
|
"server_jitsi_server_url",
|
||||||
"server_name_changes_disabled",
|
"server_name_changes_disabled",
|
||||||
"server_needs_upgrade",
|
"server_needs_upgrade",
|
||||||
"server_presence_offline_threshold_seconds",
|
"server_presence_offline_threshold_seconds",
|
||||||
|
|
|
@ -864,6 +864,36 @@ class RealmTest(ZulipTestCase):
|
||||||
result = self.client_patch("/json/realm", req)
|
result = self.client_patch("/json/realm", req)
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
|
|
||||||
|
def test_jitsi_server_url(self) -> None:
|
||||||
|
self.login("iago")
|
||||||
|
realm = get_realm("zulip")
|
||||||
|
self.assertEqual(realm.video_chat_provider, Realm.VIDEO_CHAT_PROVIDERS["jitsi_meet"]["id"])
|
||||||
|
|
||||||
|
req = dict(jitsi_server_url=orjson.dumps("").decode())
|
||||||
|
result = self.client_patch("/json/realm", req)
|
||||||
|
self.assert_json_error(result, "jitsi_server_url is not an allowed_type")
|
||||||
|
|
||||||
|
req = dict(jitsi_server_url=orjson.dumps("invalidURL").decode())
|
||||||
|
result = self.client_patch("/json/realm", req)
|
||||||
|
self.assert_json_error(result, "jitsi_server_url is not an allowed_type")
|
||||||
|
|
||||||
|
req = dict(jitsi_server_url=orjson.dumps(12).decode())
|
||||||
|
result = self.client_patch("/json/realm", req)
|
||||||
|
self.assert_json_error(result, "jitsi_server_url is not an allowed_type")
|
||||||
|
|
||||||
|
valid_url = "https://jitsi.example.com"
|
||||||
|
req = dict(jitsi_server_url=orjson.dumps(valid_url).decode())
|
||||||
|
result = self.client_patch("/json/realm", req)
|
||||||
|
self.assert_json_success(result)
|
||||||
|
realm = get_realm("zulip")
|
||||||
|
self.assertEqual(realm.jitsi_server_url, valid_url)
|
||||||
|
|
||||||
|
req = dict(jitsi_server_url=orjson.dumps("default").decode())
|
||||||
|
result = self.client_patch("/json/realm", req)
|
||||||
|
self.assert_json_success(result)
|
||||||
|
realm = get_realm("zulip")
|
||||||
|
self.assertEqual(realm.jitsi_server_url, None)
|
||||||
|
|
||||||
def test_do_create_realm(self) -> None:
|
def test_do_create_realm(self) -> None:
|
||||||
realm = do_create_realm("realm_string_id", "realm name")
|
realm = do_create_realm("realm_string_id", "realm name")
|
||||||
|
|
||||||
|
@ -1179,6 +1209,11 @@ class RealmAPITest(ZulipTestCase):
|
||||||
).decode(),
|
).decode(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
jitsi_server_url=[
|
||||||
|
dict(
|
||||||
|
jitsi_server_url=orjson.dumps("https://example.jit.si").decode(),
|
||||||
|
),
|
||||||
|
],
|
||||||
giphy_rating=[
|
giphy_rating=[
|
||||||
Realm.GIPHY_RATING_OPTIONS["y"]["id"],
|
Realm.GIPHY_RATING_OPTIONS["y"]["id"],
|
||||||
Realm.GIPHY_RATING_OPTIONS["r"]["id"],
|
Realm.GIPHY_RATING_OPTIONS["r"]["id"],
|
||||||
|
@ -1200,7 +1235,7 @@ class RealmAPITest(ZulipTestCase):
|
||||||
if vals is None:
|
if vals is None:
|
||||||
raise AssertionError(f"No test created for {name}")
|
raise AssertionError(f"No test created for {name}")
|
||||||
|
|
||||||
if name == "video_chat_provider":
|
if name in ("video_chat_provider", "jitsi_server_url"):
|
||||||
self.set_up_db(name, vals[0][name])
|
self.set_up_db(name, vals[0][name])
|
||||||
realm = self.update_with_api_multiple_value(vals[0])
|
realm = self.update_with_api_multiple_value(vals[0])
|
||||||
self.assertEqual(getattr(realm, name), orjson.loads(vals[0][name]))
|
self.assertEqual(getattr(realm, name), orjson.loads(vals[0][name]))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Any, Dict, Optional, Union
|
from typing import Any, Dict, Mapping, Optional, Union
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
@ -37,12 +37,23 @@ from zerver.lib.validator import (
|
||||||
check_int_in,
|
check_int_in,
|
||||||
check_string_in,
|
check_string_in,
|
||||||
check_string_or_int,
|
check_string_or_int,
|
||||||
|
check_union,
|
||||||
|
check_url,
|
||||||
to_non_negative_int,
|
to_non_negative_int,
|
||||||
)
|
)
|
||||||
from zerver.models import Realm, RealmReactivationStatus, RealmUserDefault, UserProfile
|
from zerver.models import Realm, RealmReactivationStatus, RealmUserDefault, UserProfile
|
||||||
from zerver.views.user_settings import check_settings_values
|
from zerver.views.user_settings import check_settings_values
|
||||||
|
|
||||||
|
|
||||||
|
def parse_jitsi_server_url(
|
||||||
|
value: str, special_values_map: Mapping[str, Optional[str]]
|
||||||
|
) -> Optional[str]:
|
||||||
|
if value in special_values_map:
|
||||||
|
return special_values_map[value]
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
@require_realm_admin
|
@require_realm_admin
|
||||||
@has_request_variables
|
@has_request_variables
|
||||||
def update_realm(
|
def update_realm(
|
||||||
|
@ -131,6 +142,13 @@ def update_realm(
|
||||||
json_validator=check_int_in(Realm.WILDCARD_MENTION_POLICY_TYPES), default=None
|
json_validator=check_int_in(Realm.WILDCARD_MENTION_POLICY_TYPES), default=None
|
||||||
),
|
),
|
||||||
video_chat_provider: Optional[int] = REQ(json_validator=check_int, default=None),
|
video_chat_provider: Optional[int] = REQ(json_validator=check_int, default=None),
|
||||||
|
jitsi_server_url_raw: Optional[str] = REQ(
|
||||||
|
"jitsi_server_url",
|
||||||
|
json_validator=check_union(
|
||||||
|
[check_string_in(list(Realm.JITSI_SERVER_SPECIAL_VALUES_MAP.keys())), check_url]
|
||||||
|
),
|
||||||
|
default=None,
|
||||||
|
),
|
||||||
giphy_rating: Optional[int] = REQ(json_validator=check_int, default=None),
|
giphy_rating: Optional[int] = REQ(json_validator=check_int, default=None),
|
||||||
default_code_block_language: Optional[str] = REQ(default=None),
|
default_code_block_language: Optional[str] = REQ(default=None),
|
||||||
digest_weekday: Optional[int] = REQ(
|
digest_weekday: Optional[int] = REQ(
|
||||||
|
@ -276,6 +294,28 @@ def update_realm(
|
||||||
"move_messages_between_streams_limit_seconds"
|
"move_messages_between_streams_limit_seconds"
|
||||||
] = move_messages_between_streams_limit_seconds
|
] = move_messages_between_streams_limit_seconds
|
||||||
|
|
||||||
|
jitsi_server_url: Optional[str] = None
|
||||||
|
if jitsi_server_url_raw is not None:
|
||||||
|
jitsi_server_url = parse_jitsi_server_url(
|
||||||
|
jitsi_server_url_raw,
|
||||||
|
Realm.JITSI_SERVER_SPECIAL_VALUES_MAP,
|
||||||
|
)
|
||||||
|
|
||||||
|
# We handle the "None" case separately here because
|
||||||
|
# in the loop below, do_set_realm_property is called only when
|
||||||
|
# the setting value is not "None". For values other than "None",
|
||||||
|
# the loop itself sets the value of 'jitsi_server_url' by
|
||||||
|
# calling do_set_realm_property.
|
||||||
|
if jitsi_server_url is None and realm.jitsi_server_url is not None:
|
||||||
|
do_set_realm_property(
|
||||||
|
realm,
|
||||||
|
"jitsi_server_url",
|
||||||
|
jitsi_server_url,
|
||||||
|
acting_user=user_profile,
|
||||||
|
)
|
||||||
|
|
||||||
|
data["jitsi_server_url"] = jitsi_server_url
|
||||||
|
|
||||||
# The user of `locals()` here is a bit of a code smell, but it's
|
# The user of `locals()` here is a bit of a code smell, but it's
|
||||||
# restricted to the elements present in realm.property_types.
|
# restricted to the elements present in realm.property_types.
|
||||||
#
|
#
|
||||||
|
|
Loading…
Reference in New Issue