Exemplo n.º 1
0
    def handle_operation(self,
                         operation,
                         wait_for_operation=False,
                         max_time_seconds=None):
        """Handles long running operations.

        Args:
            operation: The operation to handle.
            wait_for_operation: Should we allow polling for the operation to complete.
                If no polling is requested, a locked model will be returned instead.
            max_time_seconds: The maximum seconds to try polling for operation complete.
                (None for no limit)

        Returns:
            dict: A dictionary of the returned model properties.

        Raises:
            TypeError: if the operation is not a dictionary.
            ValueError: If the operation is malformed.
            UnknownError: If the server responds with an unexpected response.
            err: If the operation exceeds polling attempts or stop_time
        """
        if not isinstance(operation, dict):
            raise TypeError('Operation must be a dictionary.')

        if operation.get('done'):
            # Operations which are immediately done don't have an operation name
            if operation.get('response'):
                return operation.get('response')
            if operation.get('error'):
                raise _utils.handle_operation_error(operation.get('error'))
            raise exceptions.UnknownError(
                message='Internal Error: Malformed Operation.')

        op_name = _validate_operation_name(operation.get('name'))
        metadata = operation.get('metadata', {})
        metadata_type = metadata.get('@type', '')
        if not metadata_type.endswith('ModelOperationMetadata'):
            raise TypeError('Unknown type of operation metadata.')
        _, model_id = _validate_and_parse_name(metadata.get('name'))
        current_attempt = 0
        start_time = datetime.datetime.now()
        stop_time = (None if max_time_seconds is None else start_time +
                     datetime.timedelta(seconds=max_time_seconds))
        while wait_for_operation and not operation.get('done'):
            # We just got this operation. Wait before getting another
            # so we don't exceed the GetOperation maximum request rate.
            self._exponential_backoff(current_attempt, stop_time)
            operation = self.get_operation(op_name)
            current_attempt += 1

        if operation.get('done'):
            if operation.get('response'):
                return operation.get('response')
            if operation.get('error'):
                raise _utils.handle_operation_error(operation.get('error'))

        # If the operation is not complete or timed out, return a (locked) model instead
        return get_model(model_id).as_dict()
Exemplo n.º 2
0
    def test_is_associated_auth_id_deleted_without_init_returns_false(self):
        init_swap = self.swap_to_always_raise(
            firebase_admin, 'initialize_app',
            error=firebase_exceptions.UnknownError('could not init'))

        with init_swap, self.capture_logging() as logs:
            self.assertFalse(
                firebase_auth_services
                .verify_external_auth_associations_are_deleted(self.USER_ID))

        self.assert_matches_regexps(logs, ['could not init'])
    def test_is_associated_auth_id_deleted_without_init_returns_false(self):
        init_swap = self.swap_to_always_raise(
            firebase_admin, 'initialize_app',
            error=firebase_exceptions.UnknownError('could not init'))

        with init_swap, self.capture_logging(min_level=logging.ERROR) as logs:
            self.assertFalse(
                firebase_auth_services.are_auth_associations_deleted(
                    self.USER_ID))

        self.assert_only_item_is_exception(logs, 'could not init')
Exemplo n.º 4
0
    def test_returns_none_when_firebase_init_fails(self):
        initialize_app_swap = self.swap_to_always_raise(
            firebase_admin, 'initialize_app',
            error=firebase_exceptions.UnknownError('could not init'))
        request = self.make_request(auth_header='Bearer DUMMY_JWT')

        with initialize_app_swap, self.capture_logging() as errors:
            auth_claims = (
                firebase_auth_services.get_auth_claims_from_request(request))

        self.assertIsNone(auth_claims)
        self.assert_matches_regexps(errors, ['could not init'])
    def test_returns_none_when_firebase_init_fails(self):
        initialize_app_swap = self.swap_to_always_raise(
            firebase_admin, 'initialize_app',
            error=firebase_exceptions.UnknownError('could not init'))
        request = self.make_request(auth_header='Bearer DUMMY_JWT')

        with initialize_app_swap, self.capture_logging() as errors:
            auth_claims = firebase_auth_services.authenticate_request(request)

        self.assertIsNone(auth_claims)
        self.assertEqual(len(errors), 1)
        self.assertIn('could not init', errors[0])
