Example #1
0
 def test_print_warning_ends_message_with_newline(self):
     message = 'test warning'
     with patch.object(display.sys.stderr, 'write') as mock_write:
         display.print_error(message)
     printed_message = mock_write.call_args[0][0]
     self.assertRegex(printed_message, '\n$',
                      'The warning message does not end in a newline.')
Example #2
0
def write_file(filename,
               data,
               mode='w',
               continue_on_error=False,
               display_errors=True):
    """Writes data to a file.

  Args:
    filename: String, the path of the file to write to disk.
    data: Serializable data to write to the file.
    mode: String, the mode in which to open the file and write to it.
    continue_on_error: Boolean, If True, suppresses any IO errors and returns to
      the caller without any externalities.
    display_errors: Boolean, If True, prints error messages when errors are
      encountered and continue_on_error is True.

  Returns:
    Boolean, True if the write operation succeeded, or False if not.
  """
    try:
        with _open_file(filename, mode) as f:
            f.write(data)
        return True

    except IOError as e:
        if continue_on_error:
            if display_errors:
                display.print_error(e)
            return False
        else:
            controlflow.system_error_exit(6, e)
Example #3
0
def handle_oauth_token_error(e, soft_errors):
    """On a token error, exits the application and writes a message to stderr.

  Args:
    e: google.auth.exceptions.RefreshError, The error to handle.
    soft_errors: Boolean, if True, suppresses any applicable errors and instead
      returns to the caller.
  """
    token_error = str(e).replace('.', '')
    if token_error in errors.OAUTH2_TOKEN_ERRORS or e.startswith(
            'Invalid response'):
        if soft_errors:
            return
        if not GM_Globals[GM_CURRENT_API_USER]:
            display.print_error(
                MESSAGE_API_ACCESS_DENIED.format(
                    GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID],
                    ','.join(GM_Globals[GM_CURRENT_API_SCOPES])))
            controlflow.system_error_exit(12, MESSAGE_API_ACCESS_CONFIG)
        else:
            controlflow.system_error_exit(
                19,
                MESSAGE_SERVICE_NOT_APPLICABLE.format(
                    GM_Globals[GM_CURRENT_API_USER]))
    controlflow.system_error_exit(18, f'Authentication Token Error - {str(e)}')
Example #4
0
 def test_print_warning_prints_error_prefix(self):
     message = 'test warning'
     with patch.object(display.sys.stderr, 'write') as mock_write:
         display.print_error(message)
     printed_message = mock_write.call_args[0][0]
     self.assertLess(
         printed_message.find(WARNING_PREFIX), printed_message.find(message),
         'The warning prefix does not appear before the error message')
Example #5
0
def system_error_exit(return_code, message):
    """Raises a system exit with the given return code and message.

  Args:
    return_code: Int, the return code to yield when the system exits.
    message: An error message to print before the system exits.
  """
    if message:
        display.print_error(message)
    sys.exit(return_code)
Example #6
0
def delete():
    cd = gapi_directory.build()
    user_email = gam.normalizeEmailAddressOrUID(sys.argv[3])
    print(f'Deleting account for {user_email}')
    try:
        gapi.call(cd.users(),
                  'delete',
                  userKey=user_email,
                  throw_reasons=[gapi_errors.ErrorReason.CONDITION_NOT_MET])
    except gam.gapi.errors.GapiConditionNotMetError as err:
        display.print_error(
            f'{err} The user {user_email} may be (or have recently been) on Google Vault Hold and thus not eligible for deletion. You can check holds with "gam user <email> show vaultholds".'
        )
Example #7
0
 def run_dual(self,
              use_console_flow,
              authorization_prompt_message='',
              console_prompt_message='',
              web_success_message='',
              open_browser=True,
              redirect_uri_trailing_slash=True,
              **kwargs):
     mgr = multiprocessing.Manager()
     d = mgr.dict()
     d['trailing_slash'] = redirect_uri_trailing_slash
     d['open_browser'] = use_console_flow
     http_client = multiprocessing.Process(target=_wait_for_http_client,
                                           args=(d, ))
     user_input = multiprocessing.Process(target=_wait_for_user_input,
                                          args=(d, ))
     http_client.start()
     # we need to wait until web server starts on avail port
     # so we know redirect_uri to use
     while 'redirect_uri' not in d:
         sleep(0.1)
     self.redirect_uri = d['redirect_uri']
     d['auth_url'], _ = self.authorization_url(**kwargs)
     print(MESSAGE_CONSOLE_AUTHORIZATION_PROMPT.format(url=d['auth_url']))
     user_input.start()
     userInput = False
     while True:
         sleep(0.1)
         if not http_client.is_alive():
             user_input.terminate()
             break
         elif not user_input.is_alive():
             userInput = True
             http_client.terminate()
             break
     while True:
         code = d['code']
         if code.startswith('http'):
             parsed_url = urlparse(code)
             parsed_params = parse_qs(parsed_url.query)
             code = parsed_params.get('code', [None])[0]
         try:
             self.fetch_token(code=code)
             break
         except Exception as e:
             if not userInput:
                 controlflow.system_error_exit(8, str(e))
             display.print_error(str(e))
             _wait_for_user_input(d)
     sys.stdout.write(MESSAGE_AUTHENTICATION_COMPLETE)
     return self.credentials
