mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-04 06:11:39 +00:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ce48a95c9 | ||
|
|
2dafbfbcfc | ||
|
|
e03086866a | ||
|
|
0422bf22ea | ||
|
|
f3d9f3d518 | ||
|
|
ea9fd3f363 | ||
|
|
bed9db37ad | ||
|
|
072dc4809a | ||
|
|
6db2309fc4 | ||
|
|
cbb0c81652 | ||
|
|
f68aca8361 | ||
|
|
d63fdb4ed9 | ||
|
|
226781766b | ||
|
|
434e30d57c | ||
|
|
2ab059926b | ||
|
|
5ae25495f7 | ||
|
|
20e226e57d | ||
|
|
b4677585bb | ||
|
|
3a1437872c | ||
|
|
602dce2f5a | ||
|
|
8ce930f01b | ||
|
|
9631882be0 | ||
|
|
32d2858e4b | ||
|
|
98370925e7 | ||
|
|
1ef5d030f6 | ||
|
|
d50b5fb61e | ||
|
|
e070e92be2 | ||
|
|
b3b6fff2f1 | ||
|
|
fea94fcc1c | ||
|
|
a0cd228110 | ||
|
|
acfcd8b723 | ||
|
|
a26494e5c6 | ||
|
|
5605e5d1b6 | ||
|
|
e0fdac6e17 | ||
|
|
53dc8e3265 | ||
|
|
993a0b403e | ||
|
|
2d7d118d32 | ||
|
|
f2bc704fd6 |
23
.github/workflows/build.yml
vendored
23
.github/workflows/build.yml
vendored
@@ -158,7 +158,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
cache.tar.xz
|
||||
key: gam-${{ matrix.jid }}-20250930
|
||||
key: gam-${{ matrix.jid }}-20251007
|
||||
|
||||
- name: Untar Cache archive
|
||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
|
||||
- name: Use pre-compiled Python for testing
|
||||
if: matrix.python != ''
|
||||
uses: actions/setup-python@3d1e2d2ca0a067f27da6fec484fce7f5256def85 # v5.6.0
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
allow-prereleases: true
|
||||
@@ -335,11 +335,11 @@ jobs:
|
||||
# --libdir=lib is needed so Python can find OpenSSL libraries
|
||||
"${PERL}" ./Configure --libdir=lib --prefix="${OPENSSL_INSTALL_PATH}" $OPENSSL_CONFIG_OPTS
|
||||
|
||||
# - name: Rename GNU link on Windows
|
||||
# if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
||||
# shell: bash
|
||||
# run: |
|
||||
# mv -v /usr/bin/link /usr/bin/gnulink
|
||||
- name: Rename GNU link on Windows
|
||||
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
mv -v /usr/bin/link /usr/bin/gnulink
|
||||
|
||||
- name: Make OpenSSL
|
||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
||||
@@ -943,11 +943,11 @@ jobs:
|
||||
$gam calendar $gam_user printevents after -0d
|
||||
$gam config enable_dasa false save
|
||||
matterid=uid:$($gam create vaultmatter name "GHA matter $newbase" description "test matter" returnidonly)
|
||||
$gam create vaulthold matter $matterid name "GHA hold $newbase" corpus mail accounts $newuser
|
||||
$gam create vaulthold matter $matterid name "GHA hold ${newbase}" corpus mail ou "$newou"
|
||||
$gam print vaultmatters matterstate open
|
||||
$gam print vaultholds matter $matterid
|
||||
$gam print vaultcount matter $matterid corpus mail everyone todrive tdnobrowser
|
||||
$gam create vaultexport matter $matterid name "GHA export $newbase" corpus mail accounts $newuser
|
||||
$gam create vaultexport matter $matterid name "GHA export $newbase" corpus mail ou "$newou"
|
||||
$gam print exports matter $matterid | $gam csv - gam info export $matterid id:~~id~~
|
||||
$gam config enable_dasa true save
|
||||
$gam csv sample.csv gam user ~email add calendar id:$newresource
|
||||
@@ -1027,7 +1027,8 @@ jobs:
|
||||
else
|
||||
tar_folders="bin/"
|
||||
fi
|
||||
tar cJvvf cache.tar.xz $tar_folders
|
||||
echo '.git*' > ./excludes.txt
|
||||
tar cJvvf cache.tar.xz --exclude-from=excludes.txt $tar_folders
|
||||
|
||||
merge:
|
||||
if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
|
||||
@@ -1077,7 +1078,7 @@ jobs:
|
||||
echo "dateversion=${dateversion}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Publish draft release
|
||||
uses: softprops/action-gh-release@fbadcc90e88ecface60a0a0d123795b784ceb239 # v2.3.2
|
||||
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
|
||||
with:
|
||||
draft: true
|
||||
prerelease: false
|
||||
|
||||
2
.github/workflows/pypi.yml
vendored
2
.github/workflows/pypi.yml
vendored
@@ -30,6 +30,6 @@ jobs:
|
||||
python -m build
|
||||
|
||||
- name: Publish package distributions to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
||||
with:
|
||||
attestation: true
|
||||
|
||||
@@ -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,17 @@ 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>] [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]
|
||||
|
||||
# Alert Center
|
||||
|
||||
@@ -1932,12 +1940,12 @@ gam calendar|calendars <CalendarEntity> info events [<EventEntity>] [maxinstance
|
||||
[formatjson]
|
||||
gam calendar|calendars <CalendarEntity> show events [<EventEntity>] <EventDisplayProperty>*
|
||||
[fields <EventFieldNameList>] [showdayofweek]
|
||||
[countsonly]
|
||||
[formatjson]
|
||||
[countsonly|formatjson]
|
||||
gam calendar|calendars <CalendarEntity> print events [<EventEntity>] <EventDisplayProperty>*
|
||||
[fields <EventFieldNameList>] [showdayofweek]
|
||||
[countsonly [eventrowfilter]]
|
||||
[formatjson [quotechar <Character>]] [todrive <ToDriveAttribute>*]
|
||||
(addcsvdata <FieldName> <String>)*
|
||||
[eventrowfilter]
|
||||
[countsonly|(formatjson [quotechar <Character>])] [todrive <ToDriveAttribute>*]
|
||||
|
||||
gam calendar <CalendarEntity> addevent <EventAttribute>+ [<EventNotificationAttribute>]
|
||||
[showdayofweek]
|
||||
@@ -3745,16 +3753,14 @@ gam print domaincontacts|peoplecontacts [todrive <ToDriveAttribute>*]
|
||||
[sources <PeopleSourceName>]
|
||||
[query <String>]
|
||||
[mergesources <PeopleMergeSourceName>]
|
||||
[coountsonly]
|
||||
[allfields|(fields <PeopleFieldNameList>)] [showmetadata]
|
||||
[formatjson [quotechar <Character>]]
|
||||
[coountsonly|(formatjson [quotechar <Character>])]
|
||||
gam show domaincontacts|peoplecontacts
|
||||
[sources <PeopleSourceName>]
|
||||
[query <String>]
|
||||
[mergesources <PeopleMergeSourceName>]
|
||||
[coountsonly]
|
||||
[allfields|(fields <PeopleFieldNameList>)] [showmetadata]
|
||||
[formatjson]
|
||||
[coountsonly|formatjson]
|
||||
|
||||
gam info people|peopleprofile <PeopleResourceNameEntity>
|
||||
[allfields|(fields <PeopleFieldNameList>)] [showmetadata]
|
||||
@@ -3762,15 +3768,13 @@ gam info people|peopleprofile <PeopleResourceNameEntity>
|
||||
gam print people|peopleprofile [todrive <ToDriveAttribute>*]
|
||||
[query <String>]
|
||||
[mergesources <PeopleMergeSourceName>]
|
||||
[coountsonly]
|
||||
[allfields|(fields <PeopleFieldNameList>)] [showmetadata]
|
||||
[formatjson [quotechar <Character>]]
|
||||
[coountsonly|(formatjson [quotechar <Character>])]
|
||||
gam show people|peopleprofile
|
||||
[query <String>]
|
||||
[mergesources <PeopleMergeSourceName>]
|
||||
[coountsonly]
|
||||
[allfields|(fields <PeopleFieldNameList>)] [showmetadata]
|
||||
[formatjson]
|
||||
[coountsonly|formatjson]
|
||||
|
||||
# Email Audit Monitor
|
||||
|
||||
@@ -6315,12 +6319,11 @@ gam <UserTypeEntity> info events <UserCalendarEntity> [<EventEntity>] [maxinstan
|
||||
[formatjson]
|
||||
gam <UserTypeEntity> show events <UserCalendarEntity> [<EventEntity>] <EventDisplayProperty>*
|
||||
[fields <EventFieldNameList>] [showdayofweek]
|
||||
[countsonly]
|
||||
[formatjson]
|
||||
[countsonly|formatjson]
|
||||
gam <UserTypeEntity> print events <UserCalendarEntity> [<EventEntity>] <EventDisplayProperty>*
|
||||
[fields <EventFieldNameList>] [showdayofweek]
|
||||
[countsonly [eventrowfilter]]
|
||||
[formatjson [quotechar <Character>]] [todrive <ToDriveAttribute>*]
|
||||
[eventrowfilter]]
|
||||
[countsonly|(formatjson [quotechar <Character>])] [todrive <ToDriveAttribute>*]
|
||||
|
||||
gam <UserTypeEntity> update calattendees <UserCalendarEntity> <EventEntity> [anyorganizer]
|
||||
[<EventNotificationAttribute>] [splitupdate] [dryrun|doit]
|
||||
@@ -8344,13 +8347,13 @@ gam <UserTypeEntity> info contacts
|
||||
gam <UserTypeEntity> show contacts
|
||||
<PeoplePrintShowUserContactSelection>
|
||||
[orderby firstname|lastname|(lastmodified ascending)|(lastnodified descending)
|
||||
[countsonly|allfields|(fields <PeopleFieldNameList>)] [showgroups] [showmetadata]
|
||||
[formatjson]
|
||||
[allfields|(fields <PeopleFieldNameList>)] [showgroups] [showmetadata]
|
||||
[countsonly|formatjson]
|
||||
gam <UserTypeEntity> print contacts [todrive <ToDriveAttribute>*]
|
||||
<PeoplePrintShowUserContactSelection>
|
||||
[orderby firstname|lastname|(lastmodified ascending)|(lastnodified descending)
|
||||
[countsonly|allfields|(fields <PeopleFieldNameList>)] [[showgroups|showgroupnameslist] showmetadata]
|
||||
[formatjson [quotechar <Character>]]
|
||||
[allfields|(fields <PeopleFieldNameList>)] [[showgroups|showgroupnameslist] showmetadata]
|
||||
[countsonly|(formatjson [quotechar <Character>])]
|
||||
|
||||
<OtherContactsFieldName> ::=
|
||||
emailaddresses|
|
||||
@@ -8584,7 +8587,7 @@ gam <UserTypeEntity> info tasklist <TasklistEntity>
|
||||
gam <UserTypeEntity> show tasklists
|
||||
[countsonly|formatjson]
|
||||
gam <UserTypeEntity> print tasklists [todrive <ToDriveAttribute>*]
|
||||
[countsonly | (formatjson [quotechar <Character>])]
|
||||
[countsonly|(formatjson [quotechar <Character>])]
|
||||
|
||||
# Users - Shared Drives
|
||||
|
||||
|
||||
@@ -1,3 +1,81 @@
|
||||
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` 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`
|
||||
and `gam calendars <CalendarEntity> print events` that adds additional columns of data to the CSV file output.
|
||||
An example would be to get the calendar name in addition to the calendar ID when printing events.
|
||||
```
|
||||
gam redirect csv ./Resources.csv print resources fields email,name
|
||||
gam redirect csv ./ResourceEventCounts.csv multiprocess redirect stderr - multiprocess csv Resources.csv gam calendar "~resourceEmail" print events starttime -1y countsonly addcsvdata calendarName "~resourceName"
|
||||
```
|
||||
|
||||
Upgraded to OpenSSL 3.6.0.
|
||||
|
||||
7.23.03
|
||||
|
||||
Upgraded to OpenSSL 3.5.4.
|
||||
|
||||
@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
|
||||
"""
|
||||
|
||||
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
|
||||
__version__ = '7.23.03'
|
||||
__version__ = '7.25.01'
|
||||
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
#pylint: disable=wrong-import-position
|
||||
@@ -2113,12 +2113,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 +3049,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 +3109,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 +3716,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
|
||||
@@ -4799,10 +4810,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():
|
||||
@@ -5609,6 +5617,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])
|
||||
@@ -7868,6 +7882,13 @@ class CSVPrintFile():
|
||||
if title not in self.titlesSet:
|
||||
self.AddTitle(title)
|
||||
|
||||
def InsertTitles(self, position, titles):
|
||||
for title in titles if isinstance(titles, list) else [titles]:
|
||||
if title not in self.titlesSet:
|
||||
self.titlesSet.add(title)
|
||||
self.titlesList.insert(position, title)
|
||||
position += 1
|
||||
|
||||
def SetTitles(self, titles):
|
||||
self.titlesSet = set()
|
||||
self.titlesList = []
|
||||
@@ -8023,7 +8044,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:
|
||||
@@ -8176,7 +8197,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:
|
||||
@@ -8689,7 +8710,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
|
||||
@@ -8722,7 +8743,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:
|
||||
@@ -8870,7 +8891,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:
|
||||
@@ -9583,7 +9604,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:
|
||||
@@ -9625,7 +9646,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:
|
||||
@@ -9671,7 +9692,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))
|
||||
@@ -9701,13 +9722,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] == '-':
|
||||
@@ -9795,7 +9816,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
|
||||
@@ -9936,7 +9957,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)
|
||||
@@ -9945,7 +9966,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)
|
||||
@@ -9960,7 +9981,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()))
|
||||
@@ -11124,7 +11145,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:
|
||||
@@ -11170,7 +11191,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':
|
||||
@@ -11186,7 +11207,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:
|
||||
@@ -11297,7 +11320,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:
|
||||
@@ -12327,8 +12350,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
|
||||
@@ -16256,7 +16277,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)
|
||||
@@ -16269,7 +16290,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
|
||||
@@ -16983,22 +17004,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',
|
||||
}
|
||||
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>] [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]
|
||||
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']
|
||||
@@ -17026,17 +17064,14 @@ def doPrintShowAdmins():
|
||||
assignedTo = admin['assignedTo']
|
||||
admin['assignedToUnknown'] = False
|
||||
if assignedTo not in assignedToIdEmailMap:
|
||||
assigneeType = admin.get('assigneeType')
|
||||
assignedToField = ASSIGNEE_EMAILTYPE_TOFIELD_MAP.get(assigneeType, None)
|
||||
assigneeEmail, assigneeType = convertUIDtoEmailAddressWithType(f'uid:{assignedTo}', cd, sal,
|
||||
emailTypes=list(ASSIGNEE_EMAILTYPE_TOFIELD_MAP.keys()))
|
||||
if not assignedToField and assigneeType in ASSIGNEE_EMAILTYPE_TOFIELD_MAP:
|
||||
assignedToField = ASSIGNEE_EMAILTYPE_TOFIELD_MAP[assigneeType]
|
||||
if assigneeType == 'unknown':
|
||||
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:
|
||||
@@ -17046,16 +17081,22 @@ 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 = 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 = {}
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
@@ -17065,6 +17106,16 @@ def doPrintShowAdmins():
|
||||
userKey = kwargs['userKey'] = getEmailAddress()
|
||||
elif myarg == 'role':
|
||||
_, roleId = getRoleId()
|
||||
elif _getAssigneeTypes(myarg):
|
||||
pass
|
||||
elif myarg == 'recursive':
|
||||
recursive = True
|
||||
memberOptions = initMemberOptions()
|
||||
memberOptions[MEMBEROPTION_INCLUDEDERIVEDMEMBERSHIP] = True
|
||||
memberOptions[MEMBEROPTION_DISPLAYMATCH] = False
|
||||
memberDisplayOptions = initIPSGMGroupMemberDisplayOptions()
|
||||
for role in [Ent.ROLE_MEMBER, Ent.ROLE_MANAGER, Ent.ROLE_OWNER]:
|
||||
memberDisplayOptions[role]['show'] = True
|
||||
elif myarg == 'condition':
|
||||
fieldsList.append('condition')
|
||||
if csvPF:
|
||||
@@ -17073,6 +17124,8 @@ def doPrintShowAdmins():
|
||||
showPrivileges = True
|
||||
elif myarg == 'oneitemperrow':
|
||||
oneItemPerRow = True
|
||||
elif myarg == 'debug':
|
||||
debug = True
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
if roleId and not kwargs:
|
||||
@@ -17084,7 +17137,7 @@ def doPrintShowAdmins():
|
||||
admins = callGAPIpages(cd.roleAssignments(), 'list', 'items',
|
||||
pageMessage=getPageMessage(),
|
||||
throwReasons=[GAPI.INVALID, GAPI.USER_NOT_FOUND,
|
||||
GAPI.FORBIDDEN, GAPI.SERVICE_NOT_AVAILABLE,
|
||||
GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.SERVICE_NOT_AVAILABLE,
|
||||
GAPI.BAD_REQUEST, GAPI.CUSTOMER_NOT_FOUND,
|
||||
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
@@ -17092,39 +17145,75 @@ def doPrintShowAdmins():
|
||||
except (GAPI.invalid, GAPI.userNotFound):
|
||||
entityUnknownWarning(Ent.ADMINISTRATOR, userKey)
|
||||
return
|
||||
except (GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedExit([Ent.ADMINISTRATOR, userKey, Ent.ADMIN_ROLE, roleId], str(e))
|
||||
except GAPI.notFound as e:
|
||||
entityActionFailedExit([Ent.ADMIN_ROLE, kwargs['roleId']], str(e))
|
||||
except GAPI.serviceNotAvailable as e:
|
||||
entityActionFailedExit([Ent.ADMINISTRATOR, userKey], str(e))
|
||||
except (GAPI.badRequest, GAPI.customerNotFound):
|
||||
accessErrorExit(cd)
|
||||
except (GAPI.forbidden, GAPI.permissionDenied) as e:
|
||||
ClientAPIAccessDeniedExit(str(e))
|
||||
count = len(admins)
|
||||
groupMembers = {}
|
||||
expandedAdmins = []
|
||||
i = 0
|
||||
for admin in admins:
|
||||
i += 1
|
||||
if roleId and roleId != admin['roleId']:
|
||||
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()
|
||||
level = 0
|
||||
getGroupMembers(cd, assignedTo, allGroupRoles, membersList, membersSet, i, count,
|
||||
memberOptions, memberDisplayOptions, level, {Ent.TYPE_USER})
|
||||
groupMembers[assignedTo] = membersList[:]
|
||||
if not _setNamesFromIds(admin, _getPrivileges(admin)):
|
||||
continue
|
||||
if not groupMembers[assignedTo]:
|
||||
expandedAdmins.append(admin)
|
||||
continue
|
||||
admin['assigneeType'] = 'user'
|
||||
admin['assignedToGroup'] = assignedToIdEmailMap[assignedTo]['assigneeEmail']
|
||||
for member in groupMembers[assignedTo]:
|
||||
userAdmin = admin.copy()
|
||||
userAdmin['assignedTo'] = member['id']
|
||||
_setNamesFromIds(userAdmin, _getPrivileges(admin))
|
||||
expandedAdmins.append(userAdmin)
|
||||
admins = expandedAdmins
|
||||
count = len(expandedAdmins)
|
||||
if not csvPF:
|
||||
count = len(admins)
|
||||
performActionNumItems(count, Ent.ADMIN_ROLE_ASSIGNMENT)
|
||||
Ind.Increment()
|
||||
i = 0
|
||||
for admin in admins:
|
||||
for admin in expandedAdmins:
|
||||
i += 1
|
||||
if roleId and roleId != admin['roleId']:
|
||||
continue
|
||||
_setNamesFromIds(admin, _getPrivileges(admin))
|
||||
printEntity([Ent.ADMIN_ROLE_ASSIGNMENT, admin['roleAssignmentId']], i, count)
|
||||
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
|
||||
if field != 'rolePrivileges':
|
||||
printKeyValueList([field, admin[field]])
|
||||
else:
|
||||
showJSON(None, admin[field])
|
||||
printKeyValueList([field, admin[field]])
|
||||
if showPrivileges:
|
||||
rolePrivileges = admin.get('rolePrivileges', [])
|
||||
jcount = len(rolePrivileges)
|
||||
if jcount > 0:
|
||||
printKeyValueList(['rolePrivileges', jcount])
|
||||
Ind.Increment()
|
||||
showJSON(None, rolePrivileges)
|
||||
Ind.Decrement()
|
||||
Ind.Decrement()
|
||||
Ind.Decrement()
|
||||
else:
|
||||
for admin in admins:
|
||||
if roleId and roleId != admin['roleId']:
|
||||
continue
|
||||
_setNamesFromIds(admin, _getPrivileges(admin))
|
||||
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:
|
||||
@@ -22429,12 +22518,12 @@ def infoUserPeopleContacts(users):
|
||||
|
||||
# gam <UserTypeEntity> print contacts [todrive <ToDriveAttribute>*] <PeoplePrintShowUserContactSelection>
|
||||
# [showgroups|showgroupnameslist] [orderby firstname|lastname|(lastmodified ascending)|(lastnodified descending)
|
||||
# [countsonly|allfields|fields <PeopleFieldNameList>] [showmetadata]
|
||||
# [formatjson [quotechar <Character>]]
|
||||
# [allfields|fields <PeopleFieldNameList>] [showmetadata]
|
||||
# [countsonly|(formatjson [quotechar <Character>])]
|
||||
# gam <UserTypeEntity> show contacts <PeoplePrintShowUserContactSelection>
|
||||
# [showgroups] [orderby firstname|lastname|(lastmodified ascending)|(lastnodified descending)
|
||||
# [countsonly|allfields|(fields <PeopleFieldNameList>)] [showmetadata]
|
||||
# [formatjson]
|
||||
# [allfields|(fields <PeopleFieldNameList>)] [showmetadata]
|
||||
# [countsonly|formatjson]
|
||||
def printShowUserPeopleContacts(users):
|
||||
entityType = Ent.USER
|
||||
entityTypeName = Ent.Singular(entityType)
|
||||
@@ -22473,6 +22562,8 @@ def printShowUserPeopleContacts(users):
|
||||
else:
|
||||
FJQC.GetFormatJSONQuoteChar(myarg, True)
|
||||
if countsOnly:
|
||||
if csvPF:
|
||||
csvPF.SetFormatJSON(False)
|
||||
fieldsList = ['emailAddresses']
|
||||
if contactQuery['mainContacts']:
|
||||
fields = _getPersonFields(PEOPLE_FIELDS_CHOICE_MAP, PEOPLE_CONTACTS_DEFAULT_FIELDS, fieldsList, parameters)
|
||||
@@ -22710,11 +22801,11 @@ def processUserPeopleOtherContacts(users):
|
||||
Ind.Decrement()
|
||||
|
||||
# gam <UserTypeEntity> print othercontacts [todrive <ToDriveAttribute>*] <OtherContactSelection>
|
||||
# [countsonly|allfields|(fields <OtherContactFieldNameList>)] [showmetadata]
|
||||
# [formatjson [quotechar <Character>]]
|
||||
# [allfields|(fields <OtherContactFieldNameList>)] [showmetadata]
|
||||
# [countsonly|(formatjson [quotechar <Character>])]
|
||||
# gam <UserTypeEntity> show othercontacts <OtherContactSelection>
|
||||
# [countsonly|allfields|(fields <OtherContactFieldNameList>)] [showmetadata]
|
||||
# [formatjson]
|
||||
# [allfields|(fields <OtherContactFieldNameList>)] [showmetadata]
|
||||
# [countsonly|formatjson]
|
||||
def printShowUserPeopleOtherContacts(users):
|
||||
entityType = Ent.USER
|
||||
entityTypeName = Ent.Singular(entityType)
|
||||
@@ -22744,6 +22835,8 @@ def printShowUserPeopleOtherContacts(users):
|
||||
else:
|
||||
FJQC.GetFormatJSONQuoteChar(myarg, True)
|
||||
if countsOnly:
|
||||
if csvPF:
|
||||
csvPF.SetFormatJSON(False)
|
||||
fieldsList = ['emailAddresses']
|
||||
fields = _getPersonFields(PEOPLE_OTHER_CONTACTS_FIELDS_CHOICE_MAP, PEOPLE_CONTACTS_DEFAULT_FIELDS, fieldsList, parameters)
|
||||
i, count, users = getEntityArgument(users)
|
||||
@@ -22813,6 +22906,8 @@ def _printShowPeople(source):
|
||||
function = 'searchDirectoryPeople'
|
||||
else:
|
||||
FJQC.GetFormatJSONQuoteChar(myarg, True)
|
||||
if countsOnly and csvPF:
|
||||
csvPF.SetFormatJSON(False)
|
||||
fields = _getPersonFields(PEOPLE_FIELDS_CHOICE_MAP, PEOPLE_CONTACTS_DEFAULT_FIELDS, fieldsList, parameters)
|
||||
printGettingAllEntityItemsForWhom(peopleEntityType, GC.Values[GC.DOMAIN], query=kwargs.get('query'))
|
||||
try:
|
||||
@@ -22850,29 +22945,24 @@ def doInfoDomainPeopleContacts():
|
||||
# gam print people|peopleprofile [todrive <ToDriveAttribute>*]
|
||||
# [query <String>]
|
||||
# [mergesources <PeopleMergeSourceName>]
|
||||
# [countsonly]
|
||||
# [allfields|(fields <PeopleFieldNameList>)] [showmetadata]
|
||||
# [formatjson [quotechar <Character>]]
|
||||
# [countsonly|(formatjson [quotechar <Character>])]
|
||||
# gam show people|peopleprofile
|
||||
# [query <String>]
|
||||
# [mergesources <PeopleMergeSourceName>]
|
||||
# [countsonly]
|
||||
# [allfields|(fields <PeopleFieldNameList>)] [showmetadata]
|
||||
# [formatjson]
|
||||
# [countsonlyformatjson]
|
||||
# gam print domaincontacts|peoplecontacts [todrive <ToDriveAttribute>*]
|
||||
# [sources <PeopleSourceName>]
|
||||
# [query <String>]
|
||||
# [mergesources <PeopleMergeSourceName>]
|
||||
# [countsonly]
|
||||
# [allfields|(fields <PeopleFieldNameList>)] [showmetadata]
|
||||
# [formatjson [quotechar <Character>]]
|
||||
# [countsonly|(formatjson [quotechar <Character>])]
|
||||
# gam show domaincontacts|peoplecontacts
|
||||
# [sources <PeopleSourceName>]
|
||||
# [query <String>]
|
||||
# [mergesources <PeopleMergeSourceName>]
|
||||
# [countsonly]
|
||||
# [allfields|(fields <PeopleFieldNameList>)] [showmetadata]
|
||||
# [formatjson]
|
||||
# [countsonlyformatjson]
|
||||
def doPrintShowDomainPeopleProfiles():
|
||||
_printShowPeople('profile')
|
||||
|
||||
@@ -40125,7 +40215,7 @@ def _createCalendarEvents(user, origCal, function, calIds, count, body, paramete
|
||||
else:
|
||||
if parameters['showDayOfWeek']:
|
||||
_getEventDaysOfWeek(event)
|
||||
_printCalendarEvent(user, calId, event, parameters['csvPF'], parameters['FJQC'])
|
||||
_printCalendarEvent(user, calId, event, parameters['csvPF'], parameters['FJQC'], {})
|
||||
except (GAPI.invalid, GAPI.required, GAPI.timeRangeEmpty, GAPI.eventDurationExceedsLimit,
|
||||
GAPI.requiredAccessLevel, GAPI.participantIsNeitherOrganizerNorAttendee,
|
||||
GAPI.malformedWorkingLocationEvent, GAPI.badRequest) as e:
|
||||
@@ -40229,7 +40319,7 @@ def _updateCalendarEvents(origUser, user, origCal, calIds, count, calendarEventE
|
||||
else:
|
||||
if parameters['showDayOfWeek']:
|
||||
_getEventDaysOfWeek(event)
|
||||
_printCalendarEvent(user, calId, event, parameters['csvPF'], parameters['FJQC'])
|
||||
_printCalendarEvent(user, calId, event, parameters['csvPF'], parameters['FJQC'], {})
|
||||
except (GAPI.notFound, GAPI.deleted) as e:
|
||||
if not checkCalendarExists(cal, calId, j, jcount):
|
||||
entityUnknownWarning(Ent.CALENDAR, calId, j, jcount)
|
||||
@@ -40526,10 +40616,12 @@ def _showCalendarEvent(primaryEmail, calId, eventEntityType, event, k, kcount, F
|
||||
showJSON(None, event, skipObjects)
|
||||
Ind.Decrement()
|
||||
|
||||
def _printCalendarEvent(user, calId, event, csvPF, FJQC):
|
||||
def _printCalendarEvent(user, calId, event, csvPF, FJQC, addCSVData):
|
||||
row = {'calendarId': calId, 'id': event['id']}
|
||||
if user:
|
||||
row['primaryEmail'] = user
|
||||
if addCSVData:
|
||||
row.update(addCSVData)
|
||||
flattenJSON(event, flattened=row, timeObjects=EVENT_TIME_OBJECTS)
|
||||
if not FJQC.formatJSON:
|
||||
csvPF.WriteRowTitles(row)
|
||||
@@ -40542,7 +40634,7 @@ def _printCalendarEvent(user, calId, event, csvPF, FJQC):
|
||||
csvPF.WriteRowNoFilter(row)
|
||||
|
||||
def _printShowCalendarEvents(origUser, user, origCal, calIds, count, calendarEventEntity,
|
||||
csvPF, FJQC, fieldsList):
|
||||
csvPF, FJQC, fieldsList, addCSVData):
|
||||
i = 0
|
||||
for calId in calIds:
|
||||
i += 1
|
||||
@@ -40568,7 +40660,7 @@ def _printShowCalendarEvents(origUser, user, origCal, calIds, count, calendarEve
|
||||
for event in events:
|
||||
if calendarEventEntity['showDayOfWeek']:
|
||||
_getEventDaysOfWeek(event)
|
||||
_printCalendarEvent(user, calId, event, csvPF, FJQC)
|
||||
_printCalendarEvent(user, calId, event, csvPF, FJQC, addCSVData)
|
||||
elif GC.Values[GC.CSV_OUTPUT_USERS_AUDIT] and user:
|
||||
csvPF.WriteRowNoFilter({'calendarId': calId, 'primaryEmail': user, 'id': ''})
|
||||
else:
|
||||
@@ -40586,6 +40678,8 @@ def _printShowCalendarEvents(origUser, user, origCal, calIds, count, calendarEve
|
||||
row = {'calendarId': calId}
|
||||
if user:
|
||||
row['primaryEmail'] = user
|
||||
if addCSVData:
|
||||
row.update(addCSVData)
|
||||
row['events'] = jcount
|
||||
if not calendarEventEntity['eventRowFilter']:
|
||||
csvPF.WriteRow(row)
|
||||
@@ -40816,6 +40910,8 @@ def _getCalendarPrintShowEventOptions(calendarEventEntity, entityType):
|
||||
csvPF = CSVPrintFile(['primaryEmail', 'calendarId', 'id'] if entityType == Ent.USER else ['calendarId', 'id'], 'sortall', indexedTitles=EVENT_INDEXED_TITLES) if Act.csvFormat() else None
|
||||
FJQC = FormatJSONQuoteChar(csvPF)
|
||||
fieldsList = []
|
||||
addCSVData = {}
|
||||
addCSVDataLoc = 2 if entityType == Ent.USER else 1
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if csvPF and myarg == 'todrive':
|
||||
@@ -40824,6 +40920,9 @@ def _getCalendarPrintShowEventOptions(calendarEventEntity, entityType):
|
||||
pass
|
||||
elif myarg == 'fields':
|
||||
_getEventFields(fieldsList)
|
||||
elif csvPF and myarg == 'addcsvdata':
|
||||
k = getString(Cmd.OB_STRING)
|
||||
addCSVData[k] = getString(Cmd.OB_STRING, minLen=0)
|
||||
elif myarg == 'countsonly':
|
||||
calendarEventEntity['countsOnly'] = True
|
||||
elif myarg == 'showdayofweek':
|
||||
@@ -40836,27 +40935,35 @@ def _getCalendarPrintShowEventOptions(calendarEventEntity, entityType):
|
||||
fieldsList = ['id']
|
||||
if csvPF:
|
||||
if calendarEventEntity['countsOnly']:
|
||||
csvPF.SetFormatJSON(False)
|
||||
csvPF.RemoveTitles(['id'])
|
||||
if addCSVData:
|
||||
csvPF.InsertTitles(addCSVDataLoc, sorted(addCSVData.keys()))
|
||||
csvPF.AddTitles(['events'])
|
||||
csvPF.SetSortAllTitles()
|
||||
calendarEventEntity['countsOnlyTitles'] = csvPF.titlesList[:]
|
||||
elif not FJQC.formatJSON and not fieldsList:
|
||||
csvPF.AddSortTitles(EVENT_PRINT_ORDER)
|
||||
else:
|
||||
if addCSVData:
|
||||
csvPF.InsertTitles(addCSVDataLoc, sorted(addCSVData.keys()))
|
||||
if not FJQC.formatJSON and not fieldsList:
|
||||
csvPF.AddTitles(EVENT_PRINT_ORDER)
|
||||
csvPF.SetSortAllTitles()
|
||||
_addEventEntitySelectFields(calendarEventEntity, fieldsList)
|
||||
return (csvPF, FJQC, fieldsList)
|
||||
return (csvPF, FJQC, fieldsList, addCSVData)
|
||||
|
||||
# gam calendars <CalendarEntity> print events <EventEntity> <EventDisplayProperties>*
|
||||
# [fields <EventFieldNameList>] [showdayofweek]
|
||||
# [countsonly [eventrowfilter]]
|
||||
# [formatjson [quotechar <Character>]] [todrive <ToDriveAttribute>*]
|
||||
# (addcsvdata <FieldName> <String>)*
|
||||
# [eventrowfilter]
|
||||
# [countsonly|(formatjson [quotechar <Character>])] [todrive <ToDriveAttribute>*]
|
||||
# gam calendars <CalendarEntity> show events <EventEntity> <EventDisplayProperties>*
|
||||
# [fields <EventFieldNameList>] [showdayofweek]
|
||||
# [countsonly]
|
||||
# [formatjson]
|
||||
# [countsonly|formatjson]
|
||||
def doCalendarsPrintShowEvents(calIds):
|
||||
calendarEventEntity = getCalendarEventEntity()
|
||||
csvPF, FJQC, fieldsList = _getCalendarPrintShowEventOptions(calendarEventEntity, Ent.CALENDAR)
|
||||
csvPF, FJQC, fieldsList, addCSVData = _getCalendarPrintShowEventOptions(calendarEventEntity, Ent.CALENDAR)
|
||||
_printShowCalendarEvents(None, None, None, calIds, len(calIds), calendarEventEntity,
|
||||
csvPF, FJQC, fieldsList)
|
||||
csvPF, FJQC, fieldsList, addCSVData)
|
||||
if csvPF:
|
||||
if calendarEventEntity['countsOnly'] and calendarEventEntity['eventRowFilter']:
|
||||
csvPF.SetTitles(calendarEventEntity['countsOnlyTitles'])
|
||||
@@ -48863,13 +48970,15 @@ def _doInfoCourses(courseIdList):
|
||||
|
||||
# gam info courses <CourseEntity> [owneraccess]
|
||||
# [owneremail] [alias|aliases] [show none|all|students|teachers] [countsonly]
|
||||
# [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>] [formatjson]
|
||||
# [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>]
|
||||
# [formatjson]
|
||||
def doInfoCourses():
|
||||
_doInfoCourses(getEntityList(Cmd.OB_COURSE_ENTITY, shlexSplit=True))
|
||||
|
||||
# gam info course <CourseID> [owneraccess]
|
||||
# [owneremail] [alias|aliases] [show none|all|students|teachers] [countsonly]
|
||||
# [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>] [formatjson]
|
||||
# [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>]
|
||||
# [formatjson]
|
||||
def doInfoCourse():
|
||||
_doInfoCourses(getStringReturnInList(Cmd.OB_COURSE_ID))
|
||||
|
||||
@@ -49187,7 +49296,7 @@ COURSE_ANNOUNCEMENTS_INDEXED_TITLES = ['materials']
|
||||
# (orderby <CourseAnnouncementOrderByFieldName> [ascending|descending])*)
|
||||
# [showcreatoremails|creatoremail] [fields <CourseAnnouncementFieldNameList>]
|
||||
# [timefilter creationtime|updatetime|scheduledtime] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>]
|
||||
# [countsonly] [formatjson [quotechar <Character>]]
|
||||
# [countsonly|(formatjson [quotechar <Character>])]
|
||||
def doPrintCourseAnnouncements():
|
||||
def _printCourseAnnouncement(course, courseAnnouncement, i, count):
|
||||
if applyCourseItemFilter and not _courseItemPassesFilter(courseAnnouncement, courseItemFilter):
|
||||
@@ -49243,6 +49352,8 @@ def doPrintCourseAnnouncements():
|
||||
coursesInfo = _getCoursesInfo(croom, courseSelectionParameters, courseShowProperties)
|
||||
if coursesInfo is None:
|
||||
return
|
||||
if countsOnly:
|
||||
csvPF.SetFormatJSON(False)
|
||||
applyCourseItemFilter = _setApplyCourseItemFilter(courseItemFilter, fieldsList)
|
||||
if showCreatorEmail and fieldsList:
|
||||
fieldsList.append('creatorUserId')
|
||||
@@ -49299,7 +49410,7 @@ COURSE_TOPICS_SORT_TITLES = ['courseId', 'courseName', 'topicId', 'name', 'updat
|
||||
# (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] states <CourseStateList>])
|
||||
# [topicids <CourseTopicIDEntity>]
|
||||
# [timefilter updatetime] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>]
|
||||
# [countsonly] [formatjson [quotechar <Character>]]
|
||||
# [countsonly|(formatjson [quotechar <Character>])]
|
||||
def doPrintCourseTopics():
|
||||
def _printCourseTopic(course, courseTopic):
|
||||
if applyCourseItemFilter and not _courseItemPassesFilter(courseTopic, courseItemFilter):
|
||||
@@ -49340,6 +49451,8 @@ def doPrintCourseTopics():
|
||||
coursesInfo = _getCoursesInfo(croom, courseSelectionParameters, courseShowProperties)
|
||||
if coursesInfo is None:
|
||||
return
|
||||
if countsOnly:
|
||||
csvPF.SetFormatJSON(False)
|
||||
applyCourseItemFilter = _setApplyCourseItemFilter(courseItemFilter, fieldsList)
|
||||
courseTopicIdsLists = courseTopicIds if isinstance(courseTopicIds, dict) else None
|
||||
i = 0
|
||||
@@ -49584,6 +49697,8 @@ def doPrintCourseWM(entityIDType, entityStateType):
|
||||
coursesInfo = _getCoursesInfo(croom, courseSelectionParameters, courseShowProperties)
|
||||
if coursesInfo is None:
|
||||
return
|
||||
if countsOnly:
|
||||
csvPF.SetFormatJSON(False)
|
||||
applyCourseItemFilter = _setApplyCourseItemFilter(courseItemFilter, fieldsList)
|
||||
courseWMIds = courseWMSelectionParameters['courseWMIds']
|
||||
courseWMIdsLists = courseWMIds if isinstance(courseWMIds, dict) else {}
|
||||
@@ -49639,7 +49754,7 @@ def doPrintCourseWM(entityIDType, entityStateType):
|
||||
# [showcreatoremails|creatoremail] [showtopicnames] [fields <CourseMaterialFieldNameList>]
|
||||
# [timefilter creationtime|updatetime|scheduledtime] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>]
|
||||
# [oneitemperrow]
|
||||
# [countsonly] [formatjson [quotechar <Character>]]
|
||||
# [countsonly|(formatjson [quotechar <Character>])]
|
||||
def doPrintCourseMaterials():
|
||||
doPrintCourseWM(Ent.COURSE_MATERIAL_ID, Ent.COURSE_MATERIAL_STATE)
|
||||
|
||||
@@ -49651,7 +49766,7 @@ def doPrintCourseMaterials():
|
||||
# [showstudentsaslist [<Boolean>]] [delimiter <Character>]
|
||||
# [timefilter creationtime|updatetime] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>]
|
||||
# [oneitemperrow]
|
||||
# [countsonly] [formatjson [quotechar <Character>]]
|
||||
# [countsonly|(formatjson [quotechar <Character>])]
|
||||
def doPrintCourseWork():
|
||||
doPrintCourseWM(Ent.COURSE_WORK_ID, Ent.COURSE_WORK_STATE)
|
||||
|
||||
@@ -49695,7 +49810,7 @@ def _gettingCourseSubmissionQuery(courseSubmissionStates, late, userId):
|
||||
# (submissionids <CourseSubmissionIDEntity>)|((submissionstates <CourseSubmissionStateList>)*) [late|notlate]
|
||||
# [fields <CourseSubmissionFieldNameList>] [showuserprofile]
|
||||
# [timefilter creationtime|updatetime] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>]
|
||||
# [countsonly] [formatjson [quotechar <Character>]]
|
||||
# [countsonly|(formatjson [quotechar <Character>])]
|
||||
def doPrintCourseSubmissions():
|
||||
def _printCourseSubmission(course, courseSubmission):
|
||||
if applyCourseItemFilter and not _courseItemPassesFilter(courseSubmission, courseItemFilter):
|
||||
@@ -49777,6 +49892,8 @@ def doPrintCourseSubmissions():
|
||||
coursesInfo = _getCoursesInfo(croom, courseSelectionParameters, courseShowProperties, getOwnerId=True)
|
||||
if coursesInfo is None:
|
||||
return
|
||||
if countsOnly:
|
||||
csvPF.SetFormatJSON(False)
|
||||
applyCourseItemFilter = _setApplyCourseItemFilter(courseItemFilter, fieldsList)
|
||||
courseWorkIds = courseWMSelectionParameters['courseWMIds']
|
||||
courseWorkIdsLists = courseWorkIds if isinstance(courseWorkIds, dict) else {}
|
||||
@@ -53523,16 +53640,16 @@ def infoCalendarEvents(users):
|
||||
|
||||
# gam <UserTypeEntity> print events <UserCalendarEntity> <EventEntity> <EventDisplayProperties>*
|
||||
# [fields <EventFieldNameList>] [showdayofweek]
|
||||
# [countsonly [eventrowfilter]]
|
||||
# [formatjson [quotechar <Character>]] [todrive <ToDriveAttribute>*]
|
||||
# (addcsvdata <FieldName> <String>)*
|
||||
# [eventrowfilter]
|
||||
# [countsonly|(formatjson [quotechar <Character>])] [todrive <ToDriveAttribute>*]
|
||||
# gam <UserTypeEntity> show events <UserCalendarEntity> <EventEntity> <EventDisplayProperties>*
|
||||
# [fields <EventFieldNameList>] [showdayofweek]
|
||||
# [countsonly]]
|
||||
# [formatjson]
|
||||
# ~[countsonly|formatjson]
|
||||
def printShowCalendarEvents(users):
|
||||
calendarEntity = getUserCalendarEntity()
|
||||
calendarEventEntity = getCalendarEventEntity()
|
||||
csvPF, FJQC, fieldsList = _getCalendarPrintShowEventOptions(calendarEventEntity, Ent.USER)
|
||||
csvPF, FJQC, fieldsList, addCSVData = _getCalendarPrintShowEventOptions(calendarEventEntity, Ent.USER)
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
@@ -53543,7 +53660,7 @@ def printShowCalendarEvents(users):
|
||||
continue
|
||||
Ind.Increment()
|
||||
_printShowCalendarEvents(origUser, user, cal, calIds, jcount, calendarEventEntity,
|
||||
csvPF, FJQC, fieldsList)
|
||||
csvPF, FJQC, fieldsList, addCSVData)
|
||||
Ind.Decrement()
|
||||
if csvPF:
|
||||
if calendarEventEntity['countsOnly'] and calendarEventEntity['eventRowFilter']:
|
||||
@@ -53987,7 +54104,7 @@ def printShowStatusEvent(users, eventType):
|
||||
for event in events:
|
||||
if showDayOfWeek:
|
||||
_getEventDaysOfWeek(event)
|
||||
_printCalendarEvent(user, calId, event, csvPF, FJQC)
|
||||
_printCalendarEvent(user, calId, event, csvPF, FJQC, {})
|
||||
if 'pdelta' in wlDate:
|
||||
first = first.shift(**wlDate['pdelta'])
|
||||
last = last.shift(**wlDate['pdelta'])
|
||||
@@ -77238,7 +77355,7 @@ TASK_QUERY_STATE_MAP = {
|
||||
# [updatedmin <Time>]
|
||||
# [showcompleted [<Boolean>]] [showdeleted [<Boolean>]] [showhidden [<Boolean>]] [showall]
|
||||
# [orderby completed|due|updated]
|
||||
# [countsonly | (formatjson [quotechar <Character>])]
|
||||
# [countsonly|(formatjson [quotechar <Character>])]
|
||||
def printShowTasks(users):
|
||||
def _showTaskAndChildren(tasklist, taskId, k, compact):
|
||||
if taskId in taskParentsProcessed:
|
||||
@@ -77305,8 +77422,11 @@ def printShowTasks(users):
|
||||
csvPF.SetTitles(['User', CSVTitle])
|
||||
else:
|
||||
FJQC.GetFormatJSONQuoteChar(myarg, False)
|
||||
if csvPF and FJQC.formatJSON:
|
||||
csvPF.SetJSONTitles(['User', 'tasklistId', 'id', 'taskId', 'title', 'JSON'])
|
||||
if csvPF:
|
||||
if countsOnly:
|
||||
csvPF.SetFormatJSON(False)
|
||||
elif FJQC.formatJSON:
|
||||
csvPF.SetJSONTitles(['User', 'tasklistId', 'id', 'taskId', 'title', 'JSON'])
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
@@ -77505,7 +77625,7 @@ def processTasklists(users):
|
||||
# gam <UserTypeEntity> show tasklists
|
||||
# [countsonly|formatjson]
|
||||
# gam <UserTypeEntity> print tasklists [todrive <ToDriveAttribute>*]
|
||||
# [countsonly | (formatjson [quotechar <Character>])]
|
||||
# [countsonly|(formatjson [quotechar <Character>])]
|
||||
def printShowTasklists(users):
|
||||
csvPF = CSVPrintFile(['User', 'id', 'title']) if Act.csvFormat() else None
|
||||
if csvPF:
|
||||
@@ -77523,6 +77643,8 @@ def printShowTasklists(users):
|
||||
csvPF.SetTitles(['User', CSVTitle])
|
||||
else:
|
||||
FJQC.GetFormatJSONQuoteChar(myarg, True)
|
||||
if countsOnly and csvPF:
|
||||
csvPF.SetFormatJSON(False)
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
|
||||
@@ -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
|
||||
@@ -344,6 +346,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: ',',
|
||||
@@ -512,6 +515,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},
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -567,7 +567,7 @@ By default, Gam displays the information as an indented list of keys and values.
|
||||
```
|
||||
gam calendar <CalendarEntity> show events [<EventEntity>] <EventDisplayProperty>*
|
||||
[fields <EventFieldNameList>] [showdayofweek]
|
||||
[countsly] [formatjson]
|
||||
[countsly|formatjson]
|
||||
```
|
||||
In `<EventEntity>`, any `<EventSelectProperty>` options must precede all other options.
|
||||
|
||||
@@ -586,8 +586,9 @@ By default, Gam displays event details, use `countsonly` to display only the num
|
||||
```
|
||||
gam calendar <CalendarEntity> print events [<EventEntity>] <EventDisplayProperty>*
|
||||
[fields <EventFieldNameList>] [showdayofweek]
|
||||
[countsonly [eventrowfilter]]
|
||||
[formatjson [quotechar <Character>]] [todrive <ToDriveAttribute>*]
|
||||
(addcsvdata <FieldName> <String>)*
|
||||
[eventrowfilter]
|
||||
[countsonly|(formatjson [quotechar <Character>])] [todrive <ToDriveAttribute>*]
|
||||
```
|
||||
In `<EventEntity>`, any `<EventSelectProperty>` options must precede all other options.
|
||||
|
||||
@@ -598,6 +599,9 @@ option `singleevents` to display all instances of a recurring event.
|
||||
|
||||
`showdayofweek` displays columns `start.dayOfWeek` and `end.dayOfWeek` when event start and end times are displayed.
|
||||
|
||||
Add additional columns of data from the command line to the output after the calendarId.
|
||||
* `addcsvdata <FieldName> <String>`
|
||||
|
||||
By default, Gam displays the information as columns of fields; the following option causes the output to be in JSON format,
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
|
||||
|
||||
@@ -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,7 +10,7 @@
|
||||
- [Delete duplicate email addresses from contacts](#delete-duplicate-email-addresses-from-contacts)
|
||||
- [Manage domain contact photos](#manage-domain-contact-photos)
|
||||
- [Display domain shared contacts](#display-domain-shared-contacts)
|
||||
- [Display global address list](#display-global-address-list)
|
||||
- [Display global address list](#Global-Address-List)
|
||||
|
||||
## API documentation
|
||||
* [Domain Shared Contacts API](https://developers.google.com/admin-sdk/domain-shared-contacts)
|
||||
|
||||
@@ -10,6 +10,85 @@ 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.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`
|
||||
and `gam calendars <CalendarEntity> print events` that adds additional columns of data to the CSV file output.
|
||||
An example would be to get the calendar name in addition to the calendar ID when printing events.
|
||||
```
|
||||
gam redirect csv ./Resources.csv print resources fields email,name
|
||||
gam redirect csv ./ResourceEventCounts.csv multiprocess redirect stderr - multiprocess csv Resources.csv gam calendar "~resourceEmail" print events starttime -1y countsonly addcsvdata calendarName "~resourceName"
|
||||
```
|
||||
|
||||
Upgraded to OpenSSL 3.6.0.
|
||||
|
||||
### 7.23.03
|
||||
|
||||
Upgraded to OpenSSL 3.5.4.
|
||||
|
||||
### 7.23.02
|
||||
|
||||
Added option `oneitemperrow` to 'gam print course-materials|course-work` to have each of a
|
||||
|
||||
@@ -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.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.25.00 - 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.00 - 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
|
||||
|
||||
@@ -653,7 +653,7 @@ By default, Gam displays the information as an indented list of keys and values.
|
||||
```
|
||||
gam <UserTypeEntity> show events <UserCalendarEntity> [<EventEntity>] <EventDisplayProperty>*
|
||||
[fields <EventFieldNameList>] [showdayofweek]
|
||||
[countsonly] [formatjson]
|
||||
[countsonly|formatjson]
|
||||
```
|
||||
In `<EventEntity>`, any `<EventSelectProperty>` options must precede all other options.
|
||||
|
||||
@@ -672,8 +672,8 @@ By default, Gam displays event details, use `countsonly` to display only the num
|
||||
```
|
||||
gam <UserTypeEntity> print events <UserCalendarEntity> [<EventEntity>] <EventDisplayProperty>*
|
||||
[fields <EventFieldNameList>] [showdayofweek]
|
||||
[countsonly]
|
||||
[formatjson [quotechar <Character>]] [todrive <ToDriveAttribute>*]
|
||||
(addcsvdata <FieldName> <String>)*
|
||||
[eventrowfilter] [countsonly|(formatjson [quotechar <Character>])] [todrive <ToDriveAttribute>*]
|
||||
```
|
||||
In `<EventEntity>`, any `<EventSelectProperty>` options must precede all other options.
|
||||
|
||||
@@ -684,6 +684,9 @@ option `singleevents` to display all instances of a recurring event.
|
||||
|
||||
`showdayofweek` displays columns `start.dayOfWeek` and `end.dayOfWeek` when event start and end times are displayed.
|
||||
|
||||
Add additional columns of data from the command line to the output after the calendarId.
|
||||
* `addcsvdata <FieldName> <String>`
|
||||
|
||||
By default, Gam displays the information as columns of fields; the following option causes the output to be in JSON format,
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
|
||||
|
||||
@@ -398,8 +398,8 @@ By default, Gam displays the information as an indented list of keys and values.
|
||||
gam <UserTypeEntity> show contacts
|
||||
<PeoplePrintShowUserContactSelection>
|
||||
[orderby firstname|lastname|(lastmodified ascending)|(lastnodified descending)
|
||||
[countsonly|allfields|(fields <PeopleFieldNameList>)] [showgroups] [showmetadata]
|
||||
[formatjson]
|
||||
[allfields|(fields <PeopleFieldNameList>)] [showgroups] [showmetadata]
|
||||
[countsonly|formatjson]
|
||||
```
|
||||
By default, Gam displays all of a user's people contacts.
|
||||
* `query <String>` - Display contacts based on the data in their fields
|
||||
@@ -416,8 +416,8 @@ By default, Gam displays the information as an indented list of keys and values.
|
||||
gam <UserTypeEntity> print contacts [todrive <ToDriveAttribute>*]
|
||||
<PeoplePrintShowUserContactSelection>
|
||||
[orderby firstname|lastname|(lastmodified ascending)|(lastnodified descending)
|
||||
[countsonly|allfields|(fields <PeopleFieldNameList>)] [showgroups] [showmetadata]
|
||||
[formatjson [quotechar <Character>]]
|
||||
[allfields|(fields <PeopleFieldNameList>)] [showgroups] [showmetadata]
|
||||
[countsonly|(formatjson [quotechar <Character>])]
|
||||
```
|
||||
By default, Gam displays all of a user's people contacts.
|
||||
* `query <String>` - Display contacts based on the data in their fields
|
||||
@@ -547,8 +547,8 @@ User: user@domain.com, Delete maximum of 15 Other Contacts
|
||||
```
|
||||
gam <UserTypeEntity> show othercontacts
|
||||
[<OtherContactsSelection>]
|
||||
[countsonly|allfields|(fields <OtherContactsFieldNameList>)] [showmetadata]
|
||||
[formatjson]
|
||||
[allfields|(fields <OtherContactsFieldNameList>)] [showmetadata]
|
||||
[countsonly|formatjson]
|
||||
```
|
||||
By default, Gam displays all of a user's Other Contacts; use
|
||||
`<OtherContactsSelection>` to display a selection of Other Contacts.
|
||||
@@ -563,8 +563,8 @@ By default, Gam displays the information as an indented list of keys and values.
|
||||
```
|
||||
gam <UserTypeEntity> print othercontacts [todrive <ToDriveAttribute>*]
|
||||
[<OtherContactsSelection>]
|
||||
[countsonly|allfields|(fields <OtherContactsFieldNameList>)] [showmetadata]
|
||||
[formatjson [quotechar <Character>]]
|
||||
[allfields|(fields <OtherContactsFieldNameList>)] [showmetadata]
|
||||
[countsonly|(formatjson [quotechar <Character>])]
|
||||
```
|
||||
By default, Gam displays all of a user's Other Contacts; use
|
||||
`<OtherContactsSelection>` to display a selection of Other Contacts.
|
||||
|
||||
@@ -122,7 +122,7 @@ gam <UserTypeEntity> show tasks [tasklists <TasklistEntity>]
|
||||
[updatedmin <Time>]
|
||||
[showcompleted [<Boolean>]] [showdeleted [<Boolean>]] [showhidden [<Boolean>]] [showall]
|
||||
[orderby completed|due|updated]
|
||||
[countsonly|compact|formatjson]
|
||||
[compact|countsonly|formatjson]
|
||||
```
|
||||
The API only supports dates in `duemin` and `duemax' but you must supply a null time:
|
||||
* `duemin YYYY-MM-DDT00:00:00Z` - Specify the starting due date
|
||||
@@ -152,7 +152,7 @@ gam <UserTypeEntity> print tasks [tasklists <TasklistEntity>] [todrive <ToDriveA
|
||||
[updatedmin <Time>]
|
||||
[showcompleted [<Boolean>]] [showdeleted [<Boolean>]] [showhidden [<Boolean>]] [showall]
|
||||
[orderby completed|due|updated]
|
||||
[countsonly | (formatjson [quotechar <Character>])]
|
||||
[countsonly|(formatjson [quotechar <Character>])]
|
||||
```
|
||||
The API only supports dates in `duemin` and `duemax' but you must supply a null time:
|
||||
* `duemin YYYY-MM-DDT00:00:00Z` - Specify the starting due date
|
||||
@@ -230,7 +230,7 @@ By default, Gam displays the task lists as an indented list of keys and values.
|
||||
|
||||
```
|
||||
gam <UserTypeEntity> print tasklists [todrive <ToDriveAttribute>*]
|
||||
[countsonly | (formatjson [quotechar <Character>])]
|
||||
[countsonly|(formatjson [quotechar <Character>])]
|
||||
```
|
||||
By default, Gam displays the information as columns of fields; the following option causes the output to be in JSON format,
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
|
||||
@@ -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.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.25.00 - 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.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.25.00 - 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.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.25.00 - 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.00
|
||||
echo $?
|
||||
1
|
||||
```
|
||||
@@ -76,7 +76,7 @@ echo $?
|
||||
Print the current version number without details
|
||||
```
|
||||
gam version simple
|
||||
7.22.00
|
||||
7.25.00
|
||||
```
|
||||
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.00 - 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