diff --git a/analytics/tests/test_counts.py b/analytics/tests/test_counts.py index 1886bf2266..cb9c8fbafe 100644 --- a/analytics/tests/test_counts.py +++ b/analytics/tests/test_counts.py @@ -32,6 +32,11 @@ from analytics.models import ( UserCount, installation_epoch, ) +from zerver.actions.create_user import ( + do_activate_mirror_dummy_user, + do_create_user, + do_reactivate_user, +) from zerver.actions.invites import ( do_invite_users, do_resend_user_invite_email, @@ -40,12 +45,9 @@ from zerver.actions.invites import ( from zerver.actions.user_activity import update_user_activity_interval from zerver.actions.users import do_deactivate_user from zerver.lib.actions import ( - do_activate_mirror_dummy_user, do_create_realm, - do_create_user, do_mark_all_as_read, do_mark_stream_messages_as_read, - do_reactivate_user, do_update_message_flags, ) from zerver.lib.create_user import create_user diff --git a/corporate/lib/registration.py b/corporate/lib/registration.py index afebbb3bac..09d4b7312f 100644 --- a/corporate/lib/registration.py +++ b/corporate/lib/registration.py @@ -5,7 +5,7 @@ from django.utils.translation import gettext as _ from corporate.lib.stripe import LicenseLimitError, get_latest_seat_count from corporate.models import get_current_plan_by_realm -from zerver.lib.actions import send_message_to_signup_notification_stream +from zerver.actions.create_user import send_message_to_signup_notification_stream from zerver.lib.exceptions import InvitationError from zerver.models import Realm, get_system_bot diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py index 548dd333bc..0e86e0c4ca 100644 --- a/corporate/tests/test_stripe.py +++ b/corporate/tests/test_stripe.py @@ -81,15 +81,13 @@ from corporate.models import ( get_current_plan_by_realm, get_customer_by_realm, ) -from zerver.actions.users import do_deactivate_user -from zerver.lib.actions import ( +from zerver.actions.create_user import ( do_activate_mirror_dummy_user, - do_create_realm, do_create_user, - do_deactivate_realm, - do_reactivate_realm, do_reactivate_user, ) +from zerver.actions.users import do_deactivate_user +from zerver.lib.actions import do_create_realm, do_deactivate_realm, do_reactivate_realm from zerver.lib.test_classes import ZulipTestCase from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime from zerver.lib.utils import assert_is_not_none diff --git a/tools/generate-integration-docs-screenshot b/tools/generate-integration-docs-screenshot index 2284dce7fc..c832fcd31a 100755 --- a/tools/generate-integration-docs-screenshot +++ b/tools/generate-integration-docs-screenshot @@ -33,9 +33,9 @@ import zulip from scripts.lib.zulip_tools import BOLDRED, ENDC from tools.lib.test_script import prepare_puppeteer_run +from zerver.actions.create_user import do_create_user, notify_created_bot from zerver.actions.streams import bulk_add_subscriptions from zerver.actions.user_settings import do_change_avatar_fields -from zerver.lib.actions import do_create_user, notify_created_bot from zerver.lib.integrations import ( DOC_SCREENSHOT_CONFIG, INTEGRATIONS, diff --git a/tools/test-api b/tools/test-api index 77cd6ac90f..b95550420f 100755 --- a/tools/test-api +++ b/tools/test-api @@ -31,13 +31,9 @@ with test_server_running( ): # zerver imports should happen after `django.setup()` is run # by the test_server_running decorator. + from zerver.actions.create_user import do_create_user, do_reactivate_user from zerver.actions.users import change_user_is_active - from zerver.lib.actions import ( - do_create_user, - do_deactivate_realm, - do_reactivate_realm, - do_reactivate_user, - ) + from zerver.lib.actions import do_deactivate_realm, do_reactivate_realm from zerver.lib.test_helpers import reset_emails_in_zulip_realm from zerver.lib.users import get_api_key from zerver.models import get_realm, get_user diff --git a/zerver/actions/create_user.py b/zerver/actions/create_user.py new file mode 100644 index 0000000000..2c43f6202a --- /dev/null +++ b/zerver/actions/create_user.py @@ -0,0 +1,566 @@ +import datetime +from collections import defaultdict +from typing import Any, Dict, Iterable, List, Optional, Sequence, Set + +import orjson +from django.conf import settings +from django.db import transaction +from django.utils.timezone import now as timezone_now +from django.utils.translation import gettext as _ +from django.utils.translation import override as override_language + +from analytics.lib.counts import COUNT_STATS, do_increment_logging_stat +from confirmation import settings as confirmation_settings +from zerver.actions.default_streams import get_default_streams_for_realm +from zerver.actions.invites import notify_invites_changed +from zerver.actions.message_send import internal_send_private_message, internal_send_stream_message +from zerver.actions.streams import bulk_add_subscriptions, send_peer_subscriber_events +from zerver.actions.user_groups import do_send_user_group_members_update_event +from zerver.actions.users import change_user_is_active, get_service_dicts_for_bot +from zerver.lib.avatar import avatar_url +from zerver.lib.create_user import create_user +from zerver.lib.email_notifications import enqueue_welcome_emails +from zerver.lib.mention import silent_mention_syntax_for_user +from zerver.lib.send_email import clear_scheduled_invitation_emails +from zerver.lib.stream_subscription import bulk_get_subscriber_peer_info +from zerver.lib.streams import get_signups_stream +from zerver.lib.user_counts import realm_user_count, realm_user_count_by_role +from zerver.lib.user_groups import get_system_user_group_for_user +from zerver.lib.users import format_user_row, get_api_key, user_profile_to_user_row +from zerver.models import ( + DefaultStreamGroup, + Message, + PreregistrationUser, + Realm, + RealmAuditLog, + Recipient, + Stream, + Subscription, + UserGroup, + UserGroupMembership, + UserMessage, + UserProfile, + active_user_ids, + bot_owner_user_ids, + get_realm, + get_system_bot, +) +from zerver.tornado.django_api import send_event + +if settings.BILLING_ENABLED: + from corporate.lib.stripe import update_license_ledger_if_needed + + +ONBOARDING_TOTAL_MESSAGES = 1000 +ONBOARDING_UNREAD_MESSAGES = 20 +ONBOARDING_RECENT_TIMEDELTA = datetime.timedelta(weeks=1) + + +def create_historical_user_messages(*, user_id: int, message_ids: List[int]) -> None: + # Users can see and interact with messages sent to streams with + # public history for which they do not have a UserMessage because + # they were not a subscriber at the time the message was sent. + # In order to add emoji reactions or mutate message flags for + # those messages, we create UserMessage objects for those messages; + # these have the special historical flag which keeps track of the + # fact that the user did not receive the message at the time it was sent. + for message_id in message_ids: + UserMessage.objects.create( + user_profile_id=user_id, + message_id=message_id, + flags=UserMessage.flags.historical | UserMessage.flags.read, + ) + + +def send_message_to_signup_notification_stream( + sender: UserProfile, realm: Realm, message: str, topic_name: str = _("signups") +) -> None: + signup_notifications_stream = realm.get_signup_notifications_stream() + if signup_notifications_stream is None: + return + + with override_language(realm.default_language): + internal_send_stream_message(sender, signup_notifications_stream, topic_name, message) + + +def notify_new_user(user_profile: UserProfile) -> None: + user_count = realm_user_count(user_profile.realm) + sender_email = settings.NOTIFICATION_BOT + sender = get_system_bot(sender_email, user_profile.realm_id) + + is_first_user = user_count == 1 + if not is_first_user: + message = _("{user} just signed up for Zulip. (total: {user_count})").format( + user=silent_mention_syntax_for_user(user_profile), user_count=user_count + ) + + if settings.BILLING_ENABLED: + from corporate.lib.registration import generate_licenses_low_warning_message_if_required + + licenses_low_warning_message = generate_licenses_low_warning_message_if_required( + user_profile.realm + ) + if licenses_low_warning_message is not None: + message += "\n" + message += licenses_low_warning_message + + send_message_to_signup_notification_stream(sender, user_profile.realm, message) + + # We also send a notification to the Zulip administrative realm + admin_realm = get_realm(settings.SYSTEM_BOT_REALM) + admin_realm_sender = get_system_bot(sender_email, admin_realm.id) + try: + # Check whether the stream exists + signups_stream = get_signups_stream(admin_realm) + # We intentionally use the same strings as above to avoid translation burden. + message = _("{user} just signed up for Zulip. (total: {user_count})").format( + user=f"{user_profile.full_name} <`{user_profile.email}`>", user_count=user_count + ) + internal_send_stream_message( + admin_realm_sender, signups_stream, user_profile.realm.display_subdomain, message + ) + + except Stream.DoesNotExist: + # 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 + + +def add_new_user_history(user_profile: UserProfile, streams: Iterable[Stream]) -> None: + """Give you the last ONBOARDING_TOTAL_MESSAGES messages on your public + streams, so you have something to look at in your home view once + you finish the tutorial. The most recent ONBOARDING_UNREAD_MESSAGES + are marked unread. + """ + one_week_ago = timezone_now() - ONBOARDING_RECENT_TIMEDELTA + + recipient_ids = [stream.recipient_id for stream in streams if not stream.invite_only] + recent_messages = Message.objects.filter( + recipient_id__in=recipient_ids, date_sent__gt=one_week_ago + ).order_by("-id") + message_ids_to_use = list( + reversed(recent_messages.values_list("id", flat=True)[0:ONBOARDING_TOTAL_MESSAGES]) + ) + if len(message_ids_to_use) == 0: + return + + # Handle the race condition where a message arrives between + # bulk_add_subscriptions above and the Message query just above + already_ids = set( + UserMessage.objects.filter( + message_id__in=message_ids_to_use, user_profile=user_profile + ).values_list("message_id", flat=True) + ) + + # Mark the newest ONBOARDING_UNREAD_MESSAGES as unread. + marked_unread = 0 + ums_to_create = [] + for message_id in reversed(message_ids_to_use): + if message_id in already_ids: + continue + + um = UserMessage(user_profile=user_profile, message_id=message_id) + if marked_unread < ONBOARDING_UNREAD_MESSAGES: + marked_unread += 1 + else: + um.flags = UserMessage.flags.read + ums_to_create.append(um) + + UserMessage.objects.bulk_create(reversed(ums_to_create)) + + +# Does the processing for a new user account: +# * Subscribes to default/invitation streams +# * Fills in some recent historical messages +# * Notifies other users in realm and Zulip about the signup +# * Deactivates PreregistrationUser objects +def process_new_human_user( + user_profile: UserProfile, + prereg_user: Optional[PreregistrationUser] = None, + default_stream_groups: Sequence[DefaultStreamGroup] = [], + realm_creation: bool = False, +) -> None: + realm = user_profile.realm + + mit_beta_user = realm.is_zephyr_mirror_realm + if prereg_user is not None: + streams: List[Stream] = list(prereg_user.streams.all()) + acting_user: Optional[UserProfile] = prereg_user.referred_by + + # A PregistrationUser should not be used for another UserProfile + assert prereg_user.created_user is None, "PregistrationUser should not be reused" + else: + streams = [] + acting_user = None + + # If the user's invitation didn't explicitly list some streams, we + # add the default streams + if len(streams) == 0: + streams = get_default_subs(user_profile) + + for default_stream_group in default_stream_groups: + default_stream_group_streams = default_stream_group.streams.all() + for stream in default_stream_group_streams: + if stream not in streams: + streams.append(stream) + + bulk_add_subscriptions( + realm, + streams, + [user_profile], + from_user_creation=True, + acting_user=acting_user, + ) + + add_new_user_history(user_profile, streams) + + # mit_beta_users don't have a referred_by field + if ( + not mit_beta_user + and prereg_user is not None + and prereg_user.referred_by is not None + and prereg_user.referred_by.is_active + ): + # This is a cross-realm private message. + with override_language(prereg_user.referred_by.default_language): + internal_send_private_message( + get_system_bot(settings.NOTIFICATION_BOT, prereg_user.referred_by.realm_id), + prereg_user.referred_by, + _("{user} accepted your invitation to join Zulip!").format( + user=f"{user_profile.full_name} <`{user_profile.email}`>" + ), + ) + + # Revoke all preregistration users except prereg_user, and link prereg_user to + # the created user + if prereg_user is None: + assert not realm_creation, "realm_creation should only happen with a PreregistrationUser" + + if prereg_user is not None: + prereg_user.status = confirmation_settings.STATUS_ACTIVE + prereg_user.created_user = user_profile + prereg_user.save(update_fields=["status", "created_user"]) + + # In the special case of realm creation, there can be no additional PreregistrationUser + # for us to want to modify - because other realm_creation PreregistrationUsers should be + # left usable for creating different realms. + if not realm_creation: + # Mark any other PreregistrationUsers in the realm that are STATUS_ACTIVE as + # inactive so we can keep track of the PreregistrationUser we + # actually used for analytics. + if prereg_user is not None: + PreregistrationUser.objects.filter( + email__iexact=user_profile.delivery_email, realm=user_profile.realm + ).exclude(id=prereg_user.id).update(status=confirmation_settings.STATUS_REVOKED) + else: + PreregistrationUser.objects.filter( + email__iexact=user_profile.delivery_email, realm=user_profile.realm + ).update(status=confirmation_settings.STATUS_REVOKED) + + if prereg_user is not None and prereg_user.referred_by is not None: + notify_invites_changed(user_profile.realm) + + notify_new_user(user_profile) + # Clear any scheduled invitation emails to prevent them + # from being sent after the user is created. + clear_scheduled_invitation_emails(user_profile.delivery_email) + if realm.send_welcome_emails: + enqueue_welcome_emails(user_profile, realm_creation) + + # We have an import loop here; it's intentional, because we want + # to keep all the onboarding code in zerver/lib/onboarding.py. + from zerver.lib.onboarding import send_initial_pms + + send_initial_pms(user_profile) + + +def notify_created_user(user_profile: UserProfile) -> None: + user_row = user_profile_to_user_row(user_profile) + person = format_user_row( + user_profile.realm, + user_profile, + user_row, + # Since we don't know what the client + # supports at this point in the code, we + # just assume client_gravatar and + # user_avatar_url_field_optional = False :( + client_gravatar=False, + user_avatar_url_field_optional=False, + # We assume there's no custom profile + # field data for a new user; initial + # values are expected to be added in a + # later event. + custom_profile_field_data={}, + ) + event: Dict[str, Any] = dict(type="realm_user", op="add", person=person) + send_event(user_profile.realm, event, active_user_ids(user_profile.realm_id)) + + +def created_bot_event(user_profile: UserProfile) -> Dict[str, Any]: + def stream_name(stream: Optional[Stream]) -> Optional[str]: + if not stream: + return None + return stream.name + + default_sending_stream_name = stream_name(user_profile.default_sending_stream) + default_events_register_stream_name = stream_name(user_profile.default_events_register_stream) + + bot = dict( + email=user_profile.email, + user_id=user_profile.id, + full_name=user_profile.full_name, + bot_type=user_profile.bot_type, + is_active=user_profile.is_active, + api_key=get_api_key(user_profile), + default_sending_stream=default_sending_stream_name, + default_events_register_stream=default_events_register_stream_name, + default_all_public_streams=user_profile.default_all_public_streams, + avatar_url=avatar_url(user_profile), + services=get_service_dicts_for_bot(user_profile.id), + ) + + # Set the owner key only when the bot has an owner. + # The default bots don't have an owner. So don't + # set the owner key while reactivating them. + if user_profile.bot_owner is not None: + bot["owner_id"] = user_profile.bot_owner.id + + return dict(type="realm_bot", op="add", bot=bot) + + +def notify_created_bot(user_profile: UserProfile) -> None: + event = created_bot_event(user_profile) + send_event(user_profile.realm, event, bot_owner_user_ids(user_profile)) + + +def do_create_user( + email: str, + password: Optional[str], + realm: Realm, + full_name: str, + bot_type: Optional[int] = None, + role: Optional[int] = None, + bot_owner: Optional[UserProfile] = None, + tos_version: Optional[str] = None, + timezone: str = "", + avatar_source: str = UserProfile.AVATAR_FROM_GRAVATAR, + default_sending_stream: Optional[Stream] = None, + default_events_register_stream: Optional[Stream] = None, + default_all_public_streams: Optional[bool] = None, + prereg_user: Optional[PreregistrationUser] = None, + default_stream_groups: Sequence[DefaultStreamGroup] = [], + source_profile: Optional[UserProfile] = None, + realm_creation: bool = False, + *, + acting_user: Optional[UserProfile], + enable_marketing_emails: bool = True, +) -> UserProfile: + with transaction.atomic(): + user_profile = create_user( + email=email, + password=password, + realm=realm, + full_name=full_name, + role=role, + bot_type=bot_type, + bot_owner=bot_owner, + tos_version=tos_version, + timezone=timezone, + avatar_source=avatar_source, + default_sending_stream=default_sending_stream, + default_events_register_stream=default_events_register_stream, + default_all_public_streams=default_all_public_streams, + source_profile=source_profile, + enable_marketing_emails=enable_marketing_emails, + ) + + event_time = user_profile.date_joined + if not acting_user: + acting_user = user_profile + RealmAuditLog.objects.create( + realm=user_profile.realm, + acting_user=acting_user, + modified_user=user_profile, + event_type=RealmAuditLog.USER_CREATED, + event_time=event_time, + extra_data=orjson.dumps( + { + RealmAuditLog.ROLE_COUNT: realm_user_count_by_role(user_profile.realm), + } + ).decode(), + ) + + if realm_creation: + # If this user just created a realm, make sure they are + # properly tagged as the creator of the realm. + realm_creation_audit_log = ( + RealmAuditLog.objects.filter(event_type=RealmAuditLog.REALM_CREATED, realm=realm) + .order_by("id") + .last() + ) + assert realm_creation_audit_log is not None + realm_creation_audit_log.acting_user = user_profile + realm_creation_audit_log.save(update_fields=["acting_user"]) + + do_increment_logging_stat( + user_profile.realm, + COUNT_STATS["active_users_log:is_bot:day"], + user_profile.is_bot, + event_time, + ) + if settings.BILLING_ENABLED: + update_license_ledger_if_needed(user_profile.realm, event_time) + + system_user_group = get_system_user_group_for_user(user_profile) + UserGroupMembership.objects.create(user_profile=user_profile, user_group=system_user_group) + + if user_profile.role == UserProfile.ROLE_MEMBER and not user_profile.is_provisional_member: + full_members_system_group = UserGroup.objects.get( + name="@role:fullmembers", realm=user_profile.realm, is_system_group=True + ) + UserGroupMembership.objects.create( + user_profile=user_profile, user_group=full_members_system_group + ) + + # Note that for bots, the caller will send an additional event + # with bot-specific info like services. + notify_created_user(user_profile) + + do_send_user_group_members_update_event("add_members", system_user_group, [user_profile.id]) + if user_profile.role == UserProfile.ROLE_MEMBER and not user_profile.is_provisional_member: + do_send_user_group_members_update_event( + "add_members", full_members_system_group, [user_profile.id] + ) + + if bot_type is None: + process_new_human_user( + user_profile, + prereg_user=prereg_user, + default_stream_groups=default_stream_groups, + realm_creation=realm_creation, + ) + + if realm_creation: + assert realm.signup_notifications_stream is not None + bulk_add_subscriptions( + realm, [realm.signup_notifications_stream], [user_profile], acting_user=None + ) + + from zerver.lib.onboarding import send_initial_realm_messages + + send_initial_realm_messages(realm) + + return user_profile + + +def do_activate_mirror_dummy_user( + user_profile: UserProfile, *, acting_user: Optional[UserProfile] +) -> None: + """Called to have a user "take over" a "mirror dummy" user + (i.e. is_mirror_dummy=True) account when they sign up with the + same email address. + + Essentially, the result should be as though we had created the + UserProfile just now with do_create_user, except that the mirror + dummy user may appear as the recipient or sender of messages from + before their account was fully created. + + TODO: This function likely has bugs resulting from this being a + parallel code path to do_create_user; e.g. it likely does not + handle preferences or default streams properly. + """ + with transaction.atomic(): + change_user_is_active(user_profile, True) + user_profile.is_mirror_dummy = False + user_profile.set_unusable_password() + user_profile.date_joined = timezone_now() + user_profile.tos_version = settings.TERMS_OF_SERVICE_VERSION + user_profile.save( + update_fields=["date_joined", "password", "is_mirror_dummy", "tos_version"] + ) + + event_time = user_profile.date_joined + RealmAuditLog.objects.create( + realm=user_profile.realm, + modified_user=user_profile, + acting_user=acting_user, + event_type=RealmAuditLog.USER_ACTIVATED, + event_time=event_time, + extra_data=orjson.dumps( + { + RealmAuditLog.ROLE_COUNT: realm_user_count_by_role(user_profile.realm), + } + ).decode(), + ) + do_increment_logging_stat( + user_profile.realm, + COUNT_STATS["active_users_log:is_bot:day"], + user_profile.is_bot, + event_time, + ) + if settings.BILLING_ENABLED: + update_license_ledger_if_needed(user_profile.realm, event_time) + + notify_created_user(user_profile) + + +def do_reactivate_user(user_profile: UserProfile, *, acting_user: Optional[UserProfile]) -> None: + """Reactivate a user that had previously been deactivated""" + with transaction.atomic(): + change_user_is_active(user_profile, True) + + event_time = timezone_now() + RealmAuditLog.objects.create( + realm=user_profile.realm, + modified_user=user_profile, + acting_user=acting_user, + event_type=RealmAuditLog.USER_REACTIVATED, + event_time=event_time, + extra_data=orjson.dumps( + { + RealmAuditLog.ROLE_COUNT: realm_user_count_by_role(user_profile.realm), + } + ).decode(), + ) + do_increment_logging_stat( + user_profile.realm, + COUNT_STATS["active_users_log:is_bot:day"], + user_profile.is_bot, + event_time, + ) + if settings.BILLING_ENABLED: + update_license_ledger_if_needed(user_profile.realm, event_time) + + notify_created_user(user_profile) + + if user_profile.is_bot: + notify_created_bot(user_profile) + + subscribed_recipient_ids = Subscription.objects.filter( + user_profile_id=user_profile.id, active=True, recipient__type=Recipient.STREAM + ).values_list("recipient__type_id", flat=True) + subscribed_streams = Stream.objects.filter(id__in=subscribed_recipient_ids, deactivated=False) + subscriber_peer_info = bulk_get_subscriber_peer_info( + realm=user_profile.realm, + streams=subscribed_streams, + ) + + altered_user_dict: Dict[int, Set[int]] = defaultdict(set) + for stream in subscribed_streams: + altered_user_dict[stream.id] = {user_profile.id} + + stream_dict = {stream.id: stream for stream in subscribed_streams} + + send_peer_subscriber_events( + op="peer_add", + realm=user_profile.realm, + altered_user_dict=altered_user_dict, + stream_dict=stream_dict, + private_peer_dict=subscriber_peer_info.private_peer_dict, + ) + + +def get_default_subs(user_profile: UserProfile) -> List[Stream]: + # Right now default streams are realm-wide. This wrapper gives us flexibility + # to some day further customize how we set up default streams for new users. + return get_default_streams_for_realm(user_profile.realm_id) diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index f2e6339315..101c3e1c81 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -16,33 +16,23 @@ from django.utils.translation import override as override_language from typing_extensions import TypedDict from analytics.lib.counts import COUNT_STATS, do_increment_logging_stat -from confirmation import settings as confirmation_settings from confirmation.models import Confirmation, create_confirmation_link, generate_key +from zerver.actions.create_user import create_historical_user_messages, created_bot_event from zerver.actions.custom_profile_fields import do_remove_realm_custom_profile_fields -from zerver.actions.default_streams import get_default_streams_for_realm -from zerver.actions.invites import notify_invites_changed from zerver.actions.message_send import ( filter_presence_idle_user_ids, get_recipient_info, - internal_send_private_message, internal_send_stream_message, render_incoming_message, ) -from zerver.actions.streams import bulk_add_subscriptions, send_peer_subscriber_events from zerver.actions.uploads import check_attachment_reference_change -from zerver.actions.user_groups import ( - do_send_user_group_members_update_event, - update_users_in_full_members_system_group, -) +from zerver.actions.user_groups import update_users_in_full_members_system_group from zerver.actions.user_settings import do_delete_avatar_image, send_user_email_update_event from zerver.actions.user_topics import do_mute_topic, do_unmute_topic -from zerver.actions.users import change_user_is_active, get_service_dicts_for_bot from zerver.lib import retention as retention -from zerver.lib.avatar import avatar_url from zerver.lib.bulk_create import create_users from zerver.lib.cache import flush_user_profile -from zerver.lib.create_user import create_user, get_display_email_address -from zerver.lib.email_notifications import enqueue_welcome_emails +from zerver.lib.create_user import get_display_email_address from zerver.lib.email_validation import email_reserved_for_system_bots_error from zerver.lib.emoji import check_emoji_request, emoji_name_to_emoji_code from zerver.lib.exceptions import JsonableError @@ -62,15 +52,10 @@ from zerver.lib.message import ( ) from zerver.lib.queue import queue_json_publish from zerver.lib.retention import move_messages_to_archive -from zerver.lib.send_email import ( - FromAddress, - clear_scheduled_invitation_emails, - send_email_to_admins, -) +from zerver.lib.send_email import FromAddress, send_email_to_admins from zerver.lib.server_initialization import create_internal_realm, server_initialized from zerver.lib.sessions import delete_user_sessions from zerver.lib.stream_subscription import ( - bulk_get_subscriber_peer_info, get_active_subscriptions_for_stream_id, subscriber_ids_with_stream_history_access, ) @@ -95,25 +80,19 @@ from zerver.lib.topic import ( update_messages_for_topic_edit, ) from zerver.lib.types import EditHistoryEvent -from zerver.lib.user_counts import realm_user_count, realm_user_count_by_role -from zerver.lib.user_groups import ( - create_system_user_groups_for_realm, - get_system_user_group_for_user, -) +from zerver.lib.user_counts import realm_user_count_by_role +from zerver.lib.user_groups import create_system_user_groups_for_realm from zerver.lib.user_message import UserMessageLite, bulk_insert_ums from zerver.lib.user_mutes import add_user_mute, get_user_mutes from zerver.lib.user_topics import get_users_muting_topic, remove_topic_mute -from zerver.lib.users import format_user_row, get_api_key, user_profile_to_user_row from zerver.lib.utils import log_statsd_event from zerver.lib.widget import is_widget_message from zerver.models import ( ArchivedAttachment, Attachment, DefaultStream, - DefaultStreamGroup, Message, MutedUser, - PreregistrationUser, Reaction, Realm, RealmAuditLog, @@ -122,9 +101,6 @@ from zerver.models import ( Recipient, ScheduledEmail, Stream, - Subscription, - UserGroup, - UserGroupMembership, UserMessage, UserProfile, active_user_ids, @@ -138,525 +114,13 @@ from zerver.models import ( from zerver.tornado.django_api import send_event if settings.BILLING_ENABLED: - from corporate.lib.stripe import ( - downgrade_now_without_creating_additional_invoices, - update_license_ledger_if_needed, - ) - - -ONBOARDING_TOTAL_MESSAGES = 1000 -ONBOARDING_UNREAD_MESSAGES = 20 -ONBOARDING_RECENT_TIMEDELTA = datetime.timedelta(weeks=1) - - -def create_historical_user_messages(*, user_id: int, message_ids: List[int]) -> None: - # Users can see and interact with messages sent to streams with - # public history for which they do not have a UserMessage because - # they were not a subscriber at the time the message was sent. - # In order to add emoji reactions or mutate message flags for - # those messages, we create UserMessage objects for those messages; - # these have the special historical flag which keeps track of the - # fact that the user did not receive the message at the time it was sent. - for message_id in message_ids: - UserMessage.objects.create( - user_profile_id=user_id, - message_id=message_id, - flags=UserMessage.flags.historical | UserMessage.flags.read, - ) + from corporate.lib.stripe import downgrade_now_without_creating_additional_invoices def subscriber_info(user_id: int) -> Dict[str, Any]: return {"id": user_id, "flags": ["read"]} -def send_message_to_signup_notification_stream( - sender: UserProfile, realm: Realm, message: str, topic_name: str = _("signups") -) -> None: - signup_notifications_stream = realm.get_signup_notifications_stream() - if signup_notifications_stream is None: - return - - with override_language(realm.default_language): - internal_send_stream_message(sender, signup_notifications_stream, topic_name, message) - - -def notify_new_user(user_profile: UserProfile) -> None: - user_count = realm_user_count(user_profile.realm) - sender_email = settings.NOTIFICATION_BOT - sender = get_system_bot(sender_email, user_profile.realm_id) - - is_first_user = user_count == 1 - if not is_first_user: - message = _("{user} just signed up for Zulip. (total: {user_count})").format( - user=silent_mention_syntax_for_user(user_profile), user_count=user_count - ) - - if settings.BILLING_ENABLED: - from corporate.lib.registration import generate_licenses_low_warning_message_if_required - - licenses_low_warning_message = generate_licenses_low_warning_message_if_required( - user_profile.realm - ) - if licenses_low_warning_message is not None: - message += "\n" - message += licenses_low_warning_message - - send_message_to_signup_notification_stream(sender, user_profile.realm, message) - - # We also send a notification to the Zulip administrative realm - admin_realm = get_realm(settings.SYSTEM_BOT_REALM) - admin_realm_sender = get_system_bot(sender_email, admin_realm.id) - try: - # Check whether the stream exists - signups_stream = get_signups_stream(admin_realm) - # We intentionally use the same strings as above to avoid translation burden. - message = _("{user} just signed up for Zulip. (total: {user_count})").format( - user=f"{user_profile.full_name} <`{user_profile.email}`>", user_count=user_count - ) - internal_send_stream_message( - admin_realm_sender, signups_stream, user_profile.realm.display_subdomain, message - ) - - except Stream.DoesNotExist: - # 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 - - -def add_new_user_history(user_profile: UserProfile, streams: Iterable[Stream]) -> None: - """Give you the last ONBOARDING_TOTAL_MESSAGES messages on your public - streams, so you have something to look at in your home view once - you finish the tutorial. The most recent ONBOARDING_UNREAD_MESSAGES - are marked unread. - """ - one_week_ago = timezone_now() - ONBOARDING_RECENT_TIMEDELTA - - recipient_ids = [stream.recipient_id for stream in streams if not stream.invite_only] - recent_messages = Message.objects.filter( - recipient_id__in=recipient_ids, date_sent__gt=one_week_ago - ).order_by("-id") - message_ids_to_use = list( - reversed(recent_messages.values_list("id", flat=True)[0:ONBOARDING_TOTAL_MESSAGES]) - ) - if len(message_ids_to_use) == 0: - return - - # Handle the race condition where a message arrives between - # bulk_add_subscriptions above and the Message query just above - already_ids = set( - UserMessage.objects.filter( - message_id__in=message_ids_to_use, user_profile=user_profile - ).values_list("message_id", flat=True) - ) - - # Mark the newest ONBOARDING_UNREAD_MESSAGES as unread. - marked_unread = 0 - ums_to_create = [] - for message_id in reversed(message_ids_to_use): - if message_id in already_ids: - continue - - um = UserMessage(user_profile=user_profile, message_id=message_id) - if marked_unread < ONBOARDING_UNREAD_MESSAGES: - marked_unread += 1 - else: - um.flags = UserMessage.flags.read - ums_to_create.append(um) - - UserMessage.objects.bulk_create(reversed(ums_to_create)) - - -# Does the processing for a new user account: -# * Subscribes to default/invitation streams -# * Fills in some recent historical messages -# * Notifies other users in realm and Zulip about the signup -# * Deactivates PreregistrationUser objects -def process_new_human_user( - user_profile: UserProfile, - prereg_user: Optional[PreregistrationUser] = None, - default_stream_groups: Sequence[DefaultStreamGroup] = [], - realm_creation: bool = False, -) -> None: - realm = user_profile.realm - - mit_beta_user = realm.is_zephyr_mirror_realm - if prereg_user is not None: - streams: List[Stream] = list(prereg_user.streams.all()) - acting_user: Optional[UserProfile] = prereg_user.referred_by - - # A PregistrationUser should not be used for another UserProfile - assert prereg_user.created_user is None, "PregistrationUser should not be reused" - else: - streams = [] - acting_user = None - - # If the user's invitation didn't explicitly list some streams, we - # add the default streams - if len(streams) == 0: - streams = get_default_subs(user_profile) - - for default_stream_group in default_stream_groups: - default_stream_group_streams = default_stream_group.streams.all() - for stream in default_stream_group_streams: - if stream not in streams: - streams.append(stream) - - bulk_add_subscriptions( - realm, - streams, - [user_profile], - from_user_creation=True, - acting_user=acting_user, - ) - - add_new_user_history(user_profile, streams) - - # mit_beta_users don't have a referred_by field - if ( - not mit_beta_user - and prereg_user is not None - and prereg_user.referred_by is not None - and prereg_user.referred_by.is_active - ): - # This is a cross-realm private message. - with override_language(prereg_user.referred_by.default_language): - internal_send_private_message( - get_system_bot(settings.NOTIFICATION_BOT, prereg_user.referred_by.realm_id), - prereg_user.referred_by, - _("{user} accepted your invitation to join Zulip!").format( - user=f"{user_profile.full_name} <`{user_profile.email}`>" - ), - ) - - # Revoke all preregistration users except prereg_user, and link prereg_user to - # the created user - if prereg_user is None: - assert not realm_creation, "realm_creation should only happen with a PreregistrationUser" - - if prereg_user is not None: - prereg_user.status = confirmation_settings.STATUS_ACTIVE - prereg_user.created_user = user_profile - prereg_user.save(update_fields=["status", "created_user"]) - - # In the special case of realm creation, there can be no additional PreregistrationUser - # for us to want to modify - because other realm_creation PreregistrationUsers should be - # left usable for creating different realms. - if not realm_creation: - # Mark any other PreregistrationUsers in the realm that are STATUS_ACTIVE as - # inactive so we can keep track of the PreregistrationUser we - # actually used for analytics. - if prereg_user is not None: - PreregistrationUser.objects.filter( - email__iexact=user_profile.delivery_email, realm=user_profile.realm - ).exclude(id=prereg_user.id).update(status=confirmation_settings.STATUS_REVOKED) - else: - PreregistrationUser.objects.filter( - email__iexact=user_profile.delivery_email, realm=user_profile.realm - ).update(status=confirmation_settings.STATUS_REVOKED) - - if prereg_user is not None and prereg_user.referred_by is not None: - notify_invites_changed(user_profile.realm) - - notify_new_user(user_profile) - # Clear any scheduled invitation emails to prevent them - # from being sent after the user is created. - clear_scheduled_invitation_emails(user_profile.delivery_email) - if realm.send_welcome_emails: - enqueue_welcome_emails(user_profile, realm_creation) - - # We have an import loop here; it's intentional, because we want - # to keep all the onboarding code in zerver/lib/onboarding.py. - from zerver.lib.onboarding import send_initial_pms - - send_initial_pms(user_profile) - - -def notify_created_user(user_profile: UserProfile) -> None: - user_row = user_profile_to_user_row(user_profile) - person = format_user_row( - user_profile.realm, - user_profile, - user_row, - # Since we don't know what the client - # supports at this point in the code, we - # just assume client_gravatar and - # user_avatar_url_field_optional = False :( - client_gravatar=False, - user_avatar_url_field_optional=False, - # We assume there's no custom profile - # field data for a new user; initial - # values are expected to be added in a - # later event. - custom_profile_field_data={}, - ) - event: Dict[str, Any] = dict(type="realm_user", op="add", person=person) - send_event(user_profile.realm, event, active_user_ids(user_profile.realm_id)) - - -def created_bot_event(user_profile: UserProfile) -> Dict[str, Any]: - def stream_name(stream: Optional[Stream]) -> Optional[str]: - if not stream: - return None - return stream.name - - default_sending_stream_name = stream_name(user_profile.default_sending_stream) - default_events_register_stream_name = stream_name(user_profile.default_events_register_stream) - - bot = dict( - email=user_profile.email, - user_id=user_profile.id, - full_name=user_profile.full_name, - bot_type=user_profile.bot_type, - is_active=user_profile.is_active, - api_key=get_api_key(user_profile), - default_sending_stream=default_sending_stream_name, - default_events_register_stream=default_events_register_stream_name, - default_all_public_streams=user_profile.default_all_public_streams, - avatar_url=avatar_url(user_profile), - services=get_service_dicts_for_bot(user_profile.id), - ) - - # Set the owner key only when the bot has an owner. - # The default bots don't have an owner. So don't - # set the owner key while reactivating them. - if user_profile.bot_owner is not None: - bot["owner_id"] = user_profile.bot_owner.id - - return dict(type="realm_bot", op="add", bot=bot) - - -def notify_created_bot(user_profile: UserProfile) -> None: - event = created_bot_event(user_profile) - send_event(user_profile.realm, event, bot_owner_user_ids(user_profile)) - - -def do_create_user( - email: str, - password: Optional[str], - realm: Realm, - full_name: str, - bot_type: Optional[int] = None, - role: Optional[int] = None, - bot_owner: Optional[UserProfile] = None, - tos_version: Optional[str] = None, - timezone: str = "", - avatar_source: str = UserProfile.AVATAR_FROM_GRAVATAR, - default_sending_stream: Optional[Stream] = None, - default_events_register_stream: Optional[Stream] = None, - default_all_public_streams: Optional[bool] = None, - prereg_user: Optional[PreregistrationUser] = None, - default_stream_groups: Sequence[DefaultStreamGroup] = [], - source_profile: Optional[UserProfile] = None, - realm_creation: bool = False, - *, - acting_user: Optional[UserProfile], - enable_marketing_emails: bool = True, -) -> UserProfile: - with transaction.atomic(): - user_profile = create_user( - email=email, - password=password, - realm=realm, - full_name=full_name, - role=role, - bot_type=bot_type, - bot_owner=bot_owner, - tos_version=tos_version, - timezone=timezone, - avatar_source=avatar_source, - default_sending_stream=default_sending_stream, - default_events_register_stream=default_events_register_stream, - default_all_public_streams=default_all_public_streams, - source_profile=source_profile, - enable_marketing_emails=enable_marketing_emails, - ) - - event_time = user_profile.date_joined - if not acting_user: - acting_user = user_profile - RealmAuditLog.objects.create( - realm=user_profile.realm, - acting_user=acting_user, - modified_user=user_profile, - event_type=RealmAuditLog.USER_CREATED, - event_time=event_time, - extra_data=orjson.dumps( - { - RealmAuditLog.ROLE_COUNT: realm_user_count_by_role(user_profile.realm), - } - ).decode(), - ) - - if realm_creation: - # If this user just created a realm, make sure they are - # properly tagged as the creator of the realm. - realm_creation_audit_log = ( - RealmAuditLog.objects.filter(event_type=RealmAuditLog.REALM_CREATED, realm=realm) - .order_by("id") - .last() - ) - assert realm_creation_audit_log is not None - realm_creation_audit_log.acting_user = user_profile - realm_creation_audit_log.save(update_fields=["acting_user"]) - - do_increment_logging_stat( - user_profile.realm, - COUNT_STATS["active_users_log:is_bot:day"], - user_profile.is_bot, - event_time, - ) - if settings.BILLING_ENABLED: - update_license_ledger_if_needed(user_profile.realm, event_time) - - system_user_group = get_system_user_group_for_user(user_profile) - UserGroupMembership.objects.create(user_profile=user_profile, user_group=system_user_group) - - if user_profile.role == UserProfile.ROLE_MEMBER and not user_profile.is_provisional_member: - full_members_system_group = UserGroup.objects.get( - name="@role:fullmembers", realm=user_profile.realm, is_system_group=True - ) - UserGroupMembership.objects.create( - user_profile=user_profile, user_group=full_members_system_group - ) - - # Note that for bots, the caller will send an additional event - # with bot-specific info like services. - notify_created_user(user_profile) - - do_send_user_group_members_update_event("add_members", system_user_group, [user_profile.id]) - if user_profile.role == UserProfile.ROLE_MEMBER and not user_profile.is_provisional_member: - do_send_user_group_members_update_event( - "add_members", full_members_system_group, [user_profile.id] - ) - - if bot_type is None: - process_new_human_user( - user_profile, - prereg_user=prereg_user, - default_stream_groups=default_stream_groups, - realm_creation=realm_creation, - ) - - if realm_creation: - assert realm.signup_notifications_stream is not None - bulk_add_subscriptions( - realm, [realm.signup_notifications_stream], [user_profile], acting_user=None - ) - - from zerver.lib.onboarding import send_initial_realm_messages - - send_initial_realm_messages(realm) - - return user_profile - - -def do_activate_mirror_dummy_user( - user_profile: UserProfile, *, acting_user: Optional[UserProfile] -) -> None: - """Called to have a user "take over" a "mirror dummy" user - (i.e. is_mirror_dummy=True) account when they sign up with the - same email address. - - Essentially, the result should be as though we had created the - UserProfile just now with do_create_user, except that the mirror - dummy user may appear as the recipient or sender of messages from - before their account was fully created. - - TODO: This function likely has bugs resulting from this being a - parallel code path to do_create_user; e.g. it likely does not - handle preferences or default streams properly. - """ - with transaction.atomic(): - change_user_is_active(user_profile, True) - user_profile.is_mirror_dummy = False - user_profile.set_unusable_password() - user_profile.date_joined = timezone_now() - user_profile.tos_version = settings.TERMS_OF_SERVICE_VERSION - user_profile.save( - update_fields=["date_joined", "password", "is_mirror_dummy", "tos_version"] - ) - - event_time = user_profile.date_joined - RealmAuditLog.objects.create( - realm=user_profile.realm, - modified_user=user_profile, - acting_user=acting_user, - event_type=RealmAuditLog.USER_ACTIVATED, - event_time=event_time, - extra_data=orjson.dumps( - { - RealmAuditLog.ROLE_COUNT: realm_user_count_by_role(user_profile.realm), - } - ).decode(), - ) - do_increment_logging_stat( - user_profile.realm, - COUNT_STATS["active_users_log:is_bot:day"], - user_profile.is_bot, - event_time, - ) - if settings.BILLING_ENABLED: - update_license_ledger_if_needed(user_profile.realm, event_time) - - notify_created_user(user_profile) - - -def do_reactivate_user(user_profile: UserProfile, *, acting_user: Optional[UserProfile]) -> None: - """Reactivate a user that had previously been deactivated""" - with transaction.atomic(): - change_user_is_active(user_profile, True) - - event_time = timezone_now() - RealmAuditLog.objects.create( - realm=user_profile.realm, - modified_user=user_profile, - acting_user=acting_user, - event_type=RealmAuditLog.USER_REACTIVATED, - event_time=event_time, - extra_data=orjson.dumps( - { - RealmAuditLog.ROLE_COUNT: realm_user_count_by_role(user_profile.realm), - } - ).decode(), - ) - do_increment_logging_stat( - user_profile.realm, - COUNT_STATS["active_users_log:is_bot:day"], - user_profile.is_bot, - event_time, - ) - if settings.BILLING_ENABLED: - update_license_ledger_if_needed(user_profile.realm, event_time) - - notify_created_user(user_profile) - - if user_profile.is_bot: - notify_created_bot(user_profile) - - subscribed_recipient_ids = Subscription.objects.filter( - user_profile_id=user_profile.id, active=True, recipient__type=Recipient.STREAM - ).values_list("recipient__type_id", flat=True) - subscribed_streams = Stream.objects.filter(id__in=subscribed_recipient_ids, deactivated=False) - subscriber_peer_info = bulk_get_subscriber_peer_info( - realm=user_profile.realm, - streams=subscribed_streams, - ) - - altered_user_dict: Dict[int, Set[int]] = defaultdict(set) - for stream in subscribed_streams: - altered_user_dict[stream.id] = {user_profile.id} - - stream_dict = {stream.id: stream for stream in subscribed_streams} - - send_peer_subscriber_events( - op="peer_add", - realm=user_profile.realm, - altered_user_dict=altered_user_dict, - stream_dict=stream_dict, - private_peer_dict=subscriber_peer_info.private_peer_dict, - ) - - def active_humans_in_realm(realm: Realm) -> Sequence[UserProfile]: return UserProfile.objects.filter(realm=realm, is_active=True, is_bot=False) @@ -1870,12 +1334,6 @@ def do_create_realm( return realm -def get_default_subs(user_profile: UserProfile) -> List[Stream]: - # Right now default streams are realm-wide. This wrapper gives us flexibility - # to some day further customize how we set up default streams for new users. - return get_default_streams_for_realm(user_profile.realm_id) - - @dataclass class ReadMessagesEvent: messages: List[int] diff --git a/zerver/lib/scim.py b/zerver/lib/scim.py index b45f07b7f6..a3670b4f19 100644 --- a/zerver/lib/scim.py +++ b/zerver/lib/scim.py @@ -9,9 +9,9 @@ from django.http import HttpRequest from django_scim.adapters import SCIMUser from scim2_filter_parser.attr_paths import AttrPath +from zerver.actions.create_user import do_create_user, do_reactivate_user from zerver.actions.user_settings import check_change_full_name, do_change_user_delivery_email from zerver.actions.users import do_deactivate_user -from zerver.lib.actions import do_create_user, do_reactivate_user from zerver.lib.email_validation import email_allowed_for_realm, validate_email_not_already_in_realm from zerver.lib.request import RequestNotes from zerver.lib.subdomains import get_subdomain diff --git a/zerver/management/commands/create_realm.py b/zerver/management/commands/create_realm.py index a42d706ac4..134f4b7663 100644 --- a/zerver/management/commands/create_realm.py +++ b/zerver/management/commands/create_realm.py @@ -3,7 +3,8 @@ from typing import Any from django.core.management.base import CommandError -from zerver.lib.actions import do_create_realm, do_create_user +from zerver.actions.create_user import do_create_user +from zerver.lib.actions import do_create_realm from zerver.lib.management import ZulipBaseCommand from zerver.models import UserProfile diff --git a/zerver/management/commands/create_user.py b/zerver/management/commands/create_user.py index fae7c4453c..d09f3eadac 100644 --- a/zerver/management/commands/create_user.py +++ b/zerver/management/commands/create_user.py @@ -4,7 +4,7 @@ from typing import Any from django.core.management.base import CommandError from django.db.utils import IntegrityError -from zerver.lib.actions import do_create_user +from zerver.actions.create_user import do_create_user from zerver.lib.management import ZulipBaseCommand diff --git a/zerver/openapi/curl_param_value_generators.py b/zerver/openapi/curl_param_value_generators.py index ac18d750fd..0a2b252592 100644 --- a/zerver/openapi/curl_param_value_generators.py +++ b/zerver/openapi/curl_param_value_generators.py @@ -10,10 +10,11 @@ from typing import Any, Callable, Dict, List, Optional, Set, Tuple from django.utils.timezone import now as timezone_now +from zerver.actions.create_user import do_create_user from zerver.actions.presence import update_user_presence from zerver.actions.realm_linkifiers import do_add_linkifier from zerver.actions.realm_playgrounds import do_add_realm_playground -from zerver.lib.actions import do_add_reaction, do_create_user +from zerver.lib.actions import do_add_reaction from zerver.lib.events import do_events_register from zerver.lib.initial_password import initial_password from zerver.lib.test_classes import ZulipTestCase diff --git a/zerver/tests/test_audit_log.py b/zerver/tests/test_audit_log.py index 7464d52ab4..2d96563421 100644 --- a/zerver/tests/test_audit_log.py +++ b/zerver/tests/test_audit_log.py @@ -6,6 +6,11 @@ from django.contrib.auth.password_validation import validate_password from django.utils.timezone import now as timezone_now from analytics.models import StreamCount +from zerver.actions.create_user import ( + do_activate_mirror_dummy_user, + do_create_user, + do_reactivate_user, +) from zerver.actions.realm_icon import do_change_icon_source from zerver.actions.realm_playgrounds import do_add_realm_playground, do_remove_realm_playground from zerver.actions.streams import ( @@ -25,17 +30,14 @@ from zerver.actions.user_settings import ( ) from zerver.actions.users import do_change_user_role, do_deactivate_user from zerver.lib.actions import ( - do_activate_mirror_dummy_user, do_add_realm_domain, do_change_bot_owner, do_change_default_all_public_streams, do_change_default_events_register_stream, do_change_default_sending_stream, do_change_realm_domain, - do_create_user, do_deactivate_realm, do_reactivate_realm, - do_reactivate_user, do_remove_realm_domain, do_set_realm_authentication_methods, do_set_realm_message_editing, diff --git a/zerver/tests/test_auth_backends.py b/zerver/tests/test_auth_backends.py index fa766bb3b8..f59734630c 100644 --- a/zerver/tests/test_auth_backends.py +++ b/zerver/tests/test_auth_backends.py @@ -40,15 +40,14 @@ from social_django.storage import BaseDjangoStorage from social_django.strategy import DjangoStrategy from confirmation.models import Confirmation, create_confirmation_link +from zerver.actions.create_user import do_create_user, do_reactivate_user from zerver.actions.invites import do_invite_users from zerver.actions.user_settings import do_change_password from zerver.actions.users import change_user_is_active, do_deactivate_user from zerver.lib.actions import ( do_create_realm, - do_create_user, do_deactivate_realm, do_reactivate_realm, - do_reactivate_user, do_set_realm_property, ) from zerver.lib.avatar import avatar_url diff --git a/zerver/tests/test_decorators.py b/zerver/tests/test_decorators.py index d98ee600d8..151fe66ade 100644 --- a/zerver/tests/test_decorators.py +++ b/zerver/tests/test_decorators.py @@ -14,6 +14,7 @@ from django.core.exceptions import ValidationError from django.http import HttpRequest, HttpResponse from django.utils.timezone import now as timezone_now +from zerver.actions.create_user import do_reactivate_user from zerver.actions.users import change_user_is_active, do_deactivate_user from zerver.decorator import ( authenticate_notify, @@ -33,7 +34,6 @@ from zerver.lib.actions import ( do_create_realm, do_deactivate_realm, do_reactivate_realm, - do_reactivate_user, do_set_realm_property, ) from zerver.lib.cache import dict_to_items_tuple, ignore_unhashable_lru_cache, items_tuple_to_dict diff --git a/zerver/tests/test_digest.py b/zerver/tests/test_digest.py index 49a53ea3f7..6fde653cf6 100644 --- a/zerver/tests/test_digest.py +++ b/zerver/tests/test_digest.py @@ -7,7 +7,7 @@ from django.test import override_settings from django.utils.timezone import now as timezone_now from confirmation.models import one_click_unsubscribe_link -from zerver.lib.actions import do_create_user +from zerver.actions.create_user import do_create_user from zerver.lib.digest import ( DigestTopic, _enqueue_emails_for_realm, diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index 843ad77ada..d4db54be71 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -14,6 +14,7 @@ import orjson from django.utils.timezone import now as timezone_now from zerver.actions.alert_words import do_add_alert_words, do_remove_alert_words +from zerver.actions.create_user import do_create_user, do_reactivate_user from zerver.actions.custom_profile_fields import ( do_remove_realm_custom_profile_field, do_update_user_custom_profile_data_if_changed, @@ -93,11 +94,9 @@ from zerver.lib.actions import ( do_change_default_sending_stream, do_change_realm_domain, do_change_realm_plan_type, - do_create_user, do_deactivate_realm, do_delete_messages, do_mute_user, - do_reactivate_user, do_remove_reaction, do_remove_realm_domain, do_set_realm_authentication_methods, diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py index b50c4cc8ce..7696d491a1 100644 --- a/zerver/tests/test_home.py +++ b/zerver/tests/test_home.py @@ -13,8 +13,9 @@ from django.test import override_settings from django.utils.timezone import now as timezone_now from corporate.models import Customer, CustomerPlan +from zerver.actions.create_user import do_create_user from zerver.actions.users import change_user_is_active -from zerver.lib.actions import do_change_realm_plan_type, do_create_user +from zerver.lib.actions import do_change_realm_plan_type from zerver.lib.compatibility import LAST_SERVER_UPGRADE_TIME, is_outdated_server from zerver.lib.home import ( get_billing_info, diff --git a/zerver/tests/test_hotspots.py b/zerver/tests/test_hotspots.py index 7c17b4a151..27d142f43b 100644 --- a/zerver/tests/test_hotspots.py +++ b/zerver/tests/test_hotspots.py @@ -1,5 +1,5 @@ +from zerver.actions.create_user import do_create_user from zerver.actions.hotspots import do_mark_hotspot_as_read -from zerver.lib.actions import do_create_user from zerver.lib.hotspots import ALL_HOTSPOTS, INTRO_HOTSPOTS, get_next_hotspots from zerver.lib.test_classes import ZulipTestCase from zerver.models import UserHotspot, UserProfile, get_realm diff --git a/zerver/tests/test_import_export.py b/zerver/tests/test_import_export.py index 32ed9f90d7..6698b200d3 100644 --- a/zerver/tests/test_import_export.py +++ b/zerver/tests/test_import_export.py @@ -11,6 +11,7 @@ from django.utils.timezone import now as timezone_now from analytics.models import UserCount from zerver.actions.alert_words import do_add_alert_words +from zerver.actions.create_user import do_create_user from zerver.actions.custom_profile_fields import ( do_update_user_custom_profile_data_if_changed, try_add_realm_custom_profile_field, @@ -27,7 +28,6 @@ from zerver.lib.actions import ( check_add_reaction, do_add_reaction, do_change_realm_plan_type, - do_create_user, do_mute_user, ) from zerver.lib.avatar_hash import user_avatar_path diff --git a/zerver/tests/test_management_commands.py b/zerver/tests/test_management_commands.py index 4cba1a63fb..9996d4b8b4 100644 --- a/zerver/tests/test_management_commands.py +++ b/zerver/tests/test_management_commands.py @@ -13,7 +13,8 @@ from django.test import override_settings from django.utils.timezone import now as timezone_now from confirmation.models import RealmCreationKey, generate_realm_creation_url -from zerver.lib.actions import do_add_reaction, do_create_user +from zerver.actions.create_user import do_create_user +from zerver.lib.actions import do_add_reaction from zerver.lib.management import ZulipBaseCommand, check_config from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import most_recent_message, stdout_suppressed diff --git a/zerver/tests/test_message_send.py b/zerver/tests/test_message_send.py index 48809f9973..b63d934595 100644 --- a/zerver/tests/test_message_send.py +++ b/zerver/tests/test_message_send.py @@ -10,6 +10,7 @@ from django.http import HttpResponse from django.test import override_settings from django.utils.timezone import now as timezone_now +from zerver.actions.create_user import do_create_user from zerver.actions.message_send import ( build_message_send_dict, check_message, @@ -27,12 +28,7 @@ from zerver.actions.message_send import ( ) from zerver.actions.streams import do_change_stream_post_policy from zerver.actions.users import do_change_can_forge_sender, do_deactivate_user -from zerver.lib.actions import ( - do_add_realm_domain, - do_create_realm, - do_create_user, - do_set_realm_property, -) +from zerver.lib.actions import do_add_realm_domain, do_create_realm, do_set_realm_property from zerver.lib.addressee import Addressee from zerver.lib.cache import cache_delete, get_stream_cache_key from zerver.lib.exceptions import JsonableError diff --git a/zerver/tests/test_new_users.py b/zerver/tests/test_new_users.py index 56ebe33c7c..fd040c21b9 100644 --- a/zerver/tests/test_new_users.py +++ b/zerver/tests/test_new_users.py @@ -8,8 +8,8 @@ from django.core import mail from django.test import override_settings from corporate.lib.stripe import get_latest_seat_count +from zerver.actions.create_user import notify_new_user from zerver.actions.user_settings import do_change_user_setting -from zerver.lib.actions import notify_new_user from zerver.lib.initial_password import initial_password from zerver.lib.streams import create_stream_if_needed from zerver.lib.test_classes import ZulipTestCase diff --git a/zerver/tests/test_outgoing_webhook_system.py b/zerver/tests/test_outgoing_webhook_system.py index ffb33058fe..9c57e057b0 100644 --- a/zerver/tests/test_outgoing_webhook_system.py +++ b/zerver/tests/test_outgoing_webhook_system.py @@ -6,7 +6,7 @@ import requests import responses from version import ZULIP_VERSION -from zerver.lib.actions import do_create_user +from zerver.actions.create_user import do_create_user from zerver.lib.outgoing_webhook import ( GenericOutgoingWebhookService, SlackOutgoingWebhookService, diff --git a/zerver/tests/test_realm_emoji.py b/zerver/tests/test_realm_emoji.py index ff832129b7..cb6a07a75b 100644 --- a/zerver/tests/test_realm_emoji.py +++ b/zerver/tests/test_realm_emoji.py @@ -1,8 +1,9 @@ from unittest import mock +from zerver.actions.create_user import do_create_user from zerver.actions.realm_emoji import check_add_realm_emoji from zerver.actions.users import do_change_user_role -from zerver.lib.actions import do_create_realm, do_create_user, do_set_realm_property +from zerver.lib.actions import do_create_realm, do_set_realm_property from zerver.lib.exceptions import JsonableError from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import get_test_image_file diff --git a/zerver/tests/test_service_bot_system.py b/zerver/tests/test_service_bot_system.py index 01e02b06fe..cc564c0c5b 100644 --- a/zerver/tests/test_service_bot_system.py +++ b/zerver/tests/test_service_bot_system.py @@ -6,8 +6,8 @@ import orjson from django.conf import settings from django.test import override_settings +from zerver.actions.create_user import do_create_user from zerver.actions.message_send import get_service_bot_events -from zerver.lib.actions import do_create_user from zerver.lib.bot_config import ConfigError, load_bot_config_template, set_bot_config from zerver.lib.bot_lib import EmbeddedBotEmptyRecipientsList, EmbeddedBotHandler, StateHandler from zerver.lib.bot_storage import StateError diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py index ca055df7c7..6c183a15b7 100644 --- a/zerver/tests/test_signup.py +++ b/zerver/tests/test_signup.py @@ -25,6 +25,7 @@ from confirmation.models import ( one_click_unsubscribe_link, ) from corporate.lib.stripe import get_latest_seat_count +from zerver.actions.create_user import add_new_user_history, do_create_user, process_new_human_user from zerver.actions.default_streams import ( do_add_default_stream, do_create_default_stream_group, @@ -41,14 +42,11 @@ from zerver.context_processors import common_context from zerver.decorator import do_two_factor_login from zerver.forms import HomepageForm, check_subdomain_available from zerver.lib.actions import ( - add_new_user_history, do_change_realm_subdomain, do_create_realm, - do_create_user, do_deactivate_realm, do_set_realm_property, do_set_realm_user_default_setting, - process_new_human_user, ) from zerver.lib.email_notifications import enqueue_welcome_emails, followup_day2_email_delay from zerver.lib.initial_password import initial_password @@ -253,7 +251,7 @@ class AddNewUserHistoryTest(ZulipTestCase): self.send_stream_message(self.example_user("hamlet"), stream.name, "test 2") self.send_stream_message(self.example_user("hamlet"), stream.name, "test 3") - with patch("zerver.lib.actions.add_new_user_history"): + with patch("zerver.actions.create_user.add_new_user_history"): self.register(self.nonreg_email("test"), "test") user_profile = self.nonreg_user("test") subs = Subscription.objects.select_related("recipient").filter( @@ -269,7 +267,9 @@ class AddNewUserHistoryTest(ZulipTestCase): # Overwrite ONBOARDING_UNREAD_MESSAGES to 2 ONBOARDING_UNREAD_MESSAGES = 2 - with patch("zerver.lib.actions.ONBOARDING_UNREAD_MESSAGES", ONBOARDING_UNREAD_MESSAGES): + with patch( + "zerver.actions.create_user.ONBOARDING_UNREAD_MESSAGES", ONBOARDING_UNREAD_MESSAGES + ): add_new_user_history(user_profile, streams) # Our first message is in the user's history diff --git a/zerver/tests/test_users.py b/zerver/tests/test_users.py index 67ff8950ba..e93f8ebbfd 100644 --- a/zerver/tests/test_users.py +++ b/zerver/tests/test_users.py @@ -12,6 +12,7 @@ from django.test import override_settings from django.utils.timezone import now as timezone_now from confirmation.models import Confirmation +from zerver.actions.create_user import do_create_user, do_reactivate_user from zerver.actions.invites import do_create_multiuse_invite_link, do_invite_users from zerver.actions.message_send import get_recipient_info from zerver.actions.users import ( @@ -21,12 +22,7 @@ from zerver.actions.users import ( do_deactivate_user, do_delete_user, ) -from zerver.lib.actions import ( - do_create_user, - do_mute_user, - do_reactivate_user, - do_set_realm_property, -) +from zerver.lib.actions import do_mute_user, do_set_realm_property from zerver.lib.avatar import avatar_url, get_gravatar_url from zerver.lib.bulk_create import create_users from zerver.lib.create_user import copy_default_settings diff --git a/zerver/views/registration.py b/zerver/views/registration.py index 6d0572d2ff..977a1de23e 100644 --- a/zerver/views/registration.py +++ b/zerver/views/registration.py @@ -25,6 +25,7 @@ from confirmation.models import ( render_confirmation_key_error, validate_key, ) +from zerver.actions.create_user import do_activate_mirror_dummy_user, do_create_user from zerver.actions.default_streams import lookup_default_stream_groups from zerver.actions.user_settings import ( do_change_full_name, @@ -40,7 +41,7 @@ from zerver.forms import ( RealmRedirectForm, RegistrationForm, ) -from zerver.lib.actions import do_activate_mirror_dummy_user, do_create_realm, do_create_user +from zerver.lib.actions import do_create_realm from zerver.lib.email_validation import email_allowed_for_realm, validate_email_not_already_in_realm from zerver.lib.exceptions import RateLimited from zerver.lib.pysa import mark_sanitized diff --git a/zerver/views/users.py b/zerver/views/users.py index c81e99780b..ac4ef87825 100644 --- a/zerver/views/users.py +++ b/zerver/views/users.py @@ -6,6 +6,7 @@ from django.http import HttpRequest, HttpResponse from django.shortcuts import redirect from django.utils.translation import gettext as _ +from zerver.actions.create_user import do_create_user, do_reactivate_user, notify_created_bot from zerver.actions.custom_profile_fields import ( check_remove_custom_profile_field_value, do_update_user_custom_profile_data_if_changed, @@ -30,9 +31,6 @@ from zerver.lib.actions import ( do_change_default_all_public_streams, do_change_default_events_register_stream, do_change_default_sending_stream, - do_create_user, - do_reactivate_user, - notify_created_bot, ) from zerver.lib.avatar import avatar_url, get_gravatar_url from zerver.lib.bot_config import set_bot_config diff --git a/zilencer/management/commands/add_mock_conversation.py b/zilencer/management/commands/add_mock_conversation.py index 6d4fd45d10..ac3f3c2eaf 100644 --- a/zilencer/management/commands/add_mock_conversation.py +++ b/zilencer/management/commands/add_mock_conversation.py @@ -2,10 +2,11 @@ from typing import Any, Dict, List from django.core.management.base import BaseCommand +from zerver.actions.create_user import do_create_user from zerver.actions.message_send import do_send_messages, internal_prep_stream_message from zerver.actions.streams import bulk_add_subscriptions from zerver.actions.user_settings import do_change_avatar_fields -from zerver.lib.actions import do_add_reaction, do_create_user +from zerver.lib.actions import do_add_reaction from zerver.lib.emoji import emoji_name_to_emoji_code from zerver.lib.streams import ensure_stream from zerver.lib.upload import upload_avatar_image diff --git a/zproject/backends.py b/zproject/backends.py index 344b1d3af1..6bf1e4d3a6 100644 --- a/zproject/backends.py +++ b/zproject/backends.py @@ -58,11 +58,11 @@ from social_core.pipeline.partial import partial from typing_extensions import TypedDict from zxcvbn import zxcvbn +from zerver.actions.create_user import do_create_user, do_reactivate_user from zerver.actions.custom_profile_fields import do_update_user_custom_profile_data_if_changed from zerver.actions.user_settings import do_regenerate_api_key from zerver.actions.users import do_deactivate_user from zerver.decorator import client_is_exempt_from_rate_limiting -from zerver.lib.actions import do_create_user, do_reactivate_user from zerver.lib.avatar import avatar_url, is_avatar_new from zerver.lib.avatar_hash import user_avatar_content_hash from zerver.lib.dev_ldap_directory import init_fakeldap