skipped 45 lines 46 46 import sys 47 47 import os 48 48 import os.path 49 + import ipaddress 50 + import multiprocessing 51 + from socket import gethostbyname 52 + from urllib.parse import urlencode, urlparse, parse_qs 53 + import wsgiref.simple_server 54 + import wsgiref.util 49 55 import time 50 56 import calendar 51 57 import random skipped 795 lines 847 853 print(content) 848 854 return long_url 849 855 856 + def _localhost_to_ip(): 857 + '''returns IPv4 or IPv6 loopback address which localhost resolves to. 858 + If localhost does not resolve to valid loopback IP address then returns 859 + 127.0.0.1''' 860 + # TODO gethostbyname() will only ever return ipv4 861 + # find a way to support IPv6 here and get preferred IP 862 + # note that IPv6 may be broken on some systems also :-( 863 + # for now IPv4 should do. 864 + local_ip = gethostbyname('localhost') 865 + local_ipaddress = ipaddress.ip_address(local_ip) 866 + ip4_local_range = ipaddress.ip_network('127.0.0.0/8') 867 + ip6_local_range = ipaddress.ip_network('::1/128') 868 + if local_ipaddress not in ip4_local_range and \ 869 + local_ipaddress not in ip6_local_range: 870 + local_ip = '127.0.0.1' 871 + return local_ip 872 + 873 + def _wait_for_http_client(d): 874 + wsgi_app = google_auth_oauthlib.flow._RedirectWSGIApp(MESSAGE_LOCAL_SERVER_SUCCESS) 875 + wsgiref.simple_server.WSGIServer.allow_reuse_address = False 876 + # Convert hostn to IP since apparently binding to the IP 877 + # reduces odds of firewall blocking us 878 + local_ip = _localhost_to_ip() 879 + for port in range(8080, 8099): 880 + try: 881 + local_server = wsgiref.simple_server.make_server( 882 + local_ip, 883 + port, 884 + wsgi_app, 885 + handler_class=wsgiref.simple_server.WSGIRequestHandler 886 + ) 887 + break 888 + except OSError: 889 + pass 890 + redirect_uri_format = ( 891 + "http://{}:{}/" if d['trailing_slash'] else "http://{}:{}" 892 + ) 893 + # provide redirect_uri to main process so it can formulate auth_url 894 + d['redirect_uri'] = redirect_uri_format.format(*local_server.server_address) 895 + # wait until main process provides auth_url 896 + # so we can open it in web browser. 897 + while 'auth_url' not in d: 898 + time.sleep(0.1) 899 + if d['open_browser']: 900 + webbrowser.open(d['auth_url'], new=1, autoraise=True) 901 + local_server.handle_request() 902 + authorization_response = wsgi_app.last_request_uri.replace("http", "https") 903 + d['code'] = authorization_response 904 + local_server.server_close() 905 + 906 + def _wait_for_user_input(d): 907 + sys.stdin = open(0) 908 + code = input(MESSAGE_CONSOLE_AUTHORIZATION_CODE) 909 + d['code'] = code 910 + 911 + MESSAGE_CONSOLE_AUTHORIZATION_PROMPT = '''\nGo to the following link in your browser: 912 + \n\t{url}\n 913 + IMPORTANT: If you get a browser error that the site can't be reached AFTER you 914 + click the Allow button, copy the URL from the browser where the error occurred 915 + and paste that here instead. 916 + ''' 917 + MESSAGE_CONSOLE_AUTHORIZATION_CODE = 'Enter verification code or browser URL: ' 918 + MESSAGE_LOCAL_SERVER_SUCCESS = ('The authentication flow has completed. You may' 919 + ' close this browser window and return to GAM.') 920 + 921 + MESSAGE_AUTHENTICATION_COMPLETE = ('\nThe authentication flow has completed.\n') 922 + 850 923 class ShortURLFlow(google_auth_oauthlib.flow.InstalledAppFlow): 851 - def authorization_url(self, **kwargs): 852 - long_url, state = super(ShortURLFlow, self).authorization_url(**kwargs) 853 - short_url = shorten_url(long_url) 854 - return short_url, state 924 + def authorization_url(self, **kwargs): 925 + long_url, state = super(ShortURLFlow, self).authorization_url(**kwargs) 926 + short_url = shorten_url(long_url) 927 + return short_url, state 928 + 929 + 930 + def run_dual(self, 931 + use_console_flow, 932 + authorization_prompt_message='', 933 + console_prompt_message='', 934 + web_success_message='', 935 + open_browser=True, 936 + redirect_uri_trailing_slash=True, 937 + **kwargs): 938 + mgr = multiprocessing.Manager() 939 + d = mgr.dict() 940 + d['trailing_slash'] = redirect_uri_trailing_slash 941 + d['open_browser'] = use_console_flow 942 + http_client = multiprocessing.Process(target=_wait_for_http_client, 943 + args=(d,)) 944 + user_input = multiprocessing.Process(target=_wait_for_user_input, 945 + args=(d,)) 946 + http_client.start() 947 + # we need to wait until web server starts on avail port 948 + # so we know redirect_uri to use 949 + while 'redirect_uri' not in d: 950 + time.sleep(0.1) 951 + self.redirect_uri = d['redirect_uri'] 952 + d['auth_url'], _ = self.authorization_url(**kwargs) 953 + print(MESSAGE_CONSOLE_AUTHORIZATION_PROMPT.format(url=d['auth_url'])) 954 + user_input.start() 955 + userInput = False 956 + while True: 957 + time.sleep(0.1) 958 + if not http_client.is_alive(): 959 + user_input.terminate() 960 + break 961 + elif not user_input.is_alive(): 962 + userInput = True 963 + http_client.terminate() 964 + break 965 + while True: 966 + code = d['code'] 967 + if code.startswith('http'): 968 + parsed_url = urlparse(code) 969 + parsed_params = parse_qs(parsed_url.query) 970 + code = parsed_params.get('code', [None])[0] 971 + try: 972 + self.fetch_token(code=code) 973 + break 974 + except Exception as e: 975 + if not userInput: 976 + controlflow.system_error_exit(8, str(e)) 977 + display.print_error(str(e)) 978 + _wait_for_user_input(d) 979 + sys.stdout.write(MESSAGE_AUTHENTICATION_COMPLETE) 980 + return self.credentials 855 981 856 - MESSAGE_CONSOLE_AUTHORIZATION_PROMPT = '\nGo to the following link in your browser:\n\n\t{url}\n' 857 - MESSAGE_CONSOLE_AUTHORIZATION_CODE = 'Enter verification code: ' 858 - MESSAGE_LOCAL_SERVER_AUTHORIZATION_PROMPT = '\nYour browser has been opened to visit:\n\n\t{url}\n\nIf your browser is on a different machine then press CTRL+C and delete the oauthbrowser.txt file in the same folder as GYB.\n' 859 - MESSAGE_LOCAL_SERVER_SUCCESS = 'The authentication flow has completed. You may close this browser window and return to GYB.' 860 982 def _run_oauth_flow(client_id, client_secret, scopes, access_type, login_hint=None): 861 983 client_config = { 862 984 'installed': { skipped 9 lines 872 994 kwargs['login_hint'] = login_hint 873 995 # Needs to be set so oauthlib doesn't puke when Google changes our scopes 874 996 os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] = 'true' 875 - if not os.path.isfile(os.path.join(options.config_folder, 'oauthbrowser.txt')): 876 - flow.run_console( 877 - authorization_prompt_message=MESSAGE_CONSOLE_AUTHORIZATION_PROMPT, 878 - authorization_code_message=MESSAGE_CONSOLE_AUTHORIZATION_CODE, 879 - **kwargs) 880 - else: 881 - flow.run_local_server( 882 - authorization_prompt_message=MESSAGE_LOCAL_SERVER_AUTHORIZATION_PROMPT, 883 - success_message=MESSAGE_LOCAL_SERVER_SUCCESS, 884 - **kwargs) 997 + flow.run_dual(use_console_flow=True, **kwargs) 885 998 return flow.credentials 886 999 887 1000 def getCRMService(login_hint): skipped 1658 lines