8000 Migrated user_mgt module to the new error handling regime · brianrodri/firebase-admin-python@f2a1cd4 · GitHub
[go: up one dir, main page]

Skip to content

Commit f2a1cd4

Browse files
committed
Migrated user_mgt module to the new error handling regime
1 parent 6b5b485 commit f2a1cd4

File tree

5 files changed

+191
-164
lines changed

5 files changed

+191
-164
lines changed

firebase_admin/_http_client.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,13 @@ def headers(self, method, url, **kwargs):
109109
resp = self.request(method, url, **kwargs)
110110
return resp.headers
111111

112-
def body(self, method, url, **kwargs):
112+
def body_and_response(self, method, url, **kwargs):
113113
resp = self.request(method, url, **kwargs)
114-
return self.parse_body(resp)
114+
return self.parse_body(resp), resp
115+
116+
def body(self, method, url, **kwargs):
117+
body, _ = self.body_and_response(method, url, **kwargs)
118+
return body
115119

116120
def headers_and_body(self, method, url, **kwargs):
117121
resp = self.request(method, url, **kwargs)

firebase_admin/_user_mgt.py

Lines changed: 80 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,13 @@
2020
import six
2121
from six.moves import urllib
2222

23+
from firebase_admin import exceptions
2324
from firebase_admin import _auth_utils
2425
from firebase_admin import _user_import
2526

2627

27-
INTERNAL_ERROR = 'INTERNAL_ERROR'
28-
USER_NOT_FOUND_ERROR = 'USER_NOT_FOUND_ERROR'
29-
USER_CREATE_ERROR = 'USER_CREATE_ERROR'
30-
USER_UPDATE_ERROR = 'USER_UPDATE_ERROR'
31-
USER_DELETE_ERROR = 'USER_DELETE_ERROR'
32-
USER_IMPORT_ERROR = 'USER_IMPORT_ERROR'
33-
USER_DOWNLOAD_ERROR = 'LIST_USERS_ERROR'
34-
GENERATE_EMAIL_ACTION_LINK_ERROR = 'GENERATE_EMAIL_ACTION_LINK_ERROR'
28+
USER_NOT_FOUND = 'USER_NOT_FOUND'
29+
UNEXPECTED_RESPONSE = 'UNEXPECTED_RESPONSE'
3530

3631
MAX_LIST_USERS_RESULTS = 1000
3732
MAX_IMPORT_USERS_SIZE = 1000
@@ -43,15 +38,6 @@ class _Unspecified(object):
4338
_UNSPECIFIED = _Unspecified()
4439

4540

46-
class ApiCallError(Exception):
47-
"""Represents an Exception encountered while invoking the Firebase user management API."""
48-
49-
def __init__(self, code, message, error=None):
50-
Exception.__init__(self, message)
51-
self.code = code
52-
self.detail = error
53-
54-
5541
class UserMetadata(object):
5642
"""Contains additional metadata associated with a user account."""
5743

@@ -374,6 +360,7 @@ def photo_url(self):
374360
def provider_id(self):
375361
return self._data.get('providerId')
376362

363+
377364
class ActionCodeSettings(object):
378365
"""Contains required continue/state URL with optional Android and iOS settings.
379366
Used when invoking the email action link generation APIs.
@@ -389,6 +376,7 @@ def __init__(self, url, handle_code_in_app=None, dynamic_link_domain=None, ios_b
389376
self.android_install_app = android_install_app
390377
self.android_minimum_version = android_minimum_version
391378

379+
392380
def encode_action_code_settings(settings):
393381
""" Validates the provided action code settings for email link generation and
394382
populates the REST api parameters.
@@ -456,6 +444,7 @@ def encode_action_code_settings(settings):
456444

457445
return parameters
458446

447+
459448
class UserManager(object):
460449
"""Provides methods for interacting with the Google Identity Toolkit."""
461450

@@ -477,16 +466,18 @@ def get_user(self, **kwargs):
477466
raise TypeError('Unsupported keyword arguments: {0}.'.format(kwargs))
478467

479468
try:
480-
response = self._client.body('post', '/accounts:lookup', json=payload)
469+
body, response = self._client.body_and_response('post', '/accounts:lookup', json=payload)
481470
except requests.exceptions.RequestException as error:
482471
msg = 'Failed to get user by {0}: {1}.'.format(key_type, key)
483-
self._handle_http_error(INTERNAL_ERROR, msg, error)
472+
self._handle_http_error(msg, error)
484473
else:
485-
if not response or not response.get('users'):
486-
raise ApiCallError(
487-
USER_NOT_FOUND_ERROR,
488-
'No user record found for the provided {0}: {1}.'.format(key_type, key))
489-
return response['users'][0]
474+
if not body or not body.get('users'):
475+
raise _auth_utils.FirebaseAuthError(
476+
exceptions.NOT_FOUND,
477+
'No user record found for the provided {0}: {1}.'.format(key_type, key),
478+
http_response=response,
479+
auth_error_code=USER_NOT_FOUND)
480+
return body['users'][0]
490481

