Skip to content

Commit 9fb0766

Browse files
authored
Introducing UserNotFoundError type (#309)
* Added UserNotFoundError type * Fixed some lint errors * Some formatting updates * Updated docs and tests
1 parent 99929ed commit 9fb0766

File tree

5 files changed

+110
-50
lines changed

5 files changed

+110
-50
lines changed

firebase_admin/_auth_utils.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,18 @@ def __init__(self, message, cause=None, http_response=None):
209209
exceptions.UnknownError.__init__(self, message, cause, http_response)
210210

211211

212+
class UserNotFoundError(exceptions.NotFoundError):
213+
"""No user record found for the specified identifier."""
214+
215+
default_message = 'No user record found for the given identifier'
216+
217+
def __init__(self, message, cause=None, http_response=None):
218+
exceptions.NotFoundError.__init__(self, message, cause, http_response)
219+
220+
212221
_CODE_TO_EXC_TYPE = {
213222
'INVALID_ID_TOKEN': InvalidIdTokenError,
223+
'USER_NOT_FOUND': UserNotFoundError,
214224
}
215225

216226

@@ -243,7 +253,7 @@ def _parse_error_body(response):
243253
pass
244254

245255
# Auth error response format: {"error": {"message": "AUTH_ERROR_CODE: Optional text"}}
246-
code = error_dict.get('message')
256+
code = error_dict.get('message') if isinstance(error_dict, dict) else None
247257
custom_message = None
248258
if code:
249259
separator = code.find(':')

firebase_admin/_user_mgt.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
from firebase_admin import _user_import
2525

2626

27-
INTERNAL_ERROR = 'INTERNAL_ERROR'
28-
USER_NOT_FOUND_ERROR = 'USER_NOT_FOUND_ERROR'
2927
USER_CREATE_ERROR = 'USER_CREATE_ERROR'
3028
USER_UPDATE_ERROR = 'USER_UPDATE_ERROR'
3129
USER_DELETE_ERROR = 'USER_DELETE_ERROR'
@@ -381,6 +379,7 @@ def photo_url(self):
381379
def provider_id(self):
382380
return self._data.get('providerId')
383381

382+
384383
class ActionCodeSettings(object):
385384
"""Contains required continue/state URL with optional Android and iOS settings.
386385
Used when invoking the email action link generation APIs.
@@ -396,6 +395,7 @@ def __init__(self, url, handle_code_in_app=None, dynamic_link_domain=None, ios_b
396395
self.android_install_app = android_install_app
397396
self.android_minimum_version = android_minimum_version
398397

398+
399399
def encode_action_code_settings(settings):
400400
""" Validates the provided action code settings for email link generation and
401401
populates the REST api parameters.
@@ -463,6 +463,7 @@ def encode_action_code_settings(settings):
463463

464464
return parameters
465465

466+
466467
class UserManager(object):
467468
"""Provides methods for interacting with the Google Identity Toolkit."""
468469

@@ -484,16 +485,16 @@ def get_user(self, **kwargs):
484485
raise TypeError('Unsupported keyword arguments: {0}.'.format(kwargs))
485486

486487
try:
487-
response = self._client.body('post', '/accounts:lookup', json=payload)
488+
body, http_resp = self._client.body_and_response(
489+
'post', '/accounts:lookup', json=payload)
488490
except requests.exceptions.RequestException as error:
489-
msg = 'Failed to get user by {0}: {1}.'.format(key_type, key)
490-
self._handle_http_error(INTERNAL_ERROR, msg, error)
491+
raise _auth_utils.handle_auth_backend_error(error)
491492
else:
492-
if not response or not response.get('users'):
493-
raise ApiCallError(
494-
USER_NOT_FOUND_ERROR,
495-
'No user record found for the provided {0}: {1}.'.format(key_type, key))
496-
return response['users'][0]
493+
if not body or not body.get('users'):
494+
raise _auth_utils.UserNotFoundError(
495+
'No user record found for the provided {0}: {1}.'.format(key_type, key),
496+
http_response=http_resp)
497+
return body['users'][0]
497498

498499
def list_users(self, page_token=None, max_results=MAX_LIST_USERS_RESULTS):
499500
"""Retrieves a batch of users."""

firebase_admin/auth.py

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
'UserImportResult',
4949
'UserInfo',
5050
'UserMetadata',
51+
'UserNotFoundError',
5152
'UserProvider',
5253
'UserRecord',
5354

@@ -83,6 +84,7 @@
8384
UserImportResult = _user_import.UserImportResult
8485
UserInfo = _user_mgt.UserInfo
8586
UserMetadata = _user_mgt.UserMetadata
87+
UserNotFoundError = _auth_utils.UserNotFoundError
8688
UserProvider = _user_import.UserProvider
8789
UserRecord = _user_mgt.UserRecord
8890

@@ -232,15 +234,12 @@ def get_user(uid, app=None):
232234
233235
Raises:
234236
ValueError: If the user ID is None, empty or malformed.
235-
AuthError: If an error occurs while retrieving the user or if the specified user ID
236-
does not exist.
237+
UserNotFoundError: If the specified user ID does not exist.
238+
FirebaseError: If an error occurs while retrieving the user.
237239
"""
238240
user_manager = _get_auth_service(app).user_manager
239-
try:
240-
response = user_manager.get_user(uid=uid)
241-
return UserRecord(response)
242-
except _user_mgt.ApiCallError as error:
243-
raise AuthError(error.code, str(error), error.detail)
241+
response = user_manager.get_user(uid=uid)
242+
return UserRecord(response)
244243

245244

246245
def get_user_by_email(email, app=None):
@@ -255,15 +254,12 @@ def get_user_by_email(email, app=None):
255254
256255
Raises:
257256
ValueError: If the email is None, empty or malformed.
258-
AuthError: If an error occurs while retrieving the user or no user exists by the specified
259-
email address.
257+
UserNotFoundError: If no user exists by the specified email address.
258+
FirebaseError: If an error occurs while retrieving the user.
260259
"""
261260
user_manager = _get_auth_service(app).user_manager
262-
try:
263-
response = user_manager.get_user(email=email)
264-
return UserRecord(response)
265-
except _user_mgt.ApiCallError as error:
266-
raise AuthError(error.code, str(error), error.detail)
261+
response = user_manager.get_user(email=email)
262+
return UserRecord(response)
267263

268264

269265
def get_user_by_phone_number(phone_number, app=None):
@@ -278,15 +274,12 @@ def get_user_by_phone_number(phone_number, app=None):
278274
279275
Raises:
280276
ValueError: If the phone number is None, empty or malformed.
281-
AuthError: If an error occurs while retrieving the user or no user exists by the specified
282-
phone number.
277+
UserNotFoundError: If no user exists by the specified phone number.
278+
FirebaseError: If an error occurs while retrieving the user.
283279
"""
284280
user_manager = _get_auth_service(app).user_manager
285-
try:
286-
response = user_manager.get_user(phone_number=phone_number)
287-
return UserRecord(response)
288-
except _user_mgt.ApiCallError as error:
289-
raise AuthError(error.code, str(error), error.detail)
281+
response = user_manager.get_user(phone_number=phone_number)
282+
return UserRecord(response)
290283

291284

292285
def list_users(page_token=None, max_results=_user_mgt.MAX_LIST_USERS_RESULTS, app=None):

integration/test_auth.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import google.oauth2.credentials
3030
from google.auth import transport
3131

32+
3233
_verify_token_url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken'
3334
_verify_password_url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword'
3435
_password_reset_url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/resetPassword'
@@ -135,14 +136,16 @@ def test_session_cookie_error():
135136
auth.create_session_cookie('not.a.token', expires_in=expires_in)
136137

137138
def test_get_non_existing_user():
138-
with pytest.raises(auth.AuthError) as excinfo:
139+
with pytest.raises(auth.UserNotFoundError) as excinfo:
139140
auth.get_user('non.existing')
140-
assert 'USER_NOT_FOUND_ERROR' in str(excinfo.value.code)
141+
assert str(excinfo.value) == 'No user record found for the provided user ID: non.existing.'
141142

142143
def test_get_non_existing_user_by_email():
143-
with pytest.raises(auth.AuthError) as excinfo:
144+
with pytest.raises(auth.UserNotFoundError) as excinfo:
144145
auth.get_user_by_email('[email protected]')
145-
assert 'USER_NOT_FOUND_ERROR' in str(excinfo.value.code)
146+
error_msg = ('No user record found for the provided email: '
147+
148+
assert str(excinfo.value) == error_msg
146149

147150
def test_update_non_existing_user():
148151
with pytest.raises(auth.AuthError) as excinfo:

tests/test_user_mgt.py

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import firebase_admin
2323
from firebase_admin import auth
24+
from firebase_admin import exceptions
2425
from firebase_admin import _auth_utils
2526
from firebase_admin import _user_import
2627
from firebase_admin import _user_mgt
@@ -211,30 +212,79 @@ def test_get_user_by_phone(self, user_mgt_app):
211212

212213
def test_get_user_non_existing(self, user_mgt_app):
213214
_instrument_user_manager(user_mgt_app, 200, '{"users":[]}')
214-
with pytest.raises(auth.AuthError) as excinfo:
215+
with pytest.raises(auth.UserNotFoundError) as excinfo:
215216
auth.get_user('nonexistentuser', user_mgt_app)
216-
assert excinfo.value.code == _user_mgt.USER_NOT_FOUND_ERROR
217+
error_msg = 'No user record found for the provided user ID: nonexistentuser.'
218+
assert excinfo.value.code == exceptions.NOT_FOUND
219+
assert str(excinfo.value) == error_msg
220+
assert excinfo.value.http_response is not None
221+
assert excinfo.value.cause is None
222+
223+
def test_get_user_by_email_non_existing(self, user_mgt_app):
224+
_instrument_user_manager(user_mgt_app, 200, '{"users":[]}')
225+
with pytest.raises(auth.UserNotFoundError) as excinfo:
226+
auth.get_user_by_email('nonexistent@user', user_mgt_app)
227+
error_msg = 'No user record found for the provided email: nonexistent@user.'
228+
assert excinfo.value.code == exceptions.NOT_FOUND
229+
assert str(excinfo.value) == error_msg
230+
assert excinfo.value.http_response is not None
231+
assert excinfo.value.cause is None
232+
233+
def test_get_user_by_phone_non_existing(self, user_mgt_app):
234+
_instrument_user_manager(user_mgt_app, 200, '{"users":[]}')
235+
with pytest.raises(auth.UserNotFoundError) as excinfo:
236+
auth.get_user_by_phone_number('+1234567890', user_mgt_app)
237+
error_msg = 'No user record found for the provided phone number: +1234567890.'
238+
assert excinfo.value.code == exceptions.NOT_FOUND
239+
assert str(excinfo.value) == error_msg
240+
assert excinfo.value.http_response is not None
241+
assert excinfo.value.cause is None
217242

218243
def test_get_user_http_error(self, user_mgt_app):
219-
_instrument_user_manager(user_mgt_app, 500, '{"error":"test"}')
220-
with pytest.raises(auth.AuthError) as excinfo:
244+
_instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "USER_NOT_FOUND"}}')
245+
with pytest.raises(auth.UserNotFoundError) as excinfo:
221246
auth.get_user('testuser', user_mgt_app)
222-
assert excinfo.value.code == _user_mgt.INTERNAL_ERROR
223-
assert '{"error":"test"}' in str(excinfo.value)
247+
error_msg = 'No user record found for the given identifier (USER_NOT_FOUND).'
248+
assert excinfo.value.code == exceptions.NOT_FOUND
249+
assert str(excinfo.value) == error_msg
250+
assert excinfo.value.http_response is not None
251+
assert excinfo.value.cause is not None
252+
253+
def test_get_user_http_error_unexpected_code(self, user_mgt_app):
254+
_instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "UNEXPECTED_CODE"}}')
255+
with pytest.raises(exceptions.InternalError) as excinfo:
256+
auth.get_user('testuser', user_mgt_app)
257+
assert str(excinfo.value) == 'Error while calling Auth service (UNEXPECTED_CODE).'
258+
assert excinfo.value.http_response is not None
259+
assert excinfo.value.cause is not None
260+
261+
def test_get_user_http_error_malformed_response(self, user_mgt_app):
262+
_instrument_user_manager(user_mgt_app, 500, '{"error": "UNEXPECTED_CODE"}')
263+
with pytest.raises(exceptions.InternalError) as excinfo:
264+
auth.get_user('testuser', user_mgt_app)
265+
assert str(excinfo.value) == 'Unexpected error response: {"error": "UNEXPECTED_CODE"}'
266+
assert excinfo.value.http_response is not None
267+
assert excinfo.value.cause is not None
224268

225269
def test_get_user_by_email_http_error(self, user_mgt_app):
226-
_instrument_user_manager(user_mgt_app, 500, '{"error":"test"}')
227-
with pytest.raises(auth.AuthError) as excinfo:
270+
_instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "USER_NOT_FOUND"}}')
271+
with pytest.raises(auth.UserNotFoundError) as excinfo:
228272
auth.get_user_by_email('[email protected]', user_mgt_app)
229-
assert excinfo.value.code == _user_mgt.INTERNAL_ERROR
230-
assert '{"error":"test"}' in str(excinfo.value)
273+
error_msg = 'No user record found for the given identifier (USER_NOT_FOUND).'
274+
assert excinfo.value.code == exceptions.NOT_FOUND
275+
assert str(excinfo.value) == error_msg
276+
assert excinfo.value.http_response is not None
277+
assert excinfo.value.cause is not None
231278

232279
def test_get_user_by_phone_http_error(self, user_mgt_app):
233-
_instrument_user_manager(user_mgt_app, 500, '{"error":"test"}')
234-
with pytest.raises(auth.AuthError) as excinfo:
280+
_instrument_user_manager(user_mgt_app, 500, '{"error":{"message": "USER_NOT_FOUND"}}')
281+
with pytest.raises(auth.UserNotFoundError) as excinfo:
235282
auth.get_user_by_phone_number('+1234567890', user_mgt_app)
236-
assert excinfo.value.code == _user_mgt.INTERNAL_ERROR
237-
assert '{"error":"test"}' in str(excinfo.value)
283+
error_msg = 'No user record found for the given identifier (USER_NOT_FOUND).'
284+
assert excinfo.value.code == exceptions.NOT_FOUND
285+
assert str(excinfo.value) == error_msg
286+
assert excinfo.value.http_response is not None
287+
assert excinfo.value.cause is not None
238288

239289

240290
class TestCreateUser(object):
@@ -718,6 +768,7 @@ def test_invalid_args(self, arg):
718768
with pytest.raises(ValueError):
719769
auth.UserMetadata(**arg)
720770

771+
721772
class TestImportUserRecord(object):
722773

723774
_INVALID_USERS = (
@@ -1003,6 +1054,7 @@ def test_revoke_refresh_tokens(self, user_mgt_app):
10031054
assert int(request['validSince']) >= int(before_time)
10041055
assert int(request['validSince']) <= int(after_time)
10051056

1057+
10061058
class TestActionCodeSetting(object):
10071059

10081060
def test_valid_data(self):
@@ -1047,6 +1099,7 @@ def test_encode_action_code_bad_data(self):
10471099
with pytest.raises(AttributeError):
10481100
_user_mgt.encode_action_code_settings({"foo":"bar"})
10491101

1102+
10501103
class TestGenerateEmailActionLink(object):
10511104

10521105
def test_email_verification_no_settings(self, user_mgt_app):

0 commit comments

Comments
 (0)