Example #8
0
def close_file(f, force_flush=False):
    """Closes a file.

  Args:
    f: The file to close
    force_flush: Flush file to disk emptying Python and OS caches. See:
       https://stackoverflow.com/a/13762137/1503886

  Returns:
    Boolean, True if the file was successfully closed. False if an error
        was encountered while closing.
  """
    if force_flush:
        f.flush()
        os.fsync(f.fileno())
    try:
        f.close()
        return True
    except IOError as e:
        display.print_error(e)
        return False
Example #9
0
def get_gapi_error_detail(e,
                          soft_errors=False,
                          silent_errors=False,
                          retry_on_http_error=False):
    """Extracts error detail from a non-200 GAPI Response.

  Args:
    e: googleapiclient.HttpError, The HTTP Error received.
    soft_errors: Boolean, If true, causes error messages to be surpressed,
      rather than sending them to stderr.
    silent_errors: Boolean, If true, suppresses and ignores any errors from
      being displayed
    retry_on_http_error: Boolean, If true, will return -1 as the HTTP Response
      code, indicating that the request can be retried. TODO: Remove this param,
        as it seems to be outside the scope of this method.

  Returns:
    A tuple containing the HTTP Response code, GAPI error reason, and error
        message.
  """
    try:
        error = json.loads(e.content.decode(UTF8))
    except ValueError:
        error_content = e.content.decode(UTF8) if isinstance(
            e.content, bytes) else e.content
        if (e.resp['status'] == '503') and (
                error_content == 'Quota exceeded for the current request'):
            return (e.resp['status'], ErrorReason.QUOTA_EXCEEDED.value,
                    error_content)
        if (e.resp['status'] == '403') and (error_content.startswith(
                'Request rate higher than configured')):
            return (e.resp['status'], ErrorReason.QUOTA_EXCEEDED.value,
                    error_content)
        if (e.resp['status'] == '502') and ('Bad Gateway' in error_content):
            return (e.resp['status'], ErrorReason.BAD_GATEWAY.value,
                    error_content)
        if (e.resp['status'] == '504') and ('Gateway Timeout' in error_content):
            return (e.resp['status'], ErrorReason.GATEWAY_TIMEOUT.value,
                    error_content)
        if (e.resp['status'] == '403') and ('Invalid domain.' in error_content):
            error = _create_http_error_dict(403, ErrorReason.NOT_FOUND.value,
                                            'Domain not found')
        elif (e.resp['status'] == '400') and (
                'InvalidSsoSigningKey' in error_content):
            error = _create_http_error_dict(400, ErrorReason.INVALID.value,
                                            'InvalidSsoSigningKey')
        elif (e.resp['status'] == '400') and ('UnknownError' in error_content):
            error = _create_http_error_dict(400, ErrorReason.INVALID.value,
                                            'UnknownError')
        elif retry_on_http_error:
            return (-1, None, None)
        elif soft_errors:
            if not silent_errors:
                display.print_error(error_content)
            return (0, None, None)
        else:
            controlflow.system_error_exit(5, error_content)
        # END: ValueError catch

    if 'error' in error:
        http_status = error['error']['code']
        try:
            message = error['error']['errors'][0]['message']
        except KeyError:
            message = error['error']['message']
        if http_status == 404:
            if 'Requested entity was not found' in message or 'does not exist' in message:
                error = _create_http_error_dict(404, ErrorReason.NOT_FOUND.value,
                                                message)
        elif http_status == 500:
            if 'Failed to convert server response to JSON' in message:
                error = _create_http_error_dict(500, ErrorReason.INTERNAL_SERVER_ERROR.value,
                                                message)
    else:
        if 'error_description' in error:
            if error['error_description'] == 'Invalid Value':
                message = error['error_description']
                http_status = 400
                error = _create_http_error_dict(400, ErrorReason.INVALID.value,
                                                message)
            else:
                controlflow.system_error_exit(4, str(error))
        else:
            controlflow.system_error_exit(4, str(error))

    # Extract the error reason
    try:
        reason = error['error']['errors'][0]['reason']
        if reason == 'notFound':
            if 'userKey' in message:
                reason = ErrorReason.USER_NOT_FOUND.value
            elif 'groupKey' in message:
                reason = ErrorReason.GROUP_NOT_FOUND.value
            elif 'memberKey' in message:
                reason = ErrorReason.MEMBER_NOT_FOUND.value
            elif 'Domain not found' in message:
                reason = ErrorReason.DOMAIN_NOT_FOUND.value
            elif 'Resource Not Found' in message:
                reason = ErrorReason.RESOURCE_NOT_FOUND.value
        elif reason == 'invalid':
            if 'userId' in message:
                reason = ErrorReason.USER_NOT_FOUND.value
            elif 'memberKey' in message:
                reason = ErrorReason.INVALID_MEMBER.value
        elif reason == 'failedPrecondition':
            if 'Bad Request' in message:
                reason = ErrorReason.BAD_REQUEST.value
            elif 'Mail service not enabled' in message:
                reason = ErrorReason.SERVICE_NOT_AVAILABLE.value
        elif reason == 'required':
            if 'memberKey' in message:
                reason = ErrorReason.MEMBER_NOT_FOUND.value
        elif reason == 'conditionNotMet':
            if 'Cyclic memberships not allowed' in message:
                reason = ErrorReason.CYCLIC_MEMBERSHIPS_NOT_ALLOWED.value
    except KeyError:
        reason = f'{http_status}'
    return (http_status, reason, message)
