Repository: zulip/zulip

Issue:
The REST API allows creating bots with empty username
# Description

Using the `/bots` endpoint in the REST API, it's possible to create a bot with an empty username, which results in it having `-bot@<domain>` as the email address.

# Steps to reproduce

1. Start the development server.
2. Get an API key for Iago, using the `/dev_fetch_api_key` endpoint:

        curl -X POST -F 'username=iago@zulip.com' \
            http://localhost:9991/api/v1/dev_fetch_api_key

3. Send an HTTP POST request to the `/bots` endpoint, using Basic auth (`iago@zulip.com:<api_key>`), and the following form-data content in the body:

       full_name: "Foo"
       short_name: ""

    For instance, using cURL:

       curl -X POST -u 'iago@zulip.com:<api_key>' \
           -F 'full_name=Foo' -F 'short_name=' \
           http://localhost:9991/api/v1/bots

4. The server will reply with an HTTP 200 status code (success).
5. The result is a bot with an empty `short_name`, that looks like this in the bot settings UI:

    ![Bot with empty username](https://user-images.githubusercontent.com/7356565/27352532-59a34794-5601-11e7-8427-b35e4fddfc06.png)

Meanwhile, the server should reply with an HTTP 400 (bad request), and an error message indicating that the format is invalid:

    {"result":"error", "msg":"Bad name or username"}


---

@zulipbot label "area: api" "bug"



Discussion:
Hello @zulip/server-api members, this issue was labeled with the **area: api** label, so you may want to check it out!
@zulipbot claim

Resolving patch:
diff --git a/zerver/lib/users.py b/zerver/lib/users.py
--- a/zerver/lib/users.py
+++ b/zerver/lib/users.py
@@ -18,6 +18,13 @@ def check_full_name(full_name_raw):
         raise JsonableError(_("Invalid characters in name!"))
     return full_name
 
+def check_short_name(short_name_raw):
+    # type: (Text) -> Text
+    short_name = short_name_raw.strip()
+    if len(short_name) == 0:
+        raise JsonableError(_("Bad name or username"))
+    return short_name
+
 def check_change_full_name(user_profile, full_name_raw, acting_user):
     # type: (UserProfile, Text, UserProfile) -> Text
     """Verifies that the user's proposed full name is valid.  The caller
diff --git a/zerver/views/users.py b/zerver/views/users.py
--- a/zerver/views/users.py
+++ b/zerver/views/users.py
@@ -23,7 +23,8 @@
 from zerver.lib.streams import access_stream_by_name
 from zerver.lib.upload import upload_avatar_image
 from zerver.lib.validator import check_bool, check_string, check_int, check_url
-from zerver.lib.users import check_valid_bot_type, check_change_full_name, check_full_name
+from zerver.lib.users import check_valid_bot_type, check_change_full_name, \
+    check_full_name, check_short_name
 from zerver.lib.utils import generate_random_token
 from zerver.models import UserProfile, Stream, Realm, Message, get_user_profile_by_email, \
     email_allowed_for_realm, get_user_profile_by_id, get_user, Service
@@ -239,13 +240,14 @@ def add_outgoing_webhook_service(name, user_profile, base_url, interface, token)
                            token=token)
 
 @has_request_variables
-def add_bot_backend(request, user_profile, full_name_raw=REQ("full_name"), short_name=REQ(),
+def add_bot_backend(request, user_profile, full_name_raw=REQ("full_name"), short_name_raw=REQ("short_name"),
                     bot_type=REQ(validator=check_int, default=UserProfile.DEFAULT_BOT),
                     payload_url=REQ(validator=check_url, default=None),
                     default_sending_stream_name=REQ('default_sending_stream', default=None),
                     default_events_register_stream_name=REQ('default_events_register_stream', default=None),
                     default_all_public_streams=REQ(validator=check_bool, default=None)):
     # type: (HttpRequest, UserProfile, Text, Text, int, Optional[Text], Optional[Text], Optional[Text], Optional[bool]) -> HttpResponse
+    short_name = check_short_name(short_name_raw)
     service_name = short_name
     short_name += "-bot"
     full_name = check_full_name(full_name_raw)


Test patch:
diff --git a/zerver/tests/test_bots.py b/zerver/tests/test_bots.py
--- a/zerver/tests/test_bots.py
+++ b/zerver/tests/test_bots.py
@@ -62,6 +62,8 @@ def test_add_bot_with_bad_username(self):
         # type: () -> None
         self.login(self.example_email('hamlet'))
         self.assert_num_bots_equal(0)
+
+        # Invalid username
         bot_info = dict(
             full_name='My bot name',
             short_name='@',
@@ -70,6 +72,15 @@ def test_add_bot_with_bad_username(self):
         self.assert_json_error(result, 'Bad name or username')
         self.assert_num_bots_equal(0)
 
+        # Empty username
+        bot_info = dict(
+            full_name='My bot name',
+            short_name='',
+        )
+        result = self.client_post("/json/bots", bot_info)
+        self.assert_json_error(result, 'Bad name or username')
+        self.assert_num_bots_equal(0)
+
     def test_add_bot_with_no_name(self):
         # type: () -> None
         self.login(self.example_email('hamlet'))