def run_test_with_image_url(image_url: str) -> None: # Test full size image. self.login(self.example_email("hamlet")) quoted_url = urllib.parse.quote(image_url, safe='') encoded_url = base64.urlsafe_b64encode(image_url.encode()).decode('utf-8') result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_url)) self.assertEqual(result.status_code, 302, result) expected_part_url = '/smart/filters:no_upscale()/' + encoded_url + '/source_type/external' self.assertIn(expected_part_url, result.url) # Test thumbnail size. result = self.client_get("/thumbnail?url=%s&size=thumbnail" % (quoted_url)) self.assertEqual(result.status_code, 302, result) expected_part_url = '/0x300/smart/filters:no_upscale():sharpen(0.5,0.2,true)/' + encoded_url + '/source_type/external' self.assertIn(expected_part_url, result.url) # Test api endpoint with standard API authentication. self.logout() user_profile = self.example_user("hamlet") result = self.api_get(user_profile.email, "/thumbnail?url=%s&size=thumbnail" % (quoted_url,)) self.assertEqual(result.status_code, 302, result) expected_part_url = '/0x300/smart/filters:no_upscale():sharpen(0.5,0.2,true)/' + encoded_url + '/source_type/external' self.assertIn(expected_part_url, result.url) # Test api endpoint with legacy API authentication. user_profile = self.example_user("hamlet") result = self.client_get("/thumbnail?url=%s&size=thumbnail&api_key=%s" % ( quoted_url, get_api_key(user_profile))) self.assertEqual(result.status_code, 302, result) expected_part_url = '/0x300/smart/filters:no_upscale():sharpen(0.5,0.2,true)/' + encoded_url + '/source_type/external' self.assertIn(expected_part_url, result.url) # Test a second logged-in user; they should also be able to access it user_profile = self.example_user("iago") result = self.client_get("/thumbnail?url=%s&size=thumbnail&api_key=%s" % ( quoted_url, get_api_key(user_profile))) self.assertEqual(result.status_code, 302, result) expected_part_url = '/0x300/smart/filters:no_upscale():sharpen(0.5,0.2,true)/' + encoded_url + '/source_type/external' self.assertIn(expected_part_url, result.url) # Test with another user trying to access image using thumbor. # File should be always accessible to user in case of external source self.login(self.example_email("iago")) result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_url)) self.assertEqual(result.status_code, 302, result) expected_part_url = '/smart/filters:no_upscale()/' + encoded_url + '/source_type/external' self.assertIn(expected_part_url, result.url)
def api_dev_fetch_api_key(request: HttpRequest, username: str=REQ()) -> HttpResponse: """This function allows logging in without a password on the Zulip mobile apps when connecting to a Zulip development environment. It requires DevAuthBackend to be included in settings.AUTHENTICATION_BACKENDS. """ if not dev_auth_enabled() or settings.PRODUCTION: return json_error(_("Dev environment not enabled.")) # Django invokes authenticate methods by matching arguments, and this # authentication flow will not invoke LDAP authentication because of # this condition of Django so no need to check if LDAP backend is # enabled. validate_login_email(username) subdomain = get_subdomain(request) realm = get_realm(subdomain) return_data = {} # type: Dict[str, bool] user_profile = authenticate(dev_auth_username=username, realm=realm, return_data=return_data) if return_data.get("inactive_realm"): return json_error(_("This organization has been deactivated."), data={"reason": "realm deactivated"}, status=403) if return_data.get("inactive_user"): return json_error(_("Your account has been disabled."), data={"reason": "user disable"}, status=403) if user_profile is None: return json_error(_("This user is not registered."), data={"reason": "unregistered"}, status=403) do_login(request, user_profile) api_key = get_api_key(user_profile) return json_success({"api_key": api_key, "email": user_profile.email})
def webathena_kerberos_login(request: HttpRequest, user_profile: UserProfile, cred: str=REQ(default=None)) -> HttpResponse: global kerberos_alter_egos if cred is None: return json_error(_("Could not find Kerberos credential")) if not user_profile.realm.webathena_enabled: return json_error(_("Webathena login not enabled")) try: parsed_cred = ujson.loads(cred) user = parsed_cred["cname"]["nameString"][0] if user in kerberos_alter_egos: user = kerberos_alter_egos[user] assert(user == user_profile.email.split("@")[0]) ccache = make_ccache(parsed_cred) except Exception: return json_error(_("Invalid Kerberos cache")) # TODO: Send these data via (say) rabbitmq try: api_key = get_api_key(user_profile) subprocess.check_call(["ssh", settings.PERSONAL_ZMIRROR_SERVER, "--", "/home/zulip/python-zulip-api/zulip/integrations/zephyr/process_ccache", force_str(user), force_str(api_key), force_str(base64.b64encode(ccache))]) except Exception: logging.exception("Error updating the user's ccache") return json_error(_("We were unable to setup mirroring for you")) return json_success()
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_verification_request(self) -> None: self.subscribe(self.test_user, self.STREAM_NAME) get_params = {'stream_name': self.STREAM_NAME, 'challenge': '9B2SVL4orbt5DxLMqJHI6pOTipTqingt2YFMIO0g06E', 'api_key': get_api_key(self.test_user)} result = self.client_get(self.url, get_params) self.assert_in_response('9B2SVL4orbt5DxLMqJHI6pOTipTqingt2YFMIO0g06E', result)
def handle(self, *args: Any, **options: str) -> None: realm = self.get_realm(options) print(self.fmt % ('email', 'password', 'API key')) for email in options['emails']: if '@' not in email: print('ERROR: %s does not look like an email address' % (email,)) continue user = self.get_user(email, realm) print(self.fmt % (email, initial_password(email), get_api_key(user)))
def json_fetch_api_key(request: HttpRequest, user_profile: UserProfile, password: str=REQ(default='')) -> HttpResponse: subdomain = get_subdomain(request) realm = get_realm(subdomain) if password_auth_enabled(user_profile.realm): if not authenticate(username=user_profile.email, password=password, realm=realm): return json_error(_("Your username or password is incorrect.")) api_key = get_api_key(user_profile) return json_success({"api_key": api_key})
def get_body(self, fixture_name: str) -> Dict[str, str]: api_key = get_api_key(self.test_user) data = ujson.loads(self.webhook_fixture_data(self.FIXTURE_DIR_NAME, 'v1_' + fixture_name)) data.update({'email': self.TEST_USER_EMAIL, 'api-key': api_key, 'payload': ujson.dumps(data['payload'])}) if self.SEND_STREAM: data['stream'] = self.STREAM_NAME if self.BRANCHES is not None: data['branches'] = self.BRANCHES return data
def test_custom_stream(self) -> None: api_key = get_api_key(self.test_user) url = "/api/v1/external/jira?api_key=%s&stream=jira_custom" % (api_key,) msg = self.send_json_payload(self.test_user, url, self.get_body('created_v2'), stream_name="jira_custom", content_type="application/json") self.assertEqual(msg.topic_name(), "BUG-15: New bug with hook") self.assertEqual(msg.content, """Leo Franchi **created** [BUG-15](http://lfranchi.com:8080/browse/BUG-15) priority Major, assigned to **no one**: > New bug with hook""")
def api_fetch_api_key(request: HttpRequest, username: str=REQ(), password: str=REQ()) -> HttpResponse: return_data = {} # type: Dict[str, bool] subdomain = get_subdomain(request) realm = get_realm(subdomain) if username == "google-oauth2-token": # This code path is auth for the legacy Android app user_profile = authenticate(google_oauth2_token=password, realm=realm, return_data=return_data) else: if not ldap_auth_enabled(realm=get_realm_from_request(request)): # In case we don't authenticate against LDAP, check for a valid # email. LDAP backend can authenticate against a non-email. validate_login_email(username) user_profile = authenticate(username=username, password=password, realm=realm, return_data=return_data) if return_data.get("inactive_user"): return json_error(_("Your account has been disabled."), data={"reason": "user disable"}, status=403) if return_data.get("inactive_realm"): return json_error(_("This organization has been deactivated."), data={"reason": "realm deactivated"}, status=403) if return_data.get("password_auth_disabled"): return json_error(_("Password auth is disabled in your team."), data={"reason": "password auth disabled"}, status=403) if user_profile is None: if return_data.get("valid_attestation"): # We can leak that the user is unregistered iff # they present a valid authentication string for the user. return json_error(_("This user is not registered; do so from a browser."), data={"reason": "unregistered"}, status=403) return json_error(_("Your username or password is incorrect."), data={"reason": "incorrect_creds"}, status=403) # Maybe sending 'user_logged_in' signal is the better approach: # user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile) # Not doing this only because over here we don't add the user information # in the session. If the signal receiver assumes that we do then that # would cause problems. email_on_new_login(sender=user_profile.__class__, request=request, user=user_profile) # Mark this request as having a logged-in user for our server logs. process_client(request, user_profile) request._email = user_profile.email api_key = get_api_key(user_profile) return json_success({"api_key": api_key, "email": user_profile.email})
def encode_credentials(self, identifier: str, realm: str="zulip") -> str: """ identifier: Can be an email or a remote server uuid. """ if identifier in API_KEYS: api_key = API_KEYS[identifier] else: if is_remote_server(identifier): api_key = get_remote_server_by_uuid(identifier).api_key else: user = get_user(identifier, get_realm(realm)) api_key = get_api_key(user) API_KEYS[identifier] = api_key credentials = "%s:%s" % (identifier, api_key) return 'Basic ' + base64.b64encode(credentials.encode('utf-8')).decode('utf-8')
def login_or_register_remote_user(request: HttpRequest, remote_username: Optional[str], user_profile: Optional[UserProfile], full_name: str='', invalid_subdomain: bool=False, mobile_flow_otp: Optional[str]=None, is_signup: bool=False, redirect_to: str='') -> HttpResponse: email = remote_user_to_email(remote_username) if user_profile is None or user_profile.is_mirror_dummy: # We have verified the user controls an email address, but # there's no associated Zulip user account. Consider sending # the request to registration. return maybe_send_to_registration(request, email, full_name, password_required=False, is_signup=is_signup) # Otherwise, the user has successfully authenticated to an # account, and we need to do the right thing depending whether # or not they're using the mobile OTP flow or want a browser session. if mobile_flow_otp is not None: # For the mobile Oauth flow, we send the API key and other # necessary details in a redirect to a zulip:// URI scheme. api_key = get_api_key(user_profile) params = { 'otp_encrypted_api_key': otp_encrypt_api_key(api_key, mobile_flow_otp), 'email': email, 'realm': user_profile.realm.uri, } # We can't use HttpResponseRedirect, since it only allows HTTP(S) URLs response = HttpResponse(status=302) response['Location'] = 'zulip://login?' + urllib.parse.urlencode(params) # Maybe sending 'user_logged_in' signal is the better approach: # user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile) # Not doing this only because over here we don't add the user information # in the session. If the signal receiver assumes that we do then that # would cause problems. email_on_new_login(sender=user_profile.__class__, request=request, user=user_profile) # Mark this request as having a logged-in user for our server logs. process_client(request, user_profile) request._email = user_profile.email return response do_login(request, user_profile) redirect_to = get_safe_redirect_to(redirect_to, user_profile.realm.uri) return HttpResponseRedirect(redirect_to)
def bot_info(bot_profile: UserProfile) -> Dict[str, Any]: default_sending_stream = get_stream_name(bot_profile.default_sending_stream) default_events_register_stream = get_stream_name(bot_profile.default_events_register_stream) # Bots are supposed to have only one API key, at least for now. # Therefore we can safely asume that one and only valid API key will be # the first one. api_key = get_api_key(bot_profile) return dict( username=bot_profile.email, full_name=bot_profile.full_name, api_key=api_key, avatar_url=avatar_url(bot_profile), default_sending_stream=default_sending_stream, default_events_register_stream=default_events_register_stream, default_all_public_streams=bot_profile.default_all_public_streams, )
def build_webhook_url(self, *args: Any, **kwargs: Any) -> str: url = self.URL_TEMPLATE if url.find("api_key") >= 0: api_key = get_api_key(self.test_user) url = self.URL_TEMPLATE.format(api_key=api_key, stream=self.STREAM_NAME) else: url = self.URL_TEMPLATE.format(stream=self.STREAM_NAME) has_arguments = kwargs or args if has_arguments and url.find('?') == -1: url = "{}?".format(url) # nocoverage else: url = "{}&".format(url) for key, value in kwargs.items(): url = "{}{}={}&".format(url, key, value) for arg in args: url = "{}{}&".format(url, arg) return url[:-1] if has_arguments else url
def test_local_file_type(self) -> None: def get_file_path_urlpart(uri: str, size: str = '') -> str: url_in_result = 'smart/filters:no_upscale():sharpen(2.2,0.8,false)/%s/source_type/local_file' if size: url_in_result = '/%s/%s' % (size, url_in_result) hex_uri = base64.urlsafe_b64encode(uri.encode()).decode('utf-8') return url_in_result % (hex_uri) self.login(self.example_email("hamlet")) fp = StringIO("zulip!") fp.name = "zulip.jpeg" result = self.client_post("/json/user_uploads", {'file': fp}) self.assert_json_success(result) json = ujson.loads(result.content) self.assertIn("uri", json) uri = json["uri"] base = '/user_uploads/' self.assertEqual(base, uri[:len(base)]) # Test full size image. # We remove the forward slash infront of the `/user_uploads/` to match # bugdown behaviour. quoted_uri = urllib.parse.quote(uri[1:], safe='') result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_uri)) self.assertEqual(result.status_code, 302, result) expected_part_url = get_file_path_urlpart(uri) self.assertIn(expected_part_url, result.url) # Test thumbnail size. result = self.client_get("/thumbnail?url=%s&size=thumbnail" % (quoted_uri)) self.assertEqual(result.status_code, 302, result) expected_part_url = get_file_path_urlpart(uri, '0x300') self.assertIn(expected_part_url, result.url) # Test with a unicode filename. fp = StringIO("zulip!") fp.name = "μένει.jpg" result = self.client_post("/json/user_uploads", {'file': fp}) self.assert_json_success(result) json = ujson.loads(result.content) self.assertIn("uri", json) uri = json["uri"] # We remove the forward slash infront of the `/user_uploads/` to match # bugdown behaviour. quoted_uri = urllib.parse.quote(uri[1:], safe='') result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_uri)) self.assertEqual(result.status_code, 302, result) expected_part_url = get_file_path_urlpart(uri) self.assertIn(expected_part_url, result.url) # Test custom emoji urls in Zulip messages. user_profile = self.example_user("hamlet") image_file = get_test_image_file("img.png") file_name = "emoji.png" upload_emoji_image(image_file, file_name, user_profile) custom_emoji_url = upload_backend.get_emoji_url( file_name, user_profile.realm_id) emoji_url_base = '/user_avatars/' self.assertEqual(emoji_url_base, custom_emoji_url[:len(emoji_url_base)]) quoted_emoji_url = urllib.parse.quote(custom_emoji_url[1:], safe='') # Test full size custom emoji image (for emoji link in messages case). result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_emoji_url)) self.assertEqual(result.status_code, 302, result) expected_part_url = get_file_path_urlpart(custom_emoji_url) self.assertIn(expected_part_url, result.url) # Tests the /api/v1/thumbnail api endpoint with HTTP basic auth. self.logout() user_profile = self.example_user("hamlet") result = self.api_get(self.example_email("hamlet"), '/thumbnail?url=%s&size=full' % (quoted_uri, )) self.assertEqual(result.status_code, 302, result) expected_part_url = get_file_path_urlpart(uri) self.assertIn(expected_part_url, result.url) # Tests the /api/v1/thumbnail api endpoint with ?api_key # auth. user_profile = self.example_user("hamlet") result = self.client_get('/thumbnail?url=%s&size=full&api_key=%s' % (quoted_uri, get_api_key(user_profile))) self.assertEqual(result.status_code, 302, result) expected_part_url = get_file_path_urlpart(uri) self.assertIn(expected_part_url, result.url) # Test with another user trying to access image using thumbor. self.login(self.example_email("iago")) result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_uri)) self.assertEqual(result.status_code, 403, result) self.assert_in_response("You are not authorized to view this file.", result)
def api_fetch_api_key( request: HttpRequest, username: str = REQ(), password: str = REQ() ) -> HttpResponse: return_data = {} # type: Dict[str, bool] subdomain = get_subdomain(request) realm = get_realm(subdomain) if username == "google-oauth2-token": # This code path is auth for the legacy Android app user_profile = authenticate(google_oauth2_token=password, realm=realm, return_data=return_data) else: if not ldap_auth_enabled(realm=get_realm_from_request(request)): # In case we don't authenticate against LDAP, check for a valid # email. LDAP backend can authenticate against a non-email. validate_login_email(username) user_profile = authenticate(username=username, password=password, realm=realm, return_data=return_data) if return_data.get("inactive_user"): return json_error(_("Your account has been disabled."), data={"reason": "user disable"}, status=403) if return_data.get("inactive_realm"): return json_error(_("This organization has been deactivated."), data={"reason": "realm deactivated"}, status=403) if return_data.get("password_auth_disabled"): return json_error(_("Password auth is disabled in your team."), data={"reason": "password auth disabled"}, status=403) if user_profile is None: if return_data.get("valid_attestation"): # We can leak that the user is unregistered iff # they present a valid authentication string for the user. return json_error( _("This user is not registered; do so from a browser."), data={"reason": "unregistered"}, status=403) return json_error(_("Your username or password is incorrect."), data={"reason": "incorrect_creds"}, status=403) # Maybe sending 'user_logged_in' signal is the better approach: # user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile) # Not doing this only because over here we don't add the user information # in the session. If the signal receiver assumes that we do then that # would cause problems. email_on_new_login(sender=user_profile.__class__, request=request, user=user_profile) # Mark this request as having a logged-in user for our server logs. process_client(request, user_profile) request._email = user_profile.email api_key = get_api_key(user_profile) return json_success({ "api_key": api_key, "email": user_profile.delivery_email })
def login_or_register_remote_user(request: HttpRequest, remote_username: Optional[str], user_profile: Optional[UserProfile], full_name: str='', invalid_subdomain: bool=False, mobile_flow_otp: Optional[str]=None, is_signup: bool=False, redirect_to: str='', multiuse_object_key: str='') -> HttpResponse: """Given a successful authentication showing the user controls given email address (remote_username) and potentially a UserProfile object (if the user already has a Zulip account), redirect the browser to the appropriate place: * The logged-in app if the user already has a Zulip account and is trying to login, potentially to an initial narrow or page that had been saved in the `redirect_to` parameter. * The registration form if is_signup was set (i.e. the user is trying to create a Zulip account) * A special `confirm_continue_registration.html` "do you want to register or try another account" if the user doesn't have a Zulip account but is_signup is False (i.e. the user tried to login and then did social authentication selecting an email address that does not have a Zulip account in this organization). * A zulip:// URL to send control back to the mobile apps if they are doing authentication using the mobile_flow_otp flow. """ email = remote_user_to_email(remote_username) if user_profile is None or user_profile.is_mirror_dummy: # We have verified the user controls an email address, but # there's no associated Zulip user account. Consider sending # the request to registration. return maybe_send_to_registration(request, email, full_name, password_required=False, is_signup=is_signup, multiuse_object_key=multiuse_object_key) # Otherwise, the user has successfully authenticated to an # account, and we need to do the right thing depending whether # or not they're using the mobile OTP flow or want a browser session. if mobile_flow_otp is not None: # For the mobile Oauth flow, we send the API key and other # necessary details in a redirect to a zulip:// URI scheme. api_key = get_api_key(user_profile) params = { 'otp_encrypted_api_key': otp_encrypt_api_key(api_key, mobile_flow_otp), 'email': email, 'realm': user_profile.realm.uri, } # We can't use HttpResponseRedirect, since it only allows HTTP(S) URLs response = HttpResponse(status=302) response['Location'] = 'zulip://login?' + urllib.parse.urlencode(params) # Since we are returning an API key instead of going through # the Django login() function (which creates a browser # session, etc.), the "new login" signal handler (which # triggers an email notification new logins) will not run # automatically. So we call it manually here. # # Arguably, sending a fake 'user_logged_in' signal would be a better approach: # user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile) email_on_new_login(sender=user_profile.__class__, request=request, user=user_profile) # Mark this request as having a logged-in user for our server logs. process_client(request, user_profile) request._email = user_profile.email return response do_login(request, user_profile) redirect_to = get_safe_redirect_to(redirect_to, user_profile.realm.uri) return HttpResponseRedirect(redirect_to)
def test_webathena_kerberos_login(self) -> None: user = self.example_user('hamlet') self.login_user(user) def post(subdomain: Any, **kwargs: Any) -> HttpResponse: params = {k: ujson.dumps(v) for k, v in kwargs.items()} return self.client_post('/accounts/webathena_kerberos_login/', params, subdomain=subdomain) result = post("zulip") self.assert_json_error(result, 'Could not find Kerberos credential') result = post("zulip", cred='whatever') self.assert_json_error(result, 'Webathena login not enabled') email = str(self.mit_email("starnine")) realm = get_realm('zephyr') user = get_user(email, realm) api_key = get_api_key(user) self.login_user(user) def ccache_mock(**kwargs: Any) -> Any: return patch('zerver.views.zephyr.make_ccache', **kwargs) def ssh_mock(**kwargs: Any) -> Any: return patch('zerver.views.zephyr.subprocess.check_call', **kwargs) def mirror_mock() -> Any: return self.settings(PERSONAL_ZMIRROR_SERVER='server') def logging_mock() -> Any: return patch('logging.exception') cred = dict(cname=dict(nameString=['starnine'])) with ccache_mock(side_effect=KeyError('foo')): result = post("zephyr", cred=cred) self.assert_json_error(result, 'Invalid Kerberos cache') with \ ccache_mock(return_value=b'1234'), \ ssh_mock(side_effect=KeyError('foo')), \ logging_mock() as log: result = post("zephyr", cred=cred) self.assert_json_error(result, 'We were unable to setup mirroring for you') log.assert_called_with("Error updating the user's ccache") with ccache_mock(return_value=b'1234'), mirror_mock(), ssh_mock() as ssh: result = post("zephyr", cred=cred) self.assert_json_success(result) ssh.assert_called_with([ 'ssh', 'server', '--', '/home/zulip/python-zulip-api/zulip/integrations/zephyr/process_ccache', 'starnine', api_key, 'MTIzNA==']) # Accounts whose Kerberos usernames are known not to match their # zephyr accounts are hardcoded, and should be handled properly. def kerberos_alter_egos_mock() -> Any: return patch( 'zerver.views.zephyr.kerberos_alter_egos', {'kerberos_alter_ego': 'starnine'}) cred = dict(cname=dict(nameString=['kerberos_alter_ego'])) with \ ccache_mock(return_value=b'1234'), \ mirror_mock(), \ ssh_mock() as ssh, \ kerberos_alter_egos_mock(): result = post("zephyr", cred=cred) self.assert_json_success(result) ssh.assert_called_with([ 'ssh', 'server', '--', '/home/zulip/python-zulip-api/zulip/integrations/zephyr/process_ccache', 'starnine', api_key, 'MTIzNA=='])
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(validator=check_int, default=UserProfile.DEFAULT_BOT), payload_url: Optional[str]=REQ(validator=check_url, default=""), service_name: Optional[str]=REQ(default=None), config_data: Dict[str, str]=REQ(default={}, validator=check_dict(value_validator=check_string)), interface_type: int=REQ(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(validator=check_bool, default=None) ) -> HttpResponse: short_name = check_short_name(short_name_raw) 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')) try: get_user_by_delivery_email(email, user_profile.realm) return json_error(_("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: return json_error(_("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_rec, 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_rec, ignored_sub) = access_stream_by_name( user_profile, default_events_register_stream_name) if bot_type == UserProfile.EMBEDDED_BOT: check_valid_bot_config(service_name, config_data) bot_profile = do_create_user(email=email, password='', realm=user_profile.realm, full_name=full_name, short_name=short_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) if len(request.FILES) == 1: user_file = list(request.FILES.values())[0] upload_avatar_image(user_file, user_profile, bot_profile) if bot_type in (UserProfile.OUTGOING_WEBHOOK_BOT, UserProfile.EMBEDDED_BOT): add_service(name=service_name, user_profile=bot_profile, base_url=payload_url, interface=interface_type, token=generate_api_key()) if bot_type == 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( 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(json_result)
def api_fetch_api_key( request: HttpRequest, username: str = REQ(), password: str = REQ() ) -> HttpResponse: return_data: Dict[str, bool] = {} realm = get_realm_from_request(request) if realm is None: return json_error(_("Invalid subdomain")) if not ldap_auth_enabled(realm=realm): # In case we don't authenticate against LDAP, check for a valid # email. LDAP backend can authenticate against a non-email. validate_login_email(username) user_profile = authenticate(request=request, username=username, password=password, realm=realm, return_data=return_data) if return_data.get("inactive_user"): return json_error(_("Your account has been disabled."), data={"reason": "user disable"}, status=403) if return_data.get("inactive_realm"): return json_error( _("This organization has been deactivated."), data={"reason": "realm deactivated"}, status=403, ) if return_data.get("password_auth_disabled"): return json_error( _("Password auth is disabled in your team."), data={"reason": "password auth disabled"}, status=403, ) if return_data.get("password_reset_needed"): return json_error( _("You need to reset your password."), data={"reason": "password reset needed"}, status=403, ) if user_profile is None: return json_error( _("Your username or password is incorrect."), data={"reason": "incorrect_creds"}, status=403, ) # Maybe sending 'user_logged_in' signal is the better approach: # user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile) # Not doing this only because over here we don't add the user information # in the session. If the signal receiver assumes that we do then that # would cause problems. email_on_new_login(sender=user_profile.__class__, request=request, user=user_profile) # Mark this request as having a logged-in user for our server logs. process_client(request, user_profile) request._requestor_for_logs = user_profile.format_requestor_for_logs() api_key = get_api_key(user_profile) return json_success({ "api_key": api_key, "email": user_profile.delivery_email })
def test_webathena_kerberos_login(self) -> None: user = self.example_user("hamlet") self.login_user(user) def post(subdomain: Any, **kwargs: Any) -> HttpResponse: params = {k: orjson.dumps(v).decode() for k, v in kwargs.items()} return self.client_post("/accounts/webathena_kerberos_login/", params, subdomain=subdomain) result = post("zulip") self.assert_json_error(result, "Could not find Kerberos credential") result = post("zulip", cred="whatever") self.assert_json_error(result, "Webathena login not enabled") email = str(self.mit_email("starnine")) realm = get_realm("zephyr") user = get_user(email, realm) api_key = get_api_key(user) self.login_user(user) def ccache_mock(**kwargs: Any) -> Any: return patch("zerver.views.zephyr.make_ccache", **kwargs) def ssh_mock(**kwargs: Any) -> Any: return patch("zerver.views.zephyr.subprocess.check_call", **kwargs) def mirror_mock() -> Any: return self.settings(PERSONAL_ZMIRROR_SERVER="server") cred = dict(cname=dict(nameString=["starnine"])) with ccache_mock(side_effect=KeyError("foo")): result = post("zephyr", cred=cred) self.assert_json_error(result, "Invalid Kerberos cache") with ccache_mock(return_value=b"1234"), ssh_mock( side_effect=subprocess.CalledProcessError( 1, [])), self.assertLogs(level="ERROR") as log: result = post("zephyr", cred=cred) self.assert_json_error(result, "We were unable to setup mirroring for you") self.assertIn("Error updating the user's ccache", log.output[0]) with ccache_mock( return_value=b"1234"), mirror_mock(), ssh_mock() as ssh: result = post("zephyr", cred=cred) self.assert_json_success(result) ssh.assert_called_with([ "ssh", "server", "--", f"/home/zulip/python-zulip-api/zulip/integrations/zephyr/process_ccache starnine {api_key} MTIzNA==", ]) # Accounts whose Kerberos usernames are known not to match their # zephyr accounts are hardcoded, and should be handled properly. def kerberos_alter_egos_mock() -> Any: return patch("zerver.views.zephyr.kerberos_alter_egos", {"kerberos_alter_ego": "starnine"}) cred = dict(cname=dict(nameString=["kerberos_alter_ego"])) with ccache_mock(return_value=b"1234"), mirror_mock(), ssh_mock( ) as ssh, kerberos_alter_egos_mock(): result = post("zephyr", cred=cred) self.assert_json_success(result) ssh.assert_called_with([ "ssh", "server", "--", f"/home/zulip/python-zulip-api/zulip/integrations/zephyr/process_ccache starnine {api_key} MTIzNA==", ])
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(validator=check_int, default=UserProfile.DEFAULT_BOT), payload_url: Optional[str] = REQ(validator=check_url, default=""), service_name: Optional[str] = REQ(default=None), config_data: Dict[str, str] = REQ( default={}, validator=check_dict(value_validator=check_string)), interface_type: int = REQ(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(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 = '%s@%s' % (short_name, user_profile.realm.get_bot_domain()) except InvalidFakeEmailDomain: return json_error( _("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: 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')) try: get_user_by_delivery_email(email, user_profile.realm) return json_error(_("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: return json_error(_("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_rec, 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_rec, 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='', realm=user_profile.realm, full_name=full_name, short_name=short_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) if len(request.FILES) == 1: user_file = list(request.FILES.values())[0] 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( 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(json_result)
def test_local_file_type(self) -> None: def get_file_path_urlpart(uri: str, size: str = '') -> str: url_in_result = 'smart/filters:no_upscale()%s/%s/source_type/local_file' sharpen_filter = '' if size: url_in_result = f'/{size}/{url_in_result}' sharpen_filter = ':sharpen(0.5,0.2,true)' hex_uri = base64.urlsafe_b64encode(uri.encode()).decode('utf-8') return url_in_result % (sharpen_filter, hex_uri) self.login('hamlet') fp = StringIO("zulip!") fp.name = "zulip.jpeg" result = self.client_post("/json/user_uploads", {'file': fp}) self.assert_json_success(result) json = orjson.loads(result.content) self.assertIn("uri", json) uri = json["uri"] base = '/user_uploads/' self.assertEqual(base, uri[:len(base)]) # Test full size image. # We remove the forward slash infront of the `/user_uploads/` to match # Markdown behaviour. result = self.client_get("/thumbnail", { "url": uri[1:], "size": "full" }) self.assertEqual(result.status_code, 302, result) expected_part_url = get_file_path_urlpart(uri) self.assertIn(expected_part_url, result.url) # Test thumbnail size. result = self.client_get("/thumbnail", { "url": uri[1:], "size": "thumbnail" }) self.assertEqual(result.status_code, 302, result) expected_part_url = get_file_path_urlpart(uri, '0x300') self.assertIn(expected_part_url, result.url) # Test with a Unicode filename. fp = StringIO("zulip!") fp.name = "μένει.jpg" result = self.client_post("/json/user_uploads", {'file': fp}) self.assert_json_success(result) json = orjson.loads(result.content) self.assertIn("uri", json) uri = json["uri"] # We remove the forward slash infront of the `/user_uploads/` to match # Markdown behaviour. result = self.client_get("/thumbnail", { "url": uri[1:], "size": "full" }) self.assertEqual(result.status_code, 302, result) expected_part_url = get_file_path_urlpart(uri) self.assertIn(expected_part_url, result.url) # Test custom emoji urls in Zulip messages. user_profile = self.example_user("hamlet") file_name = "emoji.png" with get_test_image_file("img.png") as image_file: upload_emoji_image(image_file, file_name, user_profile) custom_emoji_url = upload_backend.get_emoji_url( file_name, user_profile.realm_id) emoji_url_base = '/user_avatars/' self.assertEqual(emoji_url_base, custom_emoji_url[:len(emoji_url_base)]) # Test full size custom emoji image (for emoji link in messages case). result = self.client_get("/thumbnail", { "url": custom_emoji_url[1:], "size": "full" }) self.assertEqual(result.status_code, 302, result) self.assertIn(custom_emoji_url, result.url) # Tests the /api/v1/thumbnail API endpoint with HTTP basic auth. self.logout() user_profile = self.example_user("hamlet") result = self.api_get(user_profile, "/thumbnail", { "url": uri[1:], "size": "full" }) self.assertEqual(result.status_code, 302, result) expected_part_url = get_file_path_urlpart(uri) self.assertIn(expected_part_url, result.url) # Tests the /api/v1/thumbnail API endpoint with ?api_key # auth. user_profile = self.example_user("hamlet") result = self.client_get("/thumbnail", { "url": uri[1:], "size": "full", "api_key": get_api_key(user_profile) }) self.assertEqual(result.status_code, 302, result) expected_part_url = get_file_path_urlpart(uri) self.assertIn(expected_part_url, result.url) # Test with another user trying to access image using thumbor. self.login('iago') result = self.client_get("/thumbnail", { "url": uri[1:], "size": "full" }) self.assertEqual(result.status_code, 403, result) self.assert_in_response("You are not authorized to view this file.", result)
def login_or_register_remote_user( request: HttpRequest, remote_username: Optional[str], user_profile: Optional[UserProfile], full_name: str = '', invalid_subdomain: bool = False, mobile_flow_otp: Optional[str] = None, is_signup: bool = False, redirect_to: str = '', multiuse_object_key: str = '') -> HttpResponse: """Given a successful authentication showing the user controls given email address (remote_username) and potentially a UserProfile object (if the user already has a Zulip account), redirect the browser to the appropriate place: * The logged-in app if the user already has a Zulip account and is trying to login, potentially to an initial narrow or page that had been saved in the `redirect_to` parameter. * The registration form if is_signup was set (i.e. the user is trying to create a Zulip account) * A special `confirm_continue_registration.html` "do you want to register or try another account" if the user doesn't have a Zulip account but is_signup is False (i.e. the user tried to login and then did social authentication selecting an email address that does not have a Zulip account in this organization). * A zulip:// URL to send control back to the mobile apps if they are doing authentication using the mobile_flow_otp flow. """ email = remote_user_to_email(remote_username) if user_profile is None or user_profile.is_mirror_dummy: # We have verified the user controls an email address, but # there's no associated Zulip user account. Consider sending # the request to registration. return maybe_send_to_registration( request, email, full_name, password_required=False, is_signup=is_signup, multiuse_object_key=multiuse_object_key) # Otherwise, the user has successfully authenticated to an # account, and we need to do the right thing depending whether # or not they're using the mobile OTP flow or want a browser session. if mobile_flow_otp is not None: # For the mobile Oauth flow, we send the API key and other # necessary details in a redirect to a zulip:// URI scheme. api_key = get_api_key(user_profile) params = { 'otp_encrypted_api_key': otp_encrypt_api_key(api_key, mobile_flow_otp), 'email': email, 'realm': user_profile.realm.uri, } # We can't use HttpResponseRedirect, since it only allows HTTP(S) URLs response = HttpResponse(status=302) response['Location'] = 'zulip://login?' + urllib.parse.urlencode( params) # Since we are returning an API key instead of going through # the Django login() function (which creates a browser # session, etc.), the "new login" signal handler (which # triggers an email notification new logins) will not run # automatically. So we call it manually here. # # Arguably, sending a fake 'user_logged_in' signal would be a better approach: # user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile) email_on_new_login(sender=user_profile.__class__, request=request, user=user_profile) # Mark this request as having a logged-in user for our server logs. process_client(request, user_profile) request._email = user_profile.email return response do_login(request, user_profile) redirect_to = get_safe_redirect_to(redirect_to, user_profile.realm.uri) return HttpResponseRedirect(redirect_to)
def test_webathena_kerberos_login(self) -> None: email = self.example_email('hamlet') self.login(email) def post(subdomain: Any, **kwargs: Any) -> HttpResponse: params = {k: ujson.dumps(v) for k, v in kwargs.items()} return self.client_post('/accounts/webathena_kerberos_login/', params, subdomain=subdomain) result = post("zulip") self.assert_json_error(result, 'Could not find Kerberos credential') result = post("zulip", cred='whatever') self.assert_json_error(result, 'Webathena login not enabled') email = str(self.mit_email("starnine")) realm = get_realm('zephyr') user = get_user(email, realm) api_key = get_api_key(user) self.login(email, realm=realm) def ccache_mock(**kwargs: Any) -> Any: return patch('zerver.views.zephyr.make_ccache', **kwargs) def ssh_mock(**kwargs: Any) -> Any: return patch('zerver.views.zephyr.subprocess.check_call', **kwargs) def mirror_mock() -> Any: return self.settings(PERSONAL_ZMIRROR_SERVER='server') def logging_mock() -> Any: return patch('logging.exception') cred = dict(cname=dict(nameString=['starnine'])) with ccache_mock(side_effect=KeyError('foo')): result = post("zephyr", cred=cred) self.assert_json_error(result, 'Invalid Kerberos cache') with \ ccache_mock(return_value=b'1234'), \ ssh_mock(side_effect=KeyError('foo')), \ logging_mock() as log: result = post("zephyr", cred=cred) self.assert_json_error(result, 'We were unable to setup mirroring for you') log.assert_called_with("Error updating the user's ccache") with ccache_mock(return_value=b'1234'), mirror_mock(), ssh_mock() as ssh: result = post("zephyr", cred=cred) self.assert_json_success(result) ssh.assert_called_with([ 'ssh', 'server', '--', '/home/zulip/python-zulip-api/zulip/integrations/zephyr/process_ccache', 'starnine', api_key, 'MTIzNA==']) # Accounts whose Kerberos usernames are known not to match their # zephyr accounts are hardcoded, and should be handled properly. def kerberos_alter_egos_mock() -> Any: return patch( 'zerver.views.zephyr.kerberos_alter_egos', {'kerberos_alter_ego': 'starnine'}) cred = dict(cname=dict(nameString=['kerberos_alter_ego'])) with \ ccache_mock(return_value=b'1234'), \ mirror_mock(), \ ssh_mock() as ssh, \ kerberos_alter_egos_mock(): result = post("zephyr", cred=cred) self.assert_json_success(result) ssh.assert_called_with([ 'ssh', 'server', '--', '/home/zulip/python-zulip-api/zulip/integrations/zephyr/process_ccache', 'starnine', api_key, 'MTIzNA=='])
def run_test_with_image_url(image_url: str) -> None: # Test full size image. self.login('hamlet') encoded_url = base64.urlsafe_b64encode( image_url.encode()).decode('utf-8') result = self.client_get("/thumbnail", { "url": image_url, "size": "full" }) self.assertEqual(result.status_code, 302, result) expected_part_url = '/smart/filters:no_upscale()/' + encoded_url + '/source_type/external' self.assertIn(expected_part_url, result.url) # Test thumbnail size. result = self.client_get("/thumbnail", { "url": image_url, "size": "thumbnail" }) self.assertEqual(result.status_code, 302, result) expected_part_url = '/0x300/smart/filters:no_upscale():sharpen(0.5,0.2,true)/' + encoded_url + '/source_type/external' self.assertIn(expected_part_url, result.url) # Test API endpoint with standard API authentication. self.logout() user_profile = self.example_user("hamlet") result = self.api_get(user_profile, "/thumbnail", { "url": image_url, "size": "thumbnail" }) self.assertEqual(result.status_code, 302, result) expected_part_url = '/0x300/smart/filters:no_upscale():sharpen(0.5,0.2,true)/' + encoded_url + '/source_type/external' self.assertIn(expected_part_url, result.url) # Test API endpoint with legacy API authentication. user_profile = self.example_user("hamlet") result = self.client_get( "/thumbnail", { "url": image_url, "size": "thumbnail", "api_key": get_api_key(user_profile) }) self.assertEqual(result.status_code, 302, result) expected_part_url = '/0x300/smart/filters:no_upscale():sharpen(0.5,0.2,true)/' + encoded_url + '/source_type/external' self.assertIn(expected_part_url, result.url) # Test a second logged-in user; they should also be able to access it user_profile = self.example_user("iago") result = self.client_get( "/thumbnail", { "url": image_url, "size": "thumbnail", "api_key": get_api_key(user_profile) }) self.assertEqual(result.status_code, 302, result) expected_part_url = '/0x300/smart/filters:no_upscale():sharpen(0.5,0.2,true)/' + encoded_url + '/source_type/external' self.assertIn(expected_part_url, result.url) # Test with another user trying to access image using thumbor. # File should be always accessible to user in case of external source self.login('iago') result = self.client_get("/thumbnail", { "url": image_url, "size": "full" }) self.assertEqual(result.status_code, 302, result) expected_part_url = '/smart/filters:no_upscale()/' + encoded_url + '/source_type/external' self.assertIn(expected_part_url, result.url)
def test_local_file_type(self) -> None: def get_file_path_urlpart(uri: str, size: str='') -> str: url_in_result = 'smart/filters:no_upscale()%s/%s/source_type/local_file' sharpen_filter = '' if size: url_in_result = '/%s/%s' % (size, url_in_result) sharpen_filter = ':sharpen(0.5,0.2,true)' hex_uri = base64.urlsafe_b64encode(uri.encode()).decode('utf-8') return url_in_result % (sharpen_filter, hex_uri) self.login(self.example_email("hamlet")) fp = StringIO("zulip!") fp.name = "zulip.jpeg" result = self.client_post("/json/user_uploads", {'file': fp}) self.assert_json_success(result) json = ujson.loads(result.content) self.assertIn("uri", json) uri = json["uri"] base = '/user_uploads/' self.assertEqual(base, uri[:len(base)]) # Test full size image. # We remove the forward slash infront of the `/user_uploads/` to match # bugdown behaviour. quoted_uri = urllib.parse.quote(uri[1:], safe='') result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_uri)) self.assertEqual(result.status_code, 302, result) expected_part_url = get_file_path_urlpart(uri) self.assertIn(expected_part_url, result.url) # Test thumbnail size. result = self.client_get("/thumbnail?url=%s&size=thumbnail" % (quoted_uri)) self.assertEqual(result.status_code, 302, result) expected_part_url = get_file_path_urlpart(uri, '0x300') self.assertIn(expected_part_url, result.url) # Test with a unicode filename. fp = StringIO("zulip!") fp.name = "μένει.jpg" result = self.client_post("/json/user_uploads", {'file': fp}) self.assert_json_success(result) json = ujson.loads(result.content) self.assertIn("uri", json) uri = json["uri"] # We remove the forward slash infront of the `/user_uploads/` to match # bugdown behaviour. quoted_uri = urllib.parse.quote(uri[1:], safe='') result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_uri)) self.assertEqual(result.status_code, 302, result) expected_part_url = get_file_path_urlpart(uri) self.assertIn(expected_part_url, result.url) # Test custom emoji urls in Zulip messages. user_profile = self.example_user("hamlet") image_file = get_test_image_file("img.png") file_name = "emoji.png" upload_emoji_image(image_file, file_name, user_profile) custom_emoji_url = upload_backend.get_emoji_url(file_name, user_profile.realm_id) emoji_url_base = '/user_avatars/' self.assertEqual(emoji_url_base, custom_emoji_url[:len(emoji_url_base)]) quoted_emoji_url = urllib.parse.quote(custom_emoji_url[1:], safe='') # Test full size custom emoji image (for emoji link in messages case). result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_emoji_url)) self.assertEqual(result.status_code, 302, result) self.assertIn(custom_emoji_url, result.url) # Tests the /api/v1/thumbnail api endpoint with HTTP basic auth. self.logout() user_profile = self.example_user("hamlet") result = self.api_get( self.example_email("hamlet"), '/thumbnail?url=%s&size=full' % (quoted_uri,)) self.assertEqual(result.status_code, 302, result) expected_part_url = get_file_path_urlpart(uri) self.assertIn(expected_part_url, result.url) # Tests the /api/v1/thumbnail api endpoint with ?api_key # auth. user_profile = self.example_user("hamlet") result = self.client_get( '/thumbnail?url=%s&size=full&api_key=%s' % (quoted_uri, get_api_key(user_profile))) self.assertEqual(result.status_code, 302, result) expected_part_url = get_file_path_urlpart(uri) self.assertIn(expected_part_url, result.url) # Test with another user trying to access image using thumbor. self.login(self.example_email("iago")) result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_uri)) self.assertEqual(result.status_code, 403, result) self.assert_in_response("You are not authorized to view this file.", result)