Repository: zulip/zulip

Issue:
Fix 500 when trying to change your email to a disposable email
This validation error should be being reformatted as a JsonableError for the "email change" UI:

```
  File "./zerver/decorator.py", line 605, in _wrapped_view_func
    return authenticate_log_and_execute_json(request, view_func, *args, **kwargs)
  File "./zerver/decorator.py", line 587, in authenticate_log_and_execute_json
    return rate_limit()(view_func)(request, user_profile, *args, **kwargs)
  File "./zerver/decorator.py", line 747, in wrapped_func
    return func(request, *args, **kwargs)
  File "./zerver/decorator.py", line 423, in _wrapped_view_func
    return view_func(request, *args, **kwargs)
  File "./zerver/lib/request.py", line 174, in _wrapped_view_func
    return view_func(request, *args, **kwargs)
  File "./zerver/views/user_settings.py", line 105, in json_change_settings
    error, skipped = validate_email(user_profile, new_email)
  File "./zerver/lib/actions.py", line 4113, in validate_email
    email_allowed_for_realm(email, user_profile.realm)
  File "./zerver/models.py", line 398, in email_allowed_for_realm
    raise DisposableEmailError
zerver.models.DisposableEmailError
```

Tagging as a priority since this is part of our goal to get back to 0 known 500 errors in Zulip.

@hackerkid this is probably a good task for you.


Discussion:
Hello @zulip/server-settings members, this issue was labeled with the "area: settings (user)" label, so you may want to check it out!

<!-- areaLabelNotification -->
@zulipbot claim

Resolving patch:
diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py
--- a/zerver/lib/actions.py
+++ b/zerver/lib/actions.py
@@ -83,7 +83,7 @@
     get_display_recipient_by_id, query_for_ids, get_huddle_recipient, \
     UserGroup, UserGroupMembership, get_default_stream_groups, \
     get_bot_services, get_bot_dicts_in_realm, DomainNotAllowedForRealmError, \
-    get_services_for_bots
+    get_services_for_bots, DisposableEmailError
 
 from zerver.lib.alert_words import alert_words_in_realm
 from zerver.lib.avatar import avatar_url, avatar_url_from_dict
@@ -4158,6 +4158,8 @@ def validate_email(user_profile: UserProfile, email: Text) -> Tuple[Optional[str
         email_allowed_for_realm(email, user_profile.realm)
     except DomainNotAllowedForRealmError:
         return _("Outside your domain."), None
+    except DisposableEmailError:
+        return _("Please use your real email address."), None
 
     try:
         validate_email_for_realm(user_profile.realm, email)


Test patch:
diff --git a/zerver/tests/test_settings.py b/zerver/tests/test_settings.py
--- a/zerver/tests/test_settings.py
+++ b/zerver/tests/test_settings.py
@@ -113,6 +113,18 @@ def test_illegal_characters_in_name_changes(self) -> None:
                                         dict(full_name='Opheli*'))
         self.assert_json_error(json_result, 'Invalid characters in name!')
 
+    def test_change_email_to_disposable_email(self) -> None:
+        email = self.example_email("hamlet")
+        self.login(email)
+        realm = get_realm("zulip")
+        realm.disallow_disposable_email_addresses = True
+        realm.restricted_to_domain = False
+        realm.save()
+
+        json_result = self.client_patch("/json/settings",
+                                        dict(email='hamlet@mailnator.com'))
+        self.assert_json_error(json_result, 'Please use your real email address.')
+
     # This is basically a don't-explode test.
     def test_notify_settings(self) -> None:
         for notification_setting in UserProfile.notification_setting_types:
diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py
--- a/zerver/tests/test_signup.py
+++ b/zerver/tests/test_signup.py
@@ -772,6 +772,23 @@ def test_invite_outside_domain_in_closed_realm(self) -> None:
             self.invite(external_address, ["Denmark"]),
             "Some emails did not validate, so we didn't send any invitations.")
 
+    def test_invite_using_disposable_email(self) -> None:
+        """
+        In a realm with `restricted_to_domain = True`, you can't invite people
+        with a different domain from that of the realm or your e-mail address.
+        """
+        zulip_realm = get_realm("zulip")
+        zulip_realm.restricted_to_domain = False
+        zulip_realm.disallow_disposable_email_addresses = True
+        zulip_realm.save()
+
+        self.login(self.example_email("hamlet"))
+        external_address = "foo@mailnator.com"
+
+        self.assert_json_error(
+            self.invite(external_address, ["Denmark"]),
+            "Some emails did not validate, so we didn't send any invitations.")
+
     def test_invite_outside_domain_in_open_realm(self) -> None:
         """
         In a realm with `restricted_to_domain = False`, you can invite people