20
20
import six
21
21
from six .moves import urllib
22
22
23
+ from firebase_admin import exceptions
23
24
from firebase_admin import _auth_utils
24
25
from firebase_admin import _user_import
25
26
26
27
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'
35
30
36
31
MAX_LIST_USERS_RESULTS = 1000
37
32
MAX_IMPORT_USERS_SIZE = 1000
@@ -43,15 +38,6 @@ class _Unspecified(object):
43
38
_UNSPECIFIED = _Unspecified ()
44
39
45
40
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
-
55
41
class UserMetadata (object ):
56
42
"""Contains additional metadata associated with a user account."""
57
43
@@ -374,6 +360,7 @@ def photo_url(self):
374
360
def provider_id (self ):
375
361
return self ._data .get ('providerId' )
376
362
363
+
377
364
class ActionCodeSettings (object ):
378
365
"""Contains required continue/state URL with optional Android and iOS settings.
379
366
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
389
376
self .android_install_app = android_install_app
390
377
self .android_minimum_version = android_minimum_version
391
378
379
+
392
380
def encode_action_code_settings (settings ):
393
381
""" Validates the provided action code settings for email link generation and
394
382
populates the REST api parameters.
@@ -456,6 +444,7 @@ def encode_action_code_settings(settings):
456
444
457
445
return parameters
458
446
447
+
459
448
class UserManager (object ):
460
449
"""Provides methods for interacting with the Google Identity Toolkit."""
461
450
@@ -477,16 +466,18 @@ def get_user(self, **kwargs):
477
466
raise TypeError ('Unsupported keyword arguments: {0}.' .format (kwargs ))
478
467
479
468
try :
480
- response = self ._client .body ('post' , '/accounts:lookup' , json = payload )
469
+ body , response = self ._client .body_and_response ('post' , '/accounts:lookup' , json = payload )
481
470
except requests .exceptions .RequestException as error :
482
471
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 )
484
473
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 ]
490
481
491
482
def list_users (self , page_token = None , max_results = MAX_LIST_USERS_RESULTS ):
492
483
"""Retrieves a batch of users."""
@@ -506,7 +497,7 @@ def list_users(self, page_token=None, max_results=MAX_LIST_USERS_RESULTS):
506
497
try :
507
498
return self ._client .body ('get' , '/accounts:batchGet' , params = payload )
508
499
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 )
510
501
511
502
def create_user (self , uid = None , display_name = None , email = None , phone_number = None ,
512
503
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
523
514
}
524
515
payload = {k : v for k , v in payload .items () if v is not None }
525
516
try :
526
- response = self ._client .body ('post' , '/accounts' , json = payload )
517
+ body , response = self ._client .body_and_response ('post' , '/accounts' , json = payload )
527
518
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 )
529
520
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' )
533
528
534
529
def update_user (self , uid , display_name = _UNSPECIFIED , email = None , phone_number = _UNSPECIFIED ,
535
530
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=_
573
568
574
569
payload = {k : v for k , v in payload .items () if v is not None }
575
570
try :
576
- response = self ._client .body ('post' , '/accounts:update' , json = payload )
571
+ body , response = self ._client .body_and_response ('post' , '/accounts:update' , json = payload )
577
572
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 )
580
574
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' )
584
582
585
583
def delete_user (self , uid ):
586
584
"""Deletes the user identified by the specified user ID."""
587
585
_auth_utils .validate_uid (uid , required = True )
588
586
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 })
590
588
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 )
593
590
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 )
596
597
597
598
def import_users (self , users , hash_alg = None ):
598
599
"""Imports the given list of users to Firebase Auth."""
@@ -612,13 +613,17 @@ def import_users(self, users, hash_alg=None):
612
613
raise ValueError ('A UserImportHash is required to import users with passwords.' )
613
614
payload .update (hash_alg .to_dict ())
614
615
try :
615
- response = self ._client .body ('post' , '/accounts:batchCreate' , json = payload )
616
+ body , response = self ._client .body_and_response ('post' , '/accounts:batchCreate' , json = payload )
616
617
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 )
618
619
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
622
627
623
628
def generate_email_action_link (self , action_type , email , action_code_settings = None ):
624
629
"""Fetches the email action links for types
@@ -633,7 +638,7 @@ def generate_email_action_link(self, action_type, email, action_code_settings=No
633
638
link_url: action url to be emailed to the user
634
639
635
640
Raises:
636
- ApiCallError : If an error occurs while generating the link
641
+ auth.FirebaseAuthError : If an error occurs while generating the link
637
642
ValueError: If the provided arguments are invalid
638
643
"""
639
644
payload = {
@@ -646,21 +651,37 @@ def generate_email_action_link(self, action_type, email, action_code_settings=No
646
651
payload .update (encode_action_code_settings (action_code_settings ))
647
652
648
653
try :
649
- response = self ._client .body ('post' , '/accounts:sendOobCode' , json = payload )
654
+ body , response = self ._client .body_and_response ('post' , '/accounts:sendOobCode' , json = payload )
650
655
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 )
653
657
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 = {}
659
678
if error .response is not None :
660
- msg += '\n Server response: {0}' .format (error .response .content .decode ())
661
- else :
662
- msg += '\n Reason: {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 )
664
685
665
686
666
687
class _UserIterator (object ):
0 commit comments