Exemplo n.º 6
0
def handle_googleapiclient_error(error,
                                 message=None,
                                 code=None,
                                 http_response=None):
    """Constructs a ``FirebaseError`` from the given googleapiclient error.

    This method is agnostic of the remote service that produced the error, whether it is a GCP
    service or otherwise. Therefore, this method does not attempt to parse the error response in
    any way.

    Args:
        error: An error raised by the googleapiclient module while making an HTTP call.
        message: A message to be included in the resulting ``FirebaseError`` (optional). If not
            specified the string representation of the ``error`` argument is used as the message.
        code: A GCP error code that will be used to determine the resulting error type (optional).
            If not specified the HTTP status code on the error response is used to determine a
            suitable error code.
        http_response: A requests HTTP response object to associate with the exception (optional).
            If not specified, one will be created from the ``error``.

    Returns:
        FirebaseError: A ``FirebaseError`` that can be raised to the user code.
    """
    if isinstance(error, socket.timeout) or (isinstance(error, socket.error)
                                             and 'timed out' in str(error)):
        return exceptions.DeadlineExceededError(
            message='Timed out while making an API call: {0}'.format(error),
            cause=error)
    elif isinstance(error, httplib2.ServerNotFoundError):
        return exceptions.UnavailableError(
            message='Failed to establish a connection: {0}'.format(error),
            cause=error)
    elif not isinstance(error, googleapiclient.errors.HttpError):
        return exceptions.UnknownError(
            message='Unknown error while making a remote service call: {0}'.
            format(error),
            cause=error)

    if not code:
        code = _http_status_to_error_code(error.resp.status)
    if not message:
        message = str(error)
    if not http_response:
        http_response = _http_response_from_googleapiclient_error(error)

    err_type = _error_code_to_exception_type(code)
    return err_type(message=message, cause=error, http_response=http_response)
Exemplo n.º 7
0
    def _poll_app_creation(self, operation_name):
        """Polls the Long-Running Operation repeatedly until it is done with exponential backoff."""
        for current_attempt in range(_ProjectManagementService.MAXIMUM_POLLING_ATTEMPTS):
            delay_factor = pow(
                _ProjectManagementService.POLL_EXPONENTIAL_BACKOFF_FACTOR, current_attempt)
            wait_time_seconds = delay_factor * _ProjectManagementService.POLL_BASE_WAIT_TIME_SECONDS
            time.sleep(wait_time_seconds)
            path = '/v1/{0}'.format(operation_name)
            poll_response, http_response = self._body_and_response('get', path)
            done = poll_response.get('done')
            if done:
                response = poll_response.get('response')
                if response:
                    return response

                raise exceptions.UnknownError(
                    'Polling finished, but the operation terminated in an error.',
                    http_response=http_response)
        raise exceptions.DeadlineExceededError('Polling deadline exceeded.')
Exemplo n.º 8
0
    def test_disable_association_warns_when_firebase_fails_to_init(self):
        firebase_admin.auth.create_user(uid='aid')
        firebase_auth_services.associate_auth_id_with_user_id(
            auth_domain.AuthIdUserIdPair('aid', 'uid'))
        init_swap = self.swap_to_always_raise(
            firebase_admin, 'initialize_app',
            error=firebase_exceptions.UnknownError('could not init'))

        self.assertIsNotNone(
            auth_models.UserIdByFirebaseAuthIdModel.get('aid', strict=False))
        self.assertFalse(firebase_admin.auth.get_user('aid').disabled)

        with init_swap, self.capture_logging() as logs:
            firebase_auth_services.mark_user_for_deletion('uid')

        self.assert_matches_regexps(logs, ['could not init'])
        self.assertIsNone(
            auth_models.UserIdByFirebaseAuthIdModel.get('aid', strict=False))
        self.assertFalse(firebase_admin.auth.get_user('aid').disabled)
Exemplo n.º 9
0
def handle_operation_error(error):
    """Constructs a ``FirebaseError`` from the given operation error.

    Args:
        error: An error returned by a long running operation.

    Returns:
        FirebaseError: A ``FirebaseError`` that can be raised to the user code.
    """
    if not isinstance(error, dict):
        return exceptions.UnknownError(
            message='Unknown error while making a remote service call: {0}'.format(error),
            cause=error)

    rpc_code = error.get('code')
    message = error.get('message')
    error_code = _rpc_code_to_error_code(rpc_code)
    err_type = _error_code_to_exception_type(error_code)
    return err_type(message=message)
