Skip to content

Commit 9406afe

Browse files
authored
Error handling revamp (v3 release) (#334)
* Introduced the exceptions module (#296) * Added the exceptions module * Cleaned up the error handling logic; Added tests * Updated docs; Fixed some typos * Migrating FCM Send APIs to the New Exceptions (#297) * Migrated FCM send APIs to the new error handling regime * Moved error parsing logic to _utils * Refactored OP error handling code * Fixing a broken test * Added utils for handling googleapiclient errors * Added tests for new error handling logic * Updated public API docs * Fixing test for python3 * Cleaning up the error code lookup code * Cleaning up the error parsing APIs * Cleaned up error parsing logic; Updated docs * Migrated remaining messaging APIs to new error types (#298) * Migrated FCM send APIs to the new error handling regime * Moved error parsing logic to _utils * Refactored OP error handling code * Fixing a broken test * Added utils for handling googleapiclient errors * Added tests for new error handling logic * Updated public API docs * Fixing test for python3 * Cleaning up the error code lookup code * Cleaning up the error parsing APIs * Cleaned up error parsing logic; Updated docs * Migrated the FCM IID APIs to the new error types * Introducing TokenSignError to represent custom token creation errors (#302) * Migrated FCM send APIs to the new error handling regime * Moved error parsing logic to _utils * Refactored OP error handling code * Fixing a broken test * Added utils for handling googleapiclient errors * Added tests for new error handling logic * Updated public API docs * Fixing test for python3 * Cleaning up the error code lookup code * Cleaning up the error parsing APIs * Cleaned up error parsing logic; Updated docs * Migrated the FCM IID APIs to the new error types * Migrated custom token API to new error types * Raising FirebaseError from create_session_cookie() API (#306) * Migrated FCM send APIs to the new error handling regime * Moved error parsing logic to _utils * Refactored OP error handling code * Fixing a broken test * Added utils for handling googleapiclient errors * Added tests for new error handling logic * Updated public API docs * Fixing test for python3 * Cleaning up the error code lookup code * Cleaning up the error parsing APIs * Cleaned up error parsing logic; Updated docs * Migrated the FCM IID APIs to the new error types * Migrated custom token API to new error types * Migrated create cookie API to new error types * Improved error message computation * Refactored the shared error handling code * Fixing lint errors * Renamed variable for clarity * Introducing UserNotFoundError type (#309) * Added UserNotFoundError type * Fixed some lint errors * Some formatting updates * Updated docs and tests * New error handling support in create/update/delete user APIs (#311) * New error handling support in create/update/delete user APIs * Fixing some lint errors * Error handling improvements in email action link APIs (#312) * New error handling support in create/update/delete user APIs * Fixing some lint errors * Error handling update in email action link APIs * Project management API migrated to new error types (#314) * Error handling updated for remaining user_mgt APIs (#315) * Error handling updated for remaining user_mgt APIs * Removed unused constants * Migrated token verification APIs to new exception types (#317) * Migrated token verification APIs to new error types * Removed old AuthError type * Added new exception types for revoked tokens * Migrated the db module to the new exception types (#318) * Migrating db module to new exception types * Error handling for transactions * Updated integration tests * Restoring the old txn abort behavior * Updated error type in snippet * Added comment * Adding a few overlooked error types (#319) * Adding some missing error types * Updated documentation * Removing the ability to delete user properties by passing None (#320) * Some types renamed to be PEP8 compliant (#330) * Upgraded Cloud Firestore and Cloud Storage dependencies (#325) * Added documentation for error codes (#339) * A few API doc updates (#340) * Added documentation for error codes * Updated API docs
1 parent 0ed372e commit 9406afe

31 files changed

+2175
-914
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Unreleased
22

3-
-
3+
- [added] Added the new `firebase_admin.exceptions` module containing the
4+
base exception types and global error codes.
5+
- [changed] Updated the `firebase_admin.instance_id` module to use the new
6+
shared exception types. The type `instance_id.ApiCallError` was removed.
47

58
# v2.18.0
69

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,15 @@ pip install firebase-admin
3636

3737
Please refer to the [CONTRIBUTING page](./CONTRIBUTING.md) for more information
3838
about how you can contribute to this project. We welcome bug reports, feature
39-
requests, code review feedback, and also pull requests.
39+
requests, code review feedback, and also pull requests.
4040

4141

4242
## Supported Python Versions
4343

44-
We support Python 2.7 and Python 3.3+. Firebase Admin Python SDK is also tested
45-
on PyPy and [Google App Engine](https://cloud.google.com/appengine/) environments.
44+
We currently support Python 2.7 and Python 3.4+. However, Python 2.7 support is
45+
being phased out, and the developers are advised to use latest Python 3.
46+
Firebase Admin Python SDK is also tested on PyPy and
47+
[Google App Engine](https://cloud.google.com/appengine/) environments.
4648

4749

4850
## Documentation

firebase_admin/_auth_utils.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
import six
2121
from six.moves import urllib
2222

23+
from firebase_admin import exceptions
24+
from firebase_admin import _utils
25+
2326

2427
MAX_CLAIMS_PAYLOAD_SIZE = 1000
2528
RESERVED_CLAIMS = set([
@@ -188,3 +191,121 @@ def validate_action_type(action_type):
188191
raise ValueError('Invalid action type provided action_type: {0}. \
189192
Valid values are {1}'.format(action_type, ', '.join(VALID_EMAIL_ACTION_TYPES)))
190193
return action_type
194+
195+
196+
class UidAlreadyExistsError(exceptions.AlreadyExistsError):
197+
"""The user with the provided uid already exists."""
198+
199+
default_message = 'The user with the provided uid already exists'
200+
201+
def __init__(self, message, cause, http_response):
202+
exceptions.AlreadyExistsError.__init__(self, message, cause, http_response)
203+
204+
205+
class EmailAlreadyExistsError(exceptions.AlreadyExistsError):
206+
"""The user with the provided email already exists."""
207+
208+
default_message = 'The user with the provided email already exists'
209+
210+
def __init__(self, message, cause, http_response):
211+
exceptions.AlreadyExistsError.__init__(self, message, cause, http_response)
212+
213+
214+
class InvalidDynamicLinkDomainError(exceptions.InvalidArgumentError):
215+
"""Dynamic link domain in ActionCodeSettings is not authorized."""
216+
217+
default_message = 'Dynamic link domain specified in ActionCodeSettings is not authorized'
218+
219+
def __init__(self, message, cause, http_response):
220+
exceptions.InvalidArgumentError.__init__(self, message, cause, http_response)
221+
222+
223+
class InvalidIdTokenError(exceptions.InvalidArgumentError):
224+
"""The provided ID token is not a valid Firebase ID token."""
225+
226+
default_message = 'The provided ID token is invalid'
227+
228+
def __init__(self, message, cause=None, http_response=None):
229+
exceptions.InvalidArgumentError.__init__(self, message, cause, http_response)
230+
231+
232+
class PhoneNumberAlreadyExistsError(exceptions.AlreadyExistsError):
233+
"""The user with the provided phone number already exists."""
234+
235+
default_message = 'The user with the provided phone number already exists'
236+
237+
def __init__(self, message, cause, http_response):
238+
exceptions.AlreadyExistsError.__init__(self, message, cause, http_response)
239+
240+
241+
class UnexpectedResponseError(exceptions.UnknownError):
242+
"""Backend service responded with an unexpected or malformed response."""
243+
244+
def __init__(self, message, cause=None, http_response=None):
245+
exceptions.UnknownError.__init__(self, message, cause, http_response)
246+
247+
248+
class UserNotFoundError(exceptions.NotFoundError):
249+
"""No user record found for the specified identifier."""
250+
251+
default_message = 'No user record found for the given identifier'
252+
253+
def __init__(self, message, cause=None, http_response=None):
254+
exceptions.NotFoundError.__init__(self, message, cause, http_response)
255+
256+
257+
_CODE_TO_EXC_TYPE = {
258+
'DUPLICATE_EMAIL': EmailAlreadyExistsError,
259+
'DUPLICATE_LOCAL_ID': UidAlreadyExistsError,
260+
'INVALID_DYNAMIC_LINK_DOMAIN': InvalidDynamicLinkDomainError,
261+
'INVALID_ID_TOKEN': InvalidIdTokenError,
262+
'PHONE_NUMBER_EXISTS': PhoneNumberAlreadyExistsError,
263+
'USER_NOT_FOUND': UserNotFoundError,
264+
}
265+
266+
267+
def handle_auth_backend_error(error):
268+
"""Converts a requests error received from the Firebase Auth service into a FirebaseError."""
269+
if error.response is None:
270+
raise _utils.handle_requests_error(error)
271+
272+
code, custom_message = _parse_error_body(error.response)
273+
if not code:
274+
msg = 'Unexpected error response: {0}'.format(error.response.content.decode())
275+
raise _utils.handle_requests_error(error, message=msg)
276+
277+
exc_type = _CODE_TO_EXC_TYPE.get(code)
278+
msg = _build_error_message(code, exc_type, custom_message)
279+
if not exc_type:
280+
return _utils.handle_requests_error(error, message=msg)
281+
282+
return exc_type(msg, cause=error, http_response=error.response)
283+
284+
285+
def _parse_error_body(response):
286+
"""Parses the given error response to extract Auth error code and message."""
287+
error_dict = {}
288+
try:
289+
parsed_body = response.json()
290+
if isinstance(parsed_body, dict):
291+
error_dict = parsed_body.get('error', {})
292+
except ValueError:
293+
pass
294+
295+
# Auth error response format: {"error": {"message": "AUTH_ERROR_CODE: Optional text"}}
296+
code = error_dict.get('message') if isinstance(error_dict, dict) else None
297+
custom_message = None
298+
if code:
299+
separator = code.find(':')
300+
if separator != -1:
301+
custom_message = code[separator + 1:].strip()
302+
code = code[:separator]
303+
304+
return code, custom_message
305+
306+
307+
def _build_error_message(code, exc_type, custom_message):
308+
default_message = exc_type.default_message if (
309+
exc_type and hasattr(exc_type, 'default_message')) else 'Error while calling Auth service'
310+
ext = ' {0}'.format(custom_message) if custom_message else ''
311+
return '{0} ({1}).{2}'.format(default_message, code, ext)

firebase_admin/_http_client.py

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

112+
def body_and_response(self, method, url, **kwargs):
113+
resp = self.request(method, url, **kwargs)
114+
return self.parse_body(resp), resp
115+
112116
def body(self, method, url, **kwargs):
113117
resp = self.request(method, url, **kwargs)
114118
return self.parse_body(resp)

firebase_admin/_messaging_utils.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222

2323
import six
2424

25+
from firebase_admin import exceptions
26+
2527

2628
class Message(object):
2729
"""A message that can be sent via Firebase Cloud Messaging.
@@ -921,3 +923,33 @@ def encode_fcm_options(cls, fcm_options):
921923
}
922924
result = cls.remove_null_values(result)
923925
return result
926+
927+
928+
class ThirdPartyAuthError(exceptions.UnauthenticatedError):
929+
"""APNs certificate or web push auth key was invalid or missing."""
930+
931+
def __init__(self, message, cause=None, http_response=None):
932+
exceptions.UnauthenticatedError.__init__(self, message, cause, http_response)
933+
934+
935+
class QuotaExceededError(exceptions.ResourceExhaustedError):
936+
"""Sending limit exceeded for the message target."""
937+
938+
def __init__(self, message, cause=None, http_response=None):
939+
exceptions.ResourceExhaustedError.__init__(self, message, cause, http_response)
940+
941+
942+
class SenderIdMismatchError(exceptions.PermissionDeniedError):
943+
"""The authenticated sender ID is different from the sender ID for the registration token."""
944+
945+
def __init__(self, message, cause=None, http_response=None):
946+
exceptions.PermissionDeniedError.__init__(self, message, cause, http_response)
947+
948+
949+
class UnregisteredError(exceptions.NotFoundError):
950+
"""App instance was unregistered from FCM.
951+
952+
This usually means that the token used is no longer valid and a new one must be used."""
953+
954+
def __init__(self, message, cause=None, http_response=None):
955+
exceptions.NotFoundError.__init__(self, message, cause, http_response)

0 commit comments

Comments
 (0)