From 4cef7c4c2d81584e8952dfe32c375622f35e2ded Mon Sep 17 00:00:00 2001 From: ejochman <34144949+ejochman@users.noreply.github.com> Date: Fri, 1 Dec 2017 10:20:54 -0800 Subject: [PATCH 1/3] Centralize credential operations and improve separation of concerns (#633) Improve code reuse and separation of concerns around credential handling by removing duplicate code handling invalid and expired credentials. --- src/gam.py | 121 +++++++++++++++++++++++++++++------------------------ 1 file changed, 66 insertions(+), 55 deletions(-) diff --git a/src/gam.py b/src/gam.py index 1a754a18..37a7c9c6 100755 --- a/src/gam.py +++ b/src/gam.py @@ -836,6 +836,17 @@ def getOauth2TxtStorageCredentials(): except (KeyError, ValueError): return (storage, None) +def getValidOauth2TxtCredentials(): + """Gets OAuth2 credentials which are guaranteed to be fresh and valid.""" + storage, credentials = getOauth2TxtStorageCredentials() + if credentials is None or credentials.invalid: + doRequestOAuth() + credentials = storage.get() + elif credentials.access_token_expired: + http = httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL]) + credentials.refresh(http) + return credentials + def getService(api, http): api, version, api_version = getAPIVersion(api) retries = 3 @@ -873,10 +884,7 @@ def getService(api, http): def buildGAPIObject(api): GM_Globals[GM_CURRENT_API_USER] = None - storage, credentials = getOauth2TxtStorageCredentials() - if not credentials or credentials.invalid: - doRequestOAuth() - credentials = storage.get() + credentials = getValidOauth2TxtCredentials() credentials.user_agent = GAM_INFO http = credentials.authorize(httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL], cache=GM_Globals[GM_CACHE_DIR])) @@ -899,7 +907,7 @@ def buildGAPIObject(api): except KeyError: GC_Values[GC_CUSTOMER_ID] = MY_CUSTOMER else: - GC_Values[GC_DOMAIN] = credentials.id_token.get(u'hd', u'UNKNOWN').lower() + GC_Values[GC_DOMAIN] = _getValueFromOAuth(u'hd', credentials=credentials) if not GC_Values[GC_CUSTOMER_ID]: GC_Values[GC_CUSTOMER_ID] = MY_CUSTOMER return service @@ -8662,11 +8670,8 @@ def doCreateResoldCustomer(): result = callGAPI(res.customers(), u'insert', body=body, customerAuthToken=customerAuthToken, fields=u'customerId,customerDomain') print u'Created customer %s with id %s' % (result[u'customerDomain'], result[u'customerId']) -def _getValueFromOAuth(field): - storage, credentials = getOauth2TxtStorageCredentials() - if credentials is None or credentials.invalid: - doRequestOAuth() - credentials = storage.get() +def _getValueFromOAuth(field, credentials=None): + credentials = credentials if credentials is not None else getValidOauth2TxtCredentials() return credentials.id_token.get(field, u'Unknown') def doGetUserInfo(user_email=None): @@ -11277,31 +11282,26 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No return full_users def OAuthInfo(): + credentials = None if len(sys.argv) > 3: access_token = sys.argv[3] else: - storage, credentials = getOauth2TxtStorageCredentials() - if credentials is None or credentials.invalid: - doRequestOAuth() - credentials = storage.get() + credentials = getValidOauth2TxtCredentials() credentials.user_agent = GAM_INFO - http = httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL]) - if credentials.access_token_expired: - credentials.refresh(http) access_token = credentials.access_token print u"\nOAuth File: %s" % GC_Values[GC_OAUTH2_TXT] + oa2 = buildGAPIObject(u'oauth2') token_info = callGAPI(oa2, u'tokeninfo', access_token=access_token) print u"Client ID: %s" % token_info[u'issued_to'] - try: + if credentials is not None: print u"Secret: %s" % credentials.client_secret - except UnboundLocalError: - pass scopes = token_info[u'scope'].split(u' ') print u'Scopes (%s):' % len(scopes) for scope in sorted(scopes): print u' %s' % scope - print u'G Suite Admin: %s' % credentials.id_token.get(u'email', u'Unknown') + if credentials is not None: + print u'G Suite Admin: %s' % _getValueFromOAuth(u'email', credentials=credentials) def doDeleteOAuth(): _, credentials = getOauth2TxtStorageCredentials() @@ -11325,6 +11325,48 @@ def doDeleteOAuth(): stderrErrorMsg(str(e)) os.remove(GC_Values[GC_OAUTH2_TXT]) +def doRequestOAuth(login_hint=None): + storage, credentials = getOauth2TxtStorageCredentials() + if credentials is None or credentials.invalid: + http = httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL]) + flags = cmd_flags(noLocalWebserver=GC_Values[GC_NO_BROWSER]) + scopes = getScopesFromUser() + client_id, client_secret = getOAuthClientIDAndSecret() + login_hint = getValidateLoginHint(login_hint) + flow = oauth2client.client.OAuth2WebServerFlow(client_id=client_id, + client_secret=client_secret, scope=scopes, redirect_uri=oauth2client.client.OOB_CALLBACK_URN, + user_agent=GAM_INFO, response_type=u'code', login_hint=login_hint) + try: + credentials = oauth2client.tools.run_flow(flow=flow, storage=storage, flags=flags, http=http) + except httplib2.CertificateValidationUnsupported: + noPythonSSLExit() + else: + print u'It looks like you\'ve already authorized GAM. Refusing to overwrite existing file:\n\n%s' % GC_Values[GC_OAUTH2_TXT] + +def getOAuthClientIDAndSecret(): + """Retrieves the OAuth client ID and client secret from JSON.""" + MISSING_CLIENT_SECRETS_MESSAGE = u'''To use GAM you need to create an API project. Please run: + +gam create project +''' + + cs_data = readFile(GC_Values[GC_CLIENT_SECRETS_JSON], mode=u'rb', continueOnError=True, displayError=True, encoding=None) + if not cs_data: + systemErrorExit(14, MISSING_CLIENT_SECRETS_MESSAGE) + try: + cs_json = json.loads(cs_data) + client_id = cs_json[u'installed'][u'client_id'] + # chop off .apps.googleusercontent.com suffix as it's not needed + # and we need to keep things short for the Auth URL. + client_id = re.sub(r'\.apps\.googleusercontent\.com$', u'', client_id) + client_secret = cs_json[u'installed'][u'client_secret'] + except (ValueError, IndexError, KeyError): + message = (u'ERROR: the format of your client secrets file:\n\n%s\n\n is ' + 'incorrect. Please recreate the file.') + systemErrorExit(3, message) + + return (client_id, client_secret) + class cmd_flags(object): def __init__(self, noLocalWebserver): self.short_url = True @@ -11434,7 +11476,8 @@ OAUTH2_MENU += ''' OAUTH2_CMDS = [u's', u'u', u'e', u'c'] MAXIMUM_SCOPES = 28 # max of 30 - 2 for email scope always included -def doRequestOAuth(login_hint=None): +def getScopesFromUser(): + """Prompts the user to choose from a list of scopes to authorize.""" def _checkMakeScopesList(scopes): del scopes[:] for i in range(num_scopes): @@ -11454,26 +11497,6 @@ def doRequestOAuth(login_hint=None): scopes.insert(0, u'email') # Email Display Scope, always included return (True, u'') - MISSING_CLIENT_SECRETS_MESSAGE = u'''To use GAM you need to create an API project. Please run: - -gam create project -''' - - cs_data = readFile(GC_Values[GC_CLIENT_SECRETS_JSON], mode=u'rb', continueOnError=True, displayError=True, encoding=None) - if not cs_data: - systemErrorExit(14, MISSING_CLIENT_SECRETS_MESSAGE) - try: - cs_json = json.loads(cs_data) - client_id = cs_json[u'installed'][u'client_id'] - # chop off .apps.googleusercontent.com suffix as it's not needed - # and we need to keep things short for the Auth URL. - client_id = re.sub(r'\.apps\.googleusercontent\.com$', u'', client_id) - client_secret = cs_json[u'installed'][u'client_secret'] - except (ValueError, IndexError, KeyError): - print u'ERROR: the format of your client secrets file:\n\n%s\n\n is incorrect. Please recreate the file.' - sys.exit(3) - - login_hint = getValidateLoginHint(login_hint) num_scopes = len(OAUTH2_SCOPES) menu = OAUTH2_MENU % tuple(range(num_scopes)) selected_scopes = [] @@ -11535,19 +11558,7 @@ gam create project status, message = _checkMakeScopesList(scopes) if status: break - flow = oauth2client.client.OAuth2WebServerFlow(client_id=client_id, - client_secret=client_secret, scope=scopes, redirect_uri=oauth2client.client.OOB_CALLBACK_URN, - user_agent=GAM_INFO, response_type=u'code', login_hint=login_hint) - storage, credentials = getOauth2TxtStorageCredentials() - if credentials is None or credentials.invalid: - http = httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL]) - flags = cmd_flags(noLocalWebserver=GC_Values[GC_NO_BROWSER]) - try: - credentials = oauth2client.tools.run_flow(flow=flow, storage=storage, flags=flags, http=http) - except httplib2.CertificateValidationUnsupported: - noPythonSSLExit() - else: - print u'It looks like you\'ve already authorized GAM. Refusing to overwrite existing file:\n\n%s' % GC_Values[GC_OAUTH2_TXT] + return scopes def init_gam_worker(): signal.signal(signal.SIGINT, signal.SIG_IGN) From 48fa6b755e07ed368bd14d70b527df163b145131 Mon Sep 17 00:00:00 2001 From: Ross Scroggs Date: Sun, 3 Dec 2017 11:27:53 -0800 Subject: [PATCH 2/3] Various cleanups (#635) * Various small cleanups * Update GamCommands.txt --- src/GamCommands.txt | 109 +++++++++++++++++++++++++++++++------------- src/gam.py | 40 ++++++++-------- 2 files changed, 98 insertions(+), 51 deletions(-) diff --git a/src/GamCommands.txt b/src/GamCommands.txt index 8eb63724..db4913dd 100644 --- a/src/GamCommands.txt +++ b/src/GamCommands.txt @@ -14,12 +14,49 @@ Primitives ::= a single character ::= 0|1|2|3|4|5|6|7|8|9 ::= + + ::= *.+ ::= |a|b|c|d|e|f|A|B|C|D|E|F ::= an actual space character ::= a string of characters, surrounded by " if it contains spaces ::= true|on|yes|enabled|1 = false|off|no|disabled|0 - ::= googledrive|gdrive|drive|"drive and docs"|calendar + + ::= ascii|mbcs|utf-8|utf-8-sig|utf-16| + ::= + aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood| + cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan| + darkblue|darkcyan|darkgoldenrod|darkgray|darkgrey|darkgreen|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred| + darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue| + firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|grey|green|greenyellow| + honeydew|hotpink|indianred|indigo|ivory|khaki| + lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgrey|lightgreen|lightpink| + lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen| + magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue| + mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy| + oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred| + papayawhip|peachpuff|peru|pink|plum|powderblue|purple|red|rosybrown|royalblue| + saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue| + tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen + ::= + csv|html|txt|tsv|jpeg|jpg|png|svg|pdf|rtf|pptx|xlsx|docx|odt|ods|openoffice|ms|microsoft|micro$oft + ::= + ach|af|ag|ak|am|ar|az|be|bem|bg|bn|br|bs|ca|chr|ckb|co|crs|cs|cy|da|de|ee|el|en|en-gb|en-us|eo|es|es-419|et|eu| + fa|fi|fo|fr|fr-ca|fy|ga|gaa|gd|gl|gn|gu|ha|haw|he|hi|hr|ht|hu|hy|ia|id|ig|in|is|it|iw|ja|jw| + ka|kg|kk|km|kn|ko|kri|ku|ky|la|lg|ln|lo|loz|lt|lua|lv|mfe|mg|mi|mk|ml|mn|mo|mr|ms|mt|my| + ne|nl|nn|no|nso|ny|nyn|oc|om|or|pa|pcm|pl|ps|pt-br|pt-pt|qu|rm|rn|ro|ru|rw| + sd|sh|si|sk|sl|sn|so|sq|sr|sr-me|st|su|sv|sw|ta|te|tg|th|ti|tk|tl|tn|to|tr|tt|tum|tw| + ug|uk|ur|uz|vi|wo|xh|yi|yo|zh-cn|zh-hk|zh-tw|zu + ::= + gdoc|gdocument| + gdrawing| + gfolder|gdirectory| + gform| + gfusion| + gmap| + gpresentation| + gscript| + gsite| + gsheet|gspreadsheet ::= Google-Apps| Google-Chrome-Device-Management| @@ -47,9 +84,6 @@ Primitives drive16tb|16tb|googledrivestorage16tb|Google-Drive-storage-16TB| vault|googlevault|Google-Vault| vfe|googlevaultformeremployee|Google-Vault-Former-Employee - ::= ascii|mbcs|utf-8|utf-8-sig|utf-16| - ::= csv|html|txt|tsv|jpeg|jpg|png|svg|pdf|rtf|pptx|xlsx|docx|odt|ods|openoffice|ms|microsoft|micro$oft - ::= ar|bn|bg|ca|zh-CN|zh-TW|hr|cs|da|nl|en|en-GB|et|fi|fr|de|el|gu|iw|is|in|it|ja|kn|ko|lv|lt|ms|ml|mr|no|or|fa|pl|pt-BR|pt-PT|ro|ru|sr|sk|sl|es|sv|tl|ta|te|th|tr|uk|ur|vi Basic items built from primitives ::= | @@ -65,9 +99,15 @@ Basic items built from primitives ::= ::= ::= - ::= -- - ::= --(|T): -