2020-06-11 00:54:34 +02:00
|
|
|
import logging
|
2024-07-12 02:30:25 +02:00
|
|
|
from collections.abc import Iterable
|
2023-01-18 05:25:49 +01:00
|
|
|
from contextlib import suppress
|
2024-07-14 19:39:20 +02:00
|
|
|
from typing import Annotated, Any
|
2022-10-27 19:08:41 +02:00
|
|
|
from urllib.parse import urlencode, urljoin
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2022-12-08 23:15:25 +01:00
|
|
|
import orjson
|
2017-01-07 21:46:03 +01:00
|
|
|
from django.conf import settings
|
2022-10-27 19:08:41 +02:00
|
|
|
from django.contrib.auth import REDIRECT_FIELD_NAME, authenticate, get_backends
|
2022-06-21 00:01:15 +02:00
|
|
|
from django.contrib.sessions.backends.base import SessionBase
|
2017-01-07 21:46:03 +01:00
|
|
|
from django.core import validators
|
2020-06-11 00:54:34 +02:00
|
|
|
from django.core.exceptions import ValidationError
|
2024-09-19 20:23:19 +02:00
|
|
|
from django.db import transaction
|
2020-08-01 15:25:54 +02:00
|
|
|
from django.db.models import Q
|
2023-10-06 22:39:02 +02:00
|
|
|
from django.db.utils import IntegrityError
|
2020-06-11 00:54:34 +02:00
|
|
|
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
|
|
|
from django.shortcuts import redirect, render
|
2022-12-08 11:08:55 +01:00
|
|
|
from django.template.response import TemplateResponse
|
2020-06-11 00:54:34 +02:00
|
|
|
from django.urls import reverse
|
2022-05-30 06:18:57 +02:00
|
|
|
from django.utils.translation import get_language
|
2023-10-06 18:03:58 +02:00
|
|
|
from django.views.defaults import server_error
|
2017-01-07 21:46:03 +01:00
|
|
|
from django_auth_ldap.backend import LDAPBackend, _LDAPUser
|
2024-07-14 19:39:20 +02:00
|
|
|
from pydantic import Json, NonNegativeInt, StringConstraints
|
2020-06-11 00:54:34 +02:00
|
|
|
|
|
|
|
from confirmation.models import (
|
|
|
|
Confirmation,
|
2022-11-17 09:30:48 +01:00
|
|
|
ConfirmationKeyError,
|
2020-06-11 00:54:34 +02:00
|
|
|
RealmCreationKey,
|
|
|
|
create_confirmation_link,
|
|
|
|
get_object_from_key,
|
|
|
|
render_confirmation_key_error,
|
|
|
|
validate_key,
|
|
|
|
)
|
2022-04-14 23:58:15 +02:00
|
|
|
from zerver.actions.create_realm import do_create_realm
|
2022-04-14 23:53:15 +02:00
|
|
|
from zerver.actions.create_user import do_activate_mirror_dummy_user, do_create_user
|
2022-04-14 23:34:23 +02:00
|
|
|
from zerver.actions.default_streams import lookup_default_stream_groups
|
2022-04-14 23:49:26 +02:00
|
|
|
from zerver.actions.user_settings import (
|
|
|
|
do_change_full_name,
|
|
|
|
do_change_password,
|
|
|
|
do_change_user_setting,
|
|
|
|
)
|
2024-09-19 20:23:19 +02:00
|
|
|
from zerver.actions.users import do_change_user_role
|
2023-03-01 10:47:38 +01:00
|
|
|
from zerver.context_processors import (
|
|
|
|
get_realm_create_form_context,
|
|
|
|
get_realm_from_request,
|
|
|
|
login_context,
|
|
|
|
)
|
2022-12-08 11:08:55 +01:00
|
|
|
from zerver.decorator import add_google_analytics, do_login, require_post
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.forms import (
|
|
|
|
FindMyTeamForm,
|
|
|
|
HomepageForm,
|
|
|
|
RealmCreationForm,
|
|
|
|
RealmRedirectForm,
|
|
|
|
RegistrationForm,
|
|
|
|
)
|
|
|
|
from zerver.lib.email_validation import email_allowed_for_realm, validate_email_not_already_in_realm
|
2022-11-17 09:30:48 +01:00
|
|
|
from zerver.lib.exceptions import RateLimitedError
|
2023-09-12 21:58:58 +02:00
|
|
|
from zerver.lib.i18n import (
|
|
|
|
get_browser_language_code,
|
|
|
|
get_default_language_for_new_user,
|
|
|
|
get_language_name,
|
|
|
|
)
|
2019-12-20 00:00:45 +01:00
|
|
|
from zerver.lib.pysa import mark_sanitized
|
2022-08-05 17:40:03 +02:00
|
|
|
from zerver.lib.rate_limiter import rate_limit_request_by_ip
|
2022-11-17 09:30:48 +01:00
|
|
|
from zerver.lib.send_email import EmailNotDeliveredError, FromAddress, send_email
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.sessions import get_expirable_session_var
|
2023-03-17 18:49:51 +01:00
|
|
|
from zerver.lib.subdomains import get_subdomain
|
2024-07-14 19:39:20 +02:00
|
|
|
from zerver.lib.typed_endpoint import (
|
|
|
|
ApiParamConfig,
|
|
|
|
PathOnly,
|
|
|
|
typed_endpoint,
|
|
|
|
typed_endpoint_without_parameters,
|
|
|
|
)
|
|
|
|
from zerver.lib.typed_endpoint_validators import (
|
|
|
|
check_int_in_validator,
|
|
|
|
non_negative_int_or_none_validator,
|
|
|
|
timezone_or_empty_validator,
|
|
|
|
)
|
2021-10-14 01:45:34 +02:00
|
|
|
from zerver.lib.url_encoding import append_url_query_string
|
2018-06-19 10:55:56 +02:00
|
|
|
from zerver.lib.users import get_accounts_for_email
|
2018-08-11 16:26:46 +02:00
|
|
|
from zerver.lib.zephyr import compute_mit_user_fullname
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.models import (
|
|
|
|
MultiuseInvite,
|
2023-03-03 14:57:40 +01:00
|
|
|
PreregistrationRealm,
|
2021-12-02 16:16:22 +01:00
|
|
|
PreregistrationUser,
|
2020-06-11 00:54:34 +02:00
|
|
|
Realm,
|
2023-02-07 15:40:46 +01:00
|
|
|
RealmUserDefault,
|
2020-06-11 00:54:34 +02:00
|
|
|
Stream,
|
|
|
|
UserProfile,
|
2023-12-15 02:14:24 +01:00
|
|
|
)
|
|
|
|
from zerver.models.constants import MAX_LANGUAGE_ID_LENGTH
|
2024-03-26 06:14:16 +01:00
|
|
|
from zerver.models.realm_audit_logs import RealmAuditLog
|
2023-12-15 02:14:24 +01:00
|
|
|
from zerver.models.realms import (
|
|
|
|
DisposableEmailError,
|
|
|
|
DomainNotAllowedForRealmError,
|
|
|
|
EmailContainsPlusError,
|
2023-03-09 12:30:03 +01:00
|
|
|
get_org_type_display_name,
|
2020-06-11 00:54:34 +02:00
|
|
|
get_realm,
|
|
|
|
name_changes_disabled,
|
|
|
|
)
|
2023-12-15 03:57:04 +01:00
|
|
|
from zerver.models.streams import get_default_stream_groups
|
2023-12-15 01:16:00 +01:00
|
|
|
from zerver.models.users import get_source_profile, get_user_by_delivery_email
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.views.auth import (
|
2023-03-03 11:58:00 +01:00
|
|
|
create_preregistration_realm,
|
2020-06-11 00:54:34 +02:00
|
|
|
create_preregistration_user,
|
|
|
|
finish_desktop_flow,
|
|
|
|
finish_mobile_flow,
|
|
|
|
redirect_and_log_into_subdomain,
|
|
|
|
redirect_to_deactivation_notice,
|
|
|
|
)
|
2023-10-06 17:58:18 +02:00
|
|
|
from zerver.views.errors import config_error
|
2020-06-11 00:54:34 +02:00
|
|
|
from zproject.backends import (
|
|
|
|
ExternalAuthResult,
|
2022-11-17 09:30:48 +01:00
|
|
|
NoMatchingLDAPUserError,
|
2020-06-11 00:54:34 +02:00
|
|
|
ZulipLDAPAuthBackend,
|
|
|
|
email_auth_enabled,
|
|
|
|
email_belongs_to_ldap,
|
2021-07-18 23:39:37 +02:00
|
|
|
get_external_method_dicts,
|
2020-06-11 00:54:34 +02:00
|
|
|
ldap_auth_enabled,
|
|
|
|
password_auth_enabled,
|
|
|
|
)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
|
|
|
|
2024-07-14 19:39:20 +02:00
|
|
|
@typed_endpoint
|
2021-11-29 16:20:59 +01:00
|
|
|
def get_prereg_key_and_redirect(
|
2024-07-14 19:39:20 +02:00
|
|
|
request: HttpRequest, *, confirmation_key: PathOnly[str], full_name: str | None = None
|
2021-07-29 18:44:18 +02:00
|
|
|
) -> HttpResponse:
|
2021-12-02 18:37:16 +01:00
|
|
|
"""
|
|
|
|
The purpose of this little endpoint is primarily to take a GET
|
|
|
|
request to a long URL containing a confirmation key, and render
|
|
|
|
a page that will via JavaScript immediately do a POST request to
|
|
|
|
/accounts/register, so that the user can create their account on
|
|
|
|
a page with a cleaner URL (and with the browser security and UX
|
|
|
|
benefits of an HTTP POST having generated the page).
|
|
|
|
|
|
|
|
The only thing it does before rendering that page is to check
|
|
|
|
the validity of the confirmation link. This is redundant with a
|
|
|
|
similar check in accounts_register, but it provides a slightly nicer
|
|
|
|
user-facing error handling experience if the URL you visited is
|
|
|
|
displayed in the browser. (E.g. you can debug that you
|
|
|
|
accidentally adding an extra character after pasting).
|
|
|
|
"""
|
2021-12-02 18:11:31 +01:00
|
|
|
try:
|
2023-03-03 14:57:40 +01:00
|
|
|
prereg_object, realm_creation = check_prereg_key(request, confirmation_key)
|
2022-11-17 09:30:48 +01:00
|
|
|
except ConfirmationKeyError as e:
|
2021-12-02 18:11:31 +01:00
|
|
|
return render_confirmation_key_error(request, e)
|
|
|
|
|
2023-02-24 09:08:26 +01:00
|
|
|
registration_url = reverse("accounts_register")
|
|
|
|
if realm_creation:
|
|
|
|
registration_url = reverse("realm_register")
|
|
|
|
|
2021-11-29 16:20:59 +01:00
|
|
|
return render(
|
|
|
|
request,
|
|
|
|
"confirmation/confirm_preregistrationuser.html",
|
2023-02-24 09:08:26 +01:00
|
|
|
context={
|
|
|
|
"key": confirmation_key,
|
|
|
|
"full_name": full_name,
|
|
|
|
"registration_url": registration_url,
|
|
|
|
},
|
2021-11-29 16:20:59 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-03-03 14:57:40 +01:00
|
|
|
def check_prereg_key(
|
|
|
|
request: HttpRequest, confirmation_key: str
|
2024-07-12 02:30:23 +02:00
|
|
|
) -> tuple[PreregistrationUser | PreregistrationRealm, bool]:
|
2021-11-29 16:20:59 +01:00
|
|
|
"""
|
2023-03-03 14:57:40 +01:00
|
|
|
Checks if the Confirmation key is valid, returning the PreregistrationUser or
|
|
|
|
PreregistrationRealm object in case of success and raising an appropriate
|
|
|
|
ConfirmationKeyError otherwise.
|
2021-11-29 16:20:59 +01:00
|
|
|
"""
|
2021-12-02 16:34:05 +01:00
|
|
|
confirmation_types = [
|
2021-02-12 08:19:30 +01:00
|
|
|
Confirmation.USER_REGISTRATION,
|
|
|
|
Confirmation.INVITATION,
|
|
|
|
Confirmation.REALM_CREATION,
|
2021-12-02 16:34:05 +01:00
|
|
|
]
|
2020-05-01 01:52:45 +02:00
|
|
|
|
2023-03-03 14:54:14 +01:00
|
|
|
prereg_object = get_object_from_key(
|
|
|
|
confirmation_key, confirmation_types, mark_object_used=False
|
|
|
|
)
|
2024-07-12 02:30:27 +02:00
|
|
|
assert isinstance(prereg_object, PreregistrationRealm | PreregistrationUser)
|
2017-11-30 05:07:18 +01:00
|
|
|
|
2023-03-03 14:57:40 +01:00
|
|
|
confirmation_obj = prereg_object.confirmation.get()
|
|
|
|
realm_creation = confirmation_obj.type == Confirmation.REALM_CREATION
|
2022-05-06 21:02:24 +02:00
|
|
|
|
2023-03-03 14:57:40 +01:00
|
|
|
if realm_creation:
|
|
|
|
assert isinstance(prereg_object, PreregistrationRealm)
|
2023-10-09 20:41:12 +02:00
|
|
|
# Defensive assert to make sure no mix-up in how .status is set leading to reuse
|
2023-03-03 14:57:40 +01:00
|
|
|
# of a PreregistrationRealm object.
|
|
|
|
assert prereg_object.created_realm is None
|
|
|
|
else:
|
|
|
|
assert isinstance(prereg_object, PreregistrationUser)
|
2023-10-09 20:41:12 +02:00
|
|
|
# Defensive assert to make sure no mix-up in how .status is set leading to reuse
|
2023-03-03 14:57:40 +01:00
|
|
|
# of a PreregistrationUser object.
|
|
|
|
assert prereg_object.created_user is None
|
|
|
|
|
|
|
|
return prereg_object, realm_creation
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-11-30 05:07:18 +01:00
|
|
|
|
2024-07-12 02:30:23 +02:00
|
|
|
def get_selected_realm_type_name(prereg_realm: PreregistrationRealm | None) -> str | None:
|
2023-03-09 12:30:03 +01:00
|
|
|
if prereg_realm is None:
|
|
|
|
# We show the selected realm type only when creating new realm.
|
|
|
|
return None
|
|
|
|
|
|
|
|
return get_org_type_display_name(prereg_realm.org_type)
|
|
|
|
|
|
|
|
|
2023-09-12 21:58:58 +02:00
|
|
|
def get_selected_realm_default_language_name(
|
2024-07-12 02:30:23 +02:00
|
|
|
prereg_realm: PreregistrationRealm | None,
|
|
|
|
) -> str | None:
|
2023-09-12 21:58:58 +02:00
|
|
|
if prereg_realm is None:
|
|
|
|
# We show the selected realm language only when creating new realm.
|
|
|
|
return None
|
|
|
|
|
|
|
|
return get_language_name(prereg_realm.default_language)
|
|
|
|
|
|
|
|
|
2023-02-01 08:05:01 +01:00
|
|
|
@add_google_analytics
|
2017-01-07 21:46:03 +01:00
|
|
|
@require_post
|
2023-02-24 09:08:26 +01:00
|
|
|
def realm_register(*args: Any, **kwargs: Any) -> HttpResponse:
|
|
|
|
return registration_helper(*args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
@require_post
|
|
|
|
def accounts_register(*args: Any, **kwargs: Any) -> HttpResponse:
|
|
|
|
return registration_helper(*args, **kwargs)
|
|
|
|
|
|
|
|
|
2024-07-14 19:39:20 +02:00
|
|
|
@typed_endpoint
|
2023-02-24 09:08:26 +01:00
|
|
|
def registration_helper(
|
2021-07-29 18:44:18 +02:00
|
|
|
request: HttpRequest,
|
2024-07-14 19:39:20 +02:00
|
|
|
*,
|
|
|
|
key: str = "",
|
|
|
|
timezone: Annotated[str, timezone_or_empty_validator()] = "",
|
|
|
|
from_confirmation: str | None = None,
|
|
|
|
form_full_name: Annotated[str | None, ApiParamConfig("full_name")] = None,
|
|
|
|
source_realm_id: Annotated[NonNegativeInt | None, non_negative_int_or_none_validator()] = None,
|
|
|
|
form_is_demo_organization: Annotated[str | None, ApiParamConfig("is_demo_organization")] = None,
|
2021-07-29 18:44:18 +02:00
|
|
|
) -> HttpResponse:
|
2021-12-02 18:11:31 +01:00
|
|
|
try:
|
2023-03-03 14:57:40 +01:00
|
|
|
prereg_object, realm_creation = check_prereg_key(request, key)
|
2022-11-17 09:30:48 +01:00
|
|
|
except ConfirmationKeyError as e:
|
2021-12-02 18:11:31 +01:00
|
|
|
return render_confirmation_key_error(request, e)
|
2020-05-02 00:23:19 +02:00
|
|
|
|
2023-03-03 14:57:40 +01:00
|
|
|
email = prereg_object.email
|
|
|
|
prereg_realm = None
|
|
|
|
prereg_user = None
|
users: Remove redundant get_role_for_new_user in lib/create_user.py.
The function get_role_for_new_user was added to get role from the
invited_as value, as invited_as values were one of (1,2,3,4)
previously, but it was then changed to be the actual role value,
i.e. one of (100, 200, 400, 600), in 1f8f227444.
So, we can safely remove this function now and use invited_as value
directly and handle realm_creation case by using an if condition.
2021-04-30 14:37:48 +02:00
|
|
|
if realm_creation:
|
2023-03-03 14:57:40 +01:00
|
|
|
assert isinstance(prereg_object, PreregistrationRealm)
|
|
|
|
prereg_realm = prereg_object
|
|
|
|
password_required = True
|
users: Remove redundant get_role_for_new_user in lib/create_user.py.
The function get_role_for_new_user was added to get role from the
invited_as value, as invited_as values were one of (1,2,3,4)
previously, but it was then changed to be the actual role value,
i.e. one of (100, 200, 400, 600), in 1f8f227444.
So, we can safely remove this function now and use invited_as value
directly and handle realm_creation case by using an if condition.
2021-04-30 14:37:48 +02:00
|
|
|
role = UserProfile.ROLE_REALM_OWNER
|
2023-03-03 14:57:40 +01:00
|
|
|
else:
|
|
|
|
assert isinstance(prereg_object, PreregistrationUser)
|
|
|
|
prereg_user = prereg_object
|
|
|
|
password_required = prereg_object.password_required
|
|
|
|
role = prereg_object.invited_as
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2021-12-23 01:41:41 +01:00
|
|
|
if form_is_demo_organization is None:
|
|
|
|
demo_organization_creation = False
|
|
|
|
else:
|
|
|
|
# Check the explicit strings that return false
|
|
|
|
# in django.forms.BooleanField.to_python.
|
|
|
|
false_strings = ("false", "0")
|
|
|
|
demo_organization_creation = form_is_demo_organization.strip().lower() not in false_strings
|
|
|
|
|
|
|
|
if email == "":
|
|
|
|
# Do not attempt to validate email for users without an email address.
|
|
|
|
# The assertions here are to help document the only circumstance under which
|
|
|
|
# this condition should be possible.
|
|
|
|
assert realm_creation and demo_organization_creation
|
|
|
|
# TODO: Remove settings.DEVELOPMENT when demo organization feature ready
|
|
|
|
# to be fully implemented.
|
|
|
|
assert settings.DEVELOPMENT
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
validators.validate_email(email)
|
|
|
|
except ValidationError:
|
|
|
|
return TemplateResponse(
|
2023-12-08 19:13:20 +01:00
|
|
|
request,
|
|
|
|
"zerver/invalid_email.html",
|
|
|
|
context={"invalid_email": True},
|
|
|
|
status=400,
|
2021-12-23 01:41:41 +01:00
|
|
|
)
|
2018-04-06 19:33:02 +02:00
|
|
|
|
2017-11-08 22:02:59 +01:00
|
|
|
if realm_creation:
|
2017-01-07 21:46:03 +01:00
|
|
|
# For creating a new realm, there is no existing realm or domain
|
|
|
|
realm = None
|
|
|
|
else:
|
2023-03-03 14:57:40 +01:00
|
|
|
assert prereg_user is not None
|
2021-12-02 16:16:22 +01:00
|
|
|
assert prereg_user.realm is not None
|
2019-05-04 04:47:44 +02:00
|
|
|
if get_subdomain(request) != prereg_user.realm.string_id:
|
2017-12-02 01:07:52 +01:00
|
|
|
return render_confirmation_key_error(
|
2022-11-17 09:30:48 +01:00
|
|
|
request, ConfirmationKeyError(ConfirmationKeyError.DOES_NOT_EXIST)
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2019-05-04 04:47:44 +02:00
|
|
|
realm = prereg_user.realm
|
2018-03-14 12:54:05 +01:00
|
|
|
try:
|
|
|
|
email_allowed_for_realm(email, realm)
|
|
|
|
except DomainNotAllowedForRealmError:
|
2023-02-01 08:05:01 +01:00
|
|
|
return TemplateResponse(
|
2021-02-12 08:19:30 +01:00
|
|
|
request,
|
|
|
|
"zerver/invalid_email.html",
|
|
|
|
context={"realm_name": realm.name, "closed_domain": True},
|
2023-12-08 19:13:20 +01:00
|
|
|
status=400,
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2018-03-14 13:25:26 +01:00
|
|
|
except DisposableEmailError:
|
2023-02-01 08:05:01 +01:00
|
|
|
return TemplateResponse(
|
2021-02-12 08:19:30 +01:00
|
|
|
request,
|
|
|
|
"zerver/invalid_email.html",
|
|
|
|
context={"realm_name": realm.name, "disposable_emails_not_allowed": True},
|
2023-12-08 19:13:20 +01:00
|
|
|
status=400,
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2018-06-20 13:08:07 +02:00
|
|
|
except EmailContainsPlusError:
|
2023-02-01 08:05:01 +01:00
|
|
|
return TemplateResponse(
|
2021-02-12 08:19:30 +01:00
|
|
|
request,
|
|
|
|
"zerver/invalid_email.html",
|
|
|
|
context={"realm_name": realm.name, "email_contains_plus": True},
|
2023-12-08 19:13:20 +01:00
|
|
|
status=400,
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2017-12-02 01:13:17 +01:00
|
|
|
if realm.deactivated:
|
|
|
|
# The user is trying to register for a deactivated realm. Advise them to
|
|
|
|
# contact support.
|
|
|
|
return redirect_to_deactivation_notice()
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2017-11-22 20:22:11 +01:00
|
|
|
try:
|
2020-03-02 13:24:50 +01:00
|
|
|
validate_email_not_already_in_realm(realm, email)
|
2019-11-03 23:02:37 +01:00
|
|
|
except ValidationError:
|
2021-01-07 20:18:45 +01:00
|
|
|
return redirect_to_email_login_url(email)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2021-05-28 15:57:08 +02:00
|
|
|
if settings.BILLING_ENABLED:
|
2024-09-24 23:27:28 +02:00
|
|
|
from corporate.lib.registration import (
|
|
|
|
check_spare_licenses_available_for_registering_new_user,
|
|
|
|
)
|
|
|
|
from corporate.lib.stripe import LicenseLimitError
|
|
|
|
|
2021-05-28 15:57:08 +02:00
|
|
|
try:
|
2022-08-14 19:42:55 +02:00
|
|
|
check_spare_licenses_available_for_registering_new_user(realm, email, role=role)
|
2021-05-28 15:57:08 +02:00
|
|
|
except LicenseLimitError:
|
2023-02-01 08:05:01 +01:00
|
|
|
return TemplateResponse(request, "zerver/no_spare_licenses.html")
|
2021-05-28 15:57:08 +02:00
|
|
|
|
2017-01-07 21:46:03 +01:00
|
|
|
name_validated = False
|
2019-01-16 09:56:17 +01:00
|
|
|
require_ldap_password = False
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2021-07-29 18:44:18 +02:00
|
|
|
if from_confirmation:
|
2023-01-18 05:25:49 +01:00
|
|
|
with suppress(KeyError):
|
2021-02-12 08:20:45 +01:00
|
|
|
del request.session["authenticated_full_name"]
|
2019-11-09 07:01:05 +01:00
|
|
|
|
|
|
|
ldap_full_name = None
|
|
|
|
if settings.POPULATE_PROFILE_VIA_LDAP:
|
|
|
|
# If the user can be found in LDAP, we'll take the full name from the directory,
|
|
|
|
# and further down create a form pre-filled with it.
|
2017-01-07 21:46:03 +01:00
|
|
|
for backend in get_backends():
|
|
|
|
if isinstance(backend, LDAPBackend):
|
auth: Improve interactions between LDAPAuthBackend and EmailAuthBackend.
Previously, if you had LDAPAuthBackend enabled, we basically blocked
any other auth backends from working at all, by requiring the user's
login flow include verifying the user's LDAP password.
We still want to enforce that in the case that the account email
matches LDAP_APPEND_DOMAIN, but there's a reasonable corner case:
Having effectively guest users from outside the LDAP domain.
We don't want to allow creating a Zulip-level password for a user
inside the LDAP domain, so we still verify the LDAP password in that
flow, but if the email is allowed to register (due to invite or
whatever) but is outside the LDAP domain for the organization, we
allow it to create an account and set a password.
For the moment, this solution only covers EmailAuthBackend. It's
likely that just extending the list of other backends we check for in
the new conditional on `email_auth_backend` would be correct, but we
haven't done any testing for those cases, and with auth code paths,
it's better to disallow than allow untested code paths.
Fixes #9422.
2018-05-29 06:52:06 +02:00
|
|
|
try:
|
2018-05-31 23:04:47 +02:00
|
|
|
ldap_username = backend.django_to_ldap_username(email)
|
2022-11-17 09:30:48 +01:00
|
|
|
except NoMatchingLDAPUserError:
|
2020-05-02 08:44:14 +02:00
|
|
|
logging.warning("New account email %s could not be found in LDAP", email)
|
auth: Improve interactions between LDAPAuthBackend and EmailAuthBackend.
Previously, if you had LDAPAuthBackend enabled, we basically blocked
any other auth backends from working at all, by requiring the user's
login flow include verifying the user's LDAP password.
We still want to enforce that in the case that the account email
matches LDAP_APPEND_DOMAIN, but there's a reasonable corner case:
Having effectively guest users from outside the LDAP domain.
We don't want to allow creating a Zulip-level password for a user
inside the LDAP domain, so we still verify the LDAP password in that
flow, but if the email is allowed to register (due to invite or
whatever) but is outside the LDAP domain for the organization, we
allow it to create an account and set a password.
For the moment, this solution only covers EmailAuthBackend. It's
likely that just extending the list of other backends we check for in
the new conditional on `email_auth_backend` would be correct, but we
haven't done any testing for those cases, and with auth code paths,
it's better to disallow than allow untested code paths.
Fixes #9422.
2018-05-29 06:52:06 +02:00
|
|
|
break
|
|
|
|
|
2019-11-09 00:27:18 +01:00
|
|
|
# Note that this `ldap_user` object is not a
|
|
|
|
# `ZulipLDAPUser` with a `Realm` attached, so
|
|
|
|
# calling `.populate_user()` on it will crash.
|
|
|
|
# This is OK, since we're just accessing this user
|
|
|
|
# to extract its name.
|
|
|
|
#
|
|
|
|
# TODO: We should potentially be accessing this
|
|
|
|
# user to sync its initial avatar and custom
|
|
|
|
# profile fields as well, if we indeed end up
|
|
|
|
# creating a user account through this flow,
|
|
|
|
# rather than waiting until `manage.py
|
|
|
|
# sync_ldap_user_data` runs to populate it.
|
2019-01-16 09:08:52 +01:00
|
|
|
ldap_user = _LDAPUser(backend, ldap_username)
|
2018-05-31 23:04:47 +02:00
|
|
|
|
2017-01-07 21:46:03 +01:00
|
|
|
try:
|
2020-07-17 20:22:10 +02:00
|
|
|
ldap_full_name = backend.get_mapped_name(ldap_user)
|
2017-01-07 21:46:03 +01:00
|
|
|
except TypeError:
|
2019-11-09 07:01:05 +01:00
|
|
|
break
|
|
|
|
|
|
|
|
# Check whether this is ZulipLDAPAuthBackend,
|
|
|
|
# which is responsible for authentication and
|
|
|
|
# requires that LDAP accounts enter their LDAP
|
|
|
|
# password to register, or ZulipLDAPUserPopulator,
|
|
|
|
# which just populates UserProfile fields (no auth).
|
|
|
|
require_ldap_password = isinstance(backend, ZulipLDAPAuthBackend)
|
|
|
|
break
|
|
|
|
|
2023-03-03 14:57:40 +01:00
|
|
|
initial_data = {}
|
|
|
|
if realm_creation:
|
|
|
|
assert prereg_realm is not None
|
|
|
|
initial_data = {
|
|
|
|
"realm_name": prereg_realm.name,
|
|
|
|
"realm_type": prereg_realm.org_type,
|
2023-09-12 21:58:58 +02:00
|
|
|
"realm_default_language": prereg_realm.default_language,
|
2023-03-03 14:57:40 +01:00
|
|
|
"realm_subdomain": prereg_realm.string_id,
|
|
|
|
}
|
|
|
|
|
2019-11-09 07:01:05 +01:00
|
|
|
if ldap_full_name:
|
2023-03-03 14:57:40 +01:00
|
|
|
# We don't add "full_name" to initial here, because if the realm
|
|
|
|
# already exists and form is complete (that is, no additional fields
|
|
|
|
# need to be filled out by the user) we want the form to validate,
|
|
|
|
# so they can be directly registered without having to go through
|
|
|
|
# this interstitial.
|
|
|
|
form = RegistrationForm(
|
2024-03-11 20:02:05 +01:00
|
|
|
{"full_name": ldap_full_name},
|
|
|
|
initial=initial_data,
|
|
|
|
realm_creation=realm_creation,
|
|
|
|
realm=realm,
|
2023-03-03 14:57:40 +01:00
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
request.session["authenticated_full_name"] = ldap_full_name
|
2019-11-09 07:01:05 +01:00
|
|
|
name_validated = True
|
|
|
|
elif realm is not None and realm.is_zephyr_mirror_realm:
|
|
|
|
# For MIT users, we can get an authoritative name from Hesiod.
|
|
|
|
# Technically we should check that this is actually an MIT
|
|
|
|
# realm, but we can cross that bridge if we ever get a non-MIT
|
|
|
|
# zephyr mirroring realm.
|
|
|
|
hesiod_name = compute_mit_user_fullname(email)
|
|
|
|
form = RegistrationForm(
|
2021-02-12 08:20:45 +01:00
|
|
|
initial={"full_name": hesiod_name if "@" not in hesiod_name else ""},
|
2021-02-12 08:19:30 +01:00
|
|
|
realm_creation=realm_creation,
|
2024-03-11 20:02:05 +01:00
|
|
|
realm=realm,
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2019-11-09 07:01:05 +01:00
|
|
|
name_validated = True
|
2023-03-03 14:57:40 +01:00
|
|
|
elif prereg_user is not None and prereg_user.full_name:
|
2019-11-01 00:00:36 +01:00
|
|
|
if prereg_user.full_name_validated:
|
2021-02-12 08:20:45 +01:00
|
|
|
request.session["authenticated_full_name"] = prereg_user.full_name
|
2019-11-01 00:00:36 +01:00
|
|
|
name_validated = True
|
2021-02-12 08:19:30 +01:00
|
|
|
form = RegistrationForm(
|
2023-03-03 14:57:40 +01:00
|
|
|
{"full_name": prereg_user.full_name},
|
|
|
|
initial=initial_data,
|
|
|
|
realm_creation=realm_creation,
|
2024-03-11 20:02:05 +01:00
|
|
|
realm=realm,
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2019-11-01 00:00:36 +01:00
|
|
|
else:
|
2023-03-03 14:57:40 +01:00
|
|
|
initial_data["full_name"] = prereg_user.full_name
|
2021-02-12 08:19:30 +01:00
|
|
|
form = RegistrationForm(
|
2023-03-03 14:57:40 +01:00
|
|
|
initial=initial_data,
|
|
|
|
realm_creation=realm_creation,
|
2024-03-11 20:02:05 +01:00
|
|
|
realm=realm,
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2021-07-29 18:44:18 +02:00
|
|
|
elif form_full_name is not None:
|
2023-03-03 14:57:40 +01:00
|
|
|
initial_data["full_name"] = form_full_name
|
2017-01-07 21:46:03 +01:00
|
|
|
form = RegistrationForm(
|
2023-03-03 14:57:40 +01:00
|
|
|
initial=initial_data,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
realm_creation=realm_creation,
|
2024-03-11 20:02:05 +01:00
|
|
|
realm=realm,
|
2017-01-07 21:46:03 +01:00
|
|
|
)
|
|
|
|
else:
|
2024-03-11 20:02:05 +01:00
|
|
|
form = RegistrationForm(
|
|
|
|
initial=initial_data, realm_creation=realm_creation, realm=realm
|
|
|
|
)
|
2017-01-07 21:46:03 +01:00
|
|
|
else:
|
|
|
|
postdata = request.POST.copy()
|
|
|
|
if name_changes_disabled(realm):
|
|
|
|
# If we populate profile information via LDAP and we have a
|
|
|
|
# verified name from you on file, use that. Otherwise, fall
|
|
|
|
# back to the full name in the request.
|
|
|
|
try:
|
2021-02-12 08:20:45 +01:00
|
|
|
postdata.update(full_name=request.session["authenticated_full_name"])
|
2017-01-07 21:46:03 +01:00
|
|
|
name_validated = True
|
|
|
|
except KeyError:
|
|
|
|
pass
|
2024-03-11 20:02:05 +01:00
|
|
|
form = RegistrationForm(postdata, realm_creation=realm_creation, realm=realm)
|
2019-10-30 20:06:46 +01:00
|
|
|
|
|
|
|
if not (password_auth_enabled(realm) and password_required):
|
2021-02-12 08:20:45 +01:00
|
|
|
form["password"].field.required = False
|
2017-01-07 21:46:03 +01:00
|
|
|
|
|
|
|
if form.is_valid():
|
2021-02-12 08:20:45 +01:00
|
|
|
if password_auth_enabled(realm) and form["password"].field.required:
|
|
|
|
password = form.cleaned_data["password"]
|
2017-01-07 21:46:03 +01:00
|
|
|
else:
|
2019-11-18 07:57:36 +01:00
|
|
|
# If the user wasn't prompted for a password when
|
|
|
|
# completing the authentication form (because they're
|
|
|
|
# signing up with SSO and no password is required), set
|
|
|
|
# the password field to `None` (Which causes Django to
|
|
|
|
# create an unusable password).
|
2017-01-07 21:46:03 +01:00
|
|
|
password = None
|
|
|
|
|
|
|
|
if realm_creation:
|
2021-02-12 08:20:45 +01:00
|
|
|
string_id = form.cleaned_data["realm_subdomain"]
|
|
|
|
realm_name = form.cleaned_data["realm_name"]
|
2021-06-24 20:05:06 +02:00
|
|
|
realm_type = form.cleaned_data["realm_type"]
|
2023-09-12 21:58:58 +02:00
|
|
|
realm_default_language = form.cleaned_data["realm_default_language"]
|
2021-12-23 01:41:41 +01:00
|
|
|
is_demo_organization = form.cleaned_data["is_demo_organization"]
|
2024-03-26 06:14:16 +01:00
|
|
|
how_realm_creator_found_zulip = RealmAuditLog.HOW_REALM_CREATOR_FOUND_ZULIP_OPTIONS[
|
|
|
|
form.cleaned_data["how_realm_creator_found_zulip"]
|
|
|
|
]
|
|
|
|
how_realm_creator_found_zulip_extra_context = ""
|
|
|
|
if (
|
|
|
|
how_realm_creator_found_zulip
|
|
|
|
== RealmAuditLog.HOW_REALM_CREATOR_FOUND_ZULIP_OPTIONS["other"]
|
|
|
|
):
|
|
|
|
how_realm_creator_found_zulip_extra_context = form.cleaned_data[
|
|
|
|
"how_realm_creator_found_zulip_other_text"
|
|
|
|
]
|
|
|
|
elif (
|
|
|
|
how_realm_creator_found_zulip
|
|
|
|
== RealmAuditLog.HOW_REALM_CREATOR_FOUND_ZULIP_OPTIONS["ad"]
|
|
|
|
):
|
|
|
|
how_realm_creator_found_zulip_extra_context = form.cleaned_data[
|
|
|
|
"how_realm_creator_found_zulip_where_ad"
|
|
|
|
]
|
|
|
|
elif (
|
|
|
|
how_realm_creator_found_zulip
|
|
|
|
== RealmAuditLog.HOW_REALM_CREATOR_FOUND_ZULIP_OPTIONS["existing_user"]
|
|
|
|
):
|
|
|
|
how_realm_creator_found_zulip_extra_context = form.cleaned_data[
|
|
|
|
"how_realm_creator_found_zulip_which_organization"
|
|
|
|
]
|
2024-09-27 07:47:59 +02:00
|
|
|
elif (
|
|
|
|
how_realm_creator_found_zulip
|
|
|
|
== RealmAuditLog.HOW_REALM_CREATOR_FOUND_ZULIP_OPTIONS["review_site"]
|
|
|
|
): # nocoverage
|
|
|
|
how_realm_creator_found_zulip_extra_context = form.cleaned_data[
|
|
|
|
"how_realm_creator_found_zulip_review_site"
|
|
|
|
]
|
2024-03-26 06:14:16 +01:00
|
|
|
|
2021-08-13 20:37:15 +02:00
|
|
|
realm = do_create_realm(
|
2023-03-10 11:42:18 +01:00
|
|
|
string_id,
|
|
|
|
realm_name,
|
|
|
|
org_type=realm_type,
|
2023-09-12 21:58:58 +02:00
|
|
|
default_language=realm_default_language,
|
2021-12-23 01:41:41 +01:00
|
|
|
is_demo_organization=is_demo_organization,
|
2023-03-10 11:42:18 +01:00
|
|
|
prereg_realm=prereg_realm,
|
2024-03-26 06:14:16 +01:00
|
|
|
how_realm_creator_found_zulip=how_realm_creator_found_zulip,
|
|
|
|
how_realm_creator_found_zulip_extra_context=how_realm_creator_found_zulip_extra_context,
|
2021-08-13 20:37:15 +02:00
|
|
|
)
|
2021-02-12 08:19:30 +01:00
|
|
|
assert realm is not None
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
full_name = form.cleaned_data["full_name"]
|
2021-08-16 21:03:33 +02:00
|
|
|
enable_marketing_emails = form.cleaned_data["enable_marketing_emails"]
|
2023-02-13 17:40:16 +01:00
|
|
|
email_address_visibility = form.cleaned_data["email_address_visibility"]
|
2021-02-12 08:20:45 +01:00
|
|
|
default_stream_group_names = request.POST.getlist("default_stream_group")
|
2017-10-26 20:31:43 +02:00
|
|
|
default_stream_groups = lookup_default_stream_groups(default_stream_group_names, realm)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2021-07-29 18:44:18 +02:00
|
|
|
if source_realm_id is not None:
|
2021-04-20 03:21:29 +02:00
|
|
|
# Non-integer realm_id values like "string" are treated
|
|
|
|
# like the "Do not import" value of "".
|
2024-07-12 02:30:23 +02:00
|
|
|
source_profile: UserProfile | None = get_source_profile(email, source_realm_id)
|
2018-05-22 18:13:51 +02:00
|
|
|
else:
|
|
|
|
source_profile = None
|
|
|
|
|
2017-11-28 03:01:44 +01:00
|
|
|
if not realm_creation:
|
|
|
|
try:
|
2024-07-12 02:30:23 +02:00
|
|
|
existing_user_profile: UserProfile | None = get_user_by_delivery_email(email, realm)
|
2017-11-28 03:01:44 +01:00
|
|
|
except UserProfile.DoesNotExist:
|
|
|
|
existing_user_profile = None
|
|
|
|
else:
|
2017-08-25 07:05:27 +02:00
|
|
|
existing_user_profile = None
|
|
|
|
|
2024-07-12 02:30:23 +02:00
|
|
|
user_profile: UserProfile | None = None
|
2024-07-12 02:30:17 +02:00
|
|
|
return_data: dict[str, bool] = {}
|
2017-10-24 19:38:31 +02:00
|
|
|
if ldap_auth_enabled(realm):
|
|
|
|
# If the user was authenticated using an external SSO
|
|
|
|
# mechanism like Google or GitHub auth, then authentication
|
|
|
|
# will have already been done before creating the
|
|
|
|
# PreregistrationUser object with password_required=False, and
|
|
|
|
# so we don't need to worry about passwords.
|
|
|
|
#
|
|
|
|
# If instead the realm is using EmailAuthBackend, we will
|
|
|
|
# set their password above.
|
|
|
|
#
|
|
|
|
# But if the realm is using LDAPAuthBackend, we need to verify
|
|
|
|
# their LDAP password (which will, as a side effect, create
|
|
|
|
# the user account) here using authenticate.
|
2022-02-08 00:13:33 +01:00
|
|
|
# prereg_user.realm_creation carries the information about whether
|
2019-11-07 05:13:08 +01:00
|
|
|
# we're in realm creation mode, and the ldap flow will handle
|
|
|
|
# that and create the user with the appropriate parameters.
|
2022-05-31 03:17:38 +02:00
|
|
|
user = authenticate(
|
2021-02-12 08:19:30 +01:00
|
|
|
request=request,
|
|
|
|
username=email,
|
|
|
|
password=password,
|
|
|
|
realm=realm,
|
|
|
|
prereg_user=prereg_user,
|
2023-03-10 10:42:00 +01:00
|
|
|
prereg_realm=prereg_realm,
|
2021-02-12 08:19:30 +01:00
|
|
|
return_data=return_data,
|
|
|
|
)
|
2022-05-31 03:17:38 +02:00
|
|
|
if user is None:
|
2023-03-12 22:15:21 +01:00
|
|
|
# This logic is security-sensitive. The user has NOT been successfully authenticated
|
|
|
|
# with LDAP and we need to carefully decide whether they should be permitted to proceed
|
|
|
|
# with account creation anyway or be stopped. There are three scenarios to consider:
|
|
|
|
#
|
|
|
|
# 1. EmailAuthBackend is enabled for the realm. That explicitly means that a user
|
|
|
|
# with a valid confirmation link should be able to create an account, because
|
|
|
|
# they were invited or organization permissions allowed sign up.
|
|
|
|
# 2. EmailAuthBackend is disabled - that means the organization wants to be authenticating
|
|
|
|
# users with an external source (LDAP or one of the ExternalAuthMethods). If the user
|
|
|
|
# came here through one of the ExternalAuthMethods, their identity can be considered
|
|
|
|
# verified and account creation can proceed.
|
|
|
|
# 3. EmailAuthBackend is disabled and the user did not come here through an ExternalAuthMethod.
|
|
|
|
# That means they came here by entering their email address on the registration page
|
|
|
|
# and clicking the confirmation link received. That means their identity needs to be
|
|
|
|
# verified with LDAP - and that has just failed above. Thus the account should NOT be
|
|
|
|
# created.
|
|
|
|
#
|
|
|
|
if email_auth_enabled(realm):
|
|
|
|
can_use_different_backend = True
|
|
|
|
# We can identify the user came here through an ExternalAuthMethod by password_required
|
|
|
|
# being set to False on the PreregistrationUser object.
|
|
|
|
elif len(get_external_method_dicts(realm)) > 0 and not password_required:
|
|
|
|
can_use_different_backend = True
|
|
|
|
else:
|
|
|
|
can_use_different_backend = False
|
|
|
|
|
2019-11-23 18:17:41 +01:00
|
|
|
if settings.LDAP_APPEND_DOMAIN:
|
2020-10-23 02:43:28 +02:00
|
|
|
# In LDAP_APPEND_DOMAIN configurations, we don't allow making a non-LDAP account
|
2019-11-23 18:17:41 +01:00
|
|
|
# if the email matches the ldap domain.
|
|
|
|
can_use_different_backend = can_use_different_backend and (
|
2021-02-12 08:19:30 +01:00
|
|
|
not email_belongs_to_ldap(realm, email)
|
|
|
|
)
|
2019-11-23 18:17:41 +01:00
|
|
|
if return_data.get("no_matching_ldap_user") and can_use_different_backend:
|
2019-11-21 14:48:49 +01:00
|
|
|
# If both the LDAP and Email or Social auth backends are
|
2019-11-07 04:26:46 +01:00
|
|
|
# enabled, and there's no matching user in the LDAP
|
|
|
|
# directory then the intent is to create a user in the
|
|
|
|
# realm with their email outside the LDAP organization
|
|
|
|
# (with e.g. a password stored in the Zulip database,
|
|
|
|
# not LDAP). So we fall through and create the new
|
|
|
|
# account.
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
# TODO: This probably isn't going to give a
|
|
|
|
# user-friendly error message, but it doesn't
|
|
|
|
# particularly matter, because the registration form
|
|
|
|
# is hidden for most users.
|
2021-02-12 08:20:45 +01:00
|
|
|
view_url = reverse("login")
|
2020-09-13 00:37:41 +02:00
|
|
|
query = urlencode({"email": email})
|
2021-10-14 01:45:34 +02:00
|
|
|
redirect_url = append_url_query_string(view_url, query)
|
2020-03-02 19:38:16 +01:00
|
|
|
return HttpResponseRedirect(redirect_url)
|
2019-11-07 05:13:08 +01:00
|
|
|
else:
|
2022-05-31 03:17:38 +02:00
|
|
|
assert isinstance(user, UserProfile)
|
|
|
|
user_profile = user
|
|
|
|
if not realm_creation:
|
|
|
|
# Since we'll have created a user, we now just log them in.
|
|
|
|
return login_and_go_to_home(request, user_profile)
|
2019-11-07 05:13:08 +01:00
|
|
|
# With realm_creation=True, we're going to return further down,
|
|
|
|
# after finishing up the creation process.
|
auth: Improve interactions between LDAPAuthBackend and EmailAuthBackend.
Previously, if you had LDAPAuthBackend enabled, we basically blocked
any other auth backends from working at all, by requiring the user's
login flow include verifying the user's LDAP password.
We still want to enforce that in the case that the account email
matches LDAP_APPEND_DOMAIN, but there's a reasonable corner case:
Having effectively guest users from outside the LDAP domain.
We don't want to allow creating a Zulip-level password for a user
inside the LDAP domain, so we still verify the LDAP password in that
flow, but if the email is allowed to register (due to invite or
whatever) but is outside the LDAP domain for the organization, we
allow it to create an account and set a password.
For the moment, this solution only covers EmailAuthBackend. It's
likely that just extending the list of other backends we check for in
the new conditional on `email_auth_backend` would be correct, but we
haven't done any testing for those cases, and with auth code paths,
it's better to disallow than allow untested code paths.
Fixes #9422.
2018-05-29 06:52:06 +02:00
|
|
|
|
|
|
|
if existing_user_profile is not None and existing_user_profile.is_mirror_dummy:
|
2017-03-19 01:42:40 +01:00
|
|
|
user_profile = existing_user_profile
|
2024-09-19 20:23:19 +02:00
|
|
|
with transaction.atomic(durable=True):
|
|
|
|
# We reactivate the mirror dummy user, but we need to respect the role
|
|
|
|
# that was passed in via the PreregistrationUser. Mirror dummy users can
|
|
|
|
# come from data import from 3rd party tools, with their role not necessarily
|
|
|
|
# set to what the realm admins may want. Such users have to explicitly go through
|
|
|
|
# the sign up flow, and thus their final role should match what the administrators
|
|
|
|
# may have set in the invitation, to avoid surprises.
|
|
|
|
#
|
|
|
|
# It's also important to reactivate the account and adjust the role in a single
|
|
|
|
# transaction, to avoid security issues if the process is interrupted halfway,
|
|
|
|
# e.g. leaving the user reactivated but with the wrong role.
|
|
|
|
do_activate_mirror_dummy_user(user_profile, acting_user=user_profile)
|
|
|
|
do_change_user_role(user_profile, role, acting_user=user_profile)
|
2017-03-19 01:42:40 +01:00
|
|
|
do_change_password(user_profile, password)
|
2017-04-07 07:28:28 +02:00
|
|
|
do_change_full_name(user_profile, full_name, user_profile)
|
2021-09-08 13:25:50 +02:00
|
|
|
do_change_user_setting(user_profile, "timezone", timezone, acting_user=user_profile)
|
2021-04-04 16:58:41 +02:00
|
|
|
do_change_user_setting(
|
|
|
|
user_profile,
|
|
|
|
"default_language",
|
2023-10-01 20:24:43 +02:00
|
|
|
get_default_language_for_new_user(realm, request=request),
|
2021-04-04 16:58:41 +02:00
|
|
|
acting_user=None,
|
|
|
|
)
|
2019-11-07 05:13:08 +01:00
|
|
|
|
|
|
|
if user_profile is None:
|
2023-10-06 22:39:02 +02:00
|
|
|
try:
|
|
|
|
user_profile = do_create_user(
|
|
|
|
email,
|
|
|
|
password,
|
|
|
|
realm,
|
|
|
|
full_name,
|
|
|
|
prereg_user=prereg_user,
|
|
|
|
prereg_realm=prereg_realm,
|
|
|
|
role=role,
|
|
|
|
tos_version=settings.TERMS_OF_SERVICE_VERSION,
|
|
|
|
timezone=timezone,
|
|
|
|
default_language=get_default_language_for_new_user(realm, request=request),
|
|
|
|
default_stream_groups=default_stream_groups,
|
|
|
|
source_profile=source_profile,
|
|
|
|
realm_creation=realm_creation,
|
|
|
|
acting_user=None,
|
|
|
|
enable_marketing_emails=enable_marketing_emails,
|
|
|
|
email_address_visibility=email_address_visibility,
|
|
|
|
)
|
|
|
|
except IntegrityError:
|
|
|
|
# Race condition making the user, leading to a
|
|
|
|
# duplicate email address. Redirect them to the login
|
|
|
|
# form.
|
|
|
|
return redirect_to_email_login_url(email)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2017-08-05 08:48:20 +02:00
|
|
|
if realm_creation:
|
2017-01-07 21:46:03 +01:00
|
|
|
# Because for realm creation, registration happens on the
|
|
|
|
# root domain, we need to log them into the subdomain for
|
|
|
|
# their new realm.
|
2021-02-12 08:19:30 +01:00
|
|
|
return redirect_and_log_into_subdomain(
|
2021-02-12 08:20:45 +01:00
|
|
|
ExternalAuthResult(user_profile=user_profile, data_dict={"is_realm_creation": True})
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
|
|
|
# This dummy_backend check below confirms the user is
|
|
|
|
# authenticating to the correct subdomain.
|
2021-02-12 08:19:30 +01:00
|
|
|
auth_result = authenticate(
|
2024-01-10 21:05:59 +01:00
|
|
|
request=request,
|
2021-02-12 08:19:30 +01:00
|
|
|
username=user_profile.delivery_email,
|
|
|
|
realm=realm,
|
|
|
|
return_data=return_data,
|
|
|
|
use_dummy_backend=True,
|
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
if return_data.get("invalid_subdomain"):
|
2017-01-07 21:46:03 +01:00
|
|
|
# By construction, this should never happen.
|
2020-05-02 08:44:14 +02:00
|
|
|
logging.error(
|
|
|
|
"Subdomain mismatch in registration %s: %s",
|
2021-02-12 08:19:30 +01:00
|
|
|
realm.subdomain,
|
|
|
|
user_profile.delivery_email,
|
2020-05-02 08:44:14 +02:00
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
return redirect("/")
|
2017-08-23 01:14:45 +02:00
|
|
|
|
2022-05-31 03:17:38 +02:00
|
|
|
assert isinstance(auth_result, UserProfile)
|
2017-10-24 19:37:14 +02:00
|
|
|
return login_and_go_to_home(request, auth_result)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2023-02-07 15:40:46 +01:00
|
|
|
default_email_address_visibility = None
|
|
|
|
if realm is not None:
|
|
|
|
realm_user_default = RealmUserDefault.objects.get(realm=realm)
|
|
|
|
default_email_address_visibility = realm_user_default.email_address_visibility
|
|
|
|
|
2023-03-17 18:49:51 +01:00
|
|
|
context = {
|
|
|
|
"form": form,
|
|
|
|
"email": email,
|
|
|
|
"key": key,
|
|
|
|
"full_name": request.session.get("authenticated_full_name", None),
|
|
|
|
"lock_name": name_validated and name_changes_disabled(realm),
|
|
|
|
# password_auth_enabled is normally set via our context processor,
|
|
|
|
# but for the registration form, there is no logged in user yet, so
|
|
|
|
# we have to set it here.
|
|
|
|
"creating_new_realm": realm_creation,
|
|
|
|
"password_required": password_auth_enabled(realm) and password_required,
|
|
|
|
"require_ldap_password": require_ldap_password,
|
|
|
|
"password_auth_enabled": password_auth_enabled(realm),
|
|
|
|
"default_stream_groups": [] if realm is None else get_default_stream_groups(realm),
|
|
|
|
"accounts": get_accounts_for_email(email),
|
|
|
|
"MAX_NAME_LENGTH": str(UserProfile.MAX_NAME_LENGTH),
|
|
|
|
"MAX_PASSWORD_LENGTH": str(form.MAX_PASSWORD_LENGTH),
|
|
|
|
"corporate_enabled": settings.CORPORATE_ENABLED,
|
|
|
|
"default_email_address_visibility": default_email_address_visibility,
|
|
|
|
"selected_realm_type_name": get_selected_realm_type_name(prereg_realm),
|
2023-09-12 21:58:58 +02:00
|
|
|
"selected_realm_default_language_name": get_selected_realm_default_language_name(
|
|
|
|
prereg_realm
|
|
|
|
),
|
2023-03-17 18:49:51 +01:00
|
|
|
"email_address_visibility_admins_only": RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_ADMINS,
|
|
|
|
"email_address_visibility_moderators": RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_MODERATORS,
|
|
|
|
"email_address_visibility_nobody": RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_NOBODY,
|
|
|
|
"email_address_visibility_options_dict": UserProfile.EMAIL_ADDRESS_VISIBILITY_ID_TO_NAME_MAP,
|
2024-03-26 06:14:16 +01:00
|
|
|
"how_realm_creator_found_zulip_options": RealmAuditLog.HOW_REALM_CREATOR_FOUND_ZULIP_OPTIONS.items(),
|
2023-03-17 18:49:51 +01:00
|
|
|
}
|
|
|
|
# Add context for realm creation part of the form.
|
|
|
|
context.update(get_realm_create_form_context())
|
|
|
|
|
|
|
|
return TemplateResponse(request, "zerver/register.html", context=context)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-11-27 09:28:57 +01:00
|
|
|
def login_and_go_to_home(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
2021-02-12 08:19:30 +01:00
|
|
|
mobile_flow_otp = get_expirable_session_var(
|
2021-02-12 08:20:45 +01:00
|
|
|
request.session, "registration_mobile_flow_otp", delete=True
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
|
|
|
desktop_flow_otp = get_expirable_session_var(
|
2021-02-12 08:20:45 +01:00
|
|
|
request.session, "registration_desktop_flow_otp", delete=True
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2020-02-06 18:27:10 +01:00
|
|
|
if mobile_flow_otp is not None:
|
|
|
|
return finish_mobile_flow(request, user_profile, mobile_flow_otp)
|
|
|
|
elif desktop_flow_otp is not None:
|
2022-12-08 23:15:25 +01:00
|
|
|
params_to_store_in_authenticated_session = orjson.loads(
|
|
|
|
get_expirable_session_var(
|
|
|
|
request.session,
|
|
|
|
"registration_desktop_flow_params_to_store_in_authenticated_session",
|
|
|
|
default_value="{}",
|
|
|
|
delete=True,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return finish_desktop_flow(
|
|
|
|
request, user_profile, desktop_flow_otp, params_to_store_in_authenticated_session
|
|
|
|
)
|
2020-02-06 18:27:10 +01:00
|
|
|
|
2017-10-24 19:37:14 +02:00
|
|
|
do_login(request, user_profile)
|
2019-12-20 00:00:45 +01:00
|
|
|
# Using 'mark_sanitized' to work around false positive where Pysa thinks
|
|
|
|
# that 'user_profile' is user-controlled
|
2024-05-06 15:27:22 +02:00
|
|
|
return HttpResponseRedirect(mark_sanitized(user_profile.realm.url) + reverse("home"))
|
2017-10-24 19:37:14 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
|
|
|
def prepare_activation_url(
|
|
|
|
email: str,
|
2022-06-21 00:01:15 +02:00
|
|
|
session: SessionBase,
|
2022-01-23 20:37:40 +01:00
|
|
|
*,
|
2024-07-12 02:30:23 +02:00
|
|
|
realm: Realm | None,
|
|
|
|
streams: Iterable[Stream] | None = None,
|
|
|
|
invited_as: int | None = None,
|
|
|
|
include_realm_default_subscriptions: bool | None = None,
|
|
|
|
multiuse_invite: MultiuseInvite | None = None,
|
2021-02-12 08:19:30 +01:00
|
|
|
) -> str:
|
2017-01-07 21:46:03 +01:00
|
|
|
"""
|
|
|
|
Send an email with a confirmation link to the provided e-mail so the user
|
|
|
|
can complete their registration.
|
|
|
|
"""
|
2023-03-03 11:58:00 +01:00
|
|
|
prereg_user = create_preregistration_user(email, realm, multiuse_invite=multiuse_invite)
|
2017-08-10 22:33:14 +02:00
|
|
|
|
|
|
|
if streams is not None:
|
2018-01-31 08:22:07 +01:00
|
|
|
prereg_user.streams.set(streams)
|
2017-08-10 22:33:14 +02:00
|
|
|
|
2019-02-06 22:57:14 +01:00
|
|
|
if invited_as is not None:
|
|
|
|
prereg_user.invited_as = invited_as
|
2023-05-25 16:06:13 +02:00
|
|
|
|
|
|
|
if include_realm_default_subscriptions is not None:
|
|
|
|
prereg_user.include_realm_default_subscriptions = include_realm_default_subscriptions
|
|
|
|
|
|
|
|
if invited_as is not None or include_realm_default_subscriptions is not None:
|
2019-02-06 22:57:14 +01:00
|
|
|
prereg_user.save()
|
|
|
|
|
2017-11-30 01:06:25 +01:00
|
|
|
confirmation_type = Confirmation.USER_REGISTRATION
|
|
|
|
|
2020-06-14 01:36:12 +02:00
|
|
|
activation_url = create_confirmation_link(prereg_user, confirmation_type)
|
2023-03-03 11:58:00 +01:00
|
|
|
return activation_url
|
|
|
|
|
|
|
|
|
|
|
|
def prepare_realm_activation_url(
|
|
|
|
email: str,
|
|
|
|
session: SessionBase,
|
|
|
|
realm_name: str,
|
|
|
|
string_id: str,
|
|
|
|
org_type: int,
|
2023-09-12 21:58:58 +02:00
|
|
|
default_language: str,
|
2023-03-03 11:58:00 +01:00
|
|
|
) -> str:
|
2023-09-12 21:58:58 +02:00
|
|
|
prereg_realm = create_preregistration_realm(
|
|
|
|
email, realm_name, string_id, org_type, default_language
|
|
|
|
)
|
2023-03-03 11:58:00 +01:00
|
|
|
activation_url = create_confirmation_link(
|
2023-12-07 22:38:44 +01:00
|
|
|
prereg_realm, Confirmation.REALM_CREATION, no_associated_realm_object=True
|
2023-03-03 11:58:00 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
if settings.DEVELOPMENT:
|
2022-01-23 20:37:40 +01:00
|
|
|
session["confirmation_key"] = {"confirmation_key": activation_url.split("/")[-1]}
|
2018-01-26 20:50:22 +01:00
|
|
|
return activation_url
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
|
|
|
def send_confirm_registration_email(
|
2021-08-31 22:01:13 +02:00
|
|
|
email: str,
|
|
|
|
activation_url: str,
|
|
|
|
*,
|
2024-07-12 02:30:23 +02:00
|
|
|
realm: Realm | None = None,
|
|
|
|
realm_subdomain: str | None = None,
|
|
|
|
realm_type: int | None = None,
|
|
|
|
request: HttpRequest | None = None,
|
2021-02-12 08:19:30 +01:00
|
|
|
) -> None:
|
2023-11-28 17:01:06 +01:00
|
|
|
org_url = ""
|
|
|
|
org_type = ""
|
|
|
|
if realm is None:
|
|
|
|
assert realm_subdomain is not None
|
|
|
|
org_url = f"{realm_subdomain}.{settings.EXTERNAL_HOST}"
|
|
|
|
assert realm_type is not None
|
|
|
|
org_type = get_org_type_display_name(realm_type)
|
2021-02-12 08:19:30 +01:00
|
|
|
send_email(
|
2021-02-12 08:20:45 +01:00
|
|
|
"zerver/emails/confirm_registration",
|
2021-02-12 08:19:30 +01:00
|
|
|
to_emails=[email],
|
|
|
|
from_address=FromAddress.tokenized_no_reply_address(),
|
2022-05-30 06:18:57 +02:00
|
|
|
language=get_language() if request is not None else None,
|
2021-05-26 19:40:12 +02:00
|
|
|
context={
|
2023-09-12 21:10:57 +02:00
|
|
|
"create_realm": realm is None,
|
2021-05-26 19:40:12 +02:00
|
|
|
"activate_url": activation_url,
|
2023-05-01 12:47:38 +02:00
|
|
|
"corporate_enabled": settings.CORPORATE_ENABLED,
|
2023-11-28 17:01:06 +01:00
|
|
|
"organization_url": org_url,
|
|
|
|
"organization_type": org_type,
|
2021-05-26 19:40:12 +02:00
|
|
|
},
|
2021-02-12 08:19:30 +01:00
|
|
|
realm=realm,
|
2021-08-31 22:01:13 +02:00
|
|
|
request=request,
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
|
|
|
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2017-11-27 09:28:57 +01:00
|
|
|
def redirect_to_email_login_url(email: str) -> HttpResponseRedirect:
|
2021-02-12 08:20:45 +01:00
|
|
|
login_url = reverse("login")
|
2021-10-14 01:45:34 +02:00
|
|
|
redirect_url = append_url_query_string(
|
2021-02-12 08:19:30 +01:00
|
|
|
login_url, urlencode({"email": email, "already_registered": 1})
|
|
|
|
)
|
2017-01-07 21:46:03 +01:00
|
|
|
return HttpResponseRedirect(redirect_url)
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2022-12-08 11:08:55 +01:00
|
|
|
@add_google_analytics
|
2024-07-12 02:30:23 +02:00
|
|
|
def create_realm(request: HttpRequest, creation_key: str | None = None) -> HttpResponse:
|
2018-01-29 20:54:49 +01:00
|
|
|
try:
|
|
|
|
key_record = validate_key(creation_key)
|
2022-11-17 09:30:48 +01:00
|
|
|
except RealmCreationKey.InvalidError:
|
2022-12-08 11:08:55 +01:00
|
|
|
return TemplateResponse(
|
2021-02-12 08:19:30 +01:00
|
|
|
request,
|
2024-06-05 06:51:07 +02:00
|
|
|
"zerver/portico_error_pages/realm_creation_link_invalid.html",
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2023-01-18 02:59:37 +01:00
|
|
|
if not settings.OPEN_REALM_CREATION and key_record is None:
|
|
|
|
return TemplateResponse(
|
|
|
|
request,
|
2024-06-05 06:51:07 +02:00
|
|
|
"zerver/portico_error_pages/realm_creation_disabled.html",
|
2023-01-18 02:59:37 +01:00
|
|
|
)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
|
|
|
# When settings.OPEN_REALM_CREATION is enabled, anyone can create a new realm,
|
2018-11-09 18:51:12 +01:00
|
|
|
# with a few restrictions on their email address.
|
2021-02-12 08:20:45 +01:00
|
|
|
if request.method == "POST":
|
2017-01-07 21:46:03 +01:00
|
|
|
form = RealmCreationForm(request.POST)
|
|
|
|
if form.is_valid():
|
2021-07-25 17:07:06 +02:00
|
|
|
try:
|
2021-11-03 21:40:28 +01:00
|
|
|
rate_limit_request_by_ip(request, domain="sends_email_by_ip")
|
2022-11-17 09:30:48 +01:00
|
|
|
except RateLimitedError as e:
|
2021-07-25 17:07:06 +02:00
|
|
|
assert e.secs_to_freedom is not None
|
2022-12-08 11:08:55 +01:00
|
|
|
return TemplateResponse(
|
2021-07-25 17:07:06 +02:00
|
|
|
request,
|
2024-06-05 06:51:07 +02:00
|
|
|
"zerver/portico_error_pages/rate_limit_exceeded.html",
|
2021-07-25 17:07:06 +02:00
|
|
|
context={"retry_after": int(e.secs_to_freedom)},
|
|
|
|
status=429,
|
|
|
|
)
|
2021-07-19 20:31:09 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
email = form.cleaned_data["email"]
|
2023-03-03 11:58:00 +01:00
|
|
|
realm_name = form.cleaned_data["realm_name"]
|
|
|
|
realm_type = form.cleaned_data["realm_type"]
|
2023-09-12 21:58:58 +02:00
|
|
|
realm_default_language = form.cleaned_data["realm_default_language"]
|
2023-03-03 11:58:00 +01:00
|
|
|
realm_subdomain = form.cleaned_data["realm_subdomain"]
|
|
|
|
activation_url = prepare_realm_activation_url(
|
2023-09-12 21:58:58 +02:00
|
|
|
email,
|
|
|
|
request.session,
|
|
|
|
realm_name,
|
|
|
|
realm_subdomain,
|
|
|
|
realm_type,
|
|
|
|
realm_default_language,
|
2022-01-23 20:37:40 +01:00
|
|
|
)
|
2018-01-26 21:12:58 +01:00
|
|
|
if key_record is not None and key_record.presume_email_valid:
|
|
|
|
# The user has a token created from the server command line;
|
|
|
|
# skip confirming the email is theirs, taking their word for it.
|
|
|
|
# This is essential on first install if the admin hasn't stopped
|
|
|
|
# to configure outbound email up front, or it isn't working yet.
|
|
|
|
key_record.delete()
|
|
|
|
return HttpResponseRedirect(activation_url)
|
|
|
|
|
2017-08-17 19:59:17 +02:00
|
|
|
try:
|
2023-11-28 17:01:06 +01:00
|
|
|
send_confirm_registration_email(
|
|
|
|
email,
|
|
|
|
activation_url,
|
|
|
|
realm_subdomain=realm_subdomain,
|
|
|
|
realm_type=realm_type,
|
|
|
|
request=request,
|
|
|
|
)
|
2022-11-17 09:30:48 +01:00
|
|
|
except EmailNotDeliveredError:
|
2023-10-06 18:01:41 +02:00
|
|
|
logging.exception("Failed to deliver email during realm creation")
|
2023-10-06 18:03:58 +02:00
|
|
|
if settings.CORPORATE_ENABLED:
|
|
|
|
return server_error(request)
|
2023-10-06 17:58:18 +02:00
|
|
|
return config_error(request, "smtp")
|
2017-08-17 19:59:17 +02:00
|
|
|
|
2018-01-29 20:54:49 +01:00
|
|
|
if key_record is not None:
|
|
|
|
key_record.delete()
|
2023-01-20 08:22:48 +01:00
|
|
|
new_realm_send_confirm_url = reverse("new_realm_send_confirm")
|
2023-04-26 12:27:30 +02:00
|
|
|
query = urlencode(
|
|
|
|
{
|
|
|
|
"email": email,
|
|
|
|
"realm_name": realm_name,
|
|
|
|
"realm_type": realm_type,
|
2023-09-12 21:58:58 +02:00
|
|
|
"realm_default_language": realm_default_language,
|
2023-04-26 12:27:30 +02:00
|
|
|
"realm_subdomain": realm_subdomain,
|
|
|
|
}
|
|
|
|
)
|
2023-01-20 08:22:48 +01:00
|
|
|
url = append_url_query_string(new_realm_send_confirm_url, query)
|
|
|
|
return HttpResponseRedirect(url)
|
2017-01-07 21:46:03 +01:00
|
|
|
else:
|
2023-09-12 21:58:58 +02:00
|
|
|
default_language_code = get_browser_language_code(request)
|
|
|
|
if default_language_code is None:
|
|
|
|
default_language_code = "en"
|
|
|
|
|
|
|
|
initial_data = {
|
|
|
|
"realm_default_language": default_language_code,
|
|
|
|
}
|
|
|
|
form = RealmCreationForm(initial=initial_data)
|
2023-03-01 10:47:38 +01:00
|
|
|
|
|
|
|
context = get_realm_create_form_context()
|
|
|
|
context.update(
|
|
|
|
{
|
|
|
|
"form": form,
|
|
|
|
"current_url": request.get_full_path,
|
|
|
|
}
|
|
|
|
)
|
2022-12-08 11:08:55 +01:00
|
|
|
return TemplateResponse(
|
2021-02-12 08:19:30 +01:00
|
|
|
request,
|
2021-02-12 08:20:45 +01:00
|
|
|
"zerver/create_realm.html",
|
2023-03-01 10:47:38 +01:00
|
|
|
context=context,
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
|
|
|
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2024-07-14 19:39:20 +02:00
|
|
|
@typed_endpoint
|
|
|
|
def signup_send_confirm(request: HttpRequest, *, email: str) -> HttpResponse:
|
2023-12-08 15:01:42 +01:00
|
|
|
try:
|
|
|
|
# Because we interpolate the email directly into the template
|
|
|
|
# from the query parameter, do a simple validation that it
|
|
|
|
# looks a at least a bit like an email address.
|
|
|
|
validators.validate_email(email)
|
|
|
|
except ValidationError:
|
|
|
|
return TemplateResponse(
|
|
|
|
request,
|
|
|
|
"zerver/invalid_email.html",
|
|
|
|
context={"invalid_email": True},
|
|
|
|
status=400,
|
|
|
|
)
|
2023-01-20 08:22:48 +01:00
|
|
|
return TemplateResponse(
|
|
|
|
request,
|
|
|
|
"zerver/accounts_send_confirm.html",
|
|
|
|
context={"email": email},
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2022-12-08 11:08:55 +01:00
|
|
|
@add_google_analytics
|
2024-07-14 19:39:20 +02:00
|
|
|
@typed_endpoint
|
2023-04-26 12:27:30 +02:00
|
|
|
def new_realm_send_confirm(
|
|
|
|
request: HttpRequest,
|
2024-07-14 19:39:20 +02:00
|
|
|
*,
|
|
|
|
email: str,
|
|
|
|
realm_name: Annotated[str, StringConstraints(max_length=Realm.MAX_REALM_NAME_LENGTH)],
|
|
|
|
realm_type: Annotated[Json[int], check_int_in_validator(Realm.ORG_TYPE_IDS)],
|
|
|
|
realm_default_language: Annotated[str, StringConstraints(max_length=MAX_LANGUAGE_ID_LENGTH)],
|
|
|
|
realm_subdomain: Annotated[str, StringConstraints(max_length=Realm.MAX_REALM_SUBDOMAIN_LENGTH)],
|
2023-04-26 12:27:30 +02:00
|
|
|
) -> HttpResponse:
|
2022-12-08 11:08:55 +01:00
|
|
|
return TemplateResponse(
|
|
|
|
request,
|
|
|
|
"zerver/accounts_send_confirm.html",
|
2023-04-26 12:27:30 +02:00
|
|
|
context={
|
|
|
|
"email": email,
|
|
|
|
# Using "new_realm_name" key here since "realm_name" key is already present in
|
|
|
|
# the context provided by zulip_default_context and it is "None" during realm
|
|
|
|
# creation.
|
|
|
|
"new_realm_name": realm_name,
|
|
|
|
"realm_type": realm_type,
|
2023-09-12 21:58:58 +02:00
|
|
|
"realm_default_language": realm_default_language,
|
2023-04-26 12:27:30 +02:00
|
|
|
"realm_subdomain": realm_subdomain,
|
|
|
|
"realm_creation": True,
|
|
|
|
},
|
2022-12-08 11:08:55 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
def accounts_home(
|
|
|
|
request: HttpRequest,
|
|
|
|
multiuse_object_key: str = "",
|
2024-07-12 02:30:23 +02:00
|
|
|
multiuse_object: MultiuseInvite | None = None,
|
2021-02-12 08:19:30 +01:00
|
|
|
) -> HttpResponse:
|
2019-05-04 04:47:44 +02:00
|
|
|
try:
|
|
|
|
realm = get_realm(get_subdomain(request))
|
|
|
|
except Realm.DoesNotExist:
|
2020-09-22 02:54:44 +02:00
|
|
|
return HttpResponseRedirect(reverse(find_account))
|
2017-11-23 02:59:51 +01:00
|
|
|
if realm.deactivated:
|
2017-08-24 09:58:44 +02:00
|
|
|
return redirect_to_deactivation_notice()
|
|
|
|
|
2017-08-10 22:34:17 +02:00
|
|
|
from_multiuse_invite = False
|
|
|
|
streams_to_subscribe = None
|
2019-02-06 22:57:14 +01:00
|
|
|
invited_as = None
|
2023-05-25 16:06:13 +02:00
|
|
|
include_realm_default_subscriptions = None
|
2017-08-10 22:34:17 +02:00
|
|
|
|
|
|
|
if multiuse_object:
|
2022-01-23 13:22:12 +01:00
|
|
|
# multiuse_object's realm should have been validated by the caller,
|
|
|
|
# so this code shouldn't be reachable with a multiuse_object which
|
|
|
|
# has its realm mismatching the realm of the request.
|
|
|
|
assert realm == multiuse_object.realm
|
|
|
|
|
2017-08-10 22:34:17 +02:00
|
|
|
streams_to_subscribe = multiuse_object.streams.all()
|
|
|
|
from_multiuse_invite = True
|
2019-02-06 22:57:14 +01:00
|
|
|
invited_as = multiuse_object.invited_as
|
2023-05-25 16:06:13 +02:00
|
|
|
include_realm_default_subscriptions = multiuse_object.include_realm_default_subscriptions
|
2017-08-10 22:34:17 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
if request.method == "POST":
|
2022-08-14 19:42:55 +02:00
|
|
|
form = HomepageForm(
|
|
|
|
request.POST,
|
|
|
|
realm=realm,
|
|
|
|
from_multiuse_invite=from_multiuse_invite,
|
|
|
|
invited_as=invited_as,
|
|
|
|
)
|
2017-01-07 21:46:03 +01:00
|
|
|
if form.is_valid():
|
2021-11-04 18:26:53 +01:00
|
|
|
try:
|
|
|
|
rate_limit_request_by_ip(request, domain="sends_email_by_ip")
|
2022-11-17 09:30:48 +01:00
|
|
|
except RateLimitedError as e:
|
2021-11-04 18:26:53 +01:00
|
|
|
assert e.secs_to_freedom is not None
|
|
|
|
return render(
|
|
|
|
request,
|
2024-06-05 06:51:07 +02:00
|
|
|
"zerver/portico_error_pages/rate_limit_exceeded.html",
|
2021-11-04 18:26:53 +01:00
|
|
|
context={"retry_after": int(e.secs_to_freedom)},
|
|
|
|
status=429,
|
|
|
|
)
|
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
email = form.cleaned_data["email"]
|
2021-01-06 18:41:44 +01:00
|
|
|
|
|
|
|
try:
|
|
|
|
validate_email_not_already_in_realm(realm, email)
|
|
|
|
except ValidationError:
|
|
|
|
return redirect_to_email_login_url(email)
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
activation_url = prepare_activation_url(
|
2022-01-23 20:37:40 +01:00
|
|
|
email,
|
|
|
|
request.session,
|
|
|
|
realm=realm,
|
|
|
|
streams=streams_to_subscribe,
|
|
|
|
invited_as=invited_as,
|
2023-05-25 16:06:13 +02:00
|
|
|
include_realm_default_subscriptions=include_realm_default_subscriptions,
|
2022-07-21 23:38:59 +02:00
|
|
|
multiuse_invite=multiuse_object,
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2017-08-17 18:28:21 +02:00
|
|
|
try:
|
2021-08-31 22:01:13 +02:00
|
|
|
send_confirm_registration_email(email, activation_url, request=request, realm=realm)
|
2022-11-17 09:30:48 +01:00
|
|
|
except EmailNotDeliveredError:
|
2023-10-06 18:01:41 +02:00
|
|
|
logging.exception("Failed to deliver email during user registration")
|
2023-10-06 18:03:58 +02:00
|
|
|
if settings.CORPORATE_ENABLED:
|
|
|
|
return server_error(request)
|
2023-10-06 17:58:18 +02:00
|
|
|
return config_error(request, "smtp")
|
2023-01-20 08:22:48 +01:00
|
|
|
signup_send_confirm_url = reverse("signup_send_confirm")
|
|
|
|
query = urlencode({"email": email})
|
|
|
|
url = append_url_query_string(signup_send_confirm_url, query)
|
|
|
|
return HttpResponseRedirect(url)
|
2017-08-25 07:05:27 +02:00
|
|
|
|
2017-01-07 21:46:03 +01:00
|
|
|
else:
|
|
|
|
form = HomepageForm(realm=realm)
|
2019-03-20 13:13:44 +01:00
|
|
|
context = login_context(request)
|
2021-02-12 08:19:30 +01:00
|
|
|
context.update(
|
|
|
|
form=form,
|
|
|
|
current_url=request.get_full_path,
|
|
|
|
multiuse_object_key=multiuse_object_key,
|
|
|
|
from_multiuse_invite=from_multiuse_invite,
|
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
return render(request, "zerver/accounts_home.html", context=context)
|
2017-01-07 21:46:03 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-11-27 09:28:57 +01:00
|
|
|
def accounts_home_from_multiuse_invite(request: HttpRequest, confirmation_key: str) -> HttpResponse:
|
2022-01-23 13:22:12 +01:00
|
|
|
realm = get_realm_from_request(request)
|
2024-07-12 02:30:23 +02:00
|
|
|
multiuse_object: MultiuseInvite | None = None
|
2017-08-10 22:34:17 +02:00
|
|
|
try:
|
2022-07-19 21:13:32 +02:00
|
|
|
confirmation_obj = get_object_from_key(
|
|
|
|
confirmation_key, [Confirmation.MULTIUSE_INVITE], mark_object_used=False
|
|
|
|
)
|
2022-06-28 21:52:24 +02:00
|
|
|
assert isinstance(confirmation_obj, MultiuseInvite)
|
|
|
|
multiuse_object = confirmation_obj
|
2022-01-23 13:22:12 +01:00
|
|
|
if realm != multiuse_object.realm:
|
|
|
|
return render(request, "confirmation/link_does_not_exist.html", status=404)
|
2021-05-10 07:02:14 +02:00
|
|
|
# Required for OAuth 2
|
2022-11-17 09:30:48 +01:00
|
|
|
except ConfirmationKeyError as exception:
|
2017-08-10 22:34:17 +02:00
|
|
|
if realm is None or realm.invite_required:
|
|
|
|
return render_confirmation_key_error(request, exception)
|
2021-02-12 08:19:30 +01:00
|
|
|
return accounts_home(
|
|
|
|
request, multiuse_object_key=confirmation_key, multiuse_object=multiuse_object
|
|
|
|
)
|
|
|
|
|
2017-08-10 22:34:17 +02:00
|
|
|
|
2024-07-14 19:39:20 +02:00
|
|
|
@typed_endpoint_without_parameters
|
2024-01-15 00:28:49 +01:00
|
|
|
def find_account(request: HttpRequest) -> HttpResponse:
|
2021-02-12 08:20:45 +01:00
|
|
|
url = reverse("find_account")
|
2024-01-15 00:28:49 +01:00
|
|
|
form = FindMyTeamForm()
|
2024-07-12 02:30:17 +02:00
|
|
|
emails: list[str] = []
|
2021-02-12 08:20:45 +01:00
|
|
|
if request.method == "POST":
|
2017-01-07 21:46:03 +01:00
|
|
|
form = FindMyTeamForm(request.POST)
|
|
|
|
if form.is_valid():
|
2024-01-15 00:28:49 +01:00
|
|
|
# Note: Show all the emails in the POST request response
|
|
|
|
# otherwise this feature can be used to ascertain which
|
|
|
|
# email addresses are associated with Zulip.
|
2021-02-12 08:20:45 +01:00
|
|
|
emails = form.cleaned_data["emails"]
|
2021-08-05 11:13:22 +02:00
|
|
|
for i in range(len(emails)):
|
|
|
|
try:
|
2021-11-03 21:40:28 +01:00
|
|
|
rate_limit_request_by_ip(request, domain="sends_email_by_ip")
|
2022-11-17 09:30:48 +01:00
|
|
|
except RateLimitedError as e:
|
2021-08-05 11:13:22 +02:00
|
|
|
assert e.secs_to_freedom is not None
|
|
|
|
return render(
|
|
|
|
request,
|
2024-06-05 06:51:07 +02:00
|
|
|
"zerver/portico_error_pages/rate_limit_exceeded.html",
|
2021-08-05 11:13:22 +02:00
|
|
|
context={"retry_after": int(e.secs_to_freedom)},
|
|
|
|
status=429,
|
|
|
|
)
|
2020-08-01 15:25:54 +02:00
|
|
|
|
|
|
|
# Django doesn't support __iexact__in lookup with EmailField, so we have
|
|
|
|
# to use Qs to get around that without needing to do multiple queries.
|
|
|
|
emails_q = Q()
|
|
|
|
for email in emails:
|
|
|
|
emails_q |= Q(delivery_email__iexact=email)
|
|
|
|
|
2021-09-15 15:29:15 +02:00
|
|
|
user_profiles = UserProfile.objects.filter(
|
2021-02-12 08:19:30 +01:00
|
|
|
emails_q, is_active=True, is_bot=False, realm__deactivated=False
|
2021-09-15 15:29:15 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
# We organize the data in preparation for sending exactly
|
|
|
|
# one outgoing email per provided email address, with each
|
|
|
|
# email listing all of the accounts that email address has
|
|
|
|
# with the current Zulip server.
|
2024-07-12 02:30:17 +02:00
|
|
|
emails_account_found: set[str] = set()
|
|
|
|
context: dict[str, dict[str, Any]] = {}
|
2021-09-15 15:29:15 +02:00
|
|
|
for user in user_profiles:
|
|
|
|
key = user.delivery_email.lower()
|
|
|
|
context.setdefault(key, {})
|
|
|
|
context[key].setdefault("realms", [])
|
|
|
|
context[key]["realms"].append(user.realm)
|
|
|
|
# This value will end up being the last user ID among
|
|
|
|
# matching accounts; since it's only used for minor
|
|
|
|
# details like language, that arbitrary choice is OK.
|
|
|
|
context[key]["to_user_id"] = user.id
|
2024-02-01 17:39:42 +01:00
|
|
|
emails_account_found.add(user.delivery_email)
|
2021-09-15 15:29:15 +02:00
|
|
|
|
2024-02-12 20:33:38 +01:00
|
|
|
# Links in find_team emails use the server's information
|
|
|
|
# and not any particular realm's information.
|
|
|
|
external_host_base_url = f"{settings.EXTERNAL_URI_SCHEME}{settings.EXTERNAL_HOST}"
|
|
|
|
help_base_url = f"{external_host_base_url}/help"
|
|
|
|
help_reset_password_link = (
|
|
|
|
f"{help_base_url}/change-your-password#if-youve-forgotten-or-never-had-a-password"
|
|
|
|
)
|
2024-02-01 17:39:42 +01:00
|
|
|
help_logging_in_link = f"{help_base_url}/logging-in#find-the-zulip-log-in-url"
|
|
|
|
find_accounts_link = f"{external_host_base_url}/accounts/find/"
|
2024-02-12 20:33:38 +01:00
|
|
|
|
2021-09-15 15:29:15 +02:00
|
|
|
for delivery_email, realm_context in context.items():
|
2021-02-12 08:19:30 +01:00
|
|
|
send_email(
|
2021-02-12 08:20:45 +01:00
|
|
|
"zerver/emails/find_team",
|
2021-09-15 15:29:15 +02:00
|
|
|
to_user_ids=[realm_context["to_user_id"]],
|
2024-02-12 20:33:38 +01:00
|
|
|
context={
|
2024-02-01 17:39:42 +01:00
|
|
|
"account_found": True,
|
2024-02-12 20:33:38 +01:00
|
|
|
"external_host": settings.EXTERNAL_HOST,
|
|
|
|
"corporate_enabled": settings.CORPORATE_ENABLED,
|
|
|
|
"help_reset_password_link": help_reset_password_link,
|
|
|
|
"realms": realm_context["realms"],
|
|
|
|
"email": delivery_email,
|
|
|
|
},
|
2021-02-12 08:19:30 +01:00
|
|
|
from_address=FromAddress.SUPPORT,
|
2021-08-31 22:01:13 +02:00
|
|
|
request=request,
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2024-02-01 17:39:42 +01:00
|
|
|
|
|
|
|
emails_lower_with_account = {email.lower() for email in emails_account_found}
|
2024-07-12 02:30:17 +02:00
|
|
|
emails_without_account: set[str] = {
|
2024-02-01 17:39:42 +01:00
|
|
|
email for email in emails if email.lower() not in emails_lower_with_account
|
|
|
|
}
|
|
|
|
if emails_without_account:
|
|
|
|
send_email(
|
|
|
|
"zerver/emails/find_team",
|
|
|
|
to_emails=list(emails_without_account),
|
|
|
|
context=(
|
|
|
|
{
|
|
|
|
"account_found": False,
|
|
|
|
"external_host": settings.EXTERNAL_HOST,
|
|
|
|
"corporate_enabled": settings.CORPORATE_ENABLED,
|
|
|
|
"find_accounts_link": find_accounts_link,
|
|
|
|
"help_logging_in_link": help_logging_in_link,
|
|
|
|
}
|
|
|
|
),
|
|
|
|
from_address=FromAddress.SUPPORT,
|
|
|
|
request=request,
|
|
|
|
)
|
2021-02-12 08:19:30 +01:00
|
|
|
return render(
|
|
|
|
request,
|
2021-02-12 08:20:45 +01:00
|
|
|
"zerver/find_account.html",
|
2024-01-16 10:21:43 +01:00
|
|
|
context={"form": form, "current_url": url, "emails": emails},
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
|
|
|
|
2018-08-25 14:06:17 +02:00
|
|
|
|
2024-07-14 19:39:20 +02:00
|
|
|
@typed_endpoint
|
|
|
|
def realm_redirect(request: HttpRequest, *, next: str = "") -> HttpResponse:
|
2021-02-12 08:20:45 +01:00
|
|
|
if request.method == "POST":
|
2018-08-25 14:06:17 +02:00
|
|
|
form = RealmRedirectForm(request.POST)
|
|
|
|
if form.is_valid():
|
2021-02-12 08:20:45 +01:00
|
|
|
subdomain = form.cleaned_data["subdomain"]
|
2018-08-25 14:06:17 +02:00
|
|
|
realm = get_realm(subdomain)
|
2024-05-06 15:27:22 +02:00
|
|
|
redirect_to = urljoin(realm.url, settings.HOME_NOT_LOGGED_IN)
|
2022-10-27 19:08:41 +02:00
|
|
|
|
|
|
|
if next:
|
|
|
|
redirect_to = append_url_query_string(
|
|
|
|
redirect_to, urlencode({REDIRECT_FIELD_NAME: next})
|
|
|
|
)
|
|
|
|
|
2018-08-25 14:06:17 +02:00
|
|
|
return HttpResponseRedirect(redirect_to)
|
|
|
|
else:
|
|
|
|
form = RealmRedirectForm()
|
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
return render(request, "zerver/realm_redirect.html", context={"form": form})
|