export: Allow generating export with no usable owner account.

8ab400b95d adds a couple guardrails
againts generating an exporting realm with no usable owner and admin
accounts.

As per #34275, this commit lifts these guardrails to allow generating
export with no usable owner accounts. Export data without usable user
account might still have other uses other than for import.
This commit is contained in:
PieterCK
2025-08-27 21:19:57 +07:00
committed by Tim Abbott
parent 7d617e3382
commit d1e5ed8971
4 changed files with 42 additions and 105 deletions

View File

@@ -3105,30 +3105,3 @@ def do_common_export_processes(output_dir: str) -> None:
logging.info("Exporting migration status")
export_migration_status(output_dir)
def check_export_with_consent_is_usable(realm: Realm) -> bool:
# Users without consent enabled will end up deactivated in the exported
# data. An organization without a consenting Owner would therefore not be
# functional after export->import. That's most likely not desired by the user
# so check for such a case.
consented_user_ids = get_consented_user_ids(realm)
return UserProfile.objects.filter(
id__in=consented_user_ids, role=UserProfile.ROLE_REALM_OWNER, realm=realm
).exists()
def check_public_export_is_usable(realm: Realm) -> bool:
# Since users with email visibility set to NOBODY won't have their real emails
# exported, this could result in a lack of functional Owner accounts.
# We make sure that at least one Owner can have their real email address exported.
return UserProfile.objects.filter(
role=UserProfile.ROLE_REALM_OWNER,
email_address_visibility__in=[
UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
UserProfile.EMAIL_ADDRESS_VISIBILITY_MEMBERS,
UserProfile.EMAIL_ADDRESS_VISIBILITY_MODERATORS,
UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS,
],
realm=realm,
).exists()

View File

@@ -9,11 +9,7 @@ from django.utils.timezone import now as timezone_now
from typing_extensions import override
from zerver.actions.realm_settings import do_deactivate_realm
from zerver.lib.export import (
check_export_with_consent_is_usable,
check_public_export_is_usable,
export_realm_wrapper,
)
from zerver.lib.export import export_realm_wrapper
from zerver.lib.management import ZulipBaseCommand
from zerver.models import RealmExport
@@ -106,11 +102,6 @@ class Command(ZulipBaseCommand):
action="store_true",
help="Whether to export private data of users who consented",
)
parser.add_argument(
"--force",
action="store_true",
help="Skip checks for whether the generated export will be a usable realm.",
)
parser.add_argument(
"--upload",
action="store_true",
@@ -161,15 +152,6 @@ class Command(ZulipBaseCommand):
f"Refusing to overwrite existing tarball: {tarball_path}. Aborting..."
)
if (not options["force"]) and (
(export_full_with_consent and not check_export_with_consent_is_usable(realm))
or (public_only and not check_public_export_is_usable(realm))
):
raise CommandError(
"The generated export will not be a usable organization! "
"You can pass --force to skip this check."
)
if options["deactivate_realm"]:
print(f"\033[94mDeactivating realm\033[0m: {realm.string_id}")
do_deactivate_realm(

View File

@@ -208,41 +208,6 @@ class RealmExportTest(ZulipTestCase):
admin = self.example_user("iago")
self.login_user(admin)
owner = self.example_user("desdemona")
do_change_user_setting(
owner,
"email_address_visibility",
UserProfile.EMAIL_ADDRESS_VISIBILITY_NOBODY,
acting_user=None,
)
result = self.client_post("/json/export/realm")
self.assert_json_error_contains(
result,
"Make sure at least one Organization Owner allows "
"other Administrators to see their email address",
)
do_change_user_setting(
owner,
"email_address_visibility",
UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS,
acting_user=None,
)
do_change_user_setting(
owner,
"allow_private_data_export",
False,
acting_user=None,
)
result = self.client_post(
"/json/export/realm",
info={"export_type": RealmExport.EXPORT_FULL_WITH_CONSENT},
)
self.assert_json_error_contains(
result, "Make sure at least one Organization Owner is consenting"
)
with (
patch(
"zerver.lib.export.do_export_realm", side_effect=Exception("failure")
@@ -383,3 +348,43 @@ class RealmExportTest(ZulipTestCase):
self.assertTrue(export_consent["consented"])
continue
self.assertFalse(export_consent["consented"])
def test_allow_export_with_no_usable_user_accounts(self) -> None:
"""
Generating export with no usable accounts should be allowed.
"""
admin = self.example_user("iago")
self.login_user(admin)
# For standard export, this means no one consented to their
# private data being shared.
UserProfile.objects.filter(
role=UserProfile.ROLE_REALM_OWNER,
realm=admin.realm,
).update(
allow_private_data_export=False,
)
result = self.client_post(
"/json/export/realm",
{
"export_type": RealmExport.EXPORT_FULL_WITH_CONSENT,
},
)
self.assert_json_success(result)
# For public export, this means everyone has set their email
# address visibility policy to nobody.
UserProfile.objects.filter(
role=UserProfile.ROLE_REALM_OWNER,
realm=admin.realm,
).update(
email_address_visibility=UserProfile.EMAIL_ADDRESS_VISIBILITY_NOBODY,
)
result = self.client_post(
"/json/export/realm",
{
"export_type": RealmExport.EXPORT_PUBLIC,
},
)
self.assert_json_success(result)

View File

@@ -11,11 +11,7 @@ from analytics.models import RealmCount
from zerver.actions.realm_export import do_delete_realm_export, notify_realm_export
from zerver.decorator import require_realm_admin
from zerver.lib.exceptions import JsonableError
from zerver.lib.export import (
check_export_with_consent_is_usable,
check_public_export_is_usable,
get_realm_exports_serialized,
)
from zerver.lib.export import get_realm_exports_serialized
from zerver.lib.queue import queue_event_on_commit
from zerver.lib.response import json_success
from zerver.lib.send_email import FromAddress
@@ -86,25 +82,6 @@ def export_realm(
)
)
if (
export_type == RealmExport.EXPORT_FULL_WITH_CONSENT
and not check_export_with_consent_is_usable(realm)
):
raise JsonableError(
_(
"Make sure at least one Organization Owner is consenting to the export "
"or contact {email} for help."
).format(email=FromAddress.SUPPORT)
)
elif export_type == RealmExport.EXPORT_PUBLIC and not check_public_export_is_usable(realm):
raise JsonableError(
_(
"Make sure at least one Organization Owner allows other "
"Administrators to see their email address "
"or contact {email} for help"
).format(email=FromAddress.SUPPORT)
)
row = RealmExport.objects.create(
realm=realm,
type=export_type,