diff --git a/zerver/models.py b/zerver/models.py index 3f4aceefe6..e31d53fe5f 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -515,6 +515,7 @@ class UserProfile(ModelReprMixin, AbstractBaseUser, PermissionsMixin): DEFAULT_BOT, INCOMING_WEBHOOK_BOT, OUTGOING_WEBHOOK_BOT, + EMBEDDED_BOT, ] SERVICE_BOT_TYPES = [ diff --git a/zerver/tests/test_bots.py b/zerver/tests/test_bots.py index 0a57a5924d..3bc4e65fd2 100644 --- a/zerver/tests/test_bots.py +++ b/zerver/tests/test_bots.py @@ -1001,3 +1001,43 @@ class BotTest(ZulipTestCase, UploadSerializeMixin): bot_info['interface_type'] = Service.GENERIC result = self.client_post("/json/bots", bot_info) self.assert_json_success(result) + + def test_create_embedded_bot(self, **extras): + # type: (**Any) -> None + self.login(self.example_email('hamlet')) + + # Test to create embedded bot with correct service_name + bot_info = { + 'full_name': 'Embedded test bot', + 'short_name': 'embeddedservicebot', + 'bot_type': UserProfile.EMBEDDED_BOT, + 'service_name': 'converter', + } + bot_info.update(extras) + with self.settings(EMBEDDED_BOTS_ENABLED=False): + result = self.client_post("/json/bots", bot_info) + self.assert_json_error(result, 'Embedded bots are not enabled.') + + result = self.client_post("/json/bots", bot_info) + self.assert_json_success(result) + + bot_email = "embeddedservicebot-bot@zulip.testserver" + bot_realm = get_realm('zulip') + bot = get_user(bot_email, bot_realm) + services = get_bot_services(bot.id) + service = services[0] + + self.assertEqual(len(services), 1) + self.assertEqual(service.name, "converter") + self.assertEqual(service.user_profile, bot) + + # Test to create embedded bot with incorrect service_name + bot_info = { + 'full_name': 'Embedded test bot', + 'short_name': 'embeddedservicebot', + 'bot_type': UserProfile.EMBEDDED_BOT, + 'service_name': 'not_existing_service', + } + bot_info.update(extras) + result = self.client_post("/json/bots", bot_info) + self.assert_json_error(result, 'Invalid embedded bot name.') diff --git a/zerver/views/users.py b/zerver/views/users.py index 48703467fa..85340a4ea3 100644 --- a/zerver/views/users.py +++ b/zerver/views/users.py @@ -18,6 +18,7 @@ from zerver.lib.actions import do_change_avatar_fields, do_change_bot_owner, \ do_change_default_events_register_stream, do_change_default_sending_stream, \ do_create_user, do_deactivate_user, do_reactivate_user, do_regenerate_api_key from zerver.lib.avatar import avatar_url, get_gravatar_url, get_avatar_field +from zerver.lib.integrations import EMBEDDED_BOTS from zerver.lib.response import json_error, json_success from zerver.lib.streams import access_stream_by_name from zerver.lib.upload import upload_avatar_image @@ -233,6 +234,7 @@ def regenerate_bot_api_key(request, user_profile, email): ) return json_success(json_result) +# Adds an outgoing webhook or embedded bot service. def add_service(name, user_profile, base_url=None, interface=None, token=None): # type: (Text, UserProfile, Text, int, Text) -> None Service.objects.create(name=name, @@ -244,18 +246,26 @@ def add_service(name, user_profile, base_url=None, interface=None, token=None): @has_request_variables 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), + payload_url=REQ(validator=check_url, default=""), + service_name=REQ(default=None), interface_type=REQ(validator=check_int, default=Service.GENERIC), 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], int, Optional[Text], Optional[Text], Optional[bool]) -> HttpResponse + # type: (HttpRequest, UserProfile, Text, Text, int, Optional[Text], Optional[Text], int, Optional[Text], Optional[Text], Optional[bool]) -> HttpResponse short_name = check_short_name(short_name_raw) - service_name = short_name + service_name = service_name or short_name short_name += "-bot" full_name = check_full_name(full_name_raw) email = '%s@%s' % (short_name, user_profile.realm.get_bot_domain()) form = CreateUserForm({'full_name': full_name, 'email': email}) + + if bot_type == UserProfile.EMBEDDED_BOT: + if not settings.EMBEDDED_BOTS_ENABLED: + return json_error(_("Embedded bots are not enabled.")) + if service_name not in [bot.name for bot in EMBEDDED_BOTS]: + return json_error(_("Invalid embedded bot name.")) + if not form.is_valid(): # We validate client-side as well return json_error(_('Bad name or username')) @@ -297,7 +307,7 @@ def add_bot_backend(request, user_profile, full_name_raw=REQ("full_name"), short user_file = list(request.FILES.values())[0] upload_avatar_image(user_file, user_profile, bot_profile) - if bot_type == UserProfile.OUTGOING_WEBHOOK_BOT: + if bot_type in (UserProfile.OUTGOING_WEBHOOK_BOT, UserProfile.EMBEDDED_BOT): add_service(name=service_name, user_profile=bot_profile, base_url=payload_url, diff --git a/zproject/dev_settings.py b/zproject/dev_settings.py index dbf012f0e6..a09e3a4a12 100644 --- a/zproject/dev_settings.py +++ b/zproject/dev_settings.py @@ -34,6 +34,8 @@ EXTRA_INSTALLED_APPS = ["zilencer", "analytics"] CAMO_URI = '' OPEN_REALM_CREATION = True +EMBEDDED_BOTS_ENABLED = True + SAVE_FRONTEND_STACKTRACES = True EVENT_LOGS_ENABLED = True SYSTEM_ONLY_REALMS = set() # type: Set[str] diff --git a/zproject/settings.py b/zproject/settings.py index bebfc8b580..419bbe03de 100644 --- a/zproject/settings.py +++ b/zproject/settings.py @@ -183,6 +183,7 @@ DEFAULT_SETTINGS = { 'PUSH_NOTIFICATION_REDACT_CONTENT': False, 'RATE_LIMITING': True, 'SEND_LOGIN_EMAILS': True, + 'EMBEDDED_BOTS_ENABLED': False, } # These settings are not documented in prod_settings_template.py.