Exemplo n.º 10
0
    def test_disable_association_warns_when_firebase_fails_to_update_user(self):
        self.firebase_sdk_stub.create_user('aid')
        firebase_auth_services.associate_auth_id_with_user_id(
            auth_domain.AuthIdUserIdPair('aid', 'uid'))
        update_user_swap = self.swap_to_always_raise(
            firebase_admin.auth, 'update_user',
            error=firebase_exceptions.UnknownError('could not update'))
        log_capturing_context = self.capture_logging()

        self.assertIsNotNone(
            auth_models.UserIdByFirebaseAuthIdModel.get('aid', strict=False))
        self.assertFalse(firebase_admin.auth.get_user('aid').disabled)

        with update_user_swap, log_capturing_context as logs:
            firebase_auth_services.mark_user_for_deletion('uid')

        self.assert_matches_regexps(logs, ['could not update'])
        self.assertIsNone(
            auth_models.UserIdByFirebaseAuthIdModel.get('aid', strict=False))
        self.assertFalse(firebase_admin.auth.get_user('aid').disabled)
Exemplo n.º 11
0
def handle_requests_error(error, message=None, code=None):
    """Constructs a ``FirebaseError`` from the given requests error.

    This method is agnostic of the remote service that produced the error, whether it is a GCP
    service or otherwise. Therefore, this method does not attempt to parse the error response in
    any way.

    Args:
        error: An error raised by the requests module while making an HTTP call.
        message: A message to be included in the resulting ``FirebaseError`` (optional). If not
            specified the string representation of the ``error`` argument is used as the message.
        code: A GCP error code that will be used to determine the resulting error type (optional).
            If not specified the HTTP status code on the error response is used to determine a
            suitable error code.

    Returns:
        FirebaseError: A ``FirebaseError`` that can be raised to the user code.
    """
    if isinstance(error, requests.exceptions.Timeout):
        return exceptions.DeadlineExceededError(
            message='Timed out while making an API call: {0}'.format(error),
            cause=error)
    elif isinstance(error, requests.exceptions.ConnectionError):
        return exceptions.UnavailableError(
            message='Failed to establish a connection: {0}'.format(error),
            cause=error)
    elif error.response is None:
        return exceptions.UnknownError(
            message='Unknown error while making a remote service call: {0}'.
            format(error),
            cause=error)

    if not code:
        code = _http_status_to_error_code(error.response.status_code)
    if not message:
        message = str(error)

    err_type = _error_code_to_exception_type(code)
    return err_type(message=message, cause=error, http_response=error.response)
