mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-04 06:11:39 +00:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd5616ec0e | ||
|
|
46ad942637 | ||
|
|
6db53c6f4c | ||
|
|
87f601dc5e | ||
|
|
e3d940c548 | ||
|
|
90beada55e | ||
|
|
670e3525f0 | ||
|
|
4a4b154d3d | ||
|
|
8b182b7b37 | ||
|
|
e9d911b5cd | ||
|
|
c67a4c9147 | ||
|
|
e583b6e20c | ||
|
|
8c23cd8e06 | ||
|
|
75fa7155a0 | ||
|
|
90e11162a0 | ||
|
|
b11617c1ea | ||
|
|
cf59f9156e | ||
|
|
35c1e44568 | ||
|
|
5cc68247a3 | ||
|
|
906ee82417 | ||
|
|
3d13d4afd8 | ||
|
|
9d68ce1b46 | ||
|
|
bd0ba995e5 | ||
|
|
0ab4b6d5cd | ||
|
|
163433f15a | ||
|
|
3d6219b551 | ||
|
|
99e363b5d6 | ||
|
|
ed03da815f | ||
|
|
ef1a40afa8 | ||
|
|
cd56f353d8 | ||
|
|
3924722f1c | ||
|
|
3ce48a95c9 | ||
|
|
2dafbfbcfc | ||
|
|
e03086866a | ||
|
|
0422bf22ea | ||
|
|
f3d9f3d518 | ||
|
|
ea9fd3f363 | ||
|
|
bed9db37ad | ||
|
|
072dc4809a | ||
|
|
6db2309fc4 | ||
|
|
cbb0c81652 | ||
|
|
f68aca8361 | ||
|
|
d63fdb4ed9 |
12
.github/workflows/build.yml
vendored
12
.github/workflows/build.yml
vendored
@@ -126,16 +126,16 @@ jobs:
|
||||
name: Test Python 3.12
|
||||
- os: ubuntu-24.04
|
||||
goal: test
|
||||
python: "3.14-dev"
|
||||
python: "3.15-dev"
|
||||
freethreaded: false
|
||||
jid: 17
|
||||
name: Test Python 3.14-dev
|
||||
name: Test Python 3.15-dev
|
||||
- os: ubuntu-24.04
|
||||
goal: test
|
||||
python: "3.14-dev"
|
||||
python: "3.14"
|
||||
freethreaded: true
|
||||
jid: 18
|
||||
name: Test Python 3.14-dev freethread
|
||||
name: Test Python 3.14 freethread
|
||||
|
||||
steps:
|
||||
|
||||
@@ -158,7 +158,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
cache.tar.xz
|
||||
key: gam-${{ matrix.jid }}-20251002
|
||||
key: gam-${{ matrix.jid }}-20251007
|
||||
|
||||
- name: Untar Cache archive
|
||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
|
||||
@@ -987,7 +987,7 @@ jobs:
|
||||
$gam report usageparameters customer
|
||||
$gam report usage customer parameters gmail:num_emails_sent,accounts:num_1day_logins
|
||||
$gam report customer todrive tdnobrowser
|
||||
#$gam report users fields accounts:is_less_secure_apps_access_allowed,gmail:last_imap_time,gmail:last_pop_time filters "accounts:last_login_time>2019-01-01T00:00:00.000Z" todrive tdnobrowser
|
||||
#$gam report users fields accounts:is_less_secure_apps_access_allowed,gmail:last_imap_time,gmail:last_pop_time filters "accounts:last_login_time>2025-01-01T00:00:00.000Z" todrive tdnobrowser
|
||||
$gam report users todrive tdnobrowser
|
||||
$gam report admin start -3d todrive tdnobrowser
|
||||
$gam print devices nopersonaldevices nodeviceusers filter "serial:$JID$JID$JID$JID-" | $gam csv - gam delete device id ~name
|
||||
|
||||
@@ -27,7 +27,7 @@ dependencies = [
|
||||
]
|
||||
description = "CLI tool to manage Google Workspace"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
requires-python = ">=3.10"
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
|
||||
@@ -2057,7 +2057,7 @@ gam create chatmessage <ChatSpace>
|
||||
[(thread <ChatThread>)|(threadkey <String>) [replyoption fail|fallbacktonew]]
|
||||
[returnidonly]
|
||||
gam update chatmessage name <ChatMessage>
|
||||
<ChatContent>
|
||||
[<ChatContent>] [clearattachments <String>]
|
||||
gam delete chatmessage name <ChatMessage>
|
||||
|
||||
<ChatMessageFieldName> ::=
|
||||
@@ -4086,7 +4086,7 @@ gam print group-members [todrive <ToDriveAttribute>*]
|
||||
gam create cigroup <EmailAddress>
|
||||
[copyfrom <GroupItem>] <GroupAttribute>*
|
||||
[makeowner] [alias|aliases <CIGroupAliasList>]
|
||||
[security|makesecuritygroup]
|
||||
[security|makesecuritygroup] [locked]
|
||||
[dynamic <QueryDynamicGroup>]
|
||||
gam update cigroup <GroupEntity> [copyfrom <GroupItem>] <GroupAttribute>
|
||||
[security|makesecuritygroup|
|
||||
@@ -5776,10 +5776,9 @@ gam create|add user <EmailAddress> [ignorenullpassword] <UserAttribute>*
|
||||
[[notify <EmailAddressList>] [notifyrecoveryemail]
|
||||
[subject <String>]
|
||||
[notifypassword <String>]
|
||||
[from <EmailAaddress>]
|
||||
[mailbox <EmailAaddress>]
|
||||
[from <EmailAaddress>] [mailbox <EmailAaddress>]
|
||||
[replyto <EmailAaddress>]
|
||||
[<NotifyMessageContent>]
|
||||
[<NotifyMessageContent>] [html [<Boolean>]]
|
||||
(replace <Tag> <UserReplacement>)*
|
||||
(replaceregex <RESearchPattern> <RESubstitution> <Tag> <UserReplacement>)*
|
||||
]
|
||||
@@ -5802,10 +5801,9 @@ gam update user <UserItem> [ignorenullpassword] <UserAttribute>*
|
||||
[[notify <EmailAddressList>] [notifyrecoveryemail]
|
||||
[subject <String>]
|
||||
[notifypassword <String>]
|
||||
[from <EmailAaddress>]
|
||||
[mailbox <EmailAddress>]
|
||||
[from <EmailAaddress>] [mailbox <EmailAddress>]
|
||||
[replyto <EmailAddress>]
|
||||
[<NotifyMessageContent>]
|
||||
[<NotifyMessageContent>] [html [<Boolean>]]
|
||||
(replace <Tag> <UserReplacement>)*
|
||||
(replaceregex <RESearchPattern> <RESubstitution> <Tag> <UserReplacement>)*
|
||||
]
|
||||
@@ -5840,10 +5838,9 @@ gam update users <UserTypeEntity> [ignorenullpassword] <UserAttribute>*
|
||||
[[notify <EmailAddressList>] [notifyrecoveryemail]
|
||||
[subject <String>]
|
||||
[notifypassword <String>]
|
||||
[from <EmailAddress>]
|
||||
[mailbox <EmailAddress>]
|
||||
[from <EmailAddress>] [mailbox <EmailAddress>]
|
||||
[replyto <EmailAaddress>]
|
||||
[<NotifyMessageContent>]
|
||||
[<NotifyMessageContent>] [html [<Boolean>]]
|
||||
(replace <Tag> <UserReplacement>)*
|
||||
(replaceregex <RESearchPattern> <RESubstitution> <Tag> <UserReplacement>)*
|
||||
]
|
||||
@@ -5877,10 +5874,9 @@ gam <UserTypeEntity> update users [ignorenullpassword] <UserAttribute>*
|
||||
[[notify <EmailAddressList>] [notifyrecoveryemail]
|
||||
[subject <String>]
|
||||
[notifypassword <String>]
|
||||
[from <EmailAaddress>]
|
||||
[mailbox <EmailAddress>]
|
||||
[from <EmailAaddress>] [mailbox <EmailAddress>]
|
||||
[replyto <EmailAddress>]
|
||||
[<NotifyMessageContent>]
|
||||
[<NotifyMessageContent>] [html [<Boolean>]]
|
||||
(replace <Tag> <UserReplacement>)*
|
||||
(replaceregex <RESearchPattern> <RESubstitution> <Tag> <UserReplacement>)*
|
||||
]
|
||||
@@ -6428,13 +6424,13 @@ gam <UserTypeEntity> update chatspace <ChatSpace>
|
||||
[type space]
|
||||
[description <String>] [guidelines|rules <String>]
|
||||
[history <Boolean>])
|
||||
[managemembersandgroups managers|members]
|
||||
[modifyspacedetails managers|members]
|
||||
[togglehistory managers|members]
|
||||
[useatmentionall managers|members]
|
||||
[manageapps managers|members]
|
||||
[managewebhooks managers|members]
|
||||
[replymessages managers|members]
|
||||
[managemembersandgroups owners|managers|members]
|
||||
[modifyspacedetails owners|managers|members]
|
||||
[togglehistory owners|managers|members]
|
||||
[useatmentionall owners|managers|members]
|
||||
[manageapps owners|managers|members]
|
||||
[managewebhooks owners|managers|members]
|
||||
[replymessages owners|managers|members]
|
||||
[formatjson]
|
||||
gam <UserTypeEntity> delete chatspace <ChatSpace>
|
||||
|
||||
@@ -6495,28 +6491,28 @@ gam <UserItem> print chatspaces asadmin [todrive <ToDriveAttribute>*]
|
||||
[formatjson [quotechar <Character>]]
|
||||
|
||||
gam <UserTypeEntity> create chatmember <ChatSpace>
|
||||
[type human|bot] [role member|manager]
|
||||
[type human|bot] [role member|manager|owner]
|
||||
(user <UserItem>)* (members <UserTypeEntity>)*
|
||||
(group <GroupItem>)* (groups <GroupEntity>)*
|
||||
[formatjson|returnidonly]
|
||||
gam <UserTypeEntity> delete chatmember <ChatSpace>
|
||||
((user <UserItem>)|(members <UserTypeEntity>)|
|
||||
(group <GroupItem>)|(groups <GroupEntity>))+
|
||||
gam <UserTypeEntity> remove chatmember members <ChatMemberList>
|
||||
`gam <UserTypeEntity> remove chatmember members <ChatMemberList>
|
||||
gam <UserTypeEntity> update chatmember <ChatSpace>
|
||||
role member|manager
|
||||
role member|manager|owner
|
||||
((user <UserItem>)|(members <UserTypeEntity>))+
|
||||
gam <UserTypeEntity> modify chatmember
|
||||
role member|manager
|
||||
role member|manager|owner
|
||||
members <ChatMemberList>
|
||||
gam <UserTypeEntity> sync chatmembers <ChatSpace>
|
||||
[role member|manager] [type human|bot]
|
||||
[role member|manager|owner] [type human|bot]
|
||||
[addonly|removeonly]
|
||||
[preview [actioncsv]]
|
||||
(users <UserTypeEntity>)* (groups <GroupEntity>)*
|
||||
|
||||
gam <UserItem> create chatmember asadmin <ChatSpace>
|
||||
[type human|bot] [role member|manager]
|
||||
[type human|bot] [role member|manager|owner]
|
||||
(user <UserItem>)* (members <UserTypeEntity>)*
|
||||
(group <GroupItem>)* (groups <GroupEntity>)*
|
||||
[formatjson|returnidonly]
|
||||
@@ -6525,13 +6521,13 @@ gam <UserItem> delete chatmember asadmin <ChatSpace>
|
||||
((user <UserItem>)|(members <UserTypeEntity>)|
|
||||
(group <GroupItem>)|(groups <GroupEntity>))+
|
||||
gam <UserItem> update chatmember asadmin <ChatSpace>
|
||||
role member|manager
|
||||
role member|manager|owner
|
||||
((user <UserItem>)|(members <UserTypeEntity>))+
|
||||
gam <UserItem> modify chatmember asadmin
|
||||
role member|manager
|
||||
role member|manager|owner
|
||||
members <ChatMemberList>
|
||||
gam <UserItem> sync chatmembers asadmin <ChatSpace>
|
||||
[role member|manager] [type human|bot]
|
||||
[role member|manager|owner`] [type human|bot]
|
||||
[addonly|removeonly]
|
||||
[preview [actioncsv]]
|
||||
(users <UserTypeEntity>)* (groups <GroupEntity>)*
|
||||
@@ -6586,7 +6582,7 @@ gam <UserTypeEntity> create chatmessage <ChatSpace>
|
||||
[replyoption fail|fallback]
|
||||
[returnidonly]
|
||||
gam <UserTypeEntity> update chatmessage name <ChatMessage>
|
||||
<ChatContent>
|
||||
[<ChatContent>] [clearattachments <String>]
|
||||
gam <UserTypeEntity> delete chatmessage name <ChatMessage>
|
||||
|
||||
<ChatMessageFieldName> ::=
|
||||
@@ -7579,7 +7575,19 @@ gam <UserTypeEntity> deprovision|deprov [popimap] [signout] [turnoff2sv]
|
||||
# Users - Gmail - Delegates
|
||||
|
||||
gam <UserTypeEntity> delegate to [convertalias] <UserEntity>
|
||||
[notify [<Boolean>]
|
||||
[subject <String>]
|
||||
[from <EmailAaddress>] [mailbox <EmailAddress>]
|
||||
[replyto <EmailAaddress>]
|
||||
[<NotifyMessageContent>] [html [<Boolean>]]
|
||||
]
|
||||
gam <UserTypeEntity> create|add delegate|delegates [convertalias] <UserEntity>
|
||||
[notify [<Boolean>]
|
||||
[subject <String>]
|
||||
[from <EmailAaddress>] [mailbox <EmailAddress>]
|
||||
[replyto <EmailAaddress>]
|
||||
[<NotifyMessageContent>] [html [<Boolean>]]
|
||||
]
|
||||
gam <UserTypeEntity> delete delegate|delegates [convertalias] <UserEntity>
|
||||
gam <UserTypeEntity> update delegate|delegates [convertalias] [<UserEntity>]
|
||||
gam <UserTypeEntity> show delegates|delegate [shownames] [csv]
|
||||
|
||||
@@ -1,3 +1,85 @@
|
||||
7.27.04
|
||||
|
||||
Added options to `gam <UserTypeEntity> create delegate` that support
|
||||
sending email notifications when a user adds a delegate.
|
||||
|
||||
* See: https://github.com/GAM-team/GAM/wiki/Users-Gmail-Delegates#delegation-notification
|
||||
|
||||
7.27.03
|
||||
|
||||
Updated `gam <UserTypeEntity> create|update|sync chatmember` role specification to `role member|manager|owner`.
|
||||
This is the mapping between the Chat UI and Chat API; GAM uses the Chat UI role names.
|
||||
```
|
||||
UI: Member, API: ROLE_MEMBER
|
||||
UI: Manager, API: ROLE_ASSISTANT_MANAGER
|
||||
UI: Owner, API: ROLE_MANAGER
|
||||
```
|
||||
|
||||
Updated `gam <UserTypeEntity> update chatspace` options for permission settings.
|
||||
```
|
||||
[managemembersandgroups owners|managers|members]
|
||||
[modifyspacedetails owners|managers|members]
|
||||
[togglehistory owners|managers|members]
|
||||
[useatmentionall owners|managers|members]
|
||||
[manageapps owners|managers|members]
|
||||
[managewebhooks owners|managers|members]
|
||||
[replymessages owners|managers|members]
|
||||
```
|
||||
|
||||
7.27.02
|
||||
|
||||
Added option `clearattachments <String>` to `gam [<UserTypeMessage>] update chatmessage`
|
||||
that clears all attachments from a Chat message. If `<ChatContent>` is not specified,
|
||||
the current message text is retained and `<String>` is appended; `<String>` must be specified
|
||||
but can be empty in which case the current message test is preserved as-is.
|
||||
|
||||
7.27.01
|
||||
|
||||
Fixed bug in `gam <UserTypeEntity> claim ownership <DriveFileEntity> ... onlyUsers|skipusers <UserTypeEntity>`
|
||||
where the email addresses in `onlyUsers|skipusers <UserTypeEntity>` were not normalized.
|
||||
|
||||
7.27.00
|
||||
|
||||
Added `debug_redaction` Boolean variable to `gam.cfg`. When True, the default,
|
||||
sensitive data like access/refresh tokens, client secret and authorization codes
|
||||
are redacted from debug output. This allows you to post debug output without
|
||||
compromising your account information. Even with debug redaction,
|
||||
anything shared publicly should be double-checked for sensitive content.
|
||||
|
||||
7.25.01
|
||||
|
||||
Fixed bug in `gam config timezone <String>` to handle timezone abbreviations correctly;
|
||||
they were incorrectly shifted to lowercase.
|
||||
|
||||
7.25.00
|
||||
|
||||
Removed a capabilty added in 7.24.00 that allowed reading command data from Google Docs and Sheets
|
||||
when a user's service account access to Drive and Sheets had been disabled. Jay was concerned
|
||||
that this change could be exploited to give access to all user's files.
|
||||
|
||||
This capability has been replaced by issuing the following commands. The admin specified in `gam oauth create`
|
||||
can read command data from Docs and Sheets to which it has access.
|
||||
```
|
||||
gam config commanddata_clientaccess true save
|
||||
gam oauth create
|
||||
Enable the following and proceed to authorization.
|
||||
|
||||
[*] 42) Drive API - commanddata_clientaccess
|
||||
[*] 54) Sheets API - commanddata_clientaccess
|
||||
```
|
||||
|
||||
Fixed in bug in `gam report` that caused a trap with either of the `thismonth` or `previousmonths` options were used.
|
||||
|
||||
Upgraded to Python 3.14.0.
|
||||
|
||||
7.24.01
|
||||
|
||||
Updated GAM to handle the following error that occurs when GAM tries to authenticate
|
||||
as a user that has been disabled by Google.
|
||||
```
|
||||
ERROR: Authentication Token Error - invalid_account: Forbidden
|
||||
```
|
||||
|
||||
7.24.00
|
||||
|
||||
If you want to disable a user's service account access to Drive and Sheets but still allow reading command data from Google Docs and Sheets,
|
||||
|
||||
@@ -83,13 +83,8 @@ echo -e '\x1B[0m'
|
||||
|
||||
version_gt()
|
||||
{
|
||||
# MacOS < 10.13 doesn't support sort -V
|
||||
echo "" | sort -V > /dev/null 2>&1
|
||||
vsort_failed=$?
|
||||
if [ "${1}" = "${2}" ]; then
|
||||
true
|
||||
elif (( $vsort_failed != 0 )); then
|
||||
false
|
||||
else
|
||||
test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"
|
||||
fi
|
||||
|
||||
@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
|
||||
"""
|
||||
|
||||
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
|
||||
__version__ = '7.24.00'
|
||||
__version__ = '7.27.04'
|
||||
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
#pylint: disable=wrong-import-position
|
||||
@@ -49,7 +49,7 @@ from email.policy import SMTP as policySMTP
|
||||
import hashlib
|
||||
from html.entities import name2codepoint
|
||||
from html.parser import HTMLParser
|
||||
import http.client as http_client
|
||||
import http.client
|
||||
import importlib
|
||||
from importlib.metadata import version as lib_version
|
||||
import io
|
||||
@@ -375,6 +375,37 @@ YUBIKEY_VALUE_ERROR_RC = 85
|
||||
YUBIKEY_MULTIPLE_CONNECTED_RC = 86
|
||||
YUBIKEY_NOT_FOUND_RC = 87
|
||||
|
||||
DEBUG_REDACTION_PATTERNS = [
|
||||
# Positional patterns that redact sensitive credentials based on their location
|
||||
(r'(Bearer\s+)\S+', r'\1*****'), # access tokens and JWTs in auth header
|
||||
(r'([?&]refresh_token=)[^&]*', r'\1*****'), # refresh token URL parameter
|
||||
(r'([?&]client_secret=)[^&]*', r'\1*****'), # client secret URL parameter
|
||||
(r'([?&]key=)[^&]*', r'\1*****'), # API key URL parameter
|
||||
(r'([?&]code=)[^&]*', r'\1*****'), # auth code URL parameter
|
||||
|
||||
# Pattern match patterns that redact sensitive credentials based on known credential pattern
|
||||
(r'ya29.[0-9A-Za-z-_]+', '*****'), # Access token
|
||||
(r'1%2F%2F[0-9A-Za-z-_]{100}|1%2F%2F[0-9A-Za-z-_]{64}|1%2F%2F[0-9A-Za-z-_]{43}', '*****'), # Refresh token
|
||||
(r'4/[0-9A-Za-z-_]+', '*****'), # Auth code
|
||||
(r'GOCSPX-[0-9a-zA-Z-_]{28}', '*****'), # Client secret
|
||||
(r'AIza[0-9A-Za-z-_]{35}', '*****'), # API key
|
||||
(r'eyJ[a-zA-Z0-9\-_]+\.eyJ[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]*', '*****'), # JWT
|
||||
]
|
||||
|
||||
def redactable_debug_print(*args):
|
||||
processed_args = []
|
||||
for arg in args:
|
||||
if arg.startswith('b\''):
|
||||
sbytes = arg[2:-1]
|
||||
sbytes = bytes(sbytes, 'utf-8')
|
||||
arg = sbytes.decode()
|
||||
arg = arg.replace('\\r\\n', "\n ")
|
||||
if GC.Values[GC.DEBUG_REDACTION]:
|
||||
for pattern, replace in DEBUG_REDACTION_PATTERNS:
|
||||
arg = re.sub(pattern, replace, arg)
|
||||
processed_args.append(arg)
|
||||
print(*processed_args)
|
||||
|
||||
# Multiprocessing lock
|
||||
mplock = None
|
||||
|
||||
@@ -2113,12 +2144,12 @@ class StartEndTime():
|
||||
else:
|
||||
firstMonth = getInteger(minVal=1, maxVal=6)
|
||||
currDate = todaysDate()
|
||||
self.startDateTime = currDate.shift(months=-firstMonth, day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
self.startDateTime = currDate.replace(day=1, hour=0, minute=0, second=0, microsecond=0).shift(months=-firstMonth)
|
||||
self.startTime = ISOformatTimeStamp(self.startDateTime)
|
||||
if myarg == 'thismonth':
|
||||
self.endDateTime = todaysTime()
|
||||
else:
|
||||
self.endDateTime = currDate.shift(day=1, hour=23, minute=59, second=59, microsecond=0).shift(days=-1)
|
||||
self.endDateTime = currDate.replace(day=1, hour=23, minute=59, second=59, microsecond=0).shift(days=-1)
|
||||
self.endTime = ISOformatTimeStamp(self.endDateTime)
|
||||
if self.startDateTime and self.endDateTime and self.endDateTime < self.startDateTime:
|
||||
Cmd.Backup()
|
||||
@@ -3049,7 +3080,10 @@ def getGDocData(gformat):
|
||||
mimeType = GDOC_FORMAT_MIME_TYPES[gformat]
|
||||
user = getEmailAddress()
|
||||
fileIdEntity = getDriveFileEntity(queryShortcutsOK=False)
|
||||
_, drive = buildGAPIServiceObject(chooseSaAPI(API.DRIVECD, API.DRIVE3), user)
|
||||
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)
|
||||
@@ -3106,7 +3140,10 @@ def getGSheetData():
|
||||
user = getEmailAddress()
|
||||
fileIdEntity = getDriveFileEntity(queryShortcutsOK=False)
|
||||
sheetEntity = getSheetEntity(False)
|
||||
user, drive = buildGAPIServiceObject(chooseSaAPI(API.DRIVECD, API.DRIVE3), user)
|
||||
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)
|
||||
@@ -3114,7 +3151,10 @@ def getGSheetData():
|
||||
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(chooseSaAPI(API.SHEETSCD, 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]
|
||||
@@ -3707,12 +3747,12 @@ def SetGlobalVariables():
|
||||
return stringlist
|
||||
|
||||
def _getCfgTimezone(sectionName, itemName):
|
||||
value = _stripStringQuotes(GM.Globals[GM.PARSER].get(sectionName, itemName).lower())
|
||||
if value in {'utc', 'z'}:
|
||||
value = _stripStringQuotes(GM.Globals[GM.PARSER].get(sectionName, itemName))
|
||||
if value.lower() in {'utc', 'z'}:
|
||||
GM.Globals[GM.CONVERT_TO_LOCAL_TIME] = False
|
||||
return arrow.now('utc').tzinfo
|
||||
GM.Globals[GM.CONVERT_TO_LOCAL_TIME] = True
|
||||
if value == 'local':
|
||||
if value.lower() == 'local':
|
||||
return arrow.now(value).tzinfo
|
||||
try:
|
||||
return arrow.now(value).tzinfo
|
||||
@@ -4115,6 +4155,8 @@ def SetGlobalVariables():
|
||||
GM.Globals[GM.OAUTH2_TXT_LOCK] = f'{GC.Values[GC.OAUTH2_TXT]}.lock'
|
||||
# Override httplib2 settings
|
||||
httplib2.debuglevel = GC.Values[GC.DEBUG_LEVEL]
|
||||
# Use our own print function for http.client so we can redact and cleanup
|
||||
http.client.print = redactable_debug_print
|
||||
# Reset global variables if required
|
||||
if prevExtraArgsTxt != GC.Values[GC.EXTRA_ARGS]:
|
||||
GM.Globals[GM.EXTRA_ARGS_LIST] = [('prettyPrint', GC.Values[GC.DEBUG_LEVEL] > 0)]
|
||||
@@ -4766,7 +4808,7 @@ def getService(api, httpObj):
|
||||
waitOnFailure(n, triesLimit, INVALID_JSON_RC, str(e))
|
||||
continue
|
||||
systemErrorExit(INVALID_JSON_RC, str(e))
|
||||
except (http_client.ResponseNotReady, OSError, googleapiclient.errors.HttpError) as e:
|
||||
except (http.client.ResponseNotReady, OSError, googleapiclient.errors.HttpError) as e:
|
||||
errMsg = f'Connection error: {str(e) or repr(e)}'
|
||||
if n != triesLimit:
|
||||
waitOnFailure(n, triesLimit, SOCKET_ERROR_RC, errMsg)
|
||||
@@ -4801,7 +4843,6 @@ 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]
|
||||
return saScopes
|
||||
|
||||
@@ -5080,7 +5121,7 @@ def callGData(service, function,
|
||||
e = e.args[0]
|
||||
handleOAuthTokenError(e, GDATA.SERVICE_NOT_APPLICABLE in throwErrors)
|
||||
raise GDATA.ERROR_CODE_EXCEPTION_MAP[GDATA.SERVICE_NOT_APPLICABLE](str(e))
|
||||
except (http_client.ResponseNotReady, OSError) as e:
|
||||
except (http.client.ResponseNotReady, OSError) as e:
|
||||
errMsg = f'Connection error: {str(e) or repr(e)}'
|
||||
if n != triesLimit:
|
||||
waitOnFailure(n, triesLimit, SOCKET_ERROR_RC, errMsg)
|
||||
@@ -5390,7 +5431,7 @@ def callGAPI(service, function,
|
||||
e = e.args[0]
|
||||
handleOAuthTokenError(e, GAPI.SERVICE_NOT_AVAILABLE in throwReasons)
|
||||
raise GAPI.REASON_EXCEPTION_MAP[GAPI.SERVICE_NOT_AVAILABLE](str(e))
|
||||
except (http_client.ResponseNotReady, OSError) as e:
|
||||
except (http.client.ResponseNotReady, OSError) as e:
|
||||
errMsg = f'Connection error: {str(e) or repr(e)}'
|
||||
if n != triesLimit:
|
||||
waitOnFailure(n, triesLimit, SOCKET_ERROR_RC, errMsg)
|
||||
@@ -9141,7 +9182,7 @@ def showJSON(showName, showValue, skipObjects=None, timeObjects=None,
|
||||
return
|
||||
if objectName is not None:
|
||||
printJSONKey(objectName)
|
||||
subObjectKey = dictObjectsKey.get(objectName)
|
||||
subObjectKey = dictObjectsKey.get(objectName)
|
||||
if isinstance(objectValue, list):
|
||||
if objectName in simpleLists:
|
||||
printJSONValue(' '.join(objectValue))
|
||||
@@ -9596,7 +9637,7 @@ def CSVFileQueueHandler(mpQueue, mpQueueStdout, mpQueueStderr, csvPF, datetimeNo
|
||||
clearRowFilters = False
|
||||
# if sys.platform.startswith('win'):
|
||||
# signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
Cmd = glclargs.GamCLArgs()
|
||||
else:
|
||||
@@ -9638,7 +9679,7 @@ def CSVFileQueueHandler(mpQueue, mpQueueStdout, mpQueueStderr, csvPF, datetimeNo
|
||||
Cmd.InitializeArguments(dataItem)
|
||||
elif dataType == GM.REDIRECT_QUEUE_GLOBALS:
|
||||
GM.Globals = dataItem
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
reopenSTDFile(GM.STDOUT)
|
||||
reopenSTDFile(GM.STDERR)
|
||||
elif dataType == GM.REDIRECT_QUEUE_VALUES:
|
||||
@@ -9684,7 +9725,7 @@ def initializeCSVFileQueueHandler(mpManager, mpQueueStdout, mpQueueStderr):
|
||||
def terminateCSVFileQueueHandler(mpQueue, mpQueueHandler):
|
||||
GM.Globals[GM.PARSER] = None
|
||||
GM.Globals[GM.CSVFILE][GM.REDIRECT_QUEUE] = None
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
mpQueue.put((GM.REDIRECT_QUEUE_ARGS, Cmd.AllArguments()))
|
||||
savedValues = saveNonPickleableValues()
|
||||
mpQueue.put((GM.REDIRECT_QUEUE_GLOBALS, GM.Globals))
|
||||
@@ -9714,13 +9755,13 @@ def StdQueueHandler(mpQueue, stdtype, gmGlobals, gcValues):
|
||||
|
||||
# if sys.platform.startswith('win'):
|
||||
# signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
GM.Globals = gmGlobals.copy()
|
||||
GC.Values = gcValues.copy()
|
||||
pid0DataItem = [KEYBOARD_INTERRUPT_RC, None]
|
||||
pidData = {}
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
if GM.Globals[stdtype][GM.REDIRECT_NAME] == 'null':
|
||||
fd = open(os.devnull, GM.Globals[stdtype][GM.REDIRECT_MODE], encoding=UTF8)
|
||||
elif GM.Globals[stdtype][GM.REDIRECT_NAME] == '-':
|
||||
@@ -9808,7 +9849,7 @@ def ProcessGAMCommandMulti(pid, numItems, logCmd, mpQueueCSVFile, mpQueueStdout,
|
||||
with mplock:
|
||||
initializeLogging()
|
||||
# if sys.platform.startswith('win'):
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
GM.Globals[GM.API_CALLS_RETRY_DATA] = {}
|
||||
GM.Globals[GM.CMDLOG_LOGGER] = None
|
||||
@@ -9949,7 +9990,7 @@ def MultiprocessGAMCommands(items, showCmds):
|
||||
mpManager = multiprocessing.Manager()
|
||||
l = mpManager.Lock()
|
||||
try:
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
pool = mpManager.Pool(processes=numPoolProcesses, initializer=initGamWorker, initargs=(l,), maxtasksperchild=200)
|
||||
else:
|
||||
pool = multiprocessing.Pool(processes=numPoolProcesses, initializer=initGamWorker, initargs=(l,), maxtasksperchild=200)
|
||||
@@ -9958,7 +9999,7 @@ def MultiprocessGAMCommands(items, showCmds):
|
||||
except AssertionError as e:
|
||||
Cmd.SetLocation(0)
|
||||
usageErrorExit(str(e))
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
savedValues = saveNonPickleableValues()
|
||||
if GM.Globals[GM.STDOUT][GM.REDIRECT_MULTIPROCESS]:
|
||||
mpQueueStdout, mpQueueHandlerStdout = initializeStdQueueHandler(mpManager, GM.STDOUT, GM.Globals, GC.Values)
|
||||
@@ -9973,7 +10014,7 @@ def MultiprocessGAMCommands(items, showCmds):
|
||||
mpQueueStderr = mpQueueStdout
|
||||
else:
|
||||
mpQueueStderr = None
|
||||
if multiprocessing.get_start_method() == 'spawn':
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
restoreNonPickleableValues(savedValues)
|
||||
if mpQueueStdout:
|
||||
mpQueueStdout.put((0, GM.REDIRECT_QUEUE_DATA, GM.Globals[GM.STDOUT][GM.REDIRECT_MULTI_FD].getvalue()))
|
||||
@@ -11137,7 +11178,7 @@ class Credentials(google.oauth2.credentials.Credentials):
|
||||
|
||||
def doOAuthRequest(currentScopes, login_hint, verifyScopes=False):
|
||||
client_id, client_secret = getOAuthClientIDAndSecret()
|
||||
scopesList = API.getClientScopesList(GC.Values[GC.TODRIVE_CLIENTACCESS])
|
||||
scopesList = API.getClientScopesList(GC.Values[GC.COMMANDDATA_CLIENTACCESS], GC.Values[GC.TODRIVE_CLIENTACCESS])
|
||||
if not currentScopes or verifyScopes:
|
||||
selectedScopes = getScopesFromUser(scopesList, True, currentScopes)
|
||||
if selectedScopes is None:
|
||||
@@ -11183,7 +11224,7 @@ def doOAuthCreate():
|
||||
else:
|
||||
login_hint = None
|
||||
scopes = []
|
||||
scopesList = API.getClientScopesList(GC.Values[GC.TODRIVE_CLIENTACCESS])
|
||||
scopesList = API.getClientScopesList(GC.Values[GC.COMMANDDATA_CLIENTACCESS], GC.Values[GC.TODRIVE_CLIENTACCESS])
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'admin':
|
||||
@@ -11199,7 +11240,9 @@ def doOAuthCreate():
|
||||
scopes.append(uscope)
|
||||
break
|
||||
else:
|
||||
invalidChoiceExit(uscope, API.getClientScopesURLs(GC.Values[GC.TODRIVE_CLIENTACCESS]), True)
|
||||
invalidChoiceExit(uscope,
|
||||
API.getClientScopesURLs(GC.Values[GC.COMMANDDATA_CLIENTACCESS], GC.Values[GC.TODRIVE_CLIENTACCESS]),
|
||||
True)
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
if len(scopes) == 0:
|
||||
@@ -11310,7 +11353,7 @@ def doOAuthUpdate():
|
||||
if 'scopes' in jsonDict:
|
||||
currentScopes = jsonDict['scopes']
|
||||
else:
|
||||
currentScopes = API.getClientScopesURLs(GC.Values[GC.TODRIVE_CLIENTACCESS])
|
||||
currentScopes = API.getClientScopesURLs(GC.Values[GC.COMMANDDATA_CLIENTACCESS], GC.Values[GC.TODRIVE_CLIENTACCESS])
|
||||
else:
|
||||
currentScopes = []
|
||||
except (AttributeError, IndexError, KeyError, SyntaxError, TypeError, ValueError) as e:
|
||||
@@ -12340,8 +12383,6 @@ def checkServiceAccount(users):
|
||||
saScopes[scope['api']].append(scope['roscope'])
|
||||
checkScopesSet.add(scope['roscope'])
|
||||
i += 1
|
||||
if API.DRIVEACTIVITY in saScopes and API.DRIVE3 in saScopes:
|
||||
saScopes[API.DRIVEACTIVITY].append(API.DRIVE_SCOPE)
|
||||
if API.DRIVE3 in saScopes:
|
||||
saScopes[API.DRIVE2] = saScopes[API.DRIVE3]
|
||||
GM.Globals[GM.OAUTH2SERVICE_JSON_DATA][API.OAUTH2SA_SCOPES] = saScopes
|
||||
@@ -26465,14 +26506,18 @@ CHAT_TIME_OBJECTS = {'createTime', 'deleteTime', 'eventTime', 'lastActiveTime',
|
||||
def _showChatItem(citem, entityType, FJQC, i=0, count=0):
|
||||
if entityType == Ent.CHAT_SPACE:
|
||||
_cleanChatSpace(citem)
|
||||
dictObjectsKey = {None: 'displayName'}
|
||||
elif entityType == Ent.CHAT_MESSAGE:
|
||||
_cleanChatMessage(citem)
|
||||
dictObjectsKey = {None: 'text'}
|
||||
else:
|
||||
dictObjectsKey={}
|
||||
if FJQC.formatJSON:
|
||||
printLine(json.dumps(cleanJSON(citem, timeObjects=CHAT_TIME_OBJECTS), ensure_ascii=False, sort_keys=True))
|
||||
return
|
||||
printEntity([entityType, citem['name']], i, count)
|
||||
Ind.Increment()
|
||||
showJSON(None, citem, timeObjects=CHAT_TIME_OBJECTS)
|
||||
showJSON(None, citem, timeObjects=CHAT_TIME_OBJECTS, dictObjectsKey=dictObjectsKey)
|
||||
Ind.Decrement()
|
||||
|
||||
def _printChatItem(user, citem, parent, entityType, csvPF, FJQC, addCSVData=None):
|
||||
@@ -26731,14 +26776,14 @@ def getChatSpaceParameters(myarg, body, typeChoicesMap, updateMask):
|
||||
|
||||
CHAT_MEMBER_ROLE_MAP = {
|
||||
'member': 'ROLE_MEMBER',
|
||||
'manager': 'ROLE_MANAGER',
|
||||
'owner': 'ROLE_OWNER',
|
||||
'manager': 'ROLE_ASSISTANT_MANAGER',
|
||||
'owner': 'ROLE_MANAGER',
|
||||
}
|
||||
|
||||
CHAT_ROLE_ENTITY_TYPE_MAP = {
|
||||
'ROLE_MEMBER': Ent.CHAT_MEMBER_USER,
|
||||
'ROLE_MANAGER': Ent.CHAT_MANAGER_USER,
|
||||
'ROLE_OWNER': Ent.CHAT_OWNER_USER,
|
||||
'ROLE_ASSISTANT_MANAGER': Ent.CHAT_MANAGER_USER,
|
||||
'ROLE_MANAGER': Ent.CHAT_OWNER_USER,
|
||||
}
|
||||
|
||||
CHAT_MEMBER_TYPE_MAP = {
|
||||
@@ -26872,7 +26917,8 @@ CHAT_UPDATE_SPACE_TYPE_MAP = {
|
||||
}
|
||||
|
||||
CHAT_SPACE_ROLE_PERMISSIONS_MAP = {
|
||||
'managers': 'managersAllowed',
|
||||
'owners': 'managersAllowed',
|
||||
'managers': 'assistantManagersAllowed',
|
||||
'members': 'membersAllowed',
|
||||
}
|
||||
|
||||
@@ -26892,13 +26938,13 @@ CHAT_UPDATE_SPACE_PERMISSIONS_MAP = {
|
||||
# [type space]
|
||||
# [description <String>] [guidelines|rules <String>]
|
||||
# [history <Boolean>])
|
||||
# managemembersandgroups managers|members
|
||||
# modifyspacedetails managers|members
|
||||
# togglehistory managers|members
|
||||
# useatmentionall managers|members
|
||||
# manageapps managers|members
|
||||
# managewebhooks managers|members
|
||||
# replymessages managers|members
|
||||
# [managemembersandgroups owners|managers|members]
|
||||
# [modifyspacedetails owners|managers|members]
|
||||
# [togglehistory owners|managers|members]
|
||||
# [useatmentionall owners|managers|members]
|
||||
# [manageapps owners|managers|members]
|
||||
# [managewebhooks owners|managers|members]
|
||||
2# [replymessages owners|managers|members]
|
||||
# [formatjson]
|
||||
def updateChatSpace(users):
|
||||
FJQC = FormatJSONQuoteChar()
|
||||
@@ -26916,9 +26962,13 @@ def updateChatSpace(users):
|
||||
body.setdefault('permissionSettings', {})
|
||||
permissionSetting = CHAT_UPDATE_SPACE_PERMISSIONS_MAP[myarg]
|
||||
role = getChoice(CHAT_SPACE_ROLE_PERMISSIONS_MAP, mapChoice=True)
|
||||
body['permissionSettings'][permissionSetting] = {'managersAllowed': True}
|
||||
body['permissionSettings'][permissionSetting] = {}
|
||||
body['permissionSettings'][permissionSetting][role] = True
|
||||
if role == 'membersAllowed':
|
||||
body['permissionSettings'][permissionSetting].update({'membersAllowed': True})
|
||||
body['permissionSettings'][permissionSetting]['assistantManagersAllowed'] = True
|
||||
body['permissionSettings'][permissionSetting]['managersAllowed'] = True
|
||||
elif role == 'assistantManagersAllowed':
|
||||
body['permissionSettings'][permissionSetting]['managersAllowed'] = True
|
||||
updateMask.add(f'permissionSettings.{permissionSetting}')
|
||||
else:
|
||||
FJQC.GetFormatJSON(myarg)
|
||||
@@ -27292,12 +27342,12 @@ def getGroupMemberID(cd, group, groupList):
|
||||
groupList.append(convertEmailAddressToUID(group, cd, emailType='group'))
|
||||
|
||||
# gam <UserTypeEntity> create chatmember <ChatSpace>
|
||||
# [type human|bot] [role member|manager]
|
||||
# [type human|bot] [role member|manager|owner]
|
||||
# (user <UserItem>)* (members <UserTypeEntity>)*
|
||||
# (group <GroupItem>)* (groups <GroupEntity>)*
|
||||
# [formatjson|returnidonly]
|
||||
# gam <UserItem> create chatmember asadmin <ChatSpace>
|
||||
# [type human|bot] [role member|manager]
|
||||
# [type human|bot] [role member|manager|owner]
|
||||
# (user <UserItem>)* (members <UserTypeEntity>)*
|
||||
# (group <GroupItem>)* (groups <GroupEntity>)*
|
||||
# [formatjson|returnidonly]
|
||||
@@ -27426,16 +27476,16 @@ def _deleteChatMembers(chat, kvList, jcount, memberNames, i, count, kwargsUAA):
|
||||
# gam <UserItem> remove chatmember asadmin
|
||||
# members <ChatMemberList>
|
||||
# gam <UserTypeEntity> update chatmember <ChatSpace>
|
||||
# role member|manager
|
||||
# role member|manager|owner
|
||||
# ((user <UserItem>)|(members <UserTypeEntity>))+
|
||||
# gam <UserTypeEntity> modify chatmember
|
||||
# role member|manager
|
||||
# role member|manager|owner
|
||||
# members <ChatMemberList>
|
||||
# gam <UserItem> update chatmember asadmin<ChatSpace>
|
||||
# role member|manager
|
||||
# role member|manager|owner
|
||||
# ((user <UserItem>)|(members <UserTypeEntity>))+
|
||||
# gam <UserItem> modify chatmember asadmin
|
||||
# role member|manager
|
||||
# role member|manager|owner
|
||||
# members <ChatMemberList>
|
||||
def deleteUpdateChatMember(users):
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
@@ -27529,7 +27579,7 @@ def deleteUpdateChatMember(users):
|
||||
CHAT_SYNC_PREVIEW_TITLES = ['space', 'member', 'role', 'action', 'message']
|
||||
|
||||
# gam <UserTypeEntity> sync chatmembers [asadmin] <ChatSpace>
|
||||
# [role member|manager] [type human|bot]
|
||||
# [role member|manager|owner] [type human|bot]
|
||||
# [addonly|removeonly]
|
||||
# [preview [actioncsv]]
|
||||
# (users <UserTypeEntity>)* (groups <GroupEntity>)*
|
||||
@@ -27945,10 +27995,11 @@ def _getChatSenderEmail(cd, sender):
|
||||
sender['email'], _ = convertUIDtoEmailAddressWithType(f'uid:{senderUid}', cd, None, emailTypes=['user'])
|
||||
|
||||
def trimChatMessageIfRequired(body):
|
||||
msgLen = len(body['text'])
|
||||
if msgLen > 4096:
|
||||
stderrWarningMsg(Msg.TRIMMED_MESSAGE_FROM_LENGTH_TO_MAXIMUM.format(msgLen, 4096))
|
||||
body['text'] = body['text'][:4095]
|
||||
if 'text' in body:
|
||||
msgLen = len(body['text'])
|
||||
if msgLen > 4096:
|
||||
stderrWarningMsg(Msg.TRIMMED_MESSAGE_FROM_LENGTH_TO_MAXIMUM.format(msgLen, 4096))
|
||||
body['text'] = body['text'][:4095]
|
||||
|
||||
CHAT_MESSAGE_REPLY_OPTION_MAP = {
|
||||
'fail': 'REPLY_MESSAGE_OR_FAIL',
|
||||
@@ -28025,22 +28076,29 @@ def doCreateChatMessage():
|
||||
createChatMessage([None])
|
||||
|
||||
# gam [<UserTypeMessage>] update chatmessage name <ChatMessage>
|
||||
# <ChatContent>
|
||||
# [<ChatContent>] [clearattachments <String>]
|
||||
def updateChatMessage(users):
|
||||
name = None
|
||||
body = {}
|
||||
updateMask = []
|
||||
clearMsg = ''
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'name':
|
||||
name = getString(Cmd.OB_CHAT_MESSAGE)
|
||||
elif myarg in SORF_TEXT_ARGUMENTS:
|
||||
body['text'] = getStringOrFile(myarg, minLen=0, unescapeCRLF=True)[0]
|
||||
updateMask.append('text')
|
||||
elif myarg == 'clearattachments':
|
||||
clearMsg = getString(Cmd.OB_STRING, minLen=0)
|
||||
body['attachment'] = []
|
||||
updateMask.append('attachment')
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
if not name:
|
||||
missingArgumentExit('name')
|
||||
if 'text' not in body:
|
||||
missingArgumentExit('text or textfile')
|
||||
if not updateMask:
|
||||
missingArgumentExit('text|textfile|clearattachments')
|
||||
trimChatMessageIfRequired(body)
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
@@ -28049,9 +28107,19 @@ def updateChatMessage(users):
|
||||
if not chat:
|
||||
continue
|
||||
try:
|
||||
if 'attachment' in updateMask and 'text' not in updateMask:
|
||||
resp = callGAPI(chat.spaces().messages(), 'get',
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.FAILED_PRECONDITION],
|
||||
name=name, fields='text')
|
||||
body['text'] = resp.get('text', '')
|
||||
if clearMsg:
|
||||
body['text'] += clearMsg
|
||||
elif not body['text']:
|
||||
body['text'] = 'Attachments cleared'
|
||||
updateMask.append('text')
|
||||
resp = callGAPI(chat.spaces().messages(), 'patch',
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.FAILED_PRECONDITION],
|
||||
name=name, updateMask='text', body=body)
|
||||
name=name, updateMask=','.join(updateMask), body=body)
|
||||
kvList.extend([Ent.CHAT_THREAD, resp['thread']['name']])
|
||||
entityActionPerformed(kvList, i, count)
|
||||
except (GAPI.notFound, GAPI.invalidArgument, GAPI.permissionDenied) as e:
|
||||
@@ -32960,6 +33028,8 @@ def doCreateGroup(ciGroupsAPI=False):
|
||||
initialGroupConfig = 'WITH_INITIAL_OWNER'
|
||||
elif ciGroupsAPI and myarg in {'security', 'makesecuritygroup'}:
|
||||
body['labels'][CIGROUP_SECURITY_LABEL] = ''
|
||||
elif ciGroupsAPI and myarg in ['locked']:
|
||||
body['labels'][CIGROUP_LOCKED_LABEL] = ''
|
||||
elif myarg == 'verifynotinvitable':
|
||||
verifyNotInvitable = True
|
||||
else:
|
||||
@@ -44166,6 +44236,28 @@ USER_JSON_SKIP_FIELDS = ['agreedToTerms', 'aliases', 'creationTime', 'customerId
|
||||
|
||||
ALLOW_EMPTY_CUSTOM_TYPE = 'allowEmptyCustomType'
|
||||
|
||||
def getNotifyArguments(myarg, notify, userNotification):
|
||||
if myarg == 'notify':
|
||||
if userNotification:
|
||||
notify['recipients'].extend(getNormalizedEmailAddressEntity(shlexSplit=True, noLower=True))
|
||||
else: #delegateNotificatiomn
|
||||
notify['notify'] = getBoolean()
|
||||
elif myarg == 'subject':
|
||||
notify['subject'] = getString(Cmd.OB_STRING)
|
||||
elif myarg in SORF_MSG_FILE_ARGUMENTS:
|
||||
notify['message'], notify['charset'], notify['html'] = getStringOrFile(myarg)
|
||||
elif myarg == 'html':
|
||||
notify['html'] = getBoolean()
|
||||
elif myarg == 'from':
|
||||
notify['from'] = getString(Cmd.OB_EMAIL_ADDRESS)
|
||||
elif myarg == 'mailbox':
|
||||
notify['mailbox'] = getString(Cmd.OB_EMAIL_ADDRESS)
|
||||
elif myarg == 'replyto':
|
||||
notify['replyto'] = getString(Cmd.OB_EMAIL_ADDRESS)
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def getUserAttributes(cd, updateCmd, noUid=False):
|
||||
def getKeywordAttribute(keywords, attrdict, **opts):
|
||||
if Cmd.ArgumentsRemaining():
|
||||
@@ -44281,22 +44373,10 @@ def getUserAttributes(cd, updateCmd, noUid=False):
|
||||
resolveConflictAccount = True
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'notify':
|
||||
notify['recipients'].extend(getNormalizedEmailAddressEntity(shlexSplit=True, noLower=True))
|
||||
if getNotifyArguments(myarg, notify, True):
|
||||
pass
|
||||
elif myarg == 'notifyrecoveryemail':
|
||||
parameters['notifyRecoveryEmail'] = True
|
||||
elif myarg == 'subject':
|
||||
notify['subject'] = getString(Cmd.OB_STRING)
|
||||
elif myarg in SORF_MSG_FILE_ARGUMENTS:
|
||||
notify['message'], notify['charset'], notify['html'] = getStringOrFile(myarg)
|
||||
elif myarg == 'html':
|
||||
notify['html'] = getBoolean()
|
||||
elif myarg == 'from':
|
||||
notify['from'] = getString(Cmd.OB_EMAIL_ADDRESS)
|
||||
elif myarg == 'replyto':
|
||||
notify['replyto'] = getString(Cmd.OB_EMAIL_ADDRESS)
|
||||
elif myarg == 'mailbox':
|
||||
notify['mailbox'] = getString(Cmd.OB_EMAIL_ADDRESS)
|
||||
elif PwdOpts.ProcessArgument(myarg, notify, notFoundBody):
|
||||
pass
|
||||
elif _getTagReplacement(myarg, tagReplacements, True):
|
||||
@@ -44702,12 +44782,12 @@ def createUserAddAliases(cd, user, aliasList, i, count):
|
||||
# [license <SKUID> [product|productid <ProductID>]]
|
||||
# [[notify <EmailAddressList>] [notifyrecoveryemail]
|
||||
# [subject <String>]
|
||||
# [notifypassword <String>]
|
||||
# [from <EmailAaddress>]
|
||||
# [from <EmailAaddress>] [mailbox <EmailAddress>]
|
||||
# [replyto <EmailAaddress>]
|
||||
# [<NotifyMessageContent>]
|
||||
# (replace <Tag> <UserReplacement>)*
|
||||
# (replaceregex <REMatchPattern> <RESubstitution> <Tag> <UserReplacement>)*]
|
||||
# [notifypassword <String>]
|
||||
# [<NotifyMessageContent>] [html [<Boolean>]]
|
||||
# (replace <Tag> <UserReplacement>)*
|
||||
# (replaceregex <REMatchPattern> <RESubstitution> <Tag> <UserReplacement>)*]
|
||||
# [logpassword <FileName>] [ignorenullpassword]
|
||||
# [addnumericsuffixonduplicate <Number>]
|
||||
def doCreateUser():
|
||||
@@ -44806,12 +44886,12 @@ def verifyUserPrimaryEmail(cd, user, createIfNotFound, i, count):
|
||||
# [alias|aliases <EmailAddressList>]
|
||||
# [[notify <EmailAddressList>] [notifyrecoveryemail]
|
||||
# [subject <String>]
|
||||
# [notifypassword <String>]
|
||||
# [from <EmailAaddress>]
|
||||
# [from <EmailAaddress>] [mailbox <EmailAddress>]
|
||||
# [replyto <EmailAaddress>]
|
||||
# [<NotifyMessageContent>
|
||||
# [<NotifyMessageContent> [html [<Boolean>]]
|
||||
# (replace <Tag> <UserReplacement>)*
|
||||
# (replaceregex <REMatchPattern> <RESubstitution> <Tag> <UserReplacement>)*]
|
||||
# [notifypassword <String>]]
|
||||
# [notifyonupdate [<Boolean>]]
|
||||
# [logpassword <FileName>] [ignorenullpassword]
|
||||
def updateUsers(entityList):
|
||||
@@ -64876,11 +64956,11 @@ def claimOwnership(users):
|
||||
elif myarg == 'onlyusers':
|
||||
_, userList = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS)
|
||||
checkOnly = True
|
||||
onlyOwners = set(userList)
|
||||
onlyOwners = {normalizeEmailAddressOrUID(user, noUid=True) for user in userList}
|
||||
elif myarg == 'skipusers':
|
||||
_, userList = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS)
|
||||
checkSkip = len(userList) > 0
|
||||
skipOwners = set(userList)
|
||||
skipOwners = {normalizeEmailAddressOrUID(user, noUid=True) for user in userList}
|
||||
elif myarg == 'subdomains':
|
||||
subdomains = getEntityList(Cmd.OB_DOMAIN_NAME_ENTITY)
|
||||
elif myarg == 'includetrashed':
|
||||
@@ -73853,14 +73933,58 @@ def printShowMessages(users):
|
||||
def printShowThreads(users):
|
||||
printShowMessagesThreads(users, Ent.THREAD)
|
||||
|
||||
def sendCreateDelegateNotification(user, delegate, basenotify, i=0, count=0, msgFrom=None):
|
||||
# Substitute for #user#, #delegate#
|
||||
def _substituteForPattern(field, pattern, value):
|
||||
if field.find('#') == -1:
|
||||
return field
|
||||
return field.replace(pattern, value)
|
||||
|
||||
def _makeSubstitutions(field):
|
||||
notify[field] = _substituteForPattern(notify[field], '#user#', user)
|
||||
notify[field] = _substituteForPattern(notify[field], '#delegate#', delegate)
|
||||
|
||||
notify = basenotify.copy()
|
||||
if not notify['subject']:
|
||||
notify['subject'] = Msg.CREATE_DELEGATE_NOTIFY_SUBJECT
|
||||
_makeSubstitutions('subject')
|
||||
if not notify['message']:
|
||||
notify['message'] = Msg.CREATE_DELEGATE_NOTIFY_MESSAGE
|
||||
elif notify['html']:
|
||||
notify['message'] = notify['message'].replace('\r', '').replace('\\n', '<br/>')
|
||||
else:
|
||||
notify['message'] = notify['message'].replace('\r', '').replace('\\n', '\n')
|
||||
_makeSubstitutions('message')
|
||||
if 'from' in notify:
|
||||
msgFrom = notify['from']
|
||||
msgReplyTo = notify.get('replyto', None)
|
||||
mailBox = notify.get('mailbox', None)
|
||||
send_email(notify['subject'], notify['message'], delegate, i, count,
|
||||
msgFrom=msgFrom, msgReplyTo=msgReplyTo, html=notify['html'], charset=notify['charset'], mailBox=mailBox)
|
||||
|
||||
# gam <UserTypeEntity> create delegate|delegates [convertalias] <UserEntity>
|
||||
# [notify [<Boolean>]
|
||||
# [subject <String>]
|
||||
# [from <EmailAaddress>] [mailbox <EmailAddress>]
|
||||
# [replyto <EmailAaddress>]
|
||||
# [<NotifyMessageContent>] [html [<Boolean>]]
|
||||
# ]
|
||||
# gam <UserTypeEntity> delete delegate|delegates [convertalias] <UserEntity>
|
||||
def processDelegates(users):
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
function = 'delete' if Act.Get() == Act.DELETE else 'create'
|
||||
createCmd = Act.Get() != Act.DELETE
|
||||
aliasAllowed = not checkArgumentPresent(['convertalias'])
|
||||
delegateEntity = getUserObjectEntity(Cmd.OB_USER_ENTITY, Ent.DELEGATE)
|
||||
checkForExtraneousArguments()
|
||||
notify = {'notify': False, 'subject': '', 'message': '', 'html': False, 'charset': UTF8}
|
||||
if createCmd:
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if getNotifyArguments(myarg, notify, False):
|
||||
pass
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
else:
|
||||
checkForExtraneousArguments()
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
@@ -73872,25 +73996,37 @@ def processDelegates(users):
|
||||
for delegate in delegates:
|
||||
j += 1
|
||||
delegateEmail = convertUIDtoEmailAddress(delegate, cd=cd, emailTypes=['user', 'group'], aliasAllowed=aliasAllowed)
|
||||
kvList = [Ent.USER, user, Ent.DELEGATE, delegateEmail]
|
||||
try:
|
||||
if function == 'create':
|
||||
callGAPI(gmail.users().settings().delegates(), function,
|
||||
if createCmd:
|
||||
callGAPI(gmail.users().settings().delegates(), 'create',
|
||||
throwReasons=GAPI.GMAIL_THROW_REASONS+[GAPI.ALREADY_EXISTS, GAPI.FAILED_PRECONDITION, GAPI.INVALID,
|
||||
GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED],
|
||||
userId='me', body={'delegateEmail': delegateEmail})
|
||||
entityActionPerformed(kvList, j, jcount)
|
||||
if notify['notify']:
|
||||
Ind.Increment()
|
||||
sendCreateDelegateNotification(user, delegateEmail, notify, j, jcount)
|
||||
Ind.Decrement()
|
||||
else:
|
||||
callGAPI(gmail.users().settings().delegates(), function,
|
||||
callGAPI(gmail.users().settings().delegates(), 'delete',
|
||||
throwReasons=GAPI.GMAIL_THROW_REASONS+[GAPI.NOT_FOUND, GAPI.INVALID_INPUT, GAPI.PERMISSION_DENIED],
|
||||
userId='me', delegateEmail=delegateEmail)
|
||||
entityActionPerformed([Ent.USER, user, Ent.DELEGATE, delegateEmail], j, jcount)
|
||||
entityActionPerformed(kvList, j, jcount)
|
||||
except (GAPI.alreadyExists, GAPI.failedPrecondition, GAPI.invalid,
|
||||
GAPI.notFound, GAPI.invalidArgument, GAPI.permissionDenied) as e:
|
||||
entityActionFailedWarning([Ent.USER, user, Ent.DELEGATE, delegateEmail], str(e), j, jcount)
|
||||
entityActionFailedWarning(kvList, str(e), j, jcount)
|
||||
except GAPI.serviceNotAvailable:
|
||||
userGmailServiceNotEnabledWarning(user, i, count)
|
||||
Ind.Decrement()
|
||||
|
||||
# gam <UserTypeEntity> delegate to [convertalias] <UserEntity>
|
||||
# [notify [<Boolean>]
|
||||
# [subject <String>]
|
||||
# [from <EmailAaddress>] [mailbox <EmailAddress>]
|
||||
# [replyto <EmailAaddress>]
|
||||
# [<NotifyMessageContent>] [html [<Boolean>]]
|
||||
# ]
|
||||
def delegateTo(users):
|
||||
checkArgumentPresent('to', required=True)
|
||||
processDelegates(users)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -60,7 +60,6 @@ DIRECTORY = 'directory'
|
||||
DOCS = 'docs'
|
||||
DRIVE2 = 'drive2'
|
||||
DRIVE3 = 'drive3'
|
||||
DRIVECD = 'drivecd'
|
||||
DRIVETD = 'drivetd'
|
||||
DRIVEACTIVITY = 'driveactivity'
|
||||
DRIVELABELS = 'drivelabels'
|
||||
@@ -92,7 +91,6 @@ SERVICEACCOUNTLOOKUP = 'serviceaccountlookup'
|
||||
SERVICEMANAGEMENT = 'servicemanagement'
|
||||
SERVICEUSAGE = 'serviceusage'
|
||||
SHEETS = 'sheets'
|
||||
SHEETSCD = 'sheetscd'
|
||||
SHEETSTD = 'sheetstd'
|
||||
SITEVERIFICATION = 'siteVerification'
|
||||
STORAGE = 'storage'
|
||||
@@ -107,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'
|
||||
@@ -158,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',
|
||||
@@ -255,7 +256,6 @@ _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'},
|
||||
DRIVECD: {'name': 'Drive API v3 - read command data', '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},
|
||||
@@ -286,7 +286,6 @@ _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},
|
||||
SHEETSCD: {'name': 'Sheets API - read command data', '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},
|
||||
@@ -534,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,
|
||||
@@ -542,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': [],
|
||||
@@ -647,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,
|
||||
@@ -660,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': [],
|
||||
@@ -754,12 +767,7 @@ _SVCACCT_SCOPES = [
|
||||
]
|
||||
|
||||
_SVCACCT_SPECIAL_SCOPES = [
|
||||
{'name': 'Drive API - read command data',
|
||||
'api': DRIVECD,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': DRIVE_SCOPE+'.readonly'},
|
||||
{'name': 'Drive API - write todrive data',
|
||||
{'name': 'Drive API - write todrive data - has access to all Drive',
|
||||
'api': DRIVETD,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
@@ -774,12 +782,7 @@ _SVCACCT_SPECIAL_SCOPES = [
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': GMAIL_SEND_SCOPE},
|
||||
{'name': 'Sheets API - read command data',
|
||||
'api': SHEETSCD,
|
||||
'offByDefault': True,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/spreadsheets.readonly'},
|
||||
{'name': 'Sheets API - write todrive data',
|
||||
{'name': 'Sheets API - write todrive data - has access to all Sheets',
|
||||
'api': SHEETSTD,
|
||||
'offByDefault': True,
|
||||
'subscopes': [],
|
||||
@@ -805,14 +808,18 @@ def getVersion(api):
|
||||
def getClientScopesSet(api):
|
||||
return {scope['scope'] for scope in _CLIENT_SCOPES if scope['api'] == api}
|
||||
|
||||
def getClientScopesList(todriveClientAccess):
|
||||
def getClientScopesList(commanddataClientAccess, todriveClientAccess):
|
||||
caScopes = _CLIENT_SCOPES[:]
|
||||
if commanddataClientAccess:
|
||||
caScopes.extend(_COMMANDDATA_CLIENT_SCOPES)
|
||||
if todriveClientAccess:
|
||||
caScopes.extend(_TODRIVE_CLIENT_SCOPES)
|
||||
return sorted(caScopes, key=lambda k: k['name'])
|
||||
|
||||
def getClientScopesURLs(todriveClientAccess):
|
||||
def getClientScopesURLs(commanddataClientAccess, todriveClientAccess):
|
||||
caScopes = _CLIENT_SCOPES[:]
|
||||
if commanddataClientAccess:
|
||||
caScopes.extend(_COMMANDDATA_CLIENT_SCOPES)
|
||||
if todriveClientAccess:
|
||||
caScopes.extend(_TODRIVE_CLIENT_SCOPES)
|
||||
return sorted({scope['scope'] for scope in _CLIENT_SCOPES})
|
||||
|
||||
@@ -85,6 +85,8 @@ CMDLOG_MAX__BACKUPS = 'cmdlog_max__backups'
|
||||
CMDLOG_MAX_BACKUPS = 'cmdlog_max_backups'
|
||||
# Command logging max kilo bytes per log file
|
||||
CMDLOG_MAX_KILO_BYTES = 'cmdlog_max_kilo_bytes'
|
||||
# Use client access for command data from Google Docs/Sheets
|
||||
COMMANDDATA_CLIENTACCESS = 'commanddata_clientaccess'
|
||||
# GAM config directory containing client_secrets.json, oauth2.txt, oauth2service.json, extra_args.txt
|
||||
CONFIG_DIR = 'config_dir'
|
||||
# When retrieving lists of Google Contacts from API, how many should be retrieved in each chunk
|
||||
@@ -147,6 +149,8 @@ CSV_OUTPUT_USERS_AUDIT = 'csv_output_users_audit'
|
||||
CUSTOMER_ID = 'customer_id'
|
||||
# If debug_level > 0: extra_args['prettyPrint'] = True, httplib2.debuglevel = gam_debug_level, appsObj.debug = True
|
||||
DEBUG_LEVEL = 'debug_level'
|
||||
# redact sensitive credentials from debug output
|
||||
DEBUG_REDACTION = 'debug_redaction'
|
||||
# Developer Preview API Key
|
||||
DEVELOPER_PREVIEW_API_KEY = 'developer_preview_api_key'
|
||||
# When retrieving lists of ChromeOS devices from API, how many should be retrieved in each chunk
|
||||
@@ -344,6 +348,7 @@ Defaults = {
|
||||
CMDLOG: '',
|
||||
CMDLOG_MAX_BACKUPS: 5,
|
||||
CMDLOG_MAX_KILO_BYTES: 1000,
|
||||
COMMANDDATA_CLIENTACCESS: FALSE,
|
||||
CONFIG_DIR: '',
|
||||
CONTACT_MAX_RESULTS: '100',
|
||||
CSV_INPUT_COLUMN_DELIMITER: ',',
|
||||
@@ -375,6 +380,7 @@ Defaults = {
|
||||
CSV_OUTPUT_USERS_AUDIT: FALSE,
|
||||
CUSTOMER_ID: MY_CUSTOMER,
|
||||
DEBUG_LEVEL: '0',
|
||||
DEBUG_REDACTION: TRUE,
|
||||
DEVELOPER_PREVIEW_API_KEY: '',
|
||||
DEVICE_MAX_RESULTS: '200',
|
||||
DOMAIN: '',
|
||||
@@ -512,6 +518,7 @@ VAR_INFO = {
|
||||
CMDLOG: {VAR_TYPE: TYPE_FILE, VAR_ACCESS: os.W_OK},
|
||||
CMDLOG_MAX_BACKUPS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 10)},
|
||||
CMDLOG_MAX_KILO_BYTES: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (100, 10000)},
|
||||
COMMANDDATA_CLIENTACCESS: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
CONFIG_DIR: {VAR_TYPE: TYPE_DIRECTORY, VAR_ENVVAR: 'GAMUSERCONFIGDIR'},
|
||||
CONTACT_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 10000)},
|
||||
CSV_INPUT_COLUMN_DELIMITER: {VAR_TYPE: TYPE_CHARACTER},
|
||||
@@ -543,6 +550,7 @@ VAR_INFO = {
|
||||
CSV_OUTPUT_USERS_AUDIT: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
CUSTOMER_ID: {VAR_TYPE: TYPE_STRING, VAR_ENVVAR: 'CUSTOMER_ID', VAR_LIMITS: (0, None)},
|
||||
DEBUG_LEVEL: {VAR_TYPE: TYPE_INTEGER, VAR_SIGFILE: 'debug.gam', VAR_LIMITS: (0, None), VAR_SFFT: ('0', '4')},
|
||||
DEBUG_REDACTION: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
DEVELOPER_PREVIEW_API_KEY: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
DEVICE_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 200)},
|
||||
DOMAIN: {VAR_TYPE: TYPE_STRING, VAR_ENVVAR: 'GA_DOMAIN', VAR_LIMITS: (0, None)},
|
||||
|
||||
@@ -107,6 +107,8 @@ CURRENT_SVCACCT_USER = 'csa'
|
||||
DATETIME_NOW = 'dtno'
|
||||
# If debug_level > 0: extra_args['prettyPrint'] = True, httplib2.debuglevel = gam_debug_level, appsObj.debug = True
|
||||
DEBUG_LEVEL = 'dbgl'
|
||||
# Whether debug output should redact sensitive credentials
|
||||
DEBUG_REDACTION = 'dbrd'
|
||||
# Decoded ID token
|
||||
DECODED_ID_TOKEN = 'didt'
|
||||
# Index of start of <UserTypeEntity> in command line
|
||||
@@ -263,6 +265,7 @@ Globals = {
|
||||
CURRENT_SVCACCT_USER: None,
|
||||
DATETIME_NOW: None,
|
||||
DEBUG_LEVEL: 0,
|
||||
DEBUG_REDACTION: True,
|
||||
DECODED_ID_TOKEN: None,
|
||||
ENTITY_CL_DELAY_START: 1,
|
||||
ENTITY_CL_START: 1,
|
||||
|
||||
@@ -224,6 +224,8 @@ COUNT_N_EXCEEDS_MAX_TO_PROCESS_M = 'Count {0} exceeds maximum to {1} {2}'
|
||||
CORRUPT_FILE = 'Corrupt file'
|
||||
COULD_NOT_FIND_ANY_YUBIKEY = 'Could not find any YubiKey\n'
|
||||
COULD_NOT_FIND_YUBIKEY_WITH_SERIAL = 'Could not find YubiKey with serial number {0}\n'
|
||||
CREATE_DELEGATE_NOTIFY_MESSAGE = '#user# has granted you #delegate# access to read, delete and send mail on their behalf.'
|
||||
CREATE_DELEGATE_NOTIFY_SUBJECT = '#user# mail delegation to #delegate#'
|
||||
CREATE_USER_NOTIFY_MESSAGE = 'Hello #givenname# #familyname#,\n\nYou have a new account at #domain#\nAccount details:\nUsername: #user#\nPassword: #password#\nStart using your new account by signing in at\nhttps://www.google.com/accounts/AccountChooser?Email=#user#&continue=https://workspace.google.com/dashboard\n'
|
||||
CREATE_USER_NOTIFY_SUBJECT = 'Welcome to #domain#'
|
||||
CSV_DATA_ALREADY_SAVED = 'CSV data already saved'
|
||||
@@ -516,7 +518,7 @@ To set up Google Chat for your current project, please go to:
|
||||
|
||||
and follow the instructions at:
|
||||
|
||||
https://github.com/GAM-team/GAM/wiki/Chat-Bot#set-up-a-chat-bot
|
||||
https://github.com/GAM-team/GAM/wiki/Chat-Bot-Setup-Use#set-up-a-chat-bot
|
||||
|
||||
You'll use projects/{1}/topics/no-topic in Connection settings Cloud Pub/Sub Topic Name
|
||||
"""
|
||||
|
||||
@@ -43,7 +43,7 @@ Even if you're not going to use GAM as a Chat Bot, you have to configure a Chat
|
||||
* Uncheck "Build this Chat app as a Workspace add-on."
|
||||
* Enter an App name and Description of your choosing.
|
||||
* For the Avatar URL you can use `https://dummyimage.com/384x256/4d4d4d/0011ff.png&text=+GAM` or a public URL to an image of your own choosing.
|
||||
* In Functionality, uncheck both "Receive 1:1 messages" and "Join spaces and group conversations"
|
||||
* Inƒ Functionality, uncheck both "Receive 1:1 messages" and "Join spaces and group conversations" if present
|
||||
* In Connection settings, choose "Cloud Pub/Sub" and enter `projects/<ProjectID>/topics/no-topic` for the Topic Name. Replace `<ProjectID>` with your GAM project ID. GAM doesn't yet listen to pub/sub so this option is not used.
|
||||
* In Visibility, uncheck "Make this Chat app available to specific people and groups in Domain Workspace".
|
||||
* Click Save.
|
||||
@@ -288,7 +288,7 @@ gam create chatmessage spaces spaces/AAAADi-pvqc gdoc announcements@domain.com n
|
||||
Updates and rewrites an existing Chat message. Message will show as edited and no notification will be sent to members.
|
||||
```
|
||||
gam update chatmessage name <ChatMessage>
|
||||
<ChatContent>
|
||||
[<ChatContent>] [clearattachments <String>]
|
||||
```
|
||||
Specify the source of the message:
|
||||
* `text <String>` - The message is `<String>`
|
||||
@@ -296,12 +296,22 @@ Specify the source of the message:
|
||||
* `gdoc <UserGoogleDoc>` - The message is read from a Google Doc.
|
||||
* `gcsdoc <StorageBucketObjectName>` - The message is read from a Google Cloud Storage file.
|
||||
|
||||
The option `clearattachments <String>` can be used to clear all attachments from a Chat message.
|
||||
If `<ChatContent>` is not specified, the current message text is retained and `<String>` is appended;
|
||||
`<String>` must be specified but can be empty in which case the current message test is preserved as-is.
|
||||
|
||||
### Example
|
||||
|
||||
This example updates an existing chat message with new text.
|
||||
```
|
||||
gam update chatmessage name spaces/AAAADi-pvqc/messages/PKJrx90ooIU.PKJrx90ooIU text "HELLO CHAT?"
|
||||
```
|
||||
|
||||
This example clears attachments from a chat message and appends ` - Attachments cleared`
|
||||
to the current message text.
|
||||
```
|
||||
gam update chatmessage name spaces/AAAADi-pvqc/messages/PKJrx90ooIU.PKJrx90ooIU clearattachments " - Attachments cleared"
|
||||
```
|
||||
----
|
||||
|
||||
## Delete a Chat Message
|
||||
|
||||
@@ -245,7 +245,7 @@ to set `<GroupAttribute>`.
|
||||
gam create cigroup <EmailAddress>
|
||||
[copyfrom <GroupItem>] <GroupAttribute>*
|
||||
[makeowner] [alias|aliases <CIGroupAliasList>]
|
||||
[security|makesecuritygroup]
|
||||
[security|makesecuritygroup] [locked]
|
||||
[dynamic <QueryDynamicGroup>]
|
||||
gam update cigroup <GroupEntity> [copyfrom <GroupItem>] <GroupAttribute>
|
||||
[security|makesecuritygroup|
|
||||
|
||||
@@ -53,286 +53,7 @@ You must enable access to policies in the GCP cloud console.
|
||||
These are the supported policies GAM can show today.
|
||||
|
||||
See: https://cloud.google.com/identity/docs/concepts/supported-policy-api-settings
|
||||
```
|
||||
user_takeout_status (is takeout enabled for service)
|
||||
blogger.user_takeout
|
||||
books.user_takeout
|
||||
location_history.user_takeout
|
||||
maps.user_takeout
|
||||
pay.user_takeout
|
||||
photos.user_takeout
|
||||
play.user_takeout
|
||||
play_console.user_takeout
|
||||
youtube.user_takeout
|
||||
service_status (is service enabled)
|
||||
ad_manager
|
||||
ads
|
||||
adsense
|
||||
alerts
|
||||
analytics
|
||||
applied_digital_skills
|
||||
appsheet
|
||||
arts_and_culture
|
||||
beyondcorp_enterprise
|
||||
blogger
|
||||
bookmarks
|
||||
books
|
||||
calendar
|
||||
campaign_manager
|
||||
chat
|
||||
chrome_canvas
|
||||
chrome_remote_desktop
|
||||
chrome_sync
|
||||
chrome_web_store
|
||||
classroom
|
||||
cloud
|
||||
cloud_search
|
||||
colab
|
||||
cs_first
|
||||
data_studio
|
||||
developers
|
||||
domains
|
||||
drive_and_docs
|
||||
earth
|
||||
enterprise_service_restrictions
|
||||
experimental_apps
|
||||
feedburner
|
||||
fi
|
||||
gmail
|
||||
groups
|
||||
groups_for_business
|
||||
jamboard
|
||||
keep
|
||||
location_history
|
||||
managed_play
|
||||
maps
|
||||
material_gallery
|
||||
meet
|
||||
merchant_center
|
||||
messages
|
||||
migrate
|
||||
my_business
|
||||
my_maps
|
||||
news
|
||||
partner_dash
|
||||
pay
|
||||
pay_for_business
|
||||
photos
|
||||
pinpoint
|
||||
play
|
||||
play_books_partner_center
|
||||
play_console
|
||||
public_data
|
||||
question_hub
|
||||
scholar_profiles
|
||||
search_ads_360
|
||||
search_and_assistant
|
||||
search_console
|
||||
sites
|
||||
socratic
|
||||
takeout
|
||||
tasks
|
||||
third_party_app_backups
|
||||
translate
|
||||
trips
|
||||
vault
|
||||
voice
|
||||
work_insights
|
||||
youtube
|
||||
calendar.appointment_schedules
|
||||
enablePayments
|
||||
chat.chat_apps_access
|
||||
enableApps
|
||||
enableWebhooks
|
||||
chat.chat_file_sharing
|
||||
externalFileSharing
|
||||
internalFileSharing
|
||||
chat.chat_history
|
||||
enableChatHistory
|
||||
historyOnByDefault
|
||||
allowUserModification
|
||||
chat.external_chat_restriction
|
||||
allowExternalChat
|
||||
chat.space_history
|
||||
historyState
|
||||
classroom.api_data_access
|
||||
enableApiAccess
|
||||
classroom.class_membership
|
||||
whoCanJoinClasses
|
||||
whichClassesCanUsersJoin
|
||||
classroom.guardian_access
|
||||
allowAccess
|
||||
whoCanManageGuardianAccess
|
||||
classroom.originality_reports
|
||||
enableOriginalityReportsSchoolMatches
|
||||
classroom.roster_import
|
||||
rosterImportOption
|
||||
classroom.student_unenrollment
|
||||
whoCanUnenrollStudents
|
||||
classroom.teacher_permissions
|
||||
whoCanCreateClasses
|
||||
cloud_sharing_options.cloud_data_sharing
|
||||
sharingOptions
|
||||
detector.regular_expression
|
||||
displayName
|
||||
regularExpression
|
||||
createTime
|
||||
updateTime
|
||||
detector.word_list
|
||||
displayName
|
||||
wordList
|
||||
createTime
|
||||
updateTime
|
||||
description
|
||||
drive_and_docs.drive_for_desktop
|
||||
allowDriveForDesktop
|
||||
restrictToAuthorizedDevices
|
||||
showDownloadLink
|
||||
allowRealTimePresence
|
||||
drive_and_docs.external_sharing
|
||||
externalSharingMode
|
||||
allowReceivingExternalFiles
|
||||
warnForSharingOutsideAllowlistedDomains
|
||||
allowReceivingFilesOutsideAllowlistedDomains
|
||||
allowNonGoogleInvitesInAllowlistedDomains
|
||||
warnForExternalSharing
|
||||
allowNonGoogleInvites
|
||||
allowPublishingFiles
|
||||
accessCheckerSuggestions
|
||||
allowedPartiesForDistributingContent
|
||||
drive_and_docs.file_security_update
|
||||
securityUpdate
|
||||
allowUsersToManageUpdate
|
||||
drive_and_docs.shared_drive_creation
|
||||
allowSharedDriveCreation
|
||||
orgUnitForNewSharedDrives
|
||||
customOrgUnit
|
||||
allowManagersToOverrideSettings
|
||||
allowExternalUserAccess
|
||||
allowNonMemberAccess
|
||||
allowedPartiesForDownloadPrintCopy
|
||||
allowContentManagersToShareFolders
|
||||
gmail.auto_forwarding
|
||||
enableAutoForwarding
|
||||
gmail.confidential_mode
|
||||
enableConfidentialMode
|
||||
gmail.email_attachment_safety
|
||||
enableEncryptedAttachmentProtection
|
||||
encryptedAttachmentProtectionConsequence
|
||||
enableAttachmentWithScriptsProtection
|
||||
attachmentWithScriptsProtectionConsequence
|
||||
enableAnomalousAttachmentProtection
|
||||
anomalousAttachmentProtectionConsequence
|
||||
allowedAnomalousAttachmentFiletypes
|
||||
applyFutureRecommendedSettingsAutomatically
|
||||
encryptedAttachmentProtectionQuarantineId
|
||||
attachmentWithScriptsProtectionQuarantineId
|
||||
anomalousAttachmentProtectionQuarantineId
|
||||
gmail.email_image_proxy_bypass
|
||||
imageProxyBypassPattern
|
||||
enableImageProxy
|
||||
gmail.enhanced_pre_delivery_message_scanning
|
||||
enableImprovedSuspiciousContentDetection
|
||||
gmail.enhanced_smime_encryption
|
||||
enableSmimeEncryption
|
||||
allowUserToUploadCertificates
|
||||
gmail.gmail_name_format
|
||||
allowCustomDisplayNames
|
||||
defaultDisplayNameFormat
|
||||
gmail.imap_access
|
||||
enableImapAccess
|
||||
gmail.links_and_external_images
|
||||
enableShortenerScanning
|
||||
enableExternalImageScanning
|
||||
enableAggressiveWarningsOnUntrustedLinks
|
||||
applyFutureSettingsAutomatically
|
||||
gmail.per_user_outbound_gateway
|
||||
allowUsersToUseExternalSmtpServers
|
||||
gmail.pop_access
|
||||
enablePopAccess
|
||||
gmail.spoofing_and_authentication
|
||||
detectDomainNameSpoofing
|
||||
detectEmployeeNameSpoofing
|
||||
detectDomainSpoofingFromUnauthenticatedSenders
|
||||
detectUnauthenticatedEmails
|
||||
domainNameSpoofingConsequence
|
||||
employeeNameSpoofingConsequence
|
||||
domainSpoofingConsequence
|
||||
unauthenticatedEmailConsequence
|
||||
detectGroupsSpoofing
|
||||
groupsSpoofingVisibilityType
|
||||
groupsSpoofingConsequence
|
||||
applyFutureSettingsAutomatically
|
||||
domainNameSpoofingQuarantineId
|
||||
employeeNameSpoofingQuarantineId
|
||||
domainSpoofingQuarantineId
|
||||
unauthenticatedEmailQuarantineId
|
||||
groupsSpoofingQuarantineId
|
||||
gmail.user_email_uploads
|
||||
enableMailAndContactsImport
|
||||
gmail.workspace_sync_for_outlook
|
||||
enableGoogleWorkspaceSyncForMicrosoftOutlook
|
||||
groups_for_business.groups_sharing
|
||||
ownersCanAllowIncomingMailFromPublic
|
||||
collaborationCapability
|
||||
createGroupsAccessLevel
|
||||
ownersCanAllowExternalMembers
|
||||
ownersCanHideGroups
|
||||
newGroupsAreHidden
|
||||
viewTopicsDefaultAccessLevel
|
||||
meet.safety_access
|
||||
meetingsAllowedToJoin
|
||||
meet.safety_domain
|
||||
usersAllowedToJoin
|
||||
meet.safety_external_participants
|
||||
enableExternalLabel
|
||||
meet.safety_host_management
|
||||
enableHostManagement
|
||||
meet.video_recording
|
||||
enableRecording
|
||||
rule.dlp
|
||||
displayName
|
||||
description
|
||||
triggers
|
||||
condition
|
||||
action
|
||||
state
|
||||
createTime
|
||||
updateTime
|
||||
ruleTypeMetadata
|
||||
rule.system_defined_alerts
|
||||
displayName
|
||||
description
|
||||
action
|
||||
state
|
||||
createTime
|
||||
updateTime
|
||||
security.advanced_protection_program
|
||||
enableAdvancedProtectionSelfEnrollment
|
||||
securityCodeOption
|
||||
security.less_secure_apps
|
||||
allowLessSecureApps
|
||||
security.login_challenges
|
||||
enableEmployeeIdChallenge
|
||||
security.password
|
||||
allowedStrength
|
||||
minimumLength
|
||||
maximumLength
|
||||
enforceRequirementsAtLogin
|
||||
allowReuse
|
||||
expirationDuration
|
||||
security.session_controls
|
||||
webSessionDuration
|
||||
security.super_admin_account_recovery
|
||||
enableAccountRecovery
|
||||
security.user_account_recovery
|
||||
enableAccountRecovery
|
||||
sites.sites_creation_and_modification
|
||||
allowSitesCreation
|
||||
allowSitesModification
|
||||
workspace_marketplace.apps_allowlist
|
||||
apps
|
||||
```
|
||||
|
||||
## Display Cloud Identity Policies
|
||||
Display selected policies.
|
||||
```
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
- [Plain Text](#plain-text)
|
||||
- [HTML](#html)
|
||||
- [Read data from a Google Sheet](#read-data-from-a-google-sheet)
|
||||
- [Limited Service Account Access](#limited-service-account-access)
|
||||
- [Read data from a Google Cloud Storage File](#read-data-from-a-google-cloud-storage-file)
|
||||
- [Plain Text](#plain-text)
|
||||
- [CSV](#csv)
|
||||
@@ -79,6 +80,25 @@ Example:
|
||||
```
|
||||
gam csv gsheet you@exmaple.com <DriveFileIDEntity> "Sheet 1" gam create user firstname "~FirstName" lastname "~lastName" email "~email"
|
||||
```
|
||||
|
||||
## Limited Service Account Access
|
||||
If you want to disable a user's service account access to Drive and Sheets but still allow reading command data from Google Docs and Sheets,
|
||||
issue the following commands. The admin specified in `gam oauth create` can read command data from Docs and Sheets to which it has access.
|
||||
```
|
||||
gam config commanddata_clientaccess true save
|
||||
gam oauth create
|
||||
Enable the following and proceed to authorization.
|
||||
|
||||
[*] 42) Drive API - commanddata_clientaccess
|
||||
[*] 54) Sheets API - commanddata_clientaccess
|
||||
```
|
||||
In these options, the `<EmailAddress> is not used, but for clarity you may want to specify the
|
||||
email address of the admin specified in `gam oauth create`.
|
||||
```
|
||||
gdoc <EmailAddress> <DriveFileIDEntity>|<DriveFileNameEntity>|(<SharedDriveEntity> <SharedDriveFileNameEntity>)
|
||||
gsheet <EmailAddress> <DriveFileIDEntity>|<DriveFileNameEntity>|(<SharedDriveEntity> <SharedDriveFileNameEntity>) <SheetEntity>
|
||||
```
|
||||
|
||||
## Read data from a Google Cloud Storage File
|
||||
```
|
||||
<StorageBucketName> ::= <String>
|
||||
|
||||
@@ -10,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](#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,103 @@ 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.27.04
|
||||
|
||||
Added options to `gam <UserTypeEntity> create delegate` that support
|
||||
sending email notifications when a user adds a delegate.
|
||||
|
||||
* See: https://github.com/GAM-team/GAM/wiki/Users-Gmail-Delegates#delegation-notification
|
||||
|
||||
### 7.27.03
|
||||
|
||||
Updated `gam <UserTypeEntity> create|update|sync chatmember` role specification to `role member|manager|owner`.
|
||||
This is the mapping between the Chat UI and Chat API; GAM uses the Chat UI role names.
|
||||
```
|
||||
UI: Member, API: ROLE_MEMBER
|
||||
UI: Manager, API: ROLE_ASSISTANT_MANAGER
|
||||
UI: Owner, API: ROLE_MANAGER
|
||||
```
|
||||
|
||||
Updated `gam <UserTypeEntity> update chatspace` options for permission settings.
|
||||
```
|
||||
[managemembersandgroups owners|managers|members]
|
||||
[modifyspacedetails owners|managers|members]
|
||||
[togglehistory owners|managers|members]
|
||||
[useatmentionall owners|managers|members]
|
||||
[manageapps owners|managers|members]
|
||||
[managewebhooks owners|managers|members]
|
||||
[replymessages owners|managers|members]
|
||||
```
|
||||
|
||||
### 7.27.02
|
||||
|
||||
Added option `clearattachments <String>` to `gam [<UserTypeMessage>] update chatmessage`
|
||||
that clears all attachments from a Chat message. If `<ChatContent>` is not specified,
|
||||
the current message text is retained and `<String>` is appended; `<String>` must be specified
|
||||
but can be empty in which case the current message test is preserved as-is.
|
||||
|
||||
### 7.27.01
|
||||
|
||||
Fixed bug in `gam <UserTypeEntity> claim ownership <DriveFileEntity> ... onlyUsers|skipusers <UserTypeEntity>`
|
||||
where the email addresses in `onlyUsers|skipusers <UserTypeEntity>` were not normalized.
|
||||
|
||||
### 7.27.00
|
||||
|
||||
Added `debug_redaction` Boolean variable to `gam.cfg`. When True, the default,
|
||||
sensitive data like access/refresh tokens, client secret and authorization codes
|
||||
are redacted from debug output. This allows you to post debug output without
|
||||
compromising your account information. Even with debug redaction,
|
||||
anything shared publicly should be double-checked for sensitive content.
|
||||
|
||||
### 7.25.01
|
||||
|
||||
Fixed bug in `gam config timezone <String>` to handle timezone abbreviations correctly;
|
||||
they were incorrectly shifted to lowercase.
|
||||
|
||||
### 7.25.00
|
||||
|
||||
Removed a capabilty added in 7.24.00 that allowed reading command data from Google Docs and Sheets
|
||||
when a user's service account access to Drive and Sheets had been disabled. Jay was concerned
|
||||
that this change could be exploited to give access to all user's files.
|
||||
|
||||
This capability has been replaced by issuing the following commands. The admin specified in `gam oauth create`
|
||||
can read command data from Docs and Sheets to which it has access.
|
||||
```
|
||||
gam config commanddata_clientaccess true save
|
||||
gam oauth create
|
||||
Enable the following and proceed to authorization.
|
||||
|
||||
[*] 42) Drive API - commanddata_clientaccess
|
||||
[*] 54) Sheets API - commanddata_clientaccess
|
||||
```
|
||||
|
||||
* 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
|
||||
|
||||
@@ -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.07 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.27.04 - 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.23.07 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM 7.27.04 - 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
- [Chat Space Permissions](#chat-space-permissions)
|
||||
- [Manage Chat Spaces](#manage-chat-spaces)
|
||||
- [Display Chat Spaces](#display-chat-spaces)
|
||||
- [UI API member role mapping](#ui-api-mwmber-role-mapping)
|
||||
- [Manage Chat Members](#manage-chat-members)
|
||||
- [Display Chat Members](#display-chat-members)
|
||||
- [Manage Chat Messages](#manage-chat-messages)
|
||||
@@ -211,7 +212,7 @@ For `type space`, the following apply:
|
||||
* `description <String>` - Optional
|
||||
* `guidelines <String>` - Optional
|
||||
* `history <Boolean>` - Optional
|
||||
* `announcement|collaboration` - Initial permission settings; default is `collaboration`; this is in Developer Preview
|
||||
* `announcement|collaboration` - Initial permission settings; default is `collaboration`
|
||||
|
||||
For `type groupchat`, the following apply:
|
||||
* `members <UserTypeEntity>` - Required, must specify between 2 and 20 users
|
||||
@@ -244,30 +245,19 @@ gam <UserTypeEntity> update chatspace <ChatSpace>
|
||||
[type space]
|
||||
[description <String>] [guidelines|rules <String>]
|
||||
[history <Boolean>])
|
||||
[managemembersandgroups managers|members]
|
||||
[modifyspacedetails managers|members]
|
||||
[togglehistory managers|members]
|
||||
[useatmentionall managers|members]
|
||||
[manageapps managers|members]
|
||||
[managewebhooks managers|members]
|
||||
[replymessages managers|members]
|
||||
[managemembersandgroups owners|managers|members]
|
||||
[modifyspacedetails owners|managers|members]
|
||||
[togglehistory owners|managers|members]
|
||||
[useatmentionall owners|managers|members]
|
||||
[manageapps owners|managers|members]
|
||||
[managewebhooks owners|managers|members]
|
||||
[replymessages owners|managers|members]
|
||||
[formatjson]
|
||||
```
|
||||
A groupchat space can be upgraded to a space by specifying `type space` and `displayname <String>`.
|
||||
|
||||
The `restricted|audience` options can not be combined with options `displayname,type,description,guidelines,history`.
|
||||
|
||||
You can manage permissions for chat spaces with the following options that are available with Developer Preview.
|
||||
[managemembersandgroups managers|members]
|
||||
[modifyspacedetails managers|members]
|
||||
[togglehistory managers|members]
|
||||
[useatmentionall managers|members]
|
||||
[manageapps managers|members]
|
||||
[managewebhooks managers|members]
|
||||
[postmessages managers|members]
|
||||
[replymessages managers|members]
|
||||
|
||||
|
||||
By default, Gam displays the information about the created chatspace as an indented list of keys and values.
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
|
||||
@@ -327,7 +317,7 @@ gam <UserTypeEntity> show chatspaces
|
||||
By default, chat spaces of all types are displayed.
|
||||
* `types <ChatSpaceTypeList>` - Display specific types of spaces.
|
||||
|
||||
When listing Chat Spaces, the Chat API does not return the `accessSettings` field; if you need to see this fieldf,
|
||||
When listing Chat Spaces, the Chat API does not return the `accessSettings` field; if you need to see this field,
|
||||
add `showaccesssettings` to the command. This requires an additional Chat API call per chat space of type `SPACE`
|
||||
to get the `accessSettings` field.
|
||||
|
||||
@@ -343,7 +333,7 @@ gam <UserTypeEntity> print chatspaces [todrive <ToDriveAttribute>*]
|
||||
By default, chat spaces of all types are displayed.
|
||||
* `types <ChatSpaceTypeList>` - Display specific types of spaces.
|
||||
|
||||
When listing Chat Spaces, the Chat API does not return the `accessSettings` field; if you need to see this fieldf,
|
||||
When listing Chat Spaces, the Chat API does not return the `accessSettings` field; if you need to see this field,
|
||||
add `showaccesssettings` to the command. This requires an additional Chat API call per chat space of type `SPACE`
|
||||
to get the `accessSettings` field.
|
||||
|
||||
@@ -401,7 +391,7 @@ By default, all chat spaces of type SPACE are displayed.
|
||||
* `query <String> [querytime<String> <Time>]` - Display selected chat spaces
|
||||
* See: https://developers.google.com/workspace/chat/api/reference/rest/v1/spaces/search
|
||||
|
||||
When listing Chat Spaces, the Chat API does not return the `accessSettings` field; if you need to see this fieldf,
|
||||
When listing Chat Spaces, the Chat API does not return the `accessSettings` field; if you need to see this field,
|
||||
add `showaccesssettings` to the command. This requires an additional Chat API call per chat space of type `SPACE`
|
||||
to get the `accessSettings` field.
|
||||
|
||||
@@ -419,7 +409,7 @@ By default, all chat spaces of type SPACE are displayed.
|
||||
* `query <String> [querytime<String> <Time>]` - Display selected chat spaces
|
||||
* See: https://developers.google.com/workspace/chat/api/reference/rest/v1/spaces/search
|
||||
|
||||
When listing Chat Spaces, the Chat API does not return the `accessSettings` field; if you need to see this fieldf,
|
||||
When listing Chat Spaces, the Chat API does not return the `accessSettings` field; if you need to see this field,
|
||||
add `showaccesssettings` to the command. This requires an additional Chat API call per chat space of type `SPACE`
|
||||
to get the `accessSettings` field.
|
||||
|
||||
@@ -432,11 +422,20 @@ When using the `formatjson` option, double quotes are used extensively in the da
|
||||
The `quotechar <Character>` option allows you to choose an alternate quote character, single quote for instance, that makes for readable/processable output.
|
||||
`quotechar` defaults to `gam.cfg/csv_output_quote_char`. When uploading CSV files to Google, double quote `"` should be used.
|
||||
|
||||
## UI API member role mapping
|
||||
GAM uses the Chat UI role names.
|
||||
|
||||
| UI setting | API setting |
|
||||
|------------|------------|
|
||||
| Member | ROLE_MEMBER |
|
||||
| Manager | ROLE_ASSISTANT_MANAGER |
|
||||
| Owner | ROLE_MANAGER |
|
||||
|
||||
## Manage Chat Members
|
||||
### Add members to a user's chat space
|
||||
```
|
||||
gam <UserTypeEntity> create chatmember <ChatSpace>
|
||||
[type human|bot] [role member|manager]
|
||||
[type human|bot] [role member|manager|owner]
|
||||
(user <UserItem>)* (members <UserTypeEntity>)*
|
||||
(group <GroupItem>)* (groups <GroupEntity>)*
|
||||
[formatjson|returnidonly]
|
||||
@@ -462,7 +461,7 @@ gam <UserTypeEntity> remove chatmember members <ChatMemberList>
|
||||
Creating memberships for users outside the administrator's Google Workspace organization isn't supported using asadmin.
|
||||
```
|
||||
gam <UserItem> create chatmember asadmin <ChatSpace>
|
||||
[type human|bot] [role member|manager]
|
||||
[type human|bot] [role member|manager|owner]
|
||||
(user <UserItem>)* (members <UserTypeEntity>)*
|
||||
(group <GroupItem>)* (groups <GroupEntity>)*
|
||||
[formatjson|returnidonly]
|
||||
@@ -488,13 +487,13 @@ gam <UserItem> remove chatmember asadmin members <ChatMemberList>
|
||||
Update members by specifying a chat space, user/group email addresses and role.
|
||||
```
|
||||
gam <UserTypeEntity> update chatmember <ChatSpace>
|
||||
role member|manager
|
||||
role member|manager|owner
|
||||
((user <UserItem>)|(members <UserTypeEntity>))+
|
||||
```
|
||||
Update members by specifying chatmember names and role.
|
||||
```
|
||||
gam <UserTypeEntity> modify chatmember
|
||||
role member|manager
|
||||
role member|manager|owner
|
||||
members <ChatMemberList>
|
||||
```
|
||||
|
||||
@@ -502,13 +501,13 @@ gam <UserTypeEntity> modify chatmember
|
||||
Update members by specifying a chat space, user/group email addresses and role.
|
||||
```
|
||||
gam <UserItem> update chatmember asadmin <ChatSpace>
|
||||
role member|manager
|
||||
role member|manager|owner
|
||||
((user <UserItem>)|(members <UserTypeEntity>))+
|
||||
```
|
||||
Update members by specifying chatmember names and role.
|
||||
```
|
||||
gam <UserItem> modify chatmember asadmin
|
||||
role member|manager
|
||||
role member|manager|owner
|
||||
members <ChatMemberList>
|
||||
```
|
||||
|
||||
@@ -751,7 +750,7 @@ gam user user@domain.com create chatmessage spaces spaces/AAAADi-pvqc gdoc annou
|
||||
Updates and rewrites an existing Chat message. Message will show as edited and no notification will be sent to members.
|
||||
```
|
||||
gam <UserTypeEntity> update chatmessage name <ChatMessage>
|
||||
<ChatContent>
|
||||
[<ChatContent>] [clearattachments <String>]
|
||||
```
|
||||
Specify the text of the message: `<ChatContent>`
|
||||
* `text <String>` - The message is `<String>`
|
||||
@@ -759,12 +758,22 @@ Specify the text of the message: `<ChatContent>`
|
||||
* `gdoc <UserGoogleDoc>` - The message is read from a Google Doc.
|
||||
* `gcsdoc <StorageBucketObjectName>` - The message is read from a Google Cloud Storage file.
|
||||
|
||||
The option `clearattachments <String>` can be used to clear all attachments from a Chat message.
|
||||
If `<ChatContent>` is not specified, the current message text is retained and `<String>` is appended;
|
||||
`<String>` must be specified but can be empty in which case the current message test is preserved as-is.
|
||||
|
||||
### Example
|
||||
This example updates an existing chat message with new text.
|
||||
```
|
||||
gam user user@domain.com update chatmessage name spaces/AAAADi-pvqc/messages/PKJrx90ooIU.PKJrx90ooIU text "HELLO CHAT?"
|
||||
```
|
||||
|
||||
This example clears attachments from a chat message and appends ` - Attachments cleared`
|
||||
to the current message text.
|
||||
```
|
||||
gam user user@domain.com update chatmessage name spaces/AAAADi-pvqc/messages/PKJrx90ooIU.PKJrx90ooIU clearattachments " - Attachments cleared"
|
||||
```
|
||||
|
||||
### Delete a Chat Message
|
||||
Deletes the given Chat message. Members will no longer see the message.
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
- [API documentation](#api-documentation)
|
||||
- [Definitions](#definitions)
|
||||
- [Aliases](#aliases)
|
||||
- [Delegation Notification](#delegation-notification)
|
||||
- [Create Gmail delegates](#create-gmail-delegates)
|
||||
- [Delete Gmail delegates](#delete-gmail-delegates)
|
||||
- [Update Gmail delegates](#update-gmail-delegates)
|
||||
@@ -31,6 +32,23 @@ mail delegation is enabled. In the admin console, go to Apps/Google Workspace/Gm
|
||||
<UserEntity> ::=
|
||||
<UserList> | <FileSelector> | <CSVkmdSelector> | <CSVDataSelector>
|
||||
See: https://github.com/GAM-team/GAM/wiki/Collections-of-Users
|
||||
|
||||
<StorageBucketName> ::= <String>
|
||||
<StorageObjectName> ::= <String>
|
||||
<StorageBucketObjectName> ::=
|
||||
https://storage.cloud.google.com/<StorageBucketName>/<StorageObjectName>|
|
||||
https://storage.googleapis.com/<StorageBucketName>/<StorageObjectName>|
|
||||
gs://<StorageBucketName>/<StorageObjectName>|
|
||||
<StorageBucketName>/<StorageObjectName>
|
||||
|
||||
<UserGoogleDoc> ::=
|
||||
<EmailAddress> <DriveFileIDEntity>|<DriveFileNameEntity>|(<SharedDriveEntity> <SharedDriveFileNameEntity>)
|
||||
|
||||
<NotifyMessageContent> ::=
|
||||
(message|textmessage|htmlmessage <String>)|
|
||||
(file|textfile|htmlfile <FileName> [charset <Charset>])|
|
||||
(gdoc|ghtml <UserGoogleDoc>)|
|
||||
(gcsdoc|gcshtml <StorageBucketObjectName>)
|
||||
```
|
||||
## Aliases
|
||||
|
||||
@@ -39,11 +57,61 @@ The `convertalias` option causes GAM to make an extra API call per user in `<Use
|
||||
to convert aliases to primary email addresses. If you know that all of the email addresses
|
||||
in `<UserEntity>` are primary, you can omit `convertalias` and avoid the extra API calls.
|
||||
|
||||
## Delegation Notification
|
||||
When creating a delegate, you can send a message to the delegate.
|
||||
```
|
||||
[notify [<Boolean>]
|
||||
[subject <String>]
|
||||
[from <EmailAaddress>] [mailbox <EmailAddress>]
|
||||
[replyto <EmailAddress>]
|
||||
[<NotifyMessageContent>] [html [<Boolean>]]
|
||||
]
|
||||
```
|
||||
* `notify [<Boolean>]` - Should notification be sent
|
||||
|
||||
In the subject and message, these strings will be replaced with the specified values:
|
||||
* `#user#` - user's email address
|
||||
* `#delegate#` - delegate's email address
|
||||
|
||||
If subject is not specified, the following value will be used:
|
||||
* `#user# mail delegation to #delegate#`
|
||||
|
||||
`<NotifyMessageContent>` is the message, there are four ways to specify it:
|
||||
* `message|textmessage|htmlmessage <String>` - Use `<String>` as the message
|
||||
* `file|htmlfile <FileName> [charset <Charset>]` - Read the message from `<FileName>`
|
||||
* `gdoc|ghtml <UserGoogleDoc>` - Read the message from `<UserGoogleDoc>`
|
||||
* `gcsdoc|gcshtml <StorageBucketObjectName>` - Read the message from the Google Cloud Storage file `<StorageBucketObjectName>`
|
||||
|
||||
If `<NotifyMessageContent>`is not specified, the following value will be used:
|
||||
* `#user# has granted you #delegate# access to read, delete and send mail on their behalf.`
|
||||
|
||||
Unless specified in `<NotifyMessageContent>`, messages are sent as plain text,
|
||||
use `html` or `html true` to indicate that the message is HTML.
|
||||
|
||||
Use `\n` in `message <String>` to indicate a line break; no other special characters are recognized.
|
||||
|
||||
By default, the email is sent from the admin user identified in oauth2.txt, `gam oauth info` will show the value.
|
||||
Use `from <EmailAddress>` to specify an alternate from address.
|
||||
Use `mailbox <EmailAddress>` if `from <EmailAddress>` specifies a group; GAM has to login as a user to be able to send a message.
|
||||
Gam gets no indication as to the status of the message delivery; the from user will get a non-delivery receipt if the message could not be sent to the delegate.
|
||||
|
||||
## Create Gmail delegates
|
||||
These two commands are equivalent.
|
||||
```
|
||||
gam <UserTypeEntity> add delegate|delegates [convertalias] <UserEntity>
|
||||
[[notify <EmailAddressList>]
|
||||
[subject <String>]
|
||||
[from <EmailAaddress>] [mailbox <EmailAddress>]
|
||||
[replyto <EmailAaddress>]
|
||||
[<NotifyMessageContent>]
|
||||
]
|
||||
gam <UserTypeEntity> delegate|delegates to [convertalias] <UserEntity>
|
||||
[[notify <EmailAddressList>]
|
||||
[subject <String>]
|
||||
[from <EmailAaddress>] [mailbox <EmailAddress>]
|
||||
[replyto <EmailAaddress>]
|
||||
[<NotifyMessageContent>]
|
||||
]
|
||||
```
|
||||
### Example
|
||||
|
||||
@@ -51,6 +119,7 @@ To give Bob access to Fred's mailbox as a delegate:
|
||||
|
||||
```
|
||||
gam user fred@domain.com add delegate bob@domain.com
|
||||
gam user fred@domain.com delegate to bob@domain.com
|
||||
```
|
||||
|
||||
## Delete Gmail delegates
|
||||
|
||||
@@ -401,14 +401,13 @@ password "helloworld" nohash
|
||||
```
|
||||
|
||||
## Password Notification
|
||||
When creating a user or updating a user's password, you can send a message with details to an email address;'
|
||||
this might be the user's secondary email address or their recovery email address.
|
||||
When creating a user or updating a user's password, you can send a message with details to an email address
|
||||
or addresses; these might be the user's secondary email address, their recovery email address or a help desk user.
|
||||
```
|
||||
[[notify <EmailAddressList>] [notifyrecoveryemail]
|
||||
[subject <String>]
|
||||
[notifypassword <String>]
|
||||
[from <EmailAaddress>]
|
||||
[mailbox <EmailAddress>]
|
||||
[from <EmailAaddress>] [mailbox <EmailAddress>]
|
||||
[replyto <EmailAddress>]
|
||||
[<NotifyMessageContent>]
|
||||
(replace <Tag> <UserReplacement>)*
|
||||
@@ -419,6 +418,15 @@ this might be the user's secondary email address or their recovery email address
|
||||
* `notify <EmailAddressList>` - Specify recipients
|
||||
* `notifyrecoveryemail` - Use the user's recovery email address (if defined) as a recipient
|
||||
|
||||
In the subject and message, these strings will be replaced with the specified values:
|
||||
* `#givenname#` - first/given name
|
||||
* `#familyname#` - last/family name
|
||||
* `#email#` - user's email address
|
||||
* `#user#` - user's email address
|
||||
* `#username#` - portion of user's email address before @
|
||||
* `#domain#` - portion of user's email after after @
|
||||
* `#password#` - password
|
||||
|
||||
If subject is not specified, the following value will be used:
|
||||
* create - `Welcome to #domain#`
|
||||
* update - `Account #user# password has been changed`
|
||||
@@ -434,14 +442,8 @@ If `<NotifyMessageContent>`is not specified, the following value will be used:
|
||||
Start using your new account by signing in at\nhttps://www.google.com/accounts/AccountChooser?Email=#user#&continue=https://workspace.google.com/dashboard\n`
|
||||
* update - `The account password for #givenname# #familyname#, #user# has been changed to: #password#\n`
|
||||
|
||||
In the subject and message, these strings will be replaced with the specified values:
|
||||
* `#givenname#` - first/given name
|
||||
* `#familyname#` - last/family name
|
||||
* `#email#` - user's email address
|
||||
* `#user#` - user's email address
|
||||
* `#username#` - portion of user's email address before @
|
||||
* `#domain#` - portion of user's email after after @
|
||||
* `#password#` - password
|
||||
Unless specified in `<NotifyMessageContent>`, messages are sent as plain text,
|
||||
use `html` or `html true` to indicate that the message is HTML.
|
||||
|
||||
Use `\n` in `message <String>` to indicate a line break; no other special characters are recognized.
|
||||
|
||||
@@ -464,8 +466,6 @@ Use `from <EmailAddress>` to specify an alternate from address.
|
||||
Use `mailbox <EmailAddress>` if `from <EmailAddress>` specifies a group; GAM has to login as a user to be able to send a message.
|
||||
Gam gets no indication as to the status of the message delivery; the from user will get a non-delivery receipt if the message could not be sent to the `notify <EmailAddressList>`.
|
||||
|
||||
By default, messages are sent as plain text, use `html` or `html true` to indicate that the message is HTML.
|
||||
|
||||
## Define schema fields
|
||||
You can set custom schema field values for users; schema fields can be scalar, a single value, or can be multivalued.
|
||||
* https://developers.google.com/admin-sdk/directory/reference/rest/v1/schemas
|
||||
@@ -658,8 +658,7 @@ gam update user <UserItem> [ignorenullpassword] <UserAttribute>*
|
||||
[[notify <EmailAddressList>] [notifyrecoveryemail]
|
||||
[subject <String>]
|
||||
[notifypassword <String>]
|
||||
[from <EmailAaddress>]
|
||||
[mailbox <EmailAddress>]
|
||||
[from <EmailAaddress>] [mailbox <EmailAddress>]
|
||||
[replyto <EmailAddress>]
|
||||
[<NotifyMessageContent>]
|
||||
(replace <Tag> <UserReplacement>)*
|
||||
@@ -680,8 +679,7 @@ gam update users <UserTypeEntity> [ignorenullpassword] <UserAttribute>*
|
||||
[[notify <EmailAddressList>] [notifyrecoveryemail]
|
||||
[subject <String>]
|
||||
[notifypassword <String>]
|
||||
[from <EmailAddress>]
|
||||
[mailbox <EmailAddress>]
|
||||
[from <EmailAddress>] [mailbox <EmailAddress>]
|
||||
[replyto <EmailAaddress>]
|
||||
[<NotifyMessageContent>]
|
||||
(replace <Tag> <UserReplacement>)*
|
||||
@@ -702,8 +700,7 @@ gam <UserTypeEntity> update users [ignorenullpassword] <UserAttribute>*
|
||||
[[notify <EmailAddressList>] [notifyrecoveryemail]
|
||||
[subject <String>]
|
||||
[notifypassword <String>]
|
||||
[from <EmailAaddress>]
|
||||
[mailbox <EmailAddress>]
|
||||
[from <EmailAaddress>] [mailbox <EmailAddress>]
|
||||
[replyto <EmailAddress>]
|
||||
[<NotifyMessageContent>]
|
||||
(replace <Tag> <UserReplacement>)*
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
Print the current version of Gam with details
|
||||
```
|
||||
gam version
|
||||
GAM 7.23.07 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.27.04 - 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.07 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.27.04 - 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.07 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.27.04 - 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.27.04
|
||||
echo $?
|
||||
1
|
||||
```
|
||||
@@ -76,7 +76,7 @@ echo $?
|
||||
Print the current version number without details
|
||||
```
|
||||
gam version simple
|
||||
7.22.00
|
||||
7.27.04
|
||||
```
|
||||
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.27.04 - 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
|
||||
@@ -298,6 +303,9 @@ debug_level
|
||||
If debug_level > 0, turn on API debugging output.
|
||||
Default: 0
|
||||
Signal file: OldGamPath/debug.gam
|
||||
debug_redaction
|
||||
Enable/disable redaction of sensitive data from API debugging output
|
||||
Default: True
|
||||
device_max_results
|
||||
When retrieving lists of ChromeOS devices from API,
|
||||
how many should be retrieved in each API call
|
||||
|
||||
Reference in New Issue
Block a user