Skip to content

Error handling updated for remaining user_mgt APIs #315

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 9 additions & 26 deletions firebase_admin/_user_mgt.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@
from firebase_admin import _user_import


USER_IMPORT_ERROR = 'USER_IMPORT_ERROR'
USER_DOWNLOAD_ERROR = 'LIST_USERS_ERROR'

MAX_LIST_USERS_RESULTS = 1000
MAX_IMPORT_USERS_SIZE = 1000

Expand All @@ -44,15 +41,6 @@ def __init__(self, description):
DELETE_ATTRIBUTE = Sentinel('Value used to delete an attribute from a user profile')


class ApiCallError(Exception):
"""Represents an Exception encountered while invoking the Firebase user management API."""

def __init__(self, code, message, error=None):
Exception.__init__(self, message)
self.code = code
self.detail = error


class UserMetadata(object):
"""Contains additional metadata associated with a user account."""

Expand Down Expand Up @@ -510,7 +498,7 @@ def list_users(self, page_token=None, max_results=MAX_LIST_USERS_RESULTS):
try:
return self._client.body('get', '/accounts:batchGet', params=payload)
except requests.exceptions.RequestException as error:
self._handle_http_error(USER_DOWNLOAD_ERROR, 'Failed to download user accounts.', error)
raise _auth_utils.handle_auth_backend_error(error)

def create_user(self, uid=None, display_name=None, email=None, phone_number=None,
photo_url=None, password=None, disabled=None, email_verified=None):
Expand Down Expand Up @@ -619,13 +607,15 @@ def import_users(self, users, hash_alg=None):
raise ValueError('A UserImportHash is required to import users with passwords.')
payload.update(hash_alg.to_dict())
try:
response = self._client.body('post', '/accounts:batchCreate', json=payload)
body, http_resp = self._client.body_and_response(
'post', '/accounts:batchCreate', json=payload)
except requests.exceptions.RequestException as error:
self._handle_http_error(USER_IMPORT_ERROR, 'Failed to import users.', error)
raise _auth_utils.handle_auth_backend_error(error)
else:
if not isinstance(response, dict):
raise ApiCallError(USER_IMPORT_ERROR, 'Failed to import users.')
return response
if not isinstance(body, dict):
raise _auth_utils.UnexpectedResponseError(
'Failed to import users.', http_response=http_resp)
return body

def generate_email_action_link(self, action_type, email, action_code_settings=None):
"""Fetches the email action links for types
Expand All @@ -640,7 +630,7 @@ def generate_email_action_link(self, action_type, email, action_code_settings=No
link_url: action url to be emailed to the user

Raises:
ApiCallError: If an error occurs while generating the link
FirebaseError: If an error occurs while generating the link
ValueError: If the provided arguments are invalid
"""
payload = {
Expand All @@ -663,13 +653,6 @@ def generate_email_action_link(self, action_type, email, action_code_settings=No
'Failed to generate email action link.', http_response=http_resp)
return body.get('oobLink')

def _handle_http_error(self, code, msg, error):
if error.response is not None:
msg += '\nServer response: {0}'.format(error.response.content.decode())
else:
msg += '\nReason: {0}'.format(error)
raise ApiCallError(code, msg, error)


class _UserIterator(object):
"""An iterator that allows iterating over user accounts, one at a time.
Expand Down
16 changes: 5 additions & 11 deletions firebase_admin/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,14 +308,11 @@ def list_users(page_token=None, max_results=_user_mgt.MAX_LIST_USERS_RESULTS, ap

Raises:
ValueError: If max_results or page_token are invalid.
AuthError: If an error occurs while retrieving the user accounts.
FirebaseError: If an error occurs while retrieving the user accounts.
"""
user_manager = _get_auth_service(app).user_manager
def download(page_token, max_results):
try:
return user_manager.list_users(page_token, max_results)
except _user_mgt.ApiCallError as error:
raise AuthError(error.code, str(error), error.detail)
return user_manager.list_users(page_token, max_results)
return ListUsersPage(download, page_token, max_results)


Expand Down Expand Up @@ -443,14 +440,11 @@ def import_users(users, hash_alg=None, app=None):

Raises:
ValueError: If the provided arguments are invalid.
AuthError: If an error occurs while importing users.
FirebaseError: If an error occurs while importing users.
"""
user_manager = _get_auth_service(app).user_manager
try:
result = user_manager.import_users(users, hash_alg)
return UserImportResult(result, len(users))
except _user_mgt.ApiCallError as error:
raise AuthError(error.code, str(error), error.detail)
result = user_manager.import_users(users, hash_alg)
return UserImportResult(result, len(users))


def generate_password_reset_link(email, action_code_settings=None, app=None):
Expand Down
24 changes: 21 additions & 3 deletions tests/test_user_mgt.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,10 +731,9 @@ def test_list_users_with_all_args(self, user_mgt_app):

def test_list_users_error(self, user_mgt_app):
_instrument_user_manager(user_mgt_app, 500, '{"error":"test"}')
with pytest.raises(auth.AuthError) as excinfo:
with pytest.raises(exceptions.InternalError) as excinfo:
auth.list_users(app=user_mgt_app)
assert excinfo.value.code == _user_mgt.USER_DOWNLOAD_ERROR
assert '{"error":"test"}' in str(excinfo.value)
assert str(excinfo.value) == 'Unexpected error response: {"error":"test"}'

def _check_page(self, page):
assert isinstance(page, auth.ListUsersPage)
Expand Down Expand Up @@ -1076,6 +1075,25 @@ def test_import_users_with_hash(self, user_mgt_app):
}
self._check_rpc_calls(recorder, expected)

def test_import_users_http_error(self, user_mgt_app):
_instrument_user_manager(user_mgt_app, 401, '{"error": {"message": "ERROR_CODE"}}')
users = [
auth.ImportUserRecord(uid='user1'),
auth.ImportUserRecord(uid='user2'),
]
with pytest.raises(exceptions.UnauthenticatedError) as excinfo:
auth.import_users(users, app=user_mgt_app)
assert str(excinfo.value) == 'Error while calling Auth service (ERROR_CODE).'

def test_import_users_unexpected_response(self, user_mgt_app):
_instrument_user_manager(user_mgt_app, 200, '"not dict"')
users = [
auth.ImportUserRecord(uid='user1'),
auth.ImportUserRecord(uid='user2'),
]
with pytest.raises(auth.UnexpectedResponseError):
auth.import_users(users, app=user_mgt_app)

def _check_rpc_calls(self, recorder, expected):
assert len(recorder) == 1
request = json.loads(recorder[0].body.decode())
Expand Down