class DeleteAuthAssociationsTests(FirebaseAuthServicesTestBase):

    EMAIL = '*****@*****.**'
    USERNAME = '******'
    AUTH_ID = 'authid'

    UNKNOWN_ERROR = firebase_exceptions.UnknownError('error')

    def setUp(self):
        super(DeleteAuthAssociationsTests, self).setUp()
        self.firebase_sdk_stub.create_user(self.AUTH_ID)
        user_settings = user_services.create_new_user(self.AUTH_ID, self.EMAIL)
        self.user_id = user_settings.user_id
        firebase_auth_services.associate_auth_id_with_user_id(
            auth_domain.AuthIdUserIdPair(self.AUTH_ID, self.user_id))

    def delete_external_auth_associations(self):
        """Runs delete_external_auth_associations on the test user."""
        firebase_auth_services.delete_external_auth_associations(self.user_id)

    def assert_firebase_account_is_deleted(self):
        """Asserts that the Firebase account has been deleted."""
        self.assertRaisesRegexp(
            firebase_admin.auth.UserNotFoundError, 'not found',
            lambda: firebase_admin.auth.get_user(self.AUTH_ID))

    def assert_firebase_account_is_not_deleted(self):
        """Asserts that the Firebase account still exists."""
        user = firebase_admin.auth.get_user(self.AUTH_ID)
        self.assertIsNotNone(user)
        self.assertEqual(user.uid, self.AUTH_ID)

    def swap_initialize_sdk_to_always_fail(self):
        """Swaps the initialize_app function so that it always fails."""
        return self.swap_to_always_raise(firebase_admin,
                                         'initialize_app',
                                         error=self.UNKNOWN_ERROR)

    def swap_get_user_to_always_fail(self):
        """Swaps the get_user function so that it always fails."""
        return self.swap_to_always_raise(firebase_admin.auth,
                                         'get_user',
                                         error=self.UNKNOWN_ERROR)

    def swap_delete_user_to_always_fail(self):
        """Swaps the delete_user function so that it always fails."""
        return self.swap_to_always_raise(firebase_admin.auth,
                                         'delete_user',
                                         error=self.UNKNOWN_ERROR)

    def test_delete_external_auth_associations_happy_path(self):
        self.delete_external_auth_associations()

        self.assert_firebase_account_is_deleted()
        self.assertTrue(
            firebase_auth_services.
            verify_external_auth_associations_are_deleted(self.user_id))

    def test_delete_external_auth_associations_after_failed_attempt(self):
        with self.swap_initialize_sdk_to_always_fail():
            self.delete_external_auth_associations()

        self.assert_firebase_account_is_not_deleted()
        self.assertFalse(
            firebase_auth_services.
            verify_external_auth_associations_are_deleted(self.user_id))

        self.delete_external_auth_associations()

        self.assert_firebase_account_is_deleted()
        self.assertTrue(
            firebase_auth_services.
            verify_external_auth_associations_are_deleted(self.user_id))

    def test_verify_delete_external_auth_associations_after_failed_attempt(
            self):
        self.delete_external_auth_associations()

        self.assert_firebase_account_is_deleted()

        with self.swap_initialize_sdk_to_always_fail():
            self.assertFalse(
                firebase_auth_services.
                verify_external_auth_associations_are_deleted(self.user_id))

        self.delete_external_auth_associations()

        self.assertTrue(
            firebase_auth_services.
            verify_external_auth_associations_are_deleted(self.user_id))

    def test_delete_external_auth_associations_when_delete_user_fails(self):
        with self.swap_delete_user_to_always_fail():
            self.delete_external_auth_associations()

        self.assert_firebase_account_is_not_deleted()

        self.assertFalse(
            firebase_auth_services.
            verify_external_auth_associations_are_deleted(self.user_id))

    def test_delete_external_auth_associations_when_get_user_fails(self):
        self.delete_external_auth_associations()

        self.assert_firebase_account_is_deleted()

        with self.swap_get_user_to_always_fail():
            self.assertFalse(
                firebase_auth_services.
                verify_external_auth_associations_are_deleted(self.user_id))

        self.assertTrue(
            firebase_auth_services.
            verify_external_auth_associations_are_deleted(self.user_id))

    def test_delete_external_auth_associations_when_init_fails_during_delete(
            self):
        with self.swap_initialize_sdk_to_always_fail():
            self.delete_external_auth_associations()

        self.assert_firebase_account_is_not_deleted()

        self.assertFalse(
            firebase_auth_services.
            verify_external_auth_associations_are_deleted(self.user_id))

    def test_delete_external_auth_associations_when_init_fails_during_verify(
            self):
        self.delete_external_auth_associations()

        self.assert_firebase_account_is_deleted()

        with self.swap_initialize_sdk_to_always_fail():
            self.assertFalse(
                firebase_auth_services.
                verify_external_auth_associations_are_deleted(self.user_id))

        self.assertTrue(
            firebase_auth_services.
            verify_external_auth_associations_are_deleted(self.user_id))
 def mock_delete_app_error(self):
     """Returns a context in which `delete_app` raises an exception."""
     return self._test.swap_to_always_raise(
         firebase_admin,
         'delete_app',
         error=firebase_exceptions.UnknownError('could not delete app'))
