mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 16:14:02 +00:00
registration: Enable import from slack using realm registration form.
Co-authored-by: Alex Vandiver <alexmv@zulip.com> Co-authored-by: Tim Abbott <tabbott@zulip.com>
This commit is contained in:
49
templates/zerver/realm_import_post_process.html
Normal file
49
templates/zerver/realm_import_post_process.html
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{% extends "zerver/portico_signup.html" %}
|
||||||
|
{% set entrypoint = "register" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<title>{{ _("Finalize organization import") }} | Zulip</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block portico_content %}
|
||||||
|
<div class="app register-page">
|
||||||
|
<div class="app-main register-page-container new-style flex full-page center">
|
||||||
|
<div class="register-form left" id="realm-import-post-process">
|
||||||
|
<div class="lead">
|
||||||
|
<h1 class="get-started">{{ _("Organization import completed!") }}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="white-box">
|
||||||
|
<form method="post" class="form-inline" action="{{ url('realm_import_post_process', args=[key]) }}">
|
||||||
|
{{ csrf_input }}
|
||||||
|
<div class="input-box no-validation">
|
||||||
|
<input type='hidden' name='key' value='{{ key }}' />
|
||||||
|
</div>
|
||||||
|
<div class="input-box slack-import-extra-info">
|
||||||
|
<div class="not-editable-realm-field">
|
||||||
|
{% trans %}
|
||||||
|
No account in the imported data matched the email address you've verified with Zulip ({{ verified_email }}).
|
||||||
|
Select an account to associate your email address with.
|
||||||
|
{% endtrans %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-box">
|
||||||
|
<label for="email" class="inline-block label-title">{{ _("Select your account") }}</label>
|
||||||
|
<select id="realm-import-owner" name="user_id" class="required">
|
||||||
|
{% for user in users %}
|
||||||
|
<option value="{{ user.id }}">
|
||||||
|
{{ user.full_name }} ({{user.delivery_email}})
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="input-box">
|
||||||
|
<button type="submit" class="register-button">
|
||||||
|
{{ _("Confirm") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
97
templates/zerver/slack_import.html
Normal file
97
templates/zerver/slack_import.html
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
{% extends "zerver/portico_signup.html" %}
|
||||||
|
{% set entrypoint = "register" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<title>{{ _("Import from Slack") }} | Zulip</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block portico_content %}
|
||||||
|
<div class="app register-page">
|
||||||
|
<div class="app-main register-page-container new-style flex full-page center">
|
||||||
|
<div class="register-form left" id="realm-creation-form-slack-import">
|
||||||
|
<div class="lead">
|
||||||
|
<h1 class="get-started">{{ _("Import from Slack") }}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="white-box">
|
||||||
|
{% if poll_for_import_completion %}
|
||||||
|
{% if import_poll_error_message %}
|
||||||
|
<p class="text-error">{{ import_poll_error_message }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<input type='hidden' name='key' value='{{ key }}' id="auth_key_for_polling" />
|
||||||
|
<div class="input-box">
|
||||||
|
<label for="uploaded-file-info">{{ _("Import progress") }}</label>
|
||||||
|
<div id="slack-import-poll-status" class="not-editable-realm-field">
|
||||||
|
{{ _("Checking import status…") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<form method="post" class="form-inline" action="{{ url('import_realm_from_slack') }}">
|
||||||
|
{{ csrf_input }}
|
||||||
|
<div class="input-box no-validation">
|
||||||
|
<input type='hidden' name='key' value='{{ key }}' id="auth_key_for_file_upload"/>
|
||||||
|
</div>
|
||||||
|
<div class="input-box slack-import-extra-info">
|
||||||
|
<div class="not-editable-realm-field">
|
||||||
|
{{ _("You will immediately see a bot user OAuth token, which is a long string of numbers and characters starting with xoxb-. Copy this token. You will use it to download user and emoji data from your Slack workspace.") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-box">
|
||||||
|
<label for="slack_access_token" class="inline-block label-title">{{ _('Slack bot user OAuth token') }}</label>
|
||||||
|
<input id="slack-access-token" type="text"
|
||||||
|
placeholder="xoxb-…"
|
||||||
|
maxlength="100" name="slack_access_token" required {% if slack_access_token %} value="{{ slack_access_token }}" {% endif %} />
|
||||||
|
{% if slack_access_token_validation_error %}
|
||||||
|
<p id="slack-access-token-validation-error" class="help-inline text-error">{{ slack_access_token_validation_error }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="input-box">
|
||||||
|
<button type="submit" class="register-button" {% if slack_access_token %} id="update-slack-access-token"{% endif %}>
|
||||||
|
{% if slack_access_token %}
|
||||||
|
{{ _("Update") }}
|
||||||
|
{% else %}
|
||||||
|
{{ _("Submit") }}
|
||||||
|
{% endif %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% if slack_access_token %}
|
||||||
|
<div class="input-box" id="slack-import-drag-drop-wrapper">
|
||||||
|
<label for="slack-import-drag-and-drop" class="inline-block label-title">{{ _('Import your data') }}</label>
|
||||||
|
<div id="slack-import-drag-and-drop" data-max-file-size="{{max_file_size}}"></div>
|
||||||
|
</div>
|
||||||
|
<form id="slack-import-start-upload-wrapper" method="post" class="form-inline {% if uploaded_import_file_name %}{% else %}hidden{% endif %}" action="{{ url('import_realm_from_slack') }}">
|
||||||
|
{{ csrf_input }}
|
||||||
|
<div class="input-box no-validation">
|
||||||
|
<input type='hidden' name='key' value='{{ key }}' />
|
||||||
|
<input type='hidden' name='start_slack_import' value="true" />
|
||||||
|
</div>
|
||||||
|
<div class="input-box">
|
||||||
|
<label for="uploaded-file-info">{{ _("Uploaded file") }}</label>
|
||||||
|
<div class="not-editable-realm-field" id="slack-import-uploaded-file-name">{{ uploaded_import_file_name }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-box">
|
||||||
|
<button type="submit" class="register-button">
|
||||||
|
{{ _("Start import") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if poll_for_import_completion %}
|
||||||
|
{% else %}
|
||||||
|
<form method="post" class="hidden" id="cancel-slack-import-form" action="{{ url('import_realm_from_slack') }}">
|
||||||
|
{{ csrf_input }}
|
||||||
|
<input type='hidden' name='key' value="{{ key }}" />
|
||||||
|
<input type='hidden' name='cancel_import' value='true'/>
|
||||||
|
</form>
|
||||||
|
<div class="bottom-text">
|
||||||
|
{% trans %}
|
||||||
|
Or <a href="#" id="cancel-slack-import">create organization</a> without importing data.
|
||||||
|
{% endtrans %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@@ -35,6 +35,7 @@ IGNORED_PHRASES = [
|
|||||||
r"LinkedIn",
|
r"LinkedIn",
|
||||||
r"LDAP",
|
r"LDAP",
|
||||||
r"Markdown",
|
r"Markdown",
|
||||||
|
r"OAuth",
|
||||||
r"OTP",
|
r"OTP",
|
||||||
r"Pivotal",
|
r"Pivotal",
|
||||||
r"Recent conversations",
|
r"Recent conversations",
|
||||||
|
@@ -569,6 +569,7 @@ html_rules: list["Rule"] = [
|
|||||||
"description": "`placeholder` value should be translatable.",
|
"description": "`placeholder` value should be translatable.",
|
||||||
"exclude_line": {
|
"exclude_line": {
|
||||||
("templates/zerver/realm_creation_form.html", 'placeholder="acme"'),
|
("templates/zerver/realm_creation_form.html", 'placeholder="acme"'),
|
||||||
|
("templates/zerver/slack_import.html", 'placeholder="xoxb-…"'),
|
||||||
},
|
},
|
||||||
"exclude": {
|
"exclude": {
|
||||||
"templates/corporate",
|
"templates/corporate",
|
||||||
|
@@ -1,3 +1,8 @@
|
|||||||
|
import {Uppy} from "@uppy/core";
|
||||||
|
import DragDrop from "@uppy/drag-drop";
|
||||||
|
import Tus from "@uppy/tus";
|
||||||
|
import "@uppy/core/dist/style.min.css";
|
||||||
|
import "@uppy/drag-drop/dist/style.min.css";
|
||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import assert from "minimalistic-assert";
|
import assert from "minimalistic-assert";
|
||||||
@@ -382,4 +387,74 @@ $(() => {
|
|||||||
}
|
}
|
||||||
}) as EventListener);
|
}) as EventListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($("#slack-import-drag-and-drop").length > 0) {
|
||||||
|
const key = $<HTMLInputElement>("#auth_key_for_file_upload").val();
|
||||||
|
const uppy = new Uppy({
|
||||||
|
autoProceed: true,
|
||||||
|
restrictions: {
|
||||||
|
maxNumberOfFiles: 1,
|
||||||
|
minNumberOfFiles: 1,
|
||||||
|
allowedFileTypes: [".zip", "application/zip"],
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
key,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
uppy.use(DragDrop, {
|
||||||
|
target: "#slack-import-drag-and-drop",
|
||||||
|
locale: {
|
||||||
|
strings: {
|
||||||
|
// Override the default text for the drag and drop area.
|
||||||
|
dropHereOr: $t({
|
||||||
|
defaultMessage:
|
||||||
|
"Drag and drop your Slack export file here, or click to browse.",
|
||||||
|
}),
|
||||||
|
// Required by typescript to define this.
|
||||||
|
browse: $t({
|
||||||
|
defaultMessage: "Browse",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
uppy.use(Tus, {endpoint: "/api/v1/tus/", removeFingerprintOnSuccess: true});
|
||||||
|
uppy.on("upload-error", (_file, error) => {
|
||||||
|
$("#slack-import-file-upload-error").text(error.message);
|
||||||
|
});
|
||||||
|
uppy.on("upload-success", (file, _response) => {
|
||||||
|
assert(file !== undefined);
|
||||||
|
$("#slack-import-start-upload-wrapper").removeClass("hidden");
|
||||||
|
$("#slack-import-uploaded-file-name").text(file.name!);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($("#slack-import-poll-status").length > 0) {
|
||||||
|
const key = $<HTMLInputElement>("#auth_key_for_polling").val();
|
||||||
|
const pollInterval = 2000; // Poll every 2 seconds
|
||||||
|
|
||||||
|
let poll_id: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
function checkImportStatus(): void {
|
||||||
|
$.get(`/json/realm/import/status/${key}`, {}, (response) => {
|
||||||
|
const {status, redirect} = z
|
||||||
|
.object({status: z.string(), redirect: z.string().optional()})
|
||||||
|
.parse(response);
|
||||||
|
$("#slack-import-poll-status").text(status);
|
||||||
|
if (poll_id && redirect !== undefined) {
|
||||||
|
clearInterval(poll_id);
|
||||||
|
window.location.assign(redirect);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start polling
|
||||||
|
poll_id = setInterval(checkImportStatus, pollInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#cancel-slack-import").on("click", () => {
|
||||||
|
$("#cancel-slack-import-form").trigger("submit");
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#slack-access-token").on("input", () => {
|
||||||
|
$("#update-slack-access-token").show();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -51,6 +51,7 @@ html {
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#realm-import-post-process,
|
||||||
#new-realm-creation {
|
#new-realm-creation {
|
||||||
.get-started {
|
.get-started {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
@@ -903,6 +904,8 @@ button#register_auth_button_gitlab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#realm-import-post-process,
|
||||||
|
#realm-creation-form-slack-import,
|
||||||
#account-deactivated-success-page-details,
|
#account-deactivated-success-page-details,
|
||||||
#server-deactivate-details,
|
#server-deactivate-details,
|
||||||
#remote-billing-confirm-login-form,
|
#remote-billing-confirm-login-form,
|
||||||
@@ -1476,3 +1479,55 @@ button#register_auth_button_gitlab {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#realm-creation-import-from-wrapper .extra-info-realm-creation-import-from {
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#slack-import-drag-drop-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
margin: 10px 0 0 5px;
|
||||||
|
|
||||||
|
#slack-import-drag-and-drop {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#realm-import-post-process,
|
||||||
|
#realm-creation-form-slack-import {
|
||||||
|
.register-button {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#update-slack-access-token {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slack-import-extra-info {
|
||||||
|
margin-top: 0;
|
||||||
|
|
||||||
|
.not-editable-realm-field {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-box.no-validation {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#slack-import-drag-and-drop .uppy-DragDrop-container {
|
||||||
|
border: 1px dashed hsl(0deg 0% 0%);
|
||||||
|
background-color: hsl(0deg 0% 0% / 5%);
|
||||||
|
color: hsl(0deg 0% 40%);
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 1px solid hsl(213deg 81% 79%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
90
zerver/actions/data_import.py
Normal file
90
zerver/actions/data_import.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import logging
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from confirmation import settings as confirmation_settings
|
||||||
|
from zerver.actions.realm_settings import do_delete_all_realm_attachments
|
||||||
|
from zerver.actions.users import do_change_user_role
|
||||||
|
from zerver.context_processors import is_realm_import_enabled
|
||||||
|
from zerver.data_import.slack import do_convert_zipfile
|
||||||
|
from zerver.lib.import_realm import do_import_realm
|
||||||
|
from zerver.lib.upload import save_attachment_contents
|
||||||
|
from zerver.models.prereg_users import PreregistrationRealm
|
||||||
|
from zerver.models.realms import Realm
|
||||||
|
from zerver.models.users import UserProfile, get_user_by_delivery_email
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def import_slack_data(event: dict[str, Any]) -> None:
|
||||||
|
# This is only possible if data imports were enqueued before the
|
||||||
|
# setting was turned off.
|
||||||
|
assert is_realm_import_enabled()
|
||||||
|
|
||||||
|
preregistration_realm = PreregistrationRealm.objects.get(id=event["preregistration_realm_id"])
|
||||||
|
string_id = preregistration_realm.string_id
|
||||||
|
output_dir = tempfile.mkdtemp(
|
||||||
|
prefix=f"import-{preregistration_realm.id}-converted-",
|
||||||
|
dir=settings.IMPORT_TMPFILE_DIRECTORY,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with tempfile.NamedTemporaryFile(
|
||||||
|
prefix=f"import-{preregistration_realm.id}-slack-",
|
||||||
|
suffix=".zip",
|
||||||
|
dir=settings.IMPORT_TMPFILE_DIRECTORY,
|
||||||
|
) as fh:
|
||||||
|
save_attachment_contents(event["filename"], fh)
|
||||||
|
fh.flush()
|
||||||
|
do_convert_zipfile(
|
||||||
|
fh.name,
|
||||||
|
output_dir,
|
||||||
|
event["slack_access_token"],
|
||||||
|
)
|
||||||
|
|
||||||
|
realm = do_import_realm(output_dir, string_id)
|
||||||
|
realm.org_type = preregistration_realm.org_type
|
||||||
|
realm.default_language = preregistration_realm.default_language
|
||||||
|
realm.save()
|
||||||
|
|
||||||
|
# Try finding the user who imported this realm and make them owner.
|
||||||
|
try:
|
||||||
|
importing_user = get_user_by_delivery_email(preregistration_realm.email, realm)
|
||||||
|
assert (
|
||||||
|
importing_user.is_active
|
||||||
|
and not importing_user.is_bot
|
||||||
|
and not importing_user.is_mirror_dummy
|
||||||
|
)
|
||||||
|
if importing_user.role != UserProfile.ROLE_REALM_OWNER:
|
||||||
|
do_change_user_role(
|
||||||
|
importing_user, UserProfile.ROLE_REALM_OWNER, acting_user=importing_user
|
||||||
|
)
|
||||||
|
preregistration_realm.status = confirmation_settings.STATUS_USED
|
||||||
|
except UserProfile.DoesNotExist:
|
||||||
|
# If the email address that the importing user
|
||||||
|
# validated with Zulip does not appear in the data
|
||||||
|
# export, we will prompt them which account is theirs.
|
||||||
|
preregistration_realm.data_import_metadata["need_select_realm_owner"] = True
|
||||||
|
|
||||||
|
preregistration_realm.created_realm = realm
|
||||||
|
preregistration_realm.data_import_metadata["is_import_work_queued"] = False
|
||||||
|
preregistration_realm.save()
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(e)
|
||||||
|
try:
|
||||||
|
# Clean up the realm if the import failed
|
||||||
|
preregistration_realm.created_realm = None
|
||||||
|
preregistration_realm.data_import_metadata["is_import_work_queued"] = False
|
||||||
|
preregistration_realm.save()
|
||||||
|
|
||||||
|
realm = Realm.objects.get(string_id=string_id)
|
||||||
|
do_delete_all_realm_attachments(realm)
|
||||||
|
realm.delete()
|
||||||
|
except Realm.DoesNotExist:
|
||||||
|
pass
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
shutil.rmtree(output_dir)
|
@@ -330,6 +330,10 @@ class HomepageForm(forms.Form):
|
|||||||
return email
|
return email
|
||||||
|
|
||||||
|
|
||||||
|
class ImportRealmOwnerSelectionForm(forms.Form):
|
||||||
|
user_id = forms.IntegerField()
|
||||||
|
|
||||||
|
|
||||||
class RealmCreationForm(RealmDetailsForm):
|
class RealmCreationForm(RealmDetailsForm):
|
||||||
# This form determines whether users can create a new realm.
|
# This form determines whether users can create a new realm.
|
||||||
email = forms.EmailField(validators=[email_not_system_bot, email_is_not_disposable])
|
email = forms.EmailField(validators=[email_not_system_bot, email_is_not_disposable])
|
||||||
|
@@ -2,7 +2,7 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Any
|
from typing import TYPE_CHECKING, Any
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
from unittest.mock import ANY
|
from unittest.mock import ANY
|
||||||
from urllib.parse import parse_qs, urlsplit
|
from urllib.parse import parse_qs, urlsplit
|
||||||
@@ -10,9 +10,14 @@ from urllib.parse import parse_qs, urlsplit
|
|||||||
import orjson
|
import orjson
|
||||||
import responses
|
import responses
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.http import HttpResponse
|
||||||
from django.utils.timezone import now as timezone_now
|
from django.utils.timezone import now as timezone_now
|
||||||
from requests.models import PreparedRequest
|
from requests.models import PreparedRequest
|
||||||
|
|
||||||
|
from confirmation import settings as confirmation_settings
|
||||||
|
from confirmation.models import Confirmation, get_object_from_key
|
||||||
|
from zerver.actions.create_realm import do_create_realm
|
||||||
|
from zerver.actions.data_import import import_slack_data
|
||||||
from zerver.data_import.import_util import (
|
from zerver.data_import.import_util import (
|
||||||
ZerverFieldsT,
|
ZerverFieldsT,
|
||||||
build_defaultstream,
|
build_defaultstream,
|
||||||
@@ -48,12 +53,22 @@ from zerver.data_import.slack import (
|
|||||||
)
|
)
|
||||||
from zerver.lib.import_realm import do_import_realm
|
from zerver.lib.import_realm import do_import_realm
|
||||||
from zerver.lib.test_classes import ZulipTestCase
|
from zerver.lib.test_classes import ZulipTestCase
|
||||||
from zerver.lib.test_helpers import read_test_image_file
|
from zerver.lib.test_helpers import find_key_by_email, read_test_image_file
|
||||||
from zerver.lib.topic import EXPORT_TOPIC_NAME
|
from zerver.lib.topic import EXPORT_TOPIC_NAME
|
||||||
from zerver.models import Message, Realm, RealmAuditLog, Recipient, UserProfile
|
from zerver.models import (
|
||||||
|
Message,
|
||||||
|
PreregistrationRealm,
|
||||||
|
Realm,
|
||||||
|
RealmAuditLog,
|
||||||
|
Recipient,
|
||||||
|
UserProfile,
|
||||||
|
)
|
||||||
from zerver.models.realm_audit_logs import AuditLogEventType
|
from zerver.models.realm_audit_logs import AuditLogEventType
|
||||||
from zerver.models.realms import get_realm
|
from zerver.models.realms import get_realm
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse
|
||||||
|
|
||||||
|
|
||||||
def remove_folder(path: str) -> None:
|
def remove_folder(path: str) -> None:
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
@@ -1778,3 +1793,415 @@ class SlackImporter(ZulipTestCase):
|
|||||||
# We need to mock EXTERNAL_HOST to be a valid domain because Slack's importer
|
# We need to mock EXTERNAL_HOST to be a valid domain because Slack's importer
|
||||||
# uses it to generate email addresses for users without an email specified.
|
# uses it to generate email addresses for users without an email specified.
|
||||||
do_convert_zipfile(test_slack_zip_file, output_dir, token)
|
do_convert_zipfile(test_slack_zip_file, output_dir, token)
|
||||||
|
|
||||||
|
@mock.patch("zerver.data_import.slack.check_token_access")
|
||||||
|
@responses.activate
|
||||||
|
def test_end_to_end_slack_import(
|
||||||
|
self,
|
||||||
|
mock_check_token_access: mock.Mock,
|
||||||
|
) -> None:
|
||||||
|
# Choose import from slack
|
||||||
|
email = "ete-slack-import@zulip.com"
|
||||||
|
string_id = "ete-slack-import"
|
||||||
|
result = self.submit_realm_creation_form(
|
||||||
|
email,
|
||||||
|
realm_subdomain=string_id,
|
||||||
|
realm_name="Slack import end to end",
|
||||||
|
import_from="slack",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Confirm email
|
||||||
|
self.assertEqual(result.status_code, 302)
|
||||||
|
self.assertTrue(
|
||||||
|
result["Location"].endswith(
|
||||||
|
"/accounts/new/send_confirm/?email=ete-slack-import%40zulip.com&realm_name=Slack+import+end+to+end&realm_type=10&realm_default_language=en&realm_subdomain=ete-slack-import"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result = self.client_get(result["Location"])
|
||||||
|
self.assert_in_response("check your email", result)
|
||||||
|
prereg_realm = PreregistrationRealm.objects.get(email=email)
|
||||||
|
self.assertEqual(prereg_realm.name, "Slack import end to end")
|
||||||
|
self.assertEqual(prereg_realm.data_import_metadata["import_from"], "slack")
|
||||||
|
|
||||||
|
# Redirect to slack data import form
|
||||||
|
confirmation_url = self.get_confirmation_url_from_outbox(email)
|
||||||
|
result = self.client_get(confirmation_url)
|
||||||
|
self.assert_in_success_response(["new/import/slack"], result)
|
||||||
|
|
||||||
|
confirmation_key = find_key_by_email(email)
|
||||||
|
assert confirmation_key is not None
|
||||||
|
|
||||||
|
# Check that the we show an error message if the token is invalid.
|
||||||
|
mock_check_token_access.side_effect = ValueError("Invalid slack token")
|
||||||
|
result = self.client_post(
|
||||||
|
"/new/import/slack/",
|
||||||
|
{
|
||||||
|
"key": confirmation_key,
|
||||||
|
"slack_access_token": "xoxb-invalid-token",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assert_in_response("Invalid slack token", result)
|
||||||
|
mock_check_token_access.side_effect = None
|
||||||
|
|
||||||
|
# Mock slack API response and mark token as valid
|
||||||
|
access_token = "xoxb-valid-token"
|
||||||
|
slack_team_info_url = "https://slack.com/api/team.info"
|
||||||
|
responses.add_callback(
|
||||||
|
responses.GET,
|
||||||
|
slack_team_info_url,
|
||||||
|
callback=lambda _: (
|
||||||
|
200,
|
||||||
|
{"x-oauth-scopes": "emoji:read,users:read,users:read.email,team:read"},
|
||||||
|
orjson.dumps({"ok": True}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
result = self.client_post(
|
||||||
|
"/new/import/slack/",
|
||||||
|
{
|
||||||
|
"key": confirmation_key,
|
||||||
|
"slack_access_token": access_token,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
prereg_realm.refresh_from_db()
|
||||||
|
self.assertEqual(prereg_realm.data_import_metadata["import_from"], "slack")
|
||||||
|
self.assertEqual(prereg_realm.data_import_metadata["slack_access_token"], access_token)
|
||||||
|
|
||||||
|
# Assume user uploaded a file.
|
||||||
|
prereg_realm.data_import_metadata["uploaded_import_file_name"] = "test_slack_importer.zip"
|
||||||
|
prereg_realm.save()
|
||||||
|
|
||||||
|
# Check that deferred_work for import is queued.
|
||||||
|
with mock.patch("zerver.views.registration.queue_json_publish_rollback_unsafe") as m:
|
||||||
|
result = self.client_post(
|
||||||
|
"/new/import/slack/",
|
||||||
|
{
|
||||||
|
"key": confirmation_key,
|
||||||
|
"start_slack_import": "true",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assert_in_success_response(["Import progress"], result)
|
||||||
|
prereg_realm.refresh_from_db()
|
||||||
|
self.assertTrue(prereg_realm.data_import_metadata["is_import_work_queued"])
|
||||||
|
|
||||||
|
m.assert_called_once_with(
|
||||||
|
"deferred_work",
|
||||||
|
{
|
||||||
|
"type": "import_slack_data",
|
||||||
|
"preregistration_realm_id": prereg_realm.id,
|
||||||
|
"filename": f"import/{prereg_realm.id}/slack.zip",
|
||||||
|
"slack_access_token": access_token,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# We don't want to test to whole realm import process here but only that
|
||||||
|
# realm import calls are made with correct arguments and different cases
|
||||||
|
# are handled well.
|
||||||
|
realm = do_create_realm(
|
||||||
|
string_id=prereg_realm.string_id,
|
||||||
|
name=prereg_realm.name,
|
||||||
|
)
|
||||||
|
with (
|
||||||
|
mock.patch(
|
||||||
|
"zerver.actions.data_import.save_attachment_contents"
|
||||||
|
) as mocked_save_attachment,
|
||||||
|
mock.patch("zerver.actions.data_import.do_convert_zipfile") as mocked_convert_zipfile,
|
||||||
|
mock.patch(
|
||||||
|
"zerver.actions.data_import.do_import_realm", return_value=realm
|
||||||
|
) as mocked_import_realm,
|
||||||
|
):
|
||||||
|
from zerver.lib.queue import queue_json_publish_rollback_unsafe
|
||||||
|
|
||||||
|
queue_json_publish_rollback_unsafe(
|
||||||
|
"deferred_work",
|
||||||
|
{
|
||||||
|
"type": "import_slack_data",
|
||||||
|
"preregistration_realm_id": prereg_realm.id,
|
||||||
|
"filename": f"import/{prereg_realm.id}/slack.zip",
|
||||||
|
"slack_access_token": access_token,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertTrue(mocked_save_attachment.called)
|
||||||
|
self.assertTrue(mocked_convert_zipfile.called)
|
||||||
|
self.assertTrue(mocked_import_realm.called)
|
||||||
|
realm.refresh_from_db()
|
||||||
|
self.assertEqual(realm.org_type, prereg_realm.org_type)
|
||||||
|
self.assertEqual(realm.default_language, prereg_realm.default_language)
|
||||||
|
prereg_realm.refresh_from_db()
|
||||||
|
self.assertTrue(prereg_realm.data_import_metadata["need_select_realm_owner"])
|
||||||
|
|
||||||
|
# Confirmation key at this point is marked, used but since we
|
||||||
|
# are mocking the process, we need to do it manually here.
|
||||||
|
get_object_from_key(confirmation_key, [Confirmation.REALM_CREATION], mark_object_used=True)
|
||||||
|
result = self.client_get(f"/json/realm/import/status/{confirmation_key}")
|
||||||
|
self.assert_in_success_response(["No users matching provided email"], result)
|
||||||
|
|
||||||
|
# Create a user who become the realm owner, ideally this will be created
|
||||||
|
# as part of the import process or we will add form for user to do so.
|
||||||
|
imported_user_to_be_owner = UserProfile.objects.create(realm=realm, delivery_email=email)
|
||||||
|
imported_user_to_be_owner.set_unusable_password()
|
||||||
|
imported_user_to_be_owner.save()
|
||||||
|
|
||||||
|
def post_process_request(key: str | None = confirmation_key) -> "TestHttpResponse":
|
||||||
|
return self.client_post(
|
||||||
|
f"/realm/import/post_process/{key}",
|
||||||
|
{
|
||||||
|
"user_id": str(imported_user_to_be_owner.id),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Show error on using wrong confirmation key.
|
||||||
|
with mock.patch(
|
||||||
|
"zerver.views.registration.render_confirmation_key_error",
|
||||||
|
return_value=HttpResponse(status=200),
|
||||||
|
) as m:
|
||||||
|
post_process_request("malformed_key")
|
||||||
|
m.assert_called_once()
|
||||||
|
|
||||||
|
# Check if we cannot find the realm, preregistration_realm is revoked.
|
||||||
|
prereg_realm.string_id = "non_existent_realm"
|
||||||
|
prereg_realm.save()
|
||||||
|
result = post_process_request()
|
||||||
|
prereg_realm.refresh_from_db()
|
||||||
|
self.assertEqual(prereg_realm.status, confirmation_settings.STATUS_REVOKED)
|
||||||
|
|
||||||
|
# Reset status for further tests.
|
||||||
|
prereg_realm.string_id = string_id
|
||||||
|
prereg_realm.status = 0
|
||||||
|
prereg_realm.save()
|
||||||
|
|
||||||
|
# Redirect user to password reset page on successful import.
|
||||||
|
result = post_process_request()
|
||||||
|
self.assertEqual(result.status_code, 302)
|
||||||
|
self.assertTrue(
|
||||||
|
result["Location"].startswith(
|
||||||
|
"http://ete-slack-import.testserver/accounts/password/reset/"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# If user refreshes the page, redirect to login page if the import was successful.
|
||||||
|
result = self.client_get(f"/realm/import/post_process/{confirmation_key}")
|
||||||
|
self.assertEqual(result.status_code, 302)
|
||||||
|
self.assertEqual(result["Location"], "http://ete-slack-import.testserver/accounts/login/")
|
||||||
|
|
||||||
|
# Check if we render a form for user to select a user if there
|
||||||
|
# are no users matching the provided email.
|
||||||
|
prereg_realm.data_import_metadata["need_select_realm_owner"] = True
|
||||||
|
prereg_realm.save()
|
||||||
|
result = self.client_get(f"/realm/import/post_process/{confirmation_key}")
|
||||||
|
self.assert_in_success_response(["Select your account"], result)
|
||||||
|
|
||||||
|
# Check that user is redirected to this form using email confirmation link.
|
||||||
|
result = self.client_get(confirmation_url)
|
||||||
|
self.assert_in_success_response(["new/import/slack"], result)
|
||||||
|
result = self.client_post(
|
||||||
|
"/new/import/slack/",
|
||||||
|
{
|
||||||
|
"key": confirmation_key,
|
||||||
|
"slack_access_token": access_token,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(result.status_code, 302)
|
||||||
|
self.assertEqual(result["Location"], f"/realm/import/post_process/{confirmation_key}")
|
||||||
|
result = self.client_get(f"/realm/import/post_process/{confirmation_key}")
|
||||||
|
self.assert_in_success_response(["Select your account"], result)
|
||||||
|
|
||||||
|
@mock.patch("zerver.actions.data_import.do_import_realm")
|
||||||
|
@mock.patch("zerver.actions.data_import.do_convert_zipfile")
|
||||||
|
@mock.patch("zerver.actions.data_import.save_attachment_contents")
|
||||||
|
def test_import_slack_data_found_user_matching_email_of_importer(
|
||||||
|
self,
|
||||||
|
mock_save_attachment_contents: mock.Mock,
|
||||||
|
mock_do_convert_zipfile: mock.Mock,
|
||||||
|
mock_do_import_realm: mock.Mock,
|
||||||
|
) -> None:
|
||||||
|
prereg_realm = PreregistrationRealm.objects.create(
|
||||||
|
string_id="test-realm-slack-import",
|
||||||
|
name="Test Realm",
|
||||||
|
email="test_import_slack_data_user@example.com",
|
||||||
|
data_import_metadata={"import_from": "slack"},
|
||||||
|
)
|
||||||
|
mock_realm = do_create_realm(
|
||||||
|
string_id=prereg_realm.string_id,
|
||||||
|
name=prereg_realm.name,
|
||||||
|
)
|
||||||
|
mock_do_import_realm.return_value = mock_realm
|
||||||
|
|
||||||
|
importing_user = UserProfile.objects.create(
|
||||||
|
realm=mock_realm,
|
||||||
|
delivery_email=prereg_realm.email,
|
||||||
|
)
|
||||||
|
|
||||||
|
event = {
|
||||||
|
"preregistration_realm_id": prereg_realm.id,
|
||||||
|
"filename": "import/test/slack.zip",
|
||||||
|
"slack_access_token": "xoxb-valid-token",
|
||||||
|
}
|
||||||
|
|
||||||
|
import_slack_data(event)
|
||||||
|
|
||||||
|
mock_save_attachment_contents.assert_called_once()
|
||||||
|
mock_do_convert_zipfile.assert_called_once_with(
|
||||||
|
mock.ANY, mock.ANY, event["slack_access_token"]
|
||||||
|
)
|
||||||
|
mock_do_import_realm.assert_called_once_with(mock.ANY, prereg_realm.string_id)
|
||||||
|
|
||||||
|
prereg_realm.refresh_from_db()
|
||||||
|
self.assertEqual(prereg_realm.status, 1) # STATUS_USED
|
||||||
|
self.assertEqual(prereg_realm.created_realm, mock_realm)
|
||||||
|
self.assertFalse(prereg_realm.data_import_metadata["is_import_work_queued"])
|
||||||
|
self.assertFalse(prereg_realm.data_import_metadata.get("need_select_realm_owner"))
|
||||||
|
|
||||||
|
# Check that the importing user was made the realm owner
|
||||||
|
importing_user.refresh_from_db()
|
||||||
|
self.assertEqual(importing_user.role, UserProfile.ROLE_REALM_OWNER)
|
||||||
|
|
||||||
|
@mock.patch("zerver.actions.data_import.do_import_realm")
|
||||||
|
@mock.patch("zerver.actions.data_import.do_convert_zipfile")
|
||||||
|
@mock.patch("zerver.actions.data_import.save_attachment_contents")
|
||||||
|
def test_import_slack_data_failure_cleanup_realm_not_created(
|
||||||
|
self,
|
||||||
|
mock_save_attachment_contents: mock.Mock,
|
||||||
|
mock_do_convert_zipfile: mock.Mock,
|
||||||
|
mock_do_import_realm: mock.Mock,
|
||||||
|
) -> None:
|
||||||
|
prereg_realm = PreregistrationRealm.objects.create(
|
||||||
|
string_id="test-realm",
|
||||||
|
name="Test Realm",
|
||||||
|
email="test@example.com",
|
||||||
|
data_import_metadata={"import_from": "slack"},
|
||||||
|
)
|
||||||
|
mock_do_import_realm.side_effect = AssertionError("Import failed")
|
||||||
|
|
||||||
|
event = {
|
||||||
|
"preregistration_realm_id": prereg_realm.id,
|
||||||
|
"filename": "import/test/slack.zip",
|
||||||
|
"slack_access_token": "xoxb-valid-token",
|
||||||
|
}
|
||||||
|
|
||||||
|
with (
|
||||||
|
self.assertRaises(AssertionError),
|
||||||
|
self.assertLogs("zerver.actions.data_import", "ERROR"),
|
||||||
|
):
|
||||||
|
import_slack_data(event)
|
||||||
|
|
||||||
|
prereg_realm.refresh_from_db()
|
||||||
|
self.assertIsNone(prereg_realm.created_realm)
|
||||||
|
self.assertFalse(prereg_realm.data_import_metadata["is_import_work_queued"])
|
||||||
|
self.assertFalse(Realm.objects.filter(string_id="test-realm").exists())
|
||||||
|
|
||||||
|
@mock.patch("zerver.actions.data_import.do_import_realm")
|
||||||
|
@mock.patch("zerver.actions.data_import.do_convert_zipfile")
|
||||||
|
@mock.patch("zerver.actions.data_import.save_attachment_contents")
|
||||||
|
def test_import_slack_data_failure_cleanup_realm_created(
|
||||||
|
self,
|
||||||
|
mock_save_attachment_contents: mock.Mock,
|
||||||
|
mock_do_convert_zipfile: mock.Mock,
|
||||||
|
mock_do_import_realm: mock.Mock,
|
||||||
|
) -> None:
|
||||||
|
prereg_realm = PreregistrationRealm.objects.create(
|
||||||
|
string_id="test-realm",
|
||||||
|
name="Test Realm",
|
||||||
|
email="test@example.com",
|
||||||
|
data_import_metadata={"import_from": "slack"},
|
||||||
|
)
|
||||||
|
do_create_realm(
|
||||||
|
string_id=prereg_realm.string_id,
|
||||||
|
name=prereg_realm.name,
|
||||||
|
)
|
||||||
|
self.assertTrue(Realm.objects.filter(string_id=prereg_realm.string_id).exists())
|
||||||
|
mock_do_import_realm.side_effect = AssertionError("Import failed")
|
||||||
|
event = {
|
||||||
|
"preregistration_realm_id": prereg_realm.id,
|
||||||
|
"filename": "import/test/slack.zip",
|
||||||
|
"slack_access_token": "xoxb-valid-token",
|
||||||
|
}
|
||||||
|
|
||||||
|
with (
|
||||||
|
self.assertRaises(AssertionError),
|
||||||
|
self.assertLogs("zerver.actions.data_import", "ERROR"),
|
||||||
|
):
|
||||||
|
import_slack_data(event)
|
||||||
|
|
||||||
|
prereg_realm.refresh_from_db()
|
||||||
|
self.assertIsNone(prereg_realm.created_realm)
|
||||||
|
self.assertFalse(prereg_realm.data_import_metadata["is_import_work_queued"])
|
||||||
|
self.assertFalse(Realm.objects.filter(string_id=prereg_realm.string_id).exists())
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_cancel_realm_import(self) -> None:
|
||||||
|
# Choose import from slack
|
||||||
|
email = "ete-slack-import@zulip.com"
|
||||||
|
self.submit_realm_creation_form(
|
||||||
|
email,
|
||||||
|
realm_subdomain="ete-slack-import",
|
||||||
|
realm_name="Slack import end to end",
|
||||||
|
import_from="slack",
|
||||||
|
)
|
||||||
|
prereg_realm = PreregistrationRealm.objects.get(email=email)
|
||||||
|
self.assertEqual(prereg_realm.data_import_metadata["import_from"], "slack")
|
||||||
|
|
||||||
|
# If the import is already in process, don't allow import cancellation.
|
||||||
|
prereg_realm.data_import_metadata["is_import_work_queued"] = True
|
||||||
|
prereg_realm.save()
|
||||||
|
|
||||||
|
confirmation_key = find_key_by_email(email)
|
||||||
|
assert confirmation_key is not None
|
||||||
|
response = self.client_post(
|
||||||
|
"/new/import/slack/",
|
||||||
|
{
|
||||||
|
"key": confirmation_key,
|
||||||
|
"cancel_import": "true",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assert_in_success_response(["Unable to cancel import"], response)
|
||||||
|
prereg_realm.refresh_from_db()
|
||||||
|
self.assertTrue(prereg_realm.data_import_metadata["is_import_work_queued"])
|
||||||
|
|
||||||
|
# Allow cancellation if the import work is not queued.
|
||||||
|
prereg_realm.data_import_metadata["is_import_work_queued"] = False
|
||||||
|
prereg_realm.save()
|
||||||
|
response = self.client_post(
|
||||||
|
"/new/import/slack/",
|
||||||
|
{
|
||||||
|
"key": confirmation_key,
|
||||||
|
"cancel_import": "true",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
prereg_realm.refresh_from_db()
|
||||||
|
self.assertIsNone(prereg_realm.data_import_metadata.get("import_from"))
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_cancel_realm_import_realm_created(self) -> None:
|
||||||
|
# If user cancelled import after realm was created,
|
||||||
|
# clean up the created realm.
|
||||||
|
email = "ete-slack-import@zulip.com"
|
||||||
|
self.submit_realm_creation_form(
|
||||||
|
email,
|
||||||
|
realm_subdomain="ete-slack-import",
|
||||||
|
realm_name="Slack import end to end",
|
||||||
|
import_from="slack",
|
||||||
|
)
|
||||||
|
prereg_realm = PreregistrationRealm.objects.get(email=email)
|
||||||
|
self.assertEqual(prereg_realm.data_import_metadata["import_from"], "slack")
|
||||||
|
realm = do_create_realm(
|
||||||
|
string_id=prereg_realm.string_id,
|
||||||
|
name=prereg_realm.name,
|
||||||
|
)
|
||||||
|
self.assertTrue(Realm.objects.filter(string_id=prereg_realm.string_id).exists())
|
||||||
|
prereg_realm.created_realm = realm
|
||||||
|
prereg_realm.save()
|
||||||
|
|
||||||
|
confirmation_key = find_key_by_email(email)
|
||||||
|
assert confirmation_key is not None
|
||||||
|
# We don't allow cancellation if the complete import work is done.
|
||||||
|
with self.assertRaises(AssertionError), self.assertLogs("django.request", "ERROR"):
|
||||||
|
self.client_post(
|
||||||
|
"/new/import/slack/",
|
||||||
|
{
|
||||||
|
"key": confirmation_key,
|
||||||
|
"cancel_import": "true",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@@ -7,6 +7,7 @@ from urllib.parse import urlencode, urljoin
|
|||||||
import orjson
|
import orjson
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import REDIRECT_FIELD_NAME, authenticate, get_backends
|
from django.contrib.auth import REDIRECT_FIELD_NAME, authenticate, get_backends
|
||||||
|
from django.contrib.auth.tokens import default_token_generator
|
||||||
from django.contrib.sessions.backends.base import SessionBase
|
from django.contrib.sessions.backends.base import SessionBase
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@@ -18,9 +19,11 @@ from django.shortcuts import redirect, render
|
|||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import get_language
|
from django.utils.translation import get_language
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from django_auth_ldap.backend import LDAPBackend, _LDAPUser
|
from django_auth_ldap.backend import LDAPBackend, _LDAPUser
|
||||||
from pydantic import Json, NonNegativeInt, StringConstraints
|
from pydantic import Json, NonNegativeInt, StringConstraints
|
||||||
|
|
||||||
|
from confirmation import settings as confirmation_settings
|
||||||
from confirmation.models import (
|
from confirmation.models import (
|
||||||
Confirmation,
|
Confirmation,
|
||||||
ConfirmationKeyError,
|
ConfirmationKeyError,
|
||||||
@@ -36,12 +39,14 @@ from zerver.actions.default_streams import lookup_default_stream_groups
|
|||||||
from zerver.actions.user_settings import (
|
from zerver.actions.user_settings import (
|
||||||
do_change_full_name,
|
do_change_full_name,
|
||||||
do_change_password,
|
do_change_password,
|
||||||
|
do_change_user_delivery_email,
|
||||||
do_change_user_setting,
|
do_change_user_setting,
|
||||||
)
|
)
|
||||||
from zerver.actions.users import do_change_user_role
|
from zerver.actions.users import do_change_user_role, generate_password_reset_url
|
||||||
from zerver.context_processors import (
|
from zerver.context_processors import (
|
||||||
get_realm_create_form_context,
|
get_realm_create_form_context,
|
||||||
get_realm_from_request,
|
get_realm_from_request,
|
||||||
|
is_realm_import_enabled,
|
||||||
login_context,
|
login_context,
|
||||||
)
|
)
|
||||||
from zerver.decorator import add_google_analytics, do_login, require_post
|
from zerver.decorator import add_google_analytics, do_login, require_post
|
||||||
@@ -49,12 +54,13 @@ from zerver.forms import (
|
|||||||
CaptchaRealmCreationForm,
|
CaptchaRealmCreationForm,
|
||||||
FindMyTeamForm,
|
FindMyTeamForm,
|
||||||
HomepageForm,
|
HomepageForm,
|
||||||
|
ImportRealmOwnerSelectionForm,
|
||||||
RealmCreationForm,
|
RealmCreationForm,
|
||||||
RealmRedirectForm,
|
RealmRedirectForm,
|
||||||
RegistrationForm,
|
RegistrationForm,
|
||||||
)
|
)
|
||||||
from zerver.lib.email_validation import email_allowed_for_realm, validate_email_not_already_in_realm
|
from zerver.lib.email_validation import email_allowed_for_realm, validate_email_not_already_in_realm
|
||||||
from zerver.lib.exceptions import RateLimitedError
|
from zerver.lib.exceptions import JsonableError, RateLimitedError
|
||||||
from zerver.lib.i18n import (
|
from zerver.lib.i18n import (
|
||||||
get_browser_language_code,
|
get_browser_language_code,
|
||||||
get_default_language_for_anonymous_user,
|
get_default_language_for_anonymous_user,
|
||||||
@@ -62,7 +68,9 @@ from zerver.lib.i18n import (
|
|||||||
get_language_name,
|
get_language_name,
|
||||||
)
|
)
|
||||||
from zerver.lib.pysa import mark_sanitized
|
from zerver.lib.pysa import mark_sanitized
|
||||||
|
from zerver.lib.queue import queue_json_publish_rollback_unsafe
|
||||||
from zerver.lib.rate_limiter import rate_limit_request_by_ip
|
from zerver.lib.rate_limiter import rate_limit_request_by_ip
|
||||||
|
from zerver.lib.response import json_success
|
||||||
from zerver.lib.send_email import EmailNotDeliveredError, FromAddress, send_email
|
from zerver.lib.send_email import EmailNotDeliveredError, FromAddress, send_email
|
||||||
from zerver.lib.sessions import get_expirable_session_var
|
from zerver.lib.sessions import get_expirable_session_var
|
||||||
from zerver.lib.subdomains import get_subdomain
|
from zerver.lib.subdomains import get_subdomain
|
||||||
@@ -77,10 +85,12 @@ from zerver.lib.typed_endpoint_validators import (
|
|||||||
non_negative_int_or_none_validator,
|
non_negative_int_or_none_validator,
|
||||||
timezone_or_empty_validator,
|
timezone_or_empty_validator,
|
||||||
)
|
)
|
||||||
|
from zerver.lib.upload import all_message_attachments
|
||||||
from zerver.lib.url_encoding import append_url_query_string
|
from zerver.lib.url_encoding import append_url_query_string
|
||||||
from zerver.lib.users import get_accounts_for_email
|
from zerver.lib.users import get_accounts_for_email
|
||||||
from zerver.lib.zephyr import compute_mit_user_fullname
|
from zerver.lib.zephyr import compute_mit_user_fullname
|
||||||
from zerver.models import (
|
from zerver.models import (
|
||||||
|
Message,
|
||||||
MultiuseInvite,
|
MultiuseInvite,
|
||||||
NamedUserGroup,
|
NamedUserGroup,
|
||||||
PreregistrationRealm,
|
PreregistrationRealm,
|
||||||
@@ -91,7 +101,7 @@ from zerver.models import (
|
|||||||
UserProfile,
|
UserProfile,
|
||||||
)
|
)
|
||||||
from zerver.models.constants import MAX_LANGUAGE_ID_LENGTH
|
from zerver.models.constants import MAX_LANGUAGE_ID_LENGTH
|
||||||
from zerver.models.realm_audit_logs import RealmAuditLog
|
from zerver.models.realm_audit_logs import AuditLogEventType, RealmAuditLog
|
||||||
from zerver.models.realms import (
|
from zerver.models.realms import (
|
||||||
DisposableEmailError,
|
DisposableEmailError,
|
||||||
DomainNotAllowedForRealmError,
|
DomainNotAllowedForRealmError,
|
||||||
@@ -101,12 +111,17 @@ from zerver.models.realms import (
|
|||||||
name_changes_disabled,
|
name_changes_disabled,
|
||||||
)
|
)
|
||||||
from zerver.models.streams import get_default_stream_groups
|
from zerver.models.streams import get_default_stream_groups
|
||||||
from zerver.models.users import get_source_profile, get_user_by_delivery_email
|
from zerver.models.users import (
|
||||||
|
get_source_profile,
|
||||||
|
get_user_by_delivery_email,
|
||||||
|
get_user_profile_by_id_in_realm,
|
||||||
|
)
|
||||||
from zerver.views.auth import (
|
from zerver.views.auth import (
|
||||||
create_preregistration_realm,
|
create_preregistration_realm,
|
||||||
create_preregistration_user,
|
create_preregistration_user,
|
||||||
finish_desktop_flow,
|
finish_desktop_flow,
|
||||||
finish_mobile_flow,
|
finish_mobile_flow,
|
||||||
|
get_safe_redirect_to,
|
||||||
redirect_and_log_into_subdomain,
|
redirect_and_log_into_subdomain,
|
||||||
redirect_to_deactivation_notice,
|
redirect_to_deactivation_notice,
|
||||||
)
|
)
|
||||||
@@ -149,7 +164,11 @@ def get_prereg_key_and_redirect(
|
|||||||
|
|
||||||
registration_url = reverse("accounts_register")
|
registration_url = reverse("accounts_register")
|
||||||
if realm_creation:
|
if realm_creation:
|
||||||
registration_url = reverse("realm_register")
|
assert isinstance(prereg_object, PreregistrationRealm)
|
||||||
|
if prereg_object.data_import_metadata.get("import_from") == "slack":
|
||||||
|
registration_url = reverse("import_realm_from_slack")
|
||||||
|
else:
|
||||||
|
registration_url = reverse("realm_register")
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
@@ -186,9 +205,16 @@ def check_prereg_key(
|
|||||||
|
|
||||||
if realm_creation:
|
if realm_creation:
|
||||||
assert isinstance(prereg_object, PreregistrationRealm)
|
assert isinstance(prereg_object, PreregistrationRealm)
|
||||||
# Defensive assert to make sure no mix-up in how .status is set leading to reuse
|
if prereg_object.data_import_metadata.get("need_select_realm_owner"):
|
||||||
# of a PreregistrationRealm object.
|
# Allow user to get back to the import page to select realm owner.
|
||||||
assert prereg_object.created_realm is None
|
# This is for a special case where the realm import has finished
|
||||||
|
# but user closed the import process browser window and needs to
|
||||||
|
# get back to the import page from the email confirmation link.
|
||||||
|
assert prereg_object.created_realm is not None
|
||||||
|
else:
|
||||||
|
# Defensive assert to make sure no mix-up in how .status is set leading to reuse
|
||||||
|
# of a PreregistrationRealm object.
|
||||||
|
assert prereg_object.created_realm is None
|
||||||
else:
|
else:
|
||||||
assert isinstance(prereg_object, PreregistrationUser)
|
assert isinstance(prereg_object, PreregistrationUser)
|
||||||
# Defensive assert to make sure no mix-up in how .status is set leading to reuse
|
# Defensive assert to make sure no mix-up in how .status is set leading to reuse
|
||||||
@@ -227,6 +253,11 @@ def accounts_register(*args: Any, **kwargs: Any) -> HttpResponse:
|
|||||||
return registration_helper(*args, **kwargs)
|
return registration_helper(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@require_post
|
||||||
|
def import_realm_from_slack(*args: Any, **kwargs: Any) -> HttpResponse:
|
||||||
|
return registration_helper(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@typed_endpoint
|
@typed_endpoint
|
||||||
def registration_helper(
|
def registration_helper(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
@@ -237,6 +268,9 @@ def registration_helper(
|
|||||||
form_full_name: Annotated[str | None, ApiParamConfig("full_name")] = None,
|
form_full_name: Annotated[str | None, ApiParamConfig("full_name")] = None,
|
||||||
source_realm_id: Annotated[NonNegativeInt | None, non_negative_int_or_none_validator()] = 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,
|
form_is_demo_organization: Annotated[str | None, ApiParamConfig("is_demo_organization")] = None,
|
||||||
|
slack_access_token: str | None = None,
|
||||||
|
start_slack_import: Json[bool] = False,
|
||||||
|
cancel_import: Json[bool] = False,
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
try:
|
try:
|
||||||
prereg_object, realm_creation = check_prereg_key(request, key)
|
prereg_object, realm_creation = check_prereg_key(request, key)
|
||||||
@@ -249,6 +283,117 @@ def registration_helper(
|
|||||||
if realm_creation:
|
if realm_creation:
|
||||||
assert isinstance(prereg_object, PreregistrationRealm)
|
assert isinstance(prereg_object, PreregistrationRealm)
|
||||||
prereg_realm = prereg_object
|
prereg_realm = prereg_object
|
||||||
|
|
||||||
|
if cancel_import:
|
||||||
|
if prereg_realm.created_realm or prereg_realm.data_import_metadata.get(
|
||||||
|
"is_import_work_queued"
|
||||||
|
):
|
||||||
|
# This cancellation flow is just to go back to normal
|
||||||
|
# realm creation before one has started it. If the
|
||||||
|
# user somehow triggers this operation (no longer
|
||||||
|
# visible in the UI) after that point, we just
|
||||||
|
# redirect user import status page with a message that
|
||||||
|
# import work has already started.
|
||||||
|
return TemplateResponse(
|
||||||
|
request,
|
||||||
|
"zerver/slack_import.html",
|
||||||
|
{
|
||||||
|
"import_poll_error_message": _(
|
||||||
|
"Unable to cancel import once it has started."
|
||||||
|
),
|
||||||
|
"poll_for_import_completion": True,
|
||||||
|
"key": key,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# If the user cancels the import process, it is critical
|
||||||
|
# to remove any metadata that was added during the import
|
||||||
|
# process.
|
||||||
|
# NOTE: Don't revoke the confirmation key here, to allow
|
||||||
|
# the user to continue with the registration process if
|
||||||
|
# no import flow has started. This is important otherwise
|
||||||
|
# the user will be unable to register using the same subdomain.
|
||||||
|
prereg_realm.data_import_metadata = {}
|
||||||
|
prereg_realm.save(update_fields=["data_import_metadata"])
|
||||||
|
# Return back to normal registration flow.
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
reverse("get_prereg_key_and_redirect", kwargs={"confirmation_key": key})
|
||||||
|
)
|
||||||
|
|
||||||
|
if start_slack_import:
|
||||||
|
assert is_realm_import_enabled()
|
||||||
|
assert prereg_realm.data_import_metadata.get("slack_access_token") is not None
|
||||||
|
assert prereg_realm.data_import_metadata.get("uploaded_import_file_name") is not None
|
||||||
|
assert prereg_realm.data_import_metadata.get("is_import_work_queued") is not True
|
||||||
|
assert prereg_realm.created_realm is None
|
||||||
|
queue_json_publish_rollback_unsafe(
|
||||||
|
"deferred_work",
|
||||||
|
{
|
||||||
|
"type": "import_slack_data",
|
||||||
|
"preregistration_realm_id": prereg_realm.id,
|
||||||
|
"filename": f"import/{prereg_realm.id}/slack.zip",
|
||||||
|
"slack_access_token": prereg_realm.data_import_metadata["slack_access_token"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# Avoid starting the import process multiple times.
|
||||||
|
prereg_realm.data_import_metadata["is_import_work_queued"] = True
|
||||||
|
prereg_realm.save(update_fields=["data_import_metadata"])
|
||||||
|
|
||||||
|
return TemplateResponse(
|
||||||
|
request,
|
||||||
|
"zerver/slack_import.html",
|
||||||
|
{
|
||||||
|
"poll_for_import_completion": True,
|
||||||
|
"key": key,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
elif prereg_realm.data_import_metadata.get("import_from") == "slack":
|
||||||
|
if prereg_realm.data_import_metadata.get("need_select_realm_owner"):
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
reverse("realm_import_post_process", kwargs={"confirmation_key": key})
|
||||||
|
)
|
||||||
|
|
||||||
|
assert is_realm_import_enabled()
|
||||||
|
context: dict[str, Any] = {
|
||||||
|
"key": key,
|
||||||
|
"max_file_size": settings.MAX_WEB_DATA_IMPORT_SIZE_MB,
|
||||||
|
}
|
||||||
|
|
||||||
|
saved_slack_access_token = prereg_realm.data_import_metadata.get("slack_access_token")
|
||||||
|
if saved_slack_access_token or slack_access_token:
|
||||||
|
if slack_access_token and slack_access_token != saved_slack_access_token:
|
||||||
|
# Verify slack token access.
|
||||||
|
from zerver.data_import.slack import (
|
||||||
|
SLACK_IMPORT_TOKEN_SCOPES,
|
||||||
|
check_token_access,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
check_token_access(slack_access_token, SLACK_IMPORT_TOKEN_SCOPES)
|
||||||
|
except Exception as e:
|
||||||
|
context["slack_access_token_validation_error"] = str(e)
|
||||||
|
return TemplateResponse(
|
||||||
|
request,
|
||||||
|
"zerver/slack_import.html",
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
|
||||||
|
saved_slack_access_token = slack_access_token
|
||||||
|
prereg_realm.data_import_metadata["slack_access_token"] = slack_access_token
|
||||||
|
prereg_realm.save(update_fields=["data_import_metadata"])
|
||||||
|
|
||||||
|
context["slack_access_token"] = saved_slack_access_token
|
||||||
|
context["uploaded_import_file_name"] = prereg_realm.data_import_metadata.get(
|
||||||
|
"uploaded_import_file_name"
|
||||||
|
)
|
||||||
|
|
||||||
|
return TemplateResponse(
|
||||||
|
request,
|
||||||
|
"zerver/slack_import.html",
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
|
||||||
password_required = True
|
password_required = True
|
||||||
role = UserProfile.ROLE_REALM_OWNER
|
role = UserProfile.ROLE_REALM_OWNER
|
||||||
else:
|
else:
|
||||||
@@ -891,6 +1036,193 @@ def redirect_to_email_login_url(email: str) -> HttpResponseRedirect:
|
|||||||
return HttpResponseRedirect(redirect_url)
|
return HttpResponseRedirect(redirect_url)
|
||||||
|
|
||||||
|
|
||||||
|
@typed_endpoint
|
||||||
|
def realm_import_status(
|
||||||
|
request: HttpRequest,
|
||||||
|
*,
|
||||||
|
confirmation_key: str,
|
||||||
|
) -> HttpResponse: # nocoverage
|
||||||
|
try:
|
||||||
|
preregistration_realm = get_object_from_key(
|
||||||
|
confirmation_key,
|
||||||
|
[Confirmation.REALM_CREATION],
|
||||||
|
mark_object_used=False,
|
||||||
|
allow_used=True,
|
||||||
|
)
|
||||||
|
except ConfirmationKeyError:
|
||||||
|
raise JsonableError(_("Unauthenticated"))
|
||||||
|
|
||||||
|
assert isinstance(preregistration_realm, PreregistrationRealm)
|
||||||
|
try:
|
||||||
|
realm = Realm.objects.get(string_id=preregistration_realm.string_id)
|
||||||
|
except Realm.DoesNotExist:
|
||||||
|
# TODO: Either store the path to the temporary conversion directory on
|
||||||
|
# preregistration_realm.data_import_metadata, or have the conversion
|
||||||
|
# process support writing updates to this for a better progress indicator.
|
||||||
|
return json_success(request, {"status": _("Converting Slack data…")})
|
||||||
|
|
||||||
|
if realm.deactivated:
|
||||||
|
# These "if" cases are in the inverse order than they're done
|
||||||
|
# in the import process, so we get the latest step that it's
|
||||||
|
# on.
|
||||||
|
if Message.objects.filter(realm_id=realm.id).exists():
|
||||||
|
return json_success(request, {"status": _("Importing messages…")})
|
||||||
|
try:
|
||||||
|
next(all_message_attachments(prefix=f"{realm.id}/"))
|
||||||
|
return json_success(request, {"status": _("Importing attachment data…")})
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
return json_success(request, {"status": _("Importing converted Slack data…")})
|
||||||
|
|
||||||
|
if (
|
||||||
|
not preregistration_realm.data_import_metadata["need_select_realm_owner"]
|
||||||
|
and preregistration_realm.created_realm is None
|
||||||
|
):
|
||||||
|
return json_success(request, {"status": _("Finalizing import…")})
|
||||||
|
|
||||||
|
# We have a non-deactivated realm and it's linked to the prereg key
|
||||||
|
result = {"status": _("Done!")}
|
||||||
|
if not preregistration_realm.data_import_metadata["need_select_realm_owner"]:
|
||||||
|
importing_user = get_user_by_delivery_email(preregistration_realm.email, realm)
|
||||||
|
# Sanity check that this is a normal user account that can login.
|
||||||
|
assert (
|
||||||
|
importing_user.is_active
|
||||||
|
and not importing_user.is_bot
|
||||||
|
and not importing_user.is_mirror_dummy
|
||||||
|
)
|
||||||
|
|
||||||
|
# Allow setting an initial password for this first account,
|
||||||
|
# being careful to ensure this data import confirmation link
|
||||||
|
# can't be abused to reset the importing user's password in
|
||||||
|
# the future.
|
||||||
|
if (
|
||||||
|
not importing_user.has_usable_password()
|
||||||
|
and not RealmAuditLog.objects.filter(
|
||||||
|
modified_user=importing_user, event_type=AuditLogEventType.USER_PASSWORD_CHANGED
|
||||||
|
).exists()
|
||||||
|
):
|
||||||
|
result["redirect"] = generate_password_reset_url(
|
||||||
|
importing_user, default_token_generator
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
result["redirect"] = get_safe_redirect_to(reverse("login"), realm.url)
|
||||||
|
else:
|
||||||
|
# The email address in the import may not match the email
|
||||||
|
# address they provided. Ask user which user they want to become.
|
||||||
|
result["status"] = _("No users matching provided email.")
|
||||||
|
result["redirect"] = reverse(
|
||||||
|
"realm_import_post_process", kwargs={"confirmation_key": confirmation_key}
|
||||||
|
)
|
||||||
|
|
||||||
|
return json_success(request, result)
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic(durable=True)
|
||||||
|
def realm_import_post_process(
|
||||||
|
request: HttpRequest,
|
||||||
|
confirmation_key: str,
|
||||||
|
) -> HttpResponse:
|
||||||
|
try:
|
||||||
|
preregistration_realm = get_object_from_key(
|
||||||
|
confirmation_key,
|
||||||
|
[Confirmation.REALM_CREATION],
|
||||||
|
mark_object_used=False,
|
||||||
|
allow_used=True,
|
||||||
|
)
|
||||||
|
except ConfirmationKeyError as exception:
|
||||||
|
return render_confirmation_key_error(request, exception)
|
||||||
|
|
||||||
|
assert isinstance(preregistration_realm, PreregistrationRealm)
|
||||||
|
try:
|
||||||
|
realm = Realm.objects.get(string_id=preregistration_realm.string_id)
|
||||||
|
except Realm.DoesNotExist:
|
||||||
|
# If we cannot find the realm, likely means there was
|
||||||
|
# something wrong with the import process, or it has been
|
||||||
|
# since deleted. Revoke the confirmation key to force user to
|
||||||
|
# restart the process.
|
||||||
|
preregistration_realm.status = confirmation_settings.STATUS_REVOKED
|
||||||
|
preregistration_realm.save(update_fields=["status"])
|
||||||
|
|
||||||
|
return render_confirmation_key_error(
|
||||||
|
request, ConfirmationKeyError(ConfirmationKeyError.EXPIRED)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not preregistration_realm.data_import_metadata["need_select_realm_owner"]:
|
||||||
|
return HttpResponseRedirect(get_safe_redirect_to(reverse("login"), realm.url))
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
form = ImportRealmOwnerSelectionForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
# This is a highly sensitive code path, since what we're
|
||||||
|
# about to do is take control of another user account AND
|
||||||
|
# promote that account to organization owner.
|
||||||
|
#
|
||||||
|
# We need this code path for Slack import if the importing
|
||||||
|
# user's account doesn't exist in the Slack import, but we
|
||||||
|
# must be VERY careful to make sure we're in that situation.
|
||||||
|
|
||||||
|
# Validate that this PreregistrationRealm object is in the
|
||||||
|
# expected state, with no user matching the email address,
|
||||||
|
# and and having created this realm.
|
||||||
|
assert preregistration_realm.data_import_metadata["need_select_realm_owner"]
|
||||||
|
assert preregistration_realm.created_realm_id == realm.id
|
||||||
|
assert preregistration_realm.status != confirmation_settings.STATUS_USED
|
||||||
|
|
||||||
|
# ID of the imported user account that the importing user has
|
||||||
|
# selected to become their account.
|
||||||
|
user_id = form.cleaned_data["user_id"]
|
||||||
|
|
||||||
|
# Validate that a normal user account that can login was selected.
|
||||||
|
importing_user = get_user_profile_by_id_in_realm(user_id, realm)
|
||||||
|
assert (
|
||||||
|
importing_user.is_active
|
||||||
|
and not importing_user.is_bot
|
||||||
|
and not importing_user.is_mirror_dummy
|
||||||
|
)
|
||||||
|
|
||||||
|
# Promote to realm owner and set email address to what
|
||||||
|
# we've validated. This is safe because we've previously
|
||||||
|
# validated that this specific confirmation link was used
|
||||||
|
# to create this specific realm and cannot have been used
|
||||||
|
# to finish creating an account yet.
|
||||||
|
do_change_user_role(
|
||||||
|
importing_user, UserProfile.ROLE_REALM_OWNER, acting_user=importing_user
|
||||||
|
)
|
||||||
|
do_change_user_delivery_email(
|
||||||
|
importing_user, preregistration_realm.email, acting_user=importing_user
|
||||||
|
)
|
||||||
|
|
||||||
|
preregistration_realm.status = confirmation_settings.STATUS_USED
|
||||||
|
preregistration_realm.data_import_metadata["need_select_realm_owner"] = False
|
||||||
|
preregistration_realm.save()
|
||||||
|
|
||||||
|
# End by letting the importing user set a password for their new account.
|
||||||
|
assert (
|
||||||
|
not importing_user.has_usable_password()
|
||||||
|
and not RealmAuditLog.objects.filter(
|
||||||
|
modified_user=importing_user, event_type=AuditLogEventType.USER_PASSWORD_CHANGED
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
generate_password_reset_url(importing_user, default_token_generator)
|
||||||
|
)
|
||||||
|
|
||||||
|
claimable_users = UserProfile.objects.filter(
|
||||||
|
realm=realm, is_active=True, is_bot=False, is_mirror_dummy=False
|
||||||
|
)
|
||||||
|
context = {
|
||||||
|
"users": claimable_users,
|
||||||
|
"verified_email": preregistration_realm.email,
|
||||||
|
"key": confirmation_key,
|
||||||
|
}
|
||||||
|
|
||||||
|
return TemplateResponse(
|
||||||
|
request,
|
||||||
|
"zerver/realm_import_post_process.html",
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@add_google_analytics
|
@add_google_analytics
|
||||||
def create_realm(request: HttpRequest, creation_key: str | None = None) -> HttpResponse:
|
def create_realm(request: HttpRequest, creation_key: str | None = None) -> HttpResponse:
|
||||||
try:
|
try:
|
||||||
|
@@ -12,6 +12,7 @@ from django.utils.translation import gettext as _
|
|||||||
from django.utils.translation import override as override_language
|
from django.utils.translation import override as override_language
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
|
from zerver.actions.data_import import import_slack_data
|
||||||
from zerver.actions.message_flags import do_mark_stream_messages_as_read
|
from zerver.actions.message_flags import do_mark_stream_messages_as_read
|
||||||
from zerver.actions.message_send import internal_send_private_message
|
from zerver.actions.message_send import internal_send_private_message
|
||||||
from zerver.actions.realm_export import notify_realm_export
|
from zerver.actions.realm_export import notify_realm_export
|
||||||
@@ -236,6 +237,8 @@ class DeferredWorker(QueueProcessingWorker):
|
|||||||
)
|
)
|
||||||
for realm in realms_to_scrub:
|
for realm in realms_to_scrub:
|
||||||
scrub_deactivated_realm(realm)
|
scrub_deactivated_realm(realm)
|
||||||
|
elif event["type"] == "import_slack_data":
|
||||||
|
import_slack_data(event)
|
||||||
|
|
||||||
end = time.time()
|
end = time.time()
|
||||||
logger.info(
|
logger.info(
|
||||||
|
@@ -639,6 +639,9 @@ NAGIOS_BOT_HOST = SYSTEM_BOT_REALM + "." + EXTERNAL_HOST
|
|||||||
# Use half of the available CPUs for data import purposes.
|
# Use half of the available CPUs for data import purposes.
|
||||||
DEFAULT_DATA_EXPORT_IMPORT_PARALLELISM = (len(os.sched_getaffinity(0)) // 2) or 1
|
DEFAULT_DATA_EXPORT_IMPORT_PARALLELISM = (len(os.sched_getaffinity(0)) // 2) or 1
|
||||||
|
|
||||||
|
# Use the default tmpfile path for automated imports
|
||||||
|
IMPORT_TMPFILE_DIRECTORY: str | None = None
|
||||||
|
|
||||||
# How long after the last upgrade to nag users that the server needs
|
# How long after the last upgrade to nag users that the server needs
|
||||||
# to be upgraded because of likely security releases in the meantime.
|
# to be upgraded because of likely security releases in the meantime.
|
||||||
# Default is 18 months, constructed as 12 months before someone should
|
# Default is 18 months, constructed as 12 months before someone should
|
||||||
|
@@ -146,7 +146,10 @@ from zerver.views.registration import (
|
|||||||
create_realm,
|
create_realm,
|
||||||
find_account,
|
find_account,
|
||||||
get_prereg_key_and_redirect,
|
get_prereg_key_and_redirect,
|
||||||
|
import_realm_from_slack,
|
||||||
new_realm_send_confirm,
|
new_realm_send_confirm,
|
||||||
|
realm_import_post_process,
|
||||||
|
realm_import_status,
|
||||||
realm_redirect,
|
realm_redirect,
|
||||||
realm_register,
|
realm_register,
|
||||||
signup_send_confirm,
|
signup_send_confirm,
|
||||||
@@ -622,6 +625,16 @@ i18n_urls = [
|
|||||||
),
|
),
|
||||||
path("accounts/register/", accounts_register, name="accounts_register"),
|
path("accounts/register/", accounts_register, name="accounts_register"),
|
||||||
path("realm/register/", realm_register, name="realm_register"),
|
path("realm/register/", realm_register, name="realm_register"),
|
||||||
|
path(
|
||||||
|
"realm/import/post_process/<confirmation_key>",
|
||||||
|
realm_import_post_process,
|
||||||
|
name="realm_import_post_process",
|
||||||
|
),
|
||||||
|
path("new/import/slack/", import_realm_from_slack, name="import_realm_from_slack"),
|
||||||
|
path(
|
||||||
|
"json/realm/import/status/<confirmation_key>",
|
||||||
|
realm_import_status,
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"accounts/do_confirm/<confirmation_key>",
|
"accounts/do_confirm/<confirmation_key>",
|
||||||
get_prereg_key_and_redirect,
|
get_prereg_key_and_redirect,
|
||||||
|
Reference in New Issue
Block a user