예제 #1
0
    def test_change_email_deactivated_user_realm(self) -> None:
        data = {"email": "*****@*****.**"}
        user_profile = self.example_user("hamlet")
        self.login_user(user_profile)
        url = "/json/settings"
        self.assert_length(mail.outbox, 0)
        result = self.client_patch(url, data)
        self.assert_length(mail.outbox, 1)
        self.assert_json_success(result)
        email_message = mail.outbox[0]
        self.assertEqual(
            email_message.subject,
            "Verify your new email address",
        )
        body = email_message.body
        self.assertIn("We received a request to change the email", body)

        activation_url = [s for s in body.split("\n") if s][2]

        do_deactivate_user(user_profile, acting_user=None)
        response = self.client_get(activation_url)
        self.assertEqual(response.status_code, 401)

        do_deactivate_realm(user_profile.realm, acting_user=None)

        response = self.client_get(activation_url)
        self.assertEqual(response.status_code, 302)
        self.assertTrue(
            response["Location"].endswith("/accounts/deactivated/"))
예제 #2
0
def do_change_realm_subdomain(
    realm: Realm,
    new_subdomain: str,
    *,
    acting_user: Optional[UserProfile],
    add_deactivated_redirect: bool = True,
) -> None:
    """Changing a realm's subdomain is a highly disruptive operation,
    because all existing clients will need to be updated to point to
    the new URL.  Further, requests to fetch data from existing event
    queues will fail with an authentication error when this change
    happens (because the old subdomain is no longer associated with
    the realm), making it hard for us to provide a graceful update
    experience for clients.
    """
    old_subdomain = realm.subdomain
    old_uri = realm.uri
    # If the realm had been a demo organization scheduled for
    # deleting, clear that state.
    realm.demo_organization_scheduled_deletion_date = None
    realm.string_id = new_subdomain
    with transaction.atomic():
        realm.save(update_fields=["string_id", "demo_organization_scheduled_deletion_date"])
        RealmAuditLog.objects.create(
            realm=realm,
            event_type=RealmAuditLog.REALM_SUBDOMAIN_CHANGED,
            event_time=timezone_now(),
            acting_user=acting_user,
            extra_data={"old_subdomain": old_subdomain, "new_subdomain": new_subdomain},
        )

        # If a realm if being renamed multiple times, we should find all the placeholder
        # realms and reset their deactivated_redirect field to point to the new realm uri
        placeholder_realms = Realm.objects.filter(deactivated_redirect=old_uri, deactivated=True)
        for placeholder_realm in placeholder_realms:
            do_add_deactivated_redirect(placeholder_realm, realm.uri)

    # The below block isn't executed in a transaction with the earlier code due to
    # the functions called below being complex and potentially sending events,
    # which we don't want to do in atomic blocks.
    # When we change a realm's subdomain the realm with old subdomain is basically
    # deactivated. We are creating a deactivated realm using old subdomain and setting
    # it's deactivated redirect to new_subdomain so that we can tell the users that
    # the realm has been moved to a new subdomain.
    if add_deactivated_redirect:
        placeholder_realm = do_create_realm(old_subdomain, realm.name)
        do_deactivate_realm(placeholder_realm, acting_user=None)
        do_add_deactivated_redirect(placeholder_realm, realm.uri)
예제 #3
0
    def test_realm_activation(self) -> None:
        realm = get_realm("zulip")
        user = self.example_user("desdemona")
        do_deactivate_realm(realm, acting_user=user)
        log_entry = RealmAuditLog.objects.get(
            realm=realm,
            event_type=RealmAuditLog.REALM_DEACTIVATED,
            acting_user=user)
        extra_data = orjson.loads(assert_is_not_none(log_entry.extra_data))
        self.check_role_count_schema(extra_data[RealmAuditLog.ROLE_COUNT])

        do_reactivate_realm(realm)
        log_entry = RealmAuditLog.objects.get(
            realm=realm, event_type=RealmAuditLog.REALM_REACTIVATED)
        extra_data = orjson.loads(assert_is_not_none(log_entry.extra_data))
        self.check_role_count_schema(extra_data[RealmAuditLog.ROLE_COUNT])
