#!/usr/bin/env python # -*- coding: utf-8 -*- # # GAM # # Copyright 2015, LLC All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. u"""GAM is a command line tool which allows Administrators to control their Google Apps domain and accounts. 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 ' __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 json import httplib2 import googleapiclient import googleapiclient.discovery import googleapiclient.errors import googleapiclient.http import oauth2client.client import oauth2client.service_account import oauth2client.file import oauth2client.tools # Override some oauth2client.tools strings saving us a few GAM-specific mods to oauth2client oauth2client.tools._FAILED_START_MESSAGE = """ Failed to start a local webserver listening on either port 8080 or port 8090. Please check your firewall settings and locally running programs that may be blocking or using those ports. Falling back to nobrowser.txt and continuing with authorization. """ oauth2client.tools._BROWSER_OPENED_MESSAGE = """ Your browser has been opened to visit: {address} If your browser is on a different machine then press CTRL+C and create a file called nobrowser.txt in the same folder as GAM. """ oauth2client.tools._GO_TO_LINK_MESSAGE = """ Go to the following link in your browser: {address} """ GAM_URL = u'http://git.io/gam' GAM_INFO = u'GAM {0} - {1} / {2} / Python {3}.{4}.{5} {6} / {7} {8} /'.format(__version__, GAM_URL, __author__, 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_APPSPOT = u'https://gam-update.appspot.com' GAM_APPSPOT_LATEST_VERSION = GAM_APPSPOT+u'/latest-version.txt?v='+__version__ GAM_APPSPOT_LATEST_VERSION_ANNOUNCEMENT = GAM_APPSPOT+u'/latest-version-announcement.txt?v='+__version__ TRUE = u'true' FALSE = u'false' true_values = [u'on', u'yes', u'enabled', u'true', u'1'] false_values = [u'off', u'no', u'disabled', u'false', u'0'] usergroup_types = [u'user', u'users', u'group', u'ou', u'org', u'ou_and_children', u'ou_and_child', u'query', u'license', u'licenses', u'licence', u'licences', u'file', u'csv', u'all', u'cros'] ERROR = u'ERROR' ERROR_PREFIX = ERROR+u': ' WARNING = u'WARNING' WARNING_PREFIX = WARNING+u': ' DEFAULT_CHARSET = [u'mbcs', u'utf-8'][os.name != u'nt'] ONE_KILO_BYTES = 1000 ONE_MEGA_BYTES = 1000000 ONE_GIGA_BYTES = 1000000000 FN_CLIENT_SECRETS_JSON = u'client_secrets.json' FN_EXTRA_ARGS_TXT = u'extra-args.txt' FN_LAST_UPDATE_CHECK_TXT = u'lastupdatecheck.txt' FN_OAUTH2SERVICE_JSON = u'oauth2service.json' FN_OAUTH2_TXT = u'oauth2.txt' MY_CUSTOMER = u'my_customer' # # Global variables # # The following GM_XXX constants are arbitrary but must be unique # Most errors print a message and bail out with a return code # Some commands want to set a non-zero return code but not bail GM_SYSEXITRC = u'sxrc' # Path to gam GM_GAM_PATH = u'gpth' # Are we on Windows? GM_WINDOWS = u'wndo' # Encodings GM_SYS_ENCODING = u'syen' # Shared by batch_worker and run_batch GM_BATCH_QUEUE = u'batq' # Extra arguments to pass to GAPI functions GM_EXTRA_ARGS_DICT = u'exad' # Current API user GM_CURRENT_API_USER = u'capu' # Current API scope GM_CURRENT_API_SCOPES = u'scoc' # Values retrieved from oauth2service.json GM_OAUTH2SERVICE_JSON_DATA = u'oajd' GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID = u'oaci' # File containing time of last GAM update check GM_LAST_UPDATE_CHECK_TXT = u'lupc' # Dictionary mapping OrgUnit ID to Name GM_MAP_ORGUNIT_ID_TO_NAME = u'oi2n' # Dictionary mapping Role ID to Name GM_MAP_ROLE_ID_TO_NAME = u'ri2n' # Dictionary mapping Role Name to ID GM_MAP_ROLE_NAME_TO_ID = u'rn2i' # Dictionary mapping User ID to Name GM_MAP_USER_ID_TO_NAME = u'ui2n' # GM_Globals = { GM_SYSEXITRC: 0, GM_GAM_PATH: os.path.dirname(os.path.realpath(__file__)) if not getattr(sys, u'frozen', False) else os.path.dirname(sys.executable), GM_WINDOWS: os.name == u'nt', GM_SYS_ENCODING: DEFAULT_CHARSET, GM_BATCH_QUEUE: None, GM_EXTRA_ARGS_DICT: {u'prettyPrint': False}, GM_CURRENT_API_USER: None, GM_CURRENT_API_SCOPES: [], GM_OAUTH2SERVICE_JSON_DATA: None, GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID: None, GM_LAST_UPDATE_CHECK_TXT: u'', GM_MAP_ORGUNIT_ID_TO_NAME: None, GM_MAP_ROLE_ID_TO_NAME: None, GM_MAP_ROLE_NAME_TO_ID: None, GM_MAP_USER_ID_TO_NAME: None, } # # Global variables defined by environment variables/signal files # # When retrieving lists of Google Drive activities from API, how many should be retrieved in each chunk GC_ACTIVITY_MAX_RESULTS = u'activity_max_results' # Automatically generate gam batch command if number of users specified in gam users xxx command exceeds this number # Default: 0, don't automatically generate gam batch commands GC_AUTO_BATCH_MIN = u'auto_batch_min' # When processing items in batches, how many should be processed in each batch GC_BATCH_SIZE = u'batch_size' # GAM cache directory. If no_cache is specified, this variable will be set to None GC_CACHE_DIR = u'cache_dir' # Character set of batch, csv, data files GC_CHARSET = u'charset' # Path to client_secrets.json GC_CLIENT_SECRETS_JSON = u'client_secrets_json' # GAM config directory containing client_secrets.json, oauth2.txt, oauth2service.json, extra_args.txt GC_CONFIG_DIR = u'config_dir' # custmerId from gam.cfg or retrieved from Google GC_CUSTOMER_ID = u'customer_id' # If debug_level > 0: extra_args[u'prettyPrint'] = True, httplib2.debuglevel = gam_debug_level, appsObj.debug = True GC_DEBUG_LEVEL = u'debug_level' # When retrieving lists of ChromeOS/Mobile devices from API, how many should be retrieved in each chunk GC_DEVICE_MAX_RESULTS = u'device_max_results' # Domain obtained from gam.cfg or oauth2.txt GC_DOMAIN = u'domain' # Google Drive download directory GC_DRIVE_DIR = u'drive_dir' # When retrieving lists of Drive files/folders from API, how many should be retrieved in each chunk GC_DRIVE_MAX_RESULTS = u'drive_max_results' # If no_browser is False, writeCSVfile won't open a browser when todrive is set # and doRequestOAuth prints a link and waits for the verification code when oauth2.txt is being created GC_NO_BROWSER = u'no_browser' # Disable GAM API caching GC_NO_CACHE = u'no_cache' # Disable GAM update check GC_NO_UPDATE_CHECK = u'no_update_check' # Disable SSL certificate validation GC_NO_VERIFY_SSL = u'no_verify_ssl' # Number of threads for gam batch GC_NUM_THREADS = u'num_threads' # Path to oauth2.txt GC_OAUTH2_TXT = u'oauth2_txt' # Path to oauth2service.json GC_OAUTH2SERVICE_JSON = u'oauth2service_json' # Default section to use for processing GC_SECTION = u'section' # Add (n/m) to end of messages if number of items to be processed exceeds this number GC_SHOW_COUNTS_MIN = u'show_counts_min' # Enable/disable "Getting ... " messages GC_SHOW_GETTINGS = u'show_gettings' # GAM config directory containing json discovery files GC_SITE_DIR = u'site_dir' # When retrieving lists of Users from API, how many should be retrieved in each chunk GC_USER_MAX_RESULTS = u'user_max_results' GC_Defaults = { GC_ACTIVITY_MAX_RESULTS: 100, GC_AUTO_BATCH_MIN: 0, GC_BATCH_SIZE: 50, GC_CACHE_DIR: u'', GC_CHARSET: DEFAULT_CHARSET, GC_CLIENT_SECRETS_JSON: FN_CLIENT_SECRETS_JSON, GC_CONFIG_DIR: u'', GC_CUSTOMER_ID: MY_CUSTOMER, GC_DEBUG_LEVEL: 0, GC_DEVICE_MAX_RESULTS: 500, GC_DOMAIN: u'', GC_DRIVE_DIR: u'', GC_DRIVE_MAX_RESULTS: 1000, GC_NO_BROWSER: FALSE, GC_NO_CACHE: FALSE, GC_NO_UPDATE_CHECK: FALSE, GC_NO_VERIFY_SSL: FALSE, GC_NUM_THREADS: 5, GC_OAUTH2_TXT: FN_OAUTH2_TXT, GC_OAUTH2SERVICE_JSON: FN_OAUTH2SERVICE_JSON, GC_SECTION: u'', GC_SHOW_COUNTS_MIN: 0, GC_SHOW_GETTINGS: TRUE, GC_SITE_DIR: u'', GC_USER_MAX_RESULTS: 500, } GC_Values = {} GC_TYPE_BOOLEAN = u'bool' GC_TYPE_CHOICE = u'choi' GC_TYPE_DIRECTORY = u'dire' GC_TYPE_EMAIL = u'emai' GC_TYPE_FILE = u'file' GC_TYPE_INTEGER = u'inte' GC_TYPE_LANGUAGE = u'lang' GC_TYPE_STRING = u'stri' GC_VAR_TYPE = u'type' GC_VAR_LIMITS = u'lmit' GC_VAR_INFO = { GC_ACTIVITY_MAX_RESULTS: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (1, 500)}, GC_AUTO_BATCH_MIN: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (0, None)}, GC_BATCH_SIZE: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (1, 1000)}, GC_CACHE_DIR: {GC_VAR_TYPE: GC_TYPE_DIRECTORY}, GC_CHARSET: {GC_VAR_TYPE: GC_TYPE_STRING}, GC_CLIENT_SECRETS_JSON: {GC_VAR_TYPE: GC_TYPE_FILE}, GC_CONFIG_DIR: {GC_VAR_TYPE: GC_TYPE_DIRECTORY}, GC_CUSTOMER_ID: {GC_VAR_TYPE: GC_TYPE_STRING}, GC_DEBUG_LEVEL: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (0, None)}, GC_DEVICE_MAX_RESULTS: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (1, 1000)}, GC_DOMAIN: {GC_VAR_TYPE: GC_TYPE_STRING}, GC_DRIVE_DIR: {GC_VAR_TYPE: GC_TYPE_DIRECTORY}, GC_DRIVE_MAX_RESULTS: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (1, 1000)}, GC_NO_BROWSER: {GC_VAR_TYPE: GC_TYPE_BOOLEAN}, GC_NO_CACHE: {GC_VAR_TYPE: GC_TYPE_BOOLEAN}, GC_NO_UPDATE_CHECK: {GC_VAR_TYPE: GC_TYPE_BOOLEAN}, GC_NO_VERIFY_SSL: {GC_VAR_TYPE: GC_TYPE_BOOLEAN}, GC_NUM_THREADS: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (1, None)}, GC_OAUTH2_TXT: {GC_VAR_TYPE: GC_TYPE_FILE}, GC_OAUTH2SERVICE_JSON: {GC_VAR_TYPE: GC_TYPE_FILE}, GC_SECTION: {GC_VAR_TYPE: GC_TYPE_STRING}, GC_SHOW_COUNTS_MIN: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (0, None)}, GC_SHOW_GETTINGS: {GC_VAR_TYPE: GC_TYPE_BOOLEAN}, GC_SITE_DIR: {GC_VAR_TYPE: GC_TYPE_DIRECTORY}, GC_USER_MAX_RESULTS: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (1, 500)}, } # Google API constants APPLICATION_VND_GOOGLE_APPS = u'application/vnd.google-apps.' MIMETYPE_GA_DOCUMENT = APPLICATION_VND_GOOGLE_APPS+u'document' MIMETYPE_GA_DRAWING = APPLICATION_VND_GOOGLE_APPS+u'drawing' MIMETYPE_GA_FOLDER = APPLICATION_VND_GOOGLE_APPS+u'folder' MIMETYPE_GA_FORM = APPLICATION_VND_GOOGLE_APPS+u'form' MIMETYPE_GA_FUSIONTABLE = APPLICATION_VND_GOOGLE_APPS+u'fusiontable' MIMETYPE_GA_MAP = APPLICATION_VND_GOOGLE_APPS+u'map' MIMETYPE_GA_PRESENTATION = APPLICATION_VND_GOOGLE_APPS+u'presentation' MIMETYPE_GA_SCRIPT = APPLICATION_VND_GOOGLE_APPS+u'script' MIMETYPE_GA_SITES = APPLICATION_VND_GOOGLE_APPS+u'sites' MIMETYPE_GA_SPREADSHEET = APPLICATION_VND_GOOGLE_APPS+u'spreadsheet' NEVER_TIME = u'1970-01-01T00:00:00.000Z' NEVER_START_DATE = u'1970-01-01' NEVER_END_DATE = u'1969-12-31' ROLE_MANAGER = u'MANAGER' ROLE_MEMBER = u'MEMBER' ROLE_OWNER = u'OWNER' ROLE_USER = u'USER' ROLE_MANAGER_MEMBER = u','.join([ROLE_MANAGER, ROLE_MEMBER]) ROLE_MANAGER_OWNER = u','.join([ROLE_MANAGER, ROLE_OWNER]) ROLE_MANAGER_MEMBER_OWNER = u','.join([ROLE_MANAGER, ROLE_MEMBER, ROLE_OWNER]) ROLE_MEMBER_OWNER = u','.join([ROLE_MEMBER, ROLE_OWNER]) PROJECTION_CHOICES_MAP = {u'basic': u'BASIC', u'full': u'FULL',} SORTORDER_CHOICES_MAP = {u'ascending': u'ASCENDING', u'descending': u'DESCENDING',} # CLEAR_NONE_ARGUMENT = [u'clear', u'none',] # MESSAGE_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_DENIED = u'API access Denied.\n\nPlease make sure the Client ID: {0} is authorized for the API Scope(s): {1}' 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_HEADER_NOT_FOUND_IN_CSV_HEADERS = u'Header "{0}" not found in CSV headers of "{1}".' MESSAGE_HIT_CONTROL_C_TO_UPDATE = u'\n\nHit CTRL+C to visit the GAM website and download the latest release or wait 15 seconds continue with this boring old version. GAM won\'t bother you with this announcement for 1 week or you can create a file named noupdatecheck.txt in the same location as gam.py or gam.exe and GAM won\'t ever check for updates.' MESSAGE_INVALID_JSON = u'The file {0} has an invalid format.' MESSAGE_NO_DISCOVERY_INFORMATION = u'No online discovery doc and {0} does not exist locally' MESSAGE_NO_PYTHON_SSL = u'You don\'t have the Python SSL module installed so we can\'t verify SSL Certificates. You can fix this by installing the Python SSL module or you can live on the edge and turn SSL validation off by creating a file named noverifyssl.txt in the same location as gam.exe / gam.py' MESSAGE_NO_TRANSFER_LACK_OF_DISK_SPACE = u'Cowardly refusing to perform migration due to lack of target drive space. Source size: {0}mb Target Free: {1}mb' MESSAGE_REQUEST_COMPLETED_NO_FILES = u'Request completed but no results/files were returned, try requesting again' MESSAGE_REQUEST_NOT_COMPLETE = u'Request needs to be completed before downloading, current status is: {0}' MESSAGE_RESULTS_TOO_LARGE_FOR_GOOGLE_SPREADSHEET = u'Results are too large for Google Spreadsheets. Uploading as a regular CSV file.' MESSAGE_SERVICE_NOT_APPLICABLE = u'Service not applicable for this address: {0}' MESSAGE_WIKI_INSTRUCTIONS_OAUTH2SERVICE_JSON = u'Please follow the instructions at this site to setup a Service Account.' MESSAGE_OAUTH2SERVICE_JSON_INVALID = u'The file {0} is missing required keys (client_email, client_id or private_key).' # oauth errors OAUTH2_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_grant: Invalid email or User ID', u'invalid_grant: Bad Request', u'invalid_request: Invalid impersonation prn email address.', u'internal_failure: Backend Error'] # # callGAPI throw reasons GAPI_BACKEND_ERROR = u'backendError' GAPI_BAD_REQUEST = u'badRequest' GAPI_FORBIDDEN = u'forbidden' GAPI_INTERNAL_ERROR = u'internalError' GAPI_INVALID = u'invalid' GAPI_NOT_FOUND = u'notFound' GAPI_QUOTA_EXCEEDED = u'quotaExceeded' GAPI_RATE_LIMIT_EXCEEDED = u'rateLimitExceeded' GAPI_SERVICE_NOT_AVAILABLE = u'serviceNotAvailable' GAPI_USER_NOT_FOUND = u'userNotFound' GAPI_USER_RATE_LIMIT_EXCEEDED = u'userRateLimitExceeded' # GAPI_DEFAULT_RETRY_REASONS = [GAPI_QUOTA_EXCEEDED, GAPI_RATE_LIMIT_EXCEEDED, GAPI_USER_RATE_LIMIT_EXCEEDED, GAPI_BACKEND_ERROR, GAPI_INTERNAL_ERROR] GAPI_GMAIL_THROW_REASONS = [GAPI_SERVICE_NOT_AVAILABLE] GAPI_GPLUS_THROW_REASONS = [GAPI_SERVICE_NOT_AVAILABLE] def convertUTF8(data): if isinstance(data, str): return data if isinstance(data, unicode): if GM_Globals[GM_WINDOWS]: return data return data.encode(GM_Globals[GM_SYS_ENCODING]) if isinstance(data, collections.Mapping): return dict(map(convertUTF8, data.iteritems())) if isinstance(data, collections.Iterable): 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) self.__text = [] def handle_data(self, data): self.__text.append(data) def handle_charref(self, name): self.__text.append(unichr(int(name[1:], 16)) if name.startswith('x') else unichr(int(name))) def handle_entityref(self, name): cp = name2codepoint.get(name) if cp: self.__text.append(unichr(cp)) else: self.__text.append(u'&'+name) def handle_starttag(self, tag, attrs): if tag == 'p': self.__text.append('\n\n') elif tag == 'br': self.__text.append('\n') elif tag == 'a': for attr in attrs: if attr[0] == 'href': self.__text.append('({0}) '.format(attr[1])) break elif tag == 'div': if not attrs: self.__text.append('\n') elif tag in ['http:', 'https']: self.__text.append(' ({0}//{1}) '.format(tag, attrs[0][0])) def handle_startendtag(self, tag, attrs): if tag == 'br': self.__text.append('\n\n') def text(self): return re.sub(r'\n{2}\n+', '\n\n', re.sub(r'\n +', '\n', ''.join(self.__text))).strip() def dehtml(text): try: parser = _DeHTMLParser() parser.feed(text.encode(u'utf-8')) parser.close() return parser.text() except: from traceback import print_exc print_exc(file=sys.stderr) return text def indentMultiLineText(message, n=0): return message.replace(u'\n', u'\n{0}'.format(u' '*n)).rstrip() def showUsage(): doGAMVersion(checkForCheck=False) print u''' Usage: gam [OPTIONS]... GAM. Retrieve or set Google Apps domain, user, group and alias settings. Exhaustive list of commands can be found at: https://github.com/jay0lee/GAM/wiki Examples: gam info domain gam create user jsmith firstname John lastname Smith password secretpass gam update user jsmith suspended on gam.exe update group announcements add member jsmith ... ''' def formatMaxMessageBytes(maxMessageBytes): if maxMessageBytes < ONE_KILO_BYTES: return maxMessageBytes if maxMessageBytes < ONE_MEGA_BYTES: return u'{0}K'.format(maxMessageBytes / ONE_KILO_BYTES) return u'{0}M'.format(maxMessageBytes / ONE_MEGA_BYTES) def formatMilliSeconds(millis): seconds, millis = divmod(millis, 1000) minutes, seconds = divmod(seconds, 60) hours, minutes = divmod(minutes, 60) return u'%02d:%02d:%02d' % (hours, minutes, seconds) # # Error handling # def stderrErrorMsg(message): sys.stderr.write(convertUTF8(u'\n{0}{1}\n'.format(ERROR_PREFIX, message))) def stderrWarningMsg(message): sys.stderr.write(convertUTF8(u'\n{0}{1}\n'.format(WARNING_PREFIX, message))) def systemErrorExit(sysRC, message): if message: stderrErrorMsg(message) sys.exit(sysRC) def invalidJSONExit(fileName): systemErrorExit(17, MESSAGE_INVALID_JSON.format(fileName)) def noPythonSSLExit(): systemErrorExit(8, MESSAGE_NO_PYTHON_SSL) def currentCount(i, count): return u' ({0}/{1})'.format(i, count) if (count > GC_Values[GC_SHOW_COUNTS_MIN]) else u'' def currentCountNL(i, count): return u' ({0}/{1})\n'.format(i, count) if (count > GC_Values[GC_SHOW_COUNTS_MIN]) else u'\n' def entityServiceNotApplicableWarning(entityType, entityName, i, count): sys.stderr.write(u'{0}: {1}, Service not applicable/Does not exist{2}'.format(entityType, entityName, currentCountNL(i, count))) def entityDoesNotExistWarning(entityType, entityName, i, count): sys.stderr.write(u'{0}: {1}, Does not exist{2}'.format(entityType, entityName, currentCountNL(i, count))) def entityUnknownWarning(entityType, entityName, i, count): domain = getEmailAddressDomain(entityName) if (domain == GC_Values[GC_DOMAIN]) or (domain.endswith(u'google.com')): entityDoesNotExistWarning(entityType, entityName, i, count) else: entityServiceNotApplicableWarning(entityType, entityName, i, count) # Invalid CSV ~Header or ~~Header~~ def csvFieldErrorExit(fieldName, fieldNames): systemErrorExit(2, MESSAGE_HEADER_NOT_FOUND_IN_CSV_HEADERS.format(fieldName, u','.join(fieldNames))) def printLine(message): sys.stdout.write(message+u'\n') # def getCharSet(i): if (i == len(sys.argv)) or (sys.argv[i].lower() != u'charset'): return (i, GC_Values.get(GC_CHARSET, GM_Globals[GM_SYS_ENCODING])) return (i+2, sys.argv[i+1]) def getString(i, item, emptyOK=False, optional=False): if i < len(sys.argv): argstr = sys.argv[i] if argstr: return argstr if emptyOK or optional: return u'' print u'ERROR: expected a Non-empty <{0}>'.format(item) sys.exit(2) elif optional: return u'' print u'ERROR: expected a <{0}>'.format(item) sys.exit(2) YYYYMMDD_FORMAT = u'%Y-%m-%d' YYYYMMDD_FORMAT_REQUIRED = u'yyyy-mm-dd' def getYYYYMMDD(i, emptyOK=False, returnTimeStamp=False): if i < len(sys.argv): argstr = sys.argv[i].strip() if argstr: try: timeStamp = time.mktime(datetime.datetime.strptime(argstr, YYYYMMDD_FORMAT).timetuple())*1000 if not returnTimeStamp: return argstr return timeStamp except ValueError: print u'ERROR: expected a <{0}>; got {1}'.format(YYYYMMDD_FORMAT_REQUIRED, argstr) sys.exit(2) elif emptyOK: return u'' print u'ERROR: expected a <{0}>'.format(YYYYMMDD_FORMAT_REQUIRED) sys.exit(2) # Get domain from email address def getEmailAddressDomain(emailAddress): atLoc = emailAddress.find(u'@') if atLoc == -1: return GC_Values[GC_DOMAIN].lower() return emailAddress[atLoc+1:].lower() # # Normalize user/group email address/uid # uid:12345abc -> 12345abc # foo -> foo@domain # foo@ -> foo@domain # foo@bar.com -> foo@bar.com # @domain -> domain # def normalizeEmailAddressOrUID(emailAddressOrUID, noUid=False): if (not noUid) and (emailAddressOrUID.find(u':') != -1): if emailAddressOrUID[:4].lower() == u'uid:': return emailAddressOrUID[4:] if emailAddressOrUID[:3].lower() == u'id:': return emailAddressOrUID[3:] atLoc = emailAddressOrUID.find(u'@') if atLoc == 0: return emailAddressOrUID[1:].lower() if (atLoc == -1) or (atLoc == len(emailAddressOrUID)-1) and GC_Values[GC_DOMAIN]: if atLoc == -1: emailAddressOrUID = u'{0}@{1}'.format(emailAddressOrUID, GC_Values[GC_DOMAIN]) else: emailAddressOrUID = u'{0}{1}'.format(emailAddressOrUID, GC_Values[GC_DOMAIN]) return emailAddressOrUID.lower() # # Open a file # def openFile(filename, mode=u'rU'): try: if filename != u'-': return open(os.path.expanduser(filename), mode) if mode.startswith(u'r'): return StringIO.StringIO(unicode(sys.stdin.read())) return sys.stdout except IOError as e: systemErrorExit(6, e) # # Close a file # def closeFile(f): try: f.close() return True except IOError as e: stderrErrorMsg(e) return False # # Read a file # def readFile(filename, mode=u'rb', continueOnError=False, displayError=True, encoding=None): try: if filename != u'-': if not encoding: with open(os.path.expanduser(filename), mode) as f: return f.read() with codecs.open(os.path.expanduser(filename), mode, encoding) as f: content = f.read() if not content.startswith(codecs.BOM_UTF8): return content return content.replace(codecs.BOM_UTF8, u'', 1) return unicode(sys.stdin.read()) except IOError as e: if continueOnError: if displayError: stderrWarningMsg(e) return None systemErrorExit(6, e) except LookupError as e: print u'ERROR: %s' % e sys.exit(2) # # Write a file # def writeFile(filename, data, mode=u'wb', continueOnError=False, displayError=True): try: with open(os.path.expanduser(filename), mode) as f: f.write(data) return True except IOError as e: if continueOnError: if displayError: stderrErrorMsg(e) return False systemErrorExit(6, e) # class UTF8Recoder(object): """ Iterator that reads an encoded stream and reencodes the input to UTF-8 """ def __init__(self, f, encoding): self.reader = codecs.getreader(encoding)(f) def __iter__(self): return self def next(self): return self.reader.next().encode(u'utf-8') class UnicodeDictReader(object): """ A CSV reader which will iterate over lines in the CSV file "f", which is encoded in the given encoding. """ def __init__(self, f, dialect=csv.excel, encoding=u'utf-8', **kwds): self.encoding = encoding try: self.reader = csv.reader(UTF8Recoder(f, encoding) if self.encoding != u'utf-8' else f, dialect=dialect, **kwds) self.fieldnames = self.reader.next() if len(self.fieldnames) > 0 and self.fieldnames[0].startswith(codecs.BOM_UTF8): self.fieldnames[0] = self.fieldnames[0].replace(codecs.BOM_UTF8, '', 1) except (csv.Error, StopIteration): self.fieldnames = [] except LookupError as e: print u'ERROR: %s' % e sys.exit(2) self.numfields = len(self.fieldnames) def __iter__(self): return self def next(self): row = self.reader.next() l = len(row) if l < self.numfields: row += ['']*(self.numfields-l) # Must be '', not u'' return dict((self.fieldnames[x], unicode(row[x], u'utf-8')) for x in range(self.numfields)) # # Set global variables # Check for GAM updates based on status of noupdatecheck.txt # def SetGlobalVariables(): def _getOldEnvVar(itemName, envVar): value = os.environ.get(envVar, GC_Defaults[itemName]) if GC_VAR_INFO[itemName][GC_VAR_TYPE] == GC_TYPE_INTEGER: try: number = int(value) minVal, maxVal = GC_VAR_INFO[itemName][GC_VAR_LIMITS] if number < minVal: number = minVal elif maxVal and (number > maxVal): number = maxVal except ValueError: number = GC_Defaults[itemName] value = number GC_Defaults[itemName] = value def _getOldSignalFile(itemName, fileName, trueValue=True, falseValue=False): GC_Defaults[itemName] = trueValue if os.path.isfile(os.path.join(GC_Defaults[GC_CONFIG_DIR], fileName)) else falseValue def _getCfgDirectory(itemName): return GC_Defaults[itemName] def _getCfgFile(itemName): value = os.path.expanduser(GC_Defaults[itemName]) if not os.path.isabs(value): value = os.path.expanduser(os.path.join(GC_Values[GC_CONFIG_DIR], value)) return value GC_Defaults[GC_CONFIG_DIR] = GM_Globals[GM_GAM_PATH] GC_Defaults[GC_CACHE_DIR] = os.path.join(GM_Globals[GM_GAM_PATH], u'gamcache') GC_Defaults[GC_DRIVE_DIR] = GM_Globals[GM_GAM_PATH] GC_Defaults[GC_SITE_DIR] = GM_Globals[GM_GAM_PATH] _getOldEnvVar(GC_CONFIG_DIR, u'GAMUSERCONFIGDIR') _getOldEnvVar(GC_SITE_DIR, u'GAMSITECONFIGDIR') _getOldEnvVar(GC_CACHE_DIR, u'GAMCACHEDIR') _getOldEnvVar(GC_DRIVE_DIR, u'GAMDRIVEDIR') _getOldEnvVar(GC_OAUTH2_TXT, u'OAUTHFILE') _getOldEnvVar(GC_OAUTH2SERVICE_JSON, u'OAUTHSERVICEFILE') if GC_Defaults[GC_OAUTH2SERVICE_JSON].find(u'.') == -1: GC_Defaults[GC_OAUTH2SERVICE_JSON] += u'.json' _getOldEnvVar(GC_CLIENT_SECRETS_JSON, u'CLIENTSECRETS') _getOldEnvVar(GC_DOMAIN, u'GA_DOMAIN') _getOldEnvVar(GC_CUSTOMER_ID, u'CUSTOMER_ID') _getOldEnvVar(GC_CHARSET, u'GAM_CHARSET') _getOldEnvVar(GC_NUM_THREADS, u'GAM_THREADS') _getOldEnvVar(GC_AUTO_BATCH_MIN, u'GAM_AUTOBATCH') _getOldEnvVar(GC_ACTIVITY_MAX_RESULTS, u'GAM_ACTIVITY_MAX_RESULTS') _getOldEnvVar(GC_DEVICE_MAX_RESULTS, u'GAM_DEVICE_MAX_RESULTS') _getOldEnvVar(GC_DRIVE_MAX_RESULTS, u'GAM_DRIVE_MAX_RESULTS') _getOldEnvVar(GC_USER_MAX_RESULTS, u'GAM_USER_MAX_RESULTS') _getOldSignalFile(GC_DEBUG_LEVEL, u'debug.gam', trueValue=4, falseValue=0) _getOldSignalFile(GC_NO_VERIFY_SSL, u'noverifyssl.txt') _getOldSignalFile(GC_NO_BROWSER, u'nobrowser.txt') _getOldSignalFile(GC_NO_CACHE, u'nocache.txt') _getOldSignalFile(GC_NO_UPDATE_CHECK, u'noupdatecheck.txt') # Assign directories first for itemName in GC_VAR_INFO: if GC_VAR_INFO[itemName][GC_VAR_TYPE] == GC_TYPE_DIRECTORY: GC_Values[itemName] = _getCfgDirectory(itemName) for itemName in GC_VAR_INFO: varType = GC_VAR_INFO[itemName][GC_VAR_TYPE] if varType == GC_TYPE_FILE: GC_Values[itemName] = _getCfgFile(itemName) else: GC_Values[itemName] = GC_Defaults[itemName] 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]: doGAMCheckForUpdates() # Globals derived from config file values GM_Globals[GM_OAUTH2SERVICE_JSON_DATA] = None GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID] = None GM_Globals[GM_EXTRA_ARGS_DICT] = {u'prettyPrint': GC_Values[GC_DEBUG_LEVEL] > 0} httplib2.debuglevel = GC_Values[GC_DEBUG_LEVEL] if os.path.isfile(os.path.join(GC_Values[GC_CONFIG_DIR], FN_EXTRA_ARGS_TXT)): import ConfigParser ea_config = ConfigParser.ConfigParser() ea_config.optionxform = str ea_config.read(os.path.join(GC_Values[GC_CONFIG_DIR], FN_EXTRA_ARGS_TXT)) GM_Globals[GM_EXTRA_ARGS_DICT].update(dict(ea_config.items(u'extra-args'))) if GC_Values[GC_NO_CACHE]: GC_Values[GC_CACHE_DIR] = None return True def doGAMCheckForUpdates(forceCheck=False): import urllib2, calendar current_version = __version__ now_time = calendar.timegm(time.gmtime()) if not forceCheck: last_check_time_str = readFile(GM_Globals[GM_LAST_UPDATE_CHECK_TXT], continueOnError=True, displayError=forceCheck) 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 try: c = urllib2.urlopen(GAM_APPSPOT_LATEST_VERSION) latest_version = c.read().strip() if forceCheck or (latest_version > current_version): print u'Version: Check, Current: {0}, Latest: {1}'.format(current_version, latest_version) if latest_version <= current_version: writeFile(GM_Globals[GM_LAST_UPDATE_CHECK_TXT], str(now_time), continueOnError=True, displayError=forceCheck) return a = urllib2.urlopen(GAM_APPSPOT_LATEST_VERSION_ANNOUNCEMENT) announcement = a.read() sys.stderr.write(announcement) try: printLine(MESSAGE_HIT_CONTROL_C_TO_UPDATE) time.sleep(15) except KeyboardInterrupt: import webbrowser webbrowser.open(GAM_RELEASES) printLine(MESSAGE_GAM_EXITING_FOR_UPDATE) sys.exit(0) writeFile(GM_Globals[GM_LAST_UPDATE_CHECK_TXT], str(now_time), continueOnError=True, displayError=forceCheck) return except (urllib2.HTTPError, urllib2.URLError): return def doGAMVersion(checkForCheck=True): import struct print u'GAM {0} - {1}\n{2}\nPython {3}.{4}.{5} {6}-bit {7}\ngoogle-api-python-client {8}\n{9} {10}\nPath: {11}'.format(__version__, GAM_URL, __author__, sys.version_info[0], sys.version_info[1], sys.version_info[2], struct.calcsize(u'P')*8, sys.version_info[3], googleapiclient.__version__, platform.platform(), platform.machine(), GM_Globals[GM_GAM_PATH]) if checkForCheck: i = 2 while i < len(sys.argv): myarg = sys.argv[i].lower().replace(u'_', u'') if myarg == u'check': doGAMCheckForUpdates(forceCheck=True) i += 1 else: print u'ERROR: %s is not a valid argument for "gam version"' % sys.argv[i] sys.exit(2) def handleOAuthTokenError(e, soft_errors): if e.message in OAUTH2_TOKEN_ERRORS: if soft_errors: return None if not GM_Globals[GM_CURRENT_API_USER]: stderrErrorMsg(MESSAGE_API_ACCESS_DENIED.format(GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID], u','.join(GM_Globals[GM_CURRENT_API_SCOPES]))) systemErrorExit(12, MESSAGE_API_ACCESS_CONFIG) else: systemErrorExit(19, MESSAGE_SERVICE_NOT_APPLICABLE.format(GM_Globals[GM_CURRENT_API_USER])) systemErrorExit(18, u'Authentication Token Error - {0}'.format(e)) def getSvcAcctCredentials(scopes, act_as): try: if not GM_Globals[GM_OAUTH2SERVICE_JSON_DATA]: json_string = readFile(GC_Values[GC_OAUTH2SERVICE_JSON], continueOnError=True, displayError=True) if not json_string: printLine(MESSAGE_WIKI_INSTRUCTIONS_OAUTH2SERVICE_JSON) printLine(GAM_WIKI_CREATE_CLIENT_SECRETS) systemErrorExit(6, None) GM_Globals[GM_OAUTH2SERVICE_JSON_DATA] = json.loads(json_string) credentials = oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_dict(GM_Globals[GM_OAUTH2SERVICE_JSON_DATA], scopes) credentials = credentials.create_delegated(act_as) credentials.user_agent = GAM_INFO serialization_data = credentials.serialization_data GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID] = serialization_data[u'client_id'] return credentials except (ValueError, KeyError): printLine(MESSAGE_WIKI_INSTRUCTIONS_OAUTH2SERVICE_JSON) printLine(GAM_WIKI_CREATE_CLIENT_SECRETS) invalidJSONExit(GC_Values[GC_OAUTH2SERVICE_JSON]) def waitOnFailure(n, retries, errMsg): wait_on_fail = min(2 ** n, 60) + float(random.randint(1, 1000)) / 1000 if n > 3: sys.stderr.write(u'Temp error {0}. Backing off {1} seconds...'.format(errMsg, int(wait_on_fail))) time.sleep(wait_on_fail) if n > 3: sys.stderr.write(u'attempt {0}/{1}\n'.format(n+1, retries)) def checkGAPIError(e, soft_errors=False, silent_errors=False, retryOnHttpError=False, service=None): try: error = json.loads(e.content) except ValueError: if (e.resp[u'status'] == u'503') and (e.content == u'Quota exceeded for the current request'): return (e.resp[u'status'], GAPI_QUOTA_EXCEEDED, e.content) if retryOnHttpError: service._http.request.credentials.refresh(httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL])) return (-1, None, None) if soft_errors: if not silent_errors: stderrErrorMsg(e.content) return (0, None, None) systemErrorExit(5, e.content) if u'error' in error: http_status = error[u'error'][u'code'] try: message = error[u'error'][u'errors'][0][u'message'] except KeyError: message = error[u'error'][u'message'] else: if u'error_description' in error: if error[u'error_description'] == u'Invalid Value': message = error[u'error_description'] http_status = 400 error = {u'error': {u'errors': [{u'reason': GAPI_INVALID, u'message': message}]}} else: systemErrorExit(4, str(error)) else: systemErrorExit(4, str(error)) try: reason = error[u'error'][u'errors'][0][u'reason'] if reason == u'notFound': if u'userKey' in message: reason = GAPI_USER_NOT_FOUND elif reason == u'invalid': if u'userId' in message: reason = GAPI_USER_NOT_FOUND elif reason == u'failedPrecondition': if u'Bad Request' in message: reason = GAPI_BAD_REQUEST elif u'Mail service not enabled' in message: reason = GAPI_SERVICE_NOT_AVAILABLE except KeyError: reason = http_status return (http_status, reason, message) class GAPI_serviceNotAvailable(Exception): pass def callGAPI(service, function, silent_errors=False, soft_errors=False, throw_reasons=None, retry_reasons=None, **kwargs): if throw_reasons == None: throw_reasons = [] if retry_reasons == None: retry_reasons = [] method = getattr(service, function) retries = 10 parameters = dict(kwargs.items() + GM_Globals[GM_EXTRA_ARGS_DICT].items()) for n in range(1, retries+1): try: return method(**parameters).execute() except googleapiclient.errors.HttpError as e: http_status, reason, message = checkGAPIError(e, soft_errors=soft_errors, silent_errors=silent_errors, retryOnHttpError=n < 3, service=service) if http_status == -1: continue if http_status == 0: return None if reason in throw_reasons: raise e if (n != retries) and (reason in GAPI_DEFAULT_RETRY_REASONS+retry_reasons): waitOnFailure(n, retries, reason) continue if soft_errors: stderrErrorMsg(u'{0}: {1} - {2}{3}'.format(http_status, message, reason, u': Giving up.\n' if n > 1 else u'')) return None systemErrorExit(int(http_status), u'{0}: {1} - {2}'.format(http_status, message, reason)) except oauth2client.client.AccessTokenRefreshError as e: handleOAuthTokenError(e, soft_errors or GAPI_SERVICE_NOT_AVAILABLE in throw_reasons) if GAPI_SERVICE_NOT_AVAILABLE in throw_reasons: raise GAPI_serviceNotAvailable(e.message) entityUnknownWarning(u'User', GM_Globals[GM_CURRENT_API_USER], 0, 0) return None except httplib2.CertificateValidationUnsupported: noPythonSSLExit() except TypeError as e: systemErrorExit(4, e) def callGAPIpages(service, function, items, page_message=None, message_attribute=None, throw_reasons=None, **kwargs): if throw_reasons == None: throw_reasons = [] pageToken = None all_pages = list() total_items = 0 while True: this_page = callGAPI(service, function, throw_reasons=throw_reasons, pageToken=pageToken, **kwargs) if this_page: pageToken = this_page.get(u'nextPageToken') if items in this_page: page_items = len(this_page[items]) total_items += page_items all_pages.extend(this_page[items]) else: this_page = {items: []} page_items = 0 else: pageToken = None this_page = {items: []} page_items = 0 if page_message: show_message = page_message.replace(u'%%num_items%%', str(page_items)) show_message = show_message.replace(u'%%total_items%%', str(total_items)) if message_attribute: try: show_message = show_message.replace(u'%%first_item%%', str(this_page[items][0][message_attribute])) show_message = show_message.replace(u'%%last_item%%', str(this_page[items][-1][message_attribute])) except (IndexError, KeyError): show_message = show_message.replace(u'%%first_item%%', u'') show_message = show_message.replace(u'%%last_item%%', u'') sys.stderr.write(u'\r') sys.stderr.flush() sys.stderr.write(show_message) if not pageToken: if page_message and (page_message[-1] != u'\n'): sys.stderr.write(u'\r\n') sys.stderr.flush() return all_pages def callGAPIitems(service, function, items, throw_reasons=None, retry_reasons=None, **kwargs): if throw_reasons == None: throw_reasons = [] if retry_reasons == None: retry_reasons = [] results = callGAPI(service, function, throw_reasons=throw_reasons, retry_reasons=retry_reasons, **kwargs) if results: return results.get(items, []) return [] API_VER_MAPPING = { u'appsactivity': u'v1', u'calendar': u'v3', u'classroom': u'v1', u'cloudprint': u'v2', u'datatransfer': u'datatransfer_v1', u'directory': u'directory_v1', u'drive': u'v2', u'email-audit': u'v1', u'email-settings': u'v2', u'gmail': u'v1', u'groupssettings': u'v1', u'licensing': u'v1', u'oauth2': u'v2', u'plus': u'v1', u'reports': u'reports_v1', u'siteVerification': u'v1', } def getAPIVersion(api): version = API_VER_MAPPING.get(api, u'v1') if api in [u'directory', u'reports', u'datatransfer']: api = u'admin' return (api, version, u'{0}-{1}'.format(api, version)) def readDiscoveryFile(api_version): disc_filename = u'%s.json' % (api_version) disc_file = os.path.join(GM_Globals[GM_GAM_PATH], disc_filename) if hasattr(sys, u'_MEIPASS'): pyinstaller_disc_file = os.path.join(sys._MEIPASS, disc_filename) else: pyinstaller_disc_file = None if os.path.isfile(disc_file): json_string = readFile(disc_file) elif pyinstaller_disc_file: json_string = readFile(pyinstaller_disc_file) else: systemErrorExit(11, MESSAGE_NO_DISCOVERY_INFORMATION.format(disc_file)) try: discovery = json.loads(json_string) return (disc_file, discovery) except ValueError: invalidJSONExit(disc_file) def getClientAPIversionHttpService(api): storage = oauth2client.file.Storage(GC_Values[GC_OAUTH2_TXT]) credentials = storage.get() if not credentials or credentials.invalid: doRequestOAuth() credentials = storage.get() credentials.user_agent = GAM_INFO api, version, api_version = getAPIVersion(api) http = credentials.authorize(httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL], cache=GC_Values[GC_CACHE_DIR])) try: return (credentials, googleapiclient.discovery.build(api, version, http=http, cache_discovery=False)) except httplib2.ServerNotFoundError as e: systemErrorExit(4, e) except httplib2.CertificateValidationUnsupported: noPythonSSLExit() except googleapiclient.errors.UnknownApiNameOrVersion: pass disc_file, discovery = readDiscoveryFile(api_version) try: return (credentials, googleapiclient.discovery.build_from_document(discovery, http=http)) except (ValueError, KeyError): invalidJSONExit(disc_file) def buildGAPIObject(api): GM_Globals[GM_CURRENT_API_USER] = None credentials, service = getClientAPIversionHttpService(api) if GC_Values[GC_DOMAIN]: if not GC_Values[GC_CUSTOMER_ID]: resp, result = service._http.request(u'https://www.googleapis.com/admin/directory/v1/users?domain={0}&maxResults=1&fields=users(customerId)'.format(GC_Values[GC_DOMAIN])) try: resultObj = json.loads(result) except ValueError: systemErrorExit(8, u'Unexpected response: {0}'.format(result)) if resp[u'status'] in [u'403', u'404']: try: message = resultObj[u'error'][u'errors'][0][u'message'] except KeyError: message = resultObj[u'error'][u'message'] systemErrorExit(8, u'{0} - {1}'.format(message, GC_Values[GC_DOMAIN])) try: GC_Values[GC_CUSTOMER_ID] = resultObj[u'users'][0][u'customerId'] except KeyError: GC_Values[GC_CUSTOMER_ID] = MY_CUSTOMER else: GC_Values[GC_DOMAIN] = credentials.id_token.get(u'hd', u'UNKNOWN').lower() if not GC_Values[GC_CUSTOMER_ID]: GC_Values[GC_CUSTOMER_ID] = MY_CUSTOMER return service # Convert User UID to email address def convertUserUIDtoEmailAddress(emailAddressOrUID): normalizedEmailAddressOrUID = normalizeEmailAddressOrUID(emailAddressOrUID) if normalizedEmailAddressOrUID.find(u'@') > 0: return normalizedEmailAddressOrUID try: cd = buildGAPIObject(u'directory') result = callGAPI(cd.users(), u'get', throw_reasons=[GAPI_USER_NOT_FOUND], userKey=normalizedEmailAddressOrUID, fields=u'primaryEmail') if u'primaryEmail' in result: return result[u'primaryEmail'].lower() except: pass return normalizedEmailAddressOrUID API_SCOPE_MAPPING = { u'appsactivity': [u'https://www.googleapis.com/auth/activity', u'https://www.googleapis.com/auth/drive'], u'calendar': [u'https://www.googleapis.com/auth/calendar',], u'drive': [u'https://www.googleapis.com/auth/drive',], u'gmail': [u'https://mail.google.com/', u'https://www.googleapis.com/auth/gmail.settings.basic', u'https://www.googleapis.com/auth/gmail.settings.sharing',], u'plus': [u'https://www.googleapis.com/auth/plus.me',], } def getSvcAcctAPIversionHttpService(api): api, version, api_version = getAPIVersion(api) http = httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL], cache=GC_Values[GC_CACHE_DIR]) try: return (api_version, http, googleapiclient.discovery.build(api, version, http=http, cache_discovery=False)) except httplib2.ServerNotFoundError as e: systemErrorExit(4, e) except googleapiclient.errors.UnknownApiNameOrVersion: pass disc_file, discovery = readDiscoveryFile(api_version) try: return (api_version, http, googleapiclient.discovery.build_from_document(discovery, http=http)) except (ValueError, KeyError): invalidJSONExit(disc_file) def buildGAPIServiceObject(api, act_as): _, http, service = getSvcAcctAPIversionHttpService(api) GM_Globals[GM_CURRENT_API_USER] = act_as GM_Globals[GM_CURRENT_API_SCOPES] = API_SCOPE_MAPPING[api] credentials = getSvcAcctCredentials(GM_Globals[GM_CURRENT_API_SCOPES], act_as) try: service._http = credentials.authorize(http) except httplib2.ServerNotFoundError as e: systemErrorExit(4, e) except oauth2client.client.AccessTokenRefreshError as e: entityServiceNotApplicableWarning([u'Calendar', u'User'][api != u'calendar'], act_as, 0, 0) return handleOAuthTokenError(e, True) return service def buildActivityGAPIObject(user): userEmail = convertUserUIDtoEmailAddress(user) return (userEmail, buildGAPIServiceObject(u'appsactivity', userEmail)) def buildCalendarGAPIObject(calname): calendarId = convertUserUIDtoEmailAddress(calname) return (calendarId, buildGAPIServiceObject(u'calendar', calendarId)) def buildDriveGAPIObject(user): userEmail = convertUserUIDtoEmailAddress(user) return (userEmail, buildGAPIServiceObject(u'drive', userEmail)) def buildGmailGAPIObject(user): userEmail = convertUserUIDtoEmailAddress(user) return (userEmail, buildGAPIServiceObject(u'gmail', userEmail)) def buildGplusGAPIObject(user): userEmail = convertUserUIDtoEmailAddress(user) return (userEmail, buildGAPIServiceObject(u'plus', userEmail)) def showReport(): def _adjustDate(errMsg): match_date = re.match(u'Data for dates later than (.*) is not yet available. Please check back later', errMsg) if not match_date: match_date = re.match(u'Start date can not be later than (.*)', errMsg) if not match_date: systemErrorExit(4, errMsg) return str(match_date.group(1)) rep = buildGAPIObject(u'reports') report = sys.argv[2].lower() customerId = GC_Values[GC_CUSTOMER_ID] if customerId == MY_CUSTOMER: customerId = None try_date = filters = parameters = actorIpAddress = startTime = endTime = eventName = None to_drive = False userKey = u'all' i = 3 while i < len(sys.argv): if sys.argv[i].lower() == u'date': try_date = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'start': startTime = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'end': endTime = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'event': eventName = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'user': userKey = sys.argv[i+1] i += 2 elif sys.argv[i].lower() in [u'filter', u'filters']: filters = sys.argv[i+1] i += 2 elif sys.argv[i].lower() in [u'fields', u'parameters']: parameters = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'ip': actorIpAddress = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'todrive': to_drive = True i += 1 else: print u'ERROR: %s is not a valid argument to "gam report"' % sys.argv[i] sys.exit(2) if try_date == None: try_date = str(datetime.date.today()) if report in [u'users', u'user']: while True: try: page_message = u'Got %%num_items%% users\n' usage = callGAPIpages(rep.userUsageReport(), u'get', u'usageReports', page_message=page_message, throw_reasons=[u'invalid'], date=try_date, userKey=userKey, customerId=customerId, filters=filters, parameters=parameters) break except googleapiclient.errors.HttpError as e: error = json.loads(e.content) try: message = error[u'error'][u'errors'][0][u'message'] except KeyError: raise try_date = _adjustDate(message) titles = [u'email', u'date'] csvRows = [] for user_report in usage: row = {u'email': user_report[u'entity'][u'userEmail'], u'date': try_date} try: for report_item in user_report[u'parameters']: items = report_item.values() name = items[1] value = items[0] if not name in titles: titles.append(name) row[name] = value except KeyError: pass csvRows.append(row) writeCSVfile(csvRows, titles, u'User Reports - %s' % try_date, to_drive) elif report in [u'customer', u'customers', u'domain']: while True: try: usage = callGAPIpages(rep.customerUsageReports(), u'get', u'usageReports', throw_reasons=[u'invalid'], customerId=customerId, date=try_date, parameters=parameters) break except googleapiclient.errors.HttpError as e: error = json.loads(e.content) try: message = error[u'error'][u'errors'][0][u'message'] except KeyError: raise try_date = _adjustDate(message) titles = [u'name', u'value', u'client_id'] csvRows = [] auth_apps = list() for item in usage[0][u'parameters']: name = item[u'name'] try: value = item[u'intValue'] except KeyError: if name == u'accounts:authorized_apps': for subitem in item[u'msgValue']: app = {} for an_item in subitem: if an_item == u'client_name': app[u'name'] = u'App: %s' % subitem[an_item] elif an_item == u'num_users': app[u'value'] = u'%s users' % subitem[an_item] elif an_item == u'client_id': app[u'client_id'] = subitem[an_item] auth_apps.append(app) continue csvRows.append({u'name': name, u'value': value}) for app in auth_apps: # put apps at bottom csvRows.append(app) writeCSVfile(csvRows, titles, u'Customer Report - %s' % try_date, todrive=to_drive) else: if report in [u'doc', u'docs']: report = u'drive' elif report in [u'calendars']: report = u'calendar' elif report == u'logins': report = u'login' elif report == u'tokens': report = u'token' elif report == u'group': report = u'groups' page_message = u'Got %%num_items%% items\n' activities = callGAPIpages(rep.activities(), u'list', u'items', page_message=page_message, applicationName=report, userKey=userKey, customerId=customerId, actorIpAddress=actorIpAddress, startTime=startTime, endTime=endTime, eventName=eventName, filters=filters) if len(activities) > 0: titles = [u'name'] csvRows = [] for activity in activities: events = activity[u'events'] del activity[u'events'] activity_row = flatten_json(activity) for event in events: row = flatten_json(event) row.update(activity_row) for item in row: if item not in titles: titles.append(item) csvRows.append(row) sortCSVTitles([u'name',], titles) writeCSVfile(csvRows, titles, u'%s Activity Report' % report.capitalize(), to_drive) def addDelegates(users, i): if i == 4: if sys.argv[i].lower() != u'to': print u'ERROR: %s is not a valid argument for "gam delegate", expected to' % sys.argv[i] sys.exit(2) i += 1 delegate = sys.argv[i].lower() if not delegate.find(u'@') > 0: delegate_domain = GC_Values[GC_DOMAIN].lower() delegate_email = u'%s@%s' % (delegate, delegate_domain) else: delegate_domain = delegate[delegate.find(u'@')+1:].lower() delegate_email = delegate i = 0 count = len(users) emailsettings = buildGAPIObject(u'email-settings') for delegator in users: i += 1 if delegator.find(u'@') > 0: delegator_domain = delegator[delegator.find(u'@')+1:].lower() delegator_email = delegator delegator = delegator[:delegator.find(u'@')] else: delegator_domain = GC_Values[GC_DOMAIN].lower() delegator_email = u'%s@%s' % (delegator, delegator_domain) uri = u'https://apps-apis.google.com/a/feeds/emailsettings/2.0/%s/%s/delegation' % (delegator_domain, delegator) body = u''' ''' % delegate_email headers = {u'GData-Version': u'2.0', u'Content-Type': u'application/atom+xml; charset=UTF-8'} print u"Giving %s delegate access to %s (%s/%s)" % (delegate_email, delegator_email, i, count) retries = 10 for n in range(1, retries+1): status, result = emailsettings._http.request(uri=uri, method=u'POST', body=body, headers=headers) httpStatus = int(status[u'status']) if httpStatus == 201: # Success time.sleep(10) # on success, sleep 10 seconds before exiting or moving on to next user to prevent ghost delegates break elif httpStatus > 499: waitOnFailure(n, retries, str(httpStatus)) else: print u'ERROR: Could not create delegation - %s - %s' % (httpStatus, result) sys.exit(3) def gen_sha512_hash(password): from passlib.handlers.sha2_crypt import sha512_crypt return sha512_crypt.encrypt(password, rounds=5000) def printShowDelegates(users, csvFormat): if csvFormat: todrive = False csvRows = [] titles = [u'User', u'delegateName', u'delegateAddress', u'delegationStatus'] else: csvStyle = False i = 5 while i < len(sys.argv): myarg = sys.argv[i].lower() if not csvFormat and myarg == u'csv': csvStyle = True i += 1 elif csvFormat and myarg == u'todrive': todrive = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam show delegates"' % sys.argv[i] sys.exit(2) emailsettings = buildGAPIObject(u'email-settings') for user in users: if user.find(u'@') == -1: userName = user user = u'%s@%s' % (user, GC_Values[GC_DOMAIN]) domainName = GC_Values[GC_DOMAIN] else: userName = user[:user.find(u'@')] domainName = user[user.find(u'@')+1:] sys.stderr.write(u"Getting delegates for %s...\n" % (user)) delegates = callGAPI(emailsettings.delegates(), u'get', soft_errors=True, v=u'2.0', domainName=domainName, delegator=userName) if delegates and u'feed' in delegates and u'entry' in delegates[u'feed']: for delegate in delegates[u'feed']['entry']: status = u'' delegateAddress = u'' delegateName = u'' delegationId = u'' for item in delegate[u'apps$property']: if item[u'name'] == u'status': status = item[u'value'] elif item[u'name'] == u'address': delegateAddress = item[u'value'] elif item[u'name'] == u'delegate': delegateName = item[u'value'] elif item[u'name'] == u'delegationId': delegationId = item[u'value'] if csvFormat: row = {u'User': user, u'delegateName': delegateName, u'delegateAddress': delegateAddress, u'delegationStatus': status} csvRows.append(row) else: if csvStyle: print u'%s,%s,%s' % (user, delegateAddress, status) else: print convertUTF8(u"Delegator: %s\n Delegate: %s\n Status: %s\n Delegate Email: %s\n Delegate ID: %s\n" % (user, delegateName, status, delegateAddress, delegationId)) if csvFormat: writeCSVfile(csvRows, titles, u'Delegates', todrive) def deleteDelegate(users): emailsettings = buildGAPIObject(u'email-settings') delegate = sys.argv[5] if not delegate.find(u'@') > 0: if users[0].find(u'@') > 0: delegatedomain = users[0][users[0].find(u'@')+1:] else: delegatedomain = GC_Values[GC_DOMAIN] delegate = u'%s@%s' % (delegate, delegatedomain) i = 0 count = len(users) for user in users: i += 1 if user.find(u'@') > 0: domainName = user[user.find(u'@')+1:] user = user[:user.find(u'@')] else: domainName = GC_Values[GC_DOMAIN] #make sure it's back at default domain print u"Deleting %s delegate access to %s (%s/%s)" % (delegate, user+u'@'+domainName, i, count) callGAPI(emailsettings.delegates(), u'delete', v=u'2.0', delegate=delegate, delegator=user, domainName=domainName) def doAddCourseParticipant(): croom = buildGAPIObject(u'classroom') courseId = sys.argv[2] body_attribute = u'userId' if len(courseId) < 3 or (not courseId.isdigit() and courseId[:2] != u'd:'): courseId = u'd:%s' % courseId participant_type = sys.argv[4].lower() new_id = sys.argv[5] if participant_type in [u'teacher', u'teachers']: service = croom.courses().teachers() elif participant_type in [u'students', u'student']: service = croom.courses().students() elif participant_type in [u'alias']: service = croom.courses().aliases() body_attribute = u'alias' if new_id[1] != u':': new_id = u'd:%s' % new_id else: print u'ERROR: %s is not a valid argument to "gam course ID add"' % participant_type sys.exit(2) body = {body_attribute: new_id} callGAPI(service, u'create', courseId=courseId, body=body) if courseId[:2] == u'd:': courseId = courseId[2:] if new_id[:2] == u'd:': new_id = new_id[2:] print u'Added %s as a %s of course %s' % (new_id, participant_type, courseId) def doSyncCourseParticipants(): courseId = sys.argv[2] if not courseId.isdigit() and courseId[:2] != u'd:': courseId = u'd:%s' % courseId participant_type = sys.argv[4].lower() diff_entity_type = sys.argv[5] diff_entity = sys.argv[6] current_course_users = getUsersToModify(entity_type=participant_type, entity=courseId) print current_course_users = [x.lower() for x in current_course_users] if diff_entity_type == u'courseparticipants': diff_entity_type = participant_type diff_against_users = getUsersToModify(entity_type=diff_entity_type, entity=diff_entity) print diff_against_users = [x.lower() for x in diff_against_users] to_add = list(set(diff_against_users) - set(current_course_users)) to_remove = list(set(current_course_users) - set(diff_against_users)) gam_commands = [] for add_email in to_add: gam_commands.append([u'course', courseId, u'add', participant_type, add_email]) for remove_email in to_remove: gam_commands.append([u'course', courseId, u'remove', participant_type, remove_email]) run_batch(gam_commands) def doDelCourseParticipant(): croom = buildGAPIObject(u'classroom') courseId = sys.argv[2] if not courseId.isdigit() and courseId[:2] != u'd:': courseId = u'd:%s' % courseId participant_type = sys.argv[4].lower() remove_id = sys.argv[5] kwargs = {} if participant_type in [u'teacher', u'teachers']: service = croom.courses().teachers() kwargs[u'userId'] = remove_id elif participant_type in [u'student', u'students']: service = croom.courses().students() kwargs[u'userId'] = remove_id elif participant_type in [u'alias']: service = croom.courses().aliases() if remove_id[1] != u':': remove_id = u'd:%s' % remove_id kwargs[u'alias'] = remove_id else: print u'ERROR: %s is not a valid argument to "gam course ID delete"' % participant_type sys.exit(2) callGAPI(service, u'delete', courseId=courseId, **kwargs) if courseId[:2] == u'd:': courseId = courseId[2:] if remove_id[:2] == u'd:': remove_id = remove_id[2:] print u'Removed %s as a %s of course %s' % (remove_id, participant_type, courseId) def doDelCourse(): croom = buildGAPIObject(u'classroom') courseId = sys.argv[3] if not courseId.isdigit() and courseId[:2] != u'd:': courseId = u'd:%s' % courseId callGAPI(croom.courses(), u'delete', id=courseId) print u'Deleted Course %s' % courseId def doUpdateCourse(): croom = buildGAPIObject(u'classroom') courseId = sys.argv[3] if not courseId.isdigit() and courseId[:2] != u'd:': courseId = u'd:%s' % courseId body = {} i = 4 while i < len(sys.argv): if sys.argv[i].lower() == u'name': body[u'name'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'section': body[u'section'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'heading': body[u'descriptionHeading'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'description': body[u'description'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'room': body[u'room'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() in [u'state', u'status']: body[u'courseState'] = sys.argv[i+1].upper() if body[u'courseState'] not in [u'ACTIVE', u'ARCHIVED', u'PROVISIONED', u'DECLINED']: print u'ERROR: course state must be active or archived; got %s' % body[u'courseState'] sys.exit(2) i += 2 else: print u'ERROR: %s is not a valid argument to "gam update course"' % sys.argv[i] sys.exit(2) updateMask = u','.join(body.keys()) body[u'id'] = courseId result = callGAPI(croom.courses(), u'patch', id=courseId, body=body, updateMask=updateMask) print u'Updated Course %s' % result[u'id'] def doCreateDomain(): cd = buildGAPIObject(u'directory') domain_name = sys.argv[3] body = {u'domainName': domain_name} callGAPI(cd.domains(), u'insert', customer=GC_Values[GC_CUSTOMER_ID], body=body) print u'Added domain %s' % domain_name def doCreateDomainAlias(): cd = buildGAPIObject(u'directory') body = {} body[u'domainAliasName'] = sys.argv[3] body[u'parentDomainName'] = sys.argv[4] callGAPI(cd.domainAliases(), u'insert', customer=GC_Values[GC_CUSTOMER_ID], body=body) def doUpdateDomain(): cd = buildGAPIObject(u'directory') domain_name = sys.argv[3] i = 4 body = {} while i < len(sys.argv): if sys.argv[i].lower() == u'primary': body[u'customerDomain'] = domain_name i += 1 else: print u'ERROR: %s is not a valid argument for "gam update domain"' % sys.argv[i] sys.exit(2) callGAPI(cd.customers(), u'update', customerKey=GC_Values[GC_CUSTOMER_ID], body=body) print u'%s is now the primary domain.' % domain_name def doGetDomainInfo(): if (len(sys.argv) < 4) or (sys.argv[3] == u'logo'): doGetCustomerInfo() return cd = buildGAPIObject(u'directory') domainName = sys.argv[3] result = callGAPI(cd.domains(), u'get', customer=GC_Values[GC_CUSTOMER_ID], domainName=domainName) if u'creationTime' in result: result[u'creationTime'] = unicode(datetime.datetime.fromtimestamp(int(result[u'creationTime'])/1000)) if u'domainAliases' in result: for i in range(0, len(result[u'domainAliases'])): if u'creationTime' in result[u'domainAliases'][i]: result[u'domainAliases'][i][u'creationTime'] = unicode(datetime.datetime.fromtimestamp(int(result[u'domainAliases'][i][u'creationTime'])/1000)) print_json(None, result) def doGetDomainAliasInfo(): cd = buildGAPIObject(u'directory') alias = sys.argv[3] result = callGAPI(cd.domainAliases(), u'get', customer=GC_Values[GC_CUSTOMER_ID], domainAliasName=alias) if u'creationTime' in result: result[u'creationTime'] = unicode(datetime.datetime.fromtimestamp(int(result[u'creationTime'])/1000)) print_json(None, result) ADDRESS_FIELDS_PRINT_ORDER = [u'contactName', u'organizationName', u'addressLine1', u'addressLine2', u'addressLine3', u'locality', u'region', u'postalCode', u'countryCode'] def doGetCustomerInfo(): cd = buildGAPIObject(u'directory') customer_info = callGAPI(cd.customers(), u'get', customerKey=GC_Values[GC_CUSTOMER_ID]) print u'Customer ID: %s' % customer_info[u'id'] print u'Primary Domain: %s' % customer_info[u'customerDomain'] result = callGAPI(cd.domains(), u'get', customer=customer_info[u'id'], domainName=customer_info[u'customerDomain'], fields=u'verified') print u'Primary Domain Verified: %s' % result[u'verified'] print u'Customer Creation Time: %s' % customer_info[u'customerCreationTime'] print u'Default Language: %s' % customer_info[u'language'] if u'postalAddress' in customer_info: print u'Address:' for field in ADDRESS_FIELDS_PRINT_ORDER: if field in customer_info[u'postalAddress']: print u' %s: %s' % (field, customer_info[u'postalAddress'][field]) if u'phoneNumber' in customer_info: print u'Phone: %s' % customer_info[u'phoneNumber'] print u'Admin Secondary Email: %s' % customer_info[u'alternateEmail'] ADDRESS_FIELDS_ARGUMENT_MAP = { u'contact': u'contactName', u'contactname': u'contactName', u'name': u'organizationName', u'organizationname': u'organizationName', u'address1': u'addressLine1', u'addressline1': u'addressLine1', u'address2': u'addressLine2', u'addressline2': u'addressLine2', u'address3': u'addressLine3', u'addressline3': u'addressLine3', u'locality': u'locality', u'region': u'region', u'postalcode': u'postalCode', u'country': u'countryCode', u'countrycode': u'countryCode', } def doUpdateCustomer(): cd = buildGAPIObject(u'directory') body = {} i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower().replace(u'_', u'') if myarg in ADDRESS_FIELDS_ARGUMENT_MAP: body.setdefault(u'postalAddress', {}) body[u'postalAddress'][ADDRESS_FIELDS_ARGUMENT_MAP[myarg]] = sys.argv[i+1] i += 2 elif myarg in [u'adminsecondaryemail', u'alternateemail']: body[u'alternateEmail'] = sys.argv[i+1] i += 2 elif myarg in [u'phone', u'phonenumber']: body[u'phoneNumber'] = sys.argv[i+1] i += 2 elif myarg == u'language': body[u'language'] = sys.argv[i+1] i += 2 else: print u'ERROR: %s is not a valid argument for "gam update customer"' % myarg sys.exit(2) callGAPI(cd.customers(), u'update', customerKey=GC_Values[GC_CUSTOMER_ID], body=body) print u'Updated customer' def doDelDomain(): cd = buildGAPIObject(u'directory') domainName = sys.argv[3] callGAPI(cd.domains(), u'delete', customer=GC_Values[GC_CUSTOMER_ID], domainName=domainName) def doDelDomainAlias(): cd = buildGAPIObject(u'directory') domainAliasName = sys.argv[3] callGAPI(cd.domainAliases(), u'delete', customer=GC_Values[GC_CUSTOMER_ID], domainAliasName=domainAliasName) def doPrintDomains(): cd = buildGAPIObject(u'directory') todrive = False titles = [u'domainName',] csvRows = [] i = 3 while i < len(sys.argv): if sys.argv[i].lower() == u'todrive': todrive = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam print domains".' % sys.argv[i] sys.exit(2) results = callGAPI(cd.domains(), u'list', customer=GC_Values[GC_CUSTOMER_ID]) for domain in results[u'domains']: domain_attributes = {} domain[u'type'] = [u'secondary', u'primary'][domain[u'isPrimary']] for attr in domain: if attr in [u'kind', u'etag', u'domainAliases', u'isPrimary']: continue if attr in [u'creationTime',]: domain[attr] = unicode(datetime.datetime.fromtimestamp(int(domain[attr])/1000)) if attr not in titles: titles.append(attr) domain_attributes[attr] = domain[attr] csvRows.append(domain_attributes) if u'domainAliases' in domain: for aliasdomain in domain[u'domainAliases']: aliasdomain[u'domainName'] = aliasdomain[u'domainAliasName'] del aliasdomain[u'domainAliasName'] aliasdomain[u'type'] = u'alias' aliasdomain_attributes = {} for attr in aliasdomain: if attr in [u'kind', u'etag']: continue if attr in [u'creationTime',]: aliasdomain[attr] = unicode(datetime.datetime.fromtimestamp(int(aliasdomain[attr])/1000)) if attr not in titles: titles.append(attr) aliasdomain_attributes[attr] = aliasdomain[attr] csvRows.append(aliasdomain_attributes) writeCSVfile(csvRows, titles, u'Domains', todrive) def doPrintDomainAliases(): cd = buildGAPIObject(u'directory') todrive = False titles = [u'domainAliasName',] csvRows = [] i = 3 while i < len(sys.argv): if sys.argv[i].lower() == u'todrive': todrive = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam print domainaliases".' % sys.argv[i] sys.exit(2) results = callGAPI(cd.domainAliases(), u'list', customer=GC_Values[GC_CUSTOMER_ID]) for domainAlias in results[u'domainAliases']: domainAlias_attributes = {} for attr in domainAlias: if attr in [u'kind', u'etag']: continue if attr == u'creationTime': domainAlias[attr] = unicode(datetime.datetime.fromtimestamp(int(domainAlias[attr])/1000)) if attr not in titles: titles.append(attr) domainAlias_attributes[attr] = domainAlias[attr] csvRows.append(domainAlias_attributes) writeCSVfile(csvRows, titles, u'Domains', todrive) def doDelAdmin(): cd = buildGAPIObject(u'directory') roleAssignmentId = sys.argv[3] print u'Deleting Admin Role Assignment %s' % roleAssignmentId callGAPI(cd.roleAssignments(), u'delete', customer=GC_Values[GC_CUSTOMER_ID], roleAssignmentId=roleAssignmentId) def doCreateAdmin(): cd = buildGAPIObject(u'directory') body = {} user = sys.argv[3] if user[:4].lower() == u'uid:': body[u'assignedTo'] = user[4:] else: print user[:3] body[u'assignedTo'] = callGAPI(cd.users(), u'get', userKey=user, projection=u'basic', fields=u'id')[u'id'] role = sys.argv[4] if role[:4].lower() == u'uid:': body[u'roleId'] = role[4:] else: body[u'roleId'] = roleid_from_role(role) if not body[u'roleId']: print u'ERROR: %s is not a valid role. Please ensure role name is exactly as shown in admin console.' % role sys.exit(4) body[u'scopeType'] = sys.argv[5].upper() if body[u'scopeType'] not in [u'CUSTOMER', u'ORG_UNIT']: print u'ERROR: scope type must be customer or org_unit; got %s' % body[u'scopeType'] sys.exit(3) if body[u'scopeType'] == u'ORG_UNIT': orgUnit = sys.argv[6] if orgUnit[:3] == u'id:': body[u'orgUnitId'] = orgUnit[3:] elif orgUnit[:4] == u'uid:': body[u'orgUnitId'] = orgUnit[4:] else: if orgUnit[0] == u'/': orgUnit = orgUnit[1:] body[u'orgUnitId'] = callGAPI(cd.orgunits(), u'get', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=orgUnit, fields=u'orgUnitId')[u'orgUnitId'][3:] if body[u'scopeType'] == u'CUSTOMER': scope = u'customer' else: scope = orgUnit print u'Giving %s admin role %s for %s' % (user, role, scope) callGAPI(cd.roleAssignments(), u'insert', customer=GC_Values[GC_CUSTOMER_ID], body=body) def doPrintAdminRoles(): cd = buildGAPIObject(u'directory') todrive = False titles = [u'roleId', u'roleName', u'roleDescription', u'isSuperAdminRole', u'isSystemRole'] fields = u'nextPageToken,items({0})'.format(u','.join(titles)) csvRows = [] i = 3 while i < len(sys.argv): if sys.argv[i].lower() == u'todrive': todrive = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam print adminroles".' % sys.argv[i] sys.exit(2) roles = callGAPIpages(cd.roles(), u'list', u'items', customer=GC_Values[GC_CUSTOMER_ID], fields=fields) for role in roles: role_attrib = {} for key, value in role.items(): role_attrib[key] = value csvRows.append(role_attrib) writeCSVfile(csvRows, titles, u'Admin Roles', todrive) def doPrintAdmins(): cd = buildGAPIObject(u'directory') roleId = None userKey = None todrive = False fields = u'nextPageToken,items({0})'.format(u','.join([u'roleAssignmentId', u'roleId', u'assignedTo', u'scopeType', u'orgUnitId'])) titles = [u'roleAssignmentId', u'roleId', u'role', u'assignedTo', u'assignedToUser', u'scopeType', u'orgUnitId', u'orgUnit'] csvRows = [] i = 3 while i < len(sys.argv): if sys.argv[i].lower() == u'user': userKey = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'role': role = sys.argv[i+1] if role[:4].lower() == u'uid:': roleId = role[4:] else: roleId = roleid_from_role(role) if not roleId: print u'ERROR: %s is not a valid role' % role sys.exit(5) i += 2 elif sys.argv[i].lower() == u'todrive': todrive = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam print admins".' % sys.argv[i] sys.exit(2) admins = callGAPIpages(cd.roleAssignments(), u'list', u'items', customer=GC_Values[GC_CUSTOMER_ID], userKey=userKey, roleId=roleId, fields=fields) for admin in admins: admin_attrib = {} for key, value in admin.items(): if key == u'assignedTo': admin_attrib[u'assignedToUser'] = user_from_userid(value) elif key == u'roleId': admin_attrib[u'role'] = role_from_roleid(value) elif key == u'orgUnitId': value = u'id:{0}'.format(value) admin_attrib[u'orgUnit'] = orgunit_from_orgunitid(value) admin_attrib[key] = value csvRows.append(admin_attrib) writeCSVfile(csvRows, titles, u'Admins', todrive) def buildOrgUnitIdToNameMap(): cd = buildGAPIObject(u'directory') result = callGAPI(cd.orgunits(), u'list', customerId=GC_Values[GC_CUSTOMER_ID], fields=u'organizationUnits(orgUnitPath,orgUnitId)', type=u'all') GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME] = {} for orgUnit in result[u'organizationUnits']: GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME][orgUnit[u'orgUnitId']] = orgUnit[u'orgUnitPath'] def orgunit_from_orgunitid(orgunitid): if not GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME]: buildOrgUnitIdToNameMap() return GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME].get(orgunitid, orgunitid) def buildRoleIdToNameToIdMap(): cd = buildGAPIObject(u'directory') result = callGAPIpages(cd.roles(), u'list', u'items', customer=GC_Values[GC_CUSTOMER_ID], fields=u'nextPageToken,items(roleId,roleName)', maxResults=100) GM_Globals[GM_MAP_ROLE_ID_TO_NAME] = {} GM_Globals[GM_MAP_ROLE_NAME_TO_ID] = {} for role in result: GM_Globals[GM_MAP_ROLE_ID_TO_NAME][role[u'roleId']] = role[u'roleName'] GM_Globals[GM_MAP_ROLE_NAME_TO_ID][role[u'roleName']] = role[u'roleId'] def role_from_roleid(roleid): if not GM_Globals[GM_MAP_ROLE_ID_TO_NAME]: buildRoleIdToNameToIdMap() return GM_Globals[GM_MAP_ROLE_ID_TO_NAME].get(roleid, roleid) def roleid_from_role(role): if not GM_Globals[GM_MAP_ROLE_NAME_TO_ID]: buildRoleIdToNameToIdMap() return GM_Globals[GM_MAP_ROLE_NAME_TO_ID].get(role, None) def buildUserIdToNameMap(): cd = buildGAPIObject(u'directory') result = callGAPIpages(cd.users(), u'list', u'users', customer=GC_Values[GC_CUSTOMER_ID], fields=u'nextPageToken,users(id,primaryEmail)', maxResults=GC_Values[GC_USER_MAX_RESULTS]) GM_Globals[GM_MAP_USER_ID_TO_NAME] = {} for user in result: GM_Globals[GM_MAP_USER_ID_TO_NAME][user[u'id']] = user[u'primaryEmail'] def user_from_userid(userid): if not GM_Globals[GM_MAP_USER_ID_TO_NAME]: buildUserIdToNameMap() return GM_Globals[GM_MAP_USER_ID_TO_NAME].get(userid, u'') SERVICE_NAME_TO_ID_MAP = {u'Drive and Docs': u'55656082996', u'Google+': u'553547912911',} def appID2app(dt, appID): for serviceName, serviceID in SERVICE_NAME_TO_ID_MAP.items(): if appID == serviceID: return serviceName online_services = callGAPIpages(dt.applications(), u'list', u'applications', customerId=GC_Values[GC_CUSTOMER_ID]) for online_service in online_services: if appID == online_service[u'id']: return online_service[u'name'] return u'applicationId: {0}'.format(appID) SERVICE_NAME_CHOICES_MAP = { u'drive': u'Drive and Docs', u'drive and docs': u'Drive and Docs', u'googledrive': u'Drive and Docs', u'gdrive': u'Drive and Docs', } def app2appID(dt, app): serviceName = app.lower() if serviceName in SERVICE_NAME_CHOICES_MAP: return (SERVICE_NAME_CHOICES_MAP[serviceName], SERVICE_NAME_TO_ID_MAP[SERVICE_NAME_CHOICES_MAP[serviceName]]) online_services = callGAPIpages(dt.applications(), u'list', u'applications', customerId=GC_Values[GC_CUSTOMER_ID]) for online_service in online_services: if serviceName == online_service[u'name'].lower(): return (online_service[u'name'], online_service[u'id']) print u'ERROR: %s is not a valid service for data transfer.' % app sys.exit(2) def convertToUserID(user): if user[:4].lower() == u'uid:': return user[4:] cd = buildGAPIObject(u'directory') if user.find(u'@') == -1: user = u'%s@%s' % (user, GC_Values[GC_DOMAIN]) try: return callGAPI(cd.users(), u'get', throw_reasons=[u'notFound'], userKey=user, fields=u'id')[u'id'] except googleapiclient.errors.HttpError: print u'ERROR: no such user %s' % user sys.exit(3) def convertUserIDtoEmail(uid): cd = buildGAPIObject(u'directory') try: return callGAPI(cd.users(), u'get', throw_reasons=[u'notFound'], userKey=uid, fields=u'primaryEmail')[u'primaryEmail'] except googleapiclient.errors.HttpError: return u'uid:{0}'.format(uid) def doCreateDataTranfer(): dt = buildGAPIObject(u'datatransfer') body = {} old_owner = sys.argv[3] body[u'oldOwnerUserId'] = convertToUserID(old_owner) serviceName, serviceID = app2appID(dt, sys.argv[4]) new_owner = sys.argv[5] body[u'newOwnerUserId'] = convertToUserID(new_owner) parameters = {} i = 6 while i < len(sys.argv): parameters[sys.argv[i].upper()] = sys.argv[i+1].upper().split(u',') i += 2 body[u'applicationDataTransfers'] = [{u'applicationId': serviceID}] for key in parameters: if u'applicationDataTransferParams' not in body[u'applicationDataTransfers'][0]: body[u'applicationDataTransfers'][0][u'applicationTransferParams'] = [] body[u'applicationDataTransfers'][0][u'applicationTransferParams'].append({u'key': key, u'value': parameters[key]}) result = callGAPI(dt.transfers(), u'insert', body=body, fields=u'id')[u'id'] print u'Submitted request id %s to transfer %s from %s to %s' % (result, serviceName, old_owner, new_owner) def doPrintTransferApps(): dt = buildGAPIObject(u'datatransfer') apps = callGAPIpages(dt.applications(), u'list', u'applications', customerId=GC_Values[GC_CUSTOMER_ID]) for app in apps: print_json(None, app) print def doPrintDataTransfers(): dt = buildGAPIObject(u'datatransfer') i = 3 newOwnerUserId = None oldOwnerUserId = None status = None todrive = False titles = [u'id',] csvRows = [] while i < len(sys.argv): if sys.argv[i].lower().replace(u'_', u'') in [u'olduser', u'oldowner']: oldOwnerUserId = convertToUserID(sys.argv[i+1]) i += 2 elif sys.argv[i].lower().replace(u'_', u'') in [u'newuser', u'newowner']: newOwnerUserId = convertToUserID(sys.argv[i+1]) i += 2 elif sys.argv[i].lower() == u'status': status = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'todrive': todrive = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam print transfers"' % sys.argv[i] sys.exit(2) transfers = callGAPIpages(dt.transfers(), u'list', u'dataTransfers', customerId=GC_Values[GC_CUSTOMER_ID], status=status, newOwnerUserId=newOwnerUserId, oldOwnerUserId=oldOwnerUserId) for transfer in transfers: for i in range(0, len(transfer[u'applicationDataTransfers'])): a_transfer = {} a_transfer[u'oldOwnerUserEmail'] = convertUserIDtoEmail(transfer[u'oldOwnerUserId']) a_transfer[u'newOwnerUserEmail'] = convertUserIDtoEmail(transfer[u'newOwnerUserId']) a_transfer[u'requestTime'] = transfer[u'requestTime'] a_transfer[u'applicationId'] = transfer[u'applicationDataTransfers'][i][u'applicationId'] a_transfer[u'application'] = appID2app(dt, a_transfer[u'applicationId']) a_transfer[u'status'] = transfer[u'applicationDataTransfers'][i][u'applicationTransferStatus'] a_transfer[u'id'] = transfer[u'id'] if u'applicationTransferParams' in transfer[u'applicationDataTransfers'][i]: for param in transfer[u'applicationDataTransfers'][i][u'applicationTransferParams']: a_transfer[param[u'key']] = u','.join(param[u'value']) for title in a_transfer: if title not in titles: titles.append(title) csvRows.append(a_transfer) writeCSVfile(csvRows, titles, u'Data Transfers', todrive) def doGetDataTransferInfo(): dt = buildGAPIObject(u'datatransfer') dtId = sys.argv[3] transfer = callGAPI(dt.transfers(), u'get', dataTransferId=dtId) print u'Old Owner: %s' % convertUserIDtoEmail(transfer[u'oldOwnerUserId']) print u'New Owner: %s' % convertUserIDtoEmail(transfer[u'newOwnerUserId']) print u'Request Time: %s' % transfer[u'requestTime'] for app in transfer[u'applicationDataTransfers']: print u'Application: %s' % appID2app(dt, app[u'applicationId']) print u'Status: %s' % app[u'applicationTransferStatus'] print u'Parameters:' if u'applicationTransferParams' in app: for param in app[u'applicationTransferParams']: print u' %s: %s' % (param[u'key'], u','.join(param[u'value'])) else: print u' None' print def doPrintShowGuardians(csvFormat): croom = buildGAPIObject(u'classroom') invitedEmailAddress = None studentIds = [u'-',] states = None service = croom.userProfiles().guardians() items = u'guardians' itemName = 'Guardians' if csvFormat: csvRows = [] todrive = False titles = [u'studentEmail', u'studentId', u'invitedEmailAddress', u'guardianId'] i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower() if csvFormat and myarg == u'todrive': todrive = True i += 1 elif myarg == u'invitedguardian': invitedEmailAddress = sys.argv[i+1] i += 2 elif myarg == u'student': studentIds = [sys.argv[i+1],] i += 2 elif myarg == u'invitations': service = croom.userProfiles().guardianInvitations() items = u'guardianInvitations' itemName = 'Guardian Invitations' titles = [u'studentEmail', u'studentId', u'invitedEmailAddress', u'invitationId'] if states == None: states = [u'COMPLETE', u'PENDING', u'GUARDIAN_INVITATION_STATE_UNSPECIFIED'] i += 1 elif myarg == u'states': states = sys.argv[i+1].upper().replace(u',', u' ').split() i += 2 elif myarg in usergroup_types: studentIds = getUsersToModify(entity_type=myarg, entity=sys.argv[i+1]) i += 2 else: print u'ERROR: %s is not a valid argument for "gam %s guardians"' % (sys.argv[i], [u'show', u'print'][csvFormat]) sys.exit(2) i = 0 count = len(studentIds) for studentId in studentIds: i += 1 kwargs = {u'invitedEmailAddress': invitedEmailAddress, u'studentId': studentId} if items == u'guardianInvitations': kwargs[u'states'] = states if studentId != u'-': if csvFormat: sys.stderr.write('\r') sys.stderr.flush() sys.stderr.write(u'Getting %s for %s%s%s' % (itemName, studentId, currentCount(i, count), u' ' * 40)) guardians = callGAPIpages(service, u'list', items=items, soft_errors=True, **kwargs) if not csvFormat: print u'Student: {0}, {1}:{2}'.format(studentId, itemName, currentCount(i, count)) for guardian in guardians: print_json(None, guardian, spacing=u' ') else: for guardian in guardians: guardian[u'studentEmail'] = studentId addRowTitlesToCSVfile(flatten_json(guardian), csvRows, titles) if csvFormat: sys.stderr.write(u'\n') writeCSVfile(csvRows, titles, itemName, todrive) def doInviteGuardian(): croom = buildGAPIObject(u'classroom') body = {u'invitedEmailAddress': sys.argv[3]} studentId = sys.argv[4] result = callGAPI(croom.userProfiles().guardianInvitations(), u'create', studentId=studentId, body=body) print u'Invited email %s as guardian of %s. Invite ID %s' % (result[u'invitedEmailAddress'], studentId, result[u'invitationId']) def doDeleteGuardian(): croom = buildGAPIObject(u'classroom') guardianId = sys.argv[3] studentId = sys.argv[4] try: callGAPI(croom.userProfiles().guardians(), u'delete', throw_reasons=[u'forbidden', u'notFound'], studentId=studentId, guardianId=guardianId) print u'Deleted %s as a guardian of %s' % (guardianId, studentId) except googleapiclient.errors.HttpError: # See if there's a pending invitation states = [u'COMPLETE', u'PENDING', u'GUARDIAN_INVITATION_STATE_UNSPECIFIED'] results = callGAPIpages(croom.userProfiles().guardianInvitations(), u'list', items=u'guardianInvitations', studentId=studentId, invitedEmailAddress=guardianId, states=states) if len(results) < 1: print u'%s is not a guardian of %s and no invitation exists.' % (guardianId, studentId) sys.exit(0) for result in results: if result[u'state'] != u'PENDING': print u'%s is not a guardian of %s and invitation %s status is %s, not PENDING. Doing nothing.' % (guardianId, studentId, result[u'invitationId'], result[u'state']) continue invitationId = result[u'invitationId'] body = {u'state': u'COMPLETE'} callGAPI(croom.userProfiles().guardianInvitations(), u'patch', studentId=studentId, invitationId=invitationId, updateMask=u'state', body=body) print u'Cancelling %s invitation for %s as guardian of %s' % (result[u'state'], result[u'invitedEmailAddress'], studentId) def doCreateCourse(): croom = buildGAPIObject(u'classroom') body = {} i = 3 while i < len(sys.argv): if sys.argv[i].lower() == u'name': body[u'name'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() in [u'alias', u'id']: body[u'id'] = u'd:%s' % sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'section': body[u'section'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'heading': body[u'descriptionHeading'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'description': body[u'description'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'room': body[u'room'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'teacher': body[u'ownerId'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() in [u'state', u'status']: body[u'courseState'] = sys.argv[i+1].upper() if body[u'courseState'] not in [u'ACTIVE', u'ARCHIVED', u'PROVISIONED', u'DECLINED']: print u'ERROR: course state must be active or archived; got %s' % body[u'courseState'] sys.exit(2) i += 2 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'] def doGetCourseInfo(): croom = buildGAPIObject(u'classroom') courseId = sys.argv[3] if not courseId.isdigit() and courseId[:2] != u'd:': courseId = u'd:%s' % courseId info = callGAPI(croom.courses(), u'get', id=courseId) print_json(None, info) teachers = callGAPIpages(croom.courses().teachers(), u'list', u'teachers', courseId=courseId) students = callGAPIpages(croom.courses().students(), u'list', u'students', courseId=courseId) try: aliases = callGAPIpages(croom.courses().aliases(), u'list', u'aliases', throw_reasons=[u'notImplemented'], courseId=courseId) except googleapiclient.errors.HttpError: aliases = [] if aliases: print u'Aliases:' for alias in aliases: print u' %s' % alias[u'alias'][2:] print u'Participants:' print u' Teachers:' for teacher in teachers: try: print convertUTF8(u' %s - %s' % (teacher[u'profile'][u'name'][u'fullName'], teacher[u'profile'][u'emailAddress'])) except KeyError: print convertUTF8(u' %s' % teacher[u'profile'][u'name'][u'fullName']) print u' Students:' for student in students: try: print convertUTF8(u' %s - %s' % (student[u'profile'][u'name'][u'fullName'], student[u'profile'][u'emailAddress'])) except KeyError: print convertUTF8(u' %s' % student[u'profile'][u'name'][u'fullName']) def doPrintCourses(): croom = buildGAPIObject(u'classroom') todrive = False titles = [u'id',] csvRows = [] teacherId = None studentId = None get_aliases = False aliasesDelimiter = u' ' i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg == u'teacher': teacherId = sys.argv[i+1] i += 2 elif myarg == u'student': studentId = sys.argv[i+1] i += 2 elif myarg == u'todrive': todrive = True i += 1 elif myarg in [u'alias', u'aliases']: get_aliases = True i += 1 elif myarg == u'delimiter': aliasesDelimiter = sys.argv[i+1] i += 2 else: print u'ERROR: %s is not a valid argument for "gam print courses"' % sys.argv[i] sys.exit(2) sys.stderr.write(u'Retrieving courses for organization (may take some time for large accounts)...\n') page_message = u'Got %%num_items%% courses...\n' all_courses = callGAPIpages(croom.courses(), u'list', u'courses', page_message=page_message, teacherId=teacherId, studentId=studentId) for course in all_courses: addRowTitlesToCSVfile(flatten_json(course), csvRows, titles) if get_aliases: titles.append(u'Aliases') i = 0 num_courses = len(csvRows) for course in csvRows: i += 1 sys.stderr.write(u'Getting aliases for course %s (%s/%s)\n' % (course[u'id'], i, num_courses)) course_aliases = callGAPIpages(croom.courses().aliases(), u'list', u'aliases', courseId=course[u'id']) my_aliases = [] for alias in course_aliases: my_aliases.append(alias[u'alias'][2:]) course.update(Aliases=aliasesDelimiter.join(my_aliases)) writeCSVfile(csvRows, titles, u'Courses', todrive) def doPrintCourseParticipants(): croom = buildGAPIObject(u'classroom') todrive = False titles = [u'courseId',] csvRows = [] courses = [] teacherId = None studentId = None i = 3 while i < len(sys.argv): if sys.argv[i].lower() in [u'course', u'class']: course = sys.argv[i+1] if not course.isdigit(): course = u'd:%s' % course courses.append(course) i += 2 elif sys.argv[i].lower() == u'teacher': teacherId = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'student': studentId = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'todrive': todrive = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam print course-participants"' % sys.argv[i] sys.exit(2) if len(courses) == 0: sys.stderr.write(u'Retrieving courses for organization (may take some time for large accounts)...\n') page_message = u'Got %%num_items%% courses...\n' all_courses = callGAPIpages(croom.courses(), u'list', u'courses', page_message=page_message, teacherId=teacherId, studentId=studentId) for course in all_courses: courses.append(course[u'id']) else: all_courses = [] for course in courses: all_courses.append(callGAPI(croom.courses(), u'get', id=course)) y = 1 num_courses = len(all_courses) for course in all_courses: course_id = course[u'id'] teacher_message = u' got %%%%num_items%%%% teachers for course %s (%s/%s)' % (course_id, y, num_courses) student_message = u' got %%%%num_items%%%% students for course %s (%s/%s)' % (course_id, y, num_courses) teachers = callGAPIpages(croom.courses().teachers(), u'list', u'teachers', page_message=teacher_message, courseId=course_id) students = callGAPIpages(croom.courses().students(), u'list', u'students', page_message=student_message, courseId=course_id) for teacher in teachers: participant = flatten_json(teacher) participant[u'courseId'] = course_id participant[u'courseName'] = course[u'name'] participant[u'userRole'] = u'TEACHER' csvRows.append(participant) for item in participant: if item not in titles: titles.append(item) for student in students: participant = flatten_json(student) participant[u'courseId'] = course_id participant[u'courseName'] = course[u'name'] participant[u'userRole'] = u'STUDENT' csvRows.append(participant) for item in participant: if item not in titles: titles.append(item) y += 1 writeCSVfile(csvRows, titles, u'Course Participants', todrive) PRINTJOB_ASCENDINGORDER_MAP = { u'createtime': u'CREATE_TIME', u'status': u'STATUS', u'title': u'TITLE', } PRINTJOB_DESCENDINGORDER_MAP = { u'CREATE_TIME': u'CREATE_TIME_DESC', u'STATUS': u'STATUS_DESC', u'TITLE': u'TITLE_DESC', } PRINTJOBS_DEFAULT_JOB_LIMIT = 25 PRINTJOBS_DEFAULT_MAX_RESULTS = 100 def doPrintPrintJobs(): cp = buildGAPIObject(u'cloudprint') todrive = False titles = [u'printerid', u'id'] csvRows = [] printerid = None owner = None status = None sortorder = None descending = False query = None age = None older_or_newer = None jobLimit = PRINTJOBS_DEFAULT_JOB_LIMIT i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower().replace(u'_', u'') if myarg == u'todrive': todrive = True i += 1 elif myarg in [u'olderthan', u'newerthan']: if myarg == u'olderthan': older_or_newer = u'older' else: older_or_newer = u'newer' age_number = sys.argv[i+1][:-1] if not age_number.isdigit(): print u'ERROR: expected a number; got %s' % age_number sys.exit(2) age_unit = sys.argv[i+1][-1].lower() if age_unit == u'm': age = int(time.time()) - (int(age_number) * 60) elif age_unit == u'h': age = int(time.time()) - (int(age_number) * 60 * 60) elif age_unit == u'd': age = int(time.time()) - (int(age_number) * 60 * 60 * 24) else: print u'ERROR: expected m (minutes), h (hours) or d (days); got %s' % age_unit sys.exit(2) i += 2 elif myarg == u'query': query = sys.argv[i+1] i += 2 elif myarg == u'status': status = sys.argv[i+1] i += 2 elif myarg == u'ascending': descending = False i += 1 elif myarg == u'descending': descending = True i += 1 elif myarg == u'orderby': sortorder = sys.argv[i+1].lower().replace(u'_', u'') if sortorder not in PRINTJOB_ASCENDINGORDER_MAP: print u'ERROR: orderby must be one of %s; got %s' % (u', '.join(PRINTJOB_ASCENDINGORDER_MAP), sortorder) sys.exit(2) sortorder = PRINTJOB_ASCENDINGORDER_MAP[sortorder] i += 2 elif myarg in [u'printer', u'printerid']: printerid = sys.argv[i+1] i += 2 elif myarg in [u'owner', u'user']: owner = sys.argv[i+1] i += 2 elif myarg == u'limit': jobLimit = max(0, int(sys.argv[i+1])) i += 2 else: print u'ERROR: %s is not a valid argument for "gam print printjobs"' % sys.argv[i] sys.exit(2) if sortorder and descending: sortorder = PRINTJOB_DESCENDINGORDER_MAP[sortorder] if printerid: result = callGAPI(cp.printers(), u'get', printerid=printerid) checkCloudPrintResult(result) jobCount = offset = 0 while True: if jobLimit == 0: limit = PRINTJOBS_DEFAULT_MAX_RESULTS else: limit = min(PRINTJOBS_DEFAULT_MAX_RESULTS, jobLimit-jobCount) if limit == 0: break result = callGAPI(cp.jobs(), u'list', printerid=printerid, q=query, status=status, sortorder=sortorder, owner=owner, offset=offset, limit=limit) checkCloudPrintResult(result) newJobs = result[u'range'][u'jobsCount'] if newJobs == 0: break jobCount += newJobs offset += newJobs for job in result[u'jobs']: createTime = int(job[u'createTime'])/1000 if older_or_newer: if older_or_newer == u'older' and createTime > age: continue elif older_or_newer == u'newer' and createTime < age: continue updateTime = int(job[u'updateTime'])/1000 job[u'createTime'] = datetime.datetime.fromtimestamp(createTime).strftime(u'%Y-%m-%d %H:%M:%S') job[u'updateTime'] = datetime.datetime.fromtimestamp(updateTime).strftime(u'%Y-%m-%d %H:%M:%S') job[u'tags'] = u' '.join(job[u'tags']) addRowTitlesToCSVfile(flatten_json(job), csvRows, titles) writeCSVfile(csvRows, titles, u'Print Jobs', todrive) def doPrintPrinters(): cp = buildGAPIObject(u'cloudprint') todrive = False titles = [u'id',] csvRows = [] query = None printer_type = None connection_status = None extra_fields = None i = 3 while i < len(sys.argv): if sys.argv[i].lower() == u'query': query = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'type': printer_type = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'status': connection_status = sys.argv[i+1] i += 2 elif sys.argv[i].lower().replace(u'_', u'') == u'extrafields': extra_fields = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'todrive': todrive = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam print printers"' % sys.argv[i] sys.exit(2) printers = callGAPI(cp.printers(), u'list', q=query, type=printer_type, connection_status=connection_status, extra_fields=extra_fields) checkCloudPrintResult(printers) for printer in printers[u'printers']: createTime = int(printer[u'createTime'])/1000 accessTime = int(printer[u'accessTime'])/1000 updateTime = int(printer[u'updateTime'])/1000 printer[u'createTime'] = datetime.datetime.fromtimestamp(createTime).strftime(u'%Y-%m-%d %H:%M:%S') printer[u'accessTime'] = datetime.datetime.fromtimestamp(accessTime).strftime(u'%Y-%m-%d %H:%M:%S') printer[u'updateTime'] = datetime.datetime.fromtimestamp(updateTime).strftime(u'%Y-%m-%d %H:%M:%S') printer[u'tags'] = u' '.join(printer[u'tags']) addRowTitlesToCSVfile(flatten_json(printer), csvRows, titles) writeCSVfile(csvRows, titles, u'Printers', todrive) def changeCalendarAttendees(users): do_it = True i = 5 allevents = False start_date = end_date = None while len(sys.argv) > i: if sys.argv[i].lower() == u'csv': csv_file = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'dryrun': do_it = False i += 1 elif sys.argv[i].lower() == u'start': start_date = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'end': end_date = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'allevents': allevents = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam update calattendees"' % sys.argv[i] sys.exit(2) attendee_map = {} f = openFile(csv_file) csvFile = csv.reader(f) for row in csvFile: attendee_map[row[0].lower()] = row[1].lower() closeFile(f) for user in users: sys.stdout.write(u'Checking user %s\n' % user) user, cal = buildCalendarGAPIObject(user) if not cal: continue page_token = None while True: events_page = callGAPI(cal.events(), u'list', calendarId=user, pageToken=page_token, timeMin=start_date, timeMax=end_date, showDeleted=False, showHiddenInvitations=False) print u'Got %s items' % len(events_page.get(u'items', [])) for event in events_page.get(u'items', []): if event[u'status'] == u'cancelled': #print u' skipping cancelled event' continue try: event_summary = convertUTF8(event[u'summary']) except (KeyError, UnicodeEncodeError, UnicodeDecodeError): event_summary = event[u'id'] try: if not allevents and event[u'organizer'][u'email'].lower() != user: #print u' skipping not-my-event %s' % event_summary continue except KeyError: pass # no email for organizer needs_update = False try: for attendee in event[u'attendees']: try: if attendee[u'email'].lower() in attendee_map: old_email = attendee[u'email'].lower() new_email = attendee_map[attendee[u'email'].lower()] print u' SWITCHING attendee %s to %s for %s' % (old_email, new_email, event_summary) event[u'attendees'].remove(attendee) event[u'attendees'].append({u'email': new_email}) needs_update = True except KeyError: # no email for that attendee pass except KeyError: continue # no attendees if needs_update: body = {} body[u'attendees'] = event[u'attendees'] print u'UPDATING %s' % event_summary if do_it: callGAPI(cal.events(), u'patch', calendarId=user, eventId=event[u'id'], sendNotifications=False, body=body) else: print u' not pulling the trigger.' #else: # print u' no update needed for %s' % event_summary try: page_token = events_page[u'nextPageToken'] except KeyError: break def deleteCalendar(users): buildGAPIObject(u'calendar') calendarId = sys.argv[5] if calendarId.find(u'@') == -1: calendarId = u'%s@%s' % (calendarId, GC_Values[GC_DOMAIN]) for user in users: user, cal = buildCalendarGAPIObject(user) if not cal: continue callGAPI(cal.calendarList(), u'delete', calendarId=calendarId) CALENDAR_REMINDER_METHODS = [u'email', u'sms', u'popup',] CALENDAR_NOTIFICATION_METHODS = [u'email', u'sms',] CALENDAR_NOTIFICATION_TYPES_MAP = { u'eventcreation': u'eventCreation', u'eventchange': u'eventChange', u'eventcancellation': u'eventCancellation', u'eventresponse': u'eventResponse', u'agenda': u'agenda', } def getCalendarAttributes(i, body, function): colorRgbFormat = False while i < len(sys.argv): myarg = sys.argv[i].lower().replace(u'_', u'') if myarg == u'selected': if sys.argv[i+1].lower() in true_values: body[u'selected'] = True elif sys.argv[i+1].lower() in false_values: body[u'selected'] = False else: print u'ERROR: Value for selected must be true or false; got %s' % sys.argv[i+1] sys.exit(2) i += 2 elif myarg == u'hidden': if sys.argv[i+1].lower() in true_values: body[u'hidden'] = True elif sys.argv[i+1].lower() in false_values: body[u'hidden'] = False else: print u'ERROR: Value for hidden must be true or false; got %s' % sys.argv[i+1] sys.exit(2) i += 2 elif myarg == u'summary': body[u'summaryOverride'] = sys.argv[i+1] i += 2 elif myarg == u'colorindex': body[u'colorId'] = str(sys.argv[i+1]) i += 2 elif myarg == u'backgroundcolor': body[u'backgroundColor'] = sys.argv[i+1] colorRgbFormat = True i += 2 elif myarg == u'foregroundcolor': body[u'foregroundColor'] = sys.argv[i+1] colorRgbFormat = True i += 2 elif myarg == u'reminder': body.setdefault(u'defaultReminders', []) method = sys.argv[i+1].lower() if method not in CLEAR_NONE_ARGUMENT: if method not in CALENDAR_REMINDER_METHODS: print u'ERROR: Method must be one of %s; got %s' % (u', '.join(CALENDAR_REMINDER_METHODS+CLEAR_NONE_ARGUMENT), method) sys.exit(2) try: minutes = int(sys.argv[i+2]) except ValueError: print u'ERROR: Reminder time must be specified in minutes; got %s' % sys.argv[i+2] sys.exit(2) body[u'defaultReminders'].append({u'method': method, u'minutes': minutes}) i += 3 else: i += 2 elif myarg == u'notification': body.setdefault(u'notificationSettings', {u'notifications': []}) method = sys.argv[i+1].lower() if method not in CLEAR_NONE_ARGUMENT: if method not in CALENDAR_NOTIFICATION_METHODS: print u'ERROR: Method must be one of %s; got %s' % (u', '.join(CALENDAR_NOTIFICATION_METHODS+CLEAR_NONE_ARGUMENT), method) sys.exit(2) eventType = sys.argv[i+2].lower() if eventType not in CALENDAR_NOTIFICATION_TYPES_MAP: print u'ERROR: Event must be one of %s; got %s' % (u', '.join(CALENDAR_NOTIFICATION_TYPES_MAP), eventType) sys.exit(2) body[u'notificationSettings'][u'notifications'].append({u'method': method, u'type': CALENDAR_NOTIFICATION_TYPES_MAP[eventType]}) i += 3 else: i += 2 else: print u'ERROR: %s is not a valid argument for "gam %s calendar"' % (sys.argv[i], function) sys.exit(2) return colorRgbFormat def addCalendar(users): buildGAPIObject(u'calendar') calendarId = sys.argv[5] if calendarId.find(u'@') == -1: calendarId = u'%s@%s' % (calendarId, GC_Values[GC_DOMAIN]) body = {u'id': calendarId, u'selected': True, u'hidden': False} colorRgbFormat = getCalendarAttributes(6, body, u'add') i = 0 count = len(users) for user in users: i += 1 user, cal = buildCalendarGAPIObject(user) if not cal: continue print u"Subscribing %s to %s calendar (%s/%s)" % (user, calendarId, i, count) callGAPI(cal.calendarList(), u'insert', body=body, colorRgbFormat=colorRgbFormat) def updateCalendar(users): buildGAPIObject(u'calendar') calendarId = sys.argv[5] if calendarId.find(u'@') == -1: calendarId = u'%s@%s' % (calendarId, GC_Values[GC_DOMAIN]) body = {} colorRgbFormat = getCalendarAttributes(6, body, u'update') i = 0 count = len(users) for user in users: i += 1 user, cal = buildCalendarGAPIObject(user) if not cal: continue print u"Updating %s's subscription to calendar %s (%s/%s)" % (user, calendarId, i, count) callGAPI(cal.calendarList(), u'update', calendarId=calendarId, body=body, colorRgbFormat=colorRgbFormat) def doPrinterShowACL(): cp = buildGAPIObject(u'cloudprint') show_printer = sys.argv[2] printer_info = callGAPI(cp.printers(), u'get', printerid=show_printer) checkCloudPrintResult(printer_info) for acl in printer_info[u'printers'][0][u'access']: if u'key' in acl: acl[u'accessURL'] = u'https://www.google.com/cloudprint/addpublicprinter.html?printerid=%s&key=%s' % (show_printer, acl[u'key']) print_json(None, acl) print def doPrinterAddACL(): cp = buildGAPIObject(u'cloudprint') printer = sys.argv[2] role = sys.argv[4].upper() scope = sys.argv[5] public = None skip_notification = True if scope.lower() == u'public': public = True scope = None role = None skip_notification = None elif scope.find(u'@') == -1: scope = u'/hd/domain/%s' % scope 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: who = u'public' role = u'user' print u'Added %s %s' % (role, who) def doPrinterDelACL(): cp = buildGAPIObject(u'cloudprint') printer = sys.argv[2] scope = sys.argv[4] public = None if scope.lower() == u'public': public = True scope = None elif scope.find(u'@') == -1: scope = u'/hd/domain/%s' % scope result = callGAPI(cp.printers(), u'unshare', printerid=printer, scope=scope, public=public) checkCloudPrintResult(result) who = scope if who == None: who = u'public' print u'Removed %s' % who def encode_multipart(fields, files, boundary=None): def escape_quote(s): return s.replace('"', '\\"') def getFormDataLine(name, value, boundary): return '--{0}'.format(boundary), 'Content-Disposition: form-data; name="{0}"'.format(escape_quote(name)), '', str(value) if boundary is None: boundary = ''.join(random.choice(string.digits + string.ascii_letters) for i in range(30)) lines = [] for name, value in fields.items(): if name == u'tags': for tag in value: lines.extend(getFormDataLine('tag', tag, boundary)) else: lines.extend(getFormDataLine(name, value, boundary)) for name, value in files.items(): filename = value[u'filename'] mimetype = value[u'mimetype'] lines.extend(( '--{0}'.format(boundary), 'Content-Disposition: form-data; name="{0}"; filename="{1}"'.format(escape_quote(name), escape_quote(filename)), 'Content-Type: {0}'.format(mimetype), '', value[u'content'], )) lines.extend(( '--{0}--'.format(boundary), '', )) body = '\r\n'.join(lines) headers = { 'Content-Type': 'multipart/form-data; boundary={0}'.format(boundary), 'Content-Length': str(len(body)), } return (body, headers) def doPrintJobFetch(): cp = buildGAPIObject(u'cloudprint') printerid = sys.argv[2] if printerid == u'any': printerid = None owner = None status = None sortorder = None descending = False query = None age = None older_or_newer = None jobLimit = PRINTJOBS_DEFAULT_JOB_LIMIT targetFolder = os.getcwd() i = 4 while i < len(sys.argv): myarg = sys.argv[i].lower().replace(u'_', u'') if myarg in [u'olderthan', u'newerthan']: if myarg == u'olderthan': older_or_newer = u'older' else: older_or_newer = u'newer' age_number = sys.argv[i+1][:-1] if not age_number.isdigit(): print u'ERROR: expected a number; got %s' % age_number sys.exit(2) age_unit = sys.argv[i+1][-1].lower() if age_unit == u'm': age = int(time.time()) - (int(age_number) * 60) elif age_unit == u'h': age = int(time.time()) - (int(age_number) * 60 * 60) elif age_unit == u'd': age = int(time.time()) - (int(age_number) * 60 * 60 * 24) else: print u'ERROR: expected m (minutes), h (hours) or d (days); got %s' % age_unit sys.exit(2) i += 2 elif myarg == u'query': query = sys.argv[i+1] i += 2 elif myarg == u'status': status = sys.argv[i+1] i += 2 elif myarg == u'ascending': descending = False i += 1 elif myarg == u'descending': descending = True i += 1 elif myarg == u'orderby': sortorder = sys.argv[i+1].lower().replace(u'_', u'') if sortorder not in PRINTJOB_ASCENDINGORDER_MAP: print u'ERROR: orderby must be one of %s; got %s' % (u', '.join(PRINTJOB_ASCENDINGORDER_MAP), sortorder) sys.exit(2) sortorder = PRINTJOB_ASCENDINGORDER_MAP[sortorder] i += 2 elif myarg in [u'owner', u'user']: owner = sys.argv[i+1] i += 2 elif myarg == u'limit': jobLimit = max(0, int(sys.argv[i+1])) i += 2 elif myarg == u'drivedir': targetFolder = GC_Values[GC_DRIVE_DIR] i += 1 elif myarg == u'targetfolder': targetFolder = os.path.expanduser(sys.argv[i+1]) if not os.path.isdir(targetFolder): os.makedirs(targetFolder) i += 2 else: print u'ERROR: %s is not a valid argument for "gam printjobs fetch"' % sys.argv[i] sys.exit(2) if sortorder and descending: sortorder = PRINTJOB_DESCENDINGORDER_MAP[sortorder] if printerid: result = callGAPI(cp.printers(), u'get', printerid=printerid) checkCloudPrintResult(result) valid_chars = u'-_.() abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' ssd = u'{"state": {"type": "DONE"}}' jobCount = offset = 0 while True: if jobLimit == 0: limit = PRINTJOBS_DEFAULT_MAX_RESULTS else: limit = min(PRINTJOBS_DEFAULT_MAX_RESULTS, jobLimit-jobCount) if limit == 0: break result = callGAPI(cp.jobs(), u'list', printerid=printerid, q=query, status=status, sortorder=sortorder, owner=owner, offset=offset, limit=limit) checkCloudPrintResult(result) newJobs = result[u'range'][u'jobsCount'] if newJobs == 0: break jobCount += newJobs offset += newJobs for job in result[u'jobs']: createTime = int(job[u'createTime'])/1000 if older_or_newer: if older_or_newer == u'older' and createTime > age: continue elif older_or_newer == u'newer' and createTime < age: continue fileUrl = job[u'fileUrl'] jobid = job[u'id'] fileName = os.path.join(targetFolder, u'{0}-{1}'.format(u''.join(c if c in valid_chars else u'_' for c in job[u'title']), jobid)) _, content = cp._http.request(uri=fileUrl, method='GET') if writeFile(fileName, content, continueOnError=True): # ticket = callGAPI(cp.jobs(), u'getticket', jobid=jobid, use_cjt=True) result = callGAPI(cp.jobs(), u'update', jobid=jobid, semantic_state_diff=ssd) checkCloudPrintResult(result) print u'Printed job %s to %s' % (jobid, fileName) if jobCount == 0: print u'No print jobs.' def doDelPrinter(): cp = buildGAPIObject(u'cloudprint') printerid = sys.argv[3] result = callGAPI(cp.printers(), u'delete', printerid=printerid) checkCloudPrintResult(result) def doGetPrinterInfo(): cp = buildGAPIObject(u'cloudprint') printerid = sys.argv[3] everything = False i = 4 while i < len(sys.argv): if sys.argv[i] == u'everything': everything = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam info printer"' % sys.argv[i] sys.exit(2) result = callGAPI(cp.printers(), u'get', printerid=printerid) checkCloudPrintResult(result) printer_info = result[u'printers'][0] createTime = int(printer_info[u'createTime'])/1000 accessTime = int(printer_info[u'accessTime'])/1000 updateTime = int(printer_info[u'updateTime'])/1000 printer_info[u'createTime'] = datetime.datetime.fromtimestamp(createTime).strftime(u'%Y-%m-%d %H:%M:%S') printer_info[u'accessTime'] = datetime.datetime.fromtimestamp(accessTime).strftime(u'%Y-%m-%d %H:%M:%S') printer_info[u'updateTime'] = datetime.datetime.fromtimestamp(updateTime).strftime(u'%Y-%m-%d %H:%M:%S') printer_info[u'tags'] = u' '.join(printer_info[u'tags']) if not everything: del printer_info[u'capabilities'] del printer_info[u'access'] print_json(None, printer_info) def doUpdatePrinter(): cp = buildGAPIObject(u'cloudprint') printerid = sys.argv[3] kwargs = {} i = 4 update_items = [u'isTosAccepted', u'gcpVersion', u'setupUrl', u'quotaEnabled', u'id', u'supportUrl', u'firmware', u'currentQuota', u'type', u'public', u'status', u'description', u'defaultDisplayName', u'proxy', u'dailyQuota', u'manufacturer', u'displayName', u'name', u'uuid', u'updateUrl', u'ownerId', u'model'] while i < len(sys.argv): arg_in_item = False for item in update_items: if item.lower() == sys.argv[i].lower(): kwargs[item] = sys.argv[i+1] i += 2 arg_in_item = True break if not arg_in_item: print u'ERROR: %s is not a valid argument for "gam update printer"' % sys.argv[i] sys.exit(2) result = callGAPI(cp.printers(), u'update', printerid=printerid, **kwargs) checkCloudPrintResult(result) print u'Updated printer %s' % printerid def doPrinterRegister(): cp = buildGAPIObject(u'cloudprint') form_fields = {u'name': u'GAM', u'proxy': u'GAM', u'uuid': cp._http.request.credentials.id_token[u'sub'], u'manufacturer': __author__, u'model': u'cp1', u'gcp_version': u'2.0', u'setup_url': GAM_URL, u'support_url': u'https://groups.google.com/forum/#!forum/google-apps-manager', u'update_url': GAM_RELEASES, u'firmware': __version__, u'semantic_state': {"version": "1.0", "printer": {"state": "IDLE",}}, u'use_cdd': True, u'capabilities': {"version": "1.0", "printer": {"supported_content_type": [{"content_type": "application/pdf", "min_version": "1.5"}, {"content_type": "image/jpeg"}, {"content_type": "text/plain"} ], "copies": {"default": 1, "max": 100}, "media_size": {"option": [{"name": "ISO_A4", "width_microns": 210000, "height_microns": 297000}, {"name": "NA_LEGAL", "width_microns": 215900, "height_microns": 355600}, {"name": "NA_LETTER", "width_microns": 215900, "height_microns": 279400, "is_default": True} ], }, }, }, u'tags': [u'GAM', GAM_URL], } body, headers = encode_multipart(form_fields, {}) #Get the printer first to make sure our OAuth access token is fresh callGAPI(cp.printers(), u'list') _, result = cp._http.request(uri='https://www.google.com/cloudprint/register', method='POST', body=body, headers=headers) result = json.loads(result) checkCloudPrintResult(result) print u'Created printer %s' % result[u'printers'][0][u'id'] def doPrintJobResubmit(): cp = buildGAPIObject(u'cloudprint') jobid = sys.argv[2] printerid = sys.argv[4] ssd = u'{"state": {"type": "HELD"}}' result = callGAPI(cp.jobs(), u'update', jobid=jobid, semantic_state_diff=ssd) checkCloudPrintResult(result) ticket = callGAPI(cp.jobs(), u'getticket', jobid=jobid, use_cjt=True) result = callGAPI(cp.jobs(), u'resubmit', printerid=printerid, jobid=jobid, ticket=ticket) checkCloudPrintResult(result) print u'Success resubmitting %s as job %s to printer %s' % (jobid, result[u'job'][u'id'], printerid) def doPrintJobSubmit(): cp = buildGAPIObject(u'cloudprint') printer = sys.argv[2] content = sys.argv[4] form_fields = {u'printerid': printer, u'title': content, u'ticket': u'{"version": "1.0"}', u'tags': [u'GAM', GAM_URL]} i = 5 while i < len(sys.argv): if sys.argv[i].lower() == u'tag': form_fields[u'tags'].append(sys.argv[i+1]) i += 2 elif sys.argv[i].lower() in [u'name', u'title']: form_fields[u'title'] = sys.argv[i+1] i += 2 else: print u'ERROR: %s is not a valid argument for "gam printer ... print"' % sys.argv[i] sys.exit(2) form_files = {} if content[:4] == u'http': form_fields[u'content'] = content form_fields[u'contentType'] = u'url' else: filepath = content content = os.path.basename(content) mimetype = mimetypes.guess_type(filepath)[0] if mimetype == None: mimetype = u'application/octet-stream' filecontent = readFile(filepath) form_files[u'content'] = {u'filename': content, u'content': filecontent, u'mimetype': mimetype} #result = callGAPI(cp.printers(), u'submit', body=body) body, headers = encode_multipart(form_fields, form_files) #Get the printer first to make sure our OAuth access token is fresh callGAPI(cp.printers(), u'get', printerid=printer) _, result = cp._http.request(uri='https://www.google.com/cloudprint/submit', method='POST', body=body, headers=headers) checkCloudPrintResult(result) if isinstance(result, str): result = json.loads(result) print u'Submitted print job %s' % result[u'job'][u'id'] def doDeletePrintJob(): cp = buildGAPIObject(u'cloudprint') job = sys.argv[2] result = callGAPI(cp.jobs(), u'delete', jobid=job) checkCloudPrintResult(result) print u'Print Job %s deleted' % job def doCancelPrintJob(): cp = buildGAPIObject(u'cloudprint') job = sys.argv[2] ssd = u'{"state": {"type": "ABORTED", "user_action_cause": {"action_code": "CANCELLED"}}}' result = callGAPI(cp.jobs(), u'update', jobid=job, semantic_state_diff=ssd) checkCloudPrintResult(result) print u'Print Job %s cancelled' % job def checkCloudPrintResult(result): if isinstance(result, str): try: result = json.loads(result) except ValueError: print u'ERROR: unexpected response: %s' % result sys.exit(3) if not result[u'success']: print u'ERROR %s: %s' % (result[u'errorCode'], result[u'message']) sys.exit(result[u'errorCode']) def formatACLRule(rule): if rule[u'scope'][u'type'] != u'default': return u'(Scope: {0}:{1}, Role: {2})'.format(rule[u'scope'][u'type'], rule[u'scope'][u'value'], rule[u'role']) return u'(Scope: {0}, Role: {1})'.format(rule[u'scope'][u'type'], rule[u'role']) def doCalendarShowACL(): cal = buildGAPIObject(u'calendar') show_cal = sys.argv[2] if show_cal.find(u'@') == -1: show_cal = u'%s@%s' % (show_cal, GC_Values[GC_DOMAIN]) acls = callGAPIitems(cal.acl(), u'list', u'items', calendarId=show_cal) i = 0 count = len(acls) for rule in acls: i += 1 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: act_as, cal = buildCalendarGAPIObject(act_as) else: cal = buildGAPIObject(u'calendar') body = {u'scope': {}} if calendarId == None: calendarId = sys.argv[2] if calendarId.find(u'@') == -1: calendarId = u'%s@%s' % (calendarId, GC_Values[GC_DOMAIN]) if role != None: body[u'role'] = role else: body[u'role'] = sys.argv[4].lower() if body[u'role'] not in [u'freebusy', u'read', u'reader', u'editor', u'owner', u'none']: print u'ERROR: Role must be one of freebusy, read, editor, owner, none; got %s' % body[u'role'] sys.exit(2) if body[u'role'] == u'freebusy': body[u'role'] = u'freeBusyReader' elif body[u'role'] in [u'read', u'reader']: body[u'role'] = u'reader' elif body[u'role'] == u'editor': body[u'role'] = u'writer' if scope != None: body[u'scope'][u'type'] = scope else: body[u'scope'][u'type'] = sys.argv[5].lower() i = 6 if body[u'scope'][u'type'] not in [u'default', u'user', u'group', u'domain']: body[u'scope'][u'type'] = u'user' i = 5 try: if entity != 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() if (body[u'scope'][u'type'] in [u'user', u'group']) and body[u'scope'][u'value'].find(u'@') == -1: body[u'scope'][u'value'] = u'%s@%s' % (body[u'scope'][u'value'], GC_Values[GC_DOMAIN]) except IndexError: pass if body[u'scope'][u'type'] == u'domain': try: body[u'scope'][u'value'] = sys.argv[6].lower() except IndexError: body[u'scope'][u'value'] = GC_Values[GC_DOMAIN] callGAPI(cal.acl(), u'insert', calendarId=calendarId, body=body) def doCalendarUpdateACL(): calendarId = sys.argv[2] role = sys.argv[4].lower() scope = sys.argv[5].lower() if len(sys.argv) > 6: entity = sys.argv[6].lower() else: entity = None doCalendarAddACL(calendarId=calendarId, role=role, scope=scope, entity=entity) def doCalendarDelACL(): calendarId = sys.argv[2] entity = sys.argv[5].lower() scope = u'user' if entity == u'domain': scope = u'domain' elif entity == u'default': scope = u'default' entity = u'' doCalendarAddACL(calendarId=calendarId, role=u'none', scope=scope, entity=entity) def doCalendarWipeData(): calendarId, cal = buildCalendarGAPIObject(sys.argv[2]) if not cal: return callGAPI(cal.calendars(), u'clear', calendarId=calendarId) def doCalendarAddEvent(): calendarId, cal = buildCalendarGAPIObject(sys.argv[2]) if not cal: return sendNotifications = timeZone = None i = 4 body = {} while i < len(sys.argv): if sys.argv[i].lower() == u'notifyattendees': sendNotifications = True i += 1 elif sys.argv[i].lower() == u'attendee': try: body[u'attendees'].append({u'email': sys.argv[i+1]}) except KeyError: body[u'attendees'] = [{u'email': sys.argv[i+1]},] i += 2 elif sys.argv[i].lower() == u'optionalattendee': try: body[u'attendees'].append({u'email': sys.argv[i+1], u'optional': True}) except TypeError: body[u'attendees'] = [{u'email': sys.argv[i+1], u'optional': True},] i += 2 elif sys.argv[i].lower() == u'anyonecanaddself': body[u'anyoneCanAddSelf'] = True i += 1 elif sys.argv[i].lower() == u'description': body[u'description'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'start': if sys.argv[i+1].lower() == u'allday': body[u'start'] = {u'date': sys.argv[i+2]} i += 3 else: body[u'start'] = {u'dateTime': sys.argv[i+1]} i += 2 elif sys.argv[i].lower() == u'end': if sys.argv[i+1].lower() == u'allday': body[u'end'] = {u'date': sys.argv[i+2]} i += 3 else: body[u'end'] = {u'dateTime': sys.argv[i+1]} i += 2 elif sys.argv[i].lower() == u'guestscantinviteothers': body[u'guestsCanInviteOthers'] = False i += 1 elif sys.argv[i].lower() == u'guestscantseeothers': body[u'guestsCanSeeOtherGuests'] = False i += 1 elif sys.argv[i].lower() == u'id': body[u'id'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'summary': body[u'summary'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'location': body[u'location'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'available': body[u'transparency'] = u'transparent' i += 1 elif sys.argv[i].lower() == u'visibility': if sys.argv[i+1].lower() in [u'default', u'public', u'private']: body[u'visibility'] = sys.argv[i+1].lower() else: print u'ERROR: visibility must be one of default, public, private; got %s' % sys.argv[i+1] sys.exit(2) i += 2 elif sys.argv[i].lower() == u'tentative': body[u'status'] = u'tentative' i += 1 elif sys.argv[i].lower() == u'source': body[u'source'] = {u'title': sys.argv[i+1], u'url': sys.argv[i+2]} i += 3 elif sys.argv[i].lower() == u'noreminders': body[u'reminders'] = {u'useDefault': False} i += 1 elif sys.argv[i].lower() == u'reminder': try: body[u'reminders'][u'overrides'].append({u'minutes': sys.argv[i+1], u'method': sys.argv[i+2]}) body[u'reminders'][u'useDefault'] = False except KeyError: body[u'reminders'] = {u'useDefault': False, u'overrides': [{u'minutes': sys.argv[i+1], u'method': sys.argv[i+2]},]} i += 3 elif sys.argv[i].lower() == u'recurrence': try: body[u'recurrence'].append(sys.argv[i+1]) except KeyError: body[u'recurrence'] = [sys.argv[i+1],] i += 2 elif sys.argv[i].lower() == u'timezone': timeZone = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'privateproperty': if u'extendedProperties' not in body: body[u'extendedProperties'] = {u'private': {}, u'shared': {}} body[u'extendedProperties'][u'private'][sys.argv[i+1]] = sys.argv[i+2] i += 3 elif sys.argv[i].lower() == u'sharedproperty': if u'extendedProperties' not in body: body[u'extendedProperties'] = {u'private': {}, u'shared': {}} body[u'extendedProperties'][u'shared'][sys.argv[i+1]] = sys.argv[i+2] i += 3 elif sys.argv[i].lower() == u'colorindex': body[u'colorId'] = str(sys.argv[i+1]) i += 2 else: print u'ERROR: %s is not a valid argument for "gam calendar"' % sys.argv[i] sys.exit(2) if not timeZone and u'recurrence' in body: timeZone = callGAPI(cal.calendars(), u'get', calendarId=calendarId, fields=u'timeZone')[u'timeZone'] if u'recurrence' in body: for a_time in [u'start', u'end']: try: body[a_time][u'timeZone'] = timeZone except KeyError: pass callGAPI(cal.events(), u'insert', calendarId=calendarId, sendNotifications=sendNotifications, body=body) def doProfile(users): cd = buildGAPIObject(u'directory') if sys.argv[4].lower() == u'share' or sys.argv[4].lower() == u'shared': body = {u'includeInGlobalAddressList': True} elif sys.argv[4].lower() == u'unshare' or sys.argv[4].lower() == u'unshared': body = {u'includeInGlobalAddressList': False} else: print u'ERROR: value for "gam profile" must be true or false; got %s' % sys.argv[4] sys.exit(2) i = 0 count = len(users) for user in users: i += 1 if user[:4].lower() == u'uid:': user = user[4:] elif user.find(u'@') == -1: user = u'%s@%s' % (user, GC_Values[GC_DOMAIN]) print u'Setting Profile Sharing to %s for %s (%s/%s)' % (body[u'includeInGlobalAddressList'], user, i, count) callGAPI(cd.users(), u'patch', soft_errors=True, userKey=user, body=body) def showProfile(users): cd = buildGAPIObject(u'directory') i = 0 count = len(users) for user in users: i += 1 if user[:4].lower() == u'uid:': user = user[4:] elif user.find(u'@') == -1: user = u'%s@%s' % (user, GC_Values[GC_DOMAIN]) result = callGAPI(cd.users(), u'get', userKey=user, fields=u'includeInGlobalAddressList') try: print u'User: %s Profile Shared: %s (%s/%s)' % (user, result[u'includeInGlobalAddressList'], i, count) except IndexError: pass def doPhoto(users): cd = buildGAPIObject(u'directory') i = 0 count = len(users) for user in users: i += 1 if user[:4].lower() == u'uid:': user = user[4:] elif user.find(u'@') == -1: user = u'%s@%s' % (user, GC_Values[GC_DOMAIN]) filename = sys.argv[5].replace(u'#user#', user) filename = filename.replace(u'#email#', user) filename = filename.replace(u'#username#', user[:user.find(u'@')]) print u"Updating photo for %s with %s (%s/%s)" % (user, filename, i, count) if re.match(u'^(ht|f)tps?://.*$', filename): import urllib2 try: f = urllib2.urlopen(filename) image_data = str(f.read()) except urllib2.HTTPError as e: print e continue else: image_data = readFile(filename, continueOnError=True, displayError=True) if image_data == None: continue image_data = base64.urlsafe_b64encode(image_data) body = {u'photoData': image_data} callGAPI(cd.users().photos(), u'update', soft_errors=True, userKey=user, body=body) def getPhoto(users): cd = buildGAPIObject(u'directory') targetFolder = os.getcwd() showPhotoData = True i = 5 while i < len(sys.argv): myarg = sys.argv[i].lower().replace(u'_', u'') if myarg == u'drivedir': targetFolder = GC_Values[GC_DRIVE_DIR] i += 1 elif myarg == u'targetfolder': targetFolder = os.path.expanduser(sys.argv[i+1]) if not os.path.isdir(targetFolder): os.makedirs(targetFolder) i += 2 elif myarg == u'noshow': showPhotoData = False i += 1 else: print u'ERROR: %s is not a valid argument for "gam get photo"' % sys.argv[i] sys.exit(2) i = 0 count = len(users) for user in users: i += 1 if user[:4].lower() == u'uid:': user = user[4:] elif user.find(u'@') == -1: user = u'%s@%s' % (user, GC_Values[GC_DOMAIN]) filename = os.path.join(targetFolder, u'{0}.jpg'.format(user)) print u"Saving photo to %s (%s/%s)" % (filename, i, count) try: photo = callGAPI(cd.users().photos(), u'get', throw_reasons=[u'notFound'], userKey=user) except googleapiclient.errors.HttpError: print u' no photo for %s' % user continue try: photo_data = str(photo[u'photoData']) if showPhotoData: print photo_data photo_data = base64.urlsafe_b64decode(photo_data) except KeyError: print u' no photo for %s' % user continue writeFile(filename, photo_data, continueOnError=True) def deletePhoto(users): cd = buildGAPIObject(u'directory') i = 0 count = len(users) for user in users: i += 1 if user[:4].lower() == u'uid:': user = user[4:] elif user.find(u'@') == -1: user = u'%s@%s' % (user, GC_Values[GC_DOMAIN]) print u"Deleting photo for %s (%s/%s)" % (user, i, count) callGAPI(cd.users().photos(), u'delete', userKey=user) def _showCalendar(userCalendar, j, jcount): print u' Calendar: {0} ({1}/{2})'.format(userCalendar[u'id'], j, jcount) print convertUTF8(u' Summary: {0}'.format(userCalendar.get(u'summaryOverride', userCalendar[u'summary']))) print convertUTF8(u' Description: {0}'.format(userCalendar.get(u'description', u''))) print u' Access Level: {0}'.format(userCalendar[u'accessRole']) print u' Timezone: {0}'.format(userCalendar[u'timeZone']) print convertUTF8(u' Location: {0}'.format(userCalendar.get(u'location', u''))) print u' Hidden: {0}'.format(userCalendar.get(u'hidden', u'False')) print u' Selected: {0}'.format(userCalendar.get(u'selected', u'False')) print u' Color ID: {0}, Background Color: {1}, Foreground Color: {2}'.format(userCalendar[u'colorId'], userCalendar[u'backgroundColor'], userCalendar[u'foregroundColor']) print u' Default Reminders:' for reminder in userCalendar.get(u'defaultReminders', []): print u' Method: {0}, Minutes: {1}'.format(reminder[u'method'], reminder[u'minutes']) print u' Notifications:' if u'notificationSettings' in userCalendar: for notification in userCalendar[u'notificationSettings'].get(u'notifications', []): print u' Method: {0}, Type: {1}'.format(notification[u'method'], notification[u'type']) def infoCalendar(users): buildGAPIObject(u'calendar') calendarId = sys.argv[5].lower() if calendarId != u'primary' and calendarId.find(u'@') == -1: calendarId = u'%s@%s' % (calendarId, GC_Values[GC_DOMAIN]) i = 0 count = len(users) for user in users: i += 1 user, cal = buildCalendarGAPIObject(user) if not cal: continue result = callGAPI(cal.calendarList(), u'get', soft_errors=True, calendarId=calendarId) if result: print u'User: {0}, Calendar: ({1}/{2})'.format(user, i, count) _showCalendar(result, 1, 1) def printShowCalendars(users, csvFormat): if csvFormat: todrive = False titles = [] csvRows = [] i = 5 while i < len(sys.argv): myarg = sys.argv[i].lower() if csvFormat and myarg == u'todrive': todrive = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam %s calendars"' % (myarg, [u'show', u'print'][csvFormat]) sys.exit(2) i = 0 count = len(users) for user in users: i += 1 user, cal = buildCalendarGAPIObject(user) if not cal: continue result = callGAPIpages(cal.calendarList(), u'list', u'items') jcount = len(result) if not csvFormat: print u'User: {0}, Calendars: ({1}/{2})'.format(user, i, count) if jcount == 0: continue j = 0 for userCalendar in result: j += 1 _showCalendar(userCalendar, j, jcount) else: if jcount == 0: continue for userCalendar in result: row = {u'primaryEmail': user} addRowTitlesToCSVfile(flatten_json(userCalendar, flattened=row), csvRows, titles) if csvFormat: sortCSVTitles([u'primaryEmail', u'id'], titles) writeCSVfile(csvRows, titles, u'Calendars', todrive) def showCalSettings(users): for user in users: user, cal = buildCalendarGAPIObject(user) if not cal: continue feed = callGAPI(cal.settings(), u'list') for setting in feed[u'items']: print u'%s: %s' % (setting[u'id'], setting[u'value']) def printDriveSettings(users): todrive = False i = 5 while i < len(sys.argv): if sys.argv[i].lower() == u'todrive': todrive = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam show drivesettings"' % sys.argv[i] sys.exit(2) dont_show = [u'kind', u'selfLink', u'exportFormats', u'importFormats', u'maxUploadSizes', u'additionalRoleInfo', u'etag', u'features', u'user', u'isCurrentAppInstalled'] csvRows = [] titles = [u'email',] i = 0 count = len(users) for user in users: i += 1 user, drive = buildDriveGAPIObject(user) if not drive: 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: continue row = {u'email': user} for setting in feed: if setting in dont_show: continue if setting == u'quotaBytesByService': for subsetting in feed[setting]: my_name = subsetting[u'serviceName'] my_bytes = int(subsetting[u'bytesUsed']) row[my_name] = u'%smb' % (my_bytes / 1024 / 1024) if my_name not in titles: titles.append(my_name) continue row[setting] = feed[setting] if setting not in titles: titles.append(setting) csvRows.append(row) writeCSVfile(csvRows, titles, u'User Drive Settings', todrive) def printDriveActivity(users): drive_ancestorId = u'root' drive_fileId = None todrive = False titles = [u'user.name', u'user.permissionId', u'target.id', u'target.name', u'target.mimeType'] csvRows = [] i = 5 while i < len(sys.argv): activity_object = sys.argv[i].lower().replace(u'_', u'') if activity_object == u'fileid': drive_fileId = sys.argv[i+1] drive_ancestorId = None i += 2 elif activity_object == u'folderid': drive_ancestorId = sys.argv[i+1] i += 2 elif activity_object == u'todrive': todrive = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam show driveactivity"' % sys.argv[i] sys.exit(2) for user in users: user, activity = buildActivityGAPIObject(user) if not activity: continue page_message = u'Retrieved %%%%total_items%%%% activities for %s' % user feed = callGAPIpages(activity.activities(), u'list', u'activities', page_message=page_message, source=u'drive.google.com', userId=u'me', drive_ancestorId=drive_ancestorId, groupingStrategy=u'none', drive_fileId=drive_fileId, pageSize=GC_Values[GC_ACTIVITY_MAX_RESULTS]) for item in feed: addRowTitlesToCSVfile(flatten_json(item[u'combinedEvent']), csvRows, titles) writeCSVfile(csvRows, titles, u'Drive Activity', todrive) def printPermission(permission): if u'name' in permission: print convertUTF8(permission[u'name']) elif u'id' in permission: if permission[u'id'] == u'anyone': print u'Anyone' elif permission[u'id'] == u'anyoneWithLink': print u'Anyone with Link' else: print permission[u'id'] for key in permission: if key in [u'name', u'kind', u'etag', u'selfLink',]: continue print u' %s: %s' % (key, permission[key]) def showDriveFileACL(users): fileId = sys.argv[5] for user in users: user, drive = buildDriveGAPIObject(user) if not drive: continue feed = callGAPI(drive.permissions(), u'list', fileId=fileId) for permission in feed[u'items']: printPermission(permission) print u'' def getPermissionId(argstr): permissionId = argstr.strip().lower() if permissionId[:3] == u'id:': return (False, argstr.strip()[3:]) if permissionId == u'anyone': return (False, permissionId) if permissionId == u'anyonewithlink': return (False, u'anyoneWithLink') if permissionId.find(u'@') == -1: permissionId = u'%s@%s' % (permissionId, GC_Values[GC_DOMAIN]) return (True, permissionId) def delDriveFileACL(users): fileId = sys.argv[5] isEmail, permissionId = getPermissionId(sys.argv[6]) for user in users: user, drive = buildDriveGAPIObject(user) if not drive: continue if isEmail: permissionId = callGAPI(drive.permissions(), u'getIdForEmail', email=permissionId, fields=u'id')[u'id'] isEmail = False print u'Removing permission for %s from %s' % (permissionId, fileId) callGAPI(drive.permissions(), u'delete', fileId=fileId, permissionId=permissionId) def addDriveFileACL(users): fileId = sys.argv[5] body = {u'type': sys.argv[6].lower()} sendNotificationEmails = False emailMessage = None if body[u'type'] not in [u'user', u'group', u'domain', u'anyone']: print u'ERROR: permission type must be user, group domain or anyone; got %s' % body[u'type'] if body[u'type'] == u'anyone': i = 7 else: body[u'value'] = sys.argv[7] i = 8 while i < len(sys.argv): if sys.argv[i].lower().replace(u'_', u'') == u'withlink': body[u'withLink'] = True i += 1 elif sys.argv[i].lower() == u'role': body[u'role'] = sys.argv[i+1] if body[u'role'] not in [u'reader', u'commenter', u'writer', u'owner', u'editor']: print u'ERROR: role must be reader, commenter, writer or owner; got %s' % body[u'role'] sys.exit(2) if body[u'role'] == u'commenter': body[u'role'] = u'reader' body[u'additionalRoles'] = [u'commenter'] elif body[u'role'] == u'editor': body[u'role'] = u'writer' i += 2 elif sys.argv[i].lower().replace(u'_', u'') == u'sendemail': sendNotificationEmails = True i += 1 elif sys.argv[i].lower().replace(u'_', u'') == u'emailmessage': sendNotificationEmails = True emailMessage = sys.argv[i+1] i += 2 else: print u'ERROR: %s is not a valid argument for "gam add drivefileacl"' % sys.argv[i] sys.exit(2) for user in users: user, drive = buildDriveGAPIObject(user) if not drive: continue result = callGAPI(drive.permissions(), u'insert', fileId=fileId, sendNotificationEmails=sendNotificationEmails, emailMessage=emailMessage, body=body) printPermission(result) def updateDriveFileACL(users): fileId = sys.argv[5] isEmail, permissionId = getPermissionId(sys.argv[6]) transferOwnership = None body = {} i = 7 while i < len(sys.argv): if sys.argv[i].lower().replace(u'_', u'') == u'withlink': body[u'withLink'] = True i += 1 elif sys.argv[i].lower() == u'role': body[u'role'] = sys.argv[i+1] if body[u'role'] not in [u'reader', u'commenter', u'writer', u'owner']: print u'ERROR: role must be reader, commenter, writer or owner; got %s' % body[u'role'] sys.exit(2) if body[u'role'] == u'commenter': body[u'role'] = u'reader' body[u'additionalRoles'] = [u'commenter'] i += 2 elif sys.argv[i].lower().replace(u'_', u'') == u'transferownership': if sys.argv[i+1].lower() in true_values: transferOwnership = True elif sys.argv[i+1].lower() in false_values: transferOwnership = False else: print u'ERROR: transferownership must be true or false; got %s' % sys.argv[i+1].lower() i += 2 else: print u'ERROR: %s is not a valid argument for "gam update drivefileacl"' % sys.argv[i] sys.exit(2) for user in users: user, drive = buildDriveGAPIObject(user) if not drive: continue if isEmail: permissionId = callGAPI(drive.permissions(), u'getIdForEmail', email=permissionId, fields=u'id')[u'id'] isEmail = False print u'updating permissions for %s to file %s' % (permissionId, fileId) result = callGAPI(drive.permissions(), u'patch', fileId=fileId, permissionId=permissionId, transferOwnership=transferOwnership, body=body) printPermission(result) DRIVEFILE_FIELDS_CHOICES_MAP = { u'alternatelink': u'alternateLink', u'appdatacontents': u'appDataContents', u'cancomment': u'canComment', u'canreadrevisions': u'canReadRevisions', u'copyable': u'copyable', u'createddate': u'createdDate', u'createdtime': u'createdDate', u'description': u'description', u'editable': u'editable', u'explicitlytrashed': u'explicitlyTrashed', u'fileextension': u'fileExtension', u'filesize': u'fileSize', u'foldercolorrgb': u'folderColorRgb', u'fullfileextension': u'fullFileExtension', u'headrevisionid': u'headRevisionId', u'iconlink': u'iconLink', u'id': u'id', u'lastmodifyinguser': u'lastModifyingUser', u'lastmodifyingusername': u'lastModifyingUserName', u'lastviewedbyme': u'lastViewedByMeDate', u'lastviewedbymedate': u'lastViewedByMeDate', u'lastviewedbymetime': u'lastViewedByMeDate', u'lastviewedbyuser': u'lastViewedByMeDate', u'md5': u'md5Checksum', u'md5checksum': u'md5Checksum', u'md5sum': u'md5Checksum', u'mime': u'mimeType', u'mimetype': u'mimeType', u'modifiedbyme': u'modifiedByMeDate', u'modifiedbymedate': u'modifiedByMeDate', u'modifiedbymetime': u'modifiedByMeDate', u'modifiedbyuser': u'modifiedByMeDate', u'modifieddate': u'modifiedDate', u'modifiedtime': u'modifiedDate', u'name': u'title', u'originalfilename': u'originalFilename', u'ownedbyme': u'ownedByMe', u'ownernames': u'ownerNames', u'owners': u'owners', u'parents': u'parents', u'permissions': u'permissions', u'quotabytesused': u'quotaBytesUsed', u'quotaused': u'quotaBytesUsed', u'shareable': u'shareable', u'shared': u'shared', u'sharedwithmedate': u'sharedWithMeDate', u'sharedwithmetime': u'sharedWithMeDate', u'sharinguser': u'sharingUser', u'spaces': u'spaces', u'thumbnaillink': u'thumbnailLink', u'title': u'title', u'userpermission': u'userPermission', u'version': u'version', u'viewedbyme': u'labels(viewed)', u'viewedbymedate': u'lastViewedByMeDate', u'viewedbymetime': u'lastViewedByMeDate', u'viewerscancopycontent': u'labels(restricted)', u'webcontentlink': u'webContentLink', u'webviewlink': u'webViewLink', u'writerscanshare': u'writersCanShare', } DRIVEFILE_LABEL_CHOICES_MAP = { u'restricted': u'restricted', u'restrict': u'restricted', u'starred': u'starred', u'star': u'starred', u'trashed': u'trashed', u'trash': u'trashed', u'viewed': u'viewed', u'view': u'viewed', } def printDriveFileList(users): allfields = todrive = False fieldsList = [] fieldsTitles = {} labelsList = [] titles = [u'Owner',] csvRows = [] query = u'"me" in owners' i = 5 while i < len(sys.argv): myarg = sys.argv[i].lower().replace(u'_', u'') if myarg == u'todrive': todrive = True i += 1 elif myarg == u'query': query += u' and %s' % sys.argv[i+1] i += 2 elif myarg == u'fullquery': query = sys.argv[i+1] i += 2 elif myarg == u'allfields': fieldsList = [] allfields = True i += 1 elif myarg in DRIVEFILE_FIELDS_CHOICES_MAP: addFieldToCSVfile(myarg, {myarg: [DRIVEFILE_FIELDS_CHOICES_MAP[myarg]]}, fieldsList, fieldsTitles, titles) i += 1 elif myarg in DRIVEFILE_LABEL_CHOICES_MAP: addFieldToCSVfile(myarg, {myarg: [DRIVEFILE_LABEL_CHOICES_MAP[myarg]]}, labelsList, fieldsTitles, titles) i += 1 else: print u'ERROR: %s is not a valid argument for "gam show filelist"' % myarg sys.exit(2) if fieldsList or labelsList: fields = u'nextPageToken,items(' if fieldsList: fields += u','.join(set(fieldsList)) if labelsList: fields += u',' if labelsList: fields += u'labels({0})'.format(u','.join(set(labelsList))) fields += u')' elif not allfields: for field in [u'name', u'alternatelink']: addFieldToCSVfile(field, {field: [DRIVEFILE_FIELDS_CHOICES_MAP[field]]}, fieldsList, fieldsTitles, titles) fields = u'nextPageToken,items({0})'.format(u','.join(set(fieldsList))) else: fields = u'*' for user in users: user, drive = buildDriveGAPIObject(user) if not drive: continue sys.stderr.write(u'Getting files for %s...\n' % user) page_message = u' got %%%%total_items%%%% files for %s...\n' % user feed = callGAPIpages(drive.files(), u'list', u'items', page_message=page_message, soft_errors=True, q=query, fields=fields, maxResults=GC_Values[GC_DRIVE_MAX_RESULTS]) for f_file in feed: a_file = {u'Owner': user} for attrib in f_file: if attrib in [u'kind', u'etag']: continue if not isinstance(f_file[attrib], dict): if isinstance(f_file[attrib], list): if f_file[attrib]: if isinstance(f_file[attrib][0], (str, unicode, int, bool)): if attrib not in titles: titles.append(attrib) a_file[attrib] = u' '.join(f_file[attrib]) else: for j, l_attrib in enumerate(f_file[attrib]): for list_attrib in l_attrib: if list_attrib in [u'kind', u'etag', u'selfLink']: continue x_attrib = u'{0}.{1}.{2}'.format(attrib, j, list_attrib) if x_attrib not in titles: titles.append(x_attrib) a_file[x_attrib] = l_attrib[list_attrib] elif isinstance(f_file[attrib], (str, unicode, int, bool)): if attrib not in titles: titles.append(attrib) a_file[attrib] = f_file[attrib] else: sys.stderr.write(u'File ID: {0}, Attribute: {1}, Unknown type: {2}\n'.format(f_file[u'id'], attrib, type(f_file[attrib]))) elif attrib == u'labels': for dict_attrib in f_file[attrib]: if dict_attrib not in titles: titles.append(dict_attrib) a_file[dict_attrib] = f_file[attrib][dict_attrib] else: for dict_attrib in f_file[attrib]: if dict_attrib in [u'kind', u'etag']: continue x_attrib = u'{0}.{1}'.format(attrib, dict_attrib) if x_attrib not in titles: titles.append(x_attrib) a_file[x_attrib] = f_file[attrib][dict_attrib] csvRows.append(a_file) if allfields: sortCSVTitles([u'Owner', u'id', u'title'], titles) writeCSVfile(csvRows, titles, u'%s %s Drive Files' % (sys.argv[1], sys.argv[2]), todrive) def doDriveSearch(drive, query=None): print u'Searching for files with query: "%s"...' % query page_message = u' got %%total_items%% files...\n' files = callGAPIpages(drive.files(), u'list', u'items', page_message=page_message, q=query, fields=u'nextPageToken,items(id)', maxResults=GC_Values[GC_DRIVE_MAX_RESULTS]) ids = list() for f_file in files: ids.append(f_file[u'id']) return ids DELETE_DRIVEFILE_FUNCTION_TO_ACTION_MAP = {u'delete': u'purging', u'trash': u'trashing', u'untrash': u'untrashing',} def deleteDriveFile(users): fileIds = sys.argv[5] function = u'trash' i = 6 while i < len(sys.argv): if sys.argv[i].lower() == u'purge': function = u'delete' i += 1 elif sys.argv[i].lower() == u'untrash': function = u'untrash' i += 1 else: print u'ERROR: %s is not a valid argument for "gam delete drivefile"' % sys.argv[i] sys.exit(2) action = DELETE_DRIVEFILE_FUNCTION_TO_ACTION_MAP[function] for user in users: user, drive = buildDriveGAPIObject(user) if not drive: continue if fileIds[:6].lower() == u'query:': file_ids = doDriveSearch(drive, query=fileIds[6:]) else: if fileIds[:8].lower() == u'https://' or fileIds[:7].lower() == u'http://': fileIds = fileIds[fileIds.find(u'/d/')+3:] if fileIds.find(u'/') != -1: fileIds = fileIds[:fileIds.find(u'/')] file_ids = [fileIds,] if not file_ids: print u'No files to %s for %s' % (function, user) i = 0 for fileId in file_ids: i += 1 print u'%s %s for %s (%s/%s)' % (action, fileId, user, i, len(file_ids)) callGAPI(drive.files(), function, fileId=fileId) def printDriveFolderContents(feed, folderId, indent): for f_file in feed: for parent in f_file[u'parents']: if folderId == parent[u'id']: print u' ' * indent, convertUTF8(f_file[u'title']) if f_file[u'mimeType'] == u'application/vnd.google-apps.folder': printDriveFolderContents(feed, f_file[u'id'], indent+1) break def showDriveFileTree(users): for user in users: user, drive = buildDriveGAPIObject(user) if not drive: continue root_folder = callGAPI(drive.about(), u'get', fields=u'rootFolderId')[u'rootFolderId'] sys.stderr.write(u'Getting all files for %s...\n' % user) page_message = u' got %%%%total_items%%%% files for %s...\n' % user feed = callGAPIpages(drive.files(), u'list', u'items', page_message=page_message, fields=u'items(id,title,parents(id),mimeType),nextPageToken', maxResults=GC_Values[GC_DRIVE_MAX_RESULTS]) printDriveFolderContents(feed, root_folder, 0) def deleteEmptyDriveFolders(users): query = u'"me" in owners and mimeType = "application/vnd.google-apps.folder"' for user in users: user, drive = buildDriveGAPIObject(user) if not drive: continue deleted_empty = True while deleted_empty: sys.stderr.write(u'Getting folders for %s...\n' % user) page_message = u' got %%%%total_items%%%% folders for %s...\n' % user feed = callGAPIpages(drive.files(), u'list', u'items', page_message=page_message, q=query, fields=u'items(title,id),nextPageToken', maxResults=GC_Values[GC_DRIVE_MAX_RESULTS]) deleted_empty = False for folder in feed: children = callGAPI(drive.children(), u'list', folderId=folder[u'id'], fields=u'items(id)', maxResults=1) if not u'items' in children or len(children[u'items']) == 0: print convertUTF8(u' deleting empty folder %s...' % folder[u'title']) callGAPI(drive.files(), u'delete', fileId=folder[u'id']) deleted_empty = True else: print convertUTF8(u' not deleting folder %s because it contains at least 1 item (%s)' % (folder[u'title'], children[u'items'][0][u'id'])) def doEmptyDriveTrash(users): for user in users: user, drive = buildDriveGAPIObject(user) if not drive: continue print u'Emptying Drive trash for %s' % user callGAPI(drive.files(), u'emptyTrash') DRIVEFILE_LABEL_CHOICES_MAP = { u'restricted': u'restricted', u'restrict': u'restricted', u'starred': u'starred', u'star': u'starred', u'trashed': u'trashed', u'trash': u'trashed', u'viewed': u'viewed', u'view': u'viewed', } MIMETYPE_CHOICES_MAP = { u'gdoc': MIMETYPE_GA_DOCUMENT, u'gdocument': MIMETYPE_GA_DOCUMENT, u'gdrawing': MIMETYPE_GA_DRAWING, u'gfolder': MIMETYPE_GA_FOLDER, u'gdirectory': MIMETYPE_GA_FOLDER, u'gform': MIMETYPE_GA_FORM, u'gfusion': MIMETYPE_GA_FUSIONTABLE, u'gpresentation': MIMETYPE_GA_PRESENTATION, u'gscript': MIMETYPE_GA_SCRIPT, u'gsite': MIMETYPE_GA_SITES, u'gsheet': MIMETYPE_GA_SPREADSHEET, u'gspreadsheet': MIMETYPE_GA_SPREADSHEET, } DFA_CONVERT = u'convert' DFA_LOCALFILEPATH = u'localFilepath' DFA_LOCALFILENAME = u'localFilename' DFA_LOCALMIMETYPE = u'localMimeType' DFA_OCR = u'ocr' DFA_OCRLANGUAGE = u'ocrLanguage' DFA_PARENTQUERY = u'parentQuery' def initializeDriveFileAttributes(): return ({}, {DFA_LOCALFILEPATH: None, DFA_LOCALFILENAME: None, DFA_LOCALMIMETYPE: None, DFA_CONVERT: None, DFA_OCR: None, DFA_OCRLANGUAGE: None, DFA_PARENTQUERY: None}) def getDriveFileAttribute(i, body, parameters, myarg, update=False): if myarg == u'localfile': parameters[DFA_LOCALFILEPATH] = sys.argv[i+1] 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: body[u'mimeType'] = u'application/octet-stream' parameters[DFA_LOCALMIMETYPE] = body[u'mimeType'] i += 2 elif myarg == u'convert': parameters[DFA_CONVERT] = True i += 1 elif myarg == u'ocr': parameters[DFA_OCR] = True i += 1 elif myarg == u'ocrlanguage': parameters[DFA_OCRLANGUAGE] = sys.argv[i+1] i += 2 elif myarg in DRIVEFILE_LABEL_CHOICES_MAP: body.setdefault(u'labels', {}) if update: value = sys.argv[i+1].lower() if value not in true_values and value not in false_values: print u'ERROR: value for %s must be true or false; got %s' % (myarg, sys.argv[i+1]) sys.exit(2) body[u'labels'][DRIVEFILE_LABEL_CHOICES_MAP[myarg]] = value i += 2 else: body[u'labels'][DRIVEFILE_LABEL_CHOICES_MAP[myarg]] = True i += 1 elif myarg in [u'lastviewedbyme', u'lastviewedbyuser', u'lastviewedbymedate', u'lastviewedbymetime']: body[u'lastViewedByMeDate'] = sys.argv[i+1] i += 2 elif myarg in [u'modifieddate', u'modifiedtime']: body[u'modifiedDate'] = sys.argv[i+1] i += 2 elif myarg == u'description': body[u'description'] = sys.argv[i+1] i += 2 elif myarg == u'mimetype': mimeType = sys.argv[i+1] if mimeType in MIMETYPE_CHOICES_MAP: body[u'mimeType'] = MIMETYPE_CHOICES_MAP[mimeType] else: print u'ERROR: mimetype must be one of %s; got %s"' % (u', '.join(MIMETYPE_CHOICES_MAP), mimeType) sys.exit(2) i += 2 elif myarg == u'parentid': body.setdefault(u'parents', []) body[u'parents'].append({u'id': sys.argv[i+1]}) i += 2 elif myarg == u'parentname': parameters[DFA_PARENTQUERY] = u'mimeType = "%s" and "me" in owners and title = "%s"' % (MIMETYPE_GA_FOLDER, sys.argv[i+1]) i += 2 elif myarg == u'writerscantshare': body[u'writersCanShare'] = False i += 1 else: print u'ERROR: %s is not a valid argument for "gam %s drivefile"' % (myarg, [u'add', u'update'][update]) sys.exit(2) return i def doUpdateDriveFile(users): fileIdSelection = {u'fileIds': None, u'query': None} media_body = None operation = u'update' body, parameters = initializeDriveFileAttributes() i = 5 while i < len(sys.argv): myarg = sys.argv[i].lower().replace(u'_', u'') if myarg == u'copy': operation = u'copy' i += 1 elif myarg == u'newfilename': body[u'title'] = sys.argv[i+1] i += 2 elif myarg == u'id': fileIdSelection[u'fileIds'] = [sys.argv[i+1],] i += 2 elif myarg == u'query': fileIdSelection[u'query'] = sys.argv[i+1] i += 2 elif myarg == u'drivefilename': fileIdSelection[u'query'] = u"'me' in owners and title = '{0}'".format(sys.argv[i+1]) i += 2 else: i = getDriveFileAttribute(i, body, parameters, myarg, True) if not fileIdSelection[u'query'] and not fileIdSelection[u'fileIds']: print u'ERROR: you need to specify either id, query or drivefilename in order to determine the file(s) to update' sys.exit(2) if fileIdSelection[u'query'] and fileIdSelection[u'fileIds']: print u'ERROR: you cannot specify multiple file identifiers. Choose one of id, drivefilename, query.' sys.exit(2) for user in users: user, drive = buildDriveGAPIObject(user) if not drive: continue if parameters[DFA_PARENTQUERY]: more_parents = doDriveSearch(drive, query=parameters[DFA_PARENTQUERY]) body.setdefault(u'parents', []) for a_parent in more_parents: body[u'parents'].append({u'id': a_parent}) if fileIdSelection[u'query']: fileIdSelection[u'fileIds'] = doDriveSearch(drive, query=fileIdSelection[u'query']) if not fileIdSelection[u'fileIds']: print u'No files to %s for %s' % (operation, user) continue if operation == u'update': if parameters[DFA_LOCALFILEPATH]: media_body = googleapiclient.http.MediaFileUpload(parameters[DFA_LOCALFILEPATH], mimetype=parameters[DFA_LOCALMIMETYPE], resumable=True) for fileId in fileIdSelection[u'fileIds']: if media_body: result = callGAPI(drive.files(), u'update', fileId=fileId, convert=parameters[DFA_CONVERT], ocr=parameters[DFA_OCR], ocrLanguage=parameters[DFA_OCRLANGUAGE], media_body=media_body, body=body, fields=u'id') print u'Successfully updated %s drive file with content from %s' % (result[u'id'], parameters[DFA_LOCALFILENAME]) else: result = callGAPI(drive.files(), u'patch', fileId=fileId, convert=parameters[DFA_CONVERT], ocr=parameters[DFA_OCR], ocrLanguage=parameters[DFA_OCRLANGUAGE], body=body, fields=u'id') print u'Successfully updated drive file/folder ID %s' % (result[u'id']) else: for fileId in fileIdSelection[u'fileIds']: result = callGAPI(drive.files(), u'copy', fileId=fileId, convert=parameters[DFA_CONVERT], ocr=parameters[DFA_OCR], ocrLanguage=parameters[DFA_OCRLANGUAGE], body=body, fields=u'id') print u'Successfully copied %s to %s' % (fileId, result[u'id']) def createDriveFile(users): media_body = None body, parameters = initializeDriveFileAttributes() i = 5 while i < len(sys.argv): myarg = sys.argv[i].lower().replace(u'_', u'') if myarg == u'drivefilename': body[u'title'] = sys.argv[i+1] i += 2 else: i = getDriveFileAttribute(i, body, parameters, myarg, False) for user in users: user, drive = buildDriveGAPIObject(user) if not drive: continue if parameters[DFA_PARENTQUERY]: more_parents = doDriveSearch(drive, query=parameters[DFA_PARENTQUERY]) body.setdefault(u'parents', []) for a_parent in more_parents: body[u'parents'].append({u'id': a_parent}) if parameters[DFA_LOCALFILEPATH]: media_body = googleapiclient.http.MediaFileUpload(parameters[DFA_LOCALFILEPATH], mimetype=parameters[DFA_LOCALMIMETYPE], resumable=True) result = callGAPI(drive.files(), u'insert', convert=parameters[DFA_CONVERT], ocr=parameters[DFA_OCR], ocrLanguage=parameters[DFA_OCRLANGUAGE], media_body=media_body, body=body, fields=u'id') if parameters[DFA_LOCALFILENAME]: print u'Successfully uploaded %s to Drive file ID %s' % (parameters[DFA_LOCALFILENAME], result[u'id']) else: print u'Successfully created drive file/folder ID %s' % (result[u'id']) DOCUMENT_FORMATS_MAP = { u'csv': [{u'mime': u'text/csv', u'ext': u'.csv'}], u'html': [{u'mime': u'text/html', u'ext': u'.html'}], u'txt': [{u'mime': u'text/plain', u'ext': u'.txt'}], u'tsv': [{u'mime': u'text/tsv', u'ext': u'.tsv'}], u'jpeg': [{u'mime': u'image/jpeg', u'ext': u'.jpeg'}], u'jpg': [{u'mime': u'image/jpeg', u'ext': u'.jpg'}], u'png': [{u'mime': u'image/png', u'ext': u'.png'}], u'svg': [{u'mime': u'image/svg+xml', u'ext': u'.svg'}], u'pdf': [{u'mime': u'application/pdf', u'ext': u'.pdf'}], u'rtf': [{u'mime': u'application/rtf', u'ext': u'.rtf'}], u'zip': [{u'mime': u'application/zip', u'ext': u'.zip'}], u'pptx': [{u'mime': u'application/vnd.openxmlformats-officedocument.presentationml.presentation', u'ext': u'.pptx'}], u'xlsx': [{u'mime': u'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', u'ext': u'.xlsx'}], u'docx': [{u'mime': u'application/vnd.openxmlformats-officedocument.wordprocessingml.document', u'ext': u'.docx'}], u'ms': [{u'mime': u'application/vnd.openxmlformats-officedocument.presentationml.presentation', u'ext': u'.pptx'}, {u'mime': u'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', u'ext': u'.xlsx'}, {u'mime': u'application/vnd.openxmlformats-officedocument.wordprocessingml.document', u'ext': u'.docx'}], u'microsoft': [{u'mime': u'application/vnd.openxmlformats-officedocument.presentationml.presentation', u'ext': u'.pptx'}, {u'mime': u'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', u'ext': u'.xlsx'}, {u'mime': u'application/vnd.openxmlformats-officedocument.wordprocessingml.document', u'ext': u'.docx'}], u'micro$oft': [{u'mime': u'application/vnd.openxmlformats-officedocument.presentationml.presentation', u'ext': u'.pptx'}, {u'mime': u'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', u'ext': u'.xlsx'}, {u'mime': u'application/vnd.openxmlformats-officedocument.wordprocessingml.document', u'ext': u'.docx'}], u'odt': [{u'mime': u'application/vnd.oasis.opendocument.text', u'ext': u'.odt'}], u'ods': [{u'mime': u'application/x-vnd.oasis.opendocument.spreadsheet', u'ext': u'.ods'}], u'openoffice': [{u'mime': u'application/vnd.oasis.opendocument.text', u'ext': u'.odt'}, {u'mime': u'application/x-vnd.oasis.opendocument.spreadsheet', u'ext': u'.ods'}], } def downloadDriveFile(users): i = 5 fileIdSelection = {u'fileIds': None, u'query': None} revisionId = None exportFormatName = u'openoffice' exportFormats = DOCUMENT_FORMATS_MAP[exportFormatName] targetFolder = GC_Values[GC_DRIVE_DIR] safe_filename_chars = "-_.() %s%s" % (string.ascii_letters, string.digits) while i < len(sys.argv): myarg = sys.argv[i].lower().replace(u'_', u'') if myarg == u'id': fileIdSelection[u'fileIds'] = [sys.argv[i+1],] i += 2 elif myarg == u'query': fileIdSelection[u'query'] = sys.argv[i+1] i += 2 elif myarg == u'drivefilename': fileIdSelection[u'query'] = u"'me' in owners and title = '{0}'".format(sys.argv[i+1]) i += 2 elif myarg == u'revision': revisionId = sys.argv[i+1] i += 2 elif myarg == u'format': exportFormatChoices = sys.argv[i+1].replace(u',', u' ').lower().split() exportFormats = [] for exportFormat in exportFormatChoices: if exportFormat in DOCUMENT_FORMATS_MAP: exportFormats.extend(DOCUMENT_FORMATS_MAP[exportFormat]) else: print u'ERROR: format must be one of {0}; got {1}'.format(u', '.join(DOCUMENT_FORMATS_MAP), exportFormat) sys.exit(2) i += 2 elif myarg == u'targetfolder': targetFolder = os.path.expanduser(sys.argv[i+1]) if not os.path.isdir(targetFolder): os.makedirs(targetFolder) i += 2 else: print u'ERROR: %s is not a valid argument for "gam get drivefile"' % sys.argv[i] sys.exit(2) if not fileIdSelection[u'query'] and not fileIdSelection[u'fileIds']: print u'ERROR: you need to specify either id, query or drivefilename in order to determine the file(s) to download' sys.exit(2) if fileIdSelection[u'query'] and fileIdSelection[u'fileIds']: print u'ERROR: you cannot specify multiple file identifiers. Choose one of id, drivefilename, query.' sys.exit(2) for user in users: user, drive = buildDriveGAPIObject(user) if not drive: continue if fileIdSelection[u'query']: fileIdSelection[u'fileIds'] = doDriveSearch(drive, query=fileIdSelection[u'query']) else: fileId = fileIdSelection[u'fileIds'][0] if fileId[:8].lower() == u'https://' or fileId[:7].lower() == u'http://': fileId = fileId[fileId.find(u'/d/')+3:] if fileId.find(u'/') != -1: fileId = fileId[:fileId.find(u'/')] fileIdSelection[u'fileIds'][0] = fileId if not fileIdSelection[u'fileIds']: print u'No files to download for %s' % user i = 0 for fileId in fileIdSelection[u'fileIds']: extension = None result = callGAPI(drive.files(), u'get', fileId=fileId, fields=u'fileSize,title,mimeType,downloadUrl,exportLinks') if result[u'mimeType'] == MIMETYPE_GA_FOLDER: print convertUTF8(u'Skipping download of folder %s' % result[u'title']) continue try: result[u'fileSize'] = int(result[u'fileSize']) if result[u'fileSize'] < 1024: filesize = u'1kb' elif result[u'fileSize'] < (1024 * 1024): filesize = u'%skb' % (result[u'fileSize'] / 1024) elif result[u'fileSize'] < (1024 * 1024 * 1024): filesize = u'%smb' % (result[u'fileSize'] / 1024 / 1024) else: filesize = u'%sgb' % (result[u'fileSize'] / 1024 / 1024 / 1024) my_line = u'Downloading: %%s of %s bytes' % filesize except KeyError: my_line = u'Downloading Google Doc: %s' if u'downloadUrl' in result: download_url = result[u'downloadUrl'] elif u'exportLinks' in result: for exportFormat in exportFormats: if exportFormat[u'mime'] in result[u'exportLinks']: download_url = result[u'exportLinks'][exportFormat[u'mime']] extension = exportFormat[u'ext'] break else: print convertUTF8(u'Skipping download of file {0}, Format {1} not available'.format(result[u'title'], u','.join(exportFormatChoices))) continue else: print convertUTF8(u'Skipping download of file {0}, Format not downloadable') continue file_title = result[u'title'] safe_file_title = u''.join(c for c in file_title if c in safe_filename_chars) filename = os.path.join(targetFolder, safe_file_title) y = 0 while True: if extension and filename.lower()[-len(extension):] != extension: filename += extension if not os.path.isfile(filename): break y += 1 filename = os.path.join(targetFolder, u'({0})-{1}'.format(y, safe_file_title)) print convertUTF8(my_line % filename) if revisionId: download_url = u'{0}&revision={1}'.format(download_url, revisionId) _, content = drive._http.request(download_url) writeFile(filename, content, continueOnError=True) def showDriveFileInfo(users): fieldsList = [] labelsList = [] fileId = sys.argv[5] i = 6 while i < len(sys.argv): myarg = sys.argv[i].lower().replace(u'_', u'') if myarg == u'allfields': fieldsList = [] i += 1 elif myarg in DRIVEFILE_FIELDS_CHOICES_MAP: fieldsList.append(DRIVEFILE_FIELDS_CHOICES_MAP[myarg]) i += 1 elif myarg in DRIVEFILE_LABEL_CHOICES_MAP: labelsList.append(DRIVEFILE_LABEL_CHOICES_MAP[myarg]) i += 1 else: print u'ERROR: %s is not a valid argument for "gam show fileinfo"' % myarg sys.exit(2) if fieldsList or labelsList: fieldsList.append(u'title') fields = u','.join(set(fieldsList)) if labelsList: fields += u',labels({0})'.format(u','.join(set(labelsList))) else: fields = u'*' for user in users: user, drive = buildDriveGAPIObject(user) if not drive: continue feed = callGAPI(drive.files(), u'get', fileId=fileId, fields=fields) print_json(None, feed) def showDriveFileRevisions(users): fileId = sys.argv[5] for user in users: user, drive = buildDriveGAPIObject(user) if not drive: continue feed = callGAPI(drive.revisions(), u'list', fileId=fileId) print_json(None, feed) def transferSecCals(users): target_user = sys.argv[5] remove_source_user = True i = 6 while i < len(sys.argv): if sys.argv[i].lower() == u'keepuser': remove_source_user = False i += 1 else: print u'ERROR: %s is not a valid argument for "gam transfer seccals"' % sys.argv[i] sys.exit(2) if remove_source_user: target_user, target_cal = buildCalendarGAPIObject(target_user) if not target_cal: return for user in users: user, source_cal = buildCalendarGAPIObject(user) if not source_cal: continue source_calendars = callGAPIpages(source_cal.calendarList(), u'list', u'items', minAccessRole=u'owner', showHidden=True, fields=u'items(id),nextPageToken') for source_cal in source_calendars: if source_cal[u'id'].find(u'@group.calendar.google.com') != -1: doCalendarAddACL(calendarId=source_cal[u'id'], act_as=user, role=u'owner', scope=u'user', entity=target_user) if remove_source_user: doCalendarAddACL(calendarId=source_cal[u'id'], act_as=target_user, role=u'none', scope=u'user', entity=user) def transferDriveFiles(users): target_user = sys.argv[5] remove_source_user = True i = 6 while i < len(sys.argv): if sys.argv[i].lower() == u'keepuser': remove_source_user = False i += 1 else: print u'ERROR: %s is not a valid argument for "gam transfer drive"' % sys.argv[i] sys.exit(2) target_user, target_drive = buildDriveGAPIObject(target_user) if not target_drive: return target_about = callGAPI(target_drive.about(), u'get', fields=u'quotaBytesTotal,quotaBytesUsed') target_drive_free = int(target_about[u'quotaBytesTotal']) - int(target_about[u'quotaBytesUsed']) for user in users: user, source_drive = buildDriveGAPIObject(user) if not source_drive: continue counter = 0 source_about = callGAPI(source_drive.about(), u'get', fields=u'quotaBytesTotal,quotaBytesUsed,rootFolderId,permissionId') source_drive_size = int(source_about[u'quotaBytesUsed']) if target_drive_free < source_drive_size: systemErrorExit(4, MESSAGE_NO_TRANSFER_LACK_OF_DISK_SPACE.format(source_drive_size / 1024 / 1024, target_drive_free / 1024 / 1024)) print u'Source drive size: %smb Target drive free: %smb' % (source_drive_size / 1024 / 1024, target_drive_free / 1024 / 1024) target_drive_free = target_drive_free - source_drive_size # prep target_drive_free for next user source_root = source_about[u'rootFolderId'] source_permissionid = source_about[u'permissionId'] print u"Getting file list for source user: %s..." % user page_message = u' got %%total_items%% files\n' source_drive_files = callGAPIpages(source_drive.files(), u'list', u'items', page_message=page_message, q=u"'me' in owners and trashed = false", fields=u'items(id,parents,mimeType),nextPageToken') all_source_file_ids = [] for source_drive_file in source_drive_files: all_source_file_ids.append(source_drive_file[u'id']) total_count = len(source_drive_files) print u"Getting folder list for target user: %s..." % target_user page_message = u' got %%total_items%% folders\n' target_folders = callGAPIpages(target_drive.files(), u'list', u'items', page_message=page_message, q=u"'me' in owners and mimeType = 'application/vnd.google-apps.folder'", fields=u'items(id,title),nextPageToken') got_top_folder = False all_target_folder_ids = [] for target_folder in target_folders: all_target_folder_ids.append(target_folder[u'id']) if (not got_top_folder) and target_folder[u'title'] == u'%s old files' % user: target_top_folder = target_folder[u'id'] got_top_folder = True if not got_top_folder: create_folder = callGAPI(target_drive.files(), u'insert', body={u'title': u'%s old files' % user, u'mimeType': u'application/vnd.google-apps.folder'}, fields=u'id') target_top_folder = create_folder[u'id'] transferred_files = [] while True: # we loop thru, skipping files until all of their parents are done skipped_files = False for drive_file in source_drive_files: file_id = drive_file[u'id'] if file_id in transferred_files: continue source_parents = drive_file[u'parents'] skip_file_for_now = False for source_parent in source_parents: if source_parent[u'id'] not in all_source_file_ids and source_parent[u'id'] not in all_target_folder_ids: continue # means this parent isn't owned by source or target, shouldn't matter if source_parent[u'id'] not in transferred_files and source_parent[u'id'] != source_root: #print u'skipping %s' % file_id skipped_files = skip_file_for_now = True break if skip_file_for_now: continue else: transferred_files.append(drive_file[u'id']) counter += 1 print u'Changing owner for file %s (%s/%s)' % (drive_file[u'id'], counter, total_count) body = {u'role': u'owner', u'type': u'user', u'value': target_user} callGAPI(source_drive.permissions(), u'insert', soft_errors=True, fileId=file_id, sendNotificationEmails=False, body=body) target_parents = [] for parent in source_parents: try: if parent[u'isRoot']: target_parents.append({u'id': target_top_folder}) else: target_parents.append({u'id': parent[u'id']}) except TypeError: pass callGAPI(target_drive.files(), u'patch', soft_errors=True, retry_reasons=[u'notFound'], fileId=file_id, body={u'parents': target_parents}) if remove_source_user: callGAPI(target_drive.permissions(), u'delete', soft_errors=True, fileId=file_id, permissionId=source_permissionid) if not skipped_files: break EMAILSETTINGS_IMAP_EXPUNGE_BEHAVIOR_CHOICES_MAP = { u'archive': u'archive', u'deleteforever': u'deleteForever', u'trash': u'trash', } EMAILSETTINGS_IMAP_MAX_FOLDER_SIZE_CHOICES = [u'0', u'1000', u'2000', u'5000', u'10000'] def doImap(users): if sys.argv[4].lower() in true_values: enable = True elif sys.argv[4].lower() in false_values: enable = False else: print u'ERROR: value for "gam imap" must be true or false; got %s' % sys.argv[4] sys.exit(2) body = {u'enabled': enable, u'autoExpunge': True, u'expungeBehavior': u'archive', u'maxFolderSize': 0} i = 5 while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg == u'noautoexpunge': body[u'autoExpunge'] = False i += 1 elif myarg == u'expungebehavior': opt = sys.argv[i+1].lower() if opt in EMAILSETTINGS_IMAP_EXPUNGE_BEHAVIOR_CHOICES_MAP: body[u'expungeBehavior'] = EMAILSETTINGS_IMAP_EXPUNGE_BEHAVIOR_CHOICES_MAP[opt] i += 2 else: print u'ERROR: value for "gam imap expungebehavior" must be one of %s; got %s' % (u', '.join(EMAILSETTINGS_IMAP_EXPUNGE_BEHAVIOR_CHOICES_MAP), opt) sys.exit(2) elif myarg == u'maxfoldersize': opt = sys.argv[i+1].lower() if opt in EMAILSETTINGS_IMAP_MAX_FOLDER_SIZE_CHOICES: body[u'maxFolderSize'] = int(opt) i += 2 else: print u'ERROR: value for "gam imap maxfoldersize" must be one of %s; got %s' % (u'|'.join(EMAILSETTINGS_IMAP_MAX_FOLDER_SIZE_CHOICES), opt) sys.exit(2) else: print u'ERROR: %s is not a valid argument for "gam imap"' % myarg sys.exit(2) i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue print u"Setting IMAP Access to %s for %s (%s/%s)" % (str(enable), user, i, count) callGAPI(gmail.users().settings(), u'updateImap', soft_errors=True, userId=u'me', body=body) def getImap(users): i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue result = callGAPI(gmail.users().settings(), u'getImap', soft_errors=True, userId=u'me') if result: enabled = result[u'enabled'] if enabled: print u'User: {0}, IMAP Enabled: {1}, autoExpunge: {2}, expungeBehavior: {3}, maxFolderSize:{4} ({5}/{6})'.format(user, enabled, result[u'autoExpunge'], result[u'expungeBehavior'], result[u'maxFolderSize'], i, count) else: print u'User: {0}, IMAP Enabled: {1} ({2}/{3})'.format(user, enabled, i, count) def getProductAndSKU(sku): if sku.lower() in [u'apps', u'gafb', u'gafw']: sku = u'Google-Apps-For-Business' elif sku.lower() in [u'gams',]: sku = u'Google-Apps-For-Postini' elif sku.lower() in [u'gau', u'unlimited', u'd4w', u'dfw']: sku = u'Google-Apps-Unlimited' elif sku.lower() in [u'lite']: sku = u'Google-Apps-Lite' elif sku.lower() == u'coordinate': sku = u'Google-Coordinate' elif sku.lower() == u'vault': sku = u'Google-Vault' elif sku.lower() in [u'vfe',]: sku = u'Google-Vault-Former-Employee' elif sku.lower() in [u'drive-20gb', u'drive20gb', u'20gb']: sku = u'Google-Drive-storage-20GB' elif sku.lower() in [u'drive-50gb', u'drive50gb', u'50gb']: sku = u'Google-Drive-storage-50GB' elif sku.lower() in [u'drive-200gb', u'drive200gb', u'200gb']: sku = u'Google-Drive-storage-200GB' elif sku.lower() in [u'drive-400gb', u'drive400gb', u'400gb']: sku = u'Google-Drive-storage-400GB' elif sku.lower() in [u'drive-1tb', u'drive1tb', u'1tb']: sku = u'Google-Drive-storage-1TB' elif sku.lower() in [u'drive-2tb', u'drive2tb', u'2tb']: sku = u'Google-Drive-storage-2TB' elif sku.lower() in [u'drive-4tb', u'drive4tb', u'4tb']: sku = u'Google-Drive-storage-4TB' elif sku.lower() in [u'drive-4tb', u'drive8tb', u'8tb']: sku = u'Google-Drive-storage-8TB' elif sku.lower() in [u'drive-16tb', u'drive16tb', u'16tb']: sku = u'Google-Drive-storage-16TB' if sku[:20] == u'Google-Drive-storage': product = u'Google-Drive-storage' else: try: product = re.search(u'^([A-Z,a-z]*-[A-Z,a-z]*)', sku).group(1) except AttributeError: product = sku return (product, sku) def doLicense(users, operation): lic = buildGAPIObject(u'licensing') sku = sys.argv[5] productId, skuId = getProductAndSKU(sku) for user in users: if user.find(u'@') == -1: user = u'%s@%s' % (user, GC_Values[GC_DOMAIN]) if operation == u'delete': callGAPI(lic.licenseAssignments(), operation, soft_errors=True, productId=productId, skuId=skuId, userId=user) elif operation == u'insert': callGAPI(lic.licenseAssignments(), operation, soft_errors=True, productId=productId, skuId=skuId, body={u'userId': user}) elif operation == u'patch': try: old_sku = sys.argv[6] if old_sku.lower() == u'from': old_sku = sys.argv[7] except KeyError: print u'ERROR: You need to specify the user\'s old SKU as the last argument' sys.exit(2) _, old_sku = getProductAndSKU(old_sku) callGAPI(lic.licenseAssignments(), operation, soft_errors=True, productId=productId, skuId=old_sku, userId=user, body={u'skuId': skuId}) EMAILSETTINGS_POP_ENABLE_FOR_CHOICES_MAP = { u'allmail': u'allMail', u'fromnowon': u'fromNowOn', u'mailfromnowon': u'fromNowOn', u'newmail': u'fromNowOn', } EMAILSETTINGS_FORWARD_POP_ACTION_CHOICES_MAP = { u'archive': u'archive', u'delete': u'trash', u'keep': u'leaveInInbox', u'leaveininbox': u'leaveInInbox', u'markread': u'markRead', u'trash': u'trash', } def doPop(users): if sys.argv[4].lower() in true_values: enable = True elif sys.argv[4].lower() in false_values: enable = False else: print u'ERROR: value for "gam pop" must be true or false; got %s' % sys.argv[4] sys.exit(2) body = {u'accessWindow': [u'disabled', u'allMail'][enable], u'disposition': u'leaveInInbox'} i = 5 while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg == u'for': opt = sys.argv[i+1].lower() if opt in EMAILSETTINGS_POP_ENABLE_FOR_CHOICES_MAP: body[u'accessWindow'] = EMAILSETTINGS_POP_ENABLE_FOR_CHOICES_MAP[opt] i += 2 else: print u'ERROR: value for "gam pop for" must be one of %s; got %s' % (u', '.join(EMAILSETTINGS_POP_ENABLE_FOR_CHOICES_MAP), opt) sys.exit(2) elif myarg == u'action': opt = sys.argv[i+1].lower() if opt in EMAILSETTINGS_FORWARD_POP_ACTION_CHOICES_MAP: body[u'disposition'] = EMAILSETTINGS_FORWARD_POP_ACTION_CHOICES_MAP[opt] i += 2 else: print u'ERROR: value for "gam pop action" must be one of %s; got %s' % (u', '.join(EMAILSETTINGS_FORWARD_POP_ACTION_CHOICES_MAP), opt) sys.exit(2) elif myarg == u'confirm': i += 1 else: print u'ERROR: %s is not a valid argument for "gam pop"' % myarg sys.exit(2) i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue print u"Setting POP Access to %s for %s (%s/%s)" % (str(enable), user, i, count) callGAPI(gmail.users().settings(), u'updatePop', soft_errors=True, userId=u'me', body=body) def getPop(users): i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue result = callGAPI(gmail.users().settings(), u'getPop', soft_errors=True, userId=u'me') if result: enabled = result[u'accessWindow'] != u'disabled' if enabled: print u'User: {0}, POP Enabled: {1}, For: {2}, Action: {3} ({4}/{5})'.format(user, enabled, result[u'accessWindow'], result[u'disposition'], i, count) else: print u'User: {0}, POP Enabled: {1} ({2}/{3})'.format(user, enabled, i, count) def _showSendAs(result, j, jcount, formatSig): if result[u'displayName']: print convertUTF8(u'SendAs Address: {0} <{1}>{2}'.format(result[u'displayName'], result[u'sendAsEmail'], currentCount(j, jcount))) else: print convertUTF8(u'SendAs Address: <{0}>{1}'.format(result[u'sendAsEmail'], currentCount(j, jcount))) if result.get(u'replyToAddress'): print u' ReplyTo: {0}'.format(result[u'replyToAddress']) print u' IsPrimary: {0}'.format(result.get(u'isPrimary', False)) print u' Default: {0}'.format(result.get(u'isDefault', False)) if not result.get(u'isPrimary', False): print u' TreatAsAlias: {0}'.format(result.get(u'treatAsAlias', False)) print u' Verification Status: {0}'.format(result.get(u'verificationStatus', u'unspecified')) sys.stdout.write(u' Signature:\n ') signature = result.get(u'signature') if not signature: signature = u'None' if formatSig: print convertUTF8(indentMultiLineText(dehtml(signature), n=4)) else: print convertUTF8(indentMultiLineText(signature, n=4)) RT_PATTERN = re.compile(r'(?s){RT}.*?{(.+?)}.*?{/RT}') RT_OPEN_PATTERN = re.compile(r'{RT}') RT_CLOSE_PATTERN = re.compile(r'{/RT}') RT_STRIP_PATTERN = re.compile(r'(?s){RT}.*?{/RT}') RT_TAG_REPLACE_PATTERN = re.compile(r'{(.*?)}') def _processTags(tagReplacements, message): while True: match = RT_PATTERN.search(message) if not match: break if tagReplacements.get(match.group(1)): message = RT_OPEN_PATTERN.sub(u'', message, count=1) message = RT_CLOSE_PATTERN.sub(u'', message, count=1) else: message = RT_STRIP_PATTERN.sub(u'', message, count=1) while True: match = RT_TAG_REPLACE_PATTERN.search(message) if not match: break message = re.sub(match.group(0), tagReplacements.get(match.group(1), u''), message) return message def getSendAsAttributes(i, myarg, body, tagReplacements, command): if myarg == u'replace': matchTag = getString(i+1, u'Tag') matchReplacement = getString(i+2, u'String', emptyOK=True) tagReplacements[matchTag] = matchReplacement i += 3 elif myarg == u'name': body[u'displayName'] = sys.argv[i+1] i += 2 elif myarg == u'replyto': body[u'replyToAddress'] = sys.argv[i+1] i += 2 elif myarg == u'default': body[u'isDefault'] = True i += 1 elif myarg == u'treatasalias': if sys.argv[i+1].lower() == u'true': body[u'treatAsAlias'] = True elif sys.argv[i+1].lower() == u'false': body[u'treatAsAlias'] = False else: print u'ERROR: value for treatasalias must be true or false; got %s' % sys.argv[i+1] sys.exit(2) i += 2 else: print u'ERROR: %s is not a valid argument for "gam %s"' % (sys.argv[i], command) sys.exit(2) return i def addUpdateSendAs(users, i, addCmd): emailAddress = sys.argv[i] if emailAddress.find(u'@') < 0: emailAddress = emailAddress+u'@'+GC_Values[GC_DOMAIN] i += 1 if addCmd: command = [u'sendas', u'add sendas'][i == 6] body = {u'sendAsEmail': emailAddress, u'displayName': sys.argv[i]} i += 1 else: command = u'update sendas' body = {} signature = None tagReplacements = {} while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg in [u'signature', u'sig']: signature = sys.argv[i+1] i += 2 if signature == u'file': filename = sys.argv[i] i, encoding = getCharSet(i+1) signature = readFile(filename, encoding=encoding) else: i = getSendAsAttributes(i, myarg, body, tagReplacements, command) if signature != None: if not signature: body[u'signature'] = None elif tagReplacements: body[u'signature'] = _processTags(tagReplacements, signature) else: body[u'signature'] = signature i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) 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) def deleteSendAs(users): emailAddress = sys.argv[5] if emailAddress.find(u'@') < 0: emailAddress = emailAddress+u'@'+GC_Values[GC_DOMAIN] i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue print u"Disallowing %s to send as %s (%s/%s)" % (user, emailAddress, i, count) callGAPI(gmail.users().settings().sendAs(), u'delete', soft_errors=True, userId=u'me', sendAsEmail=emailAddress) def printShowSendAs(users, csvFormat): if csvFormat: todrive = False titles = [u'User', u'displayName', u'sendAsEmail', u'replyToAddress', u'isPrimary', u'isDefault', u'treatAsAlias', u'verificationStatus'] csvRows = [] formatSig = False i = 5 while i < len(sys.argv): myarg = sys.argv[i].lower() if csvFormat and myarg == u'todrive': todrive = True i += 1 elif not csvFormat and myarg == u'format': formatSig = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam %s sendas"' % (myarg, [u'show', u'print'][csvFormat]) sys.exit(2) i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue result = callGAPI(gmail.users().settings().sendAs(), u'list', soft_errors=True, userId=u'me') jcount = len(result.get(u'sendAs', [])) if (result) else 0 if not csvFormat: print u'User: {0}, SendAs Addresses: ({1}/{2})'.format(user, i, count) if jcount == 0: continue j = 0 for sendas in result[u'sendAs']: j += 1 _showSendAs(sendas, j, jcount, formatSig) else: if jcount == 0: continue for sendas in result[u'sendAs']: row = {u'User': user, u'isPrimary': False} for item in sendas: if item not in titles: titles.append(item) row[item] = sendas[item] csvRows.append(row) if csvFormat: writeCSVfile(csvRows, titles, u'SendAs', todrive) def infoSendAs(users): emailAddress = sys.argv[5] if emailAddress.find(u'@') < 0: emailAddress = emailAddress+u'@'+GC_Values[GC_DOMAIN] formatSig = False i = 6 while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg == u'format': formatSig = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam info sendas"' % sys.argv[i] sys.exit(2) i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue print u'User: {0}, Show SendAs Address:{1}'.format(user, currentCount(i, count)) result = callGAPI(gmail.users().settings().sendAs(), u'get', soft_errors=True, userId=u'me', sendAsEmail=emailAddress) if result: _showSendAs(result, i, count, formatSig) def doLabel(users, i): label = sys.argv[i] i += 1 body = {u'name': label} while i < len(sys.argv): if sys.argv[i].lower().replace(u'_', u'') == u'labellistvisibility': if sys.argv[i+1].lower().replace(u'_', u'') == u'hide': body[u'labelListVisibility'] = u'labelHide' elif sys.argv[i+1].lower().replace(u'_', u'') == u'show': body[u'labelListVisibility'] = u'labelShow' elif sys.argv[i+1].lower().replace(u'_', u'') == u'showifunread': body[u'labelListVisibility'] = u'labelShowIfUnread' else: print u'ERROR: label_list_visibility must be one of hide, show, show_if_unread; got %s' % sys.argv[i+1] sys.exit(2) i += 2 elif sys.argv[i].lower().replace(u'_', u'') == u'messagelistvisibility': if sys.argv[i+1].lower().replace(u'_', u'') == u'hide': body[u'messageListVisibility'] = u'hide' elif sys.argv[i+1].lower().replace(u'_', u'') == u'show': body[u'messageListVisibility'] = u'show' else: print u'ERROR: message_list_visibility must be one of hide or show; got %s' % sys.argv[i+1] sys.exit(2) i += 2 else: print u'ERROR: %s is not a valid argument for this command.' % sys.argv[i] sys.exit(2) i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue print u"Creating label %s for %s (%s/%s)" % (label, user, i, count) callGAPI(gmail.users().labels(), u'create', soft_errors=True, userId=user, body=body) PROCESS_MESSAGE_FUNCTION_TO_ACTION_MAP = {u'delete': u'deleted', u'trash': u'trashed', u'untrash': u'untrashed', u'modify': u'modified'} def labelsToLabelIds(gmail, labels): allLabels = { u'INBOX': u'INBOX', u'SPAM': u'SPAM', u'TRASH': u'TRASH', u'UNREAD': u'UNREAD', u'STARRED': u'STARRED', u'IMPORTANT': u'IMPORTANT', u'SENT': u'SENT', u'DRAFT': u'DRAFT', u'CATEGORY_PERSONAL': u'CATEGORY_PERSONAL', u'CATEGORY_SOCIAL': u'CATEGORY_SOCIAL', u'CATEGORY_PROMOTIONS': u'CATEGORY_PROMOTIONS', u'CATEGORY_UPDATES': u'CATEGORY_UPDATES', u'CATEGORY_FORUMS': u'CATEGORY_FORUMS', } labelIds = list() for label in labels: if label not in allLabels: # first refresh labels in user mailbox label_results = callGAPI(gmail.users().labels(), u'list', userId=u'me', fields=u'labels(id,name,type)') for a_label in label_results[u'labels']: if a_label[u'type'] == u'system': allLabels[a_label[u'id']] = a_label[u'id'] else: allLabels[a_label[u'name']] = a_label[u'id'] if label not in allLabels: # if still not there, create it label_results = callGAPI(gmail.users().labels(), u'create', body={u'labelListVisibility': u'labelShow', u'messageListVisibility': u'show', u'name': label}, userId=u'me', fields=u'id') allLabels[label] = label_results[u'id'] try: labelIds.append(allLabels[label]) except KeyError: pass if label.find(u'/') != -1: # make sure to create parent labels for proper nesting parent_label = label[:label.rfind(u'/')] while True: if not parent_label in allLabels: label_result = callGAPI(gmail.users().labels(), u'create', userId=u'me', body={u'name': parent_label}) allLabels[parent_label] = label_result[u'id'] if parent_label.find(u'/') == -1: break parent_label = parent_label[:parent_label.rfind(u'/')] return labelIds def doProcessMessages(users, function): query = None doIt = False maxToProcess = 1 body = {} i = 5 while i < len(sys.argv): myarg = sys.argv[i].lower().replace(u'_', u'') if myarg == u'query': query = sys.argv[i+1] i += 2 elif myarg == u'doit': doIt = True i += 1 elif myarg in [u'maxtodelete', u'maxtotrash', u'maxtomodify', u'maxtountrash']: maxToProcess = int(sys.argv[i+1]) i += 2 elif (function == u'modify') and (myarg == u'addlabel'): body.setdefault(u'addLabelIds', []) body[u'addLabelIds'].append(sys.argv[i+1]) i += 2 elif (function == u'modify') and (myarg == u'removelabel'): body.setdefault(u'removeLabelIds', []) body[u'removeLabelIds'].append(sys.argv[i+1]) i += 2 else: print u'ERROR: %s is not a valid argument for "gam %s messages"' % (sys.argv[i], function) sys.exit(2) if not query: print u'ERROR: No query specified. You must specify some query!' sys.exit(2) action = PROCESS_MESSAGE_FUNCTION_TO_ACTION_MAP[function] for user in users: user, gmail = buildGmailGAPIObject(user) if not gmail: continue print u'Searching messages for %s' % user page_message = u'Got %%%%total_items%%%% messages for user %s' % user listResult = callGAPIpages(gmail.users().messages(), u'list', u'messages', page_message=page_message, userId=u'me', q=query, includeSpamTrash=True, soft_errors=True) result_count = len(listResult) if not doIt or result_count == 0: print u'would try to %s %s messages for user %s (max %s)\n' % (function, result_count, user, maxToProcess) continue elif result_count > maxToProcess: print u'WARNING: refusing to %s ANY messages for %s since max messages to process is %s and messages to be %s is %s\n' % (function, user, maxToProcess, action, result_count) continue i = 0 if function == u'delete': id_batches = [[]] for del_me in listResult: id_batches[i].append(del_me[u'id']) if len(id_batches[i]) == 1000: i += 1 id_batches.append([]) deleted_messages = 0 for id_batch in id_batches: print u'deleting %s messages' % len(id_batch) callGAPI(gmail.users().messages(), u'batchDelete', body={u'ids': id_batch}, userId=u'me') deleted_messages += len(id_batch) print u'deleted %s of %s messages' % (deleted_messages, result_count) continue if not body: kwargs = {} else: kwargs = {u'body': {}} for my_key in body.keys(): kwargs[u'body'][my_key] = labelsToLabelIds(gmail, body[my_key]) for a_message in listResult: i += 1 print u' %s message %s for user %s (%s/%s)' % (function, a_message[u'id'], user, i, result_count) callGAPI(gmail.users().messages(), function, id=a_message[u'id'], userId=u'me', **kwargs) def doDeleteLabel(users): label = sys.argv[5] label_name_lower = label.lower() for user in users: user, gmail = buildGmailGAPIObject(user) if not gmail: continue print u'Getting all labels for %s...' % user labels = callGAPI(gmail.users().labels(), u'list', userId=user, fields=u'labels(id,name,type)') del_labels = [] if label == u'--ALL_LABELS--': for del_label in labels[u'labels']: if del_label[u'type'] == u'system': continue del_labels.append(del_label) elif label[:6].lower() == u'regex:': regex = label[6:] p = re.compile(regex) for del_label in labels[u'labels']: if del_label[u'type'] == u'system': continue elif p.match(del_label[u'name']): del_labels.append(del_label) else: for del_label in labels[u'labels']: if label_name_lower == del_label[u'name'].lower(): del_labels.append(del_label) break else: print u' Error: no such label for %s' % user continue j = 0 del_me_count = len(del_labels) dbatch = googleapiclient.http.BatchHttpRequest() for del_me in del_labels: j += 1 print u' deleting label %s (%s/%s)' % (del_me[u'name'], j, del_me_count) dbatch.add(gmail.users().labels().delete(userId=user, id=del_me[u'id']), callback=gmail_del_result) if len(dbatch._order) == 10: dbatch.execute() dbatch = googleapiclient.http.BatchHttpRequest() if len(dbatch._order) > 0: dbatch.execute() def gmail_del_result(request_id, response, exception): if exception: print exception def showLabels(users): i = 5 onlyUser = showCounts = False while i < len(sys.argv): myarg = sys.argv[i].lower().replace(u'_', u'') if myarg == u'onlyuser': onlyUser = True i += 1 elif myarg == u'showcounts': showCounts = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam show labels"' % sys.argv[i] sys.exit(2) for user in users: user, gmail = buildGmailGAPIObject(user) if not gmail: continue labels = callGAPI(gmail.users().labels(), u'list', userId=user, soft_errors=True) if labels: for label in labels[u'labels']: if onlyUser and (label[u'type'] == u'system'): continue print convertUTF8(label[u'name']) for a_key in label: if a_key == u'name': continue print u' %s: %s' % (a_key, label[a_key]) if showCounts: counts = callGAPI(gmail.users().labels(), u'get', userId=user, id=label[u'id'], fields=u'messagesTotal,messagesUnread,threadsTotal,threadsUnread') for a_key in counts: print u' %s: %s' % (a_key, counts[a_key]) print u'' def showGmailProfile(users): todrive = False i = 6 while i < len(sys.argv): if sys.argv[i].lower() == u'todrive': todrive = True i += 1 else: print u'ERROR: %s is not a valid argument for gam show gmailprofile' % sys.argv[i] sys.exit(2) csvRows = [] titles = [u'emailAddress'] i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue sys.stderr.write(u'Getting Gmail profile for %s\n' % user) try: results = callGAPI(gmail.users(), u'getProfile', throw_reasons=GAPI_GMAIL_THROW_REASONS, userId=u'me') if results: for item in results: if item not in titles: titles.append(item) csvRows.append(results) except GAPI_serviceNotAvailable: entityServiceNotApplicableWarning(u'User', user, i, count) sortCSVTitles([u'emailAddress',], titles) writeCSVfile(csvRows, titles, list_type=u'Gmail Profiles', todrive=todrive) def showGplusProfile(users): todrive = False i = 6 while i < len(sys.argv): if sys.argv[i].lower() == u'todrive': todrive = True i += 1 else: print u'ERROR: %s is not a valid argument for gam show gplusprofile' % sys.argv[i] sys.exit(2) csvRows = [] titles = [u'id'] i = 0 count = len(users) for user in users: i += 1 user, gplus = buildGplusGAPIObject(user) if not gplus: continue sys.stderr.write(u'Getting Gplus profile for %s\n' % user) try: results = callGAPI(gplus.people(), u'get', throw_reasons=GAPI_GPLUS_THROW_REASONS, userId=u'me') if results: results = flatten_json(results) csvRows.append(results) for item in results: if item not in titles: titles.append(item) except GAPI_serviceNotAvailable: entityServiceNotApplicableWarning(u'User', user, i, count) sortCSVTitles([u'id',], titles) writeCSVfile(csvRows, titles, list_type=u'Gplus Profiles', todrive=todrive) def updateLabels(users): label_name = sys.argv[5] label_name_lower = label_name.lower() body = {} i = 6 while i < len(sys.argv): if sys.argv[i].lower() == u'name': body[u'name'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower().replace(u'_', u'') == u'messagelistvisibility': body[u'messageListVisibility'] = sys.argv[i+1].lower() if body[u'messageListVisibility'] not in [u'hide', u'show']: print u'ERROR: message_list_visibility must be show or hide; got %s' % sys.argv[i+1] sys.exit(2) i += 2 elif sys.argv[i].lower().replace(u' ', u'') == u'labellistvisibility': if sys.argv[i+1].lower().replace(u'_', u'') == u'showifunread': body[u'labelListVisibility'] = u'labelShowIfUnread' elif sys.argv[i+1].lower().replace(u'_', u'') == u'show': body[u'labelListVisibility'] = u'labelShow' elif sys.argv[i+1].lower().replace(u'_', u'') == u'hide': body[u'labelListVisibility'] = u'labelHide' else: print u'ERROR: label_list_visibility must be hide, show, show_if_unread; got %s' % sys.argv[i+1] sys.exit(2) i += 2 else: print u'ERROR: %s is not a valid argument for "gam update labels"' % sys.argv[i] sys.exit(2) for user in users: user, gmail = buildGmailGAPIObject(user) if not gmail: continue labels = callGAPI(gmail.users().labels(), u'list', userId=user, fields=u'labels(id,name)') for label in labels[u'labels']: if label[u'name'].lower() == label_name_lower: callGAPI(gmail.users().labels(), u'patch', soft_errors=True, userId=user, id=label[u'id'], body=body) break else: print u'Error: user does not have a label named %s' % label_name def renameLabels(users): search = u'^Inbox/(.*)$' replace = u'%s' merge = False i = 5 while i < len(sys.argv): if sys.argv[i].lower() == u'search': search = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'replace': replace = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'merge': merge = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam rename label"' % sys.argv[i] sys.exit(2) pattern = re.compile(search, re.IGNORECASE) for user in users: user, gmail = buildGmailGAPIObject(user) if not gmail: continue labels = callGAPI(gmail.users().labels(), u'list', userId=user) for label in labels[u'labels']: if label[u'type'] == u'system': continue match_result = re.search(pattern, label[u'name']) if match_result != None: new_label_name = replace % match_result.groups() print u' Renaming "%s" to "%s"' % (label[u'name'], new_label_name) try: callGAPI(gmail.users().labels(), u'patch', soft_errors=True, throw_reasons=[u'aborted'], id=label[u'id'], userId=user, body={u'name': new_label_name}) except googleapiclient.errors.HttpError: if merge: print u' Merging %s label to existing %s label' % (label[u'name'], new_label_name) q = u'label:"%s"' % label[u'name'] messages_to_relabel = callGAPIpages(gmail.users().messages(), u'list', u'messages', userId=user, q=q) if len(messages_to_relabel) > 0: for new_label in labels[u'labels']: if new_label[u'name'].lower() == new_label_name.lower(): new_label_id = new_label[u'id'] body = {u'addLabelIds': [new_label_id]} break j = 1 for message_to_relabel in messages_to_relabel: print u' relabeling message %s (%s/%s)' % (message_to_relabel[u'id'], j, len(messages_to_relabel)) callGAPI(gmail.users().messages(), u'modify', userId=user, id=message_to_relabel[u'id'], body=body) j += 1 else: print u' no messages with %s label' % label[u'name'] print u' Deleting label %s' % label[u'name'] callGAPI(gmail.users().labels(), u'delete', id=label[u'id'], userId=user) else: print u' Error: looks like %s already exists, not renaming. Use the "merge" argument to merge the labels' % new_label_name def _getUserGmailLabels(gmail, user, i, count, **kwargs): try: labels = callGAPI(gmail.users().labels(), u'list', throw_reasons=GAPI_GMAIL_THROW_REASONS, userId=u'me', **kwargs) if not labels: labels = {u'labels': []} return labels except GAPI_serviceNotAvailable: entityServiceNotApplicableWarning(u'User', user, i, count) return None def _getLabelId(labels, labelName): for label in labels[u'labels']: if label[u'id'] == labelName or label[u'name'] == labelName: return label[u'id'] return labelName def _getLabelName(labels, labelId): for label in labels[u'labels']: if label[u'id'] == labelId: return label[u'name'] return labelId FILTER_ADD_LABEL_TO_ARGUMENT_MAP = { u'IMPORTANT': u'important', u'STARRED': u'star', u'TRASH': u'trash', } FILTER_REMOVE_LABEL_TO_ARGUMENT_MAP = { u'IMPORTANT': u'notimportant', u'UNREAD': u'markread', u'INBOX': u'archive', u'SPAM': u'neverspam', } def _printFilter(user, userFilter, labels): row = {u'User': user, u'id': userFilter[u'id']} for item in userFilter[u'criteria']: if item in [u'hasAttachment', u'excludeChats']: row[item] = item elif item == u'size': row[item] = u'size {0} {1}'.format(userFilter[u'criteria'][u'sizeComparison'], userFilter[u'criteria'][item]) elif item == u'sizeComparison': pass else: row[item] = u'{0} {1}'.format(item, userFilter[u'criteria'][item]) 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] else: row[u'label'] = u'label {0}'.format(_getLabelName(labels, labelId)) for labelId in userFilter[u'action'].get(u'removeLabelIds', []): if labelId in FILTER_REMOVE_LABEL_TO_ARGUMENT_MAP: 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']) return row def _showFilter(userFilter, j, jcount, labels): print u' Filter: {0}{1}'.format(userFilter[u'id'], currentCount(j, jcount)) print u' Criteria:' for item in userFilter[u'criteria']: if item in [u'hasAttachment', u'excludeChats']: print u' {0}'.format(item) elif item == u'size': print u' {0} {1} {2}'.format(item, userFilter[u'criteria'][u'sizeComparison'], userFilter[u'criteria'][item]) elif item == u'sizeComparison': pass else: print convertUTF8(u' {0} "{1}"'.format(item, userFilter[u'criteria'][item])) print u' Actions:' 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]) else: print convertUTF8(u' label "{0}"'.format(_getLabelName(labels, labelId))) for labelId in userFilter[u'action'].get(u'removeLabelIds', []): if labelId in FILTER_REMOVE_LABEL_TO_ARGUMENT_MAP: 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']) # FILTER_CRITERIA_CHOICES_MAP = { u'excludechats': u'excludeChats', u'from': u'from', u'hasattachment': u'hasAttachment', u'haswords': u'query', u'musthaveattachment': u'hasAttachment', u'negatedquery': u'negatedQuery', u'nowords': u'negatedQuery', u'query': u'query', u'size': u'size', u'subject': u'subject', u'to': u'to', } FILTER_ACTION_CHOICES = [u'archive', u'forward', u'important', u'label', u'markread', u'neverspam', u'notimportant', u'star', u'trash',] def addFilter(users, i): body = {} addLabelName = None addLabelIds = [] removeLabelIds = [] while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg in FILTER_CRITERIA_CHOICES_MAP: myarg = FILTER_CRITERIA_CHOICES_MAP[myarg] body.setdefault(u'criteria', {}) if myarg == u'from': body[u'criteria'][myarg] = sys.argv[i+1] i += 2 elif myarg == u'to': body[u'criteria'][myarg] = sys.argv[i+1] i += 2 elif myarg in [u'subject', u'query', u'negatedQuery']: body[u'criteria'][myarg] = sys.argv[i+1] i += 2 elif myarg in [u'hasAttachment', u'excludeChats']: body[u'criteria'][myarg] = True i += 1 elif myarg == u'size': body[u'criteria'][u'sizeComparison'] = sys.argv[i+1].lower() if body[u'criteria'][u'sizeComparison'] not in [u'larger', u'smaller']: print u'ERROR: size must be followed by larger or smaller; got %s' % sys.argv[i+1].lower() sys.exit(2) body[u'criteria'][myarg] = sys.argv[i+2] i += 3 elif myarg in FILTER_ACTION_CHOICES: body.setdefault(u'action', {}) if myarg == u'label': addLabelName = sys.argv[i+1] i += 2 elif myarg == u'important': addLabelIds.append(u'IMPORTANT') if u'IMPORTANT' in removeLabelIds: removeLabelIds.remove(u'IMPORTANT') i += 1 elif myarg == u'star': addLabelIds.append(u'STARRED') i += 1 elif myarg == u'trash': addLabelIds.append(u'TRASH') i += 1 elif myarg == u'notimportant': removeLabelIds.append(u'IMPORTANT') if u'IMPORTANT' in addLabelIds: addLabelIds.remove(u'IMPORTANT') i += 1 elif myarg == u'markread': removeLabelIds.append(u'UNREAD') i += 1 elif myarg == u'archive': removeLabelIds.append(u'INBOX') i += 1 elif myarg == u'neverspam': removeLabelIds.append(u'SPAM') i += 1 elif sys.argv[i].lower() == u'forward': body[u'action'][u'forward'] = sys.argv[i+1] i += 2 else: print u'ERROR: %s is not a valid argument for "gam filter"' % sys.argv[i] sys.exit(2) if u'criteria' not in body: print u'ERROR: you must specify a crtieria <{0}> for "gam filter"'.format(u'|'.join(FILTER_CRITERIA_CHOICES_MAP)) sys.exit(2) if u'action' not in body: print u'ERROR: you must specify an action <{0}> for "gam filter"'.format(u'|'.join(FILTER_ACTION_CHOICES)) sys.exit(2) if removeLabelIds: body[u'action'][u'removeLabelIds'] = removeLabelIds i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue labels = _getUserGmailLabels(gmail, user, i, count, fields=u'labels(id,name)') if not labels: continue if addLabelIds: body[u'action'][u'addLabelIds'] = addLabelIds[:] if addLabelName: if not addLabelIds: body[u'action'][u'addLabelIds'] = [] body[u'action'][u'addLabelIds'].append(_getLabelId(labels, addLabelName)) print u"Adding filter for %s (%s/%s)" % (user, i, count) result = callGAPI(gmail.users().settings().filters(), u'create', soft_errors=True, userId=u'me', body=body) if result: print u"User: %s, Filter: %s, Added (%s/%s)" % (user, result[u'id'], i, count) def deleteFilters(users): filterId = sys.argv[5] i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue print u"Deleting filter %s for %s (%s/%s)" % (filterId, user, i, count) callGAPI(gmail.users().settings().filters(), u'delete', soft_errors=True, userId=u'me', id=filterId) def printShowFilters(users, csvFormat): if csvFormat: todrive = False csvRows = [] titles = [u'User', u'id'] i = 5 while i < len(sys.argv): myarg = sys.argv[i].lower() if csvFormat and myarg == u'todrive': todrive = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam %s filter"' % (myarg, [u'show', u'print'][csvFormat]) sys.exit(2) i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue labels = callGAPI(gmail.users().labels(), u'list', soft_errors=True, userId=u'me', fields=u'labels(id,name)') if not labels: labels = {u'labels': []} result = callGAPI(gmail.users().settings().filters(), u'list', soft_errors=True, userId=u'me') jcount = len(result.get(u'filter', [])) if (result) else 0 if not csvFormat: print u'User: {0}, Filters: ({1}/{2})'.format(user, i, count) if jcount == 0: continue j = 0 for userFilter in result[u'filter']: j += 1 _showFilter(userFilter, j, jcount, labels) else: if jcount == 0: continue for userFilter in result[u'filter']: row = _printFilter(user, userFilter, labels) for item in row: if item not in titles: titles.append(item) csvRows.append(row) if csvFormat: sortCSVTitles([u'User', u'id'], titles) writeCSVfile(csvRows, titles, u'Filters', todrive) def infoFilters(users): filterId = sys.argv[5] i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue labels = callGAPI(gmail.users().labels(), u'list', soft_errors=True, userId=u'me', fields=u'labels(id,name)') if not labels: labels = {u'labels': []} result = callGAPI(gmail.users().settings().filters(), u'get', soft_errors=True, userId=u'me', id=filterId) if result: print u'User: {0}, Filter: ({1}/{2})'.format(user, i, count) _showFilter(result, 1, 1, labels) EMAILSETTINGS_OLD_NEW_OLD_FORWARD_ACTION_MAP = { u'ARCHIVE': u'archive', u'DELETE': u'trash', u'KEEP': u'leaveInInBox', u'MARK_READ': u'markRead', u'archive': u'ARCHIVE', u'trash': u'DELETE', u'leaveInInbox': u'KEEP', u'markRead': u'MARK_READ', } def doForward(users): if sys.argv[4].lower() in true_values: enable = True elif sys.argv[4].lower() in false_values: enable = False else: print u'ERROR: value for "gam forward" must be true or false; got %s' % sys.argv[4] sys.exit(2) body = {u'enabled': enable} i = 5 while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg in EMAILSETTINGS_FORWARD_POP_ACTION_CHOICES_MAP: body[u'disposition'] = EMAILSETTINGS_FORWARD_POP_ACTION_CHOICES_MAP[myarg] i += 1 elif myarg == u'confirm': i += 1 elif myarg.find(u'@') != -1: body[u'emailAddress'] = sys.argv[i] i += 1 else: print u'ERROR: %s is not a valid argument for "gam forward"' % myarg sys.exit(2) if enable and (not body.get(u'disposition') or not body.get(u'emailAddress')): print u'ERROR: you must specify an action and a forwarding address for "gam forward' sys.exit(2) i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue if enable: print u"User: %s, Forward Enabled: %s, Forwarding Address: %s, Action: %s (%s/%s)" % (user, enable, body[u'emailAddress'], body[u'disposition'], i, count) else: print u"User: %s, Forward Enabled: %s (%s/%s)" % (user, enable, i, count) callGAPI(gmail.users().settings(), u'updateAutoForwarding', soft_errors=True, userId=u'me', body=body) def printShowForward(users, csvFormat): def _showForward(user, i, count, result): if u'enabled' in result: enabled = result[u'enabled'] if enabled: print u"User: %s, Forward Enabled: %s, Forwarding Address: %s, Action: %s (%s/%s)" % (user, enabled, result[u'emailAddress'], result[u'disposition'], i, count) else: print u"User: %s, Forward Enabled: %s (%s/%s)" % (user, enabled, i, count) else: enabled = result[u'enable'] == u'true' if enabled: print u"User: %s, Forward Enabled: %s, Forwarding Address: %s, Action: %s (%s/%s)" % (user, enabled, result[u'forwardTo'], EMAILSETTINGS_OLD_NEW_OLD_FORWARD_ACTION_MAP[result[u'action']], i, count) else: print u"User: %s, Forward Enabled: %s (%s/%s)" % (user, enabled, i, count) def _printForward(user, result): if u'enabled' in result: row = {u'User': user, u'forwardEnabled': result[u'enabled']} if result[u'enabled']: row[u'forwardTo'] = result[u'emailAddress'] row[u'disposition'] = result[u'disposition'] else: row = {u'User': user, u'forwardEnabled': result[u'enable']} if result[u'enable'] == u'true': row[u'forwardTo'] = result[u'forwardTo'] row[u'disposition'] = EMAILSETTINGS_OLD_NEW_OLD_FORWARD_ACTION_MAP[result[u'action']] csvRows.append(row) if csvFormat: todrive = False csvRows = [] titles = [u'User', u'forwardEnabled', u'forwardTo', u'disposition'] i = 5 while i < len(sys.argv): myarg = sys.argv[i].lower() if csvFormat and myarg == u'todrive': todrive = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam %s forward"' % (myarg, [u'show', u'print'][csvFormat]) sys.exit(2) i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue result = callGAPI(gmail.users().settings(), u'getAutoForwarding', soft_errors=True, userId=u'me') if result: if not csvFormat: _showForward(user, i, count, result) else: _printForward(user, result) if csvFormat: writeCSVfile(csvRows, titles, u'Forward', todrive) def addForwardingAddresses(users): emailAddress = sys.argv[5] if emailAddress.find(u'@') == -1: emailAddress = u'%s@%s' % (emailAddress, GC_Values[GC_DOMAIN]) body = {u'forwardingEmail': emailAddress} i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue print u"Adding Forwarding Address %s for %s (%s/%s)" % (emailAddress, user, i, count) callGAPI(gmail.users().settings().forwardingAddresses(), u'create', soft_errors=True, userId=u'me', body=body) def deleteForwardingAddresses(users): emailAddress = sys.argv[5] if emailAddress.find(u'@') == -1: emailAddress = u'%s@%s' % (emailAddress, GC_Values[GC_DOMAIN]) i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue print u"Deleting Forwarding Address %s for %s (%s/%s)" % (emailAddress, user, i, count) callGAPI(gmail.users().settings().forwardingAddresses(), u'delete', soft_errors=True, userId=u'me', forwardingEmail=emailAddress) def printShowForwardingAddresses(users, csvFormat): if csvFormat: todrive = False csvRows = [] titles = [u'User', u'forwardingEmail', u'verificationStatus'] i = 5 while i < len(sys.argv): myarg = sys.argv[i].lower() if csvFormat and myarg == u'todrive': todrive = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam %s forwardingaddresses"' % (myarg, [u'show', u'print'][csvFormat]) sys.exit(2) i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue result = callGAPI(gmail.users().settings().forwardingAddresses(), u'list', soft_errors=True, userId=u'me') jcount = len(result.get(u'forwardingAddresses', [])) if (result) else 0 if not csvFormat: print u'User: {0}, Forwarding Addresses: ({1}/{2})'.format(user, i, count) if jcount == 0: continue j = 0 for forward in result[u'forwardingAddresses']: j += 1 print u' Forwarding Address: {0}, Verification Status: {1} ({2}/{3})'.format(forward[u'forwardingEmail'], forward[u'verificationStatus'], j, jcount) else: if jcount == 0: continue for forward in result[u'forwardingAddresses']: row = {u'User': user, u'forwardingEmail': forward[u'forwardingEmail'], u'verificationStatus': forward[u'verificationStatus']} csvRows.append(row) if csvFormat: writeCSVfile(csvRows, titles, u'Forwarding Addresses', todrive) def infoForwardingAddresses(users): emailAddress = sys.argv[5] if emailAddress.find(u'@') == -1: emailAddress = u'%s@%s' % (emailAddress, GC_Values[GC_DOMAIN]) i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue forward = callGAPI(gmail.users().settings().forwardingAddresses(), u'get', soft_errors=True, userId=u'me', forwardingEmail=emailAddress) if forward: print u'User: {0}, Forwarding Address: {1}, Verification Status: {2} ({3}/{4})'.format(user, forward[u'forwardingEmail'], forward[u'verificationStatus'], i, count) def doSignature(users): tagReplacements = {} i = 4 if sys.argv[i].lower() == u'file': filename = sys.argv[i+1] i, encoding = getCharSet(i+2) signature = readFile(filename, encoding=encoding).replace(u'\\n', u'
').replace(u'\n', u'
') else: signature = getString(i, u'String', emptyOK=True).replace(u'\\n', u'
').replace(u'\n', u'
') i += 1 body = {} while i < len(sys.argv): myarg = sys.argv[i].lower() i = getSendAsAttributes(i, myarg, body, tagReplacements, u'signature') if tagReplacements: body[u'signature'] = _processTags(tagReplacements, signature) else: body[u'signature'] = signature i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue print u'Setting Signature for {0} ({1}/{2})'.format(user, i, count) callGAPI(gmail.users().settings().sendAs(), u'patch', soft_errors=True, userId=u'me', body=body, sendAsEmail=user) def getSignature(users): formatSig = False i = 5 while i < len(sys.argv): if sys.argv[i].lower() == u'format': formatSig = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam show signature"' % sys.argv[i] sys.exit(2) i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue result = callGAPI(gmail.users().settings().sendAs(), u'get', soft_errors=True, userId=u'me', sendAsEmail=user) if result: _showSendAs(result, i, count, formatSig) def doVacation(users): if sys.argv[4].lower() in true_values: enable = True elif sys.argv[4].lower() in false_values: enable = False else: print u'ERROR: value for "gam vacation" must be true or false; got %s' % sys.argv[4] sys.exit(2) body = {u'enableAutoReply': enable} if enable: responseBodyType = u'responseBodyPlainText' message = None tagReplacements = {} i = 5 while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg == u'subject': body[u'responseSubject'] = sys.argv[i+1] i += 2 elif myarg == u'message': message = sys.argv[i+1] i += 2 elif myarg == u'file': filename = sys.argv[i+1] i, encoding = getCharSet(i+2) message = readFile(filename, encoding=encoding) elif myarg == u'replace': matchTag = getString(i+1, u'Tag') matchReplacement = getString(i+2, u'String', emptyOK=True) tagReplacements[matchTag] = matchReplacement i += 3 elif myarg == u'html': responseBodyType = u'responseBodyHtml' i += 1 elif myarg == u'contactsonly': body[u'restrictToContacts'] = True i += 1 elif myarg == u'domainonly': body[u'restrictToDomain'] = True i += 1 elif myarg == u'startdate': body[u'startTime'] = getYYYYMMDD(i+1, returnTimeStamp=True) i += 2 elif myarg == u'enddate': body[u'endTime'] = getYYYYMMDD(i+1, returnTimeStamp=True) i += 2 else: print u'ERROR: %s is not a valid argument for "gam vacation"' % sys.argv[i] sys.exit(2) if message: if responseBodyType == u'responseBodyHtml': message = message.replace(u'\\n', u'
').replace(u'\n', u'
') else: message = message.replace(u'\\n', u'\n') if tagReplacements: message = _processTags(tagReplacements, message) body[responseBodyType] = message if not message and not body.get(u'responseSubject'): print u'ERROR: You must specify a non-blank subject or message!' sys.exit(2) i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue print u"Setting Vacation for %s (%s/%s)" % (user, i, count) callGAPI(gmail.users().settings(), u'updateVacation', soft_errors=True, userId=u'me', body=body) def getVacation(users): formatReply = False i = 5 while i < len(sys.argv): if sys.argv[i].lower() == u'format': formatReply = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam show vacation"' % sys.argv[i] sys.exit(2) i = 0 count = len(users) for user in users: i += 1 user, gmail = buildGmailGAPIObject(user) if not gmail: continue result = callGAPI(gmail.users().settings(), u'getVacation', soft_errors=True, userId=u'me') if result: enabled = result[u'enableAutoReply'] print u'User: {0}, Vacation: ({1}/{2})'.format(user, i, count) print u' Enabled: {0}'.format(enabled) if enabled: print u' Contacts Only: {0}'.format(result[u'restrictToContacts']) print u' Domain Only: {0}'.format(result[u'restrictToDomain']) if u'startTime' in result: print u' Start Date: {0}'.format(datetime.datetime.fromtimestamp(int(result[u'startTime'])/1000).strftime('%Y-%m-%d')) else: print u' Start Date: Started' if u'endTime' in result: print u' End Date: {0}'.format(datetime.datetime.fromtimestamp(int(result[u'endTime'])/1000).strftime('%Y-%m-%d')) else: print u' End Date: Not specified' print convertUTF8(u' Subject: {0}'.format(result.get(u'responseSubject', u'None'))) sys.stdout.write(u' Message:\n ') if result.get(u'responseBodyPlainText'): print convertUTF8(indentMultiLineText(result[u'responseBodyPlainText'], n=4)) elif result.get(u'responseBodyHtml'): if formatReply: print convertUTF8(indentMultiLineText(dehtml(result[u'responseBodyHtml']), n=4)) else: print convertUTF8(indentMultiLineText(result[u'responseBodyHtml'], n=4)) else: print u'None' def doDelSchema(): cd = buildGAPIObject(u'directory') schemaKey = sys.argv[3] callGAPI(cd.schemas(), u'delete', customerId=GC_Values[GC_CUSTOMER_ID], schemaKey=schemaKey) print u'Deleted schema %s' % schemaKey def doCreateOrUpdateUserSchema(updateCmd): cd = buildGAPIObject(u'directory') schemaKey = sys.argv[3] if updateCmd: cmd = u'update' try: body = callGAPI(cd.schemas(), u'get', throw_reasons=[u'notFound'], customerId=GC_Values[GC_CUSTOMER_ID], schemaKey=schemaKey) except googleapiclient.errors.HttpError: print u'ERROR: Schema %s does not exist.' % schemaKey sys.exit(3) else: # create cmd = u'create' body = {u'schemaName': schemaKey, u'fields': []} i = 4 while i < len(sys.argv): if sys.argv[i] in [u'field']: if updateCmd: # clear field if it exists on update for n, field in enumerate(body[u'fields']): if field[u'fieldName'].lower() == sys.argv[i+1].lower(): del body[u'fields'][n] break a_field = {u'fieldName': sys.argv[i+1]} i += 2 while True: if sys.argv[i].lower() in [u'type']: a_field[u'fieldType'] = sys.argv[i+1].upper() if a_field[u'fieldType'] not in [u'BOOL', u'DOUBLE', u'EMAIL', u'INT64', u'PHONE', u'STRING']: print u'ERROR: type must be one of bool, double, email, int64, phone, string; got %s' % a_field[u'fieldType'] sys.exit(2) i += 2 elif sys.argv[i].lower() in [u'multivalued']: a_field[u'multiValued'] = True i += 1 elif sys.argv[i].lower() in [u'indexed']: a_field[u'indexed'] = True i += 1 elif sys.argv[i].lower() in [u'restricted']: a_field[u'readAccessType'] = u'ADMINS_AND_SELF' i += 1 elif sys.argv[i].lower() in [u'range']: a_field[u'numericIndexingSpec'] = {u'minValue': sys.argv[i+1], u'maxValue': sys.argv[i+2]} i += 3 elif sys.argv[i].lower() in [u'endfield']: body[u'fields'].append(a_field) i += 1 break else: print u'ERROR: %s is not a valid argument for "gam %s schema"' % (sys.argv[i], cmd) sys.exit(2) elif updateCmd and sys.argv[i] in [u'deletefield']: for n, field in enumerate(body[u'fields']): if field[u'fieldName'].lower() == sys.argv[i+1].lower(): del body[u'fields'][n] break else: print u'ERROR: field %s not found in schema %s' % (sys.argv[i+1], schemaKey) sys.exit(3) i += 2 else: print u'ERROR: %s is not a valid argument for "gam %s schema"' % (sys.argv[i], cmd) sys.exit(2) if updateCmd: result = callGAPI(cd.schemas(), u'update', customerId=GC_Values[GC_CUSTOMER_ID], body=body, schemaKey=schemaKey) print u'Updated user schema %s' % result[u'schemaName'] else: result = callGAPI(cd.schemas(), u'insert', customerId=GC_Values[GC_CUSTOMER_ID], body=body) print u'Created user schema %s' % result[u'schemaName'] def _showSchema(schema): print u'Schema: %s' % schema[u'schemaName'] for a_key in schema: if a_key not in [u'schemaName', u'fields', u'etag', u'kind']: print u' %s: %s' % (a_key, schema[a_key]) for field in schema[u'fields']: print u' Field: %s' % field[u'fieldName'] for a_key in field: if a_key not in [u'fieldName', u'kind', u'etag']: print u' %s: %s' % (a_key, field[a_key]) def doPrintShowUserSchemas(csvFormat): cd = buildGAPIObject(u'directory') if csvFormat: todrive = False csvRows = [] titles = [] i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower() if csvFormat and myarg == u'todrive': todrive = True i += 1 else: 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: return for schema in schemas[u'schemas']: if not csvFormat: _showSchema(schema) else: row = {u'fields.Count': len(schema[u'fields'])} addRowTitlesToCSVfile(flatten_json(schema, flattened=row), csvRows, titles) if csvFormat: sortCSVTitles([u'schemaId', u'schemaName', u'fields.Count'], titles) writeCSVfile(csvRows, titles, u'User Schemas', todrive) def doGetUserSchema(): cd = buildGAPIObject(u'directory') schemaKey = sys.argv[3] schema = callGAPI(cd.schemas(), u'get', customerId=GC_Values[GC_CUSTOMER_ID], schemaKey=schemaKey) _showSchema(schema) def checkClearBodyList(i, body, itemName): if sys.argv[i].lower() == u'clear': if itemName in body: del body[itemName] body.setdefault(itemName, None) return True return False def appendItemToBodyList(body, itemName, itemValue): if (itemName in body) and (body[itemName] == None): del body[itemName] body.setdefault(itemName, []) body[itemName].append(itemValue) def getUserAttributes(i, updateCmd=False): if updateCmd: body = {} need_password = False else: body = {u'name': {u'givenName': u'Unknown', u'familyName': u'Unknown'}} body[u'primaryEmail'] = sys.argv[i] if body[u'primaryEmail'].find(u'@') == -1: body[u'primaryEmail'] = u'%s@%s' % (body[u'primaryEmail'], GC_Values[GC_DOMAIN]) i += 1 need_password = True need_to_hash_password = True admin_body = {} while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg == u'firstname': body.setdefault(u'name', {}) body[u'name'][u'givenName'] = sys.argv[i+1] i += 2 elif myarg == u'lastname': body.setdefault(u'name', {}) body[u'name'][u'familyName'] = sys.argv[i+1] i += 2 elif myarg in [u'username', u'email', u'primaryemail'] and updateCmd: body[u'primaryEmail'] = sys.argv[i+1] if body[u'primaryEmail'].find(u'@') == -1: body[u'primaryEmail'] = u'%s@%s' % (body[u'primaryEmail'], GC_Values[GC_DOMAIN]) i += 2 elif myarg == u'customerid' and updateCmd: body[u'customerId'] = sys.argv[i+1] i += 2 elif myarg == u'password': need_password = False body[u'password'] = sys.argv[i+1] if body[u'password'].lower() == u'random': need_password = True i += 2 elif myarg == u'admin': if sys.argv[i+1].lower() in true_values: admin_body[u'status'] = True elif sys.argv[i+1].lower() in false_values: admin_body[u'status'] = False else: print u'ERROR: admin must be on or off; got %s' % sys.argv[i+1] sys.exit(2) i += 2 elif myarg == u'suspended': if sys.argv[i+1].lower() in true_values: body[u'suspended'] = True elif sys.argv[i+1].lower() in false_values: body[u'suspended'] = False else: print u'ERROR: suspended must be on or off; got %s' % sys.argv[i+1] sys.exit(2) i += 2 elif myarg == u'gal': if sys.argv[i+1].lower() in true_values: body[u'includeInGlobalAddressList'] = True elif sys.argv[i+1].lower() in false_values: body[u'includeInGlobalAddressList'] = False else: print u'ERROR: gal must be on or off; got %s' % sys.argv[i+1] sys.exit(2) i += 2 elif myarg in [u'sha', u'sha1', u'sha-1']: body[u'hashFunction'] = u'SHA-1' need_to_hash_password = False i += 1 elif myarg == u'md5': body[u'hashFunction'] = u'MD5' need_to_hash_password = False i += 1 elif myarg == u'crypt': body[u'hashFunction'] = u'crypt' need_to_hash_password = False i += 1 elif myarg == u'nohash': need_to_hash_password = False i += 1 elif myarg == u'changepassword': if sys.argv[i+1].lower() in true_values: body[u'changePasswordAtNextLogin'] = True elif sys.argv[i+1].lower() in false_values: body[u'changePasswordAtNextLogin'] = False else: print u'ERROR: changepassword must be on or off; got %s' % sys.argv[i+1] sys.exit(2) i += 2 elif myarg == u'ipwhitelisted': if sys.argv[i+1].lower() in true_values: body[u'ipWhitelisted'] = True elif sys.argv[i+1].lower() in false_values: body[u'ipWhitelisted'] = False else: print u'ERROR: ipwhitelisted must be on or off; got %s' % sys.argv[i+1] sys.exit(2) i += 2 elif myarg == u'agreedtoterms': if sys.argv[i+1].lower() in true_values: body[u'agreedToTerms'] = True elif sys.argv[i+1].lower() in false_values: body[u'agreedToTerms'] = False else: print u'ERROR: agreedtoterms must be on or off; got %s' % sys.argv[i+1] sys.exit(2) i += 2 elif myarg in [u'org', u'ou']: body[u'orgUnitPath'] = sys.argv[i+1] if body[u'orgUnitPath'][0] != u'/': body[u'orgUnitPath'] = u'/%s' % body[u'orgUnitPath'] i += 2 elif myarg in [u'address', u'addresses']: i += 1 if checkClearBodyList(i, body, u'addresses'): i += 1 continue address = {} if sys.argv[i].lower() != u'type': print u'ERROR: wrong format for account address details. Expected type got %s' % sys.argv[i] sys.exit(2) i += 1 address[u'type'] = sys.argv[i].lower() if address[u'type'] not in [u'custom', u'home', u'other', u'work']: print u'ERROR: wrong type must be one of custom, home, other, work; got %s' % address[u'type'] sys.exit(2) if address[u'type'] == u'custom': i += 1 address[u'customType'] = sys.argv[i] i += 1 if sys.argv[i].lower() == u'unstructured': i += 1 address[u'sourceIsStructured'] = False address[u'formatted'] = sys.argv[i] i += 1 while True: myopt = sys.argv[i].lower() if myopt == u'pobox': address[u'poBox'] = sys.argv[i+1] i += 2 elif myopt == u'extendedaddress': address[u'extendedAddress'] = sys.argv[i+1] i += 2 elif myopt == u'streetaddress': address[u'streetAddress'] = sys.argv[i+1] i += 2 elif myopt == u'locality': address[u'locality'] = sys.argv[i+1] i += 2 elif myopt == u'region': address[u'region'] = sys.argv[i+1] i += 2 elif myopt == u'postalcode': address[u'postalCode'] = sys.argv[i+1] i += 2 elif myopt == u'country': address[u'country'] = sys.argv[i+1] i += 2 elif myopt == u'countrycode': address[u'countryCode'] = sys.argv[i+1] i += 2 elif myopt in [u'notprimary', u'primary']: address[u'primary'] = myopt == u'primary' i += 1 break else: print u'ERROR: invalid argument (%s) for account address details' % sys.argv[i] sys.exit(2) appendItemToBodyList(body, u'addresses', address) elif myarg in [u'emails', u'otheremail', u'otheremails']: i += 1 if checkClearBodyList(i, body, u'emails'): i += 1 continue an_email = {} an_email[u'type'] = sys.argv[i].lower() if an_email[u'type'] == u'custom': i += 1 an_email[u'customType'] = sys.argv[i] elif an_email[u'type'] not in [u'home', u'work', u'other']: an_email[u'type'] = u'custom' an_email[u'customType'] = sys.argv[i] i += 1 an_email[u'address'] = sys.argv[i] i += 1 appendItemToBodyList(body, u'emails', an_email) elif myarg in [u'im', u'ims']: i += 1 if checkClearBodyList(i, body, u'ims'): i += 1 continue im = {} if sys.argv[i].lower() != u'type': print u'ERROR: wrong format for account im details. Expected type got %s' % sys.argv[i] sys.exit(2) i += 1 im[u'type'] = sys.argv[i].lower() if im[u'type'] not in [u'custom', u'home', u'other', u'work']: print u'ERROR: type must be one of custom, home, other, work; got %s' % im[u'type'] sys.exit(2) if im[u'type'] == u'custom': i += 1 im[u'customType'] = sys.argv[i] i += 1 if sys.argv[i].lower() != u'protocol': print u'ERROR: wrong format for account details. Expected protocol got %s' % sys.argv[i] sys.exit(2) i += 1 im[u'protocol'] = sys.argv[i].lower() if im[u'protocol'] not in [u'custom_protocol', u'aim', u'gtalk', u'icq', u'jabber', u'msn', u'net_meeting', u'qq', u'skype', u'yahoo']: print u'ERROR: protocol must be one of custom_protocol, aim, gtalk, icq, jabber, msn, net_meeting, qq, skype, yahoo; got %s' % im[u'protocol'] sys.exit(2) if im[u'protocol'] == u'custom_protocol': i += 1 im[u'customProtocol'] = sys.argv[i] i += 1 # Backwards compatability: notprimary|primary on either side of IM address myopt = sys.argv[i].lower() if myopt in [u'notprimary', u'primary']: im[u'primary'] = myopt == u'primary' i += 1 im[u'im'] = sys.argv[i] i += 1 myopt = sys.argv[i].lower() if myopt in [u'notprimary', u'primary']: im[u'primary'] = myopt == u'primary' i += 1 appendItemToBodyList(body, u'ims', im) elif myarg in [u'organization', u'organizations']: i += 1 if checkClearBodyList(i, body, u'organizations'): i += 1 continue organization = {} while True: myopt = sys.argv[i].lower() if myopt == u'name': organization[u'name'] = sys.argv[i+1] i += 2 elif myopt == u'title': organization[u'title'] = sys.argv[i+1] i += 2 elif myopt == u'customtype': organization[u'customType'] = sys.argv[i+1] i += 2 elif myopt == u'type': organization[u'type'] = sys.argv[i+1].lower() if organization[u'type'] not in [u'domain_only', u'school', u'unknown', u'work']: print u'ERROR: organization type must be one of domain_only, school, unknown, work; got %s' % organization[u'type'] sys.exit(2) i += 2 elif myopt == u'department': organization[u'department'] = sys.argv[i+1] i += 2 elif myopt == u'symbol': organization[u'symbol'] = sys.argv[i+1] i += 2 elif myopt == u'costcenter': organization[u'costCenter'] = sys.argv[i+1] i += 2 elif myopt == u'location': organization[u'location'] = sys.argv[i+1] i += 2 elif myopt == u'description': organization[u'description'] = sys.argv[i+1] i += 2 elif myopt == u'domain': organization[u'domain'] = sys.argv[i+1] i += 2 elif myopt in [u'notprimary', u'primary']: organization[u'primary'] = myopt == u'primary' i += 1 break else: print u'ERROR: invalid argument (%s) for account organization details' % sys.argv[i] sys.exit(2) appendItemToBodyList(body, u'organizations', organization) elif myarg in [u'phone', u'phones']: i += 1 if checkClearBodyList(i, body, u'phones'): i += 1 continue phone = {} while True: myopt = sys.argv[i].lower() if myopt == u'value': phone[u'value'] = sys.argv[i+1] i += 2 elif myopt == u'type': phone[u'type'] = sys.argv[i+1].lower() if phone[u'type'] not in [u'assistant', u'callback', u'car', u'company_main', u'custom', u'grand_central', u'home', u'home_fax', u'isdn', u'main', u'mobile', u'other', u'other_fax', u'pager', u'radio', u'telex', u'tty_tdd', u'work', u'work_fax', u'work_mobile', u'work_pager']: print u'ERROR: phone type must be one of assistant, callback, car, company_main, custom, grand_central, home, home_fax, isdn, main, mobile, other, other_fax, pager, radio, telex, tty_tdd, work, work_fax, work_mobile, work_pager; got %s' % phone[u'type'] sys.exit(2) i += 2 if phone[u'type'] == u'custom': phone[u'customType'] = sys.argv[i] i += 1 elif myopt in [u'notprimary', u'primary']: phone[u'primary'] = myopt == u'primary' i += 1 break else: print u'ERROR: invalid argument (%s) for account phone details' % sys.argv[i] sys.exit(2) appendItemToBodyList(body, u'phones', phone) elif myarg in [u'relation', u'relations']: i += 1 if checkClearBodyList(i, body, u'relations'): i += 1 continue relation = {} relation[u'type'] = sys.argv[i].lower() if relation[u'type'] not in [u'mother', u'father', u'sister', u'brother', u'manager', u'assistant', u'partner']: relation[u'type'] = u'custom' relation[u'customType'] = sys.argv[i] i += 1 relation[u'value'] = sys.argv[i] i += 1 appendItemToBodyList(body, u'relations', relation) elif myarg in [u'externalid', u'externalids']: i += 1 if checkClearBodyList(i, body, u'externalIds'): i += 1 continue externalid = {} externalid[u'type'] = sys.argv[i].lower() if externalid[u'type'] not in [u'account', u'customer', u'network', u'organization']: externalid[u'type'] = u'custom' externalid[u'customType'] = sys.argv[i] i += 1 externalid[u'value'] = sys.argv[i] i += 1 appendItemToBodyList(body, u'externalIds', externalid) elif myarg in [u'website', u'websites']: i += 1 if checkClearBodyList(i, body, u'websites'): i += 1 continue website = {} website[u'type'] = sys.argv[i].lower() if website[u'type'] == u'custom': i += 1 website[u'customType'] = sys.argv[i] elif website[u'type'] not in [u'home', u'work', u'home_page', u'ftp', u'blog', u'profile', u'other', u'reservations', u'app_install_page']: website[u'type'] = u'custom' website[u'customType'] = sys.argv[i] i += 1 website[u'value'] = sys.argv[i] i += 1 myopt = sys.argv[i].lower() if myopt in [u'notprimary', u'primary']: website[u'primary'] = myopt == u'primary' i += 1 appendItemToBodyList(body, u'websites', website) elif myarg in [u'note', u'notes']: i += 1 if checkClearBodyList(i, body, u'notes'): i += 1 continue note = {} if sys.argv[i].lower() in [u'text_plain', u'text_html']: note[u'contentType'] = sys.argv[i].lower() i += 1 if sys.argv[i].lower() == u'file': i += 1 note[u'value'] = readFile(sys.argv[i], encoding=GM_Globals[GM_SYS_ENCODING]) else: note[u'value'] = sys.argv[i].replace(u'\\n', u'\n') body[u'notes'] = note i += 1 else: if u'customSchemas' not in body: body[u'customSchemas'] = {} try: (schemaName, fieldName) = sys.argv[i].split(u'.') except ValueError: print u'ERROR: %s is not a valid create/update user argument or custom schema name.' % sys.argv[i] sys.exit(2) field_value = sys.argv[i+1] is_multivalue = False if field_value.lower() in [u'multivalue', u'multivalued', u'value']: is_multivalue = True i += 1 field_value = sys.argv[i+1] body[u'customSchemas'].setdefault(schemaName, {}) if is_multivalue: body[u'customSchemas'][schemaName].setdefault(fieldName, []) body[u'customSchemas'][schemaName][fieldName].append({u'value': field_value}) else: body[u'customSchemas'][schemaName][fieldName] = field_value i += 2 if need_password: body[u'password'] = u''.join(random.sample(u'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789~`!@#$%^&*()-=_+:;"\'{}[]\\|', 25)) if u'password' in body and need_to_hash_password: body[u'password'] = gen_sha512_hash(body[u'password']) body[u'hashFunction'] = u'crypt' return (body, admin_body) def doCreateUser(): cd = buildGAPIObject(u'directory') body, admin_body = getUserAttributes(3, updateCmd=False) print u"Creating account for %s" % body[u'primaryEmail'] callGAPI(cd.users(), u'insert', body=body, fields=u'primaryEmail') if admin_body: print u' Changing admin status for %s to %s' % (body[u'primaryEmail'], admin_body[u'status']) callGAPI(cd.users(), u'makeAdmin', userKey=body[u'primaryEmail'], body=admin_body) def doCreateGroup(): cd = buildGAPIObject(u'directory') body = {u'email': sys.argv[3]} if body[u'email'].find(u'@') == -1: body[u'email'] = u'%s@%s' % (body[u'email'], GC_Values[GC_DOMAIN]) got_name = False i = 4 gs_body = {} gs = None while i < len(sys.argv): if sys.argv[i].lower() == u'name': body[u'name'] = sys.argv[i+1] got_name = True i += 2 elif sys.argv[i].lower() == u'description': body[u'description'] = sys.argv[i+1] i += 2 else: value = sys.argv[i+1] if not gs: gs = buildGAPIObject(u'groupssettings') gs_object = gs._rootDesc matches_gs_setting = False for (attrib, params) in gs_object[u'schemas'][u'Groups'][u'properties'].items(): if attrib in [u'kind', u'etag', u'email', u'name', u'description']: continue if sys.argv[i].lower().replace(u'_', u'') == attrib.lower(): matches_gs_setting = True if params[u'type'] == u'integer': try: if value[-1:].upper() == u'M': value = int(value[:-1]) * 1024 * 1024 elif value[-1:].upper() == u'K': value = int(value[:-1]) * 1024 elif value[-1].upper() == u'B': value = int(value[:-1]) else: value = int(value) except ValueError: print u'ERROR: %s must be a number ending with M (megabytes), K (kilobytes) or nothing (bytes); got %s' % value sys.exit(2) elif params[u'type'] == u'string': if params[u'description'].find(value.upper()) != -1: # ugly hack because API wants some values uppercased. value = value.upper() elif value.lower() in true_values: value = u'true' elif value.lower() in false_values: value = u'false' break if not matches_gs_setting: print u'ERROR: %s is not a valid argument for "gam create group"' % sys.argv[i] sys.exit(2) gs_body[attrib] = value i += 2 if not got_name: body[u'name'] = body[u'email'] print u"Creating group %s" % body[u'email'] callGAPI(cd.groups(), u'insert', body=body, fields=u'email') if gs: callGAPI(gs.groups(), u'patch', retry_reasons=[u'serviceLimit'], groupUniqueId=body[u'email'], body=gs_body) def doCreateAlias(): cd = buildGAPIObject(u'directory') body = {u'alias': sys.argv[3]} if body[u'alias'].find(u'@') == -1: body[u'alias'] = u'%s@%s' % (body[u'alias'], GC_Values[GC_DOMAIN]) target_type = sys.argv[4].lower() if target_type not in [u'user', u'group', u'target']: print u'ERROR: type of target must be user or group; got %s' % target_type sys.exit(2) targetKey = sys.argv[5] if targetKey.find(u'@') == -1: targetKey = u'%s@%s' % (targetKey, GC_Values[GC_DOMAIN]) print u'Creating alias %s for %s %s' % (body[u'alias'], target_type, targetKey) if target_type == u'user': callGAPI(cd.users().aliases(), u'insert', userKey=targetKey, body=body) elif target_type == u'group': callGAPI(cd.groups().aliases(), u'insert', groupKey=targetKey, body=body) elif target_type == u'target': try: callGAPI(cd.users().aliases(), u'insert', throw_reasons=[u'invalid', u'badRequest'], userKey=targetKey, body=body) except googleapiclient.errors.HttpError: callGAPI(cd.groups().aliases(), u'insert', groupKey=targetKey, body=body) def doCreateOrg(): cd = buildGAPIObject(u'directory') body = {u'name': sys.argv[3]} if body[u'name'][0] == u'/': body[u'name'] = body[u'name'][1:] i = 4 body[u'parentOrgUnitPath'] = u'/' while i < len(sys.argv): if sys.argv[i].lower() == u'description': body[u'description'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'parent': body[u'parentOrgUnitPath'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'noinherit': body[u'blockInheritance'] = True i += 1 elif sys.argv[i].lower() == u'inherit': body[u'blockInheritance'] = False i += 1 else: print u'ERROR: %s is not a valid argument for "gam create org"' % sys.argv[i] sys.exit(2) callGAPI(cd.orgunits(), u'insert', customerId=GC_Values[GC_CUSTOMER_ID], body=body) def doCreateResourceCalendar(): cd = buildGAPIObject(u'directory') body = {u'resourceId': sys.argv[3], u'resourceName': sys.argv[4]} i = 5 while i < len(sys.argv): if sys.argv[i].lower() == u'description': body[u'resourceDescription'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'type': body[u'resourceType'] = sys.argv[i+1] i += 2 else: print u'ERROR: %s is not a valid argument for "gam create resource"' % sys.argv[i] sys.exit(2) print u'Creating resource %s...' % body[u'resourceId'] callGAPI(cd.resources().calendars(), u'insert', customer=GC_Values[GC_CUSTOMER_ID], body=body) def doUpdateUser(users, i): cd = buildGAPIObject(u'directory') body, admin_body = getUserAttributes(i, updateCmd=True) for user in users: if user[:4].lower() == u'uid:': user = user[4:] elif user.find(u'@') == -1: user = u'%s@%s' % (user, GC_Values[GC_DOMAIN]) if u'primaryEmail' in body and body[u'primaryEmail'][:4].lower() == u'vfe@': user_primary = callGAPI(cd.users(), u'get', userKey=user, fields=u'primaryEmail,id') user = user_primary[u'id'] user_primary = user_primary[u'primaryEmail'] user_name = user_primary[:user_primary.find(u'@')] user_domain = user_primary[user_primary.find(u'@')+1:] body[u'primaryEmail'] = u'vfe.%s.%05d@%s' % (user_name, random.randint(1, 99999), user_domain) body[u'emails'] = [{u'type': u'custom', u'customType': u'former_employee', u'primary': False, u'address': user_primary}] sys.stdout.write(u'updating user %s...\n' % user) if body: callGAPI(cd.users(), u'patch', userKey=user, body=body) if admin_body: callGAPI(cd.users(), u'makeAdmin', userKey=user, body=admin_body) def doRemoveUsersAliases(users): cd = buildGAPIObject(u'directory') for user in users: user_aliases = callGAPI(cd.users(), u'get', userKey=user, fields=u'aliases,id,primaryEmail') user_id = user_aliases[u'id'] user_primary = user_aliases[u'primaryEmail'] if u'aliases' in user_aliases: print u'%s has %s aliases' % (user_primary, len(user_aliases[u'aliases'])) for an_alias in user_aliases[u'aliases']: print u' removing alias %s for %s...' % (an_alias, user_primary) callGAPI(cd.users().aliases(), u'delete', userKey=user_id, alias=an_alias) else: print u'%s has no aliases' % user_primary def deleteUserFromGroups(users): cd = buildGAPIObject(u'directory') for user in users: user_groups = callGAPIpages(cd.groups(), u'list', u'groups', userKey=user, fields=u'groups(id,email)') num_groups = len(user_groups) print u'%s is in %s groups' % (user, num_groups) j = 0 for user_group in user_groups: j += 1 print u' removing %s from %s (%s/%s)' % (user, user_group[u'email'], j, num_groups) callGAPI(cd.members(), u'delete', soft_errors=True, groupKey=user_group[u'id'], memberKey=user) print u'' UPDATE_GROUP_SUBCMDS = [u'add', u'clear', u'delete', u'remove', u'sync', u'update'] def doUpdateGroup(): cd = buildGAPIObject(u'directory') group = sys.argv[3] myarg = sys.argv[4].lower() if myarg in UPDATE_GROUP_SUBCMDS: if group[0:3].lower() == u'uid:': group = group[4:] elif group.find(u'@') == -1: group = u'%s@%s' % (group, GC_Values[GC_DOMAIN]) checkNotSuspended = False if myarg == u'add': role = ROLE_MEMBER i = 5 if sys.argv[i].upper() in [ROLE_OWNER, ROLE_MANAGER, ROLE_MEMBER]: role = sys.argv[i].upper() i += 1 if sys.argv[i] == u'notsuspended': checkNotSuspended = True i += 1 if sys.argv[i].lower() in usergroup_types: users_email = getUsersToModify(entity_type=sys.argv[i], entity=sys.argv[i+1], checkNotSuspended=checkNotSuspended) else: users_email = [sys.argv[i],] for user_email in users_email: if user_email != u'*' and user_email.find(u'@') == -1: user_email = u'%s@%s' % (user_email, GC_Values[GC_DOMAIN]) sys.stderr.write(u' adding %s %s...\n' % (role.lower(), user_email)) try: body = {u'role': role, u'email': user_email} callGAPI(cd.members(), u'insert', soft_errors=True, groupKey=group, body=body) except googleapiclient.errors.HttpError: pass elif myarg == u'sync': role = ROLE_MEMBER i = 5 if sys.argv[i].upper() in [ROLE_OWNER, ROLE_MANAGER, ROLE_MEMBER]: role = sys.argv[i].upper() i += 1 if sys.argv[i] == u'notsuspended': checkNotSuspended = True i += 1 users_email = getUsersToModify(entity_type=sys.argv[i], entity=sys.argv[i+1], checkNotSuspended=checkNotSuspended) users_email = [x.lower() for x in users_email] current_emails = getUsersToModify(entity_type=u'group', entity=group, member_type=role) current_emails = [x.lower() for x in current_emails] to_add = list(set(users_email) - set(current_emails)) to_remove = list(set(current_emails) - set(users_email)) sys.stderr.write(u'Need to add %s %s and remove %s.\n' % (len(to_add), role, len(to_remove))) items = [] for user_email in to_add: items.append([u'update', u'group', group, u'add', role, user_email]) for user_email in to_remove: items.append([u'update', u'group', group, u'remove', user_email]) run_batch(items) elif myarg in [u'delete', u'remove']: i = 5 if sys.argv[i].lower() in [u'member', u'manager', u'owner']: i += 1 if sys.argv[i].lower() in usergroup_types: user_emails = getUsersToModify(entity_type=sys.argv[i], entity=sys.argv[i+1]) else: user_emails = [sys.argv[i],] for user_email in user_emails: if user_email[:4].lower() == u'uid:': user_email = user_email[4:] elif user_email != u'*' and user_email.find(u'@') == -1: user_email = u'%s@%s' % (user_email, GC_Values[GC_DOMAIN]) sys.stderr.write(u' removing %s\n' % user_email) callGAPI(cd.members(), u'delete', soft_errors=True, groupKey=group, memberKey=user_email) elif myarg == u'update': role = ROLE_MEMBER i = 5 if sys.argv[i].upper() in [ROLE_OWNER, ROLE_MANAGER, ROLE_MEMBER]: role = sys.argv[i].upper() i += 1 if sys.argv[i].lower() in usergroup_types: users_email = getUsersToModify(entity_type=sys.argv[i], entity=sys.argv[i+1]) else: users_email = [sys.argv[i],] body = {u'role': role} for user_email in users_email: if user_email != u'*' and user_email.find(u'@') == -1: user_email = u'%s@%s' % (user_email, GC_Values[GC_DOMAIN]) sys.stderr.write(u' updating %s %s...\n' % (role.lower(), user_email)) try: callGAPI(cd.members(), u'update', soft_errors=True, groupKey=group, memberKey=user_email, body=body) except googleapiclient.errors.HttpError: pass else: # clear roles = [] i = 5 while i < len(sys.argv): role = sys.argv[i].upper() if role in [ROLE_OWNER, ROLE_MANAGER, ROLE_MEMBER]: roles.append(role) i += 1 else: print u'ERROR: %s is not a valid argument for "gam update group clear"' % sys.argv[i] sys.exit(2) if roles: roles = u','.join(sorted(set(roles))) else: roles = ROLE_MEMBER user_emails = getUsersToModify(entity_type=u'group', entity=group, member_type=roles) for user_email in user_emails: sys.stderr.write(u' removing %s\n' % user_email) callGAPI(cd.members(), u'delete', soft_errors=True, groupKey=group, memberKey=user_email) else: i = 4 use_cd_api = False gs = None gs_body = {} cd_body = {} while i < len(sys.argv): if sys.argv[i].lower() == u'email': use_cd_api = True cd_body[u'email'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'admincreated': use_cd_api = True cd_body[u'adminCreated'] = sys.argv[i+1].lower() if cd_body[u'adminCreated'] not in [u'true', u'false']: print u'ERROR: Value for admincreated must be true or false; got %s' % cd_body[u'adminCreated'] sys.exit(2) i += 2 else: value = sys.argv[i+1] if not gs: gs = buildGAPIObject(u'groupssettings') gs_object = gs._rootDesc matches_gs_setting = False for (attrib, params) in gs_object[u'schemas'][u'Groups'][u'properties'].items(): if attrib in [u'kind', u'etag', u'email']: continue if sys.argv[i].lower().replace(u'_', u'') == attrib.lower(): matches_gs_setting = True if params[u'type'] == u'integer': try: if value[-1:].upper() == u'M': value = int(value[:-1]) * 1024 * 1024 elif value[-1:].upper() == u'K': value = int(value[:-1]) * 1024 elif value[-1].upper() == u'B': value = int(value[:-1]) else: value = int(value) except ValueError: print u'ERROR: %s must be a number ending with M (megabytes), K (kilobytes) or nothing (bytes); got %s' % value sys.exit(2) elif params[u'type'] == u'string': if params[u'description'].find(value.upper()) != -1: # ugly hack because API wants some values uppercased. value = value.upper() elif value.lower() in true_values: value = u'true' elif value.lower() in false_values: value = u'false' break if not matches_gs_setting: print u'ERROR: %s is not a valid argument for "gam update group"' % sys.argv[i] sys.exit(2) gs_body[attrib] = value i += 2 if group[:4].lower() == u'uid:': # group settings API won't take uid so we make sure cd API is used so that we can grab real email. use_cd_api = True group = group[4:] elif group.find(u'@') == -1: group = u'%s@%s' % (group, GC_Values[GC_DOMAIN]) if use_cd_api: try: if cd_body[u'email'].find(u'@') == -1: cd_body[u'email'] = u'%s@%s' % (cd_body[u'email'], GC_Values[GC_DOMAIN]) except KeyError: pass cd_result = callGAPI(cd.groups(), u'patch', groupKey=group, body=cd_body) if gs: if use_cd_api: group = cd_result[u'email'] callGAPI(gs.groups(), u'patch', retry_reasons=[u'serviceLimit'], groupUniqueId=group, body=gs_body) print u'updated group %s' % group def doUpdateAlias(): cd = buildGAPIObject(u'directory') alias = sys.argv[3] target_type = sys.argv[4].lower() if target_type not in [u'user', u'group', u'target']: print u'ERROR: target type must be one of user, group, target; got %s' % target_type sys.exit(2) target_email = sys.argv[5] if alias.find(u'@') == -1: alias = u'%s@%s' % (alias, GC_Values[GC_DOMAIN]) if target_email.find(u'@') == -1: target_email = u'%s@%s' % (target_email, GC_Values[GC_DOMAIN]) try: callGAPI(cd.users().aliases(), u'delete', throw_reasons=[u'invalid'], userKey=alias, alias=alias) except googleapiclient.errors.HttpError: callGAPI(cd.groups().aliases(), u'delete', groupKey=alias, alias=alias) if target_type == u'user': callGAPI(cd.users().aliases(), u'insert', userKey=target_email, body={u'alias': alias}) elif target_type == u'group': callGAPI(cd.groups().aliases(), u'insert', groupKey=target_email, body={u'alias': alias}) elif target_type == u'target': try: callGAPI(cd.users().aliases(), u'insert', throw_reasons=[u'invalid'], userKey=target_email, body={u'alias': alias}) except googleapiclient.errors.HttpError: callGAPI(cd.groups().aliases(), u'insert', groupKey=target_email, body={u'alias': alias}) print u'updated alias %s' % alias def doUpdateResourceCalendar(): cd = buildGAPIObject(u'directory') resId = sys.argv[3] body = {} i = 4 while i < len(sys.argv): if sys.argv[i].lower() == u'name': body[u'resourceName'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'description': body[u'resourceDescription'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'type': body[u'resourceType'] = sys.argv[i+1] i += 2 else: print u'ERROR: %s is not a valid argument for "gam update resource"' % sys.argv[i] sys.exit(2) # Use patch since it seems to work better. # update requires name to be set. callGAPI(cd.resources().calendars(), u'patch', customer=GC_Values[GC_CUSTOMER_ID], calendarResourceId=resId, body=body, fields=u'') print u'updated resource %s' % resId def doUpdateCros(): cd = buildGAPIObject(u'directory') deviceId = sys.argv[3] if deviceId[:6].lower() == u'query:': query = deviceId[6:] devices_result = callGAPIpages(cd.chromeosdevices(), u'list', u'chromeosdevices', query=query, customerId=GC_Values[GC_CUSTOMER_ID], fields=u'chromeosdevices/deviceId,nextPageToken') devices = list() for a_device in devices_result: devices.append(a_device[u'deviceId']) else: devices = [deviceId,] i = 4 body = {} update_device = True action_device = False ack_wipe = False while i < len(sys.argv): if sys.argv[i].lower() == u'user': body[u'annotatedUser'] = sys.argv[i + 1] i += 2 elif sys.argv[i].lower() == u'location': body[u'annotatedLocation'] = sys.argv[i + 1] i += 2 elif sys.argv[i].lower() == u'notes': body[u'notes'] = sys.argv[i + 1] i += 2 elif sys.argv[i].lower() == u'action': update_device = False action_device = True action = sys.argv[i+1].replace(u'_', u'').replace(u'-', u'').lower() deprovisionReason = None if action in [u'deprovisionsamemodelreplace', u'deprovisionsamemodelreplacement']: action = u'deprovision' deprovisionReason = u'same_model_replacement' elif action in [u'deprovisiondifferentmodelreplace', u'deprovisiondifferentmodelreplacement']: action = u'deprovision' deprovisionReason = u'different_model_replacement' elif action in [u'deprovisionretiringdevice']: action = u'deprovision' deprovisionReason = u'retiring_device' elif action not in [u'disable', u'reenable']: print u'ERROR: expected action of deprovision_same_model_replace, deprovision_different_model_replace, deprovision_retiring_device, disable or reenable, got %s' % action sys.exit(3) body = {u'action': action} if deprovisionReason: body[u'deprovisionReason'] = deprovisionReason i += 2 elif sys.argv[i].replace(u'_', u'').lower() in [u'acknowledgedevicetouchrequirement']: ack_wipe = True i += 1 elif sys.argv[i].lower() in [u'tag', u'asset', u'assetid']: body[u'annotatedAssetId'] = sys.argv[i + 1] i += 2 elif sys.argv[i].lower() in [u'ou', u'org']: body[u'orgUnitPath'] = sys.argv[i + 1] if body[u'orgUnitPath'][0] != u'/': body[u'orgUnitPath'] = u'/%s' % body[u'orgUnitPath'] i += 2 else: print u'ERROR: %s is not a valid argument for "gam update cros"' % sys.argv[i] sys.exit(2) i = 1 device_count = len(devices) for this_device in devices: if update_device: print u' updating %s (%s of %s)' % (this_device, i, device_count) callGAPI(service=cd.chromeosdevices(), function=u'patch', deviceId=this_device, body=body, customerId=GC_Values[GC_CUSTOMER_ID]) elif action_device: if body[u'action'] == u'deprovision' and not ack_wipe: print u'WARNING: Refusing to deprovision %s because acknowledge_device_touch_requirement not specified. Deprovisioning a device means the device will have to be physically wiped and re-enrolled to be managed by your domain again. This requires physical access to the device and is very time consuming to perform for each device. Please add "acknowledge_device_touch_requirement" to the GAM command if you understand this and wish to proceed with the deprovision. Please also be aware that deprovisioning can have an effect on your device license count. See https://support.google.com/chrome/a/answer/3523633 for full details.' % (this_device) sys.exit(3) print u' performing action %s for %s (%s of %s)' % (action, this_device, i, device_count) callGAPI(cd.chromeosdevices(), function=u'action', customerId=GC_Values[GC_CUSTOMER_ID], resourceId=this_device, body=body) i += 1 def doUpdateMobile(): cd = buildGAPIObject(u'directory') resourceId = sys.argv[3] i = 4 action_body = {} patch_body = {} doPatch = doAction = False while i < len(sys.argv): if sys.argv[i].lower() == u'action': action_body[u'action'] = sys.argv[i+1].lower() if action_body[u'action'] == u'wipe': action_body[u'action'] = u'admin_remote_wipe' elif action_body[u'action'].replace(u'_', u'') in [u'accountwipe', u'wipeaccount']: action_body[u'action'] = u'admin_account_wipe' if action_body[u'action'] not in [u'admin_remote_wipe', u'admin_account_wipe', u'approve', u'block', u'cancel_remote_wipe_then_activate', u'cancel_remote_wipe_then_block']: print u'ERROR: action must be one of wipe, wipeaccount, approve, block, cancel_remote_wipe_then_activate, cancel_remote_wipe_then_block; got %s' % action_body[u'action'] sys.exit(2) doAction = True i += 2 elif sys.argv[i].lower() == u'model': patch_body[u'model'] = sys.argv[i+1] i += 2 doPatch = True elif sys.argv[i].lower() == u'os': patch_body[u'os'] = sys.argv[i+1] i += 2 doPatch = True elif sys.argv[i].lower() == u'useragent': patch_body[u'userAgent'] = sys.argv[i+1] i += 2 doPatch = True else: print u'ERROR: %s is not a valid argument for "gam update mobile"' % sys.argv[i] sys.exit(2) if doPatch: callGAPI(cd.mobiledevices(), u'patch', resourceId=resourceId, body=patch_body, customerId=GC_Values[GC_CUSTOMER_ID]) if doAction: callGAPI(cd.mobiledevices(), u'action', resourceId=resourceId, body=action_body, customerId=GC_Values[GC_CUSTOMER_ID]) def doDeleteMobile(): cd = buildGAPIObject(u'directory') resourceId = sys.argv[3] callGAPI(cd.mobiledevices(), u'delete', resourceId=resourceId, customerId=GC_Values[GC_CUSTOMER_ID]) def doUpdateOrg(): cd = buildGAPIObject(u'directory') orgUnitPath = sys.argv[3] if sys.argv[4].lower() in [u'move', u'add']: if sys.argv[5].lower() in usergroup_types: users = getUsersToModify(entity_type=sys.argv[5].lower(), entity=sys.argv[6]) else: users = getUsersToModify(entity_type=u'user', entity=sys.argv[5]) if (sys.argv[5].lower() == u'cros') or ((sys.argv[5].lower() == u'all') and (sys.argv[6].lower() == u'cros')): current_cros = 0 cros_count = len(users) for cros in users: current_cros += 1 sys.stderr.write(u' moving %s to %s (%s/%s)\n' % (cros, orgUnitPath, current_cros, cros_count)) callGAPI(cd.chromeosdevices(), u'patch', soft_errors=True, customerId=GC_Values[GC_CUSTOMER_ID], deviceId=cros, body={u'orgUnitPath': u'//%s' % orgUnitPath}) else: if orgUnitPath != u'/' and orgUnitPath[0] != u'/': # we do want a / at the beginning for user updates orgUnitPath = u'/%s' % orgUnitPath current_user = 0 user_count = len(users) for user in users: current_user += 1 sys.stderr.write(u' moving %s to %s (%s/%s)\n' % (user, orgUnitPath, current_user, user_count)) try: callGAPI(cd.users(), u'patch', throw_reasons=[u'conditionNotMet'], userKey=user, body={u'orgUnitPath': orgUnitPath}) except googleapiclient.errors.HttpError: pass else: body = {} i = 4 while i < len(sys.argv): if sys.argv[i].lower() == u'name': body[u'name'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'description': body[u'description'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'parent': body[u'parentOrgUnitPath'] = sys.argv[i+1] if body[u'parentOrgUnitPath'][0] != u'/': body[u'parentOrgUnitPath'] = u'/'+body[u'parentOrgUnitPath'] i += 2 elif sys.argv[i].lower() == u'noinherit': body[u'blockInheritance'] = True i += 1 elif sys.argv[i].lower() == u'inherit': body[u'blockInheritance'] = False i += 1 else: print u'ERROR: %s is not a valid argument for "gam update org"' % sys.argv[i] sys.exit(2) if orgUnitPath[0] == u'/': # we don't want a / at the beginning for OU updates orgUnitPath = orgUnitPath[1:] callGAPI(cd.orgunits(), u'update', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=orgUnitPath, body=body) def doWhatIs(): cd = buildGAPIObject(u'directory') email = sys.argv[2] if email.find(u'@') == -1: email = u'%s@%s' % (email, GC_Values[GC_DOMAIN]) try: user_or_alias = callGAPI(cd.users(), u'get', throw_reasons=[u'notFound', u'badRequest', u'invalid'], userKey=email, fields=u'primaryEmail') if user_or_alias[u'primaryEmail'].lower() == email.lower(): sys.stderr.write(u'%s is a user\n\n' % email) doGetUserInfo(user_email=email) return else: sys.stderr.write(u'%s is a user alias\n\n' % email) doGetAliasInfo(alias_email=email) return except googleapiclient.errors.HttpError: sys.stderr.write(u'%s is not a user...\n' % email) sys.stderr.write(u'%s is not a user alias...\n' % email) try: group = callGAPI(cd.groups(), u'get', throw_reasons=[u'notFound', u'badRequest'], groupKey=email, fields=u'email') except googleapiclient.errors.HttpError: sys.stderr.write(u'%s is not a group either!\n\nDoesn\'t seem to exist!\n\n' % email) sys.exit(1) if group[u'email'].lower() == email.lower(): sys.stderr.write(u'%s is a group\n\n' % email) doGetGroupInfo(group_name=email) else: sys.stderr.write(u'%s is a group alias\n\n' % email) doGetAliasInfo(alias_email=email) def doGetUserInfo(user_email=None): cd = buildGAPIObject(u'directory') i = 3 if user_email == None: if len(sys.argv) > 3: user_email = sys.argv[3] i = 4 else: storage = oauth2client.file.Storage(GC_Values[GC_OAUTH2_TXT]) credentials = storage.get() if credentials is None or credentials.invalid: doRequestOAuth() credentials = storage.get() user_email = credentials.id_token[u'email'] if user_email[:4].lower() == u'uid:': user_email = user_email[4:] elif user_email.find(u'@') == -1: user_email = u'%s@%s' % (user_email, GC_Values[GC_DOMAIN]) getSchemas = getAliases = getGroups = getLicenses = True projection = u'full' customFieldMask = viewType = None skus = [u'Google-Apps-For-Business', u'Google-Apps-Unlimited', u'Google-Apps-For-Postini', u'Google-Apps-Lite', u'Google-Vault', u'Google-Vault-Former-Employee'] while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg == u'noaliases': getAliases = False i += 1 elif myarg == u'nogroups': getGroups = False i += 1 elif myarg in [u'nolicenses', u'nolicences']: getLicenses = False i += 1 elif myarg in [u'sku', u'skus']: skus = sys.argv[i+1].replace(u',', u' ').split() i += 2 elif myarg == u'noschemas': getSchemas = False projection = u'basic' i += 1 elif myarg == u'schemas': getSchemas = True projection = u'custom' customFieldMask = sys.argv[i+1] i += 2 elif myarg == u'userview': viewType = u'domain_public' getGroups = getLicenses = False i += 1 elif myarg in [u'nousers', u'groups']: i += 1 else: print u'ERROR: %s is not a valid argument for "gam info user"' % myarg sys.exit(2) user = callGAPI(cd.users(), u'get', userKey=user_email, projection=projection, customFieldMask=customFieldMask, viewType=viewType) print u'User: %s' % user[u'primaryEmail'] if u'name' in user and u'givenName' in user[u'name']: print convertUTF8(u'First Name: %s' % user[u'name'][u'givenName']) if u'name' in user and u'familyName' in user[u'name']: print convertUTF8(u'Last Name: %s' % user[u'name'][u'familyName']) if u'isAdmin' in user: print u'Is a Super Admin: %s' % user[u'isAdmin'] if u'isDelegatedAdmin' in user: print u'Is Delegated Admin: %s' % user[u'isDelegatedAdmin'] if u'agreedToTerms' in user: print u'Has Agreed to Terms: %s' % user[u'agreedToTerms'] if u'ipWhitelisted' in user: print u'IP Whitelisted: %s' % user[u'ipWhitelisted'] if u'suspended' in user: print u'Account Suspended: %s' % user[u'suspended'] if u'suspensionReason' in user: print u'Suspension Reason: %s' % user[u'suspensionReason'] if u'changePasswordAtNextLogin' in user: print u'Must Change Password: %s' % user[u'changePasswordAtNextLogin'] if u'id' in user: print u'Google Unique ID: %s' % user[u'id'] if u'customerId' in user: print u'Customer ID: %s' % user[u'customerId'] if u'isMailboxSetup' in user: print u'Mailbox is setup: %s' % user[u'isMailboxSetup'] if u'includeInGlobalAddressList' in user: print u'Included in GAL: %s' % user[u'includeInGlobalAddressList'] if u'creationTime' in user: print u'Creation Time: %s' % user[u'creationTime'] if u'lastLoginTime' in user: if user[u'lastLoginTime'] == NEVER_TIME: print u'Last login time: Never' else: print u'Last login time: %s' % user[u'lastLoginTime'] if u'orgUnitPath' in user: print u'Google Org Unit Path: %s\n' % user[u'orgUnitPath'] if u'thumbnailPhotoUrl' in user: print u'Photo URL: %s\n' % user[u'thumbnailPhotoUrl'] if u'notes' in user: print u'Notes:' notes = user[u'notes'] if isinstance(notes, dict): contentType = notes.get(u'contentType', u'text_plain') print u' %s: %s' % (u'contentType', contentType) if contentType == u'text_html': print convertUTF8(indentMultiLineText(u' value: {0}'.format(dehtml(notes[u'value'])), n=2)) else: print convertUTF8(indentMultiLineText(u' value: {0}'.format(notes[u'value']), n=2)) else: print convertUTF8(indentMultiLineText(u' value: {0}'.format(notes), n=2)) print u'' if u'ims' in user: print u'IMs:' for im in user[u'ims']: for key in im: print convertUTF8(u' %s: %s' % (key, im[key])) print u'' if u'addresses' in user: print u'Addresses:' for address in user[u'addresses']: for key in address: print convertUTF8(u' %s: %s' % (key, address[key])) print u'' if u'organizations' in user: print u'Organizations:' for org in user[u'organizations']: for key in org: if key == u'customType' and not org[key]: continue print convertUTF8(u' %s: %s' % (key, org[key])) print u'' if u'phones' in user: print u'Phones:' for phone in user[u'phones']: for key in phone: print convertUTF8(u' %s: %s' % (key, phone[key])) print u'' if u'emails' in user: if len(user[u'emails']) > 1: print u'Other Emails:' for an_email in user[u'emails']: if an_email[u'address'].lower() == user[u'primaryEmail'].lower(): continue for key in an_email: if key == u'type' and an_email[key] == u'custom': continue if key == u'customType': print convertUTF8(u' type: %s' % an_email[key]) else: print convertUTF8(u' %s: %s' % (key, an_email[key])) print u'' if u'relations' in user: print u'Relations:' for relation in user[u'relations']: for key in relation: if key == u'type' and relation[key] == u'custom': continue elif key == u'customType': print convertUTF8(u' %s: %s' % (u'type', relation[key])) else: print convertUTF8(u' %s: %s' % (key, relation[key])) print u'' if u'externalIds' in user: print u'External IDs:' for externalId in user[u'externalIds']: for key in externalId: if key == u'type' and externalId[key] == u'custom': continue elif key == u'customType': print convertUTF8(u' %s: %s' % (u'type', externalId[key])) else: print convertUTF8(u' %s: %s' % (key, externalId[key])) print u'' if u'websites' in user: print u'Websites:' for website in user[u'websites']: for key in website: if key == u'type' and website[key] == u'custom': continue elif key == u'customType': print convertUTF8(u' %s: %s' % (u'type', website[key])) else: print convertUTF8(u' %s: %s' % (key, website[key])) print u'' if getSchemas: if u'customSchemas' in user: print u'Custom Schemas:' for schema in user[u'customSchemas']: print u' Schema: %s' % schema for field in user[u'customSchemas'][schema]: if isinstance(user[u'customSchemas'][schema][field], list): print u' %s:' % field for an_item in user[u'customSchemas'][schema][field]: print convertUTF8(u' %s' % an_item[u'value']) else: print convertUTF8(u' %s: %s' % (field, user[u'customSchemas'][schema][field])) print if getAliases: if u'aliases' in user: print u'Email Aliases:' for alias in user[u'aliases']: print u' %s' % alias if u'nonEditableAliases' in user: print u'Non-Editable Aliases:' for alias in user[u'nonEditableAliases']: print u' %s' % alias if getGroups: groups = callGAPIpages(cd.groups(), u'list', u'groups', userKey=user_email, fields=u'groups(name,email),nextPageToken') if len(groups) > 0: print u'Groups: (%s)' % len(groups) for group in groups: print u' %s <%s>' % (group[u'name'], group[u'email']) if getLicenses: print u'Licenses:' lic = buildGAPIObject(u'licensing') for sku in skus: productId, skuId = getProductAndSKU(sku) try: result = callGAPI(lic.licenseAssignments(), u'get', throw_reasons=[u'notFound', u'invalid', u'forbidden'], userId=user_email, productId=productId, skuId=skuId) except googleapiclient.errors.HttpError: continue print u' %s' % result[u'skuId'] def doGetGroupInfo(group_name=None): cd = buildGAPIObject(u'directory') gs = buildGAPIObject(u'groupssettings') getAliases = getUsers = True getGroups = False if group_name == None: group_name = sys.argv[3] i = 4 else: i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg == u'nousers': getUsers = False i += 1 elif myarg == u'noaliases': getAliases = False i += 1 elif myarg == u'groups': getGroups = True i += 1 elif myarg in [u'nogroups', u'nolicenses', u'nolicences', u'noschemas', u'schemas', u'userview']: i += 1 if myarg == u'schemas': i += 1 else: print u'ERROR: %s is not a valid argument for "gam info group"' % myarg sys.exit(2) if group_name[:4].lower() == u'uid:': group_name = group_name[4:] elif group_name.find(u'@') == -1: group_name = group_name+u'@'+GC_Values[GC_DOMAIN] basic_info = callGAPI(cd.groups(), u'get', groupKey=group_name) try: settings = callGAPI(gs.groups(), u'get', retry_reasons=[u'serviceLimit'], throw_reasons=u'authError', groupUniqueId=basic_info[u'email']) # Use email address retrieved from cd since GS API doesn't support uid except googleapiclient.errors.HttpError: pass print u'' print u'Group Settings:' for key, value in basic_info.items(): if (key in [u'kind', u'etag']) or ((key == u'aliases') and (not getAliases)): continue if isinstance(value, list): print u' %s:' % key for val in value: print u' %s' % val else: print convertUTF8(u' %s: %s' % (key, value)) try: for key, value in settings.items(): if key in [u'kind', u'etag', u'description', u'email', u'name']: continue elif key == u'maxMessageBytes': if value > 1024*1024: value = u'%sM' % (value / 1024 / 1024) elif value > 1024: value = u'%sK' % (value / 1024) print u' %s: %s' % (key, value) except UnboundLocalError: pass if getGroups: groups = callGAPIpages(cd.groups(), u'list', u'groups', userKey=basic_info[u'email'], fields=u'nextPageToken,groups(name,email)') if groups: print u'Groups: ({0})'.format(len(groups)) for groupm in groups: print u' %s: %s' % (groupm[u'name'], groupm[u'email']) if getUsers: members = callGAPIpages(cd.members(), u'list', u'members', groupKey=group_name) print u'Members:' for member in members: try: print u' %s: %s (%s)' % (member[u'role'].lower(), member[u'email'], member[u'type'].lower()) except KeyError: try: print u' member: %s (%s)' % (member[u'email'], member[u'type'].lower()) except KeyError: print u' member: %s (%s)' % (member[u'id'], member[u'type'].lower()) print u'Total %s users in group' % len(members) def doGetAliasInfo(alias_email=None): cd = buildGAPIObject(u'directory') if alias_email == None: alias_email = sys.argv[3] if alias_email.find(u'@') == -1: alias_email = u'%s@%s' % (alias_email, GC_Values[GC_DOMAIN]) try: result = callGAPI(cd.users(), u'get', throw_reasons=[u'invalid', u'badRequest'], userKey=alias_email) except googleapiclient.errors.HttpError: result = callGAPI(cd.groups(), u'get', groupKey=alias_email) print u' Alias Email: %s' % alias_email try: if result[u'primaryEmail'].lower() == alias_email.lower(): print u'Error: %s is a primary user email address, not an alias.' % alias_email sys.exit(3) print u' User Email: %s' % result[u'primaryEmail'] except KeyError: print u' Group Email: %s' % result[u'email'] print u' Unique ID: %s' % result[u'id'] def doGetResourceCalendarInfo(): cd = buildGAPIObject(u'directory') resId = sys.argv[3] resource = callGAPI(cd.resources().calendars(), u'get', customer=GC_Values[GC_CUSTOMER_ID], calendarResourceId=resId) for key, value in resource.items(): if key in [u'kind', u'etag', u'etags']: continue print u'%s: %s' % (key, value) CROS_ARGUMENT_TO_PROPERTY_MAP = { u'activetimeranges': [u'activeTimeRanges.activeTime', u'activeTimeRanges.date'], u'annotatedassetid': [u'annotatedAssetId',], u'annotatedlocation': [u'annotatedLocation',], u'annotateduser': [u'annotatedUser',], u'asset': [u'annotatedAssetId',], u'assetid': [u'annotatedAssetId',], u'bootmode': [u'bootMode',], u'deviceid': [u'deviceId',], u'ethernetmacaddress': [u'ethernetMacAddress',], u'firmwareversion': [u'firmwareVersion',], u'lastenrollmenttime': [u'lastEnrollmentTime',], u'lastsync': [u'lastSync',], u'location': [u'annotatedLocation',], u'macaddress': [u'macAddress',], u'meid': [u'meid',], u'model': [u'model',], u'notes': [u'notes',], u'ordernumber': [u'orderNumber',], u'org': [u'orgUnitPath',], u'orgunitpath': [u'orgUnitPath',], u'osversion': [u'osVersion',], u'ou': [u'orgUnitPath',], u'platformversion': [u'platformVersion',], u'recentusers': [u'recentUsers.email', u'recentUsers.type'], u'serialnumber': [u'serialNumber',], u'status': [u'status',], u'supportenddate': [u'supportEndDate',], u'tag': [u'annotatedAssetId',], u'timeranges': [u'activeTimeRanges.activeTime', u'activeTimeRanges.date'], u'user': [u'annotatedUser',], u'willautorenew': [u'willAutoRenew',], } CROS_BASIC_FIELDS_LIST = [u'deviceId', u'annotatedAssetId', u'annotatedLocation', u'annotatedUser', u'lastSync', u'notes', u'serialNumber', u'status'] CROS_SCALAR_PROPERTY_PRINT_ORDER = [ u'orgUnitPath', u'annotatedAssetId', u'annotatedLocation', u'annotatedUser', u'lastSync', u'notes', u'serialNumber', u'status', u'model', u'firmwareVersion', u'platformVersion', u'osVersion', u'bootMode', u'meid', u'ethernetMacAddress', u'macAddress', u'lastEnrollmentTime', u'orderNumber', u'supportEndDate', u'willAutoRenew', ] def doGetCrosInfo(): cd = buildGAPIObject(u'directory') deviceId = sys.argv[3] projection = None fieldsList = [] noLists = False listLimit = 0 i = 4 while i < len(sys.argv): myarg = sys.argv[i].lower().replace(u'_', u'') if myarg == u'nolists': noLists = True i += 1 elif myarg == u'listlimit': listLimit = int(sys.argv[i+1]) i += 2 elif myarg == u'allfields': projection = u'FULL' fieldsList = [] i += 1 elif myarg in PROJECTION_CHOICES_MAP: projection = PROJECTION_CHOICES_MAP[myarg] if projection == u'FULL': fieldsList = [] else: fieldsList = CROS_BASIC_FIELDS_LIST[:] i += 1 elif myarg in CROS_ARGUMENT_TO_PROPERTY_MAP: if not fieldsList: fieldsList = [u'deviceId',] fieldsList.extend(CROS_ARGUMENT_TO_PROPERTY_MAP[myarg]) i += 1 elif myarg == u'fields': if not fieldsList: fieldsList = [u'deviceId',] fieldNameList = sys.argv[i+1] for field in fieldNameList.lower().replace(u',', u' ').split(): if field in CROS_ARGUMENT_TO_PROPERTY_MAP: fieldsList.extend(CROS_ARGUMENT_TO_PROPERTY_MAP[field]) if field in [u'recentusers', u'timeranges', u'activetimeranges']: projection = u'FULL' noLists = False else: print u'ERROR: %s is not a valid argument for "gam info cros fields"' % field sys.exit(2) i += 2 else: print u'ERROR: %s is not a valid argument for "gam info cros"' % sys.argv[i] sys.exit(2) if fieldsList: fields = u','.join(set(fieldsList)).replace(u'.', u'/') else: fields = None cros = callGAPI(cd.chromeosdevices(), u'get', customerId=GC_Values[GC_CUSTOMER_ID], deviceId=deviceId, projection=projection, fields=fields) print u'CrOS Device: {0}'.format(deviceId) for up in CROS_SCALAR_PROPERTY_PRINT_ORDER: if up in cros: print u' {0}: {1}'.format(up, cros[up]) if not noLists: activeTimeRanges = cros.get(u'activeTimeRanges', []) lenATR = len(activeTimeRanges) if lenATR: print u' activeTimeRanges' for i in xrange(min(listLimit, lenATR) if listLimit else lenATR): print u' date: {0}'.format(activeTimeRanges[i][u'date']) print u' activeTime: {0}'.format(str(activeTimeRanges[i][u'activeTime'])) print u' duration: {0}'.format(formatMilliSeconds(activeTimeRanges[i][u'activeTime'])) recentUsers = cros.get(u'recentUsers', []) lenRU = len(recentUsers) if lenRU: print u' recentUsers' for i in xrange(min(listLimit, lenRU) if listLimit else lenRU): print u' type: {0}'.format(recentUsers[i][u'type']) print u' email: {0}'.format(recentUsers[i].get(u'email', u'')) def doGetMobileInfo(): cd = buildGAPIObject(u'directory') deviceId = sys.argv[3] info = callGAPI(cd.mobiledevices(), u'get', customerId=GC_Values[GC_CUSTOMER_ID], resourceId=deviceId) print_json(None, info) def print_json(object_name, object_value, spacing=u''): if object_name in [u'kind', u'etag', u'etags']: return if object_name != 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: sys.stdout.write(u'\n') for a_value in object_value: if isinstance(a_value, (str, unicode, int, bool)): sys.stdout.write(convertUTF8(u' %s%s\n' % (spacing, a_value))) else: print_json(None, a_value, u' %s' % spacing) elif isinstance(object_value, dict): print if object_name != None: sys.stdout.write(u'\n') for another_object in object_value: print_json(another_object, object_value[another_object], u' %s' % spacing) else: sys.stdout.write(convertUTF8(u'%s\n' % (object_value))) def doUpdateNotification(): cd = buildGAPIObject(u'directory') ids = list() get_all = False i = 3 isUnread = None while i < len(sys.argv): if sys.argv[i].lower() == u'unread': isUnread = True mark_as = u'unread' i += 1 elif sys.argv[i].lower() == u'read': isUnread = False mark_as = u'read' i += 1 elif sys.argv[i].lower() == u'id': if sys.argv[i+1].lower() == u'all': get_all = True else: ids.append(sys.argv[i+1]) i += 2 else: print u'ERROR: %s is not a valid argument for "gam update notification"' % sys.argv[i] sys.exit(2) if isUnread == None: print u'ERROR: notifications need to be marked as read or unread.' sys.exit(2) if get_all: notifications = callGAPIpages(cd.notifications(), u'list', u'items', customer=GC_Values[GC_CUSTOMER_ID], fields=u'items(notificationId,isUnread),nextPageToken') for noti in notifications: if noti[u'isUnread'] != isUnread: ids.append(noti[u'notificationId']) print u'Marking %s notification(s) as %s...' % (len(ids), mark_as) for notificationId in ids: result = callGAPI(cd.notifications(), u'patch', customer=GC_Values[GC_CUSTOMER_ID], notificationId=notificationId, body={u'isUnread': isUnread}, fields=u'notificationId,isUnread') if result[u'isUnread']: read_result = u'unread' else: read_result = u'read' print u'marked %s as %s' % (result[u'notificationId'], read_result) def doDeleteNotification(): cd = buildGAPIObject(u'directory') ids = list() get_all = False i = 3 while i < len(sys.argv): if sys.argv[i].lower() == u'id': if sys.argv[i+1].lower() == u'all': get_all = True else: ids.append(sys.argv[i+1]) i += 2 else: print u'ERROR: %s is not a valid argument for "gam delete notification", expected id' % sys.argv[i] sys.exit(2) if get_all: notifications = callGAPIpages(cd.notifications(), u'list', u'items', customer=GC_Values[GC_CUSTOMER_ID], fields=u'items(notificationId),nextPageToken') for noti in notifications: ids.append(noti[u'notificationId']) print u'Deleting %s notification(s)...' % len(ids) for notificationId in ids: callGAPI(cd.notifications(), u'delete', customer=GC_Values[GC_CUSTOMER_ID], notificationId=notificationId) print u'deleted %s' % id def doSiteVerifyShow(): verif = buildGAPIObject(u'siteVerification') a_domain = sys.argv[3] txt_record = callGAPI(verif.webResource(), u'getToken', body={u'site':{u'type':u'INET_DOMAIN', u'identifier':a_domain}, u'verificationMethod':u'DNS_TXT'}) print u'TXT Record Name: %s' % a_domain print u'TXT Record Value: %s' % txt_record[u'token'] print cname_record = callGAPI(verif.webResource(), u'getToken', body={u'site':{u'type':u'INET_DOMAIN', u'identifier':a_domain}, u'verificationMethod':u'DNS_CNAME'}) cname_token = cname_record[u'token'] cname_list = cname_token.split(u' ') cname_subdomain = cname_list[0] cname_value = cname_list[1] print u'CNAME Record Name: %s.%s' % (cname_subdomain, a_domain) print u'CNAME Record Value: %s' % cname_value print u'' webserver_file_record = callGAPI(verif.webResource(), u'getToken', body={u'site':{u'type':u'SITE', u'identifier':u'http://%s/' % a_domain}, u'verificationMethod':u'FILE'}) webserver_file_token = webserver_file_record[u'token'] print u'Saving web server verification file to: %s' % webserver_file_token writeFile(webserver_file_token, u'google-site-verification: {0}'.format(webserver_file_token), continueOnError=True) print u'Verification File URL: http://%s/%s' % (a_domain, webserver_file_token) print webserver_meta_record = callGAPI(verif.webResource(), u'getToken', body={u'site':{u'type':u'SITE', u'identifier':u'http://%s/' % a_domain}, u'verificationMethod':u'META'}) print u'Meta URL: http://%s/' % a_domain print u'Meta HTML Header Data: %s' % webserver_meta_record[u'token'] print def doGetSiteVerifications(): verif = buildGAPIObject(u'siteVerification') sites = callGAPIitems(verif.webResource(), u'list', u'items') if len(sites) > 0: for site in sites: print u'Site: %s' % site[u'site'][u'identifier'] print u'Type: %s' % site[u'site'][u'type'] print u'Owners:' for owner in site[u'owners']: print u' %s' % owner print else: print u'No Sites Verified.' def doSiteVerifyAttempt(): verif = buildGAPIObject(u'siteVerification') a_domain = sys.argv[3] verificationMethod = sys.argv[4].upper() if verificationMethod == u'CNAME': verificationMethod = u'DNS_CNAME' elif verificationMethod in [u'TXT', u'TEXT']: verificationMethod = u'DNS_TXT' if verificationMethod in [u'DNS_TXT', u'DNS_CNAME']: verify_type = u'INET_DOMAIN' identifier = a_domain else: verify_type = u'SITE' identifier = u'http://%s/' % a_domain body = {u'site':{u'type':verify_type, u'identifier':identifier}, u'verificationMethod':verificationMethod} try: verify_result = callGAPI(verif.webResource(), u'insert', throw_reasons=[u'badRequest'], verificationMethod=verificationMethod, body=body) except googleapiclient.errors.HttpError as e: error = json.loads(e.content) message = error[u'error'][u'errors'][0][u'message'] print u'ERROR: %s' % message verify_data = callGAPI(verif.webResource(), u'getToken', body=body) print u'Method: %s' % verify_data[u'method'] print u'Token: %s' % verify_data[u'token'] if verify_data[u'method'] == u'DNS_CNAME': try: import dns.resolver resolver = dns.resolver.Resolver() resolver.nameservers = [u'8.8.8.8', u'8.8.4.4'] cname_token = verify_data[u'token'] cname_list = cname_token.split(u' ') cname_subdomain = cname_list[0] try: answers = resolver.query(u'%s.%s' % (cname_subdomain, a_domain), u'A') for answer in answers: print u'DNS Record: %s' % answer except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): print u'ERROR: No such domain found in DNS!' except ImportError: pass elif verify_data[u'method'] == u'DNS_TXT': try: import dns.resolver resolver = dns.resolver.Resolver() resolver.nameservers = [u'8.8.8.8', u'8.8.4.4'] try: answers = resolver.query(a_domain, u'TXT') for answer in answers: print u'DNS Record: %s' % str(answer).replace(u'"', u'') except dns.resolver.NXDOMAIN: print u'ERROR: no such domain found in DNS!' except ImportError: pass return print u'SUCCESS!' print u'Verified: %s' % verify_result[u'site'][u'identifier'] print u'ID: %s' % verify_result[u'id'] print u'Type: %s' % verify_result[u'site'][u'type'] print u'All Owners:' try: for owner in verify_result[u'owners']: print u' %s' % owner except KeyError: pass print print u'You can now add %s or it\'s subdomains as secondary or domain aliases of the %s Google Apps Account.' % (a_domain, GC_Values[GC_DOMAIN]) def doGetNotifications(): cd = buildGAPIObject(u'directory') i = 3 unread_only = False while i < len(sys.argv): if sys.argv[i].lower() == u'unreadonly': unread_only = True else: print u'ERROR: %s is not a valid argument for "gam info notification", expected unreadonly' % sys.argv[i] sys.exit(2) i += 1 notifications = callGAPIpages(cd.notifications(), u'list', u'items', customer=GC_Values[GC_CUSTOMER_ID]) for notification in notifications: if unread_only and not notification[u'isUnread']: continue print u'From: %s' % notification[u'fromAddress'] print u'Subject: %s' % notification[u'subject'] print u'Date: %s' % notification[u'sendTime'] print u'ID: %s' % notification[u'notificationId'] print u'Read Status: %s' % ([u'READ', u'UNREAD'][notification[u'isUnread']]) print u'' print convertUTF8(dehtml(notification[u'body'])) print u'' print u'--------------' print u'' def doGetOrgInfo(): cd = buildGAPIObject(u'directory') name = sys.argv[3] get_users = True show_children = False i = 4 while i < len(sys.argv): if sys.argv[i].lower() == u'nousers': get_users = False i += 1 elif sys.argv[i].lower() in [u'children', u'child']: show_children = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam info org"' % sys.argv[i] sys.exit(2) if name == u'/': orgs = callGAPI(cd.orgunits(), u'list', customerId=GC_Values[GC_CUSTOMER_ID], type=u'children', fields=u'organizationUnits/parentOrgUnitId') name = orgs[u'organizationUnits'][0][u'parentOrgUnitId'] if len(name) > 1 and name[0] == u'/': name = name[1:] result = callGAPI(cd.orgunits(), u'get', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=name) print_json(None, result) if get_users: name = result[u'orgUnitPath'] print u'Users: ' page_message = u'Got %%total_items%% users: %%first_item%% - %%last_item%%\n' users = callGAPIpages(cd.users(), u'list', u'users', page_message=page_message, message_attribute=u'primaryEmail', customer=GC_Values[GC_CUSTOMER_ID], query=u"orgUnitPath='%s'" % name, fields=u'users(primaryEmail,orgUnitPath),nextPageToken', maxResults=GC_Values[GC_USER_MAX_RESULTS]) for user in users: if show_children or (name.lower() == user[u'orgUnitPath'].lower()): sys.stdout.write(u' %s' % user[u'primaryEmail']) if name.lower() != user[u'orgUnitPath'].lower(): print u' (child)' else: print u'' def doGetASPs(users): cd = buildGAPIObject(u'directory') for user in users: asps = callGAPIitems(cd.asps(), u'list', u'items', userKey=user) if len(asps) > 0: print u'Application-Specific Passwords for %s' % user for asp in asps: if asp[u'creationTime'] == u'0': created_date = u'Unknown' else: created_date = datetime.datetime.fromtimestamp(int(asp[u'creationTime'])/1000).strftime(u'%Y-%m-%d %H:%M:%S') if asp[u'lastTimeUsed'] == u'0': used_date = u'Never' else: used_date = datetime.datetime.fromtimestamp(int(asp[u'lastTimeUsed'])/1000).strftime(u'%Y-%m-%d %H:%M:%S') print u' ID: %s\n Name: %s\n Created: %s\n Last Used: %s\n' % (asp[u'codeId'], asp[u'name'], created_date, used_date) else: print u' no ASPs for %s\n' % user def doDelASP(users): cd = buildGAPIObject(u'directory') codeId = sys.argv[5] for user in users: callGAPI(cd.asps(), u'delete', userKey=user, codeId=codeId) print u'deleted ASP %s for %s' % (codeId, user) def printBackupCodes(user, codes): jcount = len(codes) print u'Backup verification codes for {0}'.format(user) print u'' if jcount > 0: j = 0 for code in codes: j += 1 print u'{0}. {1}'.format(j, code[u'verificationCode']) print u'' def doGetBackupCodes(users): cd = buildGAPIObject(u'directory') for user in users: try: codes = callGAPIitems(cd.verificationCodes(), u'list', u'items', throw_reasons=[u'invalidArgument', u'invalid'], userKey=user) except googleapiclient.errors.HttpError: codes = [] printBackupCodes(user, codes) def doGenBackupCodes(users): cd = buildGAPIObject(u'directory') for user in users: callGAPI(cd.verificationCodes(), u'generate', userKey=user) codes = callGAPIitems(cd.verificationCodes(), u'list', u'items', userKey=user) printBackupCodes(user, codes) def doDelBackupCodes(users): cd = buildGAPIObject(u'directory') for user in users: try: callGAPI(cd.verificationCodes(), u'invalidate', soft_errors=True, throw_reasons=[u'invalid',], userKey=user) except googleapiclient.errors.HttpError: print u'No 2SV backup codes for %s' % user continue print u'2SV backup codes for %s invalidated' % user def commonClientIds(clientId): if clientId == u'gasmo': return u'1095133494869.apps.googleusercontent.com' return clientId def doDelTokens(users): cd = buildGAPIObject(u'directory') clientId = sys.argv[6] clientId = commonClientIds(clientId) for user in users: callGAPI(cd.tokens(), u'delete', userKey=user, clientId=clientId) print u'Deleted token for %s' % user def printShowTokens(i, entityType, users, csvFormat): def _showToken(token): print u' Client ID: %s' % token[u'clientId'] for item in [u'displayText', u'anonymous', u'nativeApp', u'userKey']: print convertUTF8(u' %s: %s' % (item, token.get(item, u''))) item = u'scopes' print u' %s:' % item for it in token.get(item, []): print u' %s' % it cd = buildGAPIObject(u'directory') if csvFormat: todrive = False titles = [u'user', u'clientId', u'displayText', u'anonymous', u'nativeApp', u'userKey', u'scopes'] csvRows = [] clientId = None while i < len(sys.argv): myarg = sys.argv[i].lower() if csvFormat and myarg == u'todrive': todrive = True i += 1 elif myarg == u'clientid': clientId = commonClientIds(sys.argv[i+1]) i += 2 elif not entityType: entityType = myarg users = getUsersToModify(entity_type=entityType, entity=sys.argv[i+1], silent=False) i += 2 else: print u'ERROR: %s is not a valid argument for "gam %s tokens"' % (myarg, [u'show', u'print'][csvFormat]) sys.exit(2) if not entityType: users = getUsersToModify(entity_type=u'all', entity=u'users', silent=False) fields = u','.join([u'clientId', u'displayText', u'anonymous', u'nativeApp', u'userKey', u'scopes']) i = 0 count = len(users) for user in users: i += 1 try: if csvFormat: sys.stderr.write(u'Getting Access Tokens for %s\n' % (user)) if clientId: results = [callGAPI(cd.tokens(), u'get', throw_reasons=[GAPI_NOT_FOUND, GAPI_USER_NOT_FOUND], userKey=user, clientId=clientId, fields=fields)] else: results = callGAPIitems(cd.tokens(), u'list', u'items', throw_reasons=[GAPI_USER_NOT_FOUND], userKey=user, fields=u'items({0})'.format(fields)) jcount = len(results) if not csvFormat: print u'User: {0}, Access Tokens ({1}/{2})'.format(user, i, count) if jcount == 0: continue for token in results: _showToken(token) else: if jcount == 0: continue for token in results: row = {u'user': user, u'scopes': u' '.join(token.get(u'scopes', []))} for item in [u'displayText', u'anonymous', u'nativeApp', u'userKey']: row[item] = token.get(item, u'') csvRows.append(row) except googleapiclient.errors.HttpError: pass if csvFormat: writeCSVfile(csvRows, titles, u'OAuth Tokens', todrive) def doDeprovUser(users): cd = buildGAPIObject(u'directory') for user in users: print u'Getting Application Specific Passwords for %s' % user asps = callGAPIitems(cd.asps(), u'list', u'items', userKey=user, fields=u'items/codeId') jcount = len(asps) if jcount > 0: j = 0 for asp in asps: j += 1 print u' deleting ASP %s of %s' % (j, jcount) callGAPI(cd.asps(), u'delete', userKey=user, codeId=asp[u'codeId']) else: print u'No ASPs' print u'Invalidating 2SV Backup Codes for %s' % user try: callGAPI(cd.verificationCodes(), u'invalidate', soft_errors=True, throw_reasons=[u'invalid'], userKey=user) except googleapiclient.errors.HttpError: print u'No 2SV Backup Codes' print u'Getting tokens for %s...' % user tokens = callGAPIitems(cd.tokens(), u'list', u'items', userKey=user, fields=u'items/clientId') jcount = len(tokens) if jcount > 0: j = 0 for token in tokens: j += 1 print u' deleting token %s of %s' % (j, jcount) callGAPI(cd.tokens(), u'delete', userKey=user, clientId=token[u'clientId']) else: print u'No Tokens' print u'Done deprovisioning %s' % user def doDeleteUser(): cd = buildGAPIObject(u'directory') user_email = sys.argv[3] if user_email[:4].lower() == u'uid:': user_email = user_email[4:] elif user_email.find(u'@') == -1: user_email = u'%s@%s' % (user_email, GC_Values[GC_DOMAIN]) print u"Deleting account for %s" % (user_email) callGAPI(cd.users(), u'delete', userKey=user_email) def doUndeleteUser(): cd = buildGAPIObject(u'directory') user = sys.argv[3].lower() user_uid = False orgUnit = u'/' i = 4 while i < len(sys.argv): if sys.argv[i].lower() in [u'ou', u'org']: orgUnit = sys.argv[i+1] i += 2 else: print u'ERROR: %s is not a valid argument for "gam undelete user"' % sys.argv[i] sys.exit(2) if user[:4].lower() == u'uid:': user_uid = user[4:] elif user.find(u'@') == -1: user = u'%s@%s' % (user, GC_Values[GC_DOMAIN]) if not user_uid: print u'Looking up UID for %s...' % user deleted_users = callGAPIpages(cd.users(), u'list', u'users', customer=GC_Values[GC_CUSTOMER_ID], showDeleted=True, maxResults=GC_Values[GC_USER_MAX_RESULTS]) matching_users = list() for deleted_user in deleted_users: if str(deleted_user[u'primaryEmail']).lower() == user: matching_users.append(deleted_user) if len(matching_users) < 1: print u'ERROR: could not find deleted user with that address.' sys.exit(3) elif len(matching_users) > 1: print u'ERROR: more than one matching deleted %s user. Please select the correct one to undelete and specify with "gam undelete user uid:"' % user print u'' for matching_user in matching_users: print u' uid:%s ' % matching_user[u'id'] for attr_name in [u'creationTime', u'lastLoginTime', u'deletionTime']: try: if matching_user[attr_name] == NEVER_TIME: matching_user[attr_name] = u'Never' print u' %s: %s ' % (attr_name, matching_user[attr_name]) except KeyError: pass print sys.exit(3) else: user_uid = matching_users[0][u'id'] print u"Undeleting account for %s" % user callGAPI(cd.users(), u'undelete', userKey=user_uid, body={u'orgUnitPath': orgUnit}) def doDeleteGroup(): cd = buildGAPIObject(u'directory') group = sys.argv[3] if group[:4].lower() == u'uid:': group = group[4:] elif group.find(u'@') == -1: group = u'%s@%s' % (group, GC_Values[GC_DOMAIN]) print u"Deleting group %s" % group callGAPI(cd.groups(), u'delete', groupKey=group) def doDeleteAlias(alias_email=None): cd = buildGAPIObject(u'directory') is_user = is_group = False if alias_email == None: alias_email = sys.argv[3] if alias_email.lower() == u'user': is_user = True alias_email = sys.argv[4] elif alias_email.lower() == u'group': is_group = True alias_email = sys.argv[4] if alias_email.find(u'@') == -1: alias_email = u'%s@%s' % (alias_email, GC_Values[GC_DOMAIN]) print u"Deleting alias %s" % alias_email if is_user or (not is_user and not is_group): try: callGAPI(cd.users().aliases(), u'delete', throw_reasons=[u'invalid', u'badRequest', u'notFound'], userKey=alias_email, alias=alias_email) return except googleapiclient.errors.HttpError as e: error = json.loads(e.content) reason = error[u'error'][u'errors'][0][u'reason'] if reason == u'notFound': print u'Error: The alias %s does not exist' % alias_email sys.exit(4) if not is_user or (not is_user and not is_group): callGAPI(cd.groups().aliases(), u'delete', groupKey=alias_email, alias=alias_email) def doDeleteResourceCalendar(): resId = sys.argv[3] cd = buildGAPIObject(u'directory') print u"Deleting resource calendar %s" % resId callGAPI(cd.resources().calendars(), u'delete', customer=GC_Values[GC_CUSTOMER_ID], calendarResourceId=resId) def doDeleteOrg(): cd = buildGAPIObject(u'directory') name = sys.argv[3] if name[0] == u'/': name = name[1:] print u"Deleting organization %s" % name callGAPI(cd.orgunits(), u'delete', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=name) # Send an email def send_email(msg_subj, msg_txt, msg_rcpt=None): from email.mime.text import MIMEText gmail = buildGAPIObject(u'gmail') sender_email = gmail._http.request.credentials.id_token[u'email'] if not msg_rcpt: msg_rcpt = sender_email msg = MIMEText(msg_txt) msg[u'Subject'] = msg_subj msg[u'From'] = sender_email msg[u'To'] = msg_rcpt callGAPI(gmail.users().messages(), u'send', userId=sender_email, body={u'raw': base64.urlsafe_b64encode(msg.as_string())}) # Write a CSV file def addTitleToCSVfile(title, titles): titles.append(title) def addTitlesToCSVfile(addTitles, titles): for title in addTitles: if title not in titles: addTitleToCSVfile(title, titles) def addRowTitlesToCSVfile(row, csvRows, titles): csvRows.append(row) for title in row: if title not in titles: addTitleToCSVfile(title, titles) # fieldName is command line argument # fieldNameMap maps fieldName to API field names; CSV file header will be API field name #ARGUMENT_TO_PROPERTY_MAP = { # u'admincreated': [u'adminCreated'], # u'aliases': [u'aliases', u'nonEditableAliases'], # } # fieldsList is the list of API fields # fieldsTitles maps the API field name to the CSV file header def addFieldToCSVfile(fieldName, fieldNameMap, fieldsList, fieldsTitles, titles): for ftList in fieldNameMap[fieldName]: if ftList not in fieldsTitles: fieldsList.append(ftList) fieldsTitles[ftList] = ftList addTitlesToCSVfile([ftList], titles) # fieldName is command line argument # fieldNameTitleMap maps fieldName to API field name and CSV file header #ARGUMENT_TO_PROPERTY_TITLE_MAP = { # u'admincreated': [u'adminCreated', u'Admin_Created'], # u'aliases': [u'aliases', u'Aliases', u'nonEditableAliases', u'NonEditableAliases'], # } # fieldsList is the list of API fields # fieldsTitles maps the API field name to the CSV file header def addFieldTitleToCSVfile(fieldName, fieldNameTitleMap, fieldsList, fieldsTitles, titles): ftList = fieldNameTitleMap[fieldName] for i in range(0, len(ftList), 2): if ftList[i] not in fieldsTitles: fieldsList.append(ftList[i]) fieldsTitles[ftList[i]] = ftList[i+1] addTitlesToCSVfile([ftList[i+1]], titles) def sortCSVTitles(firstTitle, titles): restoreTitles = [] for title in firstTitle: if title in titles: titles.remove(title) restoreTitles.append(title) titles.sort() for title in restoreTitles[::-1]: titles.insert(0, title) def writeCSVfile(csvRows, titles, list_type, todrive): csv.register_dialect(u'nixstdout', lineterminator=u'\n') if todrive: string_file = StringIO.StringIO() writer = csv.DictWriter(string_file, fieldnames=titles, dialect=u'nixstdout', quoting=csv.QUOTE_MINIMAL) else: writer = csv.DictWriter(sys.stdout, fieldnames=titles, dialect=u'nixstdout', quoting=csv.QUOTE_MINIMAL) try: writer.writerow(dict((item, item) for item in writer.fieldnames)) writer.writerows(csvRows) except IOError as e: systemErrorExit(6, e) if todrive: columns = len(csvRows[0]) rows = len(csvRows) cell_count = rows * columns convert = True if cell_count > 500000 or columns > 256: print u'{0}{1}'.format(WARNING_PREFIX, MESSAGE_RESULTS_TOO_LARGE_FOR_GOOGLE_SPREADSHEET) convert = False drive = buildGAPIObject(u'drive') result = callGAPI(drive.files(), u'insert', convert=convert, body={u'description': u' '.join(sys.argv), u'title': u'%s - %s' % (GC_Values[GC_DOMAIN], list_type), u'mimeType': u'text/csv'}, media_body=googleapiclient.http.MediaInMemoryUpload(string_file.getvalue(), mimetype=u'text/csv')) file_url = result[u'alternateLink'] if GC_Values[GC_NO_BROWSER]: msg_txt = u'Drive file uploaded to:\n %s' % file_url msg_subj = u'%s - %s' % (GC_Values[GC_DOMAIN], list_type) send_email(msg_subj, msg_txt) print msg_txt else: import webbrowser webbrowser.open(file_url) def flatten_json(structure, key=u'', path=u'', flattened=None, listLimit=None): if flattened == None: flattened = {} if not isinstance(structure, (dict, list)): flattened[((path + u'.') if path else u'') + key] = structure elif isinstance(structure, list): for i, item in enumerate(structure): if listLimit and (i >= listLimit): break flatten_json(item, u'{0}'.format(i), u'.'.join([item for item in [path, key] if item]), flattened=flattened, listLimit=listLimit) else: for new_key, value in structure.items(): if new_key in [u'kind', u'etag']: continue if value == NEVER_TIME: value = u'Never' flatten_json(value, new_key, u'.'.join([item for item in [path, key] if item]), flattened=flattened, listLimit=listLimit) return flattened USER_ARGUMENT_TO_PROPERTY_MAP = { u'address': [u'addresses',], u'addresses': [u'addresses',], u'admin': [u'isAdmin', u'isDelegatedAdmin',], u'agreed2terms': [u'agreedToTerms',], u'agreedtoterms': [u'agreedToTerms',], u'aliases': [u'aliases', u'nonEditableAliases',], u'changepassword': [u'changePasswordAtNextLogin',], u'changepasswordatnextlogin': [u'changePasswordAtNextLogin',], u'creationtime': [u'creationTime',], u'deletiontime': [u'deletionTime',], u'email': [u'emails',], u'emails': [u'emails',], u'externalid': [u'externalIds',], u'externalids': [u'externalIds',], u'familyname': [u'name.familyName',], u'firstname': [u'name.givenName',], u'fullname': [u'name.fullName',], u'gal': [u'includeInGlobalAddressList',], u'givenname': [u'name.givenName',], u'id': [u'id',], u'im': [u'ims',], u'ims': [u'ims',], u'includeinglobaladdresslist': [u'includeInGlobalAddressList',], u'ipwhitelisted': [u'ipWhitelisted',], u'isadmin': [u'isAdmin', u'isDelegatedAdmin',], u'isdelegatedadmin': [u'isAdmin', u'isDelegatedAdmin',], u'ismailboxsetup': [u'isMailboxSetup',], u'lastlogintime': [u'lastLoginTime',], u'lastname': [u'name.familyName',], u'name': [u'name.givenName', u'name.familyName', u'name.fullName',], u'nicknames': [u'aliases', u'nonEditableAliases',], u'noneditablealiases': [u'aliases', u'nonEditableAliases',], u'note': [u'notes',], u'notes': [u'notes',], u'org': [u'orgUnitPath',], u'organization': [u'organizations',], u'organizations': [u'organizations',], u'orgunitpath': [u'orgUnitPath',], u'otheremail': [u'emails',], u'otheremails': [u'emails',], u'ou': [u'orgUnitPath',], u'phone': [u'phones',], u'phones': [u'phones',], u'photo': [u'thumbnailPhotoUrl',], u'photourl': [u'thumbnailPhotoUrl',], u'primaryemail': [u'primaryEmail',], u'relation': [u'relations',], u'relations': [u'relations',], u'suspended': [u'suspended', u'suspensionReason',], u'thumbnailphotourl': [u'thumbnailPhotoUrl',], u'username': [u'primaryEmail',], u'website': [u'websites',], u'websites': [u'websites',], } def doPrintUsers(): cd = buildGAPIObject(u'directory') todrive = False fieldsList = [] fieldsTitles = {} titles = [] csvRows = [] addFieldToCSVfile(u'primaryemail', USER_ARGUMENT_TO_PROPERTY_MAP, fieldsList, fieldsTitles, titles) customer = GC_Values[GC_CUSTOMER_ID] domain = None query = None projection = u'basic' customFieldMask = None sortHeaders = getGroupFeed = getLicenseFeed = email_parts = False viewType = deleted_only = orderBy = sortOrder = None i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower().replace(u'_', u'') if myarg in PROJECTION_CHOICES_MAP: projection = myarg sortHeaders = True fieldsList = [] i += 1 elif myarg == u'allfields': projection = u'basic' sortHeaders = True fieldsList = [] i += 1 elif myarg == u'custom': fieldsList.append(u'customSchemas') if sys.argv[i+1].lower() == u'all': projection = u'full' else: projection = u'custom' customFieldMask = sys.argv[i+1] i += 2 elif myarg == u'todrive': todrive = True i += 1 elif myarg in [u'deletedonly', u'onlydeleted']: deleted_only = True i += 1 elif myarg == u'orderby': orderBy = sys.argv[i+1] if orderBy.lower() not in [u'email', u'familyname', u'givenname', u'firstname', u'lastname']: print u'ERROR: orderby must be one of email, familyName, givenName; got %s' % orderBy sys.exit(2) elif orderBy.lower() in [u'familyname', u'lastname']: orderBy = u'familyName' elif orderBy.lower() in [u'givenname', u'firstname']: orderBy = u'givenName' i += 2 elif myarg == u'userview': viewType = u'domain_public' i += 1 elif myarg in SORTORDER_CHOICES_MAP: sortOrder = SORTORDER_CHOICES_MAP[myarg] i += 1 elif myarg == u'domain': domain = sys.argv[i+1] customer = None i += 2 elif myarg == u'query': query = sys.argv[i+1] i += 2 elif myarg in USER_ARGUMENT_TO_PROPERTY_MAP: if not fieldsList: fieldsList = [u'primaryEmail',] addFieldToCSVfile(myarg, USER_ARGUMENT_TO_PROPERTY_MAP, fieldsList, fieldsTitles, titles) i += 1 elif myarg == u'fields': if not fieldsList: fieldsList = [u'primaryEmail',] fieldNameList = sys.argv[i+1] for field in fieldNameList.lower().replace(u',', u' ').split(): if field in USER_ARGUMENT_TO_PROPERTY_MAP: addFieldToCSVfile(field, USER_ARGUMENT_TO_PROPERTY_MAP, fieldsList, fieldsTitles, titles) else: print u'ERROR: %s is not a valid argument for "gam print users fields"' % field sys.exit(2) i += 2 elif myarg == u'groups': getGroupFeed = True i += 1 elif myarg in [u'license', u'licenses', u'licence', u'licences']: getLicenseFeed = True i += 1 elif myarg in [u'emailpart', u'emailparts', u'username']: email_parts = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam print users"' % sys.argv[i] sys.exit(2) if fieldsList: fields = u'nextPageToken,users(%s)' % u','.join(set(fieldsList)).replace(u'.', u'/') else: fields = None sys.stderr.write(u"Getting all users in Google Apps account (may take some time on a large account)...\n") page_message = u'Got %%total_items%% users: %%first_item%% - %%last_item%%\n' all_users = callGAPIpages(cd.users(), u'list', u'users', page_message=page_message, message_attribute=u'primaryEmail', customer=customer, domain=domain, fields=fields, showDeleted=deleted_only, orderBy=orderBy, sortOrder=sortOrder, viewType=viewType, query=query, projection=projection, customFieldMask=customFieldMask, maxResults=GC_Values[GC_USER_MAX_RESULTS]) for user in all_users: if email_parts: try: user_email = user[u'primaryEmail'] if user_email.find(u'@') != -1: user[u'primaryEmailLocal'] = user_email[:user_email.find(u'@')] user[u'primaryEmailDomain'] = user_email[user_email.find(u'@')+1:] except KeyError: pass addRowTitlesToCSVfile(flatten_json(user), csvRows, titles) if sortHeaders: sortCSVTitles([u'primaryEmail',], titles) if getGroupFeed: total_users = len(csvRows) user_count = 1 titles.append(u'Groups') for user in csvRows: 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_count += 1 if getLicenseFeed: titles.append(u'Licenses') licenses = doPrintLicenses(return_list=True) if len(licenses) > 1: for user in csvRows: user_licenses = [] for u_license in licenses: if u_license[u'userId'].lower() == user[u'primaryEmail'].lower(): user_licenses.append(u_license[u'skuId']) user.update(Licenses=u' '.join(user_licenses)) writeCSVfile(csvRows, titles, u'Users', todrive) GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP = { u'admincreated': [u'adminCreated', u'Admin_Created'], u'aliases': [u'aliases', u'Aliases', u'nonEditableAliases', u'NonEditableAliases'], u'description': [u'description', u'Description'], u'email': [u'email', u'Email'], u'id': [u'id', u'ID'], u'name': [u'name', u'Name'], } GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP = { u'allowexternalmembers': u'allowExternalMembers', u'allowgooglecommunication': u'allowGoogleCommunication', u'allowwebposting': u'allowWebPosting', u'archiveonly': u'archiveOnly', u'customfootertext': u'customFooterText', u'customreplyto': u'customReplyTo', u'defaultmessagedenynotificationtext': u'defaultMessageDenyNotificationText', u'gal': u'includeInGlobalAddressList', u'includecustomfooter': u'includeCustomFooter', u'includeinglobaladdresslist': u'includeInGlobalAddressList', u'isarchived': u'isArchived', u'maxmessagebytes': u'maxMessageBytes', u'memberscanpostasthegroup': u'membersCanPostAsTheGroup', u'messagedisplayfont': u'messageDisplayFont', u'messagemoderationlevel': u'messageModerationLevel', u'primarylanguage': u'primaryLanguage', u'replyto': u'replyTo', u'sendmessagedenynotification': u'sendMessageDenyNotification', u'showingroupdirectory': u'showInGroupDirectory', u'spammoderationlevel': u'spamModerationLevel', u'whocanadd': u'whoCanAdd', u'whocancontactowner': u'whoCanContactOwner', u'whocaninvite': u'whoCanInvite', u'whocanjoin': u'whoCanJoin', u'whocanleavegroup': u'whoCanLeaveGroup', u'whocanpostmessage': u'whoCanPostMessage', u'whocanviewgroup': u'whoCanViewGroup', u'whocanviewmembership': u'whoCanViewMembership', } def doPrintGroups(): cd = buildGAPIObject(u'directory') i = 3 members = owners = managers = False customer = GC_Values[GC_CUSTOMER_ID] usedomain = usemember = None aliasDelimiter = u' ' memberDelimiter = u'\n' todrive = False cdfieldsList = [] gsfieldsList = [] fieldsTitles = {} titles = [] csvRows = [] addFieldTitleToCSVfile(u'email', GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP, cdfieldsList, fieldsTitles, titles) maxResults = None roles = [] getSettings = False while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg == u'todrive': todrive = True i += 1 elif myarg == u'domain': usedomain = sys.argv[i+1].lower() customer = None i += 2 elif myarg == u'member': usemember = sys.argv[i+1].lower() customer = None i += 2 elif myarg == u'maxresults': maxResults = int(sys.argv[i+1]) i += 2 elif myarg == u'delimiter': aliasDelimiter = memberDelimiter = sys.argv[i+1] i += 2 elif myarg in GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP: addFieldTitleToCSVfile(myarg, GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP, cdfieldsList, fieldsTitles, titles) i += 1 elif myarg == u'fields': fieldNameList = sys.argv[i+1] for field in fieldNameList.lower().replace(u',', u' ').split(): if field in GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP: addFieldTitleToCSVfile(field, GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP, cdfieldsList, fieldsTitles, titles) elif field in GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP: addFieldToCSVfile(field, {field: [GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP[field]]}, gsfieldsList, fieldsTitles, titles) gsfieldsList.extend([GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP[field],]) else: print u'ERROR: %s is not a valid argument for "gam print groups fields"' % field sys.exit(2) i += 2 elif myarg == u'members': if myarg not in roles: roles.append(ROLE_MEMBER) addTitleToCSVfile(u'Members', titles) members = True i += 1 elif myarg == u'owners': if myarg not in roles: roles.append(ROLE_OWNER) addTitleToCSVfile(u'Owners', titles) owners = True i += 1 elif myarg == u'managers': if myarg not in roles: roles.append(ROLE_MANAGER) addTitleToCSVfile(u'Managers', titles) managers = True i += 1 elif myarg == u'settings': getSettings = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam print groups"' % sys.argv[i] sys.exit(2) cdfields = u','.join(set(cdfieldsList)) if len(gsfieldsList) > 0: getSettings = True gsfields = u','.join(set(gsfieldsList)) elif getSettings: gsfields = None roles = u','.join(sorted(set(roles))) sys.stderr.write(u"Retrieving All Groups for Google Apps account (may take some time on a large account)...\n") page_message = u'Got %%num_items%% groups: %%first_item%% - %%last_item%%\n' entityList = callGAPIpages(cd.groups(), u'list', u'groups', page_message=page_message, message_attribute=u'email', customer=customer, domain=usedomain, userKey=usemember, fields=u'nextPageToken,groups({0})'.format(cdfields), maxResults=maxResults) i = 0 count = len(entityList) for groupEntity in entityList: i += 1 groupEmail = groupEntity[u'email'] group = {} for field in cdfieldsList: if field in groupEntity: if isinstance(groupEntity[field], list): group[fieldsTitles[field]] = aliasDelimiter.join(groupEntity[field]) else: group[fieldsTitles[field]] = groupEntity[field] if roles: sys.stderr.write(u' Getting %s for %s (%s/%s)\n' % (roles, groupEmail, i, count)) page_message = u'Got %%num_items%% members: %%first_item%% - %%last_item%%\n' groupMembers = callGAPIpages(cd.members(), u'list', u'members', page_message=page_message, message_attribute=u'email', groupKey=groupEmail, roles=roles, fields=u'nextPageToken,members(email,id,role)') if members: allMembers = list() if managers: allManagers = list() if owners: allOwners = list() for member in groupMembers: member_email = member.get(u'email', member.get(u'id', None)) if not member_email: sys.stderr.write(u' Not sure what to do with: %s' % member) continue role = member.get(u'role', None) if role: if role == ROLE_MEMBER: if members: allMembers.append(member_email) elif role == ROLE_MANAGER: if managers: allManagers.append(member_email) elif role == ROLE_OWNER: if owners: allOwners.append(member_email) elif members: allMembers.append(member_email) elif members: allMembers.append(member_email) if members: group[u'Members'] = memberDelimiter.join(allMembers) if managers: group[u'Managers'] = memberDelimiter.join(allManagers) if owners: group[u'Owners'] = memberDelimiter.join(allOwners) if getSettings: sys.stderr.write(u" Retrieving Settings for group %s (%s/%s)...\r\n" % (groupEmail, i, count)) gs = buildGAPIObject(u'groupssettings') settings = callGAPI(gs.groups(), u'get', retry_reasons=[u'serviceLimit'], groupUniqueId=groupEmail, fields=gsfields) for key in settings: if key in [u'email', u'name', u'description', u'kind', u'etag']: continue setting_value = settings[key] if setting_value == None: setting_value = u'' if key not in titles: addTitleToCSVfile(key, titles) group[key] = setting_value csvRows.append(group) writeCSVfile(csvRows, titles, u'Groups', todrive) ORG_ARGUMENT_TO_PROPERTY_TITLE_MAP = { u'description': [u'description', u'Description'], u'id': [u'orgUnitId', u'ID'], u'inherit': [u'blockInheritance', u'InheritanceBlocked'], u'orgunitpath': [u'orgUnitPath', u'Path'], u'path': [u'orgUnitPath', u'Path'], u'name': [u'name', u'Name'], u'parent': [u'parentOrgUnitPath', u'Parent'], u'parentid': [u'parentOrgUnitId', u'ParentID'], } ORG_FIELD_PRINT_ORDER = [u'orgunitpath', u'id', u'name', u'description', u'parent', u'parentid', u'inherit'] def doPrintOrgs(): cd = buildGAPIObject(u'directory') listType = u'all' orgUnitPath = u"/" todrive = False fieldsList = [] fieldsTitles = {} titles = [] csvRows = [] i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower().replace(u'_', u'') if myarg == u'todrive': todrive = True i += 1 elif myarg == u'toplevelonly': listType = u'children' i += 1 elif myarg == u'fromparent': orgUnitPath = sys.argv[i+1] i += 2 elif myarg == u'allfields': fieldsList = [] fieldsTitles = {} titles = [] for field in ORG_FIELD_PRINT_ORDER: addFieldTitleToCSVfile(field, ORG_ARGUMENT_TO_PROPERTY_TITLE_MAP, fieldsList, fieldsTitles, titles) i += 1 elif myarg in ORG_ARGUMENT_TO_PROPERTY_TITLE_MAP: addFieldTitleToCSVfile(myarg, ORG_ARGUMENT_TO_PROPERTY_TITLE_MAP, fieldsList, fieldsTitles, titles) i += 1 else: print u'ERROR: %s is not a valid argument for "gam print orgs"' % sys.argv[i] sys.exit(2) if not fieldsList: addFieldTitleToCSVfile(u'orgunitpath', ORG_ARGUMENT_TO_PROPERTY_TITLE_MAP, fieldsList, fieldsTitles, titles) sys.stderr.write(u"Retrieving All Organizational Units for your account (may take some time on large domain)...") orgs = callGAPI(cd.orgunits(), u'list', customerId=GC_Values[GC_CUSTOMER_ID], type=listType, orgUnitPath=orgUnitPath, fields=u'organizationUnits({0})'.format(u','.join(set(fieldsList)))) sys.stderr.write(u"done\n") if not u'organizationUnits' in orgs: print u'0 org units in this Google Apps instance...' return for orgEntity in orgs[u'organizationUnits']: orgUnit = {} for field in fieldsList: orgUnit[fieldsTitles[field]] = orgEntity.get(field, u'') csvRows.append(orgUnit) writeCSVfile(csvRows, titles, u'Orgs', todrive) def doPrintAliases(): cd = buildGAPIObject(u'directory') todrive = False titles = [u'Alias', u'Target', u'TargetType'] csvRows = [] i = 3 while i < len(sys.argv): if sys.argv[i].lower() == u'todrive': todrive = True i += 1 else: print u'ERROR: %s is not a valid argument for "gam print aliases"' % sys.argv[i] sys.exit(2) sys.stderr.write(u"Retrieving All User Aliases for %s organization (may take some time on large domain)...\n" % GC_Values[GC_DOMAIN]) page_message = u'Got %%num_items%% users %%first_item%% - %%last_item%%\n' all_users = callGAPIpages(cd.users(), u'list', u'users', page_message=page_message, message_attribute=u'primaryEmail', customer=GC_Values[GC_CUSTOMER_ID], fields=u'users(primaryEmail,aliases),nextPageToken', maxResults=GC_Values[GC_USER_MAX_RESULTS]) for user in all_users: try: for alias in user[u'aliases']: csvRows.append({u'Alias': alias, u'Target': user[u'primaryEmail'], u'TargetType': u'User'}) except KeyError: continue sys.stderr.write(u"Retrieving All User Aliases for %s organization (may take some time on large domain)...\n" % GC_Values[GC_DOMAIN]) page_message = u'Got %%num_items%% groups %%first_item%% - %%last_item%%\n' all_groups = callGAPIpages(cd.groups(), u'list', u'groups', page_message=page_message, message_attribute=u'email', customer=GC_Values[GC_CUSTOMER_ID], fields=u'groups(email,aliases),nextPageToken') for group in all_groups: try: for alias in group[u'aliases']: csvRows.append({u'Alias': alias, u'Target': group[u'email'], u'TargetType': u'Group'}) except KeyError: continue writeCSVfile(csvRows, titles, u'Aliases', todrive) def doPrintGroupMembers(): cd = buildGAPIObject(u'directory') todrive = False membernames = False customer = GC_Values[GC_CUSTOMER_ID] usedomain = None usemember = None fields = None titles = [u'group'] csvRows = [] groups_to_get = [] i = 3 while i < len(sys.argv): if sys.argv[i].lower() == u'domain': usedomain = sys.argv[i+1].lower() customer = None i += 2 elif sys.argv[i].lower() == u'todrive': todrive = True i += 1 elif sys.argv[i].lower() == u'member': usemember = sys.argv[i+1].lower() customer = None i += 2 elif sys.argv[i].lower() == u'fields': memberFieldsList = sys.argv[i+1].replace(u',', u' ').lower().split() fields = u'nextPageToken,members(%s)' % (','.join(memberFieldsList)) i += 2 elif sys.argv[i].lower() == u'membernames': membernames = True titles.append(u'name') i += 1 elif sys.argv[i].lower() == u'group': group_email = sys.argv[i+1].lower() if group_email.find(u'@') == -1: group_email = u'%s@%s' % (group_email, GC_Values[GC_DOMAIN]) groups_to_get = [{u'email': group_email}] i += 2 else: print u'ERROR: %s is not a valid argument for "gam print group-members"' % sys.argv[i] sys.exit(2) if not groups_to_get: groups_to_get = callGAPIpages(cd.groups(), u'list', u'groups', message_attribute=u'email', customer=customer, domain=usedomain, userKey=usemember, fields=u'nextPageToken,groups(email)') i = 0 count = len(groups_to_get) for group in groups_to_get: i += 1 group_email = group[u'email'] sys.stderr.write(u'Getting members for %s (%s/%s)\n' % (group_email, i, count)) group_members = callGAPIpages(cd.members(), u'list', u'members', message_attribute=u'email', groupKey=group_email, fields=fields) for member in group_members: for unwanted_item in [u'kind', u'etag']: if unwanted_item in member: del member[unwanted_item] for title in member: if title not in titles: titles.append(title) member[u'group'] = group_email if membernames and u'type' in member and u'id' in member: if member[u'type'] == u'USER': try: mbinfo = callGAPI(cd.users(), u'get', throw_reasons=[u'notFound', u'forbidden'], userKey=member[u'id'], fields=u'name') memberName = mbinfo[u'name'][u'fullName'] except googleapiclient.errors.HttpError: memberName = u'Unknown' elif member[u'type'] == u'GROUP': try: mbinfo = callGAPI(cd.groups(), u'get', throw_reasons=[u'notFound', u'forbidden'], groupKey=member[u'id'], fields=u'name') memberName = mbinfo[u'name'] except googleapiclient.errors.HttpError: memberName = u'Unknown' else: memberName = u'Unknown' member[u'name'] = memberName csvRows.append(member) writeCSVfile(csvRows, titles, u'Group Members', todrive) def doPrintMobileDevices(): cd = buildGAPIObject(u'directory') todrive = False titles = [u'resourceId',] csvRows = [] query = projection = orderBy = sortOrder = None i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg == u'query': query = sys.argv[i+1] i += 2 elif myarg == u'todrive': todrive = True i += 1 elif myarg == u'orderby': orderBy = sys.argv[i+1].lower() allowed_values = [u'deviceid', u'email', u'lastsync', u'model', u'name', u'os', u'status', u'type'] if orderBy.lower() not in allowed_values: print u'ERROR: orderBy must be one of %s; got %s' % (u', '.join(allowed_values), orderBy) sys.exit(2) elif orderBy == u'lastsync': orderBy = u'lastSync' elif orderBy == u'deviceid': orderBy = u'deviceId' i += 2 elif myarg in SORTORDER_CHOICES_MAP: sortOrder = SORTORDER_CHOICES_MAP[myarg] i += 1 elif myarg in PROJECTION_CHOICES_MAP: projection = PROJECTION_CHOICES_MAP[myarg] i += 1 else: print u'ERROR: %s is not a valid argument for "gam print mobile"' % sys.argv[i] sys.exit(2) sys.stderr.write(u'Retrieving All Mobile Devices for organization (may take some time for large accounts)...\n') page_message = u'Got %%num_items%% mobile devices...\n' all_mobile = callGAPIpages(cd.mobiledevices(), u'list', u'mobiledevices', page_message=page_message, customerId=GC_Values[GC_CUSTOMER_ID], query=query, projection=projection, orderBy=orderBy, sortOrder=sortOrder, maxResults=GC_Values[GC_DEVICE_MAX_RESULTS]) for mobile in all_mobile: mobiledevice = {} for attrib in mobile: if attrib in [u'kind', u'etag', u'applications']: continue if attrib not in titles: titles.append(attrib) if attrib in [u'name', u'email']: if mobile[attrib]: mobiledevice[attrib] = mobile[attrib][0] else: mobiledevice[attrib] = mobile[attrib] csvRows.append(mobiledevice) writeCSVfile(csvRows, titles, u'Mobile', todrive) def doPrintCrosDevices(): cd = buildGAPIObject(u'directory') todrive = False fieldsList = [] fieldsTitles = {} titles = [] csvRows = [] addFieldToCSVfile(u'deviceid', CROS_ARGUMENT_TO_PROPERTY_MAP, fieldsList, fieldsTitles, titles) sortHeaders = False query = projection = orderBy = sortOrder = None noLists = False listLimit = 0 selectActiveTimeRanges = selectRecentUsers = None i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower().replace(u'_', u'') if myarg == u'query': query = sys.argv[i+1] i += 2 elif myarg == u'todrive': todrive = True i += 1 elif myarg == u'nolists': noLists = True selectActiveTimeRanges = selectRecentUsers = None i += 1 elif myarg == u'recentusers': projection = u'FULL' selectRecentUsers = u'recentUsers' noLists = False if fieldsList: fieldsList.append(selectRecentUsers) i += 1 elif myarg in [u'timeranges', u'activetimeranges']: projection = u'FULL' selectActiveTimeRanges = u'activeTimeRanges' noLists = False if fieldsList: fieldsList.append(selectActiveTimeRanges) i += 1 elif myarg == u'listlimit': listLimit = int(sys.argv[i+1]) i += 2 elif myarg == u'orderby': orderBy = sys.argv[i+1].lower().replace(u'_', u'') allowed_values = [u'location', u'user', u'lastsync', u'notes', u'serialnumber', u'status', u'supportenddate'] if orderBy not in allowed_values: print u'ERROR: orderBy must be one of %s; got %s' % (u', '.join(allowed_values), orderBy) sys.exit(2) elif orderBy == u'location': orderBy = u'annotatedLocation' elif orderBy == u'user': orderBy = u'annotatedUser' elif orderBy == u'lastsync': orderBy = u'lastSync' elif orderBy == u'serialnumber': orderBy = u'serialNumber' elif orderBy == u'supportEndDate': orderBy = u'supportEndDate' i += 2 elif myarg in SORTORDER_CHOICES_MAP: sortOrder = SORTORDER_CHOICES_MAP[myarg] i += 1 elif myarg in PROJECTION_CHOICES_MAP: projection = PROJECTION_CHOICES_MAP[myarg] sortHeaders = True if projection == u'FULL': fieldsList = [] else: fieldsList = CROS_BASIC_FIELDS_LIST[:] i += 1 elif myarg == u'allfields': projection = u'FULL' sortHeaders = True fieldsList = [] i += 1 elif myarg in CROS_ARGUMENT_TO_PROPERTY_MAP: if not fieldsList: fieldsList = [u'deviceId',] addFieldToCSVfile(myarg, CROS_ARGUMENT_TO_PROPERTY_MAP, fieldsList, fieldsTitles, titles) i += 1 elif myarg == u'fields': if not fieldsList: fieldsList = [u'deviceId',] fieldNameList = sys.argv[i+1] for field in fieldNameList.lower().replace(u',', u' ').split(): if field in CROS_ARGUMENT_TO_PROPERTY_MAP: addFieldToCSVfile(field, CROS_ARGUMENT_TO_PROPERTY_MAP, fieldsList, fieldsTitles, titles) if field == u'recentusers': projection = u'FULL' selectRecentUsers = u'recentUsers' noLists = False elif field in [u'timeranges', u'activetimeranges']: projection = u'FULL' selectActiveTimeRanges = u'activeTimeRanges' noLists = False else: print u'ERROR: %s is not a valid argument for "gam print cros fields"' % field sys.exit(2) i += 2 else: print u'ERROR: %s is not a valid argument for "gam print cros"' % sys.argv[i] sys.exit(2) if fieldsList: fields = u'nextPageToken,chromeosdevices({0})'.format(u','.join(set(fieldsList))).replace(u'.', u'/') else: fields = None sys.stderr.write(u'Retrieving All Chrome OS Devices for organization (may take some time for large accounts)...\n') page_message = u'Got %%num_items%% Chrome devices...\n' all_cros = callGAPIpages(cd.chromeosdevices(), u'list', u'chromeosdevices', page_message=page_message, query=query, customerId=GC_Values[GC_CUSTOMER_ID], projection=projection, orderBy=orderBy, sortOrder=sortOrder, fields=fields, maxResults=GC_Values[GC_DEVICE_MAX_RESULTS]) if all_cros: if (not noLists) and (not selectActiveTimeRanges) and (not selectRecentUsers): for cros in all_cros: addRowTitlesToCSVfile(flatten_json(cros, listLimit=listLimit), csvRows, titles) else: if not noLists: if selectActiveTimeRanges: for attrib in [u'activeTimeRanges.activeTime', u'activeTimeRanges.date']: titles.append(attrib) if selectRecentUsers: for attrib in [u'recentUsers.email', u'recentUsers.type']: titles.append(attrib) for cros in all_cros: row = {} for attrib in cros: if attrib in [u'kind', u'etag', u'recentUsers', u'activeTimeRanges']: continue if attrib not in titles: titles.append(attrib) row[attrib] = cros[attrib] activeTimeRanges = cros.get(selectActiveTimeRanges, []) if selectActiveTimeRanges else [] recentUsers = cros.get(selectRecentUsers, []) if selectRecentUsers else [] if noLists or (not activeTimeRanges and not recentUsers): csvRows.append(row) else: lenATR = len(activeTimeRanges) lenRU = len(recentUsers) for i in xrange(min(listLimit, max(lenATR, lenRU)) if listLimit else max(lenATR, lenRU)): new_row = row.copy() if i < lenATR: new_row[u'activeTimeRanges.activeTime'] = str(activeTimeRanges[i][u'activeTime']) new_row[u'activeTimeRanges.date'] = activeTimeRanges[i][u'date'] if i < lenRU: new_row[u'recentUsers.email'] = recentUsers[i].get(u'email', u'') new_row[u'recentUsers.type'] = recentUsers[i][u'type'] csvRows.append(new_row) if sortHeaders: sortCSVTitles([u'deviceId',], titles) writeCSVfile(csvRows, titles, u'CrOS', todrive) def doPrintLicenses(return_list=False, skus=None): lic = buildGAPIObject(u'licensing') products = [u'Google-Apps', u'Google-Vault'] licenses = [] titles = [u'userId', u'productId', u'skuId'] csvRows = [] todrive = False i = 3 while i < len(sys.argv) and not return_list: if sys.argv[i].lower() == u'todrive': todrive = True i += 1 elif sys.argv[i].lower() in [u'products', u'product']: products = sys.argv[i+1].replace(u',', u' ').split() i += 2 elif sys.argv[i].lower() in [u'sku', u'skus']: skus = sys.argv[i+1].replace(u',', u' ').split() i += 2 else: print u'ERROR: %s is not a valid argument for "gam print licenses"' % sys.argv[i] sys.exit(2) if skus: for sku in skus: product, sku = getProductAndSKU(sku) page_message = u'Got %%%%total_items%%%% Licenses for %s...\n' % sku try: licenses += callGAPIpages(lic.licenseAssignments(), u'listForProductAndSku', u'items', throw_reasons=[u'invalid', u'forbidden'], page_message=page_message, customerId=GC_Values[GC_DOMAIN], productId=product, skuId=sku, fields=u'items(productId,skuId,userId),nextPageToken') except googleapiclient.errors.HttpError: pass else: for productId in products: page_message = u'Got %%%%total_items%%%% Licenses for %s...\n' % productId try: licenses += callGAPIpages(lic.licenseAssignments(), u'listForProduct', u'items', throw_reasons=[u'invalid', u'forbidden'], page_message=page_message, customerId=GC_Values[GC_DOMAIN], productId=productId, fields=u'items(productId,skuId,userId),nextPageToken') except googleapiclient.errors.HttpError: pass for u_license in licenses: a_license = {} for title in u_license: if title in [u'kind', u'etags', u'selfLink']: continue if title not in titles: titles.append(title) a_license[title] = u_license[title] csvRows.append(a_license) if return_list: return csvRows writeCSVfile(csvRows, titles, u'Licenses', todrive) RESCAL_DFLTFIELDS = [u'id', u'name', u'email',] RESCAL_ALLFIELDS = [u'id', u'name', u'email', u'description', u'type',] RESCAL_ARGUMENT_TO_PROPERTY_MAP = { u'description': [u'resourceDescription'], u'email': [u'resourceEmail'], u'id': [u'resourceId'], u'name': [u'resourceName'], u'type': [u'resourceType'], } def doPrintResourceCalendars(): cd = buildGAPIObject(u'directory') todrive = False fieldsList = [] fieldsTitles = {} titles = [] csvRows = [] i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg == u'todrive': todrive = True i += 1 elif myarg == u'allfields': fieldsList = [] fieldsTitles = {} titles = [] for field in RESCAL_ALLFIELDS: addFieldToCSVfile(field, RESCAL_ARGUMENT_TO_PROPERTY_MAP, fieldsList, fieldsTitles, titles) i += 1 elif myarg in RESCAL_ARGUMENT_TO_PROPERTY_MAP: addFieldToCSVfile(myarg, RESCAL_ARGUMENT_TO_PROPERTY_MAP, fieldsList, fieldsTitles, titles) i += 1 else: print u'ERROR: %s is not a valid argument for "gam print resources"' % sys.argv[i] sys.exit(2) if not fieldsList: for field in RESCAL_DFLTFIELDS: addFieldToCSVfile(field, RESCAL_ARGUMENT_TO_PROPERTY_MAP, fieldsList, fieldsTitles, titles) sys.stderr.write(u"Retrieving All Resource Calendars for your account (may take some time on a large domain)\n") page_message = u'Got %%total_items%% resources: %%first_item%% - %%last_item%%\n' resources = callGAPIpages(cd.resources().calendars(), u'list', u'items', page_message=page_message, message_attribute=u'resourceId', customer=GC_Values[GC_CUSTOMER_ID], fields=u'nextPageToken,items({0})'.format(u','.join(set(fieldsList)))) for resource in resources: resUnit = {} for field in fieldsList: resUnit[fieldsTitles[field]] = resource.get(field, u'') csvRows.append(resUnit) writeCSVfile(csvRows, titles, u'Resources', todrive) def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=None, checkNotSuspended=False): got_uids = False if entity_type == None: entity_type = sys.argv[1].lower() if entity == None: entity = sys.argv[2] cd = buildGAPIObject(u'directory') if entity_type == u'user': users = [entity,] elif entity_type == u'users': users = entity.replace(u',', u' ').split() elif entity_type == u'group': got_uids = True group = entity if member_type == None: member_type_message = u'all members' else: member_type_message = u'%ss' % member_type.lower() if group.find(u'@') == -1: group = u'%s@%s' % (group, GC_Values[GC_DOMAIN]) page_message = None if not silent: sys.stderr.write(u"Getting %s of %s (may take some time for large groups)...\n" % (member_type_message, group)) page_message = u'Got %%%%total_items%%%% %s...' % member_type_message members = callGAPIpages(cd.members(), u'list', u'members', page_message=page_message, groupKey=group, roles=member_type, fields=u'nextPageToken,members(email)') users = [] for member in members: users.append(member[u'email']) elif entity_type in [u'ou', u'org']: got_uids = True ou = entity if ou[0] != u'/': ou = u'/%s' % ou users = [] page_message = None if not silent: sys.stderr.write(u"Getting all users in the Google Apps organization (may take some time on a large domain)...\n") page_message = u'Got %%total_items%% users...' members = callGAPIpages(cd.users(), u'list', u'users', page_message=page_message, customer=GC_Values[GC_CUSTOMER_ID], fields=u'nextPageToken,users(primaryEmail,suspended,orgUnitPath)', query=u"orgUnitPath='%s'" % ou, maxResults=GC_Values[GC_USER_MAX_RESULTS]) for member in members: if ou.lower() != member[u'orgUnitPath'].lower(): continue if not checkNotSuspended or not member[u'suspended']: users.append(member[u'primaryEmail']) if not silent: sys.stderr.write(u"%s users are directly in the OU.\n" % len(users)) elif entity_type in [u'ou_and_children', u'ou_and_child']: got_uids = True ou = entity if ou[0] != u'/': ou = u'/%s' % ou users = [] page_message = None if not silent: sys.stderr.write(u"Getting all users in the Google Apps organization (may take some time on a large domain)...\n") page_message = u'Got %%total_items%% users..' members = callGAPIpages(cd.users(), u'list', u'users', page_message=page_message, customer=GC_Values[GC_CUSTOMER_ID], fields=u'nextPageToken,users(primaryEmail,suspended)', query=u"orgUnitPath='%s'" % ou, maxResults=GC_Values[GC_USER_MAX_RESULTS]) for member in members: if not checkNotSuspended or not member[u'suspended']: users.append(member[u'primaryEmail']) if not silent: sys.stderr.write(u"done.\r\n") elif entity_type in [u'query',]: got_uids = True users = [] if not silent: sys.stderr.write(u"Getting all users that match query %s (may take some time on a large domain)...\n" % entity) page_message = u'Got %%total_items%% users...' members = callGAPIpages(cd.users(), u'list', u'users', page_message=page_message, customer=GC_Values[GC_CUSTOMER_ID], fields=u'nextPageToken,users(primaryEmail,suspended)', query=entity, maxResults=GC_Values[GC_USER_MAX_RESULTS]) for member in members: if not checkNotSuspended or not member[u'suspended']: users.append(member[u'primaryEmail']) if not silent: sys.stderr.write(u"done.\r\n") elif entity_type in [u'license', u'licenses', u'licence', u'licences']: users = [] licenses = doPrintLicenses(return_list=True, skus=entity.split(u',')) for row in licenses: try: users.append(row[u'userId']) except KeyError: pass elif entity_type == u'file': users = [] f = openFile(entity) for row in f: user = row.strip() if user: users.append(user) closeFile(f) elif entity_type in [u'csv', u'csvfile']: try: (filename, column) = entity.split(u':') except ValueError: filename = column = None if (not filename) or (not column): systemErrorExit(2, u'Expected {0} FileName:FieldName'.format(entity_type)) f = openFile(filename) input_file = csv.DictReader(f, restval=u'') if column not in input_file.fieldnames: csvFieldErrorExit(column, input_file.fieldnames) users = [] for row in input_file: user = row[column].strip() if user: users.append(user) closeFile(f) elif entity_type in [u'courseparticipants', u'teachers', u'students']: croom = buildGAPIObject(u'classroom') users = [] if not entity.isdigit() and entity[:2] != u'd:': entity = u'd:%s' % entity if entity_type in [u'courseparticipants', u'teachers']: page_message = u'Got %%total_items%% teachers...' teachers = callGAPIpages(croom.courses().teachers(), u'list', u'teachers', page_message=page_message, courseId=entity) for teacher in teachers: email = teacher[u'profile'].get(u'emailAddress', None) if email: users.append(email) if entity_type in [u'courseparticipants', u'students']: page_message = u'Got %%total_items%% students...' students = callGAPIpages(croom.courses().students(), u'list', u'students', page_message=page_message, courseId=entity) for student in students: email = student[u'profile'].get(u'emailAddress', None) if email: users.append(email) elif entity_type == u'all': got_uids = True users = [] if entity.lower() == u'users': if not silent: sys.stderr.write(u"Getting all users in Google Apps account (may take some time on a large account)...\n") page_message = u'Got %%total_items%% users...' all_users = callGAPIpages(cd.users(), u'list', u'users', page_message=page_message, customer=GC_Values[GC_CUSTOMER_ID], fields=u'nextPageToken,users(primaryEmail,suspended)', maxResults=GC_Values[GC_USER_MAX_RESULTS]) for member in all_users: if not member[u'suspended']: users.append(member[u'primaryEmail']) if not silent: sys.stderr.write(u"done getting %s users.\r\n" % len(users)) elif entity.lower() == u'cros': if not silent: sys.stderr.write(u"Getting all CrOS devices in Google Apps account (may take some time on a large account)...\n") all_cros = callGAPIpages(cd.chromeosdevices(), u'list', u'chromeosdevices', customerId=GC_Values[GC_CUSTOMER_ID], fields=u'nextPageToken,chromeosdevices(deviceId)', maxResults=GC_Values[GC_DEVICE_MAX_RESULTS]) for member in all_cros: users.append(member[u'deviceId']) if not silent: sys.stderr.write(u"done getting %s CrOS devices.\r\n" % len(users)) else: print u'ERROR: %s is not a valid argument for "gam all"' % entity sys.exit(3) elif entity_type == u'cros': users = entity.replace(u',', u' ').split() entity = u'cros' else: print u'ERROR: %s is not a valid argument for "gam"' % entity_type sys.exit(2) full_users = list() if entity != u'cros' and not got_uids: for user in users: if user[:4] == u'uid:': full_users.append(user[4:]) elif user.find(u'@') == -1: full_users.append(u'%s@%s' % (user, GC_Values[GC_DOMAIN])) else: full_users.append(user) else: full_users = users return full_users def OAuthInfo(): if len(sys.argv) > 3: access_token = sys.argv[3] else: storage = oauth2client.file.Storage(GC_Values[GC_OAUTH2_TXT]) credentials = storage.get() if credentials is None or credentials.invalid: doRequestOAuth() credentials = storage.get() 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: print u"Secret: %s" % credentials.client_secret except UnboundLocalError: pass print u'Scopes:' for scope in token_info[u'scope'].split(u' '): print u' %s' % scope try: print u'Google Apps Admin: %s' % token_info[u'email'] except KeyError: print u'Google Apps Admin: Unknown' def doDeleteOAuth(): storage = oauth2client.file.Storage(GC_Values[GC_OAUTH2_TXT]) credentials = storage.get() try: credentials.revoke_uri = oauth2client.GOOGLE_REVOKE_URI except AttributeError: systemErrorExit(1, u'Authorization doesn\'t exist') http = httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL]) sys.stderr.write(u'This OAuth token will self-destruct in 3...') time.sleep(1) sys.stderr.write(u'2...') time.sleep(1) sys.stderr.write(u'1...') time.sleep(1) sys.stderr.write(u'boom!\n') try: credentials.revoke(http) except oauth2client.client.TokenRevokeError as e: stderrErrorMsg(e.message) os.remove(GC_Values[GC_OAUTH2_TXT]) class cmd_flags(object): def __init__(self, noLocalWebserver): self.short_url = True self.noauth_local_webserver = noLocalWebserver self.logging_level = u'ERROR' self.auth_host_name = u'localhost' self.auth_host_port = [8080, 9090] OAUTH2_SCOPES = [ {u'name': u'Group Directory API', u'subscopes': [u'readonly'], u'scopes': u'https://www.googleapis.com/auth/admin.directory.group'}, {u'name': u'Organizational Unit Directory API', u'subscopes': [u'readonly'], u'scopes': u'https://www.googleapis.com/auth/admin.directory.orgunit'}, {u'name': u'Users Directory API', u'subscopes': [u'readonly'], u'scopes': u'https://www.googleapis.com/auth/admin.directory.user'}, {u'name': u'Chrome OS Devices Directory API', u'subscopes': [u'readonly'], u'scopes': u'https://www.googleapis.com/auth/admin.directory.device.chromeos'}, {u'name': u'Mobile Devices Directory API', u'subscopes': [u'readonly', u'action'], u'scopes': u'https://www.googleapis.com/auth/admin.directory.device.mobile'}, {u'name': u'Legacy Email Settings API - Delegation', u'subscopes': [], u'scopes': u'https://apps-apis.google.com/a/feeds/emailsettings/2.0/'}, {u'name': u'Resource Calendar API', u'subscopes': [u'readonly'], u'scopes': u'https://www.googleapis.com/auth/admin.directory.resource.calendar'}, {u'name': u'Group Settings API', u'subscopes': [], u'scopes': u'https://www.googleapis.com/auth/apps.groups.settings'}, {u'name': u'Calendar Data API', u'subscopes': [u'readonly'], u'scopes': u'https://www.googleapis.com/auth/calendar'}, {u'name': u'Audit Reports API', u'subscopes': [], u'scopes': u'https://www.googleapis.com/auth/admin.reports.audit.readonly'}, {u'name': u'Usage Reports API', u'subscopes': [], u'scopes': u'https://www.googleapis.com/auth/admin.reports.usage.readonly'}, {u'name': u'Drive API - create report docs only', u'subscopes': [], u'scopes': u'https://www.googleapis.com/auth/drive.file'}, {u'name': u'License Manager API', u'subscopes': [], u'scopes': u'https://www.googleapis.com/auth/apps.licensing'}, {u'name': u'User Security Directory API', u'subscopes': [], u'scopes': u'https://www.googleapis.com/auth/admin.directory.user.security'}, {u'name': u'Notifications Directory API', u'subscopes': [], u'scopes': u'https://www.googleapis.com/auth/admin.directory.notifications'}, {u'name': u'Site Verification API', u'offByDefault': True, u'subscopes': [], u'scopes': u'https://www.googleapis.com/auth/siteverification'}, {u'name': u'Gmail API - send report docs todrive notifications only', u'subscopes': [], u'scopes': u'https://www.googleapis.com/auth/gmail.send'}, {u'name': u'User Schema Directory API', u'subscopes': [u'readonly'], u'scopes': u'https://www.googleapis.com/auth/admin.directory.userschema'}, {u'name': u'Classroom API - counts as 5 scopes', u'subscopes': [], u'scopes': [u'https://www.googleapis.com/auth/classroom.rosters', u'https://www.googleapis.com/auth/classroom.courses', u'https://www.googleapis.com/auth/classroom.profile.emails', u'https://www.googleapis.com/auth/classroom.profile.photos', u'https://www.googleapis.com/auth/classroom.guardianlinks.students']}, {u'name': u'Cloud Print API', u'subscopes': [], u'scopes': u'https://www.googleapis.com/auth/cloudprint'}, {u'name': u'Data Transfer API', u'subscopes': [u'readonly'], u'scopes': u'https://www.googleapis.com/auth/admin.datatransfer'}, {u'name': u'Customer API', u'subscopes': [u'readonly'], u'scopes': u'https://www.googleapis.com/auth/admin.directory.customer'}, {u'name': u'Domains Directory API', u'subscopes': [u'readonly'], u'scopes': u'https://www.googleapis.com/auth/admin.directory.domain'}, {u'name': u'Roles Directory API', u'subscopes': [u'readonly'], u'scopes': u'https://www.googleapis.com/auth/admin.directory.rolemanagement'}, ] OAUTH2_MENU = u''' Select the authorized scopes by entering a number. Append an 'r' to grant read-only access or an 'a' to grant action-only access. ''' for a_scope in OAUTH2_SCOPES: OAUTH2_MENU += u'[%%%%s] %%2d) %s' % (a_scope[u'name']) if a_scope[u'subscopes']: OAUTH2_MENU += u' (supports %s)' % (u' and '.join(a_scope[u'subscopes'])) OAUTH2_MENU += '\n' OAUTH2_MENU += ''' s) Select all scopes u) Unselect all scopes e) Exit without changes c) Continue to authorization ''' OAUTH2_CMDS = [u's', u'u', u'e', u'c'] MAXIMUM_SCOPES = 28 def doRequestOAuth(): def _checkMakeScopesList(scopes): del scopes[:] for i in range(num_scopes): if selected_scopes[i] == u'*': if not isinstance(OAUTH2_SCOPES[i][u'scopes'], list): scopes.append(OAUTH2_SCOPES[i][u'scopes']) else: scopes += OAUTH2_SCOPES[i][u'scopes'] elif selected_scopes[i] == u'R': scopes.append(u'%s.readonly' % OAUTH2_SCOPES[i][u'scopes']) elif selected_scopes[i] == u'A': scopes.append(u'%s.action' % OAUTH2_SCOPES[i][u'scopes']) if len(scopes) > MAXIMUM_SCOPES: return (False, u'ERROR: {0} scopes selected, maximum is {1}, please unselect some.\n'.format(len(scopes), MAXIMUM_SCOPES)) if len(scopes) == 0: return (False, u'ERROR: No scopes selected, please select at least one.\n') scopes.insert(0, u'email') # Email Display Scope, always included return (True, u'') MISSING_CLIENT_SECRETS_MESSAGE = u"""Please configure OAuth 2.0 To make GAM run you will need to populate the {0} file found at: {1} with information from the APIs Console . See this site for instructions: {2} """.format(FN_CLIENT_SECRETS_JSON, GC_Values[GC_CLIENT_SECRETS_JSON], GAM_WIKI_CREATE_CLIENT_SECRETS) num_scopes = len(OAUTH2_SCOPES) menu = OAUTH2_MENU % tuple(range(num_scopes)) selected_scopes = [] for scope in OAUTH2_SCOPES: if scope.get(u'offByDefault', False): selected_scopes.append(u' ') else: selected_scopes.append(u'*') scopes = [] prompt = u'Please enter 0-{0}[a|r] or {1}: '.format(num_scopes-1, u'|'.join(OAUTH2_CMDS)) message = u'' while True: os.system([u'clear', u'cls'][GM_Globals[GM_WINDOWS]]) if message: sys.stdout.write(message) message = u'' sys.stdout.write(menu % tuple(selected_scopes)) while True: choice = raw_input(prompt) if choice: selection = choice.lower() if selection.find(u'r') >= 0: mode = u'R' selection = selection.replace(u'r', u'') elif selection.find(u'a') >= 0: mode = u'A' selection = selection.replace(u'a', u'') else: mode = u' ' if selection and selection.isdigit(): selection = int(selection) if isinstance(selection, int) and selection < num_scopes: if mode == u'R': if u'readonly' not in OAUTH2_SCOPES[selection][u'subscopes']: sys.stdout.write(u'{0}Scope {1} does not support read-only mode!\n'.format(ERROR_PREFIX, selection)) continue elif mode == u'A': if u'action' not in OAUTH2_SCOPES[selection][u'subscopes']: sys.stdout.write(u'{0}Scope {1} does not support action-only mode!\n'.format(ERROR_PREFIX, selection)) continue elif selected_scopes[selection] != u'*': mode = u'*' else: mode = u' ' selected_scopes[selection] = mode break elif isinstance(selection, str) and selection in OAUTH2_CMDS: if selection == u's': for i in range(num_scopes): selected_scopes[i] = u'*' elif selection == u'u': for i in range(num_scopes): selected_scopes[i] = u' ' elif selection == u'e': return 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 try: FLOW = oauth2client.client.flow_from_clientsecrets(GC_Values[GC_CLIENT_SECRETS_JSON], scope=scopes) except oauth2client.client.clientsecrets.InvalidClientSecretsError: systemErrorExit(14, MISSING_CLIENT_SECRETS_MESSAGE) storage = oauth2client.file.Storage(GC_Values[GC_OAUTH2_TXT]) credentials = storage.get() flags = cmd_flags(noLocalWebserver=GC_Values[GC_NO_BROWSER]) if credentials is None or credentials.invalid: http = httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL]) try: credentials = oauth2client.tools.run_flow(flow=FLOW, storage=storage, flags=flags, http=http) except httplib2.CertificateValidationUnsupported: noPythonSSLExit() def batch_worker(): while True: item = GM_Globals[GM_BATCH_QUEUE].get() subprocess.call(item, stderr=subprocess.STDOUT) GM_Globals[GM_BATCH_QUEUE].task_done() def run_batch(items): import Queue, threading total_items = len(items) current_item = 0 python_cmd = [sys.executable.lower(),] if not getattr(sys, u'frozen', False): # we're not frozen python_cmd.append(os.path.realpath(sys.argv[0])) num_worker_threads = min(total_items, GC_Values[GC_NUM_THREADS]) GM_Globals[GM_BATCH_QUEUE] = Queue.Queue(maxsize=num_worker_threads) # GM_Globals[GM_BATCH_QUEUE].put() gets blocked when trying to create more items than there are workers sys.stderr.write(u'starting %s worker threads...\n' % num_worker_threads) for _ in range(num_worker_threads): t = threading.Thread(target=batch_worker) t.daemon = True t.start() for item in items: current_item += 1 if not current_item % 100: sys.stderr.write(u'starting job %s / %s\n' % (current_item, total_items)) if item[0] == u'commit-batch': sys.stderr.write(u'commit-batch - waiting for running processes to finish before proceeding...') GM_Globals[GM_BATCH_QUEUE].join() sys.stderr.write(u'done with commit-batch\n') continue GM_Globals[GM_BATCH_QUEUE].put(python_cmd+item) GM_Globals[GM_BATCH_QUEUE].join() # # Process command line arguments, find substitutions # An argument containing instances of ~~xxx~~ has xxx replaced by the value of field xxx from the CSV file # An argument containing exactly ~xxx is replaced by the value of field xxx from the CSV file # Otherwise, the argument is preserved as is # # SubFields is a dictionary; the key is the argument number, the value is a list of tuples that mark # the substition (fieldname, start, end). # Example: update user '~User' address type work unstructured '~~Street~~, ~~City~~, ~~State~~ ~~ZIP~~' primary # {2: [('User', 0, 5)], 7: [('Street', 0, 10), ('City', 12, 20), ('State', 22, 31), ('ZIP', 32, 39)]} # def getSubFields(i, fieldNames): subFields = {} PATTERN = re.compile(r'~~(.+?)~~') GAM_argv = [] GAM_argvI = 0 while i < len(sys.argv): myarg = sys.argv[i] if not myarg: GAM_argv.append(myarg) elif PATTERN.search(myarg): pos = 0 while True: match = PATTERN.search(myarg, pos) if not match: break fieldName = match.group(1) if fieldName in fieldNames: subFields.setdefault(GAM_argvI, []) subFields[GAM_argvI].append((fieldName, match.start(), match.end())) else: csvFieldErrorExit(fieldName, fieldNames) pos = match.end() GAM_argv.append(myarg) elif myarg[0] == u'~': fieldName = myarg[1:] if fieldName in fieldNames: subFields[GAM_argvI] = [(fieldName, 0, len(myarg))] GAM_argv.append(myarg) else: csvFieldErrorExit(fieldName, fieldNames) else: GAM_argv.append(myarg.encode(GM_Globals[GM_SYS_ENCODING])) GAM_argvI += 1 i += 1 return(GAM_argv, subFields) # def processSubFields(GAM_argv, row, subFields): argv = GAM_argv[:] for GAM_argvI, fields in subFields.iteritems(): oargv = argv[GAM_argvI][:] argv[GAM_argvI] = u'' pos = 0 for field in fields: argv[GAM_argvI] += oargv[pos:field[1]] if row[field[0]]: argv[GAM_argvI] += row[field[0]] pos = field[2] argv[GAM_argvI] += oargv[pos:] argv[GAM_argvI] = argv[GAM_argvI].encode(GM_Globals[GM_SYS_ENCODING]) return argv # Process GAM command def ProcessGAMCommand(args): if args != sys.argv: sys.argv = args[:] GM_Globals[GM_SYSEXITRC] = 0 try: SetGlobalVariables() command = sys.argv[1].lower() if command == u'batch': import shlex i = 2 filename = sys.argv[i] i, encoding = getCharSet(i+1) f = openFile(filename) batchFile = UTF8Recoder(f, encoding) if encoding != u'utf-8' else f items = [] for line in batchFile: argv = shlex.split(line) if not argv: continue cmd = argv[0].strip().lower() if (not cmd) or cmd.startswith(u'#') or ((len(argv) == 1) and (cmd != u'commit-batch')): continue if cmd == u'gam': items.append([arg.encode(GM_Globals[GM_SYS_ENCODING]) for arg in argv[1:]]) elif cmd == u'commit-batch': items.append([cmd]) else: print u'ERROR: "%s" is not a valid gam command' % line.strip() closeFile(f) run_batch(items) sys.exit(0) elif command == u'csv': if httplib2.debuglevel > 0: print u'Sorry, CSV commands are not compatible with debug. Delete debug.gam and try again.' sys.exit(1) i = 2 filename = sys.argv[i] i, encoding = getCharSet(i+1) f = openFile(filename) csvFile = UnicodeDictReader(f, encoding=encoding) if (i == len(sys.argv)) or (sys.argv[i].lower() != u'gam') or (i+1 == len(sys.argv)): print u'ERROR: "gam csv " must be followed by a full GAM command...' sys.exit(3) i += 1 GAM_argv, subFields = getSubFields(i, csvFile.fieldnames) items = [] for row in csvFile: items.append(processSubFields(GAM_argv, row, subFields)) closeFile(f) run_batch(items) sys.exit(0) elif command == u'version': doGAMVersion() sys.exit(0) elif command == u'create': argument = sys.argv[2].lower() if argument == u'user': doCreateUser() elif argument == u'group': doCreateGroup() elif argument in [u'nickname', u'alias']: doCreateAlias() elif argument in [u'org', u'ou']: doCreateOrg() elif argument == u'resource': doCreateResourceCalendar() elif argument in [u'verify', u'verification']: doSiteVerifyShow() elif argument == u'schema': doCreateOrUpdateUserSchema(False) elif argument in [u'course', u'class']: doCreateCourse() elif argument in [u'transfer', u'datatransfer']: doCreateDataTranfer() elif argument == u'domain': doCreateDomain() elif argument in [u'domainalias', u'aliasdomain']: doCreateDomainAlias() elif argument == u'admin': doCreateAdmin() elif argument in [u'guardianinvite', u'inviteguardian', u'guardian']: doInviteGuardian() else: print u'ERROR: %s is not a valid argument for "gam create"' % argument sys.exit(2) sys.exit(0) elif command == u'update': argument = sys.argv[2].lower() if argument == u'user': doUpdateUser([sys.argv[3],], 4) elif argument == u'group': doUpdateGroup() elif argument in [u'nickname', u'alias']: doUpdateAlias() elif argument in [u'ou', u'org']: doUpdateOrg() elif argument == u'resource': doUpdateResourceCalendar() elif argument == u'cros': doUpdateCros() elif argument == u'mobile': doUpdateMobile() elif argument in [u'notification', u'notifications']: doUpdateNotification() elif argument in [u'verify', u'verification']: doSiteVerifyAttempt() elif argument in [u'schema', u'schemas']: doCreateOrUpdateUserSchema(True) elif argument in [u'course', u'class']: doUpdateCourse() elif argument in [u'printer', u'print']: doUpdatePrinter() elif argument == u'domain': doUpdateDomain() elif argument == u'customer': doUpdateCustomer() else: print u'ERROR: %s is not a valid argument for "gam update"' % argument sys.exit(2) sys.exit(0) elif command == u'info': argument = sys.argv[2].lower() if argument == u'user': doGetUserInfo() elif argument == u'group': doGetGroupInfo() elif argument in [u'nickname', u'alias']: doGetAliasInfo() elif argument == u'instance': doGetCustomerInfo() elif argument in [u'org', u'ou']: doGetOrgInfo() elif argument == u'resource': doGetResourceCalendarInfo() elif argument == u'cros': doGetCrosInfo() elif argument == u'mobile': doGetMobileInfo() elif argument in [u'notifications', u'notification']: doGetNotifications() elif argument in [u'verify', u'verification']: doGetSiteVerifications() elif argument in [u'schema', u'schemas']: doGetUserSchema() elif argument in [u'course', u'class']: doGetCourseInfo() elif argument in [u'printer', u'print']: doGetPrinterInfo() elif argument in [u'transfer', u'datatransfer']: doGetDataTransferInfo() elif argument == u'customer': doGetCustomerInfo() elif argument == u'domain': doGetDomainInfo() elif argument in [u'domainalias', u'aliasdomain']: doGetDomainAliasInfo() else: print u'ERROR: %s is not a valid argument for "gam info"' % argument sys.exit(2) sys.exit(0) elif command == u'delete': argument = sys.argv[2].lower() if argument == u'user': doDeleteUser() elif argument == u'group': doDeleteGroup() elif argument in [u'nickname', u'alias']: doDeleteAlias() elif argument == u'org': doDeleteOrg() elif argument == u'resource': doDeleteResourceCalendar() elif argument == u'mobile': doDeleteMobile() elif argument in [u'notification', u'notifications']: doDeleteNotification() elif argument in [u'schema', u'schemas']: doDelSchema() elif argument in [u'course', u'class']: doDelCourse() elif argument in [u'printer', u'printers']: doDelPrinter() elif argument == u'domain': doDelDomain() elif argument in [u'domainalias', u'aliasdomain']: doDelDomainAlias() elif argument == u'admin': doDelAdmin() elif argument in [u'guardian', u'guardians']: doDeleteGuardian() else: print u'ERROR: %s is not a valid argument for "gam delete"' % argument sys.exit(2) sys.exit(0) elif command == u'undelete': argument = sys.argv[2].lower() if argument == u'user': doUndeleteUser() else: print u'ERROR: %s is not a valid argument for "gam undelete"' % argument sys.exit(2) sys.exit(0) elif command == u'print': argument = sys.argv[2].lower() 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']: doPrintGroupMembers() elif argument in [u'orgs', u'ous']: doPrintOrgs() elif argument == u'resources': doPrintResourceCalendars() elif argument == u'cros': doPrintCrosDevices() elif argument == u'mobile': doPrintMobileDevices() elif argument in [u'license', u'licenses', u'licence', u'licences']: doPrintLicenses() elif argument in [u'token', u'tokens', u'oauth', u'3lo']: printShowTokens(3, None, None, True) elif argument in [u'schema', u'schemas']: doPrintShowUserSchemas(True) elif argument in [u'courses', u'classes']: doPrintCourses() elif argument in [u'course-participants', u'class-participants']: doPrintCourseParticipants() elif argument == u'printers': doPrintPrinters() elif argument == u'printjobs': doPrintPrintJobs() elif argument in [u'transfers', u'datatransfers']: doPrintDataTransfers() elif argument == u'transferapps': doPrintTransferApps() elif argument == u'domains': doPrintDomains() elif argument in [u'domainaliases', u'aliasdomains']: doPrintDomainAliases() elif argument == u'admins': doPrintAdmins() elif argument in [u'roles', u'adminroles']: doPrintAdminRoles() elif argument in [u'guardian', u'guardians']: doPrintShowGuardians(True) else: print u'ERROR: %s is not a valid argument for "gam print"' % argument sys.exit(2) sys.exit(0) elif command == u'show': argument = sys.argv[2].lower() if argument in [u'schema', u'schemas']: doPrintShowUserSchemas(False) elif argument in [u'guardian', u'guardians']: doPrintShowGuardians(False) else: print u'ERROR: %s is not a valid argument for "gam show"' % argument sys.exit(2) sys.exit(0) elif command in [u'oauth', u'oauth2']: argument = sys.argv[2].lower() if argument in [u'request', u'create']: doRequestOAuth() elif argument in [u'info', u'verify']: OAuthInfo() elif argument in [u'delete', u'revoke']: doDeleteOAuth() else: print u'ERROR: %s is not a valid argument for "gam oauth"' % argument sys.exit(2) sys.exit(0) elif command == u'calendar': argument = sys.argv[3].lower() if argument == u'showacl': doCalendarShowACL() elif argument == u'add': doCalendarAddACL() elif argument in [u'del', u'delete']: doCalendarDelACL() elif argument == u'update': doCalendarUpdateACL() elif argument == u'wipe': doCalendarWipeData() elif argument == u'addevent': doCalendarAddEvent() else: print u'ERROR: %s is not a valid argument for "gam calendar"' % argument sys.exit(2) sys.exit(0) elif command == u'printer': argument = sys.argv[3].lower() if argument == u'showacl': doPrinterShowACL() elif argument == u'add': doPrinterAddACL() elif argument in [u'del', u'delete', u'remove']: doPrinterDelACL() elif argument == u'register': doPrinterRegister() else: print u'ERROR: %s is not a valid argument for "gam printer..."' % argument sys.exit(2) sys.exit(0) elif command == u'printjob': argument = sys.argv[3].lower() if argument == u'delete': doDeletePrintJob() elif argument == u'cancel': doCancelPrintJob() elif argument == u'submit': doPrintJobSubmit() elif argument == u'fetch': doPrintJobFetch() elif argument == u'resubmit': doPrintJobResubmit() else: print u'ERROR: %s is not a valid argument for "gam printjob"' % argument sys.exit(2) sys.exit(0) elif command == u'report': showReport() sys.exit(0) elif command == u'whatis': doWhatIs() sys.exit(0) elif command in [u'course', u'class']: argument = sys.argv[3].lower() if argument in [u'add', u'create']: doAddCourseParticipant() elif argument in [u'del', u'delete', u'remove']: doDelCourseParticipant() elif argument == u'sync': doSyncCourseParticipants() else: print u'ERROR: %s is not a valid argument for "gam course"' % argument sys.exit(2) sys.exit(0) users = getUsersToModify() command = sys.argv[3].lower() if command == u'print' and len(sys.argv) == 4: for user in users: print user sys.exit(0) try: if (GC_Values[GC_AUTO_BATCH_MIN] > 0) and (len(users) > GC_Values[GC_AUTO_BATCH_MIN]): items = [] for user in users: items.append([u'user', user] + sys.argv[3:]) run_batch(items) sys.exit(0) except TypeError: pass if command == u'transfer': transferWhat = sys.argv[4].lower() if transferWhat == u'drive': transferDriveFiles(users) elif transferWhat == u'seccals': transferSecCals(users) else: print u'ERROR: %s is not a valid argument for "gam transfer"' % transferWhat sys.exit(2) elif command == u'show': showWhat = sys.argv[4].lower() if showWhat in [u'labels', u'label']: showLabels(users) elif showWhat == u'profile': showProfile(users) elif showWhat == u'calendars': printShowCalendars(users, False) elif showWhat == u'calsettings': showCalSettings(users) elif showWhat == u'drivesettings': printDriveSettings(users) elif showWhat == u'drivefileacl': showDriveFileACL(users) elif showWhat == u'filelist': printDriveFileList(users) elif showWhat == u'filetree': showDriveFileTree(users) elif showWhat == u'fileinfo': showDriveFileInfo(users) elif showWhat == u'filerevisions': showDriveFileRevisions(users) elif showWhat == u'sendas': printShowSendAs(users, False) elif showWhat == u'gmailprofile': showGmailProfile(users) elif showWhat == u'gplusprofile': showGplusProfile(users) elif showWhat in [u'sig', u'signature']: getSignature(users) elif showWhat == u'forward': printShowForward(users, False) elif showWhat in [u'pop', u'pop3']: getPop(users) elif showWhat in [u'imap', u'imap4']: getImap(users) elif showWhat == u'vacation': getVacation(users) elif showWhat in [u'delegate', u'delegates']: printShowDelegates(users, False) elif showWhat in [u'backupcode', u'backupcodes', u'verificationcodes']: doGetBackupCodes(users) elif showWhat in [u'asp', u'asps', u'applicationspecificpasswords']: doGetASPs(users) elif showWhat in [u'token', u'tokens', u'oauth', u'3lo']: printShowTokens(5, u'users', users, False) elif showWhat == u'driveactivity': printDriveActivity(users) elif showWhat in [u'filter', u'filters']: printShowFilters(users, False) elif showWhat in [u'forwardingaddress', u'forwardingaddresses']: printShowForwardingAddresses(users, False) else: print u'ERROR: %s is not a valid argument for "gam show"' % showWhat sys.exit(2) elif command == u'print': printWhat = sys.argv[4].lower() if printWhat == u'calendars': printShowCalendars(users, True) elif printWhat in [u'delegate', u'delegates']: printShowDelegates(users, True) elif printWhat == u'driveactivity': printDriveActivity(users) elif printWhat == u'drivesettings': printDriveSettings(users) elif printWhat == u'filelist': printDriveFileList(users) elif printWhat in [u'filter', u'filters']: printShowFilters(users, True) elif printWhat == u'forward': printShowForward(users, True) elif printWhat in [u'forwardingaddress', u'forwardingaddresses']: printShowForwardingAddresses(users, True) elif printWhat == u'sendas': printShowSendAs(users, True) elif printWhat in [u'token', u'tokens', u'oauth', u'3lo']: printShowTokens(5, u'users', users, True) else: print u'ERROR: %s is not a valid argument for "gam print"' % printWhat sys.exit(2) elif command == u'modify': modifyWhat = sys.argv[4].lower() if modifyWhat in [u'message', u'messages']: doProcessMessages(users, u'modify') else: print u'ERROR: %s is not a valid argument for "gam modify"' % modifyWhat sys.exit(2) elif command == u'trash': trashWhat = sys.argv[4].lower() if trashWhat in [u'message', u'messages']: doProcessMessages(users, u'trash') else: print u'ERROR: %s is not a valid argument for "gam trash"' % trashWhat sys.exit(2) elif command == u'untrash': untrashWhat = sys.argv[4].lower() if untrashWhat in [u'message', u'messages']: doProcessMessages(users, u'untrash') else: print u'ERROR: %s is not a valid argument for "gam untrash"' % untrashWhat sys.exit(2) elif command in [u'delete', u'del']: delWhat = sys.argv[4].lower() if delWhat == u'delegate': deleteDelegate(users) elif delWhat == u'calendar': deleteCalendar(users) elif delWhat == u'label': doDeleteLabel(users) elif delWhat in [u'message', u'messages']: doProcessMessages(users, u'delete') elif delWhat == u'photo': deletePhoto(users) elif delWhat in [u'license', u'licence']: doLicense(users, u'delete') elif delWhat in [u'backupcode', u'backupcodes', u'verificationcodes']: doDelBackupCodes(users) elif delWhat in [u'asp', u'asps', u'applicationspecificpasswords']: doDelASP(users) elif delWhat in [u'token', u'tokens', u'oauth', u'3lo']: doDelTokens(users) elif delWhat in [u'group', u'groups']: deleteUserFromGroups(users) elif delWhat in [u'alias', u'aliases']: doRemoveUsersAliases(users) elif delWhat == u'emptydrivefolders': deleteEmptyDriveFolders(users) elif delWhat == u'drivefile': deleteDriveFile(users) elif delWhat in [u'drivefileacl', u'drivefileacls']: delDriveFileACL(users) elif delWhat in [u'filter', u'filters']: deleteFilters(users) elif delWhat in [u'forwardingaddress', u'forwardingaddresses']: deleteForwardingAddresses(users) elif delWhat == u'sendas': deleteSendAs(users) else: print u'ERROR: %s is not a valid argument for "gam delete"' % delWhat sys.exit(2) elif command == u'add': addWhat = sys.argv[4].lower() if addWhat == u'calendar': addCalendar(users) elif addWhat == u'drivefile': createDriveFile(users) elif addWhat in [u'license', u'licence']: doLicense(users, u'insert') elif addWhat in [u'drivefileacl', u'drivefileacls']: addDriveFileACL(users) elif addWhat in [u'label', u'labels']: doLabel(users, 5) elif addWhat in [u'delegate', u'delegates']: addDelegates(users, 5) elif addWhat in [u'filter', u'filters']: addFilter(users, 5) elif addWhat in [u'forwardingaddress', u'forwardingaddresses']: addForwardingAddresses(users) elif addWhat == u'sendas': addUpdateSendAs(users, 5, True) else: print u'ERROR: %s is not a valid argument for "gam add"' % addWhat sys.exit(2) elif command == u'update': updateWhat = sys.argv[4].lower() if updateWhat == u'calendar': updateCalendar(users) elif updateWhat == u'calattendees': changeCalendarAttendees(users) elif updateWhat == u'photo': doPhoto(users) elif updateWhat in [u'license', u'licence']: doLicense(users, u'patch') elif updateWhat == u'user': doUpdateUser(users, 5) elif updateWhat in [u'backupcode', u'backupcodes', u'verificationcodes']: doGenBackupCodes(users) elif updateWhat == u'drivefile': doUpdateDriveFile(users) elif updateWhat in [u'drivefileacls', u'drivefileacl']: updateDriveFileACL(users) elif updateWhat in [u'label', u'labels']: renameLabels(users) elif updateWhat == u'labelsettings': updateLabels(users) elif updateWhat == u'sendas': addUpdateSendAs(users, 5, False) else: print u'ERROR: %s is not a valid argument for "gam update"' % updateWhat sys.exit(2) elif command in [u'deprov', u'deprovision']: doDeprovUser(users) elif command == u'get': getWhat = sys.argv[4].lower() if getWhat == u'photo': getPhoto(users) elif getWhat == u'drivefile': downloadDriveFile(users) else: print u'ERROR: %s is not a valid argument for "gam get"' % getWhat sys.exit(2) elif command == u'empty': emptyWhat = sys.argv[4].lower() if emptyWhat == u'drivetrash': doEmptyDriveTrash(users) else: print u'ERROR: %s is not a valid argument for "gam empty"' % emptyWhat sys.exit(2) elif command == u'info': infoWhat = sys.argv[4].lower() if infoWhat == u'calendar': infoCalendar(users) elif infoWhat in [u'filter', u'filters']: infoFilters(users) elif infoWhat in [u'forwardingaddress', u'forwardingaddresses']: infoForwardingAddresses(users) elif infoWhat == u'sendas': infoSendAs(users) else: print u'ERROR: %s is not a valid argument for "gam info"' % infoWhat sys.exit(2) elif command == u'profile': doProfile(users) elif command == u'imap': doImap(users) elif command in [u'pop', u'pop3']: doPop(users) elif command == u'sendas': addUpdateSendAs(users, 4, True) elif command == u'label': doLabel(users, 4) elif command == u'filter': addFilter(users, 4) elif command == u'forward': doForward(users) elif command in [u'sig', u'signature']: doSignature(users) elif command == u'vacation': doVacation(users) elif command in [u'delegate', u'delegates']: addDelegates(users, 4) else: print u'ERROR: %s is not a valid argument for "gam"' % command sys.exit(2) except IndexError: showUsage() sys.exit(2) except KeyboardInterrupt: sys.exit(50) except socket.error as e: stderrErrorMsg(e) sys.exit(3) except MemoryError: stderrErrorMsg(MESSAGE_GAM_OUT_OF_MEMORY) sys.exit(99) except SystemExit as e: GM_Globals[GM_SYSEXITRC] = e.code return GM_Globals[GM_SYSEXITRC] def win32_unicode_argv(): from ctypes import POINTER, byref, cdll, c_int, windll from ctypes.wintypes import LPCWSTR, LPWSTR GetCommandLineW = cdll.kernel32.GetCommandLineW GetCommandLineW.argtypes = [] GetCommandLineW.restype = LPCWSTR CommandLineToArgvW = windll.shell32.CommandLineToArgvW CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] CommandLineToArgvW.restype = POINTER(LPWSTR) cmd = GetCommandLineW() argc = c_int(0) argv = CommandLineToArgvW(cmd, byref(argc)) if argc.value > 0: # Remove Python executable and commands if present argc_value = int(argc.value) sys.argv = argv[argc_value-len(sys.argv):argc_value] # Run from command line if __name__ == "__main__": reload(sys) if sys.version_info[:2] != (2, 7): print u'ERROR: GAM requires Python 2.7. You are running %s.%s.%s. Please upgrade your Python version or use one of the binary GAM downloads.' % sys.version_info[:3] sys.exit(5) if hasattr(sys, u'setdefaultencoding'): sys.setdefaultencoding(u'UTF-8') if GM_Globals[GM_WINDOWS]: win32_unicode_argv() # cleanup sys.argv on Windows sys.exit(ProcessGAMCommand(sys.argv))