mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-08 08:11:37 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fc8c8d718 | ||
|
|
a3e5f7b504 | ||
|
|
4137b3b77b | ||
|
|
ce47c9bc7c | ||
|
|
d302563045 | ||
|
|
c5f4bb18fa | ||
|
|
0fdcab4c4f | ||
|
|
130a245906 | ||
|
|
d8bf368c92 | ||
|
|
e7b238e85e |
@@ -4926,10 +4926,10 @@ gam print schema|schemas [todrive <ToDriveAttribute>*]
|
||||
gam sendemail [recipient|to] <RecipientEntity>
|
||||
[from <EmailAddress>] [mailbox <EmailAddress>] [replyto <EmailAddress>]
|
||||
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||
[subject <String>] [<MessageContent>]
|
||||
[subject <String>] [<MessageContent>] [html [<Boolean>]]
|
||||
(replace <Tag> <String>)*
|
||||
(replaceregex <RESearchPattern> <RESubstitution> <Tag> <String>)*
|
||||
[html [<Boolean>]] (attach <FileName> [charset <Charset>])*
|
||||
(attach <FileName> [charset <Charset>])*
|
||||
(embedimage <FileName> <String>)*
|
||||
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
||||
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||
@@ -4937,10 +4937,10 @@ gam sendemail [recipient|to] <RecipientEntity>
|
||||
gam <UserTypeEntity> sendemail recipient|to <RecipientEntity>
|
||||
[replyto <EmailAddress>]
|
||||
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||
[subject <String>] [<MessageContent>]
|
||||
[subject <String>] [<MessageContent>] [html [<Boolean>]]
|
||||
(replace <Tag> <String>)*
|
||||
(replaceregex <RESearchPattern> <RESubstitution> <Tag> <String>)*
|
||||
[html [<Boolean>]] (attach <FileName> [charset <Charset>])*
|
||||
(attach <FileName> [charset <Charset>])*
|
||||
(embedimage <FileName> <String>)*
|
||||
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
||||
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||
@@ -4948,14 +4948,21 @@ gam <UserTypeEntity> sendemail recipient|to <RecipientEntity>
|
||||
gam <UserTypeEntity> sendemail from <EmailAddress>
|
||||
[replyto <EmailAddress>]
|
||||
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||
[subject <String>] [<MessageContent>]
|
||||
[subject <String>] [<MessageContent>] [html [<Boolean>]]
|
||||
(replace <Tag> <String>)*
|
||||
(replaceregex <RESearchPattern> <RESubstitution> <Tag> <String>)*
|
||||
[html [<Boolean>]] (attach <FileName> [charset <Charset>])*
|
||||
(attach <FileName> [charset <Charset>])*
|
||||
(embedimage <FileName> <String>)*
|
||||
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
||||
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||
[threadid <String>]
|
||||
gam <UserTypeEntity> sendreply
|
||||
(((query <QueryGmail> [querytime<String> <Date>]*) [or|and])+) | (ids <MessageIDEntity>)
|
||||
[replyto <EmailAddress>]
|
||||
[subject <String>] [<MessageContent>] [html [<Boolean>]]
|
||||
(attach <FileName> [charset <CharSet>])*
|
||||
(embedimage <FileName> <String>)*
|
||||
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||
|
||||
# Shared Drives - Administrator
|
||||
|
||||
@@ -8907,17 +8914,17 @@ gam <UserTypeEntity> delete tokens clientid <ClientID>
|
||||
|
||||
gam <UserTypeEntity> print tokens|token [todrive <ToDriveAttribute>*] [clientid <ClientID>]
|
||||
[usertokencounts|(aggregateusersby|orderby clientid|id|appname|displaytext)]
|
||||
[delimiter <Character>]
|
||||
[delimiter <Character>] [gcpdetails]
|
||||
gam <UserTypeEntity> show tokens|token|3lo|oauth [clientid <ClientID>]
|
||||
[usertokencounts|(aggregateusersby|orderby clientid|id|appname|displaytext)]
|
||||
[delimiter <Character>]
|
||||
[delimiter <Character>] [gcpdetails]
|
||||
gam print tokens|token [todrive <ToDriveAttribute>*] [clientid <ClientID>]
|
||||
[usertokencounts|(aggregateusersby|orderby clientid|id|appname|displaytext)]
|
||||
[delimiter <Character>]
|
||||
[delimiter <Character>] [gcpdetails]
|
||||
[<UserTypeEntity>]
|
||||
gam show tokens|token [clientid <ClientID>]
|
||||
[usertokencounts|(aggregateusersby|orderby clientid|id|appname|displaytext)]
|
||||
[delimiter <Character>]
|
||||
[delimiter <Character>] [gcpdetails]
|
||||
[<UserTypeEntity>]
|
||||
|
||||
# Users - YouTube
|
||||
|
||||
@@ -1,3 +1,33 @@
|
||||
7.37.00
|
||||
|
||||
Added new client access scopes used by `gam print tokens`.
|
||||
```
|
||||
[*] 52) Resource Manager API - Organizations readonly
|
||||
[*] 53) Resource Manager API - Projects readonly
|
||||
```
|
||||
|
||||
Added option `gcpdetails` to `gam print tokens` that uses these scopes to get additional project information.
|
||||
|
||||
7.36.03
|
||||
|
||||
Added command to send email replies that causes Gmail to recognize the message
|
||||
in conversation mode for the user sending the reply and the user receiving the reply;
|
||||
GAM supplies the necessary headers and options.
|
||||
```
|
||||
gam <UserTypeEntity> sendreply
|
||||
(((query <QueryGmail> [querytime<String> <Date>]*) [or|and])+) | (ids <MessageIDEntity>)
|
||||
[replyto <EmailAddress>]
|
||||
[subject <String>] [<MessageContent>] [html [<Boolean>]]
|
||||
(attach <FileName> [charset <CharSet>])*
|
||||
(embedimage <FileName> <String>)*
|
||||
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||
|
||||
gam user user@domain.com sendreply query "rfc822MsgId:<CAAMmEdqj43...1OsQ@mail.gmail.com>" textmessage "Thanks for the information"
|
||||
gam user user@domain.com sendreply ids 19cfc3506c02c22b textmessage "Thanks for the information"
|
||||
```
|
||||
|
||||
* See: https://github.com/GAM-team/GAM/wiki/Send-Email#conversation-mode
|
||||
|
||||
7.36.02
|
||||
|
||||
Added option `threadid <String>` to `gam [<UserTypeEntity>] sendemail` that causes Gmail to recognize the message
|
||||
|
||||
@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
|
||||
"""
|
||||
|
||||
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
|
||||
__version__ = '7.36.02'
|
||||
__version__ = '7.37.00'
|
||||
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
# pylint: disable=wrong-import-position
|
||||
@@ -5681,15 +5681,9 @@ def buildGAPIObject(api, credentials=None):
|
||||
httpObj = transportAuthorizedHttp(credentials, http=getHttpObj(cache=GM.Globals[GM.CACHE_DIR]))
|
||||
service = getService(api, httpObj)
|
||||
if not GC.Values[GC.ENABLE_DASA]:
|
||||
try:
|
||||
API_Scopes = set(list(service._rootDesc['auth']['oauth2']['scopes']))
|
||||
except KeyError:
|
||||
if api == API.VAULT:
|
||||
API_Scopes = set(API.VAULT_SCOPES)
|
||||
elif api == API.BUSINESSACCOUNTMANAGEMENT:
|
||||
API_Scopes = {API.BUSINESSACCOUNTMANAGEMENT_SCOPE}
|
||||
else:
|
||||
API_Scopes = set()
|
||||
discovery_scopes = list(service._rootDesc.get('auth', {}).get('oauth2', {}).get('scopes', {}).keys())
|
||||
extra_scopes = API.EXTRA_SCOPES.get(api, [])
|
||||
API_Scopes = set(discovery_scopes + extra_scopes)
|
||||
GM.Globals[GM.CURRENT_CLIENT_API] = api
|
||||
GM.Globals[GM.CURRENT_CLIENT_API_SCOPES] = API_Scopes.intersection(GM.Globals[GM.CREDENTIALS_SCOPES])
|
||||
if api not in API.SCOPELESS_APIS and not GM.Globals[GM.CURRENT_CLIENT_API_SCOPES]:
|
||||
@@ -7347,7 +7341,8 @@ def _addEmbeddedImagesToMessage(message, embeddedImages):
|
||||
# Send an email
|
||||
def send_email(msgSubject, msgBody, msgTo, i=0, count=0, clientAccess=False, msgFrom=None, msgReplyTo=None,
|
||||
html=False, charset=UTF8, attachments=None, embeddedImages=None,
|
||||
msgHeaders=None, ccRecipients=None, bccRecipients=None, mailBox=None, threadId=None):
|
||||
msgHeaders=None, ccRecipients=None, bccRecipients=None, mailBox=None, threadId=None,
|
||||
action=Act.SENDEMAIL):
|
||||
def checkResult(entityType, recipients):
|
||||
if not recipients:
|
||||
return
|
||||
@@ -7403,12 +7398,13 @@ def send_email(msgSubject, msgBody, msgTo, i=0, count=0, clientAccess=False, msg
|
||||
if mailBox is None:
|
||||
mailBox = msgFromAddr
|
||||
_, mailBoxAddr = cleanAddr(mailBox)
|
||||
action = Act.Get()
|
||||
Act.Set(Act.SENDEMAIL)
|
||||
parentAction = Act.Get()
|
||||
Act.Set(action)
|
||||
if not GC.Values[GC.SMTP_HOST]:
|
||||
if not clientAccess:
|
||||
userId, gmail = buildGAPIServiceObject(API.GMAIL, mailBoxAddr)
|
||||
if not gmail:
|
||||
Act.Set(parentAction)
|
||||
return
|
||||
else:
|
||||
userId = mailBoxAddr
|
||||
@@ -7450,7 +7446,7 @@ def send_email(msgSubject, msgBody, msgTo, i=0, count=0, clientAccess=False, msg
|
||||
server.quit()
|
||||
except Exception:
|
||||
pass
|
||||
Act.Set(action)
|
||||
Act.Set(parentAction)
|
||||
|
||||
def addFieldToFieldsList(fieldName, fieldsChoiceMap, fieldsList):
|
||||
fields = fieldsChoiceMap[fieldName.lower()]
|
||||
@@ -10820,14 +10816,14 @@ def getScopesFromUser(scopesList, clientAccess, currentScopes=None):
|
||||
numScopes = len(scopesList)
|
||||
for a_scope in scopesList:
|
||||
oauth2_menu += f"[%%s] %2d) {a_scope['name']}"
|
||||
if a_scope['subscopes']:
|
||||
if a_scope.get('subscopes'):
|
||||
oauth2_menu += f' (supports {" and ".join(a_scope["subscopes"])})'
|
||||
oauth2_menu += '\n'
|
||||
oauth2_menu += '''
|
||||
Select an unselected scope [ ] by entering a number; yields [*]
|
||||
For scopes that support readonly, enter a number and an 'r' to grant read-only access; yields [R]
|
||||
For scopes that support action, enter a number and an 'a' to grant action-only access; yields [A]
|
||||
Clear read-only access [R] or action-only access [A] from a scope by entering a number; yields [*]
|
||||
For scopes that optionally support readonly, enter a number and an 'r' to grant readonly access; yields [R]
|
||||
For scopes that optionally support actiononly, enter a number and an 'a' to grant actiononly access; yields [A]
|
||||
Clear readonly access [R] or actiononly access [A] from a scope by entering a number; yields [*]
|
||||
Unselect a selected scope [*] by entering a number; yields [ ]
|
||||
Select all default scopes by entering an 's'; yields [*] for default scopes, [ ] for others
|
||||
Unselect all scopes by entering a 'u'; yields [ ] for all scopes
|
||||
@@ -10848,15 +10844,16 @@ Continue to authorization by entering a 'c'
|
||||
for a_scope in scopesList:
|
||||
selectedScopes[i] = ' '
|
||||
possibleScope = a_scope['scope']
|
||||
subScopes = a_scope.get('subscopes', [])
|
||||
for currentScope in currentScopes:
|
||||
if currentScope == possibleScope:
|
||||
selectedScopes[i] = '*'
|
||||
break
|
||||
if 'readonly' in a_scope['subscopes']:
|
||||
if 'readonly' in subScopes:
|
||||
if currentScope == possibleScope+'.readonly':
|
||||
selectedScopes[i] = 'R'
|
||||
break
|
||||
if 'action' in a_scope['subscopes']:
|
||||
if 'actiononly' in subScopes:
|
||||
if currentScope == possibleScope+'.action':
|
||||
selectedScopes[i] = 'A'
|
||||
break
|
||||
@@ -10867,13 +10864,14 @@ Continue to authorization by entering a 'c'
|
||||
selectedScopes[i] = ' '
|
||||
api = a_scope['api']
|
||||
possibleScope = a_scope['scope']
|
||||
subScopes = a_scope.get('subscopes', [])
|
||||
if api in currentScopes:
|
||||
if not isinstance(possibleScope, list):
|
||||
for scope in currentScopes[api]:
|
||||
if scope == possibleScope:
|
||||
selectedScopes[i] = '*'
|
||||
break
|
||||
if 'readonly' in a_scope['subscopes']:
|
||||
if 'readonly' in subScopes:
|
||||
if (scope == possibleScope+'.readonly') or (scope == a_scope.get('roscope')):
|
||||
selectedScopes[i] = 'R'
|
||||
break
|
||||
@@ -10914,12 +10912,12 @@ Continue to authorization by entering a 'c'
|
||||
selection = int(selection)
|
||||
if isinstance(selection, int) and selection < numScopes:
|
||||
if mode == 'R':
|
||||
if 'readonly' not in scopesList[selection]['subscopes']:
|
||||
sys.stdout.write(f'{ERROR_PREFIX}Scope {selection} does not support read-only mode!\n')
|
||||
if 'readonly' not in scopesList[selection].get('subscopes',[]):
|
||||
sys.stdout.write(f'{ERROR_PREFIX}Scope {selection} does not support readonly mode!\n')
|
||||
continue
|
||||
elif mode == 'A':
|
||||
if 'action' not in scopesList[selection]['subscopes']:
|
||||
sys.stdout.write(f'{ERROR_PREFIX}Scope {selection} does not support action-only mode!\n')
|
||||
if 'actiononly' not in scopesList[selection].get('subscopes', []):
|
||||
sys.stdout.write(f'{ERROR_PREFIX}Scope {selection} does not support actiononly mode!\n')
|
||||
continue
|
||||
elif selectedScopes[selection] != '*':
|
||||
mode = '*'
|
||||
@@ -11361,9 +11359,10 @@ def doOAuthCreate():
|
||||
if uscope in {'openid', 'email', API.USERINFO_EMAIL_SCOPE, 'profile', API.USERINFO_PROFILE_SCOPE}:
|
||||
continue
|
||||
for scope in scopesList:
|
||||
subScopes = scope.get('subscopes', [])
|
||||
if ((uscope == scope['scope']) or
|
||||
(uscope.endswith('.action') and 'action' in scope['subscopes']) or
|
||||
(uscope.endswith('.readonly') and 'readonly' in scope['subscopes'])):
|
||||
(uscope.endswith('.action') and 'actiononly' in subScopes) or
|
||||
(uscope.endswith('.readonly') and 'readonly' in subScopes)):
|
||||
scopes.append(uscope)
|
||||
break
|
||||
else:
|
||||
@@ -11996,12 +11995,13 @@ def getGCPOrg(crm, login_hint, login_domain):
|
||||
try:
|
||||
getorg = callGAPI(crm.organizations(), 'search',
|
||||
throwReasons=[GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED],
|
||||
query=f'domain:{login_domain}')
|
||||
query=f'domain:{login_domain}',
|
||||
pageSize=1, fields='organizations/name')
|
||||
except (GAPI.invalidArgument, GAPI.permissionDenied) as e:
|
||||
entityActionFailedExit([Ent.USER, login_hint, Ent.DOMAIN, login_domain], str(e))
|
||||
try:
|
||||
organization = getorg['organizations'][0]['name']
|
||||
sys.stdout.write(Msg.YOUR_ORGANIZATION_NAME_IS.format(organization))
|
||||
# sys.stdout.write(Msg.YOUR_ORGANIZATION_NAME_IS.format(organization))
|
||||
return organization
|
||||
except (KeyError, IndexError):
|
||||
systemErrorExit(3, Msg.YOU_HAVE_NO_RIGHTS_TO_CREATE_PROJECTS_AND_YOU_ARE_NOT_A_SUPER_ADMIN)
|
||||
@@ -15391,32 +15391,35 @@ def getRecipients():
|
||||
return [normalizeEmailAddressOrUID(emailAddress, noUid=True, noLower=True) for emailAddress in recipients]
|
||||
return getNormalizedEmailAddressEntity(shlexSplit=True, noLower=True)
|
||||
|
||||
# gam sendemail [recipient|to] <RecipientEntity> [from <EmailAddress>] [mailbox <EmailAddress>] [replyto <EmailAddress>]
|
||||
# gam sendemail [recipient|to] <RecipientEntity>
|
||||
# [from <EmailAddress>] [mailbox <EmailAddress>] [replyto <EmailAddress>]
|
||||
# [cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||
# [subject <String>] [<MessageContent>]
|
||||
# [subject <String>] [<MessageContent>] [html [<Boolean>]]
|
||||
# (replace <Tag> <String>)*
|
||||
# (replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
|
||||
# [html [<Boolean>]] (attach <FileName> [charset <CharSet>])*
|
||||
# (attach <FileName> [charset <CharSet>])*
|
||||
# (embedimage <FileName> <String>)*
|
||||
# [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
||||
# (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||
# [threadid <String>]
|
||||
# gam <UserTypeEntity> sendemail recipient|to <RecipientEntity> [replyto <EmailAddress>]
|
||||
# gam <UserTypeEntity> sendemail recipient|to <RecipientEntity>
|
||||
# [replyto <EmailAddress>]
|
||||
# [cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||
# [subject <String>] [<MessageContent>]
|
||||
# [subject <String>] [<MessageContent>] [html [<Boolean>]]
|
||||
# (replace <Tag> <String>)*
|
||||
# (replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
|
||||
# [html [<Boolean>]] (attach <FileName> [charset <CharSet>])*
|
||||
# (attach <FileName> [charset <CharSet>])*
|
||||
# (embedimage <FileName> <String>)*
|
||||
# [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
||||
# (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||
# [threadid <String>]
|
||||
# gam <UserTypeEntity> sendemail from <EmailAddress> [replyto <EmailAddress>]
|
||||
# gam <UserTypeEntity> sendemail from <EmailAddress>
|
||||
# [replyto <EmailAddress>]
|
||||
# [cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||
# [subject <String>] [<MessageContent>]
|
||||
# [subject <String>] [<MessageContent> ][html [<Boolean>]]
|
||||
# (replace <Tag> <String>)*
|
||||
# (replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
|
||||
# [html [<Boolean>]] (attach <FileName> [charset <CharSet>])*
|
||||
# (attach <FileName> [charset <CharSet>])*
|
||||
# (embedimage <FileName> <String>)*
|
||||
# [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
||||
# (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||
@@ -15537,6 +15540,125 @@ def doSendEmail(users=None):
|
||||
attachments=attachments, embeddedImages=embeddedImages, msgHeaders=msgHeaders, mailBox=mailBox, threadId=threadId)
|
||||
Ind.Decrement()
|
||||
|
||||
# gam <UserTypeEntity> sendreply
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) [or|and])+) | (ids <MessageIDEntity>)
|
||||
# [replyto <EmailAddress>]
|
||||
# [subject <String>] [<MessageContent>] [html [<Boolean>]]
|
||||
# (attach <FileName> [charset <CharSet>])*
|
||||
# (embedimage <FileName> <String>)*
|
||||
# (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||
def doSendReply(users):
|
||||
def _getHeaderValue(name):
|
||||
for header in messageInfo['payload']['headers']:
|
||||
if name == header['name']:
|
||||
return _decodeHeader(header['value'])
|
||||
return ''
|
||||
|
||||
notify = {'subject': '', 'message': '', 'html': False, 'charset': UTF8}
|
||||
query = ''
|
||||
queryTimes = {}
|
||||
messageIds = []
|
||||
msgHeaders = {}
|
||||
msgReplyTo = None
|
||||
attachments = []
|
||||
embeddedImages = []
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'query':
|
||||
selectLocation = Cmd.Location()
|
||||
if query:
|
||||
query += ' '
|
||||
query += f'({getString(Cmd.OB_QUERY)})'
|
||||
elif myarg.startswith('querytime'):
|
||||
queryTimes[myarg] = getDateOrDeltaFromNow().replace('-', '/')
|
||||
elif myarg in {'or', 'and'}:
|
||||
if query:
|
||||
query += f' {myarg.upper()}'
|
||||
elif myarg == 'ids':
|
||||
selectLocation = Cmd.Location()
|
||||
messageIds = getEntityList(Cmd.OB_MESSAGE_ID)
|
||||
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 == 'replyto':
|
||||
msgReplyTo = getString(Cmd.OB_EMAIL_ADDRESS)
|
||||
elif myarg == 'html':
|
||||
notify['html'] = getBoolean()
|
||||
elif myarg == 'attach':
|
||||
attachments.append((getFilename(), getCharSet()))
|
||||
elif myarg == 'embedimage':
|
||||
embeddedImages.append((getFilename(), getString(Cmd.OB_STRING)))
|
||||
elif myarg in SMTP_HEADERS_MAP:
|
||||
if myarg in SMTP_DATE_HEADERS:
|
||||
msgDate, _, _ = getTimeOrDeltaFromNow(True)
|
||||
msgHeaders[SMTP_HEADERS_MAP[myarg]] = formatdate(time.mktime(msgDate.timetuple()) + msgDate.microsecond/1E6, True)
|
||||
else:
|
||||
msgHeaders[SMTP_HEADERS_MAP[myarg]] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'header':
|
||||
header = getString(Cmd.OB_STRING, minLen=1)
|
||||
msgHeaders[SMTP_HEADERS_MAP.get(header.lower(), header)] = getString(Cmd.OB_STRING)
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
if query and messageIds:
|
||||
Cmd.SetLocation(selectLocation-1)
|
||||
usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format('query <QueryGmail>', 'ids <MessageIDEntity>'))
|
||||
notify['message'] = notify['message'].replace('\r', '').replace('\\n', '\n')
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
user, gmail = buildGAPIServiceObject(API.GMAIL, user, i, count)
|
||||
if not gmail:
|
||||
continue
|
||||
try:
|
||||
if query:
|
||||
printGettingAllEntityItemsForWhom(Ent.MESSAGE, user, i, count, query=query)
|
||||
listResult = callGAPIpages(gmail.users().messages(), 'list', 'messages',
|
||||
pageMessage=getPageMessageForWhom(),
|
||||
throwReasons=GAPI.GMAIL_THROW_REASONS+GAPI.GMAIL_LIST_THROW_REASONS,
|
||||
userId='me', q=query, fields='nextPageToken,messages(id)',
|
||||
maxResults=GC.Values[GC.MESSAGE_MAX_RESULTS])
|
||||
messageIds = [message['id'] for message in listResult]
|
||||
except (GAPI.failedPrecondition, GAPI.permissionDenied, GAPI.invalid, GAPI.invalidArgument) as e:
|
||||
entityActionFailedWarning([Ent.USER, user], str(e), i, count)
|
||||
continue
|
||||
except GAPI.serviceNotAvailable:
|
||||
userGmailServiceNotEnabledWarning(user, i, count)
|
||||
continue
|
||||
jcount = len(messageIds)
|
||||
if jcount == 0:
|
||||
entityNumEntitiesActionNotPerformedWarning([Ent.USER, user], Ent.MESSAGE, jcount, Msg.NO_ENTITIES_MATCHED.format(Ent.Plural(Ent.MESSAGE)), i, count)
|
||||
setSysExitRC(NO_ENTITIES_FOUND_RC)
|
||||
continue
|
||||
entityPerformActionModifierNumItems([Ent.USER, user], Act.MODIFIER_TO, jcount, Ent.RECIPIENT, i, count)
|
||||
Ind.Increment()
|
||||
j = 0
|
||||
for messageId in messageIds:
|
||||
j += 1
|
||||
try:
|
||||
messageInfo = callGAPI(gmail.users().messages(), 'get',
|
||||
throwReasons=GAPI.GMAIL_THROW_REASONS+[GAPI.NOT_FOUND, GAPI.INVALID_MESSAGE_ID],
|
||||
userId='me', id=messageId, fields='id,threadId,payload(headers)')
|
||||
threadId = messageInfo['threadId']
|
||||
msgHeaders['References'] = msgHeaders['In-Reply-To'] = _getHeaderValue('Message-ID')
|
||||
msgSubject = notify['subject'] if notify['subject'] else f"Re: {_getHeaderValue('Subject')}"
|
||||
recipient = _getHeaderValue('From')
|
||||
send_email(msgSubject, notify['message'], recipient, j, jcount,
|
||||
msgFrom=user, msgReplyTo=msgReplyTo, html=notify['html'], charset=notify['charset'],
|
||||
attachments=attachments, embeddedImages=embeddedImages, msgHeaders=msgHeaders, threadId=threadId,
|
||||
action=Act.SENDREPLY)
|
||||
except GAPI.notFound:
|
||||
entityActionFailedWarning([Ent.USER, user, Ent.MESSAGE, messageId], Msg.DOES_NOT_EXIST, j, jcount)
|
||||
except GAPI.invalidMessageId:
|
||||
entityActionFailedWarning([Ent.USER, user, Ent.MESSAGE, messageId], Msg.INVALID_MESSAGE_ID, j, jcount)
|
||||
except (GAPI.failedPrecondition, GAPI.permissionDenied, GAPI.invalid, GAPI.invalidArgument) as e:
|
||||
entityActionFailedWarning([Ent.USER, user], str(e), i, count)
|
||||
break
|
||||
except GAPI.serviceNotAvailable:
|
||||
userGmailServiceNotEnabledWarning(user, i, count)
|
||||
break
|
||||
Ind.Decrement()
|
||||
|
||||
ADDRESS_FIELDS_PRINT_ORDER = ['contactName', 'organizationName', 'addressLine1', 'addressLine2', 'addressLine3', 'locality', 'region', 'postalCode', 'countryCode']
|
||||
|
||||
def _showCustomerAddressPhoneNumber(customerInfo):
|
||||
@@ -72321,6 +72443,29 @@ def _printShowTokens(entityType, users):
|
||||
Ind.Decrement()
|
||||
Ind.Decrement()
|
||||
|
||||
def project_from_client_id(client_id):
|
||||
match = re.search(r'^\d+', client_id)
|
||||
return match.group()
|
||||
|
||||
def get_gcp_info(results):
|
||||
for result in results:
|
||||
result['project'] = project_from_client_id(result.get('clientId'))
|
||||
if result['project'] in internal_projects:
|
||||
result['internal'] = True
|
||||
continue
|
||||
try:
|
||||
results = callGAPI(crm1.projects(), 'getAncestry',
|
||||
throwReasons=[GAPI.PERMISSION_DENIED],
|
||||
projectId=result['project'])
|
||||
for ancestor in results.get('ancestor', []):
|
||||
if ancestor.get('resourceId', {}).get('type') == 'organization' and ancestor.get('resourceId', {}).get('id') == org_id:
|
||||
result['internal'] = True
|
||||
internal_projects.add(result['project'])
|
||||
except GAPI.permissionDenied:
|
||||
# we don't have permission to get project. This might be an external project
|
||||
# or it might be an internal project we don't have rights to get.
|
||||
pass
|
||||
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
csvPF = CSVPrintFile() if Act.csvFormat() else None
|
||||
clientId = None
|
||||
@@ -72329,6 +72474,8 @@ def _printShowTokens(entityType, users):
|
||||
delimiter = GC.Values[GC.CSV_OUTPUT_FIELD_DELIMITER]
|
||||
aggregateTokensById = {}
|
||||
tokenNameIdMap = None
|
||||
getGCPDetails = False
|
||||
extra_titles = []
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if csvPF and myarg == 'todrive':
|
||||
@@ -72345,6 +72492,9 @@ def _printShowTokens(entityType, users):
|
||||
aggregateUsersBy = 'user'
|
||||
elif myarg == 'delimiter':
|
||||
delimiter = getCharacter()
|
||||
elif myarg == 'gcpdetails':
|
||||
getGCPDetails = True
|
||||
extra_titles = ['project', 'internal']
|
||||
elif not entityType:
|
||||
Cmd.Backup()
|
||||
entityType, users = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS)
|
||||
@@ -72354,9 +72504,9 @@ def _printShowTokens(entityType, users):
|
||||
users = getItemsToModify(Cmd.ENTITY_ALL_USERS_NS, None)
|
||||
if csvPF:
|
||||
if not aggregateUsersBy:
|
||||
csvPF.SetTitles(['user']+TOKENS_FIELDS_TITLES)
|
||||
csvPF.SetTitles(['user'] + TOKENS_FIELDS_TITLES + extra_titles)
|
||||
elif aggregateUsersBy != 'user':
|
||||
csvPF.SetTitles(TOKENS_AGGREGATE_FIELDS_TITLES)
|
||||
csvPF.SetTitles(TOKENS_AGGREGATE_FIELDS_TITLES + extra_titles)
|
||||
else:
|
||||
csvPF.SetTitles(['user', 'tokenCount'])
|
||||
else:
|
||||
@@ -72364,6 +72514,13 @@ def _printShowTokens(entityType, users):
|
||||
tokenTitle = TOKENS_TITLE_MAP[orderBy]
|
||||
else:
|
||||
tokenTitle = TOKENS_TITLE_MAP[aggregateUsersBy]
|
||||
if getGCPDetails:
|
||||
internal_projects = set() # cache
|
||||
crm = buildGAPIObject('cloudresourcemanager')
|
||||
crm1 = buildGAPIObject('cloudresourcemanagerv1')
|
||||
admin_email = _getAdminEmail()
|
||||
admin_domain = getEmailAddressDomain(admin_email)
|
||||
org_id = getGCPOrg(crm, admin_email, admin_domain).split('/')[1]
|
||||
fields = ','.join(TOKENS_FIELDS_TITLES)
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
@@ -72385,6 +72542,8 @@ def _printShowTokens(entityType, users):
|
||||
GAPI.DOMAIN_CANNOT_USE_APIS, GAPI.BAD_REQUEST,
|
||||
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||
userKey=user, fields=f'items({fields})')
|
||||
if getGCPDetails:
|
||||
get_gcp_info(results)
|
||||
if not aggregateUsersBy:
|
||||
if not csvPF:
|
||||
jcount = len(results)
|
||||
@@ -80845,6 +81004,7 @@ USER_COMMANDS = {
|
||||
'profile': (Act.SET, setProfile),
|
||||
'sendas': (Act.ADD, createUpdateSendAs),
|
||||
'sendemail': (Act.SENDEMAIL, doSendEmail),
|
||||
'sendreply': (Act.SENDREPLY, doSendReply),
|
||||
'signature': (Act.SET, setSignature),
|
||||
'signout': (Act.SIGNOUT, signoutTurnoff2SVUsers),
|
||||
'turnoff2sv': (Act.TURNOFF2SV, signoutTurnoff2SVUsers),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2024 Ross Scroggs All Rights Reserved.
|
||||
# Copyright (C) 2026 Ross Scroggs All Rights Reserved.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
@@ -107,6 +107,7 @@ class GamAction():
|
||||
SAVE = 'save'
|
||||
SEND = 'send'
|
||||
SENDEMAIL = 'snem'
|
||||
SENDREPLY = 'sner'
|
||||
SET = 'set '
|
||||
SETUP = 'setu'
|
||||
SHARE = 'shar'
|
||||
@@ -225,6 +226,7 @@ class GamAction():
|
||||
SAVE: ['Saved', 'Save'],
|
||||
SEND: ['Sent', 'Send'],
|
||||
SENDEMAIL: ['Email Sent', 'Send Email'],
|
||||
SENDREPLY: ['Reply Sent', 'Send Reply'],
|
||||
SET: ['Set', 'Set'],
|
||||
SETUP: ['Set Up', 'Set Up'],
|
||||
SHARE: ['Shared', 'Share'],
|
||||
|
||||
@@ -54,6 +54,7 @@ CLOUDIDENTITY_POLICY = 'cloudidentitypolicy'
|
||||
CLOUDIDENTITY_POLICY_BETA = 'cloudidentitypolicybeta'
|
||||
CLOUDIDENTITY_USERINVITATIONS = 'cloudidentityuserinvitations'
|
||||
CLOUDRESOURCEMANAGER = 'cloudresourcemanager'
|
||||
CLOUDRESOURCEMANAGERV1 = 'cloudresourcemanagerv1'
|
||||
CONTACTS = 'contacts'
|
||||
CONTACTDELEGATION = 'contactdelegation'
|
||||
DATATRANSFER = 'datatransfer'
|
||||
@@ -103,7 +104,6 @@ TASKS = 'tasks'
|
||||
VAULT = 'vault'
|
||||
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'
|
||||
@@ -119,7 +119,6 @@ STORAGE_READONLY_SCOPE = 'https://www.googleapis.com/auth/devstorage.read_only'
|
||||
STORAGE_READWRITE_SCOPE = 'https://www.googleapis.com/auth/devstorage.read_write'
|
||||
USERINFO_EMAIL_SCOPE = 'https://www.googleapis.com/auth/userinfo.email' # email
|
||||
USERINFO_PROFILE_SCOPE = 'https://www.googleapis.com/auth/userinfo.profile' # profile
|
||||
VAULT_SCOPES = ['https://www.googleapis.com/auth/ediscovery', 'https://www.googleapis.com/auth/ediscovery.readonly']
|
||||
REQUIRED_SCOPES = [USERINFO_EMAIL_SCOPE, USERINFO_PROFILE_SCOPE]
|
||||
REQUIRED_SCOPES_SET = set(REQUIRED_SCOPES)
|
||||
NUM_CLIENT_SCOPES_ERROR_LIMIT = 48
|
||||
@@ -138,6 +137,21 @@ SCOPELESS_APIS = {
|
||||
SERVICEACCOUNTLOOKUP,
|
||||
}
|
||||
#
|
||||
|
||||
# Scopes not in the discovery doc that are still valid for the API.
|
||||
EXTRA_SCOPES = {
|
||||
BUSINESSACCOUNTMANAGEMENT: ['https://www.googleapis.com/auth/business.manage'],
|
||||
CLOUDRESOURCEMANAGER: ['https://www.googleapis.com/auth/cloudplatformfolders',
|
||||
'https://www.googleapis.com/auth/cloudplatformfolders.readonly',
|
||||
'https://www.googleapis.com/auth/cloudplatformprojects',
|
||||
'https://www.googleapis.com/auth/cloudplatformprojects.readonly',
|
||||
'https://www.googleapis.com/auth/cloudplatformorganizations',
|
||||
'https://www.googleapis.com/auth/cloudplatformorganizations.readonly',
|
||||
],
|
||||
VAULT: ['https://www.googleapis.com/auth/ediscovery', 'https://www.googleapis.com/auth/ediscovery.readonly'],
|
||||
}
|
||||
EXTRA_SCOPES[CLOUDRESOURCEMANAGERV1] = EXTRA_SCOPES[CLOUDRESOURCEMANAGER]
|
||||
|
||||
APIS_NEEDING_ACCESS_TOKEN = {
|
||||
CBCM: ['https://www.googleapis.com/auth/admin.directory.device.chromebrowsers']
|
||||
}
|
||||
@@ -250,7 +264,8 @@ _INFO = {
|
||||
CLOUDIDENTITY_POLICY: {'name': 'Cloud Identity API - Policy', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_POLICY_BETA: {'name': 'Cloud Identity API - Policy Beta', 'version': 'v1beta1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_USERINVITATIONS: {'name': 'Cloud Identity API - User Invitations', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDRESOURCEMANAGER: {'name': 'Cloud Resource Manager API v3', 'version': 'v3', 'v2discovery': True},
|
||||
CLOUDRESOURCEMANAGER: {'name': 'Resource Manager API v3', 'version': 'v3', 'v2discovery': True},
|
||||
CLOUDRESOURCEMANAGERV1: {'name': 'Resource Manager API v1', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudresourcemanager'},
|
||||
CONTACTS: {'name': 'Contacts API', 'version': 'v3', 'v2discovery': False},
|
||||
CONTACTDELEGATION: {'name': 'Contact Delegation API', 'version': 'v1', 'v2discovery': True, 'localjson': True},
|
||||
DATATRANSFER: {'name': 'Data Transfer API', 'version': 'datatransfer_v1', 'v2discovery': True, 'mappedAPI': 'admin'},
|
||||
@@ -305,9 +320,8 @@ READONLY = ['readonly',]
|
||||
_CLIENT_SCOPES = [
|
||||
{'name': 'Business Account Management API',
|
||||
'api': BUSINESSACCOUNTMANAGEMENT,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': BUSINESSACCOUNTMANAGEMENT_SCOPE},
|
||||
'scope': EXTRA_SCOPES[BUSINESSACCOUNTMANAGEMENT]},
|
||||
{'name': 'Calendar API',
|
||||
'api': CALENDAR,
|
||||
'subscopes': READONLY,
|
||||
@@ -316,21 +330,18 @@ _CLIENT_SCOPES = [
|
||||
'api': CBCM,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.device.chromebrowsers'},
|
||||
{'name': 'Chrome Management API - read only',
|
||||
{'name': 'Chrome Management API - readonly',
|
||||
'api': CHROMEMANAGEMENT,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/chrome.management.reports.readonly'},
|
||||
{'name': 'Chrome Management API - AppDetails read only',
|
||||
{'name': 'Chrome Management API - AppDetails readonly',
|
||||
'api': CHROMEMANAGEMENT_APPDETAILS,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/chrome.management.appdetails.readonly'},
|
||||
{'name': 'Chrome Management API - Profiles',
|
||||
'api': CHROMEMANAGEMENT_CHROMEPROFILES,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/chrome.management.profiles'},
|
||||
{'name': 'Chrome Management API - Telemetry read only',
|
||||
{'name': 'Chrome Management API - Telemetry readonly',
|
||||
'api': CHROMEMANAGEMENT_TELEMETRY,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/chrome.management.telemetry.readonly'},
|
||||
{'name': 'Chrome Policy API',
|
||||
'api': CHROMEPOLICY,
|
||||
@@ -342,7 +353,6 @@ _CLIENT_SCOPES = [
|
||||
'scope': 'https://www.googleapis.com/auth/admin.chrome.printers'},
|
||||
{'name': 'Chrome Version History API',
|
||||
'api': CHROMEVERSIONHISTORY,
|
||||
'subscopes': [],
|
||||
'scope': ''},
|
||||
{'name': 'Classroom API - Courses',
|
||||
'api': CLASSROOM,
|
||||
@@ -370,11 +380,9 @@ _CLIENT_SCOPES = [
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.guardianlinks.students'},
|
||||
{'name': 'Classroom API - Profile Emails',
|
||||
'api': CLASSROOM,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.profile.emails'},
|
||||
{'name': 'Classroom API - Profile Photos',
|
||||
'api': CLASSROOM,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.profile.photos'},
|
||||
{'name': 'Classroom API - Rosters',
|
||||
'api': CLASSROOM,
|
||||
@@ -404,7 +412,6 @@ _CLIENT_SCOPES = [
|
||||
'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'},
|
||||
{'name': 'Cloud Identity API - Policy Beta',
|
||||
'api': CLOUDIDENTITY_POLICY_BETA,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'},
|
||||
{'name': 'Cloud Identity API - User Invitations',
|
||||
@@ -413,17 +420,14 @@ _CLIENT_SCOPES = [
|
||||
'scope': 'https://www.googleapis.com/auth/cloud-identity.userinvitations'},
|
||||
{'name': 'Cloud Storage API (Read Only, Vault/Takeout Download, Cloud Storage)',
|
||||
'api': STORAGEREAD,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': STORAGE_READONLY_SCOPE},
|
||||
{'name': 'Cloud Storage API (Read/Write, Vault/Takeout Copy/Download, Cloud Storage)',
|
||||
'api': STORAGEWRITE,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': STORAGE_READWRITE_SCOPE},
|
||||
{'name': 'Contacts API - Domain Shared Contacts',
|
||||
'api': CONTACTS,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.google.com/m8/feeds'},
|
||||
{'name': 'Contact Delegation API',
|
||||
'api': CONTACTDELEGATION,
|
||||
@@ -451,7 +455,7 @@ _CLIENT_SCOPES = [
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.group'},
|
||||
{'name': 'Directory API - Mobile Devices Directory',
|
||||
'api': DIRECTORY,
|
||||
'subscopes': ['readonly', 'action'],
|
||||
'subscopes': ['readonly', 'actiononly'],
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.device.mobile'},
|
||||
{'name': 'Directory API - Organizational Units',
|
||||
'api': DIRECTORY,
|
||||
@@ -471,7 +475,6 @@ _CLIENT_SCOPES = [
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.userschema'},
|
||||
{'name': 'Directory API - User Security',
|
||||
'api': DIRECTORY,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.user.security'},
|
||||
{'name': 'Directory API - Users',
|
||||
'api': DIRECTORY,
|
||||
@@ -479,24 +482,19 @@ _CLIENT_SCOPES = [
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.user'},
|
||||
{'name': 'Email Audit API',
|
||||
'api': EMAIL_AUDIT,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': 'https://apps-apis.google.com/a/feeds/compliance/audit/'},
|
||||
{'name': 'Groups Migration API',
|
||||
'api': GROUPSMIGRATION,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/apps.groups.migration'},
|
||||
{'name': 'Groups Settings API',
|
||||
'api': GROUPSSETTINGS,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/apps.groups.settings'},
|
||||
{'name': 'License Manager API',
|
||||
'api': LICENSING,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/apps.licensing'},
|
||||
{'name': 'People Directory API - read only',
|
||||
{'name': 'People Directory API - readonly',
|
||||
'api': PEOPLE_DIRECTORY,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/directory.readonly'},
|
||||
{'name': 'People API',
|
||||
'api': PEOPLE,
|
||||
@@ -504,29 +502,31 @@ _CLIENT_SCOPES = [
|
||||
'scope': PEOPLE_SCOPE},
|
||||
{'name': 'Pub / Sub API',
|
||||
'api': PUBSUB,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/pubsub'},
|
||||
{'name': 'Reports API - Audit Reports',
|
||||
{'name': 'Reports API - Audit Reports readonly',
|
||||
'api': REPORTS,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/admin.reports.audit.readonly'},
|
||||
{'name': 'Reports API - Usage Reports',
|
||||
{'name': 'Reports API - Usage Reports readonly',
|
||||
'api': REPORTS,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/admin.reports.usage.readonly'},
|
||||
{'name': 'Reseller API',
|
||||
'api': RESELLER,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/apps.order'},
|
||||
{'name': 'Resource Manager API - Organizations readonly',
|
||||
'api': CLOUDRESOURCEMANAGER,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/cloudplatformorganizations.readonly'},
|
||||
{'name': 'Resource Manager API - Projects readonly',
|
||||
'api': CLOUDRESOURCEMANAGER,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/cloudplatformprojects.readonly'},
|
||||
{'name': 'Service Account Lookup pseudo-API',
|
||||
'api': SERVICEACCOUNTLOOKUP,
|
||||
'subscopes': [],
|
||||
'scope': ''},
|
||||
{'name': 'Site Verification API',
|
||||
'api': SITEVERIFICATION,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/siteverification'},
|
||||
{'name': 'Vault API',
|
||||
@@ -538,30 +538,24 @@ _CLIENT_SCOPES = [
|
||||
_COMMANDDATA_CLIENT_SCOPES = [
|
||||
{'name': 'Drive API - commanddata_clientaccess',
|
||||
'api': DRIVE3,
|
||||
'subscopes': [],
|
||||
'scope': DRIVE_READONLY_SCOPE},
|
||||
{'name': 'Sheets API - commanddata_clientaccess',
|
||||
{'name': 'Sheets API - commanddata_clientaccess readonly',
|
||||
'api': SHEETS,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/spreadsheets.readonly'},
|
||||
]
|
||||
|
||||
_TODRIVE_CLIENT_SCOPES = [
|
||||
{'name': 'Drive API - todrive_clientaccess',
|
||||
'api': DRIVE3,
|
||||
'subscopes': [],
|
||||
'scope': DRIVE_SCOPE},
|
||||
{'name': 'Drive File API - todrive_clientaccess',
|
||||
'api': DRIVE3,
|
||||
'subscopes': [],
|
||||
'scope': DRIVE_FILE_SCOPE},
|
||||
{'name': 'Gmail API - todrive_clientaccess',
|
||||
'api': GMAIL,
|
||||
'subscopes': [],
|
||||
'scope': GMAIL_SEND_SCOPE},
|
||||
{'name': 'Sheets API - todrive_clientaccess',
|
||||
'api': SHEETS,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/spreadsheets'},
|
||||
]
|
||||
|
||||
@@ -570,11 +564,9 @@ OAUTH2SA_SCOPES = 'us_scopes'
|
||||
_SVCACCT_SCOPES = [
|
||||
{'name': 'AlertCenter API',
|
||||
'api': ALERTCENTER,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/apps.alerts'},
|
||||
{'name': 'Analytics Admin API - read only',
|
||||
{'name': 'Analytics Admin API - readonly',
|
||||
'api': ANALYTICS_ADMIN,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/analytics.readonly'},
|
||||
{'name': 'Calendar API',
|
||||
'api': CALENDAR,
|
||||
@@ -611,11 +603,9 @@ _SVCACCT_SCOPES = [
|
||||
'scope': 'https://www.googleapis.com/auth/chat.admin.spaces'},
|
||||
{'name': 'Chat API - Spaces Delete',
|
||||
'api': CHAT_SPACES_DELETE,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/chat.delete'},
|
||||
{'name': 'Chat API - Spaces Delete Admin',
|
||||
'api': CHAT_SPACES_DELETE_ADMIN,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/chat.admin.delete'},
|
||||
{'name': 'Classroom API - Course Announcements',
|
||||
'api': CLASSROOM,
|
||||
@@ -635,11 +625,9 @@ _SVCACCT_SCOPES = [
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.coursework.students'},
|
||||
{'name': 'Classroom API - Profile Emails',
|
||||
'api': CLASSROOM,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.profile.emails'},
|
||||
{'name': 'Classroom API - Profile Photos',
|
||||
'api': CLASSROOM,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/classroom.profile.photos'},
|
||||
{'name': 'Classroom API - Rosters',
|
||||
'api': CLASSROOM,
|
||||
@@ -656,7 +644,6 @@ _SVCACCT_SCOPES = [
|
||||
# 'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'},
|
||||
# {'name': 'Cloud Identity API - Policy Beta',
|
||||
# 'api': CLOUDIDENTITY_POLICY_BETA,
|
||||
# 'subscopes': [],
|
||||
# 'offByDefault': True,
|
||||
# 'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'},
|
||||
# {'name': 'Cloud Identity User Invitations API',
|
||||
@@ -665,7 +652,6 @@ _SVCACCT_SCOPES = [
|
||||
# 'scope': 'https://www.googleapis.com/auth/cloud-identity'},
|
||||
# {'name': 'Contacts API - Users',
|
||||
# 'api': CONTACTS,
|
||||
# 'subscopes': [],
|
||||
# 'scope': 'https://www.google.com/m8/feeds'},
|
||||
{'name': 'Drive API',
|
||||
'api': DRIVE3,
|
||||
@@ -673,7 +659,6 @@ _SVCACCT_SCOPES = [
|
||||
'scope': DRIVE_SCOPE},
|
||||
{'name': 'Drive Activity API v2 - must pair with Drive API',
|
||||
'api': DRIVEACTIVITY,
|
||||
'subscopes': [],
|
||||
'scope': [DRIVE_READONLY_SCOPE,
|
||||
'https://www.googleapis.com/auth/drive.activity']},
|
||||
{'name': 'Drive Labels API - Admin',
|
||||
@@ -690,30 +675,24 @@ _SVCACCT_SCOPES = [
|
||||
'scope': 'https://www.googleapis.com/auth/documents'},
|
||||
{'name': 'Forms API - must pair with Drive API',
|
||||
'api': FORMS,
|
||||
'subscopes': [],
|
||||
'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': [],
|
||||
'scope': 'https://mail.google.com/'},
|
||||
{'name': 'Gmail API - Full Access (Labels, Messages) except delete message',
|
||||
'api': GMAIL,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/gmail.modify'},
|
||||
{'name': 'Gmail API - Basic Settings (Filters, IMAP, Language, POP, Vacation) - read/write, Sharing Settings (Delegates, Forwarding, SendAs) - read',
|
||||
'api': GMAIL,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/gmail.settings.basic'},
|
||||
{'name': 'Gmail API - Sharing Settings (Delegates, Forwarding, SendAs) - write',
|
||||
'api': GMAIL,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/gmail.settings.sharing'},
|
||||
# {'name': 'Identity and Access Management API',
|
||||
# 'api': IAM,
|
||||
# 'offByDefault': True,
|
||||
# 'subscopes': [],
|
||||
# 'scope': CLOUD_PLATFORM_SCOPE},
|
||||
{'name': 'Keep API',
|
||||
'api': KEEP,
|
||||
@@ -725,32 +704,26 @@ _SVCACCT_SCOPES = [
|
||||
'scope': 'https://www.googleapis.com/auth/datastudio'},
|
||||
{'name': 'Meet API - Manage/Display Meeting Spaces',
|
||||
'api': MEET_SPACES,
|
||||
'subscopes': [],
|
||||
'scope': ['https://www.googleapis.com/auth/meetings.space.created',
|
||||
'https://www.googleapis.com/auth/meetings.space.settings']},
|
||||
{'name': 'Meet API - Read Meeting Spaces metadata',
|
||||
{'name': 'Meet API - Read Meeting Spaces metadata readonly',
|
||||
'api': MEET_READONLY,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/meetings.space.readonly'},
|
||||
{'name': 'OAuth2 API',
|
||||
'api': OAUTH2,
|
||||
'subscopes': [],
|
||||
'scope': USERINFO_PROFILE_SCOPE},
|
||||
{'name': 'People API',
|
||||
'api': PEOPLE,
|
||||
'subscopes': READONLY,
|
||||
'scope': PEOPLE_SCOPE},
|
||||
{'name': 'People Directory API - read only',
|
||||
{'name': 'People Directory API - readonly',
|
||||
'api': PEOPLE_DIRECTORY,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/directory.readonly'},
|
||||
{'name': 'People API - Other Contacts - read only',
|
||||
{'name': 'People API - Other Contacts - readonly',
|
||||
'api': PEOPLE_OTHERCONTACTS,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/contacts.other.readonly'},
|
||||
{'name': 'Search Console API - read only',
|
||||
{'name': 'Search Console API - readonly',
|
||||
'api': SEARCHCONSOLE,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/webmasters.readonly'},
|
||||
{'name': 'Sheets API',
|
||||
@@ -759,26 +732,22 @@ _SVCACCT_SCOPES = [
|
||||
'scope': 'https://www.googleapis.com/auth/spreadsheets'},
|
||||
{'name': 'Site Verification API',
|
||||
'api': SITEVERIFICATION,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/siteverification'},
|
||||
{'name': 'Tag Manager API - Accounts, Containers, Workspaces, Tags - read only',
|
||||
{'name': 'Tag Manager API - Accounts, Containers, Workspaces, Tags - readonly',
|
||||
'api': TAGMANAGER,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/tagmanager.readonly'},
|
||||
{'name': 'Tag Manager API - Users',
|
||||
'api': TAGMANAGER_USERS,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/tagmanager.manage.users'},
|
||||
{'name': 'Tasks API',
|
||||
'api': TASKS,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/tasks'},
|
||||
{'name': 'Youtube API - read only',
|
||||
{'name': 'Youtube API - readonly',
|
||||
'api': YOUTUBE,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/youtube.readonly'},
|
||||
]
|
||||
@@ -786,30 +755,25 @@ _SVCACCT_SCOPES = [
|
||||
_SVCACCT_SPECIAL_SCOPES = [
|
||||
{'name': 'Drive API - write todrive data - has access to all Drive',
|
||||
'api': DRIVETD,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': DRIVE_SCOPE},
|
||||
{'name': 'Gmail API - Full Access - read only',
|
||||
{'name': 'Gmail API - Full Access - readonly',
|
||||
'api': GMAIL,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/gmail.readonly'},
|
||||
{'name': 'Gmail API - Send Messages - including todrive',
|
||||
'api': GMAIL,
|
||||
'subscopes': [],
|
||||
'offByDefault': True,
|
||||
'scope': GMAIL_SEND_SCOPE},
|
||||
{'name': 'Sheets API - write todrive data - has access to all Sheets',
|
||||
'api': SHEETSTD,
|
||||
'offByDefault': True,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/spreadsheets'},
|
||||
]
|
||||
|
||||
_USER_SVCACCT_ONLY_SCOPES = [
|
||||
{'name': 'Groups Migration API',
|
||||
'api': GROUPSMIGRATION,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/apps.groups.migration'},
|
||||
]
|
||||
|
||||
@@ -849,7 +813,7 @@ def getClientScopesURLs(commanddataClientAccess, todriveClientAccess):
|
||||
|
||||
def getSvcAcctScopeAPI(uscope):
|
||||
for scope in _SVCACCT_SCOPES:
|
||||
if uscope == scope['scope'] or (uscope.endswith('.readonly') and 'readonly' in scope['subscopes']):
|
||||
if uscope == scope['scope'] or (uscope.endswith('.readonly') and 'readonly' in scope.get('subscopes', [])):
|
||||
return scope['api']
|
||||
return None
|
||||
|
||||
@@ -877,11 +841,11 @@ def findAPIforScope(scopesList):
|
||||
if cscope['scope'] == scope:
|
||||
requiredAPIs.append(cscope['name'])
|
||||
return True
|
||||
if cscope['subscopes'] == READONLY and cscope['scope']+'.readonly' == scope:
|
||||
if 'readonly' in cscope.get('subscopes', []) and cscope['scope']+'.readonly' == scope:
|
||||
requiredAPIs.append(cscope['name']+' (supports readonly)')
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
requiredAPIs = []
|
||||
for scope in scopesList:
|
||||
for cscope in _CLIENT_SCOPES:
|
||||
|
||||
@@ -10,6 +10,26 @@ 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.36.03
|
||||
|
||||
Added command to send email replies that causes Gmail to recognize the message
|
||||
in conversation mode for the user sending the reply and the user receiving the reply;
|
||||
GAM supplies the necessary headers and options.
|
||||
```
|
||||
gam <UserTypeEntity> sendreply
|
||||
(((query <QueryGmail> [querytime<String> <Date>]*) [or|and])+) | (ids <MessageIDEntity>)
|
||||
[replyto <EmailAddress>]
|
||||
[subject <String>] [<MessageContent>] [html [<Boolean>]]
|
||||
(attach <FileName> [charset <CharSet>])*
|
||||
(embedimage <FileName> <String>)*
|
||||
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||
|
||||
gam user user@domain.com sendreply query "rfc822MsgId:<CAAMmEdqj43...1OsQ@mail.gmail.com>" textmessage "Thanks for the information"
|
||||
gam user user@domain.com sendreply ids 19cfc3506c02c22b textmessage "Thanks for the information"
|
||||
```
|
||||
|
||||
* See: https://github.com/GAM-team/GAM/wiki/Send-Email#conversation-mode
|
||||
|
||||
### 7.36.02
|
||||
|
||||
Added option `threadid <String>` to `gam [<UserTypeEntity>] sendemail` that causes Gmail to recognize the message
|
||||
|
||||
@@ -251,7 +251,7 @@ writes the credentials into the file oauth2.txt.
|
||||
```
|
||||
gamteam@server:/Users/gamteam$ rm -f /Users/gamteam/GAMConfig/oauth2.txt
|
||||
gamteam@server:/Users/gamteam$ gam version
|
||||
GAM 7.36.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.36.03 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.3 64-bit final
|
||||
macOS Tahoe 26.3.1 arm64
|
||||
@@ -1034,7 +1034,7 @@ writes the credentials into the file oauth2.txt.
|
||||
```
|
||||
C:\>del C:\GAMConfig\oauth2.txt
|
||||
C:\>gam version
|
||||
GAM 7.36.02 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM 7.36.03 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.3 64-bit final
|
||||
Windows 11 10.0.26200 AMD64
|
||||
|
||||
@@ -465,15 +465,28 @@ gam user recipient@domain.com sendemail to sender@domain.com references "<CAAMab
|
||||
If you want to have Gmail recognize the reply in conversation mode in the Sent folder of the original recipient,
|
||||
you must include `threadid <String>`; you can get the 'threadId` with:
|
||||
```
|
||||
gam user recipient@domain.com show messages query "rfc822MsgId:<CAAMabc...XYZQ@mail.gmail.com>"
|
||||
gam user recipient@domain.com show threads query "rfc822MsgId:<CAAMabc...XYZQ@mail.gmail.com>"
|
||||
Getting all Messages that match query ((rfc822MsgId:<CAAMabc...XYZQ@mail.gmail.com>)) for recipient@domain.com
|
||||
Got 1 Message that matched query ((rfc822MsgId:<CAAMabc...XYZQ@mail.gmail.com>)) for recipient@domain.com...
|
||||
User: recipient@domain.com, Show 1 Message
|
||||
Message: 19cfd414fe48430d
|
||||
User: recipient@domain.com, Show 1 Thread
|
||||
Thread: 19cfd414fe48430d
|
||||
Message: 19cfd414fe48430d
|
||||
...
|
||||
|
||||
gam user recipient@domain.com sendemail to sender@domain.com references "<CAAMabc...XYZQ@mail.gmail.com>" in-reply-to "<CAAMabc...XYZQ@mail.gmail.com>" subject "Re: Original subject" textmessage "Reply text" threadid 19cfd414fe48430d
|
||||
```
|
||||
As of version 7.36.03, GAM has a command to simplify this process.
|
||||
```
|
||||
gam <UserTypeEntity> sendreply
|
||||
(((query <QueryGmail> [querytime<String> <Date>]*) [or|and])+) | (ids <MessageIDEntity>)
|
||||
[replyto <EmailAddress>]
|
||||
[subject <String>] [<MessageContent>] [html [<Boolean>]]
|
||||
(attach <FileName> [charset <CharSet>])*
|
||||
(embedimage <FileName> <String>)*
|
||||
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||
|
||||
gam user recipient@domain.com sendreply query "rfc822MsgId:<CAAMabc...XYZQ@mail.gmail.com>" textmessage "Reply text"
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Print the current version of Gam with details
|
||||
```
|
||||
gam version
|
||||
GAM 7.36.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.36.03 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.3 64-bit final
|
||||
macOS Tahoe 26.3.1 arm64
|
||||
@@ -15,7 +15,7 @@ Time: 2026-02-15T07:51:00-08:00
|
||||
Print the current version of Gam with details and time offset information
|
||||
```
|
||||
gam version timeoffset
|
||||
GAM 7.36.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.36.03 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.3 64-bit final
|
||||
macOS Tahoe 26.3.1 arm64
|
||||
@@ -27,7 +27,7 @@ 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.36.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.36.03 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.3 64-bit final
|
||||
macOS Tahoe 26.3.1 arm64
|
||||
@@ -68,7 +68,7 @@ MacOS High Sierra 10.13.6 x86_64
|
||||
Path: /Users/gamteam/bin/gam7
|
||||
Version Check:
|
||||
Current: 5.35.08
|
||||
Latest: 7.36.02
|
||||
Latest: 7.36.03
|
||||
echo $?
|
||||
1
|
||||
```
|
||||
@@ -76,7 +76,7 @@ echo $?
|
||||
Print the current version number without details
|
||||
```
|
||||
gam version simple
|
||||
7.36.02
|
||||
7.36.03
|
||||
```
|
||||
In Linux/MacOS you can do:
|
||||
```
|
||||
@@ -86,7 +86,7 @@ echo $VER
|
||||
Print the current version of Gam and address of this Wiki
|
||||
```
|
||||
gam help
|
||||
GAM 7.36.02 - https://github.com/GAM-team/GAM
|
||||
GAM 7.36.03 - https://github.com/GAM-team/GAM
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.3 64-bit final
|
||||
macOS Tahoe 26.3.1 arm64
|
||||
|
||||
Reference in New Issue
Block a user