def test_notify_bot_owner_on_invalid_json(self) -> None: @api_key_only_webhook_view('ClientName', notify_bot_owner_on_invalid_json=False) def my_webhook_no_notify(request: HttpRequest, user_profile: UserProfile) -> None: raise InvalidJSONError("Malformed JSON") @api_key_only_webhook_view('ClientName', notify_bot_owner_on_invalid_json=True) def my_webhook_notify(request: HttpRequest, user_profile: UserProfile) -> None: raise InvalidJSONError("Malformed JSON") webhook_bot_email = '*****@*****.**' webhook_bot_realm = get_realm('zulip') webhook_bot = get_user(webhook_bot_email, webhook_bot_realm) webhook_bot_api_key = get_api_key(webhook_bot) request = HostRequestMock() request.POST['api_key'] = webhook_bot_api_key request.host = "zulip.testserver" expected_msg = INVALID_JSON_MESSAGE.format(webhook_name='ClientName') last_message_id = self.get_last_message().id with self.assertRaisesRegex(JsonableError, "Malformed JSON"): my_webhook_no_notify(request) # type: ignore # mypy doesn't seem to apply the decorator # First verify that without the setting, it doesn't send a PM to bot owner. msg = self.get_last_message() self.assertEqual(msg.id, last_message_id) self.assertNotEqual(msg.content, expected_msg.strip()) # Then verify that with the setting, it does send such a message. with self.assertRaisesRegex(JsonableError, "Malformed JSON"): my_webhook_notify(request) # type: ignore # mypy doesn't seem to apply the decorator msg = self.get_last_message() self.assertNotEqual(msg.id, last_message_id) self.assertEqual(msg.sender.email, self.notification_bot().email) self.assertEqual(msg.content, expected_msg.strip())
def test_webhook_http_header_header_exists(self) -> None: webhook_bot = get_user('*****@*****.**', get_realm('zulip')) request = HostRequestMock() request.META['HTTP_X_CUSTOM_HEADER'] = 'custom_value' request.user = webhook_bot header_value = validate_extract_webhook_http_header(request, 'X_CUSTOM_HEADER', 'test_webhook') self.assertEqual(header_value, 'custom_value')
def test_webhook_http_header_header_does_not_exist(self) -> None: webhook_bot = get_user('*****@*****.**', get_realm('zulip')) webhook_bot.last_reminder = None notification_bot = self.notification_bot() request = HostRequestMock() request.user = webhook_bot request.path = 'some/random/path' exception_msg = "Missing the HTTP event header 'X_CUSTOM_HEADER'" with self.assertRaisesRegex(MissingHTTPEventHeader, exception_msg): validate_extract_webhook_http_header(request, 'X_CUSTOM_HEADER', 'test_webhook') msg = self.get_last_message() expected_message = MISSING_EVENT_HEADER_MESSAGE.format( bot_name=webhook_bot.full_name, request_path=request.path, header_name='X_CUSTOM_HEADER', integration_name='test_webhook', support_email=FromAddress.SUPPORT ).rstrip() self.assertEqual(msg.sender.email, notification_bot.email) self.assertEqual(msg.content, expected_message)
def test_html_settings_links(self) -> None: context: Dict[str, Any] = dict() with self.settings(ROOT_DOMAIN_LANDING_PAGE=True): add_api_uri_context(context, HostRequestMock()) self.assertEqual(context['settings_html'], 'Zulip settings page') self.assertEqual(context['subscriptions_html'], 'streams page') context = dict() with self.settings(ROOT_DOMAIN_LANDING_PAGE=True): add_api_uri_context(context, HostRequestMock(host="mysubdomain.testserver")) self.assertEqual(context['settings_html'], '<a href="/#settings">Zulip settings page</a>') self.assertEqual( context['subscriptions_html'], '<a target="_blank" href="/#streams">streams page</a>') context = dict() add_api_uri_context(context, HostRequestMock()) self.assertEqual(context['settings_html'], '<a href="/#settings">Zulip settings page</a>') self.assertEqual( context['subscriptions_html'], '<a target="_blank" href="/#streams">streams page</a>')
def test_notify_bot_owner_on_invalid_json(self) -> None: @webhook_view("ClientName", notify_bot_owner_on_invalid_json=False) def my_webhook_no_notify(request: HttpRequest, user_profile: UserProfile) -> None: raise InvalidJSONError("Malformed JSON") @webhook_view("ClientName", notify_bot_owner_on_invalid_json=True) def my_webhook_notify(request: HttpRequest, user_profile: UserProfile) -> None: raise InvalidJSONError("Malformed JSON") webhook_bot_email = "*****@*****.**" webhook_bot_realm = get_realm("zulip") webhook_bot = get_user(webhook_bot_email, webhook_bot_realm) webhook_bot_api_key = get_api_key(webhook_bot) request = HostRequestMock() request.POST["api_key"] = webhook_bot_api_key request.host = "zulip.testserver" expected_msg = INVALID_JSON_MESSAGE.format(webhook_name="ClientName") last_message_id = self.get_last_message().id with self.assertRaisesRegex(JsonableError, "Malformed JSON"): my_webhook_no_notify(request) # First verify that without the setting, it doesn't send a PM to bot owner. msg = self.get_last_message() self.assertEqual(msg.id, last_message_id) self.assertNotEqual(msg.content, expected_msg.strip()) # Then verify that with the setting, it does send such a message. with self.assertRaisesRegex(JsonableError, "Malformed JSON"): my_webhook_notify(request) msg = self.get_last_message() self.assertNotEqual(msg.id, last_message_id) self.assertEqual(msg.sender.email, self.notification_bot().email) self.assertEqual(msg.content, expected_msg.strip())
def test_invalid_email(self) -> None: invalid_email = "alice AT example.com" recipients = [invalid_email] # We use an MIT user here to maximize code coverage user = self.mit_user("starnine") sender = user post_data = dict(sender=sender.email, type="private") for client_name in ["zephyr_mirror", "irc_mirror", "jabber_mirror"]: request = HostRequestMock(post_data=post_data, client_name=client_name) with self.assertRaises(InvalidMirrorInput): create_mirrored_message_users(request, user, recipients)
def test_zephyr_mirror_new_sender(self, ignored: object) -> None: """Test mirror dummy user creation for sender when sending to stream""" user = self.mit_user("starnine") sender_email = "*****@*****.**" recipients = ["stream_name"] # Now make the request. post_data = dict(sender=sender_email, type="stream") request = HostRequestMock(post_data=post_data, client_name="zephyr_mirror") mirror_sender = create_mirrored_message_users(request, user, recipients) assert mirror_sender is not None self.assertEqual(mirror_sender.email, sender_email) self.assertTrue(mirror_sender.is_mirror_dummy)
def test_realm_export_rate_limited(self) -> None: admin = self.example_user("iago") self.login_user(admin) current_log = RealmAuditLog.objects.filter( event_type=RealmAuditLog.REALM_EXPORTED) self.assert_length(current_log, 0) exports = [] for i in range(0, 5): exports.append( RealmAuditLog( realm=admin.realm, event_type=RealmAuditLog.REALM_EXPORTED, event_time=timezone_now(), )) RealmAuditLog.objects.bulk_create(exports) with self.assertRaises(JsonableError) as error: export_realm(HostRequestMock(), admin) self.assertEqual(str(error.exception), "Exceeded rate limit.")
def test_get_browser_language_code(self) -> None: req = HostRequestMock() self.assertIsNone(get_browser_language_code(req)) req.META["HTTP_ACCEPT_LANGUAGE"] = "de" self.assertEqual(get_browser_language_code(req), "de") req.META["HTTP_ACCEPT_LANGUAGE"] = "en-GB,en;q=0.8" self.assertEqual(get_browser_language_code(req), "en-gb") # Case when unsupported language has higher weight. req.META["HTTP_ACCEPT_LANGUAGE"] = "en-IND;q=0.9,de;q=0.8" self.assertEqual(get_browser_language_code(req), "de") # Browser locale is set to unsupported language. req.META["HTTP_ACCEPT_LANGUAGE"] = "en-IND" self.assertIsNone(get_browser_language_code(req)) req.META["HTTP_ACCEPT_LANGUAGE"] = "*" self.assertIsNone(get_browser_language_code(req))
def test_tornado_endpoint(self) -> None: # This test is mostly intended to get minimal coverage on # the /notify_tornado endpoint, so we can have 100% URL coverage, # but it does exercise a little bit of the codepath. post_data = dict(data=orjson.dumps( dict( event=dict(type="other", ), users=[self.example_user("hamlet").id], ), ).decode(), ) req = HostRequestMock(post_data, user_profile=None) req.META["REMOTE_ADDR"] = "127.0.0.1" result = self.client_post_request("/notify_tornado", req) self.assert_json_error(result, "Access denied", status_code=403) post_data["secret"] = settings.SHARED_SECRET req = HostRequestMock(post_data, user_profile=None) req.META["REMOTE_ADDR"] = "127.0.0.1" result = self.client_post_request("/notify_tornado", req) self.assert_json_success(result)
def test_validate_api_key_if_profile_is_not_active(self): # type: () -> None self._change_is_active_field(self.default_bot, False) with self.assertRaises(JsonableError): validate_api_key(HostRequestMock(), self.default_bot.email, self.default_bot.api_key) self._change_is_active_field(self.default_bot, True)
def test_api_url_view_subdomains_homepage_base(self) -> None: context: Dict[str, Any] = {} add_api_uri_context(context, HostRequestMock()) self.assertEqual(context["api_url_scheme_relative"], "yourZulipDomain.testserver/api") self.assertEqual(context["api_url"], "http://yourZulipDomain.testserver/api") self.assertFalse(context["html_settings_links"])
def test_api_key_only_webhook_view(self): # type: () -> None @api_key_only_webhook_view('ClientName') def my_webhook(request, user_profile): # type: (HttpRequest, UserProfile) -> Text return user_profile.email @api_key_only_webhook_view('ClientName') def my_webhook_raises_exception(request, user_profile): # type: (HttpRequest, UserProfile) -> None raise Exception("raised by webhook function") webhook_bot_email = '*****@*****.**' webhook_bot_realm = get_realm('zulip') webhook_bot = get_user(webhook_bot_email, webhook_bot_realm) webhook_bot_api_key = webhook_bot.api_key webhook_client_name = "ZulipClientNameWebhook" request = HostRequestMock() request.POST['api_key'] = 'not_existing_api_key' with self.assertRaisesRegex(JsonableError, "Invalid API key"): my_webhook(request) # Start a valid request here request.POST['api_key'] = webhook_bot_api_key with mock.patch('logging.warning') as mock_warning: with self.assertRaisesRegex(JsonableError, "Account is not associated with this subdomain"): api_result = my_webhook(request) mock_warning.assert_called_with( "User {} ({}) attempted to access API on wrong " "subdomain ({})".format(webhook_bot_email, 'zulip', '')) with mock.patch('logging.warning') as mock_warning: with self.assertRaisesRegex(JsonableError, "Account is not associated with this subdomain"): request.host = "acme." + settings.EXTERNAL_HOST api_result = my_webhook(request) mock_warning.assert_called_with( "User {} ({}) attempted to access API on wrong " "subdomain ({})".format(webhook_bot_email, 'zulip', 'acme')) request.host = "zulip.testserver" # Test when content_type is application/json and request.body # is valid JSON; exception raised in the webhook function # should be re-raised with mock.patch('zerver.decorator.webhook_logger.exception') as mock_exception: with self.assertRaisesRegex(Exception, "raised by webhook function"): request.body = "{}" request.content_type = 'application/json' my_webhook_raises_exception(request) # Test when content_type is not application/json; exception raised # in the webhook function should be re-raised with mock.patch('zerver.decorator.webhook_logger.exception') as mock_exception: with self.assertRaisesRegex(Exception, "raised by webhook function"): request.body = "notjson" request.content_type = 'text/plain' my_webhook_raises_exception(request) # Test when content_type is application/json but request.body # is not valid JSON; invalid JSON should be logged and the # exception raised in the webhook function should be re-raised with mock.patch('zerver.decorator.webhook_logger.exception') as mock_exception: with self.assertRaisesRegex(Exception, "raised by webhook function"): request.body = "invalidjson" request.content_type = 'application/json' my_webhook_raises_exception(request) message = """ user: {email} ({realm}) client: {client_name} URL: {path_info} content_type: {content_type} body: {body} """ mock_exception.assert_called_with(message.format( email=webhook_bot_email, realm=webhook_bot_realm.string_id, client_name=webhook_client_name, path_info=request.META.get('PATH_INFO'), content_type=request.content_type, body=request.body, )) with self.settings(RATE_LIMITING=True): with mock.patch('zerver.decorator.rate_limit_user') as rate_limit_mock: api_result = my_webhook(request) # Verify rate limiting was attempted. self.assertTrue(rate_limit_mock.called) # Verify decorator set the magic _email field used by some of our back end logging. self.assertEqual(request._email, webhook_bot_email) # Verify the main purpose of the decorator, which is that it passed in the # user_profile to my_webhook, allowing it return the correct # email for the bot (despite the API caller only knowing the API key). self.assertEqual(api_result, webhook_bot_email) # Now deactivate the user webhook_bot.is_active = False webhook_bot.save() with self.assertRaisesRegex(JsonableError, "Account not active"): my_webhook(request) # Reactive the user, but deactivate their realm. webhook_bot.is_active = True webhook_bot.save() webhook_bot.realm.deactivated = True webhook_bot.realm.save() with self.assertRaisesRegex(JsonableError, "Realm for account has been deactivated"): my_webhook(request)
def test_validate_api_key_if_profile_is_incoming_webhook_and_is_webhook_is_set(self): # type: () -> None profile = validate_api_key(HostRequestMock(host="zulip.testserver"), self.webhook_bot.email, self.webhook_bot.api_key, is_webhook=True) self.assertEqual(profile.id, self.webhook_bot.id)
def test_validate_api_key_if_profile_does_not_exist(self): # type: () -> None with self.assertRaises(JsonableError): validate_api_key(HostRequestMock(), '*****@*****.**', 'api_key')
def test_export_as_not_admin(self) -> None: user = self.example_user("hamlet") self.login_user(user) with self.assertRaises(JsonableError): export_realm(HostRequestMock(), user)
def test_api_url_view_subdomains_base(self) -> None: context = dict() # type: Dict[str, Any] add_api_uri_context(context, HostRequestMock()) self.assertEqual(context["api_url_scheme_relative"], "testserver/api") self.assertEqual(context["api_url"], "http://testserver/api") self.assertTrue(context["html_settings_links"])
def test_validate_api_key_if_profile_is_incoming_webhook_and_is_webhook_is_unset(self): # type: () -> None with self.assertRaises(JsonableError): validate_api_key(HostRequestMock(), self.webhook_bot.email, self.webhook_bot.api_key)