ruff: Fix UP007 Use X | Y for type annotations.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg
2024-07-11 17:30:23 -07:00
committed by Tim Abbott
parent e08a24e47f
commit 531b34cb4c
355 changed files with 2759 additions and 3029 deletions

View File

@@ -17,7 +17,7 @@ import json
import logging
from abc import ABC, abstractmethod
from email.headerregistry import Address
from typing import Any, Callable, Optional, TypedDict, TypeVar, Union, cast
from typing import Any, Callable, TypedDict, TypeVar, cast
from urllib.parse import urlencode
import magic
@@ -153,8 +153,8 @@ def pad_method_dict(method_dict: dict[str, bool]) -> dict[str, bool]:
def auth_enabled_helper(
backends_to_check: list[str],
realm: Optional[Realm],
realm_authentication_methods: Optional[dict[str, bool]] = None,
realm: Realm | None,
realm_authentication_methods: dict[str, bool] | None = None,
) -> bool:
"""
realm_authentication_methods can be passed if already fetched to avoid
@@ -179,19 +179,19 @@ def auth_enabled_helper(
def ldap_auth_enabled(
realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None
realm: Realm | None = None, realm_authentication_methods: dict[str, bool] | None = None
) -> bool:
return auth_enabled_helper(["LDAP"], realm, realm_authentication_methods)
def email_auth_enabled(
realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None
realm: Realm | None = None, realm_authentication_methods: dict[str, bool] | None = None
) -> bool:
return auth_enabled_helper(["Email"], realm, realm_authentication_methods)
def password_auth_enabled(
realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None
realm: Realm | None = None, realm_authentication_methods: dict[str, bool] | None = None
) -> bool:
return ldap_auth_enabled(realm, realm_authentication_methods) or email_auth_enabled(
realm, realm_authentication_methods
@@ -199,48 +199,48 @@ def password_auth_enabled(
def dev_auth_enabled(
realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None
realm: Realm | None = None, realm_authentication_methods: dict[str, bool] | None = None
) -> bool:
return auth_enabled_helper(["Dev"], realm, realm_authentication_methods)
def google_auth_enabled(
realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None
realm: Realm | None = None, realm_authentication_methods: dict[str, bool] | None = None
) -> bool:
return auth_enabled_helper(["Google"], realm, realm_authentication_methods)
def github_auth_enabled(
realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None
realm: Realm | None = None, realm_authentication_methods: dict[str, bool] | None = None
) -> bool:
return auth_enabled_helper(["GitHub"], realm, realm_authentication_methods)
def gitlab_auth_enabled(
realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None
realm: Realm | None = None, realm_authentication_methods: dict[str, bool] | None = None
) -> bool:
return auth_enabled_helper(["GitLab"], realm, realm_authentication_methods)
def apple_auth_enabled(
realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None
realm: Realm | None = None, realm_authentication_methods: dict[str, bool] | None = None
) -> bool:
return auth_enabled_helper(["Apple"], realm, realm_authentication_methods)
def saml_auth_enabled(
realm: Optional[Realm] = None, realm_authentication_methods: Optional[dict[str, bool]] = None
realm: Realm | None = None, realm_authentication_methods: dict[str, bool] | None = None
) -> bool:
return auth_enabled_helper(["SAML"], realm, realm_authentication_methods)
def require_email_format_usernames(realm: Optional[Realm] = None) -> bool:
def require_email_format_usernames(realm: Realm | None = None) -> bool:
if ldap_auth_enabled(realm) and (settings.LDAP_EMAIL_ATTR or settings.LDAP_APPEND_DOMAIN):
return False
return True
def is_user_active(user_profile: UserProfile, return_data: Optional[dict[str, Any]] = None) -> bool:
def is_user_active(user_profile: UserProfile, return_data: dict[str, Any] | None = None) -> bool:
if user_profile.realm.deactivated:
if return_data is not None:
return_data["inactive_realm"] = True
@@ -258,8 +258,8 @@ def is_user_active(user_profile: UserProfile, return_data: Optional[dict[str, An
def common_get_active_user(
email: str, realm: Realm, return_data: Optional[dict[str, Any]] = None
) -> Optional[UserProfile]:
email: str, realm: Realm, return_data: dict[str, Any] | None = None
) -> UserProfile | None:
"""This is the core common function used by essentially all
authentication backends to check if there's an active user account
with a given email address in the organization, handling both
@@ -305,7 +305,7 @@ def is_subdomain_in_allowed_subdomains_list(subdomain: str, allowed_subdomains:
return False
AuthFuncT = TypeVar("AuthFuncT", bound=Callable[..., Optional[UserProfile]])
AuthFuncT = TypeVar("AuthFuncT", bound=Callable[..., UserProfile | None])
class RateLimitedAuthenticationByUsername(RateLimitedObject):
@@ -340,7 +340,7 @@ def auth_rate_limiting_already_applied(request: HttpRequest) -> bool:
# @decorator does this for us.
# The usual @wraps from functools breaks signatures, so it can't be used here.
@decorator
def custom_auth_decorator(auth_func: AuthFuncT, *args: Any, **kwargs: Any) -> Optional[UserProfile]:
def custom_auth_decorator(auth_func: AuthFuncT, *args: Any, **kwargs: Any) -> UserProfile | None:
custom_auth_wrapper_func = settings.CUSTOM_AUTHENTICATION_WRAPPER_FUNCTION
if custom_auth_wrapper_func is None:
return auth_func(*args, **kwargs)
@@ -349,7 +349,7 @@ def custom_auth_decorator(auth_func: AuthFuncT, *args: Any, **kwargs: Any) -> Op
@decorator
def rate_limit_auth(auth_func: AuthFuncT, *args: Any, **kwargs: Any) -> Optional[UserProfile]:
def rate_limit_auth(auth_func: AuthFuncT, *args: Any, **kwargs: Any) -> UserProfile | None:
if not settings.RATE_LIMITING_AUTHENTICATE:
return auth_func(*args, **kwargs)
@@ -379,7 +379,7 @@ def rate_limit_auth(auth_func: AuthFuncT, *args: Any, **kwargs: Any) -> Optional
@decorator
def log_auth_attempts(auth_func: AuthFuncT, *args: Any, **kwargs: Any) -> Optional[UserProfile]:
def log_auth_attempts(auth_func: AuthFuncT, *args: Any, **kwargs: Any) -> UserProfile | None:
result = auth_func(*args, **kwargs)
backend_instance = args[0]
@@ -428,12 +428,12 @@ class ZulipAuthMixin:
"""
name = "undefined"
_logger: Optional[logging.Logger] = None
_logger: logging.Logger | None = None
# Describes which plans gives access to this authentication method on zulipchat.com.
# None means the backend is available regardless of the plan.
# Otherwise, it should be a list of Realm.plan_type values that give access to the backend.
available_for_cloud_plans: Optional[list[int]] = None
available_for_cloud_plans: list[int] | None = None
@property
def logger(self) -> logging.Logger:
@@ -441,7 +441,7 @@ class ZulipAuthMixin:
self._logger = logging.getLogger(f"zulip.auth.{self.name}")
return self._logger
def get_user(self, user_profile_id: int) -> Optional[UserProfile]:
def get_user(self, user_profile_id: int) -> UserProfile | None:
"""Override the Django method for getting a UserProfile object from
the user_profile_id,."""
try:
@@ -464,13 +464,13 @@ class ZulipDummyBackend(ZulipAuthMixin):
@custom_auth_decorator
def authenticate(
self,
request: Optional[HttpRequest] = None,
request: HttpRequest | None = None,
*,
username: str,
realm: Realm,
use_dummy_backend: bool = False,
return_data: Optional[dict[str, Any]] = None,
) -> Optional[UserProfile]:
return_data: dict[str, Any] | None = None,
) -> UserProfile | None:
if use_dummy_backend:
return common_get_active_user(username, realm, return_data)
return None
@@ -514,8 +514,8 @@ class EmailAuthBackend(ZulipAuthMixin):
username: str,
password: str,
realm: Realm,
return_data: Optional[dict[str, Any]] = None,
) -> Optional[UserProfile]:
return_data: dict[str, Any] | None = None,
) -> UserProfile | None:
"""Authenticate a user based on email address as the user name."""
if not password_auth_enabled(realm):
if return_data is not None:
@@ -706,16 +706,16 @@ class ZulipLDAPAuthBackendBase(ZulipAuthMixin, LDAPBackend):
# Disable django-auth-ldap's permissions functions -- we don't use
# the standard Django user/group permissions system because they
# are prone to performance issues.
def has_perm(self, user: Optional[UserProfile], perm: Any, obj: Any = None) -> bool:
def has_perm(self, user: UserProfile | None, perm: Any, obj: Any = None) -> bool:
return False
def has_module_perms(self, user: Optional[UserProfile], app_label: Optional[str]) -> bool:
def has_module_perms(self, user: UserProfile | None, app_label: str | None) -> bool:
return False
def get_all_permissions(self, user: Optional[UserProfile], obj: Any = None) -> set[Any]:
def get_all_permissions(self, user: UserProfile | None, obj: Any = None) -> set[Any]:
return set()
def get_group_permissions(self, user: Optional[UserProfile], obj: Any = None) -> set[Any]:
def get_group_permissions(self, user: UserProfile | None, obj: Any = None) -> set[Any]:
return set()
def django_to_ldap_username(self, username: str) -> str:
@@ -929,7 +929,7 @@ class ZulipLDAPAuthBackendBase(ZulipAuthMixin, LDAPBackend):
def sync_custom_profile_fields_from_ldap(
self, user_profile: UserProfile, ldap_user: _LDAPUser
) -> None:
values_by_var_name: dict[str, Union[int, str, list[int]]] = {}
values_by_var_name: dict[str, int | str | list[int]] = {}
for attr, ldap_attr in settings.AUTH_LDAP_USER_ATTR_MAP.items():
if not attr.startswith("custom_profile_field__"):
continue
@@ -1029,15 +1029,15 @@ class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase):
@custom_auth_decorator
def authenticate(
self,
request: Optional[HttpRequest] = None,
request: HttpRequest | None = None,
*,
username: str,
password: str,
realm: Realm,
prereg_realm: Optional[PreregistrationRealm] = None,
prereg_user: Optional[PreregistrationUser] = None,
return_data: Optional[dict[str, Any]] = None,
) -> Optional[UserProfile]:
prereg_realm: PreregistrationRealm | None = None,
prereg_user: PreregistrationUser | None = None,
return_data: dict[str, Any] | None = None,
) -> UserProfile | None:
self._realm = realm
self._prereg_user = prereg_user
self._prereg_realm = prereg_realm
@@ -1204,13 +1204,13 @@ class ZulipLDAPUserPopulator(ZulipLDAPAuthBackendBase):
def authenticate(
self,
request: Optional[HttpRequest] = None,
request: HttpRequest | None = None,
*,
username: str,
password: str,
realm: Realm,
return_data: Optional[dict[str, Any]] = None,
) -> Optional[UserProfile]:
return_data: dict[str, Any] | None = None,
) -> UserProfile | None:
return None
def get_or_build_user(
@@ -1348,12 +1348,12 @@ class DevAuthBackend(ZulipAuthMixin):
def authenticate(
self,
request: Optional[HttpRequest] = None,
request: HttpRequest | None = None,
*,
dev_auth_username: str,
realm: Realm,
return_data: Optional[dict[str, Any]] = None,
) -> Optional[UserProfile]:
return_data: dict[str, Any] | None = None,
) -> UserProfile | None:
if not dev_auth_enabled(realm):
return None
return common_get_active_user(dev_auth_username, realm, return_data=return_data)
@@ -1362,7 +1362,7 @@ class DevAuthBackend(ZulipAuthMixin):
class ExternalAuthMethodDictT(TypedDict):
name: str
display_name: str
display_icon: Optional[str]
display_icon: str | None
login_url: str
signup_url: str
@@ -1377,7 +1377,7 @@ class ExternalAuthMethod(ABC):
auth_backend_name = "undeclared"
name = "undeclared"
display_icon: Optional[str] = None
display_icon: str | None = None
# Used to determine how to order buttons on login form, backend with
# higher sort order are displayed first.
@@ -1385,7 +1385,7 @@ class ExternalAuthMethod(ABC):
@classmethod
@abstractmethod
def dict_representation(cls, realm: Optional[Realm] = None) -> list[ExternalAuthMethodDictT]:
def dict_representation(cls, realm: Realm | None = None) -> list[ExternalAuthMethodDictT]:
"""
Method returning dictionaries representing the authentication methods
corresponding to the backend that subclasses this. The documentation
@@ -1415,8 +1415,8 @@ class ExternalAuthDataDict(TypedDict, total=False):
is_signup: bool
is_realm_creation: bool
redirect_to: str
mobile_flow_otp: Optional[str]
desktop_flow_otp: Optional[str]
mobile_flow_otp: str | None
desktop_flow_otp: str | None
multiuse_object_key: str
full_name_validated: bool
# The mobile app doesn't actually use a session, so this
@@ -1433,10 +1433,10 @@ class ExternalAuthResult:
def __init__(
self,
*,
user_profile: Optional[UserProfile] = None,
data_dict: Optional[ExternalAuthDataDict] = None,
request: Optional[HttpRequest] = None,
login_token: Optional[str] = None,
user_profile: UserProfile | None = None,
data_dict: ExternalAuthDataDict | None = None,
request: HttpRequest | None = None,
login_token: str | None = None,
delete_stored_data: bool = True,
) -> None:
if data_dict is None:
@@ -1580,12 +1580,12 @@ class ZulipRemoteUserBackend(ZulipAuthMixin, RemoteUserBackend, ExternalAuthMeth
@override
def authenticate( # type: ignore[override] # authenticate has an incompatible signature with ModelBackend and BaseBackend
self,
request: Optional[HttpRequest] = None,
request: HttpRequest | None = None,
*,
remote_user: str,
realm: Realm,
return_data: Optional[dict[str, Any]] = None,
) -> Optional[UserProfile]:
return_data: dict[str, Any] | None = None,
) -> UserProfile | None:
if not auth_enabled_helper(["RemoteUser"], realm):
return None
@@ -1594,7 +1594,7 @@ class ZulipRemoteUserBackend(ZulipAuthMixin, RemoteUserBackend, ExternalAuthMeth
@classmethod
@override
def dict_representation(cls, realm: Optional[Realm] = None) -> list[ExternalAuthMethodDictT]:
def dict_representation(cls, realm: Realm | None = None) -> list[ExternalAuthMethodDictT]:
return [
dict(
name=cls.name,
@@ -1631,7 +1631,7 @@ def redirect_deactivated_user_to_login(realm: Realm, email: str) -> HttpResponse
def social_associate_user_helper(
backend: BaseAuth, return_data: dict[str, Any], *args: Any, **kwargs: Any
) -> Union[HttpResponse, Optional[UserProfile]]:
) -> HttpResponse | UserProfile | None:
"""Responsible for doing the Zulip account lookup and validation parts
of the Zulip social auth pipeline (similar to the authenticate()
methods in most other auth backends in this file).
@@ -1783,7 +1783,7 @@ def social_associate_user_helper(
@partial
def social_auth_associate_user(
backend: BaseAuth, *args: Any, **kwargs: Any
) -> Union[HttpResponse, dict[str, Any]]:
) -> HttpResponse | dict[str, Any]:
"""A simple wrapper function to reformat the return data from
social_associate_user_helper as a dictionary. The
python-social-auth infrastructure will then pass those values into
@@ -1807,7 +1807,7 @@ def social_auth_associate_user(
def social_auth_finish(
backend: Any, details: dict[str, Any], response: HttpResponse, *args: Any, **kwargs: Any
) -> Optional[HttpResponse]:
) -> HttpResponse | None:
"""Given the determination in social_auth_associate_user for whether
the user should be authenticated, this takes care of actually
logging in the user (if appropriate) and redirecting the browser
@@ -2013,7 +2013,7 @@ class SocialAuthMixin(ZulipAuthMixin, ExternalAuthMethod, BaseAuth):
standard_relay_params = [*settings.SOCIAL_AUTH_FIELDS_STORED_IN_SESSION, "next"]
def auth_complete(self, *args: Any, **kwargs: Any) -> Optional[HttpResponse]:
def auth_complete(self, *args: Any, **kwargs: Any) -> HttpResponse | None:
"""This is a small wrapper around the core `auth_complete` method of
python-social-auth, designed primarily to prevent 500s for
exceptions in the social auth code from situations that are
@@ -2049,7 +2049,7 @@ class SocialAuthMixin(ZulipAuthMixin, ExternalAuthMethod, BaseAuth):
@classmethod
@override
def dict_representation(cls, realm: Optional[Realm] = None) -> list[ExternalAuthMethodDictT]:
def dict_representation(cls, realm: Realm | None = None) -> list[ExternalAuthMethodDictT]:
return [
dict(
name=cls.name,
@@ -2286,7 +2286,7 @@ class AppleAuthBackend(SocialAuthMixin, AppleIdAuth):
)
return state
def validate_state(self) -> Optional[str]:
def validate_state(self) -> str | None:
"""
This method replaces a method from python-social-auth; it is
adapted to retrieve the data stored in Redis, save it in
@@ -2337,7 +2337,7 @@ class AppleAuthBackend(SocialAuthMixin, AppleIdAuth):
return user_details
@override
def auth_complete(self, *args: Any, **kwargs: Any) -> Optional[HttpResponse]:
def auth_complete(self, *args: Any, **kwargs: Any) -> HttpResponse | None:
if not self.is_native_flow():
# The default implementation in python-social-auth is the browser flow.
return super().auth_complete(*args, **kwargs)
@@ -2428,7 +2428,7 @@ class SAMLDocument:
self.encoded_saml_message = encoded_saml_message
self.backend = backend
self._decoded_saml_message: Optional[str] = None
self._decoded_saml_message: str | None = None
@property
def logger(self) -> logging.Logger:
@@ -2457,7 +2457,7 @@ class SAMLDocument:
"""
return type(self).__name__
def get_issuing_idp(self) -> Optional[str]:
def get_issuing_idp(self) -> str | None:
"""
Given a SAMLResponse or SAMLRequest, returns which of the configured IdPs
is declared as the issuer.
@@ -2523,7 +2523,7 @@ class SAMLResponse(SAMLDocument):
self.logger.error("Error parsing SAMLResponse: %s", str(e))
return []
def get_session_index(self) -> Optional[str]:
def get_session_index(self) -> str | None:
"""
Returns the SessionIndex from the SAMLResponse.
"""
@@ -2637,7 +2637,7 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth):
)
@classmethod
def get_data_from_redis(cls, key: str) -> Optional[dict[str, Any]]:
def get_data_from_redis(cls, key: str) -> dict[str, Any] | None:
data = None
if key.startswith("saml_token_"):
# Safety if statement, to not allow someone to poke around arbitrary Redis keys here.
@@ -2664,7 +2664,7 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth):
except orjson.JSONDecodeError:
return {}
def choose_subdomain(self, relayed_params: dict[str, Any]) -> Optional[str]:
def choose_subdomain(self, relayed_params: dict[str, Any]) -> str | None:
subdomain = relayed_params.get("subdomain")
if subdomain is not None:
return subdomain
@@ -2707,7 +2707,7 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth):
return
subdomain = self.strategy.session_get("subdomain")
entitlements: Union[str, list[str]] = attributes.get(org_membership_attribute, [])
entitlements: str | list[str] = attributes.get(org_membership_attribute, [])
if isinstance(entitlements, str): # nocoverage
# This shouldn't happen as we'd always expect a list from this attribute even
# if it only has one element, but it's safer to have this defensive code.
@@ -2725,7 +2725,7 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth):
)
raise AuthFailed(self, error_msg)
def process_logout(self, subdomain: str, idp_name: str) -> Optional[HttpResponse]:
def process_logout(self, subdomain: str, idp_name: str) -> HttpResponse | None:
"""
We override process_logout, because we need to customize
the way of revoking sessions and introduce NameID validation.
@@ -2797,7 +2797,7 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth):
return HttpResponseRedirect(url)
@override
def auth_complete(self, *args: Any, **kwargs: Any) -> Optional[HttpResponse]:
def auth_complete(self, *args: Any, **kwargs: Any) -> HttpResponse | None:
"""
Additional ugly wrapping on top of auth_complete in SocialAuthMixin.
We handle two things for processing SAMLResponses here:
@@ -2948,7 +2948,7 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth):
@classmethod
@override
def dict_representation(cls, realm: Optional[Realm] = None) -> list[ExternalAuthMethodDictT]:
def dict_representation(cls, realm: Realm | None = None) -> list[ExternalAuthMethodDictT]:
result: list[ExternalAuthMethodDictT] = []
for idp_name, idp_dict in settings.SOCIAL_AUTH_SAML_ENABLED_IDPS.items():
if realm and not cls.validate_idp_for_subdomain(idp_name, realm.subdomain):
@@ -3019,7 +3019,7 @@ class GenericOpenIdConnectBackend(SocialAuthMixin, OpenIdConnectAuth):
settings_dict: OIDCIdPConfigDict
[settings_dict] = settings.SOCIAL_AUTH_OIDC_ENABLED_IDPS.values() or [OIDCIdPConfigDict()]
display_icon: Optional[str] = settings_dict.get("display_icon", None)
display_icon: str | None = settings_dict.get("display_icon", None)
display_name: str = settings_dict.get("display_name", "OIDC")
full_name_validated = getattr(settings, "SOCIAL_AUTH_OIDC_FULL_NAME_VALIDATED", False)
@@ -3050,7 +3050,7 @@ class GenericOpenIdConnectBackend(SocialAuthMixin, OpenIdConnectAuth):
@classmethod
@override
def dict_representation(cls, realm: Optional[Realm] = None) -> list[ExternalAuthMethodDictT]:
def dict_representation(cls, realm: Realm | None = None) -> list[ExternalAuthMethodDictT]:
return [
dict(
name=f"oidc:{cls.name}",
@@ -3069,7 +3069,7 @@ class GenericOpenIdConnectBackend(SocialAuthMixin, OpenIdConnectAuth):
def validate_otp_params(
mobile_flow_otp: Optional[str] = None, desktop_flow_otp: Optional[str] = None
mobile_flow_otp: str | None = None, desktop_flow_otp: str | None = None
) -> None:
for otp in [mobile_flow_otp, desktop_flow_otp]:
if otp is not None and not is_valid_otp(otp):
@@ -3081,7 +3081,7 @@ def validate_otp_params(
class SAMLSPInitiatedLogout:
@classmethod
def get_logged_in_user_idp(cls, request: HttpRequest) -> Optional[str]:
def get_logged_in_user_idp(cls, request: HttpRequest) -> str | None:
"""
Information about the authentication method which was used for
this session is stored in social_auth_backend session attribute.
@@ -3098,7 +3098,7 @@ class SAMLSPInitiatedLogout:
return authentication_method.split("saml:")[1]
@classmethod
def get_logged_in_user_session_index(cls, request: HttpRequest) -> Optional[str]:
def get_logged_in_user_session_index(cls, request: HttpRequest) -> str | None:
"""
During SAML authentication, we obtain the SessionIndex value provided
by the IdP and save it in the session. This function can be used
@@ -3112,9 +3112,7 @@ class SAMLSPInitiatedLogout:
return session_index
@classmethod
def slo_request_to_idp(
cls, request: HttpRequest, return_to: Optional[str] = None
) -> HttpResponse:
def slo_request_to_idp(cls, request: HttpRequest, return_to: str | None = None) -> HttpResponse:
"""
Generates the redirect to the IdP's SLO endpoint with
the appropriately generated LogoutRequest. This should only be called
@@ -3163,7 +3161,7 @@ class SAMLSPInitiatedLogout:
return HttpResponseRedirect(settings.LOGIN_URL)
def get_external_method_dicts(realm: Optional[Realm] = None) -> list[ExternalAuthMethodDictT]:
def get_external_method_dicts(realm: Realm | None = None) -> list[ExternalAuthMethodDictT]:
"""
Returns a list of dictionaries that represent social backends, sorted
in the order in which they should be displayed.

View File

@@ -3,7 +3,7 @@ import os
import sys
import time
from copy import deepcopy
from typing import Any, Final, Literal, Union
from typing import Any, Final, Literal
from urllib.parse import urljoin
from scripts.lib.zulip_tools import get_tornado_ports
@@ -576,7 +576,7 @@ else:
########################################################################
# List of callables that know how to import templates from various sources.
LOADERS: list[Union[str, tuple[object, ...]]] = [
LOADERS: list[str | tuple[object, ...]] = [
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
]

View File

@@ -1,6 +1,6 @@
import configparser
import os
from typing import Optional, Union, overload
from typing import overload
from scripts.lib.zulip_tools import get_config as get_config_from_file
@@ -27,12 +27,12 @@ else:
@overload
def get_secret(
key: str, default_value: None = None, development_only: bool = False
) -> Optional[str]: ...
) -> str | None: ...
@overload
def get_secret(key: str, default_value: str, development_only: bool = False) -> str: ...
def get_secret(
key: str, default_value: Optional[str] = None, development_only: bool = False
) -> Optional[str]:
key: str, default_value: str | None = None, development_only: bool = False
) -> str | None:
if development_only and PRODUCTION: # nocoverage
return default_value
return secrets_file.get("secrets", key, fallback=default_value)
@@ -48,14 +48,14 @@ def get_mandatory_secret(key: str) -> str:
@overload
def get_config(section: str, key: str, default_value: None = None) -> Optional[str]: ...
def get_config(section: str, key: str, default_value: None = None) -> str | None: ...
@overload
def get_config(section: str, key: str, default_value: str) -> str: ...
@overload
def get_config(section: str, key: str, default_value: bool) -> bool: ...
def get_config(
section: str, key: str, default_value: Union[str, bool, None] = None
) -> Union[str, bool, None]:
section: str, key: str, default_value: str | bool | None = None
) -> str | bool | None:
return get_config_from_file(config_file, section, key, default_value)

View File

@@ -1,6 +1,6 @@
import os
from email.headerregistry import Address
from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Union
from typing import TYPE_CHECKING, Any, Callable, Literal, Optional
from django_auth_ldap.config import GroupOfUniqueNamesType, LDAPGroupType
@@ -21,7 +21,7 @@ DEBUG = DEVELOPMENT
EXTERNAL_HOST_WITHOUT_PORT = deport(EXTERNAL_HOST)
STATIC_URL: Optional[str] = None
STATIC_URL: str | None = None
# These settings are intended for the server admin to set. We document them in
# prod_settings_template.py, and in the initial /etc/zulip/settings.py on a new
@@ -40,7 +40,7 @@ PHYSICAL_ADDRESS = ""
FAKE_EMAIL_DOMAIN = EXTERNAL_HOST_WITHOUT_PORT
# SMTP settings
EMAIL_HOST: Optional[str] = None
EMAIL_HOST: str | None = None
# Other settings, like EMAIL_HOST_USER, EMAIL_PORT, and EMAIL_USE_TLS,
# we leave up to Django's defaults.
@@ -48,17 +48,17 @@ EMAIL_HOST: Optional[str] = None
AUTH_LDAP_SERVER_URI = ""
AUTH_LDAP_BIND_DN = ""
AUTH_LDAP_USER_SEARCH: Optional["LDAPSearch"] = None
LDAP_APPEND_DOMAIN: Optional[str] = None
LDAP_EMAIL_ATTR: Optional[str] = None
LDAP_APPEND_DOMAIN: str | None = None
LDAP_EMAIL_ATTR: str | None = None
AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
AUTH_LDAP_USERNAME_ATTR: Optional[str] = None
AUTH_LDAP_USERNAME_ATTR: str | None = None
# AUTH_LDAP_USER_ATTR_MAP is uncommented in prod_settings_template.py,
# so the value here mainly serves to help document the default.
AUTH_LDAP_USER_ATTR_MAP: dict[str, str] = {
"full_name": "cn",
}
# Automatically deactivate users not found by the AUTH_LDAP_USER_SEARCH query.
LDAP_DEACTIVATE_NON_MATCHING_USERS: Optional[bool] = None
LDAP_DEACTIVATE_NON_MATCHING_USERS: bool | None = None
# AUTH_LDAP_CONNECTION_OPTIONS: we set ldap.OPT_REFERRALS in settings.py if unset.
AUTH_LDAP_CONNECTION_OPTIONS: dict[int, object] = {}
# Disable django-auth-ldap caching, to prevent problems with OU changes.
@@ -68,28 +68,28 @@ AUTH_LDAP_ALWAYS_UPDATE_USER = False
# Development-only settings for fake LDAP authentication; used to
# support local development of LDAP auth without an LDAP server.
# Detailed docs in zproject/dev_settings.py.
FAKE_LDAP_MODE: Optional[str] = None
FAKE_LDAP_MODE: str | None = None
FAKE_LDAP_NUM_USERS = 8
AUTH_LDAP_ADVANCED_REALM_ACCESS_CONTROL: Optional[dict[str, Any]] = None
AUTH_LDAP_ADVANCED_REALM_ACCESS_CONTROL: dict[str, Any] | None = None
LDAP_SYNCHRONIZED_GROUPS_BY_REALM: dict[str, list[str]] = {}
AUTH_LDAP_GROUP_TYPE: LDAPGroupType = GroupOfUniqueNamesType()
# Social auth; we support providing values for some of these
# settings in zulip-secrets.conf instead of settings.py in development.
SOCIAL_AUTH_GITHUB_KEY = get_secret("social_auth_github_key", development_only=True)
SOCIAL_AUTH_GITHUB_ORG_NAME: Optional[str] = None
SOCIAL_AUTH_GITHUB_TEAM_ID: Optional[str] = None
SOCIAL_AUTH_GITHUB_ORG_NAME: str | None = None
SOCIAL_AUTH_GITHUB_TEAM_ID: str | None = None
SOCIAL_AUTH_GITLAB_KEY = get_secret("social_auth_gitlab_key", development_only=True)
SOCIAL_AUTH_SUBDOMAIN: Optional[str] = None
SOCIAL_AUTH_SUBDOMAIN: str | None = None
SOCIAL_AUTH_AZUREAD_OAUTH2_KEY = get_secret("social_auth_azuread_oauth2_key", development_only=True)
SOCIAL_AUTH_GOOGLE_KEY = get_secret("social_auth_google_key", development_only=True)
# SAML:
SOCIAL_AUTH_SAML_SP_ENTITY_ID: Optional[str] = None
SOCIAL_AUTH_SAML_SP_ENTITY_ID: str | None = None
SOCIAL_AUTH_SAML_SP_PUBLIC_CERT = ""
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY = ""
SOCIAL_AUTH_SAML_ORG_INFO: Optional[dict[str, dict[str, str]]] = None
SOCIAL_AUTH_SAML_TECHNICAL_CONTACT: Optional[dict[str, str]] = None
SOCIAL_AUTH_SAML_SUPPORT_CONTACT: Optional[dict[str, str]] = None
SOCIAL_AUTH_SAML_ORG_INFO: dict[str, dict[str, str]] | None = None
SOCIAL_AUTH_SAML_TECHNICAL_CONTACT: dict[str, str] | None = None
SOCIAL_AUTH_SAML_SUPPORT_CONTACT: dict[str, str] | None = None
SOCIAL_AUTH_SAML_ENABLED_IDPS: dict[str, SAMLIdPConfigDict] = {}
SOCIAL_AUTH_SAML_SECURITY_CONFIG: dict[str, Any] = {}
# Set this to True to enforce that any configured IdP needs to specify
@@ -97,7 +97,7 @@ SOCIAL_AUTH_SAML_SECURITY_CONFIG: dict[str, Any] = {}
SAML_REQUIRE_LIMIT_TO_SUBDOMAINS = False
# Historical name for SOCIAL_AUTH_GITHUB_KEY; still allowed in production.
GOOGLE_OAUTH2_CLIENT_ID: Optional[str] = None
GOOGLE_OAUTH2_CLIENT_ID: str | None = None
# Apple:
SOCIAL_AUTH_APPLE_SERVICES_ID = get_secret("social_auth_apple_services_id", development_only=True)
@@ -114,20 +114,20 @@ SOCIAL_AUTH_OIDC_FULL_NAME_VALIDATED = False
SOCIAL_AUTH_SYNC_CUSTOM_ATTRS_DICT: dict[str, dict[str, dict[str, str]]] = {}
# Other auth
SSO_APPEND_DOMAIN: Optional[str] = None
CUSTOM_HOME_NOT_LOGGED_IN: Optional[str] = None
SSO_APPEND_DOMAIN: str | None = None
CUSTOM_HOME_NOT_LOGGED_IN: str | None = None
VIDEO_ZOOM_CLIENT_ID = get_secret("video_zoom_client_id", development_only=True)
VIDEO_ZOOM_CLIENT_SECRET = get_secret("video_zoom_client_secret")
# Email gateway
EMAIL_GATEWAY_PATTERN = ""
EMAIL_GATEWAY_LOGIN: Optional[str] = None
EMAIL_GATEWAY_IMAP_SERVER: Optional[str] = None
EMAIL_GATEWAY_IMAP_PORT: Optional[int] = None
EMAIL_GATEWAY_IMAP_FOLDER: Optional[str] = None
EMAIL_GATEWAY_LOGIN: str | None = None
EMAIL_GATEWAY_IMAP_SERVER: str | None = None
EMAIL_GATEWAY_IMAP_PORT: int | None = None
EMAIL_GATEWAY_IMAP_FOLDER: str | None = None
# Not documented for in /etc/zulip/settings.py, since it's rarely needed.
EMAIL_GATEWAY_EXTRA_PATTERN_HACK: Optional[str] = None
EMAIL_GATEWAY_EXTRA_PATTERN_HACK: str | None = None
# Error reporting
ERROR_REPORTING = True
@@ -135,22 +135,22 @@ LOGGING_SHOW_MODULE = False
LOGGING_SHOW_PID = False
# Sentry.io error defaults to off
SENTRY_DSN: Optional[str] = get_config("sentry", "project_dsn", None)
SENTRY_TRACE_WORKER_RATE: Union[float, dict[str, float]] = 0.0
SENTRY_DSN: str | None = get_config("sentry", "project_dsn", None)
SENTRY_TRACE_WORKER_RATE: float | dict[str, float] = 0.0
SENTRY_TRACE_RATE: float = 0.0
SENTRY_PROFILE_RATE: float = 0.1
SENTRY_FRONTEND_DSN: Optional[str] = get_config("sentry", "frontend_project_dsn", None)
SENTRY_FRONTEND_DSN: str | None = get_config("sentry", "frontend_project_dsn", None)
SENTRY_FRONTEND_SAMPLE_RATE: float = 1.0
SENTRY_FRONTEND_TRACE_RATE: float = 0.1
# File uploads and avatars
# TODO: Rename MAX_FILE_UPLOAD_SIZE to have unit in name.
DEFAULT_AVATAR_URI: Optional[str] = None
DEFAULT_LOGO_URI: Optional[str] = None
DEFAULT_AVATAR_URI: str | None = None
DEFAULT_LOGO_URI: str | None = None
S3_AVATAR_BUCKET = ""
S3_AUTH_UPLOADS_BUCKET = ""
S3_REGION: Optional[str] = None
S3_ENDPOINT_URL: Optional[str] = None
S3_REGION: str | None = None
S3_ENDPOINT_URL: str | None = None
S3_ADDRESSING_STYLE: Literal["auto", "virtual", "path"] = "auto"
S3_SKIP_PROXY = True
S3_UPLOADS_STORAGE_CLASS: Literal[
@@ -161,17 +161,17 @@ S3_UPLOADS_STORAGE_CLASS: Literal[
"STANDARD",
"STANDARD_IA",
] = "STANDARD"
S3_AVATAR_PUBLIC_URL_PREFIX: Optional[str] = None
LOCAL_UPLOADS_DIR: Optional[str] = None
LOCAL_AVATARS_DIR: Optional[str] = None
LOCAL_FILES_DIR: Optional[str] = None
S3_AVATAR_PUBLIC_URL_PREFIX: str | None = None
LOCAL_UPLOADS_DIR: str | None = None
LOCAL_AVATARS_DIR: str | None = None
LOCAL_FILES_DIR: str | None = None
MAX_FILE_UPLOAD_SIZE = 25
# How many GB an organization on a paid plan can upload per user,
# on zulipchat.com.
UPLOAD_QUOTA_PER_USER_GB = 5
# Jitsi Meet video call integration; set to None to disable integration.
JITSI_SERVER_URL: Optional[str] = "https://meet.jit.si"
JITSI_SERVER_URL: str | None = "https://meet.jit.si"
# GIPHY API key.
GIPHY_API_KEY = get_secret("giphy_api_key")
@@ -217,7 +217,7 @@ NAME_CHANGES_DISABLED = False
AVATAR_CHANGES_DISABLED = False
PASSWORD_MIN_LENGTH = 6
PASSWORD_MIN_GUESSES = 10000
PUSH_NOTIFICATION_BOUNCER_URL: Optional[str] = None
PUSH_NOTIFICATION_BOUNCER_URL: str | None = None
PUSH_NOTIFICATION_REDACT_CONTENT = False
SUBMIT_USAGE_STATISTICS = True
PROMOTE_SPONSORING_ZULIP = True
@@ -399,7 +399,7 @@ USING_PGROONGA = False
# How Django should send emails. Set for most contexts in settings.py, but
# available for sysadmin override in unusual cases.
EMAIL_BACKEND: Optional[str] = None
EMAIL_BACKEND: str | None = None
# Whether to give admins a warning in the web app that email isn't set up.
# Set in settings.py when email isn't configured.
@@ -418,14 +418,14 @@ POST_MIGRATION_CACHE_FLUSHING = False
# Settings for APNS. Only needed on push.zulipchat.com or if
# rebuilding the mobile app with a different push notifications
# server.
APNS_CERT_FILE: Optional[str] = None
APNS_TOKEN_KEY_FILE: Optional[str] = None
APNS_CERT_FILE: str | None = None
APNS_TOKEN_KEY_FILE: str | None = None
APNS_TOKEN_KEY_ID = get_secret("apns_token_key_id", development_only=True)
APNS_TEAM_ID = get_secret("apns_team_id", development_only=True)
APNS_SANDBOX = True
# APNS_TOPIC is obsolete. Clients now pass the APNs topic to use.
# ZULIP_IOS_APP_ID is obsolete. Clients now pass the iOS app ID to use for APNs.
ANDROID_FCM_CREDENTIALS_PATH: Optional[str] = None
ANDROID_FCM_CREDENTIALS_PATH: str | None = None
# Limits related to the size of file uploads; last few in MB.
DATA_UPLOAD_MAX_MEMORY_SIZE = 25 * 1024 * 1024
@@ -448,7 +448,7 @@ INVITES_NEW_REALM_LIMIT_DAYS = [(1, 100)]
INVITES_NEW_REALM_DAYS = 7
# Controls for which links are published in portico footers/headers/etc.
REGISTER_LINK_DISABLED: Optional[bool] = None
REGISTER_LINK_DISABLED: bool | None = None
LOGIN_LINK_DISABLED = False
FIND_TEAM_LINK_DISABLED = True
@@ -458,11 +458,11 @@ ROOT_SUBDOMAIN_ALIASES = ["www"]
ROOT_DOMAIN_LANDING_PAGE = False
# Subdomain for serving endpoints to users from self-hosted deployments.
SELF_HOSTING_MANAGEMENT_SUBDOMAIN: Optional[str] = None
SELF_HOSTING_MANAGEMENT_SUBDOMAIN: str | None = None
# If using the Zephyr mirroring supervisord configuration, the
# hostname to connect to in order to transfer credentials from webathena.
PERSONAL_ZMIRROR_SERVER: Optional[str] = None
PERSONAL_ZMIRROR_SERVER: str | None = None
# When security-relevant links in emails expire.
CONFIRMATION_LINK_DEFAULT_VALIDITY_DAYS = 1
@@ -472,17 +472,17 @@ REALM_CREATION_LINK_VALIDITY_DAYS = 7
# Version number for ToS. Change this if you want to force every
# user to click through to re-accept terms of service before using
# Zulip again on the web.
TERMS_OF_SERVICE_VERSION: Optional[str] = None
TERMS_OF_SERVICE_VERSION: str | None = None
# HTML template path (e.g. "corporate/zulipchat_migration_tos.html")
# displayed to users when increasing TERMS_OF_SERVICE_VERSION when a
# user is to accept the terms of service for the first time, but
# already has an account. This primarily comes up when doing a data
# import.
FIRST_TIME_TERMS_OF_SERVICE_TEMPLATE: Optional[str] = None
FIRST_TIME_TERMS_OF_SERVICE_TEMPLATE: str | None = None
# Custom message (HTML allowed) to be displayed to explain why users
# need to re-accept the terms of service when a new major version is
# written.
TERMS_OF_SERVICE_MESSAGE: Optional[str] = None
TERMS_OF_SERVICE_MESSAGE: str | None = None
# Configuration for JWT auth (sign in and API key fetch)
JWT_AUTH_KEYS: dict[str, JwtAuthKey] = {}
@@ -494,7 +494,7 @@ SERVER_EMAIL = ZULIP_ADMINISTRATOR
ADMINS = (("Zulip Administrator", ZULIP_ADMINISTRATOR),)
# From address for welcome emails.
WELCOME_EMAIL_SENDER: Optional[dict[str, str]] = None
WELCOME_EMAIL_SENDER: dict[str, str] | None = None
# Whether to send periodic digests of activity.
SEND_DIGEST_EMAILS = True
@@ -502,11 +502,11 @@ SEND_DIGEST_EMAILS = True
INSTALLATION_NAME = EXTERNAL_HOST
# Used to change the Zulip logo in portico pages.
CUSTOM_LOGO_URL: Optional[str] = None
CUSTOM_LOGO_URL: str | None = None
# Random salt used when deterministically generating passwords in
# development.
INITIAL_PASSWORD_SALT: Optional[str] = None
INITIAL_PASSWORD_SALT: str | None = None
# Settings configuring the special instrumentation of the send_event
# code path used in generating API documentation for /events.
@@ -559,12 +559,12 @@ ARCHIVED_DATA_VACUUMING_DELAY_DAYS = 30
# are available to all realms.
BILLING_ENABLED = False
CLOUD_FREE_TRIAL_DAYS: Optional[int] = int(get_secret("cloud_free_trial_days", "0"))
SELF_HOSTING_FREE_TRIAL_DAYS: Optional[int] = int(get_secret("self_hosting_free_trial_days", "30"))
CLOUD_FREE_TRIAL_DAYS: int | None = int(get_secret("cloud_free_trial_days", "0"))
SELF_HOSTING_FREE_TRIAL_DAYS: int | None = int(get_secret("self_hosting_free_trial_days", "30"))
# Custom message (supports HTML) to be shown in the navbar of landing pages. Used mainly for
# making announcements.
LANDING_PAGE_NAVBAR_MESSAGE: Optional[str] = None
LANDING_PAGE_NAVBAR_MESSAGE: str | None = None
# Automatically catch-up soft deactivated users when running the
# `soft-deactivate-users` cron. Turn this off if the server has 10Ks of
@@ -573,7 +573,7 @@ LANDING_PAGE_NAVBAR_MESSAGE: Optional[str] = None
AUTO_CATCH_UP_SOFT_DEACTIVATED_USERS = True
# Enables Google Analytics on selected portico pages.
GOOGLE_ANALYTICS_ID: Optional[str] = None
GOOGLE_ANALYTICS_ID: str | None = None
# This is overridden by dev_settings.py for droplets.
IS_DEV_DROPLET = False
@@ -636,7 +636,7 @@ CAN_ACCESS_ALL_USERS_GROUP_LIMITS_PRESENCE = False
# in some places through the codebase.
SIGNED_ACCESS_TOKEN_VALIDITY_IN_SECONDS = 60
CUSTOM_AUTHENTICATION_WRAPPER_FUNCTION: Optional[Callable[..., Any]] = None
CUSTOM_AUTHENTICATION_WRAPPER_FUNCTION: Callable[..., Any] | None = None
# Whether we allow settings to be set to a collection of users and
# groups as described in api_docs/group-setting-values.md. Set to

View File

@@ -1,6 +1,5 @@
import os
import pwd
from typing import Optional
from scripts.lib.zulip_tools import deport
from zproject.settings_types import SCIMConfigDict
@@ -94,7 +93,7 @@ LOGIN_URL = "/devlogin/"
# For development convenience, configure the ToS/Privacy Policies
POLICIES_DIRECTORY = "corporate/policies"
TERMS_OF_SERVICE_VERSION = "1.0"
TERMS_OF_SERVICE_MESSAGE: Optional[str] = "Description of changes to the ToS!"
TERMS_OF_SERVICE_MESSAGE: str | None = "Description of changes to the ToS!"
EMBEDDED_BOTS_ENABLED = True
@@ -133,7 +132,7 @@ TWO_FACTOR_SMS_GATEWAY = "two_factor.gateways.fake.Fake"
# (C) If LDAP usernames are completely unrelated to email addresses.
#
# Fake LDAP data has e.g. ("ldapuser1", "ldapuser1@zulip.com") for username/email.
FAKE_LDAP_MODE: Optional[str] = None
FAKE_LDAP_MODE: str | None = None
# FAKE_LDAP_NUM_USERS = 8
if FAKE_LDAP_MODE:
@@ -180,7 +179,7 @@ if FAKE_LDAP_MODE:
AUTHENTICATION_BACKENDS += ("zproject.backends.ZulipLDAPAuthBackend",)
BILLING_ENABLED = True
LANDING_PAGE_NAVBAR_MESSAGE: Optional[str] = None
LANDING_PAGE_NAVBAR_MESSAGE: str | None = None
# Our run-dev proxy uses X-Forwarded-Port to communicate to Django
# that the request is actually on port 9991, not port 9992 (the Django
@@ -194,7 +193,7 @@ SOCIAL_AUTH_SAML_SP_ENTITY_ID = "http://localhost:9991"
SOCIAL_AUTH_SUBDOMAIN = "auth"
MEMCACHED_USERNAME: Optional[str] = None
MEMCACHED_USERNAME: str | None = None
SCIM_CONFIG: dict[str, SCIMConfigDict] = {
"zulip": {

View File

@@ -2,7 +2,7 @@
import configparser
import logging
from email.message import Message
from typing import MutableSequence, Sequence, Union
from typing import MutableSequence, Sequence
from django.conf import settings
from django.core.mail import EmailMultiAlternatives
@@ -37,7 +37,7 @@ class EmailLogBackEnd(EmailBackend):
@staticmethod
def log_email(email: EmailMessage) -> None:
"""Used in development to record sent emails in a nice HTML log"""
html_message: Union[bytes, EmailMessage, Message, str] = "Missing HTML message"
html_message: bytes | EmailMessage | Message | str = "Missing HTML message"
assert isinstance(email, EmailMultiAlternatives)
if len(email.alternatives) > 0:
html_message = email.alternatives[0][0]

View File

@@ -1,5 +1,5 @@
import os
from typing import TYPE_CHECKING, Any, Optional, Union
from typing import TYPE_CHECKING, Any, Optional
import sentry_sdk
from django.utils.translation import override as override_language
@@ -50,7 +50,7 @@ def add_context(event: "Event", hint: "Hint") -> Optional["Event"]:
return event
def traces_sampler(sampling_context: dict[str, Any]) -> Union[float, bool]:
def traces_sampler(sampling_context: dict[str, Any]) -> float | bool:
from django.conf import settings
queue = sampling_context.get("queue")
@@ -63,7 +63,7 @@ def traces_sampler(sampling_context: dict[str, Any]) -> Union[float, bool]:
return settings.SENTRY_TRACE_RATE
def setup_sentry(dsn: Optional[str], environment: str) -> None:
def setup_sentry(dsn: str | None, environment: str) -> None:
from django.conf import settings
if not dsn:

View File

@@ -1,4 +1,4 @@
from typing import Optional, TypedDict
from typing import TypedDict
class JwtAuthKey(TypedDict):
@@ -31,9 +31,9 @@ class SAMLIdPConfigDict(TypedDict, total=False):
class OIDCIdPConfigDict(TypedDict, total=False):
oidc_url: str
display_name: str
display_icon: Optional[str]
display_icon: str | None
client_id: str
secret: Optional[str]
secret: str | None
auto_signup: bool

View File

@@ -1,5 +1,4 @@
from pathlib import Path
from typing import Union
from django.template.loaders import app_directories
from typing_extensions import override
@@ -7,11 +6,11 @@ from typing_extensions import override
class TwoFactorLoader(app_directories.Loader):
@override
def get_dirs(self) -> list[Union[str, Path]]:
def get_dirs(self) -> list[str | Path]:
dirs = super().get_dirs()
# app_directories.Loader returns only a list of
# Path objects by calling get_app_template_dirs
two_factor_dirs: list[Union[str, Path]] = []
two_factor_dirs: list[str | Path] = []
for d in dirs:
assert isinstance(d, Path)
if d.match("two_factor/*"):

View File

@@ -1,5 +1,4 @@
import os
from typing import Optional
import ldap
from django_auth_ldap.config import LDAPSearch
@@ -155,8 +154,8 @@ HOME_NOT_LOGGED_IN = "/login/"
LOGIN_URL = "/accounts/login/"
# If dev_settings.py found a key or cert file to use here, ignore it.
APNS_TOKEN_KEY_FILE: Optional[str] = None
APNS_CERT_FILE: Optional[str] = None
APNS_TOKEN_KEY_FILE: str | None = None
APNS_CERT_FILE: str | None = None
# By default will not send emails when login occurs.
# Explicitly set this to True within tests that must have this on.
@@ -202,7 +201,7 @@ BIG_BLUE_BUTTON_URL = "https://bbb.example.com/bigbluebutton/"
# By default two factor authentication is disabled in tests.
# Explicitly set this to True within tests that must have this on.
TWO_FACTOR_AUTHENTICATION_ENABLED = False
PUSH_NOTIFICATION_BOUNCER_URL: Optional[str] = None
PUSH_NOTIFICATION_BOUNCER_URL: str | None = None
DEVELOPMENT_DISABLE_PUSH_BOUNCER_DOMAIN_CHECK = False
# Logging the emails while running the tests adds them
@@ -258,8 +257,8 @@ RATE_LIMITING_RULES: dict[str, list[tuple[int, int]]] = {
"sends_email_by_remote_server": [],
}
CLOUD_FREE_TRIAL_DAYS: Optional[int] = None
SELF_HOSTING_FREE_TRIAL_DAYS: Optional[int] = None
CLOUD_FREE_TRIAL_DAYS: int | None = None
SELF_HOSTING_FREE_TRIAL_DAYS: int | None = None
SCIM_CONFIG: dict[str, SCIMConfigDict] = {
"zulip": {

View File

@@ -1,5 +1,4 @@
import os
from typing import Union
from django.conf import settings
from django.conf.urls import include
@@ -632,7 +631,7 @@ i18n_urls = [
]
# Make a copy of i18n_urls so that they appear without prefix for english
urls: list[Union[URLPattern, URLResolver]] = list(i18n_urls)
urls: list[URLPattern | URLResolver] = list(i18n_urls)
# Include the dual-use patterns twice
urls += [