This commit is contained in:
Jay Lee
2017-12-13 17:27:33 -05:00
2 changed files with 188 additions and 117 deletions

View File

@ -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
@ -3869,8 +3877,8 @@ def showDriveFileACL(users):
if not drive:
continue
feed = callGAPIpages(drive.permissions(), u'list', u'permissions',
fileId=fileId, fields=u'*', supportsTeamDrives=True,
useDomainAdminAccess=useDomainAdminAccess)
fileId=fileId, fields=u'*', supportsTeamDrives=True,
useDomainAdminAccess=useDomainAdminAccess)
for permission in feed:
printPermission(permission)
print u''
@ -3908,8 +3916,8 @@ def delDriveFileACL(users):
continue
print u'Removing permission for %s from %s' % (permissionId, fileId)
callGAPI(drive.permissions(), u'delete', fileId=fileId,
permissionId=permissionId, supportsTeamDrives=True,
useDomainAdminAccess=useDomainAdminAccess)
permissionId=permissionId, supportsTeamDrives=True,
useDomainAdminAccess=useDomainAdminAccess)
def addDriveFileACL(users):
fileId = sys.argv[5]
@ -7310,7 +7318,7 @@ def doGetTeamDriveInfo(users):
print u'Failed to access Drive as %s' % user
continue
result = callGAPI(drive.teamdrives(), u'get', teamDriveId=teamDriveId,
useDomainAdminAccess=useDomainAdminAccess, fields=u'*')
useDomainAdminAccess=useDomainAdminAccess, fields=u'*')
print_json(None, result)
def doCreateTeamDrive(users):
@ -7322,7 +7330,6 @@ def doCreateTeamDrive(users):
i += 2
else:
print u'ERROR: %s is not a valid argument to "gam <users> create teamdrive"' % sys.argv[i]
print sys.argv
sys.exit(3)
for user in users:
drive = buildGAPIServiceObject(u'drive3', user)
@ -7396,8 +7403,8 @@ def printShowTeamDrives(users, csvFormat):
if not drive:
continue
results = callGAPIpages(drive.teamdrives(), u'list', u'teamDrives',
useDomainAdminAccess=useDomainAdminAccess, fields=u'*',
q=q, soft_errors=True)
useDomainAdminAccess=useDomainAdminAccess, fields=u'*',
q=q, soft_errors=True)
if not results:
continue
for td in results:
@ -8662,11 +8669,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):
@ -9485,14 +9489,16 @@ def doGetASPs(users):
def doDelASP(users):
cd = buildGAPIObject(u'directory')
codeIds = sys.argv[5].lower().split(u',')
codeIdList = sys.argv[5].lower()
if codeIdList == u'all':
allCodeIds = True
else:
allCodeIds = False
codeIds = codeIdList.replace(u',', u' ').split()
for user in users:
if codeIds == [u'all']:
codeIds = []
asps = callGAPIitems(cd.asps(), u'list', u'items', userKey=user)
if asps:
for asp in asps:
codeIds.append(asp[u'codeId'])
if allCodeIds:
asps = callGAPIitems(cd.asps(), u'list', u'items', userKey=user, fields=u'items/codeId')
codeIds = [asp[u'codeId'] for asp in asps]
for codeId in codeIds:
callGAPI(cd.asps(), u'delete', userKey=user, codeId=codeId)
print u'deleted ASP %s for %s' % (codeId, user)
@ -9938,13 +9944,15 @@ USER_ARGUMENT_TO_PROPERTY_MAP = {
u'ipwhitelisted': [u'ipWhitelisted',],
u'isadmin': [u'isAdmin', u'isDelegatedAdmin',],
u'isdelegatedadmin': [u'isAdmin', u'isDelegatedAdmin',],
u'isenforcedin2sv': [u'isEnforcedIn2Sv',],
u'isenrolledin2sv': [u'isEnrolledIn2Sv',],
u'is2svenforced': [u'isEnforcedIn2Sv',],
u'is2svenrolled': [u'isEnrolledIn2Sv',],
u'ismailboxsetup': [u'isMailboxSetup',],
u'lastlogintime': [u'lastLoginTime',],
u'lastname': [u'name.familyName',],
u'location': [u'locations',],
u'locations': [u'locations'],
u'locations': [u'locations',],
u'name': [u'name.givenName', u'name.familyName', u'name.fullName',],
u'nicknames': [u'aliases', u'nonEditableAliases',],
u'noneditablealiases': [u'aliases', u'nonEditableAliases',],
@ -9991,6 +9999,8 @@ def doPrintUsers():
customFieldMask = None
sortHeaders = getGroupFeed = getLicenseFeed = email_parts = False
viewType = deleted_only = orderBy = sortOrder = None
groupDelimiter = u' '
licenseDelimiter = u','
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace(u'_', u'')
@ -10004,6 +10014,12 @@ def doPrintUsers():
sortHeaders = True
fieldsList = []
i += 1
elif myarg == u'delimiter':
groupDelimiter = licenseDelimiter = sys.argv[i+1]
i += 2
elif myarg == u'sortheaders':
sortHeaders = True
i += 1
elif myarg in [u'custom', u'schemas']:
fieldsList.append(u'customSchemas')
if sys.argv[i+1].lower() == u'all':
@ -10099,12 +10115,7 @@ def doPrintUsers():
user_email = user[u'primaryEmail']
sys.stderr.write(u"Getting Group Membership for %s (%s/%s)\r\n" % (user_email, user_count, total_users))
groups = callGAPIpages(cd.groups(), u'list', u'groups', userKey=user_email)
grouplist = u''
for groupname in groups:
grouplist += groupname[u'email']+u' '
if grouplist[-1:] == u' ':
grouplist = grouplist[:-1]
user.update(Groups=grouplist)
user[u'Groups'] = groupDelimiter.join([groupname[u'email'] for groupname in groups])
user_count += 1
if getLicenseFeed:
titles.append(u'Licenses')
@ -10113,7 +10124,7 @@ def doPrintUsers():
for user in csvRows:
u_licenses = licenses.get(user[u'primaryEmail'].lower())
if u_licenses:
user[u'Licenses'] = u','.join([_skuIdToDisplayName(skuId) for skuId in u_licenses])
user[u'Licenses'] = licenseDelimiter.join([_skuIdToDisplayName(skuId) for skuId in u_licenses])
writeCSVfile(csvRows, titles, u'Users', todrive)
GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP = {
@ -10208,6 +10219,9 @@ def doPrintGroups():
for field in GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP:
addFieldTitleToCSVfile(field, GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP, cdfieldsList, fieldsTitles, titles)
i += 1
elif myarg == u'sortheaders':
sortHeaders = True
i += 1
elif myarg == u'fields':
fieldNameList = sys.argv[i+1]
for field in fieldNameList.lower().replace(u',', u' ').split():
@ -10762,8 +10776,7 @@ def doPrintCrosActivity():
startDate = endDate = None
listLimit = 0
delimiter = u','
query = None
orgUnitPath = None
query = orgUnitPath = None
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace(u'_', u'')
@ -11296,7 +11309,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
for user in users:
if user[:4] == u'uid:':
full_users.append(user[4:])
elif user != u'*' and user.find(u'@') == -1:
elif user != u'*' and user != GC_Values[GC_CUSTOMER_ID] and user.find(u'@') == -1:
full_users.append(u'%s@%s' % (user, GC_Values[GC_DOMAIN]))
else:
full_users.append(user)
@ -11305,31 +11318,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()
@ -11353,6 +11361,50 @@ 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()
if scopes is None:
return
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
@ -11462,7 +11514,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):
@ -11482,26 +11535,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 = []
@ -11556,26 +11589,14 @@ gam create project
for i in range(num_scopes):
selected_scopes[i] = u' '
elif selection == u'e':
return
return None
break
sys.stdout.write(u'{0}Invalid input "{1}"\n'.format(ERROR_PREFIX, choice))
if selection == u'c':
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)
@ -12337,7 +12358,7 @@ def ProcessGAMCommand(args):
elif addWhat == u'teamdrive':
doCreateTeamDrive(users)
else:
print u'ERROR: %s is not a valid argument for "gam <users> add"' % addWhat
print u'ERROR: %s is not a valid argument for "gam <users> %s"' % (addWhat, command)
sys.exit(2)
elif command == u'update':
updateWhat = sys.argv[4].lower()