Files
upstream-zulip/zerver/checks.py
upstream-mirror 64898b2c79
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
2026-09-15 15:05:30 +05:00

247 lines
8.9 KiB
Python

import os
import re
from collections.abc import Iterable, Sequence
from typing import Any
from django.apps.config import AppConfig
from django.conf import settings
from django.core import checks
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
def get_settings_error_message_strings(setting_name: str) -> tuple[str, str]:
# Even in Docker, MANUAL_CONFIGURATION means the admin
# manages /etc/zulip/settings.py themselves, so the SETTING_*
# environment variables are not where to make changes.
if settings.RUNNING_IN_HELM:
settings_location = "your Helm values"
setting_display_name = "zulip.environment.SETTING_" + setting_name
elif settings.RUNNING_IN_DOCKER and os.environ.get("MANUAL_CONFIGURATION") != "True":
settings_location = "your Docker environment configuration"
setting_display_name = "SETTING_" + setting_name
else:
settings_location = "/etc/zulip/settings.py"
setting_display_name = setting_name
return settings_location, setting_display_name
def check_required_settings(
app_configs: Sequence[AppConfig] | None,
databases: Sequence[str] | None,
**kwargs: Any,
) -> Iterable[checks.CheckMessage]:
# These are the settings that we will check that the user has filled in for
# production deployments before starting the app. It consists of a series
# of pairs of (setting name, default value that it must be changed from)
required_settings = [
("EXTERNAL_HOST", "zulip.example.com"),
("ZULIP_ADMINISTRATOR", "zulip-admin@example.com"),
# SECRET_KEY doesn't really need to be here, in
# that we set it automatically, but just in
# case, it seems worth having in this list
("SECRET_KEY", ""),
("AUTHENTICATION_BACKENDS", ()),
]
errors = []
for setting_name, default in required_settings:
value = getattr(settings, setting_name, None)
if value and value != default:
continue
settings_location, setting_display_name = get_settings_error_message_strings(setting_name)
if value:
# The setting is still the example value from the
# documentation, which the admin must replace -- saying
# "you must set" it would be confusing, as it is set.
message = (
f"{setting_display_name} is still set to the example value {default!r}; "
f"change it in {settings_location}"
)
else:
message = f"You must set {setting_display_name} in {settings_location}"
errors.append(
checks.Error(
message,
obj=f"settings.{setting_name}",
id="zulip.E001",
)
)
return errors
def check_external_host_setting(
app_configs: Sequence[AppConfig] | None,
databases: Sequence[str] | None,
**kwargs: Any,
) -> Iterable[checks.CheckMessage]:
if not hasattr(settings, "EXTERNAL_HOST"): # nocoverage
return []
errors = []
scheme = settings.EXTERNAL_URI_SCHEME
if scheme != "https://" and not settings.DEVELOPMENT:
errors.append(
checks.Error(
"Zulip does not support a non-HTTPS external scheme in production",
obj="settings.EXTERNAL_URI_SCHEME",
hint="Do not override EXTERNAL_URI_SCHEME in production",
id="zulip.E004",
)
)
hostname = settings.EXTERNAL_HOST
if "." not in hostname and os.environ.get("ZULIP_TEST_SUITE") != "true" and settings.PRODUCTION:
suggest = ".localdomain" if hostname == "localhost" else ".local"
errors.append(
checks.Error(
f"EXTERNAL_HOST ({hostname}) does not contain a domain part",
obj="settings.EXTERNAL_HOST",
hint=f"Add {suggest} to the end",
id="zulip.E002",
)
)
if ":" in hostname:
hostname = hostname.split(":")[0]
if len(hostname) > 255:
errors.append(
checks.Error(
f"EXTERNAL_HOST ({hostname}) is too long to be a valid hostname",
obj="settings.EXTERNAL_HOST",
id="zulip.E002",
)
)
domain_part = re.compile(r"(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
if not hostname.isascii():
suggestion = ".".join(
"xn--" + part.encode("punycode").decode() if not part.isascii() else part
for part in hostname.split(".")
)
errors.append(
checks.Error(
f"EXTERNAL_HOST ({hostname}) contains non-ASCII characters",
hint=f"Switch to punycode: {suggestion}",
obj="settings.EXTERNAL_HOST",
id="zulip.E002",
)
)
elif not all(domain_part.match(x) for x in hostname.split(".")):
errors.append(
checks.Error(
f"EXTERNAL_HOST ({hostname}) does not validate as a hostname",
obj="settings.EXTERNAL_HOST",
id="zulip.E002",
)
)
return errors
def check_auth_settings(
app_configs: Sequence[AppConfig] | None,
databases: Sequence[str] | None,
**kwargs: Any,
) -> Iterable[checks.CheckMessage]:
errors = []
for idp_name, idp_dict in settings.SOCIAL_AUTH_SAML_ENABLED_IDPS.items():
if "zulip_groups" in idp_dict.get("extra_attrs", []):
errors.append(
checks.Error(
"zulip_groups can't be listed in extra_attrs",
obj=f'settings.SOCIAL_AUTH_SAML_ENABLED_IDPS["{idp_name}"]["extra_attrs"]',
id="zulip.E003",
)
)
for subdomain, config_dict in settings.SOCIAL_AUTH_SYNC_ATTRS_DICT.items():
for auth_name, attrs_map in config_dict.items():
for attr_key, attr_value in attrs_map.items():
if attr_value == "zulip_groups":
errors.append(
checks.Error(
"zulip_groups can't be listed as a SAML attribute",
obj=f'settings.SOCIAL_AUTH_SYNC_ATTRS_DICT["{subdomain}"]["{auth_name}"]["{attr_key}"]',
id="zulip.E004",
)
)
return errors
def check_uploads_settings(
app_configs: Sequence[AppConfig] | None,
databases: Sequence[str] | None,
**kwargs: Any,
) -> Iterable[checks.CheckMessage]:
uploads_dir = settings.LOCAL_UPLOADS_DIR
if uploads_dir is None:
errors = []
if settings.S3_AUTH_UPLOADS_BUCKET == "":
errors.append(
checks.Error(
"Neither settings.LOCAL_UPLOADS_DIR nor settings.S3_AUTH_UPLOADS_BUCKET is set",
obj="settings.S3_AUTH_UPLOADS_BUCKET",
id="zulip.E005",
)
)
if settings.S3_AVATAR_BUCKET == "":
errors.append(
checks.Error(
"Neither settings.LOCAL_UPLOADS_DIR nor settings.S3_AVATAR_BUCKET is set",
obj="settings.S3_AVATAR_BUCKET",
id="zulip.E005",
)
)
return errors
if not os.path.isdir(uploads_dir):
return [
checks.Error(
f"settings.LOCAL_UPLOADS_DIR ({uploads_dir}) does not exist",
obj="settings.LOCAL_UPLOADS_DIR",
id="zulip.E006",
)
]
elif not os.access(uploads_dir, os.W_OK):
return [
checks.Error(
f"settings.LOCAL_UPLOADS_DIR ({uploads_dir}) is not writable",
obj="settings.LOCAL_UPLOADS_DIR",
id="zulip.E006",
)
]
return []
def check_jitsi_server_url(
app_configs: Sequence[AppConfig] | None,
databases: Sequence[str] | None,
**kwargs: Any,
) -> Iterable[checks.CheckMessage]:
# As of Zulip 12.0, the web app will not load if a server-level
# Jitsi Meet integration has been configured that is not a URL.
# A None value is the valid way to disable the integration. By
# default, the integration uses the SaaS https://meet.jit.si
# server.
jitsi_server_url = settings.JITSI_SERVER_URL
if jitsi_server_url is None or jitsi_server_url == "https://meet.jit.si":
return []
assert isinstance(jitsi_server_url, str)
validate = URLValidator()
try:
validate(jitsi_server_url)
return []
except ValidationError:
settings_location, setting_display_name = get_settings_error_message_strings(
"JITSI_SERVER_URL"
)
message = f"{setting_display_name} in {settings_location} is not a URL"
return [
checks.Error(
message,
obj="settings.JITSI_SERVER_URL",
id="zulip.E007",
)
]