Service creation, dynamic scope cleanup

Make routine getAPIversionHttpService to handle all steps to get a
service.
Make routine handleOAuthTokenError to handle OAuth token errors.
In doRequestOAuth, get admin email address from oauth2.txt if it
exists, otherwise prompt for it.
Move reading of gamscopes,json into SetGlobalVariables as a local
routine _getScopesAdminDomainFromGamScopesJson.
This commit is contained in:
Ross Scroggs
2016-01-10 09:58:21 -08:00
parent 8929ee534f
commit 0333e29eef

View File

@ -181,7 +181,7 @@ GC_Defaults = {
GC_CACHE_DIR: u'', GC_CACHE_DIR: u'',
GC_CHARSET: u'utf-8', GC_CHARSET: u'utf-8',
GC_CONFIG_DIR: u'', GC_CONFIG_DIR: u'',
GC_CUSTOMER_ID: u'my_customer', GC_CUSTOMER_ID: MY_CUSTOMER,
GC_DEBUG_LEVEL: 0, GC_DEBUG_LEVEL: 0,
GC_DEVICE_MAX_RESULTS: 500, GC_DEVICE_MAX_RESULTS: 500,
GC_DOMAIN: u'', GC_DOMAIN: u'',
@ -238,8 +238,8 @@ GC_VAR_INFO = {
} }
MESSAGE_BATCH_CSV_DASH_DEBUG_INCOMPATIBLE = u'"gam {0} - ..." is not compatible with debugging. Disable debugging by deleting debug.gam and try again.' MESSAGE_BATCH_CSV_DASH_DEBUG_INCOMPATIBLE = u'"gam {0} - ..." is not compatible with debugging. Disable debugging by deleting debug.gam and try again.'
MESSAGE_CLIENT_API_ACCESS_CONFIG = u'API access is configured in your Control Panel under: Security-Show more-Advanced settings-Manage API client access' MESSAGE_API_ACCESS_CONFIG = u'API access is configured in your Control Panel under: Security-Show more-Advanced settings-Manage API client access'
MESSAGE_CLIENT_API_ACCESS_DENIED = u'API access denied. Please make sure the Service account Client ID: {0} is authorized for the API Scope(s): {1}' MESSAGE_API_ACCESS_DENIED = u'API access denied.\n\nPlease make sure the Service account Client ID: {0} is authorized for the API Scope(s): {1}\n\nPlease make sure the Admin email address: {2} is valid'
MESSAGE_GAMSCOPES_JSON_INVALID = u'The file {0} has an invalid format.' MESSAGE_GAMSCOPES_JSON_INVALID = u'The file {0} has an invalid format.'
MESSAGE_GAM_EXITING_FOR_UPDATE = u'GAM is now exiting so that you can overwrite this old version with the latest release' MESSAGE_GAM_EXITING_FOR_UPDATE = u'GAM is now exiting so that you can overwrite this old version with the latest release'
MESSAGE_GAM_OUT_OF_MEMORY = u'GAM has run out of memory. If this is a large Google Apps instance, you should use a 64-bit version of GAM on Windows or a 64-bit version of Python on other systems.' MESSAGE_GAM_OUT_OF_MEMORY = u'GAM has run out of memory. If this is a large Google Apps instance, you should use a 64-bit version of GAM on Windows or a 64-bit version of Python on other systems.'
@ -256,6 +256,8 @@ MESSAGE_REQUEST_NOT_COMPLETE = u'Request needs to be completed before downloadin
MESSAGE_RESULTS_TOO_LARGE_FOR_GOOGLE_SPREADSHEET = u'Results are too large for Google Spreadsheets. Uploading as a regular CSV file.' MESSAGE_RESULTS_TOO_LARGE_FOR_GOOGLE_SPREADSHEET = u'Results are too large for Google Spreadsheets. Uploading as a regular CSV file.'
MESSAGE_WIKI_INSTRUCTIONS_OAUTH2SERVICE_JSON = u'Please follow the instructions at this site to setup a Service account.' MESSAGE_WIKI_INSTRUCTIONS_OAUTH2SERVICE_JSON = u'Please follow the instructions at this site to setup a Service account.'
OAUTH_TOKEN_ERRORS = [u'access_denied', u'unauthorized_client: Unauthorized client or scope in request.', u'access_denied: Requested client not authorized.', u'invalid_grant: Not a valid email.', u'invalid_request: Invalid impersonation prn email address.']
def convertUTF8(data): def convertUTF8(data):
import collections import collections
if isinstance(data, str): if isinstance(data, str):
@ -355,6 +357,9 @@ def systemErrorExit(sysRC, message):
sys.stderr.write(u'\n{0}{1}\n'.format(ERROR_PREFIX, message)) sys.stderr.write(u'\n{0}{1}\n'.format(ERROR_PREFIX, message))
sys.exit(sysRC) sys.exit(sysRC)
def badGAMScopesExit():
systemErrorExit(19, MESSAGE_GAMSCOPES_JSON_INVALID.format(GC_Values[GC_GAMSCOPES_JSON]))
def noPythonSSLExit(): def noPythonSSLExit():
systemErrorExit(8, MESSAGE_NO_PYTHON_SSL) systemErrorExit(8, MESSAGE_NO_PYTHON_SSL)
@ -416,7 +421,15 @@ def writeFile(filename, data, mode=u'wb', continueOnError=False, displayError=Tr
sys.stderr.write(u'{0}{1}\n'.format(ERROR_PREFIX, e)) sys.stderr.write(u'{0}{1}\n'.format(ERROR_PREFIX, e))
return False return False
systemErrorExit(6, e) systemErrorExit(6, e)
# Get global domain from global admin email address
# #
def getDomainFromAdmin():
if GC_Values[GC_ADMIN]:
loc = GC_Values[GC_ADMIN].find(u'@')
if loc > 0:
GC_Values[GC_DOMAIN] = GC_Values[GC_ADMIN][loc+1:]
# Set global variables # Set global variables
# Check for GAM updates based on status of noupdatecheck.txt # Check for GAM updates based on status of noupdatecheck.txt
# #
@ -440,19 +453,25 @@ def SetGlobalVariables():
def _getOldSignalFile(itemName, trueValue=True, falseValue=False): def _getOldSignalFile(itemName, trueValue=True, falseValue=False):
GC_Defaults[itemName] = trueValue if os.path.isfile(os.path.join(GC_Defaults[GC_CONFIG_DIR], GC_VAR_INFO[itemName][GC_VAR_ENVVAR_KEY])) else falseValue GC_Defaults[itemName] = trueValue if os.path.isfile(os.path.join(GC_Defaults[GC_CONFIG_DIR], GC_VAR_INFO[itemName][GC_VAR_ENVVAR_KEY])) else falseValue
def _getAdminDomainFromOldOauth2Txt(): def _getScopesAdminDomainFromGamScopesJson():
if (not GC_Defaults[GC_ADMIN]) or (not GC_Defaults[GC_DOMAIN]): GM_Globals[GM_GAMSCOPES_BY_API] = {}
srcFile = os.path.expanduser(os.environ.get(u'OAUTHFILE', u'oauth2.txt')) json_string = readFile(GC_Values[GC_GAMSCOPES_JSON], continueOnError=True, displayError=False)
if not os.path.isabs(srcFile): if json_string == None:
srcFile = os.path.expanduser(os.path.join(GC_Defaults[GC_CONFIG_DIR], srcFile)) return
if os.path.isfile(srcFile): json_data = json.loads(json_string)
json_string = readFile(srcFile, continueOnError=True, displayError=False) if not isinstance(json_data, dict):
if json_string: badGAMScopesExit()
json_data = json.loads(json_string) scopes = json_data.get(u'scopes', None)
if not GC_Defaults[GC_ADMIN]: if (not scopes) or (not isinstance(scopes, dict)):
GC_Defaults[GC_ADMIN] = json_data.get(u'id_token', {}).get(u'email', u'') badGAMScopesExit()
if not GC_Defaults[GC_DOMAIN]: for api, value in scopes.items():
GC_Defaults[GC_DOMAIN] = json_data.get(u'id_token', {}).get(u'hd', u'') if not isinstance(value, list):
badGAMScopesExit()
GM_Globals[GM_GAMSCOPES_BY_API][api] = list(set(value))
if not GC_Values[GC_ADMIN]:
GC_Values[GC_ADMIN] = json_data.get(u'admin', GC_Defaults[GC_ADMIN])
if not GC_Values[GC_DOMAIN]:
GC_Values[GC_DOMAIN] = json_data.get(u'domain', GC_Defaults[GC_DOMAIN])
def _getCfgDirectory(itemName): def _getCfgDirectory(itemName):
return GC_Defaults[itemName] return GC_Defaults[itemName]
@ -505,7 +524,6 @@ def SetGlobalVariables():
_getOldSignalFile(GC_NO_BROWSER) _getOldSignalFile(GC_NO_BROWSER)
_getOldSignalFile(GC_NO_CACHE) _getOldSignalFile(GC_NO_CACHE)
_getOldSignalFile(GC_NO_UPDATE_CHECK) _getOldSignalFile(GC_NO_UPDATE_CHECK)
_getAdminDomainFromOldOauth2Txt()
# Assign directories first # Assign directories first
for itemName in GC_VAR_INFO: for itemName in GC_VAR_INFO:
if GC_VAR_INFO[itemName][GC_VAR_TYPE_KEY] == GC_TYPE_DIRECTORY: if GC_VAR_INFO[itemName][GC_VAR_TYPE_KEY] == GC_TYPE_DIRECTORY:
@ -519,10 +537,6 @@ def SetGlobalVariables():
GM_Globals[GM_LAST_UPDATE_CHECK_TXT] = os.path.join(GC_Values[GC_CONFIG_DIR], FN_LAST_UPDATE_CHECK_TXT) GM_Globals[GM_LAST_UPDATE_CHECK_TXT] = os.path.join(GC_Values[GC_CONFIG_DIR], FN_LAST_UPDATE_CHECK_TXT)
if not GC_Values[GC_NO_UPDATE_CHECK]: if not GC_Values[GC_NO_UPDATE_CHECK]:
doGAMCheckForUpdates() doGAMCheckForUpdates()
if (not GC_Values[GC_DOMAIN]) and GC_Values[GC_ADMIN]:
loc = GC_Values[GC_ADMIN].find(u'@')
if loc > 0:
GC_Values[GC_DOMAIN] = GC_Values[GC_ADMIN][loc+1:]
# Globals derived from config file values # Globals derived from config file values
GM_Globals[GM_EXTRA_ARGS_DICT] = {u'prettyPrint': GC_Values[GC_DEBUG_LEVEL] > 0} GM_Globals[GM_EXTRA_ARGS_DICT] = {u'prettyPrint': GC_Values[GC_DEBUG_LEVEL] > 0}
httplib2.debuglevel = GC_Values[GC_DEBUG_LEVEL] httplib2.debuglevel = GC_Values[GC_DEBUG_LEVEL]
@ -535,10 +549,9 @@ def SetGlobalVariables():
GM_Globals[GM_OAUTH2SERVICE_KEY] = None GM_Globals[GM_OAUTH2SERVICE_KEY] = None
GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_EMAIL] = None GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_EMAIL] = None
GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID] = None GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID] = None
GM_Globals[GM_GAMSCOPES_BY_API] = {} _getScopesAdminDomainFromGamScopesJson()
json_string = readFile(GC_Values[GC_GAMSCOPES_JSON], continueOnError=True, displayError=False) if not GC_Values[GC_DOMAIN]:
if json_string and not validateSetGAMScopes(json.loads(json_string)): getDomainFromAdmin()
systemErrorExit(19, MESSAGE_GAMSCOPES_JSON_INVALID.format(GC_Values[GC_GAMSCOPES_JSON]))
_chkCfgDirectories() _chkCfgDirectories()
_chkCfgFiles() _chkCfgFiles()
if GC_Values[GC_NO_CACHE]: if GC_Values[GC_NO_CACHE]:
@ -595,6 +608,16 @@ def doGAMVersion():
platform.platform(), platform.machine(), platform.platform(), platform.machine(),
GM_Globals[GM_GAM_PATH]) GM_Globals[GM_GAM_PATH])
def handleOAuthTokenError(e, soft_errors):
if e.message in OAUTH_TOKEN_ERRORS:
if soft_errors:
return None
sys.stderr.write(u'{0}{1}\n'.format(ERROR_PREFIX, MESSAGE_API_ACCESS_DENIED.format(GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID],
u','.join(GM_Globals[GM_CURRENT_API_SCOPES]), GC_Values[GC_ADMIN])))
systemErrorExit(12, MESSAGE_API_ACCESS_CONFIG)
systemErrorExit(18, u'Authentication Token Error - {0}'.format(e))
def tryOAuth(gdataObject, soft_errors=False): def tryOAuth(gdataObject, soft_errors=False):
credentials = oauth2client.client.SignedJwtAssertionCredentials(GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_EMAIL], credentials = oauth2client.client.SignedJwtAssertionCredentials(GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_EMAIL],
GM_Globals[GM_OAUTH2SERVICE_KEY], GM_Globals[GM_OAUTH2SERVICE_KEY],
@ -603,21 +626,11 @@ def tryOAuth(gdataObject, soft_errors=False):
cache=GC_Values[GC_CACHE_DIR]) cache=GC_Values[GC_CACHE_DIR])
try: try:
credentials.refresh(http) credentials.refresh(http)
except httplib2.ServerNotFoundError as e:
systemErrorExit(4, e)
except oauth2client.client.AccessTokenRefreshError, e: except oauth2client.client.AccessTokenRefreshError, e:
if e.message in [u'access_denied', return handleOAuthTokenError(e, soft_errors)
u'unauthorized_client: Unauthorized client or scope in request.',
u'access_denied: Requested client not authorized.']:
sys.stderr.write(u'{0}{1}\n'.format(ERROR_PREFIX, MESSAGE_CLIENT_API_ACCESS_DENIED.format(GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID], u','.join(GM_Globals[GM_CURRENT_API_SCOPES]))))
systemErrorExit(5, MESSAGE_CLIENT_API_ACCESS_CONFIG)
sys.stderr.write(u'{0}{1}\n'.format(ERROR_PREFIX, e))
if soft_errors:
return False
sys.exit(4)
gdataObject.additional_headers = {u'Authorization': u'Bearer %s' % credentials.access_token} gdataObject.additional_headers = {u'Authorization': u'Bearer %s' % credentials.access_token}
if not GC_Values[GC_DOMAIN]:
GC_Values[GC_DOMAIN] = GC_Values[GC_ADMIN][GC_Values[GC_ADMIN].find(u'@')+1:].lower()
if not GC_Values[GC_CUSTOMER_ID]:
GC_Values[GC_CUSTOMER_ID] = MY_CUSTOMER
gdataObject.domain = GC_Values[GC_DOMAIN] gdataObject.domain = GC_Values[GC_DOMAIN]
return True return True
@ -699,6 +712,8 @@ def callGData(service, function, soft_errors=False, throw_errors=[], **kwargs):
sys.stderr.write(u' - Giving up.\n') sys.stderr.write(u' - Giving up.\n')
return None return None
sys.exit(int(e.error_code)) sys.exit(int(e.error_code))
except oauth2client.client.AccessTokenRefreshError as e:
return handleOAuthTokenError(e, soft_errors)
def callGAPI(service, function, silent_errors=False, soft_errors=False, throw_reasons=[], retry_reasons=[], **kwargs): def callGAPI(service, function, silent_errors=False, soft_errors=False, throw_reasons=[], retry_reasons=[], **kwargs):
method = getattr(service, function) method = getattr(service, function)
@ -747,8 +762,7 @@ def callGAPI(service, function, silent_errors=False, soft_errors=False, throw_re
return None return None
sys.exit(int(http_status)) sys.exit(int(http_status))
except oauth2client.client.AccessTokenRefreshError, e: except oauth2client.client.AccessTokenRefreshError, e:
sys.stderr.write(u'{0}Authentication Token Error: {1}\n'.format(ERROR_PREFIX, e)) return handleOAuthTokenError(e, False)
sys.exit(403)
except httplib2.CertificateValidationUnsupported: except httplib2.CertificateValidationUnsupported:
noPythonSSLExit() noPythonSSLExit()
except TypeError, e: except TypeError, e:
@ -810,23 +824,11 @@ API_VER_MAPPING = {
u'siteVerification': u'v1', u'siteVerification': u'v1',
} }
def getAPIVer(api): def getAPIVersion(api):
return API_VER_MAPPING.get(api, u'v1') version = API_VER_MAPPING.get(api, u'v1')
if api in [u'directory', u'reports', u'datatransfer']:
def getServiceFromDiscoveryDocument(api, version, http=None): api = u'admin'
disc_filename = u'%s-%s.json' % (api, version) return (api, version, u'{0}-{1}'.format(api, version))
disc_file = os.path.join(GC_Values[GC_SITE_DIR], disc_filename)
if hasattr(sys, '_MEIPASS'):
pyinstaller_disc_file = os.path.join(sys._MEIPASS, disc_filename)
else:
pyinstaller_disc_file = None
if os.path.isfile(disc_file):
discovery = readFile(disc_file)
elif pyinstaller_disc_file:
discovery = readFile(pyinstaller_disc_file)
else:
systemErrorExit(4, MESSAGE_NO_DISCOVERY_INFORMATION.format(disc_file))
return googleapiclient.discovery.build_from_document(discovery, base=u'https://www.googleapis.com', http=http)
def getOAuth2ServiceDetails(): def getOAuth2ServiceDetails():
if not GM_Globals[GM_OAUTH2SERVICE_KEY]: if not GM_Globals[GM_OAUTH2SERVICE_KEY]:
@ -845,38 +847,46 @@ def getOAuth2ServiceDetails():
printLine(GAM_WIKI_CREATE_CLIENT_SECRETS) printLine(GAM_WIKI_CREATE_CLIENT_SECRETS)
systemErrorExit(17, MESSAGE_OAUTH2SERVICE_JSON_INVALID.format(GC_Values[GC_OAUTH2SERVICE_JSON])) systemErrorExit(17, MESSAGE_OAUTH2SERVICE_JSON_INVALID.format(GC_Values[GC_OAUTH2SERVICE_JSON]))
def buildGAPIObject(api, act_as=None, soft_errors=False): def getAPIversionHttpService(api):
svcsub = act_as if act_as else GC_Values[GC_ADMIN]
getOAuth2ServiceDetails() getOAuth2ServiceDetails()
version = getAPIVer(api) api, version, api_version = getAPIVersion(api)
if api in [u'directory', u'reports', u'datatransfer']:
api = u'admin'
http = httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL], http = httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL],
cache=GC_Values[GC_CACHE_DIR]) cache=GC_Values[GC_CACHE_DIR])
try: try:
service = googleapiclient.discovery.build(api, version, http=http, cache_discovery=False) service = googleapiclient.discovery.build(api, version, http=http, cache_discovery=False)
except googleapiclient.errors.UnknownApiNameOrVersion: return (api_version, http, service)
service = getServiceFromDiscoveryDocument(api, version, http)
except httplib2.ServerNotFoundError as e: except httplib2.ServerNotFoundError as e:
systemErrorExit(4, e) systemErrorExit(4, e)
scopes = GM_Globals[GM_GAMSCOPES_BY_API].get(u'{0}-{1}'.format(api, version), []) except googleapiclient.errors.UnknownApiNameOrVersion:
if not scopes: pass
disc_filename = u'%s.json' % (api_version)
disc_file = os.path.join(GC_Values[GC_SITE_DIR], disc_filename)
if hasattr(sys, '_MEIPASS'):
pyinstaller_disc_file = os.path.join(sys._MEIPASS, disc_filename)
else:
pyinstaller_disc_file = None
if os.path.isfile(disc_file):
discovery = readFile(disc_file)
elif pyinstaller_disc_file:
discovery = readFile(pyinstaller_disc_file)
else:
systemErrorExit(11, MESSAGE_NO_DISCOVERY_INFORMATION.format(disc_file))
service = googleapiclient.discovery.build_from_document(discovery, http=http)
return (api_version, http, service)
def buildGAPIObject(api, act_as=None, soft_errors=False):
svcsub = act_as if act_as else GC_Values[GC_ADMIN]
api_version, http, service = getAPIversionHttpService(api)
GM_Globals[GM_CURRENT_API_SCOPES] = GM_Globals[GM_GAMSCOPES_BY_API].get(api_version, [])
if not GM_Globals[GM_CURRENT_API_SCOPES]:
systemErrorExit(15, MESSAGE_NO_SCOPES_FOR_API.format(service._rootDesc[u'title'])) systemErrorExit(15, MESSAGE_NO_SCOPES_FOR_API.format(service._rootDesc[u'title']))
credentials = oauth2client.client.SignedJwtAssertionCredentials(GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_EMAIL], credentials = oauth2client.client.SignedJwtAssertionCredentials(GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_EMAIL],
GM_Globals[GM_OAUTH2SERVICE_KEY], GM_Globals[GM_OAUTH2SERVICE_KEY],
scope=scopes, user_agent=GAM_INFO, sub=svcsub) scope=GM_Globals[GM_CURRENT_API_SCOPES], user_agent=GAM_INFO, sub=svcsub)
try: try:
service._http = credentials.authorize(http) service._http = credentials.authorize(http)
except oauth2client.client.AccessTokenRefreshError, e: except oauth2client.client.AccessTokenRefreshError, e:
if e.message in [u'access_denied', return handleOAuthTokenError(e, soft_errors)
u'unauthorized_client: Unauthorized client or scope in request.',
u'access_denied: Requested client not authorized.']:
sys.stderr.write(u'{0}{1}\n'.format(ERROR_PREFIX, MESSAGE_CLIENT_API_ACCESS_DENIED.format(GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID], u','.join(scopes))))
systemErrorExit(5, MESSAGE_CLIENT_API_ACCESS_CONFIG)
if soft_errors:
sys.stderr.write(u'{0}{1}\n'.format(ERROR_PREFIX, e))
return False
systemErrorExit(4, e)
return service return service
GDATA_API_INFO = { GDATA_API_INFO = {
@ -887,12 +897,11 @@ GDATA_API_INFO = {
def commonAppsObjInit(appsObj, api): def commonAppsObjInit(appsObj, api):
getOAuth2ServiceDetails() getOAuth2ServiceDetails()
GM_Globals[GM_CURRENT_API_SCOPES] = GM_Globals[GM_GAMSCOPES_BY_API].get(u'{0}-{1}'.format(api, getAPIVer(api)), []) api, _, api_version = getAPIVersion(api)
GM_Globals[GM_CURRENT_API_SCOPES] = GM_Globals[GM_GAMSCOPES_BY_API].get(api_version, [])
if not GM_Globals[GM_CURRENT_API_SCOPES]: if not GM_Globals[GM_CURRENT_API_SCOPES]:
systemErrorExit(15, MESSAGE_NO_SCOPES_FOR_API.format(GDATA_API_INFO[api])) systemErrorExit(15, MESSAGE_NO_SCOPES_FOR_API.format(GDATA_API_INFO[api]))
if not tryOAuth(appsObj): tryOAuth(appsObj)
doRequestOAuth()
tryOAuth(appsObj)
#Identify GAM to Google's Servers #Identify GAM to Google's Servers
appsObj.source = GAM_INFO appsObj.source = GAM_INFO
#Show debugging output if debug.gam exists #Show debugging output if debug.gam exists
@ -8720,35 +8729,14 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, return_uids=Fa
full_users = new_full_users full_users = new_full_users
return full_users return full_users
def validateSetGAMScopes(json_data):
GM_Globals[GM_GAMSCOPES_BY_API] = {}
if not isinstance(json_data, dict):
return False
for api, value in json_data.items():
if not isinstance(value, list):
return False
GM_Globals[GM_GAMSCOPES_BY_API][api] = list(set(value))
return len(GM_Globals[GM_GAMSCOPES_BY_API]) > 0
def OAuthInfo(): def OAuthInfo():
configRequired = False configRequired = False
getOAuth2ServiceDetails() print u'API Access, Admin: {0}'.format(GC_Values[GC_ADMIN])
print u'API Access'
for api in sorted(API_VER_MAPPING.keys()): for api in sorted(API_VER_MAPPING.keys()):
print u' API: {0}'.format(api) print u' API: {0}'.format(api)
version = getAPIVer(api) api_version, http, service = getAPIversionHttpService(api)
if api in [u'directory', u'reports', u'datatransfer']:
api = u'admin'
http = httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL],
cache=GC_Values[GC_CACHE_DIR])
try:
service = googleapiclient.discovery.build(api, version, http=http, cache_discovery=False)
except googleapiclient.errors.UnknownApiNameOrVersion:
service = getServiceFromDiscoveryDocument(api, version, http)
except httplib2.ServerNotFoundError as e:
systemErrorExit(4, e)
api_scopes = service._rootDesc[u'auth'][u'oauth2'][u'scopes'] api_scopes = service._rootDesc[u'auth'][u'oauth2'][u'scopes']
requested_scopes = GM_Globals[GM_GAMSCOPES_BY_API].get(u'{0}-{1}'.format(api, version), []) requested_scopes = GM_Globals[GM_GAMSCOPES_BY_API].get(api_version, [])
if requested_scopes: if requested_scopes:
for scope in requested_scopes: for scope in requested_scopes:
credentials = oauth2client.client.SignedJwtAssertionCredentials(GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_EMAIL], credentials = oauth2client.client.SignedJwtAssertionCredentials(GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_EMAIL],
@ -8759,17 +8747,15 @@ def OAuthInfo():
status = u'Authorized' status = u'Authorized'
except oauth2client.client.AccessTokenRefreshError, e: except oauth2client.client.AccessTokenRefreshError, e:
configRequired = True configRequired = True
if e.message in [u'access_denied', if e.message in OAUTH_TOKEN_ERRORS:
u'unauthorized_client: Unauthorized client or scope in request.',
u'access_denied: Requested client not authorized.']:
status = u'DENIED' status = u'DENIED'
else: else:
status = u'{0}{1}'.format(ERROR_PREFIX, e) status = u'{0}Authentication Token Error - {1}'.format(ERROR_PREFIX, e)
print u' {0}\n {1}\n Access: {2}'.format(api_scopes[scope][u'description'], scope, status) print u' {0}\n {1}\n Access: {2}'.format(api_scopes[scope][u'description'], scope, status)
else: else:
print u' Access: Not requested' print u' Access: Not requested'
if configRequired: if configRequired:
print MESSAGE_CLIENT_API_ACCESS_CONFIG print MESSAGE_API_ACCESS_CONFIG
def doDeleteOAuth(): def doDeleteOAuth():
sys.stdout.write(u'Scopes file: {0}, will be Deleted in 3...'.format(GC_Values[GC_GAMSCOPES_JSON])) sys.stdout.write(u'Scopes file: {0}, will be Deleted in 3...'.format(GC_Values[GC_GAMSCOPES_JSON]))
@ -8789,69 +8775,87 @@ def doDeleteOAuth():
except OSError as e: except OSError as e:
sys.stderr.write(u'{0}{1}\n'.format(WARNING_PREFIX, e)) sys.stderr.write(u'{0}{1}\n'.format(WARNING_PREFIX, e))
UBER_SCOPES = { EMAIL_PATTERN = re.compile(r'^(.+)@(.+\..+)$')
u'gmail-v1': [u'https://mail.google.com/'], EMAIL_FORMAT_REQUIRED = u'<Name>@<Name>.<TLD>'
}
def select_default_scopes(apis): UBER_SCOPES = {u'gmail-v1': [u'https://mail.google.com/'],}
for api_name, api in apis.items():
if api_name in UBER_SCOPES:
api[u'use_scopes'] = UBER_SCOPES[api_name]
else:
scopes = api[u'auth'][u'oauth2'][u'scopes'].keys()
scopes.sort()
api[u'use_scopes'] = []
# reduce # of scopes by checking if a scope is a substring of another
# which should mean it covers same API operations. Add a . at end
# to prevent things like directory.users removing directory.userschema
i = 0
count = len(scopes)
while i < count:
scope = scopes[i]
api[u'use_scopes'].append(scope)
i += 1
scope += u'.'
while (i < count) and scopes[i].startswith(scope):
i += 1
def getSelection(limit):
while True:
selection = raw_input(u'Your selection: ')
if selection:
if selection.isdigit():
selection = int(selection)
if (selection >= 0) and (selection <= limit):
return selection
print u'ERROR: enter number in range 0-{0}'.format(limit)
else:
print u'ERROR: please enter numbers only'
def doRequestOAuth(): def doRequestOAuth():
def _getAdminDomain():
srcFile = os.path.expanduser(os.environ.get(u'OAUTHFILE', u'oauth2.txt'))
if not os.path.isabs(srcFile):
srcFile = os.path.expanduser(os.path.join(GC_Values[GC_CONFIG_DIR], srcFile))
if os.path.isfile(srcFile):
json_string = readFile(srcFile, continueOnError=True, displayError=False)
if json_string:
json_data = json.loads(json_string)
GC_Values[GC_ADMIN] = json_data.get(u'id_token', {}).get(u'email', GC_Defaults[GC_ADMIN])
if not GC_Values[GC_DOMAIN]:
GC_Values[GC_DOMAIN] = json_data.get(u'id_token', {}).get(u'hd', GC_Defaults[GC_DOMAIN])
if GC_Values[GC_ADMIN]:
return
print u''
while True:
value = raw_input(u'Enter Admin email address: ').strip().lower()
ema = EMAIL_PATTERN.match(value)
if ema:
GC_Values[GC_ADMIN] = value
if not GC_Values[GC_DOMAIN]:
GC_Values[GC_DOMAIN] = ema.group(2)
return
print u'{0}Enter full email address: {1}'.format(ERROR_PREFIX, EMAIL_FORMAT_REQUIRED)
def _select_default_scopes(apis):
for api_name, api in apis.items():
if api_name in UBER_SCOPES:
api[u'use_scopes'] = UBER_SCOPES[api_name]
else:
scopes = sorted(api[u'auth'][u'oauth2'][u'scopes'].keys())
api[u'use_scopes'] = []
# reduce # of scopes by checking if a scope is a substring of another
# which should mean it covers same API operations. Add a . at end
# to prevent things like directory.users removing directory.userschema
i = 0
count = len(scopes)
while i < count:
scope = scopes[i]
api[u'use_scopes'].append(scope)
i += 1
scope += u'.'
while (i < count) and scopes[i].startswith(scope):
i += 1
def _getSelection(limit):
while True:
selection = raw_input(u'Your selection: ')
if selection:
if selection.isdigit():
selection = int(selection)
if (selection >= 0) and (selection <= limit):
return selection
print u'ERROR: enter number in range 0-{0}'.format(limit)
else:
print u'ERROR: please enter numbers only'
apis = API_VER_MAPPING.keys() apis = API_VER_MAPPING.keys()
all_apis = {} all_apis = {}
api_titles = {} api_titles = {}
for api in apis: for api in apis:
version = getAPIVer(api) api_version, _, service = getAPIversionHttpService(api)
if api in [u'directory', u'reports', u'datatransfer']: all_apis[api_version] = service._rootDesc
api = u'admin' api_titles[api_version] = api_version
http = httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL],
cache=GC_Values[GC_CACHE_DIR])
try:
service = googleapiclient.discovery.build(api, version, http=http, cache_discovery=False)
except googleapiclient.errors.UnknownApiNameOrVersion:
service = getServiceFromDiscoveryDocument(api, version, http)
api_name = u'%s-%s' % (api, version)
all_apis[api_name] = service._rootDesc
api_titles[api_name] = api_name
api_index = [] api_index = []
for _, api_name in sorted(api_titles.items()): for _, api_version in sorted(api_titles.items()):
api_index.append(api_name) api_index.append(api_version)
i = len(api_index) i = len(api_index)
if GM_Globals[GM_GAMSCOPES_BY_API]: if GM_Globals[GM_GAMSCOPES_BY_API]:
for api in all_apis: for api in all_apis:
all_apis[api][u'use_scopes'] = GM_Globals[GM_GAMSCOPES_BY_API][api] all_apis[api][u'use_scopes'] = GM_Globals[GM_GAMSCOPES_BY_API][api]
else: else:
select_default_scopes(all_apis) _select_default_scopes(all_apis)
if not GC_Values[GC_ADMIN]:
_getAdminDomain()
while True: while True:
#os.system([u'clear', u'cls'][GM_Globals[GM_WINDOWS]]) #os.system([u'clear', u'cls'][GM_Globals[GM_WINDOWS]])
print u'Select the APIs to use with GAM.' print u'Select the APIs to use with GAM.'
@ -8872,9 +8876,9 @@ def doRequestOAuth():
print u' %2d) Cancel' % (i+2) print u' %2d) Cancel' % (i+2)
print u' %2d) Continue' % (i+3) print u' %2d) Continue' % (i+3)
print print
selection = getSelection(i+3) selection = _getSelection(i+3)
if selection == i: # defaults if selection == i: # defaults
select_default_scopes(all_apis) _select_default_scopes(all_apis)
elif selection == i+1: # unselect all elif selection == i+1: # unselect all
for api in all_apis.keys(): for api in all_apis.keys():
all_apis[api][u'use_scopes'] = [] all_apis[api][u'use_scopes'] = []
@ -8888,7 +8892,9 @@ def doRequestOAuth():
if not selected_scopes: if not selected_scopes:
print u'YOU MUST SELECT AT LEAST ONE SCOPE' print u'YOU MUST SELECT AT LEAST ONE SCOPE'
continue continue
writeFile(GC_Values[GC_GAMSCOPES_JSON], json.dumps(GM_Globals[GM_GAMSCOPES_BY_API])) writeFile(GC_Values[GC_GAMSCOPES_JSON], json.dumps({u'scopes': GM_Globals[GM_GAMSCOPES_BY_API],
u'admin': GC_Values[GC_ADMIN],
u'domain': GC_Values[GC_DOMAIN]}))
print u'Scopes file: {0}, Created'.format(GC_Values[GC_GAMSCOPES_JSON]) print u'Scopes file: {0}, Created'.format(GC_Values[GC_GAMSCOPES_JSON])
print MESSAGE_PLEASE_AUTHORIZE_SERVICE_ACCOUNT.format(len(selected_scopes), u','.join(selected_scopes)) print MESSAGE_PLEASE_AUTHORIZE_SERVICE_ACCOUNT.format(len(selected_scopes), u','.join(selected_scopes))
return return
@ -8921,7 +8927,7 @@ def doRequestOAuth():
print u' %2d) Cancel' % (x+3) print u' %2d) Cancel' % (x+3)
print u' %2d) Back to all APIs' % (x+4) print u' %2d) Back to all APIs' % (x+4)
print print
selection = getSelection(x+4) selection = _getSelection(x+4)
if selection < x: # select if selection < x: # select
if api_scopes[selection] in all_apis[api][u'use_scopes']: if api_scopes[selection] in all_apis[api][u'use_scopes']:
all_apis[api][u'use_scopes'].remove(api_scopes[selection]) all_apis[api][u'use_scopes'].remove(api_scopes[selection])
@ -8929,7 +8935,7 @@ def doRequestOAuth():
all_apis[api][u'use_scopes'].append(api_scopes[selection]) all_apis[api][u'use_scopes'].append(api_scopes[selection])
elif selection == x: # defaults elif selection == x: # defaults
just_this_api = {api: all_apis[api]} just_this_api = {api: all_apis[api]}
select_default_scopes(just_this_api) _select_default_scopes(just_this_api)
all_apis[api][u'use_scopes'] = just_this_api[api][u'use_scopes'] all_apis[api][u'use_scopes'] = just_this_api[api][u'use_scopes']
elif selection == x+1: # read-only elif selection == x+1: # read-only
all_apis[api][u'use_scopes'] = [] all_apis[api][u'use_scopes'] = []