491482
def list_users(self, page_token=None, max_results=MAX_LIST_USERS_RESULTS):
492483
"""Retrieves a batch of users."""
@@ -506,7 +497,7 @@ def list_users(self, page_token=None, max_results=MAX_LIST_USERS_RESULTS):
506497
try:
507498
return self._client.body('get', '/accounts:batchGet', params=payload)
508499
except requests.exceptions.RequestException as error:
509-
self._handle_http_error(USER_DOWNLOAD_ERROR, 'Failed to download user accounts.', error)
500+
self._handle_http_error('Failed to download user accounts.', error)
510501

511502
def create_user(self, uid=None, display_name=None, email=None, phone_number=None,
512503
photo_url=None, password=None, disabled=None, email_verified=None):
@@ -523,13 +514,17 @@ def create_user(self, uid=None, display_name=None, email=None, phone_number=None
523514
}
524515
payload = {k: v for k, v in payload.items() if v is not None}
525516
try:
526-
response = self._client.body('post', '/accounts', json=payload)
517+
body, response = self._client.body_and_response('post', '/accounts', json=payload)
527518
except requests.exceptions.RequestException as error:
528-
self._handle_http_error(USER_CREATE_ERROR, 'Failed to create new user.', error)
519+
self._handle_http_error('Failed to create new user.', error)
529520
else:
530-
if not response or not response.get('localId'):
531-
raise ApiCallError(USER_CREATE_ERROR, 'Failed to create new user.')
532-
return response.get('localId')
521+
if not body or not body.get('localId'):
522+
raise _auth_utils.FirebaseAuthError(
523+
exceptions.UNKNOWN,
524+
'Failed to create new user.',
525+
http_response=response,
526+
auth_error_code=UNEXPECTED_RESPONSE)
527+
return body.get('localId')
533528

