mirror of
https://github.com/zulip/zulip.git
synced 2026-07-03 21:10:12 +08:00
So far, we've used the BitField .authentication_methods on Realm for tracking which backends are enabled for an organization. This however made it a pain to add new backends (requiring altering the column and a migration - particularly troublesome if someone wanted to create their own custom auth backend for their server). Instead this will be tracked through the existence of the appropriate rows in the RealmAuthenticationMethods table.
311 lines
12 KiB
Python
311 lines
12 KiB
Python
import datetime
|
|
import logging
|
|
from typing import Any, Dict, Optional
|
|
|
|
from django.conf import settings
|
|
from django.db import transaction
|
|
from django.utils.timezone import now as timezone_now
|
|
|
|
from confirmation import settings as confirmation_settings
|
|
from zerver.actions.message_send import internal_send_stream_message
|
|
from zerver.actions.realm_settings import (
|
|
do_add_deactivated_redirect,
|
|
do_change_realm_plan_type,
|
|
do_deactivate_realm,
|
|
)
|
|
from zerver.lib.bulk_create import create_users
|
|
from zerver.lib.server_initialization import create_internal_realm, server_initialized
|
|
from zerver.lib.streams import ensure_stream, get_signups_stream
|
|
from zerver.lib.user_groups import create_system_user_groups_for_realm
|
|
from zerver.models import (
|
|
DefaultStream,
|
|
PreregistrationRealm,
|
|
Realm,
|
|
RealmAuditLog,
|
|
RealmAuthenticationMethod,
|
|
RealmUserDefault,
|
|
Stream,
|
|
UserProfile,
|
|
get_org_type_display_name,
|
|
get_realm,
|
|
get_system_bot,
|
|
)
|
|
from zproject.backends import all_implemented_backend_names
|
|
|
|
if settings.CORPORATE_ENABLED:
|
|
from corporate.lib.support import get_support_url
|
|
|
|
|
|
def do_change_realm_subdomain(
|
|
realm: Realm,
|
|
new_subdomain: str,
|
|
*,
|
|
acting_user: Optional[UserProfile],
|
|
add_deactivated_redirect: bool = True,
|
|
) -> None:
|
|
"""Changing a realm's subdomain is a highly disruptive operation,
|
|
because all existing clients will need to be updated to point to
|
|
the new URL. Further, requests to fetch data from existing event
|
|
queues will fail with an authentication error when this change
|
|
happens (because the old subdomain is no longer associated with
|
|
the realm), making it hard for us to provide a graceful update
|
|
experience for clients.
|
|
"""
|
|
old_subdomain = realm.subdomain
|
|
old_uri = realm.uri
|
|
# If the realm had been a demo organization scheduled for
|
|
# deleting, clear that state.
|
|
realm.demo_organization_scheduled_deletion_date = None
|
|
realm.string_id = new_subdomain
|
|
with transaction.atomic():
|
|
realm.save(update_fields=["string_id", "demo_organization_scheduled_deletion_date"])
|
|
RealmAuditLog.objects.create(
|
|
realm=realm,
|
|
event_type=RealmAuditLog.REALM_SUBDOMAIN_CHANGED,
|
|
event_time=timezone_now(),
|
|
acting_user=acting_user,
|
|
extra_data=str({"old_subdomain": old_subdomain, "new_subdomain": new_subdomain}),
|
|
)
|
|
|
|
# If a realm if being renamed multiple times, we should find all the placeholder
|
|
# realms and reset their deactivated_redirect field to point to the new realm uri
|
|
placeholder_realms = Realm.objects.filter(deactivated_redirect=old_uri, deactivated=True)
|
|
for placeholder_realm in placeholder_realms:
|
|
do_add_deactivated_redirect(placeholder_realm, realm.uri)
|
|
|
|
# The below block isn't executed in a transaction with the earlier code due to
|
|
# the functions called below being complex and potentially sending events,
|
|
# which we don't want to do in atomic blocks.
|
|
# When we change a realm's subdomain the realm with old subdomain is basically
|
|
# deactivated. We are creating a deactivated realm using old subdomain and setting
|
|
# it's deactivated redirect to new_subdomain so that we can tell the users that
|
|
# the realm has been moved to a new subdomain.
|
|
if add_deactivated_redirect:
|
|
placeholder_realm = do_create_realm(old_subdomain, realm.name)
|
|
do_deactivate_realm(placeholder_realm, acting_user=None)
|
|
do_add_deactivated_redirect(placeholder_realm, realm.uri)
|
|
|
|
|
|
def set_realm_permissions_based_on_org_type(realm: Realm) -> None:
|
|
"""This function implements overrides for the default configuration
|
|
for new organizations when the administrator selected specific
|
|
organization types.
|
|
|
|
This substantially simplifies our /help/ advice for folks setting
|
|
up new organizations of these types.
|
|
"""
|
|
|
|
# Custom configuration for educational organizations. The present
|
|
# defaults are designed for a single class, not a department or
|
|
# larger institution, since those are more common.
|
|
if (
|
|
realm.org_type == Realm.ORG_TYPES["education_nonprofit"]["id"]
|
|
or realm.org_type == Realm.ORG_TYPES["education"]["id"]
|
|
):
|
|
# Limit user creation to administrators.
|
|
realm.invite_to_realm_policy = Realm.POLICY_ADMINS_ONLY
|
|
# Restrict public stream creation to staff, but allow private
|
|
# streams (useful for study groups, etc.).
|
|
realm.create_public_stream_policy = Realm.POLICY_ADMINS_ONLY
|
|
# Don't allow members (students) to manage user groups or
|
|
# stream subscriptions.
|
|
realm.user_group_edit_policy = Realm.POLICY_MODERATORS_ONLY
|
|
realm.invite_to_stream_policy = Realm.POLICY_MODERATORS_ONLY
|
|
# Allow moderators (TAs?) to move topics between streams.
|
|
realm.move_messages_between_streams_policy = Realm.POLICY_MODERATORS_ONLY
|
|
|
|
|
|
def setup_realm_internal_bots(realm: Realm) -> None:
|
|
"""Create this realm's internal bots.
|
|
|
|
This function is idempotent; it does nothing for a bot that
|
|
already exists.
|
|
"""
|
|
internal_bots = [
|
|
(bot["name"], bot["email_template"] % (settings.INTERNAL_BOT_DOMAIN,))
|
|
for bot in settings.REALM_INTERNAL_BOTS
|
|
]
|
|
create_users(realm, internal_bots, bot_type=UserProfile.DEFAULT_BOT)
|
|
bots = UserProfile.objects.filter(
|
|
realm=realm,
|
|
email__in=[bot_info[1] for bot_info in internal_bots],
|
|
bot_owner__isnull=True,
|
|
)
|
|
for bot in bots:
|
|
bot.bot_owner = bot
|
|
bot.save()
|
|
|
|
|
|
def do_create_realm(
|
|
string_id: str,
|
|
name: str,
|
|
*,
|
|
emails_restricted_to_domains: Optional[bool] = None,
|
|
email_address_visibility: Optional[int] = None,
|
|
description: Optional[str] = None,
|
|
invite_required: Optional[bool] = None,
|
|
plan_type: Optional[int] = None,
|
|
org_type: Optional[int] = None,
|
|
date_created: Optional[datetime.datetime] = None,
|
|
is_demo_organization: bool = False,
|
|
enable_read_receipts: Optional[bool] = None,
|
|
enable_spectator_access: Optional[bool] = None,
|
|
prereg_realm: Optional[PreregistrationRealm] = None,
|
|
) -> Realm:
|
|
if string_id == settings.SOCIAL_AUTH_SUBDOMAIN:
|
|
raise AssertionError("Creating a realm on SOCIAL_AUTH_SUBDOMAIN is not allowed!")
|
|
if Realm.objects.filter(string_id=string_id).exists():
|
|
raise AssertionError(f"Realm {string_id} already exists!")
|
|
if not server_initialized():
|
|
logging.info("Server not yet initialized. Creating the internal realm first.")
|
|
create_internal_realm()
|
|
|
|
kwargs: Dict[str, Any] = {}
|
|
if emails_restricted_to_domains is not None:
|
|
kwargs["emails_restricted_to_domains"] = emails_restricted_to_domains
|
|
if email_address_visibility is not None:
|
|
kwargs["email_address_visibility"] = email_address_visibility
|
|
if description is not None:
|
|
kwargs["description"] = description
|
|
if invite_required is not None:
|
|
kwargs["invite_required"] = invite_required
|
|
if plan_type is not None:
|
|
kwargs["plan_type"] = plan_type
|
|
if org_type is not None:
|
|
kwargs["org_type"] = org_type
|
|
if enable_spectator_access is not None:
|
|
if enable_spectator_access:
|
|
# Realms with LIMITED plan cannot have spectators enabled.
|
|
assert plan_type != Realm.PLAN_TYPE_LIMITED
|
|
assert plan_type is not None or not settings.BILLING_ENABLED
|
|
kwargs["enable_spectator_access"] = enable_spectator_access
|
|
|
|
if date_created is not None:
|
|
# The date_created parameter is intended only for use by test
|
|
# suites that want to backdate the date of a realm's creation.
|
|
assert not settings.PRODUCTION
|
|
kwargs["date_created"] = date_created
|
|
|
|
# Generally, closed organizations like companies want read
|
|
# receipts, whereas it's unclear what an open organization's
|
|
# preferences will be. We enable the setting by default only for
|
|
# closed organizations.
|
|
if enable_read_receipts is not None:
|
|
kwargs["enable_read_receipts"] = enable_read_receipts
|
|
else:
|
|
# Hacky: The default of invited_required is True, so we need
|
|
# to check for None too.
|
|
kwargs["enable_read_receipts"] = (
|
|
invite_required is None or invite_required is True or emails_restricted_to_domains
|
|
)
|
|
|
|
with transaction.atomic():
|
|
realm = Realm(string_id=string_id, name=name, **kwargs)
|
|
if is_demo_organization:
|
|
realm.demo_organization_scheduled_deletion_date = (
|
|
realm.date_created + datetime.timedelta(days=settings.DEMO_ORG_DEADLINE_DAYS)
|
|
)
|
|
|
|
set_realm_permissions_based_on_org_type(realm)
|
|
realm.save()
|
|
|
|
RealmAuditLog.objects.create(
|
|
# acting_user will be set as the initial realm owner inside
|
|
# do_create_user(..., realm_creation=True).
|
|
acting_user=None,
|
|
realm=realm,
|
|
event_type=RealmAuditLog.REALM_CREATED,
|
|
event_time=realm.date_created,
|
|
)
|
|
|
|
realm_default_email_address_visibility = RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_EVERYONE
|
|
if (
|
|
realm.org_type == Realm.ORG_TYPES["education_nonprofit"]["id"]
|
|
or realm.org_type == Realm.ORG_TYPES["education"]["id"]
|
|
):
|
|
# Email address of users should be initially visible to admins only.
|
|
realm_default_email_address_visibility = (
|
|
RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_ADMINS
|
|
)
|
|
|
|
RealmUserDefault.objects.create(
|
|
realm=realm, email_address_visibility=realm_default_email_address_visibility
|
|
)
|
|
|
|
create_system_user_groups_for_realm(realm)
|
|
|
|
# We create realms with all authentications methods enabled by default.
|
|
RealmAuthenticationMethod.objects.bulk_create(
|
|
[
|
|
RealmAuthenticationMethod(name=backend_name, realm=realm)
|
|
for backend_name in all_implemented_backend_names()
|
|
]
|
|
)
|
|
|
|
# Create stream once Realm object has been saved
|
|
notifications_stream = ensure_stream(
|
|
realm,
|
|
Realm.DEFAULT_NOTIFICATION_STREAM_NAME,
|
|
stream_description="Everyone is added to this stream by default. Welcome! :octopus:",
|
|
acting_user=None,
|
|
)
|
|
realm.notifications_stream = notifications_stream
|
|
|
|
# With the current initial streams situation, the only public
|
|
# stream is the notifications_stream.
|
|
DefaultStream.objects.create(stream=notifications_stream, realm=realm)
|
|
|
|
signup_notifications_stream = ensure_stream(
|
|
realm,
|
|
Realm.INITIAL_PRIVATE_STREAM_NAME,
|
|
invite_only=True,
|
|
stream_description="A private stream for core team members.",
|
|
acting_user=None,
|
|
)
|
|
realm.signup_notifications_stream = signup_notifications_stream
|
|
|
|
realm.save(update_fields=["notifications_stream", "signup_notifications_stream"])
|
|
|
|
if plan_type is None and settings.BILLING_ENABLED:
|
|
# We use acting_user=None for setting the initial plan type.
|
|
do_change_realm_plan_type(realm, Realm.PLAN_TYPE_LIMITED, acting_user=None)
|
|
|
|
if prereg_realm is not None:
|
|
prereg_realm.status = confirmation_settings.STATUS_USED
|
|
prereg_realm.created_realm = realm
|
|
prereg_realm.save(update_fields=["status", "created_realm"])
|
|
|
|
# Send a notification to the admin realm when a new organization registers.
|
|
if settings.CORPORATE_ENABLED:
|
|
admin_realm = get_realm(settings.SYSTEM_BOT_REALM)
|
|
sender = get_system_bot(settings.NOTIFICATION_BOT, admin_realm.id)
|
|
|
|
support_url = get_support_url(realm)
|
|
organization_type = get_org_type_display_name(realm.org_type)
|
|
|
|
message = "[{name}]({support_link}) ([{subdomain}]({realm_link})). Organization type: {type}".format(
|
|
name=realm.name,
|
|
subdomain=realm.display_subdomain,
|
|
realm_link=realm.uri,
|
|
support_link=support_url,
|
|
type=organization_type,
|
|
)
|
|
topic = "new organizations"
|
|
|
|
try:
|
|
signups_stream = get_signups_stream(admin_realm)
|
|
|
|
internal_send_stream_message(
|
|
sender,
|
|
signups_stream,
|
|
topic,
|
|
message,
|
|
)
|
|
except Stream.DoesNotExist: # nocoverage
|
|
# If the signups stream hasn't been created in the admin
|
|
# realm, don't auto-create it to send to it; just do nothing.
|
|
pass
|
|
|
|
setup_realm_internal_bots(realm)
|
|
return realm
|