Compare commits

...

34 Commits
v4.90 ... v4.94

Author SHA1 Message Date
Jay Lee
cfd36c2836 GAM 4.94, pull in Ross changes in #1003 2019-08-30 11:45:52 -04:00
Jay Lee
7689ac7bed disable ssl verify on time sync so we know when clock is WAY off 2019-08-30 11:15:56 -04:00
Jay Lee
7a5ba99b36 Even better SA check 2019-08-30 11:02:42 -04:00
Ross Scroggs
8f4a40bc9a Add timeoffset option to gam version (#1002)
* Add timeoffset option to gam version

* Update timeOffset checking
2019-08-29 14:32:45 -04:00
Jay Lee
e3ab846d70 update user passwords 2019-08-27 14:28:04 -04:00
Jay Lee
29db574bc5 Merge branch 'master' of https://github.com/jay0lee/GAM 2019-08-27 10:19:48 -04:00
Jay Lee
4851d5b62f upgrade MUSL 2019-08-27 10:19:33 -04:00
Jay Lee
caef16bdee add email scope to SA, check serviceaccount verifies proper DwD and scopes for token 2019-08-27 10:18:36 -04:00
Ross Scroggs
1a0f9ab66a handle empty recoveryPhone (#999)
* handle empty recoveryPhone

* Test empty recoveryemail/recoveryphone

* Handle autoUpdateExpiration for CrOS
2019-08-21 15:03:05 -04:00
Ross Scroggs
021c3bfb13 Handle more errors with short URL oauth create (#998) 2019-08-18 17:34:55 -04:00
Jay Lee
b3dfa41df6 GAM user agent on short URL 2019-08-15 18:21:54 -04:00
Jay Lee
5f94263db2 Use _createHttpObj() for short URL 2019-08-15 18:15:56 -04:00
Jay Lee
68d8e46b4c force pip upgrades 2019-08-15 14:50:23 -04:00
Jay Lee
ed221b0d7b just use static recovery phone 2019-08-15 13:31:26 -04:00
Jay Lee
1243563cd4 try to make MacOS tr happy 2019-08-15 12:38:47 -04:00
Jay Lee
1170457a39 GAM 4.93, remove *MAX_RESULTS config options 2019-08-15 12:35:10 -04:00
Jay Lee
435ed9f568 use area code 212 always 2019-08-15 12:18:36 -04:00
Jay Lee
81884e48d0 Support recovery email/phone for users 2019-08-15 12:00:36 -04:00
Jay Lee
a7be6d233b confirm API call supports maxResults before checking value 2019-08-15 10:08:03 -04:00
Jay Lee
584ddba1a5 Dynamic maxResults from discovery or exception 2019-08-14 16:11:14 -04:00
Jay Lee
2bc6c8bca0 200 or bust for URL shorten 2019-08-13 08:35:06 -04:00
Jay Lee
fc1e81a01d oauthbrowser.txt, GAM 4.92 2019-08-12 14:02:59 -04:00
Jay Lee
eebfaaf373 finish pyinstaller install 2019-08-12 12:48:01 -04:00
Jay Lee
652223d9bc Pyinstaller windows 64 bit bootloader compile 2019-08-12 12:29:51 -04:00
Jay Lee
e75664fd2e roll Python 3.5 back to Xenial 2019-08-12 12:08:09 -04:00
Jay Lee
556278b216 Use bionic for Python source tests 2019-08-12 11:57:24 -04:00
Jay Lee
f9bfaa98bb fix cros/mobile print 2019-08-12 11:47:44 -04:00
Jay Lee
b6bd2da6ce Short OAuth URLs, make console flow default to reduce issues 2019-08-12 11:00:26 -04:00
Jay Lee
7c36a6b601 Merge branch 'master' of https://github.com/jay0lee/GAM 2019-08-12 10:41:22 -04:00
Ross Scroggs
413924b11a Generalize expression to find group settings values (#994) 2019-08-12 10:41:17 -04:00
Jay Lee
251883dae5 add cros/mobile print tests 2019-08-10 15:46:52 -04:00
Jay Lee
7e4d0da8fb GAM 4.91 2019-08-10 15:39:14 -04:00
Ross Scroggs
3fc2aeed4d Fix group settings (#990)
* Fix group settings

Clean up md5MatchesFile

* Simplify doGetCustomerInfo

Avoid trap when doPrintDomains returned a domain creationTime with no fraction.
Traceback (most recent call last):
  File "gam.py", line 14761, in <module>
  File "gam.py", line 14159, in ProcessGAMCommand
  File "gam.py", line 2053, in doGetDomainInfo
  File "gam.py", line 2088, in doGetCustomerInfo
  File "_strptime.py", line 577, in _strptime_datetime
  File "_strptime.py", line 359, in _strptime
ValueError: time data '2019-04-23 16:14:56' does not match format '%Y-%m-%d %H:%
M:%S.%f'

lansa.co.uk,True,2019-04-23 16:14:13.041000,secondary,
lansa.com.au,True,2019-04-23 16:14:56,secondary
2019-08-10 15:37:44 -04:00
Ross Scroggs
7f4f785f0b Lower default limit for 500 to 200 for devices (#987)
* Lower default limit for 500 to 200 for devices

* Lower limit from 200 to 100 to handle mobile devices
2019-08-05 18:16:46 -04:00
8 changed files with 405 additions and 183 deletions

View File

@@ -5,7 +5,7 @@ env:
- BUILD_PYTHON_VERSION=3.7.4
- BUILD_OPENSSL_VERSION=1.1.1c
- PATCHELF_VERSION=0.9
- MUSL_VERSION=1.1.22
- MUSL_VERSION=1.1.23
- PYINSTALLER_VERSION=3.5
- secure: "FSKvLaiqhKz21SVgAQZI3bSX34Ffyev4l+R2G//QXNDu6UVQcuFsykzw+eZEG7fkhotXr8BMDL7xIkookiL8eLwUtcd/Z95HCjPBBHcmCSQleyvuuJBxdrQ9xldmiGLzMCYiumSH9OH4uJhQ39Yjnjsa8TK+PlTci6a/BTzlYyBSyDYDf7Iv/uhfQPDHL3pNwrQPHf4fL6/jcvo+uaPcv83AVZkNzZjjyoi9Aa+uh9xlbyHg11jp44463qqxoxTdYik3pYuXRBPjknjOGcnFHqn+QOVSdRQoiwbmT8xVuYuCzTv9THhuJ//i5u7s4y3Xyl7u17B3tdm86UlMpQHy/w9EsYaSBPOU4oPNomRtOnTSugh0v9ZBwptP5XfbslII/iA+LQdzTHhchn0W0CRyDqjOMSestWlrsq5NZJtBJTYHbebllOhEI7xbj9tY+re1zFWSPMOPgHJP23ovsdk3hD9OT93AzRHInCx5IxL6QvEgRhAancRuGkf2rGP0g/vX9fQ0Il3rNMSQxHB5CyHUBtUJ9nhU79YkMDZicD0jFMEwjWJO3itAp3ynoLXRgktgQCYUfgc9SpdWKD5SXLCYnSo22JD3D1P6h2EertRHaoKRLb+CRXQC/lM8uh/W+BjA2Xe6Vut2I/72ndjM+10T7E2xk1CFyCH37a5p8cH26Fs="
- secure: "J9380tGLOZWa7dSH1y5Il8T5JQpN6ad81gI6VR1HIU0svpRdjgikyDA7ca2MKYDUYYY9yVSkTV6gCl6iIU/9+SKaYugpP+tkvdGYkC2moJdcTgYM/WOnIK9ExQ3BPhN1neGxJjPTwKo1ft27mtZ2I5vuCiBwIcnKWLnKPyW3PD+mWpfqiLuEzkHoAh6G3jC4qbcCrZDeX/knE+PzqESUEi+8k1G8gYcSDWujba9ypSsqZ8T/MXagGla6l7y2Rz+/KZTJmFHwKAA10V+xPLVqxoiqi4ar66yUqy0BamwRXPcseI+ns3Q+4lUpMqVQ5GlRy7LF1xC8myjmcAexXk0F9hg+CMzewKI8UgmQH/ZJvQZEh8s6mW26+CqA4d3zMQkWaR0WtEtpiuH7AGHCflIqvEQ6UiG7ia3B8iZfW2wl0j/kqx4OuHkS3r0pWKVVIIvCj9Ow2BHP7SpiV1AcUGsVxzwbgTh67fitna3Z3c6Uj8ccQlNr7ZIt1az6Wf3w5njijkLOiBpQSLKunTTCTSge/JzBTKUcie3RE9vzirl58gUxAt36nDtPWnory+RttMZrOkBVbTeSxp+IUe8pNwLFPHABsafXsjkfzBOtFmm+0ZXWt2Rlog5NvlemJfQUWDlsL4g+BSakzN+4sIPKzSauWDHyaEeULY7Uprkil6c5zwo="
@@ -70,7 +70,7 @@ matrix:
- PLATFORM=x86_64
- VMTYPE=build
- os: linux
name: "Linux 64-bit Xenial - Python 3.5 Source Testing"
name: "Linux 64-bit - Python 3.5 Source Testing"
dist: xenial
language: python
python:
@@ -80,8 +80,8 @@ matrix:
- PLATFORM=x86_64
- VMTYPE=test
- os: linux
name: "Linux 64-bit Xenial - Python 3.6 Source Testing"
dist: xenial
name: "Linux 64-bit - Python 3.6 Source Testing"
dist: bionic
language: python
python:
- "3.6"
@@ -90,8 +90,8 @@ matrix:
- PLATFORM=x86_64
- VMTYPE=test
- os: linux
name: "Linux 64-bit Xenial - Python 3.8-dev Source Testing"
dist: xenial
name: "Linux 64-bit - Python 3.8-dev Source Testing"
dist: bionic
language: python
python:
- "3.8-dev"
@@ -100,8 +100,8 @@ matrix:
- PLATFORM=x86_64
- VMTYPE=test
- os: linux
name: "Linux 64-bit Xenial - Python nightly Source Testing"
dist: xenial
name: "Linux 64-bit - Python nightly Source Testing"
dist: bionic
language: python
python:
- "nightly"
@@ -170,13 +170,15 @@ script:
for i in {01..20};
do echo $newbase-bulkuser-$i >> sample.csv;
done; fi
- if [ "$e2e" = true ]; then $gam create user $newuser firstname Travis lastname $jid password random travis.jid $jid; fi
- if [ "$e2e" = true ]; then $gam create user $newuser firstname Travis lastname $jid password random recoveryphone 12125121110 recoveryemail jay0lee@gmail.com travis.jid $jid; fi
- if [ "$e2e" = true ]; then $gam user $gam_user sendemail recipient $newuser subject "test message $newbase" message "Travis test message"; fi
- if [ "$e2e" = true ]; then $gam create group $newgroup name "Travis $jid group" description "This is a description" isarchived true; fi
- if [ "$e2e" = true ]; then $gam user $newuser add license gsuitebusiness; fi
- if [ "$e2e" = true ]; then $gam update group $newgroup add owner $gam_user; fi
- if [ "$e2e" = true ]; then $gam update group $newgroup add member $newuser; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam create user ~~email~~ firstname "Travis Bulk" lastname ~~email~~ travis.jid $jid; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam update user ~~email~~ recoveryphone 12125121110 recoveryemail jay0lee@gmail.com password random; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam update user ~~email~~ recoveryphone "" recoveryemail ""; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam user ~email add license gsuitebusiness; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam user $gam_user sendemail recipient ~~email~~@pdl.jaylee.us subject "test message $newbase" message "Travis test message"; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam update group $newgroup add member ~email; fi
@@ -227,6 +229,8 @@ script:
- if [ "$e2e" = true ]; then $gam user $gam_user show tokens; fi
- if [ "$e2e" = true ]; then $gam delete user $newuser; fi
- if [ "$e2e" = true ]; then $gam print users query "travis.jid=$jid" | $gam csv - gam delete user ~primaryEmail; fi
- if [ "$e2e" = true ]; then $gam print mobile; fi
- if [ "$e2e" = true ]; then $gam print cros allfields nolists; fi
before_deploy:
- export TRAVIS_TAG="preview"

View File

@@ -257,16 +257,20 @@ If an item contains spaces, it should be surrounded by ".
annotatedassetid|assedid|asset|
annotatedlocation|location|
annotateduser|user|
autoupdateexpiration|
bootmode|
cpustatusreports|
devicefiles|
deviceid|
diskvolumereports|
dockmacaddress|
ethernetmacaddress|
ethernetmacaddress0|
firmwareversion|
lastenrollmenttime|
lastsync|
macaddress|
manufacturedate|
meid|
model|
notes|
@@ -521,6 +525,8 @@ If an item contains spaces, it should be surrounded by ".
phones|phone|
posixaccounts|posix|
primaryemail|username|
recoveryemail|
recoveryphone|
relations|relation|
ssh|sshkeys|sshpublickeys|
suspended|
@@ -776,6 +782,8 @@ Specify a collection of Users by directly specifying them or by specifiying item
(note clear|([text_html|text_plain] <String>|(file <FileName> [charset <Charset>])))|
(org|ou|orgunitpath <OrgUnitPath>)
(password random|<Password>)|
(recoveryemail <EmailAddress>)|
(recoveryphone <string>)|
(suspended <Boolean>)|
(<SchemaName>.<FieldName> [multivalued|multivalue|value|multinonempty [type home|other|work|(custom <String>)]] <String>)
<UserMultiAttributes> ::=
@@ -799,7 +807,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
<UserBasicAttribute>|
<UserMultiAttribute>
gam version [check|checkrc|simple|extended] [location <HostName>]
gam version [check|checkrc|simple|extended] [timeoffset] [location <HostName>]
gam help
gam batch <FileName>|- [charset <Charset>]
@@ -874,7 +882,7 @@ gam info resoldsubscriptions <CustomerID> [customer_auth_token <String>]
<ReportsAppList> ::= "<ReportsApp>(,<ReportsApp>)*"
gam report users|user [todrive] [date <Date>] [fulldatarequired all|<ReportsAppList>]
[(user all|<UserItem>)|(orgunit|org|ou <OrgUnitPath>)] [filter|filters <String>] [fields|parameters <String>]
[(user <UserItem>)|(orgunit|org|ou <OrgUnitPath>)] [filter|filters <String>] [fields|parameters <String>]
gam report customers|customer|domain [todrive] [date <Date>] [fulldatarequired all|<ReportsAppList>]
[fields|parameters <String>]
gam report admin|calendar|calendars|drive|docs|doc|groups|group|logins|login|mobile|tokens|token [todrive]
@@ -1295,7 +1303,7 @@ gam <UserTypeEntity> draftemail [recipient|to <EmailAddress>] [from <EmailAddres
gam <UserTypeEntity> importemail [recipient|to <EmailAddress>] [from <EmailAddress>]
[subject <String>] [(message <String>)|(file <FileName> [charset <Charset>])]
[labels <LabelNameList>] (header <String> <String>)*
[deleted] [date <Time>]
[deleted] [date <Time>]
[nevercheckspam] [processforcalendar]
gam <UserTypeEntity> insertemail [recipient|to <EmailAddress>] [from <EmailAddress>]
[subject <String>] [(message <String>)|(file <FileName> [charset <Charset>])]

View File

@@ -106,11 +106,11 @@ google_auth_httplib2.Request.__call__ = _request_with_user_agent(
google_auth_httplib2.AuthorizedHttp.request = _request_with_user_agent(
google_auth_httplib2.AuthorizedHttp.request)
def _createHttpObj(cache=None, override_min_tls=None, override_max_tls=None):
def _createHttpObj(cache=None, timeout=None, override_min_tls=None, override_max_tls=None):
tls_minimum_version = override_min_tls if override_min_tls else GC_Values[GC_TLS_MIN_VERSION]
tls_maximum_version = override_max_tls if override_max_tls else GC_Values[GC_TLS_MAX_VERSION]
return httplib2.Http(ca_certs=GC_Values[GC_CA_FILE], tls_maximum_version=tls_maximum_version, tls_minimum_version=tls_minimum_version,
cache=cache)
cache=cache, timeout=timeout)
def showUsage():
doGAMVersion(checkForArgs=False)
@@ -669,13 +669,8 @@ def SetGlobalVariables():
_getOldEnvVar(GC_CUSTOMER_ID, 'CUSTOMER_ID')
_getOldEnvVar(GC_CHARSET, 'GAM_CHARSET')
_getOldEnvVar(GC_NUM_THREADS, 'GAM_THREADS')
_getOldEnvVar(GC_ACTIVITY_MAX_RESULTS, 'GAM_ACTIVITY_MAX_RESULTS')
_getOldEnvVar(GC_AUTO_BATCH_MIN, 'GAM_AUTOBATCH')
_getOldEnvVar(GC_BATCH_SIZE, 'GAM_BATCH_SIZE')
_getOldEnvVar(GC_DEVICE_MAX_RESULTS, 'GAM_DEVICE_MAX_RESULTS')
_getOldEnvVar(GC_DRIVE_MAX_RESULTS, 'GAM_DRIVE_MAX_RESULTS')
_getOldEnvVar(GC_MEMBER_MAX_RESULTS, 'GAM_MEMBER_MAX_RESULTS')
_getOldEnvVar(GC_USER_MAX_RESULTS, 'GAM_USER_MAX_RESULTS')
_getOldEnvVar(GC_CSV_HEADER_FILTER, 'GAM_CSV_HEADER_FILTER')
_getOldEnvVar(GC_CSV_ROW_FILTER, 'GAM_CSV_ROW_FILTER')
_getOldEnvVar(GC_TLS_MIN_VERSION, 'GAM_TLS_MIN_VERSION')
@@ -683,6 +678,7 @@ def SetGlobalVariables():
_getOldEnvVar(GC_CA_FILE, 'GAM_CA_FILE')
_getOldSignalFile(GC_DEBUG_LEVEL, 'debug.gam', filePresentValue=4, fileAbsentValue=0)
_getOldSignalFile(GC_NO_BROWSER, 'nobrowser.txt')
_getOldSignalFile(GC_OAUTH_BROWSER, 'oauthbrowser.txt')
# _getOldSignalFile(GC_NO_CACHE, u'nocache.txt')
# _getOldSignalFile(GC_CACHE_DISCOVERY_ONLY, u'allcache.txt', filePresentValue=False, fileAbsentValue=True)
_getOldSignalFile(GC_NO_CACHE, 'allcache.txt', filePresentValue=False, fileAbsentValue=True)
@@ -724,6 +720,33 @@ def SetGlobalVariables():
GM_Globals[GM_CACHE_DISCOVERY_ONLY] = False
return True
def getLocalGoogleTimeOffset(testLocation='www.googleapis.com'):
localUTC = datetime.datetime.now(datetime.timezone.utc)
try:
# we disable SSL verify so we can still get time even if clock
# is way off. This could be spoofed / MitM but we'll fail for those
# situations everywhere else but here.
badhttp = _createHttpObj()
badhttp.disable_ssl_certificate_validation = True
googleUTC = dateutil.parser.parse(badhttp.request('https://'+testLocation, 'HEAD')[0]['date'])
offset = abs(localUTC-googleUTC).total_seconds()
days, remainder = divmod(offset, 86400)
hours, remainder = divmod(remainder, 3600)
minutes, seconds = divmod(remainder, 60)
timeoff = []
if days:
timeoff.append('%d days' % days)
if hours:
timeoff.append('%d hours' % hours)
if minutes:
timeoff.append('%d minutes' % minutes)
if seconds:
timeoff.append('%d seconds' % seconds)
nicetime = ', '.join(timeoff)
return (offset, nicetime)
except (httplib2.ServerNotFoundError, RuntimeError, ValueError) as e:
systemErrorExit(4, str(e))
def doGAMCheckForUpdates(forceCheck=False):
def _gamLatestVersionNotAvailable():
@@ -741,8 +764,7 @@ def doGAMCheckForUpdates(forceCheck=False):
return
check_url = GAM_LATEST_RELEASE # latest full release
headers = {'Accept': 'application/vnd.github.v3.text+json'}
simplehttp = _createHttpObj()
simplehttp.timeout = 10
simplehttp = _createHttpObj(timeout=10)
try:
(_, c) = simplehttp.request(check_url, 'GET', headers=headers)
try:
@@ -779,9 +801,7 @@ def doGAMCheckForUpdates(forceCheck=False):
return
def doGAMVersion(checkForArgs=True):
force_check = False
simple = False
extended = False
force_check = extended = simple = timeOffset = False
testLocation = 'www.googleapis.com'
if checkForArgs:
i = 2
@@ -795,6 +815,10 @@ def doGAMVersion(checkForArgs=True):
i += 1
elif myarg == 'extended':
extended = True
timeOffset = True
i += 1
elif myarg == 'timeoffset':
timeOffset = True
i += 1
elif myarg == 'location':
testLocation = sys.argv[i+1]
@@ -809,6 +833,11 @@ def doGAMVersion(checkForArgs=True):
sys.version_info[1], sys.version_info[2], struct.calcsize('P')*8,
sys.version_info[3], googleapiclient.__version__,
platform.platform(), platform.machine(), GM_Globals[GM_GAM_PATH]))
if timeOffset:
offset, nicetime = getLocalGoogleTimeOffset(testLocation)
print('Your computer is %s off from Google\'s time.' % (nicetime))
if offset > MAX_LOCAL_GOOGLE_TIME_OFFSET:
systemErrorExit(4, 'Please fix your system clock.')
if force_check:
doGAMCheckForUpdates(forceCheck=True)
if extended:
@@ -858,7 +887,8 @@ def getSvcAcctCredentials(scopes, act_as):
GM_Globals[GM_OAUTH2SERVICE_JSON_DATA] = json.loads(json_string)
credentials = google.oauth2.service_account.Credentials.from_service_account_info(GM_Globals[GM_OAUTH2SERVICE_JSON_DATA])
credentials = credentials.with_scopes(scopes)
credentials = credentials.with_subject(act_as)
if act_as:
credentials = credentials.with_subject(act_as)
GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID] = GM_Globals[GM_OAUTH2SERVICE_JSON_DATA]['client_id']
return credentials
except (ValueError, KeyError):
@@ -1082,6 +1112,22 @@ def callGAPI(service, function,
except TypeError as e:
systemErrorExit(4, str(e))
def getPageSize(service, function, kwargs):
"""Gets maximum maxResults value for API call. Uses value from discovery if
it exists, otherwise value from MAX_RESULTS_API_EXCEPTIONS, otherwise None"""
method = getattr(service, function)
api_id = method(**kwargs).methodId
for resource in service._rootDesc.get('resources', {}).values():
for a_method in resource.get('methods', {}).values():
if a_method.get('id') == api_id:
if not a_method.get('parameters') or a_method['parameters'].get('pageSize') or not a_method['parameters'].get('maxResults'):
# make sure API call supports maxResults. For now we don't care to
# set pageSize since all known pageSize API calls have
# default pageSize == max pageSize
return
return {'maxResults': a_method['parameters']['maxResults'].get('maximum', MAX_RESULTS_API_EXCEPTIONS.get(api_id, None))}
def callGAPIpages(service, function, items='items',
page_message=None, message_attribute=None,
soft_errors=False, throw_reasons=None, retry_reasons=None,
@@ -1124,6 +1170,10 @@ def callGAPIpages(service, function, items='items',
Returns:
A list of all items received from all paged responses.
"""
if 'maxResults' not in kwargs and 'pageSize' not in kwargs:
page_key = getPageSize(service, function, kwargs)
if page_key:
kwargs.update(page_key)
all_items = []
page_token = None
total_items = 0
@@ -1311,7 +1361,6 @@ def buildGAPIObject(api):
GM_Globals[GM_CURRENT_API_USER] = None
credentials = getValidOauth2TxtCredentials()
credentials.user_agent = GAM_INFO
#http = credentials.authorize(httplib2.Http(cache=GM_Globals[GM_CACHE_DIR]))
http = google_auth_httplib2.AuthorizedHttp(credentials, _createHttpObj(cache=GM_Globals[GM_CACHE_DIR]))
service = getService(api, http)
if GC_Values[GC_DOMAIN]:
@@ -1468,7 +1517,39 @@ def buildGmailGAPIObject(user):
userEmail = convertUIDtoEmailAddress(user)
return (userEmail, buildGAPIServiceObject('gmail', userEmail))
def printPassFail(description, result):
padding = 80 - len(description) - 2
print(' {} {:>{padding}}'.format(description, result, padding=str(padding)))
def doCheckServiceAccount(users):
something_failed = False
print('Computer clock status:')
timeOffset, nicetime = getLocalGoogleTimeOffset()
if timeOffset < MAX_LOCAL_GOOGLE_TIME_OFFSET:
time_status = 'PASS'
else:
time_status = 'FAIL'
something_failed = True
printPassFail('Your computer clock differs from Google by %s' % nicetime, time_status)
oa2 = googleapiclient.discovery.build('oauth2', 'v1', _createHttpObj())
print('Service Account Private Key Authentication:')
# We are explicitly not doing DwD here, just confirming service account can auth
auth_error = ''
try:
credentials = getSvcAcctCredentials([USERINFO_EMAIL_SCOPE], None)
request = google_auth_httplib2.Request(_createHttpObj())
credentials.refresh(request)
sa_token_info = callGAPI(oa2, 'tokeninfo', access_token=credentials.token)
if sa_token_info:
sa_token_result = 'PASS'
else:
sa_token_result = 'FAIL'
something_failed = True
except google.auth.exceptions.RefreshError as e:
sa_token_result = 'FAIL'
something_failed = True
auth_error = str(e.args[0])
printPassFail('Authenticating...%s' % auth_error, sa_token_result)
all_scopes = []
for _, scopes in list(API_SCOPE_MAPPING.items()):
for scope in scopes:
@@ -1476,25 +1557,40 @@ def doCheckServiceAccount(users):
all_scopes.append(scope)
all_scopes.sort()
for user in users:
user = user.lower()
all_scopes_pass = True
print('User: %s' % (user))
oa2 = googleapiclient.discovery.build('oauth2', 'v1', _createHttpObj())
print('Domain-Wide Delegation authentication as %s:' % (user))
for scope in all_scopes:
try:
credentials = getSvcAcctCredentials([scope], user)
request = google_auth_httplib2.Request(_createHttpObj())
credentials.refresh(request)
result = 'PASS'
except (httplib2.ServerNotFoundError, RuntimeError) as e:
systemErrorExit(4, e)
except google.auth.exceptions.RefreshError:
# try with and without email scope
for scopes in [[scope, USERINFO_EMAIL_SCOPE], [scope]]:
try:
credentials = getSvcAcctCredentials(scopes, user)
credentials.refresh(request)
break
except (httplib2.ServerNotFoundError, RuntimeError) as e:
systemErrorExit(4, e)
except google.auth.exceptions.RefreshError:
continue
if credentials.token:
token_info = callGAPI(oa2, 'tokeninfo', access_token=credentials.token)
if scope in token_info.get('scope', '').split(' ') and \
user == token_info.get('email', user).lower():
result = 'PASS'
else:
result = 'FAIL'
all_scopes_pass = False
else:
result = 'FAIL'
all_scopes_pass = False
print(' Scope: {0:60} {1}'.format(scope, result))
printPassFail(scope, result)
service_account = GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID]
if all_scopes_pass:
print('\nAll scopes passed!\nService account %s is fully authorized.' % service_account)
return
user_domain = user[user.find('@')+1:]
# Tack on email scope for more accurate checking
all_scopes.append('https://www.googleapis.com/auth/userinfo.email')
scopes_failed = '''Some scopes failed! Please go to:
https://admin.google.com/%s/AdminHome?#OGX:ManageOauthClients
@@ -2082,13 +2178,14 @@ def doGetCustomerInfo():
# If customer has changed primary domain customerCreationTime is date
# of current primary being added, not customer create date.
# We should also get all domains and use oldest date
domains = doPrintDomains(return_results=True)
oldest = datetime.datetime.strptime(customer_info['customerCreationTime'], '%Y-%m-%dT%H:%M:%S.%fZ')
domains = callGAPIitems(cd.domains(), 'list', 'domains',
customer=GC_Values[GC_CUSTOMER_ID], fields='domains(creationTime)')
for domain in domains:
domain_creation = datetime.datetime.strptime(domain['creationTime'], '%Y-%m-%d %H:%M:%S.%f')
domain_creation = datetime.datetime.fromtimestamp(int(domain['creationTime'])/1000)
if domain_creation < oldest:
oldest = domain_creation
print('Customer Creation Time: %s' % oldest)
print('Customer Creation Time: %s' % oldest.strftime('%Y-%m-%dT%H:%M:%SZ'))
print('Default Language: %s' % customer_info.get('language', 'Unset (defaults to en)'))
if 'postalAddress' in customer_info:
print('Address:')
@@ -2167,7 +2264,7 @@ def doDelDomainAlias():
domainAliasName = sys.argv[3]
callGAPI(cd.domainAliases(), 'delete', customer=GC_Values[GC_CUSTOMER_ID], domainAliasName=domainAliasName)
def doPrintDomains(return_results=False):
def doPrintDomains():
cd = buildGAPIObject('directory')
todrive = False
titles = ['domainName',]
@@ -2208,8 +2305,6 @@ def doPrintDomains(return_results=False):
titles.append(attr)
aliasdomain_attributes[attr] = aliasdomain[attr]
csvRows.append(aliasdomain_attributes)
if return_results:
return csvRows
writeCSVfile(csvRows, titles, 'Domains', todrive)
def doPrintDomainAliases():
@@ -2344,8 +2439,7 @@ def buildRoleIdToNameToIdMap():
cd = buildGAPIObject('directory')
result = callGAPIpages(cd.roles(), 'list', 'items',
customer=GC_Values[GC_CUSTOMER_ID],
fields='nextPageToken,items(roleId,roleName)',
maxResults=100)
fields='nextPageToken,items(roleId,roleName)')
GM_Globals[GM_MAP_ROLE_ID_TO_NAME] = {}
GM_Globals[GM_MAP_ROLE_NAME_TO_ID] = {}
for role in result:
@@ -2376,8 +2470,7 @@ def buildUserIdToNameMap():
cd = buildGAPIObject('directory')
result = callGAPIpages(cd.users(), 'list', 'users',
customer=GC_Values[GC_CUSTOMER_ID],
fields='nextPageToken,users(id,primaryEmail)',
maxResults=GC_Values[GC_USER_MAX_RESULTS])
fields='nextPageToken,users(id,primaryEmail)')
GM_Globals[GM_MAP_USER_ID_TO_NAME] = {}
for user in result:
GM_Globals[GM_MAP_USER_ID_TO_NAME][user['id']] = user['primaryEmail']
@@ -3162,7 +3255,10 @@ def changeCalendarAttendees(users):
continue
page_token = None
while True:
events_page = callGAPI(cal.events(), 'list', calendarId=user, pageToken=page_token, timeMin=start_date, timeMax=end_date, showDeleted=False, showHiddenInvitations=False)
events_page = callGAPI(cal.events(), 'list', calendarId=user,
pageToken=page_token, timeMin=start_date,
timeMax=end_date, showDeleted=False,
showHiddenInvitations=False)
print('Got %s items' % len(events_page.get('items', [])))
for event in events_page.get('items', []):
if event['status'] == 'cancelled':
@@ -3843,8 +3939,8 @@ def doCalendarPrintEvents():
systemErrorExit(2, '%s is not a valid argument for "gam calendar <email> printevents"' % sys.argv[i])
page_message = 'Got %%%%total_items%%%% events for %s' % calendarId
results = callGAPIpages(cal.events(), 'list', 'items', page_message=page_message,
maxResults=2500, calendarId=calendarId,
q=q, showDeleted=showDeleted, showHiddenInvitations=showHiddenInvitations,
calendarId=calendarId, q=q, showDeleted=showDeleted,
showHiddenInvitations=showHiddenInvitations,
timeMin=timeMin, timeMax=timeMax, timeZone=timeZone, updatedMin=updatedMin)
for result in results:
row = {'calendarId': calendarId}
@@ -4309,7 +4405,7 @@ def printDriveActivity(users):
feed = callGAPIpages(activity.activities(), 'list', 'activities',
page_message=page_message, source='drive.google.com', userId='me',
drive_ancestorId=drive_ancestorId, groupingStrategy='none',
drive_fileId=drive_fileId, pageSize=GC_Values[GC_ACTIVITY_MAX_RESULTS])
drive_fileId=drive_fileId)
for item in feed:
addRowTitlesToCSVfile(flatten_json(item['combinedEvent']), csvRows, titles)
writeCSVfile(csvRows, titles, 'Drive Activity', todrive)
@@ -4589,7 +4685,7 @@ def printDriveFileList(users):
page_message = ' Got %%%%total_items%%%% files for %s...\n' % user
feed = callGAPIpages(drive.files(), 'list', 'items',
page_message=page_message, soft_errors=True,
q=query, orderBy=orderBy, fields=fields, maxResults=GC_Values[GC_DRIVE_MAX_RESULTS])
q=query, orderBy=orderBy, fields=fields)
for f_file in feed:
a_file = {'Owner': user}
for attrib in f_file:
@@ -4643,7 +4739,7 @@ def doDriveSearch(drive, query=None, quiet=False):
page_message = None
files = callGAPIpages(drive.files(), 'list', 'items',
page_message=page_message,
q=query, fields='nextPageToken,items(id)', maxResults=GC_Values[GC_DRIVE_MAX_RESULTS])
q=query, fields='nextPageToken,items(id)')
ids = list()
for f_file in files:
ids.append(f_file['id'])
@@ -4763,7 +4859,7 @@ def showDriveFileTree(users):
sys.stderr.write('Getting all files for %s...\n' % user)
page_message = ' Got %%%%total_items%%%% files for %s...\n' % user
feed = callGAPIpages(drive.files(), 'list', 'items', page_message=page_message,
q=query, orderBy=orderBy, fields='items(id,title,parents(id),mimeType),nextPageToken', maxResults=GC_Values[GC_DRIVE_MAX_RESULTS])
q=query, orderBy=orderBy, fields='items(id,title,parents(id),mimeType),nextPageToken')
printDriveFolderContents(feed, root_folder, 0)
def deleteEmptyDriveFolders(users):
@@ -4777,7 +4873,7 @@ def deleteEmptyDriveFolders(users):
sys.stderr.write('Getting folders for %s...\n' % user)
page_message = ' Got %%%%total_items%%%% folders for %s...\n' % user
feed = callGAPIpages(drive.files(), 'list', 'items', page_message=page_message,
q=query, fields='items(title,id),nextPageToken', maxResults=GC_Values[GC_DRIVE_MAX_RESULTS])
q=query, fields='items(title,id),nextPageToken')
deleted_empty = False
for folder in feed:
children = callGAPI(drive.children(), 'list',
@@ -7551,6 +7647,14 @@ def getUserAttributes(i, cd, updateCmd):
keyword['value'] = sys.argv[i]
i += 1
appendItemToBodyList(body, 'keywords', keyword)
elif myarg in ['recoveryemail']:
body['recoveryEmail'] = sys.argv[i+1]
i += 2
elif myarg in ['recoveryphone']:
body['recoveryPhone'] = sys.argv[i+1]
if body['recoveryPhone'] and body['recoveryPhone'][0] != '+':
body['recoveryPhone'] = '+' + body['recoveryPhone']
i += 2
elif myarg == 'clearschema':
if not updateCmd:
systemErrorExit(2, '%s is not a valid create user argument.' % sys.argv[i])
@@ -7605,6 +7709,24 @@ def getUserAttributes(i, cd, updateCmd):
body['hashFunction'] = 'crypt'
return body
class ShortURLFlow(google_auth_oauthlib.flow.InstalledAppFlow):
def authorization_url(self, **kwargs):
long_url, state = super(ShortURLFlow, self).authorization_url(**kwargs)
simplehttp = _createHttpObj(timeout=10)
url_shortnr = 'https://gam-shortn.appspot.com/create'
headers = {'Content-Type': 'application/json',
'user-agent': GAM_INFO}
try:
resp, content = simplehttp.request(url_shortnr, 'POST', '{"long_url": "%s"}' % long_url, headers=headers)
except:
return long_url, state
if resp.status != 200:
return long_url, state
try:
return json.loads(content).get('short_url', long_url), state
except:
return long_url, state
def _run_oauth_flow(client_id, client_secret, scopes, access_type, login_hint=None):
client_config = {
'installed': {
@@ -7616,11 +7738,11 @@ def _run_oauth_flow(client_id, client_secret, scopes, access_type, login_hint=No
}
}
flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_config(client_config, scopes)
flow = ShortURLFlow.from_client_config(client_config, scopes)
kwargs = {'access_type': access_type}
if login_hint:
kwargs['login_hint'] = login_hint
if GC_Values[GC_NO_BROWSER]:
if not GC_Values[GC_OAUTH_BROWSER]:
flow.run_console(
authorization_prompt_message=MESSAGE_CONSOLE_AUTHORIZATION_PROMPT,
authorization_code_message=MESSAGE_CONSOLE_AUTHORIZATION_CODE,
@@ -8431,11 +8553,10 @@ def _getCloudStorageObject(s, bucket, object_, local_file=None, expectedMd5=None
if expectedMd5:
sys.stdout.write('Verifying %s hash...' % expectedMd5)
sys.stdout.flush()
if md5MatchesFile(local_file, expectedMd5):
if md5MatchesFile(local_file, expectedMd5, False):
print('VERIFIED')
return
else:
print('not verified. Downloading again and over-writing...')
print('not verified. Downloading again and over-writing...')
else:
return # nothing to verify, just assume we're good.
print('saving to %s' % local_file)
@@ -8460,19 +8581,18 @@ def _getCloudStorageObject(s, bucket, object_, local_file=None, expectedMd5=None
f = openFile(local_file, 'rb')
sys.stdout.write(' Verifying file hash is %s...' % expectedMd5)
sys.stdout.flush()
if md5MatchesFile(local_file, expectedMd5):
print('VERIFIED')
else:
print('ERROR: actual hash was %s. Exiting on corrupt file.' % actual_hash)
sys.exit(6)
md5MatchesFile(local_file, expectedMd5, True)
print('VERIFIED')
closeFile(f)
def md5MatchesFile(local_file, expected_md5):
def md5MatchesFile(local_file, expected_md5, exitOnError):
f = openFile(local_file, 'rb')
hash_md5 = hashlib.md5()
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
actual_hash = hash_md5.hexdigest()
if exitOnError and actual_hash != expected_md5:
systemErrorExit(6, 'actual hash was %s. Exiting on corrupt file.' % actual_hash)
return actual_hash == expected_md5
def doDownloadCloudStorageBucket():
@@ -8541,11 +8661,8 @@ def doDownloadVaultExport():
expected_hash = s_file['md5Hash']
sys.stdout.write(' Verifying file hash is %s...' % expected_hash)
sys.stdout.flush()
if md5MatchesFile(filename, expected_hash):
print('VERIFIED')
else:
print('ERROR: actual hash was %s. Exiting on corrupt file.' % actual_hash)
sys.exit(6)
md5MatchesFile(filename, expected_hash, True)
print('VERIFIED')
if extractFiles and re.search(r'\.zip$', filename):
extract_nested_zip(filename, targetFolder)
@@ -8869,21 +8986,11 @@ def doCreateUser():
def GroupIsAbuseOrPostmaster(emailAddr):
return emailAddr.startswith('abuse@') or emailAddr.startswith('postmaster@')
def mapCollaborativeACL(myarg, value):
value = value.lower().replace('_', '')
if value in COLLABORATIVE_ACL_CHOICES:
return COLLABORATIVE_ACL_CHOICES[value]
systemErrorExit(3, 'allowed choices for %s are %s, got %s' % (myarg, ', '.join(sorted(COLLABORATIVE_ACL_CHOICES)), value))
GROUP_SETTINGS_LIST_PATTERN = re.compile(r'([A-Z][A-Z_]+[A-Z]?)')
def getGroupAttrValue(myarg, value, gs_object, gs_body, function):
if myarg == 'collaborative':
value = mapCollaborativeACL(myarg, value)
for attrName, attrValue in list(COLLABORATIVE_INBOX_ATTRIBUTES.items()):
if attrValue == 'acl':
gs_body[attrName] = value
else:
gs_body[attrName] = attrValue
return
myarg = 'enablecollaborativeinbox'
for (attrib, params) in list(gs_object['schemas']['Groups']['properties'].items()):
if attrib in ['kind', 'etag', 'email']:
continue
@@ -8905,19 +9012,19 @@ def getGroupAttrValue(myarg, value, gs_object, gs_body, function):
value = value.replace('\\n', '\n')
elif attrib == 'primaryLanguage':
value = LANGUAGE_CODES_MAP.get(value.lower(), value)
elif COLLABORATIVE_INBOX_ATTRIBUTES.get(attrib) == 'acl':
value = mapCollaborativeACL(myarg, value)
elif params['description'].find(value.upper()) != -1: # ugly hack because API wants some values uppercased.
elif attrib in GROUP_SETTINGS_LIST_ATTRIBUTES:
value = value.upper()
elif value.lower() in true_values:
value = 'true'
elif value.lower() in false_values:
value = 'false'
# Another ugly hack because Groups Settings API doesn't have proper enumerator values set in discovery file.
if 'description' in params and params['description'].find('Possible values are: ') != -1:
possible_values = params['description'][params['description'].find('Possible values are: ')+21:].split(' ')
if value not in possible_values:
systemErrorExit(2, 'value for %s must be one of %s. Got %s.' % (attrib, ', '.join(possible_values), value))
possible_values = GROUP_SETTINGS_LIST_PATTERN.findall(params['description'])
if value not in possible_values:
systemErrorExit(2, 'value for %s must be one of %s. Got %s.' % (attrib, ', '.join(possible_values), value))
elif attrib in GROUP_SETTINGS_BOOLEAN_ATTRIBUTES:
value = value.lower()
if value in true_values:
value = 'true'
elif value in false_values:
value = 'false'
else:
systemErrorExit(2, 'value for %s must be true|false. Got %s.' % (attrib, value))
gs_body[attrib] = value
return
systemErrorExit(2, '%s is not a valid argument for "gam %s group"' % (myarg, function))
@@ -9518,7 +9625,7 @@ def doUpdateGroup():
result = callGAPIpages(cd.members(), 'list', 'members',
page_message=page_message,
throw_reasons=GAPI_MEMBERS_THROW_REASONS,
groupKey=group, roles=listRoles, fields=listFields, maxResults=GC_Values[GC_MEMBER_MAX_RESULTS])
groupKey=group, roles=listRoles, fields=listFields)
if not result:
print('Group already has 0 members')
return
@@ -9697,9 +9804,12 @@ def doUpdateMobile():
doit = False
if resourceIds[:6] == 'query:':
query = resourceIds[6:]
fields='nextPageToken,mobiledevices(resourceId,email)'
fields = 'nextPageToken,mobiledevices(resourceId,email)'
page_message = 'Got %%total_items%% mobile devices...\n'
devices = callGAPIpages(cd.mobiledevices(), 'list', page_message=page_message, customerId=GC_Values[GC_CUSTOMER_ID], items='mobiledevices', query=query, fields=fields)
devices = callGAPIpages(cd.mobiledevices(), 'list',
page_message=page_message,
customerId=GC_Values[GC_CUSTOMER_ID],
items='mobiledevices', query=query, fields=fields)
else:
devices = [{'resourceId': resourceIds, 'email': ['not set']}]
doit = True
@@ -10104,9 +10214,13 @@ def doGetUserInfo(user_email=None):
else:
print('Last login time: %s' % user['lastLoginTime'])
if 'orgUnitPath' in user:
print('Google Org Unit Path: %s\n' % user['orgUnitPath'])
print('Google Org Unit Path: %s' % user['orgUnitPath'])
if 'thumbnailPhotoUrl' in user:
print('Photo URL: %s\n' % user['thumbnailPhotoUrl'])
if 'recoveryPhone' in user:
print('Recovery Phone: %s' % user['recoveryPhone'])
if 'recoveryEmail' in user:
print('Recovery Email: %s' % user['recoveryEmail'])
if 'notes' in user:
print('Notes:')
notes = user['notes']
@@ -10344,7 +10458,7 @@ def doGetGroupInfo(group_name=None):
for groupm in groups:
print(' %s: %s' % (groupm['name'], groupm['email']))
if getUsers:
members = callGAPIpages(cd.members(), 'list', 'members', groupKey=group_name, fields='nextPageToken,members(email,id,role,type)', maxResults=GC_Values[GC_MEMBER_MAX_RESULTS])
members = callGAPIpages(cd.members(), 'list', 'members', groupKey=group_name, fields='nextPageToken,members(email,id,role,type)')
print('Members:')
for member in members:
print(' %s: %s (%s)' % (member.get('role', ROLE_MEMBER).lower(), member.get('email', member['id']), member['type'].lower()))
@@ -10483,6 +10597,8 @@ def doGetCrosInfo():
print('CrOS Device: {0} ({1} of {2})'.format(deviceId, i, device_count))
if 'notes' in cros:
cros['notes'] = cros['notes'].replace('\n', '\\n')
if 'autoUpdateExpiration' in cros:
cros['autoUpdateExpiration'] = utils.formatTimestampYMD(cros['autoUpdateExpiration'])
_checkTPMVulnerability(cros)
if guess_aue:
_guessAUE(cros, guessedAUEs)
@@ -10824,7 +10940,7 @@ def doGetOrgInfo(name=None, return_attrib=None):
page_message = 'Got %%total_items%% Users: %%first_item%% - %%last_item%%\n'
users = callGAPIpages(cd.users(), 'list', 'users', page_message=page_message,
message_attribute='primaryEmail', customer=GC_Values[GC_CUSTOMER_ID], query=orgUnitPathQuery(name, checkSuspended),
fields='users(primaryEmail,orgUnitPath),nextPageToken', maxResults=GC_Values[GC_USER_MAX_RESULTS])
fields='users(primaryEmail,orgUnitPath),nextPageToken')
if checkSuspended is None:
print('Users:')
elif not checkSuspended:
@@ -11067,7 +11183,7 @@ def doUndeleteUser():
else:
print('Looking up UID for %s...' % user)
deleted_users = callGAPIpages(cd.users(), 'list', 'users',
customer=GC_Values[GC_CUSTOMER_ID], showDeleted=True, maxResults=GC_Values[GC_USER_MAX_RESULTS])
customer=GC_Values[GC_CUSTOMER_ID], showDeleted=True)
matching_users = list()
for deleted_user in deleted_users:
if str(deleted_user['primaryEmail']).lower() == user:
@@ -11559,7 +11675,7 @@ def doPrintUsers():
all_users = callGAPIpages(cd.users(), 'list', 'users', page_message=page_message,
message_attribute='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])
query=query, projection=projection, customFieldMask=customFieldMask)
for user in all_users:
if email_parts and ('primaryEmail' in user):
user_email = user['primaryEmail']
@@ -11639,11 +11755,14 @@ GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP = {
GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP = {
'allowexternalmembers': 'allowExternalMembers',
'allowgooglecommunication': 'allowGoogleCommunication',
'allowwebposting': 'allowWebPosting',
'archiveonly': 'archiveOnly',
'customfootertext': 'customFooterText',
'customreplyto': 'customReplyTo',
'defaultmessagedenynotificationtext': 'defaultMessageDenyNotificationText',
'enablecollaborativeinbox': 'enableCollaborativeInbox',
'favoriterepliesontop': 'favoriteRepliesOnTop',
'gal': 'includeInGlobalAddressList',
'includecustomfooter': 'includeCustomFooter',
'includeinglobaladdresslist': 'includeInGlobalAddressList',
@@ -11656,16 +11775,33 @@ GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP = {
'showingroupdirectory': 'showInGroupDirectory',
'spammoderationlevel': 'spamModerationLevel',
'whocanadd': 'whoCanAdd',
'whocanapprovemembers': 'whoCanApproveMembers',
'whocanapprovemessages': 'whoCanApproveMessages',
'whocanassigntopics': 'whoCanAssignTopics',
'whocanassistcontent': 'whoCanAssistContent',
'whocanbanusers': 'whoCanBanUsers',
'whocancontactowner': 'whoCanContactOwner',
'whocandeleteanypost': 'whoCanDeleteAnyPost',
'whocandeletetopics': 'whoCanDeleteTopics',
'whocandiscovergroup': 'whoCanDiscoverGroup',
'whocanenterfreeformtags': 'whoCanEnterFreeFormTags',
'whocanhideabuse': 'whoCanHideAbuse',
'whocaninvite': 'whoCanInvite',
'whocanjoin': 'whoCanJoin',
'whocanleavegroup': 'whoCanLeaveGroup',
'whocanlocktopics': 'whoCanLockTopics',
'whocanmaketopicssticky': 'whoCanMakeTopicsSticky',
'whocanmarkduplicate': 'whoCanMarkDuplicate',
'whocanmarkfavoritereplyonanytopic': 'whoCanMarkFavoriteReplyOnAnyTopic',
'whocanmarkfavoritereplyonowntopic': 'whoCanMarkFavoriteReplyOnOwnTopic',
'whocanmarknoresponseneeded': 'whoCanMarkNoResponseNeeded',
'whocanmoderatecontent': 'whoCanModerateContent',
'whocanmoderatemembers': 'whoCanModerateMembers',
'whocanmodifymembers': 'whoCanModifyMembers',
'whocanmodifytagsandcategories': 'whoCanModifyTagsAndCategories',
'whocanmovetopicsin': 'whoCanMoveTopicsIn',
'whocanmovetopicsout': 'whoCanMoveTopicsOut',
'whocanpostannouncements': 'whoCanPostAnnouncements',
'whocanpostmessage': 'whoCanPostMessage',
'whocantaketopics': 'whoCanTakeTopics',
'whocanunassigntopic': 'whoCanUnassignTopic',
@@ -11689,7 +11825,6 @@ def doPrintGroups():
titles = []
csvRows = []
addFieldTitleToCSVfile('email', GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP, cdfieldsList, fieldsTitles, titles)
maxResults = None
roles = []
getSettings = sortHeaders = False
while i < len(sys.argv):
@@ -11710,7 +11845,7 @@ def doPrintGroups():
usemember = None
i += 2
elif myarg == 'maxresults':
maxResults = getInteger(sys.argv[i+1], myarg, minVal=0)
# deprecated argument
i += 2
elif myarg == 'delimiter':
aliasDelimiter = memberDelimiter = sys.argv[i+1]
@@ -11792,8 +11927,7 @@ def doPrintGroups():
entityList = callGAPIpages(cd.groups(), 'list', 'groups',
page_message=page_message, message_attribute='email',
customer=customer, domain=usedomain, userKey=usemember, query=usequery,
fields='nextPageToken,groups({0})'.format(cdfields),
maxResults=maxResults)
fields='nextPageToken,groups({0})'.format(cdfields))
i = 0
count = len(entityList)
for groupEntity in entityList:
@@ -11813,7 +11947,7 @@ def doPrintGroups():
groupMembers = callGAPIpages(cd.members(), 'list', 'members',
page_message=page_message, message_attribute='email',
soft_errors=True,
groupKey=groupEmail, roles=listRoles, fields=listFields, maxResults=GC_Values[GC_MEMBER_MAX_RESULTS])
groupKey=groupEmail, roles=listRoles, fields=listFields)
if members:
membersList = []
membersCount = 0
@@ -12000,7 +12134,7 @@ def doPrintAliases():
page_message = 'Got %%num_items%% Users %%first_item%% - %%last_item%%\n'
all_users = callGAPIpages(cd.users(), 'list', 'users', page_message=page_message,
message_attribute='primaryEmail', customer=GC_Values[GC_CUSTOMER_ID], query=query,
fields='nextPageToken,users({0})'.format(','.join(userFields)), maxResults=GC_Values[GC_USER_MAX_RESULTS])
fields='nextPageToken,users({0})'.format(','.join(userFields)))
for user in all_users:
for alias in user.get('aliases', []):
csvRows.append({'Alias': alias, 'Target': user['primaryEmail'], 'TargetType': 'User'})
@@ -12089,7 +12223,7 @@ def doPrintGroupMembers():
validRoles, listRoles, listFields = _getRoleVerification(','.join(roles), fields)
group_members = callGAPIpages(cd.members(), 'list', 'members',
soft_errors=True,
groupKey=group_email, roles=listRoles, fields=listFields, maxResults=GC_Values[GC_MEMBER_MAX_RESULTS])
groupKey=group_email, roles=listRoles, fields=listFields)
for member in group_members:
if not _checkMemberRoleIsSuspended(member, validRoles, checkSuspended):
continue
@@ -12283,7 +12417,7 @@ def doPrintMobileDevices():
page_message = 'Got %%num_items%% Mobile Devices...\n'
all_mobile = callGAPIpages(cd.mobiledevices(), 'list', 'mobiledevices', page_message=page_message,
customerId=GC_Values[GC_CUSTOMER_ID], query=query, projection=projection, fields=fields,
orderBy=orderBy, sortOrder=sortOrder, maxResults=GC_Values[GC_DEVICE_MAX_RESULTS])
orderBy=orderBy, sortOrder=sortOrder)
for mobile in all_mobile:
row = {}
for attrib in mobile:
@@ -12400,7 +12534,7 @@ def doPrintCrosActivity():
page_message = 'Got %%total_items%% CrOS Devices...\n'
all_cros = callGAPIpages(cd.chromeosdevices(), 'list', 'chromeosdevices', page_message=page_message,
query=query, customerId=GC_Values[GC_CUSTOMER_ID], projection='FULL',
fields=fields, maxResults=GC_Values[GC_DEVICE_MAX_RESULTS], orgUnitPath=orgUnitPath)
fields=fields, orgUnitPath=orgUnitPath)
for cros in all_cros:
row = {}
for attrib in cros:
@@ -12581,7 +12715,7 @@ def doPrintCrosDevices():
page_message = 'Got %%total_items%% CrOS Devices...\n'
all_cros = callGAPIpages(cd.chromeosdevices(), 'list', 'chromeosdevices', page_message=page_message,
query=query, customerId=GC_Values[GC_CUSTOMER_ID], projection=projection, orgUnitPath=orgUnitPath,
orderBy=orderBy, sortOrder=sortOrder, fields=fields, maxResults=GC_Values[GC_DEVICE_MAX_RESULTS])
orderBy=orderBy, sortOrder=sortOrder, fields=fields)
for cros in all_cros:
_checkTPMVulnerability(cros)
if guess_aue:
@@ -12590,6 +12724,8 @@ def doPrintCrosDevices():
for cros in all_cros:
if 'notes' in cros:
cros['notes'] = cros['notes'].replace('\n', '\\n')
if 'autoUpdateExpiration' in cros:
cros['autoUpdateExpiration'] = utils.formatTimestampYMD(cros['autoUpdateExpiration'])
for cpuStatusReport in cros.get('cpuStatusReports', []):
for tempInfo in cpuStatusReport.get('cpuTemperatureInfo', []):
tempInfo['label'] = tempInfo['label'].strip()
@@ -12598,6 +12734,8 @@ def doPrintCrosDevices():
for cros in all_cros:
if 'notes' in cros:
cros['notes'] = cros['notes'].replace('\n', '\\n')
if 'autoUpdateExpiration' in cros:
cros['autoUpdateExpiration'] = utils.formatTimestampYMD(cros['autoUpdateExpiration'])
row = {}
for attrib in cros:
if attrib not in set(['kind', 'etag', 'tpmVersionInfo', 'recentUsers', 'activeTimeRanges',
@@ -12985,7 +13123,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
page_message = 'Got %%%%total_items%%%% %s...' % member_type_message
validRoles, listRoles, listFields = _getRoleVerification(member_type, 'nextPageToken,members(email,id,type,status)')
members = callGAPIpages(cd.members(), 'list', 'members', page_message=page_message,
groupKey=group, roles=listRoles, fields=listFields, maxResults=GC_Values[GC_MEMBER_MAX_RESULTS])
groupKey=group, roles=listRoles, fields=listFields)
users = []
for member in members:
if ((not groupUserMembersOnly) or (member['type'] == 'USER')) and _checkMemberRoleIsSuspended(member, validRoles, checkSuspended):
@@ -13008,7 +13146,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
page_message = 'Got %%total_items%% Users...'
members = callGAPIpages(cd.users(), 'list', 'users', page_message=page_message,
customer=GC_Values[GC_CUSTOMER_ID], fields='nextPageToken,users(primaryEmail,orgUnitPath)',
query=query, maxResults=GC_Values[GC_USER_MAX_RESULTS])
query=query)
ou = ou.lower()
for member in members:
if ou == member.get('orgUnitPath', '').lower():
@@ -13030,7 +13168,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
page_message = 'Got %%total_items%% Users...'
members = callGAPIpages(cd.users(), 'list', 'users', page_message=page_message,
customer=GC_Values[GC_CUSTOMER_ID], fields='nextPageToken,users(primaryEmail)',
query=query, maxResults=GC_Values[GC_USER_MAX_RESULTS])
query=query)
for member in members:
users.append(member['primaryEmail'])
if not silent:
@@ -13049,7 +13187,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
page_message = 'Got %%total_items%% Users...'
members = callGAPIpages(cd.users(), 'list', 'users', page_message=page_message,
customer=GC_Values[GC_CUSTOMER_ID], fields='nextPageToken,users(primaryEmail,suspended)',
query=query, maxResults=GC_Values[GC_USER_MAX_RESULTS])
query=query)
for member in members:
email = member['primaryEmail']
if (checkSuspended is None or checkSuspended == member['suspended']) and email not in usersSet:
@@ -13115,7 +13253,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
page_message = 'Got %%total_items%% Users...'
all_users = callGAPIpages(cd.users(), 'list', 'users', page_message=page_message,
customer=GC_Values[GC_CUSTOMER_ID], query=query,
fields='nextPageToken,users(primaryEmail)', maxResults=GC_Values[GC_USER_MAX_RESULTS])
fields='nextPageToken,users(primaryEmail)')
for member in all_users:
users.append(member['primaryEmail'])
if not silent:
@@ -13125,8 +13263,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
printGettingAllItems('CrOS Devices', None)
page_message = 'Got %%total_items%% CrOS Devices...'
all_cros = callGAPIpages(cd.chromeosdevices(), 'list', 'chromeosdevices', page_message=page_message,
customerId=GC_Values[GC_CUSTOMER_ID], fields='nextPageToken,chromeosdevices(deviceId)',
maxResults=GC_Values[GC_DEVICE_MAX_RESULTS])
customerId=GC_Values[GC_CUSTOMER_ID], fields='nextPageToken,chromeosdevices(deviceId)')
for member in all_cros:
users.append(member['deviceId'])
if not silent:
@@ -13151,7 +13288,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
page_message = 'Got %%total_items%% CrOS Devices...'
members = callGAPIpages(cd.chromeosdevices(), 'list', 'chromeosdevices', page_message=page_message,
customerId=GC_Values[GC_CUSTOMER_ID], fields='nextPageToken,chromeosdevices(deviceId)',
query=query, maxResults=GC_Values[GC_DEVICE_MAX_RESULTS])
query=query)
for member in members:
deviceId = member['deviceId']
if deviceId not in usersSet:

View File

@@ -114,7 +114,6 @@ else
fi
echo "Upgrading pip packages..."
$pip freeze > upgrades.txt
$pip install --upgrade -r upgrades.txt
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U
$pip install --upgrade -r src/requirements.txt
$pip install --upgrade pyinstaller

View File

@@ -65,7 +65,6 @@ cd $whereibelong
export PATH=/usr/local/opt/python/libexec/bin:$PATH
$pip install --upgrade pip
$pip freeze > upgrades.txt
$pip install --upgrade -r upgrades.txt
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U
$pip install --upgrade -r src/requirements.txt
$pip install --upgrade pyinstaller

View File

@@ -21,8 +21,7 @@ until cinst -y wixtoolset; do echo "trying again..."; done
export PATH=$PATH:/c/Python37/scripts
cd $mypath
pip install --upgrade pip
pip freeze > upgrades.txt
pip install --upgrade -r upgrades.txt
pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 pip install -U
pip install --upgrade -r src/requirements.txt
#pip install --upgrade pyinstaller

View File

@@ -21,7 +21,23 @@ until cinst -y wixtoolset; do echo "trying again..."; done
export PATH=$PATH:/c/Python37/scripts
cd $mypath
pip install --upgrade pip
pip freeze > upgrades.txt
pip install --upgrade -r upgrades.txt
pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 pip install -U
pip install --upgrade -r src/requirements.txt
pip install --upgrade pyinstaller
#pip install --upgrade pyinstaller
# Install PyInstaller from source and build bootloader
# to try and avoid getting flagged as malware since
# lots of malware uses PyInstaller default bootloader
# https://stackoverflow.com/questions/53584395/how-to-recompile-the-bootloader-of-pyinstaller
echo "Downloading PyInstaller..."
wget --quiet https://github.com/pyinstaller/pyinstaller/releases/download/v$PYINSTALLER_VERSION/PyInstaller-$PYINSTALLER_VERSION.tar.gz
tar xf PyInstaller-$PYINSTALLER_VERSION.tar.gz
cd PyInstaller-$PYINSTALLER_VERSION/bootloader
echo "bootloader before:"
md5sum ../PyInstaller/bootloader/Windows-64bit/*
/c/python37/python ./waf all --target-arch=64bit
echo "bootloader after:"
md5sum ../PyInstaller/bootloader/Windows-64bit/*
echo "PATH: $PATH"
cd ..
/c/python37/python setup.py install
cd $mypath

View File

@@ -6,15 +6,15 @@ import platform
import re
gam_author = 'Jay Lee <jay0lee@gmail.com>'
gam_version = '4.90'
gam_version = '4.94'
gam_license = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
GAM_URL = 'https://git.io/gam'
GAM_INFO = 'GAM {0} - {1} / {2} / Python {3}.{4}.{5} {6} / {7} {8} /'.format(gam_version, GAM_URL,
gam_author,
sys.version_info[0], sys.version_info[1],
sys.version_info[2], sys.version_info[3],
platform.platform(), platform.machine())
gam_author,
sys.version_info[0], sys.version_info[1],
sys.version_info[2], sys.version_info[3],
platform.platform(), platform.machine())
GAM_RELEASES = 'https://github.com/jay0lee/GAM/releases'
GAM_WIKI = 'https://github.com/jay0lee/GAM/wiki'
@@ -39,6 +39,8 @@ FN_LAST_UPDATE_CHECK_TXT = 'lastupdatecheck.txt'
MY_CUSTOMER = 'my_customer'
# See https://support.google.com/drive/answer/37603
MAX_GOOGLE_SHEET_CELLS = 5000000
MAX_LOCAL_GOOGLE_TIME_OFFSET = 30
SKUS = {
'1010010001': {
'product': '101001', 'aliases': ['identity', 'cloudidentity'], 'displayName': 'Cloud Identity'},
@@ -154,16 +156,18 @@ API_VER_MAPPING = {
'vault': 'v1',
}
USERINFO_EMAIL_SCOPE = 'https://www.googleapis.com/auth/userinfo.email'
API_SCOPE_MAPPING = {
'alertcenter': ['https://www.googleapis.com/auth/apps.alerts',],
'appsactivity': ['https://www.googleapis.com/auth/activity',
'https://www.googleapis.com/auth/drive',],
'https://www.googleapis.com/auth/drive',],
'calendar': ['https://www.googleapis.com/auth/calendar',],
'drive': ['https://www.googleapis.com/auth/drive',],
'drive3': ['https://www.googleapis.com/auth/drive',],
'gmail': ['https://mail.google.com/',
'https://www.googleapis.com/auth/gmail.settings.basic',
'https://www.googleapis.com/auth/gmail.settings.sharing',],
'https://www.googleapis.com/auth/gmail.settings.basic',
'https://www.googleapis.com/auth/gmail.settings.sharing',],
'sheets': ['https://www.googleapis.com/auth/spreadsheets',],
}
@@ -403,7 +407,7 @@ DOCUMENT_FORMATS_MAP = {
'mht': [{'mime': 'message/rfc822', 'ext': 'mht'}],
'odp': [{'mime': 'application/vnd.oasis.opendocument.presentation', 'ext': '.odp'}],
'ods': [{'mime': 'application/x-vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'},
{'mime': 'application/vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'}],
{'mime': 'application/vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'}],
'odt': [{'mime': 'application/vnd.oasis.opendocument.text', 'ext': '.odt'}],
'pdf': [{'mime': 'application/pdf', 'ext': '.pdf'}],
'png': [{'mime': 'image/png', 'ext': '.png'}],
@@ -414,7 +418,7 @@ DOCUMENT_FORMATS_MAP = {
'rtf': [{'mime': 'application/rtf', 'ext': '.rtf'}],
'svg': [{'mime': 'image/svg+xml', 'ext': '.svg'}],
'tsv': [{'mime': 'text/tab-separated-values', 'ext': '.tsv'},
{'mime': 'text/tsv', 'ext': '.tsv'}],
{'mime': 'text/tsv', 'ext': '.tsv'}],
'txt': [{'mime': 'text/plain', 'ext': '.txt'}],
'xls': [{'mime': 'application/vnd.ms-excel', 'ext': '.xls'}],
'xlt': [{'mime': 'application/vnd.ms-excel', 'ext': '.xlt'}],
@@ -425,9 +429,9 @@ DOCUMENT_FORMATS_MAP = {
'microsoft': _MICROSOFT_FORMATS_LIST,
'micro$oft': _MICROSOFT_FORMATS_LIST,
'openoffice': [{'mime': 'application/vnd.oasis.opendocument.presentation', 'ext': '.odp'},
{'mime': 'application/x-vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'},
{'mime': 'application/vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'},
{'mime': 'application/vnd.oasis.opendocument.text', 'ext': '.odt'}],
{'mime': 'application/x-vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'},
{'mime': 'application/vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'},
{'mime': 'application/vnd.oasis.opendocument.text', 'ext': '.odt'}],
}
DNS_ERROR_CODES_MAP = {
@@ -534,17 +538,21 @@ CROS_ARGUMENT_TO_PROPERTY_MAP = {
'annotateduser': ['annotatedUser',],
'asset': ['annotatedAssetId',],
'assetid': ['annotatedAssetId',],
'autoupdateexpiration': ['autoUpdateExpiration',],
'bootmode': ['bootMode',],
'cpustatusreports': ['cpuStatusReports',],
'devicefiles': ['deviceFiles',],
'deviceid': ['deviceId',],
'dockmacaddress': ['dockMacAddress',],
'diskvolumereports': ['diskVolumeReports',],
'ethernetmacaddress': ['ethernetMacAddress',],
'ethernetmacaddress0': ['ethernetMacAddress0',],
'firmwareversion': ['firmwareVersion',],
'lastenrollmenttime': ['lastEnrollmentTime',],
'lastsync': ['lastSync',],
'location': ['annotatedLocation',],
'macaddress': ['macAddress',],
'manufacturedate': ['manufactureDate',],
'meid': ['meid',],
'model': ['model',],
'notes': ['notes',],
@@ -586,12 +594,16 @@ CROS_SCALAR_PROPERTY_PRINT_ORDER = [
'osVersion',
'bootMode',
'meid',
'dockMacAddress',
'ethernetMacAddress',
'ethernetMacAddress0',
'macAddress',
'systemRamTotal',
'lastEnrollmentTime',
'orderNumber',
'manufactureDate',
'supportEndDate',
'autoUpdateExpiration',
'guessedAUEDate',
'guessedAUEModel',
'tpmVersionInfo',
@@ -612,28 +624,76 @@ CROS_END_ARGUMENTS = ['end', 'enddate']
CROS_TPM_VULN_VERSIONS = ['41f', '420', '628', '8520',]
CROS_TPM_FIXED_VERSIONS = ['422', '62b', '8521',]
COLLABORATIVE_ACL_CHOICES = {
'members': 'ALL_MEMBERS',
'managersonly': 'MANAGERS_ONLY',
'managers': 'OWNERS_AND_MANAGERS',
'owners': 'OWNERS_ONLY',
'none': 'NONE',
}
COLLABORATIVE_INBOX_ATTRIBUTES = [
'whoCanAddReferences',
'whoCanAssignTopics',
'whoCanEnterFreeFormTags',
'whoCanMarkDuplicate',
'whoCanMarkFavoriteReplyOnAnyTopic',
'whoCanMarkFavoriteReplyOnOwnTopic',
'whoCanMarkNoResponseNeeded',
'whoCanModifyTagsAndCategories',
'whoCanTakeTopics',
'whoCanUnassignTopic',
'whoCanUnmarkFavoriteReplyOnAnyTopic',
'favoriteRepliesOnTop',
]
COLLABORATIVE_INBOX_ATTRIBUTES = {
'whoCanAddReferences': 'acl',
'whoCanAssignTopics': 'acl',
'whoCanEnterFreeFormTags': 'acl',
'whoCanMarkDuplicate': 'acl',
'whoCanMarkFavoriteReplyOnAnyTopic': 'acl',
'whoCanMarkFavoriteReplyOnOwnTopic': 'acl',
'whoCanMarkNoResponseNeeded': 'acl',
'whoCanModifyTagsAndCategories': 'acl',
'whoCanTakeTopics': 'acl',
'whoCanUnassignTopic': 'acl',
'whoCanUnmarkFavoriteReplyOnAnyTopic': 'acl',
'favoriteRepliesOnTop': True,
}
GROUP_SETTINGS_LIST_ATTRIBUTES = set([
# ACL choices
'whoCanAdd',
'whoCanApproveMembers',
'whoCanApproveMessages',
'whoCanAssignTopics',
'whoCanAssistContent',
'whoCanBanUsers',
'whoCanContactOwner',
'whoCanDeleteAnyPost',
'whoCanDeleteTopics',
'whoCanDiscoverGroup',
'whoCanEnterFreeFormTags',
'whoCanHideAbuse',
'whoCanInvite',
'whoCanJoin',
'whoCanLeaveGroup',
'whoCanLockTopics',
'whoCanMakeTopicsSticky',
'whoCanMarkDuplicate',
'whoCanMarkFavoriteReplyOnAnyTopic',
'whoCanMarkFavoriteReplyOnOwnTopic',
'whoCanMarkNoResponseNeeded',
'whoCanModerateContent',
'whoCanModerateMembers',
'whoCanModifyMembers',
'whoCanModifyTagsAndCategories',
'whoCanMoveTopicsIn',
'whoCanMoveTopicsOut',
'whoCanPostAnnouncements',
'whoCanPostMessage',
'whoCanTakeTopics',
'whoCanUnassignTopic',
'whoCanUnmarkFavoriteReplyOnAnyTopic',
'whoCanViewGroup',
'whoCanViewMembership',
# Miscellaneous hoices
'messageModerationLevel',
'replyTo',
'spamModerationLevel',
])
GROUP_SETTINGS_BOOLEAN_ATTRIBUTES = set([
'allowExternalMembers',
'allowGoogleCommunication',
'allowWebPosting',
'archiveOnly',
'enableCollaborativeInbox',
'favoriteRepliesOnTop',
'includeCustomFooter',
'includeInGlobalAddressList',
'isArchived',
'membersCanPostAsTheGroup',
'sendMessageDenyNotification',
'showInGroupDirectory',
])
#
# Global variables
@@ -709,8 +769,6 @@ GM_Globals = {
#
# 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 = '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 = 'auto_batch_min'
@@ -732,19 +790,15 @@ GC_CUSTOMER_ID = 'customer_id'
GC_DEBUG_LEVEL = 'debug_level'
# ID Token decoded from OAuth 2.0 refresh token response. Includes hd (domain) and email of authorized user
GC_DECODED_ID_TOKEN = 'decoded_id_token'
# When retrieving lists of ChromeOS/Mobile devices from API, how many should be retrieved in each chunk
GC_DEVICE_MAX_RESULTS = 'device_max_results'
# Domain obtained from gam.cfg or oauth2.txt
GC_DOMAIN = 'domain'
# Google Drive download directory
GC_DRIVE_DIR = 'drive_dir'
# When retrieving lists of Drive files/folders from API, how many should be retrieved in each chunk
GC_DRIVE_MAX_RESULTS = 'drive_max_results'
# When retrieving lists of Google Group members from API, how many should be retrieved in each chunk
GC_MEMBER_MAX_RESULTS = 'member_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 = 'no_browser'
# oauth_browser forces usage of web server OAuth flow that proved problematic.
GC_OAUTH_BROWSER = 'oauth_browser'
# Disable GAM API caching
GC_NO_CACHE = 'no_cache'
# Disable GAM update check
@@ -763,8 +817,6 @@ GC_SHOW_COUNTS_MIN = 'show_counts_min'
GC_SHOW_GETTINGS = 'show_gettings'
# GAM config directory containing json discovery files
GC_SITE_DIR = 'site_dir'
# When retrieving lists of Users from API, how many should be retrieved in each chunk
GC_USER_MAX_RESULTS = 'user_max_results'
# CSV Columns GAM should show on CSV output
GC_CSV_HEADER_FILTER = 'csv_header_filter'
# CSV Rows GAM should filter
@@ -778,7 +830,6 @@ GC_CA_FILE = 'ca_file'
tls_min = "TLSv1_2" if hasattr(ssl.SSLContext(), "minimum_version") else None
GC_Defaults = {
GC_ACTIVITY_MAX_RESULTS: 100,
GC_AUTO_BATCH_MIN: 0,
GC_BATCH_SIZE: 50,
GC_CACHE_DIR: '',
@@ -789,22 +840,19 @@ GC_Defaults = {
GC_CUSTOMER_ID: MY_CUSTOMER,
GC_DEBUG_LEVEL: 0,
GC_DECODED_ID_TOKEN: '',
GC_DEVICE_MAX_RESULTS: 500,
GC_DOMAIN: '',
GC_DRIVE_DIR: '',
GC_DRIVE_MAX_RESULTS: 1000,
GC_MEMBER_MAX_RESULTS: 200,
GC_NO_BROWSER: False,
GC_NO_CACHE: False,
GC_NO_UPDATE_CHECK: False,
GC_NUM_THREADS: 25,
GC_OAUTH_BROWSER: False,
GC_OAUTH2_TXT: _FN_OAUTH2_TXT,
GC_OAUTH2SERVICE_JSON: _FN_OAUTH2SERVICE_JSON,
GC_SECTION: '',
GC_SHOW_COUNTS_MIN: 0,
GC_SHOW_GETTINGS: True,
GC_SITE_DIR: '',
GC_USER_MAX_RESULTS: 500,
GC_CSV_HEADER_FILTER: '',
GC_CSV_ROW_FILTER: '',
GC_TLS_MIN_VERSION: tls_min,
@@ -829,7 +877,6 @@ GC_VAR_TYPE = 'type'
GC_VAR_LIMITS = '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},
@@ -840,22 +887,19 @@ GC_VAR_INFO = {
GC_CUSTOMER_ID: {GC_VAR_TYPE: GC_TYPE_STRING},
GC_DEBUG_LEVEL: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (0, None)},
GC_DECODED_ID_TOKEN: {GC_VAR_TYPE: GC_TYPE_STRING},
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_MEMBER_MAX_RESULTS: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (1, 10000)},
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_NUM_THREADS: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (1, None)},
GC_OAUTH_BROWSER: {GC_VAR_TYPE: GC_TYPE_BOOLEAN},
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)},
GC_CSV_HEADER_FILTER: {GC_VAR_TYPE: GC_TYPE_HEADERFILTER},
GC_CSV_ROW_FILTER: {GC_VAR_TYPE: GC_TYPE_ROWFILTER},
GC_TLS_MIN_VERSION: {GC_VAR_TYPE: GC_TYPE_STRING},
@@ -1151,3 +1195,19 @@ LANGUAGE_CODES_MAP = {
'ur': 'ur', 'uz': 'uz', 'vi': 'vi', 'wo': 'wo', 'xh': 'xh', 'yi': 'yi', 'yo': 'yo', #Urdu, Uzbek, Vietnamese, Wolof, Xhosa, Yiddish, Yoruba
'zh-cn': 'zh-CN', 'zh-hk': 'zh-HK', 'zh-tw': 'zh-TW', 'zu': 'zu', #Chinese (Simplified), Chinese (Hong Kong/Traditional), Chinese (Taiwan/Traditional), Zulu
}
# maxResults exception values for API list calls. Should only be listed if:
# - discovery doc does not specify maximum value (we use maximum value if it
# exists, not this)
# - actual max API returns with maxResults=<bigNum> > default API returns
# when maxResults isn't specified (we should use default otherwise by not
# setting maxResults)
MAX_RESULTS_API_EXCEPTIONS = {
'calendar.acl.list': 250,
'calendar.calendarList.list': 250,
'calendar.events.list': 2500,
'calendar.settings.list': 250,
'directory.chromeosdevices.list': 200,
'drive.files.list': 1000,
}