예제 #4
0
    def handle(self, *args: Any, **options: str) -> None:
        realm = self.get_realm(options)

        assert realm is not None  # Should be ensured by parser

        if options["redirect_url"]:
            print("Setting the redirect URL to", options["redirect_url"])
            do_add_deactivated_redirect(realm, options["redirect_url"])

        if realm.deactivated:
            print("The realm", options["realm_id"], "is already deactivated.")
            exit(0)

        print("Deactivating", options["realm_id"])
        do_deactivate_realm(realm, acting_user=None)
        print("Done!")
예제 #5
0
def deactivate_realm(request: HttpRequest, user: UserProfile) -> HttpResponse:
    realm = user.realm
    do_deactivate_realm(realm, acting_user=user)
    return json_success(request)
예제 #6
0
    def handle(self, *args: Any, **options: Any) -> None:
        realm = self.get_realm(options)
        assert realm is not None  # Should be ensured by parser

        output_dir = options["output_dir"]
        public_only = options["public_only"]
        consent_message_id = options["consent_message_id"]

        print(f"\033[94mExporting realm\033[0m: {realm.string_id}")

        num_threads = int(options["threads"])
        if num_threads < 1:
            raise CommandError("You must have at least one thread.")

        if public_only and consent_message_id is not None:
            raise CommandError(
                "Please pass either --public-only or --consent-message-id")

        if options["deactivate_realm"] and realm.deactivated:
            raise CommandError(
                f"The realm {realm.string_id} is already deactivated.  Aborting..."
            )

        if consent_message_id is not None:
            try:
                message = Message.objects.get(id=consent_message_id)
            except Message.DoesNotExist:
                raise CommandError(
                    "Message with given ID does not exist. Aborting...")

            if message.last_edit_time is not None:
                raise CommandError("Message was edited. Aborting...")

            # Since the message might have been sent by
            # Notification Bot, we can't trivially check the realm of
            # the message through message.sender.realm.  So instead we
            # check the realm of the people who reacted to the message
            # (who must all be in the message's realm).
            reactions = Reaction.objects.filter(
                message=message,
                # outbox = 1f4e4
                emoji_code="1f4e4",
                reaction_type="unicode_emoji",
            )
            for reaction in reactions:
                if reaction.user_profile.realm != realm:
                    raise CommandError(
                        "Users from a different realm reacted to message. Aborting..."
                    )

            print(f"\n\033[94mMessage content:\033[0m\n{message.content}\n")

            user_count = (
                UserProfile.objects.filter(
                    realm_id=realm.id,
                    is_active=True,
                    is_bot=False,
                ).exclude(
                    # We exclude guests, because they're not a priority for
                    # looking at whether most users are being exported.
                    role=UserProfile.ROLE_GUEST, ).count())
            print(
                f"\033[94mNumber of users that reacted outbox:\033[0m {len(reactions)} / {user_count} total non-guest users\n"
            )

            proceed = input("Continue? [y/N] ")
            if proceed.lower() not in ("y", "yes"):
                raise CommandError("Aborting!")

        if output_dir is None:
            output_dir = tempfile.mkdtemp(prefix="zulip-export-")
        else:
            output_dir = os.path.realpath(os.path.expanduser(output_dir))
            if os.path.exists(output_dir):
                if os.listdir(output_dir):
                    raise CommandError(
                        f"Refusing to overwrite nonempty directory: {output_dir}. Aborting...",
                    )
            else:
                os.makedirs(output_dir)

        tarball_path = output_dir.rstrip("/") + ".tar.gz"
        try:
            with open(tarball_path, "x"):
                pass
        except FileExistsError:
            raise CommandError(
                f"Refusing to overwrite existing tarball: {tarball_path}. Aborting..."
            )

        if options["deactivate_realm"]:
            print(f"\033[94mDeactivating realm\033[0m: {realm.string_id}")
            do_deactivate_realm(realm, acting_user=None)

        def percent_callback(bytes_transferred: Any) -> None:
            print(end=".", flush=True)

        # Allows us to trigger exports separately from command line argument parsing
        export_realm_wrapper(
            realm=realm,
            output_dir=output_dir,
            threads=num_threads,
            upload=options["upload"],
            public_only=public_only,
            percent_callback=percent_callback,
            consent_message_id=consent_message_id,
        )