Example #10
0
def call(service,
         function,
         silent_errors=False,
         soft_errors=False,
         throw_reasons=None,
         retry_reasons=None,
         **kwargs):
    """Executes a single request on a Google service function.

  Args:
    service: A Google service object for the desired API.
    function: String, The name of a service request method to execute.
    silent_errors: Bool, If True, error messages are suppressed when
      encountered.
    soft_errors: Bool, If True, writes non-fatal errors to stderr.
    throw_reasons: A list of Google HTTP error reason strings indicating the
      errors generated by this request should be re-thrown. All other HTTP
      errors are consumed.
    retry_reasons: A list of Google HTTP error reason strings indicating which
      error should be retried, using exponential backoff techniques, when the
      error reason is encountered.
    **kwargs: Additional params to pass to the request method.

  Returns:
    A response object for the corresponding Google API call.
  """
    if throw_reasons is None:
        throw_reasons = []
    if retry_reasons is None:
        retry_reasons = []

    method = getattr(service, function)
    retries = 10
    parameters = dict(
        list(kwargs.items()) + list(GM_Globals[GM_EXTRA_ARGS_DICT].items()))
    for n in range(1, retries + 1):
        try:
            return method(**parameters).execute()
        except googleapiclient.errors.HttpError as e:
            http_status, reason, message = errors.get_gapi_error_detail(
                e,
                soft_errors=soft_errors,
                silent_errors=silent_errors,
                retry_on_http_error=n < 3)
            if http_status == -1:
                # The error detail indicated that we should retry this request
                # We'll refresh credentials and make another pass
                service._http.credentials.refresh(transport.create_http())
                continue
            if http_status == 0:
                return None

            is_known_error_reason = reason in [
                r.value for r in errors.ErrorReason
            ]
            if is_known_error_reason and errors.ErrorReason(
                    reason) in throw_reasons:
                if errors.ErrorReason(
                        reason) in errors.ERROR_REASON_TO_EXCEPTION:
                    raise errors.ERROR_REASON_TO_EXCEPTION[errors.ErrorReason(
                        reason)](message)
                raise e
            if (n != retries) and (is_known_error_reason and errors.ErrorReason(
                    reason) in errors.DEFAULT_RETRY_REASONS + retry_reasons):
                controlflow.wait_on_failure(n, retries, reason)
                continue
            if soft_errors:
                display.print_error(
                    f'{http_status}: {message} - {reason}{["", ": Giving up."][n > 1]}'
                )
                return None
            controlflow.system_error_exit(
                int(http_status), f'{http_status}: {message} - {reason}')
        except google.auth.exceptions.RefreshError as e:
            handle_oauth_token_error(
                e, soft_errors or
                errors.ErrorReason.SERVICE_NOT_AVAILABLE in throw_reasons)
            if errors.ErrorReason.SERVICE_NOT_AVAILABLE in throw_reasons:
                raise errors.GapiServiceNotAvailableError(str(e))
            display.print_error(
                f'User {GM_Globals[GM_CURRENT_API_USER]}: {str(e)}')
            return None
        except ValueError as e:
            if hasattr(service._http,
                       'cache') and service._http.cache is not None:
                service._http.cache = None
                continue
            controlflow.system_error_exit(4, str(e))
        except (httplib2.ServerNotFoundError, RuntimeError) as e:
            if n != retries:
                service._http.connections = {}
                controlflow.wait_on_failure(n, retries, str(e))
                continue
            controlflow.system_error_exit(4, str(e))
        except TypeError as e:
            controlflow.system_error_exit(4, str(e))
Example #11
0
 def test_print_warning_prints_to_stderr(self):
     message = 'test warning'
     with patch.object(display.sys.stderr, 'write') as mock_write:
         display.print_error(message)
     printed_message = mock_write.call_args[0][0]
     self.assertIn(message, printed_message)