534529
def update_user(self, uid, display_name=_UNSPECIFIED, email=None, phone_number=_UNSPECIFIED,
535530
photo_url=_UNSPECIFIED, password=None, disabled=None, email_verified=None,
@@ -573,26 +568,32 @@ def update_user(self, uid, display_name=_UNSPECIFIED, email=None, phone_number=_
573568

574569
payload = {k: v for k, v in payload.items() if v is not None}
575570
try:
576-
response = self._client.body('post', '/accounts:update', json=payload)
571+
body, response = self._client.body_and_response('post', '/accounts:update', json=payload)
577572
except requests.exceptions.RequestException as error:
578-
self._handle_http_error(
579-
USER_UPDATE_ERROR, 'Failed to update user: {0}.'.format(uid), error)
573+
self._handle_http_error('Failed to update user: {0}.'.format(uid), error)
580574
else:
581-
if not response or not response.get('localId'):
582-
raise ApiCallError(USER_UPDATE_ERROR, 'Failed to update user: {0}.'.format(uid))
583-
return response.get('localId')
575+
if not body or not body.get('localId'):
576+
raise _auth_utils.FirebaseAuthError(
577+
exceptions.UNKNOWN,
578+
'Failed to update user: {0}.'.format(uid),
579+
http_response=response,
580+
auth_error_code=UNEXPECTED_RESPONSE)
581+
return body.get('localId')
584582

585583
def delete_user(self, uid):
586584
"""Deletes the user identified by the specified user ID."""
587585
_auth_utils.validate_uid(uid, required=True)
588586
try:
589-
response = self._client.body('post', '/accounts:delete', json={'localId' : uid})
587+
body, response = self._client.body_and_response('post', '/accounts:delete', json={'localId' : uid})
590588
except requests.exceptions.RequestException as error:
591-
self._handle_http_error(
592-
USER_DELETE_ERROR, 'Failed to delete user: {0}.'.format(uid), error)
589+
self._handle_http_error('Failed to delete user: {0}.'.format(uid), error)
593590
else:
594-
if not response or not response.get('kind'):
595-
raise ApiCallError(USER_DELETE_ERROR, 'Failed to delete user: {0}.'.format(uid))
591+
if not body or not body.get('kind'):
592+
raise _auth_utils.FirebaseAuthError(
593+
exceptions.UNKNOWN,
594+
'Failed to delete user: {0}.'.format(uid),
595+
http_response=response,
596+
auth_error_code=UNEXPECTED_RESPONSE)
596597

597598
def import_users(self, users, hash_alg=None):
598599
"""Imports the given list of users to Firebase Auth."""
@@ -612,13 +613,17 @@ def import_users(self, users, hash_alg=None):
612613
raise ValueError('A UserImportHash is required to import users with passwords.')
613614
payload.update(hash_alg.to_dict())
614615
try:
615-
response = self._client.body('post', '/accounts:batchCreate', json=payload)
616+
body, response = self._client.body_and_response('post', '/accounts:batchCreate', json=payload)
616617
except requests.exceptions.RequestException as error:
617-
self._handle_http_error(USER_IMPORT_ERROR, 'Failed to import users.', error)
618+
self._handle_http_error('Failed to import users.', error)
618619
else:
619-
if not isinstance(response, dict):
620-
raise ApiCallError(USER_IMPORT_ERROR, 'Failed to import users.')
621-
return response
620+
if not isinstance(body, dict):
621+
raise _auth_utils.FirebaseAuthError(
622+
exceptions.UNKNOWN,
623+
'Failed to import users.',
624+
http_response=response,
625+
auth_error_code=UNEXPECTED_RESPONSE)
626+
return body
622627

623628
def generate_email_action_link(self, action_type, email, action_code_settings=None):
624629
"""Fetches the email action links for types
@@ -633,7 +638,7 @@ def generate_email_action_link(self, action_type, email, action_code_settings=No
633638
link_url: action url to be emailed to the user
634639
635640
Raises:
636-
ApiCallError: If an error occurs while generating the link
641+
auth.FirebaseAuthError: If an error occurs while generating the link
637642
ValueError: If the provided arguments are invalid
638643
"""
639644
payload = {
@@ -646,21 +651,37 @@ def generate_email_action_link(self, action_type, email, action_code_settings=No
646651
payload.update(encode_action_code_settings(action_code_settings))
647652

648653
try:
649-
response = self._client.body('post', '/accounts:sendOobCode', json=payload)
654+
body, response = self._client.body_and_response('post', '/accounts:sendOobCode', json=payload)
650655
except requests.exceptions.RequestException as error:
651-
self._handle_http_error(GENERATE_EMAIL_ACTION_LINK_ERROR, 'Failed to generate link.',
652-
error)
656+
self._handle_http_error('Failed to generate link.', error)
653657
else:
654-
if not response or not response.get('oobLink'):
655-
raise ApiCallError(GENERATE_EMAIL_ACTION_LINK_ERROR, 'Failed to generate link.')
656-
return response.get('oobLink')
657-
658-
def _handle_http_error(self, code, msg, error):
658+
if not body or not body.get('oobLink'):
659+
raise _auth_utils.FirebaseAuthError(
660+
exceptions.UNKNOWN,
661+
'Failed to generate link.',
662+
http_response=response,
663+
auth_error_code=UNEXPECTED_RESPONSE)
664+
return body.get('oobLink')
665+
666+
_ERROR_CODE_MAPPINGS = {
667+
'CLAIMS_TOO_LARGE': exceptions.INVALID_ARGUMENT,
668+
'INVALID_EMAIL': exceptions.INVALID_ARGUMENT,
669+
'INSUFFICIENT_PERMISSION': exceptions.PERMISSION_DENIED,
670+
'OPERATION_NOT_ALLOWED': exceptions.PERMISSION_DENIED,
671+
'PERMISSION_DENIED': exceptions.PERMISSION_DENIED,
672+
'USER_NOT_FOUND': exceptions.NOT_FOUND,
673+
'DUPLICATE_EMAIL': exceptions.ALREADY_EXISTS,
674+
}
675+
676+
def _handle_http_error(self, msg, error):
677+
response_payload = {}
659678
if error.response is not None:
660-
msg += '\nServer response: {0}'.format(error.response.content.decode())
661-
else:
662-
msg += '\nReason: {0}'.format(error)
663-
raise ApiCallError(code, msg, error)
679+
response_payload = error.response.json()
680+
msg += '\n Server response: {0}'.format(error.response.content.decode())
681+
server_code = response_payload.get('error', {}).get('message')
682+
canonical_code = self._ERROR_CODE_MAPPINGS.get(server_code, exceptions.UNKNOWN)
683+
raise _auth_utils.FirebaseAuthError(
684+
canonical_code, msg, cause=error, http_response=error.response, auth_error_code=server_code)
664685

665686

666687
class _UserIterator(object):

0 commit comments

Comments
 (0)
0