예제 #7
0
def support(
    request: HttpRequest,
    realm_id: Optional[int] = REQ(default=None, converter=to_non_negative_int),
    plan_type: Optional[int] = REQ(default=None,
                                   converter=to_non_negative_int),
    discount: Optional[Decimal] = REQ(default=None, converter=to_decimal),
    new_subdomain: Optional[str] = REQ(default=None),
    status: Optional[str] = REQ(
        default=None, str_validator=check_string_in(VALID_STATUS_VALUES)),
    billing_method: Optional[str] = REQ(
        default=None, str_validator=check_string_in(VALID_BILLING_METHODS)),
    sponsorship_pending: Optional[bool] = REQ(default=None,
                                              json_validator=check_bool),
    approve_sponsorship: bool = REQ(default=False, json_validator=check_bool),
    downgrade_method: Optional[str] = REQ(
        default=None, str_validator=check_string_in(VALID_DOWNGRADE_METHODS)),
    scrub_realm: bool = REQ(default=False, json_validator=check_bool),
    query: Optional[str] = REQ("q", default=None),
    org_type: Optional[int] = REQ(default=None, converter=to_non_negative_int),
) -> HttpResponse:
    context: Dict[str, Any] = {}

    if "success_message" in request.session:
        context["success_message"] = request.session["success_message"]
        del request.session["success_message"]

    if settings.BILLING_ENABLED and request.method == "POST":
        # We check that request.POST only has two keys in it: The
        # realm_id and a field to change.
        keys = set(request.POST.keys())
        if "csrfmiddlewaretoken" in keys:
            keys.remove("csrfmiddlewaretoken")
        if len(keys) != 2:
            raise JsonableError(_("Invalid parameters"))

        assert realm_id is not None
        realm = Realm.objects.get(id=realm_id)

        acting_user = request.user
        assert isinstance(acting_user, UserProfile)
        if plan_type is not None:
            current_plan_type = realm.plan_type
            do_change_realm_plan_type(realm,
                                      plan_type,
                                      acting_user=acting_user)
            msg = f"Plan type of {realm.string_id} changed from {get_plan_name(current_plan_type)} to {get_plan_name(plan_type)} "
            context["success_message"] = msg
        elif org_type is not None:
            current_realm_type = realm.org_type
            do_change_realm_org_type(realm, org_type, acting_user=acting_user)
            msg = f"Org type of {realm.string_id} changed from {get_org_type_display_name(current_realm_type)} to {get_org_type_display_name(org_type)} "
            context["success_message"] = msg
        elif discount is not None:
            current_discount = get_discount_for_realm(realm) or 0
            attach_discount_to_realm(realm, discount, acting_user=acting_user)
            context[
                "success_message"] = f"Discount of {realm.string_id} changed to {discount}% from {current_discount}%."
        elif new_subdomain is not None:
            old_subdomain = realm.string_id
            try:
                check_subdomain_available(new_subdomain)
            except ValidationError as error:
                context["error_message"] = error.message
            else:
                do_change_realm_subdomain(realm,
                                          new_subdomain,
                                          acting_user=acting_user)
                request.session[
                    "success_message"] = f"Subdomain changed from {old_subdomain} to {new_subdomain}"
                return HttpResponseRedirect(
                    reverse("support") + "?" + urlencode({"q": new_subdomain}))
        elif status is not None:
            if status == "active":
                do_send_realm_reactivation_email(realm,
                                                 acting_user=acting_user)
                context[
                    "success_message"] = f"Realm reactivation email sent to admins of {realm.string_id}."
            elif status == "deactivated":
                do_deactivate_realm(realm, acting_user=acting_user)
                context["success_message"] = f"{realm.string_id} deactivated."
        elif billing_method is not None:
            if billing_method == "send_invoice":
                update_billing_method_of_current_plan(
                    realm, charge_automatically=False, acting_user=acting_user)
                context[
                    "success_message"] = f"Billing method of {realm.string_id} updated to pay by invoice."
            elif billing_method == "charge_automatically":
                update_billing_method_of_current_plan(
                    realm, charge_automatically=True, acting_user=acting_user)
                context[
                    "success_message"] = f"Billing method of {realm.string_id} updated to charge automatically."
        elif sponsorship_pending is not None:
            if sponsorship_pending:
                update_sponsorship_status(realm, True, acting_user=acting_user)
                context[
                    "success_message"] = f"{realm.string_id} marked as pending sponsorship."
            else:
                update_sponsorship_status(realm,
                                          False,
                                          acting_user=acting_user)
                context[
                    "success_message"] = f"{realm.string_id} is no longer pending sponsorship."
        elif approve_sponsorship:
            do_approve_sponsorship(realm, acting_user=acting_user)
            context[
                "success_message"] = f"Sponsorship approved for {realm.string_id}"
        elif downgrade_method is not None:
            if downgrade_method == "downgrade_at_billing_cycle_end":
                downgrade_at_the_end_of_billing_cycle(realm)
                context[
                    "success_message"] = f"{realm.string_id} marked for downgrade at the end of billing cycle"
            elif downgrade_method == "downgrade_now_without_additional_licenses":
                downgrade_now_without_creating_additional_invoices(realm)
                context[
                    "success_message"] = f"{realm.string_id} downgraded without creating additional invoices"
            elif downgrade_method == "downgrade_now_void_open_invoices":
                downgrade_now_without_creating_additional_invoices(realm)
                voided_invoices_count = void_all_open_invoices(realm)
                context[
                    "success_message"] = f"{realm.string_id} downgraded and voided {voided_invoices_count} open invoices"
        elif scrub_realm:
            do_scrub_realm(realm, acting_user=acting_user)
            context["success_message"] = f"{realm.string_id} scrubbed."

    if query:
        key_words = get_invitee_emails_set(query)

        users = set(UserProfile.objects.filter(delivery_email__in=key_words))
        realms = set(Realm.objects.filter(string_id__in=key_words))

        for key_word in key_words:
            try:
                URLValidator()(key_word)
                parse_result = urllib.parse.urlparse(key_word)
                hostname = parse_result.hostname
                assert hostname is not None
                if parse_result.port:
                    hostname = f"{hostname}:{parse_result.port}"
                subdomain = get_subdomain_from_hostname(hostname)
                try:
                    realms.add(get_realm(subdomain))
                except Realm.DoesNotExist:
                    pass
            except ValidationError:
                users.update(
                    UserProfile.objects.filter(full_name__iexact=key_word))

        for realm in realms:
            realm.customer = get_customer_by_realm(realm)

            current_plan = get_current_plan_by_realm(realm)
            if current_plan is not None:
                new_plan, last_ledger_entry = make_end_of_cycle_updates_if_needed(
                    current_plan, timezone_now())
                if last_ledger_entry is not None:
                    if new_plan is not None:
                        realm.current_plan = new_plan
                    else:
                        realm.current_plan = current_plan
                    realm.current_plan.licenses = last_ledger_entry.licenses
                    realm.current_plan.licenses_used = get_latest_seat_count(
                        realm)

        # full_names can have , in them
        users.update(UserProfile.objects.filter(full_name__iexact=query))

        context["users"] = users
        context["realms"] = realms

        confirmations: List[Dict[str, Any]] = []

        preregistration_users = PreregistrationUser.objects.filter(
            email__in=key_words)
        confirmations += get_confirmations(
            [
                Confirmation.USER_REGISTRATION, Confirmation.INVITATION,
                Confirmation.REALM_CREATION
            ],
            preregistration_users,
            hostname=request.get_host(),
        )

        multiuse_invites = MultiuseInvite.objects.filter(realm__in=realms)
        confirmations += get_confirmations([Confirmation.MULTIUSE_INVITE],
                                           multiuse_invites)

        confirmations += get_confirmations([Confirmation.REALM_REACTIVATION],
                                           [realm.id for realm in realms])

        context["confirmations"] = confirmations

    def get_realm_owner_emails_as_string(realm: Realm) -> str:
        return ", ".join(realm.get_human_owner_users().order_by(
            "delivery_email").values_list("delivery_email", flat=True))

    def get_realm_admin_emails_as_string(realm: Realm) -> str:
        return ", ".join(
            realm.get_human_admin_users(include_realm_owners=False).order_by(
                "delivery_email").values_list("delivery_email", flat=True))

    context[
        "get_realm_owner_emails_as_string"] = get_realm_owner_emails_as_string
    context[
        "get_realm_admin_emails_as_string"] = get_realm_admin_emails_as_string
    context["get_discount_for_realm"] = get_discount_for_realm
    context["get_org_type_display_name"] = get_org_type_display_name
    context["realm_icon_url"] = realm_icon_url
    context["Confirmation"] = Confirmation
    context["sorted_realm_types"] = sorted(Realm.ORG_TYPES.values(),
                                           key=lambda d: d["display_order"])

    return render(request, "analytics/support.html", context=context)