class FirebaseAccountWipeoutTests(test_utils.GenericTestBase):
    """Tests for wipeout_service that is specific to Firebase authentication."""

    EMAIL = '*****@*****.**'
    USERNAME = '******'
    AUTH_ID = 'authid'

    UNKNOWN_ERROR = firebase_exceptions.UnknownError('error')

    def setUp(self):
        super(FirebaseAccountWipeoutTests, self).setUp()
        self._uninstall_stub = FirebaseAdminSdkStub.install(self)

        firebase_admin.auth.create_user(uid=self.AUTH_ID)
        self.signup(self.EMAIL, self.USERNAME)
        self.user_id = self.get_user_id_from_email(self.EMAIL)
        firebase_auth_services.associate_auth_id_to_user_id(
            auth_domain.AuthIdUserIdPair(self.AUTH_ID, self.user_id))
        wipeout_service.pre_delete_user(self.user_id)

    def tearDown(self):
        self._uninstall_stub()
        super(FirebaseAccountWipeoutTests, self).tearDown()

    def wipeout(self):
        """Runs wipeout on the user created by this test."""
        wipeout_service.delete_user(
            wipeout_service.get_pending_deletion_request(self.user_id))

    def assert_wipeout_is_verified(self):
        """Asserts that the wipeout has been acknowledged as complete."""
        self.assertTrue(wipeout_service.verify_user_deleted(self.user_id))

    def assert_wipeout_is_not_verified(self):
        """Asserts that the wipeout has been acknowledged as incomplete."""
        self.assertFalse(wipeout_service.verify_user_deleted(self.user_id))

    def assert_firebase_account_is_deleted(self):
        """Asserts that the Firebase account has been deleted."""
        self.assertRaisesRegexp(
            firebase_admin.auth.UserNotFoundError, 'not found',
            lambda: firebase_admin.auth.get_user(self.AUTH_ID))

    def assert_firebase_account_is_not_deleted(self):
        """Asserts that the Firebase account still exists."""
        user = firebase_admin.auth.get_user(self.AUTH_ID)
        self.assertIsNotNone(user)
        self.assertEqual(user.uid, self.AUTH_ID)

    def swap_initialize_sdk_to_always_fail(self):
        """Swaps the initialize_app function so that it always fails."""
        return self.swap_to_always_raise(
            firebase_admin, 'initialize_app', error=self.UNKNOWN_ERROR)

    def swap_get_user_to_always_fail(self):
        """Swaps the get_user function so that it always fails."""
        return self.swap_to_always_raise(
            firebase_admin.auth, 'get_user', error=self.UNKNOWN_ERROR)

    def swap_delete_user_to_always_fail(self):
        """Swaps the delete_user function so that it always fails."""
        return self.swap_to_always_raise(
            firebase_admin.auth, 'delete_user', error=self.UNKNOWN_ERROR)

    def test_wipeout_happy_path(self):
        self.wipeout()

        self.assert_firebase_account_is_deleted()
        self.assert_wipeout_is_verified()

    def test_wipeout_retry_after_failed_attempt(self):
        with self.swap_initialize_sdk_to_always_fail():
            self.wipeout()

        self.assert_firebase_account_is_not_deleted()
        self.assert_wipeout_is_not_verified()

        self.wipeout()

        self.assert_firebase_account_is_deleted()
        self.assert_wipeout_is_verified()

    def test_wipeout_retry_after_unverified_successful_attempt(self):
        self.wipeout()

        self.assert_firebase_account_is_deleted()

        with self.swap_initialize_sdk_to_always_fail():
            self.assert_wipeout_is_not_verified()

        self.wipeout()

        self.assert_wipeout_is_verified()

    def test_wipeout_when_delete_user_fails(self):
        with self.swap_delete_user_to_always_fail():
            self.wipeout()

        self.assert_firebase_account_is_not_deleted()

        self.assert_wipeout_is_not_verified()

    def test_wipeout_when_get_user_fails(self):
        self.wipeout()

        self.assert_firebase_account_is_deleted()

        with self.swap_get_user_to_always_fail():
            self.assert_wipeout_is_not_verified()

        self.assert_wipeout_is_verified()

    def test_wipeout_when_init_fails_during_delete(self):
        with self.swap_initialize_sdk_to_always_fail():
            self.wipeout()

        self.assert_firebase_account_is_not_deleted()

        self.assert_wipeout_is_not_verified()

    def test_wipeout_when_init_fails_during_verify(self):
        self.wipeout()

        self.assert_firebase_account_is_deleted()

        with self.swap_initialize_sdk_to_always_fail():
            self.assert_wipeout_is_not_verified()

        self.assert_wipeout_is_verified()