Ross Scroggs ed48e5ecd9 Version (#297)
* Use string version rather than float; add check to gam version

This lets you have 3.72.0 or 3.72a; current scheme continues to work as
well.

Handle bad data in lastupdatecheck.txt

$ gam version check
GAM 3.72 - http://git.io/gam
Jay Lee <jay0lee@gmail.com>
Python 2.7.12 64-bit final
google-api-python-client 1.5.2
Darwin-15.6.0-x86_64-i386-64bit x86_64
Path: /Users/admin/Documents/GoogleApps/GAM
Version: Check, Current: 3.72, Latest: 3.71
$

Note that latest version at appspot is behind

* Don't allow check argument from showUsage
2016-10-22 11:52:00 -04:00

10383 lines
395 KiB
Python
Executable File

#!/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 <jay0lee@gmail.com>'
__version__ = u'3.72'
__license__ = u'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
import sys, os, time, datetime, random, socket, csv, platform, re, base64, string, codecs, StringIO, subprocess, collections, mimetypes
import 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 <users> 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'''<?xml version="1.0" encoding="utf-8"?>
<atom:entry xmlns:atom="http://www.w3.org/2005/Atom" xmlns:apps="http://schemas.google.com/apps/2006">
<apps:property name="address" value="%s" />
</atom:entry>''' % 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 <users> show delegates"' % sys.argv[i]
sys.exit(2)
emailsettings = buildGAPIObject(u'email-settings')
for user in users:
if user.find(u'@') == -1:
userName = user
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 <users> 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 <users> 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 <users> 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 <users> %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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> %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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> %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 <users> %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 <users> 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 <users> %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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> filter"' % sys.argv[i]
sys.exit(2)
if u'criteria' not in body:
print u'ERROR: you must specify a crtieria <{0}> for "gam <users> 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 <users> 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 <users> %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 <users> 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 <users> 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 <users> 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 <users> %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 <users> %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'<br/>').replace(u'\n', u'<br/>')
else:
signature = getString(i, u'String', emptyOK=True).replace(u'\\n', u'<br/>').replace(u'\n', u'<br/>')
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 <users> 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 <users> 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 <users> vacation"' % sys.argv[i]
sys.exit(2)
if message:
if responseBodyType == u'responseBodyHtml':
message = message.replace(u'\\n', u'<br/>').replace(u'\n', u'<br/>')
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 <users> 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 <users> %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 <users> %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:<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 <https://console.developers.google.com>.
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 <filename>" 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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 <users> 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))