def test_get_user(self) -> None: mit_realm = get_realm("zephyr") user_profile = self.example_user("hamlet") email = user_profile.delivery_email self.assertEqual(self.command.get_user(email, self.zulip_realm), user_profile) self.assertEqual(self.command.get_user(email, None), user_profile) error_message = f"The realm '{mit_realm}' does not contain a user with email" with self.assertRaisesRegex(CommandError, error_message): self.command.get_user(email, mit_realm) with self.assertRaisesRegex( CommandError, "server does not contain a user with email"): self.command.get_user("*****@*****.**", None) do_create_user(email, "password", mit_realm, "full_name", acting_user=None) with self.assertRaisesRegex( CommandError, "server contains multiple users with that email"): self.command.get_user(email, None)
def handle(self, *args: Any, **options: str) -> None: realm_name = options["realm_name"] string_id = options["string_id"] create_user_params = self.get_create_user_params(options) try: realm = do_create_realm(string_id=string_id, name=realm_name) except AssertionError as e: raise CommandError(str(e)) do_create_user( create_user_params.email, create_user_params.password, realm, create_user_params.full_name, # Explicitly set tos_version=None. For servers that # have configured Terms of Service, this means that # users created via this mechanism will be prompted to # accept the Terms of Service on first login. role=UserProfile.ROLE_REALM_OWNER, realm_creation=True, tos_version=None, acting_user=None, )
def test_no_email_digest_for_bots(self) -> None: RealmAuditLog.objects.all().delete() cutoff = timezone_now() - datetime.timedelta(days=5) realm = get_realm("zulip") realm.digest_emails_enabled = True realm.save() bot = do_create_user( "*****@*****.**", "password", realm, "some_bot", bot_type=UserProfile.DEFAULT_BOT, acting_user=None, ) # Check that bots are not sent emails with mock.patch( "zerver.lib.digest.queue_digest_user_ids") as queue_mock: _enqueue_emails_for_realm(realm, cutoff) num_queued_users = len(queue_mock.call_args[0][0]) assert num_queued_users >= 5 for arg in queue_mock.call_args_list: user_ids = arg[0][0] for user_id in user_ids: self.assertNotEqual(user_id, bot.id)
def test_upload_already_existed_emoji_in_check_add_realm_emoji( self) -> None: realm_1 = do_create_realm("test_realm", "test_realm") emoji_author = do_create_user("*****@*****.**", password="******", realm=realm_1, full_name="abc", acting_user=None) emoji_name = "emoji_test" with get_test_image_file("img.png") as img_file: # Because we want to verify the IntegrityError handling # logic in check_add_realm_emoji rather than the primary # check in upload_emoji, we need to make this request via # that helper rather than via the API. check_add_realm_emoji(realm=emoji_author.realm, name=emoji_name, author=emoji_author, image_file=img_file) with self.assertRaises(JsonableError): check_add_realm_emoji( realm=emoji_author.realm, name=emoji_name, author=emoji_author, image_file=img_file, )
def deactivate_user() -> Dict[str, object]: user_profile = do_create_user( email="*****@*****.**", password=None, full_name="test_user", realm=get_realm("zulip"), acting_user=None, ) return {"user_id": user_profile.id}
def create_non_active_user(self, realm: Realm, email: str, name: str) -> UserProfile: user = do_create_user( email=email, password="******", realm=realm, full_name=name, acting_user=None ) # Doing a full-stack deactivation would be expensive here, # and we really only need to flip the flag to get a valid # test. change_user_is_active(user, False) return user
def create_user_backend( request: HttpRequest, user_profile: UserProfile, email: str = REQ(), password: str = REQ(), full_name_raw: str = REQ("full_name"), ) -> HttpResponse: if not user_profile.can_create_users: raise JsonableError(_("User not authorized for this query")) full_name = check_full_name(full_name_raw) form = CreateUserForm({"full_name": full_name, "email": email}) if not form.is_valid(): raise JsonableError(_("Bad name or username")) # Check that the new user's email address belongs to the admin's realm # (Since this is an admin API, we don't require the user to have been # invited first.) realm = user_profile.realm try: email_allowed_for_realm(email, user_profile.realm) except DomainNotAllowedForRealmError: raise JsonableError( _("Email '{email}' not allowed in this organization").format( email=email, )) except DisposableEmailError: raise JsonableError( _("Disposable email addresses are not allowed in this organization" )) except EmailContainsPlusError: raise JsonableError(_("Email addresses containing + are not allowed.")) try: get_user_by_delivery_email(email, user_profile.realm) raise JsonableError(_("Email '{}' already in use").format(email)) except UserProfile.DoesNotExist: pass if not check_password_strength(password): raise JsonableError(PASSWORD_TOO_WEAK_ERROR) target_user = do_create_user( email, password, realm, full_name, # Explicitly set tos_version=None. For servers that have # configured Terms of Service, this means that users created # via this mechanism will be prompted to accept the Terms of # Service on first login. tos_version=None, acting_user=user_profile, ) return json_success(request, data={"user_id": target_user.id})
def create_bot(self, owner: UserProfile, bot_email: str, bot_name: str) -> UserProfile: user = do_create_user( email=bot_email, password="******", realm=owner.realm, full_name=bot_name, bot_type=UserProfile.DEFAULT_BOT, bot_owner=owner, acting_user=None, ) return user
def handle(self, *args: Any, **options: Any) -> None: realm = self.get_realm(options) assert realm is not None # Should be ensured by parser create_user_params = self.get_create_user_params(options) try: do_create_user( create_user_params.email, create_user_params.password, realm, create_user_params.full_name, # Explicitly set tos_version=None. For servers that # have configured Terms of Service, this means that # users created via this mechanism will be prompted to # accept the Terms of Service on first login. tos_version=None, acting_user=None, ) except IntegrityError: raise CommandError("User already exists.")
def _get_outgoing_bot(self) -> UserProfile: outgoing_bot = do_create_user( email="*****@*****.**", password="******", realm=get_realm("zulip"), full_name="BarBot", bot_type=UserProfile.OUTGOING_WEBHOOK_BOT, bot_owner=self.example_user("cordelia"), acting_user=None, ) return outgoing_bot
def setUp(self) -> None: super().setUp() self.user_profile = self.example_user("othello") self.bot_profile = do_create_user( email="*****@*****.**", password="******", realm=get_realm("zulip"), full_name="FooBot", bot_type=UserProfile.OUTGOING_WEBHOOK_BOT, bot_owner=self.user_profile, acting_user=None, ) self.second_bot_profile = do_create_user( email="*****@*****.**", password="******", realm=get_realm("zulip"), full_name="BarBot", bot_type=UserProfile.OUTGOING_WEBHOOK_BOT, bot_owner=self.user_profile, acting_user=None, )
def setUp(self) -> None: super().setUp() self.user_profile = self.example_user("othello") self.bot_profile = do_create_user( email="*****@*****.**", password="******", realm=get_realm("zulip"), full_name="EmbeddedBo1", bot_type=UserProfile.EMBEDDED_BOT, bot_owner=self.user_profile, acting_user=None, ) self.second_bot_profile = do_create_user( email="*****@*****.**", password="******", realm=get_realm("zulip"), full_name="EmbeddedBot2", bot_type=UserProfile.EMBEDDED_BOT, bot_owner=self.user_profile, acting_user=None, )
def test_check_admin_different_realm_emoji(self) -> None: # Test that two different realm emojis in two different realms but # having same name can be administered independently. realm_1 = do_create_realm("test_realm", "test_realm") emoji_author_1 = do_create_user("*****@*****.**", password="******", realm=realm_1, full_name="abc", acting_user=None) self.create_test_emoji("test_emoji", emoji_author_1) emoji_author_2 = self.example_user("othello") self.create_test_emoji("test_emoji", emoji_author_2) self.login_user(emoji_author_2) result = self.client_delete("/json/realm/emoji/test_emoji") self.assert_json_success(result)
def deactivate_own_user() -> Dict[str, object]: test_user_email = "*****@*****.**" deactivate_test_user = do_create_user( test_user_email, "secret", get_realm("zulip"), "Mr. Delete", role=200, acting_user=None, ) realm = get_realm("zulip") test_user = get_user(test_user_email, realm) test_user_api_key = get_api_key(test_user) # change authentication line to allow test_client to delete itself. AUTHENTICATION_LINE[ 0] = f"{deactivate_test_user.email}:{test_user_api_key}" return {}
def test_user_activation(self) -> None: realm = get_realm("zulip") now = timezone_now() user = do_create_user("email", "password", realm, "full_name", acting_user=None) do_deactivate_user(user, acting_user=user) do_activate_mirror_dummy_user(user, acting_user=user) do_deactivate_user(user, acting_user=user) do_reactivate_user(user, acting_user=user) self.assertEqual( RealmAuditLog.objects.filter(event_time__gte=now).count(), 6) event_types = list( RealmAuditLog.objects.filter( realm=realm, acting_user=user, modified_user=user, modified_stream=None, event_time__gte=now, event_time__lte=now + timedelta(minutes=60), ).order_by("event_time").values_list("event_type", flat=True)) self.assertEqual( event_types, [ RealmAuditLog.USER_CREATED, RealmAuditLog.USER_DEACTIVATED, RealmAuditLog.USER_ACTIVATED, RealmAuditLog.USER_DEACTIVATED, RealmAuditLog.USER_REACTIVATED, ], ) for event in RealmAuditLog.objects.filter( realm=realm, acting_user=user, modified_user=user, modified_stream=None, event_time__gte=now, event_time__lte=now + timedelta(minutes=60), ): extra_data = orjson.loads(assert_is_not_none(event.extra_data)) self.check_role_count_schema(extra_data[RealmAuditLog.ROLE_COUNT]) self.assertNotIn(RealmAuditLog.OLD_VALUE, extra_data)
def test_multiple_services(self) -> None: bot_owner = self.example_user("othello") bot = do_create_user( bot_owner=bot_owner, bot_type=UserProfile.OUTGOING_WEBHOOK_BOT, full_name="Outgoing Webhook Bot", email="whatever", realm=bot_owner.realm, password=None, acting_user=None, ) add_service( "weather", user_profile=bot, interface=Service.GENERIC, base_url="https://weather.example.com/", token="weather_token", ) add_service( "qotd", user_profile=bot, interface=Service.GENERIC, base_url="https://qotd.example.com/", token="qotd_token", ) sender = self.example_user("hamlet") responses.add( responses.POST, "https://weather.example.com/", json={}, ) responses.add( responses.POST, "https://qotd.example.com/", json={}, ) with self.assertLogs(level="INFO") as logs: self.send_personal_message( sender, bot, content="some content", ) self.assert_length(logs.output, 2) self.assertIn(f"Outgoing webhook request from {bot.id}@zulip took ", logs.output[0]) self.assertIn(f"Outgoing webhook request from {bot.id}@zulip took ", logs.output[1]) self.assert_length(responses.calls, 2) calls_by_url = { call.request.url: orjson.loads(call.request.body or b"") for call in responses.calls } weather_req = calls_by_url["https://weather.example.com/"] self.assertEqual(weather_req["token"], "weather_token") self.assertEqual(weather_req["message"]["content"], "some content") self.assertEqual(weather_req["message"]["sender_id"], sender.id) qotd_req = calls_by_url["https://qotd.example.com/"] self.assertEqual(qotd_req["token"], "qotd_token") self.assertEqual(qotd_req["message"]["content"], "some content") self.assertEqual(qotd_req["message"]["sender_id"], sender.id)
def save(self) -> None: """ This method is called at the end of operations modifying a user, and is responsible for actually applying the requested changes, writing them to the database. """ realm = RequestNotes.get_notes(self._request).realm assert realm is not None email_new_value = getattr(self, "_email_new_value", None) is_active_new_value = getattr(self, "_is_active_new_value", None) full_name_new_value = getattr(self, "_full_name_new_value", None) password = getattr(self, "_password_set_to", None) # Clean up the internal "pending change" state, now that we've # fetched the values: self._email_new_value = None self._is_active_new_value = None self._full_name_new_value = None self._password_set_to = None if email_new_value: try: # Note that the validate_email check that usually # appears adjacent to email_allowed_for_realm is # present in save(). email_allowed_for_realm(email_new_value, realm) except DomainNotAllowedForRealmError: raise scim_exceptions.BadRequestError( "This email domain isn't allowed in this organization.") except DisposableEmailError: # nocoverage raise scim_exceptions.BadRequestError( "Disposable email domains are not allowed for this realm.") except EmailContainsPlusError: # nocoverage raise scim_exceptions.BadRequestError( "Email address can't contain + characters.") try: validate_email_not_already_in_realm(realm, email_new_value) except ValidationError as e: raise ConflictError("Email address already in use: " + str(e)) if self.is_new_user(): assert full_name_new_value is not None self.obj = do_create_user( email_new_value, password, realm, full_name_new_value, acting_user=None, ) return # TODO: The below operations should ideally be executed in a single # atomic block to avoid failing with partial changes getting saved. # This can be fixed once we figure out how do_deactivate_user can be run # inside an atomic block. # We process full_name first here, since it's the only one that can fail. if full_name_new_value: check_change_full_name(self.obj, full_name_new_value, acting_user=None) if email_new_value: do_change_user_delivery_email(self.obj, email_new_value) if is_active_new_value is not None and is_active_new_value: do_reactivate_user(self.obj, acting_user=None) elif is_active_new_value is not None and not is_active_new_value: do_deactivate_user(self.obj, acting_user=None)
def accounts_register( request: HttpRequest, key: str = REQ(default=""), timezone: str = REQ(default="", converter=to_timezone_or_empty), from_confirmation: Optional[str] = REQ(default=None), form_full_name: Optional[str] = REQ("full_name", default=None), source_realm_id: Optional[int] = REQ(default=None, converter=to_converted_or_fallback( to_non_negative_int, None)), ) -> HttpResponse: try: prereg_user = check_prereg_key(request, key) except ConfirmationKeyException as e: return render_confirmation_key_error(request, e) email = prereg_user.email realm_creation = prereg_user.realm_creation password_required = prereg_user.password_required role = prereg_user.invited_as if realm_creation: role = UserProfile.ROLE_REALM_OWNER try: validators.validate_email(email) except ValidationError: return render(request, "zerver/invalid_email.html", context={"invalid_email": True}) if realm_creation: # For creating a new realm, there is no existing realm or domain realm = None else: assert prereg_user.realm is not None if get_subdomain(request) != prereg_user.realm.string_id: return render_confirmation_key_error( request, ConfirmationKeyException( ConfirmationKeyException.DOES_NOT_EXIST)) realm = prereg_user.realm try: email_allowed_for_realm(email, realm) except DomainNotAllowedForRealmError: return render( request, "zerver/invalid_email.html", context={ "realm_name": realm.name, "closed_domain": True }, ) except DisposableEmailError: return render( request, "zerver/invalid_email.html", context={ "realm_name": realm.name, "disposable_emails_not_allowed": True }, ) except EmailContainsPlusError: return render( request, "zerver/invalid_email.html", context={ "realm_name": realm.name, "email_contains_plus": True }, ) if realm.deactivated: # The user is trying to register for a deactivated realm. Advise them to # contact support. return redirect_to_deactivation_notice() try: validate_email_not_already_in_realm(realm, email) except ValidationError: return redirect_to_email_login_url(email) if settings.BILLING_ENABLED: try: check_spare_licenses_available_for_registering_new_user( realm, email) except LicenseLimitError: return render(request, "zerver/no_spare_licenses.html") name_validated = False require_ldap_password = False if from_confirmation: try: del request.session["authenticated_full_name"] except KeyError: pass ldap_full_name = None if settings.POPULATE_PROFILE_VIA_LDAP: # If the user can be found in LDAP, we'll take the full name from the directory, # and further down create a form pre-filled with it. for backend in get_backends(): if isinstance(backend, LDAPBackend): try: ldap_username = backend.django_to_ldap_username(email) except ZulipLDAPExceptionNoMatchingLDAPUser: logging.warning( "New account email %s could not be found in LDAP", email) break # Note that this `ldap_user` object is not a # `ZulipLDAPUser` with a `Realm` attached, so # calling `.populate_user()` on it will crash. # This is OK, since we're just accessing this user # to extract its name. # # TODO: We should potentially be accessing this # user to sync its initial avatar and custom # profile fields as well, if we indeed end up # creating a user account through this flow, # rather than waiting until `manage.py # sync_ldap_user_data` runs to populate it. ldap_user = _LDAPUser(backend, ldap_username) try: ldap_full_name = backend.get_mapped_name(ldap_user) except TypeError: break # Check whether this is ZulipLDAPAuthBackend, # which is responsible for authentication and # requires that LDAP accounts enter their LDAP # password to register, or ZulipLDAPUserPopulator, # which just populates UserProfile fields (no auth). require_ldap_password = isinstance(backend, ZulipLDAPAuthBackend) break if ldap_full_name: # We don't use initial= here, because if the form is # complete (that is, no additional fields need to be # filled out by the user) we want the form to validate, # so they can be directly registered without having to # go through this interstitial. form = RegistrationForm({"full_name": ldap_full_name}, realm_creation=realm_creation) request.session["authenticated_full_name"] = ldap_full_name name_validated = True elif realm is not None and realm.is_zephyr_mirror_realm: # For MIT users, we can get an authoritative name from Hesiod. # Technically we should check that this is actually an MIT # realm, but we can cross that bridge if we ever get a non-MIT # zephyr mirroring realm. hesiod_name = compute_mit_user_fullname(email) form = RegistrationForm( initial={ "full_name": hesiod_name if "@" not in hesiod_name else "" }, realm_creation=realm_creation, ) name_validated = True elif prereg_user.full_name: if prereg_user.full_name_validated: request.session[ "authenticated_full_name"] = prereg_user.full_name name_validated = True form = RegistrationForm({"full_name": prereg_user.full_name}, realm_creation=realm_creation) else: form = RegistrationForm( initial={"full_name": prereg_user.full_name}, realm_creation=realm_creation) elif form_full_name is not None: form = RegistrationForm( initial={"full_name": form_full_name}, realm_creation=realm_creation, ) else: form = RegistrationForm(realm_creation=realm_creation) else: postdata = request.POST.copy() if name_changes_disabled(realm): # If we populate profile information via LDAP and we have a # verified name from you on file, use that. Otherwise, fall # back to the full name in the request. try: postdata.update( full_name=request.session["authenticated_full_name"]) name_validated = True except KeyError: pass form = RegistrationForm(postdata, realm_creation=realm_creation) if not (password_auth_enabled(realm) and password_required): form["password"].field.required = False if form.is_valid(): if password_auth_enabled(realm) and form["password"].field.required: password = form.cleaned_data["password"] else: # If the user wasn't prompted for a password when # completing the authentication form (because they're # signing up with SSO and no password is required), set # the password field to `None` (Which causes Django to # create an unusable password). password = None if realm_creation: string_id = form.cleaned_data["realm_subdomain"] realm_name = form.cleaned_data["realm_name"] realm_type = form.cleaned_data["realm_type"] is_demo_org = form.cleaned_data["is_demo_organization"] realm = do_create_realm(string_id, realm_name, org_type=realm_type, is_demo_organization=is_demo_org) assert realm is not None full_name = form.cleaned_data["full_name"] enable_marketing_emails = form.cleaned_data["enable_marketing_emails"] default_stream_group_names = request.POST.getlist( "default_stream_group") default_stream_groups = lookup_default_stream_groups( default_stream_group_names, realm) if source_realm_id is not None: # Non-integer realm_id values like "string" are treated # like the "Do not import" value of "". source_profile: Optional[UserProfile] = get_source_profile( email, source_realm_id) else: source_profile = None if not realm_creation: try: existing_user_profile: Optional[ UserProfile] = get_user_by_delivery_email(email, realm) except UserProfile.DoesNotExist: existing_user_profile = None else: existing_user_profile = None user_profile: Optional[UserProfile] = None return_data: Dict[str, bool] = {} if ldap_auth_enabled(realm): # If the user was authenticated using an external SSO # mechanism like Google or GitHub auth, then authentication # will have already been done before creating the # PreregistrationUser object with password_required=False, and # so we don't need to worry about passwords. # # If instead the realm is using EmailAuthBackend, we will # set their password above. # # But if the realm is using LDAPAuthBackend, we need to verify # their LDAP password (which will, as a side effect, create # the user account) here using authenticate. # prereg_user.realm_creation carries the information about whether # we're in realm creation mode, and the ldap flow will handle # that and create the user with the appropriate parameters. user_profile = authenticate( request=request, username=email, password=password, realm=realm, prereg_user=prereg_user, return_data=return_data, ) if user_profile is None: can_use_different_backend = email_auth_enabled(realm) or (len( get_external_method_dicts(realm)) > 0) if settings.LDAP_APPEND_DOMAIN: # In LDAP_APPEND_DOMAIN configurations, we don't allow making a non-LDAP account # if the email matches the ldap domain. can_use_different_backend = can_use_different_backend and ( not email_belongs_to_ldap(realm, email)) if return_data.get( "no_matching_ldap_user") and can_use_different_backend: # If both the LDAP and Email or Social auth backends are # enabled, and there's no matching user in the LDAP # directory then the intent is to create a user in the # realm with their email outside the LDAP organization # (with e.g. a password stored in the Zulip database, # not LDAP). So we fall through and create the new # account. pass else: # TODO: This probably isn't going to give a # user-friendly error message, but it doesn't # particularly matter, because the registration form # is hidden for most users. view_url = reverse("login") query = urlencode({"email": email}) redirect_url = append_url_query_string(view_url, query) return HttpResponseRedirect(redirect_url) elif not realm_creation: # Since we'll have created a user, we now just log them in. return login_and_go_to_home(request, user_profile) else: # With realm_creation=True, we're going to return further down, # after finishing up the creation process. pass if existing_user_profile is not None and existing_user_profile.is_mirror_dummy: user_profile = existing_user_profile do_activate_mirror_dummy_user(user_profile, acting_user=user_profile) do_change_password(user_profile, password) do_change_full_name(user_profile, full_name, user_profile) do_change_user_setting(user_profile, "timezone", timezone, acting_user=user_profile) do_change_user_setting( user_profile, "default_language", get_default_language_for_new_user(request, realm), acting_user=None, ) # TODO: When we clean up the `do_activate_mirror_dummy_user` code path, # make it respect invited_as_admin / is_realm_admin. if user_profile is None: user_profile = do_create_user( email, password, realm, full_name, prereg_user=prereg_user, role=role, tos_version=settings.TERMS_OF_SERVICE_VERSION, timezone=timezone, default_language=get_default_language_for_new_user( request, realm), default_stream_groups=default_stream_groups, source_profile=source_profile, realm_creation=realm_creation, acting_user=None, enable_marketing_emails=enable_marketing_emails, ) if realm_creation: # Because for realm creation, registration happens on the # root domain, we need to log them into the subdomain for # their new realm. return redirect_and_log_into_subdomain( ExternalAuthResult(user_profile=user_profile, data_dict={"is_realm_creation": True})) # This dummy_backend check below confirms the user is # authenticating to the correct subdomain. auth_result = authenticate( username=user_profile.delivery_email, realm=realm, return_data=return_data, use_dummy_backend=True, ) if return_data.get("invalid_subdomain"): # By construction, this should never happen. logging.error( "Subdomain mismatch in registration %s: %s", realm.subdomain, user_profile.delivery_email, ) return redirect("/") return login_and_go_to_home(request, auth_result) return render( request, "zerver/register.html", context={ "form": form, "email": email, "key": key, "full_name": request.session.get("authenticated_full_name", None), "lock_name": name_validated and name_changes_disabled(realm), # password_auth_enabled is normally set via our context processor, # but for the registration form, there is no logged in user yet, so # we have to set it here. "creating_new_team": realm_creation, "password_required": password_auth_enabled(realm) and password_required, "require_ldap_password": require_ldap_password, "password_auth_enabled": password_auth_enabled(realm), "root_domain_available": is_root_domain_available(), "default_stream_groups": [] if realm is None else get_default_stream_groups(realm), "accounts": get_accounts_for_email(email), "MAX_REALM_NAME_LENGTH": str(Realm.MAX_REALM_NAME_LENGTH), "MAX_NAME_LENGTH": str(UserProfile.MAX_NAME_LENGTH), "MAX_PASSWORD_LENGTH": str(form.MAX_PASSWORD_LENGTH), "MAX_REALM_SUBDOMAIN_LENGTH": str(Realm.MAX_REALM_SUBDOMAIN_LENGTH), "corporate_enabled": settings.CORPORATE_ENABLED, "sorted_realm_types": sorted(Realm.ORG_TYPES.values(), key=lambda d: d["display_order"]), }, )
def setUp(self) -> None: super().setUp() self.user = do_create_user( "*****@*****.**", "password", get_realm("zulip"), "user", acting_user=None )
def add_message_formatting_conversation(self) -> None: realm = get_realm("zulip") stream = ensure_stream(realm, "zulip features", acting_user=None) UserProfile.objects.filter(email__contains="stage").delete() starr = do_create_user("*****@*****.**", "password", realm, "Ada Starr", acting_user=None) self.set_avatar(starr, "static/images/characters/starr.png") fisher = do_create_user("*****@*****.**", "password", realm, "Bel Fisher", acting_user=None) self.set_avatar(fisher, "static/images/characters/fisher.png") twitter_bot = do_create_user( "*****@*****.**", "password", realm, "Twitter Bot", bot_type=UserProfile.DEFAULT_BOT, acting_user=None, ) self.set_avatar(twitter_bot, "static/images/features/twitter.png") bulk_add_subscriptions(realm, [stream], list(UserProfile.objects.filter(realm=realm)), acting_user=None) staged_messages: List[Dict[str, Any]] = [ { "sender": starr, "content": "Hey @**Bel Fisher**, check out Zulip's Markdown formatting! " "You can have:\n* bulleted lists\n * with sub-bullets too\n" "* **bold**, *italic*, and ~~strikethrough~~ text\n" "* LaTeX for mathematical formulas, both inline -- $$O(n^2)$$ -- and displayed:\n" "```math\n\\int_a^b f(t)\\, dt=F(b)-F(a)\n```", }, { "sender": fisher, "content": "My favorite is the syntax highlighting for code blocks\n" "```python\ndef fib(n: int) -> int:\n # returns the n-th Fibonacci number\n" " return fib(n-1) + fib(n-2)\n```", }, { "sender": starr, "content": "I think you forgot your base case there, Bel :laughing:\n" "```quote\n```python\ndef fib(n: int) -> int:\n # returns the n-th Fibonacci number\n" " return fib(n-1) + fib(n-2)\n```\n```", }, { "sender": fisher, "content": "I'm also a big fan of inline link, tweet, video, and image previews. " "Check out this picture of Çet Whalin[](/static/images/features/whale.png)!", }, { "sender": starr, "content": "I just set up a custom linkifier, " "so `#1234` becomes [#1234](github.com/zulip/zulip/1234), " "a link to the corresponding GitHub issue.", }, { "sender": twitter_bot, "content": "https://twitter.com/gvanrossum/status/786661035637772288", }, { "sender": fisher, "content": "Oops, the Twitter bot I set up shouldn't be posting here. Let me go fix that.", }, ] messages = [ internal_prep_stream_message( message["sender"], stream, "message formatting", message["content"], ) for message in staged_messages ] message_ids = do_send_messages(messages) preview_message = Message.objects.get( id__in=message_ids, content__icontains="image previews") (emoji_code, reaction_type) = emoji_name_to_emoji_code(realm, "whale") do_add_reaction(starr, preview_message, "whale", emoji_code, reaction_type) twitter_message = Message.objects.get(id__in=message_ids, content__icontains="gvanrossum") # Setting up a twitter integration in dev is a decent amount of work. If you need # to update this tweet, either copy the format below, or send the link to the tweet # to chat.zulip.org and ask an admin of that server to get you the rendered_content. twitter_message.rendered_content = ( "<p><a>https://twitter.com/gvanrossum/status/786661035637772288</a></p>\n" '<div class="inline-preview-twitter"><div class="twitter-tweet">' '<a><img class="twitter-avatar" ' 'src="https://pbs.twimg.com/profile_images/424495004/GuidoAvatar_bigger.jpg"></a>' "<p>Great blog post about Zulip's use of mypy: " "<a>http://blog.zulip.org/2016/10/13/static-types-in-python-oh-mypy/</a></p>" "<span>- Guido van Rossum (@gvanrossum)</span></div></div>") twitter_message.save(update_fields=["rendered_content"]) # Put a short pause between the whale reaction and this, so that the # thumbs_up shows up second (emoji_code, reaction_type) = emoji_name_to_emoji_code(realm, "thumbs_up") do_add_reaction(starr, preview_message, "thumbs_up", emoji_code, reaction_type)
def add_bot_backend( request: HttpRequest, user_profile: UserProfile, full_name_raw: str = REQ("full_name"), short_name_raw: str = REQ("short_name"), bot_type: int = REQ(json_validator=check_int, default=UserProfile.DEFAULT_BOT), payload_url: str = REQ(json_validator=check_url, default=""), service_name: Optional[str] = REQ(default=None), config_data: Dict[str, str] = REQ( default={}, json_validator=check_dict(value_validator=check_string)), interface_type: int = REQ(json_validator=check_int, default=Service.GENERIC), default_sending_stream_name: Optional[str] = REQ("default_sending_stream", default=None), default_events_register_stream_name: Optional[str] = REQ( "default_events_register_stream", default=None), default_all_public_streams: Optional[bool] = REQ(json_validator=check_bool, default=None), ) -> HttpResponse: short_name = check_short_name(short_name_raw) if bot_type != UserProfile.INCOMING_WEBHOOK_BOT: service_name = service_name or short_name short_name += "-bot" full_name = check_full_name(full_name_raw) try: email = f"{short_name}@{user_profile.realm.get_bot_domain()}" except InvalidFakeEmailDomain: raise JsonableError( _("Can't create bots until FAKE_EMAIL_DOMAIN is correctly configured.\n" "Please contact your server administrator.")) form = CreateUserForm({"full_name": full_name, "email": email}) if bot_type == UserProfile.EMBEDDED_BOT: if not settings.EMBEDDED_BOTS_ENABLED: raise JsonableError(_("Embedded bots are not enabled.")) if service_name not in [bot.name for bot in EMBEDDED_BOTS]: raise JsonableError(_("Invalid embedded bot name.")) if not form.is_valid(): # We validate client-side as well raise JsonableError(_("Bad name or username")) try: get_user_by_delivery_email(email, user_profile.realm) raise JsonableError(_("Username already in use")) except UserProfile.DoesNotExist: pass check_bot_name_available( realm_id=user_profile.realm_id, full_name=full_name, ) check_bot_creation_policy(user_profile, bot_type) check_valid_bot_type(user_profile, bot_type) check_valid_interface_type(interface_type) if len(request.FILES) == 0: avatar_source = UserProfile.AVATAR_FROM_GRAVATAR elif len(request.FILES) != 1: raise JsonableError(_("You may only upload one file at a time")) else: avatar_source = UserProfile.AVATAR_FROM_USER default_sending_stream = None if default_sending_stream_name is not None: (default_sending_stream, ignored_sub) = access_stream_by_name(user_profile, default_sending_stream_name) default_events_register_stream = None if default_events_register_stream_name is not None: (default_events_register_stream, ignored_sub) = access_stream_by_name( user_profile, default_events_register_stream_name) if bot_type in (UserProfile.INCOMING_WEBHOOK_BOT, UserProfile.EMBEDDED_BOT) and service_name: check_valid_bot_config(bot_type, service_name, config_data) bot_profile = do_create_user( email=email, password=None, realm=user_profile.realm, full_name=full_name, bot_type=bot_type, bot_owner=user_profile, avatar_source=avatar_source, default_sending_stream=default_sending_stream, default_events_register_stream=default_events_register_stream, default_all_public_streams=default_all_public_streams, acting_user=user_profile, ) if len(request.FILES) == 1: user_file = list(request.FILES.values())[0] assert isinstance(user_file, UploadedFile) assert user_file.size is not None upload_avatar_image(user_file, user_profile, bot_profile) if bot_type in (UserProfile.OUTGOING_WEBHOOK_BOT, UserProfile.EMBEDDED_BOT): assert isinstance(service_name, str) add_service( name=service_name, user_profile=bot_profile, base_url=payload_url, interface=interface_type, token=generate_api_key(), ) if bot_type == UserProfile.INCOMING_WEBHOOK_BOT and service_name: set_bot_config(bot_profile, "integration_id", service_name) if bot_type in (UserProfile.INCOMING_WEBHOOK_BOT, UserProfile.EMBEDDED_BOT): for key, value in config_data.items(): set_bot_config(bot_profile, key, value) notify_created_bot(bot_profile) api_key = get_api_key(bot_profile) json_result = dict( user_id=bot_profile.id, api_key=api_key, avatar_url=avatar_url(bot_profile), default_sending_stream=get_stream_name( bot_profile.default_sending_stream), default_events_register_stream=get_stream_name( bot_profile.default_events_register_stream), default_all_public_streams=bot_profile.default_all_public_streams, ) return json_success(request, data=json_result)