From e33dd3ada1557a969f72a4641a033e04e3f87990 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Fri, 12 Jul 2019 11:35:04 -0700 Subject: [PATCH 1/4] Added UserNotFoundError type --- firebase_admin/_auth_utils.py | 12 +++++++++- firebase_admin/_user_mgt.py | 18 +++++++-------- firebase_admin/auth.py | 2 ++ integration/test_auth.py | 9 ++++---- tests/test_user_mgt.py | 42 +++++++++++++++++++++++------------ 5 files changed, 54 insertions(+), 29 deletions(-) diff --git a/firebase_admin/_auth_utils.py b/firebase_admin/_auth_utils.py index d9b8c66e0..ae94627f3 100644 --- a/firebase_admin/_auth_utils.py +++ b/firebase_admin/_auth_utils.py @@ -209,8 +209,18 @@ def __init__(self, message, cause=None, http_response=None): exceptions.UnknownError.__init__(self, message, cause, http_response) +class UserNotFoundError(exceptions.NotFoundError): + """Failed to find a user with the specified details.""" + + default_message = 'No user record found for the given identifier' + + def __init__(self, message, cause=None, http_response=None): + exceptions.NotFoundError.__init__(self, message, cause, http_response) + + _CODE_TO_EXC_TYPE = { 'INVALID_ID_TOKEN': InvalidIdTokenError, + 'USER_NOT_FOUND': UserNotFoundError, } @@ -243,7 +253,7 @@ def _parse_error_body(response): pass # Auth error response format: {"error": {"message": "AUTH_ERROR_CODE: Optional text"}} - code = error_dict.get('message') + code = error_dict.get('message') if isinstance(error_dict, dict) else None custom_message = None if code: separator = code.find(':') diff --git a/firebase_admin/_user_mgt.py b/firebase_admin/_user_mgt.py index 24bb2bdb6..e0997c4c2 100644 --- a/firebase_admin/_user_mgt.py +++ b/firebase_admin/_user_mgt.py @@ -24,8 +24,6 @@ from firebase_admin import _user_import -INTERNAL_ERROR = 'INTERNAL_ERROR' -USER_NOT_FOUND_ERROR = 'USER_NOT_FOUND_ERROR' USER_CREATE_ERROR = 'USER_CREATE_ERROR' USER_UPDATE_ERROR = 'USER_UPDATE_ERROR' USER_DELETE_ERROR = 'USER_DELETE_ERROR' @@ -484,16 +482,16 @@ def get_user(self, **kwargs): raise TypeError('Unsupported keyword arguments: {0}.'.format(kwargs)) try: - response = self._client.body('post', '/accounts:lookup', json=payload) + body, http_resp = self._client.body_and_response( + 'post', '/accounts:lookup', json=payload) except requests.exceptions.RequestException as error: - msg = 'Failed to get user by {0}: {1}.'.format(key_type, key) - self._handle_http_error(INTERNAL_ERROR, msg, error) + raise _auth_utils.handle_auth_backend_error(error) else: - if not response or not response.get('users'): - raise ApiCallError( - USER_NOT_FOUND_ERROR, - 'No user record found for the provided {0}: {1}.'.format(key_type, key)) - return response['users'][0] + if not body or not body.get('users'): + raise _auth_utils.UserNotFoundError( + 'No user record found for the provided {0}: {1}.'.format(key_type, key), + http_response=http_resp) + return body['users'][0] def list_users(self, page_token=None, max_results=MAX_LIST_USERS_RESULTS): """Retrieves a batch of users.""" diff --git a/firebase_admin/auth.py b/firebase_admin/auth.py index cd9a882e6..806b1d6aa 100644 --- a/firebase_admin/auth.py +++ b/firebase_admin/auth.py @@ -48,6 +48,7 @@ 'UserImportResult', 'UserInfo', 'UserMetadata', + 'UserNotFoundError', 'UserProvider', 'UserRecord', @@ -83,6 +84,7 @@ UserImportResult = _user_import.UserImportResult UserInfo = _user_mgt.UserInfo UserMetadata = _user_mgt.UserMetadata +UserNotFoundError = _auth_utils.UserNotFoundError UserProvider = _user_import.UserProvider UserRecord = _user_mgt.UserRecord diff --git a/integration/test_auth.py b/integration/test_auth.py index a2905d881..b5c95d1fc 100644 --- a/integration/test_auth.py +++ b/integration/test_auth.py @@ -29,6 +29,7 @@ import google.oauth2.credentials from google.auth import transport + _verify_token_url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken' _verify_password_url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword' _password_reset_url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/resetPassword' @@ -135,14 +136,14 @@ def test_session_cookie_error(): auth.create_session_cookie('not.a.token', expires_in=expires_in) def test_get_non_existing_user(): - with pytest.raises(auth.AuthError) as excinfo: + with pytest.raises(auth.UserNotFoundError) as excinfo: auth.get_user('non.existing') - assert 'USER_NOT_FOUND_ERROR' in str(excinfo.value.code) + assert str(excinfo.value) == 'No user record found for the provided user ID: non.existing.' def test_get_non_existing_user_by_email(): - with pytest.raises(auth.AuthError) as excinfo: + with pytest.raises(auth.UserNotFoundError) as excinfo: auth.get_user_by_email('non.existing@definitely.non.existing') - assert 'USER_NOT_FOUND_ERROR' in str(excinfo.value.code) + assert str(excinfo.value) == 'No user record found for the provided email: non.existing@definitely.non.existing.' def test_update_non_existing_user(): with pytest.raises(auth.AuthError) as excinfo: diff --git a/tests/test_user_mgt.py b/tests/test_user_mgt.py index 797e0ce59..179ed3e86 100644 --- a/tests/test_user_mgt.py +++ b/tests/test_user_mgt.py @@ -21,6 +21,7 @@ import firebase_admin from firebase_admin import auth +from firebase_admin import exceptions from firebase_admin import _auth_utils from firebase_admin import _user_import from firebase_admin import _user_mgt @@ -211,30 +212,43 @@ def test_get_user_by_phone(self, user_mgt_app): def test_get_user_non_existing(self, user_mgt_app): _instrument_user_manager(user_mgt_app, 200, '{"users":[]}') - with pytest.raises(auth.AuthError) as excinfo: + with pytest.raises(auth.UserNotFoundError) as excinfo: auth.get_user('nonexistentuser', user_mgt_app) - assert excinfo.value.code == _user_mgt.USER_NOT_FOUND_ERROR + assert excinfo.value.code == exceptions.NOT_FOUND + assert str(excinfo.value) == 'No user record found for the provided user ID: nonexistentuser.' def test_get_user_http_error(self, user_mgt_app): - _instrument_user_manager(user_mgt_app, 500, '{"error":"test"}') - with pytest.raises(auth.AuthError) as excinfo: + _instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "USER_NOT_FOUND"}}') + with pytest.raises(auth.UserNotFoundError) as excinfo: auth.get_user('testuser', user_mgt_app) - assert excinfo.value.code == _user_mgt.INTERNAL_ERROR - assert '{"error":"test"}' in str(excinfo.value) + assert excinfo.value.code == exceptions.NOT_FOUND + assert str(excinfo.value) == 'No user record found for the given identifier (USER_NOT_FOUND).' + + def test_get_user_http_error_unexpected_code(self, user_mgt_app): + _instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "UNEXPECTED_CODE"}}') + with pytest.raises(exceptions.InternalError) as excinfo: + auth.get_user('testuser', user_mgt_app) + assert str(excinfo.value) == 'Error while calling Auth service (UNEXPECTED_CODE).' + + def test_get_user_http_error_malformed_response(self, user_mgt_app): + _instrument_user_manager(user_mgt_app, 500, '{"error": "UNEXPECTED_CODE"}') + with pytest.raises(exceptions.InternalError) as excinfo: + auth.get_user('testuser', user_mgt_app) + assert str(excinfo.value) == 'Unexpected error response: {"error": "UNEXPECTED_CODE"}' def test_get_user_by_email_http_error(self, user_mgt_app): - _instrument_user_manager(user_mgt_app, 500, '{"error":"test"}') - with pytest.raises(auth.AuthError) as excinfo: + _instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "USER_NOT_FOUND"}}') + with pytest.raises(auth.UserNotFoundError) as excinfo: auth.get_user_by_email('non.existent.user@example.com', user_mgt_app) - assert excinfo.value.code == _user_mgt.INTERNAL_ERROR - assert '{"error":"test"}' in str(excinfo.value) + assert excinfo.value.code == exceptions.NOT_FOUND + assert str(excinfo.value) == 'No user record found for the given identifier (USER_NOT_FOUND).' def test_get_user_by_phone_http_error(self, user_mgt_app): - _instrument_user_manager(user_mgt_app, 500, '{"error":"test"}') - with pytest.raises(auth.AuthError) as excinfo: + _instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "USER_NOT_FOUND"}}') + with pytest.raises(auth.UserNotFoundError) as excinfo: auth.get_user_by_phone_number('+1234567890', user_mgt_app) - assert excinfo.value.code == _user_mgt.INTERNAL_ERROR - assert '{"error":"test"}' in str(excinfo.value) + assert excinfo.value.code == exceptions.NOT_FOUND + assert str(excinfo.value) == 'No user record found for the given identifier (USER_NOT_FOUND).' class TestCreateUser(object): From 3354a137a41848d9ac1315e37141584a23714f75 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Fri, 12 Jul 2019 11:43:50 -0700 Subject: [PATCH 2/4] Fixed some lint errors --- integration/test_auth.py | 4 +++- tests/test_user_mgt.py | 12 ++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/integration/test_auth.py b/integration/test_auth.py index b5c95d1fc..3af7be288 100644 --- a/integration/test_auth.py +++ b/integration/test_auth.py @@ -143,7 +143,9 @@ def test_get_non_existing_user(): def test_get_non_existing_user_by_email(): with pytest.raises(auth.UserNotFoundError) as excinfo: auth.get_user_by_email('non.existing@definitely.non.existing') - assert str(excinfo.value) == 'No user record found for the provided email: non.existing@definitely.non.existing.' + error_msg = ('No user record found for the provided email: ' + 'non.existing@definitely.non.existing.') + assert str(excinfo.value) == error_msg def test_update_non_existing_user(): with pytest.raises(auth.AuthError) as excinfo: diff --git a/tests/test_user_mgt.py b/tests/test_user_mgt.py index 179ed3e86..0b12892bb 100644 --- a/tests/test_user_mgt.py +++ b/tests/test_user_mgt.py @@ -214,15 +214,17 @@ def test_get_user_non_existing(self, user_mgt_app): _instrument_user_manager(user_mgt_app, 200, '{"users":[]}') with pytest.raises(auth.UserNotFoundError) as excinfo: auth.get_user('nonexistentuser', user_mgt_app) + error_msg = 'No user record found for the provided user ID: nonexistentuser.' assert excinfo.value.code == exceptions.NOT_FOUND - assert str(excinfo.value) == 'No user record found for the provided user ID: nonexistentuser.' + assert str(excinfo.value) == error_msg def test_get_user_http_error(self, user_mgt_app): _instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "USER_NOT_FOUND"}}') with pytest.raises(auth.UserNotFoundError) as excinfo: auth.get_user('testuser', user_mgt_app) + error_msg = 'No user record found for the given identifier (USER_NOT_FOUND).' assert excinfo.value.code == exceptions.NOT_FOUND - assert str(excinfo.value) == 'No user record found for the given identifier (USER_NOT_FOUND).' + assert str(excinfo.value) == error_msg def test_get_user_http_error_unexpected_code(self, user_mgt_app): _instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "UNEXPECTED_CODE"}}') @@ -240,15 +242,17 @@ def test_get_user_by_email_http_error(self, user_mgt_app): _instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "USER_NOT_FOUND"}}') with pytest.raises(auth.UserNotFoundError) as excinfo: auth.get_user_by_email('non.existent.user@example.com', user_mgt_app) + error_msg = 'No user record found for the given identifier (USER_NOT_FOUND).' assert excinfo.value.code == exceptions.NOT_FOUND - assert str(excinfo.value) == 'No user record found for the given identifier (USER_NOT_FOUND).' + assert str(excinfo.value) == error_msg def test_get_user_by_phone_http_error(self, user_mgt_app): _instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "USER_NOT_FOUND"}}') with pytest.raises(auth.UserNotFoundError) as excinfo: auth.get_user_by_phone_number('+1234567890', user_mgt_app) + error_msg = 'No user record found for the given identifier (USER_NOT_FOUND).' assert excinfo.value.code == exceptions.NOT_FOUND - assert str(excinfo.value) == 'No user record found for the given identifier (USER_NOT_FOUND).' + assert str(excinfo.value) == error_msg class TestCreateUser(object): From 32509780ce8eab26cf60ffd3d64109018747e0f8 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Fri, 12 Jul 2019 13:42:51 -0700 Subject: [PATCH 3/4] Some formatting updates --- firebase_admin/_auth_utils.py | 2 +- firebase_admin/_user_mgt.py | 3 +++ tests/test_user_mgt.py | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/firebase_admin/_auth_utils.py b/firebase_admin/_auth_utils.py index ae94627f3..7e992db06 100644 --- a/firebase_admin/_auth_utils.py +++ b/firebase_admin/_auth_utils.py @@ -210,7 +210,7 @@ def __init__(self, message, cause=None, http_response=None): class UserNotFoundError(exceptions.NotFoundError): - """Failed to find a user with the specified details.""" + """No user record found for the specified identifier.""" default_message = 'No user record found for the given identifier' diff --git a/firebase_admin/_user_mgt.py b/firebase_admin/_user_mgt.py index e0997c4c2..a217d108c 100644 --- a/firebase_admin/_user_mgt.py +++ b/firebase_admin/_user_mgt.py @@ -379,6 +379,7 @@ def photo_url(self): def provider_id(self): return self._data.get('providerId') + class ActionCodeSettings(object): """Contains required continue/state URL with optional Android and iOS settings. Used when invoking the email action link generation APIs. @@ -394,6 +395,7 @@ def __init__(self, url, handle_code_in_app=None, dynamic_link_domain=None, ios_b self.android_install_app = android_install_app self.android_minimum_version = android_minimum_version + def encode_action_code_settings(settings): """ Validates the provided action code settings for email link generation and populates the REST api parameters. @@ -461,6 +463,7 @@ def encode_action_code_settings(settings): return parameters + class UserManager(object): """Provides methods for interacting with the Google Identity Toolkit.""" diff --git a/tests/test_user_mgt.py b/tests/test_user_mgt.py index 0b12892bb..b3759c733 100644 --- a/tests/test_user_mgt.py +++ b/tests/test_user_mgt.py @@ -736,6 +736,7 @@ def test_invalid_args(self, arg): with pytest.raises(ValueError): auth.UserMetadata(**arg) + class TestImportUserRecord(object): _INVALID_USERS = ( @@ -1021,6 +1022,7 @@ def test_revoke_refresh_tokens(self, user_mgt_app): assert int(request['validSince']) >= int(before_time) assert int(request['validSince']) <= int(after_time) + class TestActionCodeSetting(object): def test_valid_data(self): @@ -1065,6 +1067,7 @@ def test_encode_action_code_bad_data(self): with pytest.raises(AttributeError): _user_mgt.encode_action_code_settings({"foo":"bar"}) + class TestGenerateEmailActionLink(object): def test_email_verification_no_settings(self, user_mgt_app): From c124f102cf220dbe467a756392df6935a6ba1559 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Fri, 12 Jul 2019 14:05:48 -0700 Subject: [PATCH 4/4] Updated docs and tests --- firebase_admin/auth.py | 33 ++++++++++++--------------------- tests/test_user_mgt.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/firebase_admin/auth.py b/firebase_admin/auth.py index 806b1d6aa..f654eae42 100644 --- a/firebase_admin/auth.py +++ b/firebase_admin/auth.py @@ -234,15 +234,12 @@ def get_user(uid, app=None): Raises: ValueError: If the user ID is None, empty or malformed. - AuthError: If an error occurs while retrieving the user or if the specified user ID - does not exist. + UserNotFoundError: If the specified user ID does not exist. + FirebaseError: If an error occurs while retrieving the user. """ user_manager = _get_auth_service(app).user_manager - try: - response = user_manager.get_user(uid=uid) - return UserRecord(response) - except _user_mgt.ApiCallError as error: - raise AuthError(error.code, str(error), error.detail) + response = user_manager.get_user(uid=uid) + return UserRecord(response) def get_user_by_email(email, app=None): @@ -257,15 +254,12 @@ def get_user_by_email(email, app=None): Raises: ValueError: If the email is None, empty or malformed. - AuthError: If an error occurs while retrieving the user or no user exists by the specified - email address. + UserNotFoundError: If no user exists by the specified email address. + FirebaseError: If an error occurs while retrieving the user. """ user_manager = _get_auth_service(app).user_manager - try: - response = user_manager.get_user(email=email) - return UserRecord(response) - except _user_mgt.ApiCallError as error: - raise AuthError(error.code, str(error), error.detail) + response = user_manager.get_user(email=email) + return UserRecord(response) def get_user_by_phone_number(phone_number, app=None): @@ -280,15 +274,12 @@ def get_user_by_phone_number(phone_number, app=None): Raises: ValueError: If the phone number is None, empty or malformed. - AuthError: If an error occurs while retrieving the user or no user exists by the specified - phone number. + UserNotFoundError: If no user exists by the specified phone number. + FirebaseError: If an error occurs while retrieving the user. """ user_manager = _get_auth_service(app).user_manager - try: - response = user_manager.get_user(phone_number=phone_number) - return UserRecord(response) - except _user_mgt.ApiCallError as error: - raise AuthError(error.code, str(error), error.detail) + response = user_manager.get_user(phone_number=phone_number) + return UserRecord(response) def list_users(page_token=None, max_results=_user_mgt.MAX_LIST_USERS_RESULTS, app=None): diff --git a/tests/test_user_mgt.py b/tests/test_user_mgt.py index b3759c733..951205621 100644 --- a/tests/test_user_mgt.py +++ b/tests/test_user_mgt.py @@ -217,6 +217,28 @@ def test_get_user_non_existing(self, user_mgt_app): error_msg = 'No user record found for the provided user ID: nonexistentuser.' assert excinfo.value.code == exceptions.NOT_FOUND assert str(excinfo.value) == error_msg + assert excinfo.value.http_response is not None + assert excinfo.value.cause is None + + def test_get_user_by_email_non_existing(self, user_mgt_app): + _instrument_user_manager(user_mgt_app, 200, '{"users":[]}') + with pytest.raises(auth.UserNotFoundError) as excinfo: + auth.get_user_by_email('nonexistent@user', user_mgt_app) + error_msg = 'No user record found for the provided email: nonexistent@user.' + assert excinfo.value.code == exceptions.NOT_FOUND + assert str(excinfo.value) == error_msg + assert excinfo.value.http_response is not None + assert excinfo.value.cause is None + + def test_get_user_by_phone_non_existing(self, user_mgt_app): + _instrument_user_manager(user_mgt_app, 200, '{"users":[]}') + with pytest.raises(auth.UserNotFoundError) as excinfo: + auth.get_user_by_phone_number('+1234567890', user_mgt_app) + error_msg = 'No user record found for the provided phone number: +1234567890.' + assert excinfo.value.code == exceptions.NOT_FOUND + assert str(excinfo.value) == error_msg + assert excinfo.value.http_response is not None + assert excinfo.value.cause is None def test_get_user_http_error(self, user_mgt_app): _instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "USER_NOT_FOUND"}}') @@ -225,18 +247,24 @@ def test_get_user_http_error(self, user_mgt_app): error_msg = 'No user record found for the given identifier (USER_NOT_FOUND).' assert excinfo.value.code == exceptions.NOT_FOUND assert str(excinfo.value) == error_msg + assert excinfo.value.http_response is not None + assert excinfo.value.cause is not None def test_get_user_http_error_unexpected_code(self, user_mgt_app): _instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "UNEXPECTED_CODE"}}') with pytest.raises(exceptions.InternalError) as excinfo: auth.get_user('testuser', user_mgt_app) assert str(excinfo.value) == 'Error while calling Auth service (UNEXPECTED_CODE).' + assert excinfo.value.http_response is not None + assert excinfo.value.cause is not None def test_get_user_http_error_malformed_response(self, user_mgt_app): _instrument_user_manager(user_mgt_app, 500, '{"error": "UNEXPECTED_CODE"}') with pytest.raises(exceptions.InternalError) as excinfo: auth.get_user('testuser', user_mgt_app) assert str(excinfo.value) == 'Unexpected error response: {"error": "UNEXPECTED_CODE"}' + assert excinfo.value.http_response is not None + assert excinfo.value.cause is not None def test_get_user_by_email_http_error(self, user_mgt_app): _instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "USER_NOT_FOUND"}}') @@ -245,6 +273,8 @@ def test_get_user_by_email_http_error(self, user_mgt_app): error_msg = 'No user record found for the given identifier (USER_NOT_FOUND).' assert excinfo.value.code == exceptions.NOT_FOUND assert str(excinfo.value) == error_msg + assert excinfo.value.http_response is not None + assert excinfo.value.cause is not None def test_get_user_by_phone_http_error(self, user_mgt_app): _instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "USER_NOT_FOUND"}}') @@ -253,6 +283,8 @@ def test_get_user_by_phone_http_error(self, user_mgt_app): error_msg = 'No user record found for the given identifier (USER_NOT_FOUND).' assert excinfo.value.code == exceptions.NOT_FOUND assert str(excinfo.value) == error_msg + assert excinfo.value.http_response is not None + assert excinfo.value.cause is not None class TestCreateUser(object):