mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-07 15:51:38 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35c1e44568 | ||
|
|
5cc68247a3 | ||
|
|
906ee82417 | ||
|
|
3d13d4afd8 | ||
|
|
9d68ce1b46 | ||
|
|
bd0ba995e5 | ||
|
|
0ab4b6d5cd | ||
|
|
163433f15a | ||
|
|
3d6219b551 | ||
|
|
99e363b5d6 | ||
|
|
ed03da815f | ||
|
|
ef1a40afa8 | ||
|
|
cd56f353d8 | ||
|
|
3924722f1c | ||
|
|
3ce48a95c9 | ||
|
|
2dafbfbcfc | ||
|
|
e03086866a | ||
|
|
0422bf22ea | ||
|
|
f3d9f3d518 | ||
|
|
ea9fd3f363 | ||
|
|
bed9db37ad | ||
|
|
072dc4809a | ||
|
|
6db2309fc4 | ||
|
|
cbb0c81652 | ||
|
|
f68aca8361 | ||
|
|
d63fdb4ed9 | ||
|
|
226781766b | ||
|
|
434e30d57c | ||
|
|
2ab059926b | ||
|
|
5ae25495f7 | ||
|
|
20e226e57d | ||
|
|
b4677585bb | ||
|
|
3a1437872c | ||
|
|
602dce2f5a | ||
|
|
8ce930f01b |
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -126,16 +126,16 @@ jobs:
|
||||
name: Test Python 3.12
|
||||
- os: ubuntu-24.04
|
||||
goal: test
|
||||
python: "3.14-dev"
|
||||
python: "3.15-dev"
|
||||
freethreaded: false
|
||||
jid: 17
|
||||
name: Test Python 3.14-dev
|
||||
name: Test Python 3.15-dev
|
||||
- os: ubuntu-24.04
|
||||
goal: test
|
||||
python: "3.14-dev"
|
||||
python: "3.14"
|
||||
freethreaded: true
|
||||
jid: 18
|
||||
name: Test Python 3.14-dev freethread
|
||||
name: Test Python 3.14 freethread
|
||||
|
||||
steps:
|
||||
|
||||
@@ -158,7 +158,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
cache.tar.xz
|
||||
key: gam-${{ matrix.jid }}-20251002
|
||||
key: gam-${{ matrix.jid }}-20251007
|
||||
|
||||
- name: Untar Cache archive
|
||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
|
||||
|
||||
@@ -27,7 +27,7 @@ dependencies = [
|
||||
]
|
||||
description = "CLI tool to manage Google Workspace"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
requires-python = ">=3.10"
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
|
||||
@@ -368,6 +368,7 @@ If an item contains spaces, it should be surrounded by ".
|
||||
## Named items
|
||||
|
||||
<AccessToken> ::= <String>
|
||||
<AdminAssigneeType> ::= group|user|serviceaccount|unknown
|
||||
<AlertID> ::= <String>
|
||||
<APIScopeURL> ::= <String>
|
||||
<APPID> ::= <String>
|
||||
@@ -691,6 +692,7 @@ If an item contains spaces, it should be surrounded by ".
|
||||
|
||||
## Lists of basic items
|
||||
|
||||
<AdminAssigneeTypeList> ::= "<AdminAssigneeType>(,<AdminAssigneeType>)*"
|
||||
<APIScopeURLList> ::= "<APIScopeURL>(,<APIScopeURL>)*"
|
||||
<ASPIDList> ::= "<ASPID>(,<ASPID>)*"
|
||||
<AssetTagList> ::= "<AssetTag>(,<AssetTag>)*"
|
||||
@@ -1551,11 +1553,16 @@ gam create|add admin <EmailAddress>|<UniqueID> <RoleItem> customer|(org_unit <Or
|
||||
[condition securitygroup|nonsecuritygroup]
|
||||
gam delete admin <RoleAssignmentId>
|
||||
|
||||
<AdminAssigneeType> ::= group|user|serviceaccount|unknown
|
||||
<AdminAssigneeTypeList> ::= "<AdminAssigneeType>(,<AdminAssigneeType>)*"
|
||||
|
||||
gam print admins [todrive <ToDriveAttribute>*]
|
||||
[user|group <EmailAddress>|<UniqueID>] [role <RoleItem>]
|
||||
[types <AdminAssigneeTypeList>]
|
||||
[recursive] [condition] [privileges] [oneitemperrow]
|
||||
gam show admins
|
||||
[user|group <EmailAddress>|<UniqueID>] [role <RoleItem>]
|
||||
[types <AdminAssigneeTypeList>]
|
||||
[recursive] [condition] [privileges]
|
||||
|
||||
# Alert Center
|
||||
|
||||
@@ -1,7 +1,76 @@
|
||||
7.26.00
|
||||
|
||||
Added `debug_redaction` Boolean variable to `gam.cfg`. When True, the default,
|
||||
sensitive data like access/refresh tokens, client secret and authorization codes
|
||||
are redacted from debug output. This allows you to post debug output without
|
||||
compromising your account information. Even with debug redaction,
|
||||
anything shared publicly should be double-checked for sensitive content.
|
||||
|
||||
7.25.01
|
||||
|
||||
Fixed bug in `gam config timezone <String>` to handle timezone abbreviations correctly;
|
||||
they were incorrectly shifted to lowercase.
|
||||
|
||||
7.25.00
|
||||
|
||||
Removed a capabilty added in 7.24.00 that allowed reading command data from Google Docs and Sheets
|
||||
when a user's service account access to Drive and Sheets had been disabled. Jay was concerned
|
||||
that this change could be exploited to give access to all user's files.
|
||||
|
||||
This capability has been replaced by issuing the following commands. The admin specified in `gam oauth create`
|
||||
can read command data from Docs and Sheets to which it has access.
|
||||
```
|
||||
gam config commanddata_clientaccess true save
|
||||
gam oauth create
|
||||
Enable the following and proceed to authorization.
|
||||
|
||||
[*] 42) Drive API - commanddata_clientaccess
|
||||
[*] 54) Sheets API - commanddata_clientaccess
|
||||
```
|
||||
|
||||
Fixed in bug in `gam report` that caused a trap with either of the `thismonth` or `previousmonths` options were used.
|
||||
|
||||
Upgraded to Python 3.14.0.
|
||||
|
||||
7.24.01
|
||||
|
||||
Updated GAM to handle the following error that occurs when GAM tries to authenticate
|
||||
as a user that has been disabled by Google.
|
||||
```
|
||||
ERROR: Authentication Token Error - invalid_account: Forbidden
|
||||
```
|
||||
|
||||
7.24.00
|
||||
|
||||
If you want to disable a user's service account access to Drive and Sheets but still allow reading command data from Google Docs and Sheets,
|
||||
issue the following command and make these settings:
|
||||
```
|
||||
gam user user@domain.com update serviceaccount
|
||||
|
||||
[ ] 20) Drive API (supports readonly)
|
||||
[*] 21) Drive API - read command data
|
||||
[ ] 42) Sheets API (supports readonly)
|
||||
[*] 43) Sheets API - read command data
|
||||
```
|
||||
|
||||
7.23.07
|
||||
|
||||
Fixed bug in `gam print|show admins` where all admin assignments were not displayed when
|
||||
`types <AdminAssigneeTypeList>` was not specified, i.e., all assignments should be displayed.
|
||||
|
||||
7.23.06
|
||||
|
||||
Added option `types <AdminAssigneeTypeList>` to `gam print|show admins` that allows filtering
|
||||
of admin assignments by the type of the assignee; by default, all assignee types are displayed.
|
||||
```
|
||||
<AdminAssigneeType> ::= group|user|serviceaccount|unknown
|
||||
<AdminAssigneeTypeList> ::= "<AdminAssigneeType>(,<AdminAssigneeType>)*"
|
||||
```
|
||||
|
||||
7.23.05
|
||||
|
||||
Added option `recursive` to `gam print|show admins` that will display assignments to the members
|
||||
of security groups assigned to roles; the security group membershop is recursively expanded.
|
||||
Added option `recursive` that will display assignments to the members
|
||||
of security groups assigned to roles; the security group membership is recursively expanded.
|
||||
|
||||
7.23.04
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
|
||||
"""
|
||||
|
||||
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
|
||||
__version__ = '7.23.05'
|
||||
__version__ = '7.26.00'
|
||||
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
#pylint: disable=wrong-import-position
|
||||
@@ -49,7 +49,7 @@ from email.policy import SMTP as policySMTP
|
||||
import hashlib
|
||||
from html.entities import name2codepoint
|
||||
from html.parser import HTMLParser
|
||||
import http.client as http_client
|
||||
import http.client
|
||||
import importlib
|
||||
from importlib.metadata import version as lib_version
|
||||
import io
|
||||
@@ -375,6 +375,37 @@ YUBIKEY_VALUE_ERROR_RC = 85
|
||||
YUBIKEY_MULTIPLE_CONNECTED_RC = 86
|
||||
YUBIKEY_NOT_FOUND_RC = 87
|
||||
|
||||
DEBUG_REDACTION_PATTERNS = [
|
||||
# Positional patterns that redact sensitive credentials based on their location
|
||||
(r'(Bearer\s+)\S+', r'\1*****'), # access tokens and JWTs in auth header
|
||||
(r'([?&]refresh_token=)[^&]*', r'\1*****'), # refresh token URL parameter
|
||||
(r'([?&]client_secret=)[^&]*', r'\1*****'), # client secret URL parameter
|
||||
(r'([?&]key=)[^&]*', r'\1*****'), # API key URL parameter
|
||||
(r'([?&]code=)[^&]*', r'\1*****'), # auth code URL parameter
|
||||
|
||||
# Pattern match patterns that redact sensitive credentials based on known credential pattern
|
||||
(r'ya29.[0-9A-Za-z-_]+', '*****'), # Access token
|
||||
(r'1%2F%2F[0-9A-Za-z-_]{100}|1%2F%2F[0-9A-Za-z-_]{64}|1%2F%2F[0-9A-Za-z-_]{43}', '*****'), # Refresh token
|
||||
(r'4/[0-9A-Za-z-_]+', '*****'), # Auth code
|
||||
(r'GOCSPX-[0-9a-zA-Z-_]{28}', '*****'), # Client secret
|
||||
(r'AIza[0-9A-Za-z-_]{35}', '*****'), # API key
|
||||
(r'eyJ[a-zA-Z0-9\-_]+\.eyJ[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]*', '*****'), # JWT
|
||||
]
|
||||
|
||||
def redactable_debug_print(*args):
|
||||
processed_args = []
|
||||
for arg in args:
|
||||
if arg.startswith('b\''):
|
||||
sbytes = arg[2:-1]
|
||||
sbytes = bytes(sbytes, 'utf-8')
|
||||
arg = sbytes.decode()
|
||||
arg = arg.replace('\\r\\n', "\n ")
|
||||
if GC.Values[GC.DEBUG_REDACTION]:
|
||||
for pattern, replace in DEBUG_REDACTION_PATTERNS:
|
||||
arg = re.sub(pattern, replace, arg)
|
||||
processed_args.append(arg)
|
||||
print(*processed_args)
|
||||
|
||||
# Multiprocessing lock
|
||||
mplock = None
|
||||
|
||||
@@ -2113,12 +2144,12 @@ class StartEndTime():
|
||||
else:
|
||||
firstMonth = getInteger(minVal=1, maxVal=6)
|
||||
currDate = todaysDate()
|
||||
self.startDateTime = currDate.shift(months=-firstMonth, day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
self.startDateTime = currDate.replace(day=1, hour=0, minute=0, second=0, microsecond=0).shift(months=-firstMonth)
|
||||
self.startTime = ISOformatTimeStamp(self.startDateTime)
|
||||
if myarg == 'thismonth':
|
||||
self.endDateTime = todaysTime()
|
||||
else:
|
||||
self.endDateTime = currDate.shift(day=1, hour=23, minute=59, second=59, microsecond=0).shift(days=-1)
|
||||
self.endDateTime = currDate.replace(day=1, hour=23, minute=59, second=59, microsecond=0).shift(days=-1)
|
||||
self.endTime = ISOformatTimeStamp(self.endDateTime)
|
||||
if self.startDateTime and self.endDateTime and self.endDateTime < self.startDateTime:
|
||||
Cmd.Backup()
|
||||
@@ -3049,9 +3080,13 @@ def getGDocData(gformat):
|
||||
mimeType = GDOC_FORMAT_MIME_TYPES[gformat]
|
||||
user = getEmailAddress()
|
||||
fileIdEntity = getDriveFileEntity(queryShortcutsOK=False)
|
||||
user, drive, jcount = _validateUserGetFileIDs(user, 0, 0, fileIdEntity)
|
||||
if not GC.Values[GC.COMMANDDATA_CLIENTACCESS]:
|
||||
_, drive = buildGAPIServiceObject(API.DRIVE3, user)
|
||||
else:
|
||||
drive = buildGAPIObject(API.DRIVE3)
|
||||
if not drive:
|
||||
sys.exit(GM.Globals[GM.SYSEXITRC])
|
||||
_, _, jcount = _validateUserGetFileIDs(user, 0, 0, fileIdEntity, drive=drive)
|
||||
if jcount == 0:
|
||||
getGDocSheetDataFailedExit([Ent.USER, user], Msg.NO_ENTITIES_FOUND.format(Ent.Singular(Ent.DRIVE_FILE)))
|
||||
if jcount > 1:
|
||||
@@ -3105,14 +3140,21 @@ def getGSheetData():
|
||||
user = getEmailAddress()
|
||||
fileIdEntity = getDriveFileEntity(queryShortcutsOK=False)
|
||||
sheetEntity = getSheetEntity(False)
|
||||
user, drive, jcount = _validateUserGetFileIDs(user, 0, 0, fileIdEntity)
|
||||
if not GC.Values[GC.COMMANDDATA_CLIENTACCESS]:
|
||||
user, drive = buildGAPIServiceObject(API.DRIVE3, user)
|
||||
else:
|
||||
drive = buildGAPIObject(API.DRIVE3)
|
||||
if not drive:
|
||||
sys.exit(GM.Globals[GM.SYSEXITRC])
|
||||
_, _, jcount = _validateUserGetFileIDs(user, 0, 0, fileIdEntity, drive=drive)
|
||||
if jcount == 0:
|
||||
getGDocSheetDataFailedExit([Ent.USER, user], Msg.NO_ENTITIES_FOUND.format(Ent.Singular(Ent.DRIVE_FILE)))
|
||||
if jcount > 1:
|
||||
getGDocSheetDataFailedExit([Ent.USER, user], Msg.MULTIPLE_ENTITIES_FOUND.format(Ent.Plural(Ent.DRIVE_FILE), jcount, ','.join(fileIdEntity['list'])))
|
||||
_, sheet = buildGAPIServiceObject(API.SHEETS, user)
|
||||
if not GC.Values[GC.COMMANDDATA_CLIENTACCESS]:
|
||||
_, sheet = buildGAPIServiceObject(API.SHEETS, user)
|
||||
else:
|
||||
sheet = buildGAPIObject(API.SHEETS)
|
||||
if not sheet:
|
||||
sys.exit(GM.Globals[GM.SYSEXITRC])
|
||||
fileId = fileIdEntity['list'][0]
|
||||
@@ -3705,12 +3747,12 @@ def SetGlobalVariables():
|
||||
return stringlist
|
||||
|
||||
def _getCfgTimezone(sectionName, itemName):
|
||||
value = _stripStringQuotes(GM.Globals[GM.PARSER].get(sectionName, itemName).lower())
|
||||
if value in {'utc', 'z'}:
|
||||
value = _stripStringQuotes(GM.Globals[GM.PARSER].get(sectionName, itemName))
|
||||
if value.lower() in {'utc', 'z'}:
|
||||
GM.Globals[GM.CONVERT_TO_LOCAL_TIME] = False
|
||||
return arrow.now('utc').tzinfo
|
||||
GM.Globals[GM.CONVERT_TO_LOCAL_TIME] = True
|
||||
if value == 'local':
|
||||
if value.lower() == 'local':
|
||||
return arrow.now(value).tzinfo
|
||||
try:
|
||||
return arrow.now(value).tzinfo
|
||||
@@ -4113,6 +4155,8 @@ def SetGlobalVariables():
|
||||
GM.Globals[GM.OAUTH2_TXT_LOCK] = f'{GC.Values[GC.OAUTH2_TXT]}.lock'
|
||||
# Override httplib2 settings
|
||||
httplib2.debuglevel = GC.Values[GC.DEBUG_LEVEL]
|
||||
# Use our own print function for http.client so we can redact and cleanup
|
||||
http.client.print = redactable_debug_print
|
||||
# Reset global variables if required
|
||||
if prevExtraArgsTxt != GC.Values[GC.EXTRA_ARGS]:
|
||||
GM.Globals[GM.EXTRA_ARGS_LIST] = [('prettyPrint', GC.Values[GC.DEBUG_LEVEL] > 0)]
|
||||
@@ -4764,7 +4808,7 @@ def getService(api, httpObj):
|
||||
waitOnFailure(n, triesLimit, INVALID_JSON_RC, str(e))
|
||||
continue
|
||||
systemErrorExit(INVALID_JSON_RC, str(e))
|
||||
except (http_client.ResponseNotReady, OSError, googleapiclient.errors.HttpError) as e:
|
||||
except (http.client.ResponseNotReady, OSError, googleapiclient.errors.HttpError) as e:
|
||||
errMsg = f'Connection error: {str(e) or repr(e)}'
|
||||
if n != triesLimit:
|
||||
waitOnFailure(n, triesLimit, SOCKET_ERROR_RC, errMsg)
|
||||
@@ -4799,10 +4843,7 @@ def defaultSvcAcctScopes():
|
||||
saScopes[scope['api']].append(scope['scope'])
|
||||
else:
|
||||
saScopes[scope['api']].extend(scope['scope'])
|
||||
saScopes[API.DRIVEACTIVITY].append(API.DRIVE_SCOPE)
|
||||
saScopes[API.DRIVE2] = saScopes[API.DRIVE3]
|
||||
saScopes[API.DRIVETD] = saScopes[API.DRIVE3]
|
||||
saScopes[API.SHEETSTD] = saScopes[API.SHEETS]
|
||||
return saScopes
|
||||
|
||||
def _getSvcAcctData():
|
||||
@@ -5080,7 +5121,7 @@ def callGData(service, function,
|
||||
e = e.args[0]
|
||||
handleOAuthTokenError(e, GDATA.SERVICE_NOT_APPLICABLE in throwErrors)
|
||||
raise GDATA.ERROR_CODE_EXCEPTION_MAP[GDATA.SERVICE_NOT_APPLICABLE](str(e))
|
||||
except (http_client.ResponseNotReady, OSError) as e:
|
||||
except (http.client.ResponseNotReady, OSError) as e:
|
||||
errMsg = f'Connection error: {str(e) or repr(e)}'
|
||||
if n != triesLimit:
|
||||
waitOnFailure(n, triesLimit, SOCKET_ERROR_RC, errMsg)
|
||||
@@ -5390,7 +5431,7 @@ def callGAPI(service, function,
|
||||
e = e.args[0]
|
||||
handleOAuthTokenError(e, GAPI.SERVICE_NOT_AVAILABLE in throwReasons)
|
||||
raise GAPI.REASON_EXCEPTION_MAP[GAPI.SERVICE_NOT_AVAILABLE](str(e))
|
||||
except (http_client.ResponseNotReady, OSError) as e:
|
||||
except (http.client.ResponseNotReady, OSError) as e:
|
||||
errMsg = f'Connection error: {str(e) or repr(e)}'
|
||||
if n != triesLimit:
|
||||
waitOnFailure(n, triesLimit, SOCKET_ERROR_RC, errMsg)
|
||||
@@ -5609,6 +5650,12 @@ def getSaUser(user):
|
||||
GM.Globals[GM.CURRENT_CLIENT_API_SCOPES] = currentClientAPIScopes
|
||||
return userEmail
|
||||
|
||||
def chooseSaAPI(api1, api2):
|
||||
_getSvcAcctData()
|
||||
if api1 in GM.Globals[GM.SVCACCT_SCOPES]:
|
||||
return api1
|
||||
return api2
|
||||
|
||||
def buildGAPIServiceObject(api, user, i=0, count=0, displayError=True):
|
||||
userEmail = getSaUser(user)
|
||||
httpObj = getHttpObj(cache=GM.Globals[GM.CACHE_DIR])
|
||||
@@ -8030,7 +8077,7 @@ class CSVPrintFile():
|
||||
|
||||
def getDriveObject():
|
||||
if not GC.Values[GC.TODRIVE_CLIENTACCESS]:
|
||||
_, drive = buildGAPIServiceObject(API.DRIVETD, self.todrive['user'])
|
||||
_, drive = buildGAPIServiceObject(chooseSaAPI(API.DRIVETD, API.DRIVE3), self.todrive['user'])
|
||||
if not drive:
|
||||
invalidTodriveUserExit(Ent.USER, Msg.NOT_FOUND)
|
||||
else:
|
||||
@@ -8183,7 +8230,7 @@ class CSVPrintFile():
|
||||
if result['mimeType'] != MIMETYPE_GA_SPREADSHEET:
|
||||
invalidTodriveFileIdExit([], f'{Msg.NOT_A} {Ent.Singular(Ent.SPREADSHEET)}', tdfileidLocation)
|
||||
if not GC.Values[GC.TODRIVE_CLIENTACCESS]:
|
||||
_, sheet = buildGAPIServiceObject(API.SHEETSTD, self.todrive['user'])
|
||||
_, sheet = buildGAPIServiceObject(chooseSaAPI(API.SHEETSTD, API.SHEETS), self.todrive['user'])
|
||||
if sheet is None:
|
||||
invalidTodriveUserExit(Ent.USER, Msg.NOT_FOUND)
|
||||
else:
|
||||
@@ -8696,7 +8743,7 @@ class CSVPrintFile():
|
||||
sheetTitle += tdtime.strftime(self.todrive['sheettimeformat'])
|
||||
action = Act.Get()
|
||||
if not GC.Values[GC.TODRIVE_CLIENTACCESS]:
|
||||
user, drive = buildGAPIServiceObject(API.DRIVETD, self.todrive['user'])
|
||||
user, drive = buildGAPIServiceObject(chooseSaAPI(API.DRIVETD, API.DRIVE3), self.todrive['user'])
|
||||
if not drive:
|
||||
closeFile(csvFile)
|
||||
return
|
||||
@@ -8729,7 +8776,7 @@ class CSVPrintFile():
|
||||
if result['mimeType'] != MIMETYPE_GA_SPREADSHEET:
|
||||
todriveCSVErrorExit(entityValueList, f'{Msg.NOT_A} {Ent.Singular(Ent.SPREADSHEET)}')
|
||||
if not GC.Values[GC.TODRIVE_CLIENTACCESS]:
|
||||
_, sheet = buildGAPIServiceObject(API.SHEETSTD, user)
|
||||
_, sheet = buildGAPIServiceObject(chooseSaAPI(API.SHEETSTD, API.SHEETS), user)
|
||||
if sheet is None:
|
||||
return
|
||||
else:
|
||||
@@ -8877,7 +8924,7 @@ class CSVPrintFile():
|
||||
(self.todrive['sheetEntity'] or self.todrive['locale'] or self.todrive['timeZone'] or
|
||||
self.todrive['sheettitle'] or self.todrive['cellwrap'] or self.todrive['cellnumberformat'])):
|
||||
if not GC.Values[GC.TODRIVE_CLIENTACCESS]:
|
||||
_, sheet = buildGAPIServiceObject(API.SHEETSTD, user)
|
||||
_, sheet = buildGAPIServiceObject(chooseSaAPI(API.SHEETSTD, API.SHEETS), user)
|
||||
if sheet is None:
|
||||
return
|
||||
else:
|
||||
@@ -9590,7 +9637,7 @@ def CSVFileQueueHandler(mpQueue, mpQueueStdout, mpQueueStderr, csvPF, datetimeNo
|
||||
clearRowFilters = False
|
||||
# if sys.platform.startswith('win'):
|
||||
# signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
Cmd = glclargs.GamCLArgs()
|
||||
else:
|
||||
@@ -9632,7 +9679,7 @@ def CSVFileQueueHandler(mpQueue, mpQueueStdout, mpQueueStderr, csvPF, datetimeNo
|
||||
Cmd.InitializeArguments(dataItem)
|
||||
elif dataType == GM.REDIRECT_QUEUE_GLOBALS:
|
||||
GM.Globals = dataItem
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
reopenSTDFile(GM.STDOUT)
|
||||
reopenSTDFile(GM.STDERR)
|
||||
elif dataType == GM.REDIRECT_QUEUE_VALUES:
|
||||
@@ -9678,7 +9725,7 @@ def initializeCSVFileQueueHandler(mpManager, mpQueueStdout, mpQueueStderr):
|
||||
def terminateCSVFileQueueHandler(mpQueue, mpQueueHandler):
|
||||
GM.Globals[GM.PARSER] = None
|
||||
GM.Globals[GM.CSVFILE][GM.REDIRECT_QUEUE] = None
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
mpQueue.put((GM.REDIRECT_QUEUE_ARGS, Cmd.AllArguments()))
|
||||
savedValues = saveNonPickleableValues()
|
||||
mpQueue.put((GM.REDIRECT_QUEUE_GLOBALS, GM.Globals))
|
||||
@@ -9708,13 +9755,13 @@ def StdQueueHandler(mpQueue, stdtype, gmGlobals, gcValues):
|
||||
|
||||
# if sys.platform.startswith('win'):
|
||||
# signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
GM.Globals = gmGlobals.copy()
|
||||
GC.Values = gcValues.copy()
|
||||
pid0DataItem = [KEYBOARD_INTERRUPT_RC, None]
|
||||
pidData = {}
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
if GM.Globals[stdtype][GM.REDIRECT_NAME] == 'null':
|
||||
fd = open(os.devnull, GM.Globals[stdtype][GM.REDIRECT_MODE], encoding=UTF8)
|
||||
elif GM.Globals[stdtype][GM.REDIRECT_NAME] == '-':
|
||||
@@ -9802,7 +9849,7 @@ def ProcessGAMCommandMulti(pid, numItems, logCmd, mpQueueCSVFile, mpQueueStdout,
|
||||
with mplock:
|
||||
initializeLogging()
|
||||
# if sys.platform.startswith('win'):
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
GM.Globals[GM.API_CALLS_RETRY_DATA] = {}
|
||||
GM.Globals[GM.CMDLOG_LOGGER] = None
|
||||
@@ -9943,7 +9990,7 @@ def MultiprocessGAMCommands(items, showCmds):
|
||||
mpManager = multiprocessing.Manager()
|
||||
l = mpManager.Lock()
|
||||
try:
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
pool = mpManager.Pool(processes=numPoolProcesses, initializer=initGamWorker, initargs=(l,), maxtasksperchild=200)
|
||||
else:
|
||||
pool = multiprocessing.Pool(processes=numPoolProcesses, initializer=initGamWorker, initargs=(l,), maxtasksperchild=200)
|
||||
@@ -9952,7 +9999,7 @@ def MultiprocessGAMCommands(items, showCmds):
|
||||
except AssertionError as e:
|
||||
Cmd.SetLocation(0)
|
||||
usageErrorExit(str(e))
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
savedValues = saveNonPickleableValues()
|
||||
if GM.Globals[GM.STDOUT][GM.REDIRECT_MULTIPROCESS]:
|
||||
mpQueueStdout, mpQueueHandlerStdout = initializeStdQueueHandler(mpManager, GM.STDOUT, GM.Globals, GC.Values)
|
||||
@@ -9967,7 +10014,7 @@ def MultiprocessGAMCommands(items, showCmds):
|
||||
mpQueueStderr = mpQueueStdout
|
||||
else:
|
||||
mpQueueStderr = None
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
restoreNonPickleableValues(savedValues)
|
||||
if mpQueueStdout:
|
||||
mpQueueStdout.put((0, GM.REDIRECT_QUEUE_DATA, GM.Globals[GM.STDOUT][GM.REDIRECT_MULTI_FD].getvalue()))
|
||||
@@ -11131,7 +11178,7 @@ class Credentials(google.oauth2.credentials.Credentials):
|
||||
|
||||
def doOAuthRequest(currentScopes, login_hint, verifyScopes=False):
|
||||
client_id, client_secret = getOAuthClientIDAndSecret()
|
||||
scopesList = API.getClientScopesList(GC.Values[GC.TODRIVE_CLIENTACCESS])
|
||||
scopesList = API.getClientScopesList(GC.Values[GC.COMMANDDATA_CLIENTACCESS], GC.Values[GC.TODRIVE_CLIENTACCESS])
|
||||
if not currentScopes or verifyScopes:
|
||||
selectedScopes = getScopesFromUser(scopesList, True, currentScopes)
|
||||
if selectedScopes is None:
|
||||
@@ -11177,7 +11224,7 @@ def doOAuthCreate():
|
||||
else:
|
||||
login_hint = None
|
||||
scopes = []
|
||||
scopesList = API.getClientScopesList(GC.Values[GC.TODRIVE_CLIENTACCESS])
|
||||
scopesList = API.getClientScopesList(GC.Values[GC.COMMANDDATA_CLIENTACCESS], GC.Values[GC.TODRIVE_CLIENTACCESS])
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'admin':
|
||||
@@ -11193,7 +11240,9 @@ def doOAuthCreate():
|
||||
scopes.append(uscope)
|
||||
break
|
||||
else:
|
||||
invalidChoiceExit(uscope, API.getClientScopesURLs(GC.Values[GC.TODRIVE_CLIENTACCESS]), True)
|
||||
invalidChoiceExit(uscope,
|
||||
API.getClientScopesURLs(GC.Values[GC.COMMANDDATA_CLIENTACCESS], GC.Values[GC.TODRIVE_CLIENTACCESS]),
|
||||
True)
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
if len(scopes) == 0:
|
||||
@@ -11304,7 +11353,7 @@ def doOAuthUpdate():
|
||||
if 'scopes' in jsonDict:
|
||||
currentScopes = jsonDict['scopes']
|
||||
else:
|
||||
currentScopes = API.getClientScopesURLs(GC.Values[GC.TODRIVE_CLIENTACCESS])
|
||||
currentScopes = API.getClientScopesURLs(GC.Values[GC.COMMANDDATA_CLIENTACCESS], GC.Values[GC.TODRIVE_CLIENTACCESS])
|
||||
else:
|
||||
currentScopes = []
|
||||
except (AttributeError, IndexError, KeyError, SyntaxError, TypeError, ValueError) as e:
|
||||
@@ -12334,8 +12383,6 @@ def checkServiceAccount(users):
|
||||
saScopes[scope['api']].append(scope['roscope'])
|
||||
checkScopesSet.add(scope['roscope'])
|
||||
i += 1
|
||||
if API.DRIVEACTIVITY in saScopes and API.DRIVE3 in saScopes:
|
||||
saScopes[API.DRIVEACTIVITY].append(API.DRIVE_SCOPE)
|
||||
if API.DRIVE3 in saScopes:
|
||||
saScopes[API.DRIVE2] = saScopes[API.DRIVE3]
|
||||
GM.Globals[GM.OAUTH2SERVICE_JSON_DATA][API.OAUTH2SA_SCOPES] = saScopes
|
||||
@@ -16263,7 +16310,7 @@ def _showCustomerLicenseInfo(customerInfo, FJQC):
|
||||
while True:
|
||||
try:
|
||||
result = callGAPI(rep.customerUsageReports(), 'get',
|
||||
throwReasons=[GAPI.INVALID, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||
throwReasons=[GAPI.INVALID, GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||
date=tryDate, customerId=customerInfo['id'],
|
||||
fields='warnings,usageReports', parameters=parameters)
|
||||
usageReports = numUsersAvailable(result)
|
||||
@@ -16276,7 +16323,7 @@ def _showCustomerLicenseInfo(customerInfo, FJQC):
|
||||
if fullData == 0:
|
||||
continue
|
||||
break
|
||||
except GAPI.invalid as e:
|
||||
except (GAPI.invalid, GAPI.failedPrecondition) as e:
|
||||
tryDate = _adjustTryDate(str(e), 0, -1, tryDate)
|
||||
if not tryDate:
|
||||
return
|
||||
@@ -16990,23 +17037,39 @@ def doDeleteAdmin():
|
||||
except (GAPI.forbidden, GAPI.permissionDenied) as e:
|
||||
ClientAPIAccessDeniedExit(str(e))
|
||||
|
||||
ASSIGNEE_EMAILTYPE_TOFIELD_MAP = {
|
||||
ADMIN_ASSIGNEE_TYPE_TO_ASSIGNEDTO_FIELD_MAP = {
|
||||
'user': 'assignedToUser',
|
||||
'group': 'assignedToGroup',
|
||||
'serviceaccount': 'assignedToServiceAccount',
|
||||
'unknown': 'assignedToUnknown',
|
||||
}
|
||||
PRINT_ADMIN_FIELDS = ['roleAssignmentId', 'roleId', 'assignedTo', 'scopeType', 'orgUnitId', 'assigneeType']
|
||||
ALL_ASSIGNEE_TYPES = ['user', 'group', 'serviceaccount']
|
||||
|
||||
PRINT_ADMIN_FIELDS = ['roleAssignmentId', 'roleId', 'assignedTo', 'scopeType', 'orgUnitId']
|
||||
PRINT_ADMIN_TITLES = ['roleAssignmentId', 'roleId', 'role',
|
||||
'assignedTo', 'assignedToUser', 'assignedToGroup', 'assignedToServiceAccount', 'assignedToUnknown',
|
||||
'scopeType', 'orgUnitId', 'orgUnit']
|
||||
|
||||
# gam print admins [todrive <ToDriveAttribute>*]
|
||||
# [user|group <EmailAddress>|<UniqueID>] [role <RoleItem>]
|
||||
# [types <AdminAssigneeTypeList>]
|
||||
# [recursive] [condition] [privileges] [oneitemperrow]
|
||||
# gam show admins
|
||||
# [user|group <EmailAddress>|<UniqueID>] [role <RoleItem>]
|
||||
# [types <AdminAssigneeTypeList>]
|
||||
# [recursive] [condition] [privileges]
|
||||
def doPrintShowAdmins():
|
||||
def _getAssigneeTypes(myarg):
|
||||
if myarg in {'type', 'types'}:
|
||||
for gtype in getString(Cmd.OB_ADMIN_ASSIGNEE_TYPE_LIST).lower().replace(',', ' ').split():
|
||||
if gtype in ADMIN_ASSIGNEE_TYPE_TO_ASSIGNEDTO_FIELD_MAP:
|
||||
typesSet.add(ADMIN_ASSIGNEE_TYPE_TO_ASSIGNEDTO_FIELD_MAP[gtype])
|
||||
else:
|
||||
invalidChoiceExit(gtype, ADMIN_ASSIGNEE_TYPE_TO_ASSIGNEDTO_FIELD_MAP, True)
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _getPrivileges(admin):
|
||||
if showPrivileges:
|
||||
roleId = admin['roleId']
|
||||
@@ -17032,16 +17095,16 @@ def doPrintShowAdmins():
|
||||
def _setNamesFromIds(admin, privileges):
|
||||
admin['role'] = role_from_roleid(admin['roleId'])
|
||||
assignedTo = admin['assignedTo']
|
||||
admin['assignedToUnknown'] = False
|
||||
if assignedTo not in assignedToIdEmailMap:
|
||||
assigneeEmail, assigneeType = convertUIDtoEmailAddressWithType(f'uid:{assignedTo}', cd, sal,
|
||||
emailTypes=allAssigneeTypes if admin.get('assigneeType') != 'group' else ['group'])
|
||||
if assigneeType in ASSIGNEE_EMAILTYPE_TOFIELD_MAP:
|
||||
assignedToField = ASSIGNEE_EMAILTYPE_TOFIELD_MAP[assigneeType]
|
||||
else:
|
||||
assignedToField = 'assignedToUnknown'
|
||||
emailTypes = ALL_ASSIGNEE_TYPES if admin.get('assigneeType', '') != 'group' else ['group']
|
||||
assigneeEmail, assigneeType = convertUIDtoEmailAddressWithType(f'uid:{assignedTo}', cd, sal, emailTypes=emailTypes)
|
||||
assignedToField = ADMIN_ASSIGNEE_TYPE_TO_ASSIGNEDTO_FIELD_MAP.get(assigneeType, 'assignedToUnknown')
|
||||
if assignedToField == 'assignedToUnknown':
|
||||
assigneeEmail = True
|
||||
assignedToIdEmailMap[assignedTo] = {'assignedToField': assignedToField, 'assigneeEmail': assigneeEmail}
|
||||
admin[assignedToIdEmailMap[assignedTo]['assignedToField']] = assignedToIdEmailMap[assignedTo]['assigneeEmail']
|
||||
admin['assignedToField'] = assignedToIdEmailMap[assignedTo]['assignedToField']
|
||||
if privileges is not None:
|
||||
admin.update(privileges)
|
||||
if 'orgUnitId' in admin:
|
||||
@@ -17051,18 +17114,23 @@ def doPrintShowAdmins():
|
||||
admin['condition'] = 'securitygroup'
|
||||
elif admin['condition'] == NONSECURITY_GROUP_CONDITION:
|
||||
admin['condition'] = 'nonsecuritygroup'
|
||||
if debug:
|
||||
print('******', admin['assignedTo'], admin.get('assigneeType', 'no type'),
|
||||
admin['assignedToField'], not typesSet or admin['assignedToField'] in typesSet)
|
||||
return not typesSet or admin['assignedToField'] in typesSet
|
||||
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
sal = buildGAPIObject(API.SERVICEACCOUNTLOOKUP)
|
||||
csvPF = CSVPrintFile(PRINT_ADMIN_TITLES) if Act.csvFormat() else None
|
||||
roleId = None
|
||||
userKey = None
|
||||
oneItemPerRow = recursive = showPrivileges = False
|
||||
debug = oneItemPerRow = recursive = showPrivileges = False
|
||||
typesSet = set()
|
||||
kwargs = {}
|
||||
rolePrivileges = {}
|
||||
fieldsList = PRINT_ADMIN_FIELDS
|
||||
allGroupRoles = ','.join(sorted(ALL_GROUP_ROLES))
|
||||
fieldsList = PRINT_ADMIN_FIELDS+['assigneeType']
|
||||
assignedToIdEmailMap = {}
|
||||
allAssigneeTypes = list(ASSIGNEE_EMAILTYPE_TOFIELD_MAP.keys())
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if csvPF and myarg == 'todrive':
|
||||
@@ -17071,9 +17139,10 @@ def doPrintShowAdmins():
|
||||
userKey = kwargs['userKey'] = getEmailAddress()
|
||||
elif myarg == 'role':
|
||||
_, roleId = getRoleId()
|
||||
elif _getAssigneeTypes(myarg):
|
||||
pass
|
||||
elif myarg == 'recursive':
|
||||
recursive = True
|
||||
allGroupRoles = ','.join(sorted(ALL_GROUP_ROLES))
|
||||
memberOptions = initMemberOptions()
|
||||
memberOptions[MEMBEROPTION_INCLUDEDERIVEDMEMBERSHIP] = True
|
||||
memberOptions[MEMBEROPTION_DISPLAYMATCH] = False
|
||||
@@ -17088,6 +17157,8 @@ def doPrintShowAdmins():
|
||||
showPrivileges = True
|
||||
elif myarg == 'oneitemperrow':
|
||||
oneItemPerRow = True
|
||||
elif myarg == 'debug':
|
||||
debug = True
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
if roleId and not kwargs:
|
||||
@@ -17109,7 +17180,7 @@ def doPrintShowAdmins():
|
||||
return
|
||||
except GAPI.notFound as e:
|
||||
entityActionFailedExit([Ent.ADMIN_ROLE, kwargs['roleId']], str(e))
|
||||
except (GAPI.forbidden, GAPI.serviceNotAvailable) as e:
|
||||
except GAPI.serviceNotAvailable as e:
|
||||
entityActionFailedExit([Ent.ADMINISTRATOR, userKey], str(e))
|
||||
except (GAPI.badRequest, GAPI.customerNotFound):
|
||||
accessErrorExit(cd)
|
||||
@@ -17123,11 +17194,11 @@ def doPrintShowAdmins():
|
||||
i += 1
|
||||
if roleId and roleId != admin['roleId']:
|
||||
continue
|
||||
if admin['assigneeType'] != 'group' or not recursive:
|
||||
_setNamesFromIds(admin, _getPrivileges(admin))
|
||||
expandedAdmins.append(admin)
|
||||
continue
|
||||
assignedTo = admin['assignedTo']
|
||||
if admin['assigneeType'] != 'group' or not recursive:
|
||||
if _setNamesFromIds(admin, _getPrivileges(admin)):
|
||||
expandedAdmins.append(admin)
|
||||
continue
|
||||
if assignedTo not in groupMembers:
|
||||
membersList = []
|
||||
membersSet = set()
|
||||
@@ -17135,7 +17206,8 @@ def doPrintShowAdmins():
|
||||
getGroupMembers(cd, assignedTo, allGroupRoles, membersList, membersSet, i, count,
|
||||
memberOptions, memberDisplayOptions, level, {Ent.TYPE_USER})
|
||||
groupMembers[assignedTo] = membersList[:]
|
||||
_setNamesFromIds(admin, _getPrivileges(admin))
|
||||
if not _setNamesFromIds(admin, _getPrivileges(admin)):
|
||||
continue
|
||||
if not groupMembers[assignedTo]:
|
||||
expandedAdmins.append(admin)
|
||||
continue
|
||||
@@ -17158,7 +17230,7 @@ def doPrintShowAdmins():
|
||||
Ind.Increment()
|
||||
for field in PRINT_ADMIN_TITLES:
|
||||
if field in admin:
|
||||
if field == 'roleAssignmentId':
|
||||
if (field == 'roleAssignmentId') or (field == 'assignedToUnknown' and not admin[field]):
|
||||
continue
|
||||
printKeyValueList([field, admin[field]])
|
||||
if showPrivileges:
|
||||
@@ -17173,6 +17245,8 @@ def doPrintShowAdmins():
|
||||
Ind.Decrement()
|
||||
else:
|
||||
for admin in expandedAdmins:
|
||||
admin.pop('assigneeType', None)
|
||||
admin.pop('assignedToField', None)
|
||||
if not oneItemPerRow or 'rolePrivileges' not in admin:
|
||||
csvPF.WriteRowTitles(flattenJSON(admin))
|
||||
else:
|
||||
|
||||
@@ -34,7 +34,12 @@ def main():
|
||||
|
||||
# Run from command line
|
||||
if __name__ == '__main__':
|
||||
if platform.system() != 'Linux':
|
||||
if getattr(sys, 'frozen', False): # we're frozen:
|
||||
multiprocessing.freeze_support()
|
||||
if platform.system() == 'Linux':
|
||||
# set explictly since it's not default in Python < 3.14, forkserver should
|
||||
# be safer than fork and less likely to see bulk command hangs.
|
||||
multiprocessing.set_start_method('forkserver')
|
||||
else:
|
||||
multiprocessing.set_start_method('spawn')
|
||||
main()
|
||||
|
||||
@@ -105,6 +105,8 @@ YOUTUBE = 'youtube'
|
||||
BUSINESSACCOUNTMANAGEMENT_SCOPE = 'https://www.googleapis.com/auth/business.manage'
|
||||
CHROMEVERSIONHISTORY_URL = 'https://versionhistory.googleapis.com/v1/chrome/platforms'
|
||||
DRIVE_SCOPE = 'https://www.googleapis.com/auth/drive'
|
||||
DRIVE_FILE_SCOPE = 'https://www.googleapis.com/auth/drive.file'
|
||||
DRIVE_READONLY_SCOPE = 'https://www.googleapis.com/auth/drive.readonly'
|
||||
GMAIL_SEND_SCOPE = 'https://www.googleapis.com/auth/gmail.send'
|
||||
GOOGLE_AUTH_PROVIDER_X509_CERT_URL = 'https://www.googleapis.com/oauth2/v1/certs'
|
||||
GOOGLE_OAUTH2_ENDPOINT = 'https://accounts.google.com/o/oauth2/v2/auth'
|
||||
@@ -156,6 +158,7 @@ OAUTH2_TOKEN_ERRORS = [
|
||||
'access_denied: Account restricted',
|
||||
'internal_failure: Backend Error',
|
||||
'internal_failure: None',
|
||||
'invalid_account: Forbidden',
|
||||
'invalid_grant',
|
||||
'invalid_grant: Bad Request',
|
||||
'invalid_grant: Invalid email or User ID',
|
||||
@@ -253,7 +256,7 @@ _INFO = {
|
||||
DOCS: {'name': 'Docs API', 'version': 'v1', 'v2discovery': True},
|
||||
DRIVE2: {'name': 'Drive API v2', 'version': 'v2', 'v2discovery': False, 'mappedAPI': 'drive'},
|
||||
DRIVE3: {'name': 'Drive API v3', 'version': 'v3', 'v2discovery': False, 'mappedAPI': 'drive'},
|
||||
DRIVETD: {'name': 'Drive API v3 - todrive', 'version': 'v3', 'v2discovery': False, 'mappedAPI': 'drive'},
|
||||
DRIVETD: {'name': 'Drive API v3 - write todrive data', 'version': 'v3', 'v2discovery': False, 'mappedAPI': 'drive'},
|
||||
DRIVEACTIVITY: {'name': 'Drive Activity API v2', 'version': 'v2', 'v2discovery': True},
|
||||
DRIVELABELS_ADMIN: {'name': 'Drive Labels API - Admin', 'version': 'v2', 'v2discovery': True, 'mappedAPI': DRIVELABELS},
|
||||
DRIVELABELS_USER: {'name': 'Drive Labels API - User', 'version': 'v2', 'v2discovery': True, 'mappedAPI': DRIVELABELS},
|
||||
@@ -283,7 +286,7 @@ _INFO = {
|
||||
SERVICEMANAGEMENT: {'name': 'Service Management API', 'version': 'v1', 'v2discovery': True},
|
||||
SERVICEUSAGE: {'name': 'Service Usage API', 'version': 'v1', 'v2discovery': True},
|
||||
SHEETS: {'name': 'Sheets API', 'version': 'v4', 'v2discovery': True},
|
||||
SHEETSTD: {'name': 'Sheets API - todrive', 'version': 'v4', 'v2discovery': True, 'mappedAPI': SHEETS},
|
||||
SHEETSTD: {'name': 'Sheets API - write todrive data', 'version': 'v4', 'v2discovery': True, 'mappedAPI': SHEETS},
|
||||
SITEVERIFICATION: {'name': 'Site Verification API', 'version': 'v1', 'v2discovery': True},
|
||||
STORAGE: {'name': 'Cloud Storage API', 'version': 'v1', 'v2discovery': True},
|
||||
STORAGEREAD: {'name': 'Cloud Storage API - Read', 'version': 'v1', 'v2discovery': True, 'mappedAPI': STORAGE},
|
||||
@@ -530,6 +533,17 @@ _CLIENT_SCOPES = [
|
||||
'scope': 'https://www.googleapis.com/auth/ediscovery'},
|
||||
]
|
||||
|
||||
_COMMANDDATA_CLIENT_SCOPES = [
|
||||
{'name': 'Drive API - commanddata_clientaccess',
|
||||
'api': DRIVE3,
|
||||
'subscopes': [],
|
||||
'scope': DRIVE_READONLY_SCOPE},
|
||||
{'name': 'Sheets API - commanddata_clientaccess',
|
||||
'api': SHEETS,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/spreadsheets.readonly'},
|
||||
]
|
||||
|
||||
_TODRIVE_CLIENT_SCOPES = [
|
||||
{'name': 'Drive API - todrive_clientaccess',
|
||||
'api': DRIVE3,
|
||||
@@ -538,7 +552,7 @@ _TODRIVE_CLIENT_SCOPES = [
|
||||
{'name': 'Drive File API - todrive_clientaccess',
|
||||
'api': DRIVE3,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/drive.file'},
|
||||
'scope': DRIVE_FILE_SCOPE},
|
||||
{'name': 'Gmail API - todrive_clientaccess',
|
||||
'api': GMAIL,
|
||||
'subscopes': [],
|
||||
@@ -643,7 +657,8 @@ _SVCACCT_SCOPES = [
|
||||
{'name': 'Drive Activity API v2 - must pair with Drive API',
|
||||
'api': DRIVEACTIVITY,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/drive.activity'},
|
||||
'scope': [DRIVE_READONLY_SCOPE,
|
||||
'https://www.googleapis.com/auth/drive.activity']},
|
||||
{'name': 'Drive Labels API - Admin',
|
||||
'api': DRIVELABELS_ADMIN,
|
||||
'subscopes': READONLY,
|
||||
@@ -656,10 +671,12 @@ _SVCACCT_SCOPES = [
|
||||
'api': DOCS,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/documents'},
|
||||
{'name': 'Forms API',
|
||||
{'name': 'Forms API - must pair with Drive API',
|
||||
'api': FORMS,
|
||||
'subscopes': [],
|
||||
'scope': DRIVE_SCOPE},
|
||||
'scope': [DRIVE_READONLY_SCOPE,
|
||||
'https://www.googleapis.com/auth/forms.body',
|
||||
'https://www.googleapis.com/auth/forms.responses.readonly']},
|
||||
{'name': 'Gmail API - Full Access (Labels, Messages)',
|
||||
'api': GMAIL,
|
||||
'subscopes': [],
|
||||
@@ -750,9 +767,10 @@ _SVCACCT_SCOPES = [
|
||||
]
|
||||
|
||||
_SVCACCT_SPECIAL_SCOPES = [
|
||||
{'name': 'Drive API - todrive',
|
||||
{'name': 'Drive API - write todrive data - has access to all Drive',
|
||||
'api': DRIVETD,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': DRIVE_SCOPE},
|
||||
{'name': 'Gmail API - Full Access - read only',
|
||||
'api': GMAIL,
|
||||
@@ -764,8 +782,9 @@ _SVCACCT_SPECIAL_SCOPES = [
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': GMAIL_SEND_SCOPE},
|
||||
{'name': 'Sheets API - todrive',
|
||||
{'name': 'Sheets API - write todrive data - has access to all Sheets',
|
||||
'api': SHEETSTD,
|
||||
'offByDefault': True,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/spreadsheets'},
|
||||
]
|
||||
@@ -789,14 +808,18 @@ def getVersion(api):
|
||||
def getClientScopesSet(api):
|
||||
return {scope['scope'] for scope in _CLIENT_SCOPES if scope['api'] == api}
|
||||
|
||||
def getClientScopesList(todriveClientAccess):
|
||||
def getClientScopesList(commanddataClientAccess, todriveClientAccess):
|
||||
caScopes = _CLIENT_SCOPES[:]
|
||||
if commanddataClientAccess:
|
||||
caScopes.extend(_COMMANDDATA_CLIENT_SCOPES)
|
||||
if todriveClientAccess:
|
||||
caScopes.extend(_TODRIVE_CLIENT_SCOPES)
|
||||
return sorted(caScopes, key=lambda k: k['name'])
|
||||
|
||||
def getClientScopesURLs(todriveClientAccess):
|
||||
def getClientScopesURLs(commanddataClientAccess, todriveClientAccess):
|
||||
caScopes = _CLIENT_SCOPES[:]
|
||||
if commanddataClientAccess:
|
||||
caScopes.extend(_COMMANDDATA_CLIENT_SCOPES)
|
||||
if todriveClientAccess:
|
||||
caScopes.extend(_TODRIVE_CLIENT_SCOPES)
|
||||
return sorted({scope['scope'] for scope in _CLIENT_SCOPES})
|
||||
|
||||
@@ -85,6 +85,8 @@ CMDLOG_MAX__BACKUPS = 'cmdlog_max__backups'
|
||||
CMDLOG_MAX_BACKUPS = 'cmdlog_max_backups'
|
||||
# Command logging max kilo bytes per log file
|
||||
CMDLOG_MAX_KILO_BYTES = 'cmdlog_max_kilo_bytes'
|
||||
# Use client access for command data from Google Docs/Sheets
|
||||
COMMANDDATA_CLIENTACCESS = 'commanddata_clientaccess'
|
||||
# GAM config directory containing client_secrets.json, oauth2.txt, oauth2service.json, extra_args.txt
|
||||
CONFIG_DIR = 'config_dir'
|
||||
# When retrieving lists of Google Contacts from API, how many should be retrieved in each chunk
|
||||
@@ -147,6 +149,8 @@ CSV_OUTPUT_USERS_AUDIT = 'csv_output_users_audit'
|
||||
CUSTOMER_ID = 'customer_id'
|
||||
# If debug_level > 0: extra_args['prettyPrint'] = True, httplib2.debuglevel = gam_debug_level, appsObj.debug = True
|
||||
DEBUG_LEVEL = 'debug_level'
|
||||
# redact sensitive credentials from debug output
|
||||
DEBUG_REDACTION = 'debug_redaction'
|
||||
# Developer Preview API Key
|
||||
DEVELOPER_PREVIEW_API_KEY = 'developer_preview_api_key'
|
||||
# When retrieving lists of ChromeOS devices from API, how many should be retrieved in each chunk
|
||||
@@ -344,6 +348,7 @@ Defaults = {
|
||||
CMDLOG: '',
|
||||
CMDLOG_MAX_BACKUPS: 5,
|
||||
CMDLOG_MAX_KILO_BYTES: 1000,
|
||||
COMMANDDATA_CLIENTACCESS: FALSE,
|
||||
CONFIG_DIR: '',
|
||||
CONTACT_MAX_RESULTS: '100',
|
||||
CSV_INPUT_COLUMN_DELIMITER: ',',
|
||||
@@ -375,6 +380,7 @@ Defaults = {
|
||||
CSV_OUTPUT_USERS_AUDIT: FALSE,
|
||||
CUSTOMER_ID: MY_CUSTOMER,
|
||||
DEBUG_LEVEL: '0',
|
||||
DEBUG_REDACTION: TRUE,
|
||||
DEVELOPER_PREVIEW_API_KEY: '',
|
||||
DEVICE_MAX_RESULTS: '200',
|
||||
DOMAIN: '',
|
||||
@@ -512,6 +518,7 @@ VAR_INFO = {
|
||||
CMDLOG: {VAR_TYPE: TYPE_FILE, VAR_ACCESS: os.W_OK},
|
||||
CMDLOG_MAX_BACKUPS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 10)},
|
||||
CMDLOG_MAX_KILO_BYTES: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (100, 10000)},
|
||||
COMMANDDATA_CLIENTACCESS: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
CONFIG_DIR: {VAR_TYPE: TYPE_DIRECTORY, VAR_ENVVAR: 'GAMUSERCONFIGDIR'},
|
||||
CONTACT_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 10000)},
|
||||
CSV_INPUT_COLUMN_DELIMITER: {VAR_TYPE: TYPE_CHARACTER},
|
||||
@@ -543,6 +550,7 @@ VAR_INFO = {
|
||||
CSV_OUTPUT_USERS_AUDIT: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
CUSTOMER_ID: {VAR_TYPE: TYPE_STRING, VAR_ENVVAR: 'CUSTOMER_ID', VAR_LIMITS: (0, None)},
|
||||
DEBUG_LEVEL: {VAR_TYPE: TYPE_INTEGER, VAR_SIGFILE: 'debug.gam', VAR_LIMITS: (0, None), VAR_SFFT: ('0', '4')},
|
||||
DEBUG_REDACTION: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
DEVELOPER_PREVIEW_API_KEY: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
DEVICE_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 200)},
|
||||
DOMAIN: {VAR_TYPE: TYPE_STRING, VAR_ENVVAR: 'GA_DOMAIN', VAR_LIMITS: (0, None)},
|
||||
|
||||
@@ -1138,6 +1138,7 @@ class GamCLArgs():
|
||||
OB_ARGUMENT = 'argument'
|
||||
OB_ASP_ID_LIST = 'ASPIDList'
|
||||
OB_ASSET_ID = 'AssetID'
|
||||
OB_ADMIN_ASSIGNEE_TYPE_LIST = 'AdminAssigneeTypeList'
|
||||
OB_BROWSER_ENROLLEMNT_TOKEN_ID = 'BrowserEnrollmentTokenID'
|
||||
OB_BROWSER_ENTITY = 'BrowserEntity'
|
||||
OB_BUILDING_ID = 'BuildingID'
|
||||
|
||||
@@ -107,6 +107,8 @@ CURRENT_SVCACCT_USER = 'csa'
|
||||
DATETIME_NOW = 'dtno'
|
||||
# If debug_level > 0: extra_args['prettyPrint'] = True, httplib2.debuglevel = gam_debug_level, appsObj.debug = True
|
||||
DEBUG_LEVEL = 'dbgl'
|
||||
# Whether debug output should redact sensitive credentials
|
||||
DEBUG_REDACTION = 'dbrd'
|
||||
# Decoded ID token
|
||||
DECODED_ID_TOKEN = 'didt'
|
||||
# Index of start of <UserTypeEntity> in command line
|
||||
@@ -263,6 +265,7 @@ Globals = {
|
||||
CURRENT_SVCACCT_USER: None,
|
||||
DATETIME_NOW: None,
|
||||
DEBUG_LEVEL: 0,
|
||||
DEBUG_REDACTION: True,
|
||||
DECODED_ID_TOKEN: None,
|
||||
ENTITY_CL_DELAY_START: 1,
|
||||
ENTITY_CL_START: 1,
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
## Definitions
|
||||
```
|
||||
<AdminAssigneeType> ::= group|user|serviceaccount|unknown
|
||||
<AdminAssigneeTypeList> ::= "<AdminAssigneeType>(,<AdminAssigneeType>)*"
|
||||
<DomainName> ::= <String>(.<String>)+
|
||||
<EmailAddress> ::= <String>@<DomainName>
|
||||
<GroupItem> ::= <EmailAddress>|<UniqueID>|<String>
|
||||
@@ -1475,16 +1477,25 @@ gam delete admin <RoleAssignmentId>
|
||||
## Display administrators
|
||||
```
|
||||
gam print admins [todrive <ToDriveAttribute>*]
|
||||
[user|group <EmailAddress>|<UniqueID>] [role <RoleItem>] [condition]
|
||||
[privileges] [oneitemperrow]
|
||||
[user|group <EmailAddress>|<UniqueID>] [role <RoleItem>]
|
||||
[types <AdminAssigneeTypeList>]
|
||||
[recursive] [condition] [privileges] [oneitemperrow]
|
||||
gam show admins
|
||||
[user|group <EmailAddress>|<UniqueID>] [role <RoleItem>] [condition] [privileges]
|
||||
[user|group <EmailAddress>|<UniqueID>] [role <RoleItem>]
|
||||
[types <AdminAssigneeTypeList>]
|
||||
[recursive] [condition] [privileges]
|
||||
```
|
||||
By default, all administrators and roles are displayed; choose from the following
|
||||
options to limit the display:
|
||||
* `user <UserItem>` - Display only this administrator
|
||||
* `user|group <EmailAddress>|<UniqueID>` - Display assignments to this administrator
|
||||
* `role <RoleItem>` - Display only administrators with this role
|
||||
|
||||
By default, all admin assignee types are displayed. use `types <AdminAssigneeTypeList>` to filter
|
||||
admin assignments by the type of the assignee.
|
||||
|
||||
By default, assignments to security groups are displayed as a single item; use `recursive`
|
||||
to display assignments to the members of the security groups; the security group membershop is recursively expanded.
|
||||
|
||||
* `condition` - Display any conditions associated with a role assignment
|
||||
* `privileges` - Display privileges associated with each role assignment
|
||||
|
||||
|
||||
@@ -265,6 +265,7 @@
|
||||
## Named items
|
||||
```
|
||||
<AccessToken> ::= <String>
|
||||
<AdminAssigneeType> ::= group|user|serviceaccount|unknown
|
||||
<AlertID> ::= <String>
|
||||
<APIScopeURL> ::= <String>
|
||||
<APPID> ::= <String>
|
||||
|
||||
@@ -53,286 +53,7 @@ You must enable access to policies in the GCP cloud console.
|
||||
These are the supported policies GAM can show today.
|
||||
|
||||
See: https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings
|
||||
```
|
||||
user_takeout_status (is takeout enabled for service)
|
||||
blogger.user_takeout
|
||||
books.user_takeout
|
||||
location_history.user_takeout
|
||||
maps.user_takeout
|
||||
pay.user_takeout
|
||||
photos.user_takeout
|
||||
play.user_takeout
|
||||
play_console.user_takeout
|
||||
youtube.user_takeout
|
||||
service_status (is service enabled)
|
||||
ad_manager
|
||||
ads
|
||||
adsense
|
||||
alerts
|
||||
analytics
|
||||
applied_digital_skills
|
||||
appsheet
|
||||
arts_and_culture
|
||||
beyondcorp_enterprise
|
||||
blogger
|
||||
bookmarks
|
||||
books
|
||||
calendar
|
||||
campaign_manager
|
||||
chat
|
||||
chrome_canvas
|
||||
chrome_remote_desktop
|
||||
chrome_sync
|
||||
chrome_web_store
|
||||
classroom
|
||||
cloud
|
||||
cloud_search
|
||||
colab
|
||||
cs_first
|
||||
data_studio
|
||||
developers
|
||||
domains
|
||||
drive_and_docs
|
||||
earth
|
||||
enterprise_service_restrictions
|
||||
experimental_apps
|
||||
feedburner
|
||||
fi
|
||||
gmail
|
||||
groups
|
||||
groups_for_business
|
||||
jamboard
|
||||
keep
|
||||
location_history
|
||||
managed_play
|
||||
maps
|
||||
material_gallery
|
||||
meet
|
||||
merchant_center
|
||||
messages
|
||||
migrate
|
||||
my_business
|
||||
my_maps
|
||||
news
|
||||
partner_dash
|
||||
pay
|
||||
pay_for_business
|
||||
photos
|
||||
pinpoint
|
||||
play
|
||||
play_books_partner_center
|
||||
play_console
|
||||
public_data
|
||||
question_hub
|
||||
scholar_profiles
|
||||
search_ads_360
|
||||
search_and_assistant
|
||||
search_console
|
||||
sites
|
||||
socratic
|
||||
takeout
|
||||
tasks
|
||||
third_party_app_backups
|
||||
translate
|
||||
trips
|
||||
vault
|
||||
voice
|
||||
work_insights
|
||||
youtube
|
||||
calendar.appointment_schedules
|
||||
enablePayments
|
||||
chat.chat_apps_access
|
||||
enableApps
|
||||
enableWebhooks
|
||||
chat.chat_file_sharing
|
||||
externalFileSharing
|
||||
internalFileSharing
|
||||
chat.chat_history
|
||||
enableChatHistory
|
||||
historyOnByDefault
|
||||
allowUserModification
|
||||
chat.external_chat_restriction
|
||||
allowExternalChat
|
||||
chat.space_history
|
||||
historyState
|
||||
classroom.api_data_access
|
||||
enableApiAccess
|
||||
classroom.class_membership
|
||||
whoCanJoinClasses
|
||||
whichClassesCanUsersJoin
|
||||
classroom.guardian_access
|
||||
allowAccess
|
||||
whoCanManageGuardianAccess
|
||||
classroom.originality_reports
|
||||
enableOriginalityReportsSchoolMatches
|
||||
classroom.roster_import
|
||||
rosterImportOption
|
||||
classroom.student_unenrollment
|
||||
whoCanUnenrollStudents
|
||||
classroom.teacher_permissions
|
||||
whoCanCreateClasses
|
||||
cloud_sharing_options.cloud_data_sharing
|
||||
sharingOptions
|
||||
detector.regular_expression
|
||||
displayName
|
||||
regularExpression
|
||||
createTime
|
||||
updateTime
|
||||
detector.word_list
|
||||
displayName
|
||||
wordList
|
||||
createTime
|
||||
updateTime
|
||||
description
|
||||
drive_and_docs.drive_for_desktop
|
||||
allowDriveForDesktop
|
||||
restrictToAuthorizedDevices
|
||||
showDownloadLink
|
||||
allowRealTimePresence
|
||||
drive_and_docs.external_sharing
|
||||
externalSharingMode
|
||||
allowReceivingExternalFiles
|
||||
warnForSharingOutsideAllowlistedDomains
|
||||
allowReceivingFilesOutsideAllowlistedDomains
|
||||
allowNonGoogleInvitesInAllowlistedDomains
|
||||
warnForExternalSharing
|
||||
allowNonGoogleInvites
|
||||
allowPublishingFiles
|
||||
accessCheckerSuggestions
|
||||
allowedPartiesForDistributingContent
|
||||
drive_and_docs.file_security_update
|
||||
securityUpdate
|
||||
allowUsersToManageUpdate
|
||||
drive_and_docs.shared_drive_creation
|
||||
allowSharedDriveCreation
|
||||
orgUnitForNewSharedDrives
|
||||
customOrgUnit
|
||||
allowManagersToOverrideSettings
|
||||
allowExternalUserAccess
|
||||
allowNonMemberAccess
|
||||
allowedPartiesForDownloadPrintCopy
|
||||
allowContentManagersToShareFolders
|
||||
gmail.auto_forwarding
|
||||
enableAutoForwarding
|
||||
gmail.confidential_mode
|
||||
enableConfidentialMode
|
||||
gmail.email_attachment_safety
|
||||
enableEncryptedAttachmentProtection
|
||||
encryptedAttachmentProtectionConsequence
|
||||
enableAttachmentWithScriptsProtection
|
||||
attachmentWithScriptsProtectionConsequence
|
||||
enableAnomalousAttachmentProtection
|
||||
anomalousAttachmentProtectionConsequence
|
||||
allowedAnomalousAttachmentFiletypes
|
||||
applyFutureRecommendedSettingsAutomatically
|
||||
encryptedAttachmentProtectionQuarantineId
|
||||
attachmentWithScriptsProtectionQuarantineId
|
||||
anomalousAttachmentProtectionQuarantineId
|
||||
gmail.email_image_proxy_bypass
|
||||
imageProxyBypassPattern
|
||||
enableImageProxy
|
||||
gmail.enhanced_pre_delivery_message_scanning
|
||||
enableImprovedSuspiciousContentDetection
|
||||
gmail.enhanced_smime_encryption
|
||||
enableSmimeEncryption
|
||||
allowUserToUploadCertificates
|
||||
gmail.gmail_name_format
|
||||
allowCustomDisplayNames
|
||||
defaultDisplayNameFormat
|
||||
gmail.imap_access
|
||||
enableImapAccess
|
||||
gmail.links_and_external_images
|
||||
enableShortenerScanning
|
||||
enableExternalImageScanning
|
||||
enableAggressiveWarningsOnUntrustedLinks
|
||||
applyFutureSettingsAutomatically
|
||||
gmail.per_user_outbound_gateway
|
||||
allowUsersToUseExternalSmtpServers
|
||||
gmail.pop_access
|
||||
enablePopAccess
|
||||
gmail.spoofing_and_authentication
|
||||
detectDomainNameSpoofing
|
||||
detectEmployeeNameSpoofing
|
||||
detectDomainSpoofingFromUnauthenticatedSenders
|
||||
detectUnauthenticatedEmails
|
||||
domainNameSpoofingConsequence
|
||||
employeeNameSpoofingConsequence
|
||||
domainSpoofingConsequence
|
||||
unauthenticatedEmailConsequence
|
||||
detectGroupsSpoofing
|
||||
groupsSpoofingVisibilityType
|
||||
groupsSpoofingConsequence
|
||||
applyFutureSettingsAutomatically
|
||||
domainNameSpoofingQuarantineId
|
||||
employeeNameSpoofingQuarantineId
|
||||
domainSpoofingQuarantineId
|
||||
unauthenticatedEmailQuarantineId
|
||||
groupsSpoofingQuarantineId
|
||||
gmail.user_email_uploads
|
||||
enableMailAndContactsImport
|
||||
gmail.workspace_sync_for_outlook
|
||||
enableGoogleWorkspaceSyncForMicrosoftOutlook
|
||||
groups_for_business.groups_sharing
|
||||
ownersCanAllowIncomingMailFromPublic
|
||||
collaborationCapability
|
||||
createGroupsAccessLevel
|
||||
ownersCanAllowExternalMembers
|
||||
ownersCanHideGroups
|
||||
newGroupsAreHidden
|
||||
viewTopicsDefaultAccessLevel
|
||||
meet.safety_access
|
||||
meetingsAllowedToJoin
|
||||
meet.safety_domain
|
||||
usersAllowedToJoin
|
||||
meet.safety_external_participants
|
||||
enableExternalLabel
|
||||
meet.safety_host_management
|
||||
enableHostManagement
|
||||
meet.video_recording
|
||||
enableRecording
|
||||
rule.dlp
|
||||
displayName
|
||||
description
|
||||
triggers
|
||||
condition
|
||||
action
|
||||
state
|
||||
createTime
|
||||
updateTime
|
||||
ruleTypeMetadata
|
||||
rule.system_defined_alerts
|
||||
displayName
|
||||
description
|
||||
action
|
||||
state
|
||||
createTime
|
||||
updateTime
|
||||
security.advanced_protection_program
|
||||
enableAdvancedProtectionSelfEnrollment
|
||||
securityCodeOption
|
||||
security.less_secure_apps
|
||||
allowLessSecureApps
|
||||
security.login_challenges
|
||||
enableEmployeeIdChallenge
|
||||
security.password
|
||||
allowedStrength
|
||||
minimumLength
|
||||
maximumLength
|
||||
enforceRequirementsAtLogin
|
||||
allowReuse
|
||||
expirationDuration
|
||||
security.session_controls
|
||||
webSessionDuration
|
||||
security.super_admin_account_recovery
|
||||
enableAccountRecovery
|
||||
security.user_account_recovery
|
||||
enableAccountRecovery
|
||||
sites.sites_creation_and_modification
|
||||
allowSitesCreation
|
||||
allowSitesModification
|
||||
workspace_marketplace.apps_allowlist
|
||||
apps
|
||||
```
|
||||
|
||||
## Display Cloud Identity Policies
|
||||
Display selected policies.
|
||||
```
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
- [Plain Text](#plain-text)
|
||||
- [HTML](#html)
|
||||
- [Read data from a Google Sheet](#read-data-from-a-google-sheet)
|
||||
- [Limited Service Account Access](#limited-service-account-access)
|
||||
- [Read data from a Google Cloud Storage File](#read-data-from-a-google-cloud-storage-file)
|
||||
- [Plain Text](#plain-text)
|
||||
- [CSV](#csv)
|
||||
@@ -79,6 +80,25 @@ Example:
|
||||
```
|
||||
gam csv gsheet you@exmaple.com <DriveFileIDEntity> "Sheet 1" gam create user firstname "~FirstName" lastname "~lastName" email "~email"
|
||||
```
|
||||
|
||||
## Limited Service Account Access
|
||||
If you want to disable a user's service account access to Drive and Sheets but still allow reading command data from Google Docs and Sheets,
|
||||
issue the following commands. The admin specified in `gam oauth create` can read command data from Docs and Sheets to which it has access.
|
||||
```
|
||||
gam config commanddata_clientaccess true save
|
||||
gam oauth create
|
||||
Enable the following and proceed to authorization.
|
||||
|
||||
[*] 42) Drive API - commanddata_clientaccess
|
||||
[*] 54) Sheets API - commanddata_clientaccess
|
||||
```
|
||||
In these options, the `<EmailAddress> is not used, but for clarity you may want to specify the
|
||||
email address of the admin specified in `gam oauth create`.
|
||||
```
|
||||
gdoc <EmailAddress> <DriveFileIDEntity>|<DriveFileNameEntity>|(<SharedDriveEntity> <SharedDriveFileNameEntity>)
|
||||
gsheet <EmailAddress> <DriveFileIDEntity>|<DriveFileNameEntity>|(<SharedDriveEntity> <SharedDriveFileNameEntity>) <SheetEntity>
|
||||
```
|
||||
|
||||
## Read data from a Google Cloud Storage File
|
||||
```
|
||||
<StorageBucketName> ::= <String>
|
||||
|
||||
@@ -10,6 +10,74 @@ Add the `-s` option to the end of the above commands to suppress creating the `g
|
||||
|
||||
See [Downloads-Installs-GAM7](https://github.com/GAM-team/GAM/wiki/Downloads-Installs) for Windows or other options, including manual installation
|
||||
|
||||
### 7.25.01
|
||||
|
||||
Fixed bug in `gam config timezone <String>` to handle timezone abbreviations correctly;
|
||||
they were incorrectly shifted to lowercase.
|
||||
|
||||
### 7.25.00
|
||||
|
||||
Removed a capabilty added in 7.24.00 that allowed reading command data from Google Docs and Sheets
|
||||
when a user's service account access to Drive and Sheets had been disabled. Jay was concerned
|
||||
that this change could be exploited to give access to all user's files.
|
||||
|
||||
This capability has been replaced by issuing the following commands. The admin specified in `gam oauth create`
|
||||
can read command data from Docs and Sheets to which it has access.
|
||||
```
|
||||
gam config commanddata_clientaccess true save
|
||||
gam oauth create
|
||||
Enable the following and proceed to authorization.
|
||||
|
||||
[*] 42) Drive API - commanddata_clientaccess
|
||||
[*] 54) Sheets API - commanddata_clientaccess
|
||||
```
|
||||
|
||||
* See: https://github.com/GAM-team/GAM/wiki/Command-Data-From-Google-Docs-Sheets-Storage#limited-service-account-access
|
||||
|
||||
Fixed in bug in `gam report` that caused a trap with either of the `thismonth` or `previousmonths` options were used.
|
||||
|
||||
Upgraded to Python 3.14.0.
|
||||
|
||||
### 7.24.01
|
||||
|
||||
Updated GAM to handle the following error that occurs when GAM tries to authenticate
|
||||
as a user that has been disabled by Google.
|
||||
```
|
||||
ERROR: Authentication Token Error - invalid_account: Forbidden
|
||||
```
|
||||
|
||||
### 7.24.00
|
||||
|
||||
If you want to disable a user's service account access to Drive and Sheets but still allow reading command data from Google Docs and Sheets,
|
||||
issue the following command and make these settings:
|
||||
```
|
||||
gam user user@domain.com update serviceaccount
|
||||
|
||||
[ ] 20) Drive API (supports readonly)
|
||||
[*] 21) Drive API - read command data
|
||||
[ ] 42) Sheets API (supports readonly)
|
||||
[*] 43) Sheets API - read command data
|
||||
```
|
||||
|
||||
### 7.23.07
|
||||
|
||||
Fixed bug in `gam print|show admins` where all admin assignments were not displayed when
|
||||
`types <AdminAssigneeTypeList>` was not specified, i.e., all assignments should be displayed.
|
||||
|
||||
### 7.23.06
|
||||
|
||||
Added option `types <AdminAssigneeTypeList>` to `gam print|show admins` that allows filtering
|
||||
of admin assignments by the type of the assignee; by default, all assignee types are displayed.
|
||||
```
|
||||
<AdminAssigneeType> ::= group|user|serviceaccount|unknown
|
||||
<AdminAssigneeTypeList> ::= "<AdminAssigneeType>(,<AdminAssigneeType>)*"
|
||||
```
|
||||
|
||||
### 7.23.05
|
||||
|
||||
Added option `recursive` to `gam print|show admins` that will display assignments to the members
|
||||
of security groups assigned to roles; the security group membership is recursively expanded.
|
||||
|
||||
### 7.23.04
|
||||
|
||||
Added option `addcsvdata <FieldName> <String>` to `gam <UserTypeEntity> print events`
|
||||
|
||||
@@ -252,9 +252,9 @@ writes the credentials into the file oauth2.txt.
|
||||
admin@server:/Users/admin$ rm -f /Users/admin/GAMConfig/oauth2.txt
|
||||
admin@server:/Users/admin$ gam version
|
||||
WARNING: Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: /Users/admin/GAMConfig/oauth2.txt, Not Found
|
||||
GAM 7.23.04 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.25.01 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.7 64-bit final
|
||||
Python 3.14.0 64-bit final
|
||||
macOS Tahoe 26.0.1 x86_64
|
||||
Path: /Users/admin/bin/gam7
|
||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
@@ -990,9 +990,9 @@ writes the credentials into the file oauth2.txt.
|
||||
C:\>del C:\GAMConfig\oauth2.txt
|
||||
C:\>gam version
|
||||
WARNING: Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: C:\GAMConfig\oauth2.txt, Not Found
|
||||
GAM 7.22.00 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM 7.25.01 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.7 64-bit final
|
||||
Python 3.14.0 64-bit final
|
||||
Windows-10-10.0.17134 AMD64
|
||||
Path: C:\GAM7
|
||||
Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
## Lists of basic items
|
||||
```
|
||||
<AdminAssigneeTypeList> ::= "<AdminAssigneeType>(,<AdminAssigneeType>)*"
|
||||
<APIScopeURLList> ::= "<APIScopeURL>(,<APIScopeURL>)*"
|
||||
<ASPIDList> ::= "<ASPID>(,<ASPID>)*"
|
||||
<AssetTagList> ::= "<AssetTag>(,<AssetTag>)*"
|
||||
|
||||
@@ -330,16 +330,16 @@ you want the updated data copied to `Latest` so you don't have to remember what
|
||||
gam redirect csv - todrive tdfileid <DriveFileID> tdupdatesheet tdsheet Tuesday tdbackupsheet "Backup Tuesday" tdcopysheet "Latest" ...
|
||||
```
|
||||
## Limited Service Account Access
|
||||
If you want to limit a user's service account access but still allow `todrive',
|
||||
issue the following command and authorize the additional service account APIs:
|
||||
If you want to limit a user's service account access to Drive, Gmail and Sheets but still allow `todrive`,
|
||||
issue the following command and make these settings:
|
||||
```
|
||||
gam user user@domain.com update serviceaccount`
|
||||
gam user user@domain.com update serviceaccount
|
||||
|
||||
Authorize these APIs:
|
||||
|
||||
Drive API - todrive
|
||||
Gmail API - Send Messages - including todrive
|
||||
Sheets API - todrive
|
||||
[ ] 20) Drive API (supports readonly)
|
||||
[*] 22) Drive API - write todrive data - has access to all Drive
|
||||
[*] 31) Gmail API - Send Messages - including todrive
|
||||
[ ] 42) Sheets API (supports readonly)
|
||||
[*] 44) Sheets API - write todrive data - has access to all Sheets
|
||||
```
|
||||
|
||||
## No Service Account Access
|
||||
|
||||
@@ -327,7 +327,7 @@ gam <UserTypeEntity> show chatspaces
|
||||
By default, chat spaces of all types are displayed.
|
||||
* `types <ChatSpaceTypeList>` - Display specific types of spaces.
|
||||
|
||||
When listing Chat Spaces, the Chat API does not return the `accessSettings` field; if you need to see this fieldf,
|
||||
When listing Chat Spaces, the Chat API does not return the `accessSettings` field; if you need to see this field,
|
||||
add `showaccesssettings` to the command. This requires an additional Chat API call per chat space of type `SPACE`
|
||||
to get the `accessSettings` field.
|
||||
|
||||
@@ -343,7 +343,7 @@ gam <UserTypeEntity> print chatspaces [todrive <ToDriveAttribute>*]
|
||||
By default, chat spaces of all types are displayed.
|
||||
* `types <ChatSpaceTypeList>` - Display specific types of spaces.
|
||||
|
||||
When listing Chat Spaces, the Chat API does not return the `accessSettings` field; if you need to see this fieldf,
|
||||
When listing Chat Spaces, the Chat API does not return the `accessSettings` field; if you need to see this field,
|
||||
add `showaccesssettings` to the command. This requires an additional Chat API call per chat space of type `SPACE`
|
||||
to get the `accessSettings` field.
|
||||
|
||||
@@ -401,7 +401,7 @@ By default, all chat spaces of type SPACE are displayed.
|
||||
* `query <String> [querytime<String> <Time>]` - Display selected chat spaces
|
||||
* See: https://developers.google.com/workspace/chat/api/reference/rest/v1/spaces/search
|
||||
|
||||
When listing Chat Spaces, the Chat API does not return the `accessSettings` field; if you need to see this fieldf,
|
||||
When listing Chat Spaces, the Chat API does not return the `accessSettings` field; if you need to see this field,
|
||||
add `showaccesssettings` to the command. This requires an additional Chat API call per chat space of type `SPACE`
|
||||
to get the `accessSettings` field.
|
||||
|
||||
@@ -419,7 +419,7 @@ By default, all chat spaces of type SPACE are displayed.
|
||||
* `query <String> [querytime<String> <Time>]` - Display selected chat spaces
|
||||
* See: https://developers.google.com/workspace/chat/api/reference/rest/v1/spaces/search
|
||||
|
||||
When listing Chat Spaces, the Chat API does not return the `accessSettings` field; if you need to see this fieldf,
|
||||
When listing Chat Spaces, the Chat API does not return the `accessSettings` field; if you need to see this field,
|
||||
add `showaccesssettings` to the command. This requires an additional Chat API call per chat space of type `SPACE`
|
||||
to get the `accessSettings` field.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
- [Delete Vault Saved Queries](#delete-vault-saved-queries)
|
||||
- [Display Vault Saved Queries](#display-vault-saved-queries)
|
||||
- [Takeout](#takeout)
|
||||
- [Copy a Takeout Bucket](#copy-a-takeoutbucket)
|
||||
- [Copy a Takeout Bucket](#copy-a-takeout-bucket)
|
||||
- [Download a Takeout Bucket](#download-a-takeout-bucket)
|
||||
|
||||
## API documentation
|
||||
@@ -848,7 +848,7 @@ gam create vaultquery <MatterItem> [name <String>]
|
||||
[<JSONData>]
|
||||
[shownames]
|
||||
[showdetails|returnidonly|formatjson]
|
||||
``
|
||||
```
|
||||
|
||||
If `name <String>` is omitted, the query is named `GAM <corpus> Query - <Time>`
|
||||
|
||||
@@ -928,7 +928,7 @@ Select fields to display:
|
||||
|
||||
The `shownames` argument controls whether org unit and shared drive names are displayed in queries; additional API calls are required to get the names.
|
||||
|
||||
# Takeout
|
||||
## Takeout
|
||||
Many thanks to Jay for these commands and documentation.
|
||||
|
||||
GAM 6.42.00 and newer support copying and downloading Google Cloud Storage (GCS) buckets generated by [organization-wide Takeout](https://support.google.com/a/answer/100458?hl=en).
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
Print the current version of Gam with details
|
||||
```
|
||||
gam version
|
||||
GAM 7.23.04 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.25.01 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.7 64-bit final
|
||||
Python 3.14.0 64-bit final
|
||||
macOS Tahoe 26.0.1 x86_64
|
||||
Path: /Users/Admin/bin/gam7
|
||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
@@ -15,9 +15,9 @@ Time: 2023-06-02T21:10:00-07:00
|
||||
Print the current version of Gam with details and time offset information
|
||||
```
|
||||
gam version timeoffset
|
||||
GAM 7.23.04 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.25.01 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.7 64-bit final
|
||||
Python 3.14.0 64-bit final
|
||||
macOS Tahoe 26.0.1 x86_64
|
||||
Path: /Users/Admin/bin/gam7
|
||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
@@ -27,9 +27,9 @@ Your system time differs from www.googleapis.com by less than 1 second
|
||||
Print the current version of Gam with extended details and SSL information
|
||||
```
|
||||
gam version extended
|
||||
GAM 7.23.04 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.25.01 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.7 64-bit final
|
||||
Python 3.14.0 64-bit final
|
||||
macOS Tahoe 26.0.1 x86_64
|
||||
Path: /Users/Admin/bin/gam7
|
||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
@@ -68,7 +68,7 @@ MacOS High Sierra 10.13.6 x86_64
|
||||
Path: /Users/Admin/bin/gam7
|
||||
Version Check:
|
||||
Current: 5.35.08
|
||||
Latest: 7.22.00
|
||||
Latest: 7.25.01
|
||||
echo $?
|
||||
1
|
||||
```
|
||||
@@ -76,7 +76,7 @@ echo $?
|
||||
Print the current version number without details
|
||||
```
|
||||
gam version simple
|
||||
7.22.00
|
||||
7.25.01
|
||||
```
|
||||
In Linux/MacOS you can do:
|
||||
```
|
||||
@@ -86,9 +86,9 @@ echo $VER
|
||||
Print the current version of Gam and address of this Wiki
|
||||
```
|
||||
gam help
|
||||
GAM 7.22.00 - https://github.com/GAM-team/GAM
|
||||
GAM 7.25.01 - https://github.com/GAM-team/GAM
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.7 64-bit final
|
||||
Python 3.14.0 64-bit final
|
||||
macOS Tahoe 26.0.1 x86_64
|
||||
Path: /Users/Admin/bin/gam7
|
||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
|
||||
@@ -140,6 +140,11 @@ cmdlog_max_kilo_bytes
|
||||
Maximum kilobytes per log file
|
||||
Default: 1000
|
||||
Range: 100 - 10000
|
||||
commanddata_clientaccess
|
||||
Enable/disable use of client access rather than service account access for the
|
||||
admin specified in `gam oauth create` when reading command data from Docs and Sheets
|
||||
to which it has access.
|
||||
Default: False
|
||||
config_dir
|
||||
GAM config directory containing client_secrets.json, oauth2.txt, oauth2service.json
|
||||
and extra_args.txt
|
||||
|
||||
Reference in New Issue
Block a user