Clean up, small fixes (#299)

* Clean up, small fixes

* Update as per Jay's suggestions

* Add Jays'changes

# Conflicts:
#	src/gam.py
This commit is contained in:
Ross Scroggs
2016-10-23 17:33:10 -07:00
committed by Jay Lee
parent facd10d882
commit 1c92ad3d6d

View File

@ -20,21 +20,37 @@ u"""GAM is a command line tool which allows Administrators to control their Goog
With GAM you can programatically create users, turn on/off services for users like POP and Forwarding and much more.
For more information, see http://git.io/gam
"""
__author__ = u'Jay Lee <jay0lee@gmail.com>'
__version__ = u'3.72'
__license__ = u'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
import sys, os, time, datetime, random, socket, csv, platform, re, base64, string, codecs, StringIO, subprocess, collections, mimetypes
import sys
import os
import string
import time
import base64
import codecs
import collections
import csv
import datetime
from htmlentitydefs import name2codepoint
from HTMLParser import HTMLParser
import json
import httplib2
import mimetypes
import platform
import random
import re
import socket
import StringIO
import subprocess
import googleapiclient
import googleapiclient.discovery
import googleapiclient.errors
import googleapiclient.http
import httplib2
import oauth2client.client
import oauth2client.service_account
import oauth2client.file
@ -71,6 +87,7 @@ GAM_INFO = u'GAM {0} - {1} / {2} / Python {3}.{4}.{5} {6} / {7} {8} /'.format(__
sys.version_info[0], sys.version_info[1], sys.version_info[2],
sys.version_info[3],
platform.platform(), platform.machine())
GAM_RELEASES = u'https://github.com/jay0lee/GAM/releases'
GAM_WIKI = u'https://github.com/jay0lee/GAM/wiki'
GAM_WIKI_CREATE_CLIENT_SECRETS = GAM_WIKI+u'/CreatingClientSecretsFile'
GAM_ALL_RELEASES = u'https://api.github.com/repos/jay0lee/GAM/releases'
@ -355,9 +372,6 @@ def convertUTF8(data):
return type(data)(map(convertUTF8, data))
return data
from HTMLParser import HTMLParser
from htmlentitydefs import name2codepoint
class _DeHTMLParser(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
@ -749,13 +763,15 @@ def SetGlobalVariables():
return True
def doGAMCheckForUpdates(forceCheck=False):
import urllib2, calendar
import urllib2
import calendar
current_version = __version__
now_time = calendar.timegm(time.gmtime())
if forceCheck:
check_url = GAM_ALL_RELEASES # includes pre-releases
else:
last_check_time_str = readFile(GM_Globals[GM_LAST_UPDATE_CHECK_TXT], continueOnError=True, displayError=forceCheck)
last_check_time_str = readFile(GM_Globals[GM_LAST_UPDATE_CHECK_TXT], continueOnError=True, displayError=False)
last_check_time = int(last_check_time_str) if last_check_time_str and last_check_time_str.isdigit() else 0
if last_check_time > now_time-604800:
return
@ -768,7 +784,7 @@ def doGAMCheckForUpdates(forceCheck=False):
release_data = json.loads(c.read())
except ValueError:
return
if type(release_data) is list:
if isinstance(release_data, list):
release_data = release_data[0] # only care about latest release
latest_version = release_data[u'tag_name']
if latest_version[0].lower() == u'v':
@ -898,7 +914,7 @@ def checkGAPIError(e, soft_errors=False, silent_errors=False, retryOnHttpError=F
elif u'Mail service not enabled' in message:
reason = GAPI_SERVICE_NOT_AVAILABLE
except KeyError:
reason = http_status
reason = u'{0}'.format(http_status)
return (http_status, reason, message)
class GAPI_serviceNotAvailable(Exception):
@ -907,9 +923,9 @@ class GAPI_serviceNotAvailable(Exception):
def callGAPI(service, function,
silent_errors=False, soft_errors=False, throw_reasons=None, retry_reasons=None,
**kwargs):
if throw_reasons == None:
if throw_reasons is None:
throw_reasons = []
if retry_reasons == None:
if retry_reasons is None:
retry_reasons = []
method = getattr(service, function)
retries = 10
@ -947,7 +963,7 @@ def callGAPIpages(service, function, items,
page_message=None, message_attribute=None,
throw_reasons=None,
**kwargs):
if throw_reasons == None:
if throw_reasons is None:
throw_reasons = []
pageToken = None
all_pages = list()
@ -989,9 +1005,9 @@ def callGAPIpages(service, function, items,
def callGAPIitems(service, function, items,
throw_reasons=None, retry_reasons=None,
**kwargs):
if throw_reasons == None:
if throw_reasons is None:
throw_reasons = []
if retry_reasons == None:
if retry_reasons is None:
retry_reasons = []
results = callGAPI(service, function,
throw_reasons=throw_reasons, retry_reasons=retry_reasons,
@ -1221,7 +1237,7 @@ def showReport():
else:
print u'ERROR: %s is not a valid argument to "gam report"' % sys.argv[i]
sys.exit(2)
if try_date == None:
if try_date is None:
try_date = str(datetime.date.today())
if report in [u'users', u'user']:
while True:
@ -1372,6 +1388,7 @@ def gen_sha512_hash(password):
return sha512_crypt.encrypt(password, rounds=5000)
def printShowDelegates(users, csvFormat):
emailsettings = buildGAPIObject(u'email-settings')
if csvFormat:
todrive = False
csvRows = []
@ -1390,7 +1407,6 @@ def printShowDelegates(users, csvFormat):
else:
print u'ERROR: %s is not a valid argument for "gam <users> show delegates"' % sys.argv[i]
sys.exit(2)
emailsettings = buildGAPIObject(u'email-settings')
for user in users:
if user.find(u'@') == -1:
userName = user
@ -2103,7 +2119,7 @@ def doPrintShowGuardians(csvFormat):
items = u'guardianInvitations'
itemName = 'Guardian Invitations'
titles = [u'studentEmail', u'studentId', u'invitedEmailAddress', u'invitationId']
if states == None:
if states is None:
states = [u'COMPLETE', u'PENDING', u'GUARDIAN_INVITATION_STATE_UNSPECIFIED']
i += 1
elif myarg == u'states':
@ -2172,7 +2188,7 @@ def doDeleteGuardian():
def doCreateCourse():
croom = buildGAPIObject(u'classroom')
body = {}
body = {u'ownerId': u'me', u'name': u'Unknown Course'}
i = 3
while i < len(sys.argv):
if sys.argv[i].lower() == u'name':
@ -2205,10 +2221,6 @@ def doCreateCourse():
else:
print u'ERROR: %s is not a valid argument for "gam create course".' % sys.argv[i]
sys.exit(2)
if not u'ownerId' in body:
body[u'ownerId'] = u'me'
if not u'name' in body:
body[u'name'] = u'Unknown Course'
result = callGAPI(croom.courses(), u'create', body=body)
print u'Created course %s' % result[u'id']
@ -2761,7 +2773,7 @@ def doPrinterAddACL():
result = callGAPI(cp.printers(), u'share', printerid=printer, role=role, scope=scope, public=public, skip_notification=skip_notification)
checkCloudPrintResult(result)
who = scope
if who == None:
if who is None:
who = u'public'
role = u'user'
print u'Added %s %s' % (role, who)
@ -2779,7 +2791,7 @@ def doPrinterDelACL():
result = callGAPI(cp.printers(), u'unshare', printerid=printer, scope=scope, public=public)
checkCloudPrintResult(result)
who = scope
if who == None:
if who is None:
who = u'public'
print u'Removed %s' % who
@ -3071,7 +3083,7 @@ def doPrintJobSubmit():
filepath = content
content = os.path.basename(content)
mimetype = mimetypes.guess_type(filepath)[0]
if mimetype == None:
if mimetype is None:
mimetype = u'application/octet-stream'
filecontent = readFile(filepath)
form_files[u'content'] = {u'filename': content, u'content': filecontent, u'mimetype': mimetype}
@ -3129,16 +3141,16 @@ def doCalendarShowACL():
print u'Calendar: {0}, ACL: {1}{2}'.format(show_cal, formatACLRule(rule), currentCount(i, count))
def doCalendarAddACL(calendarId=None, act_as=None, role=None, scope=None, entity=None):
if act_as != None:
if act_as is not None:
act_as, cal = buildCalendarGAPIObject(act_as)
else:
cal = buildGAPIObject(u'calendar')
body = {u'scope': {}}
if calendarId == None:
if calendarId is None:
calendarId = sys.argv[2]
if calendarId.find(u'@') == -1:
calendarId = u'%s@%s' % (calendarId, GC_Values[GC_DOMAIN])
if role != None:
if role is not None:
body[u'role'] = role
else:
body[u'role'] = sys.argv[4].lower()
@ -3151,7 +3163,7 @@ def doCalendarAddACL(calendarId=None, act_as=None, role=None, scope=None, entity
body[u'role'] = u'reader'
elif body[u'role'] == u'editor':
body[u'role'] = u'writer'
if scope != None:
if scope is not None:
body[u'scope'][u'type'] = scope
else:
body[u'scope'][u'type'] = sys.argv[5].lower()
@ -3160,7 +3172,7 @@ def doCalendarAddACL(calendarId=None, act_as=None, role=None, scope=None, entity
body[u'scope'][u'type'] = u'user'
i = 5
try:
if entity != None and body[u'scope'][u'type'] != u'default':
if entity is not None and body[u'scope'][u'type'] != u'default':
body[u'scope'][u'value'] = entity
else:
body[u'scope'][u'value'] = sys.argv[i].lower()
@ -3381,7 +3393,7 @@ def doPhoto(users):
continue
else:
image_data = readFile(filename, continueOnError=True, displayError=True)
if image_data == None:
if image_data is None:
continue
image_data = base64.urlsafe_b64encode(image_data)
body = {u'photoData': image_data}
@ -3555,7 +3567,7 @@ def printDriveSettings(users):
continue
sys.stderr.write(u'Getting Drive settings for %s (%s/%s)\n' % (user, i, count))
feed = callGAPI(drive.about(), u'get', soft_errors=True)
if feed == None:
if feed is None:
continue
row = {u'email': user}
for setting in feed:
@ -4065,7 +4077,7 @@ def getDriveFileAttribute(i, body, parameters, myarg, update=False):
parameters[DFA_LOCALFILENAME] = os.path.basename(parameters[DFA_LOCALFILEPATH])
body.setdefault(u'title', parameters[DFA_LOCALFILENAME])
body[u'mimeType'] = mimetypes.guess_type(parameters[DFA_LOCALFILEPATH])[0]
if body[u'mimeType'] == None:
if body[u'mimeType'] is None:
body[u'mimeType'] = u'application/octet-stream'
parameters[DFA_LOCALMIMETYPE] = body[u'mimeType']
i += 2
@ -4123,7 +4135,7 @@ def getDriveFileAttribute(i, body, parameters, myarg, update=False):
return i
def doUpdateDriveFile(users):
fileIdSelection = {u'fileIds': None, u'query': None}
fileIdSelection = {u'fileIds': [], u'query': None}
media_body = None
operation = u'update'
body, parameters = initializeDriveFileAttributes()
@ -4246,7 +4258,7 @@ DOCUMENT_FORMATS_MAP = {
def downloadDriveFile(users):
i = 5
fileIdSelection = {u'fileIds': None, u'query': None}
fileIdSelection = {u'fileIds': [], u'query': None}
revisionId = None
exportFormatName = u'openoffice'
exportFormats = DOCUMENT_FORMATS_MAP[exportFormatName]
@ -4827,13 +4839,16 @@ def addUpdateSendAs(users, i, addCmd):
signature = readFile(filename, encoding=encoding)
else:
i = getSendAsAttributes(i, myarg, body, tagReplacements, command)
if signature != None:
if signature is not None:
if not signature:
body[u'signature'] = None
elif tagReplacements:
body[u'signature'] = _processTags(tagReplacements, signature)
else:
body[u'signature'] = signature
kwargs = {u'body': body}
if not addCmd:
kwargs[u'sendAsEmail'] = emailAddress
i = 0
count = len(users)
for user in users:
@ -4842,9 +4857,6 @@ def addUpdateSendAs(users, i, addCmd):
if not gmail:
continue
print u"Allowing %s to send as %s (%s/%s)" % (user, emailAddress, i, count)
kwargs = {u'body': body}
if not addCmd:
kwargs[u'sendAsEmail'] = emailAddress
callGAPI(gmail.users().settings().sendAs(), [u'patch', u'create'][addCmd],
soft_errors=True,
userId=u'me', **kwargs)
@ -5096,7 +5108,7 @@ def doProcessMessages(users, function):
kwargs = {}
else:
kwargs = {u'body': {}}
for my_key in body.keys():
for my_key in body:
kwargs[u'body'][my_key] = labelsToLabelIds(gmail, body[my_key])
for a_message in listResult:
i += 1
@ -5327,7 +5339,7 @@ def renameLabels(users):
if label[u'type'] == u'system':
continue
match_result = re.search(pattern, label[u'name'])
if match_result != None:
if match_result is not None:
new_label_name = replace % match_result.groups()
print u' Renaming "%s" to "%s"' % (label[u'name'], new_label_name)
try:
@ -5394,6 +5406,7 @@ FILTER_REMOVE_LABEL_TO_ARGUMENT_MAP = {
def _printFilter(user, userFilter, labels):
row = {u'User': user, u'id': userFilter[u'id']}
if u'criteria' in userFilter:
for item in userFilter[u'criteria']:
if item in [u'hasAttachment', u'excludeChats']:
row[item] = item
@ -5403,6 +5416,9 @@ def _printFilter(user, userFilter, labels):
pass
else:
row[item] = u'{0} {1}'.format(item, userFilter[u'criteria'][item])
else:
row[u'error'] = u'NoCriteria'
if u'action' in userFilter:
for labelId in userFilter[u'action'].get(u'addLabelIds', []):
if labelId in FILTER_ADD_LABEL_TO_ARGUMENT_MAP:
row[FILTER_ADD_LABEL_TO_ARGUMENT_MAP[labelId]] = FILTER_ADD_LABEL_TO_ARGUMENT_MAP[labelId]
@ -5413,11 +5429,14 @@ def _printFilter(user, userFilter, labels):
row[FILTER_REMOVE_LABEL_TO_ARGUMENT_MAP[labelId]] = FILTER_REMOVE_LABEL_TO_ARGUMENT_MAP[labelId]
if userFilter[u'action'].get(u'forward'):
row[u'forward'] = u'forward {0}'.format(userFilter[u'action'][u'forward'])
else:
row[u'error'] = u'NoActions'
return row
def _showFilter(userFilter, j, jcount, labels):
print u' Filter: {0}{1}'.format(userFilter[u'id'], currentCount(j, jcount))
print u' Criteria:'
if u'criteria' in userFilter:
for item in userFilter[u'criteria']:
if item in [u'hasAttachment', u'excludeChats']:
print u' {0}'.format(item)
@ -5427,7 +5446,10 @@ def _showFilter(userFilter, j, jcount, labels):
pass
else:
print convertUTF8(u' {0} "{1}"'.format(item, userFilter[u'criteria'][item]))
else:
print u' ERROR: No Filter criteria'
print u' Actions:'
if u'action' in userFilter:
for labelId in userFilter[u'action'].get(u'addLabelIds', []):
if labelId in FILTER_ADD_LABEL_TO_ARGUMENT_MAP:
print u' {0}'.format(FILTER_ADD_LABEL_TO_ARGUMENT_MAP[labelId])
@ -5438,6 +5460,8 @@ def _showFilter(userFilter, j, jcount, labels):
print u' {0}'.format(FILTER_REMOVE_LABEL_TO_ARGUMENT_MAP[labelId])
if userFilter[u'action'].get(u'forward'):
print u' Forwarding Address: {0}'.format(userFilter[u'action'][u'forward'])
else:
print u' ERROR: No Filter actions'
#
FILTER_CRITERIA_CHOICES_MAP = {
u'excludechats': u'excludeChats',
@ -6109,7 +6133,7 @@ def doPrintShowUserSchemas(csvFormat):
todrive = True
i += 1
else:
print u'ERROR: %s is not a valid argument for "gam <users> %s schemas"' % (myarg, [u'show', u'print'][csvFormat])
print u'ERROR: %s is not a valid argument for "gam %s schemas"' % (myarg, [u'show', u'print'][csvFormat])
sys.exit(2)
schemas = callGAPI(cd.schemas(), u'list', customerId=GC_Values[GC_CUSTOMER_ID])
if not schemas or u'schemas' not in schemas:
@ -6139,7 +6163,7 @@ def checkClearBodyList(i, body, itemName):
return False
def appendItemToBodyList(body, itemName, itemValue):
if (itemName in body) and (body[itemName] == None):
if (itemName in body) and (body[itemName] is None):
del body[itemName]
body.setdefault(itemName, [])
body[itemName].append(itemValue)
@ -7170,7 +7194,7 @@ def doWhatIs():
def doGetUserInfo(user_email=None):
cd = buildGAPIObject(u'directory')
i = 3
if user_email == None:
if user_email is None:
if len(sys.argv) > 3:
user_email = sys.argv[3]
i = 4
@ -7208,7 +7232,7 @@ def doGetUserInfo(user_email=None):
getSchemas = False
projection = u'basic'
i += 1
elif myarg == u'schemas':
elif myarg in [u'custom', u'schemas']:
getSchemas = True
projection = u'custom'
customFieldMask = sys.argv[i+1]
@ -7391,7 +7415,7 @@ def doGetGroupInfo(group_name=None):
gs = buildGAPIObject(u'groupssettings')
getAliases = getUsers = True
getGroups = False
if group_name == None:
if group_name is None:
group_name = sys.argv[3]
i = 4
else:
@ -7469,7 +7493,7 @@ def doGetGroupInfo(group_name=None):
def doGetAliasInfo(alias_email=None):
cd = buildGAPIObject(u'directory')
if alias_email == None:
if alias_email is None:
alias_email = sys.argv[3]
if alias_email.find(u'@') == -1:
alias_email = u'%s@%s' % (alias_email, GC_Values[GC_DOMAIN])
@ -7641,13 +7665,13 @@ def doGetMobileInfo():
def print_json(object_name, object_value, spacing=u''):
if object_name in [u'kind', u'etag', u'etags']:
return
if object_name != None:
if object_name is not None:
sys.stdout.write(u'%s%s: ' % (spacing, object_name))
if isinstance(object_value, list):
if len(object_value) == 1 and isinstance(object_value[0], (str, unicode, int, bool)):
sys.stdout.write(convertUTF8(u'%s\n' % object_value[0]))
return
if object_name != None:
if object_name is not None:
sys.stdout.write(u'\n')
for a_value in object_value:
if isinstance(a_value, (str, unicode, int, bool)):
@ -7656,7 +7680,7 @@ def print_json(object_name, object_value, spacing=u''):
print_json(None, a_value, u' %s' % spacing)
elif isinstance(object_value, dict):
print
if object_name != None:
if object_name is not None:
sys.stdout.write(u'\n')
for another_object in object_value:
print_json(another_object, object_value[another_object], u' %s' % spacing)
@ -7687,7 +7711,7 @@ def doUpdateNotification():
else:
print u'ERROR: %s is not a valid argument for "gam update notification"' % sys.argv[i]
sys.exit(2)
if isUnread == None:
if isUnread is None:
print u'ERROR: notifications need to be marked as read or unread.'
sys.exit(2)
if get_all:
@ -8148,7 +8172,7 @@ def doDeleteGroup():
def doDeleteAlias(alias_email=None):
cd = buildGAPIObject(u'directory')
is_user = is_group = False
if alias_email == None:
if alias_email is None:
alias_email = sys.argv[3]
if alias_email.lower() == u'user':
is_user = True
@ -8292,7 +8316,7 @@ def writeCSVfile(csvRows, titles, list_type, todrive):
webbrowser.open(file_url)
def flatten_json(structure, key=u'', path=u'', flattened=None, listLimit=None):
if flattened == None:
if flattened is None:
flattened = {}
if not isinstance(structure, (dict, list)):
flattened[((path + u'.') if path else u'') + key] = structure
@ -8394,7 +8418,7 @@ def doPrintUsers():
sortHeaders = True
fieldsList = []
i += 1
elif myarg == u'custom':
elif myarg in [u'custom', u'schemas']:
fieldsList.append(u'customSchemas')
if sys.argv[i+1].lower() == u'all':
projection = u'full'
@ -8698,7 +8722,7 @@ def doPrintGroups():
if key in [u'email', u'name', u'description', u'kind', u'etag']:
continue
setting_value = settings[key]
if setting_value == None:
if setting_value is None:
setting_value = u''
if key not in titles:
addTitleToCSVfile(key, titles)
@ -8869,7 +8893,7 @@ def doPrintGroupMembers():
if member[u'type'] == u'USER':
try:
mbinfo = callGAPI(cd.users(), u'get',
throw_reasons=[u'notFound', u'forbidden'],
throw_reasons=[u'userNotFound', u'notFound', u'forbidden'],
userKey=member[u'id'], fields=u'name')
memberName = mbinfo[u'name'][u'fullName']
except googleapiclient.errors.HttpError:
@ -9200,9 +9224,9 @@ def doPrintResourceCalendars():
def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=None, checkNotSuspended=False):
got_uids = False
if entity_type == None:
if entity_type is None:
entity_type = sys.argv[1].lower()
if entity == None:
if entity is None:
entity = sys.argv[2]
cd = buildGAPIObject(u'directory')
if entity_type == u'user':
@ -9212,7 +9236,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
elif entity_type == u'group':
got_uids = True
group = entity
if member_type == None:
if member_type is None:
member_type_message = u'all members'
else:
member_type_message = u'%ss' % member_type.lower()
@ -9650,7 +9674,8 @@ def batch_worker():
GM_Globals[GM_BATCH_QUEUE].task_done()
def run_batch(items):
import Queue, threading
import Queue
import threading
total_items = len(items)
current_item = 0
python_cmd = [sys.executable.lower(),]
@ -9940,14 +9965,14 @@ def ProcessGAMCommand(args):
sys.exit(2)
sys.exit(0)
elif command == u'print':
argument = sys.argv[2].lower()
argument = sys.argv[2].lower().replace(u'-', u'')
if argument == u'users':
doPrintUsers()
elif argument in [u'nicknames', u'aliases']:
doPrintAliases()
elif argument == u'groups':
doPrintGroups()
elif argument in [u'group-members', u'groups-members']:
elif argument in [u'groupmembers', u'groupsmembers']:
doPrintGroupMembers()
elif argument in [u'orgs', u'ous']:
doPrintOrgs()
@ -9965,7 +9990,7 @@ def ProcessGAMCommand(args):
doPrintShowUserSchemas(True)
elif argument in [u'courses', u'classes']:
doPrintCourses()
elif argument in [u'course-participants', u'class-participants']:
elif argument in [u'courseparticipants', u'classparticipants']:
doPrintCourseParticipants()
elif argument == u'printers':
doPrintPrinters()