2017-10-20 06:30:34 +02:00
|
|
|
import re
|
2021-03-23 10:34:55 +01:00
|
|
|
import urllib
|
2022-10-06 21:24:46 +02:00
|
|
|
from typing import Optional
|
2017-10-19 07:21:57 +02:00
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
from django.conf import settings
|
|
|
|
from django.http import HttpRequest
|
|
|
|
|
2021-06-05 02:38:54 +02:00
|
|
|
from zerver.lib.upload import get_public_upload_root_url
|
2019-05-04 04:47:44 +02:00
|
|
|
from zerver.models import Realm, UserProfile
|
2017-10-19 07:46:05 +02:00
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
|
2018-05-10 19:13:36 +02:00
|
|
|
def get_subdomain(request: HttpRequest) -> str:
|
2017-10-20 06:30:34 +02:00
|
|
|
# The HTTP spec allows, but doesn't require, a client to omit the
|
|
|
|
# port in the `Host` header if it's "the default port for the
|
|
|
|
# service requested", i.e. typically either 443 or 80; and
|
|
|
|
# whatever Django gets there, or from proxies reporting that via
|
|
|
|
# X-Forwarded-Host, it passes right through the same way. So our
|
|
|
|
# logic is a bit complicated to allow for that variation.
|
|
|
|
#
|
alias domains: Add a v1 of this feature.
The main limitation of this version is that it's controlled entirely
from settings, with nothing in the database and no web UI or even
management command to control it. That makes it a bit more of a
burden for the server admins than it'd ideally be, but that's fine
for now.
Relatedly, the web flow for realm creation still requires choosing a
subdomain even if the realm is destined to live at an alias domain.
Specific to the dev environment, there is an annoying quirk: the
special dev login flow doesn't work on a REALM_HOSTS realm. Also,
in this version the `add_new_realm` and `add_new_user` management
commands, which are intended for use in development environments only,
don't support this feature.
In manual testing, I've confirmed that a REALM_HOSTS realm works for
signup and login, with email/password, Google SSO, or GitHub SSO.
Most of that was in dev; I used zulipstaging.com to also test
* logging in with email and password;
* logging in with Google SSO... far enough to correctly determine
that my email address is associated with some other realm.
2017-10-20 06:36:50 +02:00
|
|
|
# For both EXTERNAL_HOST and REALM_HOSTS, we take a missing port
|
|
|
|
# to mean that any port should be accepted in Host. It's not
|
|
|
|
# totally clear that's the right behavior, but it keeps
|
|
|
|
# compatibility with older versions of Zulip, so that's a start.
|
2017-10-20 06:30:34 +02:00
|
|
|
|
2017-10-20 05:23:00 +02:00
|
|
|
host = request.get_host().lower()
|
2019-03-06 12:59:40 +01:00
|
|
|
return get_subdomain_from_hostname(host)
|
2017-10-20 06:30:34 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-03-06 12:59:40 +01:00
|
|
|
def get_subdomain_from_hostname(host: str) -> str:
|
2022-02-15 23:45:41 +01:00
|
|
|
m = re.search(rf"\.{settings.EXTERNAL_HOST}(:\d+)?$", host)
|
2017-10-20 06:30:34 +02:00
|
|
|
if m:
|
2021-02-12 08:19:30 +01:00
|
|
|
subdomain = host[: m.start()]
|
2017-10-20 06:30:34 +02:00
|
|
|
if subdomain in settings.ROOT_SUBDOMAIN_ALIASES:
|
|
|
|
return Realm.SUBDOMAIN_FOR_ROOT_DOMAIN
|
|
|
|
return subdomain
|
|
|
|
|
alias domains: Add a v1 of this feature.
The main limitation of this version is that it's controlled entirely
from settings, with nothing in the database and no web UI or even
management command to control it. That makes it a bit more of a
burden for the server admins than it'd ideally be, but that's fine
for now.
Relatedly, the web flow for realm creation still requires choosing a
subdomain even if the realm is destined to live at an alias domain.
Specific to the dev environment, there is an annoying quirk: the
special dev login flow doesn't work on a REALM_HOSTS realm. Also,
in this version the `add_new_realm` and `add_new_user` management
commands, which are intended for use in development environments only,
don't support this feature.
In manual testing, I've confirmed that a REALM_HOSTS realm works for
signup and login, with email/password, Google SSO, or GitHub SSO.
Most of that was in dev; I used zulipstaging.com to also test
* logging in with email and password;
* logging in with Google SSO... far enough to correctly determine
that my email address is associated with some other realm.
2017-10-20 06:36:50 +02:00
|
|
|
for subdomain, realm_host in settings.REALM_HOSTS.items():
|
2022-02-15 23:45:41 +01:00
|
|
|
if re.search(rf"^{realm_host}(:\d+)?$", host):
|
alias domains: Add a v1 of this feature.
The main limitation of this version is that it's controlled entirely
from settings, with nothing in the database and no web UI or even
management command to control it. That makes it a bit more of a
burden for the server admins than it'd ideally be, but that's fine
for now.
Relatedly, the web flow for realm creation still requires choosing a
subdomain even if the realm is destined to live at an alias domain.
Specific to the dev environment, there is an annoying quirk: the
special dev login flow doesn't work on a REALM_HOSTS realm. Also,
in this version the `add_new_realm` and `add_new_user` management
commands, which are intended for use in development environments only,
don't support this feature.
In manual testing, I've confirmed that a REALM_HOSTS realm works for
signup and login, with email/password, Google SSO, or GitHub SSO.
Most of that was in dev; I used zulipstaging.com to also test
* logging in with email and password;
* logging in with Google SSO... far enough to correctly determine
that my email address is associated with some other realm.
2017-10-20 06:36:50 +02:00
|
|
|
return subdomain
|
|
|
|
|
2017-10-20 06:30:34 +02:00
|
|
|
return Realm.SUBDOMAIN_FOR_ROOT_DOMAIN
|
2017-10-19 07:21:57 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def is_subdomain_root_or_alias(request: HttpRequest) -> bool:
|
2017-10-20 05:10:32 +02:00
|
|
|
return get_subdomain(request) == Realm.SUBDOMAIN_FOR_ROOT_DOMAIN
|
2017-10-19 07:21:57 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2022-09-14 07:45:28 +02:00
|
|
|
def user_matches_subdomain(realm_subdomain: str, user_profile: UserProfile) -> bool:
|
2017-10-20 02:54:57 +02:00
|
|
|
return user_profile.realm.subdomain == realm_subdomain
|
2017-10-20 02:53:24 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def is_root_domain_available() -> bool:
|
2017-10-19 07:42:03 +02:00
|
|
|
if settings.ROOT_DOMAIN_LANDING_PAGE:
|
|
|
|
return False
|
2019-05-04 04:47:44 +02:00
|
|
|
return not Realm.objects.filter(string_id=Realm.SUBDOMAIN_FOR_ROOT_DOMAIN).exists()
|
2021-03-23 10:34:55 +01:00
|
|
|
|
|
|
|
|
2022-10-06 21:24:46 +02:00
|
|
|
def is_static_or_current_realm_url(url: str, realm: Optional[Realm]) -> bool:
|
2021-03-23 10:34:55 +01:00
|
|
|
split_url = urllib.parse.urlsplit(url)
|
|
|
|
split_static_url = urllib.parse.urlsplit(settings.STATIC_URL)
|
|
|
|
|
|
|
|
# The netloc check here is important to correctness if STATIC_URL
|
|
|
|
# does not contain a `/`; see the tests for why.
|
|
|
|
if split_url.netloc == split_static_url.netloc and url.startswith(settings.STATIC_URL):
|
|
|
|
return True
|
|
|
|
|
|
|
|
# HTTPS access to this Zulip organization's domain; our existing
|
|
|
|
# HTTPS protects this request, and there's no privacy benefit to
|
|
|
|
# using camo in front of the Zulip server itself.
|
2022-10-06 21:24:46 +02:00
|
|
|
if (
|
|
|
|
realm is not None
|
|
|
|
and split_url.netloc == realm.host
|
|
|
|
and f"{split_url.scheme}://" == settings.EXTERNAL_URI_SCHEME
|
|
|
|
):
|
2021-03-23 10:34:55 +01:00
|
|
|
return True
|
|
|
|
|
|
|
|
# Relative URLs will be processed by the browser the same way as the above.
|
|
|
|
if split_url.netloc == "" and split_url.scheme == "":
|
|
|
|
return True
|
|
|
|
|
2021-06-05 02:38:54 +02:00
|
|
|
# S3 storage we control, if used, is also static and thus exempt
|
|
|
|
if settings.LOCAL_UPLOADS_DIR is None:
|
|
|
|
# The startswith check is correct here because the public
|
|
|
|
# upload base URL is guaranteed to end with /.
|
|
|
|
public_upload_root_url = get_public_upload_root_url()
|
|
|
|
assert public_upload_root_url.endswith("/")
|
|
|
|
if url.startswith(public_upload_root_url):
|
|
|
|
return True
|
|
|
|
|
2021-03-23 10:34:55 +01:00
|
|
|
return False
|