diff --git a/aws_google_auth/__init__.py b/aws_google_auth/__init__.py index 14cc291..1ef6a3f 100644 --- a/aws_google_auth/__init__.py +++ b/aws_google_auth/__init__.py @@ -1,6 +1,4 @@ #!/usr/bin/env python -from __future__ import print_function - import argparse import base64 import os @@ -8,7 +6,6 @@ import logging import keyring -from six import print_ as print from tzlocal import get_localzone from aws_google_auth import _version @@ -77,7 +74,7 @@ def cli(cli_args): config = resolve_config(args) process_auth(args, config) except google.ExpectedGoogleException as ex: - print(ex) + logging.error(ex) sys.exit(1) except KeyboardInterrupt: pass @@ -274,8 +271,8 @@ def process_auth(args, config): else: config.role_arn, config.provider = util.Util.pick_a_role(roles) if not config.quiet: - print("Assuming " + config.role_arn) - print("Credentials Expiration: " + format(amazon_client.expiration.astimezone(get_localzone()))) + util.Util.message("Assuming " + config.role_arn) + util.Util.message("Credentials Expiration: " + format(amazon_client.expiration.astimezone(get_localzone()))) if config.print_creds: amazon_client.print_export_line() diff --git a/aws_google_auth/amazon.py b/aws_google_auth/amazon.py index fa6a0cc..8f905ad 100644 --- a/aws_google_auth/amazon.py +++ b/aws_google_auth/amazon.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +from __future__ import print_function import base64 import boto3 @@ -72,6 +73,7 @@ def print_export_line(self): self.session_token, self.expiration.strftime('%Y-%m-%dT%H:%M:%S%z')) + # Print to stdout (not stderr) so the output can be sourced by a shell. print(formatted) @property diff --git a/aws_google_auth/google.py b/aws_google_auth/google.py index c7f641c..85691b8 100644 --- a/aws_google_auth/google.py +++ b/aws_google_auth/google.py @@ -1,7 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import print_function - import base64 import io import json @@ -15,10 +13,10 @@ from distutils.spawn import find_executable from bs4 import BeautifulSoup from requests import HTTPError -from six import print_ as print -from six.moves import urllib_parse, input +from six.moves import urllib_parse from aws_google_auth import _version +from aws_google_auth.util import Util # The U2F USB Library is optional, if it's there, include it. try: @@ -377,7 +375,7 @@ def handle_captcha(self, sess, payload): if find_executable('xv') is None and find_executable('display') is None: open_image = False - print("Please visit the following URL to view your CAPTCHA: {}".format(captcha_url)) + Util.message("Please visit the following URL to view your CAPTCHA: {}".format(captcha_url)) if open_image: try: @@ -387,10 +385,7 @@ def handle_captcha(self, sess, payload): except Exception: pass - try: - captcha_input = raw_input("Captcha (case insensitive): ") or None - except NameError: - captcha_input = input("Captcha (case insensitive): ") or None + captcha_input = Util.get_input("Captcha (case insensitive): ") # Update the payload payload['identifier-captcha-input'] = captcha_input @@ -475,7 +470,7 @@ def handle_sk(self, sess): if attempts_remaining <= 0: break else: - input( + Util.get_input( "Insert your U2F device and press enter to try again..." ) attempts_remaining -= 1 @@ -531,7 +526,7 @@ def handle_sms(self, sess): response_page = BeautifulSoup(sess.text, 'html.parser') challenge_url = sess.url.split("?")[0] - sms_token = input("Enter SMS token: G-") or None + sms_token = Util.get_input("Enter SMS token: G-") or None payload = { 'challengeId': @@ -597,7 +592,7 @@ def handle_prompt(self, sess): self.check_prompt_code(response_page) - print("Open the Google App, and tap 'Yes' on the prompt to sign in ...") + Util.message("Open the Google App, and tap 'Yes' on the prompt to sign in ...") self.session.headers['Referer'] = sess.url @@ -673,7 +668,7 @@ def check_prompt_code(response): """ num_code = response.find("div", {"jsname": "EKvSSd"}) if num_code: - print("numerical code for prompt: {}".format(num_code.string)) + Util.message("numerical code for prompt: {}".format(num_code.string)) def handle_totp(self, sess): response_page = BeautifulSoup(sess.text, 'html.parser') @@ -682,7 +677,7 @@ def handle_totp(self, sess): challenge_url = sess.url.split("?")[0] challenge_id = challenge_url.split("totp/")[1] - mfa_token = input("MFA token: ") or None + mfa_token = Util.get_input("MFA token: ") or None if not mfa_token: raise ValueError( @@ -709,12 +704,12 @@ def handle_totp(self, sess): def handle_iap(self, sess): response_page = BeautifulSoup(sess.text, 'html.parser') challenge_url = sess.url.split("?")[0] - phone_number = input('Enter your phone number:') or None + phone_number = Util.get_input('Enter your phone number:') or None while True: try: choice = int( - input( + Util.get_input( 'Type 1 to receive a code by SMS or 2 for a voice call:' )) if choice not in [1, 2]: @@ -778,7 +773,7 @@ def handle_iap(self, sess): response_page = BeautifulSoup(sess.text, 'html.parser') challenge_url = sess.url.split("?")[0] - token = input("Enter " + send_method + " token: G-") or None + token = Util.get_input("Enter " + send_method + " token: G-") or None payload = { 'challengeId': @@ -858,12 +853,13 @@ def handle_selectchallenge(self, sess): if k in auth_methods and k not in unavailable_challenge_ids } - print('Choose MFA method from available:') - print('\n'.join( + Util.message('Choose MFA method from available:') + Util.message('\n'.join( '{}: {}'.format(*i) for i in list(auth_methods.items()))) - selected_challenge = input("Enter MFA choice number ({}): ".format( - challenge_ids[-1:][0])) or None + selected_challenge = Util.get_input( + "Enter MFA choice number ({}): ".format( + challenge_ids[-1:][0])) or None if selected_challenge is not None and int(selected_challenge) in challenge_ids: challenge_id = int(selected_challenge) @@ -871,7 +867,7 @@ def handle_selectchallenge(self, sess): # use the highest index as that will default to prompt, then sms, then totp, etc. challenge_id = challenge_ids[-1:][0] - print("MFA Type Chosen: {}".format(auth_methods[challenge_id])) + Util.message("MFA Type Chosen: {}".format(auth_methods[challenge_id])) # We need the specific form of the challenge chosen challenge_form = response_page.find( diff --git a/aws_google_auth/u2f.py b/aws_google_auth/u2f.py index 3775943..fd51a52 100644 --- a/aws_google_auth/u2f.py +++ b/aws_google_auth/u2f.py @@ -7,6 +7,8 @@ from u2flib_host import u2f, exc, appid from u2flib_host.constants import APDU_USE_NOT_SATISFIED +from aws_google_auth.util import Util + """ The facet/appID used by Google auth does not seem to be valid Need to apply some patches to u2flib_host to not validate the @@ -69,8 +71,9 @@ def u2f_auth(challenges, facet): if e.code == APDU_USE_NOT_SATISFIED: remove = False if not prompted: - print('Touch the flashing U2F device to ' - 'authenticate...') + Util.message( + 'Touch the flashing U2F device to ' + 'authenticate...') prompted = True else: pass diff --git a/aws_google_auth/util.py b/aws_google_auth/util.py index 4aacac6..a28c502 100644 --- a/aws_google_auth/util.py +++ b/aws_google_auth/util.py @@ -15,7 +15,8 @@ class Util: @staticmethod def get_input(prompt): - return input(prompt) + Util.message(prompt, end="") + return input() @staticmethod def pick_a_role(roles, aliases=None, account=None): @@ -43,18 +44,18 @@ def pick_a_role(roles, aliases=None, account=None): enriched_roles_tab.append([i + 1, role_property[0], role_property[1]]) while True: - print(tabulate(enriched_roles_tab, headers=['No', 'AWS account', 'Role'], )) + Util.message(tabulate(enriched_roles_tab, headers=['No', 'AWS account', 'Role'], )) prompt = 'Type the number (1 - {:d}) of the role to assume: '.format(len(enriched_roles)) choice = Util.get_input(prompt) try: return list(ordered_roles.items())[int(choice) - 1] except (IndexError, ValueError): - print("Invalid choice, try again.") + Util.message("Invalid choice, try again.") else: while True: for i, role in enumerate(filtered_roles): - print("[{:>3d}] {}".format(i + 1, role)) + Util.message("[{:>3d}] {}".format(i + 1, role)) prompt = 'Type the number (1 - {:d}) of the role to assume: '.format(len(filtered_roles)) choice = Util.get_input(prompt) @@ -62,7 +63,7 @@ def pick_a_role(roles, aliases=None, account=None): try: return list(filtered_roles.items())[int(choice) - 1] except (IndexError, ValueError): - print("Invalid choice, try again.") + Util.message("Invalid choice, try again.") @staticmethod def touch(file_name, mode=0o600): @@ -95,8 +96,12 @@ def get_password(prompt): if sys.stdin.isatty(): password = getpass.getpass(prompt) else: - print(prompt, end="") - sys.stdout.flush() + Util.message(prompt, end="") password = sys.stdin.readline() - print("") + Util.message("") return password + + @staticmethod + def message(*msg, **print_kwargs): + """Print a user-interaction message to stderr.""" + print(*msg, file=sys.stderr, **print_kwargs)