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/"))
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)
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])
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!")
def deactivate_realm(request: HttpRequest, user: UserProfile) -> HttpResponse: realm = user.realm do_deactivate_realm(realm, acting_user=user) return json_success(request)
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, )
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)