From f6219745def40ae29a1b0857176498ac9b7da459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20Gonz=C3=A1lez?= Date: Wed, 1 Aug 2018 10:53:40 +0200 Subject: [PATCH] users: Get all API keys via wrapper method. Now reading API keys from a user is done with the get_api_key wrapper method, rather than directly fetching it from the user object. Also, every place where an action should be done for each API key is now using get_all_api_keys. This method returns for the moment a single-item list, containing the specified user's API key. This commit is the first step towards allowing users have multiple API keys. --- tools/test-api | 6 ++- zerver/lib/actions.py | 9 ++++- zerver/lib/cache.py | 5 ++- zerver/lib/cache_helpers.py | 4 +- zerver/lib/test_classes.py | 6 ++- zerver/lib/users.py | 7 ++++ zerver/tests/test_auth_backends.py | 16 ++++---- zerver/tests/test_decorators.py | 38 +++++++++++-------- zerver/tests/test_events.py | 3 +- zerver/tests/test_settings.py | 8 ++-- zerver/tests/test_thumbnail.py | 8 ++-- zerver/tests/test_upload.py | 3 +- zerver/tests/test_zephyr.py | 7 +++- zerver/views/auth.py | 11 ++++-- zerver/views/users.py | 12 +++++- zerver/views/zephyr.py | 4 +- zerver/webhooks/dropbox/tests.py | 3 +- zerver/webhooks/github_legacy/tests.py | 5 ++- zerver/webhooks/jira/tests.py | 3 +- .../commands/print_initial_password.py | 4 +- 20 files changed, 110 insertions(+), 52 deletions(-) diff --git a/tools/test-api b/tools/test-api index cc477e1de0..a00ed1a39c 100755 --- a/tools/test-api +++ b/tools/test-api @@ -23,6 +23,7 @@ from zerver.lib.api_test_helpers import test_the_api, test_invalid_api_key, \ os.environ['DJANGO_SETTINGS_MODULE'] = 'zproject.test_settings' django.setup() from zerver.lib.actions import do_create_user +from zerver.lib.users import get_api_key from zerver.models import get_user, get_realm usage = """test-js-with-casper [options]""" @@ -45,7 +46,8 @@ with test_server_running(force=options.force, external_host='zulipdev.com:9981') # Prepare the admin client email = 'iago@zulip.com' # Iago is an admin realm = get_realm("zulip") - api_key = get_user(email, realm).api_key + user = get_user(email, realm) + api_key = get_api_key(user) site = 'http://zulip.zulipdev.com:9981' client = Client( @@ -58,7 +60,7 @@ with test_server_running(force=options.force, external_host='zulipdev.com:9981') email = 'guest@zulip.com' # guest is not an admin guest_user = do_create_user('guest@zulip.com', 'secret', get_realm('zulip'), 'Mr. Guest', 'guest') - api_key = guest_user.api_key + api_key = get_api_key(guest_user) nonadmin_client = Client( email=email, api_key=api_key, diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index 69d23148c6..91f08e6caf 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -60,7 +60,12 @@ from zerver.lib.topic_mutes import ( add_topic_mute, remove_topic_mute, ) -from zerver.lib.users import bulk_get_users, check_full_name, user_ids_to_users +from zerver.lib.users import ( + bulk_get_users, + check_full_name, + get_api_key, + user_ids_to_users +) from zerver.lib.user_groups import create_user_group, access_user_group_by_id from zerver.models import Realm, RealmEmoji, Stream, UserProfile, UserActivity, \ RealmDomain, Service, SubMessage, \ @@ -498,7 +503,7 @@ def created_bot_event(user_profile: UserProfile) -> Dict[str, Any]: full_name=user_profile.full_name, bot_type=user_profile.bot_type, is_active=user_profile.is_active, - api_key=user_profile.api_key, + api_key=get_api_key(user_profile), default_sending_stream=default_sending_stream_name, default_events_register_stream=default_events_register_stream_name, default_all_public_streams=user_profile.default_all_public_streams, diff --git a/zerver/lib/cache.py b/zerver/lib/cache.py index 30c5d45147..720b62e6cf 100644 --- a/zerver/lib/cache.py +++ b/zerver/lib/cache.py @@ -349,11 +349,14 @@ def get_stream_cache_key(stream_name: str, realm_id: int) -> str: realm_id, make_safe_digest(stream_name.strip().lower())) def delete_user_profile_caches(user_profiles: Iterable['UserProfile']) -> None: + # Imported here to avoid cyclic dependency. + from zerver.lib.users import get_all_api_keys keys = [] for user_profile in user_profiles: keys.append(user_profile_by_email_cache_key(user_profile.email)) keys.append(user_profile_by_id_cache_key(user_profile.id)) - keys.append(user_profile_by_api_key_cache_key(user_profile.api_key)) + for api_key in get_all_api_keys(user_profile): + keys.append(user_profile_by_api_key_cache_key(api_key)) keys.append(user_profile_cache_key(user_profile.email, user_profile.realm)) cache_delete_many(keys) diff --git a/zerver/lib/cache_helpers.py b/zerver/lib/cache_helpers.py index 5971a85f06..f0e21ffdf8 100644 --- a/zerver/lib/cache_helpers.py +++ b/zerver/lib/cache_helpers.py @@ -18,6 +18,7 @@ from zerver.lib.cache import cache_with_key, cache_set, \ user_profile_cache_key, get_remote_cache_time, get_remote_cache_requests, \ cache_set_many, to_dict_cache_key_id from zerver.lib.message import MessageDict +from zerver.lib.users import get_all_api_keys from importlib import import_module from django.contrib.sessions.models import Session from django.db.models import Q @@ -45,7 +46,8 @@ def message_cache_items(items_for_remote_cache: Dict[str, Tuple[bytes]], def user_cache_items(items_for_remote_cache: Dict[str, Tuple[UserProfile]], user_profile: UserProfile) -> None: - items_for_remote_cache[user_profile_by_api_key_cache_key(user_profile.api_key)] = (user_profile,) + for api_key in get_all_api_keys(user_profile): + items_for_remote_cache[user_profile_by_api_key_cache_key(api_key)] = (user_profile,) items_for_remote_cache[user_profile_cache_key(user_profile.email, user_profile.realm)] = (user_profile,) # We have other user_profile caches, but none of them are on the # core serving path for lots of requests. diff --git a/zerver/lib/test_classes.py b/zerver/lib/test_classes.py index bd999e0061..474f3ebba7 100644 --- a/zerver/lib/test_classes.py +++ b/zerver/lib/test_classes.py @@ -20,6 +20,7 @@ from django.http import HttpRequest from two_factor.models import PhoneDevice from zerver.lib.initial_password import initial_password from zerver.lib.utils import is_remote_server +from zerver.lib.users import get_api_key from zerver.lib.actions import ( check_send_message, create_stream_if_needed, bulk_add_subscriptions, @@ -370,7 +371,8 @@ class ZulipTestCase(TestCase): if is_remote_server(identifier): api_key = get_remote_server_by_uuid(identifier).api_key else: - api_key = get_user(identifier, get_realm(realm)).api_key + user = get_user(identifier, get_realm(realm)) + api_key = get_api_key(user) API_KEYS[identifier] = api_key credentials = "%s:%s" % (identifier, api_key) @@ -707,7 +709,7 @@ class WebhookTestCase(ZulipTestCase): def build_webhook_url(self, *args: Any, **kwargs: Any) -> str: url = self.URL_TEMPLATE if url.find("api_key") >= 0: - api_key = self.test_user.api_key + api_key = get_api_key(self.test_user) url = self.URL_TEMPLATE.format(api_key=api_key, stream=self.STREAM_NAME) else: diff --git a/zerver/lib/users.py b/zerver/lib/users.py index 223d4d499f..2af42b48e4 100644 --- a/zerver/lib/users.py +++ b/zerver/lib/users.py @@ -179,3 +179,10 @@ def get_accounts_for_email(email: str) -> List[Dict[str, Optional[str]]]: "full_name": profile.full_name, "avatar": avatar_url(profile)} for profile in profiles] + +def get_api_key(user_profile: UserProfile) -> str: + return user_profile.api_key + +def get_all_api_keys(user_profile: UserProfile) -> List[str]: + # Users can only have one API key for now + return [user_profile.api_key] diff --git a/zerver/tests/test_auth_backends.py b/zerver/tests/test_auth_backends.py index 5042a6de6f..fb6333c6ac 100644 --- a/zerver/tests/test_auth_backends.py +++ b/zerver/tests/test_auth_backends.py @@ -33,6 +33,7 @@ from zerver.lib.mobile_auth_otp import otp_decrypt_api_key from zerver.lib.validator import validate_login_email, \ check_bool, check_dict_only, check_string, Validator from zerver.lib.request import JsonableError +from zerver.lib.users import get_all_api_keys from zerver.lib.initial_password import initial_password from zerver.lib.sessions import get_session_dict_user from zerver.lib.test_classes import ( @@ -676,8 +677,8 @@ class GitHubAuthBackendTest(ZulipTestCase): self.assertEqual(query_params["realm"], ['http://zulip.testserver']) self.assertEqual(query_params["email"], [self.example_email("hamlet")]) encrypted_api_key = query_params["otp_encrypted_api_key"][0] - self.assertEqual(self.example_user('hamlet').api_key, - otp_decrypt_api_key(encrypted_api_key, mobile_flow_otp)) + hamlet_api_keys = get_all_api_keys(self.example_user('hamlet')) + self.assertIn(otp_decrypt_api_key(encrypted_api_key, mobile_flow_otp), hamlet_api_keys) self.assertEqual(len(mail.outbox), 1) self.assertIn('Zulip on Android', mail.outbox[0].body) @@ -931,8 +932,8 @@ class GoogleSubdomainLoginTest(GoogleOAuthTest): self.assertEqual(query_params["realm"], ['http://zulip.testserver']) self.assertEqual(query_params["email"], [self.example_email("hamlet")]) encrypted_api_key = query_params["otp_encrypted_api_key"][0] - self.assertEqual(self.example_user('hamlet').api_key, - otp_decrypt_api_key(encrypted_api_key, mobile_flow_otp)) + hamlet_api_keys = get_all_api_keys(self.example_user('hamlet')) + self.assertIn(otp_decrypt_api_key(encrypted_api_key, mobile_flow_otp), hamlet_api_keys) self.assertEqual(len(mail.outbox), 1) self.assertIn('Zulip on Android', mail.outbox[0].body) @@ -1446,7 +1447,8 @@ class DevFetchAPIKeyTest(ZulipTestCase): self.assert_json_success(result) data = result.json() self.assertEqual(data["email"], self.email) - self.assertEqual(data['api_key'], self.user_profile.api_key) + user_api_keys = get_all_api_keys(self.user_profile) + self.assertIn(data['api_key'], user_api_keys) def test_invalid_email(self) -> None: email = 'hamlet' @@ -1924,8 +1926,8 @@ class TestZulipRemoteUserBackend(ZulipTestCase): self.assertEqual(query_params["realm"], ['http://zulip.testserver']) self.assertEqual(query_params["email"], [self.example_email("hamlet")]) encrypted_api_key = query_params["otp_encrypted_api_key"][0] - self.assertEqual(self.example_user('hamlet').api_key, - otp_decrypt_api_key(encrypted_api_key, mobile_flow_otp)) + hamlet_api_keys = get_all_api_keys(self.example_user('hamlet')) + self.assertIn(otp_decrypt_api_key(encrypted_api_key, mobile_flow_otp), hamlet_api_keys) self.assertEqual(len(mail.outbox), 1) self.assertIn('Zulip on Android', mail.outbox[0].body) diff --git a/zerver/tests/test_decorators.py b/zerver/tests/test_decorators.py index b605a1f24f..cb2f1e7167 100644 --- a/zerver/tests/test_decorators.py +++ b/zerver/tests/test_decorators.py @@ -26,6 +26,7 @@ from zerver.lib.test_classes import ( WebhookTestCase, ) from zerver.lib.response import json_response +from zerver.lib.users import get_api_key from zerver.lib.user_agent import parse_user_agent from zerver.lib.request import \ REQ, has_request_variables, RequestVariableMissingError, \ @@ -249,7 +250,7 @@ class DecoratorTestCase(TestCase): webhook_bot_email = 'webhook-bot@zulip.com' 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_bot_api_key = get_api_key(webhook_bot) webhook_client_name = "ZulipClientNameWebhook" request = HostRequestMock() @@ -369,7 +370,7 @@ class DecoratorLoggingTestCase(ZulipTestCase): webhook_bot_email = 'webhook-bot@zulip.com' 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_bot_api_key = get_api_key(webhook_bot) request = HostRequestMock() request.method = 'POST' @@ -470,7 +471,8 @@ body: def test_authenticated_rest_api_view_errors(self) -> None: user_profile = self.example_user("hamlet") - credentials = "%s:%s" % (user_profile.email, user_profile.api_key) + api_key = get_api_key(user_profile) + credentials = "%s:%s" % (user_profile.email, api_key) api_auth = 'Digest ' + base64.b64encode(credentials.encode('utf-8')).decode('utf-8') result = self.client_post('/api/v1/external/zendesk', {}, HTTP_AUTHORIZATION=api_auth) @@ -856,8 +858,8 @@ class DeactivatedRealmTest(ZulipTestCase): """ do_deactivate_realm(get_realm("zulip")) user_profile = self.example_user("hamlet") - url = "/api/v1/external/jira?api_key=%s&stream=jira_custom" % ( - user_profile.api_key,) + api_key = get_api_key(user_profile) + url = "/api/v1/external/jira?api_key=%s&stream=jira_custom" % (api_key,) data = self.webhook_fixture_data('jira', 'created_v2') result = self.client_post(url, data, content_type="application/json") @@ -1029,8 +1031,8 @@ class InactiveUserTest(ZulipTestCase): user_profile = self.example_user('hamlet') do_deactivate_user(user_profile) - url = "/api/v1/external/jira?api_key=%s&stream=jira_custom" % ( - user_profile.api_key,) + api_key = get_api_key(user_profile) + url = "/api/v1/external/jira?api_key=%s&stream=jira_custom" % (api_key,) data = self.webhook_fixture_data('jira', 'created_v2') result = self.client_post(url, data, content_type="application/json") @@ -1072,36 +1074,41 @@ class TestValidateApiKey(ZulipTestCase): # We use default_bot's key but webhook_bot's email address to test # the logic when an API key is passed and it doesn't belong to the # user whose email address has been provided. - validate_api_key(HostRequestMock(), self.webhook_bot.email, self.default_bot.api_key) + api_key = get_api_key(self.default_bot) + validate_api_key(HostRequestMock(), self.webhook_bot.email, api_key) def test_validate_api_key_if_profile_is_not_active(self) -> 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) + api_key = get_api_key(self.default_bot) + validate_api_key(HostRequestMock(), self.default_bot.email, api_key) self._change_is_active_field(self.default_bot, True) def test_validate_api_key_if_profile_is_incoming_webhook_and_is_webhook_is_unset(self) -> None: with self.assertRaises(JsonableError): - validate_api_key(HostRequestMock(), self.webhook_bot.email, self.webhook_bot.api_key) + api_key = get_api_key(self.webhook_bot) + validate_api_key(HostRequestMock(), self.webhook_bot.email, api_key) def test_validate_api_key_if_profile_is_incoming_webhook_and_is_webhook_is_set(self) -> None: + api_key = get_api_key(self.webhook_bot) profile = validate_api_key(HostRequestMock(host="zulip.testserver"), - self.webhook_bot.email, self.webhook_bot.api_key, + self.webhook_bot.email, api_key, is_webhook=True) self.assertEqual(profile.id, self.webhook_bot.id) def test_validate_api_key_if_email_is_case_insensitive(self) -> None: - profile = validate_api_key(HostRequestMock(host="zulip.testserver"), self.default_bot.email.upper(), self.default_bot.api_key) + api_key = get_api_key(self.default_bot) + profile = validate_api_key(HostRequestMock(host="zulip.testserver"), self.default_bot.email.upper(), api_key) self.assertEqual(profile.id, self.default_bot.id) def test_valid_api_key_if_user_is_on_wrong_subdomain(self) -> None: with self.settings(RUNNING_INSIDE_TORNADO=False): + api_key = get_api_key(self.default_bot) with mock.patch('logging.warning') as mock_warning: with self.assertRaisesRegex(JsonableError, "Account is not associated with this subdomain"): validate_api_key(HostRequestMock(host=settings.EXTERNAL_HOST), - self.default_bot.email, - self.default_bot.api_key) + self.default_bot.email, api_key) mock_warning.assert_called_with( "User {} ({}) attempted to access API on wrong " @@ -1111,8 +1118,7 @@ class TestValidateApiKey(ZulipTestCase): with self.assertRaisesRegex(JsonableError, "Account is not associated with this subdomain"): validate_api_key(HostRequestMock(host='acme.' + settings.EXTERNAL_HOST), - self.default_bot.email, - self.default_bot.api_key) + self.default_bot.email, api_key) mock_warning.assert_called_with( "User {} ({}) attempted to access API on wrong " diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index 79c38acb84..5f5f58da80 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -119,6 +119,7 @@ from zerver.lib.validator import ( equals, check_none_or, Validator, check_url ) from zerver.lib.upload import upload_backend, attachment_url_to_path_id +from zerver.lib.users import get_api_key from zerver.views.events_register import _default_all_public_streams, _default_narrow @@ -2341,7 +2342,7 @@ class FetchInitialStateDataTest(ZulipTestCase): self.assert_length(result['realm_bots'], 0) # additionally the API key for a random bot is not present in the data - api_key = self.notification_bot().api_key + api_key = get_api_key(self.notification_bot()) self.assertNotIn(api_key, str(result)) # Admin users have access to all bots in the realm_bots field diff --git a/zerver/tests/test_settings.py b/zerver/tests/test_settings.py index 2086b44238..956e208e60 100644 --- a/zerver/tests/test_settings.py +++ b/zerver/tests/test_settings.py @@ -10,6 +10,7 @@ from zerver.lib.initial_password import initial_password from zerver.lib.sessions import get_session_dict_user from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import MockLDAP +from zerver.lib.users import get_all_api_keys from zerver.models import get_realm, get_user, UserProfile class ChangeSettingsTest(ZulipTestCase): @@ -286,10 +287,11 @@ class UserChangesTest(ZulipTestCase): user = self.example_user('hamlet') email = user.email self.login(email) - old_api_key = user.api_key + old_api_keys = get_all_api_keys(user) result = self.client_post('/json/users/me/api_key/regenerate') self.assert_json_success(result) new_api_key = result.json()['api_key'] - self.assertNotEqual(old_api_key, new_api_key) + self.assertNotIn(new_api_key, old_api_keys) user = self.example_user('hamlet') - self.assertEqual(new_api_key, user.api_key) + current_api_keys = get_all_api_keys(user) + self.assertIn(new_api_key, current_api_keys) diff --git a/zerver/tests/test_thumbnail.py b/zerver/tests/test_thumbnail.py index f03e3a004b..c188a091cd 100644 --- a/zerver/tests/test_thumbnail.py +++ b/zerver/tests/test_thumbnail.py @@ -3,6 +3,7 @@ from django.conf import settings from zerver.lib.test_classes import ZulipTestCase, UploadSerializeMixin from zerver.lib.test_helpers import use_s3_backend, override_settings +from zerver.lib.users import get_api_key from io import StringIO from boto.s3.connection import S3Connection @@ -97,14 +98,15 @@ class ThumbnailTest(ZulipTestCase): # 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, user_profile.api_key)) + quoted_url, get_api_key(user_profile))) self.assertEqual(result.status_code, 302, result) expected_part_url = '/0x100/smart/filters:no_upscale()/' + 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, user_profile.api_key)) + 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 = '/0x100/smart/filters:no_upscale()/' + encoded_url + '/source_type/external' self.assertIn(expected_part_url, result.url) @@ -194,7 +196,7 @@ class ThumbnailTest(ZulipTestCase): user_profile = self.example_user("hamlet") result = self.client_get( '/thumbnail?url=%s&size=full&api_key=%s' % - (quoted_uri, user_profile.api_key)) + (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) diff --git a/zerver/tests/test_upload.py b/zerver/tests/test_upload.py index ef49594783..9eecf85403 100644 --- a/zerver/tests/test_upload.py +++ b/zerver/tests/test_upload.py @@ -36,6 +36,7 @@ from zerver.lib.actions import ( ) from zerver.lib.create_user import copy_user_settings from zerver.lib.request import JsonableError +from zerver.lib.users import get_api_key from zerver.views.upload import upload_file_backend, serve_local import urllib @@ -115,7 +116,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase): response = self.client_get(uri + "?api_key=" + "invalid") self.assertEqual(response.status_code, 400) - response = self.client_get(uri + "?api_key=" + user_profile.api_key) + response = self.client_get(uri + "?api_key=" + get_api_key(user_profile)) self.assertEqual(response.status_code, 200) data = b"".join(response.streaming_content) self.assertEqual(b"zulip!", data) diff --git a/zerver/tests/test_zephyr.py b/zerver/tests/test_zephyr.py index b474fc6692..9264d76416 100644 --- a/zerver/tests/test_zephyr.py +++ b/zerver/tests/test_zephyr.py @@ -6,6 +6,7 @@ from mock import patch from typing import Any, Dict from zerver.lib.test_classes import ZulipTestCase +from zerver.lib.users import get_api_key from zerver.models import get_user, get_realm @@ -27,6 +28,8 @@ class ZephyrTest(ZulipTestCase): 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: @@ -66,7 +69,7 @@ class ZephyrTest(ZulipTestCase): '--', '/home/zulip/python-zulip-api/zulip/integrations/zephyr/process_ccache', 'starnine', - get_user(email, realm).api_key, + api_key, 'MTIzNA==']) # Accounts whose Kerberos usernames are known not to match their @@ -92,5 +95,5 @@ class ZephyrTest(ZulipTestCase): '--', '/home/zulip/python-zulip-api/zulip/integrations/zephyr/process_ccache', 'starnine', - get_user(email, realm).api_key, + api_key, 'MTIzNA==']) diff --git a/zerver/views/auth.py b/zerver/views/auth.py index 2d053ccdc8..1b9b7e13ed 100644 --- a/zerver/views/auth.py +++ b/zerver/views/auth.py @@ -32,6 +32,7 @@ from zerver.lib.push_notifications import push_notifications_enabled from zerver.lib.request import REQ, has_request_variables, JsonableError from zerver.lib.response import json_success, json_error from zerver.lib.subdomains import get_subdomain, is_subdomain_root_or_alias +from zerver.lib.users import get_api_key from zerver.lib.validator import validate_login_email from zerver.models import PreregistrationUser, UserProfile, remote_user_to_email, Realm, \ get_realm @@ -730,7 +731,8 @@ def api_dev_fetch_api_key(request: HttpRequest, username: str=REQ()) -> HttpResp return json_error(_("This user is not registered."), data={"reason": "unregistered"}, status=403) do_login(request, user_profile) - return json_success({"api_key": user_profile.api_key, "email": user_profile.email}) + api_key = get_api_key(user_profile) + return json_success({"api_key": api_key, "email": user_profile.email}) @csrf_exempt def api_dev_list_users(request: HttpRequest) -> HttpResponse: @@ -793,7 +795,8 @@ def api_fetch_api_key(request: HttpRequest, username: str=REQ(), password: str=R process_client(request, user_profile) request._email = user_profile.email - return json_success({"api_key": user_profile.api_key, "email": user_profile.email}) + api_key = get_api_key(user_profile) + return json_success({"api_key": api_key, "email": user_profile.email}) def get_auth_backends_data(request: HttpRequest) -> Dict[str, Any]: """Returns which authentication methods are enabled on the server""" @@ -863,7 +866,9 @@ def json_fetch_api_key(request: HttpRequest, user_profile: UserProfile, if not authenticate(username=user_profile.email, password=password, realm=realm): return json_error(_("Your username or password is incorrect.")) - return json_success({"api_key": user_profile.api_key}) + + api_key = get_api_key(user_profile) + return json_success({"api_key": api_key}) @csrf_exempt def api_fetch_google_client_id(request: HttpRequest) -> HttpResponse: diff --git a/zerver/views/users.py b/zerver/views/users.py index 7549af40ce..ddbecb83e4 100644 --- a/zerver/views/users.py +++ b/zerver/views/users.py @@ -27,6 +27,7 @@ from zerver.lib.request import has_request_variables, REQ from zerver.lib.response import json_error, json_success from zerver.lib.streams import access_stream_by_name from zerver.lib.upload import upload_avatar_image +from zerver.lib.users import get_api_key from zerver.lib.validator import check_bool, check_string, check_int, check_url, check_dict from zerver.lib.users import check_valid_bot_type, check_bot_creation_policy, \ check_full_name, check_short_name, check_valid_interface_type, check_valid_bot_config, \ @@ -317,8 +318,10 @@ def add_bot_backend( notify_created_bot(bot_profile) + api_key = get_api_key(bot_profile) + json_result = dict( - api_key=bot_profile.api_key, + 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), @@ -337,10 +340,15 @@ def get_bots_backend(request: HttpRequest, user_profile: UserProfile) -> HttpRes 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=bot_profile.api_key, + api_key=api_key, avatar_url=avatar_url(bot_profile), default_sending_stream=default_sending_stream, default_events_register_stream=default_events_register_stream, diff --git a/zerver/views/zephyr.py b/zerver/views/zephyr.py index 2dc97c63f3..94836f3d8d 100644 --- a/zerver/views/zephyr.py +++ b/zerver/views/zephyr.py @@ -8,6 +8,7 @@ from zerver.lib.ccache import make_ccache from zerver.lib.request import has_request_variables, REQ, JsonableError from zerver.lib.response import json_success, json_error from zerver.lib.str_utils import force_str +from zerver.lib.users import get_api_key from zerver.models import UserProfile import base64 @@ -44,10 +45,11 @@ def webathena_kerberos_login(request: HttpRequest, user_profile: UserProfile, # 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(user_profile.api_key), + force_str(api_key), force_str(base64.b64encode(ccache))]) except Exception: logging.exception("Error updating the user's ccache") diff --git a/zerver/webhooks/dropbox/tests.py b/zerver/webhooks/dropbox/tests.py index 1ce77f0f40..c754be9df8 100644 --- a/zerver/webhooks/dropbox/tests.py +++ b/zerver/webhooks/dropbox/tests.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from zerver.lib.test_classes import WebhookTestCase +from zerver.lib.users import get_api_key class DropboxHookTests(WebhookTestCase): STREAM_NAME = 'test' @@ -21,7 +22,7 @@ class DropboxHookTests(WebhookTestCase): self.subscribe(self.test_user, self.STREAM_NAME) get_params = {'stream_name': self.STREAM_NAME, 'challenge': '9B2SVL4orbt5DxLMqJHI6pOTipTqingt2YFMIO0g06E', - 'api_key': self.test_user.api_key} + 'api_key': get_api_key(self.test_user)} result = self.client_get(self.url, get_params) self.assert_in_response('9B2SVL4orbt5DxLMqJHI6pOTipTqingt2YFMIO0g06E', result) diff --git a/zerver/webhooks/github_legacy/tests.py b/zerver/webhooks/github_legacy/tests.py index 1b9bdd96aa..5fe5b65052 100644 --- a/zerver/webhooks/github_legacy/tests.py +++ b/zerver/webhooks/github_legacy/tests.py @@ -3,6 +3,7 @@ from typing import Dict, Optional import ujson from zerver.lib.test_classes import WebhookTestCase +from zerver.lib.users import get_api_key from zerver.lib.webhooks.git import COMMITS_LIMIT from zerver.models import Message @@ -39,7 +40,7 @@ class GithubV1HookTests(WebhookTestCase): self.assertEqual(prior_count, after_count) def get_body(self, fixture_name: str) -> Dict[str, str]: - api_key = self.test_user.api_key + 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, @@ -164,7 +165,7 @@ class GithubV2HookTests(WebhookTestCase): self.assertEqual(prior_count, after_count) def get_body(self, fixture_name: str) -> Dict[str, str]: - api_key = self.test_user.api_key + api_key = get_api_key(self.test_user) data = ujson.loads(self.webhook_fixture_data(self.FIXTURE_DIR_NAME, 'v2_' + fixture_name)) data.update({'email': self.TEST_USER_EMAIL, 'api-key': api_key, diff --git a/zerver/webhooks/jira/tests.py b/zerver/webhooks/jira/tests.py index 0c5df90001..9b81b7e30e 100644 --- a/zerver/webhooks/jira/tests.py +++ b/zerver/webhooks/jira/tests.py @@ -1,13 +1,14 @@ # -*- coding: utf-8 -*- from zerver.lib.test_classes import WebhookTestCase +from zerver.lib.users import get_api_key class JiraHookTests(WebhookTestCase): STREAM_NAME = 'jira' URL_TEMPLATE = u"/api/v1/external/jira?api_key={api_key}&stream={stream}" def test_custom_stream(self) -> None: - api_key = self.test_user.api_key + 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, diff --git a/zilencer/management/commands/print_initial_password.py b/zilencer/management/commands/print_initial_password.py index cf8a2a3fad..df0cbb3dbf 100644 --- a/zilencer/management/commands/print_initial_password.py +++ b/zilencer/management/commands/print_initial_password.py @@ -3,6 +3,7 @@ from typing import Any from zerver.lib.initial_password import initial_password from zerver.lib.management import ZulipBaseCommand +from zerver.lib.users import get_api_key class Command(ZulipBaseCommand): help = "Print the initial password and API key for accounts as created by populate_db" @@ -21,4 +22,5 @@ class Command(ZulipBaseCommand): if '@' not in email: print('ERROR: %s does not look like an email address' % (email,)) continue - print(self.fmt % (email, initial_password(email), self.get_user(email, realm).api_key)) + user = self.get_user(email, realm) + print(self.fmt % (email, initial_password(email), get_api_key(user)))