def test_do_change_realm_subdomain_clears_user_realm_cache(self) -> None: """The main complicated thing about changing realm subdomains is updating the cache, and we start by populating the cache for Hamlet, and we end by checking the cache to ensure that his realm appears to be deactivated. You can make this test fail by disabling cache.flush_realm().""" hamlet_id = self.example_user("hamlet").id user = get_user_profile_by_id(hamlet_id) realm = get_realm("zulip") iago = self.example_user("iago") do_change_realm_subdomain(realm, "newzulip", acting_user=iago) user = get_user_profile_by_id(hamlet_id) self.assertEqual(user.realm.string_id, "newzulip") placeholder_realm = get_realm("zulip") self.assertTrue(placeholder_realm.deactivated) self.assertEqual(placeholder_realm.deactivated_redirect, user.realm.uri) realm_audit_log = RealmAuditLog.objects.filter( event_type=RealmAuditLog.REALM_SUBDOMAIN_CHANGED, acting_user=iago).last() expected_extra_data = { "old_subdomain": "zulip", "new_subdomain": "newzulip" } self.assertEqual(realm_audit_log.extra_data, str(expected_extra_data)) self.assertEqual(realm_audit_log.acting_user, iago)
def handle(self, *args: Any, **options: str) -> None: realm = self.get_realm(options) assert realm is not None # Should be ensured by parser new_subdomain = options["new_subdomain"] do_change_realm_subdomain(realm, new_subdomain, acting_user=None) print("Done!")
def test_do_change_realm_subdomain_clears_user_realm_cache(self) -> None: """The main complicated thing about changing realm subdomains is updating the cache, and we start by populating the cache for Hamlet, and we end by checking the cache to ensure that his realm appears to be deactivated. You can make this test fail by disabling cache.flush_realm().""" user = get_user_profile_by_email('*****@*****.**') realm = get_realm('zulip') do_change_realm_subdomain(realm, "newzulip") user = get_user_profile_by_email('*****@*****.**') self.assertEqual(user.realm.string_id, "newzulip") # This doesn't use a cache right now, but may later. self.assertIsNone(get_realm("zulip"))
def test_do_change_realm_subdomain_clears_user_realm_cache(self) -> None: """The main complicated thing about changing realm subdomains is updating the cache, and we start by populating the cache for Hamlet, and we end by checking the cache to ensure that his realm appears to be deactivated. You can make this test fail by disabling cache.flush_realm().""" user = get_user_profile_by_email("*****@*****.**") realm = get_realm("zulip") do_change_realm_subdomain(realm, "newzulip") user = get_user_profile_by_email("*****@*****.**") self.assertEqual(user.realm.string_id, "newzulip") placeholder_realm = get_realm("zulip") self.assertTrue(placeholder_realm.deactivated) self.assertEqual(placeholder_realm.deactivated_redirect, user.realm.uri)
def update_realm( request: HttpRequest, user_profile: UserProfile, name: Optional[str] = REQ(str_validator=check_capped_string( Realm.MAX_REALM_NAME_LENGTH), default=None), description: Optional[str] = REQ(str_validator=check_capped_string( Realm.MAX_REALM_DESCRIPTION_LENGTH), default=None), emails_restricted_to_domains: Optional[bool] = REQ( json_validator=check_bool, default=None), disallow_disposable_email_addresses: Optional[bool] = REQ( json_validator=check_bool, default=None), invite_required: Optional[bool] = REQ(json_validator=check_bool, default=None), invite_to_realm_policy: Optional[int] = REQ(json_validator=check_int_in( Realm.INVITE_TO_REALM_POLICY_TYPES), default=None), name_changes_disabled: Optional[bool] = REQ(json_validator=check_bool, default=None), email_changes_disabled: Optional[bool] = REQ(json_validator=check_bool, default=None), avatar_changes_disabled: Optional[bool] = REQ(json_validator=check_bool, default=None), inline_image_preview: Optional[bool] = REQ(json_validator=check_bool, default=None), inline_url_embed_preview: Optional[bool] = REQ(json_validator=check_bool, default=None), add_custom_emoji_policy: Optional[int] = REQ(json_validator=check_int_in( Realm.COMMON_POLICY_TYPES), default=None), delete_own_message_policy: Optional[int] = REQ(json_validator=check_int_in( Realm.COMMON_MESSAGE_POLICY_TYPES), default=None), message_content_delete_limit_seconds_raw: Optional[Union[int, str]] = REQ( "message_content_delete_limit_seconds", json_validator=check_string_or_int, default=None), allow_message_editing: Optional[bool] = REQ(json_validator=check_bool, default=None), edit_topic_policy: Optional[int] = REQ(json_validator=check_int_in( Realm.COMMON_MESSAGE_POLICY_TYPES), default=None), mandatory_topics: Optional[bool] = REQ(json_validator=check_bool, default=None), message_content_edit_limit_seconds: Optional[int] = REQ( converter=to_non_negative_int, default=None), allow_edit_history: Optional[bool] = REQ(json_validator=check_bool, default=None), default_language: Optional[str] = REQ(default=None), waiting_period_threshold: Optional[int] = REQ( converter=to_non_negative_int, default=None), authentication_methods: Optional[Dict[str, Any]] = REQ( json_validator=check_dict([]), default=None), notifications_stream_id: Optional[int] = REQ(json_validator=check_int, default=None), signup_notifications_stream_id: Optional[int] = REQ( json_validator=check_int, default=None), message_retention_days_raw: Optional[Union[int, str]] = REQ( "message_retention_days", json_validator=check_string_or_int, default=None), send_welcome_emails: Optional[bool] = REQ(json_validator=check_bool, default=None), digest_emails_enabled: Optional[bool] = REQ(json_validator=check_bool, default=None), message_content_allowed_in_email_notifications: Optional[bool] = REQ( json_validator=check_bool, default=None), bot_creation_policy: Optional[int] = REQ(json_validator=check_int_in( Realm.BOT_CREATION_POLICY_TYPES), default=None), create_public_stream_policy: Optional[int] = REQ( json_validator=check_int_in(Realm.COMMON_POLICY_TYPES), default=None), create_private_stream_policy: Optional[int] = REQ( json_validator=check_int_in(Realm.COMMON_POLICY_TYPES), default=None), create_web_public_stream_policy: Optional[int] = REQ( json_validator=check_int_in( Realm.CREATE_WEB_PUBLIC_STREAM_POLICY_TYPES), default=None), invite_to_stream_policy: Optional[int] = REQ(json_validator=check_int_in( Realm.COMMON_POLICY_TYPES), default=None), move_messages_between_streams_policy: Optional[int] = REQ( json_validator=check_int_in(Realm.COMMON_POLICY_TYPES), default=None), user_group_edit_policy: Optional[int] = REQ(json_validator=check_int_in( Realm.COMMON_POLICY_TYPES), default=None), private_message_policy: Optional[int] = REQ(json_validator=check_int_in( Realm.PRIVATE_MESSAGE_POLICY_TYPES), default=None), wildcard_mention_policy: Optional[int] = REQ(json_validator=check_int_in( Realm.WILDCARD_MENTION_POLICY_TYPES), default=None), email_address_visibility: Optional[int] = REQ(json_validator=check_int_in( Realm.EMAIL_ADDRESS_VISIBILITY_TYPES), default=None), video_chat_provider: Optional[int] = REQ(json_validator=check_int, default=None), giphy_rating: Optional[int] = REQ(json_validator=check_int, default=None), default_code_block_language: Optional[str] = REQ(default=None), digest_weekday: Optional[int] = REQ(json_validator=check_int_in( Realm.DIGEST_WEEKDAY_VALUES), default=None), string_id: Optional[str] = REQ( str_validator=check_capped_string(Realm.MAX_REALM_SUBDOMAIN_LENGTH), default=None, ), enable_spectator_access: Optional[bool] = REQ(json_validator=check_bool, default=None), ) -> HttpResponse: realm = user_profile.realm # Additional validation/error checking beyond types go here, so # the entire request can succeed or fail atomically. if default_language is not None and default_language not in get_available_language_codes( ): raise JsonableError( _("Invalid language '{}'").format(default_language)) if authentication_methods is not None: if not user_profile.is_realm_owner: raise OrganizationOwnerRequired() if True not in list(authentication_methods.values()): raise JsonableError( _("At least one authentication method must be enabled.")) if video_chat_provider is not None and video_chat_provider not in { p["id"] for p in Realm.VIDEO_CHAT_PROVIDERS.values() }: raise JsonableError( _("Invalid video_chat_provider {}").format(video_chat_provider)) if giphy_rating is not None and giphy_rating not in { p["id"] for p in Realm.GIPHY_RATING_OPTIONS.values() }: raise JsonableError(_("Invalid giphy_rating {}").format(giphy_rating)) message_retention_days: Optional[int] = None if message_retention_days_raw is not None: if not user_profile.is_realm_owner: raise OrganizationOwnerRequired() realm.ensure_not_on_limited_plan() message_retention_days = parse_message_retention_days( message_retention_days_raw, Realm.MESSAGE_RETENTION_SPECIAL_VALUES_MAP) if invite_to_realm_policy is not None and not user_profile.is_realm_owner: raise OrganizationOwnerRequired() data: Dict[str, Any] = {} message_content_delete_limit_seconds: Optional[int] = None if message_content_delete_limit_seconds_raw is not None: message_content_delete_limit_seconds = parse_message_content_delete_limit( message_content_delete_limit_seconds_raw, Realm.MESSAGE_CONTENT_DELETE_LIMIT_SPECIAL_VALUES_MAP, ) do_set_realm_property( realm, "message_content_delete_limit_seconds", message_content_delete_limit_seconds, acting_user=user_profile, ) data[ "message_content_delete_limit_seconds"] = message_content_delete_limit_seconds # The user of `locals()` here is a bit of a code smell, but it's # restricted to the elements present in realm.property_types. # # TODO: It should be possible to deduplicate this function up # further by some more advanced usage of the # `REQ/has_request_variables` extraction. req_vars = { k: v for k, v in list(locals().items()) if k in realm.property_types } for k, v in list(req_vars.items()): if v is not None and getattr(realm, k) != v: do_set_realm_property(realm, k, v, acting_user=user_profile) if isinstance(v, str): data[k] = "updated" else: data[k] = v # The following realm properties do not fit the pattern above # authentication_methods is not supported by the do_set_realm_property # framework because of its bitfield. if authentication_methods is not None and ( realm.authentication_methods_dict() != authentication_methods): do_set_realm_authentication_methods(realm, authentication_methods, acting_user=user_profile) data["authentication_methods"] = authentication_methods # The message_editing settings are coupled to each other, and thus don't fit # into the do_set_realm_property framework. if ((allow_message_editing is not None and realm.allow_message_editing != allow_message_editing) or (message_content_edit_limit_seconds is not None and realm.message_content_edit_limit_seconds != message_content_edit_limit_seconds) or (edit_topic_policy is not None and realm.edit_topic_policy != edit_topic_policy)): if allow_message_editing is None: allow_message_editing = realm.allow_message_editing if message_content_edit_limit_seconds is None: message_content_edit_limit_seconds = realm.message_content_edit_limit_seconds if edit_topic_policy is None: edit_topic_policy = realm.edit_topic_policy do_set_realm_message_editing( realm, allow_message_editing, message_content_edit_limit_seconds, edit_topic_policy, acting_user=user_profile, ) data["allow_message_editing"] = allow_message_editing data[ "message_content_edit_limit_seconds"] = message_content_edit_limit_seconds data["edit_topic_policy"] = edit_topic_policy # Realm.notifications_stream and Realm.signup_notifications_stream are not boolean, # str or integer field, and thus doesn't fit into the do_set_realm_property framework. if notifications_stream_id is not None: if realm.notifications_stream is None or (realm.notifications_stream.id != notifications_stream_id): new_notifications_stream = None if notifications_stream_id >= 0: (new_notifications_stream, sub) = access_stream_by_id(user_profile, notifications_stream_id) do_set_realm_notifications_stream(realm, new_notifications_stream, notifications_stream_id, acting_user=user_profile) data["notifications_stream_id"] = notifications_stream_id if signup_notifications_stream_id is not None: if realm.signup_notifications_stream is None or ( realm.signup_notifications_stream.id != signup_notifications_stream_id): new_signup_notifications_stream = None if signup_notifications_stream_id >= 0: (new_signup_notifications_stream, sub) = access_stream_by_id(user_profile, signup_notifications_stream_id) do_set_realm_signup_notifications_stream( realm, new_signup_notifications_stream, signup_notifications_stream_id, acting_user=user_profile, ) data[ "signup_notifications_stream_id"] = signup_notifications_stream_id if default_code_block_language is not None: # Migrate '', used in the API to encode the default/None behavior of this feature. if default_code_block_language == "": data["default_code_block_language"] = None else: data["default_code_block_language"] = default_code_block_language if string_id is not None: if not user_profile.is_realm_owner: raise OrganizationOwnerRequired() if realm.demo_organization_scheduled_deletion_date is None: raise JsonableError(_("Must be a demo organization.")) try: check_subdomain(string_id) except ValidationError as err: raise JsonableError(str(err.message)) do_change_realm_subdomain(realm, string_id, acting_user=user_profile) data["realm_uri"] = realm.uri return json_success(data)
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: Optional[bool] = REQ(default=None, json_validator=check_bool), downgrade_method: Optional[str] = REQ( default=None, str_validator=check_string_in(VALID_DOWNGRADE_METHODS)), scrub_realm: Optional[bool] = REQ(default=None, 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")) 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_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)